8000 The actual port · alexcchan/twilio-python@98a0e91 · GitHub
[go: up one dir, main page]

Skip to content

Commit 98a0e91

Browse files
committed
The actual port
Had to embed completely jwt as its python 3 compatibility is not real
1 parent a3ceb91 commit 98a0e91

File tree

15 files changed

+139
-44
lines changed

15 files changed

+139
-44
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1+
six
12
httplib2
2-
pyjwt

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
author_email = "help@twilio.com",
1818
url = "http://github.com/twilio/twilio-python/",
1919
keywords = ["twilio","twiml"],
20-
install_requires = ["httplib2 >= 0.7", "pyjwt"],
20+
install_requires = ["httplib2 >= 0.7", "six"],
2121
packages = find_packages(),
2222
classifiers = [
2323
"Development Status :: 5 - Production/Stable",
@@ -28,6 +28,7 @@
2828
"Programming Language :: Python :: 2.5",
2929
"Programming Language :: Python :: 2.6",
3030
"Programming Language :: Python :: 2.7",
31+
"Programming Language :: Python :: 3.2",
3132
"Topic :: Software Development :: Libraries :: Python Modules",
3233
"Topic :: Communications :: Telephony",
3334
],

tests/test_base_resource.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from twilio.rest.resources import Resource
1414
from twilio.rest.resources import ListResource
1515
from twilio.rest.resources import InstanceResource
16+
from six import advance_iterator
1617

1718
base_uri = "https://api.twilio.com/2010-04-01"
1819
account_sid = "AC123"
@@ -51,30 +52,30 @@ def testIterNoKey(self):
5152
self.r.request.return_value = Mock(), {}
5253

5354
with self.assertRaises(StopIteration):
54-
self.r.iter().next()
55+
advance_iterator(self.r.iter())
5556

5657
def testRequest(self):
5758
self.r.request = Mock()
5859
self.r.request.return_value = Mock(), {self.r.key: [{'sid': 'foo'}]}
59-
self.r.iter().next()
60+
advance_iterator(self.r.iter())
6061
self.r.request.assert_called_with("GET", "https://api.twilio.com/2010-04-01/Resources", params={})
6162

6263
def testIterOneItem(self):
6364
self.r.request = Mock()
6465
self.r.request.return_value = Mock(), {self.r.key: [{'sid': 'foo'}]}
6566

6667
items = self.r.iter()
67-
items.next()
68+
advance_iterator(items)
6869

6970
with self.assertRaises(StopIteration):
70-
items.next()
71+
advance_iterator(items)
7172

7273
def testIterNoNextPage(self):
7374
self.r.request = Mock()
7475
self.r.request.return_value = Mock(), {self.r.key: []}
7576

7677
with self.assertRaises(StopIteration):
77-
self.r.iter().next()
78+
advance_iterator(self.r.iter())
7879

7980
def testKeyValue(self):
8081
self.r.key = "Hey"

tests/test_jwt.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import jwt
1+
from twilio import jwt
22
import sys
33
if sys.version_info < (2, 7):
44
import unittest2 as unittest
55
else:
66
import unittest
7-
import urllib
7+
88
from twilio.util import TwilioCapability
99

1010
class TokenTest(unittest.TestCase):

tests/test_twiml.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import with_statement
33
import re
4+
from six import u, text_type
45
import twilio
56
import sys
67
if sys.version_info < (2, 7):
@@ -15,7 +16,7 @@
1516

1617
class TwilioTest(unittest.TestCase):
1718
def strip(self, xml):
18-
return str(xml)
19+
return text_type(xml)
1920

