๐ฆ DuckDI
DuckDI is a minimal, type-safe, and architecture-friendly dependency injection library for Python.
It provides a clean interface to register and resolve dependencies at runtime using a TOML-based configuration, following the duck typing principle: "if it implements the expected methods, itโs good enough."
Ideal for developers who want clarity, zero magic, and full control over dependency resolution.
๐ Features
- โ Clean and lightweight API
- โ Zero runtime dependencies
- โ Fully type-safe (no introspection magic)
- โ Supports singleton and transient resolution
- โ Uses TOML to bind interfaces to adapters
- โ
Works with
ABCand regular classes (no need for Protocols) - โ Clear and informative error messages
- โ
Environment-based configuration (
INJECTIONS_PATH)
๐ฆ Installation
With Poetry:
poetry add duckdi
Or using pip:
pip install duckdi
๐ ๏ธ Usage
1. Define an interface
from duckdi import Interface
from abc import ABC, abstractmethod
@Interface
class IUserRepository(ABC):
@abstractmethod
def get_user(self, user_id: str) -> dict: ...
2. Register an adapter
from duckdi import register
class PostgresUserRepository(IUserRepository):
def get_user(self, user_id: str) -> dict:
return {"id": user_id, "name": "John Doe"}
register(PostgresUserRepository)
You can also register it as a singleton:
register(PostgresUserRepository, is_singleton=True)
3. Create your injection payload
Create a file called injections.toml:
[injections]
"i_user_repository" = "postgres_user_repository"
4. Set the environment variable
Set the injection file path using the INJECTIONS_PATH environment variable:
export INJECTIONS_PATH=./injections.toml
5. Resolve your dependencies
from duckdi import Get
repo = Get(IUserRepository)
user = repo.get_user("123")
print(user) # {'id': '123', 'name': 'John Doe'}
๐ฅ Error Handling
MissingInjectionPayloadError
Raised when no injection payload file is found at the specified path.
InvalidAdapterImplementationError
Raised when the adapter registered does not implement the expected interface.
InterfaceAlreadyRegisteredError
Raised when trying to register the same interface twice.
AdapterAlreadyRegisteredError
Raised when the same adapter is registered more than once.
๐ Project Structure
duckdi/
โโโ pyproject.toml
โโโ README.md
โโโ src/
โ โโโ duckdi/
โ โโโ __init__.py
โ โโโ cli.py
โ โโโ duck.py
โ โโโ errors/
โ โ โโโ __init__.py
โ โ โโโ invalid_adapter_implementation_error.py
โ โ โโโ interface_already_registered_error.py
โ โ โโโ adapter_already_registered_error.py
โ โ โโโ missing_injection_payload_error.py
โ โโโ utils/
โ โ โโโ __init__.py
โ โ โโโ buffer_readers.py
โ โ โโโ to_snake.py
โ โโโ injections/
โ โโโ injections_container.py
โ โโโ injections_payload.py
โโโ tests/
โโโ test_interface.py
โโโ test_register.py
โโโ test_get.py
๐งฉ Advanced Example
You can register multiple adapters and resolve them dynamically based on the TOML mapping:
from duckdi import Interface, register, Get
@Interface
class INotifier:
def send(self, msg: str): ...
class EmailNotifier(INotifier):
def send(self, msg: str):
print(f"Sending email: {msg}")
register(EmailNotifier)
# injections.toml
# [injections]
# "i_notifier" = "email_notifier"
notifier = Get(INotifier)
notifier.send("Hello from DuckDI!")
๐งช Testing
To run tests:
pytest
Or via Makefile:
make test
To check static typing:
make check
๐ License
Licensed under the MIT License.
See the LICENSE file for more information.
๐ค Author
Made with โค๏ธ by PhePato
Pull requests, issues and ideas are always welcome!
Metadata
Release files for duckdi 0.1.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| duckdi-0.1.13.tar.gz | 6.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| duckdi-0.1.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.9 kB
Release files / duckdi-0.1.13.tar.gz
| Download URL | duckdi-0.1.13.tar.gz |
|---|---|
| Size | 6.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b19e73970461c444191c68b89c5f87fb8e47fd64baf0f72451a807cc8981bb69
|
|
BLAKE2b-256 checksum How to use checksums |
99e2108bea54cc384c8268e4fa17617ff515cf15beedde19f5370e58e544411e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.4 Darwin/24.0.0
|
Release files / duckdi-0.1.13-py3-none-any.whl
| Download URL | duckdi-0.1.13-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b9990934713491cdd8db4d61d9c6a73647655f4338dda4e2fbb54b209654a819
|
|
BLAKE2b-256 checksum How to use checksums |
9ed4edcb0640cc7b330d6e02200d8f3f471af0297321d552281951a2cb1be26a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.4 Darwin/24.0.0
|
Security for duckdi 0.1.13
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.