A dependency injection library for Python, Optimized for serverless applications
Project description
Ididi
Genius simplicity, unmatched power
ididi is 100% test covered and strictly typed.
Documentation: https://raceychan.github.io/ididi
Source Code: https://github.com/raceychan/ididi
Install
ididi requires python >= 3.9
pip install ididi
To view viusal dependency graph, install graphviz
pip install ididi[graphviz]
Features
- No / minial changes to your existing code
- Smart injection based on type hints with strong support to
typingmodule. - Advanced scope support
- Formated and detail-rich error messages
- Highly performant, on part with nodejs and go
Usage
Quick Start
from typing import AsyncGenerator
from ididi import use, entry
async def conn_factory(engine: AsyncEngine) -> AsyncGenerator[AsyncConnection, None]:
async with engine.begin() as conn:
yield conn
class UnitOfWork:
def __init__(self, conn: AsyncConnection=use(conn_factory)):
self._conn = conn
@entry
async def main(command: CreateUser, uow: UnitOfWork):
await uow.execute(build_query(command))
# note uow is automatically injected here
await main(CreateUser(name='user'))
Dependency factory
assign which factory method to use with ididi.use
from ididi import use
from datetime import datetime, timezone
def utc_factory() -> datetime:
return datetime.now(timezone.utc)
UTC_DATETIME = Annotated[datetime, use(utc_factory)]
class Timer:
def __init__(self, time: UTC_DATETIME):
self.time = time
tmer = ididi.resolve(Timer)
assert tmer.time.tzinfo == timezone.utc
[!TIP]
DependencyGraph.nodeaccepts a wide arrange of types, such as dependent class, sync/async facotry, sync/async resource factory, with typing support.
Scope
Using Scope to manage resources
- Infinite number of nested scope
- Parent scope can be accssed by its child scopes(within the same context)
- Resources will be shared across dependents only withint the same scope
- Resources will be automatically closed and destroyed when the scope is exited.
- Classes that implment
contextlib.AbstractContextManagerorcontextlib.AbstractAsyncContextManagerare also considered to be resources and can/should be resolved within scope. - Scopes are separated by context
[!TIP] If you have two call stack of
a1 -> b1anda2 -> b2, Herea1anda2are two calls to the same functiona, then, inb1, you can only access scope created by thea1, nota2.
This is particularly useful when you try to separate resources by route, endpoint, request, etc.
Async scope
@dg.node
def get_resource() -> ty.Generator[Resource, None, None]:
res = Resource()
with res:
yield res
@dg.node
async def get_asyncresource() -> ty.Generator[AsyncResource, None, None]:
res = AsyncResource()
async with res:
yield res
with dg.scope() as scope:
resource = scope.resolve(Resource)
# For async generator
async with dg.scope() as scope:
resource = await scope.resolve(AsyncResource)
[!TIP]
dg.nodewill leave your class/factory untouched, i.e., you can use it just like it is not decorated.
Contexted Scope
You can use dg.use_scope to retrive most recent scope, context-wise, this allows your to have access the scope without passing it around, e.g.
async def service_factory():
async with dg.scope() as scope:
service = scope.resolve(Service)
yield service
@app.get("users")
async def get_user(service: Service = Depends(service_factory))
await service.create_user(...)
Then somewhere deep in your service.create_user call stack
async def create_and_publish():
uow = dg.use_scope().resolve(UnitOfWork)
async with uow.trans():
user_repo.add_user(user)
event_store.add(user_created_event)
Here dg.use_scope() would return the same scope you created in your service_factory.
Named Scope
You can create infinite level of scopes by assigning hashable name to scopes
# at the top most entry of a request
async with dg.scope(request_id) as scope:
...
now scope with name request_id is accessible everywhere within the request context
request_scope = dg.use_scope(request_id)
[!NOTE] Two or more scopes with the same name would follow most recent rule.
Nested Nmaed Scope
async with dg.scope(app_name) as app_scope:
async with dg.scope(router_name) as router_scope:
async with dg.scope(endpoint_name) as endpoint_scope:
async with dg.scope(user_id) as user_scope:
async with dg.scope(request_id) as request_scope:
...
For any functions called within the request_scope, you can get the most recent scope with dg.use_scope(),
or its parent scopes, i.e. dg.use_scope(app_name) to get app_scope.
More
For more detailed information, check out Documentation
-
Tutorial
-
Usage of factory
-
Visualize the dependency graph(beta)
-
Circular Dependency Detection
-
Lazy Dependency(Beta)
-
Error context
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ididi-1.2.4.tar.gz.
File metadata
- Download URL: ididi-1.2.4.tar.gz
- Upload date:
- Size: 99.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a70cb4924de577ee155808b2e2b7f9bb420bbe144b178215612c53b53057280f
|
|
| MD5 |
b665e37bf555ae0d420b24b09add79ad
|
|
| BLAKE2b-256 |
09e1ebb5dc73cf3b1218b39dafa5bc305125392c10f5851a40ad9dc43b9ee80c
|
File details
Details for the file ididi-1.2.4-py3-none-any.whl.
File metadata
- Download URL: ididi-1.2.4-py3-none-any.whl
- Upload date:
- Size: 28.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec10dc247760ea8d416f8a30169d7078c2fee90d7cab9f6bf1dd05ee9629ba93
|
|
| MD5 |
c0620e64042b9b460dc86ec925979722
|
|
| BLAKE2b-256 |
edaef975d2b781a78eb5e818134973ad348aa559f396607e97db6e7944539f97
|