2021
def improperAppend(self, verb):
2122
self.assertRaises(TwimlException, verb.append, twiml.Say(""))
@@ -62,8 +63,8 @@ def testSayHelloWorld(self):
6263
def testSayFrench(self):
6364
"""should say hello monkey"""
6465
r = Response()
65-
r.append(twiml.Say(u"nécessaire et d'autres"))
66-
self.assertEquals(unicode(r),
66+
r.append(twiml.Say(u("nécessaire et d'autres")))
67+
self.assertEquals(text_type(r),
6768
'<?xml version="1.0" encoding="UTF-8"?><Response><Say>n&#233;cessaire et d\'autres</Say></Response>')
6869

6970
def testSayLoop(self):

tests/test_unicode.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
from mock import patch, Mock
3+
from six import u
34
from twilio.rest import resources
45

56

@@ -41,7 +42,7 @@ def test_double_encoding(resp_mock, mock):
4142
http = mock.return_value
4243
http.request.return_value = (Mock(), Mock())
4344

44-
body = u"Chloéñ"
45+
body = u("Chloéñ")
4546

4647
data = {
4748
"body": body.encode('utf-8'),
@@ -60,7 +61,7 @@ def test_paging(resp_mock, mock):
6061
http.request.return_value = (Mock(), Mock())
6162

6263
data = {
63-
"body": u"Chloéñ",
64+
"body": u("Chloéñ"),
6465
}
6566

6667
resources.make_request("GET", "http://www.example.com", data=data)

tests/test_validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_validation(self):
4444
"ToZip": "94612",
4545
}
4646

47-
expected = "fF+xx6dTinOaCdZ0aIeNkHr/ZAA="
47+
expected = b"fF+xx6dTinOaCdZ0aIeNkHr/ZAA="
4848

4949
self.assertEquals(validator.compute_signature(uri, params), expected)
5050
self.assertTrue(validator.validate(uri, params, expected))

twilio/compat/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
# Those are not supported by the six library and needs to be done manually
3+
from six import binary_type
4+
5+
try:
6+
# python 3
7+
from urllib.parse import urlencode, urlparse, urljoin
8+
except ImportError:
9+
# python 2 backward compatibility
10+
#noinspection PyUnresolvedReferences
11+
from urllib import urlencode
12+
#noinspection PyUnresolvedReferences
13+
from urlparse import urlparse, urljoin

twilio/jwt/__init__.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
""" JSON Web Token implementation
2+
3+
Minimum implementation based on this spec:
4+
http://self-issued.info/docs/draft-jones-json-web-token-01.html
5+
"""
6+
import base64
7+
import hashlib
8+
import hmac
9+
from six import text_type, binary_type
10+
11+
# default text to binary representation conversion
12+
def binary(txt):
13+
return txt.encode('utf-8')
14+
try:
15+
import json
16+
except ImportError:
17+
import simplejson as json
18+
19+
__all__ = ['encode', 'decode', 'DecodeError']
20+
21+
class DecodeError(Exception): pass
22+
23+
signing_methods = {
24+
'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(),
25+
'HS384': lambda msg, key: hmac.new(key, msg, hashlib.sha384).digest(),
26+
'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest(),
27+
}
28+
29+
def base64url_decode(input):
30+
input += b'=' * (4 - (len(input) % 4))
31+
return base64.urlsafe_b64decode(input)
32+
33+
def base64url_encode(input):
34+
return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '')
35+
36+
def header(jwt):
37+
header_segment = jwt.split('.', 1)[0]
38+
try:
39+
return json.loads(base64url_decode(header_segment))
40+
except (ValueError, TypeError):
41+
raise DecodeError("Invalid header encoding")
42+
43+
def encode(payload, key, algorithm='HS256'):
44+
segments = []
45+
header = {"typ": "JWT", "alg": algorithm}
46+
header_as_binary = binary(json.dumps(header))
47+
segments.append(base64url_encode(header_as_binary))
48+
payload_as_binary = binary(json.dumps(payload))
49+
segments.append(base64url_encode(payload_as_binary))
50+
signing_input = '.'.join(segments)
51+
try:
52+
ascii_key = text_type(key).encode('utf8')
53+
signature = signing_methods[algorithm](binary(signing_input), ascii_key)
54+
except KeyError:
55+
raise NotImplementedError("Algorithm not supported")
56+
segments.append(base64url_encode(signature))
57+
return '.'.join(segments)
58+
59+
def decode(jwt, key='', verify=True):
60+
try:
61+
signing_input, crypto_segment = jwt.rsplit('.', 1)
62+
header_segment, payload_segment = signing_input.split('.', 1)
63+
except ValueError:
64+
raise DecodeError("Not enough segments")
65+
try:
66+
header = json.loads(base64url_decode(binary(header_segment)).decode('utf-8'))
67+
payload = json.loads(base64url_decode(binary(payload_segment)).decode('utf-8'))
68+
signature = base64url_decode(binary(crypto_segment))
69+
except (ValueError, TypeError):
70+
raise DecodeError("Invalid segment encoding")
71+
if verify:
72+
try:
73+
ascii_key = key
74+
if not signature == signing_methods[header['alg']](binary(signing_input), binary(ascii_key)):
75+
raise DecodeError("Signature verification failed")
76+
except KeyError:
77+
raise DecodeError("Algorithm not supported")
78+
return payload

