For a partial update, omitted usually means preserve the stored value, explicit null can mean clear it, and a concrete value means replace it. Define those meanings per field before using FastAPI or Pydantic serialization flags. exclude_unset preserves the distinction between absent and explicitly supplied fields; adding exclude_none removes explicit nulls and can silently change a clear operation into no change.
Decide the field contract before the model
A profile may allow nickname to be cleared but require timezone to remain a valid non-null value. Optional input typing alone does not decide those domain rules. Document omission, null, empty string and replacement independently. Also specify whether arrays replace the whole collection or merge, and whether nested objects are recursively patched. HTTP PATCH does not choose those application semantics automatically.
FastAPI's update guide demonstrates exclude_unset for partial updates. Use the Pydantic API appropriate to the installed major version and check the resulting dictionary before mapping it to SQL. An existing POST endpoint with merge-like behavior is not automatically a PATCH resource; name the actual route and compatibility contract accurately when applying this pattern.
FastAPI: partial updates and exclude_unset
Executed omitted/null fixture
Our isolated fixture ran with Pydantic 2.12.5 on Python 3.12.13. The stored synthetic profile starts with nickname Mira, timezone UTC and bio Original. The payload supplies nickname as null, omits timezone and replaces bio. With exclude_unset alone, nickname remains present as None. With exclude_unset plus exclude_none, nickname disappears from the update dictionary.
class Patch(BaseModel):
nickname: str | None = None
timezone: str | None = None
bio: str | None = None
payload = {'nickname': None, 'bio': 'Updated'}
p = Patch(**payload)
clear_intent = p.model_dump(exclude_unset=True)
# {'nickname': None, 'bio': 'Updated'}
drop_null = p.model_dump(exclude_unset=True, exclude_none=True)
# {'bio': 'Updated'}The fixture merges both dictionaries into separate copies of the original profile. The clear contract produces nickname null, timezone UTC and bio Updated. The null-dropping contract leaves nickname Mira. Those are actual local model-and-dictionary results, not an executed database update. They demonstrate the semantic difference without implying that either policy is correct for every field.
| Payload field | exclude_unset | Result under clear contract |
|---|---|---|
| nickname: null | Included as None | Nickname cleared |
| timezone omitted | Absent | UTC preserved |
| bio: Updated | Included | Bio replaced |
| nickname with exclude_none | Dropped | Mira preserved instead |
Validate the resolved update before persistence
After extracting supplied fields, apply an allowlist and field-specific rules. Reject null for a field that cannot be cleared, validate a replacement timezone and normalize a permitted empty string consistently. Then validate cross-field constraints on the prospective result where the domain requires it. Updating a start date while preserving an old end date can create an invalid pair even when the individual new field is well typed.
Be careful with model-copy update shortcuts: do not assume merging arbitrary data revalidates every business invariant. Build a validated final representation or invoke the domain service's explicit update validation. Map supplied fields to parameterized assignments and keep immutable identity and ownership columns outside the patch surface. The authorization context must come from verified identity, not a tenant field the caller added.
Preserve old-client meaning deliberately
Some older clients send null for every unknown optional field. If a new server interprets those values as clear, an apparently harmless release can erase data. Inspect actual supported-client payloads, define a versioned or capability-aware transition where needed and add fixtures for omitted fields from old releases. A feature flag cannot teach an installed client a new null contract.
Also include concurrent updates in the acceptance suite. A minimal patch can still overwrite a changed field when the UI submits stale data. Use a documented version or conflict rule for workflows that require it. The saved Pydantic artifact proves field-presence handling for one version; endpoint authorization, transaction behavior and old-client parsing remain separate checks to run before release.
Mobile app API backward compatibility
Building UltraFit360: the app, coach portal and shared backend
Bring one API or data workflow and a sanitized failure example for a bounded implementation assessment.
How do omitted fields differ from null in FastAPI PATCH requests?
Under an explicit clear contract, omission preserves a value, null clears it and a supplied value replaces it. exclude_unset retains explicit null; exclude_none drops it.
Should exclude_none always be avoided?
No. It is appropriate when null intentionally means ignore; the key is matching the documented field contract.
Does a nullable field have to be clearable?
No. Input typing and business update permissions are separate; reject unsupported clear operations explicitly.
