From da33db6c977e6b45c49d80b7101cdc1973885405 Mon Sep 17 00:00:00 2001 From: Senthil Ramakrishnan Date: Fri, 9 Oct 2015 18:50:27 -0700 Subject: [PATCH 01/46] 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 02/46] 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 03/46] 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 04/46] 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 05/46] 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 0e7e73d750748571256c2bf3b278b5df41ccb789 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 29 Oct 2015 13:52:11 -0700 Subject: [PATCH 06/46] 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 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] 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 32/46] 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 33/46] 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 34/46] 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 35/46] 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 36/46] 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 37/46] 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 38/46] 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 39/46] 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 40/46] 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 41/46] 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 42/46] 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 43/46] 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 44/46] 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 2eccd6a3dbaf4b903155eac1f2ee8e07f9aa1a66 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 28 Jan 2016 14:22:47 -0800 Subject: [PATCH 45/46] Readd signing keys --- 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 27f09067c5..bc3c45731f 100644 --- a/twilio/rest/client.py +++ b/twilio/rest/client.py @@ -21,6 +21,7 @@ Queues, Recordings, Sandboxes, + SigningKeys, Sip, Sms, Tokens, @@ -74,6 +75,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) self.keys = Keys(self.account_uri, self.auth, timeout) diff --git a/twilio/rest/resources/__init__.py b/twilio/rest/resources/__init__.py index 7e74cad645..36279a9148 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 78c2f65370..0fafd507dd 100644 --- a/twilio/rest/resources/accounts.py +++ b/twilio/rest/resources/accounts.py @@ -14,6 +14,7 @@ from .messages import Messages from .media import MediaList from .sip import Sip +from .signing_keys import SigningKeys class Account(InstanceResource): @@ -42,6 +43,7 @@ class Account(InstanceResource): UsageTriggers, MediaList, Messages, + SigningKeys, Sip, Keys, ] From 4763b91e4c54a138b8406a6f8017410ef44817d4 Mon Sep 17 00:00:00 2001 From: Jingming Niu Date: Thu, 28 Jan 2016 14:28:09 -0800 Subject: [PATCH 46/46] Remove git marks --- CHANGES.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9ce57a23c0..edf76ab486 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -78,24 +78,15 @@ Released November 3, 2015: Version 4.8.0 ------------- -<<<<<<< HEAD Released November 2, 2015: - Add SMS pricing endpoint -Version 4.6.0-edge +Version 4.6.0 ------------- Released October 28, 2015: -======= -- Add support for SMS pricing - - -Version 4.6.0 -------------- - ->>>>>>> bae308162e9e9505a73651d1de0a946e59c30267 - Add /Keys endpoint Version 4.6.0