Registering and invoking services
The first examples are standalone programs for learning service lifetimes. In a generated application, register the same bindings in a ServiceProvider.register() method and let controllers or commands request dependencies through type annotations. The provider guide shows where to save and register that code.
Quick start
Section titled “Quick start”Register a contract and let constructor annotations build the graph:
import asynciofrom abc import ABC, abstractmethod
from orionis.container.container import Container
class Greeter(ABC): @abstractmethod def greet(self, name: str) -> str: raise NotImplementedError
class EnglishGreeter(Greeter): def greet(self, name: str) -> str: return f"Hello, {name}!"
class WelcomeService: def __init__(self, greeter: Greeter) -> None: self.greeter = greeter
class DocsContainer(Container): pass
async def main() -> None: container = DocsContainer() container.singleton(Greeter, EnglishGreeter) service = await container.build(WelcomeService) print(service.greeter.greet("Orionis")) # Hello, Orionis!
asyncio.run(main())The unbound WelcomeService is auto-built; its annotated Greeter dependency resolves through the singleton binding.
Core concepts
Section titled “Core concepts”Contract, concrete, alias, and binding
Section titled “Contract, concrete, alias, and binding”A binding relates a contract to a compatible concrete class and a Lifetime. The contract defaults to the concrete class when abstract=None. An optional non-empty alias resolves to that same contract. Registration refuses duplicates unless override=True.
Service lifetimes
Section titled “Service lifetimes”TRANSIENT: construct on every resolution.SINGLETON: construct once per container instance and reuse globally.SCOPED: construct once in the active scope and discard when the scope closes.instance: publish an already initialized object globally, or into the active scope when called inside one.
Automatic resolution and invocation
Section titled “Automatic resolution and invocation”The container caches reflection plans, combines supplied positional/keyword values with annotated dependencies/defaults, recursively builds class parameters, and awaits returned awaitables. Orionis schema and HTTP request parameters receive specialized handling during callable injection.
Scope propagation
Section titled “Scope propagation”ScopeManager places itself in a ContextVar. Child asyncio tasks inherit the context reference, but the manager marks itself closed and clears instances on exit, preventing inherited tasks from resolving stale scoped services afterward.
Facades
Section titled “Facades”A Facade class maps attribute access to a container accessor. Providers may pin a resolved singleton for direct calls. ScopedFacade never uses the global pinned cache; it reads the service already present in the active scope.
Examples
Section titled “Examples”Compare transient and singleton lifetimes
Section titled “Compare transient and singleton lifetimes”import asyncio
from orionis.container.container import Container
class Service: pass
class LifetimeContainer(Container): pass
async def main() -> None: container = LifetimeContainer() container.transient(None, Service) first = await container.make(Service) second = await container.make(Service) print(first is second) # False
container.singleton(None, Service, override=True) first = await container.make(Service) second = await container.make(Service) print(first is second) # True
asyncio.run(main())Inject a function parameter
Section titled “Inject a function parameter”import asyncio
from orionis.container.container import Container
class Formatter: def format(self, value: int) -> str: return f"value={value}"
def render(value: int, formatter: Formatter) -> str: return formatter.format(value)
class InvokeContainer(Container): pass
async def main() -> None: container = InvokeContainer() container.singleton(None, Formatter) print(await container.invoke(render, 7)) # value=7
asyncio.run(main())Reject a circular dependency
Section titled “Reject a circular dependency”import asyncio
from orionis.container.container import Containerfrom orionis.container.exceptions import CircularDependencyException
class First: def __init__(self, second: "Second") -> None: self.second = second
class Second: def __init__(self, first: First) -> None: self.first = first
# Resolve the one forward reference after both classes exist.First.__init__.__annotations__["second"] = Second
class CycleContainer(Container): pass
async def main() -> None: try: await CycleContainer().build(First) except CircularDependencyException: print("cycle detected")
asyncio.run(main())