question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature] Dependency injection without resorting to global state

See original GitHub issue

Hi there! I’m starting the FastAPI web server via Uvicorn after completing various initialization steps (reading command line arguments, reading a configuration file, setting up shared memory etc.). Now I would like to access said dependencies (command line arguments, config, shared memory, …) in my endpoints or, more specifically, pass them as argument to my endpoints. Unfortunately, the current way of injecting dependencies into endpoints via Depends basically amounts to using global state, as the dependency has to be declared in the endpoint’s function signature as a Callable and so has to be available already when Python runs the semantic analysis on the code.

At the same time, app.include_router(my_router, additional_dependencies=[ ]) does not solve this issue, either, as AFAIK there is no way to access these dependencies inside my endpoints.

What’s the best practice to solve this issue? Ideally I would like to do something like

from dataclasses import dataclass

from fastapi import FastAPI, APIRouter

@dataclass
class MyConfig:
    foo: str = ""

router = APIRouter()

@router.post("/")
def my_endpoint(my_config: MyConfig = DependsOnType(MyConfig)):
    ...

def create_app(config: MyConfig) -> FastAPI:
    app = FastAPI()
    app.register_dependency(type=MyConfig, value=config)
    app.include_router(router, prefix="/myrouter")
    return app

Then I could do app = create_app(MyConfig(foo="bar")) to create different versions of the app, depending on the configuration and other dependencies.

EDIT: While there is a way to achieve this (see Kludex’s comment below), it has various deficiencies (see the discussion below) and I would like to propose the above functionality involving DependsOnType and register_dependency() as a new feature.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:5
  • Comments:15 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
Mausecommented, Nov 23, 2020

With the above snippet, you can just inject it as a normal dependency (and you don’t really need the function either)


def webserver_process(config: Configuration):
    ...

    app.dependency_overrides[Configuration] = lambda: config

    ....


@app.get('/')
def index(configuration: Configuration = Depends()):
    ...

This is a working example:


from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends
from pydantic import BaseModel


class Config(BaseModel):
    name: str


app = FastAPI()
app.dependency_overrides[Config] = lambda: Config(name='hello')


@app.get('/')
def index(config: Config = Depends()):
    return config


tc = TestClient(app)
print(tc.get('/').json())

2reactions
jonathanslenderscommented, May 28, 2021

I worked around this limitation by using Python’s context variables.

A few lines of middleware will put them in place. The API endpoints can then either use some helper functions to get data from the context variables, possibly through fastapi.Depends.

It’s possible for the entry point of the application, e.g., the main() to wrap the ASGI app in middleware like this:

from typing import Optional
from contextvars import ContextVar
from starlette.types import ASGIApp, Receive, Scope, Send

class ServerState:
    " Data to be exposed. Whatever would be global state otherwise. "

_server_state_var: ContextVar[Optional[ServerState]] = ContextVar("_server_state_var")

class ContextvarMiddleware:
    """
    Middleware that exposes the `ServerState` through contextvars to all
    underlying API endpoints.
    """
    def __init__(self, app: ASGIApp, server_state: ServerState) -> None:
        self.app = app
        self.server_state = server_state

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        _server_state_var.set(self.server_state)

        try:
            await self.app(scope, receive, send)
        finally:
            _server_state_var.set(None)

def create_api_handler(server_state: ServerState) -> ASGIApp:
    # To be called from "main()" or wherever the state is set.
    return ContextvarMiddleware(app, server_state=server_state)

With this, it’s easy to define the FastAPI instance at the top level of a module and have it decoupled from any mutable state:

from fastapi import FastAPI, Depends

def get_server_state() -> ServerState:
    server_state = _server_state_var.get()
    if server_state is None:
        raise RuntimeError("Application not wrapped in ContextvarMiddleware.")
    return server_state

api = FastAPI()

@api.get('/')
def home(server_state: ServerState = Depends(get_server_state)):
   ...

Personally, I prefer to run ASGI applications using Hypercorn for this, because it allows running ASGI applications programatically from a main() function, which is what you need if you have an installable entry point (with setup.py and maybe click for command line parsing).

@codethief: Would the above work for you? @tiangolo: What do you think about this? Would it be possible or make sense to add this to the docs somewhere? (I don’t think it’s too much boilerplate to copy/paste for the people that need this functionality. Those who need it probably want to change it anyway, and will appreciate that it can be completely typed (which is maybe not the case if we try to come up with a generic solution.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

[Feature] Dependency injection without resorting to global state -
Hi there! I'm starting the FastAPI web server via Uvicorn after completing various initialization steps (reading command line arguments, ...
Read more >
How to use Dependency Injection in Functional Programming
Dependency Injection is a technique to make the classes in Object Oriented Programming easier to test and configure.
Read more >
What are the benefits of dependency injection in cases where ...
If you do not use dependency injection, you are completely out of luck, as only one global instance of each resource can exist...
Read more >
How not to do dependency injection - the static or singleton ...
You can use an IoC container without doing dependency injection and in fact, it is an extremely common (bad) practice. It is this...
Read more >
Dependency injection guidelines - .NET | Microsoft Learn
Avoid stateful, static classes and members. Avoid creating global state by designing apps to use singleton services instead. · Avoid direct ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found