Как создать представления на основе классов FastAPI для класса данных?Python

Программы на Python
Ответить
Anonymous
 Как создать представления на основе классов FastAPI для класса данных?

Сообщение Anonymous »

Предположим, я хочу создать экземпляр класса с параметрами и получить от него маршрутизатор FastAPI, определяя при этом обработчики обычным способом, то есть @router.get(...). Как лучше всего это сделать?
Несколько рабочих примеров:

Код: Выделить всё

#!/usr/bin/env python3
from __future__ import annotations

import abc
import asyncio
import copy
import dataclasses
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar

import fastapi
import fastapi.routing
import starlette.routing
import uvicorn

if TYPE_CHECKING:
from collections.abc import Callable

_TRoute = TypeVar("_TRoute", bound=starlette.routing.Route)

def _update_route(route: _TRoute, endpoint: Callable[..., Any]) -> _TRoute:
result = copy.copy(route)
result.endpoint = endpoint
return result

def _get_bound_endpoint(route: starlette.routing.Route, instance: Any) -> Callable[..., Any]:
name = route.endpoint.__name__
return getattr(instance, name)

def _update_route_endpoint(
route: fastapi.routing.APIRoute | starlette.routing.BaseRoute, instance: Any
) -> starlette.routing.BaseRoute:
assert isinstance(route, starlette.routing.Route)
return _update_route(route, endpoint=_get_bound_endpoint(route, instance))

def bind_class_routes(router: fastapi.APIRouter, instance: Any) -> fastapi.APIRouter:
routes = [_update_route_endpoint(route, instance) for route in router.routes]
return fastapi.APIRouter(routes=routes)

class ApiAppHandlers(abc.ABC):
_CLS_ROUTER: ClassVar[fastapi.APIRouter]

def build_router(self) -> fastapi.APIRouter:
return bind_class_routes(self._CLS_ROUTER, self)

@dataclasses.dataclass(kw_only=True)
class ApiAppRunner:
handlers: ApiAppHandlers
port: int = 29849
bind: str = "127.0.0.1"

async def _handle_ping(self) -> dict[str, Any]:
return {"response": "pong"}

def _build_app(self) -> fastapi.FastAPI:
app = fastapi.FastAPI()
app.include_router(self.handlers.build_router())
return app

async def run_uvicorn(self) -> None:
app = self._build_app()
config = uvicorn.Config(app, host=self.bind, port=self.port, server_header=False)
config.load()
server = uvicorn.Server(config)
# Pieces of `await server.serve()`, just to skip signal handler installation.
server.lifespan = config.lifespan_class(config)
await server.startup()
try:
await server.main_loop()
finally:
await server.shutdown()

async def run(self) -> None:
await self.run_uvicorn()

@dataclasses.dataclass(kw_only=True)
class SampleHandlers(ApiAppHandlers):
# Not shown here: for subclassing, this would require an explicit copy, or __mro__ walk.
_CLS_ROUTER: ClassVar[fastapi.APIRouter] = fastapi.APIRouter()

value: str

@_CLS_ROUTER.get("/hello")
async def get_hello(self) -> dict[str, Any]:
return {"value": self.value}

async def amain() -> None:
handlers = SampleHandlers(value="sample_value")
runner = ApiAppRunner(handlers=handlers)
await runner.run()

def main() -> None:
asyncio.run(amain())

if __name__ == "__main__":
main()
Альтернативы:
  • Код: Выделить всё

    APIRouter.add_api_route
    частично отделяет метод от определения. (ссылка)
  • fastapi-utils cbv: не создает маршрутизатор для каждого экземпляра, и «Аргументы функции init вводятся FastAPI так же, как и для обычных функций». (ссылка)
  • Код: Выделить всё

    Depends()
    + app.state: усложняет инициализацию приложения и не позволяет создавать несколько копий одних и тех же маршрутов (например, подклассов).
  • classy-fastapi наиболее похож на приведенный выше пример; выбирает другой набор хаков (например, functools.partial вместо связанных методов); в частности, используется обширная копипаста определений методов маршрутизатора (

    Код: Выделить всё

    def get(...)
    ).
Возможно ли что-то лучшее, если оно будет интегрировано в сам FastAPI? Например, наличие частичного класса маршрута (также вместо декоратора), маршрута, являющегося классом данных, и т. д.?

Подробнее здесь: https://stackoverflow.com/questions/798 ... -dataclass
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»