Features
- Use it everywhere: Use dependency injection in web servers, background tasks, console applications, Jupyter notebooks, tests, etc.
- Lifetimes:
Singleton(same instance per application),Scoped(same instance per HTTP request scope) andTransient(different instance per resolution). - FastAPI integration out of the box, and pluggable to any web framework.
- Automatic resolution and disposal: Automatically resolve constructor parameters and manage async and non-async context managers. It's no longer our concern to know how to create or dispose services.
- Clear design inspired by one of the most used and battle-tested DI libraries, adding async-native support, important features and good defaults.
- Centralized configuration: Register all services in one place using a clean syntax, and without decorators.
- ty and Pyright strict compliant.
📦 Installation
uv add aspy-dependency-injection
✨ Quickstart with FastAPI
Inject services into async endpoints using Annotated[..., FromServices()].
class EmailService:
pass
class UserService:
def __init__(self, email_service: EmailService) -> None:
self.email_service = email_service
app = FastAPI()
@app.post("/users")
async def create_user(user_service: Annotated[UserService, FromServices()]) -> None:
...
services = ServiceCollection()
services.add_transient(EmailService)
services.add_transient(UserService)
services.configure_fastapi(app)
✨ Quickstart without FastAPI
Define services and create a service provider.
class EmailService:
pass
class UserService:
def __init__(self, email_service: EmailService) -> None:
self.email_service = email_service
services = ServiceCollection()
services.add_transient(EmailService)
services.add_transient(UserService)
async with services.build_service_provider() as service_provider:
user_service = await service_provider.get_required_service(UserService)
If we want a scope per operation (e.g., per HTTP request or message from a queue), we can create a scope from the service provider:
async with service_provider.create_scope() as service_scope:
user_service = await service_scope.get_required_service(UserService)
🔄 Lifetimes
Transient: A new instance is created every time the service is requested. Examples: Services without state, workflows, repositories, service clients...Singleton: The same instance is used every time the service is requested. Examples: Settings (pydantic-settings), machine learning models, database connection pools, caches.Scoped: A new instance is created for each new scope, but the same instance is returned within the same scope. Examples: Database clients, unit of work.
🏭 Factories
Sometimes, we need to use a factory function to create a service. For example, we have settings (a connection string, database name, etc.) stored using the package pydantic-settings and we want to provide them to a service DatabaseClient to access a database.
class ApplicationSettings(BaseSettings):
database_connection_string: str
class DatabaseClient:
def __init__(self, connection_string: str) -> None:
pass
In a real DatabaseClient implementation, we must use a sync or async context manager, i.e., we instance it with:
async with DatabaseClient(database_connection_string) as client:
...
And, if we want to reuse it, we create a factory function with yield:
async def create_database_client(application_settings: ApplicationSettings) -> AsyncGenerator[DatabaseClient]:
async with DatabaseClient(application_settings.database_connection_string) as database_client:
yield database_client
With that factory, we have to provide manually a singleton of ApplicationSettings, and to know if DatabaseClient implements a sync or async context manager, or neither. Apart from that, if we need a singleton or scoped instance of DatabaseClient, it's very complex to manage the disposal of the instance.
Then, why don't just return it? With this package, we just have this:
def inject_database_client(application_settings: ApplicationSettings) -> DatabaseClient:
return DatabaseClient(
connection_string=application_settings.database_connection_string
)
services.add_transient(inject_database_client)
The factories can take as parameters other services registered. In this case, inject_database_client takes ApplicationSettings as a parameter, and the dependency injection mechanism will resolve it automatically.
🧪 Simplified testing
We can create a fixture in conftest.py that provides a ServiceProvider instance:
@pytest.fixture
async def service_provider() -> AsyncGenerator[ServiceProvider]:
async with services.build_service_provider() as service_provider:
yield service_provider
And then we can inject it into our tests and resolve the services.
async def test_create_user(service_provider: ServiceProvider) -> None:
user_service = await service_provider.get_required_service(UserService)
📝 Interfaces & abstract classes
We can register a service by specifying both the service type (interface / abstract class) and the implementation type (concrete class). This is useful when we want to inject services using abstractions.
class NotificationService(ABC):
@abstractmethod
async def send_notification(self, user_id: str, message: str) -> None:
...
class EmailService(NotificationService):
@override
async def send_notification(self, user_id: str, message: str) -> None:
pass
class UserService:
def __init__(self, notification_service: NotificationService) -> None:
self.notification_service = notification_service
async def create_user(self, email: str) -> None:
user = self.create_user(email)
await self.notification_service.send_notification(user.id, "Welcome to our service!")
services.add_transient(NotificationService, EmailService)
📝 Keyed services
We can register a service by specifying both the service type and a key. This is useful when we want to resolve services using abstractions and an explicit key.
class NotificationService(ABC):
@abstractmethod
async def send_notification(self, user_id: str, message: str) -> None:
...
class EmailService(NotificationService):
@override
async def send_notification(self, user_id: str, message: str) -> None:
pass
class PushNotificationService(NotificationService):
@override
async def send_notification(self, user_id: str, message: str) -> None:
pass
class UserService:
def __init__(
self,
notification_service: Annotated[NotificationService, FromKeyedServices("email"),
) -> None:
self.notification_service = notification_service
async def create_user(self, email: str) -> None:
user = self.create_user(email)
await self.notification_service.send_notification(user.id, "Welcome to our service!")
services.add_keyed_transient("email", NotificationService, EmailService)
services.add_keyed_transient("push", NotificationService, PushNotificationService)
📝 Auto-activated services
We can register a service as auto-activated. This is useful when we want to ensure our FastAPI application doesn't start to serve requests until certain services are fully initialized (e.g., machine learning models, database connection pools and caches).
services.add_auto_activated_singleton(MachineLearningModel)
📚 Documentation
For more information, check out the documentation.
Metadata
Release files for aspy-dependency-injection 0.6.0
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 | Actions |
|---|---|---|---|---|
| aspy_dependency_injection-0.6.0.tar.gz | 28.3 kB | Without provenance | Details for aspy_dependency_injection-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| aspy_dependency_injection-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for aspy_dependency_injection-0.6.0-py3-none-any.whl |
Total release size: 76.2 kB
Release files / aspy_dependency_injection-0.6.0.tar.gz
| Download URL | aspy_dependency_injection-0.6.0.tar.gz |
|---|---|
| Size | 28.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9899ec876a4f53b354dc9a2faf1f357fcbdeb7e6ac1f4779b0eb58a64699813
|
|
BLAKE2b-256 checksum How to use checksums |
e34847e5d2f576874df15bc59a37e7e7e76f55f9cca7036be32fb886f0544390
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","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}
|
Release files / aspy_dependency_injection-0.6.0-py3-none-any.whl
| Download URL | aspy_dependency_injection-0.6.0-py3-none-any.whl |
|---|---|
| Size | 47.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1d367d88a352720e95f7b5574c4ad24086fac8180616f6358211426aa1a2caa5
|
|
BLAKE2b-256 checksum How to use checksums |
b3bd5779cad1b7939b0f31d3f865050e2474d96388791710e7fcbecdc9d4ccbb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","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}
|
Security for aspy-dependency-injection 0.6.0
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.