From 459cccaaed001bcb8174f4cf6bad76e9ac1fdac3 Mon Sep 17 00:00:00 2001 From: Lennart Kuijs Date: Sun, 1 Feb 2026 21:38:15 +0100 Subject: [PATCH] [CHA-2354] Add ParsedPredefinedFilterResponse type Add type definition for the new `predefined_filter` field returned in QueryChannels responses when a predefined filter is used. - Added ParsedPredefinedFilterResponse TypedDict - Added unit tests for type definitions Co-Authored-By: Claude Opus 4.5 --- stream_chat/tests/test_types.py | 67 +++++++++++++++++++++++++++++++++ stream_chat/types/base.py | 19 +++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 stream_chat/tests/test_types.py diff --git a/stream_chat/tests/test_types.py b/stream_chat/tests/test_types.py new file mode 100644 index 00000000..76a096cc --- /dev/null +++ b/stream_chat/tests/test_types.py @@ -0,0 +1,67 @@ +"""Tests for type definitions.""" + +from stream_chat.types.base import ( + ParsedPredefinedFilterResponse, + SortOrder, + SortParam, +) + + +class TestParsedPredefinedFilterResponse: + """Tests for ParsedPredefinedFilterResponse type.""" + + def test_type_definition(self): + """Test that ParsedPredefinedFilterResponse can be used as a type hint.""" + response: ParsedPredefinedFilterResponse = { + "name": "user_messaging", + "filter": {"type": "messaging", "members": {"$in": ["user123"]}}, + "sort": [{"field": "last_message_at", "direction": SortOrder.DESC}], + } + + assert response["name"] == "user_messaging" + assert response["filter"]["type"] == "messaging" + assert response["sort"][0]["field"] == "last_message_at" + assert response["sort"][0]["direction"] == SortOrder.DESC + + def test_optional_sort(self): + """Test that sort is optional in ParsedPredefinedFilterResponse.""" + response: ParsedPredefinedFilterResponse = { + "name": "simple_filter", + "filter": {"type": "messaging"}, + } + + assert response["name"] == "simple_filter" + assert "sort" not in response + + def test_empty_filter(self): + """Test that filter can be empty dict.""" + response: ParsedPredefinedFilterResponse = { + "name": "empty_filter", + "filter": {}, + } + + assert response["filter"] == {} + + +class TestSortParam: + """Tests for SortParam type.""" + + def test_ascending_sort(self): + """Test ascending sort parameter.""" + sort: SortParam = { + "field": "created_at", + "direction": SortOrder.ASC, + } + + assert sort["field"] == "created_at" + assert sort["direction"] == 1 + + def test_descending_sort(self): + """Test descending sort parameter.""" + sort: SortParam = { + "field": "last_message_at", + "direction": SortOrder.DESC, + } + + assert sort["field"] == "last_message_at" + assert sort["direction"] == -1 diff --git a/stream_chat/types/base.py b/stream_chat/types/base.py index bf7742d3..9419d178 100644 --- a/stream_chat/types/base.py +++ b/stream_chat/types/base.py @@ -1,6 +1,6 @@ import sys from enum import IntEnum -from typing import Optional +from typing import Any, Dict, List, Optional if sys.version_info >= (3, 8): from typing import TypedDict @@ -43,3 +43,20 @@ class Pager(TypedDict, total=False): limit: Optional[int] next: Optional[str] prev: Optional[str] + + +class ParsedPredefinedFilterResponse(TypedDict, total=False): + """ + Represents the parsed/interpolated predefined filter returned in QueryChannels response. + + This is only present when a predefined filter is used in the query. + + Parameters: + name: The name of the predefined filter that was used. + filter: The interpolated filter with placeholders replaced by actual values. + sort: The interpolated sort parameters (optional). + """ + + name: str + filter: Dict[str, Any] + sort: Optional[List[SortParam]]