Skip to content

Commit bf68108

Browse files
committed
Add initial implementation for publish_to_users
1 parent 1f6407a commit bf68108

File tree

3 files changed

+497
-3
lines changed

3 files changed

+497
-3
lines changed

pusher_push_notifications/__init__.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Pusher Push Notifications Python server SDK"""
22

3+
import copy
34
import datetime
45
import json
56
import re
@@ -17,6 +18,7 @@
1718

1819
USER_ID_MAX_LENGTH = 164
1920
AUTH_TOKEN_DURATION = datetime.timedelta(days=1)
21+
MAX_NUMBER_OF_USER_IDS = 1000
2022

2123

2224
class PusherError(Exception):
@@ -40,6 +42,11 @@ class PusherServerError(PusherError, Exception):
4042
error
4143
"""
4244

45+
class PusherBadResponseError(PusherError, Exception):
46+
"""Error thrown when the server returns a response the library cannot
47+
understand
48+
"""
49+
4350

4451
def handle_http_error(response_body, status_code):
4552
"""Handle different http error codes from the Push Notifications service"""
@@ -153,6 +160,7 @@ def publish(self, interests, publish_body):
153160
+ 'numbers or one of _=@,.;-'
154161
)
155162

163+
publish_body = copy.deepcopy(publish_body)
156164
publish_body['interests'] = interests
157165
session = requests.Session()
158166
request = requests.Request(
@@ -172,19 +180,118 @@ def publish(self, interests, publish_body):
172180
)
173181

174182
response = session.send(request.prepare())
183+
184+
if response.status_code != 200:
185+
try:
186+
response_body = response.json()
187+
except ValueError:
188+
response_body = {}
189+
handle_http_error(response_body, response.status_code)
190+
175191
try:
176192
response_body = response.json()
177193
except ValueError:
178-
response_body = {}
194+
raise PusherBadResponseError(
195+
'The server returned a malformed response',
196+
)
197+
198+
return response_body
199+
200+
def publish_to_users(self, user_ids, publish_body):
201+
"""Publish the given publish_body to the specified users.
202+
203+
Args:
204+
user_ids (list): List of ids of users that the publish body should
205+
be sent to.
206+
publish_body (dict): Dict containing the body of the push
207+
notification publish request.
208+
(see https://docs.pusher.com/push-notifications)
209+
210+
Returns:
211+
A dict containing the publish response from the Pusher Push
212+
Notifications service.
213+
(see https://docs.pusher.com/push-notifications)
214+
215+
Raises:
216+
PusherAuthError: if the secret_key is incorrect
217+
PusherMissingInstanceError: if the instance_id is incorrect
218+
PusherServerError: if the Push Notifications service returns
219+
an error
220+
PusherValidationError: if the publish_body is invalid
221+
TypeError: if user_ids is not a list
222+
TypeError: if publish_body is not a dict
223+
TypeError: if any user id is not a string
224+
ValueError: if len(user_ids) < 1
225+
ValueError: if len(user_ids) is greater than the max
226+
ValueError: if any user id length is greater than the max
227+
228+
"""
229+
if not isinstance(user_ids, list):
230+
raise TypeError('user_ids must be a list')
231+
if not isinstance(publish_body, dict):
232+
raise TypeError('publish_body must be a dictionary')
233+
if not user_ids:
234+
raise ValueError('Publishes must target at least one user')
235+
if len(user_ids) > MAX_NUMBER_OF_USER_IDS:
236+
raise ValueError(
237+
'Number of user ids ({}) exceeds maximum of {}'.format(
238+
len(user_ids),
239+
MAX_NUMBER_OF_USER_IDS,
240+
),
241+
)
242+
for user_id in user_ids:
243+
if not isinstance(user_id, six.string_types):
244+
raise TypeError(
245+
'User id {} is not a string'.format(user_id)
246+
)
247+
if len(user_id) > USER_ID_MAX_LENGTH:
248+
raise ValueError(
249+
'User id "{}" is longer than the maximum of {} chars'.format(
250+
user_id,
251+
USER_ID_MAX_LENGTH,
252+
)
253+
)
254+
255+
publish_body = copy.deepcopy(publish_body)
256+
publish_body['users'] = user_ids
257+
session = requests.Session()
258+
request = requests.Request(
259+
'POST',
260+
'https://{}/publish_api/v1/instances/{}/publishes/users'.format(
261+
self.endpoint,
262+
self.instance_id,
263+
),
264+
json=publish_body,
265+
headers={
266+
'host': self.endpoint,
267+
'authorization': 'Bearer {}'.format(self.secret_key),
268+
'x-pusher-library': 'pusher-push-notifications-python {}'.format(
269+
SDK_VERSION,
270+
)
271+
},
272+
)
273+
274+
response = session.send(request.prepare())
179275

180276
if response.status_code != 200:
277+
try:
278+
response_body = response.json()
279+
except ValueError:
280+
response_body = {}
181281
handle_http_error(response_body, response.status_code)
182282

283+
try:
284+
response_body = response.json()
285+
except ValueError:
286+
raise PusherBadResponseError(
287+
'The server returned a malformed response',
288+
)
289+
183290
return response_body
184291

185292
def authenticate_user(self, user_id):
186293
"""Generate an auth token which will allow devices to associate
187-
themselves with the given user id
294+
themselves with the given user id
188295
189296
Args:
190297
user_id (string): user id for which the token will be valid

tests/test_interests.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
PusherMissingInstanceError,
1212
PusherServerError,
1313
PusherValidationError,
14+
PusherBadResponseError,
1415
)
1516

1617

@@ -387,3 +388,28 @@ def test_publish_should_error_correctly_if_error_not_json(self):
387388
},
388389
)
389390
self.assertIn('Unknown error: no description', str(e.exception))
391+
392+
def test_publish_should_handle_not_json_success(self):
393+
pn_client = PushNotifications(
394+
'INSTANCE_ID',
395+
'SECRET_KEY'
396+
)
397+
with requests_mock.Mocker() as http_mock:
398+
http_mock.register_uri(
399+
requests_mock.ANY,
400+
requests_mock.ANY,
401+
status_code=200,
402+
text='<notjson></notjson>',
403+
)
404+
with self.assertRaises(PusherBadResponseError) as e:
405+
pn_client.publish(
406+
interests=['donuts'],
407+
publish_body={
408+
'apns': {
409+
'aps': {
410+
'alert': 'Hello World!',
411+
},
412+
},
413+
},
414+
)
415+
self.assertIn('The server returned a malformed response', str(e.exception))

0 commit comments

Comments
 (0)