diff --git a/pyproject.toml b/pyproject.toml index 4872af6c..e5614864 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,7 @@ isort.no-lines-before = ["standard-library", "local-folder"] addopts = "" testpaths = ["tests"] asyncio_mode = "auto" -filterwarnings = [] +filterwarnings = ["error"] pythonpath = ["."] asyncio_default_fixture_loop_scope = "function" diff --git a/tests/providers/test_alias.py b/tests/providers/test_alias.py index a6dbe7b0..87ffcdf4 100644 --- a/tests/providers/test_alias.py +++ b/tests/providers/test_alias.py @@ -28,7 +28,6 @@ class MyGroup(Group): def test_alias_delegates_to_source() -> None: container = Container(groups=[MyGroup]) - container.open() concrete = container.resolve(PostgresRepository) abstract = container.resolve(AbstractRepository) assert isinstance(abstract, PostgresRepository) @@ -41,7 +40,6 @@ class G(Group): abstract = providers.Alias(source_type=PostgresRepository, bound_type=AbstractRepository) container = Container(groups=[G]) - container.open() a = container.resolve(AbstractRepository) b = container.resolve(PostgresRepository) assert isinstance(a, PostgresRepository) @@ -55,19 +53,16 @@ class G(Group): abstract = providers.Alias(source_type=PostgresRepository, bound_type=AbstractRepository) app_container = Container(groups=[G]) - app_container.open() with pytest.raises(ScopeNotInitializedError): app_container.resolve(AbstractRepository) request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance = request_container.resolve(AbstractRepository) assert isinstance(instance, PostgresRepository) def test_alias_override_does_not_affect_source() -> None: container = Container(groups=[MyGroup]) - container.open() mock = PostgresRepository(dsn="mock-alias") container.override(MyGroup.abstract_repo, mock) @@ -77,7 +72,6 @@ def test_alias_override_does_not_affect_source() -> None: def test_source_override_propagates_through_alias() -> None: container = Container(groups=[MyGroup]) - container.open() mock = PostgresRepository(dsn="mock-source") container.override(MyGroup.repo, mock) @@ -90,7 +84,6 @@ class G(Group): abstract = providers.Alias(source_type=PostgresRepository, bound_type=AbstractRepository) container = Container(groups=[G]) - container.open() with pytest.raises(AliasSourceNotRegisteredError, match="PostgresRepository") as exc: container.resolve(AbstractRepository) assert exc.value.source_type is PostgresRepository @@ -102,7 +95,6 @@ class G(Group): abstract = providers.Alias(source_type=PostgresRepository, bound_type=AbstractRepository) container = Container(groups=[G]) - container.open() with pytest.raises(AliasSourceNotRegisteredError, match="PostgresRepository"): container.resolve_provider(G.abstract) @@ -191,7 +183,6 @@ def test_alias_of_alias_resolves_to_source_and_validates() -> None: # all alias sources registered, so B-5 validate aggregation is not in play container = Container(scope=Scope.APP, groups=[_ChainGroup]) container.validate() - container.open() impl = container.resolve(_ChainImpl) assert container.resolve(_ChainIfB) is impl assert container.resolve(_ChainIfA) is impl @@ -199,10 +190,8 @@ def test_alias_of_alias_resolves_to_source_and_validates() -> None: def test_alias_resolved_from_child_returns_app_cached_singleton() -> None: container = Container(scope=Scope.APP, groups=[_ChainGroup]) - container.open() app_instance = container.resolve(_ChainImpl) request = container.build_child_container(scope=Scope.REQUEST) - request.open() assert request.resolve(_ChainIfA) is app_instance @@ -222,10 +211,8 @@ class _AliasScopeGroup(Group): def test_validate_does_not_flag_alias_whose_scope_is_shallower_than_source() -> None: app = Container(scope=Scope.APP, groups=[_AliasScopeGroup]) - app.open() app.validate() # must NOT raise for the alias->impl edge request = app.build_child_container(scope=Scope.REQUEST) - request.open() assert isinstance(request.resolve(_ShallowIface), _DeepImpl) # resolution works @@ -250,7 +237,6 @@ class _AliasChainErrGroup(Group): def test_alias_appears_in_resolution_error_chain() -> None: container = Container(scope=Scope.APP, groups=[_AliasChainErrGroup]) - container.open() with pytest.raises(exceptions.ArgumentResolutionError) as exc_info: container.resolve(_AliasTargetIface) rendered = str(exc_info.value) @@ -272,7 +258,6 @@ class _NullBoundAliasGroup(Group): def test_alias_null_bound_type_resolution_error_uses_repr_fallback() -> None: container = Container(scope=Scope.APP, groups=[_NullBoundAliasGroup]) - container.open() with pytest.raises(exceptions.ArgumentResolutionError) as exc_info: container.resolve_provider(_NullBoundAliasGroup.iface) rendered = str(exc_info.value) @@ -408,7 +393,6 @@ class G(Group): abstract = providers.Alias(PostgresRepository, bound_type=AbstractRepository) container = Container(groups=[G]) - container.open() assert isinstance(container.resolve(AbstractRepository), PostgresRepository) @@ -504,7 +488,6 @@ def test_alias_on_a_closed_container_raises() -> None: # The alias hop itself carries no closed-container check; the entry `resolve` raises, # exactly as it does for a context provider or `container_provider`. container = Container(groups=[MyGroup]) - container.open() container.resolve(AbstractRepository) container.close_sync() @@ -525,7 +508,6 @@ class G(Group): iface = providers.Alias(source_type=Late, bound_type=LateIface) container = Container(groups=[G]) - container.open() with pytest.raises(AliasSourceNotRegisteredError): container.resolve(LateIface) @@ -549,7 +531,6 @@ class G(Group): parent = providers.Factory(creator=Parent) container = Container(groups=[G]) - container.open() with pytest.raises(AliasSourceNotRegisteredError): container.resolve(Parent) @@ -572,7 +553,6 @@ class G(Group): second = providers.Alias(source_type=First, bound_type=Second) container = Container(groups=[G]) - container.open() # Asserted via `match=` rather than after the block: a RecursionError tears down the # trace function, so below 3.12 -- where coverage traces instead of using diff --git a/tests/providers/test_singleton.py b/tests/providers/test_cached_factory.py similarity index 79% rename from tests/providers/test_singleton.py rename to tests/providers/test_cached_factory.py index 0eafda25..aacca6b0 100644 --- a/tests/providers/test_singleton.py +++ b/tests/providers/test_cached_factory.py @@ -1,8 +1,7 @@ import asyncio import dataclasses import threading -import time -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import ThreadPoolExecutor import pytest @@ -26,45 +25,44 @@ async def async_finalizer(_: DependentCreator) -> None: class MyGroup(Group): - app_singleton = providers.Factory( + app_cached = providers.Factory( creator=SimpleCreator, kwargs={"dep1": "original"}, cache=True, ) - request_singleton = providers.Factory( + request_cached = providers.Factory( scope=Scope.REQUEST, creator=DependentCreator, cache=providers.CacheSettings(finalizer=async_finalizer) ) -async def test_app_singleton() -> None: +async def test_app_cached_factory() -> None: sync_calls: list[SimpleCreator] = [] class LocalGroup(Group): - singleton = providers.Factory( + cached = providers.Factory( creator=SimpleCreator, kwargs={"dep1": "original"}, cache=providers.CacheSettings(clear_cache=False, finalizer=sync_calls.append), ) app_container = Container(groups=[LocalGroup]) - app_container.open() - singleton1 = app_container.resolve_provider(LocalGroup.singleton) - singleton2 = app_container.resolve_provider(LocalGroup.singleton) - assert singleton1 is singleton2 + instance1 = app_container.resolve_provider(LocalGroup.cached) + instance2 = app_container.resolve_provider(LocalGroup.cached) + assert instance1 is instance2 app_container.close_sync() - assert sync_calls == [singleton1] # finalizer ran once on close + assert sync_calls == [instance1] # finalizer ran once on close # clear_cache=False: the instance survives the close and is returned again once the container # is reopened, without re-running the creator or finalizer. with pytest.raises(ContainerClosedError): - app_container.resolve_provider(LocalGroup.singleton) + app_container.resolve_provider(LocalGroup.cached) app_container.open() - assert app_container.resolve_provider(LocalGroup.singleton) is singleton1 - assert sync_calls == [singleton1] # finalizer did not re-fire + assert app_container.resolve_provider(LocalGroup.cached) is instance1 + assert sync_calls == [instance1] # finalizer did not re-fire await app_container.close_async() - assert sync_calls == [singleton1] + assert sync_calls == [instance1] def test_close_does_not_re_finalize_with_clear_cache_false() -> None: @@ -78,7 +76,6 @@ class G(Group): ) container = Container(groups=[G]) - container.open() container.resolve(str) container.close_sync() container.close_sync() @@ -86,30 +83,6 @@ class G(Group): assert calls == ["r"] -def test_closed_container_raises_then_rebuilds_after_open_with_clear_cache_true() -> None: - calls: list[str] = [] - - class G(Group): - f = providers.Factory( - creator=lambda: "r", - bound_type=str, - cache=providers.CacheSettings(clear_cache=True, finalizer=calls.append), - ) - - container = Container(groups=[G]) - container.open() - container.resolve(str) - container.close_sync() - assert calls == ["r"] - with pytest.raises(ContainerClosedError): - container.resolve(str) - assert calls == ["r"] - container.open() - container.resolve(str) # rebuilds, since clear_cache=True cleared the cache on close - container.close_sync() - assert calls == ["r", "r"] # the rebuilt instance is finalized again on this second close - - async def test_close_async_runs_sync_finalizer() -> None: calls: list[str] = [] @@ -121,30 +94,26 @@ class G(Group): ) container = Container(groups=[G]) - container.open() container.resolve(str) await container.close_async() assert calls == ["r"] -async def test_request_singleton() -> None: +async def test_request_cached_factory() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() - instance1 = request_container.resolve_provider(MyGroup.request_singleton) + instance1 = request_container.resolve_provider(MyGroup.request_cached) instance2 = request_container.resolve(DependentCreator) assert isinstance(instance1.dep1, SimpleCreator) assert instance1 is instance2 request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() - instance3 = request_container.resolve_provider(MyGroup.request_singleton) + instance3 = request_container.resolve_provider(MyGroup.request_cached) instance4 = request_container.resolve(DependentCreator) assert instance3 is instance4 assert instance1 is not instance3 - cache_item = request_container._cache_registry.fetch_cache_item(MyGroup.request_singleton) + cache_item = request_container._cache_registry.fetch_cache_item(MyGroup.request_cached) with pytest.raises(FinalizerError) as exc_info: request_container.close_sync() @@ -158,18 +127,15 @@ async def test_request_singleton() -> None: assert cache_item.cache is UNSET -def test_app_singleton_in_request_scope() -> None: +def test_app_cached_factory_resolves_once_across_request_children() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() - singleton1 = request_container.resolve_provider(MyGroup.app_singleton) + instance1 = request_container.resolve_provider(MyGroup.app_cached) request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() - singleton2 = request_container.resolve_provider(MyGroup.app_singleton) + instance2 = request_container.resolve_provider(MyGroup.app_cached) - assert singleton1 is singleton2 + assert instance1 is instance2 def test_sync_finalizer_exception_does_not_abort_remaining_cleanup() -> None: @@ -196,7 +162,6 @@ class BrokenGroup(Group): ) app_container = Container(groups=[BrokenGroup]) - app_container.open() app_container.resolve_provider(BrokenGroup.first) app_container.resolve_provider(BrokenGroup.second) @@ -232,7 +197,6 @@ class BrokenAsyncGroup(Group): ) app_container = Container(groups=[BrokenAsyncGroup]) - app_container.open() app_container.resolve_provider(BrokenAsyncGroup.first) app_container.resolve_provider(BrokenAsyncGroup.second) @@ -259,7 +223,6 @@ class _ValueErrorFinalizerGroup(Group): def test_finalizer_error_is_an_exception_group() -> None: container = Container(groups=[_ValueErrorFinalizerGroup]) - container.open() container.resolve_provider(_ValueErrorFinalizerGroup.failing) with pytest.raises(FinalizerError) as exc_info: @@ -279,7 +242,6 @@ def test_finalizer_error_is_an_exception_group() -> None: def test_except_star_catches_user_finalizer_error_from_close_sync() -> None: container = Container(groups=[_ValueErrorFinalizerGroup]) - container.open() container.resolve_provider(_ValueErrorFinalizerGroup.failing) caught: list[ExceptionGroup[ValueError]] = [] @@ -296,7 +258,6 @@ def test_except_star_catches_user_finalizer_error_from_close_sync() -> None: async def test_except_star_catches_user_finalizer_error_from_close_async() -> None: container = Container(groups=[_ValueErrorFinalizerGroup]) - container.open() container.resolve_provider(_ValueErrorFinalizerGroup.failing) caught: list[ExceptionGroup[ValueError]] = [] @@ -312,10 +273,8 @@ async def test_except_star_catches_user_finalizer_error_from_close_async() -> No async def test_except_star_catches_async_finalizer_in_sync_close() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() - request_container.resolve_provider(MyGroup.request_singleton) + request_container.resolve_provider(MyGroup.request_cached) caught: list[ExceptionGroup[AsyncFinalizerInSyncCloseError]] = [] try: @@ -358,7 +317,6 @@ class FalsyGroup(Group): ) app_container = Container(groups=[FalsyGroup]) - app_container.open() instance = app_container.resolve_provider(FalsyGroup.empty_dict) assert instance == {} @@ -379,7 +337,6 @@ class FalsyGroup(Group): ) app_container = Container(groups=[FalsyGroup]) - app_container.open() instance = app_container.resolve_provider(FalsyGroup.empty_list) assert instance == [] @@ -406,7 +363,6 @@ class NoneGroup(Group): ) app_container = Container(groups=[NoneGroup]) - app_container.open() app_container.resolve_provider(NoneGroup.none_resource) app_container.resolve_provider(NoneGroup.none_resource) @@ -417,36 +373,44 @@ class NoneGroup(Group): assert cleaned_up == [None] -@pytest.mark.repeat(10) -def test_singleton_threading_concurrency() -> None: - calls: int = 0 - lock = threading.Lock() +class _Gate: ... - def create_singleton() -> str: - nonlocal calls - with lock: - calls += 1 - time.sleep(0.01) - return "" - singleton = providers.Factory(creator=create_singleton, cache=True) +class _GatedValue: + def __init__(self, gate: _Gate) -> None: + self.gate = gate - def resolve_singleton(container: Container) -> str: - return container.resolve_provider(singleton) - app_container = Container() - app_container.open() - with ThreadPoolExecutor(max_workers=4) as pool: - tasks = [ - pool.submit(resolve_singleton, app_container), - pool.submit(resolve_singleton, app_container), - pool.submit(resolve_singleton, app_container), - pool.submit(resolve_singleton, app_container), - ] - results = [x.result() for x in as_completed(tasks)] +def test_concurrent_cache_misses_create_once() -> None: + """Threads that all miss the cache together still run the creator once. + + The barrier sits in a dependency, and dependencies resolve outside the lock, so every thread is + past the unlocked cache check before any of them creates. If the lock covered dependency + resolution, the barrier would time out and the test would fail instead of hanging. + """ + n = 4 + barrier = threading.Barrier(n, timeout=5) + created: list[_GatedValue] = [] + + def make_gate() -> _Gate: + barrier.wait() + return _Gate() + + def make_value(gate: _Gate) -> _GatedValue: + value = _GatedValue(gate) + created.append(value) + return value + + class G(Group): + gate = providers.Factory(creator=make_gate) + value = providers.Factory(creator=make_value, cache=True) + + container = Container(groups=[G]) + with ThreadPoolExecutor(max_workers=n) as pool: + results = list(pool.map(lambda _: container.resolve(_GatedValue), range(n))) - assert all(x == "" for x in results) - assert calls == 1 + assert len(created) == 1 + assert all(result is created[0] for result in results) _lifo_events: list[str] = [] @@ -486,14 +450,13 @@ class _LifoGroup(Group): def test_finalizers_run_in_reverse_creation_order_even_with_warmup() -> None: _lifo_events.clear() container = Container(scope=Scope.APP, groups=[_LifoGroup]) - container.open() container.resolve(_LifoLeaf) # the docs-recommended warmup pattern container.resolve(_LifoTop) container.close_sync() assert _lifo_events == ["top", "mid", "leaf"] -def test_singleton_resolution_is_reentrant() -> None: +def test_cached_resolution_is_reentrant() -> None: class Inner: pass @@ -506,7 +469,6 @@ class ReentrantGroup(Group): outer = providers.Factory(creator=Outer, cache=True) container = Container(groups=[ReentrantGroup]) - container.open() result: list[Outer] = [] # Use a daemon Thread (not ThreadPoolExecutor) so the worker can be abandoned @@ -519,7 +481,7 @@ def worker() -> None: thread.start() thread.join(timeout=5) - assert not thread.is_alive(), "container.resolve deadlocked — singleton lock is not re-entrant" + assert not thread.is_alive(), "container.resolve deadlocked: the cache lock is not re-entrant" assert len(result) == 1 assert isinstance(result[0], Outer) assert isinstance(result[0].inner, Inner) @@ -546,7 +508,6 @@ class _AwaitableFinGroup(Group): async def test_sync_finalizer_returning_awaitable_is_awaited_in_async_close() -> None: _awaitable_fin_events.clear() container = Container(scope=Scope.APP, groups=[_AwaitableFinGroup]) - container.open() container.resolve(_AwaitableFinSvc) await container.close_async() assert _awaitable_fin_events == ["cleaned"] @@ -555,7 +516,6 @@ async def test_sync_finalizer_returning_awaitable_is_awaited_in_async_close() -> async def test_sync_finalizer_returning_awaitable_raises_in_sync_close_then_recovers() -> None: _awaitable_fin_events.clear() container = Container(scope=Scope.APP, groups=[_AwaitableFinGroup]) - container.open() container.resolve(_AwaitableFinSvc) with pytest.raises(FinalizerError): container.close_sync() @@ -564,46 +524,26 @@ async def test_sync_finalizer_returning_awaitable_raises_in_sync_close_then_reco assert _awaitable_fin_events == ["cleaned"] -_cycle_events: list[str] = [] - - -class _PersistentBroker: ... - - -class _EphemeralSvc: ... - - -class _CycleGroup(Group): - broker = providers.Factory( - scope=Scope.APP, - creator=_PersistentBroker, - cache=providers.CacheSettings(clear_cache=False, finalizer=lambda _: _cycle_events.append("broker-finalized")), - ) - svc = providers.Factory( - scope=Scope.APP, - creator=_EphemeralSvc, - cache=True, # clear_cache=True default - ) +async def test_sync_finalizer_returning_a_future_raises_in_sync_close_then_recovers() -> None: + """A future is awaitable but is not a coroutine, so sync close rejects it without closing anything.""" + future: asyncio.Future[None] = asyncio.get_running_loop().create_future() + class G(Group): + svc = providers.Factory( + scope=Scope.APP, creator=_AwaitableFinSvc, cache=providers.CacheSettings(finalizer=lambda _: future) + ) -def test_persistent_provider_survives_close_reopen_cycle() -> None: - _cycle_events.clear() - container = Container(scope=Scope.APP, groups=[_CycleGroup]) - with container: - broker1 = container.resolve(_PersistentBroker) - svc1 = container.resolve(_EphemeralSvc) - # exited → closed; finalizer ran once - assert _cycle_events == ["broker-finalized"] - with pytest.raises(ContainerClosedError): - container.resolve(_PersistentBroker) - # re-enter → reopen - with container: - broker2 = container.resolve(_PersistentBroker) - svc2 = container.resolve(_EphemeralSvc) - assert broker2 is broker1 # persistent: same instance preserved - assert svc2 is not svc1 # ephemeral: rebuilt fresh - # finalizer did NOT re-fire for the preserved broker - assert _cycle_events == ["broker-finalized"] + container = Container(scope=Scope.APP, groups=[G]) + container.resolve(_AwaitableFinSvc) + with pytest.raises(FinalizerError) as exc: + container.close_sync() + (inner,) = exc.value.exceptions + assert isinstance(inner, AsyncFinalizerInSyncCloseError) + assert inner.finalizer_type is _AwaitableFinSvc + assert container._cache_registry.cached_count() == 1 + future.set_result(None) + await container.close_async() + assert container._cache_registry.cached_count() == 0 class _First: ... diff --git a/tests/providers/test_container_provider.py b/tests/providers/test_container_provider.py index b76d4336..146c7a8e 100644 --- a/tests/providers/test_container_provider.py +++ b/tests/providers/test_container_provider.py @@ -3,11 +3,9 @@ def test_container_provider_direct_resolving() -> None: app_container = Container() - app_container.open() assert app_container.resolve(Container) is app_container request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() assert request_container.resolve_provider(providers.container_provider) is request_container @@ -19,9 +17,7 @@ class MyGroup(Group): factory = providers.Factory(scope=Scope.REQUEST, creator=creator) app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance = request_container.resolve(Scope) assert instance == Scope.REQUEST @@ -31,6 +27,5 @@ def test_container_provider_override_direct() -> None: # An override of the container provider compiles to a constant resolver, so resolving it # directly returns the override rather than the resolving container. app_container = Container() - app_container.open() app_container.override(providers.container_provider, "mock-container") assert app_container.resolve_provider(providers.container_provider) == "mock-container" diff --git a/tests/providers/test_context_provider.py b/tests/providers/test_context_provider.py index 47538054..fb34012c 100644 --- a/tests/providers/test_context_provider.py +++ b/tests/providers/test_context_provider.py @@ -1,7 +1,6 @@ import dataclasses import datetime import typing -import warnings import pytest @@ -30,7 +29,6 @@ class MyGroup(Group): def test_context_provider() -> None: now = datetime.datetime.now(tz=datetime.UTC) app_container = Container(groups=[MyGroup], context={datetime.datetime: now}) - app_container.open() instance1 = app_container.resolve_provider(MyGroup.context_provider) instance2 = app_container.resolve_provider(MyGroup.context_provider) assert instance1 is instance2 is now @@ -39,7 +37,6 @@ def test_context_provider() -> None: def test_context_provider_set_context_after_creation() -> None: now = datetime.datetime.now(tz=datetime.UTC) app_container = Container() - app_container.open() app_container.set_context(datetime.datetime, now) instance1 = app_container.resolve_provider(MyGroup.context_provider) instance2 = app_container.resolve_provider(MyGroup.context_provider) @@ -48,7 +45,6 @@ def test_context_provider_set_context_after_creation() -> None: def test_context_provider_not_found() -> None: app_container = Container() - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(MyGroup.context_provider) assert exc_info.value.context_type is datetime.datetime @@ -56,7 +52,6 @@ def test_context_provider_not_found() -> None: def test_context_provider_not_found_but_required() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises( ContextValueNotSetError, match=r"No context value is set for \(scope APP\), needed for argument arg1", @@ -70,9 +65,7 @@ def test_context_provider_not_found_but_required() -> None: def test_context_provider_in_request_scope() -> None: now = datetime.datetime.now(tz=datetime.UTC) app_container = Container() - app_container.open() request_container = app_container.build_child_container(context={datetime.datetime: now}, scope=Scope.REQUEST) - request_container.open() instance1 = request_container.resolve_provider(request_context_provider) instance2 = request_container.resolve_provider(request_context_provider) assert instance1 is instance2 is now @@ -98,7 +91,6 @@ def test_context_provider_returns_falsy_values(value: object) -> None: context_type = type(value) provider = providers.ContextProvider(scope=Scope.APP, context_type=context_type) app_container = Container(context={context_type: value}) - app_container.open() assert app_container.resolve_provider(provider) == value @@ -112,7 +104,6 @@ class FlagGroup(Group): consumer = providers.Factory(creator=FlagConsumer) app_container = Container(groups=[FlagGroup], context={bool: False}) - app_container.open() instance = app_container.resolve(FlagConsumer) assert instance.flag is False @@ -127,7 +118,6 @@ class NoneGroup(Group): holder = providers.Factory(creator=NoneHolder) app_container = Container(groups=[NoneGroup], context={datetime.datetime: None}) - app_container.open() instance = app_container.resolve(NoneHolder) assert instance.value is None @@ -148,13 +138,11 @@ class TsDefaultGroup(Group): holder = providers.Factory(creator=TsHolder) app_container = Container(groups=[TsGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc: app_container.resolve(TsHolder) assert exc.value.parameter_name == "ts" defaulted = Container(groups=[TsDefaultGroup]) - defaulted.open() assert defaulted.resolve(TsHolder).ts == default @@ -178,7 +166,6 @@ def test_set_context_after_first_resolve_is_seen_by_later_resolves() -> None: later `set_context` invisible to non-cached factories across scopes. """ container = Container(scope=Scope.APP, groups=[_LateCtxGroup]) - container.open() first = container.resolve(_NeedsLateCtx) assert first.ctx is None # context unset, default applied value = _LateCtx() @@ -190,9 +177,7 @@ def test_set_context_after_first_resolve_is_seen_by_later_resolves() -> None: def test_context_provider_through_closed_owning_container_raises() -> None: now = datetime.datetime.now(tz=datetime.UTC) app = Container(groups=[MyGroup], context={datetime.datetime: now}) - app.open() child = app.build_child_container(scope=Scope.REQUEST) - child.open() app.close_sync() with pytest.raises(ContainerClosedError) as exc: child.resolve_provider(MyGroup.context_provider) @@ -215,9 +200,7 @@ class _ScopedCtxGroup(Group): def test_context_provider_reads_registry_at_its_own_scope_not_resolving_container() -> None: value = _ScopedCtx() app = Container(scope=Scope.APP, groups=[_ScopedCtxGroup]) - app.open() request = app.build_child_container(scope=Scope.REQUEST, context={_ScopedCtx: _ScopedCtx()}) - request.open() # context set on the CHILD must be invisible to an APP-scoped provider with pytest.raises(ContextValueNotSetError) as exc_info: request.resolve(_ScopedCtx) @@ -270,9 +253,7 @@ class _CrossRequiredGroup(Group): def test_late_app_context_seen_by_request_factory_defaulted_param() -> None: app = Container(scope=Scope.APP, groups=[_CrossDefaultGroup]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) - request.open() assert request.resolve(_CrossDefaultSvc).ctx is None # context unset at first resolve value = _CrossCtx() app.set_context(_CrossCtx, value) @@ -281,9 +262,7 @@ def test_late_app_context_seen_by_request_factory_defaulted_param() -> None: def test_late_app_context_seen_by_request_factory_nullable_param() -> None: app = Container(scope=Scope.APP, groups=[_CrossNullableGroup]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) - request.open() assert request.resolve(_CrossNullableSvc).ctx is None value = _CrossCtx() app.set_context(_CrossCtx, value) @@ -292,9 +271,7 @@ def test_late_app_context_seen_by_request_factory_nullable_param() -> None: def test_late_app_context_required_param_raises_then_resolves_across_scopes() -> None: app = Container(scope=Scope.APP, groups=[_CrossRequiredGroup]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) - request.open() with pytest.raises(ContextValueNotSetError) as exc: request.resolve(_CrossRequiredSvc) assert exc.value.parameter_name == "ctx" @@ -305,9 +282,7 @@ def test_late_app_context_required_param_raises_then_resolves_across_scopes() -> def test_override_of_context_param_applies_after_first_resolve_across_scopes() -> None: app = Container(scope=Scope.APP, groups=[_CrossDefaultGroup]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) - request.open() assert request.resolve(_CrossDefaultSvc).ctx is None override_value = _CrossCtx() app.override(_CrossDefaultGroup.ctx, override_value) @@ -331,7 +306,6 @@ def test_late_context_does_not_rebuild_cached_singleton() -> None: would turn `cache=True` into a per-resolve check. """ app = Container(scope=Scope.APP, groups=[_CachedCtxGroup]) - app.open() first = app.resolve(_CachedCtxSvc) assert first.ctx is None app.set_context(_CrossCtx, _CrossCtx()) @@ -343,7 +317,6 @@ def test_late_context_does_not_rebuild_cached_singleton() -> None: def test_cached_factory_injects_present_context_at_cold_build() -> None: # Context set before the first (cold) build is injected into the cached instance. app = Container(scope=Scope.APP, groups=[_CachedCtxGroup]) - app.open() ctx = _CrossCtx() app.set_context(_CrossCtx, ctx) svc = app.resolve(_CachedCtxSvc) @@ -352,26 +325,15 @@ def test_cached_factory_injects_present_context_at_cold_build() -> None: def test_direct_resolve_unset_context_raises() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(MyGroup.context_provider) assert exc_info.value.context_type is datetime.datetime -def test_set_context_provider_direct_resolve_does_not_warn() -> None: - now = datetime.datetime.now(tz=datetime.UTC) - app_container = Container(groups=[MyGroup], context={datetime.datetime: now}) - app_container.open() - with warnings.catch_warnings(): - warnings.simplefilter("error") - assert app_container.resolve_provider(MyGroup.context_provider) is now - - def test_context_provider_accepts_positional_context_type() -> None: provider = providers.ContextProvider(datetime.datetime) now = datetime.datetime.now(tz=datetime.UTC) app_container = Container(context={datetime.datetime: now}) - app_container.open() assert app_container.resolve_provider(provider) is now @@ -385,11 +347,8 @@ def test_context_provider_override_direct_short_circuits() -> None: # returns the override with no ContextValueNotSetError, even with nothing in the registry. override_value = datetime.datetime(2024, 1, 1, tzinfo=datetime.UTC) app_container = Container(groups=[MyGroup]) - app_container.open() app_container.override(MyGroup.context_provider, override_value) - with warnings.catch_warnings(): - warnings.simplefilter("error") - assert app_container.resolve_provider(MyGroup.context_provider) is override_value + assert app_container.resolve_provider(MyGroup.context_provider) is override_value _SENTINEL_DEFAULT = datetime.datetime(1999, 9, 9, tzinfo=datetime.UTC) @@ -416,13 +375,11 @@ class _KwargsCtxExplicitGroup(Group): def test_kwargs_context_provider_ignores_creator_default_when_unset() -> None: app_container = Container(groups=[_KwargsCtxExplicitGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc: app_container.resolve_provider(_KwargsCtxExplicitGroup.out) assert exc.value.parameter_name == "ctx" defaulted = Container(groups=[_KwargsCtxDefaultedGroup]) - defaulted.open() assert defaulted.resolve_provider(_KwargsCtxDefaultedGroup.out) == "default-applied" @@ -430,9 +387,7 @@ def test_kwargs_context_provider_matches_by_type_wiring() -> None: # The same creator wired both ways agrees: how the ContextProvider reaches the parameter is a # declaration detail, not a behavior switch. by_type = Container(groups=[_KwargsCtxByTypeGroup]) - by_type.open() explicit = Container(groups=[_KwargsCtxExplicitGroup]) - explicit.open() with pytest.raises(ContextValueNotSetError) as by_type_exc: by_type.resolve_provider(_KwargsCtxByTypeGroup.out) with pytest.raises(ContextValueNotSetError) as explicit_exc: @@ -443,14 +398,12 @@ def test_kwargs_context_provider_matches_by_type_wiring() -> None: def test_kwargs_context_provider_injects_present_value() -> None: now = datetime.datetime.now(tz=datetime.UTC) app_container = Container(groups=[_KwargsCtxExplicitGroup], context={datetime.datetime: now}) - app_container.open() assert app_container.resolve_provider(_KwargsCtxExplicitGroup.out) == f"got {now!r}" def test_kwargs_context_provider_override_wins() -> None: override_value = datetime.datetime(2024, 1, 1, tzinfo=datetime.UTC) app_container = Container(groups=[_KwargsCtxExplicitGroup]) - app_container.open() app_container.override(_KwargsCtxExplicitGroup.ctx, override_value) assert app_container.resolve_provider(_KwargsCtxExplicitGroup.out) == f"got {override_value!r}" @@ -466,7 +419,6 @@ class _KwargsCtxNoSignatureGroup(Group): def test_kwargs_context_provider_without_parsed_signature_keeps_direct_resolve() -> None: app_container = Container(groups=[_KwargsCtxNoSignatureGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(_KwargsCtxNoSignatureGroup.out) assert exc_info.value.context_type is datetime.datetime @@ -478,7 +430,6 @@ def test_kwargs_context_provider_without_parsed_signature_injects_present_value( # returns it normally and the creator runs. now = datetime.datetime.now(tz=datetime.UTC) app_container = Container(groups=[_KwargsCtxNoSignatureGroup], context={datetime.datetime: now}) - app_container.open() assert app_container.resolve_provider(_KwargsCtxNoSignatureGroup.out) == f"ctx={now!r}" @@ -502,7 +453,6 @@ class G(Group): svc = providers.Factory(creator=Svc, scope=Scope.APP, cache=cache) container = Container(scope=Scope.APP, groups=[G]) - container.open() with pytest.raises(ScopeNotInitializedError) as exc: container.resolve(Svc) @@ -529,7 +479,6 @@ class G(Group): svc = providers.Factory(creator=Svc, scope=Scope.REQUEST) app = Container(scope=Scope.APP, groups=[G]) - app.open() request = app.build_child_container(scope=Scope.REQUEST, context={Cfg: Cfg()}) calls: list[object] = [] @@ -551,7 +500,6 @@ class G2(Group): wider = providers.Factory(creator=Wider, scope=Scope.REQUEST) app2 = Container(scope=Scope.APP, groups=[G2], context={AppCfg: AppCfg()}) - app2.open() request2 = app2.build_child_container(scope=Scope.REQUEST) calls.clear() @@ -592,7 +540,6 @@ class G(Group): svc = providers.Factory(creator=_CachedNullable, scope=Scope.APP, cache=True) container = Container(scope=Scope.APP, groups=[G]) - container.open() sentinel = _CachedCtx() container.override(G.ctx, sentinel) assert container.resolve(_CachedNullable).ctx is sentinel @@ -604,7 +551,6 @@ class G(Group): svc = providers.Factory(creator=_CachedNullable, scope=Scope.APP) container = Container(scope=Scope.APP, groups=[G]) - container.open() sentinel = _CachedCtx() container.override(G.ctx, sentinel) assert container.resolve(_CachedNullable).ctx is sentinel @@ -620,11 +566,9 @@ class Required(Group): svc = providers.Factory(creator=_CachedNullable, scope=Scope.APP, cache=True) container = Container(scope=Scope.APP, groups=[G]) - container.open() assert container.resolve(_CachedNullable).ctx is None required = Container(scope=Scope.APP, groups=[Required]) - required.open() with pytest.raises(ContextValueNotSetError) as exc: required.resolve(_CachedNullable) assert exc.value.parameter_name == "ctx" @@ -636,7 +580,6 @@ class G(Group): svc = providers.Factory(creator=_CachedRequired, scope=Scope.APP, cache=True) container = Container(scope=Scope.APP, groups=[G]) - container.open() with pytest.raises(ContextValueNotSetError) as exc: container.resolve(_CachedRequired) assert exc.value.parameter_name == "ctx" @@ -649,7 +592,6 @@ class G(Group): value = _CachedCtx() app = Container(scope=Scope.APP, groups=[G], context={_CachedCtx: value}) - app.open() request = app.build_child_container(scope=Scope.REQUEST) app.close_sync() @@ -664,7 +606,6 @@ class G(Group): value = _CachedCtx() app = Container(scope=Scope.APP, groups=[G], context={_CachedCtx: value}) - app.open() request = app.build_child_container(scope=Scope.REQUEST) app.close_sync() @@ -686,7 +627,6 @@ class G(Group): cfg = providers.ContextProvider(Cfg, scope=Scope.REQUEST) app = Container(scope=Scope.APP, groups=[G]) - app.open() request = app.build_child_container(scope=Scope.REQUEST, context={Cfg: Cfg()}) assert isinstance(request.resolve(Cfg), Cfg) # compile the resolver @@ -709,7 +649,6 @@ def counting_scope(self: providers.ContextProvider[object]) -> object: def test_unset_context_as_factory_argument_raises_naming_the_parameter() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve(SomeFactory) assert exc_info.value.context_type is datetime.datetime @@ -719,7 +658,6 @@ def test_unset_context_as_factory_argument_raises_naming_the_parameter() -> None def test_direct_resolve_of_unset_context_names_no_parameter() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(MyGroup.context_provider) assert exc_info.value.parameter_name is None @@ -733,7 +671,6 @@ def test_direct_resolve_of_unset_context_names_no_parameter() -> None: def test_context_provider_default_is_returned_when_unset(default: datetime.datetime | None) -> None: provider = providers.ContextProvider(datetime.datetime, scope=Scope.APP, default=default) app_container = Container() - app_container.open() assert app_container.resolve_provider(provider) is default @@ -742,7 +679,6 @@ def test_context_provider_default_yields_to_a_set_value(default: datetime.dateti now = datetime.datetime.now(tz=datetime.UTC) provider = providers.ContextProvider(datetime.datetime, scope=Scope.APP, default=default) app_container = Container(context={datetime.datetime: now}) - app_container.open() assert app_container.resolve_provider(provider) is now @@ -757,7 +693,6 @@ class G(Group): holder = providers.Factory(creator=Holder) app_container = Container(groups=[G]) - app_container.open() assert app_container.resolve(Holder).ts is default now = datetime.datetime.now(tz=datetime.UTC) app_container.set_context(datetime.datetime, now) @@ -785,7 +720,6 @@ class G(Group): outer = providers.Factory(creator=_NamedOuter, cache=cache) app_container = Container(groups=[G]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve(_NamedOuter) assert exc_info.value.parameter_name == "named" @@ -806,7 +740,6 @@ class G(Group): holder = providers.Factory(creator=Holder) app_container = Container(groups=[G]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve(Holder) assert exc_info.value.parameter_name == "via_alias" @@ -823,7 +756,6 @@ class G(Group): out = providers.Factory(creator, bound_type=None, kwargs={"optional": optional}) app_container = Container(groups=[G]) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(G.out) assert exc_info.value.parameter_name == "required" @@ -918,7 +850,6 @@ class G(Group): pair = providers.Factory(creator, bound_type=None, cache=cache) app_container = Container(groups=[G], context={_FirstCtx: _FirstCtx()}) - app_container.open() with pytest.raises(ContextValueNotSetError) as exc_info: app_container.resolve_provider(G.pair) assert exc_info.value.context_type is _SecondCtx @@ -1028,7 +959,6 @@ class _LeakGroup(Group): def test_set_context_on_child_does_not_leak_to_sibling_or_caller_dict() -> None: app = Container(scope=Scope.APP, groups=[_LeakGroup]) - app.open() shared = {_SharedReq: _SharedReq()} first = app.build_child_container(scope=Scope.REQUEST, context=shared) second = app.build_child_container(scope=Scope.REQUEST, context=shared) diff --git a/tests/providers/test_factory.py b/tests/providers/test_factory.py index ccc4eecd..9836cb67 100644 --- a/tests/providers/test_factory.py +++ b/tests/providers/test_factory.py @@ -50,7 +50,6 @@ class MyGroup(Group): def test_app_factory() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() instance1 = app_container.resolve_provider(MyGroup.app_factory) instance2 = app_container.resolve(dependency_type=SimpleCreator) assert isinstance(instance1, SimpleCreator) @@ -60,7 +59,6 @@ def test_app_factory() -> None: def test_app_factory_skip_creator_parsing() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises( exceptions.CreatorCallError, match=re.escape("SimpleCreator.__init__() missing 1 required keyword-only argument: 'dep1'"), @@ -70,7 +68,6 @@ def test_app_factory_skip_creator_parsing() -> None: def test_app_factory_unresolvable() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises(ArgumentResolutionError, match="Argument dep1 of type cannot be resolved") as exc: app_container.resolve_provider(MyGroup.app_factory_unresolvable) assert exc.value.parameter_name == "dep1" @@ -79,7 +76,6 @@ def test_app_factory_unresolvable() -> None: def test_func_with_union_factory() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() instance1 = app_container.resolve_provider(MyGroup.func_with_union_factory) assert instance1 == str(SimpleCreator(dep1="original")) @@ -89,7 +85,6 @@ def test_func_with_broken_annotation() -> None: factory = providers.Factory(creator=func_with_broken_annotation, bound_type=None) app_container = Container() - app_container.open() app_container._providers_registry.add_providers(factory) with pytest.raises(ArgumentResolutionError, match="has no usable type annotation"): app_container.resolve_provider(factory) @@ -97,9 +92,7 @@ def test_func_with_broken_annotation() -> None: def test_request_factory() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() request_container.resolve_provider(MyGroup.request_factory) instance1 = request_container.resolve_provider(MyGroup.request_factory) instance2 = request_container.resolve_provider(MyGroup.request_factory) @@ -107,7 +100,6 @@ def test_request_factory() -> None: assert instance1 is not instance2 request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance3 = request_container.resolve_provider(MyGroup.request_factory) instance4 = request_container.resolve_provider(MyGroup.request_factory) assert instance3 is not instance4 @@ -117,9 +109,7 @@ def test_request_factory() -> None: def test_request_factory_with_di_container() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance1 = request_container.resolve_provider(MyGroup.request_factory_with_di_container) instance2 = request_container.resolve_provider(MyGroup.request_factory_with_di_container) assert instance1 is not instance2 @@ -128,7 +118,6 @@ def test_request_factory_with_di_container() -> None: assert instance1.di_container is instance2.di_container request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance3 = request_container.resolve_provider(MyGroup.request_factory_with_di_container) instance4 = request_container.resolve_provider(MyGroup.request_factory_with_di_container) assert instance3 is not instance4 @@ -138,7 +127,6 @@ def test_request_factory_with_di_container() -> None: def test_factory_overridden_app_scope() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() instance1 = app_container.resolve_provider(MyGroup.app_factory) app_container.override(MyGroup.app_factory, SimpleCreator(dep1="override")) @@ -160,16 +148,13 @@ def test_factory_overridden_app_scope() -> None: def test_factory_overridden_request_scope() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() app_container.override(MyGroup.request_factory, DependentCreator(dep1=SimpleCreator(dep1="override"))) request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance1 = request_container.resolve(DependentCreator) request_container.close_sync() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() instance2 = request_container.resolve(DependentCreator) assert instance1 is instance2 assert instance2.dep1.dep1 == instance1.dep1.dep1 == "override" @@ -187,7 +172,6 @@ def test_override_bypasses_scope_check_from_shallower_container() -> None: # provider resolves from whichever container was asked -- the short-circuit fires # before find_container. app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises(ScopeNotInitializedError): app_container.resolve_provider(MyGroup.request_factory) @@ -198,7 +182,6 @@ def test_override_bypasses_scope_check_from_shallower_container() -> None: def test_factory_scope_is_not_initialized() -> None: app_container = Container(groups=[MyGroup]) - app_container.open() with pytest.raises( ScopeNotInitializedError, match=r"Provider of scope REQUEST cannot be resolved in container of scope APP.", @@ -216,7 +199,6 @@ def second_creator(first_factory: str) -> str: second_factory = providers.Factory(creator=second_creator, kwargs={"first_factory": first_factory}) app_container = Container() - app_container.open() app_container._providers_registry.add_providers(first_factory, second_factory) assert app_container.resolve_provider(second_factory) == "one two" @@ -232,7 +214,6 @@ def make(x: int | SelfRef = 1) -> SelfRef: factory = providers.Factory(creator=make) app_container = Container() - app_container.open() app_container._providers_registry.add_providers(factory) result = app_container.resolve(SelfRef) @@ -250,7 +231,6 @@ def make(nested: SelfRefByType = SelfRefByType()) -> SelfRefByType: # noqa: B00 factory = providers.Factory(creator=make) app_container = Container() - app_container.open() app_container._providers_registry.add_providers(factory) # `nested` is typed as the factory's own bound type: it must not wire to itself, @@ -329,7 +309,6 @@ def make(**kwargs: object) -> dict[str, object]: factory = providers.Factory(creator=make, kwargs={"anything": 1, "extra": 2}) container = Container() - container.open() container._providers_registry.add_providers(factory) result = container.resolve(dict) assert result == {"anything": 1, "extra": 2} @@ -344,7 +323,6 @@ def make(x: SomeUnregisteredType = unittest.mock.ANY) -> str: factory = providers.Factory(creator=make) container = Container() - container.open() container._providers_registry.add_providers(factory) result = container.resolve(str) assert result == repr(unittest.mock.ANY) @@ -362,7 +340,6 @@ def test_unannotated_param_error_explains_missing_annotation() -> None: sentinel = object() assert _unannotated_creator(sentinel) is sentinel # exercise body for coverage container = Container(scope=Scope.APP, groups=[_UnannotatedGroup]) - container.open() with pytest.raises(ArgumentResolutionError, match="has no usable type annotation"): container.resolve(object) @@ -385,7 +362,6 @@ def test_union_param_error_names_the_union_members() -> None: dep = _UnionDep1() assert _union_creator(dep) == str(dep) # exercise body for coverage container = Container(scope=Scope.APP, groups=[_UnionGroup]) - container.open() with pytest.raises(ArgumentResolutionError, match=r"_UnionDep1 \| _UnionDep2") as exc: container.resolve(str) assert exc.value.parameter_type is None @@ -417,7 +393,6 @@ class _PrecedenceGroup(Group): def test_static_kwargs_win_over_type_matched_provider() -> None: container = Container(scope=Scope.APP, groups=[_PrecedenceGroup]) - container.open() svc = container.resolve(_PrecedenceSvc) assert svc.dep is _static_dep assert svc.dep.label == "from-kwargs" @@ -459,7 +434,6 @@ def test_creator_raising_mid_creation_caches_nothing_and_retry_succeeds() -> Non _flaky_state["raised"] = False _flaky_events.clear() container = Container(scope=Scope.APP, groups=[_FlakyGroup]) - container.open() with pytest.raises(RuntimeError, match="boom"): container.resolve(_FlakySvc) expected_cached_after_failure = 1 # only the dep cached; failed svc not cached @@ -489,7 +463,6 @@ class _MemoGroup(Group): real_build = wiring_mod.WiringPlan.build with unittest.mock.patch.object(wiring_mod.WiringPlan, "build", autospec=True, side_effect=real_build) as build_spy: app_container = Container(scope=Scope.APP, groups=[_MemoGroup]) - app_container.open() for _ in range(50): with app_container.build_child_container(scope=Scope.REQUEST) as request_container: request_container.resolve(DependentCreator) @@ -519,8 +492,6 @@ class WithoutLeaf(Group): with_leaf = Container(scope=Scope.APP, groups=[WithLeaf]) without_leaf = Container(scope=Scope.APP, groups=[WithoutLeaf]) - with_leaf.open() - without_leaf.open() assert with_leaf.find_provider(OptionalDepSvc) is svc_factory assert without_leaf.find_provider(OptionalDepSvc) is svc_factory @@ -550,7 +521,6 @@ def __init__(self, dep: "_OptionalDep | _OtherDep | None") -> None: def test_optional_param_injects_none_when_no_provider() -> None: factory: providers.Factory[_NeedsOptionalSingle] = providers.Factory(creator=_NeedsOptionalSingle, scope=Scope.APP) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(_NeedsOptionalSingle, factory) obj = container.resolve(_NeedsOptionalSingle) assert obj.dep is None @@ -560,7 +530,6 @@ def test_optional_param_uses_provider_when_present() -> None: dep_factory: providers.Factory[_OptionalDep] = providers.Factory(creator=_OptionalDep, scope=Scope.APP) factory: providers.Factory[_NeedsOptionalSingle] = providers.Factory(creator=_NeedsOptionalSingle, scope=Scope.APP) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(_OptionalDep, dep_factory) container._providers_registry.register(_NeedsOptionalSingle, factory) obj = container.resolve(_NeedsOptionalSingle) @@ -570,7 +539,6 @@ def test_optional_param_uses_provider_when_present() -> None: def test_optional_multi_member_union_injects_none_when_no_provider() -> None: factory: providers.Factory[_NeedsOptionalUnion] = providers.Factory(creator=_NeedsOptionalUnion, scope=Scope.APP) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(_NeedsOptionalUnion, factory) obj = container.resolve(_NeedsOptionalUnion) assert obj.dep is None @@ -600,7 +568,6 @@ def test_optional_param_backed_by_unset_context_provider_raises() -> None: ) factory: providers.Factory[_NeedsOptionalCtx] = providers.Factory(creator=_NeedsOptionalCtx, scope=Scope.APP) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(_OptionalCtx, ctx_provider) container._providers_registry.register(_NeedsOptionalCtx, factory) with pytest.raises(exceptions.ContextValueNotSetError) as exc: @@ -627,7 +594,6 @@ def test_skip_creator_parsing_missing_args_raises_di_error() -> None: creator=_needs_two_args, bound_type=int, skip_creator_parsing=True, kwargs={"a": 1} ) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(int, factory) with pytest.raises(exceptions.CreatorCallError) as exc_info: container.resolve(int) @@ -645,7 +611,6 @@ def test_skip_creator_parsing_missing_args_cached_raises_di_error() -> None: cache=True, ) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(int, factory) with pytest.raises(exceptions.CreatorCallError) as exc_info: container.resolve(int) @@ -665,7 +630,6 @@ def test_internal_typeerror_from_creator_body_is_not_wrapped() -> None: creator=_InternalTypeErrorService, bound_type=_InternalTypeErrorService, skip_creator_parsing=True ) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(_InternalTypeErrorService, factory) with pytest.raises(TypeError) as exc_info: container.resolve(_InternalTypeErrorService) @@ -694,7 +658,6 @@ def test_repeated_failing_resolve_breadcrumb_does_not_compound() -> None: """ factory: providers.Factory[_NeedsUnregistered] = providers.Factory(creator=_NeedsUnregistered, scope=Scope.APP) container = Container(scope=Scope.APP) # exercise resolve-time breadcrumb, not validation - container.open() container._providers_registry.register(_NeedsUnregistered, factory) def _grab() -> str: @@ -732,7 +695,6 @@ def __init__(self, leaf: _Leaf2) -> None: leaf2: providers.Factory[_Leaf2] = providers.Factory(creator=_Leaf2, scope=Scope.APP) parent2: providers.Factory[_Parent2] = providers.Factory(creator=_Parent2, scope=Scope.APP) c2 = Container(scope=Scope.APP) # exercise resolve-time breadcrumb, not validation - c2.open() c2._providers_registry.register(_Leaf2, leaf2) c2._providers_registry.register(_Parent2, parent2) @@ -755,7 +717,6 @@ class G(Group): f = providers.Factory(creator=SimpleCreator, kwargs={"dep1": "x"}, cache=True) container = Container(groups=[G]) - container.open() assert container.resolve_provider(G.f) is container.resolve_provider(G.f) assert isinstance(G.f.cache_settings, providers.CacheSettings) @@ -768,7 +729,6 @@ class G(Group): f = providers.Factory(creator=SimpleCreator, cache=True, bound_type=None) container = Container(groups=[G]) - container.open() with pytest.raises(ArgumentResolutionError, match="Argument dep1 of type cannot be resolved") as exc: container.resolve_provider(G.f) assert exc.value.parameter_name == "dep1" @@ -784,7 +744,6 @@ class G(Group): f = providers.Factory(creator=SimpleCreator, kwargs={"dep1": "x"}) container = Container(groups=[G]) - container.open() assert container.resolve_provider(G.f) is not container.resolve_provider(G.f) assert G.f.cache_settings is None @@ -794,7 +753,6 @@ class G(Group): f = providers.Factory(creator=SimpleCreator, kwargs={"dep1": "x"}, cache=False) container = Container(groups=[G]) - container.open() assert container.resolve_provider(G.f) is not container.resolve_provider(G.f) assert G.f.cache_settings is None @@ -806,7 +764,6 @@ class G(Group): f = providers.Factory(creator=dict, cache=providers.CacheSettings(finalizer=cleaned.append)) container = Container(groups=[G]) - container.open() instance = container.resolve_provider(G.f) assert container.resolve_provider(G.f) is instance container.close_sync() @@ -828,7 +785,6 @@ class G(Group): factory = providers.Factory(SimpleCreator, kwargs={"dep1": "positional"}) container = Container(groups=[G]) - container.open() instance = container.resolve(SimpleCreator) assert instance.dep1 == "positional" @@ -935,7 +891,7 @@ def __call__(self) -> int: def test_nonetype_param_with_default_uses_the_default() -> None: - """A `None`-annotated parameter with a default is OMIT, not UNWIRABLE.""" + """A `None`-annotated parameter with a default is omitted, so the creator gets its own default.""" class Svc: def __init__(self, hook: None = None) -> None: @@ -943,7 +899,6 @@ def __init__(self, hook: None = None) -> None: factory = providers.Factory(scope=Scope.APP, creator=Svc) container = Container() - container.open() container._providers_registry.add_providers(factory) result = container.resolve(Svc) @@ -959,7 +914,6 @@ def __init__(self, hook: None) -> None: factory = providers.Factory(scope=Scope.APP, creator=Svc) container = Container() - container.open() container._providers_registry.add_providers(factory) result = container.resolve(Svc) @@ -1009,16 +963,13 @@ class G(Group): thing = providers.Factory(creator=_cov_pos_only_creator, scope=Scope.APP) container = Container(groups=[G]) - container.open() assert container.resolve_provider(G.thing) == _CovPosOnlyResult(prefix="P", dep=_CovLeaf()) def _build_closed_app_and_request(*group: type[Group]) -> tuple[Container, Container]: """Return (closed APP container, open REQUEST child) sharing `group`'s providers.""" app = Container(scope=Scope.APP, groups=list(group)) - app.open() request = app.build_child_container(scope=Scope.REQUEST) - request.open() app.close_sync() return app, request @@ -1059,7 +1010,6 @@ class G(Group): thing = providers.Factory(creator=_CovKwOnlyBodyTypeError, scope=Scope.APP) container = Container(groups=[G]) - container.open() with pytest.raises(TypeError) as exc: container.resolve_provider(G.thing) assert not isinstance(exc.value, exceptions.CreatorCallError) @@ -1078,7 +1028,6 @@ class G(Group): thing = providers.Factory(creator=_CovPosNeedsReq, scope=Scope.APP, cache=True) container = Container(groups=[G]) - container.open() with pytest.raises(ScopeNotInitializedError): container.resolve_provider(G.thing) @@ -1107,7 +1056,6 @@ class G(Group): ) container = Container(groups=[G]) - container.open() with pytest.raises(exceptions.CreatorCallError): container.resolve_provider(G.thing) @@ -1130,7 +1078,6 @@ class G(Group): ) container = Container(groups=[G]) - container.open() with pytest.raises(TypeError) as exc: container.resolve_provider(G.thing) assert not isinstance(exc.value, exceptions.CreatorCallError) @@ -1149,7 +1096,6 @@ class G(Group): thing = providers.Factory(creator=_CovCachedNeedsReq, scope=Scope.APP, cache=True) container = Container(groups=[G]) - container.open() with pytest.raises(ScopeNotInitializedError): container.resolve_provider(G.thing) @@ -1161,7 +1107,6 @@ class G(Group): thing = providers.Factory(creator=SimpleCreator, bound_type=None) container = Container(groups=[G]) - container.open() mock = SimpleCreator(dep1="mock") container.override(G.thing, mock) assert container.resolve_provider(G.thing) is mock @@ -1186,7 +1131,6 @@ class G(Group): thing = providers.Factory(creator=_CovKwOnlyBodyTypeError, scope=Scope.APP, cache=True) container = Container(groups=[G]) - container.open() with pytest.raises(TypeError) as exc: container.resolve_provider(G.thing) assert not isinstance(exc.value, exceptions.CreatorCallError) @@ -1205,7 +1149,6 @@ class G(Group): ) container = Container(groups=[G]) - container.open() with pytest.raises(exceptions.CreatorCallError): container.resolve_provider(G.thing) diff --git a/tests/registries/test_providers_registry.py b/tests/registries/test_providers_registry.py index 59e33997..58cd1f54 100644 --- a/tests/registries/test_providers_registry.py +++ b/tests/registries/test_providers_registry.py @@ -170,7 +170,6 @@ class _G(Group): root = providers.Factory(creator=_Root, scope=Scope.APP) container = Container(groups=[_G]) - container.open() real_compile = pr_mod.compile_resolver entered = threading.Event() release = threading.Event() diff --git a/tests/test_container.py b/tests/test_container.py index f0dd3fd9..aedfdccc 100644 --- a/tests/test_container.py +++ b/tests/test_container.py @@ -2,8 +2,8 @@ import dataclasses import gc import inspect +import threading import typing -import warnings import weakref import pytest @@ -37,7 +37,6 @@ def test_container_prevent_copy() -> None: def test_container_scope_skipped() -> None: app_factory = providers.Factory(creator=lambda: "test") container = Container(scope=Scope.REQUEST) - container.open() with pytest.raises(ScopeSkippedError, match=r"No APP-scope container exists in this chain") as exc: container.resolve_provider(app_factory) assert exc.value.provider_scope == Scope.APP @@ -45,7 +44,6 @@ def test_container_scope_skipped() -> None: def test_container_build_child() -> None: app_container = Container() - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) assert request_container.scope == Scope.REQUEST assert app_container.scope == Scope.APP @@ -53,7 +51,6 @@ def test_container_build_child() -> None: def test_container_scope_limit_reached() -> None: step_container = Container(scope=Scope.STEP) - step_container.open() with pytest.raises(MaxScopeReachedError, match=r"Max scope of STEP is reached.") as exc: step_container.build_child_container() assert exc.value.parent_scope == Scope.STEP @@ -61,7 +58,6 @@ def test_container_scope_limit_reached() -> None: def test_container_build_child_wrong_scope() -> None: app_container = Container() - app_container.open() with pytest.raises(InvalidChildScopeError, match="Scope of child container cannot be") as exc: app_container.build_child_container(scope=Scope.APP) assert exc.value.parent_scope == Scope.APP @@ -119,7 +115,6 @@ class G(Group): def test_container_repr() -> None: container = Container() - container.open() assert repr(container) == "Container(scope=APP, parent=None, providers=1, cached=0)" request_container = container.build_child_container(scope=Scope.REQUEST) @@ -386,7 +381,7 @@ def test_validation_failed_error_str_renders_inner_errors() -> None: def test_constructor_rejects_use_lock() -> None: - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="unexpected keyword argument 'use_lock'"): Container(use_lock=False) # ty: ignore[unknown-argument] @@ -410,7 +405,6 @@ class G(Group): svc = providers.Factory(creator=Service) container = MyContainer(groups=[G]) - container.open() instance = container.resolve(Service) assert instance.di_container is container @@ -425,7 +419,7 @@ def test_constructor_takes_only_scope_positionally() -> None: class G(Group): name = providers.Factory(creator=lambda: "r", bound_type=str) - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="positional arguments"): Container(Scope.APP, None) # ty: ignore[too-many-positional-arguments] assert Container(Scope.APP).scope is Scope.APP @@ -435,7 +429,6 @@ class G(Group): def test_constructor_rejects_parent_with_non_increasing_scope() -> None: app = Container(scope=Scope.APP) - app.open() with pytest.raises(InvalidChildScopeError): Container(scope=Scope.APP, parent_container=app) request = app.build_child_container(scope=Scope.REQUEST) @@ -508,9 +501,7 @@ async def test_closed_container_async_path_raises_by_reference() -> None: def test_resolving_through_closed_parent_via_open_child_raises() -> None: app = Container(scope=Scope.APP, groups=[_AppBrokerGroup]) - app.open() child = app.build_child_container(scope=Scope.REQUEST) - child.open() app.close_sync() with pytest.raises(ContainerClosedError) as exc: child.resolve(_PersistentBroker) @@ -553,14 +544,24 @@ class G(Group): assert container._cache_registry.cached_count() == 0 -def test_explicit_open_after_close_does_not_warn() -> None: - container = Container(scope=Scope.APP) - container.open() +def test_reopen_rebuilds_a_value_that_close_cleared() -> None: + class Svc: ... + + finalized: list[Svc] = [] + + class G(Group): + svc = providers.Factory(creator=Svc, cache=providers.CacheSettings(finalizer=finalized.append)) + + container = Container(groups=[G]) + first = container.resolve(Svc) container.close_sync() - with warnings.catch_warnings(): - warnings.simplefilter("error") # a deliberate reopen is silent - container.open() - assert container.resolve(Container) is container + assert finalized == [first] + with pytest.raises(ContainerClosedError): + container.resolve(Svc) + with container: + second = container.resolve(Svc) + assert second is not first + assert finalized == [first, second] def test_child_built_off_closed_parent_raises_only_when_the_parent_resolves() -> None: @@ -571,7 +572,6 @@ def test_child_built_off_closed_parent_raises_only_when_the_parent_resolves() -> would break every integration that builds a request child after a shutdown/restart cycle. """ app = Container(scope=Scope.APP, groups=[_AppBrokerGroup]) - app.open() app.close_sync() child = app.build_child_container(scope=Scope.REQUEST) assert child.resolve(Container) is child @@ -603,22 +603,9 @@ def test_fresh_container_is_open() -> None: assert container.closed is False -def test_construct_then_close_then_reuse_raises_until_reopened() -> None: - container = Container(scope=Scope.APP) - container.close_sync() - with pytest.raises(ContainerClosedError): - container.resolve(Container) - with pytest.raises(ContainerClosedError): - container.resolve(Container) - container.open() - assert container.resolve(Container) is container - - def test_fresh_container_resolves_without_open() -> None: container = Container(scope=Scope.APP) - with warnings.catch_warnings(): - warnings.simplefilter("error") # a never-closed container must not warn - assert container.resolve(Container) is container + assert container.resolve(Container) is container assert container.closed is False @@ -629,26 +616,30 @@ def test_fresh_container_builds_child_and_child_resolves_without_open() -> None: assert app.closed is False # building a child does not close the parent -def test_build_child_off_closed_parent_is_allowed() -> None: - app = Container(scope=Scope.APP) - app.open() - app.close_sync() - child = app.build_child_container(scope=Scope.REQUEST) # no raise: builds nothing, resolves nothing - assert child.scope is Scope.REQUEST - +def test_warm_cached_resolve_does_not_wait_for_the_lock() -> None: + """A cached value resolves while another thread holds the container lock. -def test_private_lock_and_scope_map_back_the_machinery() -> None: + The lock is taken only on a cache miss. If a warm resolve took it too, the worker would block + until the join timeout and the test would fail instead of hanging. + """ + root = Container(scope=Scope.APP, groups=[_AppBrokerGroup]) + warm = root.resolve(_PersistentBroker) + child = root.build_child_container(scope=Scope.REQUEST) + results: list[_PersistentBroker] = [] + worker = threading.Thread(target=lambda: results.append(child.resolve(_PersistentBroker)), daemon=True) + with root._lock: + worker.start() + worker.join(timeout=5) + finished_while_held = not worker.is_alive() + worker.join(timeout=5) + assert finished_while_held + assert results[0] is warm + + +def test_private_scope_map_backs_find_container() -> None: root = Container() - root.open() child = root.build_child_container(scope=Scope.REQUEST) - # _lock is a reentrant lock (threading.RLock is a factory, not a type, so - # assert behavior, not isinstance) - assert root._lock is not None - assert root._lock.acquire() - assert root._lock.acquire() # reentrant - root._lock.release() - root._lock.release() # The map holds ancestors only — never the container itself, which would be a reference cycle. # `find_container` short-circuits on its own scope, so a self-entry would be dead weight. assert set(child._scope_map) == {Scope.APP} @@ -673,7 +664,6 @@ def _count(_: object) -> None: n_children = 100 root = Container(scope=Scope.APP) - root.open() gc.collect() was_enabled = gc.isenabled() gc.disable() @@ -683,7 +673,6 @@ def _count(_: object) -> None: sentinel = Sentinel() child = root.build_child_container(scope=Scope.REQUEST, context={Sentinel: sentinel}) weakref.finalize(sentinel, _count, None) - child.open() child.close_sync() children.append(child) del children, child, sentinel @@ -698,7 +687,6 @@ def _count(_: object) -> None: def test_add_providers_registers_and_resolves_by_type_and_reference() -> None: container = Container(scope=Scope.APP) - container.open() str_factory = providers.Factory(creator=lambda: "added", bound_type=str) container.add_providers(str_factory) @@ -730,7 +718,6 @@ def test_add_providers_raises_on_duplicate_intra_batch() -> None: def test_add_providers_on_child_container_raises() -> None: root = Container(scope=Scope.APP) - root.open() child = root.build_child_container(scope=Scope.REQUEST) str_factory = providers.Factory(creator=lambda: "added", bound_type=str) @@ -776,7 +763,6 @@ class G(Group): cached = providers.Factory(creator=lambda: "value", bound_type=str, cache=True) container = Container(groups=[G]) - container.open() via_dispatch = container.resolve_dependency(G.cached) via_resolve_provider = container.resolve_provider(G.cached) assert via_dispatch is via_resolve_provider @@ -794,7 +780,6 @@ class Outer: inner: Inner | None = None container = Container(scope=Scope.APP) - container.open() outer_factory = providers.Factory(creator=Outer) # not cached: second resolve rebuilds first = container.resolve_provider(outer_factory) @@ -818,7 +803,6 @@ class Outer: inner: Inner container = Container(scope=Scope.APP) - container.open() outer_factory = providers.Factory(creator=Outer) with pytest.raises(ArgumentResolutionError): @@ -849,7 +833,6 @@ class G(Group): cached = providers.Factory(creator=lambda: "value", bound_type=str, cache=True) container = Container(groups=[G]) - container.open() via_dispatch = container.resolve_dependency(str) via_resolve = container.resolve(str) assert via_dispatch is via_resolve @@ -864,7 +847,6 @@ class G(Group): app_factory = providers.Factory(creator=Service) container = Container(groups=[G]) - container.open() override = Service(name="override") container.override(G.app_factory, override) @@ -898,9 +880,7 @@ class G(Group): request_factory = providers.Factory(scope=Scope.REQUEST, creator=lambda: "value", bound_type=str) app_container = Container(groups=[G]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() assert request_container.resolve_dependency(str) == "value" assert request_container.resolve_dependency(G.request_factory) == "value" @@ -915,7 +895,6 @@ class _OverrideGroup(Group): def test_override_context_manager_applies_and_resets() -> None: container = Container(groups=[_OverrideGroup]) - container.open() mock = _OverrideSvc() with container.override(_OverrideGroup.svc, mock) as bound: assert bound is mock @@ -925,7 +904,6 @@ def test_override_context_manager_applies_and_resets() -> None: def test_override_context_manager_restores_prior_imperative_override() -> None: container = Container(groups=[_OverrideGroup]) - container.open() first = _OverrideSvc() second = _OverrideSvc() container.override(_OverrideGroup.svc, first) @@ -936,7 +914,6 @@ def test_override_context_manager_restores_prior_imperative_override() -> None: def test_override_context_manager_nested_unwinds_in_order() -> None: container = Container(groups=[_OverrideGroup]) - container.open() outer = _OverrideSvc() inner = _OverrideSvc() with container.override(_OverrideGroup.svc, outer): @@ -950,7 +927,6 @@ def test_override_context_manager_nested_unwinds_in_order() -> None: def test_override_context_manager_restores_on_exception() -> None: container = Container(groups=[_OverrideGroup]) - container.open() mock = _OverrideSvc() msg = "boom" with pytest.raises(RuntimeError), container.override(_OverrideGroup.svc, mock): @@ -960,7 +936,6 @@ def test_override_context_manager_restores_on_exception() -> None: def test_override_context_manager_exit_restores_snapshot_after_inner_reset() -> None: container = Container(groups=[_OverrideGroup]) - container.open() first = _OverrideSvc() second = _OverrideSvc() container.override(_OverrideGroup.svc, first) @@ -973,7 +948,6 @@ def test_override_context_manager_exit_restores_snapshot_after_inner_reset() -> def test_override_survives_root_close_sync_and_reopen() -> None: container = Container(groups=[_OverrideGroup]) - container.open() mock = _OverrideSvc() container.override(_OverrideGroup.svc, mock) container.close_sync() @@ -983,7 +957,6 @@ def test_override_survives_root_close_sync_and_reopen() -> None: async def test_override_survives_root_close_async_and_reopen() -> None: container = Container(groups=[_OverrideGroup]) - container.open() mock = _OverrideSvc() container.override(_OverrideGroup.svc, mock) await container.close_async() @@ -993,7 +966,6 @@ async def test_override_survives_root_close_async_and_reopen() -> None: def test_override_context_manager_exit_after_root_close_restores_prior_override() -> None: container = Container(groups=[_OverrideGroup]) - container.open() first = _OverrideSvc() second = _OverrideSvc() container.override(_OverrideGroup.svc, first) @@ -1012,7 +984,6 @@ class _UnknownProvider(AbstractProvider[object]): provider = _UnknownProvider(scope=Scope.APP, bound_type=None) container = Container() - container.open() with pytest.raises(TypeError, match="no compiled resolver for provider type _UnknownProvider"): container.resolve_provider(provider) diff --git a/tests/test_custom_scope.py b/tests/test_custom_scope.py index 43ae09ae..72b2a7af 100644 --- a/tests/test_custom_scope.py +++ b/tests/test_custom_scope.py @@ -36,7 +36,6 @@ class TenantService: def test_build_child_at_custom_scope_from_step() -> None: step_container = Container(scope=Scope.STEP) - step_container.open() tenant_container = step_container.build_child_container(scope=MyScope.TENANT) assert tenant_container.scope is MyScope.TENANT assert tenant_container.parent_container is step_container @@ -44,7 +43,6 @@ def test_build_child_at_custom_scope_from_step() -> None: def test_build_child_at_custom_scope_from_app_skips_intermediate() -> None: app_container = Container() - app_container.open() tenant_container = app_container.build_child_container(scope=MyScope.TENANT) assert tenant_container.scope is MyScope.TENANT @@ -54,9 +52,7 @@ class TenantGroup(Group): svc = providers.Factory(scope=MyScope.TENANT, creator=TenantService) app_container = Container(groups=[TenantGroup]) - app_container.open() tenant_container = app_container.build_child_container(scope=MyScope.TENANT) - tenant_container.open() instance = tenant_container.resolve(TenantService) assert isinstance(instance, TenantService) @@ -67,7 +63,6 @@ class TenantGroup(Group): svc = providers.Factory(scope=MyScope.TENANT, creator=TenantService) app_container = Container(groups=[TenantGroup]) - app_container.open() with pytest.raises(ScopeNotInitializedError, match="TENANT") as exc: app_container.resolve(TenantService) assert exc.value.provider_scope is MyScope.TENANT @@ -77,7 +72,6 @@ class TenantGroup(Group): def test_resolve_app_provider_from_custom_scope_with_skipped_chain() -> None: # A standalone tenant container that never went through APP -> ... chain tenant_container = Container(scope=MyScope.TENANT) - tenant_container.open() app_factory = providers.Factory(creator=lambda: "x") with pytest.raises(ScopeSkippedError, match="APP"): tenant_container.resolve_provider(app_factory) @@ -85,7 +79,6 @@ def test_resolve_app_provider_from_custom_scope_with_skipped_chain() -> None: def test_invalid_child_scope_uses_parent_enum_for_allowed_list() -> None: tenant_container = Container(scope=MyScope.TENANT) - tenant_container.open() with pytest.raises(InvalidChildScopeError) as exc: tenant_container.build_child_container(scope=MyScope.TENANT) # allowed_scopes must be drawn from the parent's own enum class (MyScope), @@ -95,7 +88,6 @@ def test_invalid_child_scope_uses_parent_enum_for_allowed_list() -> None: def test_invalid_child_scope_with_conflicting_value() -> None: app_container = Container() - app_container.open() with pytest.raises(InvalidChildScopeError) as exc: app_container.build_child_container(scope=ConflictingScope.SAME_AS_APP) assert exc.value.parent_scope is Scope.APP @@ -136,11 +128,8 @@ class TenantGroup(Group): ) app_container = Container(groups=[TenantGroup]) - app_container.open() tenant_a = app_container.build_child_container(scope=MyScope.TENANT) - tenant_a.open() tenant_b = app_container.build_child_container(scope=MyScope.TENANT) - tenant_b.open() instance_a = tenant_a.resolve(TenantService) instance_b = tenant_b.resolve(TenantService) @@ -150,7 +139,6 @@ class TenantGroup(Group): def test_auto_derive_within_custom_enum() -> None: tenant_container = Container(scope=MyScope.TENANT) - tenant_container.open() bg_container = tenant_container.build_child_container() assert bg_container.scope is MyScope.BACKGROUND_JOB @@ -164,14 +152,12 @@ def test_auto_derive_with_gapped_custom_enum() -> None: # Non-contiguous values: the next scope is the smallest member greater than the # current one, not current.value + 1 (which would not be a valid member). tenant_container = Container(scope=GappedScope.TENANT) - tenant_container.open() bg_container = tenant_container.build_child_container() assert bg_container.scope is GappedScope.BACKGROUND_JOB def test_auto_derive_at_deepest_gapped_scope_raises_max() -> None: bg_container = Container(scope=GappedScope.BACKGROUND_JOB) - bg_container.open() with pytest.raises(MaxScopeReachedError): bg_container.build_child_container() @@ -192,7 +178,6 @@ class ZeroEnum(enum.IntEnum): TWO = 2 parent = Container(scope=ZeroEnum.ONE) - parent.open() with pytest.raises(InvalidChildScopeError): parent.build_child_container(scope=ZeroEnum.ZERO) diff --git a/tests/test_dependency_graph_contract.py b/tests/test_dependency_graph_contract.py index 120d29d3..5d8d5172 100644 --- a/tests/test_dependency_graph_contract.py +++ b/tests/test_dependency_graph_contract.py @@ -92,7 +92,6 @@ class G(Group): b = Factory(scope=Scope.APP, creator=_B) container = Container(scope=Scope.APP, groups=[G]) - container.open() with pytest.raises(exceptions.CircularDependencyError): container.resolve(_A) diff --git a/tests/test_dependency_path.py b/tests/test_dependency_path.py index af9d8651..019231f1 100644 --- a/tests/test_dependency_path.py +++ b/tests/test_dependency_path.py @@ -38,7 +38,6 @@ class IncompleteGroup(Group): def test_chain_appears_when_arg_unresolvable() -> None: container = Container(groups=[IncompleteGroup]) - container.open() with pytest.raises(ArgumentResolutionError) as exc_info: container.resolve(MyService) @@ -78,9 +77,7 @@ class CrossScope(Group): outer = providers.Factory(scope=Scope.REQUEST, creator=Outer) container = Container(groups=[CrossScope]) - container.open() request = container.build_child_container(scope=Scope.REQUEST) - request.open() with pytest.raises(ArgumentResolutionError) as exc_info: request.resolve(Outer) @@ -127,9 +124,7 @@ class CaptiveGroup(Group): consumer = providers.Factory(scope=Scope.APP, creator=CaptiveConsumer) app_container = Container(groups=[CaptiveGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() with pytest.raises(ScopeNotInitializedError) as exc_info: request_container.resolve(CaptiveConsumer) @@ -149,7 +144,6 @@ class AliasCaptiveGroup(Group): alias = providers.Alias(source_type=ScopedResource, bound_type=AbstractResource) app_container = Container(groups=[AliasCaptiveGroup]) - app_container.open() with pytest.raises(ScopeNotInitializedError) as exc_info: app_container.resolve(AbstractResource) @@ -232,7 +226,6 @@ class _G(Group): anchored = providers.Factory(_Anchored, scope=Scope.REQUEST) container = Container(groups=[_G]) - container.open() with pytest.raises(ScopeNotInitializedError) as exc_info: container.resolve(_Anchored) lineno = inspect.getsourcelines(_Anchored)[1] @@ -273,7 +266,6 @@ def _validate_chain_names() -> list[str]: def _runtime_chain_names() -> list[str]: container = Container(groups=[_AliasScopeViolationGroup]) - container.open() with pytest.raises(ScopeNotInitializedError) as exc_info: container.resolve(_Captor) return [step.name for step in exc_info.value.dependency_path] @@ -322,7 +314,6 @@ class _TwoEdgesGroup(Group): ) def test_runtime_chain_names_the_edge_that_failed(consumer: type, expected: list[str]) -> None: container = Container(groups=[_TwoEdgesGroup]) - container.open() with pytest.raises(ScopeNotInitializedError) as exc_info: container.resolve(consumer) assert [step.name for step in exc_info.value.dependency_path] == expected @@ -348,7 +339,6 @@ class _DanglingUnderParentGroup(Group): def test_dangling_alias_under_a_parent_names_both() -> None: container = Container(groups=[_DanglingUnderParentGroup]) - container.open() with pytest.raises(exceptions.AliasSourceNotRegisteredError) as exc_info: container.resolve(_NeedsDangling) assert [step.name for step in exc_info.value.dependency_path] == ["_NeedsDangling", "_DanglingIface"] diff --git a/tests/test_free_threading.py b/tests/test_free_threading.py index 8e37c384..3d7f0388 100644 --- a/tests/test_free_threading.py +++ b/tests/test_free_threading.py @@ -41,7 +41,6 @@ class _StressGroup(Group): def test_concurrent_resolution_shares_app_singletons() -> None: container = Container(groups=[_StressGroup]) - container.open() n = 32 barrier = threading.Barrier(n) top_results: list[_Top | None] = [None] * n @@ -75,7 +74,6 @@ class G(Group): leaf = providers.Factory(creator=_Leaf, scope=Scope.APP, cache=True) container = Container(scope=Scope.APP, groups=[G]) - container.open() container.close_sync() n = 8 raised: list[BaseException] = [] diff --git a/tests/test_group.py b/tests/test_group.py index 24b9cd82..1ee49fe4 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -43,7 +43,6 @@ class Child(Base): b = providers.Factory(creator=_B) container = Container(groups=[Child]) - container.open() assert isinstance(container.resolve(_A), _A) assert isinstance(container.resolve(_B), _B) @@ -56,7 +55,6 @@ class Child(Base): a = providers.Factory(creator=_A, bound_type=_B) # override; resolve _B yields _A instance container = Container(groups=[Child]) - container.open() assert isinstance(container.resolve(_B), _A) @@ -170,9 +168,7 @@ class RequestGroup(Group, scope=Scope.REQUEST): assert RequestGroup.svc.scope is Scope.REQUEST assert RequestGroup.ctx.scope is Scope.REQUEST app_container = Container(groups=[RequestGroup]) - app_container.open() request_container = app_container.build_child_container(scope=Scope.REQUEST) - request_container.open() assert isinstance(request_container.resolve(_Svc), _Svc) diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 5b08ab1c..79cd1466 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -53,7 +53,6 @@ class _Deps(Group): def test_marker_resolve_delegates_to_container_resolve_dependency() -> None: container = Container(groups=[_Deps]) - container.open() marker: Marker[_Service] = Marker(_Service) resolved = marker.resolve(container) @@ -124,7 +123,6 @@ def handler(a: int) -> None: def test_resolve_markers_resolves_each_marker_by_name() -> None: container = Container(groups=[_Deps]) - container.open() markers = {"service": Marker(_Service)} resolved = resolve_markers(container, markers) diff --git a/tests/test_resolver_compiler.py b/tests/test_resolver_compiler.py index 7bed80ed..afad921d 100644 --- a/tests/test_resolver_compiler.py +++ b/tests/test_resolver_compiler.py @@ -173,7 +173,6 @@ class G(Group): ordered = providers.Factory(creator=_make, scope=Scope.APP) container = Container(groups=[G]) - container.open() plan = _plan(container._providers_registry, G.ordered) assert G.ordered._can_call_positionally(plan) # self-guard: positional path selected @@ -200,7 +199,6 @@ def test_positional_path_binds_args_in_signature_order_at_every_arity(arity: int group = _pytypes.new_class(f"_ArityGroup{arity}", (Group,), exec_body=lambda ns: ns.update(members)) container = Container(scope=Scope.APP, groups=[group]) - container.open() plan = _plan(container._providers_registry, members["bag"]) assert members["bag"]._can_call_positionally(plan) # self-guard: positional path selected @@ -242,7 +240,6 @@ def test_arity_rung_front_guards_the_override(arity: int) -> None: """ group = _arity_group(arity) container = Container(scope=Scope.APP, groups=[group]) - container.open() sentinel = object() container.override(group.target, sentinel) assert container.resolve_provider(group.target) is sentinel @@ -259,7 +256,6 @@ def test_arity_rung_navigates_to_its_own_scope(arity: int) -> None: """ group = _arity_group(arity) app = Container(scope=Scope.APP, groups=[group]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) assert isinstance(request.resolve_provider(group.target), _Bag) @@ -271,7 +267,6 @@ def test_arity_rung_raises_for_a_closed_target(arity: int) -> None: # own `if target._closed` guard. group = _arity_group(arity) app = Container(scope=Scope.APP, groups=[group]) - app.open() request = app.build_child_container(scope=Scope.REQUEST) app.close_sync() with pytest.raises(exceptions.ContainerClosedError) as exc: @@ -299,7 +294,6 @@ def _needs_one_more(*args: object, extra: object) -> _Bag: # noqa: ARG001 # pr group = _arity_group(arity, creator=_needs_one_more) container = Container(scope=Scope.APP, groups=[group]) - container.open() with pytest.raises(exceptions.CreatorCallError): container.resolve_provider(group.target) @@ -314,7 +308,6 @@ def test_arity_rung_reraises_a_type_error_from_inside_the_creator(arity: int) -> group = _arity_group(arity, creator=ns["_c"]) container = Container(scope=Scope.APP, groups=[group]) - container.open() with pytest.raises(TypeError, match="from inside") as exc: container.resolve_provider(group.target) assert not isinstance(exc.value, exceptions.CreatorCallError) @@ -336,7 +329,6 @@ def test_arity_rung_prepends_its_step_to_a_dependency_error(arity: int) -> None: group = _pytypes.new_class(f"_DepErr{arity}", (Group,), exec_body=lambda gns: gns.update(members)) container = Container(scope=Scope.APP, groups=[group]) - container.open() with pytest.raises(exceptions.ScopeNotInitializedError) as exc: container.resolve_provider(members["shallow"]) assert "_Shallow" in str(exc.value) @@ -490,7 +482,6 @@ class G(Group): ordered = providers.Factory(creator=_make, scope=Scope.APP) container = Container(groups=[G]) # parse_creator already ran at G's class def - container.open() calls: list[object] = [] real_signature = inspect.signature diff --git a/tests/test_runtime_cycle_guard.py b/tests/test_runtime_cycle_guard.py index b052861b..4d636492 100644 --- a/tests/test_runtime_cycle_guard.py +++ b/tests/test_runtime_cycle_guard.py @@ -96,7 +96,6 @@ def test_unvalidated_cycle_raises_circular_dependency_error() -> None: # `_SHALLOW_RECURSION_LIMIT` above. It mirrors the guard's own `except RecursionError` shape # in `resolve_provider`. container = Container(groups=[CycleGroup]) # exercise the runtime guard, not validation - container.open() original_limit = sys.getrecursionlimit() sys.setrecursionlimit(_SHALLOW_RECURSION_LIMIT) try: @@ -124,7 +123,6 @@ def _assert_deep_chain_cycle_is_self_contained(exc: exceptions.CircularDependenc def test_deep_chain_cycle_is_self_contained() -> None: container = Container(groups=[DeepCycleGroup]) # exercise the runtime guard, not validation - container.open() original_limit = sys.getrecursionlimit() sys.setrecursionlimit(_SHALLOW_RECURSION_LIMIT) try: @@ -147,7 +145,6 @@ class RecursiveGroup(Group): # The registry starts unvalidated (nothing here calls validate()), so the guard runs # find_cycle_from (no static cycle -> re-raise) rather than short-circuiting on the validated flag. container = Container(groups=[RecursiveGroup]) - container.open() with pytest.raises(RecursionError): container.resolve(str) @@ -168,7 +165,6 @@ class G(Group): container = Container(scope=Scope.APP, groups=[G]) container.validate() # marks the graph validated -> the recursion guard short-circuits below - container.open() def _explode(*_: object, **__: object) -> object: # pragma: no cover - validated graph must skip find_cycle_from msg = "walked" @@ -207,7 +203,6 @@ class G(Group): b = providers.Factory(creator=_CanonicalB, scope=Scope.APP) container = Container(scope=Scope.APP, groups=[G]) - container.open() limit = sys.getrecursionlimit() sys.setrecursionlimit(80) try: @@ -228,7 +223,6 @@ def test_by_reference_cycle_raises_circular_dependency_error() -> None: # `test_by_reference_recursionerror_passes_through`, which reaches it without an overflow. # Same `except`-clause shape and shallow limit, per `_SHALLOW_RECURSION_LIMIT`. container = Container(groups=[CycleGroup]) - container.open() original_limit = sys.getrecursionlimit() sys.setrecursionlimit(_SHALLOW_RECURSION_LIMIT) try: @@ -255,6 +249,5 @@ class G(Group): container = Container(scope=Scope.APP, groups=[G]) container.validate() - container.open() with pytest.raises(RecursionError): container.resolve_provider(G.s) diff --git a/tests/test_suggestions.py b/tests/test_suggestions.py index 8132ecdb..6354e22f 100644 --- a/tests/test_suggestions.py +++ b/tests/test_suggestions.py @@ -81,9 +81,7 @@ class G(Group): db = providers.Factory(scope=Scope.REQUEST, creator=PostgresDatabase) container = Container(groups=[G]) - container.open() request_container = container.build_child_container(scope=Scope.REQUEST) - request_container.open() with pytest.raises(ProviderNotRegisteredError) as exc_info: request_container.resolve(Database) @@ -153,6 +151,11 @@ def test_suggest_skips_non_class_bound_type() -> None: assert suggester.suggest(int, [provider]) == [] +def test_suggest_skips_bound_type_without_a_name() -> None: + provider = providers.Factory(creator=lambda: 1, bound_type=typing.ForwardRef("Database")) # ty: ignore[invalid-argument-type] + assert suggester.suggest(Database, [provider]) == [] + + def test_suggest_swallows_protocol_typeerror() -> None: class MyProto(typing.Protocol): def foo(self) -> None: ... @@ -171,7 +174,6 @@ class G(Group): service = providers.Factory(creator=Service) container = Container(groups=[G]) - container.open() with pytest.raises(ArgumentResolutionError) as exc_info: container.resolve(Service) @@ -193,7 +195,6 @@ class G(Group): service = providers.Factory(creator=Service) container = Container(groups=[G]) - container.open() with pytest.raises(ArgumentResolutionError) as exc_info: container.resolve(Service) @@ -216,7 +217,6 @@ class G(Group): service = providers.Factory(creator=Service) container = Container(groups=[G]) - container.open() with pytest.raises(ArgumentResolutionError) as exc_info: container.resolve(Service) @@ -234,7 +234,6 @@ class G(Group): service = providers.Factory(creator=Service) container = Container(groups=[G]) - container.open() with pytest.raises(ArgumentResolutionError) as exc_info: container.resolve(Service) diff --git a/tests/test_types_parser.py b/tests/test_types_parser.py index 33fc8607..cd920c4c 100644 --- a/tests/test_types_parser.py +++ b/tests/test_types_parser.py @@ -203,6 +203,16 @@ def __init__(self, arg1: "WrongType", arg2: "int") -> None: ... # ty: ignore[un ), ), (int, (SignatureItem(arg_type=int), {})), + ], +) +def test_parse_creator(creator: type, result: tuple[SignatureItem | None, dict[str, SignatureItem]]) -> None: + parsed = parse_creator(creator) + assert (parsed.return_type, parsed.params) == result + + +@pytest.mark.parametrize( + ("creator", "result"), + [ (func_with_wrong_annotations, (SignatureItem(), {"arg1": SignatureItem(), "arg2": SignatureItem()})), ( ClassWithWrongAnnotations, @@ -210,8 +220,11 @@ def __init__(self, arg1: "WrongType", arg2: "int") -> None: ... # ty: ignore[un ), ], ) -def test_parse_creator(creator: type, result: tuple[SignatureItem | None, dict[str, SignatureItem]]) -> None: - parsed = parse_creator(creator) +def test_parse_creator_with_unresolvable_annotations_warns( + creator: type, result: tuple[SignatureItem | None, dict[str, SignatureItem]] +) -> None: + with pytest.warns(UserWarning, match="Failed to resolve type hints"): + parsed = parse_creator(creator) assert (parsed.return_type, parsed.params) == result @@ -269,7 +282,6 @@ def test_parameterized_generic_param_supplied_via_kwargs_is_allowed() -> None: sentinel = [_GenericDep()] provider = providers.Factory(creator=_generic_param_creator, kwargs={"x": sentinel}) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(str, provider) assert container.resolve(str) == str(sentinel) @@ -282,7 +294,6 @@ def test_parameterized_generic_param_with_default_is_allowed() -> None: assert _generic_param_with_default(("a",)) == str(("a",)) provider = providers.Factory(creator=_generic_param_with_default) container = Container(scope=Scope.APP) - container.open() container._providers_registry.register(str, provider) assert container.resolve(str) == str(())