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.16.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.16-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ps_dependency_injection-0.2.16.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.16.tar.gz
Algorithm Hash digest
SHA256 4a6a034e4cf9f317e66435d51800443f8f88fc87138e8db81680c57703325c0e
MD5 69fa252913fbcaa4f1a8ef28b79ca545
BLAKE2b-256 4debec51f98f2575eeb63f13c00465dbc3593a8ee716f552892c8781e10a308b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for ps_dependency_injection-0.2.16-py3-none-any.whl
Algorithm Hash digest
SHA256 6e4ae271ff5287015aa3ed8fb6b1d9b0b2af0d68e401d86e6f4b22d0d3c8e6f5
MD5 9879c8d784f14ff0e6253c4e868a0612
BLAKE2b-256 dc693ad3b78026ef7470df7073350e953113ea4abaa5313da31775ed736da047

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