سحرارآمیز



اسکیمای اختصاصی و پویا برای خروجی apiها در FastApi

این fastapi کلا روی type hint های پایتون تاکید داره، کل داستان داکیومنت خوبش و تولید مشخصات openapi با بهره‌گیری از type hint ها و کمک pydantic هست.

یه چیز خوب توی fastapi این response_model هست که باهاش میشه نوع خروجی یک endpoint رو مشخص کرد. علاوه بر اینکه از این نوع مشخص شده برای کامل کردن مستنداتش استفاده میکنه، موقع تولید خروجی هم با استفاده از اسکیمای مشخص شده دیتا رو سریالایز و تایید میکنه.

به نقل از مستندات خودش:

FastAPI will use this `response_model` to:

-   Convert the output data to its type declaration.
-   Validate the data.
-   Add a JSON Schema for the response, in the OpenAPI _path operation_.
-   Will be used by the automatic documentation systems.

But most importantly:

-   Will limit the output data to that of the model. We'll see how that's important below.

ولی معمولا apiهایی که درست میکنیم یه قالب خروجی سفارشی دارن مثلا به این شکل:

{
    "header": {
        "status": 1,
        "mesage": "Success",
        "persian_message": "عملیات موفق"
    },
    "content": {
        "id": 1,
        "name": "Foo"
    }
}

تعریفمون توی api همچین شکلی میشه:

class Folan(BaseModel):
    id: int
    name: str


@app.post("/folan/", response_model=Folan)
async def create_item(folan: Folan):
    return folan

و خیلی هم خوب کار میکنه، هم دیتای خروجی سریالایز و تایید میشه هم توی docs/ یه داکیومنت خوب تحویل داده شده که اسکیمای خروجی رو هم دقیقا بر اساس مدل تعریف شده pydantic نشون داده.

ولی اگر بخوایم توی داکیومنت اسکیمای سفارشی شده‌ی خودمون که header و content داره رو نمایش بدیم چنتا کار اضافی باید انجام بدیم.

اول این که باید یه تایپ جدید اضاف کنیم که شامل content و header باشه.

این تایپ جدید که تعریف میکنیم باید بتونه هر تایپ دیگه‌ای رو هم در بر بگیره (به این نوع از تایپ‌ها میگیم (Generic Type).

یعنی باید بتونه مثلا این شکلی کار کنه:

def do_something(custom_var: MyCustomType[List[int]]):
    ...

چون درک Generic Type های پایتون برام سخت بود و نمونه‌ی این روش رو توی ابزار fastapi-pagination دیده بودم به کدهای همین ابزار مراجعه کردم و از این قسمت کدهاش با آزمون و خطا کد زیر رو برای استفاده‌ی خودم درآوردم.

import enum
from typing import Generic, TypeVar

from pydantic.generics import GenericModel
from pydantic import Field


T = TypeVar("T")


class ApiStatus(enum):
    SUCCESS = 1
    FAILURE = 0


class ApiResponseHeader(GenericModel, Generic[T]):
    """ Header type of APIResponseType"""
    status: int = Field(..., description=str(list(ApiStatus)))
    message: str = "Success"
    persian_message: str = "عملیات موفق"


class APIResponseType(GenericModel, Generic[T]):
    """
    an api response type for using as the api's router response_model
    """
    header: ApiResponseHeader
    content: T
class Folan(BaseModel):
    id: int
    name: str


@app.post("/folan/", response_model=APIResponseType[Folan])
async def create_item(folan: Folan):
    return folan

برای خروجی‌هایی که در قالب لیست با اطلاعات صفحه‌بندی (pagination) هستن هم میشه این Type رو تعریف کرد:

class PaginatedContent(GenericModel, Generic[T]):
    """ Content data type for lists with pagination"""
    data: T
    total_count: int = 0
    limit: int = 100
    offset: int = 0
class Folan(BaseModel):
    id: int
    name: str


@app.get(
    "/folan/",
    response_model=APIResponseType[PaginatedContent[List[Folan]]],
    )
async def read_folans(limit: int = None, skip: int = 0):
    return {
            "data": [Folan(name="first"), Folan(name="second")],
            "total_count": 10,
            "limit": limit,
            "offset": skip,
        }

همین کدها روی گیتهاب

دوشنبه 27 تیر 1401
بچه کد