Dead simple dependency injection for python.
Project description
diny
Dead simple dependency injection for python.
Just works
Drop in @singleton / @inject and delete the orchestration, lifecycle, and wiring code:
+from diny import inject, singleton
+
+@singleton
class Config:
def __init__(self):
self.url = "postgres://localhost"
+@singleton
class Database:
def __init__(self, config: Config):
self.conn = connect(config.url)
+@inject
def list_users(db: Database):
return db.query("SELECT * FROM users")
-# Lots of lines of orchestration and lifecycle code
-config = ...
-db = ...
-
-list_users(db)
+list_users() # Config and Database built on first call, cached after
No provide() needed — the cache is process-wide until you scope it.
Decorators
@singleton caches one instance per scope. @factory builds a fresh one at each site. @inject resolves a function's typed deps on call.
from diny import inject, singleton, factory
from uuid import uuid4
@singleton
class Database:
def __init__(self, config: Config): ...
@factory
class RequestId:
def __init__(self): self.value = uuid4()
@inject
def handler(db: Database, req: RequestId):
# db is cached across calls; req is fresh every time
...
Plain params pass through untouched:
@inject
def get_user(user_id: int, db: Database):
return db.query("SELECT * FROM users WHERE id = %s", user_id)
get_user(42)
Annotations
For classes you don't own — or to override a class's default scope at one call site — use Singleton[T] / Factory[T]:
from diny import Singleton, Factory
@inject
def handler(
client: Singleton[ThirdPartyClient], # cached
id_a: Factory[RequestId], # fresh
id_b: Factory[RequestId], # fresh, different instance
):
...
Site annotations beat class decorators — Singleton[RequestId] forces singleton even if RequestId is @factory.
Undecorated classes without a site annotation are passed through to the caller. Nothing is auto-injected behind your back.
Providers
Open a scope with provide() to override any dep — classes, instances, or factory functions:
from diny import provide
class FakeDatabase(Database):
def __init__(self, config: Config):
self.fake = True
with provide(Database=FakeDatabase):
list_users() # uses FakeDatabase
with provide(Config(url="test://")):
list_users() # uses this Config instance
A provider can be a function — its own typed deps get injected too:
def make_db(config: Config):
return PostgresDB(config.url, pool_size=10)
with provide(Database=make_db):
list_users()
Scopes nest:
with provide(Config(url="prod")):
handler() # prod
with provide(Config(url="test")):
handler() # test
handler() # prod again
Async
from diny import inject, aprovide
@inject
async def handler(db: Database):
return await db.fetch_all()
async def main():
async with aprovide():
await handler()
Async provider functions work inside aprovide:
async def make_pool(config: Config):
return await asyncpg.create_pool(config.url)
async with aprovide(Pool=make_pool):
await handler()
pytest
@pytest.fixture
def di():
with provide(Database=FakeDatabase):
yield
def test_users(di):
list_users()
Resolution
For a site requesting T:
| Registry contains | Singleton[T] |
Factory[T] |
|---|---|---|
| nothing | build T, cache | build T |
class C |
build C, cache | build C |
callable f |
call f, cache | call f |
instance i |
return i | build type(i) |
Out of scope
Lifecycle hooks, config loading, named deps, framework glue, string forward refs. Pass resources in, use with for cleanup, use real types.
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
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 diny-0.1.0-py3-none-any.whl.
File metadata
- Download URL: diny-0.1.0-py3-none-any.whl
- Upload date:
- Size: 5.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a61715a7f98d97f20b246cfaa3ad4bc2ef8515d3f6bfdfd43eef7a5148d511d6
|
|
| MD5 |
1d588a056ddbb88be9d580e087937085
|
|
| BLAKE2b-256 |
edbd45ff0f20e396b9b82dd2c85cd379fafbf41b9b20025232578e8cbd571ed9
|