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
)

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.2.tar.gz (6.9 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.2-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: af_di_core-0.0.2.tar.gz
  • Upload date:
  • Size: 6.9 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.2.tar.gz
Algorithm Hash digest
SHA256 129a04fc25c98b657c81c0dc8a000f27e6df5d8aa02f3f3244be3639ebf81532
MD5 98bfac1f7aa43066039eb061e88a8a41
BLAKE2b-256 78d7af10e6c341fb0015e126f6d0ad31fa4d91ec397dfcf52db3618ed66017c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for af_di_core-0.0.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: af_di_core-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 8.7 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5c7d8d04d540736d00ff71f11f90bd6fce5ccd40e2bc9cc008fc28697893548b
MD5 9969984939600be449a4e4b1e05f9237
BLAKE2b-256 43708d8aa304f92240fbfd2b89bbd5fbb1c6437e8b8a350fcc3cafd5d2278e23

See more details on using hashes here.

Provenance

The following attestation bundles were made for af_di_core-0.0.2-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