Dependency injection
Project description
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 not longer your 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[..., Inject()].
class EmailService:
pass
class UserService:
def __init__(self, email_service: EmailService) -> None:
self.email_service = email_service
async def create_user(self) -> None:
pass
app = FastAPI()
@app.post("/users")
async def create_user(user_service: Annotated[UserService, Inject()]) -> None:
await user_service.create_user()
services = ServiceCollection()
services.add_transient(EmailService)
services.add_transient(UserService)
services.configure_fastapi(app)
Quickstart without FastAPI
You convert the service collection into a service provider:
class EmailService:
pass
class UserService:
def __init__(self, email_service: EmailService) -> None:
self.email_service = email_service
async def create_user(self) -> None:
pass
services = ServiceCollection()
services.add_transient(EmailService)
services.add_transient(UserService)
async def main() -> None:
async with services.build_service_provider() as service_provider:
user_service = await service_provider.get_required_service(UserService)
await user_service.create_user()
if __name__ == "__main__":
asyncio.run(main())
If you want a scope per operation (e.g., per HTTP request or message from a queue), you 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)
await user_service.create_user()
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, you need to use a factory function to create a service. For example, you have settings (a connection string, database name, etc.) stored using the package pydantic-settings and you 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, you must use a sync or async context manager, i.e., you instance it with:
async with DatabaseClient(database_connection_string) as client:
...
And, if you want to re-use it, you 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, you 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 you 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, you just have this:
def inject_database_client(application_settings: ApplicationSettings) -> DatabaseClient:
return DatabaseClient(
connection_string=application_settings.database_connection_string
)
services.add_singleton(ApplicationSettings)
services.add_transient(inject_database_client)
Testing
TBD
Registration with a base class
You can register a service by specifying both the service type (base class / interface) and the implementation type (concrete class). This is useful when you want to inject services using abstractions.
class NotificationService(ABC):
async def send_notification(self, recipient: str, message: str) -> None:
...
class EmailService(NotificationService):
async def send_notification(self, recipient: 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:
await self.notification_service.send_notification(email, "Welcome to our service!")
services.add_transient(NotificationService, EmailService)
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 aspy_dependency_injection-0.2.0.tar.gz.
File metadata
- Download URL: aspy_dependency_injection-0.2.0.tar.gz
- Upload date:
- Size: 18.4 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bb54e6005d933e487831ee9fb720bb5424baaf54bcd99e020deb47248c1e9bb
|
|
| MD5 |
e989547d881bc963ccd2a9080596a7f2
|
|
| BLAKE2b-256 |
6358fae74f0b646c09f7339d7153b5e25f343abb7d495d35720c6094478f5322
|
File details
Details for the file aspy_dependency_injection-0.2.0-py3-none-any.whl.
File metadata
- Download URL: aspy_dependency_injection-0.2.0-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6618d0f220dbbf8a79da225dc194637c361f21e4d64bd4352566e6df8617c491
|
|
| MD5 |
5ae53abf3970e87cdb4b737755756990
|
|
| BLAKE2b-256 |
5030eadb0722515068602d727380d883149328637faab0f2e36951e498349b66
|