|
12 | 12 | from test.conftest import Container |
13 | 13 |
|
14 | 14 |
|
| 15 | +class AsyncContextManagerResource: |
| 16 | + def __init__(self, cleanup_events: list[str]) -> None: |
| 17 | + self.cleanup_events = cleanup_events |
| 18 | + self.entered = False |
| 19 | + self.exited = False |
| 20 | + |
| 21 | + async def __aenter__(self) -> "AsyncContextManagerResource": |
| 22 | + self.entered = True |
| 23 | + return self |
| 24 | + |
| 25 | + async def __aexit__(self, *_: object) -> None: |
| 26 | + self.exited = True |
| 27 | + self.cleanup_events.append("resource") |
| 28 | + |
| 29 | + |
15 | 30 | def test_cleans_up_on_exit(container: Container) -> None: |
16 | 31 | _cleanup_performed = False |
17 | 32 | Something = NewType("Something", str) |
@@ -50,6 +65,26 @@ async def target(smth: Injected[Something]): |
50 | 65 | assert _cleanup_performed |
51 | 66 |
|
52 | 67 |
|
| 68 | +async def test_async_generator_factory_closes_async_context_manager_on_container_close() -> None: |
| 69 | + cleanup_events: list[str] = [] |
| 70 | + |
| 71 | + @injectable |
| 72 | + async def some_factory() -> AsyncIterator[AsyncContextManagerResource]: |
| 73 | + async with AsyncContextManagerResource(cleanup_events) as resource: |
| 74 | + yield resource |
| 75 | + |
| 76 | + container = wireup.create_async_container(injectables=[some_factory]) |
| 77 | + resource = await container.get(AsyncContextManagerResource) |
| 78 | + |
| 79 | + assert resource.entered |
| 80 | + assert not resource.exited |
| 81 | + |
| 82 | + await container.close() |
| 83 | + |
| 84 | + assert resource.exited |
| 85 | + assert cleanup_events == ["resource"] |
| 86 | + |
| 87 | + |
53 | 88 | def test_injects_transient() -> None: |
54 | 89 | _cleanups: list[str] = [] |
55 | 90 | Something = NewType("Something", str) |
@@ -103,6 +138,28 @@ def f2(something: Something) -> Iterator[SomethingElse]: |
103 | 138 | assert _cleanups == ["f2", "f1"] |
104 | 139 |
|
105 | 140 |
|
| 141 | +@pytest.mark.parametrize("lifetime", ["scoped", "transient"]) |
| 142 | +async def test_async_generator_factory_closes_async_context_manager_on_scope_exit( |
| 143 | + lifetime: InjectableLifetime, |
| 144 | +) -> None: |
| 145 | + cleanup_events: list[str] = [] |
| 146 | + |
| 147 | + @injectable(lifetime=lifetime) |
| 148 | + async def some_factory() -> AsyncIterator[AsyncContextManagerResource]: |
| 149 | + async with AsyncContextManagerResource(cleanup_events) as resource: |
| 150 | + yield resource |
| 151 | + |
| 152 | + container = wireup.create_async_container(injectables=[some_factory]) |
| 153 | + |
| 154 | + async with container.enter_scope() as scoped: |
| 155 | + resource = await scoped.get(AsyncContextManagerResource) |
| 156 | + assert resource.entered |
| 157 | + assert not resource.exited |
| 158 | + |
| 159 | + assert resource.exited |
| 160 | + assert cleanup_events == ["resource"] |
| 161 | + |
| 162 | + |
106 | 163 | def test_cleans_up_in_order() -> None: |
107 | 164 | _cleanups: list[str] = [] |
108 | 165 | Something = NewType("Something", str) |
|
0 commit comments