Errors¶
Circular dependencies¶
Demonstrates how diwire detects and reports circular dependencies.
from dataclasses import dataclass
from diwire import Container
from diwire.exceptions import DIWireCircularDependencyError
# Circular dependency: ServiceA -> ServiceB -> ServiceA
@dataclass
class ServiceA:
"""Service that depends on ServiceB."""
b: "ServiceB"
@dataclass
class ServiceB:
"""Service that depends on ServiceA (creating a cycle)."""
a: ServiceA
def main() -> None:
# Disable compilation to ensure we hit the runtime resolution path that reports
# DIWireCircularDependencyError (instead of recursing during compilation).
container = Container(auto_compile=False)
container.register(ServiceA)
container.register(ServiceB)
print("Attempting to resolve circular dependency chain:")
print(" ServiceA -> ServiceB -> ServiceA")
print()
try:
container.resolve(ServiceA)
except DIWireCircularDependencyError as e:
print("DIWireCircularDependencyError caught!")
print(f" Service key: {e.service_key}")
print(f" Resolution chain: {' -> '.join(str(k) for k in e.resolution_chain)}")
# How to avoid: use factories, lazy loading, or restructure dependencies
print("\nSolutions to avoid circular dependencies:")
print(" 1. Restructure code to break the cycle")
print(" 2. Use a factory to lazily create one of the services")
print(" 3. Introduce an interface/protocol to invert the dependency")
if __name__ == "__main__":
main()
Missing dependencies¶
Demonstrates DIWireMissingDependenciesError when a required dependency cannot be resolved.
from dataclasses import dataclass
from diwire import Container
from diwire.exceptions import DIWireMissingDependenciesError
class ExternalAPI:
"""An external API client that requires configuration."""
def __init__(self, api_key: str) -> None:
self.api_key = api_key
@dataclass
class UserService:
"""Service that depends on ExternalAPI."""
api: ExternalAPI
def main() -> None:
# Disable auto-registration to see DIWireMissingDependenciesError
container = Container(autoregister=False)
# Only register UserService, not ExternalAPI
container.register(UserService)
print("Attempting to resolve service with missing dependency:")
print(" UserService requires ExternalAPI, but ExternalAPI is not registered")
print()
try:
container.resolve(UserService)
except DIWireMissingDependenciesError as e:
print("DIWireMissingDependenciesError caught!")
print(f" Service key: {e.service_key}")
print(f" Missing dependencies: {e.missing}")
# With autoregister=True (default), simple classes would be auto-registered
print("\nWith autoregister=True (default):")
container_auto = Container(autoregister=True)
container_auto.register(UserService)
# Note: ExternalAPI still fails because it has a non-injectable 'api_key' param
try:
container_auto.resolve(UserService)
except DIWireMissingDependenciesError as e:
print(f" Still fails: {e.missing}")
print(" (ExternalAPI has 'api_key: str' which cannot be auto-resolved)")
if __name__ == "__main__":
main()
Scope mismatch¶
Demonstrates DIWireScopeMismatchError when trying to resolve from an exited scope.
from enum import Enum
from diwire import Container, Lifetime
from diwire.exceptions import DIWireScopeMismatchError
class Scope(str, Enum):
"""Application scope definitions."""
REQUEST = "request"
class RequestSession:
"""Session that must be resolved within a REQUEST scope."""
def main() -> None:
container = Container()
# Register session as SCOPED for REQUEST scope
container.register(
RequestSession,
lifetime=Lifetime.SCOPED,
scope=Scope.REQUEST,
)
# Trying to use a scope after it has exited
print("Scenario: Using a scope reference after it has exited\n")
scope_ref = None
with container.enter_scope(Scope.REQUEST) as scope:
# Save reference to scope
scope_ref = scope
session = scope.resolve(RequestSession)
print(f"Inside scope: resolved {session}")
# Now scope has exited but we try to use the saved reference
print("\nAttempting to resolve from exited scope:")
try:
scope_ref.resolve(RequestSession)
except DIWireScopeMismatchError as e:
print(" DIWireScopeMismatchError caught!")
print(f" Service: {e.service_key}")
print(f" Registered scope: {e.registered_scope}")
print(f" Current scope: {e.current_scope}")
# Correct usage - always use scopes within their context manager
print("\nCorrect usage - resolve within active scope context:")
with container.enter_scope(Scope.REQUEST) as scope:
session = scope.resolve(RequestSession)
print(f" Successfully resolved: {session}")
if __name__ == "__main__":
main()
Scoped resolved outside scope (auto-register safety)¶
Demonstrates DIWireScopeMismatchError when:
a service is registered only as
SCOPEDyou try to resolve it outside the required scope
This prevents the container from silently auto-registering a second, unscoped instance.
from dataclasses import dataclass
from diwire import Container, Lifetime
from diwire.exceptions import DIWireScopeMismatchError
@dataclass
class Session:
active: bool = True
def main() -> None:
container = Container(autoregister=True)
container.register(Session, lifetime=Lifetime.SCOPED, scope="request")
print("Resolving a SCOPED service outside its scope:\n")
try:
container.resolve(Session)
except DIWireScopeMismatchError as e:
print("DIWireScopeMismatchError caught!")
print(f" service: {e.service_key}")
print(f" registered_scope: {e.registered_scope}")
print(f" current_scope: {e.current_scope}")
print("\nResolving inside the correct scope:")
with container.enter_scope("request") as scope:
session = scope.resolve(Session)
print(f" session.active={session.active}")
if __name__ == "__main__":
main()