-
-
Notifications
You must be signed in to change notification settings - Fork 343
Description
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