Skip to content

Commit 0bb7e16

Browse files
authored
[TOOLSLIBS-471] Recurring schedules (#180)
* adds new schedule times to inits * adds recurring schedule tests * adds recurring schedule to payload * adds recurring sched and exclusion * adds pause and resume endpoints * typo fixes * fixes test to raise on correct problem * adds schedule validate method
1 parent 1f8bbb2 commit 0bb7e16

File tree

5 files changed

+303
-18
lines changed

5 files changed

+303
-18
lines changed

tests/push/test_schedule.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55

66

77
class TestSchedule(unittest.TestCase):
8+
def setUp(self):
9+
self.start_hour = 7
10+
self.end_hour = 15
11+
self.start_date = datetime.datetime(2021, 1, 15)
12+
self.end_date = datetime.datetime(2021, 4, 15)
13+
self.days_of_week = ["friday", "saturday", "sunday"]
14+
815
def test_scheduled_time(self):
916
d = datetime.datetime(2013, 1, 1, 12, 56)
1017
self.assertEqual(
@@ -20,3 +27,128 @@ def test_local_scheduled_time(self):
2027
def test_best_time(self):
2128
d = datetime.datetime(2018, 10, 8)
2229
self.assertEqual(ua.best_time(d), {"best_time": {"send_date": "2018-10-08"}})
30+
31+
def test_schedule_exclusion(self):
32+
self.assertEqual(
33+
ua.schedule_exclusion(
34+
start_hour=self.start_hour,
35+
end_hour=self.end_hour,
36+
start_date=self.start_date,
37+
end_date=self.end_date,
38+
days_of_week=self.days_of_week,
39+
),
40+
{
41+
"hour_range": "7-15",
42+
"date_range": "2021-01-15T00:00:00/2021-04-15T00:00:00",
43+
"days_of_week": ["friday", "saturday", "sunday"],
44+
},
45+
)
46+
47+
def test_schedule_exclusion_raises_bad_hour(self):
48+
with self.assertRaises(ValueError):
49+
ua.schedule_exclusion(
50+
start_hour=self.start_hour,
51+
end_hour=25,
52+
start_date=self.start_date,
53+
end_date=self.end_date,
54+
days_of_week=self.days_of_week,
55+
)
56+
57+
def test_schedule_exclusion_raises_bad_date(self):
58+
with self.assertRaises(ValueError):
59+
ua.schedule_exclusion(
60+
start_hour=self.start_hour,
61+
end_hour=self.end_hour,
62+
start_date=self.start_date,
63+
end_date="not_a_datetime",
64+
days_of_week=self.days_of_week,
65+
)
66+
67+
def test_schedule_exclusion_raises_bad_day_of_week(self):
68+
with self.assertRaises(ValueError):
69+
ua.schedule_exclusion(
70+
start_hour=self.start_hour,
71+
end_hour=self.end_hour,
72+
start_date=self.start_date,
73+
end_date=self.end_date,
74+
days_of_week=["fakesday"],
75+
)
76+
77+
def test_recurring_schedule_standard(self):
78+
self.assertEqual(
79+
ua.recurring_schedule(
80+
count=1,
81+
type="daily",
82+
end_time=datetime.datetime(2030, 1, 15, 12, 0, 0),
83+
paused=False,
84+
exclusions=[
85+
ua.schedule_exclusion(
86+
start_hour=self.start_hour,
87+
end_hour=self.end_hour,
88+
start_date=self.start_date,
89+
end_date=self.end_date,
90+
days_of_week=self.days_of_week,
91+
)
92+
],
93+
),
94+
{
95+
"recurring": {
96+
"cadence": {"type": "daily", "count": 1},
97+
"end_time": "2030-01-15T12:00:00",
98+
"exclusions": [
99+
{
100+
"hour_range": "7-15",
101+
"date_range": "2021-01-15T00:00:00/2021-04-15T00:00:00",
102+
"days_of_week": ["friday", "saturday", "sunday"],
103+
}
104+
],
105+
"paused": False,
106+
}
107+
},
108+
)
109+
110+
def test_recurring_schedule_weekly(self):
111+
self.assertEqual(
112+
ua.recurring_schedule(
113+
count=1,
114+
type="weekly",
115+
days_of_week=["monday", "wednesday", "friday"],
116+
end_time=datetime.datetime(2030, 1, 15, 12, 0, 0),
117+
paused=False,
118+
exclusions=[
119+
ua.schedule_exclusion(
120+
start_hour=self.start_hour,
121+
end_hour=self.end_hour,
122+
start_date=self.start_date,
123+
end_date=self.end_date,
124+
days_of_week=self.days_of_week,
125+
)
126+
],
127+
),
128+
{
129+
"recurring": {
130+
"cadence": {
131+
"type": "weekly",
132+
"count": 1,
133+
"days_of_week": ["monday", "wednesday", "friday"],
134+
},
135+
"end_time": "2030-01-15T12:00:00",
136+
"exclusions": [
137+
{
138+
"hour_range": "7-15",
139+
"date_range": "2021-01-15T00:00:00/2021-04-15T00:00:00",
140+
"days_of_week": ["friday", "saturday", "sunday"],
141+
}
142+
],
143+
"paused": False,
144+
}
145+
},
146+
)
147+
148+
def test_recurring_schedule_raises_bad_day(self):
149+
with self.assertRaises(ValueError):
150+
ua.recurring_schedule(count=1, type="weekly", days_of_week=["fakesday"])
151+
152+
def test_recurring_schedule_raises_bad_type(self):
153+
with self.assertRaises(ValueError):
154+
ua.recurring_schedule(count=1, type="fake_type")

urbanairship/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
or_,
7777
public_notification,
7878
recent_date,
79+
recurring_schedule,
80+
schedule_exclusion,
7981
scheduled_time,
8082
segment,
8183
sms,

urbanairship/push/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,14 @@
4747
web,
4848
wns_payload,
4949
)
50-
from .schedule import ScheduledList, best_time, local_scheduled_time, scheduled_time
50+
from .schedule import (
51+
ScheduledList,
52+
best_time,
53+
local_scheduled_time,
54+
scheduled_time,
55+
schedule_exclusion,
56+
recurring_schedule,
57+
)
5158
from .template import Template, TemplateList, merge_data
5259

5360
# Common selector for audience & device_types

urbanairship/push/core.py

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class ScheduledPush(object):
9999
def __init__(self, airship):
100100
self._airship = airship
101101
self.schedule = None
102+
self.recurring = None
102103
self.name = None
103104
self.push = None
104105
self.url = None
@@ -137,23 +138,33 @@ def payload(self):
137138
if hasattr(self.push, "merge_data"): # create template payload
138139
data = self.push.payload
139140
data["schedule"] = self.schedule
140-
elif isinstance(self.push, CreateAndSendPush): # create cas payload
141+
elif isinstance(self.push, CreateAndSendPush): # create create and send payload
141142
if "scheduled_time" not in self.schedule:
142143
raise ValueError(
143144
"only scheduled_time supported with create and send schedules"
144145
)
145146
data = {"schedule": self.schedule, "push": self.push.payload}
146147
else:
147-
data = {
148-
"schedule": self.schedule,
149-
"push": self.push.payload,
150-
}
148+
data = {"schedule": self.schedule, "push": self.push.payload}
149+
if self.recurring:
150+
data["schedule"].update(self.recurring)
151151

152152
if self.name is not None:
153153
data["name"] = self.name
154154

155155
return data
156156

157+
@property
158+
def api_url(self):
159+
if hasattr(self.push, "merge_data"):
160+
url = self._airship.urls.get("schedule_template_url")
161+
elif isinstance(self.push, CreateAndSendPush):
162+
url = self._airship.urls.get("schedule_create_and_send_url")
163+
else:
164+
url = self._airship.urls.get("schedules_url")
165+
166+
return url
167+
157168
def send(self):
158169
"""Schedule the notification
159170
@@ -163,19 +174,10 @@ def send(self):
163174
:raises Unauthorized: Authentication failed.
164175
165176
"""
166-
body = json.dumps(self.payload)
167-
168-
if hasattr(self.push, "merge_data"):
169-
url = self._airship.urls.get("schedule_template_url")
170-
elif isinstance(self.push, CreateAndSendPush):
171-
url = self._airship.urls.get("schedule_create_and_send_url")
172-
else:
173-
url = self._airship.urls.get("schedules_url")
174-
175177
response = self._airship._request(
176178
method="POST",
177-
body=body,
178-
url=url,
179+
body=json.dumps(self.payload),
180+
url=self.api_url,
179181
content_type="application/json",
180182
version=3,
181183
)
@@ -194,12 +196,50 @@ def send(self):
194196

195197
return PushResponse(response)
196198

199+
def validate(self):
200+
"""Validates a scheduled push for sending"""
201+
response = self._airship._request(
202+
method="POST",
203+
body=json.dumps(self.payload),
204+
url=self.api_url,
205+
content_type="application/json",
206+
version=3,
207+
)
208+
209+
return response
210+
211+
def pause(self):
212+
"""Pause a recurring schedule"""
213+
if not self.url:
214+
raise ValueError("Cannot pause ScheduledPush without url.")
215+
216+
response = self._airship._request(
217+
method="POST", body="", url=self.url + "/pause", version=3
218+
)
219+
220+
return response
221+
222+
def resume(self):
223+
"""Resume a paused recurring schedule"""
224+
if not self.url:
225+
raise ValueError("Cannot resume ScheduledPush without url.")
226+
227+
response = self._airship._request(
228+
method="POST", body="", url=self.url + "/resume", version=3
229+
)
230+
231+
return response
232+
197233
def cancel(self):
198234
"""Cancel a previously scheduled notification."""
199235
if not self.url:
200236
raise ValueError("Cannot cancel ScheduledPush without url.")
201237

202-
self._airship._request(method="DELETE", body=None, url=self.url, version=3)
238+
response = self._airship._request(
239+
method="DELETE", body=None, url=self.url, version=3
240+
)
241+
242+
return response
203243

204244
def update(self):
205245
if not self.url:

0 commit comments

Comments
 (0)