Skip to main content

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.

Metadata

Release files for af-di-core 0.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for af-di-core 0.0.3
File Size Uploaded Provenance
af_di_core-0.0.3.tar.gz 7.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for af-di-core 0.0.3
File Interpreter ABI Platform
af_di_core-0.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 15.9 kB

Release files / af_di_core-0.0.3.tar.gz

Download URL af_di_core-0.0.3.tar.gz
Size 7.0 kB
Tags Source
SHA-256 checksum
How to use checksums
4a80df561dcddd30cd537801b0b0d0ef7430454dbec50674c710c674ac052672
BLAKE2b-256 checksum
How to use checksums
f14c33e1f06e0ca9ffb5a49fba73f032fdddbf5293b629ae8b781a197c9f02c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 30, 2026.

Transparency log

Release files / af_di_core-0.0.3-py3-none-any.whl

Download URL af_di_core-0.0.3-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dc812ca2b100cb9388ef15677291dee5f1bab8a4013ce28aa0ce898b379a43d6
BLAKE2b-256 checksum
How to use checksums
314982c4138e4b237b37bbf99aeba0559defc97e768a4d5d5f4c979a5b333aec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 30, 2026.

Transparency log

Security for af-di-core 0.0.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for af_di_core-0.0.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.3 This release

2 release files Full provenance

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