Using Pydantic's MISSING sentinel in FastAPI for PATCH endpoints
Starting with Pydantic 2.12.0, the MISSING sentinel can be used to annotate non-required, non-nullable fields, which is very useful for PATCH endpoints.
This article is a follow-up to Using TypeDicts in FastAPI for PATCH endpoints.
Let's say we want to add the following endpoint:
PATCH /movies/{movie_id}: Update a movie's rating or comment
The request body will contain only the fields that user wants to update, and fail if the user provides the wrong data types, or if an unexpected field is included.
Solution: MISSING sentinel and extra="forbid"
The MISSING sentinel can be used to annotate non-required, non-nullable fields, and the extra="forbid" configuration can be used to disallow extra fields:
from pydantic import BaseModel, ConfigDict
from pydantic_core import MISSING
class MovieUpdate(BaseModel):
model_config = ConfigDict(extra="forbid")
rating: Rating | MISSING = MISSING
comment: Comment | MISSING = MISSING
@app.patch("/movies/{movie_id}")
def update_movie(movie_id: int, update: schemas.MovieUpdate) -> schemas.MovieRead:
if movie := db.get(doc_id=movie_id):
movie.update(update.model_dump())
db.update(movie, doc_ids=[movie_id])
return schemas.MovieRead.model_validate({"id": movie.doc_id, **movie})
raise HTTPException(
status_code=http.HTTPStatus.NOT_FOUND,
detail="Movie not found",
)
This will correctly disallow updating fields that are not in the MovieUpdate model, such as title and year, while the rating and comment fields are optional and can be omitted from the request body:
-
Fields are optional
-
Extra fields are not allowed
-
Fields are not nullable
Appendix: Full code
| my_app/app.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |