From ef7bd68adfcb59f8e09ed831f6abaef87e4e95df Mon Sep 17 00:00:00 2001 From: Alexandre Payment Date: Mon, 27 Apr 2015 16:51:22 -0700 Subject: [PATCH 001/115] Scoped authentication token and signing keys implementation --- tests/test_make_request.py | 234 +++++++++++++--------- tests/test_scoped_authentication_token.py | 80 ++++++++ twilio/jwt/__init__.py | 8 +- twilio/rest/resources/base.py | 11 +- twilio/rest/resources/signing_keys.py | 45 +++++ twilio/scoped_authentication_token.py | 48 +++++ 6 files changed, 328 insertions(+), 98 deletions(-) create mode 100644 tests/test_scoped_authentication_token.py create mode 100644 twilio/rest/resources/signing_keys.py create mode 100644 twilio/scoped_authentication_token.py diff --git a/tests/test_make_request.py b/tests/test_make_request.py index 0eab2e8288..b4633cd66d 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -3,15 +3,21 @@ Uses the awesome httpbin.org to validate responses """ +import base64 import platform +import unittest -import twilio +from httplib2 import Response from nose.tools import assert_equal, raises from mock import patch, Mock, ANY + +import twilio from twilio.rest.exceptions import TwilioRestException from twilio.rest.resources.base import make_request, make_twilio_request from twilio.rest.resources.connection import Connection from twilio.rest.resources.connection import PROXY_TYPE_SOCKS5 +from twilio.scoped_authentication_token import ScopedAuthenticationToken + get_headers = { "User-Agent": "twilio-python/{version} (Python {python_version})".format( @@ -26,96 +32,136 @@ post_headers["Content-Type"] = "application/x-www-form-urlencoded" -@patch('twilio.rest.resources.base.Response') -@patch('httplib2.Http') -def test_get_params(http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get", params={"hey": "you"}) - http.request.assert_called_with("http://httpbin.org/get?hey=you", "GET", - body=None, headers=None) - - -@patch('twilio.rest.resources.base.Response') -@patch('httplib2.Http') -def test_get_extra_params(http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get?foo=bar", params={"hey": "you"}) - http.request.assert_called_with("http://httpbin.org/get?foo=bar&hey=you", "GET", - body=None, headers=None) - - -@patch('twilio.rest.resources.base.Response') -@patch('httplib2.Http') -def test_resp_uri(http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get") - http.request.assert_called_with("http://httpbin.org/get", "GET", - body=None, headers=None) - - -@patch('twilio.rest.resources.base.Response') -@patch('httplib2.Http') -def test_sequence_data(http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request( - "POST", - "http://httpbin.org/post", - data={"a_list": ["here", "is", "some", "stuff"]}, - ) - http.request.assert_called_with( - "http://httpbin.org/post", - "POST", - body="a_list=here&a_list=is&a_list=some&a_list=stuff", - headers=None, - ) - - -@patch('twilio.rest.resources.base.make_request') -def test_make_twilio_request_headers(mock): - url = "http://random/url" - make_twilio_request("POST", url, use_json_extension=True) - mock.assert_called_with("POST", "http://random/url.json", - headers=post_headers) - - -@raises(TwilioRestException) -@patch('twilio.rest.resources.base.make_request') -def test_make_twilio_request_bad_data(mock): - resp = Mock() - resp.ok = False - resp.return_value = "error" - mock.return_value = resp - - url = "http://random/url" - make_twilio_request("POST", url) - mock.assert_called_with("POST", "http://random/url.json", - headers=post_headers) - - -@patch('twilio.rest.resources.base.Response') -@patch('httplib2.Http') -def test_proxy_info(http_mock, resp_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - Connection.set_proxy_info( - 'example.com', - 8080, - proxy_type=PROXY_TYPE_SOCKS5, - ) - make_request("GET", "http://httpbin.org/get") - http_mock.assert_called_with(timeout=None, ca_certs=ANY, proxy_info=ANY) - http.request.assert_called_with("http://httpbin.org/get", "GET", - body=None, headers=None) - proxy_info = http_mock.call_args[1]['proxy_info'] - assert_equal(proxy_info.proxy_host, 'example.com') - assert_equal(proxy_info.proxy_port, 8080) - assert_equal(proxy_info.proxy_type, PROXY_TYPE_SOCKS5) +class MakeRequestTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http') + def test_get_params(self, http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get", params={"hey": "you"}) + http.request.assert_called_with("http://httpbin.org/get?hey=you", "GET", + body=None, headers=None) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http') + def test_get_extra_params(self, http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get?foo=bar", params={"hey": "you"}) + http.request.assert_called_with("http://httpbin.org/get?foo=bar&hey=you", "GET", + body=None, headers=None) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http') + def test_resp_uri(self, http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get") + http.request.assert_called_with("http://httpbin.org/get", "GET", + body=None, headers=None) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http') + def test_sequence_data(self, http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request( + "POST", + "http://httpbin.org/post", + data={"a_list": ["here", "is", "some", "stuff"]}, + ) + http.request.assert_called_with( + "http://httpbin.org/post", + "POST", + body="a_list=here&a_list=is&a_list=some&a_list=stuff", + headers=None, + ) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http._conn_request') + def test_make_request_basic_auth(self, mock_request, mock_response): + response = Response({ + 'status': '401', + 'WWW-Authenticate': 'Basic realm="Twilio API"' + }) + mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] + make_request('GET', 'http://httpbin.org/get', auth=('AC123', 'AuthToken')) + mock_request.assert_called_with(ANY, + '/get', + 'GET', + None, + { + 'accept-encoding': 'gzip, deflate', + 'authorization': 'Basic {}'.format( + base64.b64encode("{}:{}".format('AC123', 'AuthToken')) + ), + 'user-agent': 'Python-httplib2/0.8 (gzip)' + }) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http._conn_request') + def test_make_request_token_auth(self, mock_request, mock_response): + scoped_authentication_token = ScopedAuthenticationToken('SK123', 'AC123') + jwt = scoped_authentication_token.generate_token('secret') + response = Response({ + 'status': '401', + 'WWW-Authenticate': 'Basic realm="Twilio API"' + }) + mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] + make_request('GET', 'http://httpbin.org/get', auth=('AC123', jwt)) + mock_request.assert_called_with(ANY, + '/get', + 'GET', + None, + { + 'accept-encoding': 'gzip, deflate', + 'authorization': 'Basic {}'.format( + base64.b64encode("{}:{}".format('Token', jwt)) + ), + 'user-agent': 'Python-httplib2/0.8 (gzip)' + }) + + @patch('twilio.rest.resources.base.make_request') + def test_make_twilio_request_headers(self, mock): + url = "http://random/url" + make_twilio_request("POST", url, use_json_extension=True) + mock.assert_called_with("POST", "http://random/url.json", + headers=post_headers) + + @raises(TwilioRestException) + @patch('twilio.rest.resources.base.make_request') + def test_make_twilio_request_bad_data(self, mock): + resp = Mock() + resp.ok = False + resp.return_value = "error" + mock.return_value = resp + + url = "http://random/url" + make_twilio_request("POST", url) + mock.assert_called_with("POST", "http://random/url.json", + headers=post_headers) + + @patch('twilio.rest.resources.base.Response') + @patch('httplib2.Http') + def test_proxy_info(self, http_mock, resp_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + Connection.set_proxy_info( + 'example.com', + 8080, + proxy_type=PROXY_TYPE_SOCKS5, + ) + make_request("GET", "http://httpbin.org/get") + http_mock.assert_called_with(timeout=None, ca_certs=ANY, proxy_info=ANY) + http.request.assert_called_with("http://httpbin.org/get", "GET", + body=None, headers=None) + proxy_info = http_mock.call_args[1]['proxy_info'] + assert_equal(proxy_info.proxy_host, 'example.com') + assert_equal(proxy_info.proxy_port, 8080) + assert_equal(proxy_info.proxy_type, PROXY_TYPE_SOCKS5) diff --git a/tests/test_scoped_authentication_token.py b/tests/test_scoped_authentication_token.py new file mode 100644 index 0000000000..71d2e52eb2 --- /dev/null +++ b/tests/test_scoped_authentication_token.py @@ -0,0 +1,80 @@ +import unittest +from decimal import Decimal + +from nose.tools import assert_equal, assert_is_not_none, assert_true + +from twilio.jwt import decode + +from twilio.scoped_authentication_token import ScopedAuthenticationToken, Grant + + +class ScopedAuthenticationTokenTest(unittest.TestCase): + def test_add_grant(self): + scoped_authentication_token = ScopedAuthenticationToken( + 'SK123', + 'AC123', + None, + 3600, + [Grant('https://api.twilio.com/**')] + ) + scoped_authentication_token.add_grant(Grant('https://taskrouter.twilio.com/**')) + assert_equal(2, len(scoped_authentication_token.grants)) + + def test_generate_token(self): + scoped_authentication_token = ScopedAuthenticationToken( + 'SK123', + 'AC123', + 'Token1', + 3600, + [Grant('https://api.twilio.com/**')] + ) + token = scoped_authentication_token.generate_token('secret') + assert_is_not_none(token) + decoded_token = decode(token, 'secret') + assert_is_not_none(decoded_token) + assert_equal('Token1', decoded_token['jti']) + assert_equal('SK123', decoded_token['iss']) + assert_equal('AC123', decoded_token['sub']) + assert_is_not_none(decoded_token['nbf']) + assert_is_not_none(decoded_token['exp']) + assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) + assert_is_not_none(decoded_token['grants']) + assert_equal('https://api.twilio.com/**', decoded_token['grants'][0]['res']) + assert_equal('*', decoded_token['grants'][0]['act'][0]) + + def test_generate_token_without_grant(self): + scoped_authentication_token = ScopedAuthenticationToken('SK123', 'AC123', 'Token1', 3600) + token = scoped_authentication_token.generate_token('secret') + assert_is_not_none(token) + decoded_token = decode(token, 'secret') + assert_is_not_none(decoded_token) + assert_equal('Token1', decoded_token['jti']) + assert_equal('SK123', decoded_token['iss']) + assert_equal('AC123', decoded_token['sub']) + assert_is_not_none(decoded_token['nbf']) + assert_is_not_none(decoded_token['exp']) + assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) + assert_is_not_none(decoded_token['grants']) + assert_equal(0, len(decoded_token['grants'])) + + def test_generate_token_without_token_id(self): + scoped_authentication_token = ScopedAuthenticationToken( + 'SK123', + 'AC123', + None, + 3600, + [Grant('https://api.twilio.com/**')] + ) + token = scoped_authentication_token.generate_token('secret') + assert_is_not_none(token) + decoded_token = decode(token, 'secret') + assert_is_not_none(decoded_token) + assert_is_not_none(decoded_token['jti']) + assert_equal('SK123', decoded_token['iss']) + assert_equal('AC123', decoded_token['sub']) + assert_is_not_none(decoded_token['nbf']) + assert_is_not_none(decoded_token['exp']) + assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) + assert_is_not_none(decoded_token['grants']) + assert_equal('https://api.twilio.com/**', decoded_token['grants'][0]['res']) + assert_equal('*', decoded_token['grants'][0]['act'][0]) diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index edb4062433..5b8ca4bda1 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -6,7 +6,9 @@ import base64 import hashlib import hmac -from six import text_type, b + +from six import b + # default text to binary representation conversion @@ -41,9 +43,11 @@ def base64url_encode(input): return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '') -def encode(payload, key, algorithm='HS256'): +def encode(payload, key, algorithm='HS256', headers=None): segments = [] header = {"typ": "JWT", "alg": algorithm} + if headers: + header.update(headers) segments.append(base64url_encode(binary(json.dumps(header)))) segments.append(base64url_encode(binary(json.dumps(payload)))) sign_input = '.'.join(segments) diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index a2a075a2cd..f006c74404 100644 --- a/twilio/rest/resources/base.py +++ b/twilio/rest/resources/base.py @@ -8,15 +8,17 @@ binary_type, iteritems ) + from ...compat import urlencode from ...compat import urlparse from ...compat import urlunparse - from ... import __version__ from ...exceptions import TwilioException from ..exceptions import TwilioRestException from .connection import Connection from .imports import parse_qs, httplib2, json +from twilio import jwt +from twilio.jwt import DecodeError from .util import ( parse_iso_date, parse_rfc2822_date, @@ -24,6 +26,7 @@ UNSET_TIMEOUT, ) + logger = logging.getLogger('twilio') @@ -83,7 +86,11 @@ def make_request(method, url, params=None, data=None, headers=None, http.follow_redirects = allow_redirects if auth is not None: - http.add_credentials(auth[0], auth[1]) + try: + jwt.decode(auth[1], verify=False) + http.add_credentials('Token', auth[1]) + except DecodeError: + http.add_credentials(auth[0], auth[1]) def encode_atom(atom): if isinstance(atom, (integer_types, binary_type)): diff --git a/twilio/rest/resources/signing_keys.py b/twilio/rest/resources/signing_keys.py new file mode 100644 index 0000000000..0afed153cc --- /dev/null +++ b/twilio/rest/resources/signing_keys.py @@ -0,0 +1,45 @@ +from twilio.rest.resources.base import InstanceResource, ListResource + + +class SigningKey(InstanceResource): + """ A signing key resource """ + + def update(self, **kwargs): + """ + Update this signing key + """ + return self.parent.update(self.name, **kwargs) + + def delete(self): + """ + Delete this signing key + """ + return self.parent.delete(self.name) + + +class SigningKeys(ListResource): + name = "SigningKeys" + key = "signing_keys" + instance = SigningKey + + def create(self, **kwargs): + """ + Create a :class:`SigningKey` with any of these optional parameters. + + :param friendly_name: A human readable description of the signing key. + """ + return self.create_instance(kwargs) + + def update(self, sid, **kwargs): + """ + Update a :class:`SigningKey` with the given parameters. + + All the parameters are describe above in :meth:`create` + """ + return self.update_instance(sid, kwargs) + + def delete(self, sid): + """ + Delete a :class:`SigningKey` + """ + return self.delete_instance(sid) diff --git a/twilio/scoped_authentication_token.py b/twilio/scoped_authentication_token.py new file mode 100644 index 0000000000..4d6a01fe81 --- /dev/null +++ b/twilio/scoped_authentication_token.py @@ -0,0 +1,48 @@ +import time + +import jwt + + +class ScopedAuthenticationToken: + ACTION_ALL = '*' + ACTION_DELETE = 'DELETE' + ACTION_GET = 'GET' + ACTION_POST = 'POST' + ACTION_PUT = 'PUT' + + def __init__(self, signing_key_sid, account_sid, token_id=None, ttl=3600, grants=[]): + self.signing_key_sid = signing_key_sid + self.account_sid = account_sid + if token_id: + self.token_id = token_id + else: + self.token_id = '{}-{}'.format(signing_key_sid, time.time()) + self.ttl = ttl + self.grants = grants + + def add_grant(self, grant): + self.grants.append(grant) + + def generate_token(self, secret): + payload = { + "jti": self.token_id, + "iss": self.signing_key_sid, + "sub": self.account_sid, + "nbf": time.time(), + "exp": time.time() + self.ttl, + "grants": [] + } + + for grant in self.grants: + payload['grants'].append({ + 'res': grant.res, + 'act': grant.act + }) + + return jwt.encode(payload, secret, headers={"cty": "twilio-sat;v=1"}) + + +class Grant: + def __init__(self, resource, action=ScopedAuthenticationToken.ACTION_ALL): + self.res = resource + self.act = action From ea2b9d9b648b77acdd448f1f86a98f19e3a6a58b Mon Sep 17 00:00:00 2001 From: Alexandre Payment Date: Tue, 28 Apr 2015 14:09:12 -0700 Subject: [PATCH 002/115] Python 2 compatibility --- twilio/jwt/__init__.py | 1 - twilio/scoped_authentication_token.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index 5b8ca4bda1..f87ba5365f 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -10,7 +10,6 @@ from six import b - # default text to binary representation conversion def binary(txt): return txt.encode('utf-8') diff --git a/twilio/scoped_authentication_token.py b/twilio/scoped_authentication_token.py index 4d6a01fe81..b9d4caee51 100644 --- a/twilio/scoped_authentication_token.py +++ b/twilio/scoped_authentication_token.py @@ -3,7 +3,7 @@ import jwt -class ScopedAuthenticationToken: +class ScopedAuthenticationToken(object): ACTION_ALL = '*' ACTION_DELETE = 'DELETE' ACTION_GET = 'GET' @@ -42,7 +42,7 @@ def generate_token(self, secret): return jwt.encode(payload, secret, headers={"cty": "twilio-sat;v=1"}) -class Grant: +class Grant(object): def __init__(self, resource, action=ScopedAuthenticationToken.ACTION_ALL): self.res = resource self.act = action From a01e39b7511d204d4e8d59cb0e176485d40b5cb4 Mon Sep 17 00:00:00 2001 From: Alexandre Payment Date: Tue, 28 Apr 2015 14:19:29 -0700 Subject: [PATCH 003/115] Add signing key attributes documentation --- twilio/rest/resources/signing_keys.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/twilio/rest/resources/signing_keys.py b/twilio/rest/resources/signing_keys.py index 0afed153cc..1aa6dbab38 100644 --- a/twilio/rest/resources/signing_keys.py +++ b/twilio/rest/resources/signing_keys.py @@ -2,7 +2,31 @@ class SigningKey(InstanceResource): - """ A signing key resource """ + """ + A signing key resource. + See https://www.twilio.com/docs/api/rest/signing-keys + + .. attribute:: sid + + The unique ID for this signing key. + + .. attribute:: friendly_name + + A human-readable description of this signing key. + + .. attribute:: secret + + This signing key's secret. + + .. attribute:: date_created + + The date this signing key was created, given as UTC in ISO 8601 format. + + .. attribute:: date_updated + + The date this singing key was last updated, given as UTC in ISO 8601 + format. + """ def update(self, **kwargs): """ From ce0a41eda8e4dab4bfead0d69ec1093f0528756e Mon Sep 17 00:00:00 2001 From: matt Date: Mon, 11 May 2015 16:51:07 -0700 Subject: [PATCH 004/115] Updating python to support Client Grants Removed the Grant class for simplicity. Moved the ACTION constants up to the module scope and renamed them for their purpose. --- tests/requirements.txt | 1 + tests/test_make_request.py | 50 +++++----- tests/test_scoped_authentication_token.py | 112 ++++++++-------------- twilio/scoped_authentication_token.py | 64 +++++++------ 4 files changed, 103 insertions(+), 124 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 9262910394..5f04d6e39f 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,4 +1,5 @@ sphinx +httplib2==0.8 mock==0.8.0 nose coverage diff --git a/tests/test_make_request.py b/tests/test_make_request.py index b4633cd66d..ab4e281a07 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -91,40 +91,44 @@ def test_make_request_basic_auth(self, mock_request, mock_response): }) mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] make_request('GET', 'http://httpbin.org/get', auth=('AC123', 'AuthToken')) - mock_request.assert_called_with(ANY, - '/get', - 'GET', - None, - { - 'accept-encoding': 'gzip, deflate', - 'authorization': 'Basic {}'.format( - base64.b64encode("{}:{}".format('AC123', 'AuthToken')) - ), - 'user-agent': 'Python-httplib2/0.8 (gzip)' - }) + mock_request.assert_called_with( + ANY, + '/get', + 'GET', + None, + { + 'accept-encoding': 'gzip, deflate', + 'authorization': 'Basic {}'.format( + base64.b64encode("{}:{}".format('AC123', 'AuthToken')) + ), + 'user-agent': 'Python-httplib2/0.8 (gzip)' + } + ) @patch('twilio.rest.resources.base.Response') @patch('httplib2.Http._conn_request') def test_make_request_token_auth(self, mock_request, mock_response): scoped_authentication_token = ScopedAuthenticationToken('SK123', 'AC123') - jwt = scoped_authentication_token.generate_token('secret') + jwt = scoped_authentication_token.encode('secret') response = Response({ 'status': '401', 'WWW-Authenticate': 'Basic realm="Twilio API"' }) mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] make_request('GET', 'http://httpbin.org/get', auth=('AC123', jwt)) - mock_request.assert_called_with(ANY, - '/get', - 'GET', - None, - { - 'accept-encoding': 'gzip, deflate', - 'authorization': 'Basic {}'.format( - base64.b64encode("{}:{}".format('Token', jwt)) - ), - 'user-agent': 'Python-httplib2/0.8 (gzip)' - }) + mock_request.assert_called_with( + ANY, + '/get', + 'GET', + None, + { + 'accept-encoding': 'gzip, deflate', + 'authorization': 'Basic {}'.format( + base64.b64encode("{}:{}".format('Token', jwt)) + ), + 'user-agent': 'Python-httplib2/0.8 (gzip)' + } + ) @patch('twilio.rest.resources.base.make_request') def test_make_twilio_request_headers(self, mock): diff --git a/tests/test_scoped_authentication_token.py b/tests/test_scoped_authentication_token.py index 71d2e52eb2..a858490835 100644 --- a/tests/test_scoped_authentication_token.py +++ b/tests/test_scoped_authentication_token.py @@ -1,80 +1,52 @@ import unittest -from decimal import Decimal - -from nose.tools import assert_equal, assert_is_not_none, assert_true +from nose.tools import assert_equal, assert_is_not_none from twilio.jwt import decode +from twilio.scoped_authentication_token import ScopedAuthenticationToken -from twilio.scoped_authentication_token import ScopedAuthenticationToken, Grant +ACCOUNT_SID = 'AC123' +SIGNING_KEY_SID = 'SK123' class ScopedAuthenticationTokenTest(unittest.TestCase): - def test_add_grant(self): - scoped_authentication_token = ScopedAuthenticationToken( - 'SK123', - 'AC123', - None, - 3600, - [Grant('https://api.twilio.com/**')] - ) - scoped_authentication_token.add_grant(Grant('https://taskrouter.twilio.com/**')) - assert_equal(2, len(scoped_authentication_token.grants)) - - def test_generate_token(self): - scoped_authentication_token = ScopedAuthenticationToken( - 'SK123', - 'AC123', - 'Token1', - 3600, - [Grant('https://api.twilio.com/**')] - ) - token = scoped_authentication_token.generate_token('secret') + def _validate_claims(self, payload): + assert_equal(SIGNING_KEY_SID, payload['iss']) + assert_equal(ACCOUNT_SID, payload['sub']) + assert_is_not_none(payload['nbf']) + assert_is_not_none(payload['exp']) + assert_equal(payload['nbf'] + 3600, payload['exp']) + assert_is_not_none(payload['jti']) + assert_equal('{}-{}'.format(payload['iss'], payload['nbf']), + payload['jti']) + assert_is_not_none(payload['grants']) + + def test_empty_grants(self): + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) + token = scat.encode('secret') assert_is_not_none(token) - decoded_token = decode(token, 'secret') - assert_is_not_none(decoded_token) - assert_equal('Token1', decoded_token['jti']) - assert_equal('SK123', decoded_token['iss']) - assert_equal('AC123', decoded_token['sub']) - assert_is_not_none(decoded_token['nbf']) - assert_is_not_none(decoded_token['exp']) - assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) - assert_is_not_none(decoded_token['grants']) - assert_equal('https://api.twilio.com/**', decoded_token['grants'][0]['res']) - assert_equal('*', decoded_token['grants'][0]['act'][0]) - - def test_generate_token_without_grant(self): - scoped_authentication_token = ScopedAuthenticationToken('SK123', 'AC123', 'Token1', 3600) - token = scoped_authentication_token.generate_token('secret') + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal([], payload['grants']) + + def test_single_grant(self): + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) + scat.add_grant('https://api.twilio.com/**') + token = scat.encode('secret') assert_is_not_none(token) - decoded_token = decode(token, 'secret') - assert_is_not_none(decoded_token) - assert_equal('Token1', decoded_token['jti']) - assert_equal('SK123', decoded_token['iss']) - assert_equal('AC123', decoded_token['sub']) - assert_is_not_none(decoded_token['nbf']) - assert_is_not_none(decoded_token['exp']) - assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) - assert_is_not_none(decoded_token['grants']) - assert_equal(0, len(decoded_token['grants'])) - - def test_generate_token_without_token_id(self): - scoped_authentication_token = ScopedAuthenticationToken( - 'SK123', - 'AC123', - None, - 3600, - [Grant('https://api.twilio.com/**')] - ) - token = scoped_authentication_token.generate_token('secret') + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal('https://api.twilio.com/**', payload['grants'][0]['res']) + assert_equal(['*'], payload['grants'][0]['act']) + + def test_endpoint_grant(self): + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) + scat.add_endpoint_grant('bob') + token = scat.encode('secret') assert_is_not_none(token) - decoded_token = decode(token, 'secret') - assert_is_not_none(decoded_token) - assert_is_not_none(decoded_token['jti']) - assert_equal('SK123', decoded_token['iss']) - assert_equal('AC123', decoded_token['sub']) - assert_is_not_none(decoded_token['nbf']) - assert_is_not_none(decoded_token['exp']) - assert_true(Decimal(decoded_token['nbf']) < Decimal(decoded_token['exp'])) - assert_is_not_none(decoded_token['grants']) - assert_equal('https://api.twilio.com/**', decoded_token['grants'][0]['res']) - assert_equal('*', decoded_token['grants'][0]['act'][0]) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal('sip:bob@AC123.endpoint.twilio.com', + payload['grants'][0]['res']) + assert_equal(['listen', 'invite'], payload['grants'][0]['act']) diff --git a/twilio/scoped_authentication_token.py b/twilio/scoped_authentication_token.py index b9d4caee51..c77cbb4902 100644 --- a/twilio/scoped_authentication_token.py +++ b/twilio/scoped_authentication_token.py @@ -2,47 +2,49 @@ import jwt +ALL = '*' -class ScopedAuthenticationToken(object): - ACTION_ALL = '*' - ACTION_DELETE = 'DELETE' - ACTION_GET = 'GET' - ACTION_POST = 'POST' - ACTION_PUT = 'PUT' +# HTTP Actions +HTTP_DELETE = 'DELETE' +HTTP_GET = 'GET' +HTTP_POST = 'POST' +HTTP_PUT = 'PUT' + +# Client Actions +CLIENT_LISTEN = 'listen' +CLIENT_INVITE = 'invite' - def __init__(self, signing_key_sid, account_sid, token_id=None, ttl=3600, grants=[]): +class ScopedAuthenticationToken(object): + def __init__(self, signing_key_sid, account_sid, ttl=3600): self.signing_key_sid = signing_key_sid self.account_sid = account_sid - if token_id: - self.token_id = token_id - else: - self.token_id = '{}-{}'.format(signing_key_sid, time.time()) self.ttl = ttl - self.grants = grants + self.grants = [] + + def add_grant(self, resource, actions=ALL): + if not isinstance(actions, list): + actions = [actions] - def add_grant(self, grant): - self.grants.append(grant) + self.grants.append({ + 'res': resource, + 'act': actions, + }) - def generate_token(self, secret): + def add_endpoint_grant(self, endpoint, actions=None): + actions = actions or [CLIENT_LISTEN, CLIENT_INVITE] + resource = 'sip:{}@{}.endpoint.twilio.com'.format(endpoint, + self.account_sid) + self.add_grant(resource, actions) + + def encode(self, secret): + now = int(time.time()) payload = { - "jti": self.token_id, + "jti": '{}-{}'.format(self.signing_key_sid, now), "iss": self.signing_key_sid, "sub": self.account_sid, - "nbf": time.time(), - "exp": time.time() + self.ttl, - "grants": [] + "nbf": now, + "exp": now + self.ttl, + "grants": self.grants } - for grant in self.grants: - payload['grants'].append({ - 'res': grant.res, - 'act': grant.act - }) - return jwt.encode(payload, secret, headers={"cty": "twilio-sat;v=1"}) - - -class Grant(object): - def __init__(self, resource, action=ScopedAuthenticationToken.ACTION_ALL): - self.res = resource - self.act = action From be9c9e269f8ab06759aa222a1e159761dae35e07 Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 12 May 2015 11:29:13 -0700 Subject: [PATCH 005/115] Removing JWT support from the Client --- tests/test_make_request.py | 26 ----------------------- tests/test_scoped_authentication_token.py | 12 +++++------ twilio/rest/resources/base.py | 8 +------ twilio/scoped_authentication_token.py | 14 +++++++++--- 4 files changed, 18 insertions(+), 42 deletions(-) diff --git a/tests/test_make_request.py b/tests/test_make_request.py index ab4e281a07..9b0c364f36 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -16,7 +16,6 @@ from twilio.rest.resources.base import make_request, make_twilio_request from twilio.rest.resources.connection import Connection from twilio.rest.resources.connection import PROXY_TYPE_SOCKS5 -from twilio.scoped_authentication_token import ScopedAuthenticationToken get_headers = { @@ -105,31 +104,6 @@ def test_make_request_basic_auth(self, mock_request, mock_response): } ) - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http._conn_request') - def test_make_request_token_auth(self, mock_request, mock_response): - scoped_authentication_token = ScopedAuthenticationToken('SK123', 'AC123') - jwt = scoped_authentication_token.encode('secret') - response = Response({ - 'status': '401', - 'WWW-Authenticate': 'Basic realm="Twilio API"' - }) - mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] - make_request('GET', 'http://httpbin.org/get', auth=('AC123', jwt)) - mock_request.assert_called_with( - ANY, - '/get', - 'GET', - None, - { - 'accept-encoding': 'gzip, deflate', - 'authorization': 'Basic {}'.format( - base64.b64encode("{}:{}".format('Token', jwt)) - ), - 'user-agent': 'Python-httplib2/0.8 (gzip)' - } - ) - @patch('twilio.rest.resources.base.make_request') def test_make_twilio_request_headers(self, mock): url = "http://random/url" diff --git a/tests/test_scoped_authentication_token.py b/tests/test_scoped_authentication_token.py index a858490835..da712a3f9e 100644 --- a/tests/test_scoped_authentication_token.py +++ b/tests/test_scoped_authentication_token.py @@ -21,17 +21,17 @@ def _validate_claims(self, payload): assert_is_not_none(payload['grants']) def test_empty_grants(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) - token = scat.encode('secret') + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') self._validate_claims(payload) assert_equal([], payload['grants']) def test_single_grant(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') scat.add_grant('https://api.twilio.com/**') - token = scat.encode('secret') + token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') self._validate_claims(payload) @@ -40,9 +40,9 @@ def test_single_grant(self): assert_equal(['*'], payload['grants'][0]['act']) def test_endpoint_grant(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID) + scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') scat.add_endpoint_grant('bob') - token = scat.encode('secret') + token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') self._validate_claims(payload) diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index f006c74404..8e8b0600a0 100644 --- a/twilio/rest/resources/base.py +++ b/twilio/rest/resources/base.py @@ -17,8 +17,6 @@ from ..exceptions import TwilioRestException from .connection import Connection from .imports import parse_qs, httplib2, json -from twilio import jwt -from twilio.jwt import DecodeError from .util import ( parse_iso_date, parse_rfc2822_date, @@ -86,11 +84,7 @@ def make_request(method, url, params=None, data=None, headers=None, http.follow_redirects = allow_redirects if auth is not None: - try: - jwt.decode(auth[1], verify=False) - http.add_credentials('Token', auth[1]) - except DecodeError: - http.add_credentials(auth[0], auth[1]) + http.add_credentials(auth[0], auth[1]) def encode_atom(atom): if isinstance(atom, (integer_types, binary_type)): diff --git a/twilio/scoped_authentication_token.py b/twilio/scoped_authentication_token.py index c77cbb4902..e9ea4acdbc 100644 --- a/twilio/scoped_authentication_token.py +++ b/twilio/scoped_authentication_token.py @@ -14,10 +14,12 @@ CLIENT_LISTEN = 'listen' CLIENT_INVITE = 'invite' + class ScopedAuthenticationToken(object): - def __init__(self, signing_key_sid, account_sid, ttl=3600): + def __init__(self, signing_key_sid, account_sid, secret, ttl=3600): self.signing_key_sid = signing_key_sid self.account_sid = account_sid + self.secret = secret self.ttl = ttl self.grants = [] @@ -36,8 +38,11 @@ def add_endpoint_grant(self, endpoint, actions=None): self.account_sid) self.add_grant(resource, actions) - def encode(self, secret): + def to_jwt(self): now = int(time.time()) + headers = { + "cty": "twilio-sat;v=1" + } payload = { "jti": '{}-{}'.format(self.signing_key_sid, now), "iss": self.signing_key_sid, @@ -47,4 +52,7 @@ def encode(self, secret): "grants": self.grants } - return jwt.encode(payload, secret, headers={"cty": "twilio-sat;v=1"}) + return jwt.encode(payload, self.secret, headers=headers) + + def __str__(self): + return self.to_jwt() From af595e70533b2f2d4bb8e1d14ed831d21f66d19a Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Tue, 12 May 2015 14:25:43 -0700 Subject: [PATCH 006/115] Add Conversations resources --- twilio/rest/conversations.py | 28 +++++++++ twilio/rest/resources/base.py | 2 +- .../rest/resources/conversations/__init__.py | 1 + .../resources/conversations/conversations.py | 57 +++++++++++++++++++ .../resources/conversations/participants.py | 34 +++++++++++ 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 twilio/rest/conversations.py create mode 100644 twilio/rest/resources/conversations/__init__.py create mode 100644 twilio/rest/resources/conversations/conversations.py create mode 100644 twilio/rest/resources/conversations/participants.py diff --git a/twilio/rest/conversations.py b/twilio/rest/conversations.py new file mode 100644 index 0000000000..93f2ddc03b --- /dev/null +++ b/twilio/rest/conversations.py @@ -0,0 +1,28 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources import UNSET_TIMEOUT +from twilio.rest.resources.conversations.conversations import ConversationsRoot + + +class TwilioConversationsClient(TwilioClient): + """ + A client for accessing the Twilio Conversations API. + + XXX more verbiage here + + :param str account: Your Account Sid from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://conversations.twilio.com", version="v1", + timeout=UNSET_TIMEOUT): + + super(TwilioConversationsClient, self).__init__(account, token, base, + version, timeout) + + self.version_uri = "%s/%s" % (base, version) + self.conversations = ConversationsRoot(self.version_uri, self.auth, + timeout) diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index a2a075a2cd..cf19e1919a 100644 --- a/twilio/rest/resources/base.py +++ b/twilio/rest/resources/base.py @@ -244,7 +244,7 @@ def load(self, entries): del entries["uri"] for key in entries.keys(): - if (key.startswith("date_") and + if ((key.startswith("date_") or key.endswith("_time")) and isinstance(entries[key], string_types)): entries[key] = self._parse_date(entries[key]) diff --git a/twilio/rest/resources/conversations/__init__.py b/twilio/rest/resources/conversations/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/twilio/rest/resources/conversations/__init__.py @@ -0,0 +1 @@ + diff --git a/twilio/rest/resources/conversations/conversations.py b/twilio/rest/resources/conversations/conversations.py new file mode 100644 index 0000000000..67f11c695e --- /dev/null +++ b/twilio/rest/resources/conversations/conversations.py @@ -0,0 +1,57 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class ConversationsRoot(object): + + def __init__(self, base_uri, *args, **kwargs): + self.uri = "%s/Conversations" % base_uri + self._instance_base = Conversations(self.uri, *args, **kwargs) + self.in_progress = Conversations("%s/InProgress" % self.uri, *args, + **kwargs) + self.completed = Conversations("%s/Completed" % self.uri, *args, + **kwargs) + + def get(self, sid): + return self._instance_base.get(sid) + + def delete_instance(self, sid): + return self._instance_base.delete_instance(sid) + + +class Conversation(NextGenInstanceResource): + """A Conversation instance representing a call between two or more participants. + + .. attribute:: sid + + .. attribute:: account_sid + + .. attribute:: status + + .. attribute:: date_created + + .. attribute:: start_time + + .. attribute:: end_time + + .. attribute:: duration + + .. attribute:: url + """ + pass + + +class Conversations(NextGenListResource): + + name = "Conversations" + instance = Conversation + + def __init__(self, uri, *args, **kwargs): + super(Conversations, self).__init__(uri, *args, **kwargs) + # This list is exposed at two different locations: /InProgress + # and /Completed. The parent Root object will hand us the full URL + # to set up at. + self._uri = uri + + @property + def uri(self): + return self._uri diff --git a/twilio/rest/resources/conversations/participants.py b/twilio/rest/resources/conversations/participants.py new file mode 100644 index 0000000000..65ad08801f --- /dev/null +++ b/twilio/rest/resources/conversations/participants.py @@ -0,0 +1,34 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Participant(NextGenInstanceResource): + """A participant in a Conversation. + + .. attribute:: sid + + .. attribute:: conversation_sid + + .. attribute:: account_sid + + .. attribute:: status + + .. attribute:: address + + .. attribute:: date_created + + .. attribute:: start_time + + .. attribute:: end_time + + .. attribute:: duration + + .. attribute:: url + """ + pass + + +class Participants(NextGenListResource): + """A list of :class:`Participant` objects.""" + + name = "Participants" + instance = Participant From 4075d2b14cbfaa92695f60269108012d1f2b0b40 Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Tue, 12 May 2015 15:21:59 -0700 Subject: [PATCH 007/115] Add initial Conversations client test --- tests/conversations/__init__.py | 1 + tests/conversations/test_client.py | 14 +++++++ .../conversations/conversation_instance.json | 13 +++++++ .../conversations/conversation_list.json | 39 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 tests/conversations/__init__.py create mode 100644 tests/conversations/test_client.py create mode 100644 tests/resources/conversations/conversation_instance.json create mode 100644 tests/resources/conversations/conversation_list.json diff --git a/tests/conversations/__init__.py b/tests/conversations/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/conversations/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/conversations/test_client.py b/tests/conversations/test_client.py new file mode 100644 index 0000000000..5f9dc3897f --- /dev/null +++ b/tests/conversations/test_client.py @@ -0,0 +1,14 @@ +from mock import patch + +from tests.tools import create_mock_json +from twilio.rest.conversations import TwilioConversationsClient + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_events(mock): + client = TwilioConversationsClient("ACCOUNT_SID", "AUTH_TOKEN") + resp = create_mock_json("tests/resources/conversations/conversation_instance.json") + mock.return_value = resp + client.conversations.get("CV4bbc4afc943cd2a5d29f0ce01c5656db") + uri = "https://conversations.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db" + mock.assert_called_with("GET", uri, auth=("ACCOUNT_SID", "AUTH_TOKEN"), use_json_extension=False) diff --git a/tests/resources/conversations/conversation_instance.json b/tests/resources/conversations/conversation_instance.json new file mode 100644 index 0000000000..a23e753f3b --- /dev/null +++ b/tests/resources/conversations/conversation_instance.json @@ -0,0 +1,13 @@ +{ + "links": { + "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db/Participants" + }, + "sid": "CV4bbc4afc943cd2a5d29f0ce01c5656db", + "status": "created", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "date_created": "2015-05-12T21:13:15Z", + "start_time": "2015-05-12T21:13:15Z", + "end_time": "2015-05-12T21:14:15Z", + "duration": 60, + "url": "https://conversations.stage.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db" +} diff --git a/tests/resources/conversations/conversation_list.json b/tests/resources/conversations/conversation_list.json new file mode 100644 index 0000000000..bc7c0e3efb --- /dev/null +++ b/tests/resources/conversations/conversation_list.json @@ -0,0 +1,39 @@ +{ + "meta": { + "key": "conversations", + "next_page_url": null, + "url": "https://conversations.stage.twilio.com/v1/Conversations/Completed?PageSize=50&Page=0", + "previous_page_url": null, + "first_page_url": "https://conversations.stage.twilio.com/v1/Conversations/Completed?PageSize=50&Page=0", + "page_size": 50, + "page": 0 + }, + "conversations": [ + { + "links": { + "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV5cd9d2f155da05660b5d487b1b69e27d/Participants" + }, + "sid": "CV5cd9d2f155da05660b5d487b1b69e27d", + "status": "completed", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "date_created": "2015-05-12T21:08:50Z", + "start_time": "2015-05-12T21:08:50Z", + "end_time": "2015-05-12T21:09:50Z", + "duration": 60, + "url": "https://conversations.stage.twilio.com/v1/Conversations/CV5cd9d2f155da05660b5d487b1b69e27d" + }, + { + "links": { + "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV878937a518876bece719861b02a4984a/Participants" + }, + "sid": "CV878937a518876bece719861b02a4984a", + "status": "completed", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "date_created": "2015-05-12T16:57:03Z", + "start_time": "2015-05-12T16:57:03Z", + "end_time": "2015-05-12T16:58:03Z", + "duration": 60, + "url": "https://conversations.stage.twilio.com/v1/Conversations/CV878937a518876bece719861b02a4984a" + } + ] +} From 7c3810552c2c212e2182c797bbf045fe5f61131a Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Tue, 12 May 2015 15:39:32 -0700 Subject: [PATCH 008/115] Add Conversations tests --- tests/conversations/test_conversations.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/conversations/test_conversations.py diff --git a/tests/conversations/test_conversations.py b/tests/conversations/test_conversations.py new file mode 100644 index 0000000000..f9fced201e --- /dev/null +++ b/tests/conversations/test_conversations.py @@ -0,0 +1,46 @@ +import unittest + +from mock import patch + +from tests.tools import create_mock_json +from twilio.rest.resources.conversations.conversations import ConversationsRoot + + +AUTH = ("AC123", "token") +BASE_URI = "https://conversations.twilio.com/v1" +CONVERSATION_SID = "CVaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + +class ConversationTest(unittest.TestCase): + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get(self, request): + resp = create_mock_json('tests/resources/conversations/conversation_instance.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) + list_resource = ConversationsRoot(BASE_URI, AUTH) + list_resource.get(CONVERSATION_SID) + request.assert_called_with("GET", uri, use_json_extension=False, auth=AUTH) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_in_progress(self, request): + resp = create_mock_json('tests/resources/conversations/conversation_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Conversations/InProgress".format(BASE_URI) + list_resource = ConversationsRoot(BASE_URI, AUTH) + list_resource.in_progress.list() + request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_completed(self, request): + resp = create_mock_json('tests/resources/conversations/conversation_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Conversations/Completed".format(BASE_URI) + list_resource = ConversationsRoot(BASE_URI, AUTH) + list_resource.completed.list() + request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) From 65b60d25fdfaa584d3f4ea87683244dd7abf8ff1 Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Tue, 12 May 2015 15:42:57 -0700 Subject: [PATCH 009/115] Whoops --- tests/conversations/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conversations/test_client.py b/tests/conversations/test_client.py index 5f9dc3897f..d19545c937 100644 --- a/tests/conversations/test_client.py +++ b/tests/conversations/test_client.py @@ -5,7 +5,7 @@ @patch("twilio.rest.resources.base.make_twilio_request") -def test_events(mock): +def test_conversations(mock): client = TwilioConversationsClient("ACCOUNT_SID", "AUTH_TOKEN") resp = create_mock_json("tests/resources/conversations/conversation_instance.json") mock.return_value = resp From 188cc5e28f567506fbc2f8ad0323af0be65c85c3 Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 12 May 2015 16:19:02 -0700 Subject: [PATCH 010/115] Renaming ScopedAuthenticationToken to AccessToken --- tests/test_scoped_authentication_token.py | 10 +++++----- ...{scoped_authentication_token.py => access_token.py} | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename twilio/{scoped_authentication_token.py => access_token.py} (97%) diff --git a/tests/test_scoped_authentication_token.py b/tests/test_scoped_authentication_token.py index da712a3f9e..3c84fa400d 100644 --- a/tests/test_scoped_authentication_token.py +++ b/tests/test_scoped_authentication_token.py @@ -2,13 +2,13 @@ from nose.tools import assert_equal, assert_is_not_none from twilio.jwt import decode -from twilio.scoped_authentication_token import ScopedAuthenticationToken +from twilio.access_token import AccessToken ACCOUNT_SID = 'AC123' SIGNING_KEY_SID = 'SK123' -class ScopedAuthenticationTokenTest(unittest.TestCase): +class AccessTokenTest(unittest.TestCase): def _validate_claims(self, payload): assert_equal(SIGNING_KEY_SID, payload['iss']) assert_equal(ACCOUNT_SID, payload['sub']) @@ -21,7 +21,7 @@ def _validate_claims(self, payload): assert_is_not_none(payload['grants']) def test_empty_grants(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') @@ -29,7 +29,7 @@ def test_empty_grants(self): assert_equal([], payload['grants']) def test_single_grant(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') scat.add_grant('https://api.twilio.com/**') token = str(scat) assert_is_not_none(token) @@ -40,7 +40,7 @@ def test_single_grant(self): assert_equal(['*'], payload['grants'][0]['act']) def test_endpoint_grant(self): - scat = ScopedAuthenticationToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') scat.add_endpoint_grant('bob') token = str(scat) assert_is_not_none(token) diff --git a/twilio/scoped_authentication_token.py b/twilio/access_token.py similarity index 97% rename from twilio/scoped_authentication_token.py rename to twilio/access_token.py index e9ea4acdbc..daba579c77 100644 --- a/twilio/scoped_authentication_token.py +++ b/twilio/access_token.py @@ -15,7 +15,7 @@ CLIENT_INVITE = 'invite' -class ScopedAuthenticationToken(object): +class AccessToken(object): def __init__(self, signing_key_sid, account_sid, secret, ttl=3600): self.signing_key_sid = signing_key_sid self.account_sid = account_sid From d96482494e9ff0e4edd736161b6dd076f825b633 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Tue, 12 May 2015 23:17:53 -0700 Subject: [PATCH 011/115] Removed the SigningKeys List --- tests/test_signing_keys.py | 13 +++++++++++++ twilio/rest/resources/signing_keys.py | 9 +++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/test_signing_keys.py diff --git a/tests/test_signing_keys.py b/tests/test_signing_keys.py new file mode 100644 index 0000000000..fa0585e29d --- /dev/null +++ b/tests/test_signing_keys.py @@ -0,0 +1,13 @@ +import unittest +from nose.tools import assert_raises +from twilio.rest.resources.signing_keys import SigningKeys + + +class SigningKeysTest(unittest.TestCase): + + def test_list(self): + """ + Tests the Error part + """ + keys = SigningKeys(self, [], {}) + assert_raises(AttributeError, keys.list) diff --git a/twilio/rest/resources/signing_keys.py b/twilio/rest/resources/signing_keys.py index 1aa6dbab38..65ffbe08c1 100644 --- a/twilio/rest/resources/signing_keys.py +++ b/twilio/rest/resources/signing_keys.py @@ -67,3 +67,12 @@ def delete(self, sid): Delete a :class:`SigningKey` """ return self.delete_instance(sid) + + def list(self, **kw): + """ + List is not supported, hence raises an Error + """ + raise AttributeError("SigningKeys do not support lists()") + + def add(x, y): + return x + y From dbdb4a8a70bb24f7cd7a18f9656c25721d5cf33c Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Thu, 14 May 2015 08:37:45 -0700 Subject: [PATCH 012/115] Add Participant tests --- tests/conversations/test_participants.py | 38 +++++++++++++++++++ .../conversations/participant_instance.json | 12 ++++++ .../conversations/participant_list.json | 37 ++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 tests/conversations/test_participants.py create mode 100644 tests/resources/conversations/participant_instance.json create mode 100644 tests/resources/conversations/participant_list.json diff --git a/tests/conversations/test_participants.py b/tests/conversations/test_participants.py new file mode 100644 index 0000000000..6e1d214f7c --- /dev/null +++ b/tests/conversations/test_participants.py @@ -0,0 +1,38 @@ +import unittest + +from mock import patch + +from tests.tools import create_mock_json +from twilio.rest.resources.conversations.participants import Participants + + +AUTH = ("AC123", "token") +BASE_URI = "https://conversations.twilio.com/v1" +CONVERSATION_SID = "CVaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +PARTICIPANT_SID = "PAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + +class ParticipantTest(unittest.TestCase): + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get(self, request): + resp = create_mock_json('tests/resources/conversations/participant_instance.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) + expected = "{0}/Participants/{1}".format(uri, PARTICIPANT_SID) + list_resource = Participants(uri, AUTH) + list_resource.get(PARTICIPANT_SID) + request.assert_called_with("GET", expected, use_json_extension=False, auth=AUTH) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_in_progress(self, request): + resp = create_mock_json('tests/resources/conversations/participant_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) + expected = "{0}/Participants".format(uri) + list_resource = Participants(uri, AUTH) + list_resource.list() + request.assert_called_with("GET", expected, params={}, auth=AUTH, use_json_extension=False) diff --git a/tests/resources/conversations/participant_instance.json b/tests/resources/conversations/participant_instance.json new file mode 100644 index 0000000000..03fb08c041 --- /dev/null +++ b/tests/resources/conversations/participant_instance.json @@ -0,0 +1,12 @@ +{ + "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA97239ce0bff1491fa82986a543bcd9c9", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "sid": "PA97239ce0bff1491fa82986a543bcd9c9", + "address": "torkel2@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", + "status": "disconnected", + "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", + "date_created": "2015-05-13T23:03:12Z", + "start_time": "2015-05-13T23:03:15Z", + "end_time": "2015-05-13T23:14:40Z", + "duration": 685 +} diff --git a/tests/resources/conversations/participant_list.json b/tests/resources/conversations/participant_list.json new file mode 100644 index 0000000000..7d41ff65d2 --- /dev/null +++ b/tests/resources/conversations/participant_list.json @@ -0,0 +1,37 @@ +{ + "meta": { + "key": "participants", + "next_page_url": null, + "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants?PageSize=50&Page=0", + "previous_page_url": null, + "first_page_url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants?PageSize=50&Page=0", + "page_size": 50, + "page": 0 + }, + "participants": [ + { + "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA97239ce0bff1491fa82986a543bcd9c9", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "sid": "PA97239ce0bff1491fa82986a543bcd9c9", + "address": "torkel2@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", + "status": "disconnected", + "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", + "date_created": "2015-05-13T23:03:12Z", + "start_time": "2015-05-13T23:03:15Z", + "end_time": "2015-05-13T23:14:40Z", + "duration": 685 + }, + { + "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA78810fba996f4087c8894b801669b9b2", + "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", + "sid": "PA78810fba996f4087c8894b801669b9b2", + "address": "torkel1@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", + "status": "disconnected", + "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", + "date_created": "2015-05-13T23:03:12Z", + "start_time": "2015-05-13T23:03:15Z", + "end_time": "2015-05-13T23:14:40Z", + "duration": 685 + } + ] +} \ No newline at end of file From 461819f67d51e2c49be3a85ed8b8e2263d068e3b Mon Sep 17 00:00:00 2001 From: Matthew Makai Date: Thu, 14 May 2015 12:33:37 -0700 Subject: [PATCH 013/115] adding signing keys to branch --- twilio/rest/client.py | 2 ++ twilio/rest/resources/__init__.py | 2 ++ twilio/rest/resources/accounts.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/twilio/rest/client.py b/twilio/rest/client.py index 9537cf41bf..d8a5e5d94f 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -20,6 +20,7 @@ Queues, Recordings, Sandboxes, + SigningKeys, Sip, Sms, Tokens, @@ -73,6 +74,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.messages = Messages(self.account_uri, self.auth, timeout) self.media = MediaList(self.account_uri, self.auth, timeout) self.sip = Sip(self.account_uri, self.auth, timeout) + self.signing_keys = SigningKeys(self.account_uri, self.auth, timeout) self.tokens = Tokens(self.account_uri, self.auth, timeout) def participants(self, conference_sid): diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index fa9aef58ec..31bf3135c1 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -44,6 +44,8 @@ from .media import Media, MediaList +from .signing_keys import SigningKey, SigningKeys + from .sip import Sip from .task_router import ( diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py index d54b9d4067..c38270d59c 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -13,6 +13,7 @@ from .messages import Messages from .media import MediaList from .sip import Sip +from .signing_keys import SigningKeys class Account(InstanceResource): @@ -41,6 +42,7 @@ class Account(InstanceResource): UsageTriggers, MediaList, Messages, + SigningKeys, Sip, ] From 9a2edffb368a6cb364306a3d29944428877915ad Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 14 May 2015 23:50:27 -0400 Subject: [PATCH 014/115] New add_rest_grant and enable_nts and tests --- ...tication_token.py => test_access_token.py} | 24 +++++++++++++++++++ twilio/access_token.py | 10 ++++++++ 2 files changed, 34 insertions(+) rename tests/{test_scoped_authentication_token.py => test_access_token.py} (66%) diff --git a/tests/test_scoped_authentication_token.py b/tests/test_access_token.py similarity index 66% rename from tests/test_scoped_authentication_token.py rename to tests/test_access_token.py index 3c84fa400d..45eb0b4870 100644 --- a/tests/test_scoped_authentication_token.py +++ b/tests/test_access_token.py @@ -50,3 +50,27 @@ def test_endpoint_grant(self): assert_equal('sip:bob@AC123.endpoint.twilio.com', payload['grants'][0]['res']) assert_equal(['listen', 'invite'], payload['grants'][0]['act']) + + def test_rest_grant(self): + scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + scat.add_rest_grant('/Apps') + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Apps', + payload['grants'][0]['res']) + assert_equal(['*'], payload['grants'][0]['act']) + + def test_enable_nts(self): + scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') + scat.enable_nts() + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Tokens', + payload['grants'][0]['res']) + assert_equal(['POST'], payload['grants'][0]['act']) diff --git a/twilio/access_token.py b/twilio/access_token.py index daba579c77..0289b44d84 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -32,12 +32,22 @@ def add_grant(self, resource, actions=ALL): 'act': actions, }) + def add_rest_grant(self, uri, actions=ALL): + resource = 'https://api.twilio.com/2010-04-01/Accounts/{}/{}'.format( + self.account_sid, + uri.lstrip('/'), + ) + self.add_grant(resource, actions) + def add_endpoint_grant(self, endpoint, actions=None): actions = actions or [CLIENT_LISTEN, CLIENT_INVITE] resource = 'sip:{}@{}.endpoint.twilio.com'.format(endpoint, self.account_sid) self.add_grant(resource, actions) + def enable_nts(self): + self.add_rest_grant('/Tokens', HTTP_POST) + def to_jwt(self): now = int(time.time()) headers = { From 29967c21d2f7f068fddfac84c4cfd92c8e8b086a Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 15 May 2015 00:12:39 -0400 Subject: [PATCH 015/115] Make token creation chainable --- twilio/access_token.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/twilio/access_token.py b/twilio/access_token.py index 0289b44d84..fcaeae84df 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -31,22 +31,23 @@ def add_grant(self, resource, actions=ALL): 'res': resource, 'act': actions, }) + return self def add_rest_grant(self, uri, actions=ALL): resource = 'https://api.twilio.com/2010-04-01/Accounts/{}/{}'.format( self.account_sid, uri.lstrip('/'), ) - self.add_grant(resource, actions) + return self.add_grant(resource, actions) def add_endpoint_grant(self, endpoint, actions=None): actions = actions or [CLIENT_LISTEN, CLIENT_INVITE] resource = 'sip:{}@{}.endpoint.twilio.com'.format(endpoint, self.account_sid) - self.add_grant(resource, actions) + return self.add_grant(resource, actions) def enable_nts(self): - self.add_rest_grant('/Tokens', HTTP_POST) + return self.add_rest_grant('/Tokens', HTTP_POST) def to_jwt(self): now = int(time.time()) From a20892df10719d73ba819992f9809bdde40a2530 Mon Sep 17 00:00:00 2001 From: Sam Kimbrel Date: Wed, 20 May 2015 17:50:20 -0700 Subject: [PATCH 016/115] Add .json to Tokens in enable_nts --- tests/conversations/__init__.py | 1 - tests/test_access_token.py | 2 +- tests/test_make_request.py | 2 +- twilio/access_token.py | 2 +- twilio/rest/resources/conversations/__init__.py | 1 - twilio/rest/resources/conversations/conversations.py | 3 ++- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/conversations/__init__.py b/tests/conversations/__init__.py index 8b13789179..e69de29bb2 100644 --- a/tests/conversations/__init__.py +++ b/tests/conversations/__init__.py @@ -1 +0,0 @@ - diff --git a/tests/test_access_token.py b/tests/test_access_token.py index 45eb0b4870..c5c56dc120 100644 --- a/tests/test_access_token.py +++ b/tests/test_access_token.py @@ -71,6 +71,6 @@ def test_enable_nts(self): payload = decode(token, 'secret') self._validate_claims(payload) assert_equal(1, len(payload['grants'])) - assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Tokens', + assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Tokens.json', payload['grants'][0]['res']) assert_equal(['POST'], payload['grants'][0]['act']) diff --git a/tests/test_make_request.py b/tests/test_make_request.py index 9b0c364f36..3f2d7f186c 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -100,7 +100,7 @@ def test_make_request_basic_auth(self, mock_request, mock_response): 'authorization': 'Basic {}'.format( base64.b64encode("{}:{}".format('AC123', 'AuthToken')) ), - 'user-agent': 'Python-httplib2/0.8 (gzip)' + 'user-agent': ANY, } ) diff --git a/twilio/access_token.py b/twilio/access_token.py index fcaeae84df..6c29b7ef9f 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -47,7 +47,7 @@ def add_endpoint_grant(self, endpoint, actions=None): return self.add_grant(resource, actions) def enable_nts(self): - return self.add_rest_grant('/Tokens', HTTP_POST) + return self.add_rest_grant('/Tokens.json', HTTP_POST) def to_jwt(self): now = int(time.time()) diff --git a/twilio/rest/resources/conversations/__init__.py b/twilio/rest/resources/conversations/__init__.py index 8b13789179..e69de29bb2 100644 --- a/twilio/rest/resources/conversations/__init__.py +++ b/twilio/rest/resources/conversations/__init__.py @@ -1 +0,0 @@ - diff --git a/twilio/rest/resources/conversations/conversations.py b/twilio/rest/resources/conversations/conversations.py index 67f11c695e..d18c0e2f7a 100644 --- a/twilio/rest/resources/conversations/conversations.py +++ b/twilio/rest/resources/conversations/conversations.py @@ -19,7 +19,8 @@ def delete_instance(self, sid): class Conversation(NextGenInstanceResource): - """A Conversation instance representing a call between two or more participants. + """A Conversation instance representing a call between two or more + participants. .. attribute:: sid From 099c06d672d17dde8cd716aadd67c374c4be9851 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:08:05 -0700 Subject: [PATCH 017/115] porting to python --- .../test_task_router_capability.py | 253 ++++++++++++++++++ .../test_task_router_taskqueue_capability.py | 132 +++++++++ .../test_task_router_worker_capability.py | 162 +++++++++++ .../test_task_router_workspace_capability.py | 131 +++++++++ twilio/task_router/__init__.py | 144 ---------- twilio/task_router/capability/__init__.py | 10 + .../task_router/capability/capability_api.py | 68 +++++ .../capability/task_router_capability.py | 184 +++++++++++++ 8 files changed, 940 insertions(+), 144 deletions(-) create mode 100644 tests/task_router/test_task_router_capability.py create mode 100644 tests/task_router/test_task_router_taskqueue_capability.py create mode 100644 tests/task_router/test_task_router_worker_capability.py create mode 100644 tests/task_router/test_task_router_workspace_capability.py delete mode 100644 twilio/task_router/__init__.py create mode 100644 twilio/task_router/capability/__init__.py create mode 100644 twilio/task_router/capability/capability_api.py create mode 100644 twilio/task_router/capability/task_router_capability.py diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py new file mode 100644 index 0000000000..35b1cb7678 --- /dev/null +++ b/tests/task_router/test_task_router_capability.py @@ -0,0 +1,253 @@ +import sys + +import time +import unittest +import warnings + +from twilio import jwt +from twilio.task_router.capability import TaskRouterCapability + +class TaskRouterCapabilityTest(unittest.TestCase): + + def test_workspace_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + channel_id = "WS456" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, channel_id) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_worker_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["worker_sid"], worker_sid) + self.assertEqual(decoded["channel"], worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # activity GET + fetch_activity = policies[0] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) + self.assertEqual("GET", fetch_activity['method']) + self.assertTrue(fetch_activity['allowed']) + self.assertEqual({}, fetch_activity['query_filter']) + self.assertEqual({}, fetch_activity['post_filter']) + + # websocket GET + get_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[2] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[3] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_task_queue_default(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + taskqueue_sid = "WQ789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, taskqueue_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], taskqueue_sid) + self.assertEqual(decoded["channel"], taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], taskqueue_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_deprecated_worker(self): + account_sid = "AC123" + auth_token = "foobar" + workspace_sid = "WS456" + worker_sid = "WK789" + capability = TaskRouterCapability(account_sid, auth_token, workspace_sid, worker_sid) + + capability.generate_token() + + token = capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["worker_sid"], worker_sid) + self.assertEqual(decoded["channel"], worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], worker_sid) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # should expect 4 policies + + # activity GET + fetch_activity = policies[0] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) + self.assertEqual("GET", fetch_activity['method']) + self.assertTrue(fetch_activity['allowed']) + self.assertEqual({}, fetch_activity['query_filter']) + self.assertEqual({}, fetch_activity['post_filter']) + + # websocket GET + get_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[2] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[3] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + # check deprecated warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_fetch_attributes() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_worker_activity_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + capability.allow_task_reservation_updates() + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "deprecated" in str(w[-1].message) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py new file mode 100644 index 0000000000..bd3cec6295 --- /dev/null +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -0,0 +1,132 @@ +import sys +import time +import unittest + +from twilio import jwt +from twilio.task_router.capability import TaskRouterTaskQueueCapability + +class TaskRouterTaskQueueCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.taskqueue_sid = "WQ789" + self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) + self.assertEqual(decoded["channel"], self.taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py new file mode 100644 index 0000000000..030190747d --- /dev/null +++ b/tests/task_router/test_task_router_worker_capability.py @@ -0,0 +1,162 @@ +import sys + +import time +import unittest +import warnings + +from twilio import jwt +from twilio.task_router.capability import TaskRouterWorkerCapability + +class TaskRouterWorkerCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.worker_sid = "WK789" + self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["worker_sid"], self.worker_sid) + self.assertEqual(decoded["channel"], self.worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.worker_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_defaults(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + + # expect 5 policies + policies = decoded['policies'] + self.assertEqual(len(policies), 5) + + # policy 0 - GET websocket + get_policy = policies[0] + self.assertIsNotNone(get_policy) + self.assertEqual(get_policy['url'], websocket_url) + self.assertEqual(get_policy['method'], 'GET') + self.assertTrue(get_policy['allowed']) + self.assertEqual(get_policy['query_filter'], {}) + self.assertEqual(get_policy['post_filter'], {}) + + # policy 1 - POST + post_policy = policies[1] + self.assertIsNotNone(post_policy) + self.assertEqual(post_policy['url'], websocket_url) + self.assertEqual(post_policy['method'], 'POST') + self.assertTrue(post_policy['allowed']) + self.assertEqual(post_policy['query_filter'], {}) + self.assertEqual(post_policy['post_filter'], {}) + + # policy 2 - Worker fetch + worker_fetch_policy = policies[2] + self.assertIsNotNone(worker_fetch_policy) + self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') + self.assertEqual(worker_fetch_policy['method'], 'GET') + self.assertTrue(worker_fetch_policy['allowed']) + self.assertEqual(worker_fetch_policy['query_filter'], {}) + self.assertEqual(worker_fetch_policy['post_filter'], {}) + + # policy 3 - Reservation fetch + reservation_fetch_policy = policies[3] + self.assertIsNotNone(reservation_fetch_policy) + self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') + self.assertEqual(reservation_fetch_policy['method'], 'GET') + self.assertTrue(reservation_fetch_policy['allowed']) + self.assertEqual(reservation_fetch_policy['query_filter'], {}) + self.assertEqual(reservation_fetch_policy['post_filter'], {}) + + # policy 4 - Activity fetch + activity_fetch_policy = policies[4] + self.assertIsNotNone(activity_fetch_policy) + self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') + self.assertEqual(activity_fetch_policy['method'], 'GET') + self.assertTrue(activity_fetch_policy['allowed']) + self.assertEqual(activity_fetch_policy['query_filter'], {}) + self.assertEqual(activity_fetch_policy['post_filter'], {}) + + def test_allow_activity_updates(self): + + # allow activity updates to the worker + self.capability.allow_activity_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allowed"]) + self.assertIsNotNone(policy['post_filter']) + self.assertEqual({}, policy['query_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) + + def test_allow_reservation_updates(self): + # allow reservation updates + self.capability.allow_reservation_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allowed"]) + self.assertIsNotNone(policy["post_filter"]) + self.assertEqual({}, policy["query_filter"]) + self.assertTrue(policy["post_filter"]["ReservationStatus"]) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py new file mode 100644 index 0000000000..cc389f7120 --- /dev/null +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -0,0 +1,131 @@ +import sys + +import time +import unittest + +from twilio import jwt +from twilio.task_router.capability import TaskRouterWorkspaceCapability + +class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): + + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["channel"], self.workspace_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.workspace_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 3) + + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allowed']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) + + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allowed']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) + + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allowed']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) + + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_fetch_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 4) + + # confirm the additional policy generated with allow_updates_subresources() + + policy = policies[3] + + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allowed']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py deleted file mode 100644 index 17975c1cba..0000000000 --- a/twilio/task_router/__init__.py +++ /dev/null @@ -1,144 +0,0 @@ -import time - -from .. import jwt - - -TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' -TASK_ROUTER_BASE_WS_URL = 'https://event-bridge.twilio.com/v1/wschannels' - -REQUIRED = {'required': True} -OPTIONAL = {'required': False} - - -class TaskRouterCapability(object): - """ - A token to control permissions for the TaskRouter service. - - :param str account_sid: The account to generate a token for - :param str auth_token: The auth token for the account. Used to sign the - token and will not be included in generated output. - :param str workspace_sid: The workspace to grant capabilities over - :param str worker_sid: The worker sid to grant capabilities over - :param str base_url: The base TaskRouter API URL - :param str base_ws_url: The base TaskRouter event stream URL - """ - def __init__(self, account_sid, auth_token, workspace_sid, worker_sid, - base_url=TASK_ROUTER_BASE_URL, - version='v1', - base_ws_url=TASK_ROUTER_BASE_WS_URL): - self.account_sid = account_sid - self.auth_token = auth_token - self.workspace_sid = workspace_sid - self.worker_sid = worker_sid - self.version = version - self.base_url = '%s/%s' % (base_url, self.version) - self.base_ws_url = base_ws_url - self.policies = [] - - self._allow_worker_websocket_urls() - self._allow_activity_list_fetch() - - @property - def workspace_url(self): - return '%s/Workspaces/%s' % (self.base_url, self.workspace_sid) - - @property - def worker_url(self): - return '%s/Workers/%s' % (self.workspace_url, self.worker_sid) - - def _allow_worker_websocket_urls(self): - worker_event_url = '%s/%s/%s' % ( - self.base_ws_url, - self.account_sid, - self.worker_sid, - ) - self.policies.append(make_policy( - worker_event_url, - 'GET', - )) - self.policies.append(make_policy( - worker_event_url, - 'POST', - )) - - def _allow_activity_list_fetch(self): - self.policies.append(make_policy( - '%s/Activities' % self.workspace_url, - 'GET', - )) - - def allow_worker_activity_updates(self): - self.policies.append(make_policy( - self.worker_url, - 'POST', - post_filter={'ActivitySid': REQUIRED}, - )) - - def allow_worker_fetch_attributes(self): - self.policies.append(make_policy( - self.worker_url, - 'GET', - )) - - def allow_task_reservation_updates(self): - tasks_url = '%s/Tasks/**' % self.workspace_url - self.policies.append(make_policy( - tasks_url, - 'POST', - post_filter={'ReservationStatus': REQUIRED}, - )) - - def generate_token(self, ttl=3600, attributes=None): - """ - Generate a token string based on the credentials and permissions - previously configured on this object. - - :param int ttl: Expiration time in seconds of the token. Defaults to - 3600 seconds (1 hour). - :param dict attributes: Extra attributes to pass into the token. - """ - - return self._generate_token( - ttl, - { - 'account_sid': self.account_sid, - 'channel': self.worker_sid, - 'worker_sid': self.worker_sid, - 'workspace_sid': self.workspace_sid, - } - ) - - def _generate_token(self, ttl, attributes=None): - payload = { - 'version': self.version, - 'friendly_name': self.worker_sid, - 'policies': self.policies, - 'iss': self.account_sid, - 'exp': int(time.time()) + ttl, - } - - if attributes is not None: - payload.update(attributes) - - return jwt.encode(payload, self.auth_token, 'HS256') - - -def make_policy(url, method, query_filter=None, post_filter=None, - allowed=True): - """ - Create a policy dictionary for the given resource and method. - - :param str url: the resource URL to grant or deny access to - :param str method: the HTTP method to allow or deny - :param dict query_filter: specific GET parameter names to require or allow - :param dict post_filter: POST parameter names to require or allow - :param allowed bool: whether this request is allowed - """ - - return { - 'url': url, - 'method': method, - 'allow': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {}, - } diff --git a/twilio/task_router/capability/__init__.py b/twilio/task_router/capability/__init__.py new file mode 100644 index 0000000000..73e66978ef --- /dev/null +++ b/twilio/task_router/capability/__init__.py @@ -0,0 +1,10 @@ +from .capability_api import ( + CapabilityAPI +) + +from .task_router_capability import ( + TaskRouterCapability, + TaskRouterWorkerCapability, + TaskRouterTaskQueueCapability, + TaskRouterWorkspaceCapability +) diff --git a/twilio/task_router/capability/capability_api.py b/twilio/task_router/capability/capability_api.py new file mode 100644 index 0000000000..af1a9bf4ed --- /dev/null +++ b/twilio/task_router/capability/capability_api.py @@ -0,0 +1,68 @@ +import time + +from .. import jwt + + +class CapabilityAPI(object): + """ + A token to control permissions for the TaskRouter service. + + :param str account_sid: The account to generate a token for + :param str auth_token: The auth token for the account. Used to sign the + token and will not be included in generated output. + :param str workspace_sid: The workspace to grant capabilities over + :param str worker_sid: The worker sid to grant capabilities over + :param str base_url: The base TaskRouter API URL + :param str base_ws_url: The base TaskRouter event stream URL + """ + def __init__(self, account_sid, auth_token, version, friendly_name): + self.account_sid = account_sid + self.auth_token = auth_token + + self.version = version + self.friendly_name = friendly_name; + self.policies = [] + + def add_policy(self, url, method, allowed, query_filter = None, post_filter = None): + policy = self.make_policy(url, method, allowed, query_filter, post_filter) + self.policies.append(policy) + + def allow(self, url, method, query_filter = None, post_filter = None): + self.add_policy(url, method, True, query_filter, post_filter) + + def deny(self, url, method, query_filter = None, post_filter = None): + self.add_policy(url, method, False, query_filter, post_filter) + + def generate_token(self, ttl = 3600, attributes = None): + return self._generate_token(ttl) + + def _generate_token(self, ttl, attributes=None): + payload = { + 'iss': self.account_sid, + 'exp': int(time.time()) + ttl, + 'version': self.version, + 'friendly_name': self.friendly_name, + 'policies': self.policies, + } + + if attributes is not None: + payload.update(attributes) + + return jwt.encode(payload, self.auth_token, 'HS256') + + def make_policy(self, url, method, allowed = True, query_filter = None, post_filter = None): + # Create a policy dictionary for the given resource and method. + + # :param str url: the resource URL to grant or deny access to + # :param str method: the HTTP method to allow or deny + # :param allowed bool: whether this request is allowed + # :param dict query_filter: specific GET parameter names to require or allow + # :param dict post_filter: POST parameter names to require or allow + + return { + 'url': url, + 'method': method, + 'allowed': allowed, + 'query_filter': query_filter or {}, + 'post_filter': post_filter or {} + } \ No newline at end of file diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py new file mode 100644 index 0000000000..c6f69b392f --- /dev/null +++ b/twilio/task_router/capability/task_router_capability.py @@ -0,0 +1,184 @@ +from .capability_api import CapabilityAPI + +import warnings +warnings.simplefilter('always', DeprecationWarning) + +TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' +TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_VERSION = "v1" + +REQUIRED = {'required': True} +OPTIONAL = {'required': False} + +def deprecated(func): + def log_warning(*args, **kwargs): + # stacklevel = 2 makes the warning refer to the caller of the deprecation rather than the source of deprecation itself + warnings.warn("Call to deprecated function {}.".format(func.__name__), stacklevel = 2, category = DeprecationWarning) + return func(*args, **kwargs) + return log_warning + + +class TaskRouterCapability(CapabilityAPI): + def __init__(self, account_sid, auth_token, workspace_sid, channel_id): + super(TaskRouterCapability, self).__init__(account_sid, auth_token, TASK_ROUTER_VERSION, channel_id) + + self.workspace_sid = workspace_sid + self.channel_id = channel_id + self.base_url = TASK_ROUTER_BASE_URL + "/" + TASK_ROUTER_VERSION + "/Workspaces/" + workspace_sid + + # validate the JWT + self.validate_jwt() + + # set up resources + self.setup_resource() + + # add permissions to GET and POST to the event-bridge channel + self.allow_web_sockets(channel_id) + + # add permissions to fetch the instance resource + self.add_policy(self.resource_url, "GET", True) + + def setup_resource(self): + if self.channel_id[0:2] == "WS": + self.resource_url = self.base_url + elif self.channel_id[0:2] == "WK": + self.resource_url = self.base_url + "/Workers/" + self.channel_id + activity_url = self.base_url + "/Activities" + self.allow(activity_url, "GET") + elif self.channel_id[0:2] == "WQ": + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + def allow_web_sockets(self, channel_id): + web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + self.account_sid + "/" + self.channel_id; + self.policies.append(self.make_policy(web_socket_url, "GET", True)) + self.policies.append(self.make_policy(web_socket_url, "POST", True)) + + def validate_jwt(self): + if self.account_sid is None or self.account_sid[0:2] != "AC": + raise ValueError('Invalid AccountSid provided: ' + self.account_sid) + if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": + raise ValueError('Invalid WorkspaceSid provided: ' + self.workspace_sid) + if self.channel_id is None: + raise ValueError('ChannelId not provided') + + prefix = self.channel_id[0:2] + if prefix != "WS" and prefix != "WK" and prefix != "WQ": + raise ValueError('Invalid ChannelId provided: ' + self.channel_id) + + def allow_fetch_subresources(self): + self.allow(self.resource_url + "/**", "GET") + + def allow_updates(self): + self.allow(self.resource_url, "POST") + + def allow_updates_subresources(self): + self.allow(self.resource_url + "/**", "POST") + + def allow_delete(self): + self.allow(self.resource_url, "DELETE") + + def allow_delete_subresources(self): + self.allow(self.resource_url + "/**", "DELETE") + + @deprecated + def allow_worker_fetch_attributes(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'GET' + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + @deprecated + def allow_worker_activity_updates(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter = {'ActivitySid': REQUIRED} + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + + @deprecated + def allow_task_reservation_updates(self): + if self.channel_id[0:2] == "WK": + tasks_url = self.base_url + "/Tasks/**" + self.policies.append(self.make_policy( + tasks_url, + 'POST', + True, + post_filter = {'ReservationStatus': REQUIRED}, + ) + ) + else: + raise ValueError("Deprecated function not applicable to non Worker") + + def get_resource_url(self): + return self.resource_url + + def generate_token(self, ttl = 3600): + task_router_attributes = {} + task_router_attributes["account_sid"] = self.account_sid + task_router_attributes["workspace_sid"] = self.workspace_sid + task_router_attributes["channel"] = self.channel_id + + if self.channel_id[0:2] == "WK": + task_router_attributes["worker_sid"] = self.channel_id + elif self.channel_id[0:2] == "WQ": + task_router_attributes["taskqueue_sid"] = self.channel_id + + return self._generate_token(ttl, task_router_attributes) + +class TaskRouterWorkerCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): + super(TaskRouterWorkerCapability, self).__init__(account_sid, auth_token, workspace_sid, worker_sid) + + self.reservations_url = self.base_url + "/Tasks/**" + self.activity_url = self.base_url + "/Activities" + + # add permissions to fetch the list of activities and list of worker reservations + self.allow(self.reservations_url, "GET") + self.allow(self.activity_url, "GET") + + def setup_resource(self): + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + def allow_activity_updates(self): + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter = {'ActivitySid': REQUIRED} + ) + ) + def allow_reservation_updates(self): + self.policies.append(self.make_policy( + self.reservations_url, + 'POST', + True, + post_filter = {'ReservationStatus': REQUIRED}, + ) + ) + +class TaskRouterTaskQueueCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): + super(TaskRouterTaskQueueCapability, self).__init__(account_sid, auth_token, workspace_sid, taskqueue_sid) + + def setup_resource(self): + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + +class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, auth_token, workspace_sid, workspace_sid) + + def setup_resource(self): + self.resource_url = self.base_url + + From 73f40e5778ef44894a6da3c66b6d52277906f52e Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:16:45 -0700 Subject: [PATCH 018/115] changed __init__.py file --- twilio/task_router/__init__.py | 10 ++++++++++ twilio/task_router/capability/__init__.py | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 twilio/task_router/__init__.py delete mode 100644 twilio/task_router/capability/__init__.py diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py new file mode 100644 index 0000000000..cf486bcc35 --- /dev/null +++ b/twilio/task_router/__init__.py @@ -0,0 +1,10 @@ +from .. import jwt +import time + +from .capability import ( + CapabilityAPI, + TaskRouterCapability, + TaskRouterWorkerCapability, + TaskRouterTaskQueueCapability, + TaskRouterWorkspaceCapability +) \ No newline at end of file diff --git a/twilio/task_router/capability/__init__.py b/twilio/task_router/capability/__init__.py deleted file mode 100644 index 73e66978ef..0000000000 --- a/twilio/task_router/capability/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .capability_api import ( - CapabilityAPI -) - -from .task_router_capability import ( - TaskRouterCapability, - TaskRouterWorkerCapability, - TaskRouterTaskQueueCapability, - TaskRouterWorkspaceCapability -) From 55c41884ac46a54a62401353824f554f0afab688 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 15:59:51 -0700 Subject: [PATCH 019/115] no reservation status --- tests/task_router/test_task_router_worker_capability.py | 4 ++-- twilio/task_router/capability/task_router_capability.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 030190747d..c36014b303 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -132,7 +132,7 @@ def test_allow_activity_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy['post_filter']) self.assertEqual({}, policy['query_filter']) - self.assertTrue(policy['post_filter']['ActivitySid']) + self.assertEqual({}, policy['post_filter']) def test_allow_reservation_updates(self): # allow reservation updates @@ -156,7 +156,7 @@ def test_allow_reservation_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy["post_filter"]) self.assertEqual({}, policy["query_filter"]) - self.assertTrue(policy["post_filter"]["ReservationStatus"]) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": unittest.main() diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index c6f69b392f..f4ecf9d03e 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -153,16 +153,14 @@ def allow_activity_updates(self): self.policies.append(self.make_policy( self.resource_url, 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} + True ) ) def allow_reservation_updates(self): self.policies.append(self.make_policy( self.reservations_url, 'POST', - True, - post_filter = {'ReservationStatus': REQUIRED}, + True ) ) From b37a9c1bae635bacbac9c8fcce69a5d02a25f77a Mon Sep 17 00:00:00 2001 From: Jen Li Date: Fri, 26 Jun 2015 16:01:54 -0700 Subject: [PATCH 020/115] wrong reservation status changed --- twilio/task_router/capability/task_router_capability.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index f4ecf9d03e..e5a6d5e3e0 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -113,7 +113,6 @@ def allow_task_reservation_updates(self): tasks_url, 'POST', True, - post_filter = {'ReservationStatus': REQUIRED}, ) ) else: @@ -153,7 +152,8 @@ def allow_activity_updates(self): self.policies.append(self.make_policy( self.resource_url, 'POST', - True + True, + post_filter = {'ActivitySid': REQUIRED} ) ) def allow_reservation_updates(self): From 7f987b13951a2bd9466eaf428792dc8c4fde5680 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Mon, 29 Jun 2015 16:59:24 -0700 Subject: [PATCH 021/115] bug fix to fetch reservations --- .../test_task_router_capability.py | 42 +++++++++++++------ .../test_task_router_worker_capability.py | 3 +- .../capability/task_router_capability.py | 5 +++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 35b1cb7678..d700fd44c6 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -9,7 +9,7 @@ class TaskRouterCapabilityTest(unittest.TestCase): - def test_workspace_default(self): + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -58,7 +58,7 @@ def test_workspace_default(self): self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) - def test_worker_default(self): + def test_worker_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -82,7 +82,7 @@ def test_worker_default(self): self.assertEqual(decoded["friendly_name"], worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 4) + self.assertEqual(len(policies), 5) # activity GET fetch_activity = policies[0] @@ -92,8 +92,16 @@ def test_worker_default(self): self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) + # reservation GET + fetch_reservation = policies[1] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) + self.assertEqual("GET", fetch_reservation['method']) + self.assertTrue(fetch_reservation['allowed']) + self.assertEqual({}, fetch_reservation['query_filter']) + self.assertEqual({}, fetch_reservation['post_filter']) + # websocket GET - get_policy = policies[1] + get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) self.assertTrue(get_policy['allowed']) @@ -101,7 +109,7 @@ def test_worker_default(self): self.assertEqual({}, get_policy['post_filter']) # websocket POST - post_policy = policies[2] + post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) self.assertTrue(post_policy['allowed']) @@ -109,14 +117,14 @@ def test_worker_default(self): self.assertEqual({}, post_policy['post_filter']) # fetch GET - fetch_policy = policies[3] + fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) self.assertTrue(fetch_policy['allowed']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) - def test_task_queue_default(self): + def test_task_queue_default(self): account_sid = "AC123" auth_token = "foobar" workspace_sid = "WS456" @@ -190,9 +198,9 @@ def test_deprecated_worker(self): self.assertEqual(decoded["friendly_name"], worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 4) + self.assertEqual(len(policies), 5) - # should expect 4 policies + # should expect 5 policies # activity GET fetch_activity = policies[0] @@ -202,8 +210,16 @@ def test_deprecated_worker(self): self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) + # reservation GET + fetch_reservation = policies[1] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) + self.assertEqual("GET", fetch_reservation['method']) + self.assertTrue(fetch_reservation['allowed']) + self.assertEqual({}, fetch_reservation['query_filter']) + self.assertEqual({}, fetch_reservation['post_filter']) + # websocket GET - get_policy = policies[1] + get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) self.assertTrue(get_policy['allowed']) @@ -211,7 +227,7 @@ def test_deprecated_worker(self): self.assertEqual({}, get_policy['post_filter']) # websocket POST - post_policy = policies[2] + post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) self.assertTrue(post_policy['allowed']) @@ -219,7 +235,7 @@ def test_deprecated_worker(self): self.assertEqual({}, post_policy['post_filter']) # fetch GET - fetch_policy = policies[3] + fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) self.assertTrue(fetch_policy['allowed']) @@ -250,4 +266,4 @@ def test_deprecated_worker(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index c36014b303..ecd9509cb1 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,5 +1,4 @@ import sys - import time import unittest import warnings @@ -132,7 +131,7 @@ def test_allow_activity_updates(self): self.assertTrue(policy["allowed"]) self.assertIsNotNone(policy['post_filter']) self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) def test_allow_reservation_updates(self): # allow reservation updates diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py index e5a6d5e3e0..6758020af6 100644 --- a/twilio/task_router/capability/task_router_capability.py +++ b/twilio/task_router/capability/task_router_capability.py @@ -43,8 +43,13 @@ def setup_resource(self): self.resource_url = self.base_url elif self.channel_id[0:2] == "WK": self.resource_url = self.base_url + "/Workers/" + self.channel_id + activity_url = self.base_url + "/Activities" self.allow(activity_url, "GET") + + reservations_url = self.base_url + "/Tasks/**" + self.allow(reservations_url, "GET") + elif self.channel_id[0:2] == "WQ": self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id From 7be91635b64092b22c0b51d474429200511cfe10 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 15 Jul 2015 09:44:47 -0700 Subject: [PATCH 022/115] Remove CapabilityAPI Fix formatting problems (whitespaces at end of lines, line length of 80 chars, two spaces before class names, new lines at end of file, no spaces between default parameter attributes,) Fix old capability test --- tests/task_router/test_capability.py | 24 +- .../test_task_router_capability.py | 39 ++- .../test_task_router_taskqueue_capability.py | 184 +++++------ .../test_task_router_worker_capability.py | 302 +++++++++--------- .../test_task_router_workspace_capability.py | 181 ++++++----- twilio/task_router/__init__.py | 255 ++++++++++++++- .../task_router/capability/capability_api.py | 68 ---- .../capability/task_router_capability.py | 187 ----------- 8 files changed, 617 insertions(+), 623 deletions(-) delete mode 100644 twilio/task_router/capability/capability_api.py delete mode 100644 twilio/task_router/capability/task_router_capability.py diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index abfb25b0f5..707fe97f83 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -59,6 +59,22 @@ def test_defaults(self): (self.account_sid, self.worker_sid) ) expected = [ + { + 'url': + 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % + (self.workspace_sid), + 'method': 'GET', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, { 'url': websocket_url, 'method': 'GET', @@ -74,13 +90,13 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': - 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % + (self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, - }, + } ] self.assertEqual(expected, decoded['policies']) @@ -140,6 +156,6 @@ def test_allow_task_reservation_updates(self): 'method': 'POST', 'allow': True, 'query_filter': {}, - 'post_filter': {'ReservationStatus': {'required': True}}, + 'post_filter': {}, } self.assertEqual(expected, decoded['policies'][-1]) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index d700fd44c6..9cbe90c1aa 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,11 +1,9 @@ -import sys - -import time import unittest import warnings from twilio import jwt -from twilio.task_router.capability import TaskRouterCapability +from twilio.task_router import TaskRouterCapability + class TaskRouterCapabilityTest(unittest.TestCase): @@ -38,7 +36,7 @@ def test_workspace_default(self): get_policy = policies[0] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -46,7 +44,7 @@ def test_workspace_default(self): post_policy = policies[1] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -54,7 +52,7 @@ def test_workspace_default(self): fetch_policy = policies[2] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -88,7 +86,7 @@ def test_worker_default(self): fetch_activity = policies[0] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allowed']) + self.assertTrue(fetch_activity['allow']) self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) @@ -96,7 +94,7 @@ def test_worker_default(self): fetch_reservation = policies[1] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allowed']) + self.assertTrue(fetch_reservation['allow']) self.assertEqual({}, fetch_reservation['query_filter']) self.assertEqual({}, fetch_reservation['post_filter']) @@ -104,7 +102,7 @@ def test_worker_default(self): get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -112,7 +110,7 @@ def test_worker_default(self): post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -120,7 +118,7 @@ def test_worker_default(self): fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -154,7 +152,7 @@ def test_task_queue_default(self): get_policy = policies[0] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -162,7 +160,7 @@ def test_task_queue_default(self): post_policy = policies[1] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -170,7 +168,7 @@ def test_task_queue_default(self): fetch_policy = policies[2] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -206,7 +204,7 @@ def test_deprecated_worker(self): fetch_activity = policies[0] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allowed']) + self.assertTrue(fetch_activity['allow']) self.assertEqual({}, fetch_activity['query_filter']) self.assertEqual({}, fetch_activity['post_filter']) @@ -214,7 +212,7 @@ def test_deprecated_worker(self): fetch_reservation = policies[1] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allowed']) + self.assertTrue(fetch_reservation['allow']) self.assertEqual({}, fetch_reservation['query_filter']) self.assertEqual({}, fetch_reservation['post_filter']) @@ -222,7 +220,7 @@ def test_deprecated_worker(self): get_policy = policies[2] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) + self.assertTrue(get_policy['allow']) self.assertEqual({}, get_policy['query_filter']) self.assertEqual({}, get_policy['post_filter']) @@ -230,7 +228,7 @@ def test_deprecated_worker(self): post_policy = policies[3] self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) + self.assertTrue(post_policy['allow']) self.assertEqual({}, post_policy['query_filter']) self.assertEqual({}, post_policy['post_filter']) @@ -238,7 +236,7 @@ def test_deprecated_worker(self): fetch_policy = policies[4] self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) + self.assertTrue(fetch_policy['allow']) self.assertEqual({}, fetch_policy['query_filter']) self.assertEqual({}, fetch_policy['post_filter']) @@ -264,6 +262,5 @@ def test_deprecated_worker(self): assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated" in str(w[-1].message) - if __name__ == "__main__": unittest.main() diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py index bd3cec6295..66f466a763 100644 --- a/tests/task_router/test_task_router_taskqueue_capability.py +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -1,132 +1,132 @@ -import sys import time import unittest from twilio import jwt -from twilio.task_router.capability import TaskRouterTaskQueueCapability +from twilio.task_router import TaskRouterTaskQueueCapability + class TaskRouterTaskQueueCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.taskqueue_sid = "WQ789" - self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.taskqueue_sid = "WQ789" + self.capability = TaskRouterTaskQueueCapability(self.account_sid, self.auth_token, self.workspace_sid, self.taskqueue_sid) - def test_generate_token(self): + def test_generate_token(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) - self.assertEqual(decoded["channel"], self.taskqueue_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["taskqueue_sid"], self.taskqueue_sid) + self.assertEqual(decoded["channel"], self.taskqueue_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.taskqueue_sid) - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - def test_generate_token_with_custom_ttl(self): - ttl = 10000 + def test_generate_token_with_custom_ttl(self): + ttl = 10000 - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - def test_default(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 3) + policies = decoded['policies'] + self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allow']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allow']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allow']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) - def test_allow_fetch_subresources(self): - self.capability.allow_fetch_subresources() + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_fetch_subresources() + # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) - def test_allow_updates_subresources(self): - self.capability.allow_updates_subresources() + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_updates_subresources() + # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() + unittest.main() diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index ecd9509cb1..35abb5b831 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,161 +1,159 @@ -import sys import time import unittest -import warnings from twilio import jwt -from twilio.task_router.capability import TaskRouterWorkerCapability +from twilio.task_router import TaskRouterWorkerCapability + class TaskRouterWorkerCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.worker_sid = "WK789" - self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) - - def test_generate_token(self): - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["worker_sid"], self.worker_sid) - self.assertEqual(decoded["channel"], self.worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.worker_sid) - - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - - def test_generate_token_with_custom_ttl(self): - ttl = 10000 - - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - - def test_defaults(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) - - # expect 5 policies - policies = decoded['policies'] - self.assertEqual(len(policies), 5) - - # policy 0 - GET websocket - get_policy = policies[0] - self.assertIsNotNone(get_policy) - self.assertEqual(get_policy['url'], websocket_url) - self.assertEqual(get_policy['method'], 'GET') - self.assertTrue(get_policy['allowed']) - self.assertEqual(get_policy['query_filter'], {}) - self.assertEqual(get_policy['post_filter'], {}) - - # policy 1 - POST - post_policy = policies[1] - self.assertIsNotNone(post_policy) - self.assertEqual(post_policy['url'], websocket_url) - self.assertEqual(post_policy['method'], 'POST') - self.assertTrue(post_policy['allowed']) - self.assertEqual(post_policy['query_filter'], {}) - self.assertEqual(post_policy['post_filter'], {}) - - # policy 2 - Worker fetch - worker_fetch_policy = policies[2] - self.assertIsNotNone(worker_fetch_policy) - self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') - self.assertEqual(worker_fetch_policy['method'], 'GET') - self.assertTrue(worker_fetch_policy['allowed']) - self.assertEqual(worker_fetch_policy['query_filter'], {}) - self.assertEqual(worker_fetch_policy['post_filter'], {}) - - # policy 3 - Reservation fetch - reservation_fetch_policy = policies[3] - self.assertIsNotNone(reservation_fetch_policy) - self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') - self.assertEqual(reservation_fetch_policy['method'], 'GET') - self.assertTrue(reservation_fetch_policy['allowed']) - self.assertEqual(reservation_fetch_policy['query_filter'], {}) - self.assertEqual(reservation_fetch_policy['post_filter'], {}) - - # policy 4 - Activity fetch - activity_fetch_policy = policies[4] - self.assertIsNotNone(activity_fetch_policy) - self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') - self.assertEqual(activity_fetch_policy['method'], 'GET') - self.assertTrue(activity_fetch_policy['allowed']) - self.assertEqual(activity_fetch_policy['query_filter'], {}) - self.assertEqual(activity_fetch_policy['post_filter'], {}) - - def test_allow_activity_updates(self): - - # allow activity updates to the worker - self.capability.allow_activity_updates() - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - policies = decoded['policies'] - self.assertEqual(len(policies), 6) - policy = policies[5] - - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) - - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allowed"]) - self.assertIsNotNone(policy['post_filter']) - self.assertEqual({}, policy['query_filter']) - self.assertTrue(policy['post_filter']['ActivitySid']) - - def test_allow_reservation_updates(self): - # allow reservation updates - self.capability.allow_reservation_updates() - - token = self.capability.generate_token() - self.assertIsNotNone(token) - - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) - - policies = decoded['policies'] - self.assertEqual(len(policies), 6) - - policy = policies[5] - - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid - - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allowed"]) - self.assertIsNotNone(policy["post_filter"]) - self.assertEqual({}, policy["query_filter"]) - self.assertEqual({}, policy['post_filter']) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.worker_sid = "WK789" + self.capability = TaskRouterWorkerCapability(self.account_sid, self.auth_token, self.workspace_sid, self.worker_sid) + + def test_generate_token(self): + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["worker_sid"], self.worker_sid) + self.assertEqual(decoded["channel"], self.worker_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.worker_sid) + + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + + def test_generate_token_with_custom_ttl(self): + ttl = 10000 + + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + + def test_defaults(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + + # expect 5 policies + policies = decoded['policies'] + self.assertEqual(len(policies), 5) + + # policy 0 - GET websocket + get_policy = policies[0] + self.assertIsNotNone(get_policy) + self.assertEqual(get_policy['url'], websocket_url) + self.assertEqual(get_policy['method'], 'GET') + self.assertTrue(get_policy['allow']) + self.assertEqual(get_policy['query_filter'], {}) + self.assertEqual(get_policy['post_filter'], {}) + + # policy 1 - POST + post_policy = policies[1] + self.assertIsNotNone(post_policy) + self.assertEqual(post_policy['url'], websocket_url) + self.assertEqual(post_policy['method'], 'POST') + self.assertTrue(post_policy['allow']) + self.assertEqual(post_policy['query_filter'], {}) + self.assertEqual(post_policy['post_filter'], {}) + + # policy 2 - Worker fetch + worker_fetch_policy = policies[2] + self.assertIsNotNone(worker_fetch_policy) + self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') + self.assertEqual(worker_fetch_policy['method'], 'GET') + self.assertTrue(worker_fetch_policy['allow']) + self.assertEqual(worker_fetch_policy['query_filter'], {}) + self.assertEqual(worker_fetch_policy['post_filter'], {}) + + # policy 3 - Reservation fetch + reservation_fetch_policy = policies[3] + self.assertIsNotNone(reservation_fetch_policy) + self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') + self.assertEqual(reservation_fetch_policy['method'], 'GET') + self.assertTrue(reservation_fetch_policy['allow']) + self.assertEqual(reservation_fetch_policy['query_filter'], {}) + self.assertEqual(reservation_fetch_policy['post_filter'], {}) + + # policy 4 - Activity fetch + activity_fetch_policy = policies[4] + self.assertIsNotNone(activity_fetch_policy) + self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') + self.assertEqual(activity_fetch_policy['method'], 'GET') + self.assertTrue(activity_fetch_policy['allow']) + self.assertEqual(activity_fetch_policy['query_filter'], {}) + self.assertEqual(activity_fetch_policy['post_filter'], {}) + + def test_allow_activity_updates(self): + + # allow activity updates to the worker + self.capability.allow_activity_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allow"]) + self.assertIsNotNone(policy['post_filter']) + self.assertEqual({}, policy['query_filter']) + self.assertTrue(policy['post_filter']['ActivitySid']) + + def test_allow_reservation_updates(self): + # allow reservation updates + self.capability.allow_reservation_updates() + + token = self.capability.generate_token() + self.assertIsNotNone(token) + + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) + + policies = decoded['policies'] + self.assertEqual(len(policies), 6) + + policy = policies[5] + + url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + + self.assertEqual(url, policy["url"]) + self.assertEqual("POST", policy["method"]) + self.assertTrue(policy["allow"]) + self.assertEqual({}, policy["query_filter"]) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() + unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index cc389f7120..b35e692897 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,131 +1,130 @@ -import sys - import time import unittest from twilio import jwt -from twilio.task_router.capability import TaskRouterWorkspaceCapability +from twilio.task_router import TaskRouterWorkspaceCapability + class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): - def setUp(self): - self.account_sid = "AC123" - self.auth_token = "foobar" - self.workspace_sid = "WS456" - self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) + def setUp(self): + self.account_sid = "AC123" + self.auth_token = "foobar" + self.workspace_sid = "WS456" + self.capability = TaskRouterWorkspaceCapability(self.account_sid, self.auth_token, self.workspace_sid) - def test_generate_token(self): + def test_generate_token(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["channel"], self.workspace_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.workspace_sid) + self.assertEqual(decoded["iss"], self.account_sid) + self.assertEqual(decoded["account_sid"], self.account_sid) + self.assertEqual(decoded["workspace_sid"], self.workspace_sid) + self.assertEqual(decoded["channel"], self.workspace_sid) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], self.workspace_sid) - def test_generate_token_with_default_ttl(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_generate_token_with_default_ttl(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 3600, decoded["exp"]) + self.assertEqual(int(time.time()) + 3600, decoded["exp"]) - def test_generate_token_with_custom_ttl(self): - ttl = 10000 + def test_generate_token_with_custom_ttl(self): + ttl = 10000 - token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + token = self.capability.generate_token(ttl) + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - self.assertEqual(int(time.time()) + 10000, decoded["exp"]) + self.assertEqual(int(time.time()) + 10000, decoded["exp"]) - def test_default(self): - token = self.capability.generate_token() - self.assertIsNotNone(token) + def test_default(self): + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 3) + policies = decoded['policies'] + self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allowed']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) + # websocket GET + get_policy = policies[0] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) + self.assertEqual("GET", get_policy['method']) + self.assertTrue(get_policy['allow']) + self.assertEqual({}, get_policy['query_filter']) + self.assertEqual({}, get_policy['post_filter']) - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allowed']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) + # websocket POST + post_policy = policies[1] + self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) + self.assertEqual("POST", post_policy['method']) + self.assertTrue(post_policy['allow']) + self.assertEqual({}, post_policy['query_filter']) + self.assertEqual({}, post_policy['post_filter']) - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allowed']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + # fetch GET + fetch_policy = policies[2] + self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) + self.assertEqual("GET", fetch_policy['method']) + self.assertTrue(fetch_policy['allow']) + self.assertEqual({}, fetch_policy['query_filter']) + self.assertEqual({}, fetch_policy['post_filter']) - def test_allow_fetch_subresources(self): - self.capability.allow_fetch_subresources() + def test_allow_fetch_subresources(self): + self.capability.allow_fetch_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_fetch_subresources() + # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "GET") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) - def test_allow_updates_subresources(self): - self.capability.allow_updates_subresources() + def test_allow_updates_subresources(self): + self.capability.allow_updates_subresources() - token = self.capability.generate_token() - self.assertIsNotNone(token) + token = self.capability.generate_token() + self.assertIsNotNone(token) - decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + decoded = jwt.decode(token, self.auth_token) + self.assertIsNotNone(decoded) - policies = decoded['policies'] - self.assertEqual(len(policies), 4) + policies = decoded['policies'] + self.assertEqual(len(policies), 4) - # confirm the additional policy generated with allow_updates_subresources() + # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] + policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allowed']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") + self.assertEqual(policy['method'], "POST") + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index cf486bcc35..58a313d5e2 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -1,10 +1,249 @@ -from .. import jwt import time +from .. import jwt + +import warnings +warnings.simplefilter('always', DeprecationWarning) + +TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' +TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' +TASK_ROUTER_VERSION = "v1" + +REQUIRED = {'required': True} +OPTIONAL = {'required': False} + + +def deprecated(func): + def log_warning(*args, **kwargs): + # stacklevel = 2 makes the warning refer to the caller of the + # deprecation rather than the source of deprecation itself + warnings.warn("Call to deprecated function {}.". + format(func.__name__), + stacklevel=2, + category=DeprecationWarning) + return func(*args, **kwargs) + return log_warning + + +class TaskRouterCapability(object): + def __init__(self, account_sid, auth_token, workspace_sid, channel_id): + self.account_sid = account_sid + self.auth_token = auth_token + self.policies = [] + + self.workspace_sid = workspace_sid + self.channel_id = channel_id + self.base_url = (TASK_ROUTER_BASE_URL + "/" + + TASK_ROUTER_VERSION + + "/Workspaces/" + workspace_sid) + + # validate the JWT + self.validate_jwt() + + # set up resources + self.setup_resource() + + # add permissions to GET and POST to the event-bridge channel + self.allow_web_sockets(channel_id) + + # add permissions to fetch the instance resource + self.add_policy(self.resource_url, "GET", True) + + def setup_resource(self): + if self.channel_id[0:2] == "WS": + self.resource_url = self.base_url + elif self.channel_id[0:2] == "WK": + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + activity_url = self.base_url + "/Activities" + self.allow(activity_url, "GET") + + reservations_url = self.base_url + "/Tasks/**" + self.allow(reservations_url, "GET") + + elif self.channel_id[0:2] == "WQ": + self.resource_url = self.base_url + \ + "/TaskQueues/" + self.channel_id + + def allow_web_sockets(self, channel_id): + web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + \ + self.account_sid + "/" + self.channel_id + + self.policies.append(self.make_policy(web_socket_url, "GET", True)) + self.policies.append(self.make_policy(web_socket_url, "POST", True)) + + def validate_jwt(self): + if self.account_sid is None or self.account_sid[0:2] != "AC": + raise ValueError('Invalid AccountSid provided: ' + + self.account_sid) + if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": + raise ValueError('Invalid WorkspaceSid provided: ' + + self.workspace_sid) + if self.channel_id is None: + raise ValueError('ChannelId not provided') + + prefix = self.channel_id[0:2] + if prefix != "WS" and prefix != "WK" and prefix != "WQ": + raise ValueError('Invalid ChannelId provided: ' + self.channel_id) + + def allow_fetch_subresources(self): + self.allow(self.resource_url + "/**", "GET") + + def allow_updates(self): + self.allow(self.resource_url, "POST") + + def allow_updates_subresources(self): + self.allow(self.resource_url + "/**", "POST") + + def allow_delete(self): + self.allow(self.resource_url, "DELETE") + + def allow_delete_subresources(self): + self.allow(self.resource_url + "/**", "DELETE") + + @deprecated + def allow_worker_fetch_attributes(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'GET')) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + @deprecated + def allow_worker_activity_updates(self): + if self.channel_id[0:2] == "WK": + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + @deprecated + def allow_task_reservation_updates(self): + if self.channel_id[0:2] == "WK": + tasks_url = self.base_url + "/Tasks/**" + self.policies.append(self.make_policy( + tasks_url, + 'POST', + True)) + else: + raise ValueError("Deprecated func not applicable to non Worker") + + def add_policy(self, url, method, + allowed, query_filter=None, post_filter=None): + + policy = self.make_policy(url, method, + allowed, query_filter, post_filter) + self.policies.append(policy) + + def allow(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, True, query_filter, post_filter) + + def deny(self, url, method, query_filter=None, post_filter=None): + self.add_policy(url, method, False, query_filter, post_filter) + + def make_policy(self, url, method, + allowed=True, query_filter=None, post_filter=None): + + # Create a policy dictionary for the given resource and method. + # :param str url: the resource URL to grant or deny access to + # :param str method: the HTTP method to allow or deny + # :param allowed bool: whether this request is allowed + # :param dict query_filter: specific GET parameter names + # to require or allow + # :param dict post_filter: POST parameter names + # to require or allow + + return { + 'url': url, + 'method': method, + 'allow': allowed, + 'query_filter': query_filter or {}, + 'post_filter': post_filter or {} + } + + def get_resource_url(self): + return self.resource_url + + def generate_token(self, ttl=3600): + task_router_attributes = {} + task_router_attributes["account_sid"] = self.account_sid + task_router_attributes["workspace_sid"] = self.workspace_sid + task_router_attributes["channel"] = self.channel_id + + if self.channel_id[0:2] == "WK": + task_router_attributes["worker_sid"] = self.channel_id + elif self.channel_id[0:2] == "WQ": + task_router_attributes["taskqueue_sid"] = self.channel_id + + return self._generate_token(ttl, task_router_attributes) + + def _generate_token(self, ttl, attributes=None): + payload = { + 'iss': self.account_sid, + 'exp': int(time.time()) + ttl, + 'version': TASK_ROUTER_VERSION, + 'friendly_name': self.channel_id, + 'policies': self.policies, + } + + if attributes is not None: + payload.update(attributes) + + return jwt.encode(payload, self.auth_token, 'HS256') + + +class TaskRouterWorkerCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): + super(TaskRouterWorkerCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + worker_sid) + + self.reservations_url = self.base_url + "/Tasks/**" + self.activity_url = self.base_url + "/Activities" + + # add permissions to fetch the list of activities and + # list of worker reservations + self.allow(self.reservations_url, "GET") + self.allow(self.activity_url, "GET") + + def setup_resource(self): + self.resource_url = self.base_url + "/Workers/" + self.channel_id + + def allow_activity_updates(self): + self.policies.append(self.make_policy( + self.resource_url, + 'POST', + True, + post_filter={'ActivitySid': REQUIRED})) + + def allow_reservation_updates(self): + self.policies.append(self.make_policy( + self.reservations_url, + 'POST', + True)) + + +class TaskRouterTaskQueueCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): + super(TaskRouterTaskQueueCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + taskqueue_sid) + + def setup_resource(self): + self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id + + +class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + workspace_sid) -from .capability import ( - CapabilityAPI, - TaskRouterCapability, - TaskRouterWorkerCapability, - TaskRouterTaskQueueCapability, - TaskRouterWorkspaceCapability -) \ No newline at end of file + def setup_resource(self): + self.resource_url = self.base_url diff --git a/twilio/task_router/capability/capability_api.py b/twilio/task_router/capability/capability_api.py deleted file mode 100644 index af1a9bf4ed..0000000000 --- a/twilio/task_router/capability/capability_api.py +++ /dev/null @@ -1,68 +0,0 @@ -import time - -from .. import jwt - - -class CapabilityAPI(object): - """ - A token to control permissions for the TaskRouter service. - - :param str account_sid: The account to generate a token for - :param str auth_token: The auth token for the account. Used to sign the - token and will not be included in generated output. - :param str workspace_sid: The workspace to grant capabilities over - :param str worker_sid: The worker sid to grant capabilities over - :param str base_url: The base TaskRouter API URL - :param str base_ws_url: The base TaskRouter event stream URL - """ - def __init__(self, account_sid, auth_token, version, friendly_name): - self.account_sid = account_sid - self.auth_token = auth_token - - self.version = version - self.friendly_name = friendly_name; - self.policies = [] - - def add_policy(self, url, method, allowed, query_filter = None, post_filter = None): - policy = self.make_policy(url, method, allowed, query_filter, post_filter) - self.policies.append(policy) - - def allow(self, url, method, query_filter = None, post_filter = None): - self.add_policy(url, method, True, query_filter, post_filter) - - def deny(self, url, method, query_filter = None, post_filter = None): - self.add_policy(url, method, False, query_filter, post_filter) - - def generate_token(self, ttl = 3600, attributes = None): - return self._generate_token(ttl) - - def _generate_token(self, ttl, attributes=None): - payload = { - 'iss': self.account_sid, - 'exp': int(time.time()) + ttl, - 'version': self.version, - 'friendly_name': self.friendly_name, - 'policies': self.policies, - } - - if attributes is not None: - payload.update(attributes) - - return jwt.encode(payload, self.auth_token, 'HS256') - - def make_policy(self, url, method, allowed = True, query_filter = None, post_filter = None): - # Create a policy dictionary for the given resource and method. - - # :param str url: the resource URL to grant or deny access to - # :param str method: the HTTP method to allow or deny - # :param allowed bool: whether this request is allowed - # :param dict query_filter: specific GET parameter names to require or allow - # :param dict post_filter: POST parameter names to require or allow - - return { - 'url': url, - 'method': method, - 'allowed': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {} - } \ No newline at end of file diff --git a/twilio/task_router/capability/task_router_capability.py b/twilio/task_router/capability/task_router_capability.py deleted file mode 100644 index 6758020af6..0000000000 --- a/twilio/task_router/capability/task_router_capability.py +++ /dev/null @@ -1,187 +0,0 @@ -from .capability_api import CapabilityAPI - -import warnings -warnings.simplefilter('always', DeprecationWarning) - -TASK_ROUTER_BASE_URL = 'https://taskrouter.twilio.com' -TASK_ROUTER_BASE_EVENTS_URL = 'https://event-bridge.twilio.com/v1/wschannels' -TASK_ROUTER_VERSION = "v1" - -REQUIRED = {'required': True} -OPTIONAL = {'required': False} - -def deprecated(func): - def log_warning(*args, **kwargs): - # stacklevel = 2 makes the warning refer to the caller of the deprecation rather than the source of deprecation itself - warnings.warn("Call to deprecated function {}.".format(func.__name__), stacklevel = 2, category = DeprecationWarning) - return func(*args, **kwargs) - return log_warning - - -class TaskRouterCapability(CapabilityAPI): - def __init__(self, account_sid, auth_token, workspace_sid, channel_id): - super(TaskRouterCapability, self).__init__(account_sid, auth_token, TASK_ROUTER_VERSION, channel_id) - - self.workspace_sid = workspace_sid - self.channel_id = channel_id - self.base_url = TASK_ROUTER_BASE_URL + "/" + TASK_ROUTER_VERSION + "/Workspaces/" + workspace_sid - - # validate the JWT - self.validate_jwt() - - # set up resources - self.setup_resource() - - # add permissions to GET and POST to the event-bridge channel - self.allow_web_sockets(channel_id) - - # add permissions to fetch the instance resource - self.add_policy(self.resource_url, "GET", True) - - def setup_resource(self): - if self.channel_id[0:2] == "WS": - self.resource_url = self.base_url - elif self.channel_id[0:2] == "WK": - self.resource_url = self.base_url + "/Workers/" + self.channel_id - - activity_url = self.base_url + "/Activities" - self.allow(activity_url, "GET") - - reservations_url = self.base_url + "/Tasks/**" - self.allow(reservations_url, "GET") - - elif self.channel_id[0:2] == "WQ": - self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id - - def allow_web_sockets(self, channel_id): - web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + self.account_sid + "/" + self.channel_id; - self.policies.append(self.make_policy(web_socket_url, "GET", True)) - self.policies.append(self.make_policy(web_socket_url, "POST", True)) - - def validate_jwt(self): - if self.account_sid is None or self.account_sid[0:2] != "AC": - raise ValueError('Invalid AccountSid provided: ' + self.account_sid) - if self.workspace_sid is None or self.workspace_sid[0:2] != "WS": - raise ValueError('Invalid WorkspaceSid provided: ' + self.workspace_sid) - if self.channel_id is None: - raise ValueError('ChannelId not provided') - - prefix = self.channel_id[0:2] - if prefix != "WS" and prefix != "WK" and prefix != "WQ": - raise ValueError('Invalid ChannelId provided: ' + self.channel_id) - - def allow_fetch_subresources(self): - self.allow(self.resource_url + "/**", "GET") - - def allow_updates(self): - self.allow(self.resource_url, "POST") - - def allow_updates_subresources(self): - self.allow(self.resource_url + "/**", "POST") - - def allow_delete(self): - self.allow(self.resource_url, "DELETE") - - def allow_delete_subresources(self): - self.allow(self.resource_url + "/**", "DELETE") - - @deprecated - def allow_worker_fetch_attributes(self): - if self.channel_id[0:2] == "WK": - self.policies.append(self.make_policy( - self.resource_url, - 'GET' - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - @deprecated - def allow_worker_activity_updates(self): - if self.channel_id[0:2] == "WK": - self.policies.append(self.make_policy( - self.resource_url, - 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - - @deprecated - def allow_task_reservation_updates(self): - if self.channel_id[0:2] == "WK": - tasks_url = self.base_url + "/Tasks/**" - self.policies.append(self.make_policy( - tasks_url, - 'POST', - True, - ) - ) - else: - raise ValueError("Deprecated function not applicable to non Worker") - - def get_resource_url(self): - return self.resource_url - - def generate_token(self, ttl = 3600): - task_router_attributes = {} - task_router_attributes["account_sid"] = self.account_sid - task_router_attributes["workspace_sid"] = self.workspace_sid - task_router_attributes["channel"] = self.channel_id - - if self.channel_id[0:2] == "WK": - task_router_attributes["worker_sid"] = self.channel_id - elif self.channel_id[0:2] == "WQ": - task_router_attributes["taskqueue_sid"] = self.channel_id - - return self._generate_token(ttl, task_router_attributes) - -class TaskRouterWorkerCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): - super(TaskRouterWorkerCapability, self).__init__(account_sid, auth_token, workspace_sid, worker_sid) - - self.reservations_url = self.base_url + "/Tasks/**" - self.activity_url = self.base_url + "/Activities" - - # add permissions to fetch the list of activities and list of worker reservations - self.allow(self.reservations_url, "GET") - self.allow(self.activity_url, "GET") - - def setup_resource(self): - self.resource_url = self.base_url + "/Workers/" + self.channel_id - - def allow_activity_updates(self): - self.policies.append(self.make_policy( - self.resource_url, - 'POST', - True, - post_filter = {'ActivitySid': REQUIRED} - ) - ) - def allow_reservation_updates(self): - self.policies.append(self.make_policy( - self.reservations_url, - 'POST', - True - ) - ) - -class TaskRouterTaskQueueCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): - super(TaskRouterTaskQueueCapability, self).__init__(account_sid, auth_token, workspace_sid, taskqueue_sid) - - def setup_resource(self): - self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id - - -class TaskRouterWorkspaceCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid): - super(TaskRouterWorkspaceCapability, self).__init__(account_sid, auth_token, workspace_sid, workspace_sid) - - def setup_resource(self): - self.resource_url = self.base_url - - From c53d711d126013a2e93f2929128a0f358dfda566 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Thu, 23 Jul 2015 14:03:53 -0700 Subject: [PATCH 023/115] Helper libs for workflow construction --- docs/usage/taskrouter.rst | 369 +++++++++++++++++- tests/task_router/test_workflow_config.py | 59 +++ twilio/rest/resources/__init__.py | 4 + twilio/rest/resources/task_router/__init__.py | 16 + .../task_router/taskrouter_config.py | 24 ++ .../resources/task_router/workflow_config.py | 32 ++ .../resources/task_router/workflow_rule.py | 43 ++ .../task_router/workflow_ruletarget.py | 43 ++ 8 files changed, 588 insertions(+), 2 deletions(-) create mode 100644 tests/task_router/test_workflow_config.py create mode 100644 twilio/rest/resources/task_router/taskrouter_config.py create mode 100644 twilio/rest/resources/task_router/workflow_config.py create mode 100644 twilio/rest/resources/task_router/workflow_rule.py create mode 100644 twilio/rest/resources/task_router/workflow_ruletarget.py diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 5915a4744e..76c095a9e9 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -38,6 +38,39 @@ its unique ID. ) print workspace.sid +.. + +The following code will create a update an existing :class:`Workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.update( + WORKSPACE_SID, + friendly_name='Test Workspace', + event_callback_uri="http://www.example.com", + template='FIFO') + +.. +The following code will delete an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workspaces.delete(WORKSPACE_SID) +.. Workflows --------- @@ -87,13 +120,97 @@ unique ID: client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) - workspace = client.workflows(WORKSPACE_SID).create( + workflow = client.workflows(WORKSPACE_SID).create( friendly_name="Incoming Call Flow", assignment_callback_url="https://example.com/callback", fallback_assignment_callback_url="https://example.com/callback2", configuration=CONFIG ) - print workspace.sid + print workflow.sid + +.. + +The following code will update an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + # Some JSON to configure the Workflow. See the documentation at + # http://www.twilio.com/docs/taskrouter for more details. + CONFIG = """ + { + "task_routing":{ + "filters":[ + { + "friendly_name":"Gold Tickets", + "expression":"customer_value == 'Gold' AND type == 'ticket'", + "targets":[ + { + "task_queue_sid":"WQ0123456789abcdef0123456789abcdef", + "priority":"2" + } + ] + }, + { + "targets": [ + { + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f", + "priority": "1" + } + ], + "friendly_name": "Marketing", + "expression": "type == 'marketing'" + } + ], + "default_filter":{ + "task_queue_sid":"WQabcdef01234567890123456789abcdef" + } + } + } + """ + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).update( + WORKFLOW_SID, + friendly_name="Incoming Call Flow", + assignment_callback_url="https://example.com/callback", + fallback_assignment_callback_url="https://example.com/callback2", + configuration=CONFIG + ) + print workflow.sid + +.. + +The following code will delete an existing :class:`Workflow` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + client.workflows(WORKSPACE_SID).delete( + WORKFLOW_SID + ) + + +.. Activities @@ -122,6 +239,49 @@ To create a new :class:`Activity`: available=False, # Whether workers are available to handle tasks during this activity ) +.. + +To update an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).update( + ACTIVITY_SID, + friendly_name="Coffee Break", + available=True, + ) + +.. + +To delete an existing :class:`Activity` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).delete( + ACTIVITY_SID + ) + +.. Workers ------- @@ -153,6 +313,54 @@ To create a new :class:`Worker`: ) print worker.sid +.. + +To update an existing :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).update( + WORKER_SID, + friendly_name="Jamie Howe", + attributes="""{ + "phone": "+14155551234", + "languages": ["EN", "ES","DE"] + } + """ + ) + print worker.sid + +.. + +To delete an exisitng :class:`Worker` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.workers(WORKSPACE_SID).delete( + WORKER_SID + ) + +.. TaskQueues ---------- @@ -186,6 +394,57 @@ To create a new :class:`TaskQueue`: ) print queue.sid +.. + +To update an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).update( + TASKQUEUE_SID, + friendly_name="Sales+Pre-Sales", + # The Activity to assign workers when a task is reserved for them + reservation_activity_sid="WA11111111111", + # The Activity to assign workers when a task is assigned to them + assignment_activity_sid="WA222222222222", + ) + print queue.sid + +.. + +To delete an existing :class:`TaskQueue` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).delete( + TASKQUEUE_SID + ) + print queue.sid + +.. + Tasks ----- @@ -223,3 +482,109 @@ To create a new :class:`Task` via the REST API: workflow_sid=WORKFLOW_SID ) print task.sid +.. + +To update an exisiting :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).update( + TASK_SID, + attributes=TASK_ATTRIBUTES, + assignment_status='pending', + workflow_sid=WORKFLOW_SID + ) + print task.sid +.. + +To delete an exisitng :class:`Task` + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + client.tasks(WORKSPACE_SID).delete( + TASK_SID + ) + +.. + + +Using Workflow builder helper classes to create a :class:`Workflow` resource. + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + rules =[] + ruleTargets=[] + anotherRuleTargets=[] + ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) + anotherRuleTarget= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) + ruleTargets.append(ruleTarget); + anotherRuleTargets.append(anotherRuleTarget); + rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rules.append(rule) + anotherRule = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") + rules.append(anotherRule); + defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) + config = WorkflowConfig(rules,defaultTarget) + print config.toJson() + + workflow = client.workflows(WORKSPACE_SID).create( + friendly_name="Incoming Call Flow", + assignment_callback_url="https://example.com/callback", + fallback_assignment_callback_url="https://example.com/callback2", + configuration=config.toJson() + ) + + print workflow.sid + + + +.. \ No newline at end of file diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py new file mode 100644 index 0000000000..cb2e772e03 --- /dev/null +++ b/tests/task_router/test_workflow_config.py @@ -0,0 +1,59 @@ +import unittest +import json + +from mock import patch, Mock + + + + +from tests.tools import create_mock_json +from twilio.rest.resources.task_router.workflow_config import WorkflowConfig +from twilio.rest.resources.task_router.workflow_rule import WorkflowRule +from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget + +class WorkflowConfigTest(unittest.TestCase): + def test_to_json(self): + rules =[] + ruleTargets=[] + ruleTargets1=[] + ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) + ruleTarget1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) + ruleTargets.append(ruleTarget); + ruleTargets1.append(ruleTarget1); + rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rules.append(rule) + rule1 = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") + rules.append(rule1) + deftarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) + + + config = WorkflowConfig(rules,deftarget) + self.assertEqual(self.is_json(config.toJson()),True) + + + + + def test_from_Json(self): + + data="{\"task_routing\": { \"filters\": [ { \"targets\": [ { \"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Sales\", \"expression\": \"type == \\\"sales\\\"\" }, { \"targets\": [ { \"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Marketing\", \"expression\": \"type == \\\"marketing\\\"\" }, { \"targets\": [ { \"queue\": \"WQe5eb317eb23500ade45087ea6522896c\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Support\", \"expression\": \"type == \\\"support\\\"\" } ], \"default_filter\": { \"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\" } }}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters),3) + self.assertEqual(len(config.task_routing.default_filter),1) + + + + def test_from_json2(self): + data ="{ \"task_routing\": { \"default_filter\": { \"expression\": null, \"priority\": null, \"queue\": \"WQYYYYY\", \"timeout\": null }, \"filters\": [ { \"expression\": \"1==1\", \"friendly_name\": \"SomeQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] }, { \"expression\": \"1==1\", \"friendly_name\": \"SomeOtherQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] } ] }}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters),2) + self.assertEqual(len(config.task_routing.default_filter),4) + + + + def is_json(self,myjson): + try: + json_object = json.loads(myjson) + except ValueError, e: + return False + return True + diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index fa9aef58ec..8a6ecc70c6 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -55,12 +55,16 @@ Reservations, Task, Tasks, + TaskRouterConfig, TaskQueue, TaskQueues, Worker, Workers, Workflow, Workflows, + WorkflowConfig, + WorkflowRule, + WorkflowRuleTarget, Workspace, Workspaces, ) diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index e312f1a882..61d786781e 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -36,3 +36,19 @@ Workspace, Workspaces ) + + +from .taskrouter_config import ( + TaskRouterConfig +) + +from .workflow_config import ( + WorkflowConfig +) + +from .workflow_ruletarget import ( + WorkflowRuleTarget +) +from .workflow_rule import ( + WorkflowRule +) diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py new file mode 100644 index 0000000000..67c12813de --- /dev/null +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -0,0 +1,24 @@ +from .workflow_rule import WorkflowRule +from .workflow_ruletarget import WorkflowRuleTarget +class TaskRouterConfig: + + """ + TaskRouterConfig represents the filter and default_filter + of a workflow configuration of taskrouter + """ + + def __init__(self, rules, defaultTarget): + self.filters = rules + self.default_filter = defaultTarget + + @property + def filters(self): + return self.filters + + @property + def defaultFilter(self): + return self.default_filter + + def __repr__(self): + out = self.__dict__ + return out diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py new file mode 100644 index 0000000000..144f3e36ab --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -0,0 +1,32 @@ +from .taskrouter_config import TaskRouterConfig +import json +from collections import namedtuple, Iterable, OrderedDict +import numpy as np +from twilio.rest.resources.task_router.workflow_rule import WorkflowRule + + +class WorkflowConfig: + + """ + WorkflowConfig represents the whole workflow config json which contains + filters and default_filter. + """ + + def __init__(self, workflowRules, defaultTarget): + #filters and default_filters + self.task_routing = TaskRouterConfig(workflowRules, defaultTarget) + + + @property + def taskrouterConfig(self): + return self.task_routing + + def toJson(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + + + @staticmethod + def json2obj(data): + m=json.loads(data) + return WorkflowConfig(m['task_routing']['filters'],m['task_routing']['default_filter']) diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py new file mode 100644 index 0000000000..fe16c93029 --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -0,0 +1,43 @@ +from .workflow_ruletarget import WorkflowRuleTarget +class WorkflowRule: + """ + WorkflowRule represents the top level filter + which contains a 1 or more targets + + ..attribute::expression + + The expression at the top level filter + + ..attribute::targets + + The list of targets under the filter + + ..attribute::friendlyName + + The name of the filter + """ + _targets = list() + def __init__(self, expression, targets, friendlyName): + + self.expression = expression + self.targets = targets + self.friendly_name = friendlyName + + @property + def expression(self): + return self.expression + + @property + def targets(self): + return self.targets + + @property + def friendlyName(self): + return self.friendly_name + + def __repr__(self): + out = dict() + out['expression'] = self.expression + out['friendlyName'] = self.friendly_name + out['target'] = self.targets + return str(out) diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/rest/resources/task_router/workflow_ruletarget.py new file mode 100644 index 0000000000..dd1da9d13a --- /dev/null +++ b/twilio/rest/resources/task_router/workflow_ruletarget.py @@ -0,0 +1,43 @@ +class WorkflowRuleTarget: + """ + Workflow Rule target which is encompassed + inside targets + + ..attribute::queue + + The queue which will handle the task matching this filter target + + ..attribute::expression + + The dynamic expression if any for this matching + + ..attribute::priority + + The priority for the target + + ..attribute::timeout + + The timeout before the reservation expires. + """ + def __init__(self, queue, expression, priority, timeout): + + self.queue = queue + self.expression = expression + self.priority = priority + self.timeout = timeout + + @property + def queue(self): + return self.queue + + @property + def expression(self): + return self.expression + + @property + def priority(self): + return self.priority + + @property + def timeout(self): + return self.timeout From 15db38a83c78372eb8d1c32956d50cfb919363e9 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 13:45:18 -0700 Subject: [PATCH 024/115] added helper function --- tests/task_router/test_capability.py | 18 +- .../test_task_router_capability.py | 164 +++++------------- 2 files changed, 46 insertions(+), 136 deletions(-) diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index 707fe97f83..eb4d15edba 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -55,21 +55,18 @@ def test_defaults(self): self.assertTrue(decoded is not None) websocket_url = ( - 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % - (self.account_sid, self.worker_sid) + 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) ) expected = [ { - 'url': - 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % - (self.workspace_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format(self.workspace_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -90,8 +87,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % - (self.workspace_sid, self.worker_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format(self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -106,7 +102,7 @@ def test_allow_worker_activity_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( self.workspace_sid, self.worker_sid, ) @@ -126,7 +122,7 @@ def test_allow_worker_fetch_attributes(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( self.workspace_sid, self.worker_sid, ) @@ -147,7 +143,7 @@ def test_allow_task_reservation_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**' % ( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format( self.workspace_sid, ) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 9cbe90c1aa..8c9cdb3131 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,3 +1,6 @@ +import sys +sys.path.append('/Users/wli/Projects/python-private/twilio/') + import unittest import warnings @@ -7,6 +10,14 @@ class TaskRouterCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + print policy + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" @@ -32,29 +43,12 @@ def test_workspace_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]), + ]: + yield self.check_policy, method, url, policy def test_worker_default(self): account_sid = "AC123" @@ -82,45 +76,14 @@ def test_worker_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 5) - # activity GET - fetch_activity = policies[0] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) - self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allow']) - self.assertEqual({}, fetch_activity['query_filter']) - self.assertEqual({}, fetch_activity['post_filter']) - - # reservation GET - fetch_reservation = policies[1] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) - self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allow']) - self.assertEqual({}, fetch_reservation['query_filter']) - self.assertEqual({}, fetch_reservation['post_filter']) - - # websocket GET - get_policy = policies[2] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[3] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[4] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/wschannels/AC123/WK789", policies[2]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ]: + yield self.check_policy, method, url, policy def test_task_queue_default(self): account_sid = "AC123" @@ -148,29 +111,12 @@ def test_task_queue_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WQ789", policies[1]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/TaskQueues/WQ789", policies[2]) + ]: + yield self.check_policy, method, url, policy def test_deprecated_worker(self): account_sid = "AC123" @@ -199,46 +145,14 @@ def test_deprecated_worker(self): self.assertEqual(len(policies), 5) # should expect 5 policies - - # activity GET - fetch_activity = policies[0] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", fetch_activity['url']) - self.assertEqual("GET", fetch_activity['method']) - self.assertTrue(fetch_activity['allow']) - self.assertEqual({}, fetch_activity['query_filter']) - self.assertEqual({}, fetch_activity['post_filter']) - - # reservation GET - fetch_reservation = policies[1] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", fetch_reservation['url']) - self.assertEqual("GET", fetch_reservation['method']) - self.assertTrue(fetch_reservation['allow']) - self.assertEqual({}, fetch_reservation['query_filter']) - self.assertEqual({}, fetch_reservation['post_filter']) - - # websocket GET - get_policy = policies[2] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[3] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[4] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[2]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ]: + yield self.check_policy, method, url, policy # check deprecated warnings with warnings.catch_warnings(record=True) as w: From 3aaae2451de6244158479faabdbb0b56b46b2d52 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 15:48:22 -0700 Subject: [PATCH 025/115] added helper function, formatting --- .../test_task_router_capability.py | 48 ++++------ .../test_task_router_worker_capability.py | 96 +++++++------------ .../test_task_router_workspace_capability.py | 73 +++++++------- twilio/task_router/__init__.py | 58 +++++------ 4 files changed, 118 insertions(+), 157 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 8c9cdb3131..7d1a99c644 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,5 +1,6 @@ import sys -sys.path.append('/Users/wli/Projects/python-private/twilio/') +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') import unittest import warnings @@ -11,13 +12,25 @@ class TaskRouterCapabilityTest(unittest.TestCase): def check_policy(self, method, url, policy): - print policy self.assertEqual(url, policy['url']) self.assertEqual(method, policy['method']) self.assertTrue(policy['allow']) self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) + def test_workspace_default(self): account_sid = "AC123" auth_token = "foobar" @@ -33,12 +46,7 @@ def test_workspace_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["channel"], channel_id) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], channel_id) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -65,13 +73,7 @@ def test_worker_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["worker_sid"], worker_sid) - self.assertEqual(decoded["channel"], worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) @@ -100,13 +102,7 @@ def test_task_queue_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["taskqueue_sid"], taskqueue_sid) - self.assertEqual(decoded["channel"], taskqueue_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], taskqueue_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, taskqueue_sid) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -133,13 +129,7 @@ def test_deprecated_worker(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], account_sid) - self.assertEqual(decoded["account_sid"], account_sid) - self.assertEqual(decoded["workspace_sid"], workspace_sid) - self.assertEqual(decoded["worker_sid"], worker_sid) - self.assertEqual(decoded["channel"], worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 35abb5b831..e2ca7a2cd9 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,3 +1,7 @@ +import sys +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') + import time import unittest @@ -6,6 +10,25 @@ class TaskRouterWorkerCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) def setUp(self): self.account_sid = "AC123" @@ -22,13 +45,7 @@ def test_generate_token(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["worker_sid"], self.worker_sid) - self.assertEqual(decoded["channel"], self.worker_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.worker_sid) + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() @@ -57,56 +74,21 @@ def test_defaults(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/%s/%s' % (self.account_sid, self.worker_sid) + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) # expect 5 policies policies = decoded['policies'] self.assertEqual(len(policies), 5) - # policy 0 - GET websocket - get_policy = policies[0] - self.assertIsNotNone(get_policy) - self.assertEqual(get_policy['url'], websocket_url) - self.assertEqual(get_policy['method'], 'GET') - self.assertTrue(get_policy['allow']) - self.assertEqual(get_policy['query_filter'], {}) - self.assertEqual(get_policy['post_filter'], {}) - - # policy 1 - POST - post_policy = policies[1] - self.assertIsNotNone(post_policy) - self.assertEqual(post_policy['url'], websocket_url) - self.assertEqual(post_policy['method'], 'POST') - self.assertTrue(post_policy['allow']) - self.assertEqual(post_policy['query_filter'], {}) - self.assertEqual(post_policy['post_filter'], {}) - - # policy 2 - Worker fetch - worker_fetch_policy = policies[2] - self.assertIsNotNone(worker_fetch_policy) - self.assertEqual(worker_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789') - self.assertEqual(worker_fetch_policy['method'], 'GET') - self.assertTrue(worker_fetch_policy['allow']) - self.assertEqual(worker_fetch_policy['query_filter'], {}) - self.assertEqual(worker_fetch_policy['post_filter'], {}) - - # policy 3 - Reservation fetch - reservation_fetch_policy = policies[3] - self.assertIsNotNone(reservation_fetch_policy) - self.assertEqual(reservation_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**') - self.assertEqual(reservation_fetch_policy['method'], 'GET') - self.assertTrue(reservation_fetch_policy['allow']) - self.assertEqual(reservation_fetch_policy['query_filter'], {}) - self.assertEqual(reservation_fetch_policy['post_filter'], {}) - - # policy 4 - Activity fetch - activity_fetch_policy = policies[4] - self.assertIsNotNone(activity_fetch_policy) - self.assertEqual(activity_fetch_policy['url'], 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities') - self.assertEqual(activity_fetch_policy['method'], 'GET') - self.assertTrue(activity_fetch_policy['allow']) - self.assertEqual(activity_fetch_policy['query_filter'], {}) - self.assertEqual(activity_fetch_policy['post_filter'], {}) + # should expect 5 policies + for method, url, policy in [ + ('GET', websocket_url, policies[0]), + ('POST', websocket_url, policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[4]) + ]: + yield self.check_policy, method, url, policy def test_allow_activity_updates(self): @@ -123,7 +105,7 @@ def test_allow_activity_updates(self): self.assertEqual(len(policies), 6) policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Workers/%s" % (self.workspace_sid, self.worker_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}".format(self.workspace_sid, self.worker_sid) self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) @@ -147,13 +129,9 @@ def test_allow_reservation_updates(self): policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/%s/Tasks/**" % self.workspace_sid + url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**".format(self.workspace_sid) - self.assertEqual(url, policy["url"]) - self.assertEqual("POST", policy["method"]) - self.assertTrue(policy["allow"]) - self.assertEqual({}, policy["query_filter"]) - self.assertEqual({}, policy['post_filter']) + self.check_policy('POST', url, policy) if __name__ == "__main__": unittest.main() diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index b35e692897..87287dd103 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,3 +1,7 @@ +import sys +sys.path.append('../../') +sys.path.append('/Library/Python/2.7/site-packages') + import time import unittest @@ -6,6 +10,25 @@ class TaskRouterWorkspaceCapabilityTest(unittest.TestCase): + def check_policy(self, method, url, policy): + self.assertEqual(url, policy['url']) + self.assertEqual(method, policy['method']) + self.assertTrue(policy['allow']) + self.assertEqual({}, policy['query_filter']) + self.assertEqual({}, policy['post_filter']) + + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + self.assertEqual(decoded["iss"], account_sid) + self.assertEqual(decoded["account_sid"], account_sid) + self.assertEqual(decoded["workspace_sid"], workspace_sid) + self.assertEqual(decoded["channel"], channel_id) + self.assertEqual(decoded["version"], "v1") + self.assertEqual(decoded["friendly_name"], channel_id) + + if 'worker_sid' in decoded.keys(): + self.assertEqual(decoded['worker_sid'], channel_sid) + if 'taskqueue_sid' in decoded.keys(): + self.assertEqual(decoded['taskqueue_sid'], channel_sid) def setUp(self): self.account_sid = "AC123" @@ -21,12 +44,7 @@ def test_generate_token(self): decoded = jwt.decode(token, self.auth_token) self.assertIsNotNone(decoded) - self.assertEqual(decoded["iss"], self.account_sid) - self.assertEqual(decoded["account_sid"], self.account_sid) - self.assertEqual(decoded["workspace_sid"], self.workspace_sid) - self.assertEqual(decoded["channel"], self.workspace_sid) - self.assertEqual(decoded["version"], "v1") - self.assertEqual(decoded["friendly_name"], self.workspace_sid) + self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() @@ -58,29 +76,12 @@ def test_default(self): policies = decoded['policies'] self.assertEqual(len(policies), 3) - # websocket GET - get_policy = policies[0] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", get_policy['url']) - self.assertEqual("GET", get_policy['method']) - self.assertTrue(get_policy['allow']) - self.assertEqual({}, get_policy['query_filter']) - self.assertEqual({}, get_policy['post_filter']) - - # websocket POST - post_policy = policies[1] - self.assertEqual("https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", post_policy['url']) - self.assertEqual("POST", post_policy['method']) - self.assertTrue(post_policy['allow']) - self.assertEqual({}, post_policy['query_filter']) - self.assertEqual({}, post_policy['post_filter']) - - # fetch GET - fetch_policy = policies[2] - self.assertEqual("https://taskrouter.twilio.com/v1/Workspaces/WS456", fetch_policy['url']) - self.assertEqual("GET", fetch_policy['method']) - self.assertTrue(fetch_policy['allow']) - self.assertEqual({}, fetch_policy['query_filter']) - self.assertEqual({}, fetch_policy['post_filter']) + for method, url, policy in [ + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WS456", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456", policies[2]) + ]: + yield self.check_policy, method, url, policy def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() @@ -95,14 +96,9 @@ def test_allow_fetch_subresources(self): self.assertEqual(len(policies), 4) # confirm the additional policy generated with allow_fetch_subresources() - policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "GET") - self.assertTrue(policy['allow']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.check_policy('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() @@ -117,14 +113,9 @@ def test_allow_updates_subresources(self): self.assertEqual(len(policies), 4) # confirm the additional policy generated with allow_updates_subresources() - policy = policies[3] - self.assertEqual(policy['url'], "https://taskrouter.twilio.com/v1/Workspaces/WS456/**") - self.assertEqual(policy['method'], "POST") - self.assertTrue(policy['allow']) - self.assertEqual({}, policy['query_filter']) - self.assertEqual({}, policy['post_filter']) + self.check_policy('POST', "https://taskrouter.twilio.com/v1/Workspaces/WS456/**", policy) if __name__ == "__main__": unittest.main() diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 58a313d5e2..065816c124 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -11,7 +11,6 @@ REQUIRED = {'required': True} OPTIONAL = {'required': False} - def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the @@ -32,9 +31,7 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = (TASK_ROUTER_BASE_URL + "/" + - TASK_ROUTER_VERSION + - "/Workspaces/" + workspace_sid) + self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, TASK_ROUTER_VERSION, workspace_sid) # validate the JWT self.validate_jwt() @@ -48,10 +45,14 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): # add permissions to fetch the instance resource self.add_policy(self.resource_url, "GET", True) + @property + def channel_prefix(self): + return self.channel_id[0:2] + def setup_resource(self): - if self.channel_id[0:2] == "WS": + if self.channel_prefix == "WS": self.resource_url = self.base_url - elif self.channel_id[0:2] == "WK": + elif self.channel_prefix == "WK": self.resource_url = self.base_url + "/Workers/" + self.channel_id activity_url = self.base_url + "/Activities" @@ -60,7 +61,7 @@ def setup_resource(self): reservations_url = self.base_url + "/Tasks/**" self.allow(reservations_url, "GET") - elif self.channel_id[0:2] == "WQ": + elif self.channel_prefix == "WQ": self.resource_url = self.base_url + \ "/TaskQueues/" + self.channel_id @@ -81,8 +82,7 @@ def validate_jwt(self): if self.channel_id is None: raise ValueError('ChannelId not provided') - prefix = self.channel_id[0:2] - if prefix != "WS" and prefix != "WK" and prefix != "WQ": + if self.channel_prefix != "WS" and self.channel_prefix != "WK" and self.channel_prefix != "WQ": raise ValueError('Invalid ChannelId provided: ' + self.channel_id) def allow_fetch_subresources(self): @@ -102,16 +102,16 @@ def allow_delete_subresources(self): @deprecated def allow_worker_fetch_attributes(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix != "WK": + raise ValueError("Deprecated func not applicable to non Worker") + else: self.policies.append(self.make_policy( self.resource_url, 'GET')) - else: - raise ValueError("Deprecated func not applicable to non Worker") @deprecated def allow_worker_activity_updates(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": self.policies.append(self.make_policy( self.resource_url, 'POST', @@ -122,7 +122,7 @@ def allow_worker_activity_updates(self): @deprecated def allow_task_reservation_updates(self): - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": tasks_url = self.base_url + "/Tasks/**" self.policies.append(self.make_policy( tasks_url, @@ -147,14 +147,15 @@ def deny(self, url, method, query_filter=None, post_filter=None): def make_policy(self, url, method, allowed=True, query_filter=None, post_filter=None): - # Create a policy dictionary for the given resource and method. - # :param str url: the resource URL to grant or deny access to - # :param str method: the HTTP method to allow or deny - # :param allowed bool: whether this request is allowed - # :param dict query_filter: specific GET parameter names - # to require or allow - # :param dict post_filter: POST parameter names - # to require or allow + """Create a policy dictionary for the given resource and method. + :param str url: the resource URL to grant or deny access to + :param str method: the HTTP method to allow or deny + :param allowed bool: whether this request is allowed + :param dict query_filter: specific GET parameter names + to require or allow + :param dict post_filter: POST parameter names + to require or allow + """ return { 'url': url, @@ -168,14 +169,15 @@ def get_resource_url(self): return self.resource_url def generate_token(self, ttl=3600): - task_router_attributes = {} - task_router_attributes["account_sid"] = self.account_sid - task_router_attributes["workspace_sid"] = self.workspace_sid - task_router_attributes["channel"] = self.channel_id + task_router_attributes = { + 'account_sid': self.account_sid, + 'workspace_sid': self.workspace_sid, + 'channel': self.channel_id + } - if self.channel_id[0:2] == "WK": + if self.channel_prefix == "WK": task_router_attributes["worker_sid"] = self.channel_id - elif self.channel_id[0:2] == "WQ": + elif self.channel_prefix == "WQ": task_router_attributes["taskqueue_sid"] = self.channel_id return self._generate_token(ttl, task_router_attributes) From 0255c472182e7c01d732e6e24c14c2f80f6d4682 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:00:24 -0700 Subject: [PATCH 026/115] forgot to remove sys path --- tests/task_router/test_task_router_capability.py | 4 ---- tests/task_router/test_task_router_worker_capability.py | 4 ---- tests/task_router/test_task_router_workspace_capability.py | 4 ---- 3 files changed, 12 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 7d1a99c644..3f3f4f0fb5 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import unittest import warnings diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index e2ca7a2cd9..6fad95e558 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import time import unittest diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 87287dd103..3f4f644389 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -1,7 +1,3 @@ -import sys -sys.path.append('../../') -sys.path.append('/Library/Python/2.7/site-packages') - import time import unittest From c58cb4ae201f2a0c6b14928b4aa0422a16f42514 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:02:25 -0700 Subject: [PATCH 027/115] removed trailing whitespaces --- tests/task_router/test_task_router_capability.py | 4 ++-- tests/task_router/test_task_router_worker_capability.py | 4 ++-- tests/task_router/test_task_router_workspace_capability.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 3f3f4f0fb5..0fa6c45805 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -14,7 +14,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -22,7 +22,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 6fad95e558..4f7578d91b 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -13,7 +13,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -21,7 +21,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 3f4f644389..2767afd59d 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -13,7 +13,7 @@ def check_policy(self, method, url, policy): self.assertEqual({}, policy['query_filter']) self.assertEqual({}, policy['post_filter']) - def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): + def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel_sid=None): self.assertEqual(decoded["iss"], account_sid) self.assertEqual(decoded["account_sid"], account_sid) self.assertEqual(decoded["workspace_sid"], workspace_sid) @@ -21,7 +21,7 @@ def check_decoded(self, decoded, account_sid, workspace_sid, channel_id, channel self.assertEqual(decoded["version"], "v1") self.assertEqual(decoded["friendly_name"], channel_id) - if 'worker_sid' in decoded.keys(): + if 'worker_sid' in decoded.keys(): self.assertEqual(decoded['worker_sid'], channel_sid) if 'taskqueue_sid' in decoded.keys(): self.assertEqual(decoded['taskqueue_sid'], channel_sid) From ef8d9b9621262cb9212d10c6dfa92ba7d55746fc Mon Sep 17 00:00:00 2001 From: Jen Li Date: Tue, 28 Jul 2015 16:23:37 -0700 Subject: [PATCH 028/115] removed unnecessary constructors and added .format() --- twilio/task_router/__init__.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 065816c124..4224884117 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -62,12 +62,10 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = self.base_url + \ - "/TaskQueues/" + self.channel_id + self.resource_url = "{}/TaskQueues/{}".format(self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = TASK_ROUTER_BASE_EVENTS_URL + "/" + \ - self.account_sid + "/" + self.channel_id + web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, self.account_sid, self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) @@ -230,22 +228,10 @@ def allow_reservation_updates(self): class TaskRouterTaskQueueCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid, taskqueue_sid): - super(TaskRouterTaskQueueCapability, self).__init__(account_sid, - auth_token, - workspace_sid, - taskqueue_sid) - def setup_resource(self): self.resource_url = self.base_url + "/TaskQueues/" + self.channel_id class TaskRouterWorkspaceCapability(TaskRouterCapability): - def __init__(self, account_sid, auth_token, workspace_sid): - super(TaskRouterWorkspaceCapability, self).__init__(account_sid, - auth_token, - workspace_sid, - workspace_sid) - def setup_resource(self): self.resource_url = self.base_url From 7435b95610eda19b0205c01ca1f1830611f3c268 Mon Sep 17 00:00:00 2001 From: Jen Li Date: Wed, 29 Jul 2015 17:53:02 -0700 Subject: [PATCH 029/115] added back constructor for TaskRouterWorkspaceCapability and fixed 80 chars --- .../test_task_router_capability.py | 6 +++--- twilio/task_router/__init__.py | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 0fa6c45805..a32768bfbf 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -69,7 +69,7 @@ def test_worker_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) @@ -98,7 +98,7 @@ def test_task_queue_default(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, taskqueue_sid) + self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -125,7 +125,7 @@ def test_deprecated_worker(self): decoded = jwt.decode(token, auth_token) self.assertIsNotNone(decoded) - self.check_decoded(decoded, account_sid, workspace_sid, channel_id, worker_sid) + self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] self.assertEqual(len(policies), 5) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 4224884117..91dca999a6 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -11,6 +11,7 @@ REQUIRED = {'required': True} OPTIONAL = {'required': False} + def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the @@ -31,7 +32,9 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, TASK_ROUTER_VERSION, workspace_sid) + self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, + TASK_ROUTER_VERSION, + workspace_sid) # validate the JWT self.validate_jwt() @@ -62,10 +65,12 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = "{}/TaskQueues/{}".format(self.base_url, self.channel_id) + self.resource_url = "{}/TaskQueues/{}".format( + self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, self.account_sid, self.channel_id) + web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, + self.account_sid, self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) @@ -80,7 +85,8 @@ def validate_jwt(self): if self.channel_id is None: raise ValueError('ChannelId not provided') - if self.channel_prefix != "WS" and self.channel_prefix != "WK" and self.channel_prefix != "WQ": + if self.channel_prefix != "WS" and self.channel_prefix != "WK" \ + and self.channel_prefix != "WQ": raise ValueError('Invalid ChannelId provided: ' + self.channel_id) def allow_fetch_subresources(self): @@ -233,5 +239,11 @@ def setup_resource(self): class TaskRouterWorkspaceCapability(TaskRouterCapability): + def __init__(self, account_sid, auth_token, workspace_sid): + super(TaskRouterWorkspaceCapability, self).__init__(account_sid, + auth_token, + workspace_sid, + workspace_sid) + def setup_resource(self): self.resource_url = self.base_url From 05d350e468d441ef86176f1c0e6c08734e70b0a0 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:11:37 -0700 Subject: [PATCH 030/115] Incorporated review comments --- docs/usage/taskrouter.rst | 266 +++++++++++++++++- tests/task_router/test_workflow_config.py | 97 ++++--- .../task_router/taskrouter_config.py | 27 +- .../resources/task_router/workflow_config.py | 18 +- .../resources/task_router/workflow_rule.py | 18 +- 5 files changed, 337 insertions(+), 89 deletions(-) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 76c095a9e9..a903aaae36 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -40,6 +40,36 @@ its unique ID. .. +The following code will get an instance of an existing :class:`workspace` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workspace = client.workspaces.get(WORKSPACE_SID) + print workspace.friendly_name +.. + +The following code will get the list of all existing :class:`workspace` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for workspace in client.workspaces.list() + print workspace.friendly_name +.. + The following code will create a update an existing :class:`Workspace` resource .. code-block:: python @@ -72,6 +102,8 @@ The following code will delete an existing :class:`workspace` resource client.workspaces.delete(WORKSPACE_SID) .. + + Workflows --------- @@ -130,6 +162,48 @@ unique ID: .. +The following code will get a instance of an existing :class:`workflow` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + workflow = client.workflows(WORKSPACE_SID).get(WORKFLOW_SID) + print workflow.friendly_name + +.. + + + +The following code will get a list of all existing :class:`workflow` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for workflow in client.workflows(WORKSPACE_SID).list() + print workflow.friendly_name + +.. + The following code will update an existing :class:`workflow` resource .. code-block:: python @@ -241,6 +315,44 @@ To create a new :class:`Activity`: .. +To get an existing :class:`activity` resource + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + activity = client.activities(WORKSPACE_SID).get(ACTIVITY_SID) + print activity.friendly_name + +.. + +To get a list of existing :class:`activity` resources + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for activity in client.activities(WORKSPACE_SID).list() + print activity.friendly_name + +.. + To update an existing :class:`Activity` .. code-block:: python @@ -315,6 +427,44 @@ To create a new :class:`Worker`: .. +To get an existing :class:`worker` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + worker = client.workers(WORKSPACE_SID).get(WORKER_SID) + print worker_friendly_name; +.. + + +To get an existing :class:`worker` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for worker in client.workers(WORKSPACE_SID).list() + print worker_friendly_name; +.. + + To update an existing :class:`Worker` .. code-block:: python @@ -342,7 +492,7 @@ To update an existing :class:`Worker` .. -To delete an exisitng :class:`Worker` +To delete an existing :class:`Worker` .. code-block:: python @@ -396,6 +546,49 @@ To create a new :class:`TaskQueue`: .. +To get an existing :class`TaskQueue` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + queue = client.task_queues(WORKSPACE_SID).get(TASKQUEUE_SID) + print queue.sid + +.. + + + +To get an existing :class`TaskQueue` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + + for queue in client.task_queues(WORKSPACE_SID).list() + print queue.sid + +.. + + To update an existing :class:`TaskQueue` .. code-block:: python @@ -484,7 +677,64 @@ To create a new :class:`Task` via the REST API: print task.sid .. -To update an exisiting :class:`Task` +To get an existing :class:`Task` instance + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + task = client.tasks(WORKSPACE_SID).delete(TASK_SID) + print task.attributes +.. + + +To get an existing :class:`Task` list + +.. code-block:: python + + from twilio.rest import TwilioTaskRouterClient + + # To find these visit https://www.twilio.com/user/account + ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" + AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" + + # See previous examples to create a Workspace + WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" + WORKFLOW_SID = "WWXXXXXXXXXXXXXX" + # Some JSON containing attributes for this task. User-defined. + TASK_ATTRIBUTES = """{ + "type": "call", + "contact": "+2014068777", + "customer-value": "gold", + "task-reason": "support", + "callSid": "CA42ed11..." + }""" + + + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + for task in client.tasks(WORKSPACE_SID).list() + print task.attributes +.. + +To update an existing :class:`Task` .. code-block:: python @@ -517,7 +767,7 @@ To update an exisiting :class:`Task` print task.sid .. -To delete an exisitng :class:`Task` +To delete an existing :class:`Task` .. code-block:: python @@ -574,13 +824,13 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. rules.append(anotherRule); defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) config = WorkflowConfig(rules,defaultTarget) - print config.toJson() + print config.to_json() workflow = client.workflows(WORKSPACE_SID).create( - friendly_name="Incoming Call Flow", - assignment_callback_url="https://example.com/callback", - fallback_assignment_callback_url="https://example.com/callback2", - configuration=config.toJson() + friendly_name= "Incoming Call Flow", + assignment_callback_url= "https://example.com/callback", + fallback_assignment_callback_url= "https://example.com/callback2", + configuration= config.to_json() ) print workflow.sid diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index cb2e772e03..d191560e26 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -1,59 +1,56 @@ import unittest import json -from mock import patch, Mock - - - -from tests.tools import create_mock_json from twilio.rest.resources.task_router.workflow_config import WorkflowConfig from twilio.rest.resources.task_router.workflow_rule import WorkflowRule from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget -class WorkflowConfigTest(unittest.TestCase): - def test_to_json(self): - rules =[] - ruleTargets=[] - ruleTargets1=[] - ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) - ruleTarget1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) - ruleTargets.append(ruleTarget); - ruleTargets1.append(ruleTarget1); - rule = WorkflowRule("1==1",ruleTargets,"SomeQ") - rules.append(rule) - rule1 = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") - rules.append(rule1) - deftarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) - - - config = WorkflowConfig(rules,deftarget) - self.assertEqual(self.is_json(config.toJson()),True) - - - - - def test_from_Json(self): - - data="{\"task_routing\": { \"filters\": [ { \"targets\": [ { \"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Sales\", \"expression\": \"type == \\\"sales\\\"\" }, { \"targets\": [ { \"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Marketing\", \"expression\": \"type == \\\"marketing\\\"\" }, { \"targets\": [ { \"queue\": \"WQe5eb317eb23500ade45087ea6522896c\", \"expression\": \"task.language IN worker.languages\" } ], \"friendly_name\": \"Support\", \"expression\": \"type == \\\"support\\\"\" } ], \"default_filter\": { \"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\" } }}" - config = WorkflowConfig.json2obj(data) - self.assertEqual(len(config.task_routing.filters),3) - self.assertEqual(len(config.task_routing.default_filter),1) - - - - def test_from_json2(self): - data ="{ \"task_routing\": { \"default_filter\": { \"expression\": null, \"priority\": null, \"queue\": \"WQYYYYY\", \"timeout\": null }, \"filters\": [ { \"expression\": \"1==1\", \"friendly_name\": \"SomeQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] }, { \"expression\": \"1==1\", \"friendly_name\": \"SomeOtherQ\", \"targets\": [ { \"expression\": \"1==1\", \"priority\": 1, \"queue\": \"WQXXXX\", \"timeout\": 20 } ] } ] }}" - config = WorkflowConfig.json2obj(data) - self.assertEqual(len(config.task_routing.filters),2) - self.assertEqual(len(config.task_routing.default_filter),4) - - - - def is_json(self,myjson): - try: - json_object = json.loads(myjson) - except ValueError, e: - return False - return True +class WorkflowConfigTest(unittest.TestCase): + def test_to_json(self): + rules = [] + rule_targets= [] + rule_targets1= [] + rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) + rule_target1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) + rule_targets.append(rule_target); + rule_targets1.append(rule_target1); + rule = WorkflowRule("1==1", rule_targets, "SomeQ") + rules.append(rule) + rule1 = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") + rules.append(rule1) + def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules , def_target) + self.assertEqual(self.is_json(config.to_json()), True) + + def test_from_json(self): + + data = "{\"task_routing\": {\"filters\": [{\"targets\": [{\"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Sales\",\"expression\": " \ + "\"type == \\\"sales\\\"\"},{\"targets\": [{\"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Marketing\",\"expression\":" \ + " \"type == \\\"marketing\\\"\"},{\"targets\": [{\"queue\": \"WQe5eb317eb23500ade45087ea6522896c\"," \ + "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Support\"," \ + "\"expression\": \"type == \\\"support\\\"\"}],\"default_filter\": " \ + "{\"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\"}}}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters), 3) + self.assertEqual(len(config.task_routing.default_filter), 1) + + def test_from_json2(self): + data = "{\"task_routing\": {\"default_filter\": {\"expression\": null,\"priority\": null,\"queue\": \"WQYYYYY\"," \ + "\"timeout\": null },\"filters\": [{\"expression\": \"1==1\",\"friendly_name\": \"SomeQ\",\"targets\": [" \ + "{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\",\"timeout\": 20}]},{\"expression\": \"1==1\"," \ + "\"friendly_name\": \"SomeOtherQ\",\"targets\": [{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\"," \ + "\"timeout\": 20}]}]}}" + config = WorkflowConfig.json2obj(data) + self.assertEqual(len(config.task_routing.filters), 2) + self.assertEqual(len(config.task_routing.default_filter), 4) + + def is_json(self, myjson): + try: + json.loads(myjson) + except ValueError, e: + return False + return True diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py index 67c12813de..36593cfc6e 100644 --- a/twilio/rest/resources/task_router/taskrouter_config.py +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -1,24 +1,25 @@ from .workflow_rule import WorkflowRule from .workflow_ruletarget import WorkflowRuleTarget + + class TaskRouterConfig: - """ - TaskRouterConfig represents the filter and default_filter - of a workflow configuration of taskrouter - """ + """ + TaskRouterConfig represents the filter and default_filter + of a workflow configuration of taskrouter + """ - def __init__(self, rules, defaultTarget): + def __init__(self, rules, default_target): self.filters = rules - self.default_filter = defaultTarget + self.default_filter = default_target - @property - def filters(self): + @property + def filters(self): return self.filters - @property - def defaultFilter(self): + @property + def default_filter(self): return self.default_filter - def __repr__(self): - out = self.__dict__ - return out + def __repr__(self): + return self.__dict__ diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index 144f3e36ab..b61930ad48 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -12,21 +12,19 @@ class WorkflowConfig: filters and default_filter. """ - def __init__(self, workflowRules, defaultTarget): - #filters and default_filters - self.task_routing = TaskRouterConfig(workflowRules, defaultTarget) + def __init__(self, workflow_rules, default_target): + # filters and default_filters + self.task_routing = TaskRouterConfig(workflow_rules, default_target) @property - def taskrouterConfig(self): + def taskrouter_config(self): return self.task_routing - def toJson(self): - return json.dumps(self, default=lambda o: o.__dict__, - sort_keys=True, indent=4) - + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__,sort_keys=True, indent=4) @staticmethod def json2obj(data): - m=json.loads(data) - return WorkflowConfig(m['task_routing']['filters'],m['task_routing']['default_filter']) + m = json.loads(data) + return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index fe16c93029..aa4650ef29 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -17,11 +17,12 @@ class WorkflowRule: The name of the filter """ _targets = list() - def __init__(self, expression, targets, friendlyName): + + def __init__(self, expression, targets, friendly_name): self.expression = expression self.targets = targets - self.friendly_name = friendlyName + self.friendly_name = friendly_name @property def expression(self): @@ -32,12 +33,13 @@ def targets(self): return self.targets @property - def friendlyName(self): + def friendly_name(self): return self.friendly_name def __repr__(self): - out = dict() - out['expression'] = self.expression - out['friendlyName'] = self.friendly_name - out['target'] = self.targets - return str(out) + return str({ + 'expression': self.expression, + 'friendly_name': self.friendly_name, + 'target': self.target, + }) + From abdb4105a6f442aa9a869e34481e26e346693116 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:17:07 -0700 Subject: [PATCH 031/115] Removing unwanted properties --- .../resources/task_router/workflow_ruletarget.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/rest/resources/task_router/workflow_ruletarget.py index dd1da9d13a..987ac0f911 100644 --- a/twilio/rest/resources/task_router/workflow_ruletarget.py +++ b/twilio/rest/resources/task_router/workflow_ruletarget.py @@ -26,18 +26,3 @@ def __init__(self, queue, expression, priority, timeout): self.priority = priority self.timeout = timeout - @property - def queue(self): - return self.queue - - @property - def expression(self): - return self.expression - - @property - def priority(self): - return self.priority - - @property - def timeout(self): - return self.timeout From e0f4295e440066415dde31a5d4b53e6502de4105 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 15:19:35 -0700 Subject: [PATCH 032/115] Formatting --- twilio/rest/resources/task_router/workflow_config.py | 4 ---- twilio/rest/resources/task_router/workflow_rule.py | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index b61930ad48..a51e045c6d 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -1,8 +1,5 @@ from .taskrouter_config import TaskRouterConfig import json -from collections import namedtuple, Iterable, OrderedDict -import numpy as np -from twilio.rest.resources.task_router.workflow_rule import WorkflowRule class WorkflowConfig: @@ -16,7 +13,6 @@ def __init__(self, workflow_rules, default_target): # filters and default_filters self.task_routing = TaskRouterConfig(workflow_rules, default_target) - @property def taskrouter_config(self): return self.task_routing diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index aa4650ef29..2df6fb77c9 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -1,5 +1,8 @@ from .workflow_ruletarget import WorkflowRuleTarget + + class WorkflowRule: + """ WorkflowRule represents the top level filter which contains a 1 or more targets From 3dea61ab97d6fed2b333f4e922c3e3c4c85b6998 Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Tue, 4 Aug 2015 16:28:34 -0700 Subject: [PATCH 033/115] Formatting and removing properties on all classes. --- docs/usage/taskrouter.rst | 22 +++++++++---------- .../task_router/taskrouter_config.py | 8 ------- .../resources/task_router/workflow_config.py | 6 +---- .../resources/task_router/workflow_rule.py | 13 ----------- 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index a903aaae36..05566a7719 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -812,18 +812,18 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" rules =[] - ruleTargets=[] - anotherRuleTargets=[] - ruleTarget = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7","1==1",1,20) - anotherRuleTarget= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da","1==1",1,210) - ruleTargets.append(ruleTarget); - anotherRuleTargets.append(anotherRuleTarget); - rule = WorkflowRule("1==1",ruleTargets,"SomeQ") + rule_targets=[] + another_rule_targets=[] + rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) + another_rule_target= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) + rule_targets.append(rule_target); + another_rule_targets.append(another_rule_target); + rule = WorkflowRule("1==1", rule_targets, "SomeQ") rules.append(rule) - anotherRule = WorkflowRule("1==1",ruleTargets1,"SomeOtherQ") - rules.append(anotherRule); - defaultTarget = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916","1==1",None,None) - config = WorkflowConfig(rules,defaultTarget) + another_rule = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") + rules.append(another_rule); + default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) + config = WorkflowConfig(rules, default_target) print config.to_json() workflow = client.workflows(WORKSPACE_SID).create( diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/rest/resources/task_router/taskrouter_config.py index 36593cfc6e..b4e8eb7b55 100644 --- a/twilio/rest/resources/task_router/taskrouter_config.py +++ b/twilio/rest/resources/task_router/taskrouter_config.py @@ -13,13 +13,5 @@ def __init__(self, rules, default_target): self.filters = rules self.default_filter = default_target - @property - def filters(self): - return self.filters - - @property - def default_filter(self): - return self.default_filter - def __repr__(self): return self.__dict__ diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/rest/resources/task_router/workflow_config.py index a51e045c6d..e79881b3f7 100644 --- a/twilio/rest/resources/task_router/workflow_config.py +++ b/twilio/rest/resources/task_router/workflow_config.py @@ -13,12 +13,8 @@ def __init__(self, workflow_rules, default_target): # filters and default_filters self.task_routing = TaskRouterConfig(workflow_rules, default_target) - @property - def taskrouter_config(self): - return self.task_routing - def to_json(self): - return json.dumps(self, default=lambda o: o.__dict__,sort_keys=True, indent=4) + return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) @staticmethod def json2obj(data): diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/rest/resources/task_router/workflow_rule.py index 2df6fb77c9..92a74b5bb9 100644 --- a/twilio/rest/resources/task_router/workflow_rule.py +++ b/twilio/rest/resources/task_router/workflow_rule.py @@ -19,7 +19,6 @@ class WorkflowRule: The name of the filter """ - _targets = list() def __init__(self, expression, targets, friendly_name): @@ -27,18 +26,6 @@ def __init__(self, expression, targets, friendly_name): self.targets = targets self.friendly_name = friendly_name - @property - def expression(self): - return self.expression - - @property - def targets(self): - return self.targets - - @property - def friendly_name(self): - return self.friendly_name - def __repr__(self): return str({ 'expression': self.expression, From 92a549b47322fc6e982d4efc8bb192bc9a92e35e Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 5 Aug 2015 13:37:50 -0700 Subject: [PATCH 034/115] Add missing Enqueue->Task Twiml generation --- tests/test_twiml.py | 67 +++++++++++++++++++++++++++++++++------------ twilio/twiml.py | 15 ++++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index 7bab2ad1e9..781ec1eaf9 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -367,16 +367,16 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find(".//Queue") + self.queue = tree.find(".//Queue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestQueueAttribute") + def test_queue_text(self): + self.assertEqual(self.queue.text.strip(), "TestQueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('url'), "") + def test_queue_waiturl(self): + self.assertEqual(self.queue.get('url'), "") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_queue_method(self): + self.assertEqual(self.queue.get('method'), "GET") class TestEnqueue(TwilioTest): @@ -390,22 +390,53 @@ def setUp(self): # parse twiml XML string with Element Tree and inspect # structure tree = ET.fromstring(xml) - self.conf = tree.find("./Enqueue") + self.enqueue = tree.find("./Enqueue") - def test_conf_text(self): - self.assertEqual(self.conf.text.strip(), "TestEnqueueAttribute") + def test_enqueue_text(self): + self.assertEqual(self.enqueue.text.strip(), "TestEnqueueAttribute") - def test_conf_waiturl(self): - self.assertEqual(self.conf.get('waitUrl'), "wait") + def test_enqueue_waiturl(self): + self.assertEqual(self.enqueue.get('waitUrl'), "wait") + + def test_enqueue_method(self): + self.assertEqual(self.enqueue.get('method'), "GET") + + def test_enqueue_action(self): + self.assertEqual(self.enqueue.get('action'), "act") + + def test_enqueue_waitmethod(self): + self.assertEqual(self.enqueue.get('waitUrlMethod'), "POST") + + +class TestEnqueueTask(TwilioTest): + + def setUp(self): + r = Response() + with r.enqueue(None, workflowSid="Workflow1") as e: + e.task('{"selected_language":"en"}', priority="10", timeout="50") + + xml = r.toxml() + + # parse twiml XML string with Element Tree and inspect + # structure + tree = ET.fromstring(xml) + self.enqueue = tree.find("./Enqueue") + self.task = self.enqueue.find("./Task") + + def test_found_task(self): + self.assertIsNotNone(self.task) + + def test_enqueue_workflow_sid(self): + self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1") - def test_conf_method(self): - self.assertEqual(self.conf.get('method'), "GET") + def test_enqueue_task_attributes(self): + self.assertEqual(self.task.text.strip(), '{"selected_language":"en"}') - def test_conf_action(self): - self.assertEqual(self.conf.get('action'), "act") + def test_enqueue_task_priority(self): + self.assertEqual(self.task.get('priority'), "10") - def test_conf_waitmethod(self): - self.assertEqual(self.conf.get('waitUrlMethod'), "POST") + def test_enqueue_task_timeout(self): + self.assertEqual(self.task.get('timeout'), "50") class TestDial(TwilioTest): diff --git a/twilio/twiml.py b/twilio/twiml.py index f05015842d..cbac1122da 100644 --- a/twilio/twiml.py +++ b/twilio/twiml.py @@ -524,10 +524,25 @@ class Enqueue(Verb): GET = 'GET' POST = 'POST' + nestables = ['Task'] + def __init__(self, name, **kwargs): super(Enqueue, self).__init__(**kwargs) self.body = name + def task(self, attributes, **kwargs): + return self.append(Task(attributes, **kwargs)) + + +class Task(Verb): + """Specify the task attributes when enqueuing a call + + :param attributes: Attributes for a task + """ + def __init__(self, attributes, **kwargs): + super(Task, self).__init__(**kwargs) + self.body = attributes + class Leave(Verb): """Signals the call to leave its queue From f0bd64f90b51e866e27bb52c784060af9681bb2b Mon Sep 17 00:00:00 2001 From: gramanathaiah Date: Thu, 6 Aug 2015 14:58:31 -0700 Subject: [PATCH 035/115] Change in package structure and formatting of json to dicts/lists --- docs/usage/taskrouter.rst | 17 ++--- tests/task_router/test_workflow_config.py | 66 ++++++++++--------- twilio/rest/resources/__init__.py | 4 -- twilio/rest/resources/task_router/__init__.py | 14 ---- twilio/task_router/__init__.py | 15 +++++ .../task_router/taskrouter_config.py | 0 .../task_router/workflow_config.py | 0 .../task_router/workflow_rule.py | 0 .../task_router/workflow_ruletarget.py | 0 9 files changed, 57 insertions(+), 59 deletions(-) rename twilio/{rest/resources => }/task_router/taskrouter_config.py (100%) rename twilio/{rest/resources => }/task_router/workflow_config.py (100%) rename twilio/{rest/resources => }/task_router/workflow_rule.py (100%) rename twilio/{rest/resources => }/task_router/workflow_ruletarget.py (100%) diff --git a/docs/usage/taskrouter.rst b/docs/usage/taskrouter.rst index 05566a7719..76de468ed3 100644 --- a/docs/usage/taskrouter.rst +++ b/docs/usage/taskrouter.rst @@ -811,21 +811,16 @@ Using Workflow builder helper classes to create a :class:`Workflow` resource. # See previous examples to create a Workspace WORKSPACE_SID = "WSZZZZZZZZZZZZZZ" - rules =[] - rule_targets=[] - another_rule_targets=[] - rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) - another_rule_target= WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) - rule_targets.append(rule_target); - another_rule_targets.append(another_rule_target); - rule = WorkflowRule("1==1", rule_targets, "SomeQ") - rules.append(rule) - another_rule = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") - rules.append(another_rule); + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] default_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules, default_target) print config.to_json() + client = TwilioTaskRouterClient(ACCOUNT_SID, AUTH_TOKEN) + workflow = client.workflows(WORKSPACE_SID).create( friendly_name= "Incoming Call Flow", assignment_callback_url= "https://example.com/callback", diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index d191560e26..d9d70a3dd2 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -1,50 +1,56 @@ import unittest import json - -from twilio.rest.resources.task_router.workflow_config import WorkflowConfig -from twilio.rest.resources.task_router.workflow_rule import WorkflowRule -from twilio.rest.resources.task_router.workflow_ruletarget import WorkflowRuleTarget +from twilio.task_router.workflow_config import WorkflowConfig +from twilio.task_router.workflow_rule import WorkflowRule +from twilio.task_router.workflow_ruletarget import WorkflowRuleTarget class WorkflowConfigTest(unittest.TestCase): def test_to_json(self): - rules = [] - rule_targets= [] - rule_targets1= [] - rule_target = WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20) - rule_target1 = WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210) - rule_targets.append(rule_target); - rule_targets1.append(rule_target1); - rule = WorkflowRule("1==1", rule_targets, "SomeQ") - rules.append(rule) - rule1 = WorkflowRule("1==1", rule_targets1, "SomeOtherQ") - rules.append(rule1) + rules = [ + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules , def_target) self.assertEqual(self.is_json(config.to_json()), True) def test_from_json(self): - data = "{\"task_routing\": {\"filters\": [{\"targets\": [{\"queue\": \"WQec62de0e1148b8477f2e24579779c8b1\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Sales\",\"expression\": " \ - "\"type == \\\"sales\\\"\"},{\"targets\": [{\"queue\": \"WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Marketing\",\"expression\":" \ - " \"type == \\\"marketing\\\"\"},{\"targets\": [{\"queue\": \"WQe5eb317eb23500ade45087ea6522896c\"," \ - "\"expression\": \"task.language IN worker.languages\"}],\"friendly_name\": \"Support\"," \ - "\"expression\": \"type == \\\"support\\\"\"}],\"default_filter\": " \ - "{\"queue\": \"WQ05f810d2d130344fd56e3c91ece2e594\"}}}" - config = WorkflowConfig.json2obj(data) + data = {'task_routing': + {'default_filter': + {'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'}, 'filters': [ + {'expression': 'type == "sales"', 'friendly_name': 'Sales', 'targets': [ + {'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages'}]}, + {'expression': 'type == "marketing"', 'friendly_name': 'Marketing', 'targets': + [{'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages'}] + }, + {'expression': 'type == "support"', 'friendly_name': 'Support', 'targets': + [{'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages'}] + } + ] + } + } + + config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(len(config.task_routing.filters), 3) self.assertEqual(len(config.task_routing.default_filter), 1) def test_from_json2(self): - data = "{\"task_routing\": {\"default_filter\": {\"expression\": null,\"priority\": null,\"queue\": \"WQYYYYY\"," \ - "\"timeout\": null },\"filters\": [{\"expression\": \"1==1\",\"friendly_name\": \"SomeQ\",\"targets\": [" \ - "{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\",\"timeout\": 20}]},{\"expression\": \"1==1\"," \ - "\"friendly_name\": \"SomeOtherQ\",\"targets\": [{\"expression\": \"1==1\",\"priority\": 1,\"queue\": \"WQXXXX\"," \ - "\"timeout\": 20}]}]}}" - config = WorkflowConfig.json2obj(data) + + data = {'task_routing': {'filters': [{'friendly_name': 'SomeQ', 'expression': '1==1', 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', 'timeout': 20}]}, + {'friendly_name': 'SomeOtherQ', 'expression': '1==1', + 'targets': [ + {'priority': 1, 'queue': 'WQXXXX', 'expression': '1==1', + 'timeout': 20}]}], + 'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None, + 'timeout': None}}} + config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(len(config.task_routing.filters), 2) self.assertEqual(len(config.task_routing.default_filter), 4) diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index 8a6ecc70c6..fa9aef58ec 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -55,16 +55,12 @@ Reservations, Task, Tasks, - TaskRouterConfig, TaskQueue, TaskQueues, Worker, Workers, Workflow, Workflows, - WorkflowConfig, - WorkflowRule, - WorkflowRuleTarget, Workspace, Workspaces, ) diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index 61d786781e..5ca394258a 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -38,17 +38,3 @@ ) -from .taskrouter_config import ( - TaskRouterConfig -) - -from .workflow_config import ( - WorkflowConfig -) - -from .workflow_ruletarget import ( - WorkflowRuleTarget -) -from .workflow_rule import ( - WorkflowRule -) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 17975c1cba..bc05e83f75 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -142,3 +142,18 @@ def make_policy(url, method, query_filter=None, post_filter=None, 'query_filter': query_filter or {}, 'post_filter': post_filter or {}, } + +from .taskrouter_config import ( + TaskRouterConfig +) + +from .workflow_config import ( + WorkflowConfig +) + +from .workflow_ruletarget import ( + WorkflowRuleTarget +) +from .workflow_rule import ( + WorkflowRule +) diff --git a/twilio/rest/resources/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py similarity index 100% rename from twilio/rest/resources/task_router/taskrouter_config.py rename to twilio/task_router/taskrouter_config.py diff --git a/twilio/rest/resources/task_router/workflow_config.py b/twilio/task_router/workflow_config.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_config.py rename to twilio/task_router/workflow_config.py diff --git a/twilio/rest/resources/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_rule.py rename to twilio/task_router/workflow_rule.py diff --git a/twilio/rest/resources/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py similarity index 100% rename from twilio/rest/resources/task_router/workflow_ruletarget.py rename to twilio/task_router/workflow_ruletarget.py From 2b691507d0d631fa3ad1416625a03a81ccac3567 Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 11 Aug 2015 10:14:10 -0700 Subject: [PATCH 036/115] Bumping version to 4.5.0 --- CHANGES.md | 10 ++++++++++ twilio/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 11b8e7dd71..edb4ea582a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,16 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.5.0 +------------- + +Released August 11, 2015: + +- Add support for new Taskrouter JWT Functionality, JWTs now grant access to + - Workspace + - Worker + - TaskQueue + Version 4.4.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 953ebe5125..a14cd79c93 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '4', '0') +__version_info__ = ('4', '5', '0') __version__ = '.'.join(__version_info__) From bb9487f6fcfc1de7e4f728d6831e7a8b19ee632c Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 26 Aug 2015 16:09:10 -0700 Subject: [PATCH 037/115] Allow fetching reservations by worker --- tests/task_router/test_reservations.py | 13 +++++++++++++ twilio/rest/task_router.py | 15 ++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/task_router/test_reservations.py b/tests/task_router/test_reservations.py index ff85ffa316..5d635dad9a 100644 --- a/tests/task_router/test_reservations.py +++ b/tests/task_router/test_reservations.py @@ -8,6 +8,7 @@ AUTH = ("AC123", "token") BASE_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Tasks/WTaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +BASE_WORKER_URI = "https://taskrouter.twilio.com/v1/Accounts/AC123/Workspaces/WSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Workers/WKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" RESERVATION_SID = "WRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" @@ -36,6 +37,18 @@ def test_list(self, request): request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') + def test_list_for_worker(self, request): + resp = create_mock_json('tests/resources/task_router/reservations_list.json') + resp.status_code = 200 + request.return_value = resp + + uri = "{0}/Reservations".format(BASE_WORKER_URI) + list_resource = Reservations(BASE_WORKER_URI, AUTH) + list_resource.list() + request.assert_called_with("GET", uri, params={}, auth=AUTH, + use_json_extension=False) + @patch('twilio.rest.resources.base.make_twilio_request') def test_update_instance(self, request): resp = create_mock_json('tests/resources/task_router/reservations_instance.json') diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index f9672d2820..b7d37b73fe 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -52,13 +52,18 @@ def events(self, workspace_sid): base_uri = "{0}/{1}".format(self.workspace_uri, workspace_sid) return Events(base_uri, self.auth, self.timeout) - def reservations(self, workspace_sid, task_sid): + def reservations(self, workspace_sid, sid): """ Return a :class:`Reservations` instance for the :class:`Reservation` - with the given workspace_sid ans task_sid - """ - base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, - workspace_sid, task_sid) + with the given workspace_sid and an task_sid or worker_sid + """ + if sid.startswith('WT'): + base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, + workspace_sid, sid) + elif sid.startswith('WK'): + base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, + workspace_sid, sid) + return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From 2c0acb1398a75e8a74e66889c477af4fcd0f5089 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Fri, 28 Aug 2015 17:09:40 -0700 Subject: [PATCH 038/115] Add nested lookup for Task --- tests/test_twiml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index 781ec1eaf9..e995c418ac 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -421,7 +421,7 @@ def setUp(self): # structure tree = ET.fromstring(xml) self.enqueue = tree.find("./Enqueue") - self.task = self.enqueue.find("./Task") + self.task = self.enqueue.find(".//Task") def test_found_task(self): self.assertIsNotNone(self.task) From b832bbc528583283c756a97efb7cca1e130e1d0f Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:27:11 -0700 Subject: [PATCH 039/115] Add Reservations as a sub resource of worker --- twilio/rest/resources/task_router/workers.py | 4 +++- twilio/rest/task_router.py | 14 +++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/twilio/rest/resources/task_router/workers.py b/twilio/rest/resources/task_router/workers.py index f257435cb9..6383a57d0c 100644 --- a/twilio/rest/resources/task_router/workers.py +++ b/twilio/rest/resources/task_router/workers.py @@ -1,5 +1,6 @@ from .. import NextGenInstanceResource, NextGenListResource from .statistics import Statistics +from .reservations import Reservations class Worker(NextGenInstanceResource): @@ -68,7 +69,8 @@ class Worker(NextGenInstanceResource): calculate :class: `Workflow` statistics. """ subresources = [ - Statistics + Statistics, + Reservations ] def delete(self): diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index b7d37b73fe..b33414cc02 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -52,17 +52,13 @@ def events(self, workspace_sid): base_uri = "{0}/{1}".format(self.workspace_uri, workspace_sid) return Events(base_uri, self.auth, self.timeout) - def reservations(self, workspace_sid, sid): + def reservations(self, workspace_sid, task_sid): """ Return a :class:`Reservations` instance for the :class:`Reservation` - with the given workspace_sid and an task_sid or worker_sid - """ - if sid.startswith('WT'): - base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, - workspace_sid, sid) - elif sid.startswith('WK'): - base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, - workspace_sid, sid) + with the given workspace_sid ans task_sid + """ + base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, + workspace_sid, task_sid) return Reservations(base_uri, self.auth, self.timeout) From 097df1976981ade934bba03e3c64e977b6845141 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:28:14 -0700 Subject: [PATCH 040/115] Cleanup extra newline --- twilio/rest/task_router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index b33414cc02..f9672d2820 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -59,7 +59,6 @@ def reservations(self, workspace_sid, task_sid): """ base_uri = "{0}/{1}/Tasks/{2}".format(self.workspace_uri, workspace_sid, task_sid) - return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From f82ec7ff5bd3904865fb30d05f0da6f1c948adaa Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 11:37:44 -0700 Subject: [PATCH 041/115] Fix assertion order --- tests/task_router/test_workflow_config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index d9d70a3dd2..6aefd48a93 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -14,7 +14,7 @@ def test_to_json(self): ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules , def_target) - self.assertEqual(self.is_json(config.to_json()), True) + self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): @@ -37,8 +37,8 @@ def test_from_json(self): } config = WorkflowConfig.json2obj(json.dumps(data)) - self.assertEqual(len(config.task_routing.filters), 3) - self.assertEqual(len(config.task_routing.default_filter), 1) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) def test_from_json2(self): @@ -51,8 +51,8 @@ def test_from_json2(self): 'default_filter': {'priority': None, 'queue': 'WQYYYYY', 'expression': None, 'timeout': None}}} config = WorkflowConfig.json2obj(json.dumps(data)) - self.assertEqual(len(config.task_routing.filters), 2) - self.assertEqual(len(config.task_routing.default_filter), 4) + self.assertEqual(2, len(config.task_routing.filters)) + self.assertEqual(4, len(config.task_routing.default_filter)) def is_json(self, myjson): try: From 1ce0abce00c1cdfe1cbabdaba2bafe3a673e9de8 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:08:51 -0700 Subject: [PATCH 042/115] Fix merge conflict --- twilio/task_router/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index d940e5a7bb..3b501a8cbc 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -247,13 +247,6 @@ def __init__(self, account_sid, auth_token, workspace_sid): def setup_resource(self): self.resource_url = self.base_url - return { - 'url': url, - 'method': method, - 'allow': allowed, - 'query_filter': query_filter or {}, - 'post_filter': post_filter or {}, - } from .taskrouter_config import ( TaskRouterConfig From f1958bcc57a6e8f3897c47e989d9585281508505 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:09:43 -0700 Subject: [PATCH 043/115] Fix make test (validation on styling) --- tests/task_router/test_workflow_config.py | 65 +++++++++++++------ twilio/rest/resources/task_router/__init__.py | 2 - twilio/task_router/workflow_config.py | 8 ++- twilio/task_router/workflow_rule.py | 1 - twilio/task_router/workflow_ruletarget.py | 1 - 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 6aefd48a93..e98aea5705 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -9,32 +9,56 @@ class WorkflowConfigTest(unittest.TestCase): def test_to_json(self): rules = [ - WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)],"SomeQ"), - WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") + WorkflowRule("1==1", [WorkflowRuleTarget("WQeae4fc2f4db7f377c5d3758fb08b79b7", "1==1", 1, 20)], "SomeQ"), + WorkflowRule("1==1", [WorkflowRuleTarget("WQ19ebe92fb33522f018b5a31d805d94da", "1==1", 1, 210)], "SomeOtherQ") ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) - config = WorkflowConfig(rules , def_target) + config = WorkflowConfig(rules, def_target) self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): - data = {'task_routing': - {'default_filter': - {'queue': 'WQ05f810d2d130344fd56e3c91ece2e594'}, 'filters': [ - {'expression': 'type == "sales"', 'friendly_name': 'Sales', 'targets': [ - {'queue': 'WQec62de0e1148b8477f2e24579779c8b1', - 'expression': 'task.language IN worker.languages'}]}, - {'expression': 'type == "marketing"', 'friendly_name': 'Marketing', 'targets': - [{'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', - 'expression': 'task.language IN worker.languages'}] - }, - {'expression': 'type == "support"', 'friendly_name': 'Support', 'targets': - [{'queue': 'WQe5eb317eb23500ade45087ea6522896c', - 'expression': 'task.language IN worker.languages'}] - } - ] - } - } + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'friendly_name': 'Sales', + 'targets': [ + { + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'friendly_name': 'Marketing', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'friendly_name': 'Support', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(3, len(config.task_routing.filters)) @@ -58,5 +82,6 @@ def is_json(self, myjson): try: json.loads(myjson) except ValueError, e: + print e return False return True diff --git a/twilio/rest/resources/task_router/__init__.py b/twilio/rest/resources/task_router/__init__.py index 5ca394258a..e312f1a882 100644 --- a/twilio/rest/resources/task_router/__init__.py +++ b/twilio/rest/resources/task_router/__init__.py @@ -36,5 +36,3 @@ Workspace, Workspaces ) - - diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py index e79881b3f7..e9c27aa378 100644 --- a/twilio/task_router/workflow_config.py +++ b/twilio/task_router/workflow_config.py @@ -14,9 +14,13 @@ def __init__(self, workflow_rules, default_target): self.task_routing = TaskRouterConfig(workflow_rules, default_target) def to_json(self): - return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + return json.dumps(self, + default=lambda o: o.__dict__, + sort_keys=True, + indent=4) @staticmethod def json2obj(data): m = json.loads(data) - return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) + return WorkflowConfig(m['task_routing']['filters'], + m['task_routing']['default_filter']) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index 92a74b5bb9..3cae68ec80 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -32,4 +32,3 @@ def __repr__(self): 'friendly_name': self.friendly_name, 'target': self.target, }) - diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py index 987ac0f911..1cee506c30 100644 --- a/twilio/task_router/workflow_ruletarget.py +++ b/twilio/task_router/workflow_ruletarget.py @@ -25,4 +25,3 @@ def __init__(self, queue, expression, priority, timeout): self.expression = expression self.priority = priority self.timeout = timeout - From d242629dcf7574e66d14f664ecb589d0c8d457ef Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 3 Sep 2015 12:16:12 -0700 Subject: [PATCH 044/115] Add worker_reservations endpoint --- twilio/rest/task_router.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index f9672d2820..a03ef2e498 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -61,6 +61,15 @@ def reservations(self, workspace_sid, task_sid): workspace_sid, task_sid) return Reservations(base_uri, self.auth, self.timeout) + def worker_reservations(self, workspace_sid, worker_sid): + """ + Return a :class:`Reservations` instance for the :class:`Reservation` + with the given workspace_sid ans worker_sid + """ + base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, + workspace_sid, worker_sid) + return Reservations(base_uri, self.auth, self.timeout) + def task_queues(self, workspace_sid): """ Return a :class:`TaskQueues` instance for the :class:`TaskQueue` with From 6eae914ae3dd77750fa437466161d61e81c216fb Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 16:29:22 -0700 Subject: [PATCH 045/115] Fix indentation --- twilio/rest/task_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index a03ef2e498..eec74857e8 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -67,7 +67,7 @@ def worker_reservations(self, workspace_sid, worker_sid): with the given workspace_sid ans worker_sid """ base_uri = "{0}/{1}/Workers/{2}".format(self.workspace_uri, - workspace_sid, worker_sid) + workspace_sid, worker_sid) return Reservations(base_uri, self.auth, self.timeout) def task_queues(self, workspace_sid): From 7978efd5e36921818c7295521d56c4e0c7e7a753 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 16:29:41 -0700 Subject: [PATCH 046/115] Remove --use-mirrors from test-install --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f51e8826a..68d3c452e8 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ venv: virtualenv venv install: venv - . venv/bin/activate; pip install . --use-mirrors + . venv/bin/activate; pip install . test-install: install . venv/bin/activate; pip install -r tests/requirements.txt From d3380d5dd8a67610f7d0d7c06e6e146c51a57373 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 17:32:37 -0700 Subject: [PATCH 047/115] Add parameter to use container-based travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a7803a70da..0e9bbdfa2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,4 @@ script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests - nosetests +sudo: false From a51d23f9ce610d7cee5051c0dd451f33bc65fb0b Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Tue, 22 Sep 2015 17:40:09 -0700 Subject: [PATCH 048/115] Add secure travis token --- .travis.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e9bbdfa2e..75a7c3dc44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,19 @@ language: python python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" + - '2.6' + - '2.7' + - '3.2' + - '3.3' + - '3.4' install: - pip install . --use-mirrors - pip install -r requirements.txt --use-mirrors - pip install -r tests/requirements.txt --use-mirrors -script: +script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false +notifications: + slack: + secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 61d4746a10cf5ec1f0aec596798f2c5fcf12b174 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 10:50:28 -0700 Subject: [PATCH 049/115] Fix syntax errors for non-2.7 --- tests/task_router/test_workflow_config.py | 2 +- twilio/task_router/__init__.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index e98aea5705..204a0709b1 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -81,7 +81,7 @@ def test_from_json2(self): def is_json(self, myjson): try: json.loads(myjson) - except ValueError, e: + except ValueError as e: print e return False return True diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 3b501a8cbc..c77feeca29 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -16,7 +16,7 @@ def deprecated(func): def log_warning(*args, **kwargs): # stacklevel = 2 makes the warning refer to the caller of the # deprecation rather than the source of deprecation itself - warnings.warn("Call to deprecated function {}.". + warnings.warn("Call to deprecated function {0}.". format(func.__name__), stacklevel=2, category=DeprecationWarning) @@ -32,9 +32,9 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): self.workspace_sid = workspace_sid self.channel_id = channel_id - self.base_url = "{}/{}/Workspaces/{}".format(TASK_ROUTER_BASE_URL, - TASK_ROUTER_VERSION, - workspace_sid) + self.base_url = "{0}/{1}/Workspaces/{2}".format(TASK_ROUTER_BASE_URL, + TASK_ROUTER_VERSION, + workspace_sid) # validate the JWT self.validate_jwt() @@ -65,12 +65,13 @@ def setup_resource(self): self.allow(reservations_url, "GET") elif self.channel_prefix == "WQ": - self.resource_url = "{}/TaskQueues/{}".format( + self.resource_url = "{0}/TaskQueues/{1}".format( self.base_url, self.channel_id) def allow_web_sockets(self, channel_id): - web_socket_url = "{}/{}/{}".format(TASK_ROUTER_BASE_EVENTS_URL, - self.account_sid, self.channel_id) + web_socket_url = "{0}/{1}/{2}".format(TASK_ROUTER_BASE_EVENTS_URL, + self.account_sid, + self.channel_id) self.policies.append(self.make_policy(web_socket_url, "GET", True)) self.policies.append(self.make_policy(web_socket_url, "POST", True)) From 8e553c4005aaf2afc8a0a3ec75d88f82e4f7ca63 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 10:55:48 -0700 Subject: [PATCH 050/115] Only build master automatically --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 75a7c3dc44..da578cee53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ script: - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false +branches: + only: + -master notifications: slack: secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 7593d7f87700ff3f641f90923e4d2ad693f36137 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 11:44:17 -0700 Subject: [PATCH 051/115] Remove calls to assertIsNotNone This call does not exist < 2.7 --- tests/task_router/test_capability.py | 12 ++++----- .../test_task_router_capability.py | 16 ++++++------ .../test_task_router_taskqueue_capability.py | 24 ++++++++--------- .../test_task_router_worker_capability.py | 26 +++++++++---------- .../test_task_router_workspace_capability.py | 24 ++++++++--------- tests/test_twiml.py | 2 +- 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index eb4d15edba..de105db281 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -55,7 +55,7 @@ def test_defaults(self): self.assertTrue(decoded is not None) websocket_url = ( - 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) + 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) ) expected = [ { @@ -66,7 +66,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format(self.workspace_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -87,7 +87,7 @@ def test_defaults(self): 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format(self.workspace_sid, self.worker_sid), + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format(self.workspace_sid, self.worker_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, @@ -102,7 +102,7 @@ def test_allow_worker_activity_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -122,7 +122,7 @@ def test_allow_worker_fetch_attributes(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}'.format( self.workspace_sid, self.worker_sid, ) @@ -143,7 +143,7 @@ def test_allow_task_reservation_updates(self): decoded = jwt.decode(token, self.auth_token) self.assertTrue(decoded is not None) - url = 'https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**'.format( + url = 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format( self.workspace_sid, ) diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index a32768bfbf..78116c0db1 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -37,10 +37,10 @@ def test_workspace_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, channel_id) @@ -64,10 +64,10 @@ def test_worker_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) @@ -93,10 +93,10 @@ def test_task_queue_default(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, taskqueue_sid, taskqueue_sid) @@ -120,10 +120,10 @@ def test_deprecated_worker(self): capability.generate_token() token = capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) diff --git a/tests/task_router/test_task_router_taskqueue_capability.py b/tests/task_router/test_task_router_taskqueue_capability.py index 66f466a763..42ed9597e6 100644 --- a/tests/task_router/test_task_router_taskqueue_capability.py +++ b/tests/task_router/test_task_router_taskqueue_capability.py @@ -17,10 +17,10 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(decoded["iss"], self.account_sid) self.assertEqual(decoded["account_sid"], self.account_sid) @@ -32,10 +32,10 @@ def test_generate_token(self): def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -43,19 +43,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_default(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -88,10 +88,10 @@ def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) @@ -110,10 +110,10 @@ def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 4f7578d91b..e2edaa64b5 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -36,19 +36,19 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.worker_sid, self.worker_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -56,19 +56,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_defaults(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) @@ -92,10 +92,10 @@ def test_allow_activity_updates(self): self.capability.allow_activity_updates() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 6) @@ -106,7 +106,7 @@ def test_allow_activity_updates(self): self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) self.assertTrue(policy["allow"]) - self.assertIsNotNone(policy['post_filter']) + self.assertNotEqual(None, policy['post_filter']) self.assertEqual({}, policy['query_filter']) self.assertTrue(policy['post_filter']['ActivitySid']) @@ -115,10 +115,10 @@ def test_allow_reservation_updates(self): self.capability.allow_reservation_updates() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 6) diff --git a/tests/task_router/test_task_router_workspace_capability.py b/tests/task_router/test_task_router_workspace_capability.py index 2767afd59d..048611166e 100644 --- a/tests/task_router/test_task_router_workspace_capability.py +++ b/tests/task_router/test_task_router_workspace_capability.py @@ -35,19 +35,19 @@ def setUp(self): def test_generate_token(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.check_decoded(decoded, self.account_sid, self.workspace_sid, self.workspace_sid) def test_generate_token_with_default_ttl(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 3600, decoded["exp"]) @@ -55,19 +55,19 @@ def test_generate_token_with_custom_ttl(self): ttl = 10000 token = self.capability.generate_token(ttl) - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) self.assertEqual(int(time.time()) + 10000, decoded["exp"]) def test_default(self): token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 3) @@ -83,10 +83,10 @@ def test_allow_fetch_subresources(self): self.capability.allow_fetch_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) @@ -100,10 +100,10 @@ def test_allow_updates_subresources(self): self.capability.allow_updates_subresources() token = self.capability.generate_token() - self.assertIsNotNone(token) + self.assertNotEqual(None, token) decoded = jwt.decode(token, self.auth_token) - self.assertIsNotNone(decoded) + self.assertNotEqual(None, decoded) policies = decoded['policies'] self.assertEqual(len(policies), 4) diff --git a/tests/test_twiml.py b/tests/test_twiml.py index e995c418ac..c022baeda0 100644 --- a/tests/test_twiml.py +++ b/tests/test_twiml.py @@ -424,7 +424,7 @@ def setUp(self): self.task = self.enqueue.find(".//Task") def test_found_task(self): - self.assertIsNotNone(self.task) + self.assertNotEqual(None, self.task) def test_enqueue_workflow_sid(self): self.assertEqual(self.enqueue.get('workflowSid'), "Workflow1") From 7fe52434ed264e7d7ccf6e62c32489d658ec4646 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 11:58:14 -0700 Subject: [PATCH 052/115] Remove branches restriction from travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index da578cee53..75a7c3dc44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,6 @@ script: - flake8 --ignore=E123,E126,E128,E501 tests - nosetests sudo: false -branches: - only: - -master notifications: slack: secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= From 96704483b7821f40531fa494181ae4e0214fcb0a Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:00:20 -0700 Subject: [PATCH 053/115] Fix rogue format --- tests/task_router/test_task_router_worker_capability.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index e2edaa64b5..83feaf6b8c 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -70,7 +70,7 @@ def test_defaults(self): decoded = jwt.decode(token, self.auth_token) self.assertNotEqual(None, decoded) - websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{}/{}'.format(self.account_sid, self.worker_sid) + websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) # expect 5 policies policies = decoded['policies'] @@ -101,7 +101,7 @@ def test_allow_activity_updates(self): self.assertEqual(len(policies), 6) policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Workers/{}".format(self.workspace_sid, self.worker_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}".format(self.workspace_sid, self.worker_sid) self.assertEqual(url, policy["url"]) self.assertEqual("POST", policy["method"]) @@ -125,7 +125,7 @@ def test_allow_reservation_updates(self): policy = policies[5] - url = "https://taskrouter.twilio.com/v1/Workspaces/{}/Tasks/**".format(self.workspace_sid) + url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid) self.check_policy('POST', url, policy) From b1af810d740198851cbdacf890d22e48c6b6c17e Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:03:49 -0700 Subject: [PATCH 054/115] Functionalize print --- tests/task_router/test_workflow_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 204a0709b1..458fecc045 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -82,6 +82,6 @@ def is_json(self, myjson): try: json.loads(myjson) except ValueError as e: - print e + print(e) return False return True From 12dbd57a73de9bf977f56db69164f57f729e388b Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 12:18:17 -0700 Subject: [PATCH 055/115] Bump to version 4.6.0 --- CHANGES.md | 9 +++++++++ twilio/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index edb4ea582a..94e2225e03 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,15 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.6.0 +------------- + +Released September 23, 2015: + +- Allow fetching TaskRouter reservations by Worker +- Add missing Enqueue->Task TwiML generation +- Add Worflow construction + Version 4.5.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index a14cd79c93..9e8a4df44c 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '5', '0') +__version_info__ = ('4', '6', '0') __version__ = '.'.join(__version_info__) From a25329c0851a080561e00595a4f4e7815e3bb2fa Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 14:28:44 -0700 Subject: [PATCH 056/115] Reconfigure travis --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 75a7c3dc44..d1e18d2a59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,9 @@ script: sudo: false notifications: slack: - secure: Axcfefsnbvly090AqDg3IL87fRzPEf9SXIc9JG5D1qiNekqMJaFz6CoLciSY1ydJKSKLygqB2TCKwPzZGwsIIxcelx2SVodFsjDjjbyHsUE2V9K7hET5eS9BX/aPgT/zoMq/0LBK+U3/9ssg07cCtck7wc+4mBd76UJ5sc37uQY= + on_success: change + on_failure: change + rooms: + secure: + - TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= + - qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= From 636bf915c3945d01a8acab85f0c7e90b48239d80 Mon Sep 17 00:00:00 2001 From: Kevin Whinnery Date: Wed, 23 Sep 2015 16:39:02 -0500 Subject: [PATCH 057/115] fix broken image link --- docs/appengine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/appengine.rst b/docs/appengine.rst index 0cdf9db9c8..74a26a4fcd 100644 --- a/docs/appengine.rst +++ b/docs/appengine.rst @@ -102,7 +102,7 @@ The key is to lay out your project in a way that makes sense. the settings you want in Google App Engine - Note the folder path ends with ``twilio-demo/src``. - .. image:: https://www.evernote.com/shard/s265/sh/1b9407b0-c89b-464d-b352-dbf8fc7a7f41/f536b8e79747f43220fc12e0e0026ee2/res/5b2f83af-8a7f-451f-afba-db092c55aa44/skitch.png + .. image:: http://howtodocs.s3.amazonaws.com/helpers/appengine.png Once App Engine is running locally, in your browser, you should be able to navigate to ``http://localhost`` + the provided Port and view the twilio From 9f62d187176bfa6f7161d1166c8a984c7fea5d89 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 23 Sep 2015 15:07:20 -0700 Subject: [PATCH 058/115] Fix travis config --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1e18d2a59..250dcb8c18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,5 @@ notifications: on_success: change on_failure: change rooms: - secure: - - TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= - - qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= + - secure: TcDlBcKXtqmMdVsa3Lsofdqc1uVjqhZouwNETC260rByRb74gTHGZ1Da7PRkv+AZIFUq7S1uWTZXTXJTm154hi4aQb9SE2UowVwTJMjIKyy4P79s1eoyADP92cFEcpqSs4iVpU3t5srTj8cg2fVks8EsV5pDVJut1oBH4qYJEZw= + - secure: qqtcwaS0y5e2SVm5g/zSRMgo7FcZ8Oa44bxQUDvJh/84/DHMD3zZoAv/A4+Vlbs0tCjnSKxMDuLxTzpiPgru4KPH7yj4fEXf7+sfwiLD//WBVWpGMYLa+PNCGS6hhnAuFkA2psZCmmzkbJbX0N03EdWiWFzV79WPgNI+WzpYIVQ= From da33db6c977e6b45c49d80b7101cdc1973885405 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Fri, 9 Oct 2015 18:50:27 -0700 Subject: [PATCH 059/115] Sms Pricing for Messaging Countries --- tests/pricing/test_messaging_countries.py | 88 +++++++++++++++++++ .../pricing/messaging_countries_instance.json | 51 +++++++++++ .../pricing/messaging_countries_list.json | 23 +++++ twilio/rest/pricing.py | 18 +++- twilio/rest/resources/pricing/__init__.py | 4 + .../resources/pricing/messaging_countries.py | 39 ++++++++ 6 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 tests/pricing/test_messaging_countries.py create mode 100644 tests/resources/pricing/messaging_countries_instance.json create mode 100644 tests/resources/pricing/messaging_countries_list.json create mode 100644 twilio/rest/resources/pricing/messaging_countries.py diff --git a/tests/pricing/test_messaging_countries.py b/tests/pricing/test_messaging_countries.py new file mode 100644 index 0000000000..8ea1431ee1 --- /dev/null +++ b/tests/pricing/test_messaging_countries.py @@ -0,0 +1,88 @@ +import unittest +from mock import patch +from nose.tools import assert_equal +from tests.tools import create_mock_json +from twilio.rest.resources.pricing.messaging_countries import ( + MessagingCountries +) + +AUTH = ("AC123", "token") +BASE_URI = "https://pricing.twilio.com/v1" + + +class MessagingCountriesTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_messaging_countries(self, request): + resp = create_mock_json( + 'tests/resources/pricing/messaging_countries_list.json') + resp.status_code = 200 + request.return_value = resp + + countries = MessagingCountries(BASE_URI + "/Messaging", AUTH) + result = countries.list() + + assert_equal(result[0].iso_country, "AT") + assert_equal(len(result), 2) + + request.assert_called_with( + "GET", + "{0}/Messaging/Countries".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + params={} + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_messaging_country(self, request): + resp = create_mock_json( + 'tests/resources/pricing/messaging_countries_instance.json') + resp.status_code = 200 + request.return_value = resp + + countries = MessagingCountries(BASE_URI + "/Messaging", AUTH) + result = countries.get('US') + + assert_equal(result.iso_country, "US") + assert_equal(result.price_unit, "usd") + assert_equal(result.outbound_sms_prices[0]['mcc'], "311") + assert_equal(result.outbound_sms_prices[0]['mnc'], "484") + assert_equal(result.outbound_sms_prices[0]['carrier'], "Verizon") + prices = result.outbound_sms_prices[0]['prices'] + + assert_equal(prices[0]['number_type'], "mobile") + assert_equal(prices[0]['base_price'], "0.0075") + assert_equal(prices[0]['current_price'], "0.0070") + + assert_equal(prices[1]['number_type'], "local") + assert_equal(prices[1]['base_price'], "0.0075") + assert_equal(prices[1]['current_price'], "0.0070") + + assert_equal(prices[2]['number_type'], "shortcode") + assert_equal(prices[2]['base_price'], "0.01") + assert_equal(prices[2]['current_price'], "0.01") + + assert_equal(prices[3]['number_type'], "toll-free") + assert_equal(prices[3]['base_price'], "0.0075") + assert_equal(prices[3]['current_price'], "0.0075") + + inbound_sms_prices = result.inbound_sms_prices + + assert_equal(inbound_sms_prices[0]['number_type'], "local") + assert_equal(inbound_sms_prices[0]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[0]['current_price'], "0.0075") + + assert_equal(inbound_sms_prices[1]['number_type'], "shortcode") + assert_equal(inbound_sms_prices[1]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[1]['current_price'], "0.005") + + assert_equal(inbound_sms_prices[2]['number_type'], "toll-free") + assert_equal(inbound_sms_prices[2]['base_price'], "0.0075") + assert_equal(inbound_sms_prices[2]['current_price'], "0.0075") + + request.assert_called_with( + "GET", + "{0}/Messaging/Countries/US".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + ) diff --git a/tests/resources/pricing/messaging_countries_instance.json b/tests/resources/pricing/messaging_countries_instance.json new file mode 100644 index 0000000000..2df45d006f --- /dev/null +++ b/tests/resources/pricing/messaging_countries_instance.json @@ -0,0 +1,51 @@ +{ + "country": "United States", + "iso_country": "US", + "price_unit": "usd", + "outbound_sms_prices": [ + { + "mcc": "311", + "mnc": "484", + "carrier": "Verizon", + "prices": [ + { + "number_type": "mobile", + "base_price": "0.0075", + "current_price": "0.0070" + }, + { + "number_type": "local", + "base_price": "0.0075", + "current_price": "0.0070" + }, + { + "number_type": "shortcode", + "base_price": "0.01", + "current_price": "0.01" + }, + { + "number_type": "toll-free", + "base_price": "0.0075", + "current_price": "0.0075" + } + ] + } + ], + "inbound_sms_prices": [ + { + "number_type": "local", + "base_price": "0.0075", + "current_price": "0.0075" + }, + { + "number_type": "shortcode", + "base_price": "0.0075", + "current_price": "0.005" + }, + { + "number_type": "toll-free", + "base_price": "0.0075", + "current_price": "0.0075" + } + ] +} \ No newline at end of file diff --git a/tests/resources/pricing/messaging_countries_list.json b/tests/resources/pricing/messaging_countries_list.json new file mode 100644 index 0000000000..07623348ec --- /dev/null +++ b/tests/resources/pricing/messaging_countries_list.json @@ -0,0 +1,23 @@ +{ + "meta": { + "first_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0", + "key": "countries", + "next_page_url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=1&PageToken=DNCZ", + "page": 0, + "page_size": 50, + "previous_page_url": null, + "url": "https://pricing.twilio.com/v1/Messaging/Countries?PageSize=50&Page=0" + }, + "countries": [ + { + "country": "Austria", + "iso_country": "AT", + "url": "https://pricing.twilio.com/v1/Messaging/Countries/AT" + }, + { + "country": "Australia", + "iso_country": "AU", + "url": "https://pricing.twilio.com/v1/Messaging/Countries/AU" + } + ] +} \ No newline at end of file diff --git a/twilio/rest/pricing.py b/twilio/rest/pricing.py index 998c711ac1..80ae81b920 100644 --- a/twilio/rest/pricing.py +++ b/twilio/rest/pricing.py @@ -3,6 +3,7 @@ from twilio.rest.resources.pricing import ( PhoneNumbers, Voice, + MessagingCountries, ) @@ -24,7 +25,18 @@ def __init__(self, account=None, token=None, super(TwilioPricingClient, self).__init__(account, token, base, version, timeout) - uri_base = "{}/{}".format(base, version) + self.uri_base = "{}/{}".format(base, version) - self.voice = Voice(uri_base, self.auth, self.timeout) - self.phone_numbers = PhoneNumbers(uri_base, self.auth, self.timeout) + self.voice = Voice(self.uri_base, self.auth, self.timeout) + self.phone_numbers = PhoneNumbers(self.uri_base, self.auth, + self.timeout) + + def messaging_countries(self): + """ + Returns a :class:`MessagingCountries` resource + :return: MessagingCountries + """ + messaging_countries_uri = "{0}/Messaging/Countries".format( + self.uri_base) + return MessagingCountries(messaging_countries_uri, self.auth, + self.timeout) diff --git a/twilio/rest/resources/pricing/__init__.py b/twilio/rest/resources/pricing/__init__.py index b1797a0a87..1edd5d260b 100644 --- a/twilio/rest/resources/pricing/__init__.py +++ b/twilio/rest/resources/pricing/__init__.py @@ -11,3 +11,7 @@ PhoneNumberCountry, PhoneNumbers, ) + +from .messaging_countries import ( + MessagingCountries +) diff --git a/twilio/rest/resources/pricing/messaging_countries.py b/twilio/rest/resources/pricing/messaging_countries.py new file mode 100644 index 0000000000..2be1a6e097 --- /dev/null +++ b/twilio/rest/resources/pricing/messaging_countries.py @@ -0,0 +1,39 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class MessagingCountry(NextGenInstanceResource): + """Pricing information for Twilio Messages in a specific country. + + .. attribute:: iso_country + + The country's 2-character ISO 3166-1 code. + + """ + id_key = "iso_country" + + +class MessagingCountries(NextGenListResource): + """A list of countries where Twilio Messages are available. + + The returned list of MessagingCountry objects will not have pricing + information populated. To get pricing information for a specific country, + retrieve it with the :meth:`get` method. + """ + + instance = MessagingCountry + key = "countries" + name = "Countries" + + def get(self, iso_country): + """Retrieve pricing information for Twilio Messages in the specified + country. + + :param iso_country: The two-letter ISO code for the country + """ + return self.get_instance(iso_country) + + def list(self, **kwargs): + """Retrieve the list of countries in which Twilio Messages are + available.""" + + return super(MessagingCountries, self).list(**kwargs) From 42bb36847a23b181bfa9d39ce5f7c2d56c9af65a Mon Sep 17 00:00:00 2001 From: Robert Morris Date: Tue, 13 Oct 2015 14:29:05 -0400 Subject: [PATCH 060/115] add test_paging_iter test for Recordings resource --- tests/test_recordings.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_recordings.py b/tests/test_recordings.py index 04960af112..e314efe1e0 100644 --- a/tests/test_recordings.py +++ b/tests/test_recordings.py @@ -27,6 +27,24 @@ def test_paging(mock): use_json_extension=True) +@patch("twilio.rest.resources.base.make_twilio_request") +def test_paging_iter(mock): + resp = create_mock_json("tests/resources/recordings_list.json") + mock.return_value = resp + + uri = "%s/Recordings" % (BASE_URI) + + next(recordings.iter(before=date(2010, 12, 5))) + exp_params = {'DateCreated<': '2010-12-05'} + mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH, + use_json_extension=True) + + next(recordings.iter(after=date(2012, 12, 7))) + exp_params = {'DateCreated>': '2012-12-07'} + mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH, + use_json_extension=True) + + @patch("twilio.rest.resources.base.make_twilio_request") def test_get(mock): resp = create_mock_json("tests/resources/recordings_instance.json") From f5303ed6ea0cfd759ed869eab4e387ed65f3cc7e Mon Sep 17 00:00:00 2001 From: Robert Morris Date: Tue, 13 Oct 2015 14:29:32 -0400 Subject: [PATCH 061/115] add iter() method to Recordings resource --- twilio/rest/resources/recordings.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/twilio/rest/resources/recordings.py b/twilio/rest/resources/recordings.py index 6a4dc227da..be88909144 100644 --- a/twilio/rest/resources/recordings.py +++ b/twilio/rest/resources/recordings.py @@ -41,6 +41,18 @@ def list(self, before=None, after=None, **kwargs): kwargs["DateCreated>"] = after return self.get_instances(kwargs) + @normalize_dates + def iter(self, before=None, after=None, **kwargs): + """ + Returns an iterator of :class:`Recording` resources. + + :param date after: Only list recordings logged after this datetime + :param date before: Only list recordings logger before this datetime + """ + kwargs["DateCreated<"] = before + kwargs["DateCreated>"] = after + return super(Recordings, self).iter(**kwargs) + def delete(self, sid): """ Delete the given recording From 3012665fc49854b28d7bd2aadb7907a0f885bc7d Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 18 Oct 2015 00:36:13 +0100 Subject: [PATCH 062/115] Adding python 3.5 support --- setup.py | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78f31fc2ae..69259aa554 100755 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ ':python_version=="3.2"': ['pysocks'], ':python_version=="3.3"': ['pysocks'], ':python_version=="3.4"': ['pysocks'], + ':python_version=="3.5"': ['pysocks'], }, packages = find_packages(), include_package_data=True, @@ -49,6 +50,7 @@ "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Communications :: Telephony", ], diff --git a/tox.ini b/tox.ini index 5249597112..51d8d59264 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] deps= -r{toxinidir}/tests/requirements.txt From 13da73bffee83dee10c849de98c787ada82fd3cd Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Fri, 9 Oct 2015 17:48:49 -0700 Subject: [PATCH 063/115] Sip Trunking SDK - Added better documentation - Removed default values --- .../trunking/credential_lists_instance.json | 9 + .../trunking/credential_lists_list.json | 22 ++ .../ip_access_control_lists_instance.json | 9 + .../ip_access_control_lists_list.json | 22 ++ .../trunking/origination_urls_instance.json | 13 ++ .../trunking/origination_urls_list.json | 26 +++ .../trunking/phone_numbers_instance.json | 34 +++ .../trunking/phone_numbers_list.json | 47 ++++ tests/resources/trunking/trunks_instance.json | 26 +++ tests/resources/trunking/trunks_list.json | 39 ++++ tests/trunking/test_credential_lists.py | 101 +++++++++ .../trunking/test_ip_access_control_lists.py | 103 +++++++++ tests/trunking/test_origination_urls.py | 150 +++++++++++++ tests/trunking/test_phone_numbers.py | 158 +++++++++++++ tests/trunking/test_trunks.py | 208 ++++++++++++++++++ twilio/rest/__init__.py | 4 +- twilio/rest/resources/__init__.py | 11 + twilio/rest/resources/trunking/__init__.py | 24 ++ .../resources/trunking/credential_lists.py | 59 +++++ .../trunking/ip_access_control_lists.py | 61 +++++ .../resources/trunking/origination_urls.py | 89 ++++++++ .../rest/resources/trunking/phone_numbers.py | 59 +++++ twilio/rest/resources/trunking/trunks.py | 65 ++++++ twilio/rest/trunking.py | 70 ++++++ 24 files changed, 1408 insertions(+), 1 deletion(-) create mode 100644 tests/resources/trunking/credential_lists_instance.json create mode 100644 tests/resources/trunking/credential_lists_list.json create mode 100644 tests/resources/trunking/ip_access_control_lists_instance.json create mode 100644 tests/resources/trunking/ip_access_control_lists_list.json create mode 100644 tests/resources/trunking/origination_urls_instance.json create mode 100644 tests/resources/trunking/origination_urls_list.json create mode 100644 tests/resources/trunking/phone_numbers_instance.json create mode 100644 tests/resources/trunking/phone_numbers_list.json create mode 100644 tests/resources/trunking/trunks_instance.json create mode 100644 tests/resources/trunking/trunks_list.json create mode 100644 tests/trunking/test_credential_lists.py create mode 100644 tests/trunking/test_ip_access_control_lists.py create mode 100644 tests/trunking/test_origination_urls.py create mode 100644 tests/trunking/test_phone_numbers.py create mode 100644 tests/trunking/test_trunks.py create mode 100644 twilio/rest/resources/trunking/__init__.py create mode 100644 twilio/rest/resources/trunking/credential_lists.py create mode 100644 twilio/rest/resources/trunking/ip_access_control_lists.py create mode 100644 twilio/rest/resources/trunking/origination_urls.py create mode 100644 twilio/rest/resources/trunking/phone_numbers.py create mode 100644 twilio/rest/resources/trunking/trunks.py create mode 100644 twilio/rest/trunking.py diff --git a/tests/resources/trunking/credential_lists_instance.json b/tests/resources/trunking/credential_lists_instance.json new file mode 100644 index 0000000000..3f36eb2c30 --- /dev/null +++ b/tests/resources/trunking/credential_lists_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid" : "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2015-01-02T11:23:45Z", + "date_updated": "2015-01-02T11:23:45Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} \ No newline at end of file diff --git a/tests/resources/trunking/credential_lists_list.json b/tests/resources/trunking/credential_lists_list.json new file mode 100644 index 0000000000..c284afd308 --- /dev/null +++ b/tests/resources/trunking/credential_lists_list.json @@ -0,0 +1,22 @@ +{ + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists?PageSize=50&Page=0", + "next_page_url": null, + "key": "credential_lists" + }, + "credential_lists": [ + { + "sid": "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test list", + "date_created": "2015-05-14T21:00:12Z", + "date_updated": "2015-05-14T21:00:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] +} \ No newline at end of file diff --git a/tests/resources/trunking/ip_access_control_lists_instance.json b/tests/resources/trunking/ip_access_control_lists_instance.json new file mode 100644 index 0000000000..9959d4bdec --- /dev/null +++ b/tests/resources/trunking/ip_access_control_lists_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2014-10-30T23:59:12Z", + "date_updated": "2014-10-30T23:59:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} \ No newline at end of file diff --git a/tests/resources/trunking/ip_access_control_lists_list.json b/tests/resources/trunking/ip_access_control_lists_list.json new file mode 100644 index 0000000000..07d91fdd13 --- /dev/null +++ b/tests/resources/trunking/ip_access_control_lists_list.json @@ -0,0 +1,22 @@ +{ + "ip_access_control_lists": [ + { + "sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "friendly_name": "Test", + "date_created": "2014-10-30T23:59:12Z", + "date_updated": "2014-10-30T23:59:12Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists?PageSize=50&Page=0", + "next_page_url": null, + "key": "ip_access_control_lists" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/origination_urls_instance.json b/tests/resources/trunking/origination_urls_instance.json new file mode 100644 index 0000000000..27087bcc98 --- /dev/null +++ b/tests/resources/trunking/origination_urls_instance.json @@ -0,0 +1,13 @@ +{ + "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "weight": 10, + "enabled": true, + "sip_url": "sip:169.10.1.35", + "friendly_name": "Name", + "priority": 20, + "date_created": "2015-09-02T23:15:56Z", + "date_updated": "2015-09-02T23:15:56Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/trunking/origination_urls_list.json b/tests/resources/trunking/origination_urls_list.json new file mode 100644 index 0000000000..43a41e7419 --- /dev/null +++ b/tests/resources/trunking/origination_urls_list.json @@ -0,0 +1,26 @@ +{ + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls?PageSize=50&Page=0", + "next_page_url": null, + "key": "origination_urls" + }, + "origination_urls": [ + { + "sid": "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "trunk_sid": "TK11111111111111111111111111111111", + "weight": 10, + "enabled": true, + "sip_url": "sip:169.10.1.35", + "friendly_name": "Name", + "priority": 20, + "date_created": "2015-09-02T23:15:56Z", + "date_updated": "2015-09-02T23:15:56Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] +} \ No newline at end of file diff --git a/tests/resources/trunking/phone_numbers_instance.json b/tests/resources/trunking/phone_numbers_instance.json new file mode 100644 index 0000000000..3a8e59e6b0 --- /dev/null +++ b/tests/resources/trunking/phone_numbers_instance.json @@ -0,0 +1,34 @@ +{ + "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2014-10-07T13:37:19Z", + "date_updated": "2015-09-02T16:27:22Z", + "friendly_name": "Name", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "phone_number": "+14158675309", + "api_version": "2010-04-01", + "voice_caller_id_lookup": false, + "voice_url": null, + "voice_method": null, + "voice_fallback_url": "", + "voice_fallback_method": "POST", + "status_callback": "", + "status_callback_method": "POST", + "voice_application_sid": null, + "trunk_sid": "TK11111111111111111111111111111111", + "sms_url": "https://demo.twilio.com/welcome/sms/reply/", + "sms_method": "POST", + "sms_fallback_url": "", + "sms_fallback_method": "POST", + "sms_application_sid": "", + "address_requirements": "none", + "beta": false, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "capabilities": { + "voice": true, + "sms": true, + "mms": true + }, + "links": { + "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/phone_numbers_list.json b/tests/resources/trunking/phone_numbers_list.json new file mode 100644 index 0000000000..d24d62195c --- /dev/null +++ b/tests/resources/trunking/phone_numbers_list.json @@ -0,0 +1,47 @@ +{ + "phone_numbers": [ + { + "sid": "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2014-10-07T13:37:19Z", + "date_updated": "2015-09-02T16:27:22Z", + "friendly_name": "Name", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "phone_number": "+14158675309", + "api_version": "2010-04-01", + "voice_caller_id_lookup": false, + "voice_url": null, + "voice_method": null, + "voice_fallback_url": "", + "voice_fallback_method": "POST", + "status_callback": "", + "status_callback_method": "POST", + "voice_application_sid": null, + "trunk_sid": "TK11111111111111111111111111111111", + "sms_url": "https://demo.twilio.com/welcome/sms/reply/", + "sms_method": "POST", + "sms_fallback_url": "", + "sms_fallback_method": "POST", + "sms_application_sid": "", + "address_requirements": "none", + "beta": false, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "capabilities": { + "voice": true, + "sms": true, + "mms": true + }, + "links": { + "phone_number": "https://api.twilio.com/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IncomingPhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers?PageSize=50&Page=0", + "next_page_url": null, + "key": "phone_numbers" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/trunks_instance.json b/tests/resources/trunking/trunks_instance.json new file mode 100644 index 0000000000..1d23a71baa --- /dev/null +++ b/tests/resources/trunking/trunks_instance.json @@ -0,0 +1,26 @@ +{ + "sid": "TK11111111111111111111111111111111", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "domain_name": "test-trunk.pstn.twilio.com", + "disaster_recovery_method": null, + "disaster_recovery_url": null, + "friendly_name": "Test", + "secure": false, + "recording": { + "trim": "do-not-trim", + "mode": "record-from-ringing" + }, + "auth_type": "CREDENTIAL_LIST", + "auth_type_set": [ + "CREDENTIAL_LIST" + ], + "date_created": "2015-05-05T20:59:07Z", + "date_updated": "2015-05-05T22:44:23Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111", + "links": { + "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls", + "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists", + "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists", + "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers" + } +} \ No newline at end of file diff --git a/tests/resources/trunking/trunks_list.json b/tests/resources/trunking/trunks_list.json new file mode 100644 index 0000000000..b2c238e143 --- /dev/null +++ b/tests/resources/trunking/trunks_list.json @@ -0,0 +1,39 @@ +{ + "trunks": [ + { + "sid": "TK11111111111111111111111111111111", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "domain_name": "test-trunk.pstn.twilio.com", + "disaster_recovery_method": null, + "disaster_recovery_url": null, + "friendly_name": "Test", + "secure": false, + "recording": { + "trim": "do-not-trim", + "mode": "record-from-ringing" + }, + "auth_type": "CREDENTIAL_LIST", + "auth_type_set": [ + "CREDENTIAL_LIST" + ], + "date_created": "2015-05-05T20:59:07Z", + "date_updated": "2015-05-05T22:44:23Z", + "url": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111", + "links": { + "origination_urls": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/OriginationUrls", + "credential_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/CredentialLists", + "ip_access_control_lists": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/IpAccessControlLists", + "phone_numbers": "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111/PhoneNumbers" + } + } + ], + "meta": { + "page": 0, + "page_size": 50, + "first_page_url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0", + "previous_page_url": null, + "url": "https://trunking.twilio.com/v1/Trunks?PageSize=50&Page=0", + "next_page_url": null, + "key": "trunks" + } +} \ No newline at end of file diff --git a/tests/trunking/test_credential_lists.py b/tests/trunking/test_credential_lists.py new file mode 100644 index 0000000000..f5b9594601 --- /dev/null +++ b/tests/trunking/test_credential_lists.py @@ -0,0 +1,101 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.credential_lists import CredentialLists + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class CredentialListsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_credential_lists(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_list.json') + resp.status_code = 200 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Test list") + assert_equal(result[0].url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/CredentialLists".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_credential_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json') + resp.status_code = 200 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.get('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_credential_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/credential_lists_instance.json') + resp.status_code = 201 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.create('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['CredentialListSid'] = 'CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/CredentialLists".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_credential_lists_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + credential_lists = CredentialLists(BASE_URI, AUTH) + result = credential_lists.delete('CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/CredentialLists/CLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_ip_access_control_lists.py b/tests/trunking/test_ip_access_control_lists.py new file mode 100644 index 0000000000..59dfa9f5f3 --- /dev/null +++ b/tests/trunking/test_ip_access_control_lists.py @@ -0,0 +1,103 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.ip_access_control_lists import ( + IpAccessControlLists +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class IpAccessControlListsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_ip_access_control_lists(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_list.json') + resp.status_code = 200 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Test") + assert_equal(result[0].url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/IpAccessControlLists".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_ip_access_control_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json') + resp.status_code = 200 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.get('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_associate_ip_access_control_lists_instance(self, request): + resp = create_mock_json('tests/resources/trunking/ip_access_control_lists_instance.json') + resp.status_code = 201 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.create('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Test") + assert_equal(result.url, "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['IpAccessControlListSid'] = 'ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/IpAccessControlLists".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_disassociate_ip_access_control_lists_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + ip_access_control_lists = IpAccessControlLists(BASE_URI, AUTH) + result = ip_access_control_lists.delete('ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_origination_urls.py b/tests/trunking/test_origination_urls.py new file mode 100644 index 0000000000..3da34feb30 --- /dev/null +++ b/tests/trunking/test_origination_urls.py @@ -0,0 +1,150 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.origination_urls import ( + OriginationUrls +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class OriginationUrlsTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_origination_urls_lists(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_list.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Name") + assert_equal(result[0].sip_url, "sip:169.10.1.35") + assert_equal(result[0].weight, 10) + assert_equal(result[0].priority, 20) + assert_true(result[0].enabled) + assert_equal(result[0].url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/OriginationUrls".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.get('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 201 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.create('Name', 'sip:169.10.1.35') + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Name' + data_dict['SipUrl'] = 'sip:169.10.1.35' + data_dict['Priority'] = 10 + data_dict['Weight'] = 10 + data_dict['Enabled'] = 'true' + + request.assert_called_with( + "POST", + "{0}/OriginationUrls".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_update_origination_urls_instance(self, request): + resp = create_mock_json('tests/resources/trunking/origination_urls_instance.json') + resp.status_code = 200 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.update('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', {'Priority': 10}) + + assert_equal(result.sid, "OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.account_sid, "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.sip_url, "sip:169.10.1.35") + assert_equal(result.weight, 10) + assert_equal(result.priority, 20) + assert_true(result.enabled) + assert_equal(result.url, "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI)) + + data_dict = dict() + data_dict['Priority'] = 10 + + request.assert_called_with( + "POST", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_origination_urls_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + origination_urls = OriginationUrls(BASE_URI, AUTH) + result = origination_urls.delete('OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/OriginationUrls/OUaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_phone_numbers.py b/tests/trunking/test_phone_numbers.py new file mode 100644 index 0000000000..8ed54ab33a --- /dev/null +++ b/tests/trunking/test_phone_numbers.py @@ -0,0 +1,158 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.phone_numbers import ( + PhoneNumbers +) + +API_BASE_URI = "https://api.twilio.com/2010-04-01/Accounts" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +PHONE_NUMBERS_BASE_URI = "{0}/{1}/{2}".format(API_BASE_URI, ACCOUNT_SID, + "IncomingPhoneNumbers") +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1/Trunks/TK11111111111111111111111111111111" + + +class PhoneNumbersTest(unittest.TestCase): + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_phone_numbers_lists(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_list.json') + resp.status_code = 200 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].account_sid, + 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result[0].friendly_name, "Name") + assert_equal(result[0].phone_number, "+14158675309") + assert_equal(result[0].api_version, "2010-04-01") + assert_equal(result[0].voice_caller_id_lookup, False) + assert_equal(result[0].voice_fallback_method, "POST") + assert_equal(result[0].status_callback_method, "POST") + assert_equal(result[0].sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result[0].sms_method, "POST") + assert_equal(result[0].sms_fallback_method, "POST") + assert_equal(result[0].address_requirements, "none") + assert_equal(result[0].beta, False) + assert_equal(result[0].url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa". + format(BASE_URI)) + assert_equal(result[0].links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + request.assert_called_with( + "GET", + "{0}/PhoneNumbers".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_phone_numbers_instance(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_instance.json') + resp.status_code = 200 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.get('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.phone_number, "+14158675309") + assert_equal(result.api_version, "2010-04-01") + assert_equal(result.voice_caller_id_lookup, False) + assert_equal(result.voice_fallback_method, "POST") + assert_equal(result.status_callback_method, "POST") + assert_equal(result.sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result.sms_method, "POST") + assert_equal(result.sms_fallback_method, "POST") + assert_equal(result.address_requirements, "none") + assert_equal(result.beta, False) + assert_equal(result.url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI)) + assert_equal(result.links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + request.assert_called_with( + "GET", + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_associate_phone_numbers_instance(self, request): + resp = create_mock_json( + 'tests/resources/trunking/phone_numbers_instance.json') + resp.status_code = 201 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.create('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_equal(result.sid, 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.trunk_sid, "TK11111111111111111111111111111111") + assert_equal(result.friendly_name, "Name") + assert_equal(result.phone_number, "+14158675309") + assert_equal(result.api_version, "2010-04-01") + assert_equal(result.voice_caller_id_lookup, False) + assert_equal(result.voice_fallback_method, "POST") + assert_equal(result.status_callback_method, "POST") + assert_equal(result.sms_url, + "https://demo.twilio.com/welcome/sms/reply/") + assert_equal(result.sms_method, "POST") + assert_equal(result.sms_fallback_method, "POST") + assert_equal(result.address_requirements, "none") + assert_equal(result.beta, False) + assert_equal(result.url, + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI)) + assert_equal(result.links['phone_number'], + "{0}/{1}".format(PHONE_NUMBERS_BASE_URI, + "PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + data_dict = dict() + data_dict['PhoneNumberSid'] = 'PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + request.assert_called_with( + "POST", + "{0}/PhoneNumbers".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_disassociate_phone_numbers_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + phone_numbers = PhoneNumbers(BASE_URI, AUTH) + result = phone_numbers.delete('PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/PhoneNumbers/PNaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".format( + BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/tests/trunking/test_trunks.py b/tests/trunking/test_trunks.py new file mode 100644 index 0000000000..6ce82d191e --- /dev/null +++ b/tests/trunking/test_trunks.py @@ -0,0 +1,208 @@ +import unittest +from mock import Mock, patch +from nose.tools import assert_equal, assert_true +from tests.tools import create_mock_json +from twilio.rest.resources.trunking.trunks import ( + Trunks +) + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "auth_token") +BASE_URI = "https://trunking.twilio.com/v1" +TRUNK_SID = "TK11111111111111111111111111111111" + + +class TrunksTest(unittest.TestCase): + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_trunks_lists(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_list.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.list() + + assert_equal(len(result), 1) + assert_equal(result[0].sid, 'TK11111111111111111111111111111111') + assert_equal(result[0].account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result[0].domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result[0].friendly_name, "Test") + assert_equal(result[0].recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result[0].auth_type, "CREDENTIAL_LIST") + assert_equal(result[0].auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result[0].url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result[0].links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result[0].links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/Trunks".format(BASE_URI), + auth=AUTH, + params={}, + use_json_extension=False, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_get_trunks_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.get('TK11111111111111111111111111111111') + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + request.assert_called_with( + "GET", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_create_trunk_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 201 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + kwargs = { + 'FriendlyName': 'Test', + 'DomainName': 'test-trunk.pstn.twilio.com' + } + result = trunks.create(**kwargs) + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Test' + data_dict['DomainName'] = 'test-trunk.pstn.twilio.com' + + request.assert_called_with( + "POST", + "{0}/Trunks".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict, + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_update_trunk_instance(self, request): + resp = create_mock_json('tests/resources/trunking/trunks_instance.json') + resp.status_code = 200 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.update('TK11111111111111111111111111111111', {'FriendlyName': 'Test'}) + + assert_equal(result.sid, 'TK11111111111111111111111111111111') + assert_equal(result.account_sid, 'ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + assert_equal(result.domain_name, "test-trunk.pstn.twilio.com") + assert_equal(result.friendly_name, "Test") + assert_equal(result.recording, + {"trim": "do-not-trim", + "mode": "record-from-ringing"}) + + assert_equal(result.auth_type, "CREDENTIAL_LIST") + assert_equal(result.auth_type_set, ["CREDENTIAL_LIST"]) + TRUNK_INSTANCE_BASE_URI = "{0}/{1}/{2}".format(BASE_URI, "Trunks", + TRUNK_SID) + + assert_equal(result.url, TRUNK_INSTANCE_BASE_URI) + + assert_equal(result.links['origination_urls'], + "{0}/OriginationUrls".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['credential_lists'], + "{0}/CredentialLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['ip_access_control_lists'], + "{0}/IpAccessControlLists".format(TRUNK_INSTANCE_BASE_URI)) + assert_equal(result.links['phone_numbers'], + "{0}/PhoneNumbers".format(TRUNK_INSTANCE_BASE_URI)) + + data_dict = dict() + data_dict['FriendlyName'] = 'Test' + + request.assert_called_with( + "POST", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False, + data=data_dict + ) + + @patch('twilio.rest.resources.base.make_twilio_request') + def test_delete_trunk_instance(self, request): + resp = Mock() + resp.status_code = 204 + request.return_value = resp + + trunks = Trunks(BASE_URI, AUTH) + result = trunks.delete('TK11111111111111111111111111111111') + + assert_true(result) + + request.assert_called_with( + "DELETE", + "{0}/Trunks/TK11111111111111111111111111111111".format(BASE_URI), + auth=AUTH, + use_json_extension=False + ) diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 31c6338fea..b1963720e0 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -3,6 +3,8 @@ from .lookups import TwilioLookupsClient from .pricing import TwilioPricingClient from .task_router import TwilioTaskRouterClient +from .trunking import TwilioTrunkingClient _hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioLookupsClient, - TwilioPricingClient, TwilioTaskRouterClient] + TwilioPricingClient, TwilioTaskRouterClient, + TwilioTrunkingClient] diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index fa9aef58ec..c5cc0b442b 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -73,3 +73,14 @@ DependentPhoneNumber, DependentPhoneNumbers, ) + +from .trunking import ( + CredentialList, + CredentialLists, + IpAccessControlList, + IpAccessControlLists, + OriginationUrl, + OriginationUrls, + Trunk, + Trunks, +) diff --git a/twilio/rest/resources/trunking/__init__.py b/twilio/rest/resources/trunking/__init__.py new file mode 100644 index 0000000000..887e5d9d3f --- /dev/null +++ b/twilio/rest/resources/trunking/__init__.py @@ -0,0 +1,24 @@ +from .credential_lists import ( + CredentialList, + CredentialLists +) + +from .ip_access_control_lists import ( + IpAccessControlList, + IpAccessControlLists +) + +from .origination_urls import ( + OriginationUrl, + OriginationUrls +) + +from .phone_numbers import ( + PhoneNumber, + PhoneNumbers +) + +from .trunks import ( + Trunk, + Trunks +) diff --git a/twilio/rest/resources/trunking/credential_lists.py b/twilio/rest/resources/trunking/credential_lists.py new file mode 100644 index 0000000000..027063ef09 --- /dev/null +++ b/twilio/rest/resources/trunking/credential_lists.py @@ -0,0 +1,59 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class CredentialList(NextGenInstanceResource): + """ + A Credential List Resource. + See the `SIP Trunking API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Credential List. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Disassociates a Credential List from the trunk. + """ + return self.parent.delete_instance(self.name) + + +class CredentialLists(NextGenListResource): + """ A list of Credential List resources """ + + name = "CredentialLists" + instance = CredentialList + key = "credential_lists" + + def list(self, **kwargs): + """ + Retrieve the list of Credential List resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(CredentialLists, self).list(**kwargs) + + def create(self, credential_list_sid): + """ + Associate a Credential List with a Trunk. + + :param credential_list_sid: A human readable Credential list sid. + """ + data = { + 'credential_list_sid': credential_list_sid + } + return self.create_instance(data) + + def delete(self, credential_list_sid): + """ + Disassociates a Credential List from the Trunk. + + :param credential_list_sid: A human readable Credential list sid. + """ + return self.delete_instance(credential_list_sid) diff --git a/twilio/rest/resources/trunking/ip_access_control_lists.py b/twilio/rest/resources/trunking/ip_access_control_lists.py new file mode 100644 index 0000000000..73d2656f4e --- /dev/null +++ b/twilio/rest/resources/trunking/ip_access_control_lists.py @@ -0,0 +1,61 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class IpAccessControlList(NextGenInstanceResource): + """ + An IP Access Control List Resource. + See the `SIP Trunking API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this IP Access Control List. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Disassociate an Ip Access Control List. + """ + return self.parent.delete_instance(self.name) + + +class IpAccessControlLists(NextGenListResource): + """ A list of IP Access Control List resources """ + + name = "IpAccessControlLists" + instance = IpAccessControlList + key = "ip_access_control_lists" + + def list(self, **kwargs): + """ + Retrieve the IP Access Control List resources. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(IpAccessControlLists, self).list(**kwargs) + + def create(self, ip_access_control_list_sid): + """ + Associate an IP Access Control List with a Trunk. + + :param ip_access_control_list_sid: + A human readable IP Access Control list sid. + """ + data = { + 'ip_access_control_list_sid': ip_access_control_list_sid + } + return self.create_instance(data) + + def delete(self, ip_access_control_list_sid): + """ + Disassociate an Ip Access Control List from the Trunk. + + :param ip_access_control_list_sid: + A human readable IP Access Control list sid. + """ + return self.delete_instance(ip_access_control_list_sid) diff --git a/twilio/rest/resources/trunking/origination_urls.py b/twilio/rest/resources/trunking/origination_urls.py new file mode 100644 index 0000000000..6cb6d4a0c2 --- /dev/null +++ b/twilio/rest/resources/trunking/origination_urls.py @@ -0,0 +1,89 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class OriginationUrl(NextGenInstanceResource): + """ + An Origination URL resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Origination URL. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Credential List. + """ + + def delete(self): + """ + Delete an Origination URL. + """ + return self.parent.delete_instance(self.name) + + def update(self, **kwargs): + """ + Update an Origination URL. + """ + return self.parent.update_instance(self.name, kwargs) + + +class OriginationUrls(NextGenListResource): + """ A list of Origination URL resources """ + + name = "OriginationUrls" + instance = OriginationUrl + key = "origination_urls" + + def create(self, friendly_name, sip_url, priority, weight, enabled): + """ + Create a Origination URL. + + :param friendly_name: A human readable descriptive text, up to 64 + characters long. + :param sip_url: The SIP address you want Twilio to route your + Origination calls to. This must be a sip: schema. + :param priority: Priority ranks the importance of the URI. Value + ranges from 0 - 65535. + :param weight: Weight is used to determine the share of load when + more than one URI has the same priority. + Value ranges from 0 - 65535. + :param enabled: A boolean value indicating whether the URL is + enabled or disabled. + + """ + data = { + 'friendly_name': friendly_name, + 'sip_url': sip_url, + 'priority': priority, + 'weight': weight, + 'enabled': enabled + } + return self.create_instance(data) + + def list(self, **kwargs): + """ + Retrieve the list of Origination URL resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(OriginationUrls, self).list(**kwargs) + + def update(self, origination_url_sid, data): + """ + Update an Origination Url. + + :param origination_url_sid: A human readable Origination Url sid. + :param data: Attributes that needs to be updated. + """ + return self.update_instance(origination_url_sid, data) + + def delete(self, origination_url_sid): + """ + Delete an Origination Url. + + :param origination_url_sid: A human readable Origination Url sid. + """ + return self.delete_instance(origination_url_sid) diff --git a/twilio/rest/resources/trunking/phone_numbers.py b/twilio/rest/resources/trunking/phone_numbers.py new file mode 100644 index 0000000000..90c3188160 --- /dev/null +++ b/twilio/rest/resources/trunking/phone_numbers.py @@ -0,0 +1,59 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class PhoneNumber(NextGenInstanceResource): + """ + A Phone Number resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Phone Number. + + .. attribute:: trunk_sid + + The unique ID of the Trunk that owns this Phone Number. + """ + + def delete(self): + """ + Removes an associated Phone Number from a Trunk. + """ + return self.parent.delete_instance(self.name) + + +class PhoneNumbers(NextGenListResource): + """ A list of Phone Numbers resources """ + + name = "PhoneNumbers" + instance = PhoneNumber + key = "phone_numbers" + + def list(self, **kwargs): + """ + Retrieves the list of Phone Number resources for a given trunk sid. + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(PhoneNumbers, self).list(**kwargs) + + def create(self, phone_number_sid): + """ + Associates a Phone Number with the given Trunk. + + :param phone_number_sid: + Associates a Phone Number with the given trunk. + """ + data = { + 'phone_number_sid': phone_number_sid + } + return self.create_instance(data) + + def delete(self, sid): + """ + Disassociates a phone number from the trunk. + :param sid: Phone Number Sid + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/trunking/trunks.py b/twilio/rest/resources/trunking/trunks.py new file mode 100644 index 0000000000..024f9777b1 --- /dev/null +++ b/twilio/rest/resources/trunking/trunks.py @@ -0,0 +1,65 @@ +from .. import NextGenInstanceResource, NextGenListResource + + +class Trunk(NextGenInstanceResource): + """ + A Trunk resource. + See the `TaskRouter API reference + _` + for more information. + + .. attribute:: sid + + The unique ID for this Trunk. + """ + + def delete(self): + """ + Deletes a Trunk. + """ + return self.parent.delete_instance(self.name) + + def update(self, **kwargs): + """ + Updates a Trunk. + + """ + return self.parent.update_instance(self.name, **kwargs) + + +class Trunks(NextGenListResource): + """ A list of Trunk resources """ + + name = "Trunks" + instance = Trunk + key = "trunks" + + def list(self, **kwargs): + """ + Retrieve the list of Trunk resources. + + :param Page: The subset of results that needs to be fetched + :param PageSize: The size of the Page that needs to be fetched + """ + return super(Trunks, self).list(**kwargs) + + def create(self, **kwargs): + """ + Creates a Trunk. + """ + return self.create_instance(kwargs) + + def update(self, sid, body): + """ + Updates a Trunk. + :param sid: A human readable 34 character unique identifier + :param body: Request body + """ + return self.update_instance(sid, body) + + def delete(self, sid): + """ + Deletes a Trunk. + :param sid: A human readable 34 character unique identifier + """ + return self.delete_instance(sid) diff --git a/twilio/rest/trunking.py b/twilio/rest/trunking.py new file mode 100644 index 0000000000..ee38962e68 --- /dev/null +++ b/twilio/rest/trunking.py @@ -0,0 +1,70 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources.trunking import ( + CredentialLists, + IpAccessControlLists, + OriginationUrls, + PhoneNumbers, + Trunks +) +from twilio.rest.resources import UNSET_TIMEOUT + + +class TwilioTrunkingClient(TwilioClient): + """ + A client for accessing the Twilio Trunking API + + :param str account: Your Account SID from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://trunking.twilio.com", version="v1", + timeout=UNSET_TIMEOUT): + """ + Create a Twilio REST API client. + """ + super(TwilioTrunkingClient, self).__init__(account, token, base, + version, timeout) + self.trunk_base_uri = "{0}/{1}/Trunks".format(base, version) + + def credential_lists(self, trunk_sid): + """ + Return a :class:`CredentialList` instance + """ + credential_lists_uri = "{0}/{1}/CredentialLists".format( + self.trunk_base_uri, trunk_sid) + return CredentialLists(credential_lists_uri, self.auth, self.timeout) + + def ip_access_control_lists(self, trunk_sid): + """ + Return a :class:`IpAccessControlList` instance + """ + ip_access_control_lists_uri = "{0}/{1}/IpAccessControlLists".format( + self.trunk_base_uri, trunk_sid) + return IpAccessControlLists(ip_access_control_lists_uri, self.auth, + self.timeout) + + def origination_urls(self, trunk_sid): + """ + Return a :class:`OriginationUrls` instance + """ + origination_urls_uri = "{0}/{1}/OriginationUrls".format( + self.trunk_base_uri, trunk_sid) + return OriginationUrls(origination_urls_uri, self.auth, self.timeout) + + def phone_numbers(self, trunk_sid): + """ + Return a :class:`PhoneNumbers` instance + """ + phone_numbers_uri = "{0}/{1}/PhoneNumbers".format(self.trunk_base_uri, + trunk_sid) + return PhoneNumbers(phone_numbers_uri, self.auth, self.timeout) + + def trunks(self): + """ + Return a :class:`Trunks` instance + """ + return Trunks(self.trunk_base_uri, self.auth, self.timeout) From 08e151489f65dc5afe63e75006305f269b35761a Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 21 Oct 2015 11:37:55 -0700 Subject: [PATCH 064/115] Add IPMessaging and tests --- tests/ip_messaging/__init__.py | 0 tests/ip_messaging/test_channels.py | 52 +++++++++++++++++ tests/ip_messaging/test_credentials.py | 52 +++++++++++++++++ tests/ip_messaging/test_members.py | 52 +++++++++++++++++ tests/ip_messaging/test_messages.py | 52 +++++++++++++++++ tests/ip_messaging/test_roles.py | 36 ++++++++++++ tests/ip_messaging/test_services.py | 52 +++++++++++++++++ tests/ip_messaging/test_users.py | 52 +++++++++++++++++ .../ip_messaging/channel_instance.json | 15 +++++ .../ip_messaging/credential_instance.json | 8 +++ .../ip_messaging/member_instance.json | 9 +++ .../ip_messaging/message_instance.json | 12 ++++ .../resources/ip_messaging/role_instance.json | 11 ++++ .../ip_messaging/service_instance.json | 17 ++++++ .../resources/ip_messaging/user_instance.json | 10 ++++ twilio/rest/ip_messaging.py | 29 ++++++++++ .../rest/resources/ip_messaging/__init__.py | 34 +++++++++++ .../rest/resources/ip_messaging/channels.py | 54 ++++++++++++++++++ .../resources/ip_messaging/credentials.py | 53 +++++++++++++++++ twilio/rest/resources/ip_messaging/members.py | 49 ++++++++++++++++ .../rest/resources/ip_messaging/messages.py | 49 ++++++++++++++++ twilio/rest/resources/ip_messaging/roles.py | 37 ++++++++++++ .../rest/resources/ip_messaging/services.py | 57 +++++++++++++++++++ twilio/rest/resources/ip_messaging/users.py | 49 ++++++++++++++++ 24 files changed, 841 insertions(+) create mode 100644 tests/ip_messaging/__init__.py create mode 100644 tests/ip_messaging/test_channels.py create mode 100644 tests/ip_messaging/test_credentials.py create mode 100644 tests/ip_messaging/test_members.py create mode 100644 tests/ip_messaging/test_messages.py create mode 100644 tests/ip_messaging/test_roles.py create mode 100644 tests/ip_messaging/test_services.py create mode 100644 tests/ip_messaging/test_users.py create mode 100644 tests/resources/ip_messaging/channel_instance.json create mode 100644 tests/resources/ip_messaging/credential_instance.json create mode 100644 tests/resources/ip_messaging/member_instance.json create mode 100644 tests/resources/ip_messaging/message_instance.json create mode 100644 tests/resources/ip_messaging/role_instance.json create mode 100644 tests/resources/ip_messaging/service_instance.json create mode 100644 tests/resources/ip_messaging/user_instance.json create mode 100644 twilio/rest/ip_messaging.py create mode 100644 twilio/rest/resources/ip_messaging/__init__.py create mode 100644 twilio/rest/resources/ip_messaging/channels.py create mode 100644 twilio/rest/resources/ip_messaging/credentials.py create mode 100644 twilio/rest/resources/ip_messaging/members.py create mode 100644 twilio/rest/resources/ip_messaging/messages.py create mode 100644 twilio/rest/resources/ip_messaging/roles.py create mode 100644 twilio/rest/resources/ip_messaging/services.py create mode 100644 twilio/rest/resources/ip_messaging/users.py diff --git a/tests/ip_messaging/__init__.py b/tests/ip_messaging/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ip_messaging/test_channels.py b/tests/ip_messaging/test_channels.py new file mode 100644 index 0000000000..61faa2dfc6 --- /dev/null +++ b/tests/ip_messaging/test_channels.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Channels, Channel +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CHANNEL_SID = "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Channels(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_channel(mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Channels" % (BASE_URI) + list_resource.create(friendly_name='TestChannel') + exp_params = { + 'FriendlyName': "TestChannel" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + mock.return_value = resp + + uri = "%s/Channels/%s" % (BASE_URI, CHANNEL_SID) + list_resource.get(CHANNEL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Channel(list_resource, "CH123") + app.delete() + uri = "%s/Channels/CH123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_credentials.py b/tests/ip_messaging/test_credentials.py new file mode 100644 index 0000000000..52b2c01c86 --- /dev/null +++ b/tests/ip_messaging/test_credentials.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Credentials, Credential +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CREDENTIAL_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Credentials(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_credential(mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Credentials" % (BASE_URI) + list_resource.create('apn') + exp_params = { + 'Type': "apn" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + mock.return_value = resp + + uri = "%s/Credentials/%s" % (BASE_URI, CREDENTIAL_SID) + list_resource.get(CREDENTIAL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Credential(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Credentials/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_members.py b/tests/ip_messaging/test_members.py new file mode 100644 index 0000000000..fa05331870 --- /dev/null +++ b/tests/ip_messaging/test_members.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Members, Member +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MEMBER_SID = "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Members(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_member(mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Members" % (BASE_URI) + list_resource.create('test_identity') + exp_params = { + 'Identity': "test_identity" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + mock.return_value = resp + + uri = "%s/Members/%s" % (BASE_URI, MEMBER_SID) + list_resource.get(MEMBER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Member(list_resource, "MB123") + app.delete() + uri = "%s/Members/MB123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_messages.py b/tests/ip_messaging/test_messages.py new file mode 100644 index 0000000000..c4fd6f8a2c --- /dev/null +++ b/tests/ip_messaging/test_messages.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Messages, Message +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MESSAGE_SID = "MSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Messages(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_message(mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Messages" % (BASE_URI) + list_resource.create('TestBody') + exp_params = { + 'Body': "TestBody" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + mock.return_value = resp + + uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) + list_resource.get(MESSAGE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Message(list_resource, "MS123") + app.delete() + uri = "%s/Messages/MS123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_roles.py b/tests/ip_messaging/test_roles.py new file mode 100644 index 0000000000..8160dcb7c9 --- /dev/null +++ b/tests/ip_messaging/test_roles.py @@ -0,0 +1,36 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Roles, Role +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +ROLE_SID = "ROaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Roles(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") + mock.return_value = resp + + uri = "%s/Roles/%s" % (BASE_URI, ROLE_SID) + list_resource.get(ROLE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Role(list_resource, "RO123") + app.delete() + uri = "%s/Roles/RO123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_services.py b/tests/ip_messaging/test_services.py new file mode 100644 index 0000000000..04b24a7f14 --- /dev/null +++ b/tests/ip_messaging/test_services.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Services, Service +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +SERVICE_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Services(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_service(mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Services" % (BASE_URI) + list_resource.create('TestService') + exp_params = { + 'FriendlyName': "TestService" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + mock.return_value = resp + + uri = "%s/Services/%s" % (BASE_URI, SERVICE_SID) + list_resource.get(SERVICE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Service(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Services/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_users.py b/tests/ip_messaging/test_users.py new file mode 100644 index 0000000000..0c90bb7e2a --- /dev/null +++ b/tests/ip_messaging/test_users.py @@ -0,0 +1,52 @@ +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Users, User +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +USER_SID = "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Users(BASE_URI, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_user(mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Users" % (BASE_URI) + list_resource.create('test_id') + exp_params = { + 'Id': "test_id" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get(mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + mock.return_value = resp + + uri = "%s/Users/%s" % (BASE_URI, USER_SID) + list_resource.get(USER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete(req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = User(list_resource, "US123") + app.delete() + uri = "%s/Users/US123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/resources/ip_messaging/channel_instance.json b/tests/resources/ip_messaging/channel_instance.json new file mode 100644 index 0000000000..d713a5111b --- /dev/null +++ b/tests/resources/ip_messaging/channel_instance.json @@ -0,0 +1,15 @@ +{ + "sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "update", + "attributes": "", + "date_created": "2015-08-20T09:30:24Z", + "date_updated": "2015-08-20T09:30:24Z", + "created_by": "system", + "url": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "members": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Member", + "messages": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages" + } +} diff --git a/tests/resources/ip_messaging/credential_instance.json b/tests/resources/ip_messaging/credential_instance.json new file mode 100644 index 0000000000..9b24940277 --- /dev/null +++ b/tests/resources/ip_messaging/credential_instance.json @@ -0,0 +1,8 @@ +{ + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sid":"CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name":"MyApp APN Certificate", + "type":"apn", + "date_created":"2015-06-30T21:16:50Z", + "date_updated":"2015-07-30T21:16:50Z" +} diff --git a/tests/resources/ip_messaging/member_instance.json b/tests/resources/ip_messaging/member_instance.json new file mode 100644 index 0000000000..adc75ddcfe --- /dev/null +++ b/tests/resources/ip_messaging/member_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "channel_sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "id": "carl@twilio.com", + "role": "admin", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Members/MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/message_instance.json b/tests/resources/ip_messaging/message_instance.json new file mode 100644 index 0000000000..acbe53124f --- /dev/null +++ b/tests/resources/ip_messaging/message_instance.json @@ -0,0 +1,12 @@ +{ + "sid": "IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-07-23T20:20:10Z", + "date_updated": "2015-07-23T20:20:10Z", + "was_edited": true, + "from": "carl@twilio.com", + "to": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "body": "Hello", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Messages/IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/role_instance.json b/tests/resources/ip_messaging/role_instance.json new file mode 100644 index 0000000000..bbd604428c --- /dev/null +++ b/tests/resources/ip_messaging/role_instance.json @@ -0,0 +1,11 @@ +{ + "sid":"RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name":"Deployment Admin", + "type":"deployment", + "permissions":[ + "createChannel", + "destroyChannel" + ] +} diff --git a/tests/resources/ip_messaging/service_instance.json b/tests/resources/ip_messaging/service_instance.json new file mode 100644 index 0000000000..bc4d56e4a9 --- /dev/null +++ b/tests/resources/ip_messaging/service_instance.json @@ -0,0 +1,17 @@ +{ + "sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "TestService", + "date_created": "2015-10-21T04:15:36Z", + "date_updated": "2015-10-21T04:15:36Z", + "default_service_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "default_channel_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "typing_indicator_timeout": 5, + "webhooks": {}, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "channels": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels", + "roles": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Roles", + "users": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users" + } +} diff --git a/tests/resources/ip_messaging/user_instance.json b/tests/resources/ip_messaging/user_instance.json new file mode 100644 index 0000000000..a2326cc20c --- /dev/null +++ b/tests/resources/ip_messaging/user_instance.json @@ -0,0 +1,10 @@ +{ + "sid": "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-08-19T18:18:00Z", + "date_updated": "2015-08-19T18:18:00Z", + "identity": "carl@twilio.com", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "role_sid": null, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users/USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py new file mode 100644 index 0000000000..a52e8e37a6 --- /dev/null +++ b/twilio/rest/ip_messaging.py @@ -0,0 +1,29 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources import UNSET_TIMEOUT +from twilio.rest.resources.ip_messaging.services import Services + + +class TwilioIpMessagingClient(TwilioClient): + """ + A client for accessing the Twilio IP Messaging API. + + The Twilio IP Messaging API provides information about events. For more + information, see the + `IP Messaging API documentation `_. + + :param str account: Your Account Sid from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://ip-messaging.twilio.com", version="v1", + timeout=UNSET_TIMEOUT): + + super(TwilioIpMessagingClient, self).__init__(account, token, base, + version, timeout) + + self.version_uri = "%s/%s" % (base, version) + self.services = Services(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/resources/ip_messaging/__init__.py b/twilio/rest/resources/ip_messaging/__init__.py new file mode 100644 index 0000000000..55f60ad203 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/__init__.py @@ -0,0 +1,34 @@ +from .services import ( + Service, + Services +) + +from .channels import ( + Channel, + Channels +) + +from .members import ( + Member, + Members +) + +from .messages import ( + Message, + Messages +) + +from .roles import ( + Role, + Roles +) + +from .users import ( + User, + Users +) + +from .credentials import ( + Credential, + Credentials +) diff --git a/twilio/rest/resources/ip_messaging/channels.py b/twilio/rest/resources/ip_messaging/channels.py new file mode 100644 index 0000000000..ad6f42ce68 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/channels.py @@ -0,0 +1,54 @@ +from .members import Members +from .messages import Messages +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Channel(NextGenInstanceResource): + + subresources = [ + Members, + Messages + ] + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this channel + """ + return self.delete_instance() + + +class Channels(NextGenListResource): + + name = "Channels" + instance = Channel + + def list(self, **kwargs): + """ + Returns a page of :class:`Channel` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Channel instance resource representation. + """ + return self.get_instances(kwargs) + + def create(self, **kwargs): + """ + Create a channel. + + :param str friendly_name: The friendly name of the channel. + :param str attributes: An attribute string with arbitrary + + :return: A :class:`Channel` object + """ + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Channel + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/credentials.py b/twilio/rest/resources/ip_messaging/credentials.py new file mode 100644 index 0000000000..c861dc28d7 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/credentials.py @@ -0,0 +1,53 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Credential(NextGenInstanceResource): + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this credential + """ + return self.delete_instance() + + +class Credentials(NextGenListResource): + + name = "Credentials" + instance = Credential + + def list(self, **kwargs): + """ + Returns a page of :class:`Credential` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Credential instance resource representation. + + :param date after: Only list alerts logged after this datetime + :param date before: Only list alerts logger before this datetime + :param log_level: If 'error', only shows errors. If 'warning', + only show warnings + """ + return self.get_instances(kwargs) + + def create(self, type, **kwargs): + """ + Make a phone call to a number. + + :param str type: The type of credential + :param str friendly_name: The friendly name of the credential. + + :return: A :class:`Credential` object + """ + kwargs["type"] = type + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Credential + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/members.py b/twilio/rest/resources/ip_messaging/members.py new file mode 100644 index 0000000000..dd3b0fae90 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/members.py @@ -0,0 +1,49 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Member(NextGenInstanceResource): + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this member + """ + return self.delete_instance() + + +class Members(NextGenListResource): + + name = "Members" + instance = Member + + def list(self, **kwargs): + """ + Returns a page of :class:`Member` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Member instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, identity, **kwargs): + """ + Create a Member. + + :param str identity: The identity of the user. + :param str role: The role to assign the member. + + :return: A :class:`Member` object + """ + kwargs["identity"] = identity + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Member + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/messages.py b/twilio/rest/resources/ip_messaging/messages.py new file mode 100644 index 0000000000..d888e21004 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/messages.py @@ -0,0 +1,49 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Message(NextGenInstanceResource): + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this message + """ + return self.delete_instance() + + +class Messages(NextGenListResource): + + name = "Messages" + instance = Message + + def list(self, **kwargs): + """ + Returns a page of :class:`Message` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Message instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, body, **kwargs): + """ + Create a Message. + + :param str body: The body of the message. + :param str from: The message author's identity. + + :return: A :class:`Message` object + """ + kwargs["body"] = body + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Message + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/roles.py b/twilio/rest/resources/ip_messaging/roles.py new file mode 100644 index 0000000000..bff9147821 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/roles.py @@ -0,0 +1,37 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Role(NextGenInstanceResource): + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this role + """ + return self.delete_instance() + + +class Roles(NextGenListResource): + + name = "Roles" + instance = Role + + def list(self, **kwargs): + """ + Returns a page of :class:`Role` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Role instance resource representation. + + """ + return self.get_instances(kwargs) + + def delete(self, sid): + """ + Delete a given Role + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/services.py b/twilio/rest/resources/ip_messaging/services.py new file mode 100644 index 0000000000..92765677da --- /dev/null +++ b/twilio/rest/resources/ip_messaging/services.py @@ -0,0 +1,57 @@ +from .channels import Channels +from .roles import Roles +from .users import Users +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Service(NextGenInstanceResource): + + subresources = [ + Channels, + Roles, + Users + ] + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this service + """ + return self.delete_instance() + + +class Services(NextGenListResource): + + name = "Services" + instance = Service + + def list(self, **kwargs): + """ + Returns a page of :class:`Service` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Service instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, friendly_name, **kwargs): + """ + Create a service. + + :param str friendly_name: The friendly name for the service + + :return: A :class:`Service` object + """ + kwargs["friendly_name"] = friendly_name + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Service + """ + return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/users.py b/twilio/rest/resources/ip_messaging/users.py new file mode 100644 index 0000000000..eb5c6e3501 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/users.py @@ -0,0 +1,49 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class User(NextGenInstanceResource): + + def update(self, sid, **kwargs): + return self.update_instance(sid, kwargs) + + def delete(self): + """ + Delete this user + """ + return self.delete_instance() + + +class Users(NextGenListResource): + + name = "Users" + instance = User + + def list(self, **kwargs): + """ + Returns a page of :class:`User` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the User instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, id, **kwargs): + """ + Make a phone call to a number. + + :param str id: The identity of the user. + :param str role: The role to assign the user. + + :return: A :class:`User` object + """ + kwargs["id"] = id + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given User + """ + return self.delete_instance(sid) From 14cbaa2dd5b80bb46be81099443cd6c5b53ffce6 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Wed, 21 Oct 2015 11:40:01 -0700 Subject: [PATCH 065/115] Fix up flake violations in Conversations --- tests/conversations/__init__.py | 1 - twilio/rest/resources/conversations/__init__.py | 1 - twilio/rest/resources/conversations/conversations.py | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/conversations/__init__.py b/tests/conversations/__init__.py index 8b13789179..e69de29bb2 100644 --- a/tests/conversations/__init__.py +++ b/tests/conversations/__init__.py @@ -1 +0,0 @@ - diff --git a/twilio/rest/resources/conversations/__init__.py b/twilio/rest/resources/conversations/__init__.py index 8b13789179..e69de29bb2 100644 --- a/twilio/rest/resources/conversations/__init__.py +++ b/twilio/rest/resources/conversations/__init__.py @@ -1 +0,0 @@ - diff --git a/twilio/rest/resources/conversations/conversations.py b/twilio/rest/resources/conversations/conversations.py index 67f11c695e..8c0adec62f 100644 --- a/twilio/rest/resources/conversations/conversations.py +++ b/twilio/rest/resources/conversations/conversations.py @@ -19,7 +19,8 @@ def delete_instance(self, sid): class Conversation(NextGenInstanceResource): - """A Conversation instance representing a call between two or more participants. + """A Conversation instance representing a call + between two or more participants. .. attribute:: sid From 6f6938f1e2117896f50ddbdb2dea36172bd63c15 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 22 Oct 2015 10:06:40 -0700 Subject: [PATCH 066/115] Keys auth --- tests/resources/keys_instance.json | 6 +++ tests/resources/keys_list.json | 18 +++++++ tests/test_keys.py | 75 ++++++++++++++++++++++++++++++ twilio/rest/client.py | 2 + twilio/rest/resources/__init__.py | 2 + twilio/rest/resources/accounts.py | 2 + twilio/rest/resources/keys.py | 75 ++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+) create mode 100644 tests/resources/keys_instance.json create mode 100644 tests/resources/keys_list.json create mode 100644 tests/test_keys.py create mode 100644 twilio/rest/resources/keys.py diff --git a/tests/resources/keys_instance.json b/tests/resources/keys_instance.json new file mode 100644 index 0000000000..7f7a9fa19e --- /dev/null +++ b/tests/resources/keys_instance.json @@ -0,0 +1,6 @@ +{ + "sid":"SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name":"Fuzzy Lumpkins' SigningKey", + "date_created":"Fri, 13 Mar 2015 13:24:01 +0000", + "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000" +} \ No newline at end of file diff --git a/tests/resources/keys_list.json b/tests/resources/keys_list.json new file mode 100644 index 0000000000..81c13a922b --- /dev/null +++ b/tests/resources/keys_list.json @@ -0,0 +1,18 @@ +{ + "keys":[ + { + "sid":"SK932e398ac43ca670b1609b05ee301e8c", + "friendly_name":"Fuzzy Lumpkins' SigningKey", + "date_created":"Fri, 13 Mar 2015 13:24:01 +0000", + "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000" + } + ], + "first_page_uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0", + "uri":"/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Keys.json?PageSize=50&Page=0", + "next_page_uri":null, + "previous_page_uri":null, + "page":0, + "page_size":50, + "start":0, + "end":1 +} \ No newline at end of file diff --git a/tests/test_keys.py b/tests/test_keys.py new file mode 100644 index 0000000000..0fb474ad0a --- /dev/null +++ b/tests/test_keys.py @@ -0,0 +1,75 @@ +from mock import patch, Mock +from twilio.rest.resources.keys import Keys, Key +from tests.tools import create_mock_json + +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/{}".format(ACCOUNT_SID) +KEY_SID = "SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Keys(BASE_URL, AUTH) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_get_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + mock.return_value = resp + + url = BASE_URL + "/Keys/{}".format(KEY_SID) + list_resource.get(KEY_SID) + + mock.assert_called_with("GET", url, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_create_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + resp.status_code = 201 + mock.return_value = resp + + url = BASE_URL + "/Keys" + list_resource.create(friendly_name="Fuzzy Lumpkins' SigningKey") + params = { + 'FriendlyName': "Fuzzy Lumpkins' SigningKey" + } + + mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_update_key(mock): + resp = create_mock_json("tests/resources/keys_instance.json") + mock.return_value = resp + + url = BASE_URL + "/Keys/{}".format(KEY_SID) + list_resource.update(sid=KEY_SID, friendly_name="Fuzzy Lumpkins' SigningKey") + params = { + 'FriendlyName': "Fuzzy Lumpkins' SigningKey" + } + + mock.assert_called_with("POST", url, data=params, auth=AUTH, use_json_extension=True) + + +@patch("twilio.rest.resources.base.Resource.request") +def test_delete_key(mock): + resp = Mock() + resp.content = "" + resp.status_code = 204 + mock.return_value = resp, {} + + key = Key(list_resource, KEY_SID) + key.delete() + + url = BASE_URL + "/Keys/{}".format(KEY_SID) + mock.assert_called_with("DELETE", url) + + +@patch("twilio.rest.resources.base.make_twilio_request") +def test_list_keys(mock): + resp = create_mock_json("tests/resources/keys_list.json") + mock.return_value = resp + + url = BASE_URL + "/Keys" + list_resource.list() + + mock.assert_called_with("GET", url, params={}, auth=AUTH, use_json_extension=True) diff --git a/twilio/rest/client.py b/twilio/rest/client.py index d8a5e5d94f..bc3c45731f 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -11,6 +11,7 @@ Conferences, ConnectApps, DependentPhoneNumbers, + Keys, MediaList, Members, Messages, @@ -76,6 +77,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.sip = Sip(self.account_uri, self.auth, timeout) self.signing_keys = SigningKeys(self.account_uri, self.auth, timeout) self.tokens = Tokens(self.account_uri, self.auth, timeout) + self.keys = Keys(self.account_uri, self.auth, timeout) def participants(self, conference_sid): """ diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index 31bf3135c1..74d0210556 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -75,3 +75,5 @@ DependentPhoneNumber, DependentPhoneNumbers, ) + +from .keys import Key, Keys diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py index c38270d59c..ab4abaaccb 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -9,6 +9,7 @@ from .conferences import Conferences from .connect_apps import ConnectApps, AuthorizedConnectApps from .queues import Queues +from twilio.rest.resources.keys import Keys from .usage import UsageRecords, UsageTriggers from .messages import Messages from .media import MediaList @@ -44,6 +45,7 @@ class Account(InstanceResource): Messages, SigningKeys, Sip, + Keys, ] def update(self, **kwargs): diff --git a/twilio/rest/resources/keys.py b/twilio/rest/resources/keys.py new file mode 100644 index 0000000000..f68698232c --- /dev/null +++ b/twilio/rest/resources/keys.py @@ -0,0 +1,75 @@ +from twilio.rest.resources.base import InstanceResource, ListResource + + +class Key(InstanceResource): + """ + A key resource. + See https://www.twilio.com/docs/api/rest-keys + + .. attribute:: sid + + The unique ID for this key. + + .. attribute:: friendly_name + + A human-readable description of this key. + + .. attribute:: secret + + This key's secret. + + .. attribute:: date_created + + The date this key was created, given as UTC in ISO 8601 format. + + .. attribute:: date_updated + + The date this singing key was last updated, given as UTC in ISO 8601 + format. + """ + + def update(self, **kwargs): + """ + Update this key + """ + return self.parent.update(self.name, **kwargs) + + def delete(self): + """ + Delete this key + """ + return self.parent.delete(self.name) + + +class Keys(ListResource): + name = "Keys" + key = "keys" + instance = Key + + def create(self, **kwargs): + """ + Create a :class:`Key` with any of these optional parameters. + + :param friendly_name: A human readable description of the signing key. + """ + return self.create_instance(kwargs) + + def update(self, sid, **kwargs): + """ + Update a :class:`Key` with the given parameters. + + All the parameters are describe above in :meth:`create` + """ + return self.update_instance(sid, kwargs) + + def delete(self, sid): + """ + Delete a :class:`Key` + """ + return self.delete_instance(sid) + + def list(self, **kwargs): + """ + Returns a page of :class:`Key` resources as a list + """ + return self.get_instances(kwargs) From 2c58ec35a3bcbd93b2065bbf145b54cec198a095 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 22 Oct 2015 10:08:26 -0700 Subject: [PATCH 067/115] Update import --- twilio/rest/resources/accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py index ab4abaaccb..0fafd507dd 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -9,7 +9,7 @@ from .conferences import Conferences from .connect_apps import ConnectApps, AuthorizedConnectApps from .queues import Queues -from twilio.rest.resources.keys import Keys +from .keys import Keys from .usage import UsageRecords, UsageTriggers from .messages import Messages from .media import MediaList From 30fd5290251fb880a51445b3a6794d2c2bc29b38 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 22 Oct 2015 10:48:48 -0700 Subject: [PATCH 068/115] Add a new line --- tests/resources/keys_instance.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/keys_instance.json b/tests/resources/keys_instance.json index 7f7a9fa19e..86459b71ec 100644 --- a/tests/resources/keys_instance.json +++ b/tests/resources/keys_instance.json @@ -3,4 +3,4 @@ "friendly_name":"Fuzzy Lumpkins' SigningKey", "date_created":"Fri, 13 Mar 2015 13:24:01 +0000", "date_updated":"Fri, 13 Mar 2015 13:24:01 +0000" -} \ No newline at end of file +} From 8f84229bb93baa719a25da6406a8046debabdbf9 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 22 Oct 2015 10:48:59 -0700 Subject: [PATCH 069/115] Add a new line --- tests/resources/keys_list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/keys_list.json b/tests/resources/keys_list.json index 81c13a922b..e82306cca8 100644 --- a/tests/resources/keys_list.json +++ b/tests/resources/keys_list.json @@ -15,4 +15,4 @@ "page_size":50, "start":0, "end":1 -} \ No newline at end of file +} From 0e7e73d750748571256c2bf3b278b5df41ccb789 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 29 Oct 2015 13:52:11 -0700 Subject: [PATCH 070/115] Fix edge build --- requirements.txt | 1 + tests/test_access_token.py | 9 +++++++-- tests/test_keys.py | 8 ++++---- tests/test_make_request.py | 9 ++++++--- twilio/access_token.py | 13 +++++++------ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/requirements.txt b/requirements.txt index bc3d0567a8..c6d498c507 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ six httplib2 socksipy-branch +pyjwt diff --git a/tests/test_access_token.py b/tests/test_access_token.py index c5c56dc120..3f1379ea03 100644 --- a/tests/test_access_token.py +++ b/tests/test_access_token.py @@ -1,6 +1,6 @@ import unittest -from nose.tools import assert_equal, assert_is_not_none +from nose.tools import assert_equal from twilio.jwt import decode from twilio.access_token import AccessToken @@ -8,6 +8,11 @@ SIGNING_KEY_SID = 'SK123' +# python2.6 support +def assert_is_not_none(obj): + assert obj is not None, '%r is None' % obj + + class AccessTokenTest(unittest.TestCase): def _validate_claims(self, payload): assert_equal(SIGNING_KEY_SID, payload['iss']) @@ -16,7 +21,7 @@ def _validate_claims(self, payload): assert_is_not_none(payload['exp']) assert_equal(payload['nbf'] + 3600, payload['exp']) assert_is_not_none(payload['jti']) - assert_equal('{}-{}'.format(payload['iss'], payload['nbf']), + assert_equal('{0}-{1}'.format(payload['iss'], payload['nbf']), payload['jti']) assert_is_not_none(payload['grants']) diff --git a/tests/test_keys.py b/tests/test_keys.py index 0fb474ad0a..e57ad65ea1 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -4,7 +4,7 @@ ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" AUTH = (ACCOUNT_SID, "token") -BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/{}".format(ACCOUNT_SID) +BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/{0}".format(ACCOUNT_SID) KEY_SID = "SKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" list_resource = Keys(BASE_URL, AUTH) @@ -15,7 +15,7 @@ def test_get_key(mock): resp = create_mock_json("tests/resources/keys_instance.json") mock.return_value = resp - url = BASE_URL + "/Keys/{}".format(KEY_SID) + url = BASE_URL + "/Keys/{0}".format(KEY_SID) list_resource.get(KEY_SID) mock.assert_called_with("GET", url, auth=AUTH, use_json_extension=True) @@ -41,7 +41,7 @@ def test_update_key(mock): resp = create_mock_json("tests/resources/keys_instance.json") mock.return_value = resp - url = BASE_URL + "/Keys/{}".format(KEY_SID) + url = BASE_URL + "/Keys/{0}".format(KEY_SID) list_resource.update(sid=KEY_SID, friendly_name="Fuzzy Lumpkins' SigningKey") params = { 'FriendlyName': "Fuzzy Lumpkins' SigningKey" @@ -60,7 +60,7 @@ def test_delete_key(mock): key = Key(list_resource, KEY_SID) key.delete() - url = BASE_URL + "/Keys/{}".format(KEY_SID) + url = BASE_URL + "/Keys/{0}".format(KEY_SID) mock.assert_called_with("DELETE", url) diff --git a/tests/test_make_request.py b/tests/test_make_request.py index 3f2d7f186c..6348fea500 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -90,6 +90,11 @@ def test_make_request_basic_auth(self, mock_request, mock_response): }) mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] make_request('GET', 'http://httpbin.org/get', auth=('AC123', 'AuthToken')) + + auth = "{0}:{1}".format('AC123', 'AuthToken') + encoded_auth = auth.encode('utf-8') + b64_auth = base64.b64encode(encoded_auth) + mock_request.assert_called_with( ANY, '/get', @@ -97,9 +102,7 @@ def test_make_request_basic_auth(self, mock_request, mock_response): None, { 'accept-encoding': 'gzip, deflate', - 'authorization': 'Basic {}'.format( - base64.b64encode("{}:{}".format('AC123', 'AuthToken')) - ), + 'authorization': 'Basic {0}'.format(b64_auth.decode('utf-8')), 'user-agent': ANY, } ) diff --git a/twilio/access_token.py b/twilio/access_token.py index 6c29b7ef9f..1ff86be43c 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -1,5 +1,4 @@ import time - import jwt ALL = '*' @@ -34,7 +33,7 @@ def add_grant(self, resource, actions=ALL): return self def add_rest_grant(self, uri, actions=ALL): - resource = 'https://api.twilio.com/2010-04-01/Accounts/{}/{}'.format( + resource = 'https://api.twilio.com/2010-04-01/Accounts/{0}/{1}'.format( self.account_sid, uri.lstrip('/'), ) @@ -42,8 +41,10 @@ def add_rest_grant(self, uri, actions=ALL): def add_endpoint_grant(self, endpoint, actions=None): actions = actions or [CLIENT_LISTEN, CLIENT_INVITE] - resource = 'sip:{}@{}.endpoint.twilio.com'.format(endpoint, - self.account_sid) + resource = 'sip:{0}@{1}.endpoint.twilio.com'.format( + endpoint, + self.account_sid + ) return self.add_grant(resource, actions) def enable_nts(self): @@ -55,7 +56,7 @@ def to_jwt(self): "cty": "twilio-sat;v=1" } payload = { - "jti": '{}-{}'.format(self.signing_key_sid, now), + "jti": '{0}-{1}'.format(self.signing_key_sid, now), "iss": self.signing_key_sid, "sub": self.account_sid, "nbf": now, @@ -66,4 +67,4 @@ def to_jwt(self): return jwt.encode(payload, self.secret, headers=headers) def __str__(self): - return self.to_jwt() + return self.to_jwt().decode('utf-8') From cf5a8b739063b0621f74f1d37c049fcc0bc5fc77 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Thu, 29 Oct 2015 16:21:09 -0700 Subject: [PATCH 071/115] Bump to version 4.7.0 --- CHANGES.md | 5 +++++ twilio/version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 94e2225e03..60e7e3edeb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ Here you can see the full list of changes between each twilio-python release. Version 4.6.0 ------------- +- Add /Keys endpoint + +Version 4.6.0 +------------- + Released September 23, 2015: - Allow fetching TaskRouter reservations by Worker diff --git a/twilio/version.py b/twilio/version.py index 9e8a4df44c..4831bcd233 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '6', '0') +__version_info__ = ('4', '7', '0') __version__ = '.'.join(__version_info__) From 0978a56636d586b58b88ea127aff5afb8200ef4b Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 2 Nov 2015 10:56:46 -0800 Subject: [PATCH 072/115] Bump version for SMS pricing release --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 60e7e3edeb..94488f1f99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,12 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.7.0 +------------- + +- Add support for SMS pricing + + Version 4.6.0 ------------- From e13a621c2b38a5c6e4ffc8acabdf42215a77d82d Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Mon, 2 Nov 2015 12:57:03 -0800 Subject: [PATCH 073/115] Change near_lat_long to be formatted string, fixes #216 --- twilio/rest/resources/phone_numbers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/twilio/rest/resources/phone_numbers.py b/twilio/rest/resources/phone_numbers.py index 91b0042210..16557d3c21 100644 --- a/twilio/rest/resources/phone_numbers.py +++ b/twilio/rest/resources/phone_numbers.py @@ -325,7 +325,8 @@ def search(self, **kwargs): :param str region: When searching the US, show numbers in this state :param str postal_code: Only show numbers in this area code :param str rate_center: US only. - :param tuple near_lat_long: Find close numbers within Distance miles. + :param str near_lat_long: Find close numbers within Distance miles. + Should be string of format "{lat},{long}" :param integer distance: Search radius for a Near- query in miles. :param boolean beta: Whether to include numbers new to the Twilio platform. From c0c79fddc5de8fcecd364428a7db530485efcb5a Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Mon, 2 Nov 2015 13:08:06 -0800 Subject: [PATCH 074/115] Add Python 3.5 to Travis build matrix --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 250dcb8c18..a0d03e2a4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - '3.2' - '3.3' - '3.4' + - '3.5' install: - pip install . --use-mirrors - pip install -r requirements.txt --use-mirrors From 8374ba200548adab47f8422a8aa502420b67eccd Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Mon, 2 Nov 2015 13:24:56 -0800 Subject: [PATCH 075/115] Remove --use-mirrors to support 3.5 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a0d03e2a4a..fead52f55d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ python: - '3.4' - '3.5' install: - - pip install . --use-mirrors - - pip install -r requirements.txt --use-mirrors - - pip install -r tests/requirements.txt --use-mirrors + - pip install . + - pip install -r requirements.txt + - pip install -r tests/requirements.txt script: - flake8 --ignore=F401 twilio - flake8 --ignore=E123,E126,E128,E501 tests From 7b11d5ffe962b0596214cad1970c35db6fec49fe Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 2 Nov 2015 15:42:26 -0800 Subject: [PATCH 076/115] Revert "Merge remote-tracking branch 'origin/signal-beta' into edge" This reverts commit 6bf0e94e2113e3564f7110fbdbe376a89d5d06b5, reversing changes made to 07bf63985cd63075ae44216cd4d39e40c7318e22. --- tests/requirements.txt | 1 - tests/test_access_token.py | 81 ---------- tests/test_make_request.py | 216 +++++++++++--------------- tests/test_signing_keys.py | 13 -- twilio/access_token.py | 70 --------- twilio/jwt/__init__.py | 7 +- twilio/rest/client.py | 2 - twilio/rest/resources/__init__.py | 2 - twilio/rest/resources/accounts.py | 2 - twilio/rest/resources/base.py | 3 +- twilio/rest/resources/signing_keys.py | 75 --------- 11 files changed, 98 insertions(+), 374 deletions(-) delete mode 100644 tests/test_access_token.py delete mode 100644 tests/test_signing_keys.py delete mode 100644 twilio/access_token.py delete mode 100644 twilio/rest/resources/signing_keys.py diff --git a/tests/requirements.txt b/tests/requirements.txt index 5f04d6e39f..9262910394 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,4 @@ sphinx -httplib2==0.8 mock==0.8.0 nose coverage diff --git a/tests/test_access_token.py b/tests/test_access_token.py deleted file mode 100644 index 3f1379ea03..0000000000 --- a/tests/test_access_token.py +++ /dev/null @@ -1,81 +0,0 @@ -import unittest - -from nose.tools import assert_equal -from twilio.jwt import decode -from twilio.access_token import AccessToken - -ACCOUNT_SID = 'AC123' -SIGNING_KEY_SID = 'SK123' - - -# python2.6 support -def assert_is_not_none(obj): - assert obj is not None, '%r is None' % obj - - -class AccessTokenTest(unittest.TestCase): - def _validate_claims(self, payload): - assert_equal(SIGNING_KEY_SID, payload['iss']) - assert_equal(ACCOUNT_SID, payload['sub']) - assert_is_not_none(payload['nbf']) - assert_is_not_none(payload['exp']) - assert_equal(payload['nbf'] + 3600, payload['exp']) - assert_is_not_none(payload['jti']) - assert_equal('{0}-{1}'.format(payload['iss'], payload['nbf']), - payload['jti']) - assert_is_not_none(payload['grants']) - - def test_empty_grants(self): - scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') - token = str(scat) - assert_is_not_none(token) - payload = decode(token, 'secret') - self._validate_claims(payload) - assert_equal([], payload['grants']) - - def test_single_grant(self): - scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') - scat.add_grant('https://api.twilio.com/**') - token = str(scat) - assert_is_not_none(token) - payload = decode(token, 'secret') - self._validate_claims(payload) - assert_equal(1, len(payload['grants'])) - assert_equal('https://api.twilio.com/**', payload['grants'][0]['res']) - assert_equal(['*'], payload['grants'][0]['act']) - - def test_endpoint_grant(self): - scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') - scat.add_endpoint_grant('bob') - token = str(scat) - assert_is_not_none(token) - payload = decode(token, 'secret') - self._validate_claims(payload) - assert_equal(1, len(payload['grants'])) - assert_equal('sip:bob@AC123.endpoint.twilio.com', - payload['grants'][0]['res']) - assert_equal(['listen', 'invite'], payload['grants'][0]['act']) - - def test_rest_grant(self): - scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') - scat.add_rest_grant('/Apps') - token = str(scat) - assert_is_not_none(token) - payload = decode(token, 'secret') - self._validate_claims(payload) - assert_equal(1, len(payload['grants'])) - assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Apps', - payload['grants'][0]['res']) - assert_equal(['*'], payload['grants'][0]['act']) - - def test_enable_nts(self): - scat = AccessToken(SIGNING_KEY_SID, ACCOUNT_SID, 'secret') - scat.enable_nts() - token = str(scat) - assert_is_not_none(token) - payload = decode(token, 'secret') - self._validate_claims(payload) - assert_equal(1, len(payload['grants'])) - assert_equal('https://api.twilio.com/2010-04-01/Accounts/AC123/Tokens.json', - payload['grants'][0]['res']) - assert_equal(['POST'], payload['grants'][0]['act']) diff --git a/tests/test_make_request.py b/tests/test_make_request.py index 6348fea500..d1e4d5d800 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -3,21 +3,16 @@ Uses the awesome httpbin.org to validate responses """ -import base64 import platform -import unittest -from httplib2 import Response +import twilio from nose.tools import assert_equal, raises from mock import patch, Mock, ANY - -import twilio from twilio.rest.exceptions import TwilioRestException from twilio.rest.resources.base import make_request, make_twilio_request from twilio.rest.resources.connection import Connection from twilio.rest.resources.connection import PROXY_TYPE_SOCKS5 - get_headers = { "User-Agent": "twilio-python/{version} (Python {python_version})".format( version=twilio.__version__, @@ -31,118 +26,97 @@ post_headers["Content-Type"] = "application/x-www-form-urlencoded" -class MakeRequestTest(unittest.TestCase): - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http') - def test_get_params(self, http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get", params={"hey": "you"}) - http.request.assert_called_with("http://httpbin.org/get?hey=you", "GET", - body=None, headers=None) - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http') - def test_get_extra_params(self, http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get?foo=bar", params={"hey": "you"}) - http.request.assert_called_with("http://httpbin.org/get?foo=bar&hey=you", "GET", - body=None, headers=None) - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http') - def test_resp_uri(self, http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request("GET", "http://httpbin.org/get") - http.request.assert_called_with("http://httpbin.org/get", "GET", - body=None, headers=None) - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http') - def test_sequence_data(self, http_mock, response_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - make_request( - "POST", - "http://httpbin.org/post", - data={"a_list": ["here", "is", "some", "stuff"]}, - ) - http.request.assert_called_with( - "http://httpbin.org/post", - "POST", - body="a_list=here&a_list=is&a_list=some&a_list=stuff", - headers=None, - ) - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http._conn_request') - def test_make_request_basic_auth(self, mock_request, mock_response): - response = Response({ - 'status': '401', - 'WWW-Authenticate': 'Basic realm="Twilio API"' - }) - mock_request.side_effect = [(response, Mock()), (Mock(), Mock())] - make_request('GET', 'http://httpbin.org/get', auth=('AC123', 'AuthToken')) - - auth = "{0}:{1}".format('AC123', 'AuthToken') - encoded_auth = auth.encode('utf-8') - b64_auth = base64.b64encode(encoded_auth) - - mock_request.assert_called_with( - ANY, - '/get', - 'GET', - None, - { - 'accept-encoding': 'gzip, deflate', - 'authorization': 'Basic {0}'.format(b64_auth.decode('utf-8')), - 'user-agent': ANY, - } - ) - - @patch('twilio.rest.resources.base.make_request') - def test_make_twilio_request_headers(self, mock): - url = "http://random/url" - make_twilio_request("POST", url, use_json_extension=True) - mock.assert_called_with("POST", "http://random/url.json", - headers=post_headers) - - @raises(TwilioRestException) - @patch('twilio.rest.resources.base.make_request') - def test_make_twilio_request_bad_data(self, mock): - resp = Mock() - resp.ok = False - resp.return_value = "error" - mock.return_value = resp - - url = "http://random/url" - make_twilio_request("POST", url) - mock.assert_called_with("POST", "http://random/url.json", - headers=post_headers) - - @patch('twilio.rest.resources.base.Response') - @patch('httplib2.Http') - def test_proxy_info(self, http_mock, resp_mock): - http = Mock() - http.request.return_value = (Mock(), Mock()) - http_mock.return_value = http - Connection.set_proxy_info( - 'example.com', - 8080, - proxy_type=PROXY_TYPE_SOCKS5, - ) - make_request("GET", "http://httpbin.org/get") - http_mock.assert_called_with(timeout=None, ca_certs=ANY, proxy_info=ANY) - http.request.assert_called_with("http://httpbin.org/get", "GET", - body=None, headers=None) - proxy_info = http_mock.call_args[1]['proxy_info'] - assert_equal(proxy_info.proxy_host, 'example.com') - assert_equal(proxy_info.proxy_port, 8080) - assert_equal(proxy_info.proxy_type, PROXY_TYPE_SOCKS5) +@patch('twilio.rest.resources.base.Response') +@patch('httplib2.Http') +def test_get_params(http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get", params={"hey": "you"}) + http.request.assert_called_with("http://httpbin.org/get?hey=you", "GET", + body=None, headers=None) + + +@patch('twilio.rest.resources.base.Response') +@patch('httplib2.Http') +def test_get_extra_params(http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get?foo=bar", params={"hey": "you"}) + http.request.assert_called_with("http://httpbin.org/get?foo=bar&hey=you", "GET", + body=None, headers=None) + + +@patch('twilio.rest.resources.base.Response') +@patch('httplib2.Http') +def test_resp_uri(http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request("GET", "http://httpbin.org/get") + http.request.assert_called_with("http://httpbin.org/get", "GET", + body=None, headers=None) + + +@patch('twilio.rest.resources.base.Response') +@patch('httplib2.Http') +def test_sequence_data(http_mock, response_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + make_request( + "POST", + "http://httpbin.org/post", + data={"a_list": ["here", "is", "some", "stuff"]}, + ) + http.request.assert_called_with( + "http://httpbin.org/post", + "POST", + body="a_list=here&a_list=is&a_list=some&a_list=stuff", + headers=None, + ) + + +@patch('twilio.rest.resources.base.make_request') +def test_make_twilio_request_headers(mock): + url = "http://random/url" + make_twilio_request("POST", url, use_json_extension=True) + mock.assert_called_with("POST", "http://random/url.json", + headers=post_headers) + + +@raises(TwilioRestException) +@patch('twilio.rest.resources.base.make_request') +def test_make_twilio_request_bad_data(mock): + resp = Mock() + resp.ok = False + resp.return_value = "error" + mock.return_value = resp + + url = "http://random/url" + make_twilio_request("POST", url) + mock.assert_called_with("POST", "http://random/url.json", + headers=post_headers) + + +@patch('twilio.rest.resources.base.Response') +@patch('httplib2.Http') +def test_proxy_info(http_mock, resp_mock): + http = Mock() + http.request.return_value = (Mock(), Mock()) + http_mock.return_value = http + Connection.set_proxy_info( + 'example.com', + 8080, + proxy_type=PROXY_TYPE_SOCKS5, + ) + make_request("GET", "http://httpbin.org/get") + http_mock.assert_called_with(timeout=None, ca_certs=ANY, proxy_info=ANY) + http.request.assert_called_with("http://httpbin.org/get", "GET", + body=None, headers=None) + proxy_info = http_mock.call_args[1]['proxy_info'] + assert_equal(proxy_info.proxy_host, 'example.com') + assert_equal(proxy_info.proxy_port, 8080) + assert_equal(proxy_info.proxy_type, PROXY_TYPE_SOCKS5) +>>>>>>> parent of 6bf0e94... Merge remote-tracking branch 'origin/signal-beta' into edge diff --git a/tests/test_signing_keys.py b/tests/test_signing_keys.py deleted file mode 100644 index fa0585e29d..0000000000 --- a/tests/test_signing_keys.py +++ /dev/null @@ -1,13 +0,0 @@ -import unittest -from nose.tools import assert_raises -from twilio.rest.resources.signing_keys import SigningKeys - - -class SigningKeysTest(unittest.TestCase): - - def test_list(self): - """ - Tests the Error part - """ - keys = SigningKeys(self, [], {}) - assert_raises(AttributeError, keys.list) diff --git a/twilio/access_token.py b/twilio/access_token.py deleted file mode 100644 index 1ff86be43c..0000000000 --- a/twilio/access_token.py +++ /dev/null @@ -1,70 +0,0 @@ -import time -import jwt - -ALL = '*' - -# HTTP Actions -HTTP_DELETE = 'DELETE' -HTTP_GET = 'GET' -HTTP_POST = 'POST' -HTTP_PUT = 'PUT' - -# Client Actions -CLIENT_LISTEN = 'listen' -CLIENT_INVITE = 'invite' - - -class AccessToken(object): - def __init__(self, signing_key_sid, account_sid, secret, ttl=3600): - self.signing_key_sid = signing_key_sid - self.account_sid = account_sid - self.secret = secret - self.ttl = ttl - self.grants = [] - - def add_grant(self, resource, actions=ALL): - if not isinstance(actions, list): - actions = [actions] - - self.grants.append({ - 'res': resource, - 'act': actions, - }) - return self - - def add_rest_grant(self, uri, actions=ALL): - resource = 'https://api.twilio.com/2010-04-01/Accounts/{0}/{1}'.format( - self.account_sid, - uri.lstrip('/'), - ) - return self.add_grant(resource, actions) - - def add_endpoint_grant(self, endpoint, actions=None): - actions = actions or [CLIENT_LISTEN, CLIENT_INVITE] - resource = 'sip:{0}@{1}.endpoint.twilio.com'.format( - endpoint, - self.account_sid - ) - return self.add_grant(resource, actions) - - def enable_nts(self): - return self.add_rest_grant('/Tokens.json', HTTP_POST) - - def to_jwt(self): - now = int(time.time()) - headers = { - "cty": "twilio-sat;v=1" - } - payload = { - "jti": '{0}-{1}'.format(self.signing_key_sid, now), - "iss": self.signing_key_sid, - "sub": self.account_sid, - "nbf": now, - "exp": now + self.ttl, - "grants": self.grants - } - - return jwt.encode(payload, self.secret, headers=headers) - - def __str__(self): - return self.to_jwt().decode('utf-8') diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index f87ba5365f..edb4062433 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -6,8 +6,7 @@ import base64 import hashlib import hmac - -from six import b +from six import text_type, b # default text to binary representation conversion @@ -42,11 +41,9 @@ def base64url_encode(input): return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '') -def encode(payload, key, algorithm='HS256', headers=None): +def encode(payload, key, algorithm='HS256'): segments = [] header = {"typ": "JWT", "alg": algorithm} - if headers: - header.update(headers) segments.append(base64url_encode(binary(json.dumps(header)))) segments.append(base64url_encode(binary(json.dumps(payload)))) sign_input = '.'.join(segments) diff --git a/twilio/rest/client.py b/twilio/rest/client.py index bc3c45731f..27f09067c5 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -21,7 +21,6 @@ Queues, Recordings, Sandboxes, - SigningKeys, Sip, Sms, Tokens, @@ -75,7 +74,6 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.messages = Messages(self.account_uri, self.auth, timeout) self.media = MediaList(self.account_uri, self.auth, timeout) self.sip = Sip(self.account_uri, self.auth, timeout) - self.signing_keys = SigningKeys(self.account_uri, self.auth, timeout) self.tokens = Tokens(self.account_uri, self.auth, timeout) self.keys = Keys(self.account_uri, self.auth, timeout) diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index 74d0210556..cf25e73bd2 100644 --- a/twilio/rest/resources/__init__.py +++ b/twilio/rest/resources/__init__.py @@ -44,8 +44,6 @@ from .media import Media, MediaList -from .signing_keys import SigningKey, SigningKeys - from .sip import Sip from .task_router import ( diff --git a/twilio/rest/resources/accounts.py b/twilio/rest/resources/accounts.py index 0fafd507dd..78c2f65370 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -14,7 +14,6 @@ from .messages import Messages from .media import MediaList from .sip import Sip -from .signing_keys import SigningKeys class Account(InstanceResource): @@ -43,7 +42,6 @@ class Account(InstanceResource): UsageTriggers, MediaList, Messages, - SigningKeys, Sip, Keys, ] diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index 5a16ec5c12..67285c11cc 100644 --- a/twilio/rest/resources/base.py +++ b/twilio/rest/resources/base.py @@ -8,10 +8,10 @@ binary_type, iteritems ) - from ...compat import urlencode from ...compat import urlparse from ...compat import urlunparse + from ... import __version__ from ...exceptions import TwilioException from ..exceptions import TwilioRestException @@ -24,7 +24,6 @@ UNSET_TIMEOUT, ) - logger = logging.getLogger('twilio') diff --git a/twilio/rest/resources/signing_keys.py b/twilio/rest/resources/signing_keys.py deleted file mode 100644 index 8eaec3eae6..0000000000 --- a/twilio/rest/resources/signing_keys.py +++ /dev/null @@ -1,75 +0,0 @@ -from twilio.rest.resources.base import InstanceResource, ListResource - - -class SigningKey(InstanceResource): - """ - A signing key resource. - See https://www.twilio.com/docs/api/rest/signing-keys - - .. attribute:: sid - - The unique ID for this signing key. - - .. attribute:: friendly_name - - A human-readable description of this signing key. - - .. attribute:: secret - - This signing key's secret. - - .. attribute:: date_created - - The date this signing key was created, given as UTC in ISO 8601 format. - - .. attribute:: date_updated - - The date this singing key was last updated, given as UTC in ISO 8601 - format. - """ - - def update(self, **kwargs): - """ - Update this signing key - """ - return self.parent.update(self.name, **kwargs) - - def delete(self): - """ - Delete this signing key - """ - return self.parent.delete(self.name) - - -class SigningKeys(ListResource): - name = "SigningKeys" - key = "signing_keys" - instance = SigningKey - - def create(self, **kwargs): - """ - Create a :class:`SigningKey` with any of these optional parameters. - - :param friendly_name: A human readable description of the signing key. - """ - return self.create_instance(kwargs) - - def update(self, sid, **kwargs): - """ - Update a :class:`SigningKey` with the given parameters. - - All the parameters are describe above in :meth:`create` - """ - return self.update_instance(sid, kwargs) - - def delete(self, sid): - """ - Delete a :class:`SigningKey` - """ - return self.delete_instance(sid) - - def list(self, **kw): - """ - List is not supported, hence raises an Error - """ - raise AttributeError("SigningKeys do not support lists()") From 20f2ed63f2e472001a5a44d062046b95b052bc09 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 2 Nov 2015 15:44:32 -0800 Subject: [PATCH 077/115] Revert "Merge branch 'conversations' into edge" This reverts commit ce08b3dead4d28d1261f703c4f729995a85d1a25, reversing changes made to 9f62d187176bfa6f7161d1166c8a984c7fea5d89. --- tests/conversations/__init__.py | 0 tests/conversations/test_client.py | 14 ----- tests/conversations/test_conversations.py | 46 --------------- tests/conversations/test_participants.py | 38 ------------ tests/ip_messaging/__init__.py | 0 tests/ip_messaging/test_channels.py | 52 ----------------- tests/ip_messaging/test_credentials.py | 52 ----------------- tests/ip_messaging/test_members.py | 52 ----------------- tests/ip_messaging/test_messages.py | 52 ----------------- tests/ip_messaging/test_roles.py | 36 ------------ tests/ip_messaging/test_services.py | 52 ----------------- tests/ip_messaging/test_users.py | 52 ----------------- .../conversations/conversation_instance.json | 13 ----- .../conversations/conversation_list.json | 39 ------------- .../conversations/participant_instance.json | 12 ---- .../conversations/participant_list.json | 37 ------------ .../ip_messaging/channel_instance.json | 15 ----- .../ip_messaging/credential_instance.json | 8 --- .../ip_messaging/member_instance.json | 9 --- .../ip_messaging/message_instance.json | 12 ---- .../resources/ip_messaging/role_instance.json | 11 ---- .../ip_messaging/service_instance.json | 17 ------ .../resources/ip_messaging/user_instance.json | 10 ---- twilio/rest/conversations.py | 28 --------- twilio/rest/ip_messaging.py | 29 ---------- twilio/rest/resources/base.py | 2 +- .../rest/resources/conversations/__init__.py | 0 .../resources/conversations/conversations.py | 58 ------------------- .../resources/conversations/participants.py | 34 ----------- .../rest/resources/ip_messaging/__init__.py | 34 ----------- .../rest/resources/ip_messaging/channels.py | 54 ----------------- .../resources/ip_messaging/credentials.py | 53 ----------------- twilio/rest/resources/ip_messaging/members.py | 49 ---------------- .../rest/resources/ip_messaging/messages.py | 49 ---------------- twilio/rest/resources/ip_messaging/roles.py | 37 ------------ .../rest/resources/ip_messaging/services.py | 57 ------------------ twilio/rest/resources/ip_messaging/users.py | 49 ---------------- 37 files changed, 1 insertion(+), 1161 deletions(-) delete mode 100644 tests/conversations/__init__.py delete mode 100644 tests/conversations/test_client.py delete mode 100644 tests/conversations/test_conversations.py delete mode 100644 tests/conversations/test_participants.py delete mode 100644 tests/ip_messaging/__init__.py delete mode 100644 tests/ip_messaging/test_channels.py delete mode 100644 tests/ip_messaging/test_credentials.py delete mode 100644 tests/ip_messaging/test_members.py delete mode 100644 tests/ip_messaging/test_messages.py delete mode 100644 tests/ip_messaging/test_roles.py delete mode 100644 tests/ip_messaging/test_services.py delete mode 100644 tests/ip_messaging/test_users.py delete mode 100644 tests/resources/conversations/conversation_instance.json delete mode 100644 tests/resources/conversations/conversation_list.json delete mode 100644 tests/resources/conversations/participant_instance.json delete mode 100644 tests/resources/conversations/participant_list.json delete mode 100644 tests/resources/ip_messaging/channel_instance.json delete mode 100644 tests/resources/ip_messaging/credential_instance.json delete mode 100644 tests/resources/ip_messaging/member_instance.json delete mode 100644 tests/resources/ip_messaging/message_instance.json delete mode 100644 tests/resources/ip_messaging/role_instance.json delete mode 100644 tests/resources/ip_messaging/service_instance.json delete mode 100644 tests/resources/ip_messaging/user_instance.json delete mode 100644 twilio/rest/conversations.py delete mode 100644 twilio/rest/ip_messaging.py delete mode 100644 twilio/rest/resources/conversations/__init__.py delete mode 100644 twilio/rest/resources/conversations/conversations.py delete mode 100644 twilio/rest/resources/conversations/participants.py delete mode 100644 twilio/rest/resources/ip_messaging/__init__.py delete mode 100644 twilio/rest/resources/ip_messaging/channels.py delete mode 100644 twilio/rest/resources/ip_messaging/credentials.py delete mode 100644 twilio/rest/resources/ip_messaging/members.py delete mode 100644 twilio/rest/resources/ip_messaging/messages.py delete mode 100644 twilio/rest/resources/ip_messaging/roles.py delete mode 100644 twilio/rest/resources/ip_messaging/services.py delete mode 100644 twilio/rest/resources/ip_messaging/users.py diff --git a/tests/conversations/__init__.py b/tests/conversations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/conversations/test_client.py b/tests/conversations/test_client.py deleted file mode 100644 index d19545c937..0000000000 --- a/tests/conversations/test_client.py +++ /dev/null @@ -1,14 +0,0 @@ -from mock import patch - -from tests.tools import create_mock_json -from twilio.rest.conversations import TwilioConversationsClient - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_conversations(mock): - client = TwilioConversationsClient("ACCOUNT_SID", "AUTH_TOKEN") - resp = create_mock_json("tests/resources/conversations/conversation_instance.json") - mock.return_value = resp - client.conversations.get("CV4bbc4afc943cd2a5d29f0ce01c5656db") - uri = "https://conversations.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db" - mock.assert_called_with("GET", uri, auth=("ACCOUNT_SID", "AUTH_TOKEN"), use_json_extension=False) diff --git a/tests/conversations/test_conversations.py b/tests/conversations/test_conversations.py deleted file mode 100644 index f9fced201e..0000000000 --- a/tests/conversations/test_conversations.py +++ /dev/null @@ -1,46 +0,0 @@ -import unittest - -from mock import patch - -from tests.tools import create_mock_json -from twilio.rest.resources.conversations.conversations import ConversationsRoot - - -AUTH = ("AC123", "token") -BASE_URI = "https://conversations.twilio.com/v1" -CONVERSATION_SID = "CVaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - - -class ConversationTest(unittest.TestCase): - @patch('twilio.rest.resources.base.make_twilio_request') - def test_get(self, request): - resp = create_mock_json('tests/resources/conversations/conversation_instance.json') - resp.status_code = 200 - request.return_value = resp - - uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) - list_resource = ConversationsRoot(BASE_URI, AUTH) - list_resource.get(CONVERSATION_SID) - request.assert_called_with("GET", uri, use_json_extension=False, auth=AUTH) - - @patch('twilio.rest.resources.base.make_twilio_request') - def test_list_in_progress(self, request): - resp = create_mock_json('tests/resources/conversations/conversation_list.json') - resp.status_code = 200 - request.return_value = resp - - uri = "{0}/Conversations/InProgress".format(BASE_URI) - list_resource = ConversationsRoot(BASE_URI, AUTH) - list_resource.in_progress.list() - request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) - - @patch('twilio.rest.resources.base.make_twilio_request') - def test_list_completed(self, request): - resp = create_mock_json('tests/resources/conversations/conversation_list.json') - resp.status_code = 200 - request.return_value = resp - - uri = "{0}/Conversations/Completed".format(BASE_URI) - list_resource = ConversationsRoot(BASE_URI, AUTH) - list_resource.completed.list() - request.assert_called_with("GET", uri, params={}, auth=AUTH, use_json_extension=False) diff --git a/tests/conversations/test_participants.py b/tests/conversations/test_participants.py deleted file mode 100644 index 6e1d214f7c..0000000000 --- a/tests/conversations/test_participants.py +++ /dev/null @@ -1,38 +0,0 @@ -import unittest - -from mock import patch - -from tests.tools import create_mock_json -from twilio.rest.resources.conversations.participants import Participants - - -AUTH = ("AC123", "token") -BASE_URI = "https://conversations.twilio.com/v1" -CONVERSATION_SID = "CVaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -PARTICIPANT_SID = "PAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - - -class ParticipantTest(unittest.TestCase): - @patch('twilio.rest.resources.base.make_twilio_request') - def test_get(self, request): - resp = create_mock_json('tests/resources/conversations/participant_instance.json') - resp.status_code = 200 - request.return_value = resp - - uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) - expected = "{0}/Participants/{1}".format(uri, PARTICIPANT_SID) - list_resource = Participants(uri, AUTH) - list_resource.get(PARTICIPANT_SID) - request.assert_called_with("GET", expected, use_json_extension=False, auth=AUTH) - - @patch('twilio.rest.resources.base.make_twilio_request') - def test_list_in_progress(self, request): - resp = create_mock_json('tests/resources/conversations/participant_list.json') - resp.status_code = 200 - request.return_value = resp - - uri = "{0}/Conversations/{1}".format(BASE_URI, CONVERSATION_SID) - expected = "{0}/Participants".format(uri) - list_resource = Participants(uri, AUTH) - list_resource.list() - request.assert_called_with("GET", expected, params={}, auth=AUTH, use_json_extension=False) diff --git a/tests/ip_messaging/__init__.py b/tests/ip_messaging/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/ip_messaging/test_channels.py b/tests/ip_messaging/test_channels.py deleted file mode 100644 index 61faa2dfc6..0000000000 --- a/tests/ip_messaging/test_channels.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Channels, Channel -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -CHANNEL_SID = "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Channels(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_channel(mock): - resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Channels" % (BASE_URI) - list_resource.create(friendly_name='TestChannel') - exp_params = { - 'FriendlyName': "TestChannel" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") - mock.return_value = resp - - uri = "%s/Channels/%s" % (BASE_URI, CHANNEL_SID) - list_resource.get(CHANNEL_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Channel(list_resource, "CH123") - app.delete() - uri = "%s/Channels/CH123" % (BASE_URI) - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_credentials.py b/tests/ip_messaging/test_credentials.py deleted file mode 100644 index 52b2c01c86..0000000000 --- a/tests/ip_messaging/test_credentials.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Credentials, Credential -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -CREDENTIAL_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Credentials(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_credential(mock): - resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Credentials" % (BASE_URI) - list_resource.create('apn') - exp_params = { - 'Type': "apn" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") - mock.return_value = resp - - uri = "%s/Credentials/%s" % (BASE_URI, CREDENTIAL_SID) - list_resource.get(CREDENTIAL_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Credential(list_resource, "IS123") - app.delete() - uri = "https://ip-messaging.twilio.com/v1/Credentials/IS123" - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_members.py b/tests/ip_messaging/test_members.py deleted file mode 100644 index fa05331870..0000000000 --- a/tests/ip_messaging/test_members.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Members, Member -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -MEMBER_SID = "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Members(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_member(mock): - resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Members" % (BASE_URI) - list_resource.create('test_identity') - exp_params = { - 'Identity': "test_identity" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") - mock.return_value = resp - - uri = "%s/Members/%s" % (BASE_URI, MEMBER_SID) - list_resource.get(MEMBER_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Member(list_resource, "MB123") - app.delete() - uri = "%s/Members/MB123" % (BASE_URI) - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_messages.py b/tests/ip_messaging/test_messages.py deleted file mode 100644 index c4fd6f8a2c..0000000000 --- a/tests/ip_messaging/test_messages.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Messages, Message -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -MESSAGE_SID = "MSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Messages(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_message(mock): - resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Messages" % (BASE_URI) - list_resource.create('TestBody') - exp_params = { - 'Body': "TestBody" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") - mock.return_value = resp - - uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) - list_resource.get(MESSAGE_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Message(list_resource, "MS123") - app.delete() - uri = "%s/Messages/MS123" % (BASE_URI) - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_roles.py b/tests/ip_messaging/test_roles.py deleted file mode 100644 index 8160dcb7c9..0000000000 --- a/tests/ip_messaging/test_roles.py +++ /dev/null @@ -1,36 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Roles, Role -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -ROLE_SID = "ROaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Roles(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") - mock.return_value = resp - - uri = "%s/Roles/%s" % (BASE_URI, ROLE_SID) - list_resource.get(ROLE_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Role(list_resource, "RO123") - app.delete() - uri = "%s/Roles/RO123" % (BASE_URI) - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_services.py b/tests/ip_messaging/test_services.py deleted file mode 100644 index 04b24a7f14..0000000000 --- a/tests/ip_messaging/test_services.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Services, Service -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -SERVICE_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Services(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_service(mock): - resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Services" % (BASE_URI) - list_resource.create('TestService') - exp_params = { - 'FriendlyName': "TestService" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") - mock.return_value = resp - - uri = "%s/Services/%s" % (BASE_URI, SERVICE_SID) - list_resource.get(SERVICE_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = Service(list_resource, "IS123") - app.delete() - uri = "https://ip-messaging.twilio.com/v1/Services/IS123" - req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_users.py b/tests/ip_messaging/test_users.py deleted file mode 100644 index 0c90bb7e2a..0000000000 --- a/tests/ip_messaging/test_users.py +++ /dev/null @@ -1,52 +0,0 @@ -from mock import patch, Mock -from twilio.rest.resources.ip_messaging import Users, User -from tests.tools import create_mock_json - -BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" -ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -AUTH = (ACCOUNT_SID, "token") -USER_SID = "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - -list_resource = Users(BASE_URI, AUTH) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_create_user(mock): - resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") - resp.status_code = 201 - mock.return_value = resp - - uri = "%s/Users" % (BASE_URI) - list_resource.create('test_id') - exp_params = { - 'Id': "test_id" - } - - mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.make_twilio_request") -def test_get(mock): - resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") - mock.return_value = resp - - uri = "%s/Users/%s" % (BASE_URI, USER_SID) - list_resource.get(USER_SID) - - mock.assert_called_with("GET", uri, auth=AUTH, - use_json_extension=False) - - -@patch("twilio.rest.resources.base.Resource.request") -def test_delete(req): - """ Deleting a call should work """ - resp = Mock() - resp.content = "" - resp.status_code = 204 - req.return_value = resp, {} - - app = User(list_resource, "US123") - app.delete() - uri = "%s/Users/US123" % (BASE_URI) - req.assert_called_with("DELETE", uri) diff --git a/tests/resources/conversations/conversation_instance.json b/tests/resources/conversations/conversation_instance.json deleted file mode 100644 index a23e753f3b..0000000000 --- a/tests/resources/conversations/conversation_instance.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "links": { - "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db/Participants" - }, - "sid": "CV4bbc4afc943cd2a5d29f0ce01c5656db", - "status": "created", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "date_created": "2015-05-12T21:13:15Z", - "start_time": "2015-05-12T21:13:15Z", - "end_time": "2015-05-12T21:14:15Z", - "duration": 60, - "url": "https://conversations.stage.twilio.com/v1/Conversations/CV4bbc4afc943cd2a5d29f0ce01c5656db" -} diff --git a/tests/resources/conversations/conversation_list.json b/tests/resources/conversations/conversation_list.json deleted file mode 100644 index bc7c0e3efb..0000000000 --- a/tests/resources/conversations/conversation_list.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "meta": { - "key": "conversations", - "next_page_url": null, - "url": "https://conversations.stage.twilio.com/v1/Conversations/Completed?PageSize=50&Page=0", - "previous_page_url": null, - "first_page_url": "https://conversations.stage.twilio.com/v1/Conversations/Completed?PageSize=50&Page=0", - "page_size": 50, - "page": 0 - }, - "conversations": [ - { - "links": { - "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV5cd9d2f155da05660b5d487b1b69e27d/Participants" - }, - "sid": "CV5cd9d2f155da05660b5d487b1b69e27d", - "status": "completed", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "date_created": "2015-05-12T21:08:50Z", - "start_time": "2015-05-12T21:08:50Z", - "end_time": "2015-05-12T21:09:50Z", - "duration": 60, - "url": "https://conversations.stage.twilio.com/v1/Conversations/CV5cd9d2f155da05660b5d487b1b69e27d" - }, - { - "links": { - "participants": "https://conversations.stage.twilio.com/v1/Conversations/CV878937a518876bece719861b02a4984a/Participants" - }, - "sid": "CV878937a518876bece719861b02a4984a", - "status": "completed", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "date_created": "2015-05-12T16:57:03Z", - "start_time": "2015-05-12T16:57:03Z", - "end_time": "2015-05-12T16:58:03Z", - "duration": 60, - "url": "https://conversations.stage.twilio.com/v1/Conversations/CV878937a518876bece719861b02a4984a" - } - ] -} diff --git a/tests/resources/conversations/participant_instance.json b/tests/resources/conversations/participant_instance.json deleted file mode 100644 index 03fb08c041..0000000000 --- a/tests/resources/conversations/participant_instance.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA97239ce0bff1491fa82986a543bcd9c9", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "sid": "PA97239ce0bff1491fa82986a543bcd9c9", - "address": "torkel2@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", - "status": "disconnected", - "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", - "date_created": "2015-05-13T23:03:12Z", - "start_time": "2015-05-13T23:03:15Z", - "end_time": "2015-05-13T23:14:40Z", - "duration": 685 -} diff --git a/tests/resources/conversations/participant_list.json b/tests/resources/conversations/participant_list.json deleted file mode 100644 index 7d41ff65d2..0000000000 --- a/tests/resources/conversations/participant_list.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "meta": { - "key": "participants", - "next_page_url": null, - "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants?PageSize=50&Page=0", - "previous_page_url": null, - "first_page_url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants?PageSize=50&Page=0", - "page_size": 50, - "page": 0 - }, - "participants": [ - { - "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA97239ce0bff1491fa82986a543bcd9c9", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "sid": "PA97239ce0bff1491fa82986a543bcd9c9", - "address": "torkel2@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", - "status": "disconnected", - "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", - "date_created": "2015-05-13T23:03:12Z", - "start_time": "2015-05-13T23:03:15Z", - "end_time": "2015-05-13T23:14:40Z", - "duration": 685 - }, - { - "url": "https://conversations.stage.twilio.com/v1/Conversations/CVe42fecfaefadbb03cbe27d94e4cef8c2/Participants/PA78810fba996f4087c8894b801669b9b2", - "account_sid": "AC998c10b68cbfda9f67277f7d8f4439c9", - "sid": "PA78810fba996f4087c8894b801669b9b2", - "address": "torkel1@AC998c10b68cbfda9f67277f7d8f4439c9.endpoint.twilio.com", - "status": "disconnected", - "conversation_sid": "CVe42fecfaefadbb03cbe27d94e4cef8c2", - "date_created": "2015-05-13T23:03:12Z", - "start_time": "2015-05-13T23:03:15Z", - "end_time": "2015-05-13T23:14:40Z", - "duration": 685 - } - ] -} \ No newline at end of file diff --git a/tests/resources/ip_messaging/channel_instance.json b/tests/resources/ip_messaging/channel_instance.json deleted file mode 100644 index d713a5111b..0000000000 --- a/tests/resources/ip_messaging/channel_instance.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "friendly_name": "update", - "attributes": "", - "date_created": "2015-08-20T09:30:24Z", - "date_updated": "2015-08-20T09:30:24Z", - "created_by": "system", - "url": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "links": { - "members": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Member", - "messages": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages" - } -} diff --git a/tests/resources/ip_messaging/credential_instance.json b/tests/resources/ip_messaging/credential_instance.json deleted file mode 100644 index 9b24940277..0000000000 --- a/tests/resources/ip_messaging/credential_instance.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "sid":"CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "friendly_name":"MyApp APN Certificate", - "type":"apn", - "date_created":"2015-06-30T21:16:50Z", - "date_updated":"2015-07-30T21:16:50Z" -} diff --git a/tests/resources/ip_messaging/member_instance.json b/tests/resources/ip_messaging/member_instance.json deleted file mode 100644 index adc75ddcfe..0000000000 --- a/tests/resources/ip_messaging/member_instance.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "sid": "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "channel_sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "id": "carl@twilio.com", - "role": "admin", - "url": "/v1/Spaces/SPxx/Channels/CHxx/Members/MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -} diff --git a/tests/resources/ip_messaging/message_instance.json b/tests/resources/ip_messaging/message_instance.json deleted file mode 100644 index acbe53124f..0000000000 --- a/tests/resources/ip_messaging/message_instance.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "sid": "IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "date_created": "2015-07-23T20:20:10Z", - "date_updated": "2015-07-23T20:20:10Z", - "was_edited": true, - "from": "carl@twilio.com", - "to": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "body": "Hello", - "url": "/v1/Spaces/SPxx/Channels/CHxx/Messages/IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -} diff --git a/tests/resources/ip_messaging/role_instance.json b/tests/resources/ip_messaging/role_instance.json deleted file mode 100644 index bbd604428c..0000000000 --- a/tests/resources/ip_messaging/role_instance.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "sid":"RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "name":"Deployment Admin", - "type":"deployment", - "permissions":[ - "createChannel", - "destroyChannel" - ] -} diff --git a/tests/resources/ip_messaging/service_instance.json b/tests/resources/ip_messaging/service_instance.json deleted file mode 100644 index bc4d56e4a9..0000000000 --- a/tests/resources/ip_messaging/service_instance.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "friendly_name": "TestService", - "date_created": "2015-10-21T04:15:36Z", - "date_updated": "2015-10-21T04:15:36Z", - "default_service_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "default_channel_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "typing_indicator_timeout": 5, - "webhooks": {}, - "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "links": { - "channels": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels", - "roles": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Roles", - "users": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users" - } -} diff --git a/tests/resources/ip_messaging/user_instance.json b/tests/resources/ip_messaging/user_instance.json deleted file mode 100644 index a2326cc20c..0000000000 --- a/tests/resources/ip_messaging/user_instance.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sid": "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "date_created": "2015-08-19T18:18:00Z", - "date_updated": "2015-08-19T18:18:00Z", - "identity": "carl@twilio.com", - "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "role_sid": null, - "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users/USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -} diff --git a/twilio/rest/conversations.py b/twilio/rest/conversations.py deleted file mode 100644 index 93f2ddc03b..0000000000 --- a/twilio/rest/conversations.py +++ /dev/null @@ -1,28 +0,0 @@ -from twilio.rest.base import TwilioClient -from twilio.rest.resources import UNSET_TIMEOUT -from twilio.rest.resources.conversations.conversations import ConversationsRoot - - -class TwilioConversationsClient(TwilioClient): - """ - A client for accessing the Twilio Conversations API. - - XXX more verbiage here - - :param str account: Your Account Sid from `your dashboard - `_ - :param str token: Your Auth Token from `your dashboard - `_ - :param float timeout: The socket and read timeout for requests to Twilio - """ - - def __init__(self, account=None, token=None, - base="https://conversations.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): - - super(TwilioConversationsClient, self).__init__(account, token, base, - version, timeout) - - self.version_uri = "%s/%s" % (base, version) - self.conversations = ConversationsRoot(self.version_uri, self.auth, - timeout) diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py deleted file mode 100644 index a52e8e37a6..0000000000 --- a/twilio/rest/ip_messaging.py +++ /dev/null @@ -1,29 +0,0 @@ -from twilio.rest.base import TwilioClient -from twilio.rest.resources import UNSET_TIMEOUT -from twilio.rest.resources.ip_messaging.services import Services - - -class TwilioIpMessagingClient(TwilioClient): - """ - A client for accessing the Twilio IP Messaging API. - - The Twilio IP Messaging API provides information about events. For more - information, see the - `IP Messaging API documentation `_. - - :param str account: Your Account Sid from `your dashboard - `_ - :param str token: Your Auth Token from `your dashboard - `_ - :param float timeout: The socket and read timeout for requests to Twilio - """ - - def __init__(self, account=None, token=None, - base="https://ip-messaging.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): - - super(TwilioIpMessagingClient, self).__init__(account, token, base, - version, timeout) - - self.version_uri = "%s/%s" % (base, version) - self.services = Services(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index 67285c11cc..d9a34b8886 100644 --- a/twilio/rest/resources/base.py +++ b/twilio/rest/resources/base.py @@ -244,7 +244,7 @@ def load(self, entries): del entries["uri"] for key in entries.keys(): - if ((key.startswith("date_") or key.endswith("_time")) and + if (key.startswith("date_") and isinstance(entries[key], string_types)): entries[key] = self._parse_date(entries[key]) diff --git a/twilio/rest/resources/conversations/__init__.py b/twilio/rest/resources/conversations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/twilio/rest/resources/conversations/conversations.py b/twilio/rest/resources/conversations/conversations.py deleted file mode 100644 index 8c0adec62f..0000000000 --- a/twilio/rest/resources/conversations/conversations.py +++ /dev/null @@ -1,58 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class ConversationsRoot(object): - - def __init__(self, base_uri, *args, **kwargs): - self.uri = "%s/Conversations" % base_uri - self._instance_base = Conversations(self.uri, *args, **kwargs) - self.in_progress = Conversations("%s/InProgress" % self.uri, *args, - **kwargs) - self.completed = Conversations("%s/Completed" % self.uri, *args, - **kwargs) - - def get(self, sid): - return self._instance_base.get(sid) - - def delete_instance(self, sid): - return self._instance_base.delete_instance(sid) - - -class Conversation(NextGenInstanceResource): - """A Conversation instance representing a call - between two or more participants. - - .. attribute:: sid - - .. attribute:: account_sid - - .. attribute:: status - - .. attribute:: date_created - - .. attribute:: start_time - - .. attribute:: end_time - - .. attribute:: duration - - .. attribute:: url - """ - pass - - -class Conversations(NextGenListResource): - - name = "Conversations" - instance = Conversation - - def __init__(self, uri, *args, **kwargs): - super(Conversations, self).__init__(uri, *args, **kwargs) - # This list is exposed at two different locations: /InProgress - # and /Completed. The parent Root object will hand us the full URL - # to set up at. - self._uri = uri - - @property - def uri(self): - return self._uri diff --git a/twilio/rest/resources/conversations/participants.py b/twilio/rest/resources/conversations/participants.py deleted file mode 100644 index 65ad08801f..0000000000 --- a/twilio/rest/resources/conversations/participants.py +++ /dev/null @@ -1,34 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Participant(NextGenInstanceResource): - """A participant in a Conversation. - - .. attribute:: sid - - .. attribute:: conversation_sid - - .. attribute:: account_sid - - .. attribute:: status - - .. attribute:: address - - .. attribute:: date_created - - .. attribute:: start_time - - .. attribute:: end_time - - .. attribute:: duration - - .. attribute:: url - """ - pass - - -class Participants(NextGenListResource): - """A list of :class:`Participant` objects.""" - - name = "Participants" - instance = Participant diff --git a/twilio/rest/resources/ip_messaging/__init__.py b/twilio/rest/resources/ip_messaging/__init__.py deleted file mode 100644 index 55f60ad203..0000000000 --- a/twilio/rest/resources/ip_messaging/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -from .services import ( - Service, - Services -) - -from .channels import ( - Channel, - Channels -) - -from .members import ( - Member, - Members -) - -from .messages import ( - Message, - Messages -) - -from .roles import ( - Role, - Roles -) - -from .users import ( - User, - Users -) - -from .credentials import ( - Credential, - Credentials -) diff --git a/twilio/rest/resources/ip_messaging/channels.py b/twilio/rest/resources/ip_messaging/channels.py deleted file mode 100644 index ad6f42ce68..0000000000 --- a/twilio/rest/resources/ip_messaging/channels.py +++ /dev/null @@ -1,54 +0,0 @@ -from .members import Members -from .messages import Messages -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Channel(NextGenInstanceResource): - - subresources = [ - Members, - Messages - ] - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this channel - """ - return self.delete_instance() - - -class Channels(NextGenListResource): - - name = "Channels" - instance = Channel - - def list(self, **kwargs): - """ - Returns a page of :class:`Channel` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Channel instance resource representation. - """ - return self.get_instances(kwargs) - - def create(self, **kwargs): - """ - Create a channel. - - :param str friendly_name: The friendly name of the channel. - :param str attributes: An attribute string with arbitrary - - :return: A :class:`Channel` object - """ - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given Channel - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/credentials.py b/twilio/rest/resources/ip_messaging/credentials.py deleted file mode 100644 index c861dc28d7..0000000000 --- a/twilio/rest/resources/ip_messaging/credentials.py +++ /dev/null @@ -1,53 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Credential(NextGenInstanceResource): - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this credential - """ - return self.delete_instance() - - -class Credentials(NextGenListResource): - - name = "Credentials" - instance = Credential - - def list(self, **kwargs): - """ - Returns a page of :class:`Credential` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Credential instance resource representation. - - :param date after: Only list alerts logged after this datetime - :param date before: Only list alerts logger before this datetime - :param log_level: If 'error', only shows errors. If 'warning', - only show warnings - """ - return self.get_instances(kwargs) - - def create(self, type, **kwargs): - """ - Make a phone call to a number. - - :param str type: The type of credential - :param str friendly_name: The friendly name of the credential. - - :return: A :class:`Credential` object - """ - kwargs["type"] = type - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given Credential - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/members.py b/twilio/rest/resources/ip_messaging/members.py deleted file mode 100644 index dd3b0fae90..0000000000 --- a/twilio/rest/resources/ip_messaging/members.py +++ /dev/null @@ -1,49 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Member(NextGenInstanceResource): - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this member - """ - return self.delete_instance() - - -class Members(NextGenListResource): - - name = "Members" - instance = Member - - def list(self, **kwargs): - """ - Returns a page of :class:`Member` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Member instance resource representation. - - """ - return self.get_instances(kwargs) - - def create(self, identity, **kwargs): - """ - Create a Member. - - :param str identity: The identity of the user. - :param str role: The role to assign the member. - - :return: A :class:`Member` object - """ - kwargs["identity"] = identity - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given Member - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/messages.py b/twilio/rest/resources/ip_messaging/messages.py deleted file mode 100644 index d888e21004..0000000000 --- a/twilio/rest/resources/ip_messaging/messages.py +++ /dev/null @@ -1,49 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Message(NextGenInstanceResource): - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this message - """ - return self.delete_instance() - - -class Messages(NextGenListResource): - - name = "Messages" - instance = Message - - def list(self, **kwargs): - """ - Returns a page of :class:`Message` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Message instance resource representation. - - """ - return self.get_instances(kwargs) - - def create(self, body, **kwargs): - """ - Create a Message. - - :param str body: The body of the message. - :param str from: The message author's identity. - - :return: A :class:`Message` object - """ - kwargs["body"] = body - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given Message - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/roles.py b/twilio/rest/resources/ip_messaging/roles.py deleted file mode 100644 index bff9147821..0000000000 --- a/twilio/rest/resources/ip_messaging/roles.py +++ /dev/null @@ -1,37 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Role(NextGenInstanceResource): - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this role - """ - return self.delete_instance() - - -class Roles(NextGenListResource): - - name = "Roles" - instance = Role - - def list(self, **kwargs): - """ - Returns a page of :class:`Role` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Role instance resource representation. - - """ - return self.get_instances(kwargs) - - def delete(self, sid): - """ - Delete a given Role - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/services.py b/twilio/rest/resources/ip_messaging/services.py deleted file mode 100644 index 92765677da..0000000000 --- a/twilio/rest/resources/ip_messaging/services.py +++ /dev/null @@ -1,57 +0,0 @@ -from .channels import Channels -from .roles import Roles -from .users import Users -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class Service(NextGenInstanceResource): - - subresources = [ - Channels, - Roles, - Users - ] - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this service - """ - return self.delete_instance() - - -class Services(NextGenListResource): - - name = "Services" - instance = Service - - def list(self, **kwargs): - """ - Returns a page of :class:`Service` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the Service instance resource representation. - - """ - return self.get_instances(kwargs) - - def create(self, friendly_name, **kwargs): - """ - Create a service. - - :param str friendly_name: The friendly name for the service - - :return: A :class:`Service` object - """ - kwargs["friendly_name"] = friendly_name - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given Service - """ - return self.delete_instance(sid) diff --git a/twilio/rest/resources/ip_messaging/users.py b/twilio/rest/resources/ip_messaging/users.py deleted file mode 100644 index eb5c6e3501..0000000000 --- a/twilio/rest/resources/ip_messaging/users.py +++ /dev/null @@ -1,49 +0,0 @@ -from twilio.rest.resources import NextGenInstanceResource, NextGenListResource - - -class User(NextGenInstanceResource): - - def update(self, sid, **kwargs): - return self.update_instance(sid, kwargs) - - def delete(self): - """ - Delete this user - """ - return self.delete_instance() - - -class Users(NextGenListResource): - - name = "Users" - instance = User - - def list(self, **kwargs): - """ - Returns a page of :class:`User` resources as a list. - For paging information see :class:`ListResource`. - - **NOTE**: Due to the potentially voluminous amount of data in an - alert, the full HTTP request and response data is only returned - in the User instance resource representation. - - """ - return self.get_instances(kwargs) - - def create(self, id, **kwargs): - """ - Make a phone call to a number. - - :param str id: The identity of the user. - :param str role: The role to assign the user. - - :return: A :class:`User` object - """ - kwargs["id"] = id - return self.create_instance(kwargs) - - def delete(self, sid): - """ - Delete a given User - """ - return self.delete_instance(sid) From b1f7f3d75b1cdd6b5cc333e5ecb383dd2f8e68fc Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 2 Nov 2015 15:48:13 -0800 Subject: [PATCH 078/115] Remove merge artifact --- tests/test_make_request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_make_request.py b/tests/test_make_request.py index d1e4d5d800..0eab2e8288 100644 --- a/tests/test_make_request.py +++ b/tests/test_make_request.py @@ -119,4 +119,3 @@ def test_proxy_info(http_mock, resp_mock): assert_equal(proxy_info.proxy_host, 'example.com') assert_equal(proxy_info.proxy_port, 8080) assert_equal(proxy_info.proxy_type, PROXY_TYPE_SOCKS5) ->>>>>>> parent of 6bf0e94... Merge remote-tracking branch 'origin/signal-beta' into edge From 15bc4cb508c28472924c64c6d3798ac9f18ab85f Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 2 Nov 2015 17:04:01 -0800 Subject: [PATCH 079/115] Bump version due to conflict --- CHANGES.md | 2 +- twilio/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 94488f1f99..71785637ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,7 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. -Version 4.7.0 +Version 4.8.0 ------------- - Add support for SMS pricing diff --git a/twilio/version.py b/twilio/version.py index 4831bcd233..86259a7a28 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '7', '0') +__version_info__ = ('4', '8', '0') __version__ = '.'.join(__version_info__) From 27f9df25b9b4a7b5ae0d734e1cf0e167fd90a659 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Tue, 3 Nov 2015 11:13:28 -0800 Subject: [PATCH 080/115] Bump version for sip trunking release --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 71785637ea..e70b569ae1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.9.0 +------------- + +Released November 3, 2015: + +- Add support for SIP Trunking + Version 4.8.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 86259a7a28..52ab1bd299 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '8', '0') +__version_info__ = ('4', '9', '0') __version__ = '.'.join(__version_info__) From c87ee2781852d4830db5ceb435c3726d1e4083a4 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Mon, 16 Nov 2015 14:20:47 -0800 Subject: [PATCH 081/115] Fixed redundant countries - DEVX-2534 --- twilio/rest/pricing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/pricing.py b/twilio/rest/pricing.py index 80ae81b920..b909243256 100644 --- a/twilio/rest/pricing.py +++ b/twilio/rest/pricing.py @@ -36,7 +36,7 @@ def messaging_countries(self): Returns a :class:`MessagingCountries` resource :return: MessagingCountries """ - messaging_countries_uri = "{0}/Messaging/Countries".format( + messaging_countries_uri = "{0}/Messaging".format( self.uri_base) return MessagingCountries(messaging_countries_uri, self.auth, self.timeout) From a7fbb657326234b14e5ab14dfa54f6aa0e65b88a Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Wed, 18 Nov 2015 11:17:55 -0800 Subject: [PATCH 082/115] Bump version to 4.9.1 --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e70b569ae1..1a40779bea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.9.1 +------------- + +Released November 18, 2015: + +- Addresses bug with SMS Pricing country + Version 4.9.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 52ab1bd299..c77eec3753 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '9', '0') +__version_info__ = ('4', '9', '1') __version__ = '.'.join(__version_info__) From a04a20f8e40daf1f7686106a0e7720d006eec13a Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Fri, 20 Nov 2015 16:31:11 -0800 Subject: [PATCH 083/115] Fix for Broken Trunking helper library - Removed the "Trunks" from the trunk base uri --- twilio/rest/trunking.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/twilio/rest/trunking.py b/twilio/rest/trunking.py index ee38962e68..c3645d921b 100644 --- a/twilio/rest/trunking.py +++ b/twilio/rest/trunking.py @@ -28,13 +28,13 @@ def __init__(self, account=None, token=None, """ super(TwilioTrunkingClient, self).__init__(account, token, base, version, timeout) - self.trunk_base_uri = "{0}/{1}/Trunks".format(base, version) + self.trunk_base_uri = "{0}/{1}".format(base, version) def credential_lists(self, trunk_sid): """ Return a :class:`CredentialList` instance """ - credential_lists_uri = "{0}/{1}/CredentialLists".format( + credential_lists_uri = "{0}/Trunks/{1}".format( self.trunk_base_uri, trunk_sid) return CredentialLists(credential_lists_uri, self.auth, self.timeout) @@ -42,7 +42,7 @@ def ip_access_control_lists(self, trunk_sid): """ Return a :class:`IpAccessControlList` instance """ - ip_access_control_lists_uri = "{0}/{1}/IpAccessControlLists".format( + ip_access_control_lists_uri = "{0}/Trunks/{1}".format( self.trunk_base_uri, trunk_sid) return IpAccessControlLists(ip_access_control_lists_uri, self.auth, self.timeout) @@ -51,7 +51,7 @@ def origination_urls(self, trunk_sid): """ Return a :class:`OriginationUrls` instance """ - origination_urls_uri = "{0}/{1}/OriginationUrls".format( + origination_urls_uri = "{0}/Trunks/{1}".format( self.trunk_base_uri, trunk_sid) return OriginationUrls(origination_urls_uri, self.auth, self.timeout) @@ -59,8 +59,8 @@ def phone_numbers(self, trunk_sid): """ Return a :class:`PhoneNumbers` instance """ - phone_numbers_uri = "{0}/{1}/PhoneNumbers".format(self.trunk_base_uri, - trunk_sid) + phone_numbers_uri = "{0}/Trunks/{1}".format(self.trunk_base_uri, + trunk_sid) return PhoneNumbers(phone_numbers_uri, self.auth, self.timeout) def trunks(self): From b0c7df5fd62a458572c76d6dbeb5fedd87e7495d Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Wed, 25 Nov 2015 10:25:11 -0800 Subject: [PATCH 084/115] Bump version to 4.9.2 - Fix for SIP Trunking bug --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1a40779bea..3440ca91c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.9.2 +------------- + +Released November 25, 2015: + +- Fix for SIP Trunking bug + Version 4.9.1 ------------- diff --git a/twilio/version.py b/twilio/version.py index c77eec3753..6c3dc7b209 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '9', '1') +__version_info__ = ('4', '9', '2') __version__ = '.'.join(__version_info__) From e0c29fac53bcccb39e3770a9e32b3785b017ae3d Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 3 Dec 2015 13:50:54 -0800 Subject: [PATCH 085/115] Add access tokens to master --- setup.py | 2 +- tests/test_access_token.py | 70 +++++++++++++++++++++++++++++ twilio/access_token.py | 91 ++++++++++++++++++++++++++++++++++++++ twilio/jwt/__init__.py | 4 +- 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 tests/test_access_token.py create mode 100644 twilio/access_token.py diff --git a/setup.py b/setup.py index 69259aa554..f6724d2024 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # # You need to have the setuptools module installed. Try reading the setuptools # documentation: http://pypi.python.org/pypi/setuptools -REQUIRES = ["httplib2 >= 0.7", "six", "pytz"] +REQUIRES = ["httplib2 >= 0.7", "six", "pytz", "pyjwt"] if sys.version_info < (2, 6): REQUIRES.append('simplejson') diff --git a/tests/test_access_token.py b/tests/test_access_token.py new file mode 100644 index 0000000000..c86e0ba72f --- /dev/null +++ b/tests/test_access_token.py @@ -0,0 +1,70 @@ +import unittest + +from nose.tools import assert_equal +from twilio.jwt import decode +from twilio.access_token import AccessToken, ConversationsGrant, IpMessagingGrant + +ACCOUNT_SID = 'AC123' +SIGNING_KEY_SID = 'SK123' + + +# python2.6 support +def assert_is_not_none(obj): + assert obj is not None, '%r is None' % obj + + +class AccessTokenTest(unittest.TestCase): + def _validate_claims(self, payload): + assert_equal(SIGNING_KEY_SID, payload['iss']) + assert_equal(ACCOUNT_SID, payload['sub']) + assert_is_not_none(payload['nbf']) + assert_is_not_none(payload['exp']) + assert_equal(payload['nbf'] + 3600, payload['exp']) + assert_is_not_none(payload['jti']) + assert_equal('{0}-{1}'.format(payload['iss'], payload['nbf']), + payload['jti']) + assert_is_not_none(payload['grants']) + + def test_empty_grants(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal({}, payload['grants']) + + def test_conversations_grant(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(ConversationsGrant()) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({}, payload['grants']['rtc']) + + def test_ip_messaging_grant(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(IpMessagingGrant()) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(1, len(payload['grants'])) + assert_equal({}, payload['grants']['ip_messaging']) + + def test_grants(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') + scat.add_grant(ConversationsGrant()) + scat.add_grant(IpMessagingGrant()) + + token = str(scat) + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(2, len(payload['grants'])) + assert_equal({}, payload['grants']['rtc']) + assert_equal({}, payload['grants']['ip_messaging']) diff --git a/twilio/access_token.py b/twilio/access_token.py new file mode 100644 index 0000000000..14cffd453d --- /dev/null +++ b/twilio/access_token.py @@ -0,0 +1,91 @@ +import time +import jwt + + +class IpMessagingGrant(object): + """ Grant to access Twilio IP Messaging """ + def __init__(self, service_sid=None, endpoint_id=None, + role_sid=None, credential_sid=None): + self.service_sid = service_sid + self.endpoint_id = endpoint_id + self.deployment_role_sid = role_sid + self.push_credential_sid = credential_sid + + @property + def key(self): + return "ip_messaging" + + def to_payload(self): + grant = {} + if self.service_sid: + grant['service_sid'] = self.service_sid + if self.endpoint_id: + grant['endpoint_id'] = self.endpoint_id + if self.deployment_role_sid: + grant['deployment_role_sid'] = self.deployment_role_sid + if self.push_credential_sid: + grant['push_credential_sid'] = self.push_credential_sid + + return grant + + +class ConversationsGrant(object): + """ Grant to access Twilio Conversations """ + def __init__(self, configuration_profile_sid=None): + self.configuration_profile_sid = configuration_profile_sid + + @property + def key(self): + return "rtc" + + def to_payload(self): + grant = {} + if self.configuration_profile_sid: + grant['configuration_profile_sid'] = self.configuration_profile_sid + + return grant + + +class AccessToken(object): + """ Access Token used to access Twilio Resources """ + def __init__(self, account_sid, signing_key_sid, secret, + identity=None, ttl=3600): + self.account_sid = account_sid + self.signing_key_sid = signing_key_sid + self.secret = secret + + self.identity = identity + self.ttl = ttl + self.grants = [] + + def add_grant(self, grant): + self.grants.append(grant) + + def to_jwt(self, algorithm='HS256'): + now = int(time.time()) + headers = { + "typ": "JWT", + "cty": "twilio-fpa;v=1" + } + + grants = {} + if self.identity: + grants["identity"] = self.identity + + for grant in self.grants: + grants[grant.key] = grant.to_payload() + + payload = { + "jti": '{0}-{1}'.format(self.signing_key_sid, now), + "iss": self.signing_key_sid, + "sub": self.account_sid, + "nbf": now, + "exp": now + self.ttl, + "grants": grants + } + + return jwt.encode(payload, self.secret, headers=headers, + algorithm=algorithm) + + def __str__(self): + return self.to_jwt().decode('utf-8') diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index edb4062433..93f6b60a34 100644 --- a/twilio/jwt/__init__.py +++ b/twilio/jwt/__init__.py @@ -41,9 +41,11 @@ def base64url_encode(input): return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '') -def encode(payload, key, algorithm='HS256'): +def encode(payload, key, algorithm='HS256', headers=None): segments = [] header = {"typ": "JWT", "alg": algorithm} + if headers: + header.update(headers) segments.append(base64url_encode(binary(json.dumps(header)))) segments.append(base64url_encode(binary(json.dumps(payload)))) sign_input = '.'.join(segments) From c2f99b5e288dd96c14dc98a648dbd8b721935840 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 3 Dec 2015 13:54:24 -0800 Subject: [PATCH 086/115] Bump versions --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e70b569ae1..a8a97973e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 4.10.0 +------------- + +Released December 3, 2015: + +- Add Access Tokens + Version 4.9.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 52ab1bd299..980f16d882 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '9', '0') +__version_info__ = ('4', '10', '0') __version__ = '.'.join(__version_info__) From ab9b1217d04ebc8e0fe1494e6bd6c62a8f8aa518 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Mon, 7 Dec 2015 17:39:28 -0800 Subject: [PATCH 087/115] Make nbf optional in access tokens --- tests/test_access_token.py | 53 ++++++++++++++++++++++++++++++++------ twilio/access_token.py | 13 ++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/tests/test_access_token.py b/tests/test_access_token.py index c86e0ba72f..589db4509a 100644 --- a/tests/test_access_token.py +++ b/tests/test_access_token.py @@ -1,5 +1,7 @@ +import time import unittest +from datetime import datetime from nose.tools import assert_equal from twilio.jwt import decode from twilio.access_token import AccessToken, ConversationsGrant, IpMessagingGrant @@ -13,18 +15,27 @@ def assert_is_not_none(obj): assert obj is not None, '%r is None' % obj +def assert_in(obj1, obj2): + assert obj1 in obj2, '%r is not in %r' % (obj1, obj2) + + +def assert_greater_equal(obj1, obj2): + assert obj1 > obj2, '%r is not greater than or equal to %r' % (obj1, obj2) + + class AccessTokenTest(unittest.TestCase): def _validate_claims(self, payload): assert_equal(SIGNING_KEY_SID, payload['iss']) assert_equal(ACCOUNT_SID, payload['sub']) - assert_is_not_none(payload['nbf']) + assert_is_not_none(payload['exp']) - assert_equal(payload['nbf'] + 3600, payload['exp']) assert_is_not_none(payload['jti']) - assert_equal('{0}-{1}'.format(payload['iss'], payload['nbf']), - payload['jti']) assert_is_not_none(payload['grants']) + assert_greater_equal(payload['exp'], int(time.time())) + + assert_in(payload['iss'], payload['jti']) + def test_empty_grants(self): scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') token = str(scat) @@ -34,27 +45,53 @@ def test_empty_grants(self): self._validate_claims(payload) assert_equal({}, payload['grants']) + def test_nbf(self): + now = int(time.mktime(datetime.now().timetuple())) + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', nbf=now) + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal(now, payload['nbf']) + + def test_identity(self): + scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret', identity='test@twilio.com') + token = str(scat) + + assert_is_not_none(token) + payload = decode(token, 'secret') + self._validate_claims(payload) + assert_equal({ + 'identity': 'test@twilio.com' + }, payload['grants']) + def test_conversations_grant(self): scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') - scat.add_grant(ConversationsGrant()) + scat.add_grant(ConversationsGrant(configuration_profile_sid='CP123')) token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') self._validate_claims(payload) assert_equal(1, len(payload['grants'])) - assert_equal({}, payload['grants']['rtc']) + assert_equal({ + 'configuration_profile_sid': 'CP123' + }, payload['grants']['rtc']) def test_ip_messaging_grant(self): scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') - scat.add_grant(IpMessagingGrant()) + scat.add_grant(IpMessagingGrant(service_sid='IS123', push_credential_sid='CR123')) token = str(scat) assert_is_not_none(token) payload = decode(token, 'secret') self._validate_claims(payload) assert_equal(1, len(payload['grants'])) - assert_equal({}, payload['grants']['ip_messaging']) + assert_equal({ + 'service_sid': 'IS123', + 'push_credential_sid': 'CR123' + }, payload['grants']['ip_messaging']) def test_grants(self): scat = AccessToken(ACCOUNT_SID, SIGNING_KEY_SID, 'secret') diff --git a/twilio/access_token.py b/twilio/access_token.py index 14cffd453d..71d7e56b73 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -5,11 +5,11 @@ class IpMessagingGrant(object): """ Grant to access Twilio IP Messaging """ def __init__(self, service_sid=None, endpoint_id=None, - role_sid=None, credential_sid=None): + deployment_role_sid=None, push_credential_sid=None): self.service_sid = service_sid self.endpoint_id = endpoint_id - self.deployment_role_sid = role_sid - self.push_credential_sid = credential_sid + self.deployment_role_sid = deployment_role_sid + self.push_credential_sid = push_credential_sid @property def key(self): @@ -49,13 +49,14 @@ def to_payload(self): class AccessToken(object): """ Access Token used to access Twilio Resources """ def __init__(self, account_sid, signing_key_sid, secret, - identity=None, ttl=3600): + identity=None, ttl=3600, nbf=None): self.account_sid = account_sid self.signing_key_sid = signing_key_sid self.secret = secret self.identity = identity self.ttl = ttl + self.nbf = nbf self.grants = [] def add_grant(self, grant): @@ -79,11 +80,13 @@ def to_jwt(self, algorithm='HS256'): "jti": '{0}-{1}'.format(self.signing_key_sid, now), "iss": self.signing_key_sid, "sub": self.account_sid, - "nbf": now, "exp": now + self.ttl, "grants": grants } + if self.nbf is not None: + payload['nbf'] = self.nbf + return jwt.encode(payload, self.secret, headers=headers, algorithm=algorithm) From b335ea1a5a96cec73e67300c0df05a475ad34b5e Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Tue, 8 Dec 2015 16:15:34 -0800 Subject: [PATCH 088/115] Bump version --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ac15270ff5..94d533ea8b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.0.0 +------------- + +Released December 8, 2015: + +- Update Access Tokens so that NBF is a optional parameter + Version 4.10.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 980f16d882..29d532c3ff 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('4', '10', '0') +__version_info__ = ('5', '0', '0') __version__ = '.'.join(__version_info__) From 5a468d82dcb04d8ba24a9c81440b0ddc7fa02d0f Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Fri, 11 Dec 2015 16:05:07 -0800 Subject: [PATCH 089/115] Remove dependecy --- requirements.txt | 1 - setup.py | 2 +- twilio/access_token.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index c6d498c507..bc3d0567a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ six httplib2 socksipy-branch -pyjwt diff --git a/setup.py b/setup.py index f6724d2024..69259aa554 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ # # You need to have the setuptools module installed. Try reading the setuptools # documentation: http://pypi.python.org/pypi/setuptools -REQUIRES = ["httplib2 >= 0.7", "six", "pytz", "pyjwt"] +REQUIRES = ["httplib2 >= 0.7", "six", "pytz"] if sys.version_info < (2, 6): REQUIRES.append('simplejson') diff --git a/twilio/access_token.py b/twilio/access_token.py index 71d7e56b73..5c798ad067 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -1,5 +1,5 @@ import time -import jwt +from twilio import jwt class IpMessagingGrant(object): From 3a58b52c3119a4dade7c1f9dd47363ae1599f2a9 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Fri, 11 Dec 2015 16:08:11 -0800 Subject: [PATCH 090/115] Remove decode --- twilio/access_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/access_token.py b/twilio/access_token.py index 5c798ad067..f7104f1742 100644 --- a/twilio/access_token.py +++ b/twilio/access_token.py @@ -91,4 +91,4 @@ def to_jwt(self, algorithm='HS256'): algorithm=algorithm) def __str__(self): - return self.to_jwt().decode('utf-8') + return self.to_jwt() From 0e9e0952d2eb8ca0e1117263ce3e54e5933eb655 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Fri, 11 Dec 2015 16:34:16 -0800 Subject: [PATCH 091/115] Bump version --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 94d533ea8b..d9e94e5c7d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.1.0 +------------- + +Released December 11, 2015: + +- Remove pyjwt dependency + Version 5.0.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 29d532c3ff..72ebfd9835 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('5', '0', '0') +__version_info__ = ('5', '1', '0') __version__ = '.'.join(__version_info__) From ef47b184b26f112ee10f8349c338ea20ed791080 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Wed, 16 Dec 2015 16:59:46 -0800 Subject: [PATCH 092/115] IP Messaging public beta release --- tests/ip_messaging/__init__.py | 0 tests/ip_messaging/test_channels.py | 54 +++++++++++++ tests/ip_messaging/test_credentials.py | 53 ++++++++++++ tests/ip_messaging/test_members.py | 53 ++++++++++++ tests/ip_messaging/test_messages.py | 68 ++++++++++++++++ tests/ip_messaging/test_roles.py | 57 +++++++++++++ tests/ip_messaging/test_services.py | 53 ++++++++++++ tests/ip_messaging/test_users.py | 53 ++++++++++++ twilio/rest/__init__.py | 7 +- twilio/rest/ip_messaging.py | 31 +++++++ .../rest/resources/ip_messaging/__init__.py | 34 ++++++++ .../rest/resources/ip_messaging/channels.py | 78 ++++++++++++++++++ .../resources/ip_messaging/credentials.py | 80 +++++++++++++++++++ twilio/rest/resources/ip_messaging/members.py | 68 ++++++++++++++++ .../rest/resources/ip_messaging/messages.py | 68 ++++++++++++++++ twilio/rest/resources/ip_messaging/roles.py | 67 ++++++++++++++++ .../rest/resources/ip_messaging/services.py | 69 ++++++++++++++++ twilio/rest/resources/ip_messaging/users.py | 63 +++++++++++++++ 18 files changed, 953 insertions(+), 3 deletions(-) create mode 100644 tests/ip_messaging/__init__.py create mode 100644 tests/ip_messaging/test_channels.py create mode 100644 tests/ip_messaging/test_credentials.py create mode 100644 tests/ip_messaging/test_members.py create mode 100644 tests/ip_messaging/test_messages.py create mode 100644 tests/ip_messaging/test_roles.py create mode 100644 tests/ip_messaging/test_services.py create mode 100644 tests/ip_messaging/test_users.py create mode 100644 twilio/rest/ip_messaging.py create mode 100644 twilio/rest/resources/ip_messaging/__init__.py create mode 100644 twilio/rest/resources/ip_messaging/channels.py create mode 100644 twilio/rest/resources/ip_messaging/credentials.py create mode 100644 twilio/rest/resources/ip_messaging/members.py create mode 100644 twilio/rest/resources/ip_messaging/messages.py create mode 100644 twilio/rest/resources/ip_messaging/roles.py create mode 100644 twilio/rest/resources/ip_messaging/services.py create mode 100644 twilio/rest/resources/ip_messaging/users.py diff --git a/tests/ip_messaging/__init__.py b/tests/ip_messaging/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/ip_messaging/test_channels.py b/tests/ip_messaging/test_channels.py new file mode 100644 index 0000000000..90720cdd3a --- /dev/null +++ b/tests/ip_messaging/test_channels.py @@ -0,0 +1,54 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Channels, Channel +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CHANNEL_SID = "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Channels(BASE_URI, AUTH) + + +class ChannelTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_channel(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Channels" % (BASE_URI) + list_resource.create(friendly_name='TestChannel', unique_name='Unique') + exp_params = { + 'FriendlyName': "TestChannel", + 'UniqueName': 'Unique' + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/channel_instance.json") + mock.return_value = resp + + uri = "%s/Channels/%s" % (BASE_URI, CHANNEL_SID) + list_resource.get(CHANNEL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Channel(list_resource, "CH123") + app.delete() + uri = "%s/Channels/CH123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_credentials.py b/tests/ip_messaging/test_credentials.py new file mode 100644 index 0000000000..ae749db875 --- /dev/null +++ b/tests/ip_messaging/test_credentials.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Credentials, Credential +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +CREDENTIAL_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Credentials(BASE_URI, AUTH) + + +class CredentialTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_credential(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Credentials" % (BASE_URI) + list_resource.create('apn') + exp_params = { + 'Type': "apn" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/credential_instance.json") + mock.return_value = resp + + uri = "%s/Credentials/%s" % (BASE_URI, CREDENTIAL_SID) + list_resource.get(CREDENTIAL_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Credential(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Credentials/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_members.py b/tests/ip_messaging/test_members.py new file mode 100644 index 0000000000..23010bbee3 --- /dev/null +++ b/tests/ip_messaging/test_members.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Members, Member +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MEMBER_SID = "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Members(BASE_URI, AUTH) + + +class MemberTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_member(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Members" % (BASE_URI) + list_resource.create('test_identity') + exp_params = { + 'Identity': "test_identity" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/member_instance.json") + mock.return_value = resp + + uri = "%s/Members/%s" % (BASE_URI, MEMBER_SID) + list_resource.get(MEMBER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Member(list_resource, "MB123") + app.delete() + uri = "%s/Members/MB123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_messages.py b/tests/ip_messaging/test_messages.py new file mode 100644 index 0000000000..49b5778225 --- /dev/null +++ b/tests/ip_messaging/test_messages.py @@ -0,0 +1,68 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Messages, Message +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx/Channels/CHxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +MESSAGE_SID = "MSaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Messages(BASE_URI, AUTH) + + +class MessageTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_message(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Messages" % (BASE_URI) + list_resource.create('TestBody') + exp_params = { + 'Body': "TestBody" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + mock.return_value = resp + + uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) + list_resource.get(MESSAGE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_update(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/message_instance.json") + mock.return_value = resp + + update_params = { + 'UniqueName': 'unique' + } + + uri = "%s/Messages/%s" % (BASE_URI, MESSAGE_SID) + list_resource.update(MESSAGE_SID, unique_name='unique') + + mock.assert_called_with("POST", uri, data=update_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Message(list_resource, "MS123") + app.delete() + uri = "%s/Messages/MS123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_roles.py b/tests/ip_messaging/test_roles.py new file mode 100644 index 0000000000..b8485361ee --- /dev/null +++ b/tests/ip_messaging/test_roles.py @@ -0,0 +1,57 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Roles, Role +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +ROLE_SID = "ROaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Roles(BASE_URI, AUTH) + + +class RoleTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get_role(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") + mock.return_value = resp + + uri = "%s/Roles/%s" % (BASE_URI, ROLE_SID) + list_resource.get(ROLE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_role(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/role_instance.json") + resp.status_code = 201 + mock.return_value = resp + + list_resource.create("Test Role", "deployment", "createChannel") + uri = "%s/Roles" % (BASE_URI) + mock.assert_called_with( + "POST", + uri, + data={ + 'FriendlyName': 'Test Role', + 'Type': 'deployment', + 'Permission': 'createChannel' + }, + auth=AUTH, + use_json_extension=False + ) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete_role(self, req): + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Role(list_resource, "RO123") + app.delete() + uri = "%s/Roles/RO123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_services.py b/tests/ip_messaging/test_services.py new file mode 100644 index 0000000000..e8bd5cde85 --- /dev/null +++ b/tests/ip_messaging/test_services.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Services, Service +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +SERVICE_SID = "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Services(BASE_URI, AUTH) + + +class ServiceTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_service(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Services" % (BASE_URI) + list_resource.create('TestService') + exp_params = { + 'FriendlyName': "TestService" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/service_instance.json") + mock.return_value = resp + + uri = "%s/Services/%s" % (BASE_URI, SERVICE_SID) + list_resource.get(SERVICE_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = Service(list_resource, "IS123") + app.delete() + uri = "https://ip-messaging.twilio.com/v1/Services/IS123" + req.assert_called_with("DELETE", uri) diff --git a/tests/ip_messaging/test_users.py b/tests/ip_messaging/test_users.py new file mode 100644 index 0000000000..7a92c15569 --- /dev/null +++ b/tests/ip_messaging/test_users.py @@ -0,0 +1,53 @@ +import unittest +from mock import patch, Mock +from twilio.rest.resources.ip_messaging import Users, User +from tests.tools import create_mock_json + +BASE_URI = "https://ip-messaging.twilio.com/v1/Services/ISxxx" +ACCOUNT_SID = "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +AUTH = (ACCOUNT_SID, "token") +USER_SID = "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +list_resource = Users(BASE_URI, AUTH) + + +class UserTest(unittest.TestCase): + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_create_user(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + resp.status_code = 201 + mock.return_value = resp + + uri = "%s/Users" % (BASE_URI) + list_resource.create('test_id') + exp_params = { + 'Identity': "test_id" + } + + mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.make_twilio_request") + def test_get(self, mock): + resp = create_mock_json("tests/resources/ip_messaging/user_instance.json") + mock.return_value = resp + + uri = "%s/Users/%s" % (BASE_URI, USER_SID) + list_resource.get(USER_SID) + + mock.assert_called_with("GET", uri, auth=AUTH, + use_json_extension=False) + + @patch("twilio.rest.resources.base.Resource.request") + def test_delete(self, req): + """ Deleting a call should work """ + resp = Mock() + resp.content = "" + resp.status_code = 204 + req.return_value = resp, {} + + app = User(list_resource, "US123") + app.delete() + uri = "%s/Users/US123" % (BASE_URI) + req.assert_called_with("DELETE", uri) diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index b1963720e0..f4077334aa 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -1,10 +1,11 @@ from .base import set_twilio_proxy from .client import TwilioRestClient +from .ip_messaging import TwilioIpMessagingClient from .lookups import TwilioLookupsClient from .pricing import TwilioPricingClient from .task_router import TwilioTaskRouterClient from .trunking import TwilioTrunkingClient -_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioLookupsClient, - TwilioPricingClient, TwilioTaskRouterClient, - TwilioTrunkingClient] +_hush_pyflakes = [set_twilio_proxy, TwilioRestClient, TwilioIpMessagingClient, + TwilioLookupsClient, TwilioPricingClient, + TwilioTaskRouterClient, TwilioTrunkingClient] diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py new file mode 100644 index 0000000000..91a07b3c30 --- /dev/null +++ b/twilio/rest/ip_messaging.py @@ -0,0 +1,31 @@ +from twilio.rest.base import TwilioClient +from twilio.rest.resources import UNSET_TIMEOUT +from twilio.rest.resources.ip_messaging.services import Services +from twilio.rest.resources.ip_messaging.credentials import Credentials + + +class TwilioIpMessagingClient(TwilioClient): + """ + A client for accessing the Twilio IP Messaging API. + + The Twilio IP Messaging API provides information about events. For more + information, see the + `IP Messaging API documentation `_. + + :param str account: Your Account Sid from `your dashboard + `_ + :param str token: Your Auth Token from `your dashboard + `_ + :param float timeout: The socket and read timeout for requests to Twilio + """ + + def __init__(self, account=None, token=None, + base="https://ip-messaging.twilio.com", version="v1", + timeout=UNSET_TIMEOUT): + + super(TwilioIpMessagingClient, self).__init__(account, token, base, + version, timeout) + + self.version_uri = "%s/%s" % (base, version) + self.services = Services(self.version_uri, self.auth, timeout) + self.credentials = Credentials(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/resources/ip_messaging/__init__.py b/twilio/rest/resources/ip_messaging/__init__.py new file mode 100644 index 0000000000..55f60ad203 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/__init__.py @@ -0,0 +1,34 @@ +from .services import ( + Service, + Services +) + +from .channels import ( + Channel, + Channels +) + +from .members import ( + Member, + Members +) + +from .messages import ( + Message, + Messages +) + +from .roles import ( + Role, + Roles +) + +from .users import ( + User, + Users +) + +from .credentials import ( + Credential, + Credentials +) diff --git a/twilio/rest/resources/ip_messaging/channels.py b/twilio/rest/resources/ip_messaging/channels.py new file mode 100644 index 0000000000..b7e4f5529a --- /dev/null +++ b/twilio/rest/resources/ip_messaging/channels.py @@ -0,0 +1,78 @@ +from .members import Members +from .messages import Messages +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Channel(NextGenInstanceResource): + + subresources = [ + Members, + Messages + ] + + def update(self, **kwargs): + """ + Updates the Channel instance + :param sid: Channel instance identifier + :param type: Channel type + :param friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param attributes: Additional attributes that needs to be stored with + channel + :return: the updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this channel + """ + return self.delete_instance() + + +class Channels(NextGenListResource): + + name = "Channels" + instance = Channel + + def list(self, **kwargs): + """ + Returns a page of :class:`Channel` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Channel instance resource representation. + """ + return self.get_instances(kwargs) + + def create(self, **kwargs): + """ + Create a channel. + + :param str friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param str attributes: Developer-specific data (json) storage + + :return: A :class:`Channel` object + """ + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Channel + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Channel instance identified by sid + :param sid: Channel instance identifier + :param type: Channel type + :param friendly_name: Channel's friendly name + :param unique_name: Channel's Unique name + :param attributes: Additional attributes that needs to be stored with + channel + :return: Updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/credentials.py b/twilio/rest/resources/ip_messaging/credentials.py new file mode 100644 index 0000000000..3b84f85364 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/credentials.py @@ -0,0 +1,80 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Credential(NextGenInstanceResource): + + def update(self, credential_type, **kwargs): + """ + Updates this Credential instance + :param sid: Credential instance identifier + :param credential_type: Credential type + :param friendly_name: Credential's friendly name + :param certificate: Credential's certificate + :param private_key: Credential's Private key + :param sandbox: Credential's Sandbox + :param api_key: Credential's Api Key + :return: Updated instance + """ + kwargs['type'] = credential_type + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this credential + """ + return self.delete_instance() + + +class Credentials(NextGenListResource): + + name = "Credentials" + instance = Credential + + def list(self, **kwargs): + """ + Returns a page of :class:`Credential` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Credential instance resource representation. + + :param date after: Only list alerts logged after this datetime + :param date before: Only list alerts logger before this datetime + :param log_level: If 'error', only shows errors. If 'warning', + only show warnings + """ + return self.get_instances(kwargs) + + def create(self, credential_type, **kwargs): + """ + Make a phone call to a number. + + :param str credential_type: The type of credential + :param str friendly_name: The friendly name of the credential. + + :return: A :class:`Credential` object + """ + kwargs["type"] = credential_type + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Credential + """ + return self.delete_instance(sid) + + def update(self, sid, credential_type, **kwargs): + """ + Updates the Credential instance identified by sid + :param sid: Credential instance identifier + :param credential_type: Credential type + :param friendly_name: Credential's friendly name + :param certificate: Credential's certificate + :param private_key: Credential's Private key + :param sandbox: Credential's Sandbox + :param api_key: Credential's Api Key + :return: Updated instance + """ + kwargs['type'] = credential_type + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/members.py b/twilio/rest/resources/ip_messaging/members.py new file mode 100644 index 0000000000..dfdaadf149 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/members.py @@ -0,0 +1,68 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Member(NextGenInstanceResource): + + def update(self, role_sid, **kwargs): + """ + Updates the Member instance identified by sid + :param sid: Member instance identifier + :param role_sid: Member's Role Sid + :param identity: Member's Identity + :return: Updated instance + """ + kwargs['role_sid'] = role_sid + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this member + """ + return self.delete_instance() + + +class Members(NextGenListResource): + + name = "Members" + instance = Member + + def list(self, **kwargs): + """ + Returns a page of :class:`Member` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Member instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, identity, **kwargs): + """ + Create a Member. + + :param str identity: The identity of the user. + :param str role: The role to assign the member. + + :return: A :class:`Member` object + """ + kwargs["identity"] = identity + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Member + """ + return self.delete_instance(sid) + + def update(self, sid, role_sid, **kwargs): + """ + Updates the Member instance identified by sid + :param sid: Member instance identifier + :param role_sid: Member's Role Sid + :param identity: Member's Identity + :return: Updated instance + """ + kwargs['role_sid'] = role_sid + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/messages.py b/twilio/rest/resources/ip_messaging/messages.py new file mode 100644 index 0000000000..848ba16095 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/messages.py @@ -0,0 +1,68 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Message(NextGenInstanceResource): + + def update(self, **kwargs): + """ + Updates the Message instance + :param sid: Message instance identifier + :param service_sid: Service instance identifier + :param channel_sid: Channel instance identifier + :param body: Message's body + :return: the updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this message + """ + return self.delete_instance() + + +class Messages(NextGenListResource): + + name = "Messages" + instance = Message + + def list(self, **kwargs): + """ + Returns a page of :class:`Message` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Message instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, body, **kwargs): + """ + Create a Message. + + :param str body: The body of the message. + :param str from: The message author's identity. + + :return: A :class:`Message` object + """ + kwargs["body"] = body + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Message + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Message instance identified by sid + :param sid: Message instance identifier + :param service_sid: Service instance identifier + :param channel_sid: Channel instance identifier + :param body: Message's body + :return: the updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/roles.py b/twilio/rest/resources/ip_messaging/roles.py new file mode 100644 index 0000000000..3e1f232842 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/roles.py @@ -0,0 +1,67 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Role(NextGenInstanceResource): + + def update(self, permission, **kwargs): + """ + Updates this Role instance + :param permission: Role permission + :return: Updated instance + """ + kwargs['permission'] = permission + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this role + """ + return self.delete_instance() + + +class Roles(NextGenListResource): + + name = "Roles" + instance = Role + + def list(self, **kwargs): + """ + Returns a page of :class:`Role` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Role instance resource representation. + + """ + return self.get_instances(kwargs) + + def delete(self, sid): + """ + Delete a given Role + """ + return self.delete_instance(sid) + + def create(self, friendly_name, role_type, permission): + """ + Creates a Role + :param str friendly_name: Human readable name to the Role + :param str role_type: Type of role - deployment or channel + :param str permission: Set of permissions for the role + """ + kwargs = { + "friendly_name": friendly_name, + "type": role_type, + "permission": permission + } + return self.create_instance(kwargs) + + def update(self, sid, permission, **kwargs): + """ + Updates the Role instance identified by sid + :param sid: Role instance identifier + :param permission: Role permission + :return: Updated instance + """ + kwargs['permission'] = permission + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/services.py b/twilio/rest/resources/ip_messaging/services.py new file mode 100644 index 0000000000..f22c30260a --- /dev/null +++ b/twilio/rest/resources/ip_messaging/services.py @@ -0,0 +1,69 @@ +from .channels import Channels +from .roles import Roles +from .users import Users +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class Service(NextGenInstanceResource): + + subresources = [ + Channels, + Roles, + Users + ] + + def update(self, **kwargs): + """ + Updates this Service instance + :return: Updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this service + """ + return self.delete_instance() + + +class Services(NextGenListResource): + + name = "Services" + instance = Service + + def list(self, **kwargs): + """ + Returns a page of :class:`Service` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the Service instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, friendly_name, **kwargs): + """ + Create a service. + + :param str friendly_name: The friendly name for the service + + :return: A :class:`Service` object + """ + kwargs["friendly_name"] = friendly_name + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given Service + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the Service instance identified by sid + :param sid: Service instance identifier + :return: Updated instance + """ + return self.update_instance(sid, kwargs) diff --git a/twilio/rest/resources/ip_messaging/users.py b/twilio/rest/resources/ip_messaging/users.py new file mode 100644 index 0000000000..1439772cc0 --- /dev/null +++ b/twilio/rest/resources/ip_messaging/users.py @@ -0,0 +1,63 @@ +from twilio.rest.resources import NextGenInstanceResource, NextGenListResource + + +class User(NextGenInstanceResource): + + def update(self, **kwargs): + """ + Updates this User instance + :param role_sid: The role to assign the user. + :return: Updated instance + """ + return self.update_instance(**kwargs) + + def delete(self): + """ + Delete this user + """ + return self.delete_instance() + + +class Users(NextGenListResource): + + name = "Users" + instance = User + + def list(self, **kwargs): + """ + Returns a page of :class:`User` resources as a list. + For paging information see :class:`ListResource`. + + **NOTE**: Due to the potentially voluminous amount of data in an + alert, the full HTTP request and response data is only returned + in the User instance resource representation. + + """ + return self.get_instances(kwargs) + + def create(self, identity, **kwargs): + """ + Creates a User + + :param str identity: The identity of the user. + :param str role_sid: The role to assign the user. + + :return: A :class:`User` object + """ + kwargs["identity"] = identity + return self.create_instance(kwargs) + + def delete(self, sid): + """ + Delete a given User + """ + return self.delete_instance(sid) + + def update(self, sid, **kwargs): + """ + Updates the User instance identified by sid + :param sid: User instance identifier + :param role_sid: The role to assign the user. + :return: Updated instance + """ + return self.update_instance(sid, kwargs) From 0f7b73c93ab687b0de0bc4e120042835de889522 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Wed, 16 Dec 2015 17:02:19 -0800 Subject: [PATCH 093/115] Test resources --- .../ip_messaging/channel_instance.json | 16 ++++++++++++++++ .../ip_messaging/credential_instance.json | 8 ++++++++ .../resources/ip_messaging/member_instance.json | 9 +++++++++ .../ip_messaging/message_instance.json | 12 ++++++++++++ tests/resources/ip_messaging/role_instance.json | 11 +++++++++++ .../ip_messaging/service_instance.json | 17 +++++++++++++++++ tests/resources/ip_messaging/user_instance.json | 10 ++++++++++ 7 files changed, 83 insertions(+) create mode 100644 tests/resources/ip_messaging/channel_instance.json create mode 100644 tests/resources/ip_messaging/credential_instance.json create mode 100644 tests/resources/ip_messaging/member_instance.json create mode 100644 tests/resources/ip_messaging/message_instance.json create mode 100644 tests/resources/ip_messaging/role_instance.json create mode 100644 tests/resources/ip_messaging/service_instance.json create mode 100644 tests/resources/ip_messaging/user_instance.json diff --git a/tests/resources/ip_messaging/channel_instance.json b/tests/resources/ip_messaging/channel_instance.json new file mode 100644 index 0000000000..938a62013a --- /dev/null +++ b/tests/resources/ip_messaging/channel_instance.json @@ -0,0 +1,16 @@ +{ + "sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "update", + "unique_name": "unique", + "attributes": "", + "date_created": "2015-08-20T09:30:24Z", + "date_updated": "2015-08-20T09:30:24Z", + "created_by": "system", + "url": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "members": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Member", + "messages": "https://ip-messaging.stage.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels/CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Messages" + } +} diff --git a/tests/resources/ip_messaging/credential_instance.json b/tests/resources/ip_messaging/credential_instance.json new file mode 100644 index 0000000000..9b24940277 --- /dev/null +++ b/tests/resources/ip_messaging/credential_instance.json @@ -0,0 +1,8 @@ +{ + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sid":"CRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name":"MyApp APN Certificate", + "type":"apn", + "date_created":"2015-06-30T21:16:50Z", + "date_updated":"2015-07-30T21:16:50Z" +} diff --git a/tests/resources/ip_messaging/member_instance.json b/tests/resources/ip_messaging/member_instance.json new file mode 100644 index 0000000000..adc75ddcfe --- /dev/null +++ b/tests/resources/ip_messaging/member_instance.json @@ -0,0 +1,9 @@ +{ + "sid": "MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "channel_sid": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "id": "carl@twilio.com", + "role": "admin", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Members/MBaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/message_instance.json b/tests/resources/ip_messaging/message_instance.json new file mode 100644 index 0000000000..acbe53124f --- /dev/null +++ b/tests/resources/ip_messaging/message_instance.json @@ -0,0 +1,12 @@ +{ + "sid": "IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-07-23T20:20:10Z", + "date_updated": "2015-07-23T20:20:10Z", + "was_edited": true, + "from": "carl@twilio.com", + "to": "CHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "body": "Hello", + "url": "/v1/Spaces/SPxx/Channels/CHxx/Messages/IMaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/tests/resources/ip_messaging/role_instance.json b/tests/resources/ip_messaging/role_instance.json new file mode 100644 index 0000000000..bbd604428c --- /dev/null +++ b/tests/resources/ip_messaging/role_instance.json @@ -0,0 +1,11 @@ +{ + "sid":"RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid":"ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "space_sid": "SPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name":"Deployment Admin", + "type":"deployment", + "permissions":[ + "createChannel", + "destroyChannel" + ] +} diff --git a/tests/resources/ip_messaging/service_instance.json b/tests/resources/ip_messaging/service_instance.json new file mode 100644 index 0000000000..bc4d56e4a9 --- /dev/null +++ b/tests/resources/ip_messaging/service_instance.json @@ -0,0 +1,17 @@ +{ + "sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "friendly_name": "TestService", + "date_created": "2015-10-21T04:15:36Z", + "date_updated": "2015-10-21T04:15:36Z", + "default_service_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "default_channel_role_sid": "RLaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "typing_indicator_timeout": 5, + "webhooks": {}, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "links": { + "channels": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Channels", + "roles": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Roles", + "users": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users" + } +} diff --git a/tests/resources/ip_messaging/user_instance.json b/tests/resources/ip_messaging/user_instance.json new file mode 100644 index 0000000000..a2326cc20c --- /dev/null +++ b/tests/resources/ip_messaging/user_instance.json @@ -0,0 +1,10 @@ +{ + "sid": "USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "date_created": "2015-08-19T18:18:00Z", + "date_updated": "2015-08-19T18:18:00Z", + "identity": "carl@twilio.com", + "service_sid": "ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "account_sid": "ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "role_sid": null, + "url": "https://ip-messaging.twilio.com/v1/Services/ISaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Users/USaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} From ea14998750680fefc00480d195c18d4f0973a885 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Thu, 17 Dec 2015 11:21:16 -0800 Subject: [PATCH 094/115] Bump version to 5.2.0 for releasing IP Messaging SDK --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d9e94e5c7d..272f9c7b3e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.2.0 +------------- + +Released December 17, 2015: + +- Add support for IP Messaging + Version 5.1.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 72ebfd9835..f553282683 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('5', '1', '0') +__version_info__ = ('5', '2', '0') __version__ = '.'.join(__version_info__) From 0cace2eda4caf038b8283828e513941c60e7cb11 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 12 Jan 2016 11:44:58 -0800 Subject: [PATCH 095/115] Supporting filter_friendly_name --- tests/task_router/test_workflow_config.py | 51 +++++++++++++++++++++++ twilio/task_router/taskrouter_config.py | 23 +++++++++- twilio/task_router/workflow_rule.py | 2 +- twilio/task_router/workflow_ruletarget.py | 2 +- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 458fecc045..724051ae9c 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -78,6 +78,57 @@ def test_from_json2(self): self.assertEqual(2, len(config.task_routing.filters)) self.assertEqual(4, len(config.task_routing.default_filter)) + def test_from_json_filter_friendly_name(self): + + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'filter_friendly_name': 'Sales', + 'targets': [ + { + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'filter_friendly_name': 'Marketing', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'filter_friendly_name': 'Support', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } + + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) + self.assertEqual("Sales", config.task_routing.workflow_rules[0].friendly_name) + self.assertEqual("Marketing", config.task_routing.workflow_rules[1].friendly_name) + self.assertEqual("Support", config.task_routing.workflow_rules[2].friendly_name) + def is_json(self, myjson): try: json.loads(myjson) diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index b4e8eb7b55..522f02c5cd 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -10,8 +10,29 @@ class TaskRouterConfig: """ def __init__(self, rules, default_target): + self.filters = rules self.default_filter = default_target + self.workflow_rules = [] + + for rule in rules: + if isinstance(rule, WorkflowRule): + self.workflow_rules.append(rule) + else: + try: + name = rule['friendly_name'] + except KeyError: + name = rule['filter_friendly_name'] + self.workflow_rules.append( + WorkflowRule(rule['expression'], rule['targets'], name)) + + + def __repr__(self): - return self.__dict__ + + return str({ + 'workflow_rules': self.workflow_rules, + 'default': self.default_filter, + 'rules': self.rules + }) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index 3cae68ec80..5c4c1fd71c 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -30,5 +30,5 @@ def __repr__(self): return str({ 'expression': self.expression, 'friendly_name': self.friendly_name, - 'target': self.target, + 'targets': self.targets, }) diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py index 1cee506c30..9cf95a65f7 100644 --- a/twilio/task_router/workflow_ruletarget.py +++ b/twilio/task_router/workflow_ruletarget.py @@ -19,7 +19,7 @@ class WorkflowRuleTarget: The timeout before the reservation expires. """ - def __init__(self, queue, expression, priority, timeout): + def __init__(self, queue, expression, priority=None, timeout=None): self.queue = queue self.expression = expression From 3d79b5762911bfed480ce208fc42287d126d6b38 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 12 Jan 2016 12:09:48 -0800 Subject: [PATCH 096/115] Moving imports to beginning of init; removing excess blank lines --- twilio/task_router/__init__.py | 19 ++++--------------- twilio/task_router/taskrouter_config.py | 3 --- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index c77feeca29..028b34f6a6 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -1,5 +1,9 @@ import time from .. import jwt +from .taskrouter_config import TaskRouterConfig +from .workflow_config import WorkflowConfig +from .workflow_ruletarget import WorkflowRuleTarget +from .workflow_rule import WorkflowRule import warnings warnings.simplefilter('always', DeprecationWarning) @@ -248,18 +252,3 @@ def __init__(self, account_sid, auth_token, workspace_sid): def setup_resource(self): self.resource_url = self.base_url - -from .taskrouter_config import ( - TaskRouterConfig -) - -from .workflow_config import ( - WorkflowConfig -) - -from .workflow_ruletarget import ( - WorkflowRuleTarget -) -from .workflow_rule import ( - WorkflowRule -) diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index 522f02c5cd..daa66922c6 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -26,9 +26,6 @@ def __init__(self, rules, default_target): self.workflow_rules.append( WorkflowRule(rule['expression'], rule['targets'], name)) - - - def __repr__(self): return str({ From 5500cba846e5b1784aaa6bed2e959981c5350855 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Sun, 17 Jan 2016 21:15:02 -0800 Subject: [PATCH 097/115] Using only filters instead of workflow_rules; added check for friendly_name when marshalling back --- tests/task_router/test_workflow_config.py | 61 +++++++++++++++++++++-- twilio/task_router/taskrouter_config.py | 26 ++++++---- twilio/task_router/workflow_rule.py | 3 +- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 724051ae9c..05b6c4ab1f 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -14,7 +14,6 @@ def test_to_json(self): ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules, def_target) - self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): @@ -125,9 +124,59 @@ def test_from_json_filter_friendly_name(self): config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(3, len(config.task_routing.filters)) self.assertEqual(1, len(config.task_routing.default_filter)) - self.assertEqual("Sales", config.task_routing.workflow_rules[0].friendly_name) - self.assertEqual("Marketing", config.task_routing.workflow_rules[1].friendly_name) - self.assertEqual("Support", config.task_routing.workflow_rules[2].friendly_name) + self.assertEqual("Sales", config.task_routing.filters[0].friendly_name) + self.assertEqual("Marketing", config.task_routing.filters[1].friendly_name) + self.assertEqual("Support", config.task_routing.filters[2].friendly_name) + + # convert back to json; should marshal as friendly_name + config_json = config.to_json() + expected_config_data = { + "task_routing": { + "default_filter": { + "queue": "WQ05f810d2d130344fd56e3c91ece2e594" + }, + "filters": [ + { + "expression": "type == \"sales\"", + "friendly_name": "Sales", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQec62de0e1148b8477f2e24579779c8b1" + } + ] + }, + { + "expression": "type == \"marketing\"", + "friendly_name": "Marketing", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" + } + ] + }, + { + "expression": "type == \"support\"", + "friendly_name": "Support", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQe5eb317eb23500ade45087ea6522896c" + } + ] + } + ] + } + } + + expected_config_json = json.dumps(expected_config_data, + default=lambda o: o.__dict__, + sort_keys=True, + indent=4) + + self.assertEqual(config_json, expected_config_json) + def is_json(self, myjson): try: @@ -136,3 +185,7 @@ def is_json(self, myjson): print(e) return False return True + +if __name__ == '__main__': + unittest.main() + diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index daa66922c6..f0c4cfd0c5 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -1,5 +1,6 @@ from .workflow_rule import WorkflowRule from .workflow_ruletarget import WorkflowRuleTarget +import json class TaskRouterConfig: @@ -11,25 +12,30 @@ class TaskRouterConfig: def __init__(self, rules, default_target): - self.filters = rules self.default_filter = default_target - self.workflow_rules = [] + workflow_rules = [] for rule in rules: if isinstance(rule, WorkflowRule): - self.workflow_rules.append(rule) + workflow_rules.append(rule) else: try: name = rule['friendly_name'] except KeyError: name = rule['filter_friendly_name'] - self.workflow_rules.append( + workflow_rules.append( WorkflowRule(rule['expression'], rule['targets'], name)) + self.filters = workflow_rules - def __repr__(self): + def to_json(self): - return str({ - 'workflow_rules': self.workflow_rules, - 'default': self.default_filter, - 'rules': self.rules - }) + return json.dumps(self, + default=lambda o: o.__dict__, + sort_keys=True, + indent=4) + + @staticmethod + def json2obj(data): + + m = json.loads(data) + return TaskRouterConfig(m['filters'], m['default_filter']) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index 5c4c1fd71c..3eabcc892f 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -1,4 +1,5 @@ from .workflow_ruletarget import WorkflowRuleTarget +import json class WorkflowRule: @@ -20,7 +21,7 @@ class WorkflowRule: The name of the filter """ - def __init__(self, expression, targets, friendly_name): + def __init__(self, expression, targets, friendly_name=None): self.expression = expression self.targets = targets From 3c0b395cde07f94165e9d4b4c5e79819bc17f649 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Sun, 17 Jan 2016 21:44:29 -0800 Subject: [PATCH 098/115] fixing indentations, fixed deletion of assertEqual --- tests/task_router/test_workflow_config.py | 90 +++++++++++------------ twilio-python.iml | 9 +++ twilio/task_router/taskrouter_config.py | 1 - 3 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 twilio-python.iml diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 05b6c4ab1f..3ec713ef8d 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -14,6 +14,7 @@ def test_to_json(self): ] def_target = WorkflowRuleTarget("WQ9963154bf3122d0a0558f3763951d916", "1==1", None, None) config = WorkflowConfig(rules, def_target) + self.assertEqual(True, self.is_json(config.to_json())) def test_from_json(self): @@ -85,7 +86,7 @@ def test_from_json_filter_friendly_name(self): 'filters': [ { 'expression': 'type == "sales"', - 'filter_friendly_name': 'Sales', + 'friendly_name': 'Sales', 'targets': [ { 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', @@ -95,7 +96,7 @@ def test_from_json_filter_friendly_name(self): }, { 'expression': 'type == "marketing"', - 'filter_friendly_name': 'Marketing', + 'friendly_name': 'Marketing', 'targets': [ { 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', @@ -105,7 +106,7 @@ def test_from_json_filter_friendly_name(self): }, { 'expression': 'type == "support"', - 'filter_friendly_name': 'Support', + 'friendly_name': 'Support', 'targets': [ { 'queue': 'WQe5eb317eb23500ade45087ea6522896c', @@ -131,44 +132,46 @@ def test_from_json_filter_friendly_name(self): # convert back to json; should marshal as friendly_name config_json = config.to_json() expected_config_data = { - "task_routing": { - "default_filter": { - "queue": "WQ05f810d2d130344fd56e3c91ece2e594" - }, - "filters": [ - { - "expression": "type == \"sales\"", - "friendly_name": "Sales", - "targets": [ - { - "expression": "task.language IN worker.languages", - "queue": "WQec62de0e1148b8477f2e24579779c8b1" - } - ] - }, - { - "expression": "type == \"marketing\"", - "friendly_name": "Marketing", - "targets": [ - { - "expression": "task.language IN worker.languages", - "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" - } - ] - }, - { - "expression": "type == \"support\"", - "friendly_name": "Support", - "targets": [ - { - "expression": "task.language IN worker.languages", - "queue": "WQe5eb317eb23500ade45087ea6522896c" - } - ] - } - ] - } - } + "task_routing": + { + "default_filter": + { + "queue": "WQ05f810d2d130344fd56e3c91ece2e594" + }, + "filters": [ + { + "expression": "type == \"sales\"", + "friendly_name": "Sales", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQec62de0e1148b8477f2e24579779c8b1" + } + ] + }, + { + "expression": "type == \"marketing\"", + "friendly_name": "Marketing", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" + } + ] + }, + { + "expression": "type == \"support\"", + "friendly_name": "Support", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQe5eb317eb23500ade45087ea6522896c" + } + ] + } + ] + } + } expected_config_json = json.dumps(expected_config_data, default=lambda o: o.__dict__, @@ -177,7 +180,6 @@ def test_from_json_filter_friendly_name(self): self.assertEqual(config_json, expected_config_json) - def is_json(self, myjson): try: json.loads(myjson) @@ -185,7 +187,3 @@ def is_json(self, myjson): print(e) return False return True - -if __name__ == '__main__': - unittest.main() - diff --git a/twilio-python.iml b/twilio-python.iml new file mode 100644 index 0000000000..8021953ed9 --- /dev/null +++ b/twilio-python.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index f0c4cfd0c5..c3bbe67b2f 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -1,5 +1,4 @@ from .workflow_rule import WorkflowRule -from .workflow_ruletarget import WorkflowRuleTarget import json From ee487bade53c95063696bad5a7d69dacef20a808 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Sun, 17 Jan 2016 21:48:16 -0800 Subject: [PATCH 099/115] removing .iml file --- twilio-python.iml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 twilio-python.iml diff --git a/twilio-python.iml b/twilio-python.iml deleted file mode 100644 index 8021953ed9..0000000000 --- a/twilio-python.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file From 79daa8dc2c80d75e5b30038688fbaf7b757defb0 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 19 Jan 2016 14:19:01 -0800 Subject: [PATCH 100/115] Reverting all changes; parsing user input for filter_friendly_name and changing to friendly_name --- tests/task_router/test_workflow_config.py | 23 +++++++---------- twilio/task_router/taskrouter_config.py | 30 +++-------------------- twilio/task_router/workflow_config.py | 3 +++ twilio/task_router/workflow_rule.py | 1 - 4 files changed, 16 insertions(+), 41 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 3ec713ef8d..a4a5000706 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -78,17 +78,17 @@ def test_from_json2(self): self.assertEqual(2, len(config.task_routing.filters)) self.assertEqual(4, len(config.task_routing.default_filter)) - def test_from_json_filter_friendly_name(self): - + def test_from_json_with_filter_friendly_name(self): data = { 'task_routing': { 'filters': [ { 'expression': 'type == "sales"', - 'friendly_name': 'Sales', + 'filter_friendly_name': 'Sales', 'targets': [ { + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', 'expression': 'task.language IN worker.languages' } @@ -96,7 +96,7 @@ def test_from_json_filter_friendly_name(self): }, { 'expression': 'type == "marketing"', - 'friendly_name': 'Marketing', + 'filter_friendly_name': 'Marketing', 'targets': [ { 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', @@ -106,7 +106,7 @@ def test_from_json_filter_friendly_name(self): }, { 'expression': 'type == "support"', - 'friendly_name': 'Support', + 'filter_friendly_name': 'Support', 'targets': [ { 'queue': 'WQe5eb317eb23500ade45087ea6522896c', @@ -121,16 +121,12 @@ def test_from_json_filter_friendly_name(self): } } } - + # marshal object config = WorkflowConfig.json2obj(json.dumps(data)) self.assertEqual(3, len(config.task_routing.filters)) self.assertEqual(1, len(config.task_routing.default_filter)) - self.assertEqual("Sales", config.task_routing.filters[0].friendly_name) - self.assertEqual("Marketing", config.task_routing.filters[1].friendly_name) - self.assertEqual("Support", config.task_routing.filters[2].friendly_name) - # convert back to json; should marshal as friendly_name - config_json = config.to_json() + # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name" expected_config_data = { "task_routing": { @@ -174,11 +170,10 @@ def test_from_json_filter_friendly_name(self): } expected_config_json = json.dumps(expected_config_data, - default=lambda o: o.__dict__, sort_keys=True, indent=4) - - self.assertEqual(config_json, expected_config_json) + # check that marshaling back stays as "friendly_name" + self.assertEqual(config.to_json(), expected_config_json) def is_json(self, myjson): try: diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index c3bbe67b2f..be17c8334c 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -1,4 +1,5 @@ from .workflow_rule import WorkflowRule +from .workflow_ruletarget import WorkflowRuleTarget import json @@ -10,31 +11,8 @@ class TaskRouterConfig: """ def __init__(self, rules, default_target): - + self.filters = rules self.default_filter = default_target - workflow_rules = [] - - for rule in rules: - if isinstance(rule, WorkflowRule): - workflow_rules.append(rule) - else: - try: - name = rule['friendly_name'] - except KeyError: - name = rule['filter_friendly_name'] - workflow_rules.append( - WorkflowRule(rule['expression'], rule['targets'], name)) - self.filters = workflow_rules - - def to_json(self): - - return json.dumps(self, - default=lambda o: o.__dict__, - sort_keys=True, - indent=4) - - @staticmethod - def json2obj(data): - m = json.loads(data) - return TaskRouterConfig(m['filters'], m['default_filter']) + def __repr__(self): + return self.__dict__ diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py index e9c27aa378..8790559b7d 100644 --- a/twilio/task_router/workflow_config.py +++ b/twilio/task_router/workflow_config.py @@ -21,6 +21,9 @@ def to_json(self): @staticmethod def json2obj(data): + # replace instances of "filter_friendly_name" with "friendly_name" + data = data.replace("filter_friendly_name", "friendly_name") + m = json.loads(data) return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index 3eabcc892f..efe6bc1bbc 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -1,5 +1,4 @@ from .workflow_ruletarget import WorkflowRuleTarget -import json class WorkflowRule: From 3bb6b3f0be07c1c0911402f9bbe3edb835831f67 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 19 Jan 2016 14:20:18 -0800 Subject: [PATCH 101/115] Reverting optional param --- twilio/task_router/workflow_rule.py | 2 +- twilio/task_router/workflow_ruletarget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/twilio/task_router/workflow_rule.py b/twilio/task_router/workflow_rule.py index efe6bc1bbc..5c4c1fd71c 100644 --- a/twilio/task_router/workflow_rule.py +++ b/twilio/task_router/workflow_rule.py @@ -20,7 +20,7 @@ class WorkflowRule: The name of the filter """ - def __init__(self, expression, targets, friendly_name=None): + def __init__(self, expression, targets, friendly_name): self.expression = expression self.targets = targets diff --git a/twilio/task_router/workflow_ruletarget.py b/twilio/task_router/workflow_ruletarget.py index 9cf95a65f7..1cee506c30 100644 --- a/twilio/task_router/workflow_ruletarget.py +++ b/twilio/task_router/workflow_ruletarget.py @@ -19,7 +19,7 @@ class WorkflowRuleTarget: The timeout before the reservation expires. """ - def __init__(self, queue, expression, priority=None, timeout=None): + def __init__(self, queue, expression, priority, timeout): self.queue = queue self.expression = expression From 11266b0d5bfc569f93d6dd7addf615a4d4e19530 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 19 Jan 2016 14:23:03 -0800 Subject: [PATCH 102/115] remove unnecessary import --- twilio/task_router/taskrouter_config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index be17c8334c..482967cff0 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -1,7 +1,5 @@ from .workflow_rule import WorkflowRule from .workflow_ruletarget import WorkflowRuleTarget -import json - class TaskRouterConfig: From 571563199050207db1b6076117aca71bdf2d7389 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 19 Jan 2016 14:25:52 -0800 Subject: [PATCH 103/115] fixed blank line --- twilio/task_router/taskrouter_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index 482967cff0..b4e8eb7b55 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -1,6 +1,7 @@ from .workflow_rule import WorkflowRule from .workflow_ruletarget import WorkflowRuleTarget + class TaskRouterConfig: """ From cba4bfbe6832e5074f193c16caeda50e78bbf610 Mon Sep 17 00:00:00 2001 From: Wanjun Li Date: Tue, 19 Jan 2016 19:18:59 -0800 Subject: [PATCH 104/115] Checking for filter_friendly_name in TaskrouterConfig --- tests/task_router/test_workflow_config.py | 100 ++++++++++++++++++++++ twilio/task_router/taskrouter_config.py | 6 ++ twilio/task_router/workflow_config.py | 3 - 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index a4a5000706..4aa99213de 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -175,6 +175,106 @@ def test_from_json_with_filter_friendly_name(self): # check that marshaling back stays as "friendly_name" self.assertEqual(config.to_json(), expected_config_json) + def test_from_json_with_both_filter_and_friendly_name(self): + data = { + 'task_routing': + { + 'filters': [ + { + 'expression': 'type == "sales"', + 'filter_friendly_name': "Sales", + 'friendly_name': 'Sales2', + 'targets': [ + { + + 'queue': 'WQec62de0e1148b8477f2e24579779c8b1', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "marketing"', + 'filter_friendly_name': 'Marketing', + 'friendly_name': 'Marketing2', + 'targets': [ + { + 'queue': 'WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f', + 'expression': 'task.language IN worker.languages' + } + ] + }, + { + 'expression': 'type == "support"', + 'filter_friendly_name': 'Support', + 'friendly_name': 'Support2', + 'targets': [ + { + 'queue': 'WQe5eb317eb23500ade45087ea6522896c', + 'expression': 'task.language IN worker.languages' + } + ] + } + ], + 'default_filter': + { + 'queue': 'WQ05f810d2d130344fd56e3c91ece2e594' + } + } + } + # marshal object + config = WorkflowConfig.json2obj(json.dumps(data)) + self.assertEqual(3, len(config.task_routing.filters)) + self.assertEqual(1, len(config.task_routing.default_filter)) + + # check that the configuration was marshaled to "friendly_name" and not "filter_friendly_name" + expected_config_data = { + "task_routing": + { + "default_filter": + { + "queue": "WQ05f810d2d130344fd56e3c91ece2e594" + }, + "filters": [ + { + "expression": "type == \"sales\"", + "friendly_name": "Sales", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQec62de0e1148b8477f2e24579779c8b1" + } + ] + }, + { + "expression": "type == \"marketing\"", + "friendly_name": "Marketing", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQ2acd4c1a41ffadce5d1bac9e1ce2fa9f" + } + ] + }, + { + "expression": "type == \"support\"", + "friendly_name": "Support", + "targets": [ + { + "expression": "task.language IN worker.languages", + "queue": "WQe5eb317eb23500ade45087ea6522896c" + } + ] + } + ] + } + } + + expected_config_json = json.dumps(expected_config_data, + sort_keys=True, + indent=4) + # check that marshaling back stays as "friendly_name" + self.assertEqual(config.to_json(), expected_config_json) + def is_json(self, myjson): try: json.loads(myjson) diff --git a/twilio/task_router/taskrouter_config.py b/twilio/task_router/taskrouter_config.py index b4e8eb7b55..22c04f1913 100644 --- a/twilio/task_router/taskrouter_config.py +++ b/twilio/task_router/taskrouter_config.py @@ -13,5 +13,11 @@ def __init__(self, rules, default_target): self.filters = rules self.default_filter = default_target + for rule in self.filters: + if not isinstance(rule, WorkflowRule): + filter_friendly_name = rule.pop('filter_friendly_name', None) + if filter_friendly_name is not None: + rule['friendly_name'] = filter_friendly_name + def __repr__(self): return self.__dict__ diff --git a/twilio/task_router/workflow_config.py b/twilio/task_router/workflow_config.py index 8790559b7d..e9c27aa378 100644 --- a/twilio/task_router/workflow_config.py +++ b/twilio/task_router/workflow_config.py @@ -21,9 +21,6 @@ def to_json(self): @staticmethod def json2obj(data): - # replace instances of "filter_friendly_name" with "friendly_name" - data = data.replace("filter_friendly_name", "friendly_name") - m = json.loads(data) return WorkflowConfig(m['task_routing']['filters'], m['task_routing']['default_filter']) From 1e7ec9cddd9c09478bc4b013a54accc7a76fee8c Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Thu, 21 Jan 2016 15:59:46 -0800 Subject: [PATCH 105/115] Allow fetching Reservations under a Worker by default Allow updating Reservations under a Worker --- tests/task_router/test_capability.py | 17 +++++++---- .../test_task_router_capability.py | 28 +++++++++--------- .../test_task_router_worker_capability.py | 27 +++++++++-------- twilio/task_router/__init__.py | 29 ++++++++++++------- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/tests/task_router/test_capability.py b/tests/task_router/test_capability.py index de105db281..70a5e46bb1 100644 --- a/tests/task_router/test_capability.py +++ b/tests/task_router/test_capability.py @@ -59,29 +59,36 @@ def test_defaults(self): ) expected = [ { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', + 'url': websocket_url, 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, { - 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid), + 'url': websocket_url, + 'method': 'POST', + 'allow': True, + 'query_filter': {}, + 'post_filter': {}, + }, + { + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities', 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, { - 'url': websocket_url, + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**'.format(self.workspace_sid), 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, }, { - 'url': websocket_url, - 'method': 'POST', + 'url': 'https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**'.format(self.workspace_sid, self.worker_sid), + 'method': 'GET', 'allow': True, 'query_filter': {}, 'post_filter': {}, diff --git a/tests/task_router/test_task_router_capability.py b/tests/task_router/test_task_router_capability.py index 78116c0db1..e1d67efad9 100644 --- a/tests/task_router/test_task_router_capability.py +++ b/tests/task_router/test_task_router_capability.py @@ -72,14 +72,15 @@ def test_worker_default(self): self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 5) + self.assertEqual(len(policies), 6) for method, url, policy in [ - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), - ('GET', "https://taskrouter.twilio.com/v1/wschannels/AC123/WK789", policies[2]), - ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5]) ]: yield self.check_policy, method, url, policy @@ -128,15 +129,16 @@ def test_deprecated_worker(self): self.check_decoded(decoded, account_sid, workspace_sid, worker_sid, worker_sid) policies = decoded['policies'] - self.assertEqual(len(policies), 5) + self.assertEqual(len(policies), 6) - # should expect 5 policies + # should expect 6 policies for method, url, policy in [ - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[0]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[1]), - ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[2]), - ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[3]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[4]) + ('GET', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[0]), + ('POST', "https://event-bridge.twilio.com/v1/wschannels/AC123/WK789", policies[1]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[2]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[5]) ]: yield self.check_policy, method, url, policy diff --git a/tests/task_router/test_task_router_worker_capability.py b/tests/task_router/test_task_router_worker_capability.py index 83feaf6b8c..97ac7d0160 100644 --- a/tests/task_router/test_task_router_worker_capability.py +++ b/tests/task_router/test_task_router_worker_capability.py @@ -72,17 +72,18 @@ def test_defaults(self): websocket_url = 'https://event-bridge.twilio.com/v1/wschannels/{0}/{1}'.format(self.account_sid, self.worker_sid) - # expect 5 policies + # expect 6 policies policies = decoded['policies'] - self.assertEqual(len(policies), 5) + self.assertEqual(len(policies), 6) - # should expect 5 policies + # should expect 6 policies for method, url, policy in [ ('GET', websocket_url, policies[0]), ('POST', websocket_url, policies[1]), ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789", policies[2]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[3]), - ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[4]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Activities", policies[3]) + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Tasks/**", policies[4]), + ('GET', "https://taskrouter.twilio.com/v1/Workspaces/WS456/Workers/WK789/Reservations/**", policies[5]) ]: yield self.check_policy, method, url, policy @@ -98,8 +99,8 @@ def test_allow_activity_updates(self): self.assertNotEqual(None, decoded) policies = decoded['policies'] - self.assertEqual(len(policies), 6) - policy = policies[5] + self.assertEqual(len(policies), 7) + policy = policies[6] url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}".format(self.workspace_sid, self.worker_sid) @@ -121,13 +122,15 @@ def test_allow_reservation_updates(self): self.assertNotEqual(None, decoded) policies = decoded['policies'] - self.assertEqual(len(policies), 6) - - policy = policies[5] + self.assertEqual(len(policies), 8) - url = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid) + taskPolicy = policies[6] + tasksUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Tasks/**".format(self.workspace_sid) + self.check_policy('POST', tasksUrl, taskPolicy) - self.check_policy('POST', url, policy) + workerReservationsPolicy = policies[7] + reservationsUrl = "https://taskrouter.twilio.com/v1/Workspaces/{0}/Workers/{1}/Reservations/**".format(self.workspace_sid, self.worker_sid) + self.check_policy('POST', reservationsUrl, workerReservationsPolicy) if __name__ == "__main__": unittest.main() diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index c77feeca29..f14ee9becf 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -39,12 +39,12 @@ def __init__(self, account_sid, auth_token, workspace_sid, channel_id): # validate the JWT self.validate_jwt() - # set up resources - self.setup_resource() - # add permissions to GET and POST to the event-bridge channel self.allow_web_sockets(channel_id) + # set up resources + self.setup_resource() + # add permissions to fetch the instance resource self.add_policy(self.resource_url, "GET", True) @@ -61,8 +61,11 @@ def setup_resource(self): activity_url = self.base_url + "/Activities" self.allow(activity_url, "GET") - reservations_url = self.base_url + "/Tasks/**" - self.allow(reservations_url, "GET") + tasks_url = self.base_url + "/Tasks/**" + self.allow(tasks_url, "GET") + + worker_reservations_url = self.resource_url + "/Reservations/**" + self.allow(worker_reservations_url, "GET") elif self.channel_prefix == "WQ": self.resource_url = "{0}/TaskQueues/{1}".format( @@ -209,13 +212,15 @@ def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): workspace_sid, worker_sid) - self.reservations_url = self.base_url + "/Tasks/**" self.activity_url = self.base_url + "/Activities" + self.tasks_url = self.base_url + "/Tasks/**" + self.worker_reservations_url = self.resource_url + "/Reservations/**" - # add permissions to fetch the list of activities and - # list of worker reservations - self.allow(self.reservations_url, "GET") + # add permissions to fetch the + # list of activities, tasks, and worker reservations self.allow(self.activity_url, "GET") + self.allow(self.tasks_url, "GET") + self.allow(self.worker_reservations_url, "GET") def setup_resource(self): self.resource_url = self.base_url + "/Workers/" + self.channel_id @@ -229,7 +234,11 @@ def allow_activity_updates(self): def allow_reservation_updates(self): self.policies.append(self.make_policy( - self.reservations_url, + self.tasks_url, + 'POST', + True)) + self.policies.append(self.make_policy( + self.worker_reservations_url, 'POST', True)) From 32444e8283aa36e1ddb1b527624acf33a55d2749 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Fri, 22 Jan 2016 13:40:51 -0800 Subject: [PATCH 106/115] Update location of imports --- twilio/task_router/__init__.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index f14ee9becf..c7963a8f83 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -1,6 +1,21 @@ import time from .. import jwt +from .taskrouter_config import ( + TaskRouterConfig +) + +from .workflow_config import ( + WorkflowConfig +) + +from .workflow_ruletarget import ( + WorkflowRuleTarget +) +from .workflow_rule import ( + WorkflowRule +) + import warnings warnings.simplefilter('always', DeprecationWarning) @@ -257,18 +272,3 @@ def __init__(self, account_sid, auth_token, workspace_sid): def setup_resource(self): self.resource_url = self.base_url - -from .taskrouter_config import ( - TaskRouterConfig -) - -from .workflow_config import ( - WorkflowConfig -) - -from .workflow_ruletarget import ( - WorkflowRuleTarget -) -from .workflow_rule import ( - WorkflowRule -) From 8e7c696d7da871b5a5d0f968c516bd0ed5c4deb8 Mon Sep 17 00:00:00 2001 From: Justin Witz Date: Wed, 27 Jan 2016 14:27:45 -0800 Subject: [PATCH 107/115] Revert back to reservations_url --- twilio/task_router/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index c7963a8f83..bed9950a1b 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -228,13 +228,13 @@ def __init__(self, account_sid, auth_token, workspace_sid, worker_sid): worker_sid) self.activity_url = self.base_url + "/Activities" - self.tasks_url = self.base_url + "/Tasks/**" + self.reservations_url = self.base_url + "/Tasks/**" self.worker_reservations_url = self.resource_url + "/Reservations/**" # add permissions to fetch the # list of activities, tasks, and worker reservations self.allow(self.activity_url, "GET") - self.allow(self.tasks_url, "GET") + self.allow(self.reservations_url, "GET") self.allow(self.worker_reservations_url, "GET") def setup_resource(self): @@ -249,7 +249,7 @@ def allow_activity_updates(self): def allow_reservation_updates(self): self.policies.append(self.make_policy( - self.tasks_url, + self.reservations_url, 'POST', True)) self.policies.append(self.make_policy( From bae308162e9e9505a73651d1de0a946e59c30267 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 28 Jan 2016 13:33:43 -0800 Subject: [PATCH 108/115] Bump to version 5.3.0 --- CHANGES.md | 8 ++++++++ twilio/task_router/__init__.py | 15 --------------- twilio/version.py | 2 +- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 272f9c7b3e..18c3cec074 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,14 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.3.0 +------------- + +Released January 28, 2016: + +- Add support for filter_friendly_name in WorkflowConfig +- Load reservations by default in TaskRouter + Version 5.2.0 ------------- diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index 6ccf74400a..ae892eda49 100644 --- a/twilio/task_router/__init__.py +++ b/twilio/task_router/__init__.py @@ -5,21 +5,6 @@ from .workflow_ruletarget import WorkflowRuleTarget from .workflow_rule import WorkflowRule -from .taskrouter_config import ( - TaskRouterConfig -) - -from .workflow_config import ( - WorkflowConfig -) - -from .workflow_ruletarget import ( - WorkflowRuleTarget -) -from .workflow_rule import ( - WorkflowRule -) - import warnings warnings.simplefilter('always', DeprecationWarning) diff --git a/twilio/version.py b/twilio/version.py index f553282683..7d0e940950 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('5', '2', '0') +__version_info__ = ('5', '3', '0') __version__ = '.'.join(__version_info__) From 971ed0eca45509ab96817567f622e578c4c5c8c7 Mon Sep 17 00:00:00 2001 From: nookiepl Date: Thu, 11 Feb 2016 13:58:55 +0100 Subject: [PATCH 109/115] Added missing "addresses" endpoint to client.py There is missing "addresses" endpoint in client.py. --- twilio/rest/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/twilio/rest/client.py b/twilio/rest/client.py index 27f09067c5..1dcafda948 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -2,6 +2,7 @@ from twilio.rest.resources import ( UNSET_TIMEOUT, Accounts, + Addresses, Applications, AuthorizedConnectApps, CallFeedback, @@ -57,6 +58,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.auth, timeout ) + self.addresses = Addresses(self.account_url, self.auth, timeout) self.calls = Calls(self.account_uri, self.auth, timeout) self.caller_ids = CallerIds(self.account_uri, self.auth, timeout) self.connect_apps = ConnectApps(self.account_uri, self.auth, timeout) From 316c6ab7b71cee1886d3dbd946b3a6cddb6b67c0 Mon Sep 17 00:00:00 2001 From: nookiepl Date: Thu, 11 Feb 2016 14:08:46 +0100 Subject: [PATCH 110/115] Fixed typo account_uri not account_url! ;) --- twilio/rest/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/client.py b/twilio/rest/client.py index 1dcafda948..ff96e94ea5 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -58,7 +58,7 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.auth, timeout ) - self.addresses = Addresses(self.account_url, self.auth, timeout) + self.addresses = Addresses(self.account_uri, self.auth, timeout) self.calls = Calls(self.account_uri, self.auth, timeout) self.caller_ids = CallerIds(self.account_uri, self.auth, timeout) self.connect_apps = ConnectApps(self.account_uri, self.auth, timeout) From c59214acda05570b0ed678ed899629ca7d1e9471 Mon Sep 17 00:00:00 2001 From: Stephen Pinkerton Date: Sat, 20 Feb 2016 22:48:13 -0800 Subject: [PATCH 111/115] Fix phone call -> message copypasta --- twilio/rest/resources/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/rest/resources/messages.py b/twilio/rest/resources/messages.py index 90779b1bc2..a554975268 100644 --- a/twilio/rest/resources/messages.py +++ b/twilio/rest/resources/messages.py @@ -116,7 +116,7 @@ def create(self, from_=None, **kwargs): :param status_callback: A URL that Twilio will POST to when your message is processed. :param str application_sid: The 34 character sid of the application - Twilio should use to handle this phone call. + Twilio should use to handle this message. """ kwargs["from"] = from_ return self.create_instance(kwargs) From 25fd789bba52d9c83bf1a52969e796ea5953335e Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Fri, 26 Feb 2016 09:29:58 -0800 Subject: [PATCH 112/115] Add issue template --- issue_template.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 issue_template.md diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 0000000000..c041708d80 --- /dev/null +++ b/issue_template.md @@ -0,0 +1,25 @@ +*Note: These issues are for bugs and feature requests for the helper libraries. If you need help or support, please email help@twilio.com and one of our experts will assist you!* + + +**Version:** +**API Subdomain (api/taskrouter/ip_messaging):** + +### Code Snippet +```python +# paste code here +``` + +### Exception / Log +``` + +``` + +### Steps to Reproduce +1. +2. +3. + + +### Feature Request +_If this is a feature request, make sure you search Issues for an existing request before creating a new one!_ + From b24dd458fc4f64e018f7c230c146a167a921ace2 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Mon, 29 Feb 2016 15:45:17 -0800 Subject: [PATCH 113/115] Add API key auth support --- twilio/rest/base.py | 7 ++++--- twilio/rest/client.py | 5 +++-- twilio/rest/ip_messaging.py | 5 +++-- twilio/rest/lookups.py | 5 +++-- twilio/rest/monitor.py | 5 +++-- twilio/rest/pricing.py | 5 +++-- twilio/rest/task_router.py | 5 +++-- twilio/rest/trunking.py | 5 +++-- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/twilio/rest/base.py b/twilio/rest/base.py index 050321c59c..7d7a2d9702 100644 --- a/twilio/rest/base.py +++ b/twilio/rest/base.py @@ -30,8 +30,8 @@ def set_twilio_proxy(proxy_url, proxy_port): class TwilioClient(object): def __init__(self, account=None, token=None, base="https://api.twilio.com", - version="2010-04-01", - timeout=UNSET_TIMEOUT): + version="2010-04-01", timeout=UNSET_TIMEOUT, + request_account=None): """ Create a Twilio API client. """ @@ -58,8 +58,9 @@ def __init__(self, account=None, token=None, base="https://api.twilio.com", self.base = base self.auth = (account, token) self.timeout = timeout + req_account = request_account if request_account else account self.account_uri = "{0}/{1}/Accounts/{2}".format(base, - version, account) + version, req_account) def request(self, path, method=None, vars=None): """sends a request and gets a response from the Twilio REST API diff --git a/twilio/rest/client.py b/twilio/rest/client.py index ff96e94ea5..ed89641e30 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -42,12 +42,13 @@ class TwilioRestClient(TwilioClient): """ def __init__(self, account=None, token=None, base="https://api.twilio.com", - version="2010-04-01", timeout=UNSET_TIMEOUT): + version="2010-04-01", timeout=UNSET_TIMEOUT, + request_account=None): """ Create a Twilio REST API client. """ super(TwilioRestClient, self).__init__(account, token, base, version, - timeout) + timeout, request_account) version_uri = "%s/%s" % (base, version) diff --git a/twilio/rest/ip_messaging.py b/twilio/rest/ip_messaging.py index 91a07b3c30..033864c801 100644 --- a/twilio/rest/ip_messaging.py +++ b/twilio/rest/ip_messaging.py @@ -21,10 +21,11 @@ class TwilioIpMessagingClient(TwilioClient): def __init__(self, account=None, token=None, base="https://ip-messaging.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioIpMessagingClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.version_uri = "%s/%s" % (base, version) self.services = Services(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/lookups.py b/twilio/rest/lookups.py index b71e30da55..8d591825c3 100644 --- a/twilio/rest/lookups.py +++ b/twilio/rest/lookups.py @@ -20,10 +20,11 @@ class TwilioLookupsClient(TwilioClient): def __init__(self, account=None, token=None, base="https://lookups.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioLookupsClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.version_uri = "%s/%s" % (base, version) self.phone_numbers = PhoneNumbers(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/monitor.py b/twilio/rest/monitor.py index 980e2f3c0b..9554a844ff 100644 --- a/twilio/rest/monitor.py +++ b/twilio/rest/monitor.py @@ -21,10 +21,11 @@ class TwilioMonitorClient(TwilioClient): def __init__(self, account=None, token=None, base="https://monitor.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioMonitorClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.version_uri = "%s/%s" % (base, version) self.events = Events(self.version_uri, self.auth, timeout) diff --git a/twilio/rest/pricing.py b/twilio/rest/pricing.py index b909243256..61e7a84058 100644 --- a/twilio/rest/pricing.py +++ b/twilio/rest/pricing.py @@ -21,9 +21,10 @@ class TwilioPricingClient(TwilioClient): def __init__(self, account=None, token=None, base="https://pricing.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): super(TwilioPricingClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.uri_base = "{}/{}".format(base, version) diff --git a/twilio/rest/task_router.py b/twilio/rest/task_router.py index eec74857e8..eb93b29fd4 100644 --- a/twilio/rest/task_router.py +++ b/twilio/rest/task_router.py @@ -25,12 +25,13 @@ class TwilioTaskRouterClient(TwilioClient): def __init__(self, account=None, token=None, base="https://taskrouter.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): """ Create a Twilio REST API client. """ super(TwilioTaskRouterClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.base_uri = "{0}/{1}".format(base, version) self.workspace_uri = "{0}/Workspaces".format(self.base_uri) diff --git a/twilio/rest/trunking.py b/twilio/rest/trunking.py index c3645d921b..b733db5268 100644 --- a/twilio/rest/trunking.py +++ b/twilio/rest/trunking.py @@ -22,12 +22,13 @@ class TwilioTrunkingClient(TwilioClient): def __init__(self, account=None, token=None, base="https://trunking.twilio.com", version="v1", - timeout=UNSET_TIMEOUT): + timeout=UNSET_TIMEOUT, request_account=None): """ Create a Twilio REST API client. """ super(TwilioTrunkingClient, self).__init__(account, token, base, - version, timeout) + version, timeout, + request_account) self.trunk_base_uri = "{0}/{1}".format(base, version) def credential_lists(self, trunk_sid): From 089dcde6470bad052b3174988259c07d9a00e2e2 Mon Sep 17 00:00:00 2001 From: Carlos Diaz-Padron Date: Mon, 29 Feb 2016 15:47:14 -0800 Subject: [PATCH 114/115] Bump to version 5.4.0 --- CHANGES.md | 7 +++++++ twilio/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 18c3cec074..eacd0d5329 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,13 @@ twilio-python Changelog Here you can see the full list of changes between each twilio-python release. +Version 5.4.0 +------------- + +Released February 29, 2016: + +- Add support for API Key auth + Version 5.3.0 ------------- diff --git a/twilio/version.py b/twilio/version.py index 7d0e940950..8886dce30c 100644 --- a/twilio/version.py +++ b/twilio/version.py @@ -1,2 +1,2 @@ -__version_info__ = ('5', '3', '0') +__version_info__ = ('5', '4', '0') __version__ = '.'.join(__version_info__) From b297a6854dda59822e25b33fe390cea7c816dccd Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 20 Mar 2016 11:49:23 -0400 Subject: [PATCH 115/115] Fix the closing tag for The closing tag for in the sample text is missing the slash. e.g. --- docs/usage/twiml.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage/twiml.rst b/docs/usage/twiml.rst index 6305f16c05..7ae32225ae 100644 --- a/docs/usage/twiml.rst +++ b/docs/usage/twiml.rst @@ -30,7 +30,7 @@ The output is perfectly formatted XML: .. code-block:: xml - Hello + Hello The verb methods (outlined in the :doc:`complete reference `) take the body (only text) of the verb as the first argument. @@ -49,7 +49,7 @@ All attributes are keyword arguments. https://api.twilio.com/cowbell.mp3 - + The Message and Sms verbs are slightly special: because :const:`from` is a Python keyword, use the :const:`sender` parameter to specify the number @@ -68,7 +68,7 @@ the message should come from: Hello MMS Monkey! - + Python 2.6+ added the :const:`with` statement for context management. Using :const:`with`, the module can *almost* emulate Ruby blocks.