Skip to content

Commit

Permalink
feat: add ratelimit info, plus remove py 3.6 support
Browse files Browse the repository at this point in the history
  • Loading branch information
peterdeme authored Jan 18, 2022
1 parent a5ae243 commit c472c0b
Show file tree
Hide file tree
Showing 22 changed files with 208 additions and 55 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = stream_chat/tests/*
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @ferhatelmas @gumuz
* @gumuz @peterdeme @ferhatelmas
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ jobs:
name: 🧪 Test & lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1
matrix:
python: [3.6, 3.7, 3.8, 3.9, "3.10"]
python: [3.7, 3.8, 3.9, "3.10"]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ coverage.xml
.project
.pydevproject
.coverage*
!.coveragerc

# Rope
.ropeproject
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ lint: ## Run linters

lint-fix:
black stream_chat
isort stream_chat

test: ## Run tests
STREAM_KEY=$(STREAM_KEY) STREAM_SECRET=$(STREAM_SECRET) pytest --cov=stream_chat --cov-report=xml stream_chat/tests
Expand Down
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

[![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/)

the official Python API client for [Stream chat](https://getstream.io/chat/) a service for building chat applications.
---
> ### :bulb: Major update in v4.0 <
> 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()`).
---

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

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/).
You can sign up for a Stream account on our [Get Started](https://getstream.io/chat/get_started/) page.

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.

### Installation

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

### Quickstart

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

# we also expose some response metadata through a custom dictionary
resp = chat.deactivate_user("bruce_lee")
print(type(resp)) # <class 'stream_chat.types.stream_response.StreamResponse'>
print(resp["user"]["id"]) # bruce_lee

rate_limit = resp.rate_limit()
print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 / 2022-01-06 12:35:00+00:00

headers = resp.headers()
print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }

status_code = resp.status_code()
print(status_code) # 200


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

# we also expose some response metadata through a custom dictionary
resp = await chat.deactivate_user("bruce_lee")
print(type(resp)) # <class 'stream_chat.types.stream_response.StreamResponse'>
print(resp["user"]["id"]) # bruce_lee

rate_limit = resp.rate_limit()
print(f"{rate_limit.limit} / {rate_limit.remaining} / {rate_limit.reset}") # 60 / 59 / 2022-01-06 12:35:00+00:00

headers = resp.headers()
print(headers) # { 'Content-Encoding': 'gzip', 'Content-Length': '33', ... }

status_code = resp.status_code()
print(status_code) # 200


if __name__ == '__main__':
loop = asyncio.get_event_loop()
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ exclude = '''
)/
'''

[tool.isort]
profile = "black"
src_paths = ["stream_chat"]
known_first_party = ["stream_chat"]

[tool.pytest.ini_options]
testpaths = ["stream_chat/tests"]
asyncio_mode = "auto"
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ci_require = [
"black",
"flake8",
"flake8-isort",
"flake8-bugbear",
"pytest-cov",
"mypy",
Expand Down Expand Up @@ -47,7 +48,7 @@
install_requires=install_requires,
extras_require={"test": tests_require, "ci": ci_require},
include_package_data=True,
python_requires=">=3.6",
python_requires=">=3.7",
classifiers=[
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
Expand All @@ -56,7 +57,6 @@
"Development Status :: 5 - Production/Stable",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand Down
2 changes: 1 addition & 1 deletion stream_chat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .client import StreamChat
from .async_chat import StreamChatAsync
from .client import StreamChat

__all__ = ["StreamChat", "StreamChatAsync"]
1 change: 0 additions & 1 deletion stream_chat/async_chat/channel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from typing import Any, Dict, Iterable, List, Union


from stream_chat.base.channel import ChannelInterface, add_user_id
from stream_chat.types.stream_response import StreamResponse

Expand Down
5 changes: 3 additions & 2 deletions stream_chat/async_chat/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
import datetime
import json
from types import TracebackType
from typing import (
Any,
Expand Down Expand Up @@ -56,7 +56,8 @@ async def _parse_response(self, response: aiohttp.ClientResponse) -> StreamRespo
raise StreamAPIException(text, response.status)
if response.status >= 399:
raise StreamAPIException(text, response.status)
return parsed_result

return StreamResponse(parsed_result, dict(response.headers), response.status)

async def _make_request(
self,
Expand Down
2 changes: 1 addition & 1 deletion stream_chat/base/channel.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import abc
from typing import Any, Awaitable, Dict, Iterable, List, Union

from stream_chat.base.client import StreamChatInterface
from stream_chat.base.exceptions import StreamChannelException
from stream_chat.types.stream_response import StreamResponse
from stream_chat.base.client import StreamChatInterface


class ChannelInterface(abc.ABC):
Expand Down
3 changes: 1 addition & 2 deletions stream_chat/base/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import datetime
import hashlib
import hmac
from typing import Any, Awaitable, Dict, Iterable, List, Union, TypeVar
from typing import Any, Awaitable, Dict, Iterable, List, TypeVar, Union

import jwt

from stream_chat.types.stream_response import StreamResponse


TChannel = TypeVar("TChannel")


Expand Down
7 changes: 5 additions & 2 deletions stream_chat/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import datetime
import json
from typing import Any, Callable, Dict, Iterable, List, Union
from urllib.parse import urlparse
from urllib.request import Request, urlopen

import requests
import datetime

from stream_chat.__pkg__ import __version__
from stream_chat.base.client import StreamChatInterface
Expand Down Expand Up @@ -44,7 +44,10 @@ def _parse_response(self, response: requests.Response) -> StreamResponse:
raise StreamAPIException(response.text, response.status_code)
if response.status_code >= 399:
raise StreamAPIException(response.text, response.status_code)
return parsed_result

return StreamResponse(
parsed_result, dict(response.headers), response.status_code
)

def _make_request(
self,
Expand Down
2 changes: 1 addition & 1 deletion stream_chat/tests/async_chat/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
import os
from typing import Dict, List
import uuid
from typing import Dict, List

import pytest

Expand Down
9 changes: 5 additions & 4 deletions stream_chat/tests/async_chat/test_channel.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from typing import Dict, List
import uuid
import time
import uuid
from typing import Dict, List

import pytest

from stream_chat.async_chat.client import StreamChatAsync
from stream_chat.async_chat.channel import Channel
from stream_chat.async_chat.client import StreamChatAsync
from stream_chat.base.exceptions import StreamAPIException


@pytest.mark.incremental
class TestChannel(object):
class TestChannel:
async def test_ban_user(
self, channel: Channel, random_user: Dict, server_user: Dict
):
Expand Down
29 changes: 25 additions & 4 deletions stream_chat/tests/async_chat/test_client.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import json
import sys
import time
import uuid
from contextlib import suppress
from datetime import datetime
from operator import itemgetter
from typing import Dict, List

import jwt
import pytest
import time
import uuid
from stream_chat.async_chat.channel import Channel

from stream_chat.async_chat import StreamChatAsync
from stream_chat.async_chat.channel import Channel
from stream_chat.base.exceptions import StreamAPIException
from stream_chat.tests.utils import wait_for_async


class TestClient(object):
class TestClient:
def test_normalize_sort(self, client: StreamChatAsync):
expected = [
{"field": "field1", "direction": 1},
Expand Down Expand Up @@ -639,3 +642,21 @@ async def test_delete_channels(self, client: StreamChatAsync, channel: Channel):
time.sleep(1)

pytest.fail("task did not succeed")

@pytest.mark.asyncio
async def test_stream_response(self, client: StreamChatAsync):
resp = await client.get_app_settings()

dumped = json.dumps(resp)
assert '{"app":' in dumped
assert "rate_limit" not in dumped
assert "headers" not in dumped
assert "status_code" not in dumped

assert len(resp.headers()) > 0
assert resp.status_code() == 200

rate_limit = resp.rate_limit()
assert rate_limit.limit > 0
assert rate_limit.remaining > 0
assert type(rate_limit.reset) is datetime
2 changes: 1 addition & 1 deletion stream_chat/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from typing import Dict, List
import uuid
from typing import Dict, List

import pytest

Expand Down
8 changes: 4 additions & 4 deletions stream_chat/tests/test_channel.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from typing import Dict, List
import uuid
import time
import uuid
from typing import Dict, List

import pytest

from stream_chat.channel import Channel
from stream_chat import StreamChat
from stream_chat.base.exceptions import StreamAPIException
from stream_chat.channel import Channel


@pytest.mark.incremental
class TestChannel(object):
class TestChannel:
def test_ban_user(self, channel: Channel, random_user, server_user: Dict):
channel.ban_user(random_user["id"], user_id=server_user["id"])
channel.ban_user(
Expand Down
48 changes: 43 additions & 5 deletions stream_chat/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import json
import sys
from operator import itemgetter
import time
import uuid
from contextlib import suppress
from datetime import datetime
from operator import itemgetter
from typing import Dict, List

import jwt
import pytest
import time
import uuid

from stream_chat import StreamChat
from stream_chat.channel import Channel
from stream_chat.base.exceptions import StreamAPIException
from stream_chat.channel import Channel
from stream_chat.tests.utils import wait_for


class TestClient(object):
class TestClient:
def test_normalize_sort(self, client: StreamChat):
expected = [
{"field": "field1", "direction": 1},
Expand Down Expand Up @@ -599,3 +602,38 @@ def test_delete_channels(self, client: StreamChat, channel: Channel):
time.sleep(1)

pytest.fail("task did not succeed")

def test_stream_response_contains_metadata(self, client: StreamChat):
resp = client.get_app_settings()

assert len(resp.headers()) > 0
assert resp.status_code() == 200

rate_limit = resp.rate_limit()
assert rate_limit.limit > 0
assert rate_limit.remaining > 0
assert type(rate_limit.reset) is datetime

def test_stream_response_can_serialize(self, client: StreamChat):
resp = client.get_app_settings()

assert len(resp) == 2
del resp["duration"]
assert '{"app":' in json.dumps(resp)

def test_stream_response(self, client: StreamChat):
resp = client.get_app_settings()

dumped = json.dumps(resp)
assert '{"app":' in dumped
assert "rate_limit" not in dumped
assert "headers" not in dumped
assert "status_code" not in dumped

assert len(resp.headers()) > 0
assert resp.status_code() == 200

rate_limit = resp.rate_limit()
assert rate_limit.limit > 0
assert rate_limit.remaining > 0
assert type(rate_limit.reset) is datetime
Loading

0 comments on commit c472c0b

Please sign in to comment.