From d2b48e7f02ecdd7fd73e45bb291cce551ba7ffee Mon Sep 17 00:00:00 2001 From: kanat_k Date: Fri, 21 Feb 2025 15:37:56 -0500 Subject: [PATCH 1/4] [CHA-536] support campaign user pagination --- stream_chat/async_chat/campaign.py | 6 ++-- stream_chat/async_chat/client.py | 9 ++++-- stream_chat/base/campaign.py | 6 ++-- stream_chat/base/client.py | 10 ++++-- stream_chat/campaign.py | 6 ++-- stream_chat/client.py | 9 ++++-- stream_chat/tests/async_chat/test_campaign.py | 32 ++++++++++++++++++- stream_chat/tests/test_campaign.py | 32 ++++++++++++++++++- stream_chat/types/campaign.py | 12 ++++++- 9 files changed, 102 insertions(+), 20 deletions(-) diff --git a/stream_chat/async_chat/campaign.py b/stream_chat/async_chat/campaign.py index 4dfd8e9..5380ed1 100644 --- a/stream_chat/async_chat/campaign.py +++ b/stream_chat/async_chat/campaign.py @@ -2,7 +2,7 @@ from typing import Any, Optional, Union from stream_chat.base.campaign import CampaignInterface -from stream_chat.types.campaign import CampaignData +from stream_chat.types.campaign import CampaignData, GetCampaignOptions from stream_chat.types.stream_response import StreamResponse @@ -22,9 +22,9 @@ async def create( self.campaign_id = state["campaign"]["id"] return state - async def get(self) -> StreamResponse: + async def get(self, options: Optional[GetCampaignOptions] = None) -> StreamResponse: return await self.client.get_campaign( # type: ignore - campaign_id=self.campaign_id + campaign_id=self.campaign_id, options=options ) async def update(self, data: CampaignData) -> StreamResponse: diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index b7a1099..5446e6a 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -20,7 +20,7 @@ from stream_chat.async_chat.campaign import Campaign from stream_chat.async_chat.segment import Segment from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions +from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -664,8 +664,11 @@ async def create_campaign( payload.update(cast(dict, data)) return await self.post("campaigns", data=payload) - async def get_campaign(self, campaign_id: str) -> StreamResponse: - return await self.get(f"campaigns/{campaign_id}") + async def get_campaign(self, campaign_id: str, options: Optional[GetCampaignOptions] = None) -> StreamResponse: + params = {} + if options and "users" in options: + params.update(options["users"]) + return await self.get(f"campaigns/{campaign_id}", params) async def query_campaigns( self, diff --git a/stream_chat/base/campaign.py b/stream_chat/base/campaign.py index 70aba77..a91eb21 100644 --- a/stream_chat/base/campaign.py +++ b/stream_chat/base/campaign.py @@ -3,7 +3,7 @@ from typing import Awaitable, Optional, Union from stream_chat.base.client import StreamChatInterface -from stream_chat.types.campaign import CampaignData +from stream_chat.types.campaign import CampaignData, GetCampaignOptions from stream_chat.types.stream_response import StreamResponse @@ -25,7 +25,9 @@ def create( pass @abc.abstractmethod - def get(self) -> Union[StreamResponse, Awaitable[StreamResponse]]: + def get( + self, options: Optional[GetCampaignOptions] = None + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: pass @abc.abstractmethod diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index a8b624b..8cc1d15 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -8,7 +8,7 @@ from typing import Any, Awaitable, Dict, Iterable, List, Optional, TypeVar, Union from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions +from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -1084,10 +1084,14 @@ def create_campaign( @abc.abstractmethod def get_campaign( - self, campaign_id: str + self, campaign_id: str, options: Optional[GetCampaignOptions] = None ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ - Create a campaign + Get a campaign + + :param campaign_id: ID of the campaign to get + :param options: Optional parameters for the request + :return: Campaign data """ pass diff --git a/stream_chat/campaign.py b/stream_chat/campaign.py index b9eafea..2086935 100644 --- a/stream_chat/campaign.py +++ b/stream_chat/campaign.py @@ -2,7 +2,7 @@ from typing import Any, Optional, Union from stream_chat.base.campaign import CampaignInterface -from stream_chat.types.campaign import CampaignData +from stream_chat.types.campaign import CampaignData, GetCampaignOptions from stream_chat.types.stream_response import StreamResponse @@ -22,8 +22,8 @@ def create( self.campaign_id = state["campaign"]["id"] # type: ignore return state # type: ignore - def get(self) -> StreamResponse: - return self.client.get_campaign(campaign_id=self.campaign_id) # type: ignore + def get(self, options: Optional[GetCampaignOptions] = None) -> StreamResponse: + return self.client.get_campaign(campaign_id=self.campaign_id, options=options) # type: ignore def update(self, data: CampaignData) -> StreamResponse: return self.client.update_campaign( # type: ignore diff --git a/stream_chat/client.py b/stream_chat/client.py index 85c1e8b..ce8face 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -9,7 +9,7 @@ from stream_chat.campaign import Campaign from stream_chat.segment import Segment from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions +from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -636,8 +636,11 @@ def create_campaign( payload.update(cast(dict, data)) return self.post("campaigns", data=payload) - def get_campaign(self, campaign_id: str) -> StreamResponse: - return self.get(f"campaigns/{campaign_id}") + def get_campaign(self, campaign_id: str, options: Optional[GetCampaignOptions] = None) -> StreamResponse: + params = {} + if options and "users" in options: + params.update(options["users"]) + return self.get(f"campaigns/{campaign_id}", params) def query_campaigns( self, diff --git a/stream_chat/tests/async_chat/test_campaign.py b/stream_chat/tests/async_chat/test_campaign.py index 24be9dc..7f3d8b1 100644 --- a/stream_chat/tests/async_chat/test_campaign.py +++ b/stream_chat/tests/async_chat/test_campaign.py @@ -1,5 +1,5 @@ import datetime -from typing import Dict +from typing import Dict, List import pytest @@ -61,6 +61,36 @@ async def test_campaign_crud(self, client: StreamChatAsync, random_user: Dict): await client.delete_segment(segment_id=segment_id) + async def test_get_campaign_with_user_pagination(self, client: StreamChatAsync, random_users: List[Dict]): + # Create a campaign with user_ids + campaign = client.campaign( + data={ + "message_template": { + "text": "Test message", + }, + "user_ids": [user["id"] for user in random_users], + "sender_id": random_users[0]["id"], + "name": "test campaign with users", + } + ) + created = await campaign.create() + assert created.is_ok() + campaign_id = created["campaign"]["id"] + + # Test get_campaign with user pagination options + response = await client.get_campaign( + campaign_id=campaign_id, + options={"users": {"limit": 2}} # Limit to 2 users per page + ) + assert response.is_ok() + assert "campaign" in response + assert response["campaign"]["id"] == campaign_id + assert "users" in response["campaign"] + assert len(response["campaign"]["users"]) <= 2 # Verify pagination limit worked + + # Cleanup + await client.delete_campaign(campaign_id=campaign_id) + async def test_campaign_start_stop( self, client: StreamChatAsync, random_user: Dict ): diff --git a/stream_chat/tests/test_campaign.py b/stream_chat/tests/test_campaign.py index 3083500..13d6b2e 100644 --- a/stream_chat/tests/test_campaign.py +++ b/stream_chat/tests/test_campaign.py @@ -1,5 +1,5 @@ import datetime -from typing import Dict +from typing import Dict, List import pytest @@ -67,6 +67,36 @@ def test_campaign_crud(self, client: StreamChat, random_user: Dict): segment_deleted = client.delete_segment(segment_id=segment_id) assert segment_deleted.is_ok() + def test_get_campaign_with_user_pagination(self, client: StreamChat, random_users: List[Dict]): + # Create a campaign with user_ids + campaign = client.campaign( + data={ + "message_template": { + "text": "Test message", + }, + "user_ids": [user["id"] for user in random_users], + "sender_id": random_users[0]["id"], + "name": "test campaign with users", + } + ) + created = campaign.create() + assert created.is_ok() + campaign_id = created["campaign"]["id"] + + # Test get_campaign with user pagination options + response = client.get_campaign( + campaign_id=campaign_id, + options={"users": {"limit": 2}} # Limit to 2 users per page + ) + assert response.is_ok() + assert "campaign" in response + assert response["campaign"]["id"] == campaign_id + assert "users" in response["campaign"] + assert len(response["campaign"]["users"]) <= 2 # Verify pagination limit worked + + # Cleanup + client.delete_campaign(campaign_id=campaign_id) + def test_campaign_start_stop(self, client: StreamChat, random_user: Dict): segment = client.create_segment(segment_type=SegmentType.USER) segment_id = segment["segment"]["id"] diff --git a/stream_chat/types/campaign.py b/stream_chat/types/campaign.py index bc1b730..32cac51 100644 --- a/stream_chat/types/campaign.py +++ b/stream_chat/types/campaign.py @@ -1,5 +1,5 @@ import sys -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union if sys.version_info >= (3, 8): from typing import TypedDict @@ -76,3 +76,13 @@ class CampaignData(TypedDict, total=False): class QueryCampaignsOptions(Pager, total=False): pass + + +class GetCampaignOptions(TypedDict, total=False): + """ + Options for getting a campaign. + + Parameters: + users: Optional Pager containing pagination options for users + """ + users: Optional[Pager] From 6496b4bfbaacf49069fd5ea3dc2f6e5b9a7c1e1b Mon Sep 17 00:00:00 2001 From: kanat_k Date: Fri, 21 Feb 2025 15:46:27 -0500 Subject: [PATCH 2/4] use campaign resource object in tests --- stream_chat/tests/async_chat/test_campaign.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stream_chat/tests/async_chat/test_campaign.py b/stream_chat/tests/async_chat/test_campaign.py index 7f3d8b1..aa2a1e8 100644 --- a/stream_chat/tests/async_chat/test_campaign.py +++ b/stream_chat/tests/async_chat/test_campaign.py @@ -78,8 +78,7 @@ async def test_get_campaign_with_user_pagination(self, client: StreamChatAsync, campaign_id = created["campaign"]["id"] # Test get_campaign with user pagination options - response = await client.get_campaign( - campaign_id=campaign_id, + response = await campaign.get( options={"users": {"limit": 2}} # Limit to 2 users per page ) assert response.is_ok() @@ -89,7 +88,7 @@ async def test_get_campaign_with_user_pagination(self, client: StreamChatAsync, assert len(response["campaign"]["users"]) <= 2 # Verify pagination limit worked # Cleanup - await client.delete_campaign(campaign_id=campaign_id) + await campaign.delete() async def test_campaign_start_stop( self, client: StreamChatAsync, random_user: Dict From 6399c393bbad0773130d616656ccb74766093a75 Mon Sep 17 00:00:00 2001 From: kanat_k Date: Fri, 21 Feb 2025 15:46:32 -0500 Subject: [PATCH 3/4] use campaign resource object in tests --- stream_chat/tests/test_campaign.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stream_chat/tests/test_campaign.py b/stream_chat/tests/test_campaign.py index 13d6b2e..dda95ec 100644 --- a/stream_chat/tests/test_campaign.py +++ b/stream_chat/tests/test_campaign.py @@ -84,8 +84,7 @@ def test_get_campaign_with_user_pagination(self, client: StreamChat, random_user campaign_id = created["campaign"]["id"] # Test get_campaign with user pagination options - response = client.get_campaign( - campaign_id=campaign_id, + response = campaign.get( options={"users": {"limit": 2}} # Limit to 2 users per page ) assert response.is_ok() @@ -95,7 +94,7 @@ def test_get_campaign_with_user_pagination(self, client: StreamChat, random_user assert len(response["campaign"]["users"]) <= 2 # Verify pagination limit worked # Cleanup - client.delete_campaign(campaign_id=campaign_id) + campaign.delete() def test_campaign_start_stop(self, client: StreamChat, random_user: Dict): segment = client.create_segment(segment_type=SegmentType.USER) From 63c00a30714c648583be4850c395833e1e0331ec Mon Sep 17 00:00:00 2001 From: Kanat Kiialbaev Date: Tue, 25 Feb 2025 12:31:45 -0500 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- stream_chat/async_chat/client.py | 10 ++++++++-- stream_chat/base/client.py | 6 +++++- stream_chat/client.py | 10 ++++++++-- stream_chat/tests/async_chat/test_campaign.py | 4 +++- stream_chat/tests/test_campaign.py | 4 +++- stream_chat/types/campaign.py | 1 + 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 5446e6a..bb8865c 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -20,7 +20,11 @@ from stream_chat.async_chat.campaign import Campaign from stream_chat.async_chat.segment import Segment from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions +from stream_chat.types.campaign import ( + CampaignData, + QueryCampaignsOptions, + GetCampaignOptions, +) from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -664,7 +668,9 @@ async def create_campaign( payload.update(cast(dict, data)) return await self.post("campaigns", data=payload) - async def get_campaign(self, campaign_id: str, options: Optional[GetCampaignOptions] = None) -> StreamResponse: + async def get_campaign( + self, campaign_id: str, options: Optional[GetCampaignOptions] = None + ) -> StreamResponse: params = {} if options and "users" in options: params.update(options["users"]) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 8cc1d15..37c4620 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -8,7 +8,11 @@ from typing import Any, Awaitable, Dict, Iterable, List, Optional, TypeVar, Union from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions +from stream_chat.types.campaign import ( + CampaignData, + QueryCampaignsOptions, + GetCampaignOptions, +) from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, diff --git a/stream_chat/client.py b/stream_chat/client.py index ce8face..72f681f 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -9,7 +9,11 @@ from stream_chat.campaign import Campaign from stream_chat.segment import Segment from stream_chat.types.base import SortParam -from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions, GetCampaignOptions +from stream_chat.types.campaign import ( + CampaignData, + QueryCampaignsOptions, + GetCampaignOptions, +) from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -636,7 +640,9 @@ def create_campaign( payload.update(cast(dict, data)) return self.post("campaigns", data=payload) - def get_campaign(self, campaign_id: str, options: Optional[GetCampaignOptions] = None) -> StreamResponse: + def get_campaign( + self, campaign_id: str, options: Optional[GetCampaignOptions] = None + ) -> StreamResponse: params = {} if options and "users" in options: params.update(options["users"]) diff --git a/stream_chat/tests/async_chat/test_campaign.py b/stream_chat/tests/async_chat/test_campaign.py index aa2a1e8..21f055e 100644 --- a/stream_chat/tests/async_chat/test_campaign.py +++ b/stream_chat/tests/async_chat/test_campaign.py @@ -61,7 +61,9 @@ async def test_campaign_crud(self, client: StreamChatAsync, random_user: Dict): await client.delete_segment(segment_id=segment_id) - async def test_get_campaign_with_user_pagination(self, client: StreamChatAsync, random_users: List[Dict]): + async def test_get_campaign_with_user_pagination( + self, client: StreamChatAsync, random_users: List[Dict] + ): # Create a campaign with user_ids campaign = client.campaign( data={ diff --git a/stream_chat/tests/test_campaign.py b/stream_chat/tests/test_campaign.py index dda95ec..7e8ae13 100644 --- a/stream_chat/tests/test_campaign.py +++ b/stream_chat/tests/test_campaign.py @@ -67,7 +67,9 @@ def test_campaign_crud(self, client: StreamChat, random_user: Dict): segment_deleted = client.delete_segment(segment_id=segment_id) assert segment_deleted.is_ok() - def test_get_campaign_with_user_pagination(self, client: StreamChat, random_users: List[Dict]): + def test_get_campaign_with_user_pagination( + self, client: StreamChat, random_users: List[Dict] + ): # Create a campaign with user_ids campaign = client.campaign( data={ diff --git a/stream_chat/types/campaign.py b/stream_chat/types/campaign.py index 32cac51..05f4da9 100644 --- a/stream_chat/types/campaign.py +++ b/stream_chat/types/campaign.py @@ -85,4 +85,5 @@ class GetCampaignOptions(TypedDict, total=False): Parameters: users: Optional Pager containing pagination options for users """ + users: Optional[Pager]