Dependency Injector for FastAPI that makes your life easier
Project description
FastAPI Magic DI
Dependency Injector for FastAPI that makes your life easier
What are the problems with FastAPI’s dependency injector?
- It forces you to use global variables.
- You need to write an endless number of fabrics with startup logic
- It makes your project highly dependent on FastAPI’s injector by using “Depends” everywhere.
To solve these problems, you can use this dead-simple Dependency Injector that will make development so much easier.
Q: But why not to use python-dependency-injector or other libs?
A: The goal of this Dependency Injector is to reduce the amount of code as much as possible and get rid of enterprise code with millions of configs, containers, and fabrics. That’s why python-dependency-injector and similar libraries are overkill. The philosophy of this injector is that clients know how to configure themselves and perform all startup routines.
Install
pip install fastapi-magic-di
Getting Started
from fastapi import FastAPI
from fastapi_di import Provide, ClientProtocol
from fastapi_di.app import inject_app
app = inject_app(FastAPI())
class Database:
connected: bool = False
def __connect__(self):
self.connected = True
def __disconnect__(self):
self.connected = False
class Service(ClientProtocol):
def __init__(self, db: Database):
self.db = db
def is_connected(self):
return self.db.connected
@app.get(path="/hello-world")
def hello_world(service: Provide[Service]) -> dict:
return {
"is_connected": service.is_connected()
}
That's all!
his simple code will recursively inject all dependencies and connect them using the __connect__
and __disconnect__
magic methods.
But what happened there?
- We created a new FastAPI app and injected it. The
inject_app
function makes the injector connect all clients on app startup and disconnect them on shutdown. That’s how you can open and close all connections (e.g., session to DB). - We defined new classes with
__connect__
and__disconnect__
magic methods. That’s how the injector finds classes that need to be injected. The injector uses duck typing to check if some class has these methods. It means you don’t need to inherit fromClientProtocol
(but you can to reduce the number of code lines). - Wrapped the
Service
type hint intoProvide
so that FastAPI can use our DI. Please note: you need to useProvide
only in FastAPI endpoints, which makes your codebase independent from FastAPI and this Dependency Injector. - PROFIT!
As you can see, in this example, you don’t need to write special constructors to store your dependencies in global variables. All you need to do to complete the startup logic is to write it in the __connect__
method.
Clients Configuration
This dependency injector promotes the idea of ‘zero-config clients’, but you can still use configurations if you prefer
Zero config clients
Simply fetch everything needed from the environment. There is no need for an additional configuration file. In this case, the library includes the env_fields module to simplify zero-client development
from dataclasses import dataclass, field
from fastapi_di.env_fields import field_str, field_bool
from redis.asyncio import Redis as RedisClient, from_url
@dataclass
class Redis:
url: str = field_str("REDIS_URL")
decode_responses: bool = field_bool("REDIS_DECODE_RESPONSES")
client: RedisClient = field(init=False)
async def __connect__(self):
self.client = await from_url(self.url, decode_responses=self.decode_responses)
await self.client.ping()
async def __disconnect__(self):
await self.client.close()
@property
def db(self) -> RedisClient:
return self.client
Just use the field_*
functions in dataclasses to fetch variables from the environment and cast them to the required data type.
Clients with Config
Inject config as dependency :)
from dataclasses import dataclass, field
from fastapi_di import ClientProtocol
from redis.asyncio import Redis as RedisClient
@dataclass
class RedisConfig(ClientProtocol):
url: str = "SOME_URL"
decode_responses: bool = True
class Redis:
db: RedisClient
def __init__(self, config: RedisConfig):
self.db = RedisClient(config.url, decode_responses=config.decode_responses)
async def __connect__(self):
await self.db.ping()
async def __disconnect__(self):
await self.db.close()
Using interfaces instead of implementations
Sometimes, you may not want to stick to a certain interface implementation everywhere. Therefore, you can use interfaces (protocols, abstract classes) with Dependency Injection (DI). With DI, you can effortlessly bind an implementation to an interface and subsequently update it if necessary.
from typing import Protocol
from fastapi import FastAPI
from fastapi_di import Provide, ClientProtocol, injector
from fastapi_di.app import inject_app
class MyInterface(Protocol):
def do_something(self) -> bool:
...
class MyInterfaceImplementation(ClientProtocol):
def do_something(self) -> bool:
return True
app = inject_app(FastAPI())
injector.bind({MyInterface: MyInterfaceImplementation})
@app.get(path="/hello-world")
def hello_world(service: Provide[MyInterface]) -> dict:
return {
"result": service.do_something(),
}
Using injector.bind
, you can bind implementations that will be injected everywhere the bound interface is used.
Testing
If you need to mock a dependency in tests, you can easily do so by using the injector.override
context manager and still use this dependency injector.
To mock clients, you can use ClientMock
from the testing
module.
from fastapi_di import injector
from fastapi_di.testing import ClientMock
def test_http_handler(client):
service_mock = ClientMock()
with injector.override({Service: service_mock.mock_cls}):
resp = client.post('/hello-world')
assert resp.status_code == 200
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file fastapi_magic_di-0.0.1a3.tar.gz
.
File metadata
- Download URL: fastapi_magic_di-0.0.1a3.tar.gz
- Upload date:
- Size: 6.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.5.1 CPython/3.10.11 Darwin/22.5.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ee4db908e42ca0243f835f6ac362a855ee8ebcda4edf9c1f87941b74beded2e8 |
|
MD5 | 69d6addbdddacab5762c0e2f99cc4e17 |
|
BLAKE2b-256 | 65d32ae025beabbbe912d6783e9e8a1c09ad85a1bed8637247d60bda5f9f79b5 |
File details
Details for the file fastapi_magic_di-0.0.1a3-py3-none-any.whl
.
File metadata
- Download URL: fastapi_magic_di-0.0.1a3-py3-none-any.whl
- Upload date:
- Size: 8.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.5.1 CPython/3.10.11 Darwin/22.5.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f124f904a977e1ae422ba7a90186e81c924ce8b46f0cfda3fc235b65bf42eee6 |
|
MD5 | aed74f3d945c084eb7eed5550b6da1c5 |
|
BLAKE2b-256 | 0539f987f181c69dcb1b974d67d1a24f57dae5f3145805f829402d16e38ba02b |