Как добавить пользовательский декоратор в маршрут FastAPI?

#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"}]