Skip to content

Commit 2ec3e97

Browse files
authored
Support sending transactional push messages (#95)
1 parent 2f50701 commit 2ec3e97

File tree

7 files changed

+145
-15
lines changed

7 files changed

+145
-15
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,16 @@ on: [ push ]
44

55
jobs:
66
build:
7-
runs-on: ubuntu-18.04
7+
runs-on: ubuntu-20.04
88
strategy:
99
matrix:
10-
python: [ '3.7', '3.6', '3.9','3.8' ]
10+
python: [ '3.6', '3.7', '3.8','3.9' ]
1111
steps:
1212
- uses: actions/checkout@v2
1313
- name: Set up Python
1414
uses: actions/setup-python@v2
1515
with:
1616
python-version: ${{ matrix.python }}
17-
# - uses: actions/cache@v2
18-
# with:
19-
# path: ./venv
20-
# key: customerio-python-${{ matrix.python }}-${{ hashFiles('requirements.txt') }}
21-
# restore-keys: |
22-
# customerio-python-${{ matrix.python }}-
2317
- name: install dependencies for the minor version
2418
run: |
2519
python -m venv venv

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [2.1]
4+
### Added
5+
- Add support for sending [transactional push messages](https://customer.io/docs/transactional-api/#transactional-push-notifications) ([#95](https://github.com/customerio/customerio-python/pull/95))
6+
37
## [2.0]
48
### Changed
59
- Updated transactional email request optional argument `amp_body` to `body_amp` for consistency across APIs ([#93](https://github.com/customerio/customerio-python/pull/93))
@@ -34,4 +38,4 @@
3438

3539
### Changed
3640
- ID fields in request path are url escaped
37-
- Sanitize event data ([#32](https://github.com/customerio/customerio-python/pull/32))
41+
- Sanitize event data ([#32](https://github.com/customerio/customerio-python/pull/32))

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,11 @@ See REST documentation [here](https://learn.customer.io/api/#apisuppress_delete)
188188

189189
### Send Transactional Messages
190190

191-
To use the [Transactional API](https://customer.io/docs/transactional-api), instantiate the Customer.io object using an [app key](https://customer.io/docs/managing-credentials#app-api-keys) and create a request object containing:
191+
To use the [Transactional API](https://customer.io/docs/transactional-api), instantiate the Customer.io object using an [app key](https://customer.io/docs/managing-credentials#app-api-keys) and create a request object for your message type.
192192

193+
## Email
194+
195+
SendEmailRequest requires:
193196
* `transactional_message_id`: the ID of the transactional message you want to send, or the `body`, `from`, and `subject` of a new message.
194197
* `to`: the email address of your recipients
195198
* an `identifiers` object containing the `id` of your recipient. If the `id` does not exist, Customer.io will create it.
@@ -226,6 +229,38 @@ response = client.send_email(request)
226229
print(response)
227230
```
228231

232+
## Push
233+
234+
SendPushRequest requires:
235+
* `transactional_message_id`: the ID of the transactional push message you want to send.
236+
* an `identifiers` object containing the `id` or `email` of your recipient. If the profile does not exist, Customer.io will create it.
237+
238+
Use `send_push` referencing your request to send a transactional message. [Learn more about transactional messages and `SendPushRequest` properties](https://customer.io/docs/transactional-api).
239+
240+
```python
241+
from customerio import APIClient, Regions, SendPushRequest
242+
client = APIClient("your API key", region=Regions.US)
243+
244+
request = SendPushRequest(
245+
transactional_message_id="3",
246+
message_data={
247+
"name": "person",
248+
"items": [
249+
{
250+
"name": "shoes",
251+
"price": "59.99",
252+
},
253+
]
254+
},
255+
identifiers={
256+
"id": "2",
257+
}
258+
)
259+
260+
response = client.send_push(request)
261+
print(response)
262+
```
263+
229264
## Notes
230265
- The Customer.io Python SDK depends on the [`Requests`](https://pypi.org/project/requests/) library which includes [`urllib3`](https://pypi.org/project/urllib3/) as a transitive dependency. The [`Requests`](https://pypi.org/project/requests/) library leverages connection pooling defined in [`urllib3`](https://pypi.org/project/urllib3/). [`urllib3`](https://pypi.org/project/urllib3/) only attempts to retry invocations of `HTTP` methods which are understood to be idempotent (See: [`Retry.DEFAULT_ALLOWED_METHODS`](https://github.com/urllib3/urllib3/blob/main/src/urllib3/util/retry.py#L184)). Since the `POST` method is not considered to be idempotent, any invocations which require `POST` are not retried.
231266

customerio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
from customerio.client_base import CustomerIOException
44
from customerio.track import CustomerIO
5-
from customerio.api import APIClient, SendEmailRequest
5+
from customerio.api import APIClient, SendEmailRequest, SendPushRequest
66
from customerio.regions import Regions

customerio/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = (2, 0, 0, 'final', 0)
1+
VERSION = (2, 1, 0, 'final', 0)
22

33
def get_version():
44
version = '%s.%s' % (VERSION[0], VERSION[1])

customerio/api.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ def send_email(self, request):
2222
resp = self.send_request('POST', self.url + "/v1/send/email", request)
2323
return json.loads(resp)
2424

25+
def send_push(self, request):
26+
if isinstance(request, SendPushRequest):
27+
request = request._to_dict()
28+
resp = self.send_request('POST', self.url + "/v1/send/push", request)
29+
return json.loads(resp)
30+
2531
# builds the session.
2632
def _build_session(self):
2733
session = super()._build_session()
@@ -30,7 +36,7 @@ def _build_session(self):
3036
return session
3137

3238
class SendEmailRequest(object):
33-
'''An object with all the options avaiable for triggering a transactional message'''
39+
'''An object with all the options avaiable for triggering a transactional email message'''
3440
def __init__(self,
3541
transactional_message_id=None,
3642
to=None,
@@ -96,7 +102,7 @@ def attach(self, name, content, encode=True):
96102
self.attachments[name] = content
97103

98104
def _to_dict(self):
99-
'''Build a request payload fromt the object'''
105+
'''Build a request payload from the object'''
100106
field_map = dict(
101107
# from is reservered keyword hence the object has the field
102108
# `_from` but in the request payload we map it to `from`
@@ -132,3 +138,76 @@ def _to_dict(self):
132138
data[name] = value
133139

134140
return data
141+
142+
class SendPushRequest(object):
143+
'''An object with all the options avaiable for triggering a transactional push message'''
144+
def __init__(self,
145+
transactional_message_id=None,
146+
to=None,
147+
identifiers=None,
148+
title=None,
149+
message=None,
150+
disable_message_retention=None,
151+
send_to_unsubscribed=None,
152+
queue_draft=None,
153+
message_data=None,
154+
send_at=None,
155+
language=None,
156+
image_url=None,
157+
link=None,
158+
custom_data=None,
159+
custom_payload=None,
160+
device=None,
161+
sound=None
162+
):
163+
164+
self.transactional_message_id = transactional_message_id
165+
self.to = to
166+
self.identifiers = identifiers
167+
self.disable_message_retention = disable_message_retention
168+
self.send_to_unsubscribed = send_to_unsubscribed
169+
self.queue_draft = queue_draft
170+
self.message_data = message_data
171+
self.send_at = send_at
172+
self.language = language
173+
174+
self.title = title
175+
self.message = message
176+
self.image_url = image_url
177+
self.link = link
178+
self.custom_data = custom_data
179+
self.custom_payload = custom_payload
180+
self.device = device
181+
self.sound = sound
182+
183+
def _to_dict(self):
184+
'''Build a request payload from the object'''
185+
field_map = dict(
186+
# field name is the same as the payload field name
187+
transactional_message_id="transactional_message_id",
188+
to="to",
189+
identifiers="identifiers",
190+
disable_message_retention="disable_message_retention",
191+
send_to_unsubscribed="send_to_unsubscribed",
192+
queue_draft="queue_draft",
193+
message_data="message_data",
194+
send_at="send_at",
195+
language="language",
196+
197+
title="title",
198+
message="message",
199+
image_url="image_url",
200+
link="link",
201+
custom_data="custom_data",
202+
custom_payload="custom_payload",
203+
device="custom_device",
204+
sound="sound"
205+
)
206+
207+
data = {}
208+
for field, name in field_map.items():
209+
value = getattr(self, field, None)
210+
if value is not None:
211+
data[name] = value
212+
213+
return data

tests/test_api.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import unittest
77

8-
from customerio import APIClient, SendEmailRequest, Regions, CustomerIOException
8+
from customerio import APIClient, SendEmailRequest, SendPushRequest, Regions, CustomerIOException
99
from customerio.__version__ import __version__ as ClientVersion
1010
from tests.server import HTTPSTestCase
1111

@@ -77,5 +77,23 @@ def test_send_email(self):
7777

7878
self.client.send_email(email)
7979

80+
def test_send_push(self):
81+
self.client.http.hooks = dict(response=partial(self._check_request, rq={
82+
'method': 'POST',
83+
'authorization': "Bearer app_api_key",
84+
'content_type': 'application/json',
85+
'url_suffix': '/v1/send/push',
86+
'body': {"identifiers": {"id":"customer_1"}, "transactional_message_id": 100, "title": "transactional push message", "message": "push message content"}
87+
}))
88+
89+
push = SendPushRequest(
90+
identifiers={"id":"customer_1"},
91+
transactional_message_id=100,
92+
title="transactional push message",
93+
message="push message content"
94+
)
95+
96+
self.client.send_push(push)
97+
8098
if __name__ == '__main__':
8199
unittest.main()

0 commit comments

Comments
 (0)