Skip to main content

Lightweight, thread-safe dependency injection container for Python

Project description

Overview

PyPI Python License

PS DI is a lightweight, thread-safe dependency injection container for Python. It provides a DI class that manages service registration, resolution, and automatic constructor injection. Registrations support singleton and transient lifetimes, priority-based ordering, and resolution by type or string name.

For working project examples, see the ps-poetry-examples repository.

Installation

pip install ps-dependency-injection

Or with Poetry:

poetry add ps-dependency-injection

Quick Start

from ps.di import DI, Lifetime

di = DI()

di.register(Logger).factory(Logger, "app")
di.register(UserRepository, Lifetime.TRANSIENT).implementation(UserRepository)

repo = di.resolve(UserRepository)

View full example

Register Services

The register method accepts a type (or string key), an optional Lifetime, and an optional Priority. It returns a Binding object that configures how the service is created.

  • .factory(callable, *args, **kwargs) — Registers a callable that produces the service. Typed parameters not covered by explicit arguments are resolved from the container at registration time, using the same injection rules as satisfy. Explicit positional and keyword arguments take precedence over container resolution.
  • .implementation(cls) — Registers a class whose constructor is invoked via spawn, allowing the container to inject known dependencies automatically.
from ps.di import DI, Lifetime

di = DI()

# Singleton (default) — one shared instance
di.register(Logger).factory(Logger, "app")

# Transient — new instance on every resolve
di.register(UserRepository, Lifetime.TRANSIENT).implementation(UserRepository)

Lifetime values:

  • SINGLETON — The factory is called once; all subsequent resolves return the same instance. This is the default.
  • TRANSIENT — The factory is called on every resolve, producing a new instance each time.

Resolve Services

Use resolve to retrieve the highest-priority registration for a type, or resolve_many to retrieve all registrations ordered by priority.

service = di.resolve(Logger)          # Logger | None
all_loggers = di.resolve_many(Logger) # list[Logger]

resolve returns None when no registration exists for the requested type. resolve_many returns an empty list in that case.

Both methods also accept a string type name instead of a class:

service = di.resolve("Logger")

String resolution matches against the __name__ attribute of registered types and raises ValueError when no match is found.

Priority

Each registration carries a Priority that determines its position relative to other registrations for the same type. Higher-priority registrations are resolved first by resolve and appear earlier in the list returned by resolve_many.

from ps.di import DI, Priority

di = DI()

di.register(NotificationService, priority=Priority.LOW).factory(NotificationService, "email")
di.register(NotificationService, priority=Priority.HIGH).factory(NotificationService, "sms")
di.register(NotificationService, priority=Priority.MEDIUM).factory(NotificationService, "push")

primary = di.resolve(NotificationService)  # sms (HIGH wins)

View full example

Priority values: LOW (default), MEDIUM, HIGH. When multiple registrations share the same priority, the most recently registered one wins.

Spawn Objects

spawn instantiates a class without registering it, injecting constructor dependencies from the container automatically. It inspects type hints on __init__ parameters and resolves them as follows:

  • A parameter typed as DI or a subclass of DI receives the container itself.
  • A parameter typed as List[T] receives the result of resolve_many(T).
  • A parameter typed as Optional[T] receives the result of resolve(T), falling back to the default value when nothing is registered.
  • Any other typed parameter receives the result of resolve(T). If resolve returns None and no default exists, spawn raises ValueError.

Positional and keyword arguments passed to spawn override automatic resolution:

from ps.di import DI

di = DI()
di.register(Logger).factory(Logger, "app")

repo = di.spawn(UserRepository)                       # Logger injected from container
repo = di.spawn(UserRepository, logger=custom_logger)  # explicit override

Satisfy Functions

satisfy binds a callable to dependencies resolved from the container at the time of the call, returning a new callable that accepts any remaining parameters at invocation time.

  • Parameters with registered types are resolved from the container automatically.
  • Parameters with defaults fall back to their default values when no registration exists.
  • Parameters typed as List[T] receive all registered instances of T.
  • Parameters typed as Optional[T] receive None when no registration exists.
  • Parameters marked with REQUIRED are excluded from DI resolution and must be supplied by the caller.
from ps.di import DI, REQUIRED

log_message = di.satisfy(format_log, message=REQUIRED)

print(log_message(message="Application started"))
print(log_message(message="Low disk space", level="WARNING"))

View full example

The returned callable accepts keyword arguments at invocation time. Any keyword argument passed at invocation time overrides the corresponding resolved value, including DI-resolved parameters.

Scopes

scope() creates a child DI instance that inherits all registrations from the parent but maintains its own isolated registry. This is useful for per-request, per-session, or any other short-lived context that needs additional or overriding registrations without affecting the parent container.

Resolution in a scoped container follows these rules:

  • resolve checks the scoped registry first; if nothing is registered, it falls through to the parent.
  • resolve_many returns scoped registrations followed by parent registrations, with scoped results first.
  • spawn and satisfy use the scoped resolver, so injected dependencies prefer scoped registrations.
  • A parameter typed as DI receives the scoped instance, not the parent.

Scopes support the context manager protocol. Exiting the with block clears the scoped registry and releases all singleton instances held by the scope, enabling deterministic cleanup of resources such as database connections or file handles.

with di.scope() as request_scope:
    request_scope.register(RequestContext).factory(RequestContext, request_id)
    handler = request_scope.spawn(RequestHandler)
    handler.handle()
# scoped singletons released here; parent container unaffected

View full example

The root container supports the same context manager protocol. Exiting a with di: block clears all registrations and releases every singleton instance held by the container.

Scopes can be nested arbitrarily. Each level sees its own registrations plus all ancestor registrations, with closer scopes taking precedence.

Thread Safety

All registration and resolution operations are protected by internal locks. Singleton creation uses double-checked locking so the factory is called exactly once even under concurrent access. Transient registrations produce independent instances per call with no shared mutable state.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ps_dependency_injection-0.2.10.tar.gz (6.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ps_dependency_injection-0.2.10-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file ps_dependency_injection-0.2.10.tar.gz.

File metadata

  • Download URL: ps_dependency_injection-0.2.10.tar.gz
  • Upload date:
  • Size: 6.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.13.12 Linux/6.17.0-1010-azure

File hashes

Hashes for ps_dependency_injection-0.2.10.tar.gz
Algorithm Hash digest
SHA256 bbfebafe1a478d20a41879da7c9f72579c3594e9685061ff2e30d6cd4396c219
MD5 1886fe12c4c5297961ccfd3a2f131973
BLAKE2b-256 99ebc1120d147f7db90bb6d723fe3874fa6ba0ff613248a873bea28cf34626e9

See more details on using hashes here.

File details

Details for the file ps_dependency_injection-0.2.10-py3-none-any.whl.

File metadata

File hashes

Hashes for ps_dependency_injection-0.2.10-py3-none-any.whl
Algorithm Hash digest
SHA256 23b152d313d45deb7544f5408da3073ffcd27d5929e0f0f155cf309a8f832d40
MD5 6c70608c78acb9e15ad6b601451b47e7
BLAKE2b-256 c723d54be451e0fee284f2b81560fab72be123401a3c70660aa5e215b9e24885

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page