Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion anydi/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
37 changes: 37 additions & 0 deletions tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 7 additions & 3 deletions tests/test_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading