python
45 lines · 8 steps
How PATCH partial updates work in FastAPI
A FastAPI route that updates only the fields a client actually sends, using Pydantic's exclude_unset to distinguish omitted from null.
Explained by
highlit
1from typing import Optional
2
3from fastapi import APIRouter, Depends, HTTPException, status
4from pydantic import BaseModel, EmailStr, Field
5from sqlalchemy.orm import Session
6
7from .database import get_db
8from .models import User
9
10router = APIRouter(prefix="/users", tags=["users"])
11
12
13class UserUpdate(BaseModel):
14 full_name: Optional[str] = Field(default=None, max_length=120)
15 email: Optional[EmailStr] = None
16 bio: Optional[str] = Field(default=None, max_length=500)
17 is_active: Optional[bool] = None
18
19
20class UserOut(BaseModel):
21 id: int
22 full_name: str
23 email: EmailStr
24 bio: Optional[str]
25 is_active: bool
26
27 model_config = {"from_attributes": True}
28
29
30@router.patch("/{user_id}", response_model=UserOut)
31def patch_user(user_id: int, payload: UserUpdate, db: Session = Depends(get_db)):
32 user = db.get(User, user_id)
33 if user is None:
34 raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
35
36 changes = payload.model_dump(exclude_unset=True)
37 if not changes:
38 raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="No fields to update")
39
40 for field, value in changes.items():
41 setattr(user, field, value)
42
43 db.commit()
44 db.refresh(user)
45 return user
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1exclude_unset separates fields a client omitted from fields set explicitly to null, which is what makes PATCH semantics correct.
- 2Optional fields with defaults let one schema validate any subset of updatable attributes.
- 3Injecting the session with Depends keeps the handler testable and ties the connection to the request lifecycle.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-patch-partial-updates-work-in-fastapi-explained-python-aaf7/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.