Observed since 20 Oct, 21:02 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Reusable framework-agnostic DI container & @component auto-discovery
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.
pip install af-di-core
# or with Poetry:
poetry add af-di-core
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.
auto_discover_components(base_package, registrars=None) runs in three steps:
@settings functions are registered first as singletons (see below).registrars — optional callbacks for manual singletons that need special construction (e.g. third-party clients) — are invoked.@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])
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.
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.
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.
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)]
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.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.0.3 | 30 Jun 2026 | 2 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:02 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:70b50ea69276244164b2b3af453b8ad95aa64bae8c1c78503025838751e553b8