Best Practice of handling FastAPI Schema

The Best Practice of handling FastAPI Schema

FastAPI Has a concept of Schema. DTO is more familiar name If you have developed at Spring or NestJS. Simply, DTO is Data Transfer Object and It is a kind of promise to exchange object information between methods or classes in a specific model.

FastAPI’s schema depends on pydantic model.

<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>

<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>
from pydantic import BaseModel class Item(BaseModel): name: str model: str manufacturer: str price: float tax: float

Enter fullscreen mode Exit fullscreen mode

As you can see, schema is made by inheriting the pydantic Base Model. Now let’s use this to construct an api endpoint.

<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
<span>item</span><span>:</span> <span>Item</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>

<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>


<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
        <span>item</span><span>:</span> <span>Item</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str model: str manufacturer: str price: float tax: float @app.get("/") async def root( item: Item ): return {"message": "Hello World"}

Enter fullscreen mode Exit fullscreen mode

If you declare that you’re gonna get an item schema at the basic endpoint,

In this way, an example of receiving the model is automatically generated. You can use this in other ways like

<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
<span>item</span><span>:</span> <span>Item</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>

<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>


<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
        <span>item</span><span>:</span> <span>Item</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
from fastapi import FastAPI, Depends from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str model: str manufacturer: str price: float tax: float @app.get("/") async def root( item: Item = Depends() ): return {"message": "Hello World"}

Enter fullscreen mode Exit fullscreen mode

If you register the value “Depends” at schema,

You can see that the part that you want to receive as a Body has changed to Parameter. Doesn’t it feel like You’ve seen it somewhere? Right. It is a common method for constructing search query. However, as can be seen above, it can be seen that there is a limit of required values. It’s a search query, but it doesn’t make sense that all members have a required option. So it is necessary to change all of this to optional.

very easily

<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
<span>model</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
<span>manufacturer</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
<span>price</span><span>:</span> <span>Optional</span><span>[</span><span>float</span><span>]</span>
<span>tax</span><span>:</span> <span>Optional</span><span>[</span><span>float</span><span>]</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span>

<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
    <span>model</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
    <span>manufacturer</span><span>:</span> <span>Optional</span><span>[</span><span>str</span><span>]</span>
    <span>price</span><span>:</span> <span>Optional</span><span>[</span><span>float</span><span>]</span>
    <span>tax</span><span>:</span> <span>Optional</span><span>[</span><span>float</span><span>]</span>
from pydantic import BaseModel from typing import Optional class Item(BaseModel): name: Optional[str] model: Optional[str] manufacturer: Optional[str] price: Optional[float] tax: Optional[float]

Enter fullscreen mode Exit fullscreen mode

It can be made in this way, but if the default value of the schema members are optional, there is a hassle of removing all the Optional when configuring the PUT method endpoint later. Oh, it is recommended that anyone who has no clear distinction between the PUT method and the PATCH method here refer to this article. In short, PUT is the promised method of changing the total value of an element, and PATCH is the method of changing some.

So I use this way.

<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>
<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
<span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
<span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
<span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
<span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
<span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
<span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
<span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
<span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>

<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
    <span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
        <span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
            <span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
        <span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
            <span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
                <span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
        <span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
from pydantic.main import ModelMetaclass class AllOptional(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)

Enter fullscreen mode Exit fullscreen mode

This class has the function of changing all member variables of the class to Optional when declared as metaclass.

