Skip to content

Commit 5e63e01

Browse files
martin-vctcustomer-engineering
andauthored
[TOOLSLIBS-1377] Add Live Activity/Update, new options, backoff on ConnectionError (#203)
* Add Live Activity/Update, new options, backoff on ConnectionError * update python versions for CI * rollback python versions * fixes * Update test_runner.yaml * Fix typing * update tests * update tests * add ConnectionFailure class * add condition for content_state keys --------- Co-authored-by: customer-engineering <[email protected]>
1 parent a40c0f2 commit 5e63e01

File tree

13 files changed

+389
-35
lines changed

13 files changed

+389
-35
lines changed

.github/workflows/test_runner.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ on: [push]
44

55
jobs:
66
build:
7-
8-
runs-on: ubuntu-latest
7+
runs-on: ubuntu-20.04
98
strategy:
109
matrix:
1110
python-version: ["3.6", "3.7", "3.8", "3.9"]

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Exceptions
8282

8383
.. autoclass:: urbanairship.Unauthorized
8484

85+
.. autoclass:: urbanairship.ConnectionFailure
8586

8687
Development
8788
============

docs/push.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ attributes, the notification is ready for delivery. Use ``Push.send()`` to send
9797
If the request is unsuccessful, an :py:class:`AirshipFailure` exception
9898
will be raised.
9999

100+
If the connection is unsuccessful, an :py:class:`ConnectionFailure` exception
101+
will be raised.
102+
100103
.. autoclass:: urbanairship.push.core.Push
101104
:members: send, validate
102105

tests/push/test_push.py

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
import requests
77
import urbanairship as ua
88
from tests import TEST_KEY, TEST_SECRET
9-
from urbanairship.push.payload import in_app, localization
9+
from urbanairship.enums import LiveActivityEvent, LiveUpdateEvent
1010

1111

1212
class TestPush(unittest.TestCase):
1313
def test_full_payload(self):
14-
1514
p = ua.Push(None)
1615
p.audience = ua.all_
1716
p.notification = ua.notification(alert="Hello")
@@ -547,6 +546,28 @@ def test_ios_overrides(self):
547546
interruption_level="critical",
548547
relevance_score=0.75,
549548
target_content_id="big day coming",
549+
media_attachment=ua.media_attachment(
550+
url="https://www.testurl.com",
551+
content={
552+
"title": "Moustache Twirl",
553+
"body": "Have you ever seen a moustache like this?!",
554+
},
555+
options={
556+
"crop": {"height": 0.5, "width": 0.5, "x": 0.25, "y": 0.25},
557+
"time": 15,
558+
},
559+
),
560+
live_activity=ua.live_activity(
561+
event=LiveActivityEvent.UPDATE,
562+
alert={"title": "test", "sound": "test", "body": "test"},
563+
name="test",
564+
priority=5,
565+
content_state={"test": "test"},
566+
relevance_score=1.0,
567+
stale_date=1234,
568+
dismissal_date=1234,
569+
timestamp=1234,
570+
),
550571
)
551572
)
552573
p.options = ua.options(10080)
@@ -570,22 +591,49 @@ def test_ios_overrides(self):
570591
"summary-arg": "Matmos",
571592
"summary-arg-count": 1,
572593
},
594+
"badge": 3,
573595
"sound": {
574596
"name": "Amplified Synapse",
575597
"volume": 0.8,
576598
"critical": False,
577599
},
578-
"thread_id": "plastic minor",
579-
"priority": 10,
580-
"badge": 3,
581600
"extra": {"office": "furniture"},
582-
"mutable_content": False,
583601
"title": "this is",
602+
"mutable_content": False,
584603
"subtitle": "backwards",
604+
"media_attachment": {
605+
"url": "https://www.testurl.com",
606+
"content": {
607+
"title": "Moustache Twirl",
608+
"body": "Have you ever seen a moustache like this?!",
609+
},
610+
"options": {
611+
"crop": {
612+
"height": 0.5,
613+
"width": 0.5,
614+
"x": 0.25,
615+
"y": 0.25,
616+
},
617+
"time": 15,
618+
},
619+
},
620+
"priority": 10,
585621
"collapse_id": "nugent sand",
622+
"thread_id": "plastic minor",
586623
"interruption_level": "critical",
587624
"relevance_score": 0.75,
588625
"target_content_id": "big day coming",
626+
"live_activity": {
627+
"alert": {"title": "test", "sound": "test", "body": "test"},
628+
"event": "update",
629+
"name": "test",
630+
"priority": 5,
631+
"content_state": {"test": "test"},
632+
"relevance_score": 1.0,
633+
"stale_date": 1234,
634+
"dismissal_date": 1234,
635+
"timestamp": 1234,
636+
},
589637
}
590638
},
591639
"device_types": "ios",
@@ -603,7 +651,7 @@ def test_full_scheduled_payload(self):
603651
p = ua.Push(None)
604652
p.audience = ua.all_
605653
p.notification = ua.notification(alert="Hello")
606-
p.options = ua.options(10080)
654+
p.options = ua.options(expiry=10080)
607655
p.device_types = ua.all_
608656
p.message = ua.message(
609657
title="Title",
@@ -1110,3 +1158,83 @@ def test_localization_raises_no_country_lang(self):
11101158
def testlocalization_raises_no_content(self):
11111159
with self.assertRaises(ValueError):
11121160
ua.localization(country="us", language="en")
1161+
1162+
def test_options_expiry_as_int(self):
1163+
result = ua.options(expiry=300)
1164+
assert result == {"expiry": 300}
1165+
1166+
def test_options_expiry_as_string(self):
1167+
result = ua.options(expiry="2023-10-19T10:00:00Z")
1168+
assert result == {"expiry": "2023-10-19T10:00:00Z"}
1169+
1170+
def test_options_with_multiple_values(self):
1171+
result = ua.options(
1172+
expiry="2023-10-19T10:00:00Z",
1173+
bypass_frequency_limits=True,
1174+
bypass_holdout_groups=True,
1175+
no_throttle=True,
1176+
omit_from_activity_log=True,
1177+
personalization=True,
1178+
redact_payload=True,
1179+
)
1180+
expected_result = {
1181+
"expiry": "2023-10-19T10:00:00Z",
1182+
"bypass_frequency_limits": True,
1183+
"bypass_holdout_groups": True,
1184+
"no_throttle": True,
1185+
"omit_from_activity_log": True,
1186+
"personalization": True,
1187+
"redact_payload": True,
1188+
}
1189+
assert result == expected_result
1190+
1191+
def test_valid_live_activity(self):
1192+
result = ua.live_activity(
1193+
event=LiveActivityEvent.UPDATE,
1194+
name="TestActivity",
1195+
alert={"body": "Test body", "sound": "default", "title": "Test Title"},
1196+
priority=10,
1197+
)
1198+
self.assertIn("alert", result)
1199+
self.assertEqual(result["event"], "update")
1200+
self.assertEqual(result["name"], "TestActivity")
1201+
self.assertEqual(result["priority"], 10)
1202+
1203+
def test_invalid_alert(self):
1204+
with self.assertRaises(ValueError):
1205+
ua.live_activity(
1206+
event=LiveActivityEvent.UPDATE,
1207+
name="TestActivity",
1208+
alert={"other_key": "test", "title": "test"},
1209+
priority=10,
1210+
)
1211+
1212+
def test_missing_name_live_activity(self):
1213+
with self.assertRaises(ValueError):
1214+
ua.live_activity(name=None, event=LiveActivityEvent.END, priority=10)
1215+
1216+
def test_valid_live_update(self):
1217+
result = ua.live_update(
1218+
event=LiveUpdateEvent.START,
1219+
name="TestUpdate",
1220+
content_state={"key": "value"},
1221+
)
1222+
self.assertEqual(result["event"], "start")
1223+
self.assertEqual(result["name"], "TestUpdate")
1224+
self.assertIn("content_state", result)
1225+
1226+
def test_invalid_content_state(self):
1227+
with self.assertRaises(TypeError):
1228+
ua.live_update(
1229+
event=LiveUpdateEvent.UPDATE, name="TestUpdate", content_state="invalid"
1230+
)
1231+
with self.assertRaises(TypeError):
1232+
ua.live_update(
1233+
event=LiveUpdateEvent.UPDATE,
1234+
name="TestUpdate",
1235+
content_state={1: "test"},
1236+
)
1237+
1238+
def test_missing_name_live_update(self):
1239+
with self.assertRaises(ValueError):
1240+
ua.live_update(name=None, event=LiveUpdateEvent.END)

urbanairship/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .core import Airship
66
from .automation import Automation, Pipeline
7-
from .common import AirshipFailure, Unauthorized
7+
from .common import AirshipFailure, ConnectionFailure, Unauthorized
88
from .custom_events import CustomEvent
99
from .devices import (
1010
APIDList,
@@ -65,8 +65,11 @@
6565
interactive,
6666
ios,
6767
ios_channel,
68+
live_activity,
69+
live_update,
6870
local_scheduled_time,
6971
localization,
72+
media_attachment,
7073
merge_data,
7174
message,
7275
mms,
@@ -115,6 +118,7 @@
115118
__all__: List[Any] = [
116119
Airship,
117120
AirshipFailure,
121+
ConnectionFailure,
118122
Unauthorized,
119123
all_,
120124
Push,
@@ -167,6 +171,9 @@
167171
static_list,
168172
subscription_list,
169173
localization,
174+
live_activity,
175+
live_update,
176+
media_attachment,
170177
ChannelList,
171178
ChannelInfo,
172179
OpenChannel,

urbanairship/common.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class Unauthorized(Exception):
1111
"""Raised when we get a 401 from the server"""
1212

1313

14+
class ConnectionFailure(Exception):
15+
"""Raised when there's a connection failure."""
16+
17+
1418
class AirshipFailure(Exception):
1519
"""Raised when we get an error response from the server.
1620

urbanairship/core.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ def _request(
193193
params: Optional[Dict[str, Any]] = None,
194194
encoding: Optional[str] = None,
195195
) -> requests.Response:
196-
197196
headers: Dict[str, str] = {
198197
"User-agent": "UAPythonLib/{0} {1}".format(__about__.__version__, self.key)
199198
}
@@ -207,7 +206,9 @@ def _request(
207206
headers["Content-Encoding"] = encoding
208207

209208
@backoff.on_exception(
210-
backoff.expo, common.AirshipFailure, max_tries=(self.retries + 1)
209+
backoff.expo,
210+
(common.AirshipFailure, common.ConnectionFailure),
211+
max_tries=(self.retries + 1),
211212
)
212213
def make_retryable_request(
213214
method: str,
@@ -216,14 +217,17 @@ def make_retryable_request(
216217
params: Optional[Dict[str, Any]],
217218
headers: Dict[str, Any],
218219
) -> requests.Response:
219-
response: requests.Response = self.session.request(
220-
method,
221-
url,
222-
data=body,
223-
params=params,
224-
headers=headers,
225-
timeout=self.timeout,
226-
)
220+
try:
221+
response: requests.Response = self.session.request(
222+
method,
223+
url,
224+
data=body,
225+
params=params,
226+
headers=headers,
227+
timeout=self.timeout,
228+
)
229+
except requests.exceptions.ConnectionError as err:
230+
raise common.ConnectionFailure(str(err))
227231

228232
logger.debug(
229233
"Making %s request to %s. Headers:\n\t%s\nBody:\n\t%s",

urbanairship/enums.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from enum import Enum
2+
3+
4+
class LiveActivityEvent(Enum):
5+
UPDATE = "update"
6+
END = "end"
7+
8+
9+
class LiveUpdateEvent(Enum):
10+
START = "start"
11+
UPDATE = "update"
12+
END = "end"

urbanairship/push/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
wearable,
4949
web,
5050
wns_payload,
51+
live_activity,
52+
live_update,
53+
media_attachment,
5154
)
5255
from .schedule import (
5356
ScheduledList,
@@ -127,4 +130,7 @@
127130
localization,
128131
recurring_schedule,
129132
schedule_exclusion,
133+
live_activity,
134+
live_update,
135+
media_attachment,
130136
]

urbanairship/push/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def send(self) -> PushResponse:
105105
:raises AirshipFailure: Request failed.
106106
:raises Unauthorized: Authentication failed.
107107
:raises ValueError: Required keys missing or incorrect values included.
108+
:raises ConnectionFailure: Connection failed.
108109
"""
109110
if "email" in self.payload["notification"]:
110111
if self.payload["device_types"] == "all":
@@ -250,6 +251,7 @@ def send(self) -> PushResponse:
250251
other response data.
251252
:raises AirshipFailure: Request failed.
252253
:raises Unauthorized: Authentication failed.
254+
:raises ConnectionFailure: Connection failed.
253255
254256
"""
255257
response = self._airship._request(
@@ -366,6 +368,7 @@ def send(self) -> PushResponse:
366368
other response data.
367369
:raises AirshipFailure: Request failed.
368370
:raises Unauthorized: Authentication failed.
371+
:raises ConnectionFailure: Connection failed.
369372
370373
"""
371374

@@ -506,6 +509,8 @@ def send(self) -> PushResponse:
506509
:raises AirshipFailure: Request failed.
507510
:raises Unauthorized: Authentication failed.
508511
:raises ValueError: Required keys missing or incorrect values included.
512+
:raises ConnectionFailure: Connection failed.
513+
509514
"""
510515
body = json.dumps(self.payload)
511516
response = self._airship._request(

0 commit comments

Comments
 (0)