From bb002bd884c78b86ca4bd3622bad09ef387e5bac Mon Sep 17 00:00:00 2001 From: Aditya Alif Nugraha Date: Fri, 11 Apr 2025 12:21:00 +0200 Subject: [PATCH 1/6] Add query threads --- .gitignore | 1 + stream_chat/async_chat/client.py | 7 +++ stream_chat/base/client.py | 13 ++++ stream_chat/base/query_threads.py | 24 +++++++ stream_chat/client.py | 7 +++ stream_chat/query_threads.py | 8 +++ .../tests/async_chat/test_query_threads.py | 62 +++++++++++++++++++ stream_chat/tests/test_query_threads.py | 57 +++++++++++++++++ 8 files changed, 179 insertions(+) create mode 100644 stream_chat/base/query_threads.py create mode 100644 stream_chat/query_threads.py create mode 100644 stream_chat/tests/async_chat/test_query_threads.py create mode 100644 stream_chat/tests/test_query_threads.py diff --git a/.gitignore b/.gitignore index 08ee67e8..7f9c3870 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 44b3763d..b0a5ef0c 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 c2e0557f..008477f3 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 00000000..e75ca767 --- /dev/null +++ b/stream_chat/base/query_threads.py @@ -0,0 +1,24 @@ +import abc +from typing import Any, Awaitable, Dict, List, Union + +from stream_chat.types.stream_response import StreamResponse + + +class QueryThreadsInterface(abc.ABC): + def __init__(self): + pass + + @property + def url(self): + 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 920f4715..4722b4bf 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 00000000..e09ce86c --- /dev/null +++ b/stream_chat/query_threads.py @@ -0,0 +1,8 @@ +from typing import Dict, List, Any, Union, Awaitable +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 00000000..8c37dc67 --- /dev/null +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -0,0 +1,62 @@ +import pytest +from typing import Dict, Any + +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 + ) + + 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, + ) + + 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 00000000..958a4929 --- /dev/null +++ b/stream_chat/tests/test_query_threads.py @@ -0,0 +1,57 @@ +import pytest +from typing import Dict, Any + +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 From 33dae658f8702c5be7439c6a8c20c57807824490 Mon Sep 17 00:00:00 2001 From: Aditya Alif Nugraha Date: Fri, 11 Apr 2025 12:25:04 +0200 Subject: [PATCH 2/6] Fix lint --- stream_chat/base/client.py | 2 +- stream_chat/base/query_threads.py | 9 +++- stream_chat/query_threads.py | 13 ++++-- .../tests/async_chat/test_query_threads.py | 42 ++++++++++++------- stream_chat/tests/test_query_threads.py | 41 +++++++++++------- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 008477f3..a0cccf38 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -568,7 +568,7 @@ def query_threads( ) -> 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 diff --git a/stream_chat/base/query_threads.py b/stream_chat/base/query_threads.py index e75ca767..3422c857 100644 --- a/stream_chat/base/query_threads.py +++ b/stream_chat/base/query_threads.py @@ -11,9 +11,14 @@ def __init__(self): @property def url(self): 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]]: + 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 diff --git a/stream_chat/query_threads.py b/stream_chat/query_threads.py index e09ce86c..63f94de0 100644 --- a/stream_chat/query_threads.py +++ b/stream_chat/query_threads.py @@ -1,8 +1,15 @@ -from typing import Dict, List, Any, Union, Awaitable +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} + 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 index 8c37dc67..b8d84303 100644 --- a/stream_chat/tests/async_chat/test_query_threads.py +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -1,33 +1,38 @@ +from typing import Any, Dict + import pytest -from typing import Dict, Any 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): + 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"]) + 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"] + 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 + filter=filter_conditions, sort=sort_conditions ) - + 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 @@ -35,27 +40,34 @@ async def test_query_threads(self, client: StreamChatAsync, channel, random_user 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): + 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"]) + 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"] + { + "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, ) - + assert isinstance(response, StreamResponse) assert "threads" in response assert len(response["threads"]) == 1 diff --git a/stream_chat/tests/test_query_threads.py b/stream_chat/tests/test_query_threads.py index 958a4929..09286250 100644 --- a/stream_chat/tests/test_query_threads.py +++ b/stream_chat/tests/test_query_threads.py @@ -1,56 +1,65 @@ +from typing import Any, Dict + import pytest -from typing import Dict, Any 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"]) + 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"] + 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"] + 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"]) + 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"] + { + "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"] + user_id=random_user["id"], ) - + assert isinstance(response, StreamResponse) assert "threads" in response assert len(response["threads"]) == 1 From 32161c5af01b6b60451ef2216fa77c2ced731047 Mon Sep 17 00:00:00 2001 From: Aditya Alif Nugraha Date: Fri, 11 Apr 2025 13:11:36 +0200 Subject: [PATCH 3/6] Fix lint --- stream_chat/tests/async_chat/test_query_threads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream_chat/tests/async_chat/test_query_threads.py b/stream_chat/tests/async_chat/test_query_threads.py index b8d84303..d1b753af 100644 --- a/stream_chat/tests/async_chat/test_query_threads.py +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Dict import pytest From f3bffa1bba13151bc274f547bfb7833394019098 Mon Sep 17 00:00:00 2001 From: Aditya Alif Nugraha Date: Fri, 11 Apr 2025 13:17:07 +0200 Subject: [PATCH 4/6] Fix lint --- stream_chat/base/query_threads.py | 8 +++++--- stream_chat/tests/test_query_threads.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stream_chat/base/query_threads.py b/stream_chat/base/query_threads.py index 3422c857..8f58d4b3 100644 --- a/stream_chat/base/query_threads.py +++ b/stream_chat/base/query_threads.py @@ -1,15 +1,17 @@ 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): - def __init__(self): - pass + @abc.abstractmethod + def __init__(self, client: StreamChatInterface): + self.client = client @property - def url(self): + def url(self) -> str: return "threads" @abc.abstractmethod diff --git a/stream_chat/tests/test_query_threads.py b/stream_chat/tests/test_query_threads.py index 09286250..1bdd543f 100644 --- a/stream_chat/tests/test_query_threads.py +++ b/stream_chat/tests/test_query_threads.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Dict import pytest From b431bf14dfb9de69891977b3a0e4c9653dbf0a34 Mon Sep 17 00:00:00 2001 From: Aditya Alif Nugraha Date: Fri, 11 Apr 2025 13:22:50 +0200 Subject: [PATCH 5/6] Fix test --- stream_chat/tests/async_chat/test_query_threads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stream_chat/tests/async_chat/test_query_threads.py b/stream_chat/tests/async_chat/test_query_threads.py index d1b753af..e0e82d21 100644 --- a/stream_chat/tests/async_chat/test_query_threads.py +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -26,7 +26,7 @@ async def test_query_threads( sort_conditions = [{"field": "created_at", "direction": -1}] response = await client.query_threads( - filter=filter_conditions, sort=sort_conditions + filter=filter_conditions, sort=sort_conditions, user_id=random_user["id"] ) assert isinstance(response, StreamResponse) @@ -66,6 +66,7 @@ async def test_query_threads_with_options( filter=filter_conditions, sort=sort_conditions, limit=1, + user_id=random_user["id"] ) assert isinstance(response, StreamResponse) From 012f2523e0a7860455387122ee0940953471a693 Mon Sep 17 00:00:00 2001 From: Lennart <1247198+totalimmersion@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:25:14 +0200 Subject: [PATCH 6/6] Update stream_chat/tests/async_chat/test_query_threads.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- stream_chat/tests/async_chat/test_query_threads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream_chat/tests/async_chat/test_query_threads.py b/stream_chat/tests/async_chat/test_query_threads.py index e0e82d21..c4a6bbbc 100644 --- a/stream_chat/tests/async_chat/test_query_threads.py +++ b/stream_chat/tests/async_chat/test_query_threads.py @@ -66,7 +66,7 @@ async def test_query_threads_with_options( filter=filter_conditions, sort=sort_conditions, limit=1, - user_id=random_user["id"] + user_id=random_user["id"], ) assert isinstance(response, StreamResponse)