<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span>
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>
<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
<span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
<span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
<span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
<span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
<span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
<span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
<span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
<span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>class</span> <span>OptionalItem</span><span>(</span><span>Item</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>pass</span>
<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
<span>item</span><span>:</span> <span>OptionalItem</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span>

<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>


<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
    <span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
        <span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
            <span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
        <span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
            <span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
                <span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
        <span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>


<span>class</span> <span>Item</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>class</span> <span>OptionalItem</span><span>(</span><span>Item</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>pass</span>


<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
        <span>item</span><span>:</span> <span>OptionalItem</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
from fastapi import FastAPI, Depends from pydantic import BaseModel from pydantic.main import ModelMetaclass from typing import Optional app = FastAPI() class AllOptional(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 Item(BaseModel): name: str model: str manufacturer: str price: float tax: float class OptionalItem(Item, metaclass=AllOptional): pass @app.get("/") async def root( item: OptionalItem = Depends() ): return {"message": "Hello World"}

Enter fullscreen mode Exit fullscreen mode

If you inherit the class made of the pydantic Base Model and declare ‘metaclass=AllOptional’ there,

You can see Parameters without required option.

However, when dealing with a schema in practice, there are things that are not needed when putting it in and necessary when bringing it later. It is common for id, created_datetime, updated_datetime, etc. And sometimes you have to remove some members and show them. In spring or nest, you can flexibly cope with this situation by using the setFieldToIgnore function or the Omit class, but as anyone who has dealt with FastAPI can see, there are no such things.

So after a lot of trial and error, i established a way of using FastAPI schema like the DTO we used before. It would be irrelevant to say that this is the best practice now. Why? I looked up numerous examples, but there was no perfect alternative, so I defined them myself and made them.

This is an example using the schema called Item used above.

<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>

<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>
from pydantic import BaseModel class BaseItem(BaseModel): name: str model: str manufacturer: str price: float tax: float

Enter fullscreen mode Exit fullscreen mode

The most basic members are defined as BaseItem. It can be seen as defining the elements used in Upsert. The pydantic model will basically be a schema used in the PUT method because it will be treated as Required if it is not set as Optional or = None.

<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>id</span><span>:</span> <span>int</span>
<span>created_datetime</span><span>:</span> <span>datetime</span>
<span>updated_datetime</span><span>:</span> <span>datetime</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>id</span><span>:</span> <span>int</span>
    <span>created_datetime</span><span>:</span> <span>datetime</span>
    <span>updated_datetime</span><span>:</span> <span>datetime</span>
class Item(BaseItem, metaclass=AllOptional): id: int created_datetime: datetime updated_datetime: datetime

Enter fullscreen mode Exit fullscreen mode

It is an item that inherits BaseItem. Even if the class inherited the Base Model is inherited, the inherited class also becomes a pydantic model, so it can be declared this simply. It is used to throw items that are not used in Upsert, including basic elements. This is a schema mainly used to inquire items, so wouldn’t it be okay not to attach Optional…? But if there is a non-Optional member and its value has no Value, an error is occurred.

<span>class</span> <span>FindBase</span><span>(</span><span>BaseModel</span><span>):</span>
<span>count</span><span>:</span> <span>int</span>
<span>page</span><span>:</span> <span>int</span>
<span>order</span><span>:</span> <span>str</span>
<span>class</span> <span>FindItem</span><span>(</span><span>FindBase</span><span>,</span> <span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>pass</span>
<span>class</span> <span>FindBase</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>count</span><span>:</span> <span>int</span>
    <span>page</span><span>:</span> <span>int</span>
    <span>order</span><span>:</span> <span>str</span>


<span>class</span> <span>FindItem</span><span>(</span><span>FindBase</span><span>,</span> <span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>pass</span>
class FindBase(BaseModel): count: int page: int order: str class FindItem(FindBase, BaseItem, metaclass=AllOptional): pass

Enter fullscreen mode Exit fullscreen mode

Parameters used to find items can be declared in this way. It inherits the BaseItem and FindBase declared above and treats them both as options with metaclass=AllOptional to remove the required option of the parameter. And lastly,

<span>class</span> <span>Omit</span><span>(</span><span>ModelMetaclass</span><span>):</span>
<span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>omit_fields</span> <span>=</span> <span>getattr</span><span>(</span><span>namespaces</span><span>.</span><span>get</span><span>(</span><span>"</span><span>Config</span><span>"</span><span>,</span> <span>{}),</span> <span>"</span><span>omit_fields</span><span>"</span><span>,</span> <span>{})</span>
<span>fields</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__fields__</span><span>'</span><span>,</span> <span>{})</span>
<span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
<span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
<span>fields</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__fields__</span><span>)</span>
<span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
<span>merged_keys</span> <span>=</span> <span>fields</span><span>.</span><span>keys</span><span>()</span> <span>&</span> <span>annotations</span><span>.</span><span>keys</span><span>()</span>
<span>[</span><span>merged_keys</span><span>.</span><span>add</span><span>(</span><span>field</span><span>)</span> <span>for</span> <span>field</span> <span>in</span> <span>fields</span><span>]</span>
<span>new_fields</span> <span>=</span> <span>{}</span>
<span>new_annotations</span> <span>=</span> <span>{}</span>
<span>for</span> <span>field</span> <span>in</span> <span>merged_keys</span><span>:</span>
<span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>)</span> <span>and</span> <span>field</span> <span>not</span> <span>in</span> <span>omit_fields</span><span>:</span>
<span>new_annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>annotations</span><span>.</span><span>get</span><span>(</span><span>field</span><span>,</span> <span>fields</span><span>[</span><span>field</span><span>].</span><span>type_</span><span>)</span>
<span>new_fields</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>fields</span><span>[</span><span>field</span><span>]</span>
<span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>new_annotations</span>
<span>namespaces</span><span>[</span><span>'</span><span>__fields__</span><span>'</span><span>]</span> <span>=</span> <span>new_fields</span>
<span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>class</span> <span>Omit</span><span>(</span><span>ModelMetaclass</span><span>):</span>
    <span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>omit_fields</span> <span>=</span> <span>getattr</span><span>(</span><span>namespaces</span><span>.</span><span>get</span><span>(</span><span>"</span><span>Config</span><span>"</span><span>,</span> <span>{}),</span> <span>"</span><span>omit_fields</span><span>"</span><span>,</span> <span>{})</span>
        <span>fields</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__fields__</span><span>'</span><span>,</span> <span>{})</span>
        <span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
        <span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
            <span>fields</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__fields__</span><span>)</span>
            <span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
        <span>merged_keys</span> <span>=</span> <span>fields</span><span>.</span><span>keys</span><span>()</span> <span>&</span> <span>annotations</span><span>.</span><span>keys</span><span>()</span>
        <span>[</span><span>merged_keys</span><span>.</span><span>add</span><span>(</span><span>field</span><span>)</span> <span>for</span> <span>field</span> <span>in</span> <span>fields</span><span>]</span>
        <span>new_fields</span> <span>=</span> <span>{}</span>
        <span>new_annotations</span> <span>=</span> <span>{}</span>
        <span>for</span> <span>field</span> <span>in</span> <span>merged_keys</span><span>:</span>
            <span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>)</span> <span>and</span> <span>field</span> <span>not</span> <span>in</span> <span>omit_fields</span><span>:</span>
                <span>new_annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>annotations</span><span>.</span><span>get</span><span>(</span><span>field</span><span>,</span> <span>fields</span><span>[</span><span>field</span><span>].</span><span>type_</span><span>)</span>
                <span>new_fields</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>fields</span><span>[</span><span>field</span><span>]</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>new_annotations</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__fields__</span><span>'</span><span>]</span> <span>=</span> <span>new_fields</span>
        <span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
class Omit(ModelMetaclass): def __new__(self, name, bases, namespaces, **kwargs): omit_fields = getattr(namespaces.get("Config", {}), "omit_fields", {}) fields = namespaces.get('__fields__', {}) annotations = namespaces.get('__annotations__', {}) for base in bases: fields.update(base.__fields__) annotations.update(base.__annotations__) merged_keys = fields.keys() & annotations.keys() [merged_keys.add(field) for field in fields] new_fields = {} new_annotations = {} for field in merged_keys: if not field.startswith('__') and field not in omit_fields: new_annotations[field] = annotations.get(field, fields[field].type_) new_fields[field] = fields[field] namespaces['__annotations__'] = new_annotations namespaces['__fields__'] = new_fields return super().__new__(self, name, bases, namespaces, **kwargs)

Enter fullscreen mode Exit fullscreen mode

This is a metaclass that supports the Omit function. I needed it, but I couldn’t find it, so I made it.

<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>class</span> <span>OmittedTaxPrice</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>Omit</span><span>):</span>
<span>class</span> <span>Config</span><span>:</span>
<span>omit_fields</span> <span>=</span> <span>{</span><span>'</span><span>tax</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>}</span>
<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>class</span> <span>OmittedTaxPrice</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>Omit</span><span>):</span>
    <span>class</span> <span>Config</span><span>:</span>
        <span>omit_fields</span> <span>=</span> <span>{</span><span>'</span><span>tax</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>}</span>
class BaseItem(BaseModel): name: str model: str manufacturer: str price: float tax: float class OmittedTaxPrice(BaseItem, metaclass=Omit): class Config: omit_fields = {'tax', 'price'}

Enter fullscreen mode Exit fullscreen mode

If you declare omit_fields in class Config in this way, you can get a new schema with removed fields.

※ And one thing to note is that metaclass is executed every time a class is created, so only one class involved in an inheritance relationship can be declared. For example

<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>id</span><span>:</span> <span>int</span>
<span>created_datetime</span><span>:</span> <span>datetime</span>
<span>updated_datetime</span><span>:</span> <span>datetime</span>
<span>class</span> <span>TestItem</span><span>(</span><span>Item</span><span>):</span>
<span>additional_number</span><span>:</span> <span>int</span>
<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>id</span><span>:</span> <span>int</span>
    <span>created_datetime</span><span>:</span> <span>datetime</span>
    <span>updated_datetime</span><span>:</span> <span>datetime</span>



<span>class</span> <span>TestItem</span><span>(</span><span>Item</span><span>):</span>
    <span>additional_number</span><span>:</span> <span>int</span>
class BaseItem(BaseModel): name: str model: str manufacturer: str price: float tax: float class Item(BaseItem, metaclass=AllOptional): id: int created_datetime: datetime updated_datetime: datetime class TestItem(Item): additional_number: int

Enter fullscreen mode Exit fullscreen mode

If the Item declared metaclass=AllOptional is inherited from TestItem, AllOptional is executed twice. Once when creating an Item class, once again when creating a TestItem that inherits the Item. Therefore, the additional_number, a member variable of TestItem that inherits the Item, is also Optional. That is metaclass. So, if you declare another metaclass like Omit, the program is broken.

<span># ... </span><span>class</span> <span>TestItem</span><span>(</span><span>Item</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>)</span>
<span>pass</span>
<span># ... # `TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases` </span>
<span># ... </span><span>class</span> <span>TestItem</span><span>(</span><span>Item</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>)</span>
    <span>pass</span>
<span># ... # `TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases` </span>
# ... class TestItem(Item, metaclass=AllOptional) pass # ... # `TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases`

Enter fullscreen mode Exit fullscreen mode

So, it is good to declare and use metaclass in the last inheritance class before it is used as schema. This makes easy to recognize its function.

Anyway, if you use the above-mentioned four http methods, GET, POST, PUT, and PATCH, which are often used, it consists of this. It can also be found in my git repo. https://github.com/jujumilk3/fastapi-best-practice/tree/schema

<span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span><span>,</span> <span>List</span>
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>
<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
<span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
<span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
<span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
<span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
<span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
<span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
<span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
<span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>class</span> <span>Omit</span><span>(</span><span>ModelMetaclass</span><span>):</span>
<span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>omit_fields</span> <span>=</span> <span>getattr</span><span>(</span><span>namespaces</span><span>.</span><span>get</span><span>(</span><span>"</span><span>Config</span><span>"</span><span>,</span> <span>{}),</span> <span>"</span><span>omit_fields</span><span>"</span><span>,</span> <span>{})</span>
<span>fields</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__fields__</span><span>'</span><span>,</span> <span>{})</span>
<span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
<span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
<span>fields</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__fields__</span><span>)</span>
<span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
<span>merged_keys</span> <span>=</span> <span>fields</span><span>.</span><span>keys</span><span>()</span> <span>&</span> <span>annotations</span><span>.</span><span>keys</span><span>()</span>
<span>[</span><span>merged_keys</span><span>.</span><span>add</span><span>(</span><span>field</span><span>)</span> <span>for</span> <span>field</span> <span>in</span> <span>fields</span><span>]</span>
<span>new_fields</span> <span>=</span> <span>{}</span>
<span>new_annotations</span> <span>=</span> <span>{}</span>
<span>for</span> <span>field</span> <span>in</span> <span>merged_keys</span><span>:</span>
<span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>)</span> <span>and</span> <span>field</span> <span>not</span> <span>in</span> <span>omit_fields</span><span>:</span>
<span>new_annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>annotations</span><span>.</span><span>get</span><span>(</span><span>field</span><span>,</span> <span>fields</span><span>[</span><span>field</span><span>].</span><span>type_</span><span>)</span>
<span>new_fields</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>fields</span><span>[</span><span>field</span><span>]</span>
<span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>new_annotations</span>
<span>namespaces</span><span>[</span><span>'</span><span>__fields__</span><span>'</span><span>]</span> <span>=</span> <span>new_fields</span>
<span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
<span>name</span><span>:</span> <span>str</span>
<span>model</span><span>:</span> <span>str</span>
<span>manufacturer</span><span>:</span> <span>str</span>
<span>price</span><span>:</span> <span>float</span>
<span>tax</span><span>:</span> <span>float</span>
<span>class</span> <span>UpsertItem</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>pass</span>
<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>id</span><span>:</span> <span>int</span>
<span>created_datetime</span><span>:</span> <span>datetime</span>
<span>updated_datetime</span><span>:</span> <span>datetime</span>
<span>class</span> <span>OmittedTaxPrice</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>Omit</span><span>):</span>
<span>class</span> <span>Config</span><span>:</span>
<span>omit_fields</span> <span>=</span> <span>{</span><span>'</span><span>tax</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>}</span>
<span>class</span> <span>FindBase</span><span>(</span><span>BaseModel</span><span>):</span>
<span>count</span><span>:</span> <span>int</span>
<span>page</span><span>:</span> <span>int</span>
<span>order</span><span>:</span> <span>str</span>
<span>class</span> <span>FindItem</span><span>(</span><span>FindBase</span><span>,</span> <span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
<span>pass</span>
<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>
<span>@app.get</span><span>(</span><span>"</span><span>/items/</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>List</span><span>[</span><span>Item</span><span>])</span>
<span>async</span> <span>def</span> <span>find_items</span><span>(</span>
<span>find_query</span><span>:</span> <span>FindItem</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
<span>@app.post</span><span>(</span><span>"</span><span>/items/</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>create_item</span><span>(</span>
<span>schema</span><span>:</span> <span>UpsertItem</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
<span>@app.patch</span><span>(</span><span>"</span><span>/items/{id}</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>update_item</span><span>(</span>
<span>id</span><span>:</span> <span>int</span><span>,</span>
<span>schema</span><span>:</span> <span>UpsertItem</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
<span>@app.put</span><span>(</span><span>"</span><span>/items/{id}</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>put_item</span><span>(</span>
<span>id</span><span>:</span> <span>int</span><span>,</span>
<span>schema</span><span>:</span> <span>BaseItem</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
<span>@app.get</span><span>(</span><span>"</span><span>/items/omitted</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>OmittedTaxPrice</span><span>)</span>
<span>async</span> <span>def</span> <span>omitted_item</span><span>(</span>
<span>schema</span><span>:</span> <span>OmittedTaxPrice</span>
<span>):</span>
<span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
<span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Depends</span>
<span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span>
<span>from</span> <span>pydantic.main</span> <span>import</span> <span>ModelMetaclass</span>
<span>from</span> <span>typing</span> <span>import</span> <span>Optional</span><span>,</span> <span>List</span>


<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>


<span>class</span> <span>AllOptional</span><span>(</span><span>ModelMetaclass</span><span>):</span>
    <span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
        <span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
            <span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
        <span>for</span> <span>field</span> <span>in</span> <span>annotations</span><span>:</span>
            <span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>):</span>
                <span>annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>Optional</span><span>[</span><span>annotations</span><span>[</span><span>field</span><span>]]</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>annotations</span>
        <span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>


<span>class</span> <span>Omit</span><span>(</span><span>ModelMetaclass</span><span>):</span>
    <span>def</span> <span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>omit_fields</span> <span>=</span> <span>getattr</span><span>(</span><span>namespaces</span><span>.</span><span>get</span><span>(</span><span>"</span><span>Config</span><span>"</span><span>,</span> <span>{}),</span> <span>"</span><span>omit_fields</span><span>"</span><span>,</span> <span>{})</span>
        <span>fields</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__fields__</span><span>'</span><span>,</span> <span>{})</span>
        <span>annotations</span> <span>=</span> <span>namespaces</span><span>.</span><span>get</span><span>(</span><span>'</span><span>__annotations__</span><span>'</span><span>,</span> <span>{})</span>
        <span>for</span> <span>base</span> <span>in</span> <span>bases</span><span>:</span>
            <span>fields</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__fields__</span><span>)</span>
            <span>annotations</span><span>.</span><span>update</span><span>(</span><span>base</span><span>.</span><span>__annotations__</span><span>)</span>
        <span>merged_keys</span> <span>=</span> <span>fields</span><span>.</span><span>keys</span><span>()</span> <span>&</span> <span>annotations</span><span>.</span><span>keys</span><span>()</span>
        <span>[</span><span>merged_keys</span><span>.</span><span>add</span><span>(</span><span>field</span><span>)</span> <span>for</span> <span>field</span> <span>in</span> <span>fields</span><span>]</span>
        <span>new_fields</span> <span>=</span> <span>{}</span>
        <span>new_annotations</span> <span>=</span> <span>{}</span>
        <span>for</span> <span>field</span> <span>in</span> <span>merged_keys</span><span>:</span>
            <span>if</span> <span>not</span> <span>field</span><span>.</span><span>startswith</span><span>(</span><span>'</span><span>__</span><span>'</span><span>)</span> <span>and</span> <span>field</span> <span>not</span> <span>in</span> <span>omit_fields</span><span>:</span>
                <span>new_annotations</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>annotations</span><span>.</span><span>get</span><span>(</span><span>field</span><span>,</span> <span>fields</span><span>[</span><span>field</span><span>].</span><span>type_</span><span>)</span>
                <span>new_fields</span><span>[</span><span>field</span><span>]</span> <span>=</span> <span>fields</span><span>[</span><span>field</span><span>]</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__annotations__</span><span>'</span><span>]</span> <span>=</span> <span>new_annotations</span>
        <span>namespaces</span><span>[</span><span>'</span><span>__fields__</span><span>'</span><span>]</span> <span>=</span> <span>new_fields</span>
        <span>return</span> <span>super</span><span>().</span><span>__new__</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>,</span> <span>bases</span><span>,</span> <span>namespaces</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>


<span>class</span> <span>BaseItem</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>name</span><span>:</span> <span>str</span>
    <span>model</span><span>:</span> <span>str</span>
    <span>manufacturer</span><span>:</span> <span>str</span>
    <span>price</span><span>:</span> <span>float</span>
    <span>tax</span><span>:</span> <span>float</span>


<span>class</span> <span>UpsertItem</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>pass</span>


<span>class</span> <span>Item</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>id</span><span>:</span> <span>int</span>
    <span>created_datetime</span><span>:</span> <span>datetime</span>
    <span>updated_datetime</span><span>:</span> <span>datetime</span>


<span>class</span> <span>OmittedTaxPrice</span><span>(</span><span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>Omit</span><span>):</span>
    <span>class</span> <span>Config</span><span>:</span>
        <span>omit_fields</span> <span>=</span> <span>{</span><span>'</span><span>tax</span><span>'</span><span>,</span> <span>'</span><span>price</span><span>'</span><span>}</span>


<span>class</span> <span>FindBase</span><span>(</span><span>BaseModel</span><span>):</span>
    <span>count</span><span>:</span> <span>int</span>
    <span>page</span><span>:</span> <span>int</span>
    <span>order</span><span>:</span> <span>str</span>


<span>class</span> <span>FindItem</span><span>(</span><span>FindBase</span><span>,</span> <span>BaseItem</span><span>,</span> <span>metaclass</span><span>=</span><span>AllOptional</span><span>):</span>
    <span>pass</span>


<span>@app.get</span><span>(</span><span>"</span><span>/</span><span>"</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Hello World</span><span>"</span><span>}</span>


<span>@app.get</span><span>(</span><span>"</span><span>/items/</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>List</span><span>[</span><span>Item</span><span>])</span>
<span>async</span> <span>def</span> <span>find_items</span><span>(</span>
        <span>find_query</span><span>:</span> <span>FindItem</span> <span>=</span> <span>Depends</span><span>()</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>


<span>@app.post</span><span>(</span><span>"</span><span>/items/</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>create_item</span><span>(</span>
        <span>schema</span><span>:</span> <span>UpsertItem</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>


<span>@app.patch</span><span>(</span><span>"</span><span>/items/{id}</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>update_item</span><span>(</span>
        <span>id</span><span>:</span> <span>int</span><span>,</span>
        <span>schema</span><span>:</span> <span>UpsertItem</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>


<span>@app.put</span><span>(</span><span>"</span><span>/items/{id}</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>Item</span><span>)</span>
<span>async</span> <span>def</span> <span>put_item</span><span>(</span>
        <span>id</span><span>:</span> <span>int</span><span>,</span>
        <span>schema</span><span>:</span> <span>BaseItem</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>


<span>@app.get</span><span>(</span><span>"</span><span>/items/omitted</span><span>"</span><span>,</span> <span>response_model</span><span>=</span><span>OmittedTaxPrice</span><span>)</span>
<span>async</span> <span>def</span> <span>omitted_item</span><span>(</span>
        <span>schema</span><span>:</span> <span>OmittedTaxPrice</span>
<span>):</span>
    <span>return</span> <span>{</span><span>"</span><span>hello</span><span>"</span><span>:</span> <span>"</span><span>world</span><span>"</span><span>}</span>
from datetime import datetime from fastapi import FastAPI, Depends from pydantic import BaseModel from pydantic.main import ModelMetaclass from typing import Optional, List app = FastAPI() class AllOptional(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 Omit(ModelMetaclass): def __new__(self, name, bases, namespaces, **kwargs): omit_fields = getattr(namespaces.get("Config", {}), "omit_fields", {}) fields = namespaces.get('__fields__', {}) annotations = namespaces.get('__annotations__', {}) for base in bases: fields.update(base.__fields__) annotations.update(base.__annotations__) merged_keys = fields.keys() & annotations.keys() [merged_keys.add(field) for field in fields] new_fields = {} new_annotations = {} for field in merged_keys: if not field.startswith('__') and field not in omit_fields: new_annotations[field] = annotations.get(field, fields[field].type_) new_fields[field] = fields[field] namespaces['__annotations__'] = new_annotations namespaces['__fields__'] = new_fields return super().__new__(self, name, bases, namespaces, **kwargs) class BaseItem(BaseModel): name: str model: str manufacturer: str price: float tax: float class UpsertItem(BaseItem, metaclass=AllOptional): pass class Item(BaseItem, metaclass=AllOptional): id: int created_datetime: datetime updated_datetime: datetime class OmittedTaxPrice(BaseItem, metaclass=Omit): class Config: omit_fields = {'tax', 'price'} class FindBase(BaseModel): count: int page: int order: str class FindItem(FindBase, BaseItem, metaclass=AllOptional): pass @app.get("/") async def root( ): return {"message": "Hello World"} @app.get("/items/", response_model=List[Item]) async def find_items( find_query: FindItem = Depends() ): return {"hello": "world"} @app.post("/items/", response_model=Item) async def create_item( schema: UpsertItem ): return {"hello": "world"} @app.patch("/items/{id}", response_model=Item) async def update_item( id: int, schema: UpsertItem ): return {"hello": "world"} @app.put("/items/{id}", response_model=Item) async def put_item( id: int, schema: BaseItem ): return {"hello": "world"} @app.get("/items/omitted", response_model=OmittedTaxPrice) async def omitted_item( schema: OmittedTaxPrice ): return {"hello": "world"}

Enter fullscreen mode Exit fullscreen mode

When configured in this way, the following swagger documents are generated.

1. find items endpoint with optional parameters

图片[1]-Best Practice of handling FastAPI Schema - 拾光赋-拾光赋

2. create item

3. Put method

If you omit the item name according to the usage of PUT METHOD, 422 error is returned to send it with some name. The PUT was formed by declaring that it would receive BaseItem as a body.

图片[2]-Best Practice of handling FastAPI Schema - 拾光赋-拾光赋

4. PATCH method

5. Omitted

In this way, we learned how to deal with FastAPIschema. If you use something, you may see more necessary functions, but the endpoint and docs configuration that you need this much is progressing without difficulty in my case. If you want to actively use FastAPI’s schema to write docs, please refer to it to reduce trial and error.

原文链接:Best Practice of handling FastAPI Schema

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
Nobody looks down on you because everybody is too busy to look at you.
没谁瞧不起你,因为别人根本就没瞧你,大家都很忙的
评论 抢沙发

请登录后发表评论

    暂无评论内容