Skip to content

Commit e998693

Browse files
author
Max Klyga
committed
Merge branch 'master' of github.com:GetStream/stream-chat-python
2 parents 41b2c80 + f1a25ab commit e998693

File tree

9 files changed

+166
-43
lines changed

9 files changed

+166
-43
lines changed

.travis.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,21 @@ language: python
22
dist: xenial
33
matrix:
44
include:
5-
- python: 2.7
6-
- python: 3.4
75
- python: 3.5
86
- python: 3.6
97
- python: 3.7
108
sudo: true
119
cache: pip
1210
install:
13-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install black; fi
14-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install pycodestyle; fi
15-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install pytest-cov; fi
16-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install codecov; fi
11+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then pip install black; fi
12+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then pip install pycodestyle; fi
13+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then pip install pytest-cov; fi
14+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then pip install codecov; fi
1715
script:
1816
- echo $STREAM_KEY
1917
- python setup.py test
20-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then black stream_chat; fi
21-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pycodestyle --ignore=E501,E225,W293 stream_chat; fi
18+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then black stream_chat; fi
19+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then pycodestyle --ignore=E501,E225,W293 stream_chat; fi
2220
- python setup.py install
2321
after_script:
24-
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then codecov; fi
22+
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 ]]; then codecov; fi

CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Oct 19th, 2019 - 1.0.0
2+
3+
- Added support for user partial update endpoint

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ channel = chat.channel("messaging", "kung-fu")
4747
channel.create("chuck")
4848

4949
# add a first message to the channel
50-
channel.send_message({"text": "AMA about kung-fu"})
50+
channel.send_message({"text": "AMA about kung-fu"}, "chuck")
5151

