Skip to content

Instantly share code, notes, and snippets.

@elsholz
Last active December 29, 2023 13:41
Show Gist options
  • Save elsholz/415a5684b6751bec30d76c831391b4e2 to your computer and use it in GitHub Desktop.
Save elsholz/415a5684b6751bec30d76c831391b4e2 to your computer and use it in GitHub Desktop.
Potentially Missing Fields for FastAPI Partial Updates

When working on patch requests with FastAPI, one may encounter the situation where you would want to ignore fields not supplied in a request. While you could do this with the Optional type annotation, this means that you have to use None as a flag that a field is not set, by setting it as the default value. This may not be suitable though, when None is an actual valid value for a field. In this case, you could use a special value for indicating a missing field.

Below is a code snippet where I have implemented this approach. The type MissingValueBaseClass is used for creating an object named Missing, which can be assigned as the default value for potentially missing fields. These fields have to be annotated using the function MaybeMissing, which takes in a type t and returns a Union with MissingValueBaseClass. By inheriting from ModelMayMissFields, the class UpdateUser can return a dict containing only values that are set with the method get_existing_fields.

from pydantic import BaseModel
from typing import Union, Optional, get_args
class MissingValueBaseClass(BaseModel):
pass
def MaybeMissing(t):
"""
Function that returns a type annotation.
TODO: Convert to Annotated, to support type hinting in IDE.
"""
return Union[MissingValueBaseClass, t]
Missing = MissingValueBaseClass()
class ModelMayMissFields(BaseModel):
def get_existing_fields(self):
"""
Iterates over the Model's fields and returns a dict containing only values that are not Missing.
If field of type ModelMayMissFields is encountered, get_existing_fields is called recursively.
"""
return {
field_name: (
field_value.get_existing_fields()
if isinstance(field_value, ModelMayMissFields)
else field_value
)
for field_name, field_spec in self.model_fields.items()
if MissingValueBaseClass in get_args(field_spec.annotation)
and (field_value := self.__getattribute__(field_name)) != Missing
}
# Example use:
class UpdateUser(UserCommons, ModelMayMissFields):
"""
Model for `patch` requests to the user endpoint.
Allows partial updates, ommitted fields will be untouched.
"""
bio: MaybeMissing(str) = Missing
display_name: MaybeMissing(str) = Missing
profile_picture: MaybeMissing(Optional[str]) = Missing
public: MaybeMissing(bool) = Missing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment