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: 15 additions & 0 deletions getstream/stream.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from contextlib import AsyncExitStack
from functools import cached_property
import time
from typing import List, Optional
Expand Down Expand Up @@ -207,6 +208,20 @@ def moderation(self) -> AsyncModerationClient:
user_agent=self.user_agent,
)

async def aclose(self):
"""Close all child clients and the main HTTPX client."""
# AsyncExitStack ensures all clients are closed even if one fails.
# video/chat/moderation are @cached_property - only close if accessed.
async with AsyncExitStack() as stack:
cached = self.__dict__
if "video" in cached:
stack.push_async_callback(self.video.aclose)
if "chat" in cached:
stack.push_async_callback(self.chat.aclose)
if "moderation" in cached:
stack.push_async_callback(self.moderation.aclose)
stack.push_async_callback(super().aclose)

@cached_property
def feeds(self):
raise NotImplementedError("Feeds not supported for async client")
Expand Down
43 changes: 43 additions & 0 deletions tests/test_async_stream_close.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

from getstream import AsyncStream


@pytest.mark.asyncio
class TestAsyncStreamClose:
async def test_aclose_closes_main_client(self):
client = AsyncStream(api_key="fake", api_secret="fake")

assert client.client.is_closed is False
await client.aclose()
assert client.client.is_closed is True

async def test_aclose_closes_video_client(self):
client = AsyncStream(api_key="fake", api_secret="fake")
_ = client.video # trigger cached_property

assert client.video.client.is_closed is False
await client.aclose()
assert client.video.client.is_closed is True

async def test_aclose_closes_chat_client(self):
client = AsyncStream(api_key="fake", api_secret="fake")
_ = client.chat

assert client.chat.client.is_closed is False
await client.aclose()
assert client.chat.client.is_closed is True

async def test_aclose_closes_moderation_client(self):
client = AsyncStream(api_key="fake", api_secret="fake")
_ = client.moderation

assert client.moderation.client.is_closed is False
await client.aclose()
assert client.moderation.client.is_closed is True

async def test_aclose_without_child_clients(self):
"""aclose() should work even if video/chat were never accessed."""
client = AsyncStream(api_key="fake", api_secret="fake")
await client.aclose()
assert client.client.is_closed is True