diff --git a/.travis.yml b/.travis.yml index 250dcb8c18..fead52f55d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,11 @@ python: - '3.2' - '3.3' - '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 diff --git a/CHANGES.md b/CHANGES.md index 77fe637566..edf76ab486 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,13 @@ Released November 25, 2015: - Adds support for python 3.5. +Version 4.9.2 +------------- + +Released November 25, 2015: + +- Fix for SIP Trunking bug + Version 5.0.1-edge ------------- @@ -75,7 +82,7 @@ Released November 2, 2015: - Add SMS pricing endpoint -Version 4.6.0-edge +Version 4.6.0 ------------- Released October 28, 2015: 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/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/tests/task_router/test_workflow_config.py b/tests/task_router/test_workflow_config.py index 458fecc045..4aa99213de 100644 --- a/tests/task_router/test_workflow_config.py +++ b/tests/task_router/test_workflow_config.py @@ -78,6 +78,203 @@ 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_with_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' + } + } + } + # 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 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/tests/test_make_request.py b/tests/test_make_request.py index 6348fea500..0eab2e8288 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,96 @@ 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) 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") diff --git a/twilio/jwt/__init__.py b/twilio/jwt/__init__.py index f87ba5365f..93f6b60a34 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 diff --git a/twilio/rest/resources/base.py b/twilio/rest/resources/base.py index 5a16ec5c12..d9a34b8886 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') @@ -245,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/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. 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 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): diff --git a/twilio/task_router/__init__.py b/twilio/task_router/__init__.py index c77feeca29..ae892eda49 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) @@ -39,12 +43,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 +65,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 +216,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.reservations_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.reservations_url, "GET") + self.allow(self.worker_reservations_url, "GET") def setup_resource(self): self.resource_url = self.base_url + "/Workers/" + self.channel_id @@ -232,6 +241,10 @@ def allow_reservation_updates(self): self.reservations_url, 'POST', True)) + self.policies.append(self.make_policy( + self.worker_reservations_url, + 'POST', + True)) class TaskRouterTaskQueueCapability(TaskRouterCapability): @@ -248,18 +261,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 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_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, })