Skip to main content

Reusable framework-agnostic DI container & @component auto-discovery

Project description

af-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 af-di-core
# or with Poetry:
poetry add af-di-core

Quickstart

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

import attrs
from allfly.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 allfly.di.core import auto_discover_components, di_provide

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.

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 allfly.di.core import auto_discover_components, di_register_singleton


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 allfly.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)

af-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 allfly.di.core import register_core_provider, di_register, di_provide


def register_db_providers() -> None:
    di_register(DatabaseSettings, providers.Object(get_db_settings()))
    di_register_singleton(SessionFactory, settings=get_provider(DatabaseSettings))


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 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 allfly.di.core import (
    component,                       # class decorator → register for auto-discovery
    settings,                        # function decorator → register a singleton by return type
    auto_discover_components,        # scan a package and wire the container

    di_provide,                      # resolve an instance by type
    di_register,                     # register a raw dependency-injector provider
    di_register_factory,             # register a Factory provider
    di_register_singleton,           # register a Singleton provider
    get_provider,                    # get the provider (not the instance) for chaining
    provider_exists,                 # check whether a type is registered

    register_core_provider,          # add an app-supplied core provider callable
    ensure_core_providers,           # run core providers once (called lazily by di_provide)

    get_global_container,            # the GlobalDependencyContainer singleton
    dependency_container,            # alias for the same singleton
    GlobalDependencyContainer,       # the container type
    get_component_registry,          # introspection: everything @component/@settings collected
    analyze_component_dependencies,  # introspection: a class's required constructor deps

    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 allfly.di.core import get_global_container

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

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

GreetingServiceDI = Annotated[GreetingService, Depends(build)]

Logging

The library logs through loguru (mostly at trace/debug). If your app does not configure loguru, these messages are simply silent by default at higher levels.

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

af_di_core-0.0.3.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

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

af_di_core-0.0.3-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

Details for the file af_di_core-0.0.3.tar.gz.

File metadata

  • Download URL: af_di_core-0.0.3.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for af_di_core-0.0.3.tar.gz
Algorithm Hash digest
SHA256 4a80df561dcddd30cd537801b0b0d0ef7430454dbec50674c710c674ac052672
MD5 857b78d8d00c210d599cf3a3935fd75e
BLAKE2b-256 f14c33e1f06e0ca9ffb5a49fba73f032fdddbf5293b629ae8b781a197c9f02c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for af_di_core-0.0.3.tar.gz:

Publisher: af-di-core-publish.yml on travelallfly/allfly-py-libs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file af_di_core-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: af_di_core-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for af_di_core-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 dc812ca2b100cb9388ef15677291dee5f1bab8a4013ce28aa0ce898b379a43d6
MD5 20e58e87a2a18332a1dc8ffac4c7fbfe
BLAKE2b-256 314982c4138e4b237b37bbf99aeba0559defc97e768a4d5d5f4c979a5b333aec

See more details on using hashes here.

Provenance

The following attestation bundles were made for af_di_core-0.0.3-py3-none-any.whl:

Publisher: af-di-core-publish.yml on travelallfly/allfly-py-libs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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