Scopes
AnyDI has three built-in scopes:
transient- Creates new instance every timesingleton- Creates one instance for entire applicationrequest- Creates one instance per request context
You can also create custom scopes for your specific needs.
transient scope
Transient providers create a new instance every time you request it.
Example
import uuid
from anydi import Container
class RequestTracker:
def __init__(self) -> None:
self.request_id = str(uuid.uuid4())
container = Container()
@container.provider(scope="transient")
def request_tracker() -> RequestTracker:
return RequestTracker()
# Each resolve creates a new instance with a different request ID
tracker1 = container.resolve(RequestTracker)
tracker2 = container.resolve(RequestTracker)
assert tracker1.request_id != tracker2.request_id
singleton scope
Singleton providers create one instance and return the same instance every time.
Example
from anydi import Container
class Service:
def __init__(self, name: str) -> None:
self.name = name
container = Container()
@container.provider(scope="singleton")
def service() -> Service:
return Service(name="demo")
assert container.resolve(Service) == container.resolve(Service)
request scope
Request providers create one instance for each request. You can only use the instance inside the request context.
Example
from anydi import Container
class Request:
def __init__(self, path: str) -> None:
self.path = path
container = Container()
@container.provider(scope="request")
def request_provider() -> Request:
return Request(path="/")
with container.request_context():
assert container.resolve(Request).path == "/"
container.resolve(Request) # this will raise LookupError
or using asynchronous request context:
from anydi import Container
container = Container()
@container.provider(scope="request")
def request_provider() -> Request:
return Request(path="/")
async def main() -> None:
async with container.arequest_context():
assert (await container.aresolve(Request).path) == "/"
request scoped instances
You can create request-scoped instances for dependencies that need to be created per request. This is useful when you have request-specific data that should be separate for each request.
To create a request context, use the request_context method (or arequest_context for async). Then you can resolve dependencies for that request.
Using from_context for external dependencies
When a scoped provider depends on a value that will be provided at runtime via context.set(), register the type with from_context=True:
from typing import Annotated
from anydi import Container
class Request:
def __init__(self, param: str) -> None:
self.param = param
class UserContext:
def __init__(self, user_id: str, tenant_id: str) -> None:
self.user_id = user_id
self.tenant_id = tenant_id
container = Container()
# Register Request as a from_context dependency
container.register(Request, scope="request", from_context=True)
@container.provider(scope="request")
def user_context(request: Request) -> Annotated[UserContext, "current_user"]:
return UserContext(user_id=request.param, tenant_id="tenant-1")
with container.request_context() as ctx:
ctx.set(Request, Request(param="user-456"))
user = container.resolve(Annotated[UserContext, "current_user"])
assert user.user_id == "user-456"
assert user.tenant_id == "tenant-1"
The from_context=True option tells AnyDI that:
- The
Requesttype will be provided viacontext.set()at runtime - No factory function is needed - instances are set directly in the context
- A
LookupErrorwill be raised if the value is not set before resolution
This makes the dependency explicit and type-safe. The from_context option can only be used with scoped contexts (like request), not with singleton or transient scopes.
Custom Scopes
You can create custom scopes for your application. Custom scopes are useful when you need to manage dependencies differently from the standard scopes.
How to register custom scopes
Use the register_scope method:
from anydi import Container
container = Container()
# Register a custom scope without parent scopes
container.register_scope("task")
# Register a custom scope with parent scopes
container.register_scope("workflow", parents=["task"])
Scope hierarchy
Custom scopes can have parent-child relationships. A scope can only use dependencies from:
- Itself
- singleton scope (always allowed)
- Its parent scopes
For example, if you have: workflow → task → singleton, then:
workflowproviders can useworkflow,task, andsingletondependenciestaskproviders can usetaskandsingletondependenciessingletonproviders can only usesingletondependenciestransientproviders can use any dependencies
How to use custom scopes
Custom scopes work like the built-in request scope:
from anydi import Container
class TaskContext:
def __init__(self, task_id: str) -> None:
self.task_id = task_id
class WorkflowEngine:
def __init__(self, task_context: TaskContext) -> None:
self.task_context = task_context
container = Container()
# Register custom scopes
container.register_scope("task")
container.register_scope("workflow", parents=["task"])
# Register providers with custom scopes
@container.provider(scope="task")
def task_context() -> TaskContext:
return TaskContext(task_id="task-123")
@container.provider(scope="workflow")
def workflow_engine(task_context: TaskContext) -> WorkflowEngine:
return WorkflowEngine(task_context)
# Use custom scoped context
with container.scoped_context("task"):
with container.scoped_context("workflow"):
engine = container.resolve(WorkflowEngine)
assert engine.task_context.task_id == "task-123"
Async Custom Scopes
Custom scopes also support async contexts:
async def process_workflow() -> None:
async with container.ascoped_context("task"):
async with container.ascoped_context("workflow"):
engine = await container.aresolve(WorkflowEngine)
# Process workflow...
Best practices
- Clear hierarchies: Structure scopes to match your application logic (e.g.,
request→transaction→batch) - Avoid deep nesting: Keep hierarchies simple for better performance
- Use clear names: Choose names that show the scope purpose (
task,session,tenant, etc.) - Validate dependencies: Container automatically checks that dependencies follow the hierarchy rules
Common Use Cases
Multi-tenancy
container.register_scope("tenant")
@container.provider(scope="tenant")
def tenant_db() -> TenantDatabase:
return TenantDatabase()
Background jobs
container.register_scope("job")
@container.provider(scope="job")
def job_context() -> JobContext:
return JobContext()
User sessions
container.register_scope("session")
@container.provider(scope="session")
def session_data() -> SessionData:
return SessionData()