如何在类中使用 FastAPI 创建路由

eek*_*eek 6 python class self python-3.x fastapi

所以我需要在一个类中有一些路由,但是路由方法需要有selfattr(访问类的属性)。但是,FastAPI 然后假定self是它自己的必需参数并将其作为查询参数放入

这就是我所拥有的:

app = FastAPI()
class Foo:
    def __init__(y: int):
        self.x = y

    @app.get("/somewhere")
    def bar(self): return self.x
Run Code Online (Sandbox Code Playgroud)

但是,422除非您转到 ,否则它将返回/somewhere?self=something。问题在于,那self是 str,因此没用。

我需要一些我仍然可以访问的方法,self而无需将其作为必需的参数。

Gus*_*rra 44

APIRouter这可以通过使用 an的方法来完成add_api_route

from fastapi import FastAPI, APIRouter


class Hello:

    def __init__(self, name: str):
        self.name = name
        self.router = APIRouter()
        self.router.add_api_route("/hello", self.hello, methods=["GET"])

    def hello(self):
        return {"Hello": self.name}


app = FastAPI()
hello = Hello("World")
app.include_router(hello.router)

Run Code Online (Sandbox Code Playgroud)

例子:

$ curl 127.0.0.1:5000/hello
{"Hello":"World"}
Run Code Online (Sandbox Code Playgroud)

add_api_route的第二个参数 ( endpoint) 具有 type Callable[..., Any],因此任何可调用都应该可以工作(只要 FastAPI 可以找到如何解析其参数 HTTP 请求数据)。此可调用函数在 FastAPI 文档中也称为路径操作函数(下面称为“POF”)。

为什么装饰方法不起作用

警告:如果您对OP答案中的代码不起作用的技术解释不感兴趣,请忽略此答案的其余部分

在类主体中使用 and 朋友装饰方法@app.get是行不通的,因为您将有效地传递Hello.hello,而不是hello.hello(又名self.hello)到add_api_route绑定和非绑定方法(自 Python 3 起简称为“函数” )具有不同的签名:

import inspect
inspect.signature(Hello.hello)  # <Signature (self)>
inspect.signature(hello.hello)  # <Signature ()>
Run Code Online (Sandbox Code Playgroud)

FastAPI 做了很多魔法来尝试自动将 HTTP 请求中的数据(正文或查询参数)解析为 POF 实际使用的对象。

通过使用未绑定方法(=常规函数)( Hello.hello) 作为 POF,FastAPI 必须:

  1. 对包含路由的类的性质做出假设并动态生成self(也称为调用Hello.__init__)。这可能会给 FastAPI 增加很多复杂性,并且 FastAPI 开发人员(可以理解)似乎对支持这个用例不感兴趣。处理应用程序/资源状态的推荐方法似乎是将整个问题推迟到Depends.

  2. 以某种方式能够self从调用者发送的 HTTP 请求数据(通常是 JSON)生成对象。这在技术上对于字符串或其他内置函数之外的任何东西都是不可行的,因此实际上不可用。

OP 代码中发生的情况是#2。FastAPI 尝试从 HTTP 请求查询参数中解析Hello.hello(= self,类型) 的第一个参数,显然失败并引发,该参数作为 HTTP 422 响应显示给调用者。HelloRequestValidationError

self从查询参数解析

只是为了证明上面的#2,这里有一个(无用的)示例,说明 FastAPI 何时可以真正self从 HTTP 请求中“解析”:

免责声明:请勿将以下代码用于任何实际应用

from fastapi import FastAPI

app = FastAPI()

class Hello(str):
    @app.get("/hello")
    def hello(self):
        return {"Hello": self}
Run Code Online (Sandbox Code Playgroud)

例子:

from fastapi import FastAPI

app = FastAPI()

class Hello(str):
    @app.get("/hello")
    def hello(self):
        return {"Hello": self}
Run Code Online (Sandbox Code Playgroud)


Evg*_*nov 9

我不喜欢这样做的标准方法,所以我编写了自己的库。你可以像这样安装它:

$ pip install cbfa
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它的示例:

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
from cbfa import ClassBased


app = FastAPI()
wrapper = ClassBased(app)

class Item(BaseModel):
    name: str
    price: float
    is_offer: Optional[bool] = None

@wrapper('/item')
class Item:
    def get(item_id: int, q: Optional[str] = None):
        return {"item_id": item_id, "q": q}

    def post(item_id: int, item: Item):
        return {"item_name": item.name, "item_id": item_id}
Run Code Online (Sandbox Code Playgroud)

请注意,您不需要在每个方法周围包装装饰器。根据方法在 HTTP 协议中的用途来命名这些方法就足够了。整个类都变成了一个装饰器。

  • 是的,它适用于各种路线。 (2认同)

ale*_*ame 7

要创建基于类的视图,您可以使用来自fastapi-utils 的@cbv装饰器。使用它的动机:

停止在相关端点的签名中一遍又一遍地重复相同的依赖关系。

您的示例可以像这样重写:

from fastapi import Depends, FastAPI
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter


def get_x():
    return 10


app = FastAPI()
router = InferringRouter()  # Step 1: Create a router


@cbv(router)  # Step 2: Create and decorate a class to hold the endpoints
class Foo:
    # Step 3: Add dependencies as class attributes
    x: int = Depends(get_x)

    @router.get("/somewhere")
    def bar(self) -> int:
        # Step 4: Use `self.<dependency_name>` to access shared dependencies
        return self.x


app.include_router(router)
Run Code Online (Sandbox Code Playgroud)


Oli*_*ain 7

我刚刚发布了一个项目,允许您使用类实例通过简单的装饰器进行路由处理。cbv很酷,但路由是在类本身上,而不是类的实例上。能够使用类实例可以让您以一种对我来说更简单、更直观的方式进行依赖项注入。

例如,以下内容按预期工作:

from classy_fastapi import Routable, get, delete

class UserRoutes(Routable):
   """Inherits from Routable."""

   # Note injection here by simply passing values
   # to the constructor. Other injection frameworks also 
   # supported as there's nothing special about this __init__ method.
   def __init__(self, dao: Dao) -> None:
      """Constructor. The Dao is injected here."""
      super().__init__()
      self.__dao = Dao

   @get('/user/{name}')
   def get_user_by_name(name: str) -> User:
      # Use our injected DAO instance.
      return self.__dao.get_user_by_name(name)

   @delete('/user/{name}')
   def delete_user(name: str) -> None:
      self.__dao.delete(name)


def main():
    args = parse_args()
    # Configure the DAO per command line arguments
    dao = Dao(args.url, args.user, args.password)
    # Simple intuitive injection
    user_routes = UserRoutes(dao)
    
    app = FastAPI()
    # router member inherited from Routable and configured per the annotations.
    app.include_router(user_routes.router)
Run Code Online (Sandbox Code Playgroud)

您可以在 PyPi 上找到它并通过 进行安装pip install classy-fastapi


小智 5

我把路线放到def __init__. 它工作正常。例子:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

class CustomAPI(FastAPI):
    def __init__(self, title: str = "CustomAPI") -> None:
        super().__init__(title=title)

        @self.get('/')
        async def home():
            """
            Home page
            """
            return HTMLResponse("<h1>CustomAPI</h1><br/><a href='/docs'>Try api now!</a>", status_code=status.HTTP_200_OK)
Run Code Online (Sandbox Code Playgroud)