Skip to content

Commit 4e3c87d

Browse files
authored
Merge pull request #230 from GetStream/fix/close-child-clients-on-aclose
fix: close child httpx clients in AsyncStream.aclose()
2 parents ebcd026 + 999715e commit 4e3c87d

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

getstream/stream.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from contextlib import AsyncExitStack
34
from functools import cached_property
45
import time
56
from typing import List, Optional
@@ -207,6 +208,20 @@ def moderation(self) -> AsyncModerationClient:
207208
user_agent=self.user_agent,
208209
)
209210

211+
async def aclose(self):
212+
"""Close all child clients and the main HTTPX client."""
213+
# AsyncExitStack ensures all clients are closed even if one fails.
214+
# video/chat/moderation are @cached_property - only close if accessed.
215+
async with AsyncExitStack() as stack:
216+
cached = self.__dict__
217+
if "video" in cached:
218+
stack.push_async_callback(self.video.aclose)
219+
if "chat" in cached:
220+
stack.push_async_callback(self.chat.aclose)
221+
if "moderation" in cached:
222+
stack.push_async_callback(self.moderation.aclose)
223+
stack.push_async_callback(super().aclose)
224+
210225
@cached_property
211226
def feeds(self):
212227
raise NotImplementedError("Feeds not supported for async client")

tests/test_async_stream_close.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from getstream import AsyncStream
4+
5+
6+
@pytest.mark.asyncio
7+
class TestAsyncStreamClose:
8+
async def test_aclose_closes_main_client(self):
9+
client = AsyncStream(api_key="fake", api_secret="fake")
10+
11+
assert client.client.is_closed is False
12+
await client.aclose()
13+
assert client.client.is_closed is True
14+
15+
async def test_aclose_closes_video_client(self):
16+
client = AsyncStream(api_key="fake", api_secret="fake")
17+
_ = client.video # trigger cached_property
18+
19+
assert client.video.client.is_closed is False
20+
await client.aclose()
21+
assert client.video.client.is_closed is True
22+
23+
async def test_aclose_closes_chat_client(self):
24+
client = AsyncStream(api_key="fake", api_secret="fake")
25+
_ = client.chat
26+
27+
assert client.chat.client.is_closed is False
28+
await client.aclose()
29+
assert client.chat.client.is_closed is True
30+
31+
async def test_aclose_closes_moderation_client(self):
32+
client = AsyncStream(api_key="fake", api_secret="fake")
33+
_ = client.moderation
34+
35+
assert client.moderation.client.is_closed is False
36+
await client.aclose()
37+
assert client.moderation.client.is_closed is True
38+
39+
async def test_aclose_without_child_clients(self):
40+
"""aclose() should work even if video/chat were never accessed."""
41+
client = AsyncStream(api_key="fake", api_secret="fake")
42+
await client.aclose()
43+
assert client.client.is_closed is True

0 commit comments

Comments
 (0)