Skip to content

Lifetimes & Scopes

Wireup controls how long service instances live and when they're shared through lifetimes and scopes.

Service Lifetimes

Configure how long service instances live using the lifetime parameter in the @service decorator.

Singleton (Default)

One instance is created and shared across the entire application:

@service  # lifetime="singleton" is the default
class Database:
    def __init__(self): ...

# Same instance everywhere
db1 = container.get(Database)  # Instance created
db2 = container.get(Database)  # Reuses instance
assert db1 is db2  # True

Scoped

One instance per scope, shared within that scope:

@service(lifetime="scoped")
class RequestContext:
    def __init__(self):
        self.request_id = uuid.uuid4()

with container.enter_scope() as scope1:
    ctx1 = scope1.get(RequestContext)
    ctx2 = scope1.get(RequestContext)
    assert ctx1 is ctx2  # Same instance within scope

with container.enter_scope() as scope2:
    ctx3 = scope2.get(RequestContext)
    assert ctx1 is not ctx3  # Different instance in different scope

Transient

Creates a new instance on every resolution:

@service(lifetime="transient")
class MessageBuilder:
    def __init__(self):
        self.timestamp = time.time()

with container.enter_scope() as scope:
    builder1 = scope.get(MessageBuilder)
    builder2 = scope.get(MessageBuilder)
    assert builder1 is not builder2  # Always different instances

Scope Required

Only singletons may be resolved using the base container instance. Scoped and Transient dependencies must be resolved within a scope to ensure proper cleanup of resources.

Singleton dependencies will be cleaned up when the container's .close() method is called.

Lifetime Summary

Lifetime Instance Creation Shared Within Best For
Singleton Once per container Entire application Configuration, database connections, caching
Scoped Once per scope Current scope only Request state, transactions, user sessions
Transient Every resolution Never shared Stateless services, temporary objects

Working with Scopes

Scopes provide isolated dependency contexts, particularly useful for web applications where you want fresh instances per request.

Creating Scopes

container = wireup.create_sync_container(services=[RequestService])

with container.enter_scope() as scoped_container:
    service1 = scoped_container.get(RequestService)
    service2 = scoped_container.get(RequestService)
    # service1 and service2 are the same instance (if scoped lifetime)

with container.enter_scope() as another_scope:
    service3 = another_scope.get(RequestService)
    # service3 is a different instance from service1/service2
container = wireup.create_async_container(services=[RequestService])

async with container.enter_scope() as scoped_container:
    service1 = await scoped_container.get(RequestService)
    service2 = await scoped_container.get(RequestService)

Automatic Scope Management

Web Framework Integrations:

The provided integrations automatically create a scope for every request.

@app.get("/users/me")
def get_current_user(auth: Injected[AuthService]):
    return auth.current_user()  # Fresh scoped services per request

Function Decorator:

The @wireup.inject_from_container decorator will also enter a new scope if none is provided in the parameters.

@wireup.inject_from_container(container)
def process_order(order_service: OrderService):
    # Scope automatically created and cleaned up
    return order_service.process()

Resource Cleanup

Scoped containers automatically clean up resources when the scope exits:

@service(lifetime="scoped")
def database_session() -> Iterator[Session]:
    session = Session()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

with container.enter_scope() as scope:
    session = scope.get(Session)
    # session.close() is automatically called when exiting this "with" block

When using generator factories with scoped lifetime, errors that occur anywhere within the scope are automatically propagated to the factories for proper error handling like rolling back database transactions.

Dependency Rules & Choosing Lifetimes

Services have restrictions on what they can depend on based on their lifetime:

  • Singletons can only depend on other singletons and parameters
  • Scoped services can depend on singletons, other scoped services, and parameters
  • Transient services can depend on any lifetime and parameters