如何将自定义装饰器添加到 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 路由?的主要内容,如果未能解决你的问题,请参考以下文章