Skip to content

Commit ddc99f7

Browse files
author
Rafael Marinho
committed
Merge branch 'master' into live_location
2 parents e59b421 + a63b949 commit ddc99f7

File tree

11 files changed

+638
-4
lines changed

11 files changed

+638
-4
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
## [4.25.0](https://github.com/GetStream/stream-chat-python/compare/v4.24.0...v4.25.0) (2025-06-18)
6+
7+
8+
### Bug Fixes
9+
10+
* add block user methods ([#199](https://github.com/GetStream/stream-chat-python/issues/199)) ([6364604](https://github.com/GetStream/stream-chat-python/commit/6364604e6ce17491e66c7b2e68b197e5de0ec25c))
11+
* make sure we don't have left over users and channels ([#197](https://github.com/GetStream/stream-chat-python/issues/197)) ([564e947](https://github.com/GetStream/stream-chat-python/commit/564e94741a7069e26670c57bb3183e41db1c955e))
12+
513
## [4.24.0](https://github.com/GetStream/stream-chat-python/compare/v4.23.0...v4.24.0) (2025-04-07)
614

715
## [4.23.0](https://github.com/GetStream/stream-chat-python/compare/v4.22.0...v4.23.0) (2025-03-11)

CONTRIBUTING.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,57 @@ We use Black (code formatter), isort (code formatter), flake8 (linter) and mypy
3636
$ make lint
3737
```
3838

39+
### Using Docker for development
40+
41+
You can also use Docker to run tests and linters without setting up a local Python environment. This is especially useful for ensuring consistent behavior across different environments.
42+
43+
#### Available Docker targets
44+
45+
- `lint_with_docker`: Run linters in Docker
46+
- `lint-fix_with_docker`: Fix linting issues in Docker
47+
- `test_with_docker`: Run tests in Docker
48+
- `check_with_docker`: Run both linters and tests in Docker
49+
50+
#### Specifying Python version
51+
52+
You can specify which Python version to use by setting the `PYTHON_VERSION` environment variable:
53+
54+
```shell
55+
$ PYTHON_VERSION=3.9 make lint_with_docker
56+
```
57+
58+
The default Python version is 3.8 if not specified.
59+
60+
#### Accessing host services from Docker
61+
62+
When running tests in Docker, the container needs to access services running on your host machine (like a local Stream Chat server). The Docker targets use `host.docker.internal` to access the host machine, which is automatically configured with the `--add-host=host.docker.internal:host-gateway` flag.
63+
64+
> ⚠️ **Note**: The `host.docker.internal` DNS name works on Docker for Mac, Docker for Windows, and recent versions of Docker for Linux. If you're using an older version of Docker for Linux, you might need to use your host's actual IP address instead.
65+
66+
For tests that need to access a Stream Chat server running on your host machine, the Docker targets automatically set `STREAM_HOST=http://host.docker.internal:3030`.
67+
68+
#### Examples
69+
70+
Run linters in Docker:
71+
```shell
72+
$ make lint_with_docker
73+
```
74+
75+
Fix linting issues in Docker:
76+
```shell
77+
$ make lint-fix_with_docker
78+
```
79+
80+
Run tests in Docker:
81+
```shell
82+
$ make test_with_docker
83+
```
84+
85+
Run both linters and tests in Docker:
86+
```shell
87+
$ make check_with_docker
88+
```
89+
3990
## Commit message convention
4091

4192
Since we're autogenerating our [CHANGELOG](./CHANGELOG.md), we need to follow a specific commit message convention.

Makefile

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
STREAM_KEY ?= NOT_EXIST
22
STREAM_SECRET ?= NOT_EXIST
3+
PYTHON_VERSION ?= 3.8
34

45
# These targets are not files
5-
.PHONY: help check test lint lint-fix
6+
.PHONY: help check test lint lint-fix test_with_docker lint_with_docker lint-fix_with_docker
67

78
help: ## Display this help message
89
@echo "Please use \`make <target>\` where <target> is one of"
@@ -14,7 +15,7 @@ lint: ## Run linters
1415
flake8 --ignore=E501,W503 stream_chat
1516
mypy stream_chat
1617

17-
lint-fix:
18+
lint-fix: ## Fix linting issues
1819
black stream_chat
1920
isort stream_chat
2021

@@ -23,6 +24,17 @@ test: ## Run tests
2324

2425
check: lint test ## Run linters + tests
2526

27+
lint_with_docker: ## Run linters in Docker (set PYTHON_VERSION to change Python version)
28+
docker run -t -i -w /code -v $(PWD):/code python:$(PYTHON_VERSION) sh -c "pip install black flake8 mypy types-requests && black --check stream_chat && flake8 --ignore=E501,W503 stream_chat && mypy stream_chat || true"
29+
30+
lint-fix_with_docker: ## Fix linting issues in Docker (set PYTHON_VERSION to change Python version)
31+
docker run -t -i -w /code -v $(PWD):/code python:$(PYTHON_VERSION) sh -c "pip install black isort && black stream_chat && isort stream_chat"
32+
33+
test_with_docker: ## Run tests in Docker (set PYTHON_VERSION to change Python version)
34+
docker run -t -i -w /code -v $(PWD):/code --add-host=host.docker.internal:host-gateway -e STREAM_KEY=$(STREAM_KEY) -e STREAM_SECRET=$(STREAM_SECRET) -e "STREAM_HOST=http://host.docker.internal:3030" python:$(PYTHON_VERSION) sh -c "pip install -e .[test,ci] && sed -i 's/Optional\[datetime\]/Optional\[datetime.datetime\]/g' stream_chat/client.py && pytest --cov=stream_chat stream_chat/tests || true"
35+
36+
check_with_docker: lint_with_docker test_with_docker ## Run linters + tests in Docker (set PYTHON_VERSION to change Python version)
37+
2638
reviewdog:
2739
black --check --diff --quiet stream_chat | reviewdog -f=diff -f.diff.strip=0 -filter-mode="diff_context" -name=black -reporter=github-pr-review
2840
flake8 --ignore=E501,W503 stream_chat | reviewdog -f=flake8 -name=flake8 -reporter=github-pr-review

stream_chat/__pkg__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__author__ = "Tommaso Barbugli"
22
__copyright__ = "Copyright 2019-2022, Stream.io, Inc"
3-
__version__ = "4.24.0"
3+
__version__ = "4.25.0"
44
__maintainer__ = "Tommaso Barbugli"
55
__email__ = "[email protected]"
66
__status__ = "Production"

stream_chat/async_chat/client.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,91 @@ async def query_drafts(
868868
data.update(cast(dict, options))
869869
return await self.post("drafts/query", data=data)
870870

871+
async def create_reminder(
872+
self,
873+
message_id: str,
874+
user_id: str,
875+
remind_at: Optional[datetime.datetime] = None,
876+
) -> StreamResponse:
877+
"""
878+
Creates a reminder for a message.
879+
880+
:param message_id: The ID of the message to create a reminder for
881+
:param user_id: The ID of the user creating the reminder
882+
:param remind_at: When to remind the user (optional)
883+
:return: API response
884+
"""
885+
data = {"user_id": user_id}
886+
remind_at_timestamp = ""
887+
if remind_at is not None:
888+
if isinstance(remind_at, datetime.datetime):
889+
remind_at_timestamp = remind_at.isoformat()
890+
else:
891+
remind_at_timestamp = str(remind_at)
892+
893+
data["remind_at"] = remind_at_timestamp
894+
895+
return await self.post(f"messages/{message_id}/reminders", data=data)
896+
897+
async def update_reminder(
898+
self,
899+
message_id: str,
900+
user_id: str,
901+
remind_at: Optional[datetime.datetime] = None,
902+
) -> StreamResponse:
903+
"""
904+
Updates a reminder for a message.
905+
906+
:param message_id: The ID of the message with the reminder
907+
:param user_id: The ID of the user who owns the reminder
908+
:param remind_at: When to remind the user (optional)
909+
:return: API response
910+
"""
911+
data = {"user_id": user_id}
912+
remind_at_timestamp = ""
913+
if remind_at is not None:
914+
if isinstance(remind_at, datetime.datetime):
915+
remind_at_timestamp = remind_at.isoformat()
916+
else:
917+
remind_at_timestamp = str(remind_at)
918+
919+
data["remind_at"] = remind_at_timestamp
920+
return await self.patch(f"messages/{message_id}/reminders", data=data)
921+
922+
async def delete_reminder(self, message_id: str, user_id: str) -> StreamResponse:
923+
"""
924+
Deletes a reminder for a message.
925+
926+
:param message_id: The ID of the message with the reminder
927+
:param user_id: The ID of the user who owns the reminder
928+
:return: API response
929+
"""
930+
return await self.delete(
931+
f"messages/{message_id}/reminders", params={"user_id": user_id}
932+
)
933+
934+
async def query_reminders(
935+
self,
936+
user_id: str,
937+
filter_conditions: Dict = None,
938+
sort: List[Dict] = None,
939+
**options: Any,
940+
) -> StreamResponse:
941+
"""
942+
Queries reminders based on filter conditions.
943+
944+
:param user_id: The ID of the user whose reminders to query
945+
:param filter_conditions: Conditions to filter reminders
946+
:param sort: Sort parameters (default: [{ field: 'remind_at', direction: 1 }])
947+
:param options: Additional query options like limit, offset
948+
:return: API response with reminders
949+
"""
950+
params = options.copy()
951+
params["filter_conditions"] = filter_conditions or {}
952+
params["sort"] = sort or [{"field": "remind_at", "direction": 1}]
953+
params["user_id"] = user_id
954+
return await self.post("reminders/query", data=params)
955+
871956
async def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse:
872957
params = {"user_id": user_id, **options}
873958
return await self.get("users/live_locations", params=params)

stream_chat/base/client.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1440,9 +1440,75 @@ def query_drafts(
14401440
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
14411441
pass
14421442

1443+
@abc.abstractmethod
1444+
def create_reminder(
1445+
self,
1446+
message_id: str,
1447+
user_id: str,
1448+
remind_at: Optional[datetime.datetime] = None,
1449+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
1450+
"""
1451+
Creates a reminder for a message.
1452+
1453+
:param message_id: The ID of the message to create a reminder for
1454+
:param user_id: The ID of the user creating the reminder
1455+
:param remind_at: When to remind the user (optional)
1456+
:return: API response
1457+
"""
1458+
pass
1459+
1460+
@abc.abstractmethod
1461+
def update_reminder(
1462+
self,
1463+
message_id: str,
1464+
user_id: str,
1465+
remind_at: Optional[datetime.datetime] = None,
1466+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
1467+
"""
1468+
Updates a reminder for a message.
1469+
1470+
:param message_id: The ID of the message with the reminder
1471+
:param user_id: The ID of the user who owns the reminder
1472+
:param remind_at: When to remind the user (optional)
1473+
:return: API response
1474+
"""
1475+
pass
1476+
1477+
@abc.abstractmethod
1478+
def delete_reminder(
1479+
self, message_id: str, user_id: str
1480+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
1481+
"""
1482+
Deletes a reminder for a message.
1483+
1484+
:param message_id: The ID of the message with the reminder
1485+
:param user_id: The ID of the user who owns the reminder
1486+
:return: API response
1487+
"""
1488+
pass
1489+
1490+
@abc.abstractmethod
1491+
def query_reminders(
1492+
self,
1493+
user_id: str,
1494+
filter_conditions: Dict = None,
1495+
sort: List[Dict] = None,
1496+
**options: Any,
1497+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
1498+
"""
1499+
Queries reminders based on filter conditions.
1500+
1501+
:param user_id: The ID of the user whose reminders to query
1502+
:param filter_conditions: Conditions to filter reminders
1503+
:param sort: Sort parameters (default: [{ field: 'remind_at', direction: 1 }])
1504+
:param options: Additional query options like limit, offset
1505+
:return: API response with reminders
1506+
"""
1507+
pass
1508+
14431509
@abc.abstractmethod
14441510
def get_user_locations(
1445-
self, user_id: str, **options: Any
1511+
self, user_id: str, **options: Any
14461512
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
14471513
"""
14481514
Get the locations of a user.

stream_chat/client.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,80 @@ def query_drafts(
826826
data.update(cast(dict, options))
827827
return self.post("drafts/query", data=data)
828828

829+
def create_reminder(
830+
self,
831+
message_id: str,
832+
user_id: str,
833+
remind_at: Optional[datetime.datetime] = None,
834+
) -> StreamResponse:
835+
"""
836+
Creates a reminder for a message.
837+
838+
:param message_id: The ID of the message to create a reminder for
839+
:param user_id: The ID of the user creating the reminder
840+
:param remind_at: When to remind the user (optional)
841+
:return: API response
842+
"""
843+
data = {"user_id": user_id}
844+
if remind_at is not None:
845+
# Format as ISO 8601 date string without microseconds
846+
data["remind_at"] = remind_at.strftime("%Y-%m-%dT%H:%M:%SZ")
847+
return self.post(f"messages/{message_id}/reminders", data=data)
848+
849+
def update_reminder(
850+
self,
851+
message_id: str,
852+
user_id: str,
853+
remind_at: Optional[datetime.datetime] = None,
854+
) -> StreamResponse:
855+
"""
856+
Updates a reminder for a message.
857+
858+
:param message_id: The ID of the message with the reminder
859+
:param user_id: The ID of the user who owns the reminder
860+
:param remind_at: When to remind the user (optional)
861+
:return: API response
862+
"""
863+
data = {"user_id": user_id}
864+
if remind_at is not None:
865+
# Format as ISO 8601 date string without microseconds
866+
data["remind_at"] = remind_at.strftime("%Y-%m-%dT%H:%M:%SZ")
867+
return self.patch(f"messages/{message_id}/reminders", data=data)
868+
869+
def delete_reminder(self, message_id: str, user_id: str) -> StreamResponse:
870+
"""
871+
Deletes a reminder for a message.
872+
873+
:param message_id: The ID of the message with the reminder
874+
:param user_id: The ID of the user who owns the reminder
875+
:return: API response
876+
"""
877+
return self.delete(
878+
f"messages/{message_id}/reminders", params={"user_id": user_id}
879+
)
880+
881+
def query_reminders(
882+
self,
883+
user_id: str,
884+
filter_conditions: Dict = None,
885+
sort: List[Dict] = None,
886+
**options: Any,
887+
) -> StreamResponse:
888+
"""
889+
Queries reminders based on filter conditions.
890+
891+
:param user_id: The ID of the user whose reminders to query
892+
:param filter_conditions: Conditions to filter reminders
893+
:param sort: Sort parameters (default: [{ field: 'remind_at', direction: 1 }])
894+
:param options: Additional query options like limit, offset
895+
:return: API response with reminders
896+
"""
897+
params = options.copy()
898+
params["filter_conditions"] = filter_conditions or {}
899+
params["sort"] = sort or [{"field": "remind_at", "direction": 1}]
900+
params["user_id"] = user_id
901+
return self.post("reminders/query", data=params)
902+
829903
def get_user_locations(self, user_id: str, **options: Any) -> StreamResponse:
830904
params = {"user_id": user_id, **options}
831905
return self.get("users/live_locations", params=params)

stream_chat/tests/async_chat/test_channel.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ async def test_get_messages(self, channel: Channel, random_user: Dict):
187187
assert len(resp["messages"]) == 1
188188

189189
async def test_mark_read(self, channel: Channel, random_user: Dict):
190+
member = {"user_id": random_user["id"]}
191+
await channel.add_members([member])
192+
190193
response = await channel.mark_read(random_user["id"])
191194
assert "event" in response
192195
assert response["event"]["type"] == "message.read"

0 commit comments

Comments
 (0)