5252
```
5353

setup.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,13 @@
33
from setuptools import find_packages, setup
44
from setuptools.command.test import test as TestCommand
55

6-
requests = "requests>=2.3.0,<3"
7-
8-
# python 2.7.9 does not support SNI
9-
if sys.version_info < (2, 7, 9):
10-
requests = "requests[security]>=2.4.1,<3"
6+
requests = "requests>=2.22.0,<3"
117

128
install_requires = [
139
"pycryptodomex==3.8.1",
1410
requests,
1511
"pyjwt==1.7.1",
16-
"six>=1.8.0",
12+
"six>=1.12.0",
1713
]
1814
long_description = open("README.md", "r").read()
1915
tests_require = ["pytest"]
@@ -59,7 +55,7 @@ def run_tests(self):
5955
extras_require={"test": tests_require},
6056
tests_require=tests_require,
6157
include_package_data=True,
62-
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
58+
python_requires='>=3.5',
6359
classifiers=[
6460
"Intended Audience :: Developers",
6561
"Intended Audience :: System Administrators",
@@ -68,7 +64,6 @@ def run_tests(self):
6864
"Development Status :: 5 - Production/Stable",
6965
"Natural Language :: English",
7066
"Programming Language :: Python :: 3",
71-
"Programming Language :: Python :: 3.4",
7267
"Programming Language :: Python :: 3.5",
7368
"Programming Language :: Python :: 3.6",
7469
"Programming Language :: Python :: 3.7",

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, Stream.io, Inc"
3-
__version__ = "0.4.3"
3+
__version__ = "1.0.0"
44
__maintainer__ = "Tommaso Barbugli"
55
__email__ = "[email protected]"
66
__status__ = "Production"

stream_chat/channel.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,23 @@ def accept_invite(self, user_id):
218218
def reject_invite(self, user_id):
219219
raise NotImplementedError
220220

221-
def send_file(self):
222-
raise NotImplementedError
221+
def send_file(self, url, name, user, content_type=None):
222+
return self.client.send_file("{}/file".format(self.url), url, name, user, content_type=content_type)
223223

224-
def send_image(self):
225-
raise NotImplementedError
224+
def send_image(self, url, name, user, content_type=None):
225+
return self.client.send_file("{}/image".format(self.url), url, name, user, content_type=content_type)
226226

227-
def delete_file(self):
228-
raise NotImplementedError
227+
def delete_file(self, url):
228+
return self.client.delete("{}/file".format(self.url), {"url": url})
229229

230-
def delete_image(self):
231-
raise NotImplementedError
230+
def delete_image(self, url):
231+
return self.client.delete("{}/image".format(self.url), {"url": url})
232+
233+
def hide(self, user_id):
234+
return self.client.post("{}/hide".format(self.url), data={"user_id": user_id})
235+
236+
def show(self, user_id):
237+
return self.client.post("{}/show".format(self.url), data={"user_id": user_id})
232238

233239

234240
def add_user_id(payload, user_id):

stream_chat/client.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
from urllib.parse import urlparse
2+
import hmac
3+
import hashlib
14
import json
5+
import urllib
26

7+
import jwt
38
import requests
49
import six
510

611
from stream_chat.__pkg__ import __version__
712
from stream_chat.channel import Channel
8-
import jwt
9-
import hmac
10-
import hashlib
11-
1213
from stream_chat.exceptions import StreamAPIException
1314

1415

@@ -105,6 +106,12 @@ def update_users(self, users):
105106
def update_user(self, user):
106107
return self.update_users([user])
107108

109+
def update_users_partial(self, updates):
110+
return self.patch("users", data={"users": updates})
111+
112+
def update_user_partial(self, update):
113+
return self.update_users_partial([update])
114+
108115
def delete_user(self, user_id, **options):
109116
return self.delete("users/{}".format(user_id), options)
110117

@@ -178,8 +185,8 @@ def update_message(self, message):
178185
raise ValueError("message must have an id")
179186
return self.post("messages/{}".format(message['id']), data={"message": message})
180187

181-
def delete_message(self, message_id):
182-
return self.delete("messages/{}".format(message_id))
188+
def delete_message(self, message_id, **options):
189+
return self.delete("messages/{}".format(message_id), options)
183190

184191
def query_users(self, filter_conditions, sort=None, **options):
185192
sort_fields = []
@@ -189,7 +196,7 @@ def query_users(self, filter_conditions, sort=None, **options):
189196
params.update({"filter_conditions": filter_conditions, "sort": sort_fields})
190197
return self.get("users", params={"payload": json.dumps(params)})
191198

192-
def query_channels(self, filter_conditions, sort, **options):
199+
def query_channels(self, filter_conditions, sort=None, **options):
193200
params = {"state": True, "watch": False, "presence": False}
194201
sort_fields = []
195202
if sort is not None:
@@ -210,7 +217,7 @@ def list_channel_types(self):
210217
return self.get("channeltypes")
211218

212219
def update_channel_type(self, channel_type, **settings):
213-
return self.put("channeltypes/{}".format(channel_type), **settings)
220+
return self.put("channeltypes/{}".format(channel_type), data=settings)
214221

215222
def delete_channel_type(self, channel_type):
216223
"""
@@ -281,4 +288,26 @@ def verify_webhook(self, request_body, x_signature):
281288
return signature == x_signature
282289

283290
def search(self, filter_conditions, query, **options):
284-
raise NotImplementedError
291+
params = options.copy()
292+
params.update({"filter_conditions": filter_conditions, "query": query})
293+
return self.get("search", params={"payload": json.dumps(params)})
294+
295+
def send_file(self, uri, url, name, user, content_type=None):
296+
headers = {}
297+
headers["Authorization"] = self.auth_token
298+
headers["stream-auth-type"] = "jwt"
299+
headers["X-Stream-Client"] = get_user_agent()
300+
parts = urlparse(url)
301+
if parts[0] == '':
302+
url = "file://" + url
303+
if content_type:
304+
file_tuple = (name, urllib.request.urlopen(url), content_type)
305+
else:
306+
file_tuple = (name, urllib.request.urlopen(url), content_type)
307+
response = requests.post(
308+
"{}/{}".format(self.base_url, uri),
309+
params=self.get_default_params(),
310+
data={"user": json.dumps(user)},
311+
files={"file": file_tuple},
312+
headers=headers)
313+
return self._parse_response(response)

stream_chat/tests/test_channel.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,44 @@ def test_get_reactions(self, channel, random_user):
120120
assert len(response["reactions"]) == 1
121121

122122
assert response["reactions"][0]["count"] == 42
123+
124+
def test_send_and_delete_file(self, channel, random_user):
125+
url = "https://getstream.io/blog/wp-content/themes/stream-theme-wordpress_2018-05-24_10-41/assets/images/stream_logo.png";
126+
resp = channel.send_file(url, "logo.png", random_user)
127+
assert "logo.png" in resp['file']
128+
resp = channel.delete_file(resp['file'])
129+
130+
def test_send_and_delete_image(self, channel, random_user):
131+
url = "https://getstream.io/blog/wp-content/themes/stream-theme-wordpress_2018-05-24_10-41/assets/images/stream_logo.png";
132+
resp = channel.send_image(url, "logo.png", random_user, content_type="image/png")
133+
assert "logo.png" in resp['file']
134+
# resp = channel.delete_image(resp['file'])
135+
136+
def test_channel_hide_show(self, client, channel, random_users):
137+
# setup
138+
channel.add_members([u['id'] for u in random_users])
139+
# verify
140+
response = client.query_channels({"id": channel.id})
141+
assert len(response['channels']) == 1
142+
response = client.query_channels({"id": channel.id}, user_id=random_users[0]['id'])
143+
assert len(response['channels']) == 1
144+
# hide
145+
channel.hide(random_users[0]['id'])
146+
response = client.query_channels({"id": channel.id}, user_id=random_users[0]['id'])
147+
assert len(response['channels']) == 0
148+
# search hidden channels
149+
response = client.query_channels({"id": channel.id, "hidden": True}, user_id=random_users[0]['id'])
150+
assert len(response['channels']) == 1
151+
# unhide
152+
channel.show(random_users[0]['id'])
153+
response = client.query_channels({"id": channel.id}, user_id=random_users[0]['id'])
154+
assert len(response['channels']) == 1
155+
# hide again
156+
channel.hide(random_users[0]['id'])
157+
response = client.query_channels({"id": channel.id}, user_id=random_users[0]['id'])
158+
assert len(response['channels']) == 0
159+
# send message
160+
msg = channel.send_message({"text": "hi"}, random_users[1]["id"])
161+
# channel should be listed now
162+
response = client.query_channels({"id": channel.id}, user_id=random_users[0]['id'])
163+
assert len(response['channels']) == 1

stream_chat/tests/test_client.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ def test_list_channel_types(self, client):
2727
response = client.list_channel_types()
2828
assert "channel_types" in response
2929

30+
def test_update_channel_type(self, client):
31+
response = client.update_channel_type("team", commands=["ban", "unban"])
32+
assert "commands" in response
33+
assert response["commands"] == ["ban", "unban"]
34+
3035
def test_create_token(self, client):
3136
token = client.create_token("tommaso")
32-
payload = jwt.decode(token, client.api_secret, algorithm="HS256")
37+
payload = jwt.decode(token, client.api_secret, algorithms=["HS256"])
3338
assert payload.get("user_id") == "tommaso"
3439

3540
def test_get_app_settings(self, client):
@@ -48,6 +53,21 @@ def test_update_users(self, client):
4853
assert "users" in response
4954
assert user["id"] in response["users"]
5055

56+
def test_update_user_partial(self, client):
57+
user_id = str(uuid.uuid4())
58+
client.update_user({"id": user_id, "field": "value"})
59+
60+
response = client.update_user_partial({
61+
"id": user_id,
62+
"set": {
63+
"field": "updated"
64+
}
65+
})
66+
67+
assert "users" in response
68+
assert user_id in response["users"]
69+
assert response["users"][user_id]["field"] == "updated"
70+
5171
def test_delete_user(self, client, random_user):
5272
response = client.delete_user(random_user["id"])
5373
assert "user" in response
@@ -58,6 +78,14 @@ def test_deactivate_user(self, client, random_user):
5878
assert "user" in response
5979
assert random_user["id"] == response["user"]["id"]
6080

81+
def test_reactivate_user(self, client, random_user):
82+
response = client.deactivate_user(random_user["id"])
83+
assert "user" in response
84+
assert random_user["id"] == response["user"]["id"]
85+
response = client.reactivate_user(random_user["id"])
86+
assert "user" in response
87+
assert random_user["id"] == response["user"]["id"]
88+
6189
def test_export_user(self, client, fellowship_of_the_ring):
6290
response = client.export_user("gandalf")
6391
assert "user" in response
@@ -99,6 +127,9 @@ def test_delete_message(self, client, channel, random_user):
99127
msg_id = str(uuid.uuid4())
100128
channel.send_message({"id": msg_id, "text": "helloworld"}, random_user["id"])
101129
client.delete_message(msg_id)
130+
msg_id = str(uuid.uuid4())
131+
channel.send_message({"id": msg_id, "text": "helloworld"}, random_user["id"])
132+
resp = client.delete_message(msg_id, hard=True)
102133

103134
def test_flag_message(self, client, channel, random_user, server_user):
104135
msg_id = str(uuid.uuid4())
@@ -116,12 +147,6 @@ def test_query_users_young_hobbits(self, client, fellowship_of_the_ring):
116147
assert len(response["users"]) == 4
117148
assert [50, 38, 36, 28] == [u["age"] for u in response["users"]]
118149

119-
def test_query_channels_members_in(self, client, fellowship_of_the_ring):
120-
response = client.query_channels({"members": {"$in": ["gimli"]}}, {"id": 1})
121-
assert len(response["channels"]) == 1
122-
assert response["channels"][0]["channel"]["id"] == "fellowship-of-the-ring"
123-
assert len(response["channels"][0]["members"]) == 9
124-
125150
def test_devices(self, client, random_user):
126151
response = client.get_devices(random_user["id"])
127152
assert "devices" in response
@@ -135,3 +160,29 @@ def test_devices(self, client, random_user):
135160
client.add_device(str(uuid.uuid4()), "apn", random_user["id"])
136161
response = client.get_devices(random_user["id"])
137162
assert len(response["devices"]) == 1
163+
164+
def test_search(self, client, channel, random_user):
165+
query = "supercalifragilisticexpialidocious"
166+
channel.send_message({"text": "How many syllables are there in {}?".format(query)}, random_user['id'])
167+
channel.send_message({"text": "Does 'cious' count as one or two?"}, random_user['id'])
168+
response = client.search(
169+
{"type": "messaging"},
170+
query,
171+
**{"limit": 2, "offset": 0}
172+
)
173+
# searches all channels so make sure at least one is found
174+
assert len(response['results']) >= 1
175+
assert query in response['results'][0]['message']['text']
176+
response = client.search(
177+
{"type": "messaging"},
178+
"cious",
179+
**{"limit": 12, "offset": 0})
180+
for message in response['results']:
181+
assert query not in message['message']['text']
182+
183+
def test_query_channels_members_in(self, client, fellowship_of_the_ring):
184+
response = client.query_channels({"members": {"$in": ["gimli"]}}, {"id": 1})
185+
assert len(response["channels"]) == 1
186+
assert response["channels"][0]["channel"]["id"] == "fellowship-of-the-ring"
187+
assert len(response["channels"][0]["members"]) == 9
188+

0 commit comments

Comments
 (0)