Dependency Injection in Python is usually tied to frameworks.
If you use FastAPI, you get DI inside request scope.
If you use aiogram, you get implicit injection with flags and hidden magic.
But what if you want:
- Explicit dependency wiring
- Functional style
- Framework-independent injection
- Clean testability
That’s why I built FunDI.
FunDI — lightweight dependency injection library for functional programming.
It helps inject dependencies in a simple and declarative way.
Why another DI library?
Most framework DIs are:
- Implicit
- Request-bound
- Harder to debug at scale
For example, in aiogram:
@router.message(F.text == "/whoami", flags={"requires": {User: "user"}})
async def on_whoami(message: Message, user: User):
...
You can’t easily see:
- where
useris created - how it’s resolved
- whether it’s even configured correctly at startup
FunDI makes dependency origin explicit:
from fundi import from_
from bo.dependencies import require_user
async def on_whoami(user: User = from_(require_user)):
...
No flags.
No hidden injection.
No guessing.
You see exactly where the value comes from.
Core Idea
In FunDI, everything revolves around functions.
Key Concepts
- Dependency — function that produces data
- Dependant — function that consumes dependencies
- Scope — injection startup environment
- Injection — resolving arguments from scope + dependency graph
-
Lifespan dependency — setup + teardown (generator with single
yield) - Side effects — dependencies whose products are not passed into dependants
- Hooks — injection lifecycle events
- Overriding — swap dependencies for testing
It’s intentionally minimal and composable.
Quick Start
from fundi import scan, from_, inject
def require_user():
return "Alice"
def greet(user: str = from_(require_user)):
print(f"Hello, {user}!")
inject({}, scan(greet))
That’s it.
No app object.
No request.
No container class.
Just functions.
Features
- Simple syntax — define dependency with
from_() - Flexible dependency resolving algorithm
- Dependency overriding
- Built-in dependency mocking
- Works outside any framework
Caching Behavior
By default, dependencies are called once per injection.
This prevents unnecessary repetition and guarantees consistent resolution.
You can disable caching:
from_(require_something, caching=False)
or
scan(func, caching=False)
Note: disabling caching applies only to the specified function, not its internal dependencies.
Lifespan Dependencies
FunDI supports two-phase dependencies using generator functions:
def acquire_resource():
resource = connect()
yield resource
resource.close()
Preparation before yield.
Cleanup after.
You can also use context manager classes:
-
__enter__/__exit__ -
__aenter__/__aexit__
This makes resource handling explicit and deterministic.
Async Support
Async dependencies work exactly the same:
async def require_random_name() -> str:
await asyncio.sleep(0.4)
return random.choice(("Bob", "Steve", "Petro"))
FunDI resolves sync and async dependencies seamlessly.
Naming Convention
Dependency names communicate behavior:
-
require_→ may raise -
optional_→ may returnNone -
acquire_→ resource lifecycle dependency
This keeps business logic clean:
async def handler(admin: User = from_(require_admin_user)):
...
Clear separation between parameter name and provider function.
Comparison
| Feature | FunDI | Aiogram | FastAPI |
|---|---|---|---|
| Implicit DI | Optional | Yes | Partial |
| Framework-bound | No | Yes | Yes |
FunDI keeps what works from FastAPI’s design, but removes request coupling.
Testing & Overriding
One of the most important features.
Dependencies can be overridden during injection.
This makes testing trivial:
- no patching globals
- no monkeypatch gymnastics
- no container rewiring
You control resolution at injection level.
Philosophy
FunDI is intentionally small.
It does one thing:
resolve dependencies in a declarative, explicit, composable way.
It does not:
- manage application lifecycle
- replace frameworks
- impose architectural patterns
It simply wires functions together cleanly.
Installation
pip install fundi
or
poetry add fundi
or
uv add fundi
When Should You Use It?
- Building CLI tools
- Writing pure services
- Designing layered architectures
- Testing business logic independently
- Want FastAPI-like DI without HTTP coupling
- Tired of implicit framework magic
FunDI is small, explicit, and predictable.
If you prefer seeing your dependency graph instead of guessing it — you might like it.
Documentation: fundi.readthedocs.org
Repository: github.com/KuyuCode/fundi
PyPI: pypi.org/project
Top comments (0)