A simple and lightweight dependency injection container for Python
Project description
PyDI
A simple and lightweight dependency injection container for Python with support for different service lifetimes, automatic dependency resolution, and circular dependency detection.
Features
- Multiple Service Lifetimes: Singleton, Scoped, and Transient
- Automatic Dependency Resolution: Uses type hints to automatically inject dependencies
- Circular Dependency Detection: Detects and prevents circular dependencies at registration time
- Lazy Initialization: Services are instantiated only when needed
- Service Factories: Create multiple instances on demand using
ServiceFactory[T] - Type Safety: Full type hint support for better IDE integration and type checking
Installation
pip install pydi-core
Quick Start
from pydi_core import PyDI
class Database:
def __init__(self):
self.connection = "Connected to database"
class UserRepository:
def __init__(self, database: Database):
self.database = database
def get_users(self):
return f"Fetching users using {self.database.connection}"
class UserService:
def __init__(self, repository: UserRepository):
self.repository = repository
def list_users(self):
return self.repository.get_users()
# Create container and register services
pydi = PyDI()
pydi.register_service(Database, PyDI.LIFETIME_SINGLETON)
pydi.register_service(UserRepository, PyDI.LIFETIME_SCOPED)
pydi.register_service(UserService, PyDI.LIFETIME_TRANSIENT)
# Get service with dependencies automatically injected
user_service = pydi.get_service(UserService)
print(user_service.list_users())
# Output: Fetching users using Connected to database
Service Lifetimes
Singleton
The service is created only once and the same instance is returned every time.
pydi.register_service(Database, PyDI.LIFETIME_SINGLETON)
Scoped
The service is created once per dependency resolution scope. Within a single
get_service() call, the same instance is reused for all dependencies that
need it, but a new instance is created for each top-level get_service() call.
pydi.register_service(UserRepository, PyDI.LIFETIME_SCOPED)
Transient
A new service instance is created every time it is requested.
pydi.register_service(UserService, PyDI.LIFETIME_TRANSIENT)
Service Factories
Use ServiceFactory[T] to create multiple instances of a service on demand:
from pydi_core import PyDI, ServiceFactory
class Connection:
def __init__(self):
self.id = id(self)
class ConnectionPool:
def __init__(self, connection_factory: ServiceFactory[Connection]):
self.factory = connection_factory
self.connections = [
self.factory.create_instance(),
self.factory.create_instance(),
self.factory.create_instance()
]
pydi = PyDI()
pydi.register_service(Connection, PyDI.LIFETIME_TRANSIENT)
pydi.register_service(ConnectionPool, PyDI.LIFETIME_SINGLETON)
pool = pydi.get_service(ConnectionPool)
# pool.connections contains 3 different Connection instances
Error Handling
PyDI provides specific exceptions for different error scenarios:
CircularDependencyError
Raised when a circular dependency is detected:
class ServiceA:
def __init__(self, service_b: 'ServiceB'):
self.service_b = service_b
class ServiceB:
def __init__(self, service_a: ServiceA):
self.service_a = service_a
pydi = PyDI()
pydi.register_service(ServiceA, PyDI.LIFETIME_SINGLETON)
pydi.register_service(ServiceB, PyDI.LIFETIME_SINGLETON)
# Raises CircularDependencyError when finalized
UnknownDependencyError
Raised when a required dependency is not registered:
class ServiceA:
def __init__(self, service_b: ServiceB):
self.service_b = service_b
pydi = PyDI()
pydi.register_service(ServiceA, PyDI.LIFETIME_SINGLETON)
# Raises UnknownDependencyError - ServiceB not registered
AlreadyFinalizedError
Raised when trying to register a service after finalization:
pydi = PyDI()
pydi.register_service(ServiceA, PyDI.LIFETIME_SINGLETON)
pydi.finalize() # Manual finalization
# Raises AlreadyFinalizedError if registering after this point
InvalidLifetimeError
Raised when an invalid lifetime value is provided:
pydi.register_service(ServiceA, 999) # Invalid lifetime
# Raises InvalidLifetimeError
API Reference
PyDI
__init__()
Create a new dependency injection container.
register_service(service: Type, lifetime: int) -> None
Register a service with the specified lifetime.
Parameters:
service: The service class to registerlifetime: One ofLIFETIME_SINGLETON,LIFETIME_SCOPED, orLIFETIME_TRANSIENT
Raises:
AlreadyFinalizedError: If called after finalizationInvalidLifetimeError: If lifetime is invalidUnknownDependencyError: If a dependency annotation is missing
finalize() -> None
Finalize the container and validate all dependencies. This is called automatically when getting a service, so manual calling is optional.
Raises:
CircularDependencyError: If a circular dependency is detectedUnknownDependencyError: If a dependency is not registered
get_service(service: Type[T]) -> T
Get an instance of the registered service with dependencies injected.
Parameters:
service: The service class to retrieve
Returns:
- An instance of the service
Raises:
CircularDependencyError: If a circular dependency is detectedUnknownDependencyError: If the service or dependency is not registered
ServiceFactory[T]
create_instance() -> T
Create a new instance of the service, respecting its registered lifetime.
Returns:
- A new instance of the service (or cached instance for singletons)
Requirements
- Python >= 3.11
License
This project is licensed under the Apache License 2.0 — see LICENSE.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Author
Lorenzo Brilli
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 pydi_core-1.0.0.tar.gz.
File metadata
- Download URL: pydi_core-1.0.0.tar.gz
- Upload date:
- Size: 8.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08009cb02d4759276e1ba4e5f0ee32e57bed8d654b59fabe9a30a13646bab373
|
|
| MD5 |
90d6784a7a1adfb27326820b49c627ce
|
|
| BLAKE2b-256 |
b130f46c8440783ba10ac0e145318972e31e1a99002176d4609cfc6012e0663a
|
File details
Details for the file pydi_core-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pydi_core-1.0.0-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8c14193566d1c5790e194481e2ed3af27246c9f496ba5cc3636fa9020c59196
|
|
| MD5 |
49f60ca2c5823b1a4e4cf4925f743988
|
|
| BLAKE2b-256 |
3dccdbdc0029b14cb2bf04390391296de822ee7a3efc33fd4fbfb4e69138f8a5
|