Fastapi: [QUESTION] How can I attach a dependency outside function parameters?

Created on 21 Apr 2019  路  9Comments  路  Source: tiangolo/fastapi

Description

Is it possible to attach a dependency outside function parameters??

Additional context

For example, I want to move this "UserCan" dependency in a decorator somehow, because I would not use the user_id it returns while it must enforce the security policies it implements.

The reason is because linters will warn about unused variables...

@router.get('/{username}', summary='Get user profile details',
            response_model=UserProfileResponse)
async def get_user_profile(username: str,
                           user_id: str = Depends(UserCan('do_ordinary_stuff'))):
question

Most helpful comment

This is now implemented in version 0.22.0 :tada:

You can do:

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

The new docs are here: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/


And you can also use it in routers, like:

app.include_router(
    items.router,
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
)

The relevant docs are here: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-prefix-tags-responses-and-dependencies

(This should solve your use case @cjw296 )

All 9 comments

I see...

It is currently not possible, and supporting it in a decorator could lead to some extra complexity that is not really necessary, which linter are you using?

The first thing you can try is just naming the variable _. It's common to assign a variable _ with content that you don't really care. And linters tend to understand that.

So, you could:

@router.get('/{username}', summary='Get user profile details',
            response_model=UserProfileResponse)
async def get_user_profile(username: str, _ = Depends(UserCan('do_ordinary_stuff'))):
    pass

@tiangolo it was what I've done the second time I thought about... Thank you for the time took reasoning it

I ran into a similar issue -- wanting to have security-related dependencies that didn't show up as function arguments. The primary reasons behind this for me were:

1) it seems bad to just let yourself get used to unused argument warnings, and
2) removing what appears to be an unused argument can have serious security implications and might seem safe to someone not familiar with FastAPI; I was hoping for a way to make it more obvious what was going on

After some experimentation I came to the following conclusions:
1) I didn't love the solution of using _ as an argument name since you can't give multiple arguments the same name, but I confirmed that PyCharm doesn't give an unused argument warning for any argument starting with "_". So using _0, _1, etc. as variable names (or more descriptive alternatives) would accomplish the goal.
2) If anyone else finds themselves in this position and wants a decorator to add "anonymous" dependencies, the following works for me:

import asyncio
from collections import OrderedDict
from functools import wraps
from inspect import Parameter, signature
from typing import Callable

from fastapi import Depends


def add_dependencies(*dependencies: Depends):
    """
    Adds dependencies to be executed, but not provided as function arguments to the underlying function

    E.g.:
    @add_dependencies(Depends(get_current_active_superuser))
    def update_user(*, user_id: int, user_update: UserUpdate):
        ...
    """

    def decorator(func: Callable):
        sig = signature(func)
        original_parameters = sig.parameters
        new_parameters = OrderedDict(original_parameters)
        for i, dependency in enumerate(dependencies):
            name = _get_anonymous_dependency_name(i, new_parameters)
            new_parameters[name] = Parameter(name, kind=Parameter.KEYWORD_ONLY, default=dependency)

        is_coroutine = asyncio.iscoroutinefunction(func)
        if is_coroutine:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                kwargs = {k: v for k, v in kwargs.items() if k in original_parameters}
                return await func(*args, **kwargs)
        else:
            @wraps(func)
            def wrapper(*args, **kwargs):
                kwargs = {k: v for k, v in kwargs.items() if k in original_parameters}
                return func(*args, **kwargs)

        wrapper.__signature__ = sig.replace(parameters=tuple(new_parameters.values()))
        return wrapper

    return decorator


def _get_anonymous_dependency_name(index: int, current_parameters: OrderedDict):
    name = f"__unnamed_dependency_{index}"
    while name in current_parameters.keys():
        index += 1
        name = f"__unnamed_dependency_{index}"
    return name

@tiangolo I'd be happy to submit the above code as a pull request, but would also understand if you'd rather not take on the maintenance burden (in that case I'll just go the underscore-first function argument route).

Wow! I am really grateful for your code because I am reluctant of reading FastAPI internals for now

I'm reopening the issue for a while as working around with the provided alternative by @dmontagu

Note mainly for myself: @tiangolo tagged this as the issue that will cover "I want all my api endpoints to require authentication by default"

This is now implemented in version 0.22.0 :tada:

You can do:

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

The new docs are here: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/


And you can also use it in routers, like:

app.include_router(
    items.router,
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
)

The relevant docs are here: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-prefix-tags-responses-and-dependencies

(This should solve your use case @cjw296 )

Oh, gosh, I love you.

Hehe :joy: Thanks @mths0x5f ! :smile: :tada:

I think this is now solved, so I'll close this issue.

But feel free to add more comments, create new issues, etc.

Was this page helpful?
0 / 5 - 0 ratings