Partial Updates with PATCH
Understand the difference between PUT and PATCH and how exclude_unset works
The problem with full replacements
Your API can create, list, retrieve, and delete expenses. But what happens when a user needs to fix a typo in the description? Or change the amount from 12.00 to 15.00?
Without an update endpoint, the client must delete the expense and create a new one. That changes the identifier, breaks any references to the original record, and forces the client to resend every field.
PUT versus PATCH
HTTP defines two methods for updates:
- PUT: Replaces the entire resource. The client must send all fields, even the ones that did not change. If the client omits a field, the server sets it to its default or removes it.
- PATCH: Applies a partial update. The client sends only the fields that changed. Everything else stays the same.
For an expense tracker, PATCH is the better fit. A user updating the amount from 12.00 to 15.00 should not need to resend the description, category, and date.
How Pydantic tracks what was sent
When a client sends {"amount": 15.00}, Pydantic records that amount was the only field set by the client. The other fields have default values, but Pydantic marks them as unset — the client never provided them.
The method model_dump(exclude_unset=True) returns a dictionary containing only the fields the client actually sent:
updates.model_dump(exclude_unset=True)
# {"amount": 15.0}Compare this to model_dump() without the flag:
updates.model_dump()
# {"description": None, "amount": 15.0, "category": None}Without exclude_unset=True, merging these values into the stored expense would overwrite the description and category with None. The exclude_unset flag prevents that.
What you will build next
In the next two chapters, you will create an ExpenseUpdate model where every field is optional, then write a PATCH endpoint that merges the changes into the stored expense.