Skip to content

Commit cca865a

Browse files
Add support for content_available for apns async (#761)
* Add support for `content_available` for apns async - add `content_available` argument for async version of apns. Argument is sent as `content-available` to apns - add tests for `content_available` - format `apns_async.py` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * type content_available as bool only --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1983620 commit cca865a

File tree

2 files changed

+120
-54
lines changed

2 files changed

+120
-54
lines changed

push_notifications/apns_async.py

+70-54
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,7 @@ def _create_notification_request_from_args(
141141
notification_request_kwargs_out = notification_request_kwargs.copy()
142142

143143
if expiration is not None:
144-
notification_request_kwargs_out["time_to_live"] = expiration - int(
145-
time.time()
146-
)
144+
notification_request_kwargs_out["time_to_live"] = expiration - int(time.time())
147145
if priority is not None:
148146
notification_request_kwargs_out["priority"] = priority
149147

@@ -216,6 +214,7 @@ def apns_send_message(
216214
topic: str = None,
217215
badge: int = None,
218216
sound: str = None,
217+
content_available: bool = None,
219218
extra: dict = {},
220219
expiration: int = None,
221220
thread_id: str = None,
@@ -240,13 +239,14 @@ def apns_send_message(
240239
:param alert: The alert message to send
241240
:param application_id: The application_id to use
242241
:param creds: The credentials to use
243-
:param mutable_content: If True, enables the "mutable-content" flag in the payload.
244-
This allows the app's Notification Service Extension to modify
245-
the notification before it is displayed.
242+
:param mutable_content: If True, enables the "mutable-content" flag in the payload.
243+
This allows the app's Notification Service Extension to modify
244+
the notification before it is displayed.
246245
:param category: The category identifier for actionable notifications.
247-
This should match a category identifier defined in the app's
248-
Notification Content Extension or UNNotificationCategory configuration.
249-
It allows the app to display custom actions with the notification.
246+
This should match a category identifier defined in the app's
247+
Notification Content Extension or UNNotificationCategory configuration.
248+
It allows the app to display custom actions with the notification.
249+
:param content_available: If True the `content-available` flag will be set to 1, allowing the app to be woken up in the background
250250
"""
251251
results = apns_send_bulk_message(
252252
registration_ids=[registration_id],
@@ -256,14 +256,15 @@ def apns_send_message(
256256
topic=topic,
257257
badge=badge,
258258
sound=sound,
259+
content_available=content_available,
259260
extra=extra,
260261
expiration=expiration,
261262
thread_id=thread_id,
262263
loc_key=loc_key,
263264
priority=priority,
264265
collapse_id=collapse_id,
265266
mutable_content=mutable_content,
266-
category = category,
267+
category=category,
267268
err_func=err_func,
268269
)
269270

@@ -282,6 +283,7 @@ def apns_send_bulk_message(
282283
topic: str = None,
283284
badge: int = None,
284285
sound: str = None,
286+
content_available: bool = None,
285287
extra: dict = {},
286288
expiration: int = None,
287289
thread_id: str = None,
@@ -304,37 +306,41 @@ def apns_send_bulk_message(
304306
:param alert: The alert message to send
305307
:param application_id: The application_id to use
306308
:param creds: The credentials to use
307-
:param mutable_content: If True, enables the "mutable-content" flag in the payload.
308-
This allows the app's Notification Service Extension to modify
309-
the notification before it is displayed.
309+
:param mutable_content: If True, enables the "mutable-content" flag in the payload.
310+
This allows the app's Notification Service Extension to modify
311+
the notification before it is displayed.
310312
:param category: The category identifier for actionable notifications.
311-
This should match a category identifier defined in the app's
312-
Notification Content Extension or UNNotificationCategory configuration.
313-
It allows the app to display custom actions with the notification.
313+
This should match a category identifier defined in the app's
314+
Notification Content Extension or UNNotificationCategory configuration.
315+
It allows the app to display custom actions with the notification.
316+
:param content_available: If True the `content-available` flag will be set to 1, allowing the app to be woken up in the background
314317
"""
315318
try:
316319
topic = get_manager().get_apns_topic(application_id)
317320
results: Dict[str, str] = {}
318321
inactive_tokens = []
319322

320-
responses = asyncio.run(_send_bulk_request(
321-
registration_ids=registration_ids,
322-
alert=alert,
323-
application_id=application_id,
324-
creds=creds,
325-
topic=topic,
326-
badge=badge,
327-
sound=sound,
328-
extra=extra,
329-
expiration=expiration,
330-
thread_id=thread_id,
331-
loc_key=loc_key,
332-
priority=priority,
333-
collapse_id=collapse_id,
334-
mutable_content=mutable_content,
335-
category=category,
336-
err_func=err_func,
337-
))
323+
responses = asyncio.run(
324+
_send_bulk_request(
325+
registration_ids=registration_ids,
326+
alert=alert,
327+
application_id=application_id,
328+
creds=creds,
329+
topic=topic,
330+
badge=badge,
331+
sound=sound,
332+
content_available=content_available,
333+
extra=extra,
334+
expiration=expiration,
335+
thread_id=thread_id,
336+
loc_key=loc_key,
337+
priority=priority,
338+
collapse_id=collapse_id,
339+
mutable_content=mutable_content,
340+
category=category,
341+
err_func=err_func,
342+
)
343+
)
338344

339345
results = {}
340346
errors = []
@@ -344,14 +350,17 @@ def apns_send_bulk_message(
344350
)
345351
if not result.is_successful:
346352
errors.append(result.description)
347-
if result.description in ["Unregistered", "BadDeviceToken",
348-
"DeviceTokenNotForTopic"]:
353+
if result.description in [
354+
"Unregistered",
355+
"BadDeviceToken",
356+
"DeviceTokenNotForTopic",
357+
]:
349358
inactive_tokens.append(registration_id)
350359

351360
if len(inactive_tokens) > 0:
352-
models.APNSDevice.objects.filter(registration_id__in=inactive_tokens).update(
353-
active=False
354-
)
361+
models.APNSDevice.objects.filter(
362+
registration_id__in=inactive_tokens
363+
).update(active=False)
355364

356365
if len(errors) > 0:
357366
msg = "One or more errors failed with errors: {}".format(", ".join(errors))
@@ -371,6 +380,7 @@ async def _send_bulk_request(
371380
topic: str = None,
372381
badge: int = None,
373382
sound: str = None,
383+
content_available: bool = None,
374384
extra: dict = {},
375385
expiration: int = None,
376386
thread_id: str = None,
@@ -391,19 +401,25 @@ async def _send_bulk_request(
391401
if category:
392402
aps_kwargs["category"] = category
393403

394-
requests = [_create_notification_request_from_args(
395-
registration_id,
396-
alert,
397-
badge=badge,
398-
sound=sound,
399-
extra=extra,
400-
expiration=expiration,
401-
thread_id=thread_id,
402-
loc_key=loc_key,
403-
priority=priority,
404-
collapse_id=collapse_id,
405-
aps_kwargs=aps_kwargs
406-
) for registration_id in registration_ids]
404+
if content_available:
405+
aps_kwargs["content-available"] = 1
406+
407+
requests = [
408+
_create_notification_request_from_args(
409+
registration_id,
410+
alert,
411+
badge=badge,
412+
sound=sound,
413+
extra=extra,
414+
expiration=expiration,
415+
thread_id=thread_id,
416+
loc_key=loc_key,
417+
priority=priority,
418+
collapse_id=collapse_id,
419+
aps_kwargs=aps_kwargs,
420+
)
421+
for registration_id in registration_ids
422+
]
407423

408424
send_requests = [_send_request(client, request) for request in requests]
409425
return await asyncio.gather(*send_requests)
@@ -417,11 +433,11 @@ async def _send_request(apns, request):
417433
return request.device_token, NotificationResult(
418434
notification_id=request.notification_id,
419435
status="failed",
420-
description="TimeoutError"
436+
description="TimeoutError",
421437
)
422438
except:
423439
return request.device_token, NotificationResult(
424440
notification_id=request.notification_id,
425441
status="failed",
426-
description="CommunicationError"
442+
description="CommunicationError",
427443
)

tests/test_apns_async_push_payload.py

+50
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,53 @@ def test_push_payload_with_category(self, mock_apns):
226226
# self.assertRaises(APNSUnsupportedPriority, _apns_send, "123",
227227
# "_" * 2049, priority=24)
228228
# s.assert_has_calls([])
229+
230+
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
231+
def test_push_payload_with_content_available_bool_true(self, mock_apns):
232+
apns_send_message(
233+
"123",
234+
"Hello world",
235+
content_available=True,
236+
creds=TokenCredentials(key="aaa", key_id="bbb", team_id="ccc"),
237+
extra={"custom_data": 12345},
238+
expiration=int(time.time()) + 3,
239+
)
240+
241+
args, kwargs = mock_apns.return_value.send_notification.call_args
242+
req = args[0]
243+
244+
assert "content-available" in req.message["aps"]
245+
assert req.message["aps"]["content-available"] == 1
246+
247+
248+
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
249+
def test_push_payload_with_content_available_bool_false(self, mock_apns):
250+
apns_send_message(
251+
"123",
252+
"Hello world",
253+
content_available=False,
254+
creds=TokenCredentials(key="aaa", key_id="bbb", team_id="ccc"),
255+
extra={"custom_data": 12345},
256+
expiration=int(time.time()) + 3,
257+
)
258+
259+
args, kwargs = mock_apns.return_value.send_notification.call_args
260+
req = args[0]
261+
262+
assert "content-available" not in req.message["aps"]
263+
264+
265+
@mock.patch("push_notifications.apns_async.APNs", autospec=True)
266+
def test_push_payload_with_content_available_not_set(self, mock_apns):
267+
apns_send_message(
268+
"123",
269+
"Hello world",
270+
creds=TokenCredentials(key="aaa", key_id="bbb", team_id="ccc"),
271+
extra={"custom_data": 12345},
272+
expiration=int(time.time()) + 3,
273+
)
274+
275+
args, kwargs = mock_apns.return_value.send_notification.call_args
276+
req = args[0]
277+
278+
assert "content-available" not in req.message["aps"]

0 commit comments

Comments
 (0)