#python #python-decorators #fastapi #pydantic
#python #python-декораторы #fastapi #pydantic
Вопрос:
Я хочу добавить auth_required
декоратора к своим конечным точкам. (Пожалуйста, учтите, что этот вопрос касается декораторов, а не промежуточного программного обеспечения)
Итак, простой декоратор выглядит так:
def auth_required(func):
def wrapper(*args, **kwargs):
if user_ctx.get() is None:
raise HTTPException(...)
return func(*args, **kwargs)
return wrapper
Итак, существует 2 варианта использования:
@auth_required
@router.post(...)
или
@router.post(...)
@auth_required
Первый способ не работает, потому router.post
что создает маршрутизатор, который сохраняется в self.routes
объекте APIRouter . Второй способ не работает, потому что он не может проверить pydantic object. В нем говорится о любой модели запроса missing args, missing kwargs
.
Итак, мой вопрос: как я могу добавить какие-либо декораторы к конечным точкам FastAPI? Должен ли я войти router.routes
и изменить существующую конечную точку? Или использовать некоторые functools.wraps
подобные функции?
Комментарии:
1. Есть ли причина, по которой вам нужно, чтобы это был декоратор? Переходя из Flask в FastAPI, я иногда думаю, что мне нужен декоратор, но пользовательский класс APIRoute для конечных точек, которым требуется аутентификация или внедрение зависимостей (пользователя), также может решить проблему.
2. Я хочу добавить этот декоратор к некоторым конечным точкам, а не к каждой. Таким образом, пользовательский класс APIRoute (я на самом деле его использую) не помогает. И у меня проблема с промежуточным программным обеспечением — оно работает в другом потоке, поэтому я не могу настроить глобальную контекстную переменную из другого потока. Я видел некоторые решения для этого, но теперь я действительно хочу знать, возможны ли декораторы.
3. Рекомендуемый стиль с FastAPI, похоже, заключается в использовании зависимостей. Вы добавляете что-то вроде
user: User = Depends(auth_function)
пути или функции. Он вызывается перед вашей конечной функцией, аналогично тому, как его оборачивает декоратор. Он также должен иметь доступ к соответствующему контексту.4. Я знаю, как использовать depends . У него есть доступ к контексту, но поскольку он работает в другом потоке, я получаю пустой контекст в основном потоке.
Ответ №1:
Как я могу добавить какие-либо декораторы к конечным точкам 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
объекту в оболочке, и я предполагаю, что это ваше основное намерение.
Если вам нужно получить доступ к запросу, вы должны добавить аргумент в функцию маршрутизатора как,
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(...)
это тоже декоратор.
Комментарии:
1. не могли бы вы уточнить
@app.middleware(...)
, что вы имеете в виду, что они также могут работать как декораторы? любой пример или учебное пособие по этому?2. Это работает для запросов post, но не для запросов get. Есть идеи, почему?
3.
@auth_required
не зависит от метода запроса.4. @AnkitJain Пожалуйста, не забудьте объявить функции конечной точки с
async def
помощью . Приведенный выше@auth_required
декоратор будет работать только с функциями, объявленными сasync def
помощью . Если вы не используетеasync
/await
в конечных функциях, просто удалите его изdef wrapper(*args, **kwargs)
определения.5. @FahadMunir Большое спасибо! Это сработало. Я добавил асинхронность в функции конечных точек. Однако это не было проблемой с запросами post.
Ответ №2:
Вот как вы можете использовать декоратор, который добавляет дополнительные параметры в обработчик маршрута:
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!"}
Комментарии:
1. Пытался, но получил
TypeError: wrapper() missing 1 required positional argument: 'request'
2. @JPG. Я обновил код после долгих исследований и тестирования. Однако я тестировал свой собственный код; Я не тестировал приведенный выше код.
3. gist.github.com/md2perpe/ee146e547a0bd910ea9683a2eea47c59
4. та же ошибка
TypeError: wrapper() missing 1 required positional argument: 'request'
5. Я сам ответил на проблему fastapi: github.com/tiangolo/fastapi/issues/2662
Ответ №3:
Просто используйте зависимости внутри декоратора операции path:
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
async def verify_token(x_token: str = Header()):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: str = Header()):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]