customerio 2.0__tar.gz → 2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: customerio
3
- Version: 2.0
3
+ Version: 2.2
4
4
  Summary: Customer.io Python bindings.
5
5
  Home-page: https://github.com/customerio/customerio-python
6
6
  Author: Peaberry Software Inc.
@@ -1,8 +1,7 @@
1
- <p align="center">
1
+ <p align=center>
2
2
  <a href="https://customer.io">
3
- <img src="https://user-images.githubusercontent.com/6409227/144680509-907ee093-d7ad-4a9c-b0a5-f640eeb060cd.png" height="60">
3
+ <img src="https://avatars.githubusercontent.com/u/1152079?s=200&v=4" height="60">
4
4
  </a>
5
- <p align="center">Power automated communication that people like to receive.</p>
6
5
  </p>
7
6
 
8
7
  [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blueviolet?logo=gitpod)](https://gitpod.io/#https://github.com/customerio/customerio-python/)
@@ -14,7 +13,7 @@
14
13
 
15
14
  # Customer.io Python
16
15
 
17
- This module has been tested with Python 3.6, 3.7, 3.8 and 3.9.
16
+ This module has been tested with Python 3.6, 3.7, 3.8 and 3.9. If you're new to Customer.io, we recommend that you integrate with our [Data Pipelines Python library](https://github.com/customerio/cdp-analytics-python) instead.
18
17
 
19
18
  ## Installing
20
19
 
@@ -54,7 +53,7 @@ values for that field.
54
53
 
55
54
  You can pass any keyword arguments to the `identify` and `track` methods. These kwargs will be converted to custom attributes.
56
55
 
57
- See original REST documentation [here](http://customer.io/docs/api/rest.html#section-Creating_or_updating_customers)
56
+ See original REST documentation [here](http://customer.io/docs/api/track/#operation/identify)
58
57
 
59
58
  ### Track a custom event
60
59
 
@@ -70,7 +69,7 @@ cio.track(customer_id="5", name='purchased', price=23.45, product="widget")
70
69
 
71
70
  You can pass any keyword arguments to the `identify` and `track` methods. These kwargs will be converted to custom attributes.
72
71
 
73
- See original REST documentation [here](http://customer.io/docs/api/rest.html#section-Track_a_custom_event)
72
+ See original REST documentation [here](http://customer.io/docs/api/track/#operation/track)
74
73
 
75
74
  ### Backfill a custom event
76
75
 
@@ -95,7 +94,7 @@ Event timestamp may be passed as a ```datetime.datetime``` object, an integer or
95
94
 
96
95
  Keyword arguments to backfill work the same as a call to ```cio.track```.
97
96
 
98
- See original REST documentation [here](http://customer.io/docs/api/rest.html#section-Track_a_custom_event)
97
+ See original REST documentation [here](http://customer.io/docs/api/track/#operation/track)
99
98
 
100
99
  ### Track an anonymous event
101
100
 
@@ -107,7 +106,7 @@ An anonymous event is an event associated with a person you haven't identified.
107
106
 
108
107
  #### Anonymous invite events
109
108
 
110
- If you previously sent [invite events](https://customer.io/docs/anonymous-invite-emails/), you can achieve the same functionality by sending an anonymous event with the anonymous identifier set to `None`. To send anonymous invites, your event *must* include a `recipient` attribute.
109
+ If you previously sent [invite events](https://customer.io/docs/journeys/anonymous-invite-emails/), you can achieve the same functionality by sending an anonymous event with the anonymous identifier set to `None`. To send anonymous invites, your event *must* include a `recipient` attribute.
111
110
 
112
111
  ```python
113
112
  cio.track_anonymous(anonymous_id=None, name="invite", first_name="alex", recipient="alex.person@example.com")
@@ -122,7 +121,7 @@ Deletes the customer profile for a specified customer.
122
121
 
123
122
  This method returns nothing. Attempts to delete non-existent customers will not raise any errors.
124
123
 
125
- See original REST documentation [here](http://customer.io/docs/api/rest.html#section-Deleting_customers)
124
+ See original REST documentation [here](https://customer.io/docs/api/track/#operation/delete)
126
125
 
127
126
 
128
127
  You can pass any keyword arguments to the `identify` and `track` methods. These kwargs will be converted to custom attributes.
@@ -175,7 +174,7 @@ cio.suppress(customer_id="1")
175
174
 
176
175
  Suppresses the specified customer. They will be deleted from Customer.io, and we will ignore all further attempts to identify or track activity for the suppressed customer ID
177
176
 
178
- See REST documentation [here](https://learn.customer.io/api/#apisuppress_add)
177
+ See REST documentation [here](https://customer.io/docs/api/track/#operation/suppress)
179
178
 
180
179
  ### Unsuppress a customer
181
180
  ```python
@@ -184,19 +183,22 @@ cio.unsuppress(customer_id="1")
184
183
 
185
184
  Unsuppresses the specified customer. We will remove the supplied id from our suppression list and start accepting new identify and track calls for the customer as normal
186
185
 
187
- See REST documentation [here](https://learn.customer.io/api/#apisuppress_delete)
186
+ See REST documentation [here](https://customer.io/docs/api/track/#operation/unsuppress)
188
187
 
189
188
  ### Send Transactional Messages
190
189
 
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:
190
+ To use the [Transactional API](https://customer.io/docs/journeys/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.
192
191
 
193
- * `transactional_message_id`: the ID of the transactional message you want to send, or the `body`, `from`, and `subject` of a new message.
192
+ ## Email
193
+
194
+ SendEmailRequest requires:
195
+ * `transactional_message_id`: the ID of the transactional message you want to send, or the `body`, `_from`, and `subject` of a new message.
194
196
  * `to`: the email address of your recipients
195
197
  * an `identifiers` object containing the `id` of your recipient. If the `id` does not exist, Customer.io will create it.
196
198
  * a `message_data` object containing properties that you want reference in your message using Liquid.
197
199
  * You can also send attachments with your message. Use `attach` to encode attachments.
198
200
 
199
- Use `send_email` referencing your request to send a transactional message. [Learn more about transactional messages and `SendEmailRequest` properties](https://customer.io/docs/transactional-api).
201
+ Use `send_email` referencing your request to send a transactional message. [Learn more about transactional messages and `SendEmailRequest` properties](https://customer.io/docs/journeys/transactional-api).
200
202
 
201
203
  ```python
202
204
  from customerio import APIClient, Regions, SendEmailRequest
@@ -204,6 +206,7 @@ client = APIClient("your API key", region=Regions.US)
204
206
 
205
207
  request = SendEmailRequest(
206
208
  to="person@example.com",
209
+ _from="override.sender@example.com",
207
210
  transactional_message_id="3",
208
211
  message_data={
209
212
  "name": "person",
@@ -219,13 +222,45 @@ request = SendEmailRequest(
219
222
  }
220
223
  )
221
224
 
222
- with open("path to file", "rb") as f:
225
+ with open("receipt.pdf", "rb") as f:
223
226
  request.attach('receipt.pdf', f.read())
224
227
 
225
228
  response = client.send_email(request)
226
229
  print(response)
227
230
  ```
228
231
 
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/journeys/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
+
229
264
  ## Notes
230
265
  - 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.
231
266
 
@@ -2,5 +2,5 @@ import warnings
2
2
 
3
3
  from customerio.client_base import CustomerIOException
4
4
  from customerio.track import CustomerIO
5
- from customerio.api import APIClient, SendEmailRequest
5
+ from customerio.api import APIClient, SendEmailRequest, SendPushRequest, SendSMSRequest
6
6
  from customerio.regions import Regions
@@ -1,4 +1,4 @@
1
- VERSION = (2, 0, 0, 'final', 0)
1
+ VERSION = (2, 2, 0, 'final', 0)
2
2
 
3
3
  def get_version():
4
4
  version = '%s.%s' % (VERSION[0], VERSION[1])
@@ -22,6 +22,18 @@ class APIClient(ClientBase):
22
22
  resp = self.send_request('POST', self.url + "/v1/send/email", request)
23
23
  return json.loads(resp)
24
24
 
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
+
31
+ def send_sms(self, request):
32
+ if isinstance(request, SendSMSRequest):
33
+ request = request._to_dict()
34
+ resp = self.send_request('POST', self.url + "/v1/send/sms", request)
35
+ return json.loads(resp)
36
+
25
37
  # builds the session.
26
38
  def _build_session(self):
27
39
  session = super()._build_session()
@@ -30,7 +42,7 @@ class APIClient(ClientBase):
30
42
  return session
31
43
 
32
44
  class SendEmailRequest(object):
33
- '''An object with all the options avaiable for triggering a transactional message'''
45
+ '''An object with all the options avaiable for triggering a transactional email message'''
34
46
  def __init__(self,
35
47
  transactional_message_id=None,
36
48
  to=None,
@@ -96,7 +108,7 @@ class SendEmailRequest(object):
96
108
  self.attachments[name] = content
97
109
 
98
110
  def _to_dict(self):
99
- '''Build a request payload fromt the object'''
111
+ '''Build a request payload from the object'''
100
112
  field_map = dict(
101
113
  # from is reservered keyword hence the object has the field
102
114
  # `_from` but in the request payload we map it to `from`
@@ -132,3 +144,123 @@ class SendEmailRequest(object):
132
144
  data[name] = value
133
145
 
134
146
  return data
147
+
148
+ class SendPushRequest(object):
149
+ '''An object with all the options avaiable for triggering a transactional push message'''
150
+ def __init__(self,
151
+ transactional_message_id=None,
152
+ to=None,
153
+ identifiers=None,
154
+ title=None,
155
+ message=None,
156
+ disable_message_retention=None,
157
+ send_to_unsubscribed=None,
158
+ queue_draft=None,
159
+ message_data=None,
160
+ send_at=None,
161
+ language=None,
162
+ image_url=None,
163
+ link=None,
164
+ custom_data=None,
165
+ custom_payload=None,
166
+ device=None,
167
+ sound=None
168
+ ):
169
+
170
+ self.transactional_message_id = transactional_message_id
171
+ self.to = to
172
+ self.identifiers = identifiers
173
+ self.disable_message_retention = disable_message_retention
174
+ self.send_to_unsubscribed = send_to_unsubscribed
175
+ self.queue_draft = queue_draft
176
+ self.message_data = message_data
177
+ self.send_at = send_at
178
+ self.language = language
179
+
180
+ self.title = title
181
+ self.message = message
182
+ self.image_url = image_url
183
+ self.link = link
184
+ self.custom_data = custom_data
185
+ self.custom_payload = custom_payload
186
+ self.device = device
187
+ self.sound = sound
188
+
189
+ def _to_dict(self):
190
+ '''Build a request payload from the object'''
191
+ field_map = dict(
192
+ # field name is the same as the payload field name
193
+ transactional_message_id="transactional_message_id",
194
+ to="to",
195
+ identifiers="identifiers",
196
+ disable_message_retention="disable_message_retention",
197
+ send_to_unsubscribed="send_to_unsubscribed",
198
+ queue_draft="queue_draft",
199
+ message_data="message_data",
200
+ send_at="send_at",
201
+ language="language",
202
+
203
+ title="title",
204
+ message="message",
205
+ image_url="image_url",
206
+ link="link",
207
+ custom_data="custom_data",
208
+ custom_payload="custom_payload",
209
+ device="custom_device",
210
+ sound="sound"
211
+ )
212
+
213
+ data = {}
214
+ for field, name in field_map.items():
215
+ value = getattr(self, field, None)
216
+ if value is not None:
217
+ data[name] = value
218
+
219
+ return data
220
+
221
+ class SendSMSRequest(object):
222
+ '''An object with all the options avaiable for triggering a transactional push message'''
223
+ def __init__(self,
224
+ transactional_message_id=None,
225
+ to=None,
226
+ identifiers=None,
227
+ disable_message_retention=None,
228
+ send_to_unsubscribed=None,
229
+ queue_draft=None,
230
+ message_data=None,
231
+ send_at=None,
232
+ language=None,
233
+ ):
234
+
235
+ self.transactional_message_id = transactional_message_id
236
+ self.to = to
237
+ self.identifiers = identifiers
238
+ self.disable_message_retention = disable_message_retention
239
+ self.send_to_unsubscribed = send_to_unsubscribed
240
+ self.queue_draft = queue_draft
241
+ self.message_data = message_data
242
+ self.send_at = send_at
243
+ self.language = language
244
+
245
+ def _to_dict(self):
246
+ '''Build a request payload from the object'''
247
+ field_map = dict(
248
+ # field name is the same as the payload field name
249
+ transactional_message_id="transactional_message_id",
250
+ to="to",
251
+ identifiers="identifiers",
252
+ disable_message_retention="disable_message_retention",
253
+ send_to_unsubscribed="send_to_unsubscribed",
254
+ queue_draft="queue_draft",
255
+ message_data="message_data",
256
+ send_at="send_at",
257
+ language="language",
258
+ )
259
+
260
+ data = {}
261
+ for field, name in field_map.items():
262
+ value = getattr(self, field, None)
263
+ if value is not None:
264
+ data[name] = value
265
+
266
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: customerio
3
- Version: 2.0
3
+ Version: 2.2
4
4
  Summary: Customer.io Python bindings.
5
5
  Home-page: https://github.com/customerio/customerio-python
6
6
  Author: Peaberry Software Inc.
@@ -3,7 +3,6 @@ try:
3
3
  except ImportError:
4
4
  from http.server import BaseHTTPRequestHandler, HTTPServer
5
5
 
6
- from functools import wraps
7
6
  from random import randint
8
7
  import json
9
8
  import ssl
@@ -11,12 +10,11 @@ import time
11
10
  import threading
12
11
  import unittest
13
12
 
14
- def sslwrap(func):
15
- @wraps(func)
16
- def bar(*args, **kw):
17
- kw['ssl_version'] = ssl.PROTOCOL_SSLv23
18
- return func(*args, **kw)
19
- return bar
13
+ def create_ssl_context():
14
+ """Create SSL context for Python 3.12+ compatibility"""
15
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
16
+ context.minimum_version = ssl.TLSVersion.TLSv1_2
17
+ return context
20
18
 
21
19
  request_counts = dict()
22
20
 
@@ -75,12 +73,11 @@ class HTTPSTestCase(unittest.TestCase):
75
73
  def setUpClass(cls):
76
74
  # create a server
77
75
  cls.server = HTTPServer(("localhost", 0), Handler)
78
- # hack needed to setup ssl server
79
- ssl.wrap_socket = sslwrap(ssl.wrap_socket)
76
+ # create SSL context for Python 3.12+ compatibility
77
+ context = create_ssl_context()
78
+ context.load_cert_chain('./tests/server.pem')
80
79
  # upgrade to https
81
- cls.server.socket = ssl.wrap_socket(cls.server.socket,
82
- certfile='./tests/server.pem',
83
- server_side=True)
80
+ cls.server.socket = context.wrap_socket(cls.server.socket, server_side=True)
84
81
  # start server instance in new thread
85
82
  cls.server_thread = threading.Thread(target=cls.server.serve_forever)
86
83
  cls.server_thread.start()
@@ -5,7 +5,7 @@ import json
5
5
  import sys
6
6
  import unittest
7
7
 
8
- from customerio import APIClient, SendEmailRequest, Regions, CustomerIOException
8
+ from customerio import APIClient, SendEmailRequest, SendPushRequest, SendSMSRequest, Regions, CustomerIOException
9
9
  from customerio.__version__ import __version__ as ClientVersion
10
10
  from tests.server import HTTPSTestCase
11
11
 
@@ -77,5 +77,39 @@ class TestAPIClient(HTTPSTestCase):
77
77
 
78
78
  self.client.send_email(email)
79
79
 
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
+
98
+ def test_send_sms(self):
99
+ self.client.http.hooks = dict(response=partial(self._check_request, rq={
100
+ 'method': 'POST',
101
+ 'authorization': "Bearer app_api_key",
102
+ 'content_type': 'application/json',
103
+ 'url_suffix': '/v1/send/sms',
104
+ 'body': {"identifiers": {"id":"customer_1"}, "transactional_message_id": 100}
105
+ }))
106
+
107
+ sms = SendSMSRequest(
108
+ identifiers={"id":"customer_1"},
109
+ transactional_message_id=100,
110
+ )
111
+
112
+ self.client.send_sms(sms)
113
+
80
114
  if __name__ == '__main__':
81
115
  unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes