From 5e035720fc4c9d93a3b85bdf8d524f689d5c6953 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Tue, 10 Jun 2025 09:18:53 +0200 Subject: [PATCH 01/11] feat(cha-769): add support to live locations --- stream_chat/async_chat/client.py | 14 ++++++++++++++ stream_chat/base/client.py | 15 +++++++++++++++ stream_chat/types/shared_locations.py | 8 ++++++++ 3 files changed, 37 insertions(+) create mode 100644 stream_chat/types/shared_locations.py diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 31cb612..0a5c090 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -22,6 +22,7 @@ from stream_chat.types.base import SortParam from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions from stream_chat.types.draft import QueryDraftsFilter, QueryDraftsOptions +from stream_chat.types.shared_locations import SharedLocationsOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -884,3 +885,16 @@ async def __aexit__( exc_tb: Optional[TracebackType], ) -> None: await self.close() + + async def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: + params = {"user_id": user_id, **options} + return await self.get("users/live_locations", params=params) + + async def update_user_location( + self, message_id: str, options: Optional[SharedLocationsOptions] = None, + ) -> StreamResponse: + data = {"message_id": message_id, **options} + if options is not None: + data.update(cast(dict, options)) + return await self.put("users/live_locations", data=data) + diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 5aa5668..56fedb7 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -10,6 +10,7 @@ from stream_chat.types.base import SortParam from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions from stream_chat.types.draft import QueryDraftsFilter, QueryDraftsOptions +from stream_chat.types.shared_locations import SharedLocationsOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -1439,6 +1440,20 @@ def query_drafts( ) -> Union[StreamResponse, Awaitable[StreamResponse]]: pass + @abc.abstractmethod + def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: + """ + Get the locations of a user. + """ + pass + + @abc.abstractmethod + def update_user_location(self, message_id: str, options: Optional[SharedLocationsOptions] = None) -> StreamResponse: + """ + Update the location of a user. + """ + pass + ##################### # Private methods # ##################### diff --git a/stream_chat/types/shared_locations.py b/stream_chat/types/shared_locations.py new file mode 100644 index 0000000..aa4b1c0 --- /dev/null +++ b/stream_chat/types/shared_locations.py @@ -0,0 +1,8 @@ +from datetime import datetime +from typing import Optional, TypedDict + + +class SharedLocationsOptions(TypedDict): + longitude: Optional[int] + latitude: Optional[int] + end_at: Optional[datetime] \ No newline at end of file From 575fb3781ccf007c0fa78dff6587303309f8ea61 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Tue, 10 Jun 2025 09:37:35 +0200 Subject: [PATCH 02/11] test(cha-769): add tests --- .../tests/async_chat/test_live_locations.py | 58 +++++++++++++++++ stream_chat/tests/test_live_locations.py | 65 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 stream_chat/tests/async_chat/test_live_locations.py create mode 100644 stream_chat/tests/test_live_locations.py diff --git a/stream_chat/tests/async_chat/test_live_locations.py b/stream_chat/tests/async_chat/test_live_locations.py new file mode 100644 index 0000000..80c1cb8 --- /dev/null +++ b/stream_chat/tests/async_chat/test_live_locations.py @@ -0,0 +1,58 @@ +import pytest +from typing import Dict + +from stream_chat.async_chat.client import StreamChatAsync + + +@pytest.mark.incremental +class TestLiveLocations: + async def test_get_user_locations(self, client: StreamChatAsync, random_user: Dict): + # First create a message to attach location to + channel = client.channel("messaging", str(random_user["id"])) + channel.create(random_user["id"]) + + # Create a message to attach location to + shared_location = { + "latitude": 37.7749, + "longitude": -122.4194, + } + + channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + + # Get user locations + response = await client.get_user_locations(random_user["id"]) + + assert "active_live_locations" in response + assert isinstance(response["active_live_locations"], list) + + async def test_update_user_location(self, client: StreamChatAsync, random_user: Dict): + # First create a message to attach location to + channel = client.channel("messaging", str(random_user["id"])) + await channel.create(random_user["id"]) + msg = await channel.send_message({"text": "Message with location"}, random_user["id"]) + message_id = msg["message"]["id"] + + # Update user location + location_data = { + "latitude": 37.7749, + "longitude": -122.4194, + } + response = await client.update_user_location(message_id, location_data) + + assert "shared_location" in response + assert response["shared_location"]["latitude"] == location_data["latitude"] + assert response["shared_location"]["longitude"] == location_data["longitude"] + + # Get user locations to verify + locations_response = await client.get_user_locations(random_user["id"]) + assert "active_live_locations" in locations_response + assert len(locations_response["active_live_locations"]) > 0 + location = locations_response["active_live_locations"][0] + assert location["latitude"] == location_data["latitude"] + assert location["longitude"] == location_data["longitude"] + + # Cleanup + try: + await channel.delete() + except Exception: + pass \ No newline at end of file diff --git a/stream_chat/tests/test_live_locations.py b/stream_chat/tests/test_live_locations.py new file mode 100644 index 0000000..76f4153 --- /dev/null +++ b/stream_chat/tests/test_live_locations.py @@ -0,0 +1,65 @@ +import pytest +from typing import Dict + +from stream_chat import StreamChat + + +@pytest.mark.incremental +class TestLiveLocations: + def test_get_user_locations(self, client: StreamChat, random_user: Dict): + # First create a message to attach location to + channel = client.channel("messaging", str(random_user["id"])) + channel.create(random_user["id"]) + + # Create a message to attach location to + shared_location = { + "latitude": 37.7749, + "longitude": -122.4194, + } + + channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + + # Get user locations + response = client.get_user_locations(random_user["id"]) + + assert "active_live_locations" in response + assert isinstance(response["active_live_locations"], list) + + def test_update_user_location(self, client: StreamChat, random_user: Dict): + # First create a message to attach location to + channel = client.channel("messaging", str(random_user["id"])) + channel.create(random_user["id"]) + + # Create a message to attach location to + shared_location = { + "latitude": 37.7749, + "longitude": -122.4194, + } + + msg = channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + message_id = msg["message"]["id"] + + # Update user location + location_data = { + "latitude": 37.7749, + "longitude": -122.4194, + } + response = client.update_user_location(message_id, location_data) + + assert "shared_location" in response + assert response["shared_location"]["latitude"] == location_data["latitude"] + assert response["shared_location"]["longitude"] == location_data["longitude"] + + # Get user locations to verify + locations_response = client.get_user_locations(random_user["id"]) + assert "active_live_locations" in locations_response + assert len(locations_response["active_live_locations"]) > 0 + location = locations_response["active_live_locations"][0] + assert location["latitude"] == location_data["latitude"] + assert location["longitude"] == location_data["longitude"] + + # Cleanup + try: + channel.delete() + except Exception: + pass \ No newline at end of file From 563deebf5ffb14da74391fcf709c13600ae2ae07 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Wed, 11 Jun 2025 14:27:49 +0200 Subject: [PATCH 03/11] feat(cha-769): support shared locations --- stream_chat/async_chat/client.py | 29 +++++++++++++---------------- stream_chat/client.py | 13 +++++++++++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 0a5c090..4a2dad2 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -860,18 +860,26 @@ async def query_drafts( data: Dict[str, Union[str, Dict[str, Any], List[SortParam]]] = { "user_id": user_id } - if filter is not None: data["filter"] = cast(dict, filter) - if sort is not None: data["sort"] = cast(dict, sort) - if options is not None: data.update(cast(dict, options)) - return await self.post("drafts/query", data=data) + async def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: + params = {"user_id": user_id, **options} + return await self.get("users/live_locations", params=params) + + async def update_user_location( + self, message_id: str, options: Optional[SharedLocationsOptions] = None, + ) -> StreamResponse: + data = {"message_id": message_id} + if options is not None: + data.update(cast(dict, options)) + return await self.put("users/live_locations", data=data) + async def close(self) -> None: await self.session.close() @@ -886,15 +894,4 @@ async def __aexit__( ) -> None: await self.close() - async def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: - params = {"user_id": user_id, **options} - return await self.get("users/live_locations", params=params) - - async def update_user_location( - self, message_id: str, options: Optional[SharedLocationsOptions] = None, - ) -> StreamResponse: - data = {"message_id": message_id, **options} - if options is not None: - data.update(cast(dict, options)) - return await self.put("users/live_locations", data=data) - + \ No newline at end of file diff --git a/stream_chat/client.py b/stream_chat/client.py index 67faed7..91b0ef8 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -18,6 +18,7 @@ SegmentType, SegmentUpdatableFields, ) +from stream_chat.types.shared_locations import SharedLocationsOptions if sys.version_info >= (3, 8): from typing import Literal @@ -824,3 +825,15 @@ def query_drafts( if options is not None: data.update(cast(dict, options)) return self.post("drafts/query", data=data) + + def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: + params = {"user_id": user_id, **options} + return self.get("users/live_locations", params=params) + + def update_user_location( + self, message_id: str, options: Optional[SharedLocationsOptions] = None, + ) -> StreamResponse: + data = {"message_id": message_id} + if options is not None: + data.update(cast(dict, options)) + return self.put("users/live_locations", data=data) From 75bcdf58622fde36bc82c2596679ddb514c9cca6 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Wed, 11 Jun 2025 14:31:01 +0200 Subject: [PATCH 04/11] refact(cha-769): reformat --- stream_chat/async_chat/client.py | 6 +++--- stream_chat/base/client.py | 4 +++- stream_chat/client.py | 4 +++- .../tests/async_chat/test_live_locations.py | 15 +++++++++++---- stream_chat/tests/test_live_locations.py | 12 +++++++++--- stream_chat/types/shared_locations.py | 2 +- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 4a2dad2..83e54de 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -873,7 +873,9 @@ async def get_user_locations(self, user_id: str, **options: Any) -> StreamRespon return await self.get("users/live_locations", params=params) async def update_user_location( - self, message_id: str, options: Optional[SharedLocationsOptions] = None, + self, + message_id: str, + options: Optional[SharedLocationsOptions] = None, ) -> StreamResponse: data = {"message_id": message_id} if options is not None: @@ -893,5 +895,3 @@ async def __aexit__( exc_tb: Optional[TracebackType], ) -> None: await self.close() - - \ No newline at end of file diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 56fedb7..fe86fb7 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1448,7 +1448,9 @@ def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: pass @abc.abstractmethod - def update_user_location(self, message_id: str, options: Optional[SharedLocationsOptions] = None) -> StreamResponse: + def update_user_location( + self, message_id: str, options: Optional[SharedLocationsOptions] = None + ) -> StreamResponse: """ Update the location of a user. """ diff --git a/stream_chat/client.py b/stream_chat/client.py index 91b0ef8..9f2c0ab 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -831,7 +831,9 @@ def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: return self.get("users/live_locations", params=params) def update_user_location( - self, message_id: str, options: Optional[SharedLocationsOptions] = None, + self, + message_id: str, + options: Optional[SharedLocationsOptions] = None, ) -> StreamResponse: data = {"message_id": message_id} if options is not None: diff --git a/stream_chat/tests/async_chat/test_live_locations.py b/stream_chat/tests/async_chat/test_live_locations.py index 80c1cb8..0433ac4 100644 --- a/stream_chat/tests/async_chat/test_live_locations.py +++ b/stream_chat/tests/async_chat/test_live_locations.py @@ -17,7 +17,10 @@ async def test_get_user_locations(self, client: StreamChatAsync, random_user: Di "longitude": -122.4194, } - channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + channel.send_message( + {"text": "Message with location", "shared_location": shared_location}, + random_user["id"], + ) # Get user locations response = await client.get_user_locations(random_user["id"]) @@ -25,11 +28,15 @@ async def test_get_user_locations(self, client: StreamChatAsync, random_user: Di assert "active_live_locations" in response assert isinstance(response["active_live_locations"], list) - async def test_update_user_location(self, client: StreamChatAsync, random_user: Dict): + async def test_update_user_location( + self, client: StreamChatAsync, random_user: Dict + ): # First create a message to attach location to channel = client.channel("messaging", str(random_user["id"])) await channel.create(random_user["id"]) - msg = await channel.send_message({"text": "Message with location"}, random_user["id"]) + msg = await channel.send_message( + {"text": "Message with location"}, random_user["id"] + ) message_id = msg["message"]["id"] # Update user location @@ -55,4 +62,4 @@ async def test_update_user_location(self, client: StreamChatAsync, random_user: try: await channel.delete() except Exception: - pass \ No newline at end of file + pass diff --git a/stream_chat/tests/test_live_locations.py b/stream_chat/tests/test_live_locations.py index 76f4153..a9435ab 100644 --- a/stream_chat/tests/test_live_locations.py +++ b/stream_chat/tests/test_live_locations.py @@ -17,7 +17,10 @@ def test_get_user_locations(self, client: StreamChat, random_user: Dict): "longitude": -122.4194, } - channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + channel.send_message( + {"text": "Message with location", "shared_location": shared_location}, + random_user["id"], + ) # Get user locations response = client.get_user_locations(random_user["id"]) @@ -36,7 +39,10 @@ def test_update_user_location(self, client: StreamChat, random_user: Dict): "longitude": -122.4194, } - msg = channel.send_message({"text": "Message with location", "shared_location": shared_location}, random_user["id"]) + msg = channel.send_message( + {"text": "Message with location", "shared_location": shared_location}, + random_user["id"], + ) message_id = msg["message"]["id"] # Update user location @@ -62,4 +68,4 @@ def test_update_user_location(self, client: StreamChat, random_user: Dict): try: channel.delete() except Exception: - pass \ No newline at end of file + pass diff --git a/stream_chat/types/shared_locations.py b/stream_chat/types/shared_locations.py index aa4b1c0..1076531 100644 --- a/stream_chat/types/shared_locations.py +++ b/stream_chat/types/shared_locations.py @@ -5,4 +5,4 @@ class SharedLocationsOptions(TypedDict): longitude: Optional[int] latitude: Optional[int] - end_at: Optional[datetime] \ No newline at end of file + end_at: Optional[datetime] From 34792f318f2efed347b471c25bd1bbf5c3016a1b Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Wed, 11 Jun 2025 14:33:36 +0200 Subject: [PATCH 05/11] refact(cha-769): lint --- stream_chat/async_chat/client.py | 2 +- stream_chat/base/client.py | 2 +- stream_chat/tests/async_chat/test_live_locations.py | 3 ++- stream_chat/tests/test_live_locations.py | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 83e54de..cf8ca6f 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -22,7 +22,6 @@ from stream_chat.types.base import SortParam from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions from stream_chat.types.draft import QueryDraftsFilter, QueryDraftsOptions -from stream_chat.types.shared_locations import SharedLocationsOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -30,6 +29,7 @@ SegmentType, SegmentUpdatableFields, ) +from stream_chat.types.shared_locations import SharedLocationsOptions if sys.version_info >= (3, 8): from typing import Literal diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index fe86fb7..704b28e 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -10,7 +10,6 @@ from stream_chat.types.base import SortParam from stream_chat.types.campaign import CampaignData, QueryCampaignsOptions from stream_chat.types.draft import QueryDraftsFilter, QueryDraftsOptions -from stream_chat.types.shared_locations import SharedLocationsOptions from stream_chat.types.segment import ( QuerySegmentsOptions, QuerySegmentTargetsOptions, @@ -18,6 +17,7 @@ SegmentType, SegmentUpdatableFields, ) +from stream_chat.types.shared_locations import SharedLocationsOptions if sys.version_info >= (3, 8): from typing import Literal diff --git a/stream_chat/tests/async_chat/test_live_locations.py b/stream_chat/tests/async_chat/test_live_locations.py index 0433ac4..c815667 100644 --- a/stream_chat/tests/async_chat/test_live_locations.py +++ b/stream_chat/tests/async_chat/test_live_locations.py @@ -1,6 +1,7 @@ -import pytest from typing import Dict +import pytest + from stream_chat.async_chat.client import StreamChatAsync diff --git a/stream_chat/tests/test_live_locations.py b/stream_chat/tests/test_live_locations.py index a9435ab..8a8b696 100644 --- a/stream_chat/tests/test_live_locations.py +++ b/stream_chat/tests/test_live_locations.py @@ -1,6 +1,7 @@ -import pytest from typing import Dict +import pytest + from stream_chat import StreamChat From a5d54e8b344356e0d0c9b6697653d9317282cecb Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Wed, 11 Jun 2025 14:37:07 +0200 Subject: [PATCH 06/11] fix(cha-769): mypy fix apply --- stream_chat/base/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 704b28e..014f503 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1441,7 +1441,7 @@ def query_drafts( pass @abc.abstractmethod - def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: + def get_user_locations(self, user_id: str, **options: Any) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Get the locations of a user. """ @@ -1450,7 +1450,7 @@ def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: @abc.abstractmethod def update_user_location( self, message_id: str, options: Optional[SharedLocationsOptions] = None - ) -> StreamResponse: + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Update the location of a user. """ From e59b421a3c8a57bdd33c0cf4243316eb0b0b137c Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Wed, 11 Jun 2025 14:41:31 +0200 Subject: [PATCH 07/11] fix(cha-769): lint --- stream_chat/base/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 014f503..fec7883 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1441,7 +1441,9 @@ def query_drafts( pass @abc.abstractmethod - def get_user_locations(self, user_id: str, **options: Any) -> Union[StreamResponse, Awaitable[StreamResponse]]: + def get_user_locations( + self, user_id: str, **options: Any + ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Get the locations of a user. """ From 7e3a0ae7fd69119620b145bb23537364bcd733f1 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Mon, 7 Jul 2025 10:23:48 +0200 Subject: [PATCH 08/11] lint(cha-648) fix lint --- stream_chat/base/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index 7ce73a3..f2c453a 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1505,10 +1505,10 @@ def query_reminders( :return: API response with reminders """ pass - + @abc.abstractmethod def get_user_locations( - self, user_id: str, **options: Any + self, user_id: str, **options: Any ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Get the locations of a user. From 1a34fc81822d20ab91fe2846aa51e07504cc5f4d Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Mon, 7 Jul 2025 10:33:44 +0200 Subject: [PATCH 09/11] tests(cha-648) fix unit tests --- stream_chat/tests/test_live_locations.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/stream_chat/tests/test_live_locations.py b/stream_chat/tests/test_live_locations.py index 8a8b696..5d42038 100644 --- a/stream_chat/tests/test_live_locations.py +++ b/stream_chat/tests/test_live_locations.py @@ -1,3 +1,4 @@ +import datetime from typing import Dict import pytest @@ -13,9 +14,13 @@ def test_get_user_locations(self, client: StreamChat, random_user: Dict): channel.create(random_user["id"]) # Create a message to attach location to + now = datetime.datetime.now(datetime.timezone.utc) + one_hour_later = now + datetime.timedelta(hours=1) shared_location = { + "created_by_device_id": "test_device_id", "latitude": 37.7749, "longitude": -122.4194, + "end_at": one_hour_later.isoformat(), } channel.send_message( @@ -35,9 +40,13 @@ def test_update_user_location(self, client: StreamChat, random_user: Dict): channel.create(random_user["id"]) # Create a message to attach location to + now = datetime.datetime.now(datetime.timezone.utc) + one_hour_later = now + datetime.timedelta(hours=1) shared_location = { + "created_by_device_id": "test_device_id", "latitude": 37.7749, "longitude": -122.4194, + "end_at": one_hour_later.isoformat(), } msg = channel.send_message( From edbb0f8607e6015e0b3b5bb1d6474f06e3fcaed8 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Mon, 7 Jul 2025 11:18:01 +0200 Subject: [PATCH 10/11] tests(cha-648) fix unit tests --- stream_chat/async_chat/client.py | 4 +- stream_chat/base/client.py | 2 +- stream_chat/client.py | 4 +- .../tests/async_chat/test_live_locations.py | 56 ++++++++++++------- stream_chat/tests/test_live_locations.py | 36 ++++++------ 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/stream_chat/async_chat/client.py b/stream_chat/async_chat/client.py index 52b1808..112cb8e 100644 --- a/stream_chat/async_chat/client.py +++ b/stream_chat/async_chat/client.py @@ -959,13 +959,15 @@ async def get_user_locations(self, user_id: str, **options: Any) -> StreamRespon async def update_user_location( self, + user_id: str, message_id: str, options: Optional[SharedLocationsOptions] = None, ) -> StreamResponse: data = {"message_id": message_id} if options is not None: data.update(cast(dict, options)) - return await self.put("users/live_locations", data=data) + params = {"user_id": user_id, **options} + return await self.put("users/live_locations", data=data, params=params) async def close(self) -> None: await self.session.close() diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index f2c453a..ee8ce3a 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1517,7 +1517,7 @@ def get_user_locations( @abc.abstractmethod def update_user_location( - self, message_id: str, options: Optional[SharedLocationsOptions] = None + self, user_id: str, message_id: str, options: Optional[SharedLocationsOptions] = None ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Update the location of a user. diff --git a/stream_chat/client.py b/stream_chat/client.py index b785b1d..1093b4e 100644 --- a/stream_chat/client.py +++ b/stream_chat/client.py @@ -906,10 +906,12 @@ def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse: def update_user_location( self, + user_id: str, message_id: str, options: Optional[SharedLocationsOptions] = None, ) -> StreamResponse: data = {"message_id": message_id} if options is not None: data.update(cast(dict, options)) - return self.put("users/live_locations", data=data) + params = {"user_id": user_id, **options} + return self.put("users/live_locations", data=data, params=params) diff --git a/stream_chat/tests/async_chat/test_live_locations.py b/stream_chat/tests/async_chat/test_live_locations.py index c815667..3b1885b 100644 --- a/stream_chat/tests/async_chat/test_live_locations.py +++ b/stream_chat/tests/async_chat/test_live_locations.py @@ -1,3 +1,4 @@ +import datetime from typing import Dict import pytest @@ -7,15 +8,28 @@ @pytest.mark.incremental class TestLiveLocations: - async def test_get_user_locations(self, client: StreamChatAsync, random_user: Dict): - # First create a message to attach location to - channel = client.channel("messaging", str(random_user["id"])) - channel.create(random_user["id"]) + @pytest.fixture(autouse=True) + @pytest.mark.asyncio + async def setup_channel_for_shared_locations(self, channel): + await channel.update_partial( + {"config_overrides": {"shared_locations": True}}, + ) + yield + await channel.update_partial( + {"config_overrides": {"shared_locations": False}}, + ) + async def test_get_user_locations( + self, client: StreamChatAsync, channel, random_user: Dict + ): # Create a message to attach location to + now = datetime.datetime.now(datetime.timezone.utc) + one_hour_later = now + datetime.timedelta(hours=1) shared_location = { + "created_by_device_id": "test_device_id", "latitude": 37.7749, "longitude": -122.4194, + "end_at": one_hour_later.isoformat(), } channel.send_message( @@ -30,26 +44,36 @@ async def test_get_user_locations(self, client: StreamChatAsync, random_user: Di assert isinstance(response["active_live_locations"], list) async def test_update_user_location( - self, client: StreamChatAsync, random_user: Dict + self, client: StreamChatAsync, channel, random_user: Dict ): - # First create a message to attach location to - channel = client.channel("messaging", str(random_user["id"])) - await channel.create(random_user["id"]) + # Create a message to attach location to + now = datetime.datetime.now(datetime.timezone.utc) + one_hour_later = now + datetime.timedelta(hours=1) + shared_location = { + "created_by_device_id": "test_device_id", + "latitude": 37.7749, + "longitude": -122.4194, + "end_at": one_hour_later.isoformat(), + } + msg = await channel.send_message( - {"text": "Message with location"}, random_user["id"] + {"text": "Message with location", "shared_location": shared_location}, + random_user["id"], ) message_id = msg["message"]["id"] # Update user location location_data = { + "created_by_device_id": "test_device_id", "latitude": 37.7749, "longitude": -122.4194, } - response = await client.update_user_location(message_id, location_data) + response = await client.update_user_location( + random_user["id"], message_id, location_data + ) - assert "shared_location" in response - assert response["shared_location"]["latitude"] == location_data["latitude"] - assert response["shared_location"]["longitude"] == location_data["longitude"] + assert response["latitude"] == location_data["latitude"] + assert response["longitude"] == location_data["longitude"] # Get user locations to verify locations_response = await client.get_user_locations(random_user["id"]) @@ -58,9 +82,3 @@ async def test_update_user_location( location = locations_response["active_live_locations"][0] assert location["latitude"] == location_data["latitude"] assert location["longitude"] == location_data["longitude"] - - # Cleanup - try: - await channel.delete() - except Exception: - pass diff --git a/stream_chat/tests/test_live_locations.py b/stream_chat/tests/test_live_locations.py index 5d42038..e26ca23 100644 --- a/stream_chat/tests/test_live_locations.py +++ b/stream_chat/tests/test_live_locations.py @@ -8,11 +8,17 @@ @pytest.mark.incremental class TestLiveLocations: - def test_get_user_locations(self, client: StreamChat, random_user: Dict): - # First create a message to attach location to - channel = client.channel("messaging", str(random_user["id"])) - channel.create(random_user["id"]) + @pytest.fixture(autouse=True) + def setup_channel_for_shared_locations(self, channel): + channel.update_partial( + {"config_overrides": {"shared_locations": True}}, + ) + yield + channel.update_partial( + {"config_overrides": {"shared_locations": False}}, + ) + def test_get_user_locations(self, client: StreamChat, channel, random_user: Dict): # Create a message to attach location to now = datetime.datetime.now(datetime.timezone.utc) one_hour_later = now + datetime.timedelta(hours=1) @@ -34,11 +40,7 @@ def test_get_user_locations(self, client: StreamChat, random_user: Dict): assert "active_live_locations" in response assert isinstance(response["active_live_locations"], list) - def test_update_user_location(self, client: StreamChat, random_user: Dict): - # First create a message to attach location to - channel = client.channel("messaging", str(random_user["id"])) - channel.create(random_user["id"]) - + def test_update_user_location(self, client: StreamChat, channel, random_user: Dict): # Create a message to attach location to now = datetime.datetime.now(datetime.timezone.utc) one_hour_later = now + datetime.timedelta(hours=1) @@ -57,14 +59,16 @@ def test_update_user_location(self, client: StreamChat, random_user: Dict): # Update user location location_data = { + "created_by_device_id": "test_device_id", "latitude": 37.7749, "longitude": -122.4194, } - response = client.update_user_location(message_id, location_data) + response = client.update_user_location( + random_user["id"], message_id, location_data + ) - assert "shared_location" in response - assert response["shared_location"]["latitude"] == location_data["latitude"] - assert response["shared_location"]["longitude"] == location_data["longitude"] + assert response["latitude"] == location_data["latitude"] + assert response["longitude"] == location_data["longitude"] # Get user locations to verify locations_response = client.get_user_locations(random_user["id"]) @@ -73,9 +77,3 @@ def test_update_user_location(self, client: StreamChat, random_user: Dict): location = locations_response["active_live_locations"][0] assert location["latitude"] == location_data["latitude"] assert location["longitude"] == location_data["longitude"] - - # Cleanup - try: - channel.delete() - except Exception: - pass From 9e9c312108b7701e4958c57b7adc104a27be7ca4 Mon Sep 17 00:00:00 2001 From: Rafael Marinho Date: Mon, 7 Jul 2025 11:19:21 +0200 Subject: [PATCH 11/11] fix lint --- stream_chat/base/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stream_chat/base/client.py b/stream_chat/base/client.py index ee8ce3a..77f16a5 100644 --- a/stream_chat/base/client.py +++ b/stream_chat/base/client.py @@ -1517,7 +1517,10 @@ def get_user_locations( @abc.abstractmethod def update_user_location( - self, user_id: str, message_id: str, options: Optional[SharedLocationsOptions] = None + self, + user_id: str, + message_id: str, + options: Optional[SharedLocationsOptions] = None, ) -> Union[StreamResponse, Awaitable[StreamResponse]]: """ Update the location of a user.