Skip to content

Commit 0e11543

Browse files
[CHA-15] Added pin, archive and partial member update functions (#179)
1 parent fa23514 commit 0e11543

File tree

6 files changed

+266
-4
lines changed

6 files changed

+266
-4
lines changed

.github/workflows/ci.yml

-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ jobs:
2424
with:
2525
fetch-depth: 0 # gives the commit message linter access to all previous commits
2626

27-
- name: Commit lint
28-
if: ${{ matrix.python == '3.8' && github.ref == 'refs/heads/master' }}
29-
uses: wagoid/commitlint-github-action@v4
30-
3127
- uses: actions/setup-python@v3
3228
with:
3329
python-version: ${{ matrix.python }}

stream_chat/async_chat/channel.py

+38
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any, Dict, Iterable, List, Union
33

44
from stream_chat.base.channel import ChannelInterface, add_user_id
5+
from stream_chat.base.exceptions import StreamChannelException
56
from stream_chat.types.stream_response import StreamResponse
67

78

@@ -209,3 +210,40 @@ async def unmute(self, user_id: str) -> StreamResponse:
209210
"channel_cid": self.cid,
210211
}
211212
return await self.client.post("moderation/unmute/channel", data=params)
213+
214+
async def pin(self, user_id: str) -> StreamResponse:
215+
if not user_id:
216+
raise StreamChannelException("user_id must not be empty")
217+
218+
payload = {"set": {"pinned": True}}
219+
return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
220+
221+
async def unpin(self, user_id: str) -> StreamResponse:
222+
if not user_id:
223+
raise StreamChannelException("user_id must not be empty")
224+
225+
payload = {"set": {"pinned": False}}
226+
return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
227+
228+
async def archive(self, user_id: str) -> StreamResponse:
229+
if not user_id:
230+
raise StreamChannelException("user_id must not be empty")
231+
232+
payload = {"set": {"archived": True}}
233+
return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
234+
235+
async def unarchive(self, user_id: str) -> StreamResponse:
236+
if not user_id:
237+
raise StreamChannelException("user_id must not be empty")
238+
239+
payload = {"set": {"archived": False}}
240+
return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)
241+
242+
async def update_member_partial(
243+
self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
244+
) -> StreamResponse:
245+
if not user_id:
246+
raise StreamChannelException("user_id must not be empty")
247+
248+
payload = {"set": to_set or {}, "unset": to_unset or []}
249+
return await self.client.patch(f"{self.url}/member/{user_id}", data=payload)

stream_chat/base/channel.py

+46
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,52 @@ def unmute(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse
440440
"""
441441
pass
442442

443+
@abc.abstractmethod
444+
def pin(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
445+
"""
446+
Pins a channel
447+
Allows a user to pin the channel (only for themselves)
448+
"""
449+
pass
450+
451+
@abc.abstractmethod
452+
def unpin(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
453+
"""
454+
Unpins a channel
455+
Allows a user to unpin the channel (only for themselves)
456+
"""
457+
pass
458+
459+
@abc.abstractmethod
460+
def archive(self, user_id: str) -> Union[StreamResponse, Awaitable[StreamResponse]]:
461+
"""
462+
Pins a channel
463+
Allows a user to archive the channel (only for themselves)
464+
"""
465+
pass
466+
467+
@abc.abstractmethod
468+
def unarchive(
469+
self, user_id: str
470+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
471+
"""
472+
Unpins a channel
473+
Allows a user to unpin the channel (only for themselves)
474+
"""
475+
pass
476+
477+
@abc.abstractmethod
478+
def update_member_partial(
479+
self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
480+
) -> Union[StreamResponse, Awaitable[StreamResponse]]:
481+
"""
482+
Update channel member partially
483+
484+
:param to_set: a dictionary of key/value pairs to set or to override
485+
:param to_unset: a list of keys to clear
486+
"""
487+
pass
488+
443489

444490
def add_user_id(payload: Dict, user_id: str) -> Dict:
445491
return {**payload, "user": {"id": user_id}}

stream_chat/channel.py

+38
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any, Dict, Iterable, List, Union
33

44
from stream_chat.base.channel import ChannelInterface, add_user_id
5+
from stream_chat.base.exceptions import StreamChannelException
56
from stream_chat.types.stream_response import StreamResponse
67

78

@@ -210,3 +211,40 @@ def unmute(self, user_id: str) -> StreamResponse:
210211
"channel_cid": self.cid,
211212
}
212213
return self.client.post("moderation/unmute/channel", data=params)
214+
215+
def pin(self, user_id: str) -> StreamResponse:
216+
if not user_id:
217+
raise StreamChannelException("user_id must not be empty")
218+
219+
payload = {"set": {"pinned": True}}
220+
return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
221+
222+
def unpin(self, user_id: str) -> StreamResponse:
223+
if not user_id:
224+
raise StreamChannelException("user_id must not be empty")
225+
226+
payload = {"set": {"pinned": False}}
227+
return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
228+
229+
def archive(self, user_id: str) -> StreamResponse:
230+
if not user_id:
231+
raise StreamChannelException("user_id must not be empty")
232+
233+
payload = {"set": {"archived": True}}
234+
return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
235+
236+
def unarchive(self, user_id: str) -> StreamResponse:
237+
if not user_id:
238+
raise StreamChannelException("user_id must not be empty")
239+
240+
payload = {"set": {"archived": False}}
241+
return self.client.patch(f"{self.url}/member/{user_id}", data=payload)
242+
243+
def update_member_partial(
244+
self, user_id: str, to_set: Dict = None, to_unset: Iterable[str] = None
245+
) -> StreamResponse:
246+
if not user_id:
247+
raise StreamChannelException("user_id must not be empty")
248+
249+
payload = {"set": to_set or {}, "unset": to_unset or []}
250+
return self.client.patch(f"{self.url}/member/{user_id}", data=payload)

stream_chat/tests/async_chat/test_channel.py

+73
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,76 @@ async def test_export_channel(
380380
assert "error" not in resp
381381
break
382382
time.sleep(0.5)
383+
384+
async def test_pin_channel(
385+
self, client: StreamChatAsync, channel: Channel, random_users: List[Dict]
386+
):
387+
user_id = random_users[0]["id"]
388+
await channel.add_members([user_id])
389+
390+
# Pin the channel
391+
response = await channel.pin(user_id)
392+
assert response is not None
393+
394+
# Query for pinned channels
395+
response = await client.query_channels(
396+
{"pinned": True, "cid": channel.cid}, user_id=user_id
397+
)
398+
assert len(response["channels"]) == 1
399+
assert response["channels"][0]["channel"]["cid"] == channel.cid
400+
401+
# Unpin the channel
402+
response = await channel.unpin(user_id)
403+
assert response is not None
404+
405+
# Query for pinned channels
406+
response = await client.query_channels(
407+
{"pinned": False, "cid": channel.cid}, user_id=user_id
408+
)
409+
assert len(response["channels"]) == 1
410+
assert response["channels"][0]["channel"]["cid"] == channel.cid
411+
412+
async def test_archive_channel(
413+
self, client: StreamChatAsync, channel: Channel, random_users: List[Dict]
414+
):
415+
user_id = random_users[0]["id"]
416+
await channel.add_members([user_id])
417+
418+
# Archive the channel
419+
response = await channel.archive(user_id)
420+
assert response is not None
421+
422+
# Query for archived channels
423+
response = await client.query_channels(
424+
{"archived": True, "cid": channel.cid}, user_id=user_id
425+
)
426+
assert len(response["channels"]) == 1
427+
assert response["channels"][0]["channel"]["cid"] == channel.cid
428+
429+
# Unarchive the channel
430+
response = await channel.unarchive(user_id)
431+
assert response is not None
432+
433+
# Query for archived channels
434+
response = await client.query_channels(
435+
{"archived": False, "cid": channel.cid}, user_id=user_id
436+
)
437+
assert len(response["channels"]) == 1
438+
assert response["channels"][0]["channel"]["cid"] == channel.cid
439+
440+
async def test_update_member_partial(
441+
self, channel: Channel, random_users: List[Dict]
442+
):
443+
user_id = random_users[0]["id"]
444+
await channel.add_members([user_id])
445+
446+
# Test setting a custom field
447+
response = await channel.update_member_partial(user_id, to_set={"hat": "blue"})
448+
assert response["channel_member"]["hat"] == "blue"
449+
450+
# Test setting a new field while unsetting the previous one
451+
response = await channel.update_member_partial(
452+
user_id, to_set={"color": "red"}, to_unset=["hat"]
453+
)
454+
assert response["channel_member"]["color"] == "red"
455+
assert "hat" not in response["channel_member"]

stream_chat/tests/test_channel.py

+71
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,74 @@ def test_export_channel(
377377
assert "error" not in resp
378378
break
379379
time.sleep(0.5)
380+
381+
def test_pin_channel(
382+
self, client: StreamChat, channel: Channel, random_users: List[Dict]
383+
):
384+
user_id = random_users[0]["id"]
385+
channel.add_members([user_id])
386+
387+
# Pin the channel
388+
response = channel.pin(user_id)
389+
assert response is not None
390+
391+
# Query for pinned channels
392+
response = client.query_channels(
393+
{"pinned": True, "cid": channel.cid}, user_id=user_id
394+
)
395+
assert len(response["channels"]) == 1
396+
assert response["channels"][0]["channel"]["cid"] == channel.cid
397+
398+
# Unpin the channel
399+
response = channel.unpin(user_id)
400+
assert response is not None
401+
402+
# Query for pinned channels
403+
response = client.query_channels(
404+
{"pinned": False, "cid": channel.cid}, user_id=user_id
405+
)
406+
assert len(response["channels"]) == 1
407+
assert response["channels"][0]["channel"]["cid"] == channel.cid
408+
409+
def test_archive_channel(
410+
self, client: StreamChat, channel: Channel, random_users: List[Dict]
411+
):
412+
user_id = random_users[0]["id"]
413+
channel.add_members([user_id])
414+
415+
# Archive the channel
416+
response = channel.archive(user_id)
417+
assert response is not None
418+
419+
# Query for archived channels
420+
response = client.query_channels(
421+
{"archived": True, "cid": channel.cid}, user_id=user_id
422+
)
423+
assert len(response["channels"]) == 1
424+
assert response["channels"][0]["channel"]["cid"] == channel.cid
425+
426+
# Unarchive the channel
427+
response = channel.unarchive(user_id)
428+
assert response is not None
429+
430+
# Query for archhived channels
431+
response = client.query_channels(
432+
{"archived": False, "cid": channel.cid}, user_id=user_id
433+
)
434+
assert len(response["channels"]) == 1
435+
assert response["channels"][0]["channel"]["cid"] == channel.cid
436+
437+
def test_update_member_partial(self, channel: Channel, random_users: List[Dict]):
438+
user_id = random_users[0]["id"]
439+
channel.add_members([user_id])
440+
441+
# Test setting a custom field
442+
response = channel.update_member_partial(user_id, to_set={"hat": "blue"})
443+
assert response["channel_member"]["hat"] == "blue"
444+
445+
# Test setting a new field while unsetting the previous one
446+
response = channel.update_member_partial(
447+
user_id, to_set={"color": "red"}, to_unset=["hat"]
448+
)
449+
assert response["channel_member"]["color"] == "red"
450+
assert "hat" not in response["channel_member"]

0 commit comments

Comments
 (0)