如何将自定义装饰器添加到 FastAPI 路由?

Posted

技术标签:

【中文标题】如何将自定义装饰器添加到 FastAPI 路由?【英文标题】:How to add a custom decorator to a FastAPI route? 【发布时间】:2021-02-06 09:04:04 【问题描述】:

我想向我的端点添加一个 auth_required 装饰器。 (请考虑这个问题是关于装饰器,而不是中间件

所以一个简单的装饰器看起来像这样:

def auth_required(func):
    def wrapper(*args, **kwargs):
        if user_ctx.get() is None:
            raise HTTPException(...)
        return func(*args, **kwargs)
    return wrapper

所以有两种用法:

@auth_required
@router.post(...)

@router.post(...)
@auth_required

第一种方法不起作用,因为router.post 创建了一个路由器,该路由器保存在 APIRouter 对象的self.routes 中。第二种方法不起作用,因为它无法验证 pydantic 对象。对于任何请求模型,它都会显示missing args, missing kwargs

所以我的问题是 - 如何将任何装饰器添加到 FastAPI 端点?我应该进入router.routes 并修改现有端点吗?或者使用一些functools.wraps 之类的函数?

【问题讨论】:

你有什么理由需要它作为装饰器吗?从 Flask 到 FastAPI,我有时认为我需要一个装饰器,但是对于需要 auth 或 Depends(User) 注入的端点,custom APIRoute class 也可以解决问题。 我想将该装饰器添加到某些端点,而不是每个端点。所以自定义 APIRoute 类(我实际使用它)没有帮助。而且我的中间件有问题 - 它在另一个线程中工作,所以我无法从另一个线程设置全局上下文变量。我看到了一些解决方案,但现在我真的很想知道装饰器是否可行。 FastAPI 的推荐风格似乎是使用依赖项。您将user: User = Depends(auth_function) 之类的内容添加到路径或函数中。它在你的端点函数之前被调用,类似于装饰器包装它的方式。它还应该可以访问 req-resp 上下文。 我知道如何使用取决于。它可以访问上下文,但由于它在另一个线程中工作,我在主线程中得到空上下文。 【参考方案1】:

以下是如何使用装饰器向路由处理程序添加额外参数:

from fastapi import FastAPI, Request
from pydantic import BaseModel


class SampleModel(BaseModel):
    name: str
    age: int


app = FastAPI()

def do_something_with_request_object(request: Request):
    print(request)

def auth_required(handler):
    async def wrapper(request: Request, *args, **kwargs):
        do_something_with_request_object(request)
        return await handler(*args, **kwargs)

    # Fix signature of wrapper
    import inspect
    wrapper.__signature__ = inspect.Signature(
        parameters = [
            # Use all parameters from handler
            *inspect.signature(handler).parameters.values(),

            # Skip *args and **kwargs from wrapper parameters:
            *filter(
                lambda p: p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD),
                inspect.signature(wrapper).parameters.values()
            )
        ],
        return_annotation = inspect.signature(handler).return_annotation,
    )

    return wrapper


@app.post("/")
@auth_required # Custom decorator
async def root(payload: SampleModel):
    return "message": f"Hello payload.name, payload.age years old!"

【讨论】:

试过了,但得到了TypeError: wrapper() missing 1 required positional argument: 'request' @JPG。经过大量调查和测试,我已经更新了代码。但是,我已经用自己的代码进行了测试;上面的代码我没有测试过。 gist.github.com/md2perpe/ee146e547a0bd910ea9683a2eea47c59 同样的错误TypeError: wrapper() missing 1 required positional argument: 'request' 我已经在 fastapi 的问题上回答了自己:github.com/tiangolo/fastapi/issues/2662【参考方案2】:

如何向 FastAPI 端点添加任何装饰器?

如你所说,你需要使用@functools.wraps(...)--(PyDoc)装饰器作为,

from functools import wraps

from fastapi import FastAPI
from pydantic import BaseModel


class SampleModel(BaseModel):
    name: str
    age: int


app = FastAPI()


def auth_required(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        return await func(*args, **kwargs)

    return wrapper


@app.post("/")
@auth_required # Custom decorator
async def root(payload: SampleModel):
    return "message": "Hello World", "payload": payload

此方法的主要警告是您无法访问包装器中的 request 对象,我认为这是您的主要意图。

如果需要访问请求,必须在router函数中添加参数为,

from fastapi import Request


@app.post("/")
@auth_required  # Custom decorator
async def root(request: Request, payload: SampleModel):
    return "message": "Hello World", "payload": payload

我不确定FastAPI中间件有什么问题,毕竟@app.middleware(...)也是一个装饰器。

【讨论】:

您能否详细说明@app.middleware(...) 您的意思是它也可以用作装饰器?这有什么例子或教程吗?

以上是关于如何将自定义装饰器添加到 FastAPI 路由?的主要内容,如果未能解决你的问题,请参考以下文章

使用自定义 TS 装饰器的组件方法中未定义角服务

19.FastAPI中间件

FastAPI利用装饰器实现定时任务

Rails 路由:将自定义路由添加到标准操作列表

PythonPydantic validator 与Fastapi 中validator使用功能介绍

将自定义 boxshadow 添加到 Flutter 卡