["python","python-3.x","fastapi","pydantic"]
Make every field as optional with Pydantic

I'm making an API with FastAPI and Pydantic. I would like to have some PATCH endpoints, where 1 or N fields of a record could be edited at once. Moreover, I would like the client to only pass the necessary fields in the payload. Example: class Item(BaseModel): name: str description: str price: float tax: float @app.post("/items", response_model=Item) async def post_item(item: Item): ... @app.patch("/items/{item_id}", response_model=Item) async def update_item(item_id: str, item: Item): ... In this example, for the POST request, I want every field to be required. However, in the PATCH endpoint, I don't mind if the payload only contains, for example, the description field. That's why I wish to have all fields as optional. Naive approach: class UpdateItem(BaseModel): name: Optional[str] = None description: Optional[str] = None price: Optional[float] = None tax: Optional[float] But that would be terrible in terms of code repetition. Any better option?
This method prevents data validation Read this by @Anime Bk: https://stackoverflow.com/a/75011200 Solution with metaclasses I've just come up with the following: class AllOptional(pydantic.main.ModelMetaclass): def __new__(cls, name, bases, namespaces, **kwargs): annotations = namespaces.get('__annotations__', {}) for base in bases: annotations.update(base.__annotations__) for field in annotations: if not field.startswith('__'): annotations[field] = Optional[annotations[field]] namespaces['__annotations__'] = annotations return super().__new__(cls, name, bases, namespaces, **kwargs) Use it as: class UpdatedItem(Item, metaclass=AllOptional): pass So basically it replace all non optional fields with Optional Any edits are welcome! With your example: from typing import Optional from fastapi import FastAPI from pydantic import BaseModel import pydantic app = FastAPI() class Item(BaseModel): name: str description: str price: float tax: float class AllOptional(pydantic.main.ModelMetaclass): def __new__(self, name, bases, namespaces, **kwargs): annotations = namespaces.get('__annotations__', {}) for base in bases: annotations.update(base.__annotations__) for field in annotations: if not field.startswith('__'): annotations[field] = Optional[annotations[field]] namespaces['__annotations__'] = annotations return super().__new__(self, name, bases, namespaces, **kwargs) class UpdatedItem(Item, metaclass=AllOptional): pass # This continues to work correctly @app.get("/items/{item_id}", response_model=Item) async def get_item(item_id: int): return { 'name': 'Uzbek Palov', 'description': 'Palov is my traditional meal', 'price': 15.0, 'tax': 0.5, } @app.patch("/items/{item_id}") # does using response_model=UpdatedItem makes mypy sad? idk, i did not check async def update_item(item_id: str, item: UpdatedItem): return item