简体   繁体   中英

Dynamic Dependencies / programmatically trigger dependency injection in FastAPI

In FastAPI path operations you can use FastAPIs Dependency injection. I have the requirement to dynamically register dependencies at startup and I have to be able to define a set of dependencies per path operation and collect their return values.

My problem now is that those dependencies can depend on sub-dependencies. My question is if and how I can realize this situation with FastAPI.

My current approach is this (can be copied, should run as is, if fastapi and uvicorn are installed):

from random import randint
from typing import List

import uvicorn
from fastapi import FastAPI, Depends

app = FastAPI()


def secondary_dep():
    return randint(0, 100)


class DepCaller:
    def __init__(self, *dependencies):
        self._dependencies = dependencies

    def __call__(self) -> List:
        return [dep() for dep in self._dependencies]


def primary_dep1(random_int: int = Depends(secondary_dep)):
    return random_int


def primary_dep2(random_int: int = Depends(secondary_dep)):
    return random_int


def dep_generator(primary_deps: List[str]) -> DepCaller:
    # This dict is a simplified version of "dynamically registered" dependencies
    defined_deps = {
        "A": primary_dep1,
        "B": primary_dep2
    }
    # Simplified; Error handling is missing for the sake of the example
    selected_deps = [defined_deps[dep] for dep in primary_deps]
    # <---!! Here is my problem. The dependencies do not resolve from this point on
    return DepCaller(*selected_deps)


@app.get("/")
def root(generated_dep=Depends(dep_generator(["A", "B"]))):
    return generated_dep


if __name__ == "__main__":
    uvicorn.run(app)

If I now query the resource: curl -X 'GET' 'http://127.0.0.1:8000/' -H 'accept: application/json' the result is [{"dependency":{},"use_cache":true},{"dependency":{},"use_cache":true}]

My desired result would be something like: [3, 65]

Some motivation to avoid me asking for a solution for B when I actually want to solve C (A->B->C):

I have a FastAPI app in which new resources can be added via plugins (python entrypoints). My dependables (primary_dep1, primary_dep2) are authentication strategies on which those new resources should be able to depend. Those strategies are also added via plugins. A path operation should now be able to depend on a set of strategies of which only one has to resolve to a successful authentication. Thus the list of dependencies to use instead of defining them explicit as parameters.

I hope I could make my problem clear and appreciate any answers, Thanks.

I could find a solution to my problem in the meantime:

def merge_dependencies(*dependencies: Callable) -> Callable:
    """
    This function wraps the given callables (dependencies) and 
    wraps 
    them in FastAPIs Depends. It returns
    a function containing these dependencies in its signature.

    :param dependencies: The dependencies to wrap
    :return: A callable which returns a list of the results of 
    the dependencies
    """

    def merged_dependencies(**kwargs):
        return list(kwargs.values())

    merged_dependencies.__signature__ = inspect.Signature(  # type: ignore
        parameters=[
            inspect.Parameter(f"dep{key}", 
            inspect.Parameter.KEYWORD_ONLY, default=Depends(dep))
            for key, dep in enumerate(dependencies)
        ]
    )
return merged_dependencies

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM