notificationhubs-rest-python 1.0.1__tar.gz → 1.0.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.
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/PKG-INFO +1 -1
- notificationhubs_rest_python-1.0.2/notificationhubs_rest_python/NotificationHub.py +256 -0
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/notificationhubs_rest_python.egg-info/PKG-INFO +1 -1
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/notificationhubs_rest_python.egg-info/SOURCES.txt +1 -0
- notificationhubs_rest_python-1.0.2/notificationhubs_rest_python.egg-info/top_level.txt +1 -0
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/setup.py +1 -1
- notificationhubs_rest_python-1.0.1/notificationhubs_rest_python.egg-info/top_level.txt +0 -1
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/LICENSE +0 -0
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/README.md +0 -0
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/notificationhubs_rest_python.egg-info/dependency_links.txt +0 -0
- {notificationhubs_rest_python-1.0.1 → notificationhubs_rest_python-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: notificationhubs-rest-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A Python library for sending notifications using Azure Notification Hubs REST API.
|
|
5
5
|
Home-page: https://github.com/panevo/notificationhubs-rest-python
|
|
6
6
|
Author: Karlo Krakan
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import hmac
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
# allows urllib.parse.quote below to be compatible with Python 2
|
|
9
|
+
import six.moves.urllib as urllib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Reference for REST API:
|
|
13
|
+
# https://msdn.microsoft.com/en-us/library/mt608572.aspx
|
|
14
|
+
# Reference for
|
|
15
|
+
class AzureNotification:
|
|
16
|
+
def __init__(self, notification_format=None, payload=None):
|
|
17
|
+
valid_formats = ['template', 'apple', 'gcm', 'windows', 'windowsphone', "adm", "baidu"]
|
|
18
|
+
if not any(x in notification_format for x in valid_formats):
|
|
19
|
+
raise Exception(
|
|
20
|
+
"Invalid Notification format. " +
|
|
21
|
+
"Must be one of the following - 'template', 'apple', 'gcm', 'windows', 'windowsphone', 'adm', 'baidu'")
|
|
22
|
+
|
|
23
|
+
self.format = notification_format
|
|
24
|
+
self.payload = payload
|
|
25
|
+
|
|
26
|
+
# array with keynames for headers
|
|
27
|
+
# Note: Some headers are mandatory: Windows: X-WNS-Type, WindowsPhone: X-NotificationType
|
|
28
|
+
# Note: For Apple you can set Expiry with header: ServiceBusNotification-ApnsExpiry
|
|
29
|
+
# in W3C DTF, YYYY-MM-DDThh:mmTZD (for example, 1997-07-16T19:20+01:00).
|
|
30
|
+
self.headers = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AzureNotificationHub:
|
|
34
|
+
DEVICE_TOKEN_HEADER_NAME = "ServiceBusNotification-DeviceHandle"
|
|
35
|
+
TAGS_HEADER_NAME = "ServiceBusNotification-Tags"
|
|
36
|
+
API_VERSION = "api-version=2015-04"
|
|
37
|
+
DEBUG_SEND = "&test"
|
|
38
|
+
SCHEDULED_NOTIFICATION_HEADER_NAME = 'ServiceBusNotification-ScheduleTime'
|
|
39
|
+
SCHEDULED_NOTIFICATION_FORMAT_HEADER_NAME = 'ServiceBusNotification-Format'
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def __init__(cls, connection_string=None, hub_name=None, debug=0):
|
|
43
|
+
cls.HubName = hub_name
|
|
44
|
+
cls.Debug = debug
|
|
45
|
+
|
|
46
|
+
# Parse connection string
|
|
47
|
+
parts = connection_string.split(';')
|
|
48
|
+
if len(parts) != 3:
|
|
49
|
+
raise Exception("Invalid ConnectionString.")
|
|
50
|
+
|
|
51
|
+
for part in parts:
|
|
52
|
+
if part.startswith('Endpoint'):
|
|
53
|
+
cls.Endpoint = 'https' + part[11:]
|
|
54
|
+
if part.startswith('SharedAccessKeyName'):
|
|
55
|
+
cls.SasKeyName = part[20:]
|
|
56
|
+
if part.startswith('SharedAccessKey'):
|
|
57
|
+
cls.SasKeyValue = part[16:]
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def __get_expiry():
|
|
61
|
+
# By default returns an expiration of 5 minutes (=300 seconds) from now
|
|
62
|
+
return int(round(time.time() + 300))
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def __encode_base64(data):
|
|
66
|
+
return base64.b64encode(data)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def __sign_string(cls, to_sign):
|
|
70
|
+
key = cls.SasKeyValue.encode('utf-8')
|
|
71
|
+
to_sign = to_sign.encode('utf-8')
|
|
72
|
+
signed_hmac_sha256 = hmac.HMAC(key, to_sign, hashlib.sha256)
|
|
73
|
+
digest = signed_hmac_sha256.digest()
|
|
74
|
+
encoded_digest = cls.__encode_base64(digest)
|
|
75
|
+
return encoded_digest
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def __generate_sas_token(cls):
|
|
79
|
+
target_uri = cls.Endpoint + cls.HubName
|
|
80
|
+
my_uri = urllib.parse.quote(target_uri, '').lower()
|
|
81
|
+
expiry = str(cls.__get_expiry())
|
|
82
|
+
to_sign = my_uri + '\n' + expiry
|
|
83
|
+
signature = urllib.parse.quote(cls.__sign_string(to_sign))
|
|
84
|
+
auth_format = 'SharedAccessSignature sig={0}&se={1}&skn={2}&sr={3}'
|
|
85
|
+
sas_token = auth_format.format(signature, expiry, cls.SasKeyName, my_uri)
|
|
86
|
+
return sas_token
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def __make_http_request(cls, url, payload, headers):
|
|
90
|
+
if cls.Debug > 0:
|
|
91
|
+
# adding this querystring parameter gets detailed information about the PNS send notification outcome
|
|
92
|
+
url += cls.DEBUG_SEND
|
|
93
|
+
print("--- REQUEST ---")
|
|
94
|
+
print("URI: " + url)
|
|
95
|
+
print("Headers: " + json.dumps(headers, sort_keys=True, indent=4, separators=(' ', ': ')))
|
|
96
|
+
print("--- END REQUEST ---\n")
|
|
97
|
+
|
|
98
|
+
response = requests.post(url, data=payload, headers=headers)
|
|
99
|
+
|
|
100
|
+
if cls.Debug > 0:
|
|
101
|
+
# print out detailed response information for debugging purpose
|
|
102
|
+
print("\n\n--- RESPONSE ---")
|
|
103
|
+
print("Code:" + str(response.status_code) + " / Reason: " + response.reason)
|
|
104
|
+
print("Headers: " + json.dumps(dict(response.headers), sort_keys=True, indent=4, separators=(' ', ': ')))
|
|
105
|
+
print("Content:\n" + response.text)
|
|
106
|
+
print("--- END RESPONSE ---")
|
|
107
|
+
|
|
108
|
+
elif response.status_code != 201:
|
|
109
|
+
# Successful outcome of send message is HTTP 201 - Created
|
|
110
|
+
raise Exception(
|
|
111
|
+
"Error sending notification. Received HTTP code " + str(response.status_code) + " " + response.reason)
|
|
112
|
+
|
|
113
|
+
response.close()
|
|
114
|
+
return response.status_code
|
|
115
|
+
|
|
116
|
+
# scheduled_time must be UTC
|
|
117
|
+
@classmethod
|
|
118
|
+
def __send_notification(cls, notification, is_direct, tag_or_tag_expression=None, scheduled_time=None,
|
|
119
|
+
device_handle=None, skip_send=False):
|
|
120
|
+
json_platforms = ['template', 'apple', 'gcm', 'adm', 'baidu']
|
|
121
|
+
|
|
122
|
+
if any(x in notification.format for x in json_platforms):
|
|
123
|
+
content_type = "application/json"
|
|
124
|
+
payload_to_send = json.dumps(notification.payload)
|
|
125
|
+
else:
|
|
126
|
+
content_type = "application/xml"
|
|
127
|
+
payload_to_send = notification.payload
|
|
128
|
+
|
|
129
|
+
headers = {
|
|
130
|
+
'Content-type': content_type,
|
|
131
|
+
'Authorization': cls.__generate_sas_token(),
|
|
132
|
+
cls.SCHEDULED_NOTIFICATION_FORMAT_HEADER_NAME: notification.format
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
url = cls.Endpoint + cls.HubName
|
|
136
|
+
|
|
137
|
+
if scheduled_time is None:
|
|
138
|
+
url += '/messages'
|
|
139
|
+
else:
|
|
140
|
+
url += '/schedulednotifications'
|
|
141
|
+
# format for UTC date:
|
|
142
|
+
# "yyyy-MM-dd'T'HH:mm:ss" Ex: 2001-07-04T12:08:56
|
|
143
|
+
scheduled_time_header = scheduled_time.strftime("%Y-%m-%d'T'%H:%M:%S")
|
|
144
|
+
headers[cls.SCHEDULED_NOTIFICATION_HEADER_NAME] = scheduled_time_header
|
|
145
|
+
|
|
146
|
+
if is_direct:
|
|
147
|
+
if device_handle is None:
|
|
148
|
+
raise Exception("Error sending direct notification. Missing PNS device handle.")
|
|
149
|
+
|
|
150
|
+
url += "/?direct"
|
|
151
|
+
url += "&" + cls.API_VERSION
|
|
152
|
+
|
|
153
|
+
headers[cls.DEVICE_TOKEN_HEADER_NAME] = device_handle
|
|
154
|
+
else:
|
|
155
|
+
url += "/?" + cls.API_VERSION
|
|
156
|
+
|
|
157
|
+
if isinstance(tag_or_tag_expression, set):
|
|
158
|
+
tag_list = ' || '.join(tag_or_tag_expression)
|
|
159
|
+
else:
|
|
160
|
+
tag_list = tag_or_tag_expression
|
|
161
|
+
|
|
162
|
+
# add the tags/tag expressions to the headers collection
|
|
163
|
+
if tag_list != "":
|
|
164
|
+
headers.update({cls.TAGS_HEADER_NAME: tag_list})
|
|
165
|
+
|
|
166
|
+
# add any custom headers to the headers collection that the user may have added
|
|
167
|
+
if notification.headers is not None:
|
|
168
|
+
headers.update(notification.headers)
|
|
169
|
+
|
|
170
|
+
if skip_send:
|
|
171
|
+
status = 201
|
|
172
|
+
else:
|
|
173
|
+
status = cls.__make_http_request(url, payload_to_send, headers)
|
|
174
|
+
|
|
175
|
+
return status, headers
|
|
176
|
+
|
|
177
|
+
def send_apple_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
178
|
+
skip_send=False):
|
|
179
|
+
nh = AzureNotification("apple", payload)
|
|
180
|
+
|
|
181
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
182
|
+
device_handle=device_handle,
|
|
183
|
+
scheduled_time=scheduled_time,
|
|
184
|
+
skip_send=skip_send)
|
|
185
|
+
return status, headers
|
|
186
|
+
|
|
187
|
+
def send_google_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
188
|
+
skip_send=False):
|
|
189
|
+
nh = AzureNotification("gcm", payload)
|
|
190
|
+
|
|
191
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
192
|
+
device_handle=device_handle,
|
|
193
|
+
scheduled_time=scheduled_time, skip_send=skip_send)
|
|
194
|
+
return status, headers
|
|
195
|
+
|
|
196
|
+
def send_amazon_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
197
|
+
skip_send=False):
|
|
198
|
+
nh = AzureNotification("adm", payload)
|
|
199
|
+
|
|
200
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
201
|
+
device_handle=device_handle,
|
|
202
|
+
scheduled_time=scheduled_time,
|
|
203
|
+
skip_send=skip_send)
|
|
204
|
+
return status, headers
|
|
205
|
+
|
|
206
|
+
def send_baidu_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
207
|
+
skip_send=False):
|
|
208
|
+
nh = AzureNotification("baidu", payload)
|
|
209
|
+
|
|
210
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
211
|
+
device_handle=device_handle,
|
|
212
|
+
scheduled_time=scheduled_time,
|
|
213
|
+
skip_send=skip_send)
|
|
214
|
+
return status, headers
|
|
215
|
+
|
|
216
|
+
def send_windows_phone_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
217
|
+
skip_send=False):
|
|
218
|
+
nh = AzureNotification("windowsphone", payload)
|
|
219
|
+
|
|
220
|
+
if "<wp:Toast>" in payload:
|
|
221
|
+
nh.headers = {'X-WindowsPhone-Target': 'toast', 'X-NotificationClass': '2'}
|
|
222
|
+
elif "<wp:Tile>" in payload:
|
|
223
|
+
nh.headers = {'X-WindowsPhone-Target': 'tile', 'X-NotificationClass': '1'}
|
|
224
|
+
|
|
225
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
226
|
+
device_handle=device_handle,
|
|
227
|
+
scheduled_time=scheduled_time,
|
|
228
|
+
skip_send=skip_send)
|
|
229
|
+
return status, headers
|
|
230
|
+
|
|
231
|
+
# example wns payload =
|
|
232
|
+
# """<toast><visual><binding template=\"ToastText01\"><text id=\"1\">Python Test</text></binding></visual></toast>"""
|
|
233
|
+
def send_windows_notification(self, is_direct, payload, tags="", device_handle=None, scheduled_time=None,
|
|
234
|
+
skip_send=False):
|
|
235
|
+
nh = AzureNotification("windows", payload)
|
|
236
|
+
|
|
237
|
+
if "<toast>" in payload:
|
|
238
|
+
nh.headers = {'X-WNS-Type': 'wns/toast'}
|
|
239
|
+
elif "<tile>" in payload:
|
|
240
|
+
nh.headers = {'X-WNS-Type': 'wns/tile'}
|
|
241
|
+
elif "<badge>" in payload:
|
|
242
|
+
nh.headers = {'X-WNS-Type': 'wns/badge'}
|
|
243
|
+
|
|
244
|
+
status, headers = self.__send_notification(nh, is_direct=is_direct, tag_or_tag_expression=tags,
|
|
245
|
+
device_handle=device_handle,
|
|
246
|
+
scheduled_time=scheduled_time,
|
|
247
|
+
skip_send=skip_send)
|
|
248
|
+
return status, headers
|
|
249
|
+
|
|
250
|
+
def send_template_notification(self, properties, tags="", scheduled_time=None, skip_send=False):
|
|
251
|
+
nh = AzureNotification("template", properties)
|
|
252
|
+
status, headers = self.__send_notification(nh, is_direct=False, tag_or_tag_expression=tags,
|
|
253
|
+
scheduled_time=scheduled_time,
|
|
254
|
+
skip_send=skip_send)
|
|
255
|
+
|
|
256
|
+
return status, headers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: notificationhubs-rest-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A Python library for sending notifications using Azure Notification Hubs REST API.
|
|
5
5
|
Home-page: https://github.com/panevo/notificationhubs-rest-python
|
|
6
6
|
Author: Karlo Krakan
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
notificationhubs_rest_python
|
|
@@ -4,7 +4,7 @@ from setuptools import setup
|
|
|
4
4
|
|
|
5
5
|
setup(
|
|
6
6
|
name="notificationhubs-rest-python",
|
|
7
|
-
version="1.0.
|
|
7
|
+
version="1.0.2",
|
|
8
8
|
include_package_data=True,
|
|
9
9
|
license="Apache-2.0 License",
|
|
10
10
|
description="A Python library for sending notifications using Azure Notification Hubs REST API.",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|