Skip to main content

voltwire-di-core

A small, dependency injection based framework for Python built on top of dependency-injector. Decorate your classes with @component, point the auto-discovery scanner at your package, and get a wired global container — no manual registration boilerplate. Works in a FastAPI app, a plain script, an AWS Lambda, or anywhere else.

Installation

pip install voltwire-di-core
# or with Poetry:
poetry add voltwire-di-core

Quickstart

Mark the classes you want managed with @component (pairs naturally with @attrs.define):

import attrs
from voltwire.di.core import component


@component
@attrs.define
class GreetingRepository:
    def greeting(self) -> str:
        return "hello"


@component
@attrs.define
class GreetingService:
    _repo: GreetingRepository

    def greet(self) -> str:
        return self._repo.greeting()

At startup, scan your package once and then resolve anything:

from voltwire.di.core import auto_discover_components, di

auto_discover_components(base_package="myapp")

service = di.provide(GreetingService)   # GreetingRepository injected automatically
service.greet()

Constructor dependencies are resolved from their type hints. Registration is multi-pass, so the order in which components are discovered does not matter. Parameters with default values are treated as optional and skipped, and an Optional[T] / T | None dependency is skipped when no provider for T exists.

Need exactly one shared instance instead of a new one per di.provide? Use @singleton instead of @component — it participates in the same auto-discovery and multi-pass dependency resolution, but is wired up with a providers.Singleton instead of a factory:

from voltwire.di.core import singleton


@singleton
class CacheService:
    def __init__(self, settings: CacheSettings):
        self._settings = settings

This is a drop-in alternative to manually calling di.register_singleton(...) after discovery — use whichever fits: @singleton for classes discovered by scanning your package, di.register_singleton for one-offs (e.g. third-party clients) inside a registrars callback.

How resolution works

auto_discover_components(base_package, registrars=None) runs in three steps:

  1. @settings functions are registered first as singletons (see below).
  2. registrars — optional callbacks for manual singletons that need special construction (e.g. third-party clients) — are invoked.
  3. @component classes are registered with multi-pass dependency resolution.
from voltwire.di.core import auto_discover_components, di


def register_external_clients() -> None:
    di.register_singleton(SomeClient, api_key="...")


auto_discover_components(base_package="myapp", registrars=[register_external_clients])

Settings providers

Use @settings on a function whose return type is the type to register. Combine with functools.lru_cache for single instantiation:

from functools import lru_cache
from voltwire.di.core import settings


@settings
@lru_cache
def get_db_settings() -> DatabaseSettings:
    return DatabaseSettings()

The returned instance is registered as a singleton keyed by the return annotation, so any @component depending on DatabaseSettings receives it.

Core providers (app-supplied)

voltwire-di-core ships no opinionated providers — it is deliberately decoupled from databases, sessions, and web frameworks. Your application supplies its own "core" providers (things that must exist before anything is resolved, e.g. a DB session factory) by registering one or more callables on the container. They run once, lazily, the first time anything is provided:

from voltwire.di.core import di


def register_db_providers() -> None:
    di.register(DatabaseSettings, providers.Object(get_db_settings()))
    di.register_singleton(SessionFactory, settings=di.get_provider(DatabaseSettings))


di.register_core_provider(register_db_providers)

# First di.provide(...) anywhere triggers ensure_core_providers() internally.
di.provide(SessionFactory)

You can also drive this explicitly via di.ensure_core_providers(). Initialization is guarded by a lock and a one-time flag, so it is safe to call repeatedly. Register core providers at startup, before the first di.provide.

Public API

from voltwire.di.core import (
    component,                       # class decorator → register for auto-discovery (factory)
    singleton,                       # class decorator → register for auto-discovery (singleton)
    settings,                        # function decorator → register a singleton by return type
    auto_discover_components,        # scan a package and wire the container

    di,                               # di.provide / di.register / di.register_factory / di.register_singleton /
                                      # di.get_provider / di.provider_exists / di.register_core_provider /
                                      # di.ensure_core_providers

    dependency_container,            # the DependencyContainer singleton
    DependencyContainer,             # the container type
    get_component_registry,          # introspection: everything @component/@singleton/@settings collected
    analyze_component_dependencies,  # introspection: a class's required constructor deps
    is_singleton_component,          # introspection: was this class marked @singleton?

    providers,                       # re-export of dependency_injector.providers (Object/Factory/Singleton/Callable)
    containers,                      # re-export of dependency_injector.containers
)

providers and containers are re-exported so consumers can build custom providers (e.g. providers.Object(instance)) without importing dependency-injector directly — this library is the single DI surface.

FastAPI

The framework intentionally does not import FastAPI. To expose a component to routes, write the small glue in your app:

from typing import Annotated
from fastapi import Depends, Request
from voltwire.di.core import dependency_container

# Make the container available on app.state at startup:
#   application.state.provide = dependency_container().provide

def build(request: Request) -> GreetingService:
    return request.app.state.provide(GreetingService)

GreetingServiceDI = Annotated[GreetingService, Depends(build)]

Download files

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

Source Distribution

voltwire_di_core-0.0.2.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

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

voltwire_di_core-0.0.2-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file voltwire_di_core-0.0.2.tar.gz.

File metadata

  • Download URL: voltwire_di_core-0.0.2.tar.gz
  • Upload date:
  • Size: 10.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voltwire_di_core-0.0.2.tar.gz
Algorithm Hash digest
SHA256 d19215db24bfabf050fb3ddd4a7eec7de659f5a748300018d2ecd116e6b8ca08
MD5 275fb5423d5d1cf107443a8ca7123f6e
BLAKE2b-256 b08a9efa2087416423e629c8363c4eb2fe1d2b2b9d63c46a515f51f897ea1fc8

See more details on using hashes here.

File details

Details for the file voltwire_di_core-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: voltwire_di_core-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for voltwire_di_core-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9bcb7a12db408d3f250e6bb320d9edad78d09add54b088b888a193275088a965
MD5 ec37f4472515ea366330112cfe9b8941
BLAKE2b-256 50239c46270b4656f36dc304604696ea3065eb23b91b5d5c876e44222c0d8835

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page