Skip to content

Factories

Factories are providers that create instances of dependencies.

Parameters

When creating a Factory provider, you can configure several parameters:

scope

Defines the lifecycle of the dependency. Available scopes are: - Scope.APP - Tied to the entire application lifetime (default) - Scope.SESSION - For websocket session lifetime - Scope.REQUEST - For dependencies created for each user request - Scope.ACTION - For lifetime less than request - Scope.STEP - For lifetime less than ACTION

Providers can have dependencies only of the same or more long-lived scopes.

creator

The callable (function or class) that will be invoked to create instances of the dependency. Modern-DI analyzes the creator's signature to: 1. Determine the return type (used for bound_type if not explicitly set) 2. Identify parameter names and types for automatic dependency resolution

bound_type

Explicitly sets the type for resolving by type. By default, this is automatically inferred from the creator's return type annotation. Set to None to make the provider unresolvable by type.

kwargs

Manual values for creator parameters that override automatic dependency resolution. Use this to provide specific values for parameters or override automatically resolved dependencies.

cache_settings

Configuration for caching instances. Only applicable for cached factories. Use providers.CacheSettings() to enable caching with optional cleanup configuration.

Union type parameters

When a parameter is annotated with a union type (e.g. dep: A | B), Modern-DI resolves the first registered type that matches. The order is determined by how types appear in the union left-to-right. If you rely on a specific type being injected, prefer a concrete type annotation over a union.

skip_creator_parsing

Disables automatic dependency resolution. When True: - No automatic dependency resolution occurs - All parameters must be provided via the kwargs parameter - The bound_type will not be automatically inferred from the creator's return type; unless bound_type is explicitly provided, it defaults to None

Types of factories

There are two types of factories:

  1. Regular Factories - Create a new instance on every call
  2. Cached Factories - Create an instance once and cache it for future calls

Regular Factories

Regular factories are initialized on every call.

import dataclasses

from modern_di import Group, Container, Scope, providers


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class IndependentFactory:
    dep1: str
    dep2: int


class Dependencies(Group):
    independent_factory = providers.Factory(
        scope=Scope.APP,
        creator=IndependentFactory,
        kwargs={"dep1": "text", "dep2": 123}
    )


container = Container(groups=[Dependencies])
# Resolve by provider reference
instance = container.resolve_provider(Dependencies.independent_factory)
assert isinstance(instance, IndependentFactory)

# Resolve by type (uses the return type of the creator function/class)
instance2 = container.resolve(IndependentFactory)
assert isinstance(instance2, IndependentFactory)

Cached Factories

Cached factories resolve the dependency only once and cache the resolved instance for future injections.

The caching mechanism is thread-safe by default, ensuring that even when multiple threads attempt to resolve the same cached factory simultaneously, only one instance will be created.

If your application is single-threaded, you can disable the lock for a small performance gain:

container = Container(groups=[Dependencies], use_lock=False)

Do not set use_lock=False in multi-threaded applications — it removes the guarantee that only one instance is created per cached factory.

import random

from modern_di import Group, Container, Scope, providers


def generate_random_number() -> float:
    return random.random()


class Dependencies(Group):
    singleton = providers.Factory(
        scope=Scope.APP,
        creator=generate_random_number,
        cache_settings=providers.CacheSettings()
    )


container = Container(groups=[Dependencies])
singleton_instance1 = container.resolve_provider(Dependencies.singleton)
singleton_instance2 = container.resolve_provider(Dependencies.singleton)

# If resolved in the same container, the instance will be the same
assert singleton_instance1 is singleton_instance2

Cache Settings

You can customize caching behavior with CacheSettings:

import contextlib

from modern_di import Group, Scope, providers


class SomeResource:
    def close(self) -> None: ...


def create_resource() -> SomeResource:
    # Create and return resource
    return SomeResource()


class Dependencies(Group):
    # Cache with cleanup — clear_cache=True (the default) ensures the closed
    # resource is evicted from cache so it cannot be returned again after close
    resource = providers.Factory(
        scope=Scope.APP,
        creator=create_resource,
        cache_settings=providers.CacheSettings(
            finalizer=lambda res: res.close(),  # Cleanup function
        )
    )