vobiz-python 0.1.0__py3-none-any.whl

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.
Files changed (63) hide show
  1. vobiz/__init__.py +4 -0
  2. vobiz/base.py +237 -0
  3. vobiz/exceptions.py +34 -0
  4. vobiz/resources/__init__.py +12 -0
  5. vobiz/resources/accounts.py +59 -0
  6. vobiz/resources/applications.py +138 -0
  7. vobiz/resources/calls_vobiz.py +206 -0
  8. vobiz/resources/cdrs.py +46 -0
  9. vobiz/resources/credentials.py +104 -0
  10. vobiz/resources/endpoints.py +101 -0
  11. vobiz/resources/ip_access_control_lists.py +100 -0
  12. vobiz/resources/numbers.py +134 -0
  13. vobiz/resources/origination_uris.py +109 -0
  14. vobiz/resources/recordings.py +91 -0
  15. vobiz/resources/sip_trunks.py +99 -0
  16. vobiz/resources/subaccounts.py +101 -0
  17. vobiz/rest/__init__.py +2 -0
  18. vobiz/rest/client.py +277 -0
  19. vobiz/utils/__init__.py +72 -0
  20. vobiz/utils/interactive.py +50 -0
  21. vobiz/utils/jwt.py +97 -0
  22. vobiz/utils/location.py +6 -0
  23. vobiz/utils/signature_v3.py +111 -0
  24. vobiz/utils/template.py +50 -0
  25. vobiz/utils/validators.py +280 -0
  26. vobiz/version.py +2 -0
  27. vobiz/xml/ConferenceElement.py +485 -0
  28. vobiz/xml/DTMFElement.py +41 -0
  29. vobiz/xml/DialElement.py +371 -0
  30. vobiz/xml/MultiPartyCallElement.py +711 -0
  31. vobiz/xml/ResponseElement.py +414 -0
  32. vobiz/xml/VobizXMLElement.py +48 -0
  33. vobiz/xml/__init__.py +31 -0
  34. vobiz/xml/breakElement.py +62 -0
  35. vobiz/xml/contElement.py +190 -0
  36. vobiz/xml/emphasisElement.py +174 -0
  37. vobiz/xml/getDigitsElement.py +294 -0
  38. vobiz/xml/getInputElement.py +369 -0
  39. vobiz/xml/hangupElement.py +57 -0
  40. vobiz/xml/langElement.py +197 -0
  41. vobiz/xml/messageElement.py +115 -0
  42. vobiz/xml/numberElement.py +77 -0
  43. vobiz/xml/pElement.py +164 -0
  44. vobiz/xml/phonemeElement.py +62 -0
  45. vobiz/xml/playElement.py +41 -0
  46. vobiz/xml/preAnswerElement.py +148 -0
  47. vobiz/xml/prosodyElement.py +227 -0
  48. vobiz/xml/recordElement.py +337 -0
  49. vobiz/xml/redirectElement.py +42 -0
  50. vobiz/xml/sElement.py +154 -0
  51. vobiz/xml/sayAsElement.py +61 -0
  52. vobiz/xml/speakElement.py +249 -0
  53. vobiz/xml/streamElement.py +123 -0
  54. vobiz/xml/subElement.py +43 -0
  55. vobiz/xml/userElement.py +79 -0
  56. vobiz/xml/wElement.py +144 -0
  57. vobiz/xml/waitElement.py +93 -0
  58. vobiz/xml/xmlUtils.py +6 -0
  59. vobiz_python-0.1.0.dist-info/METADATA +640 -0
  60. vobiz_python-0.1.0.dist-info/RECORD +63 -0
  61. vobiz_python-0.1.0.dist-info/WHEEL +5 -0
  62. vobiz_python-0.1.0.dist-info/licenses/LICENSE.txt +19 -0
  63. vobiz_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,6 @@
