(Better) Dependency Injection in FastAPI
how to inject FastAPI bearer instances into Depends
it's possible to do dependency injection in Fastapi using abstract class? - Stack Overflow
FastAPI + Dependency Injector
I've tried to document my thought process for picking a dependency injection library, and I ended up with a bit of a rant. Followed by my actual thought process and implementation. Please let me know what you think of it (downvotes are fine :)) ), I'm curious if my approach/thought process makes sense to more experienced Python devs.
To tell you the truth, I'm a big fan of dependency injection. One you get to a certain app size (and/or component lifetime requirements), having your dependency instances handled for you is a godsend.
I just don't like how it works in FastAPI
You see, in FastAPI if you want to inject a component in, say, an endpoint you would do something like def my_endpoint(a=Depends(my_a_factory)), and have your my_a_factory create an instance of a or whatever. Simple, right? And, if a depends on, say, b, you then create a my_b_factory, responsible for creating b, then change the signature of my_a_factory to something like def my_a_factory(b=Depends(my_b_factory)). Easy.
But wait! What if b requires some dependencies itself? Well, I hope you're using your comfortable keyboard, because you're gonna have to write and wire up a lot of factories. One for each component. Each one Depends-ing on others. With you managing all their little lifetimes by hand. It's factories all the way down, friend. All the way down.
And sure, I mean, this approach is fine. You can use it to check user permissions, inject your db session, and stuff. It's easy to get your head around it.
But for building something more complex? Where class A needs an instance of class B, and B in turn needs C & D instances, and (guess what) D depends on E & F? Nah, man, ain't nobody got time for that.
And I haven't even mentioned the plethora of instance lifetimes -- say, B, D, & E are singletons, C is per-FastAPI-request, and F is transient, i.e. it's instantiated every time. Implement this with Depends and you'll be working on your very own, extremely private, utterly personal, HELL.
So anyway, this is how I ended up looking at DI libraries for Python
There's not that many Python dependency injection libraries, mind you. Looks like a lot of Python devs are happily building singletons left and right and don't need to inject no dependencies, while most of the others think DI is all about simplifying unit tests and just don't see the point of inverting control.
To me though, dependency inversion/injection is all about component lifetime management. I don't want to care how to instantiate nor how to dispose a dependency. I just want to declare it and then jump straight to using it. And the harder it is for me to use it, i.e. by instantiating it and its "rich" dependency tree, disposing each one when appropriate, etc, the more likely that I won't even bother at all. Simple things should be simple.
So as I said, there's not a lot of DI frameworks in Python. Just take a look at this Awesome Dependency Injection in Python, it's depressing, really (the content, not the list, the list is cool). Only 3 libraries have more than 1k stars on Github. Some of the smaller ones are cute, others not so much.
Out of the three, the most popular seemed to be python-dependency-injector, but I didn't like the big development gap between Dec 2022 and Aug 2024. Development seems to have picked up recently, but I've decided to give it a little more time to settle. It has a bunch of providers, but it wasn't clear to me how I would get a per-request lifetime. Their FastAPI example looks a bit weird to me, I'm not a fan of those Depends(Provide[Container.config.default.query]) calls (why should ALL my code know where I'm configuring my dependencies?!?).
The second most popular one is returns, which looks interesting and a bit weird, but ultimely it doesn't seem to be what I'm after.
The third one is injector. Not terribly updated, but not abandoned either. I like that I can define the lifetimes of my components in a single place. I..kinda dislike that I need to decorate all my injectable classes with @inject but beggars can't be choosers, am I right? The documentation is not nearly as good as python-dependency-injector's. I can couple it with fastapi-injector to get request-scoped dependencies.
In the end, after looking at a gazillion other options, I went with the injector + fastapi-injector combo -- it covered most of my pain points (single point for defining my dependencies and their lifetimes, easy to integrate with FastAPI, reasonably up to date), and the drawbacks (that pesky @inject) were minimal.
Here's how I set it up to handle my convoluted example above
Where class
Aneeds an instance of classB, andBin turn needsC&Dinstances, and (guess what)Ddepends onE&F
First, the classes. The only thing they need to know is that they'll be @injected somewhere, and, if they require some dependencies, to declare and annotated them.
# classes.py
from injector import inject
@inject
class F
def __init__(self)
pass
@inject
class E
def __init__(self)
pass
@inject
class D
def __init__(self, e: E, f: F):
self.e = e
self.f = f
@inject
class C:
def __init__(self)
pass
@inject
class B:
def __init__(self, c: C, d: D):
self.c = c
self.d = d
@inject
class A:
def __init__(self, b: B):
self.b = bsay,
B,D, &Eare singletons,Cis per-FastAPI-request, andFis transient, i.e. it's instantiated every time.
The lifetimes are defined in one place and one place only, while the rest of the code doesn't know anything about this.
# dependencies.py
from classes import A, B, C, D, E, F
from fastapi_injector import request_scope
from injector import Module, singleton, noscope
class Dependencies(Module):
def configure(self, binder):
binder.bind(A, scope=noscope)
binder.bind(B, scope=singleton)
binder.bind(C, scope=request_scope)
binder.bind(D, scope=singleton)
binder.bind(E, scope=singleton)
binder.bind(F, scope=noscope)
# this one's just for fun 🙃
binder.bind(logging.Logger, to=lambda: logging.getLogger())
Then, attach the injector middleware to your app, and start injecting dependencies in your routes with Injected.
# main.py
from fastapi_injector import InjectorMiddleware, attach_injector
from injector import Injector
app = FastAPI()
injector = Injector(Dependencies())
app.add_middleware(InjectorMiddleware, injector=injector)
attach_injector(app, injector)
@app.get("/")
def root(a: A = Injected(A)):
passNot too shabby. It's not a perfect solution, but it's quite close to what I had gotten used to in .NET land. I'm sticking with it for now.
(and yes, I've posted this online too, over here)
» pip install fastapi-injector
The dependency injection technique can be accomplished with FastAPI using the Depends class.
Here's an example of what FastAPI can offer.
Repository:
Start building the repository by combining python's ABC class with a product repo, and assuming the scope is to CRUD a product, then pydantic can help us represent the model using the BaseModel
from abc import ABC, abstractmethod
from fastapi import FastAPI, Depends
from pydantic import BaseModel
class ProductModel(BaseModel):
"""
Pydantic model for request/response
"""
title: str
class ProductRepositoryABC(ABC):
"""
Abstract base product repository
"""
@abstractmethod
def create_product(self, product: ProductModel) -> ProductModel:
raise NotImplementedError
class ProductRepository(ProductRepositoryABC):
"""
Product repository
"""
def create_product(self, product: ProductModel) -> ProductModel:
print(f"I'm creating a new product: {product}")
return product
Service:
After creating the model and the repository, we can start creating a service by injecting the repo as a dependency.
class ProductService(object):
"""
Product service
"""
def __init__(self, product_repo: ProductRepositoryABC = Depends(ProductRepository)):
self.product_repo = product_repo
def create_product(self, product: ProductModel) -> ProductModel:
return self.product_repo.create_product(product=product)
Views/Routes:
Injecting a service into the route function is one step ahead; pass the service to the function as a param and use Depends to inject the service, which will be accessible in the function scope.
app = FastAPI()
@app.post(
"/products",
name="products:create",
response_model=ProductModel
)
def create_product(
product: ProductModel,
product_srv: ProductService = Depends(ProductService)
) -> ProductModel:
return product_srv.create_product(product=product)
Complete code:
main.py
from abc import ABC, abstractmethod
from fastapi import FastAPI, Depends
from pydantic import BaseModel
class ProductModel(BaseModel):
"""
Pydantic model for request/response
"""
title: str
class ProductRepositoryABC(ABC):
"""
Abstract base product repository
"""
@abstractmethod
def create_product(self, product: ProductModel) -> ProductModel:
raise NotImplementedError
class ProductRepository(ProductRepositoryABC):
"""
Product repository
"""
def create_product(self, product: ProductModel) -> ProductModel:
print(f"I'm creating a new product: {product}")
return product
class ProductService(object):
"""
Product service
"""
def __init__(self, product_repo: ProductRepositoryABC = Depends(ProductRepository)):
self.product_repo = product_repo
def create_product(self, product: ProductModel) -> ProductModel:
return self.product_repo.create_product(product=product)
app = FastAPI()
@app.post(
"/products",
name="products:create",
response_model=ProductModel
)
def create_product(
product: ProductModel,
product_srv: ProductService = Depends(ProductService)
) -> ProductModel:
return product_srv.create_product(product=product)
Global settings:
You can use Pydantic to build config-base settings with a class mapper for global settings, and build a utility for string imports, and there's a quite good well-testing functions on the Django community which you can use to import from string or use the import_module directly dynamically from importlib
config.py
from pydantic import BaseSettings
from .utils import import_string #YOUR IMPORT STRING FUNCTION
class Settings(BaseSettings):
app_name: str = "Awesome API"
services = {
"product_repo": {"class": import_string("repositories.ProductRepository")}
}
settings.py
from functools import lru_cache
from . import config
app = FastAPI()
@lru_cache()
def get_settings():
return config.Settings()
Start packaging your module with a more robust structure like the following
services/products.py
from settings import get_settings
settings = get_settings()
class ProductService(object):
"""
Product service
"""
def __init__(self, product_repo: ProductRepositoryABC = Depends(settings.get('services').get('class')):
self.product_repo = product_repo
def create_product(self, product: ProductModel) -> ProductModel:
return self.product_repo.create_product(product=product)
Note: One of the limitations of Depends is that currently there is no straight forward way to use it outside of FastAPI context, but luckily you can still use and combine it with a powerful tool like https://github.com/ets-labs/python-dependency-injector to build a robust decoupled modules FastAPI example
Final result:

@Josu16
I also had the same problem in FastAPI class as dependency, what I did and it may funny, is I created instance of the ProductService and pass the ProductRepository itslef to the argument of it. out side of the @router
pro_service = ProductService(ProductRepository)
it may notbest practice but it just work.
the product service file is like this :
def __init__(self, repo: ProductRepositoryABC = Depends()) -> None:
I'm trying to figureout the way based on Depends=()
Hey,
I've released a new version of the Dependency Injector 4.4. It makes possible to use Dependency Injector with FastAPI.
Example
File fastapi_di_example.py:
import sys
from fastapi import FastAPI, Depends
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
class Service:
async def process(self) -> str:
return 'Ok'
class Container(containers.DeclarativeContainer):
service = providers.Factory(Service)
app = FastAPI()
@app.api_route('/')
@inject
async def index(service: Service = Depends(Provide[Container.service])):
result = await service.process()
return {'result': result}
container = Container()
container.wire(modules=[sys.modules[__name__]])To run the example install the dependencies:
pip install fastapi dependency-injector uvicorn
and run uvicorn:
uvicorn fastapi_di_example:app --reload
You should see in the terminal something like:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [11910] using watchgod INFO: Started server process [11912] INFO: Waiting for application startup. INFO: Application startup complete.
and http://127.0.0.1:8000 should return:
{"result": "Ok"}Testing
File tests.py:
from unittest import mock
import pytest
from httpx import AsyncClient
from fastapi_di_example import app, container, Service
@pytest.fixture
def client(event_loop):
client = AsyncClient(app=app, base_url='http://test')
yield client
event_loop.run_until_complete(client.aclose())
@pytest.mark.asyncio
async def test_index(client):
service_mock = mock.AsyncMock(spec=Service)
service_mock.process.return_value = 'Foo'
with container.service.override(service_mock):
response = await client.get('/')
assert response.status_code == 200
assert response.json() == {'result': 'Foo'}To run the test install the dependencies:
pip install pytest pytest-asyncio httpx
and execute:
pytest tests.py
You will something like that in the terminal:
======= test session starts ======= platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 rootdir: ... plugins: asyncio-0.14.0 collected 1 item tests.py . [100%] ======= 1 passed in 0.17s =======
What is the advantage?
FastAPI is a powerful framework for building API. It has basic dependency injection mechanism.
This integration brings the dependency injection in FastAPI to the next level. It makes possible to use it with Dependency Injector providers, overridings, config, and resources.