twilio/rest/resources/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import logging
44
import twilio
55
from twilio import TwilioException, TwilioRestException
6-
from urllib import urlencode
7-
from urlparse import urlparse
86

97
from twilio.rest.resources.imports import (
108
parse_qs, json, httplib2

twilio/rest/resources/base.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
2-
from urlparse import urlparse
3-
from urllib import urlencode
2+
from six import text_type, iteritems, binary_type
3+
from twilio.compat import urlparse
4+
from twilio.compat import urlencode
45

56
import twilio
67
from twilio import TwilioException, TwilioRestException
@@ -37,11 +38,14 @@ def make_request(method, url,
3738

3839
if data is not None:
3940
udata = {}
40-
for k, v in data.iteritems():
41-
try:
42-
udata[k.encode('utf-8')] = unicode(v).encode('utf-8')
43-
except UnicodeDecodeError:
44-
udata[k.encode('utf-8')] = unicode(v, 'utf-8').encode('utf-8')
41+
for k, v in iteritems(data):
42+
key = k.encode('utf-8')
43+
if isinstance(v, text_type):
44+
udata[key] = v.encode('utf-8')
45+
elif isinstance(v, binary_type):
46+
udata[key] = v
47+
else:
48+
raise ValueError('data should be either a binary or a string')
4549
data = urlencode(udata)
4650

4751
if params is not None:
@@ -71,7 +75,7 @@ def make_twilio_request(method, uri, **kwargs):
7175

7276
if "Accept" not in headers:
7377
headers["Accept"] = "application/json"
74-
uri = uri + ".json"
78+
uri += ".json"
7579

7680
resp = make_request(method, uri, **kwargs)
7781

twilio/rest/resources/connect_apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from twilio.rest.resources import InstanceResource, ListResource
2-
2+
from six import iteritems
33

44
class ConnectApp(InstanceResource):
55
""" An authorized connect app """
@@ -30,7 +30,7 @@ def load(self, entries):
3030
""" Translate certain parameters into others"""
3131
result = {}
3232

33-
for k, v in entries.iteritems():
33+
for k, v in iteritems(entries):
3434
k = k.replace("connect_app_", "")
3535
result[k] = v
3636

twilio/rest/resources/util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
from six import iteritems
23

34

45
def transform_params(p):
@@ -70,7 +71,7 @@ def convert_keys(d):
7071

7172
result = {}
7273

73-
for k, v in d.iteritems():
74+
for k, v in iteritems(d):
7475
if k in special:
7576
result[special[k]] = v
7677
else:
@@ -81,7 +82,7 @@ def convert_keys(d):
8182

8283
def normalize_dates(myfunc):
8384
def inner_func(*args, **kwargs):
84-
for k, v in kwargs.iteritems():
85+
for k, v in iteritems(kwargs):
8586
res = [True for s in ["after", "before", "on"] if s in k]
8687
if len(res):
8788
kwargs[k] = parse_date(v)

twilio/twiml.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import xml.etree.ElementTree as ET
6+
from six import u
67

78

89
class TwimlException(Exception):
@@ -52,18 +53,18 @@ def toxml(self, xml_declaration=True):
5253
:param bool xml_declaration: Include the XML declaration. Defaults to
5354
True
5455
"""
55-
xml = ET.tostring(self.xml()).encode("utf-8")
56+
xml = ET.tostring(self.xml()).decode('utf-8')
5657

5758
if xml_declaration:
58-
return u'<?xml version="1.0" encoding="UTF-8"?>' + xml
59+
return '<?xml version="1.0" encoding="UTF-8"?>' + xml
5960
else:
6061
return xml
6162

6263
def xml(self):
6364
el = ET.Element(self.name)
6465

6566
keys = self.attrs.keys()
66-
keys.sort()
67+
keys = sorted(keys)
6768
for a in keys:
6869
value = self.attrs[a]
6970

0 commit comments

Comments
 (0)
0