1
+ class Location:
2
+ def __init__(self, latitude, longitude, name, address):
3
+ self.latitude = latitude
4
+ self.longitude = longitude
5
+ self.name = name
6
+ self.address = address
@@ -0,0 +1,111 @@
1
+ import sys
2
+ from hmac import new as sign
3
+ from hashlib import sha256
4
+ from urllib.parse import urlparse, urlunparse, parse_qs
5
+ from base64 import encodebytes as encode
6
+
7
+ from .validators import *
8
+
9
+
10
+ def string_format(value):
11
+ if isinstance(value, bytes):
12
+ return ''.join(chr(x) for x in bytearray(value))
13
+ if isinstance(value, (int, float, bool)):
14
+ return str(value)
15
+ if isinstance(value, list):
16
+ return [string_format(x) for x in value]
17
+ return value
18
+
19
+
20
+ def get_map_from_query(query):
21
+ res_map = dict()
22
+ for key, value in parse_qs(query, keep_blank_values=True).items():
23
+ res_map[string_format(key)] = string_format(value)
24
+ return res_map
25
+
26
+
27
+ def get_sorted_query_string(params):
28
+ keys = sorted(params.keys())
29
+ res_params = []
30
+ for key in keys:
31
+ value = params[key]
32
+ if isinstance(value, list):
33
+ res_params.append(
34
+ '&'.join(['{}={}'.format(string_format(key), val) for val in sorted(string_format(value))]))
35
+ else:
36
+ res_params.append('{}={}'.format(string_format(key), string_format(value)))
37
+ return '&'.join(res_params)
38
+
39
+
40
+ def get_sorted_params_string(params):
41
+ keys = sorted(params.keys())
42
+ res_params = []
43
+ for key in keys:
44
+ value = params[key]
45
+ if isinstance(value, list):
46
+ res_params.append(
47
+ ''.join(['{}{}'.format(string_format(key), val) for val in sorted(string_format(value))]))
48
+ elif isinstance(value, dict):
49
+ res_params.append('{}{}'.format(string_format(key), get_sorted_params_string(value)))
50
+ else:
51
+ res_params.append('{}{}'.format(string_format(key), string_format(value)))
52
+ return ''.join(res_params)
53
+
54
+
55
+ def construct_get_url(uri, params, empty_post_params=True):
56
+ parsed_uri = urlparse(uri.encode('utf-8'))
57
+ base_url = urlunparse((parsed_uri.scheme.decode('utf-8'),
58
+ parsed_uri.netloc.decode('utf-8'),
59
+ parsed_uri.path.decode('utf-8'), '', '',
60
+ '')).encode('utf-8')
61
+
62
+ params.update(get_map_from_query(parsed_uri.query))
63
+ query_params = get_sorted_query_string(params)
64
+ if len(query_params) > 0 or not empty_post_params:
65
+ base_url = base_url + bytearray('?' + query_params, 'utf-8')
66
+ if len(query_params) > 0 and not empty_post_params:
67
+ base_url = base_url + bytearray('.', 'utf-8')
68
+ return base_url
69
+
70
+
71
+ def construct_post_url(uri, params):
72
+ base_url = construct_get_url(uri, dict(), True if len(params) == 0 else False)
73
+ return base_url + bytearray(get_sorted_params_string(params), 'utf-8')
74
+
75
+
76
+ def get_signature_v3(auth_token, base_url, nonce):
77
+ base_url = bytearray('{}.{}'.format(string_format(base_url), string_format(nonce)), 'utf-8')
78
+ try:
79
+ return encode(sign(auth_token, base_url, sha256).digest()).strip()
80
+ except TypeError:
81
+ return encode(sign(bytearray(auth_token, 'utf-8'), base_url, sha256).digest()).strip()
82
+
83
+
84
+ @validate_args(
85
+ method=[all_of(of_type(str), is_in(('POST', 'GET'), case_sensitive=False))],
86
+ uri=[is_url()],
87
+ params=[optional(of_type(dict))],
88
+ nonce=[of_type(str)],
89
+ auth_token=[of_type(str)],
90
+ v3_signature=[of_type(str)],
91
+ )
92
+ def validate_v3_signature(method, uri, nonce, auth_token, v3_signature, params=None):
93
+ """
94
+ Validates V3 Signature received from Vobiz to your server.
95
+
96
+ :param method: Your callback method (GET or POST)
97
+ :param uri: Your callback URL
98
+ :param params: Params received in callback from Vobiz
99
+ :param nonce: X-Vobiz-Signature-V3-Nonce header
100
+ :param v3_signature: X-Vobiz-Signature-V3 header
101
+ :param auth_token: (Sub)Account auth token
102
+ :return: True if the request matches signature, False otherwise
103
+ """
104
+ if params is None:
105
+ params = dict()
106
+ auth_token = bytes(auth_token.encode('utf-8'))
107
+ nonce = bytes(nonce.encode('utf-8'))
108
+ v3_signature = bytes(v3_signature.encode('utf-8'))
109
+ base_url = construct_get_url(uri, params).decode('utf-8') if method == 'GET' else construct_post_url(uri, params).decode('utf-8')
110
+ signature = get_signature_v3(auth_token, base_url, nonce)
111
+ return signature in v3_signature.split(b',')
@@ -0,0 +1,50 @@
1
+ from vobiz.utils.location import Location
2
+
3
+
4
+ class Parameter:
5
+ def __init__(
6
+ self,
7
+ type,
8
+ text=None,
9
+ media=None,
10
+ payload=None,
11
+ currency=None,
12
+ date_time=None,
13
+ location=None,
14
+ parameter_name=None,
15
+ ):
16
+ self.type = type
17
+ self.text = text
18
+ self.media = media
19
+ self.payload = payload
20
+ self.currency = Currency(**currency) if currency else None
21
+ self.date_time = DateTime(**date_time) if date_time else None
22
+ self.location = location
23
+ self.parameter_name = parameter_name
24
+
25
+
26
+ class Component:
27
+ def __init__(self, type, sub_type=None, index=None, parameters=None):
28
+ self.type = type
29
+ self.sub_type = sub_type
30
+ self.index = index
31
+ self.parameters = parameters if parameters is not None else []
32
+
33
+
34
+ class Template:
35
+ def __init__(self, name, language, components=None):
36
+ self.name = name
37
+ self.language = language
38
+ self.components = components if components is not None else []
39
+
40
+
41
+ class Currency:
42
+ def __init__(self, fallback_value, currency_code, amount_1000):
43
+ self.fallback_value = fallback_value
44
+ self.currency_code = currency_code
45
+ self.amount_1000 = amount_1000
46
+
47
+
48
+ class DateTime:
49
+ def __init__(self, fallback_value):
50
+ self.fallback_value = fallback_value
@@ -0,0 +1,280 @@
1
+ # -*- coding: utf-8 -*-
2
+ import functools
3
+ import importlib
4
+ import inspect
5
+ import re
6
+
7
+ import decorator
8
+
9
+ from vobiz.exceptions import ValidationError
10
+
11
+
12
+ def regex(regex):
13
+ rexp = re.compile(regex)
14
+
15
+ def f(name, value):
16
+ if not rexp.match(value):
17
+ return None, [
18
+ '{} should match format {} (actual value: {})'.format(
19
+ name, regex, value)
20
+ ]
21
+
22
+ return value, []
23
+
24
+ return f
25
+
26
+
27
+ def all_of(*validators):
28
+ def f(name, value):
29
+ for validator in validators:
30
+ value, errs = validator(name, value)
31
+ if errs:
32
+ return None, errs
33
+ return value, []
34
+
35
+ return f
36
+
37
+
38
+ def one_of(*validators):
39
+ def f(name, value):
40
+ for validator in validators:
41
+ new_val, errs = validator(name, value)
42
+ if errs:
43
+ continue
44
+ else:
45
+ return new_val, []
46
+ return None, '{} did not satisfy any of the required types'.format(
47
+ name)
48
+
49
+ return f
50
+
51
+
52
+ def check(checker, message=None):
53
+ def f(name, value):
54
+ inr = checker(value)
55
+ msg = message or '{} should be in range'.format(name)
56
+ if inr:
57
+ return value, []
58
+ else:
59
+ return None, ['{} (actual value: {})'.format(msg, value)]
60
+
61
+ return f
62
+
63
+
64
+ def is_in(iterable, message=None, case_sensitive=True, case_type='upper'):
65
+ def f(name, value):
66
+ actual_value = value
67
+ if not case_sensitive:
68
+ if case_type == 'upper':
69
+ value = str(value).upper()
70
+ elif case_type == 'lower':
71
+ value = str(value).lower()
72
+ elif case_type == 'title':
73
+ value = str(value).title()
74
+
75
+ msg = message or '{} should be in {}'.format(name, iterable)
76
+ if value in iterable:
77
+ return value, []
78
+ else:
79
+ return None, ['{} (actual value: {})'.format(msg, actual_value)]
80
+ return f
81
+
82
+
83
+ def multi_is_in(iterable, message=None, case_sensitive=True, make_lower_case=False, separator=','):
84
+ def f(name, value):
85
+ actual_value = value
86
+ if not case_sensitive:
87
+ if make_lower_case:
88
+ value = str(value).lower()
89
+ else:
90
+ value = str(value).upper()
91
+ msg = message or '{} should be among {}. multiple values should be COMMA(,) separated'.format(name, iterable)
92
+ for val in value.split(separator):
93
+ if val not in iterable:
94
+ return None, ['{} (actual value: {})'.format(msg, actual_value)]
95
+ return value, []
96
+
97
+ return f
98
+
99
+
100
+ def optional(*validators):
101
+ def f(name, value):
102
+ if value is None:
103
+ return None, []
104
+ return all_of(*validators)(name, value)
105
+
106
+ return f
107
+
108
+
109
+ def required(validate):
110
+ def f(name, value):
111
+ if not value:
112
+ return None, '{} is required (current value: None)'.format(name)
113
+ return validate(name, value)
114
+
115
+ return f
116
+
117
+
118
+ def of_type(*args):
119
+ def f(name, value):
120
+ if value is None:
121
+ return None, ['{name} cannot be None'.format(name=name)]
122
+ for typ in args:
123
+ if isinstance(typ, str):
124
+ parts = typ.split('.')
125
+ typ = getattr(
126
+ importlib.import_module('.'.join(parts[:-1])), parts[-1])
127
+ try:
128
+ value = typ(value)
129
+ return value, []
130
+ except ValueError:
131
+ pass
132
+ return None, [
133
+ '{name} should be of type: {types}'.format(
134
+ name=name, types=[arg.__name__ for arg in args])
135
+ ]
136
+
137
+ return f
138
+
139
+
140
+ def of_type_exact(*args):
141
+ def f(name, value):
142
+ for typ in args:
143
+ if isinstance(typ, str):
144
+ parts = typ.split('.')
145
+ typ = getattr(
146
+ importlib.import_module('.'.join(parts[:-1])), parts[-1])
147
+ if not isinstance(value, typ):
148
+ continue
149
+ return value, []
150
+ return None, [
151
+ '{name} should be of type: {types}'.format(
152
+ name=name,
153
+ types=[getattr(arg, '__name__', str(arg)) for arg in args])
154
+ ]
155
+
156
+ return f
157
+
158
+
159
+ def is_iterable(validator, sep=None):
160
+ def f(name, value):
161
+ try:
162
+ l = []
163
+ v, e = validator(name, value)
164
+ if v and not e:
165
+ raise TypeError() # hack
166
+ for i, item in enumerate(iter(value)):
167
+ val, errs = validator('{}[{}]'.format(name, i), item)
168
+ if errs:
169
+ return None, errs
170
+ l.append(val)
171
+ ret = (l, []) if not sep else (sep.join(l), [])
172
+ return ret
173
+ except TypeError:
174
+ val, errs = validator(name, value)
175
+ if errs:
176
+ return None, errs
177
+ return ([val], []) if not sep else (val, [])
178
+
179
+ return required(f)
180
+
181
+
182
+ def multiple_valid_integers():
183
+ def f(name, value):
184
+ if isinstance(value, str):
185
+ values = value.split('<')
186
+ for i in values:
187
+ try:
188
+ int(i)
189
+ except ValueError:
190
+ return None, ['{} destination value must be integer'.format(name)]
191
+ return value, []
192
+ return value, []
193
+ return f
194
+
195
+
196
+ def validate_args(**to_validate):
197
+ def outer(wrapped):
198
+ @functools.wraps(wrapped)
199
+ def wrapper(self, *args, **kwargs):
200
+ params = inspect.getcallargs(wrapped, *args, **kwargs)
201
+ for arg_name, validators in to_validate.items():
202
+ for validator in validators:
203
+ params[arg_name], errs = validator(arg_name,
204
+ params.get(
205
+ arg_name, None))
206
+ if errs:
207
+ raise ValidationError(errs)
208
+ return wrapped(**params)
209
+
210
+ return decorator.decorate(wrapped, wrapper)
211
+
212
+ return outer
213
+
214
+
215
+ def validate_list_items(instance_type):
216
+ def f(arg_name, value):
217
+ if not isinstance(value, list):
218
+ return [], ["{} must be a list".format(arg_name)]
219
+
220
+ errors = []
221
+ validated_items = []
222
+ for idx, item in enumerate(value):
223
+ flag = 0
224
+ if isinstance(item, dict):
225
+ try:
226
+ instance_type(**item)
227
+ except Exception as exception:
228
+ errors.append(str(exception))
229
+ flag = 1
230
+ else:
231
+ err = "Invalid item at index {} in {}: should be of type {}".format(idx, arg_name, instance_type.__name__)
232
+ if not isinstance(item, instance_type):
233
+ errors.append(err)
234
+ flag = 1
235
+
236
+ if not flag:
237
+ validated_items.append(item)
238
+
239
+ return validated_items, errors
240
+
241
+ return f
242
+
243
+
244
+ def validate_dict_items(instance_type):
245
+ def validator(arg_name, value):
246
+ if not isinstance(value, dict):
247
+ return None, ["{} must be a dictionary".format(arg_name)]
248
+
249
+ errors = []
250
+ try:
251
+ instance_type(**value)
252
+ except TypeError as e:
253
+ errors.append(str(e))
254
+
255
+ if errors:
256
+ return None, errors
257
+ return value, []
258
+
259
+ return validator
260
+
261
+
262
+ # Type validator helpers — Python 3 only, no six dependency
263
+ is_valid_date = functools.partial(of_type, str)
264
+ is_phonenumber = functools.partial(of_type, str)
265
+ is_subaccount_id = functools.partial(all_of, of_type(str),
266
+ regex(r'^SA[A-Z0-9]{18}$'))
267
+ is_mainaccount_id = functools.partial(all_of, of_type(str),
268
+ regex(r'^MA[A-Z0-9]{18}$'))
269
+ is_account_id = functools.partial(all_of, of_type(str),
270
+ regex(r'^(M|S)A[A-Z0-9]{18}$'))
271
+ is_subaccount = functools.partial(
272
+ one_of, of_type_exact('vobiz.resources.accounts.Subaccount'),
273
+ is_subaccount_id())
274
+ is_url = functools.partial(
275
+ all_of, of_type(str),
276
+ regex(
277
+ r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+|None)'
278
+ ))
279
+ is_proper_date_format = functools.partial(all_of, of_type_exact(str),
280
+ regex(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?$'))
vobiz/version.py ADDED
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ __version__ = '0.1.0'