diff --git a/anydi/_context.py b/anydi/_context.py index 0f9bf36..7be2e37 100644 --- a/anydi/_context.py +++ b/anydi/_context.py @@ -138,7 +138,10 @@ def __exit__( ) -> Any: """Exit the context.""" self._reject_async_resources() - return self._exit_stack(exc_type, exc_val, exc_tb) + try: + return self._exit_stack(exc_type, exc_val, exc_tb) + finally: + self._forget_closed() def _exit_stack( self, @@ -182,12 +185,22 @@ async def __aexit__( ) if self._async_stack is not None: async_exit = await self._async_stack.__aexit__(exc_type, exc_val, exc_tb) + self._forget_closed() return bool(sync_exit) or bool(async_exit) async def aclose(self) -> None: """Close the scoped context asynchronously.""" await self.__aexit__(None, None, None) + def _forget_closed(self) -> None: + """Drop the instances whose resources this context closed.""" + for key in (*self._closers, *self._aclosers): + self._items.pop(key, None) + self._closers.clear() + self._aclosers.clear() + self._stack = None + self._async_stack = None + def lock(self) -> threading.RLock: """Acquire the context lock.""" if self._lock is None: diff --git a/tests/test_container.py b/tests/test_container.py index 8c89591..db27f65 100644 --- a/tests/test_container.py +++ b/tests/test_container.py @@ -2023,6 +2023,43 @@ def test_release_transient_instance(self, container: Container) -> None: assert not container.is_resolved(str) + def test_a_reopened_container_makes_the_resource_again( + self, container: Container + ) -> None: + events = [] + + def provide_conn() -> Iterator[str]: + events.append("open") + yield "conn" + events.append("close") + + container.register(str, provide_conn, scope="singleton") + + with container: + container.resolve(str) + with container: + container.resolve(str) + + assert events == ["open", "close", "open", "close"] + + def test_a_reopened_container_keeps_what_it_did_not_close( + self, container: Container + ) -> None: + made = [] + + class Service: + def __init__(self) -> None: + made.append(self) + + container.register(Service, Service, scope="singleton") + + with container: + first = container.resolve(Service) + with container: + assert container.resolve(Service) is first + + assert len(made) == 1 + def test_release_forgets_an_instance_whose_finaliser_raises( self, container: Container ) -> None: diff --git a/tests/test_ref.py b/tests/test_ref.py index 65867b9..0b9fa70 100644 --- a/tests/test_ref.py +++ b/tests/test_ref.py @@ -451,20 +451,24 @@ def get_db() -> Database: assert db.dsn == "postgres://1" def test_ref_reflects_resolve_after_close(self, container: Container) -> None: + closed = [] + @container.provider(scope="singleton") def get_db() -> Iterator[Database]: db = Database() yield db db.closed = True + closed.append(db) db = container.ref(Database) with container: assert db.dsn == "postgres://localhost" - # Closing finalizes the resource, but the container keeps the instance - assert db.closed is True - assert db == container.resolve(Database) + # Closing finalizes the resource, so the reference reads a new instance + assert len(closed) == 1 + assert db.closed is False + assert db != closed[0] class TestRefUsage: