Skip to content

Commit c472c0b

Browse files
authored
feat: add ratelimit info, plus remove py 3.6 support
1 parent a5ae243 commit c472c0b

22 files changed

+208
-55
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
omit = stream_chat/tests/*

.github/CODEOWNERS

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* @ferhatelmas @gumuz
1+
* @gumuz @peterdeme @ferhatelmas

.github/workflows/ci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ jobs:
1414
name: 🧪 Test & lint
1515
runs-on: ubuntu-latest
1616
strategy:
17+
fail-fast: false
1718
max-parallel: 1
1819
matrix:
19-
python: [3.6, 3.7, 3.8, 3.9, "3.10"]
20+
python: [3.7, 3.8, 3.9, "3.10"]
2021
steps:
2122
- uses: actions/checkout@v2
2223
- uses: actions/setup-python@v2

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ coverage.xml
4444
.project
4545
.pydevproject
4646
.coverage*
47+
!.coveragerc
4748

4849
# Rope
4950
.ropeproject

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lint: ## Run linters
1616

1717
lint-fix:
1818
black stream_chat
19+
isort stream_chat
1920

2021
test: ## Run tests
2122
STREAM_KEY=$(STREAM_KEY) STREAM_SECRET=$(STREAM_SECRET) pytest --cov=stream_chat --cov-report=xml stream_chat/tests

README.md

+37-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
[![build](https://github.com/GetStream/stream-chat-python/workflows/build/badge.svg)](https://github.com/GetStream/stream-chat-python/actions) [![PyPI version](https://badge.fury.io/py/stream-chat.svg)](http://badge.fury.io/py/stream-chat) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/stream-chat.svg) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
44

5-
the official Python API client for [Stream chat](https://getstream.io/chat/) a service for building chat applications.
5+
---
6+
> ### :bulb: Major update in v4.0 <
7+
> The returned response objects are instances of [`StreamResponse`](https://github.com/GetStream/stream-chat-python/blob/master/stream_chat/types/stream_response.py) class. It inherits from `dict`, so it's fully backward compatible. Additionally, it provides other benefits such as rate limit information (`resp.rate_limit()`), response headers (`resp.headers()`) or status code (`resp.status_code()`).
8+
---
69

7-
You can sign up for a Stream account at https://getstream.io/chat/get_started/.
10+
The official Python API client for [Stream chat](https://getstream.io/chat/) a service for building chat applications.
811

9-
You can use this library to access chat API endpoints server-side, for the client-side integrations (web and mobile) have a look at the Javascript, iOS and Android SDK libraries (https://getstream.io/chat/).
12+
You can sign up for a Stream account on our [Get Started](https://getstream.io/chat/get_started/) page.
13+
14+
You can use this library to access chat API endpoints server-side, for the client-side integrations (web and mobile) have a look at the Javascript, iOS and Android SDK libraries.
1015

1116
### Installation
1217

@@ -34,6 +39,7 @@ pip install stream-chat
3439
- User search
3540
- Channel search
3641
- Campaign API (alpha - susceptible changes and even won't be available in some regions yet)
42+
- Rate limit in response
3743

3844
### Quickstart
3945

@@ -58,6 +64,20 @@ def main():
5864
# add a first message to the channel
5965
channel.send_message({"text": "AMA about kung-fu"}, "chuck")
6066

67+
# we also expose some response metadata through a custom dictionary
68+
resp = chat.deactivate_user("bruce_lee")
69+
print(type(resp)) # <class 'stream_chat.types.stream_response.StreamResponse'>
70+
print(resp["user"]["id"]) # bruce_lee
71+
72+
rate_limit = resp.rate_limit()
73+
print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 / 2022-01-06 12:35:00+00:00
74+
75+
headers = resp.headers()
76+
print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }
77+
78+
status_code = resp.status_code()
79+
print(status_code) # 200
80+
6181

6282
if __name__ == '__main__':
6383
main()
@@ -83,6 +103,20 @@ async def main():
83103
# add a first message to the channel
84104
await channel.send_message({"text": "AMA about kung-fu"}, "chuck")
85105

106+
# we also expose some response metadata through a custom dictionary
107+
resp = await chat.deactivate_user("bruce_lee")
108+
print(type(resp)) # <class 'stream_chat.types.stream_response.StreamResponse'>
109+
print(resp["user"]["id"]) # bruce_lee
110+
111+
rate_limit = resp.rate_limit()
112+
print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 / 2022-01-06 12:35:00+00:00
113+
114+
headers = resp.headers()
115+
print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }
116+
117+
status_code = resp.status_code()
118+
print(status_code) # 200
119+
86120

87121
if __name__ == '__main__':
88122
loop = asyncio.get_event_loop()

pyproject.toml

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ exclude = '''
2424
)/
2525
'''
2626

27+
[tool.isort]
28+
profile = "black"
29+
src_paths = ["stream_chat"]
30+
known_first_party = ["stream_chat"]
31+
2732
[tool.pytest.ini_options]
2833
testpaths = ["stream_chat/tests"]
2934
asyncio_mode = "auto"

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ci_require = [
1313
"black",
1414
"flake8",
15+
"flake8-isort",
1516
"flake8-bugbear",
1617
"pytest-cov",
1718
"mypy",
@@ -47,7 +48,7 @@
4748
install_requires=install_requires,
4849
extras_require={"test": tests_require, "ci": ci_require},
4950
include_package_data=True,
50-
python_requires=">=3.6",
51+
python_requires=">=3.7",
5152
classifiers=[
5253
"Intended Audience :: Developers",
5354
"Intended Audience :: System Administrators",
@@ -56,7 +57,6 @@
5657
"Development Status :: 5 - Production/Stable",
5758
"Natural Language :: English",
5859
"Programming Language :: Python :: 3",
59-
"Programming Language :: Python :: 3.6",
6060
"Programming Language :: Python :: 3.7",
6161
"Programming Language :: Python :: 3.8",
6262
"Programming Language :: Python :: 3.9",

stream_chat/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .client import StreamChat
21
from .async_chat import StreamChatAsync
2+
from .client import StreamChat
33

44
__all__ = ["StreamChat", "StreamChatAsync"]

stream_chat/async_chat/channel.py

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

4-
54
from stream_chat.base.channel import ChannelInterface, add_user_id
65
from stream_chat.types.stream_response import StreamResponse
76

stream_chat/async_chat/client.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import json
21
import datetime
2+
import json
33
from types import TracebackType
44
from typing import (
55
Any,
@@ -56,7 +56,8 @@ async def _parse_response(self, response: aiohttp.ClientResponse) -> StreamRespo
5656
raise StreamAPIException(text, response.status)
5757
if response.status >= 399:
5858
raise StreamAPIException(text, response.status)
59-
return parsed_result
59+
60+
return StreamResponse(parsed_result, dict(response.headers), response.status)
6061

6162
async def _make_request(
6263
self,

stream_chat/base/channel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import abc
22
from typing import Any, Awaitable, Dict, Iterable, List, Union
33

4+
from stream_chat.base.client import StreamChatInterface
45
from stream_chat.base.exceptions import StreamChannelException
56
from stream_chat.types.stream_response import StreamResponse
6-
from stream_chat.base.client import StreamChatInterface
77

88

99
class ChannelInterface(abc.ABC):

stream_chat/base/client.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
import datetime
44
import hashlib
55
import hmac
6-
from typing import Any, Awaitable, Dict, Iterable, List, Union, TypeVar
6+
from typing import Any, Awaitable, Dict, Iterable, List, TypeVar, Union
77

88
import jwt
99

1010
from stream_chat.types.stream_response import StreamResponse
1111

12-
1312
TChannel = TypeVar("TChannel")
1413

1514

stream_chat/client.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import datetime
12
import json
23
from typing import Any, Callable, Dict, Iterable, List, Union
34
from urllib.parse import urlparse
45
from urllib.request import Request, urlopen
56

67
import requests
7-
import datetime
88

99
from stream_chat.__pkg__ import __version__
1010
from stream_chat.base.client import StreamChatInterface
@@ -44,7 +44,10 @@ def _parse_response(self, response: requests.Response) -> StreamResponse:
4444
raise StreamAPIException(response.text, response.status_code)
4545
if response.status_code >= 399:
4646
raise StreamAPIException(response.text, response.status_code)
47-
return parsed_result
47+
48+
return StreamResponse(
49+
parsed_result, dict(response.headers), response.status_code
50+
)
4851

4952
def _make_request(
5053
self,

stream_chat/tests/async_chat/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import os
3-
from typing import Dict, List
43
import uuid
4+
from typing import Dict, List
55

66
import pytest
77

stream_chat/tests/async_chat/test_channel.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
from typing import Dict, List
2-
import uuid
31
import time
2+
import uuid
3+
from typing import Dict, List
4+
45
import pytest
56

6-
from stream_chat.async_chat.client import StreamChatAsync
77
from stream_chat.async_chat.channel import Channel
8+
from stream_chat.async_chat.client import StreamChatAsync
89
from stream_chat.base.exceptions import StreamAPIException
910

1011

1112
@pytest.mark.incremental
12-
class TestChannel(object):
13+
class TestChannel:
1314
async def test_ban_user(
1415
self, channel: Channel, random_user: Dict, server_user: Dict
1516
):

stream_chat/tests/async_chat/test_client.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
import json
12
import sys
3+
import time
4+
import uuid
25
from contextlib import suppress
6+
from datetime import datetime
37
from operator import itemgetter
48
from typing import Dict, List
59

610
import jwt
711
import pytest
8-
import time
9-
import uuid
10-
from stream_chat.async_chat.channel import Channel
12+
1113
from stream_chat.async_chat import StreamChatAsync
14+
from stream_chat.async_chat.channel import Channel
1215
from stream_chat.base.exceptions import StreamAPIException
1316
from stream_chat.tests.utils import wait_for_async
1417

1518

16-
class TestClient(object):
19+
class TestClient:
1720
def test_normalize_sort(self, client: StreamChatAsync):
1821
expected = [
1922
{"field": "field1", "direction": 1},
@@ -639,3 +642,21 @@ async def test_delete_channels(self, client: StreamChatAsync, channel: Channel):
639642
time.sleep(1)
640643

641644
pytest.fail("task did not succeed")
645+
646+
@pytest.mark.asyncio
647+
async def test_stream_response(self, client: StreamChatAsync):
648+
resp = await client.get_app_settings()
649+
650+
dumped = json.dumps(resp)
651+
assert '{"app":' in dumped
652+
assert "rate_limit" not in dumped
653+
assert "headers" not in dumped
654+
assert "status_code" not in dumped
655+
656+
assert len(resp.headers()) > 0
657+
assert resp.status_code() == 200
658+
659+
rate_limit = resp.rate_limit()
660+
assert rate_limit.limit > 0
661+
assert rate_limit.remaining > 0
662+
assert type(rate_limit.reset) is datetime

stream_chat/tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
2-
from typing import Dict, List
32
import uuid
3+
from typing import Dict, List
44

55
import pytest
66

stream_chat/tests/test_channel.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
from typing import Dict, List
2-
import uuid
31
import time
2+
import uuid
3+
from typing import Dict, List
44

55
import pytest
66

7-
from stream_chat.channel import Channel
87
from stream_chat import StreamChat
98
from stream_chat.base.exceptions import StreamAPIException
9+
from stream_chat.channel import Channel
1010

1111

1212
@pytest.mark.incremental
13-
class TestChannel(object):
13+
class TestChannel:
1414
def test_ban_user(self, channel: Channel, random_user, server_user: Dict):
1515
channel.ban_user(random_user["id"], user_id=server_user["id"])
1616
channel.ban_user(

stream_chat/tests/test_client.py

+43-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
import json
12
import sys
2-
from operator import itemgetter
3+
import time
4+
import uuid
35
from contextlib import suppress
6+
from datetime import datetime
7+
from operator import itemgetter
48
from typing import Dict, List
59

610
import jwt
711
import pytest
8-
import time
9-
import uuid
12+
1013
from stream_chat import StreamChat
11-
from stream_chat.channel import Channel
1214
from stream_chat.base.exceptions import StreamAPIException
15+
from stream_chat.channel import Channel
1316
from stream_chat.tests.utils import wait_for
1417

1518

16-
class TestClient(object):
19+
class TestClient:
1720
def test_normalize_sort(self, client: StreamChat):
1821
expected = [
1922
{"field": "field1", "direction": 1},
@@ -599,3 +602,38 @@ def test_delete_channels(self, client: StreamChat, channel: Channel):
599602
time.sleep(1)
600603

601604
pytest.fail("task did not succeed")
605+
606+
def test_stream_response_contains_metadata(self, client: StreamChat):
607+
resp = client.get_app_settings()
608+
609+
assert len(resp.headers()) > 0
610+
assert resp.status_code() == 200
611+
612+
rate_limit = resp.rate_limit()
613+
assert rate_limit.limit > 0
614+
assert rate_limit.remaining > 0
615+
assert type(rate_limit.reset) is datetime
616+
617+
def test_stream_response_can_serialize(self, client: StreamChat):
618+
resp = client.get_app_settings()
619+
620+
assert len(resp) == 2
621+
del resp["duration"]
622+
assert '{"app":' in json.dumps(resp)
623+
624+
def test_stream_response(self, client: StreamChat):
625+
resp = client.get_app_settings()
626+
627+
dumped = json.dumps(resp)
628+
assert '{"app":' in dumped
629+
assert "rate_limit" not in dumped
630+
assert "headers" not in dumped
631+
assert "status_code" not in dumped
632+
633+
assert len(resp.headers()) > 0
634+
assert resp.status_code() == 200
635+
636+
rate_limit = resp.rate_limit()
637+
assert rate_limit.limit > 0
638+
assert rate_limit.remaining > 0
639+
assert type(rate_limit.reset) is datetime

0 commit comments

Comments
 (0)