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
8 changes: 7 additions & 1 deletion anydi/_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,14 +890,20 @@ def release(self, dependency_type: Any, /) -> None:
def reset(self) -> None:
"""Reset resolved instances."""
self._invalidate_refs()
failure: BaseException | None = None
for dependency_type, provider in self._providers.items():
if provider.scope == "transient":
continue
try:
context = self._get_instance_context(provider.scope)
except LookupError:
continue
context.release(dependency_type)
try:
context.release(dependency_type)
except Exception as exc: # the rest still resets
failure = failure or exc
if failure is not None:
raise failure

# == Lazy References ==

Expand Down
4 changes: 3 additions & 1 deletion anydi/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,11 @@ def release(self, key: Any) -> None:
"with `aclose()` instead."
)
closer = self._closers.pop(key, None)
# Dropped first: a finaliser that raises must not leave the instance
# behind, or the next resolution hands out a closed one.
self._items.pop(key, None)
if closer is not None:
closer(None, None, None)
self._items.pop(key, None)

def __setitem__(self, key: Any, value: Any) -> None:
self._items[key] = value
Expand Down
48 changes: 48 additions & 0 deletions tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,54 @@ def test_release_transient_instance(self, container: Container) -> None:

assert not container.is_resolved(str)

def test_release_forgets_an_instance_whose_finaliser_raises(
self, container: Container
) -> None:
events = []

def provide_conn() -> Iterator[str]:
events.append("open")
yield "conn"
raise RuntimeError("no")

container.register(str, provide_conn, scope="singleton")
container.resolve(str)

with pytest.raises(RuntimeError, match="no"):
container.release(str)

assert not container.is_resolved(str)

container.resolve(str)

assert events == ["open", "open"]

def test_reset_releases_them_all_when_one_finaliser_raises(
self, container: Container
) -> None:
events = []

def provide_first() -> Iterator[str]:
yield "first"
events.append("first")
raise RuntimeError("no")

def provide_second() -> Iterator[int]:
yield 1
events.append("second")

container.register(str, provide_first, scope="singleton")
container.register(int, provide_second, scope="singleton")
container.resolve(str)
container.resolve(int)

with pytest.raises(RuntimeError, match="no"):
container.reset()

assert events == ["first", "second"]
assert not container.is_resolved(str)
assert not container.is_resolved(int)

def test_release_closes_the_resource(self, container: Container) -> None:
events = []

Expand Down
Loading