Skip to content

.required() configs don't re-evaluate after a singleton reset #954

@fredtcaroli

Description

@fredtcaroli

Bug Description

When using .required() on a configuration option within a Singleton provider definition, the configuration value appears to be captured/evaluated at container initialization time rather than being lazily evaluated when the singleton is instantiated. This prevents configuration overrides from taking effect even after resetting the singleton.

Expected Behavior

Configuration values should be resolved lazily when the singleton is instantiated, allowing overrides to work correctly after calling .reset() on the singleton provider. This should work the same whether or not .required() is used.

Actual Behavior

When .required() is used on a config option in a Singleton definition, the config value is captured at definition time. Subsequent overrides and resets do not affect the singleton - it continues to use the originally captured value.

Minimal Reproducible Example

from dependency_injector import containers, providers


class Database:
    def __init__(self, path: str):
        self.path = path
        print(f"Database created with path: {path}")


class Service:
    def __init__(self, db: Database):
        self.db = db


class ContainerWithRequired(containers.DeclarativeContainer):
    config = providers.Configuration()
    
    # Using .required() - BROKEN BEHAVIOR
    database = providers.Singleton(
        Database,
        path=config.db.path.required(),
    )
    
    service = providers.Singleton(
        Service,
        db=database,
    )


class ContainerWithoutRequired(containers.DeclarativeContainer):
    config = providers.Configuration()
    
    # Without .required() - WORKS AS EXPECTED
    database = providers.Singleton(
        Database,
        path=config.db.path,
    )
    
    service = providers.Singleton(
        Service,
        db=database,
    )


if __name__ == "__main__":
    print("=" * 70)
    print("EXAMPLE 1: WITH .required() - BROKEN BEHAVIOR")
    print("=" * 70)
    
    container1 = ContainerWithRequired()
    container1.config.db.path.from_value("/initial/path")
    
    print("\n1. Creating service with initial config:")
    service1 = container1.service()
    print(f"   Service database path: {service1.db.path}")
    
    print("\n2. Resetting singleton and overriding config:")
    with container1.config.db.path.override("/overridden/path"):
        container1.database.reset()
        container1.service.reset()
        service2 = container1.service()
        print(f"   Service database path: {service2.db.path}")
    
    print("\n" + "=" * 70)
    print("EXAMPLE 2: WITHOUT .required() - WORKS CORRECTLY")
    print("=" * 70)
    
    container2 = ContainerWithoutRequired()
    container2.config.db.path.from_value("/initial/path")
    
    print("\n1. Creating service with initial config:")
    service3 = container2.service()
    print(f"   Service database path: {service3.db.path}")
    
    print("\n2. Resetting singleton and overriding config:")
    with container2.config.db.path.override("/overridden/path"):
        container2.database.reset()
        container2.service.reset()
        service4 = container2.service()
        print(f"   Service database path: {service4.db.path}")

The following output is produced:

======================================================================
EXAMPLE 1: WITH .required() - BROKEN BEHAVIOR
======================================================================

1. Creating service with initial config:
Database created with path: /initial/path
   Service database path: /initial/path

2. Resetting singleton and overriding config:
Database created with path: /initial/path
   Service database path: /initial/path

======================================================================
EXAMPLE 2: WITHOUT .required() - WORKS CORRECTLY
======================================================================

1. Creating service with initial config:
Database created with path: /initial/path
   Service database path: /initial/path

2. Resetting singleton and overriding config:
Database created with path: /overridden/path
   Service database path: /overridden/path

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions