diff --git a/.gitignore b/.gitignore index 08ee67e..7f9c387 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ secrets.*sh .idea .venv +venv .python-version pip-selfcheck.json .idea diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 44b3763..b0a5ef0 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -360,6 +360,13 @@ async def query_message_history( ) return await self.post("messages/history", data=params) + async def query_threads( + self, filter: Dict = None, sort: List[Dict] = None, **options: Any + ) -> StreamResponse: + params = options.copy() + params.update({"filter": filter, "sort": self.normalize_sort(sort)}) + return await self.post("threads", data=params) + async def query_users( self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any ) -> StreamResponse: diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index c2e0557..a0cccf3 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -562,6 +562,19 @@ def query_message_history( """ pass + @abc.abstractmethod + def query_threads( + self, filter: Dict = None, sort: List[Dict] = None, **options: Any + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: + """ + Allows you to query threads using filter and sort. You can find the complete list of supported operators in the query syntax section of the docs. + + :param filter: Filter conditions for the query + :param sort: Sort conditions for the query + :return: StreamResponse containing the threads + """ + pass + @abc.abstractmethod def query_users( self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any diff --git a/stream_chat/base/query_threads.py b/stream_chat/base/query_threads.py new file mode 100644 index 0000000..8f58d4b --- /dev/null +++ b/stream_chat/base/query_threads.py @@ -0,0 +1,31 @@ +import abc +from typing import Any, Awaitable, Dict, List, Union + +from stream_chat.base.client import StreamChatInterface +from stream_chat.types.stream_response import StreamResponse + + +class QueryThreadsInterface(abc.ABC): + @abc.abstractmethod + def __init__(self, client: StreamChatInterface): + self.client = client + + @property + def url(self) -> str: + return "threads" + + @abc.abstractmethod + def query_threads( + self, + filter: Dict[str, Dict[str, Any]], + sort: List[Dict[str, Any]], + **options: Any, + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: + """ + Get a list of threads given filter and sort options + + :param filter: filter conditions (e.g. {"created_by_user_id": {"$eq": "user_123"}}) + :param sort: sort options (e.g. [{"field": "last_message_at", "direction": -1}]) + :return: the Server Response + """ + pass diff --git a/stream_chat/client.py b/stream_chat/client.py index 920f471..4722b4b 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -342,6 +342,13 @@ def query_message_history( params.update({"filter": filter, "sort": self.normalize_sort(sort)}) return self.post("messages/history", data=params) + def query_threads( + self, filter: Dict = None, sort: List[Dict] = None, **options: Any + ) -> StreamResponse: + params = options.copy() + params.update({"filter": filter, "sort": self.normalize_sort(sort)}) + return self.post("threads", data=params) + def query_users( self, filter_conditions: Dict, sort: List[Dict] = None, **options: Any ) -> StreamResponse: diff --git a/stream_chat/query_threads.py b/stream_chat/query_threads.py new file mode 100644 index 0000000..63f94de --- /dev/null +++ b/stream_chat/query_threads.py @@ -0,0 +1,15 @@ +from typing import Any, Awaitable, Dict, List, Union + +from stream_chat.base.query_threads import QueryThreadsInterface +from stream_chat.types.stream_response import StreamResponse + + +class QueryThreads(QueryThreadsInterface): + def query_threads( + self, + filter: Dict[str, Dict[str, Any]], + sort: List[Dict[str, Any]], + **options: Any, + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: + payload = {"filter": filter, "sort": sort, **options} + return self.client.post(self.url, data=payload) diff --git a/stream_chat/tests/async_chat/test_query_threads.py b/stream_chat/tests/async_chat/test_query_threads.py new file mode 100644 index 0000000..c4a6bbb --- /dev/null +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -0,0 +1,75 @@ +from typing import Dict + +import pytest + +from stream_chat.async_chat import StreamChatAsync +from stream_chat.types.stream_response import StreamResponse + + +@pytest.mark.incremental +class TestQueryThreads: + @pytest.mark.asyncio + async def test_query_threads( + self, client: StreamChatAsync, channel, random_user: Dict + ): + # Create a thread with some messages + parent_message = await channel.send_message( + {"text": "Parent message"}, random_user["id"] + ) + thread_message = await channel.send_message( + {"text": "Thread message", "parent_id": parent_message["message"]["id"]}, + random_user["id"], + ) + + # Query threads with filter and sort + filter_conditions = {"parent_id": parent_message["message"]["id"]} + sort_conditions = [{"field": "created_at", "direction": -1}] + + response = await client.query_threads( + filter=filter_conditions, sort=sort_conditions, user_id=random_user["id"] + ) + + assert isinstance(response, StreamResponse) + assert "threads" in response + assert len(response["threads"]) > 0 + + # Verify the thread message is in the response + thread = response["threads"][0] + assert "latest_replies" in thread + assert len(thread["latest_replies"]) > 0 + assert thread["latest_replies"][0]["text"] == thread_message["message"]["text"] + + @pytest.mark.asyncio + async def test_query_threads_with_options( + self, client: StreamChatAsync, channel, random_user: Dict + ): + # Create a thread with multiple messages + parent_message = await channel.send_message( + {"text": "Parent message"}, random_user["id"] + ) + thread_messages = [] + for i in range(3): + msg = await channel.send_message( + { + "text": f"Thread message {i}", + "parent_id": parent_message["message"]["id"], + }, + random_user["id"], + ) + thread_messages.append(msg) + + # Query threads with limit and offset + filter_conditions = {"parent_id": parent_message["message"]["id"]} + sort_conditions = [{"field": "created_at", "direction": -1}] + + response = await client.query_threads( + filter=filter_conditions, + sort=sort_conditions, + limit=1, + user_id=random_user["id"], + ) + + assert isinstance(response, StreamResponse) + assert "threads" in response + assert len(response["threads"]) == 1 + assert "next" in response diff --git a/stream_chat/tests/test_query_threads.py b/stream_chat/tests/test_query_threads.py new file mode 100644 index 0000000..1bdd543 --- /dev/null +++ b/stream_chat/tests/test_query_threads.py @@ -0,0 +1,66 @@ +from typing import Dict + +import pytest + +from stream_chat import StreamChat +from stream_chat.types.stream_response import StreamResponse + + +@pytest.mark.incremental +class TestQueryThreads: + def test_query_threads(self, client: StreamChat, channel, random_user: Dict): + parent_message = channel.send_message( + {"text": "Parent message"}, random_user["id"] + ) + thread_message = channel.send_message( + {"text": "Thread message", "parent_id": parent_message["message"]["id"]}, + random_user["id"], + ) + + filter_conditions = {"parent_id": parent_message["message"]["id"]} + sort_conditions = [{"field": "created_at", "direction": -1}] + + response = client.query_threads( + filter=filter_conditions, sort=sort_conditions, user_id=random_user["id"] + ) + + assert isinstance(response, StreamResponse) + assert "threads" in response + assert len(response["threads"]) > 0 + + thread = response["threads"][0] + assert "latest_replies" in thread + assert len(thread["latest_replies"]) > 0 + assert thread["latest_replies"][0]["text"] == thread_message["message"]["text"] + + def test_query_threads_with_options( + self, client: StreamChat, channel, random_user: Dict + ): + parent_message = channel.send_message( + {"text": "Parent message"}, random_user["id"] + ) + thread_messages = [] + for i in range(3): + msg = channel.send_message( + { + "text": f"Thread message {i}", + "parent_id": parent_message["message"]["id"], + }, + random_user["id"], + ) + thread_messages.append(msg) + + filter_conditions = {"parent_id": parent_message["message"]["id"]} + sort_conditions = [{"field": "created_at", "direction": -1}] + + response = client.query_threads( + filter=filter_conditions, + sort=sort_conditions, + limit=1, + user_id=random_user["id"], + ) + + assert isinstance(response, StreamResponse) + assert "threads" in response + assert len(response["threads"]) == 1 + assert "next" in response