Create the Expense Table Model
Exit

Create the Expense Table Model

Define Expense as a SQLModel table model and move it to a dedicated models.py file

💻

Writing code and entering commands is only available on desktop. Open this page on a larger screen to complete this chapter.

Moving models to their own file

Your Expense, ExpenseUpdate, and ExpenseOut classes currently live in main.py. As your project grows to multiple files — main.py, database.py, models.py — keeping models in main.py creates circular imports: database.py needs the models, main.py needs the database, and you end up with everything importing everything else.

Putting models in models.py breaks that cycle. Both main.py and database.py can import from models.py without importing from each other.

What we changed for you

We went ahead and migrated the models to models.py. The imports, the validator, and ExpenseUpdate are already in place. Two things are still missing — the two changes that matter most.

From BaseModel to SQLModel

The base class needs to change:

# Before
class Expense(BaseModel):

# After
class Expense(SQLModel, table=True):

table=True tells SQLModel to create a matching table in the database. Every field in the class becomes a column. Pydantic validation still works — SQLModel extends BaseModel, so Field(min_length=1), Field(gt=0), and @field_validator all behave exactly as before.

The id field

The old Expense model had no id field — the application assigned IDs using the counter variable. With a database, IDs are assigned by the database itself:

id: Optional[int] = Field(default=None, primary_key=True)

  • Optional[int] — the field can be None before the record is saved
  • default=None — you do not provide an ID when creating an expense; the database fills it in
  • primary_key=True — this column uniquely identifies each row

ExpenseOut is gone

ExpenseOut was a separate model used as the response type for endpoints. Because Expense(SQLModel, table=True) is both a Pydantic model and a database table, it handles serialization too. One class, three roles.

Instructions

Two changes turn Expense from a plain data class into a database table.

  1. On the class Expense(SQLModel): line, add table=True so it reads class Expense(SQLModel, table=True):. This tells SQLModel to create a matching table in the database.
  2. Add id: Optional[int] = Field(default=None, primary_key=True) as the first field inside the Expense class, right above description. This lets the database assign IDs automatically — you do not provide one when creating an expense.