Skip to main content

voltwire-di-core

A small, framework-agnostic dependency-injection framework for Python. 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.

Built on dependency-injector for the underlying provider machinery.

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)]

Logging

The library emits to per-module logging.getLogger(__name__) loggers under voltwire.di.core.* using Python's standard logging module (mostly at debug). Registration/resolution failures log at error. To activate debug output:

import logging
logging.getLogger("voltwire.di.core").setLevel(logging.DEBUG)

If your app uses loguru, intercept stdlib logging once at startup:

import logging
from loguru import logger

class InterceptHandler(logging.Handler):
    def emit(self, record: logging.LogRecord) -> None:
        logger.opt(depth=6, exception=record.exc_info).log(
            record.levelname, record.getMessage()
        )

logging.getLogger("voltwire.di.core").addHandler(InterceptHandler())

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.1.tar.gz (10.8 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.1-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: voltwire_di_core-0.0.1.tar.gz
  • Upload date:
  • Size: 10.8 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.1.tar.gz
Algorithm Hash digest
SHA256 c9bd0e039a1f1062308a5619089648c479f7a007f17cb9ef351613d4906f7224
MD5 a47a0675406785ade4bacfd328b452ca
BLAKE2b-256 b02cb75b8327ab974e62f5e1746a548e206896ccc355c486e6c3457a1d50616b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: voltwire_di_core-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 9.4 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ed8bfadd72f9ff5977d2f5b880532b33b9c36ca487495a264dae7636e1343ebb
MD5 a44d056678eb1823d6d0c7114380754a
BLAKE2b-256 9166912730e56db3b8b4e82566aa6ad85d8b861cf339097d1164016b04deab30

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.2

2 files

This release

0.0.1 This release

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