├── requirements.txt ├── docs ├── changelog.rst ├── _themes │ ├── .gitignore │ ├── kr │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── static │ │ │ └── small_flask.css │ │ └── layout.html │ ├── kr_small │ │ ├── theme.conf │ │ └── layout.html │ ├── README.rst │ ├── LICENSE │ └── flask_theme_support.py ├── src │ └── pip-delete-this-directory.txt ├── api │ ├── rest │ │ └── index.rst │ ├── util.rst │ └── twiml.rst ├── api.rst ├── usage │ ├── transcriptions.rst │ ├── caller-ids.rst │ ├── notifications.rst │ ├── messages.rst │ ├── twiml.rst │ ├── accounts.rst │ ├── validation.rst │ ├── token-generation.rst │ ├── recordings.rst │ ├── applications.rst │ ├── conferences.rst │ ├── queues.rst │ └── basics.rst ├── getting-started.rst ├── index.rst ├── faq.rst ├── appengine.rst ├── Makefile └── make.bat ├── tests ├── requirements.txt ├── resources │ ├── outgoing_caller_ids_validation.json │ ├── members_instance.json │ ├── queues_instance.json │ ├── outgoing_caller_ids_instance.json │ ├── recordings_instance.json │ ├── sandbox_instance.json │ ├── transcriptions_instance.json │ ├── participants_instance.json │ ├── sms_messages_instance.json │ ├── conferences_instance.json │ ├── usage_triggers_instance.json │ ├── incoming_phone_numbers_instance.json │ ├── members_list.json │ ├── outgoing_caller_ids_list.json │ ├── authorized_connect_apps.json │ ├── process.py │ ├── calls_instance.json │ ├── participants_list.json │ ├── accounts_instance.json │ ├── queues_list.json │ ├── notifications_instance.json │ ├── conferences_list.json │ ├── usage_triggers_list.json │ ├── available_phone_numbers_us_tollfree.json │ ├── incoming_phone_numbers_list.json │ ├── usage_records_list.json │ └── accounts_list.json ├── tools.py ├── README.md ├── test_accounts.py ├── test_transcriptions.py ├── test_sms_messages.py ├── test_conferences.py ├── test_members.py ├── test_client.py ├── test_validation.py ├── test_notifications.py ├── test_connect_apps.py ├── test_recordings.py ├── test_unicode.py ├── test_applications.py ├── test_queues.py ├── test_make_request.py ├── test_authorized_connect_apps.py ├── test_usage.py ├── test_calls.py ├── test_core.py ├── test_available_phonenumber.py ├── test_phone_numbers.py ├── test_base_resource.py └── test_jwt.py ├── Makefile ├── .travis.yml ├── twilio ├── rest │ └── resources │ │ ├── imports.py │ │ ├── sandboxes.py │ │ ├── connect_apps.py │ │ ├── notifications.py │ │ ├── __init__.py │ │ ├── recordings.py │ │ ├── caller_ids.py │ │ ├── util.py │ │ ├── queues.py │ │ ├── conferences.py │ │ ├── accounts.py │ │ ├── applications.py │ │ └── sms_messages.py ├── __init__.py ├── compat │ └── __init__.py └── jwt │ └── __init__.py ├── .gitignore ├── LICENSE ├── setup.py ├── CHANGES └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | httplib2 3 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES 2 | -------------------------------------------------------------------------------- /docs/_themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | mock==0.8.0 3 | nose 4 | coverage 5 | nosexcover 6 | flake8 7 | -------------------------------------------------------------------------------- /docs/_themes/kr/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | touch_icon = 8 | -------------------------------------------------------------------------------- /tests/resources/outgoing_caller_ids_validation.json: -------------------------------------------------------------------------------- 1 | {"account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","phone_number":"+141586753096","friendly_name":null,"validation_code":147423} 2 | -------------------------------------------------------------------------------- /docs/src/pip-delete-this-directory.txt: -------------------------------------------------------------------------------- 1 | This file is placed here by pip to indicate the source was put 2 | here by pip. 3 | 4 | Once this package is successfully installed this source code will be 5 | deleted (unless you remove this file). 6 | -------------------------------------------------------------------------------- /docs/api/rest/index.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest 2 | 3 | ============================== 4 | :mod:`twilio.rest` 5 | ============================== 6 | 7 | .. autoclass:: TwilioRestClient 8 | :members: 9 | :inherited-members: 10 | 11 | -------------------------------------------------------------------------------- /tests/tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from mock import Mock 3 | 4 | 5 | def create_mock_json(path): 6 | with open(path) as f: 7 | resp = Mock() 8 | resp.content = f.read() 9 | return resp 10 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | API Documentation 3 | ================= 4 | 5 | A complete API reference to the :data:`twilio` module. 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | api/rest/index 11 | api/rest/resources 12 | api/twiml 13 | api/util 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | venv: 2 | virtualenv venv 3 | 4 | install: venv 5 | . venv/bin/activate; pip install . 6 | 7 | analysis: 8 | . venv/bin/activate; flake8 --ignore=E123,E126,E128,E501 tests 9 | . venv/bin/activate; flake8 --ignore=F401 twilio 10 | 11 | test: analysis 12 | . venv/bin/activate; nosetests 13 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # twilio-python unit tests 2 | 3 | ## Setup 4 | 5 | From the root directory of this repository: 6 | 7 | $ python setup.py develop 8 | $ pip install -r tests/requirements.txt 9 | 10 | ## Running the tests 11 | 12 | Again, from the root directory of this repository: 13 | 14 | $ nosetests tests/ 15 | -------------------------------------------------------------------------------- /tests/resources/members_instance.json: -------------------------------------------------------------------------------- 1 | {"call_sid": "CAaaf2e9ded94aba3e57c42a3d55be6ff2", "date_enqueued": "Tue, 07 Aug 2012 22:57:41 +0000", "wait_time": 143, "current_position": 1, "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUe675e1913214346f48a9d2296a8d3788/Members/CAaaf2e9ded94aba3e57c42a3d55be6ff2.json"} 2 | -------------------------------------------------------------------------------- /docs/api/util.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | :mod:`twilio.util` 3 | ==================== 4 | 5 | .. automodule:: twilio.util 6 | 7 | .. autoclass:: twilio.util.RequestValidator 8 | :members: 9 | 10 | .. autoclass:: twilio.util.TwilioCapability 11 | :members: allow_client_incoming, allow_client_outgoing, generate 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/resources/queues_instance.json: -------------------------------------------------------------------------------- 1 | {"sid": "QU6e2cd5a7c8074edc8b383a3b198b2f8b", "friendly_name": "CutieQueue", "current_size": null, "average_wait_time": null, "max_size": 100, "date_created": "Tue, 07 Aug 2012 21:08:51 +0000", "date_updated": "Tue, 07 Aug 2012 21:08:51 +0000", "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QU6e2cd5a7c8074edc8b383a3b198b2f8b.json"} 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.5" 4 | - "2.6" 5 | - "2.7" 6 | - "3.2" 7 | - "3.3" 8 | install: 9 | - pip install . --use-mirrors 10 | - pip install -r requirements.txt --use-mirrors 11 | - pip install -r tests/requirements.txt --use-mirrors 12 | script: 13 | - flake8 --ignore=F401 twilio 14 | - flake8 --ignore=E123,E126,E128,E501 tests 15 | - nosetests 16 | -------------------------------------------------------------------------------- /twilio/rest/resources/imports.py: -------------------------------------------------------------------------------- 1 | # parse_qs 2 | try: 3 | from urlparse import parse_qs 4 | except ImportError: 5 | from cgi import parse_qs 6 | 7 | # json 8 | try: 9 | import json 10 | except ImportError: 11 | try: 12 | import simplejson as json 13 | except ImportError: 14 | from django.utils import simplejson as json 15 | 16 | # httplib2 17 | import httplib2 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | develop-eggs 12 | .installed.cfg 13 | scratch 14 | env 15 | venv 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | .DS_Store 25 | 26 | # Sphinx 27 | docs/tmp 28 | docs/_build 29 | cover 30 | 31 | # tools 32 | .idea 33 | -------------------------------------------------------------------------------- /tests/resources/outgoing_caller_ids_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"PN947e73eded59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"(415) 867-5309","phone_number":"+141586753096","date_created":"Fri, 21 Aug 2009 00:11:24 +0000","date_updated":"Fri, 21 Aug 2009 00:11:24 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds\/PN947e73eded59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/resources/recordings_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"RE19e96a31ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","call_sid":"CA31fd3b90ed59a5733d2c1c1c69a83a28","duration":"6","date_created":"Wed, 01 Sep 2010 15:15:41 +0000","api_version":"2010-04-01","date_updated":"Wed, 01 Sep 2010 15:15:41 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Recordings\/RE19e96a31ed59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/resources/sandbox_instance.json: -------------------------------------------------------------------------------- 1 | {"pin":"66528411","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","phone_number":"4155992671","voice_url":"http:\/\/www.digg.com","voice_method":"POST","sms_url":"http:\/\/demo.twilio.com\/welcome\/sms","sms_method":"POST","status_callback":null,"status_callback_method":null,"date_created":"Sun, 15 Mar 2009 02:08:47 +0000","date_updated":"Fri, 18 Feb 2011 17:37:18 +0000","api_version":"2008-08-01","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Sandbox.json"} 2 | -------------------------------------------------------------------------------- /twilio/__init__.py: -------------------------------------------------------------------------------- 1 | __version_info__ = ('3', '4', '5') 2 | __version__ = '.'.join(__version_info__) 3 | 4 | 5 | class TwilioException(Exception): 6 | pass 7 | 8 | 9 | class TwilioRestException(TwilioException): 10 | 11 | def __init__(self, status, uri, msg="", code=None): 12 | self.uri = uri 13 | self.status = status 14 | self.msg = msg 15 | self.code = code 16 | 17 | def __str__(self): 18 | return "HTTP ERROR %s: %s \n %s" % (self.status, self.msg, self.uri) 19 | -------------------------------------------------------------------------------- /tests/resources/transcriptions_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"TR4d6e2ae6ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","date_created":"Sun, 13 Feb 2011 02:12:08 +0000","date_updated":"Sun, 13 Feb 2011 02:30:01 +0000","status":"failed","type":"fast","recording_sid":"RE83fccacded59a5733d2c1c1c69a83a28","duration":"1","transcription_text":"(blank)","api_version":"2008-08-01","price":"-0.05000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Transcriptions\/TR4d6e2ae6ed59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/resources/participants_instance.json: -------------------------------------------------------------------------------- 1 | {"call_sid":"CAd31637cced59a5733d2c1c1c69a83a28","conference_sid":"CFf2fe8498ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","muted":false,"end_conference_on_exit":false,"start_conference_on_enter":true,"date_created":"Fri, 18 Feb 2011 21:07:19 +0000","date_updated":"Fri, 18 Feb 2011 21:07:19 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants\/CAd31637cced59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/resources/sms_messages_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"SM6144a547ed59a5733d2c1c1c69a83a28","date_created":"Mon, 26 Jul 2010 21:46:42 +0000","date_updated":"Mon, 26 Jul 2010 21:46:44 +0000","date_sent":"Mon, 26 Jul 2010 21:46:44 +0000","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","to":"+141586753096","from":"+141586753093","body":"n","status":"sent","direction":"outbound-api","api_version":"2008-08-01","price":"-0.03000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/SMS\/Messages\/SM6144a547ed59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/resources/conferences_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"CFe3bb5bfbed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"AHH YEAH","status":"completed","date_created":"Fri, 18 Feb 2011 19:26:50 +0000","api_version":"2008-08-01","date_updated":"Fri, 18 Feb 2011 19:27:33 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFe3bb5bfbed59a5733d2c1c1c69a83a28.json","subresource_uris":{"participants":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFe3bb5bfbed59a5733d2c1c1c69a83a28\/Participants.json"}} 2 | -------------------------------------------------------------------------------- /twilio/compat/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # Those are not supported by the six library and needs to be done manually 3 | from six import binary_type 4 | 5 | try: 6 | # python 3 7 | from urllib.parse import urlencode, urlparse, urljoin 8 | except ImportError: 9 | # python 2 backward compatibility 10 | #noinspection PyUnresolvedReferences 11 | from urllib import urlencode 12 | #noinspection PyUnresolvedReferences 13 | from urlparse import urlparse, urljoin 14 | 15 | try: 16 | # python 2 17 | from itertools import izip 18 | except ImportError: 19 | # python 3 20 | izip = zip 21 | -------------------------------------------------------------------------------- /docs/_themes/kr/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /tests/resources/usage_triggers_instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "sid": "UT33c6aeeba34e48f38d6899ea5b765ad4", 3 | "date_updated": "2012-09-29 12:47:54", 4 | "current_usage_value": "7", 5 | "friendly_name": "Trigger for calls at usage of 500", 6 | "uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Triggers/UT33c6aeeba34e48f38d6899ea5b765ad4/UT33c6aeeba34e48f38d6899ea5b765ad4.json", 7 | "usage_record_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Category=calls", 8 | "trigger_by": "usage", 9 | "callback_method": "POST", 10 | "date_created": "2012-09-29 12:45:43", 11 | "callback_url": "http://www.example.com/", 12 | "recurring": "daily", 13 | "usage_category": "calls", 14 | "trigger_value": "500.000000" 15 | } 16 | -------------------------------------------------------------------------------- /docs/_themes/kr_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /tests/resources/incoming_phone_numbers_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"PNd2ae06cced59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"An example Twilio Application","phone_number":"+141586753092","voice_url":"http:\/\/apps.dev.twilio.com\/~kjconroy\/mobilemusical\/index.php","voice_method":"POST","voice_fallback_url":"","voice_fallback_method":null,"voice_caller_id_lookup":false,"date_created":"Sat, 13 Jun 2009 01:00:39 +0000","date_updated":"Tue, 04 May 2010 09:33:02 +0000","sms_url":"","sms_method":null,"sms_fallback_url":"","sms_fallback_method":null,"capabilities":{"voice":true,"sms":false},"status_callback":null,"status_callback_method":null,"api_version":"2008-08-01","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers\/PNd2ae06cced59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /docs/_themes/README.rst: -------------------------------------------------------------------------------- 1 | krTheme Sphinx Style 2 | ==================== 3 | 4 | This repository contains sphinx styles Kenneth Reitz uses in most of 5 | his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related 6 | projects. To use this style in your Sphinx documentation, follow 7 | this guide: 8 | 9 | 1. put this folder as _themes into your docs folder. Alternatively 10 | you can also use git submodules to check out the contents there. 11 | 12 | 2. add this to your conf.py: :: 13 | 14 | sys.path.append(os.path.abspath('_themes')) 15 | html_theme_path = ['_themes'] 16 | html_theme = 'flask' 17 | 18 | The following themes exist: 19 | 20 | **kr** 21 | the standard flask documentation theme for large projects 22 | 23 | **kr_small** 24 | small one-page theme. Intended to be used by very small addon libraries. 25 | 26 | -------------------------------------------------------------------------------- /tests/resources/members_list.json: -------------------------------------------------------------------------------- 1 | {"first_page_uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUe675e1913214346f48a9d2296a8d3788/Members.json?Page=0&PageSize=50", "num_pages": 1, "previous_page_uri": null, "last_page_uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUe675e1913214346f48a9d2296a8d3788/Members.json?Page=0&PageSize=50", "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUe675e1913214346f48a9d2296a8d3788/Members.json", "page_size": 50, "start": 0, "next_page_uri": null, "end": 0, "total": 1, "queue_members": [{"call_sid": "CAaaf2e9ded94aba3e57c42a3d55be6ff2", "date_enqueued": "Tue, 07 Aug 2012 22:57:41 +0000", "wait_time": 124, "current_position": 1, "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUe675e1913214346f48a9d2296a8d3788/Members/CAaaf2e9ded94aba3e57c42a3d55be6ff2.json"}], "page": 0} 2 | -------------------------------------------------------------------------------- /tests/resources/outgoing_caller_ids_list.json: -------------------------------------------------------------------------------- 1 | {"page":0,"num_pages":1,"page_size":50,"total":1,"start":0,"end":0,"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","first_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json?Page=0&PageSize=50","outgoing_caller_ids":[{"sid":"PN947e73eded59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"(415) 867-5309","phone_number":"+141586753096","date_created":"Fri, 21 Aug 2009 00:11:24 +0000","date_updated":"Fri, 21 Aug 2009 00:11:24 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds\/PN947e73eded59a5733d2c1c1c69a83a28.json"}]} 2 | -------------------------------------------------------------------------------- /twilio/rest/resources/sandboxes.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources.util import transform_params 2 | from twilio.rest.resources import InstanceResource, ListResource 3 | 4 | 5 | class Sandbox(InstanceResource): 6 | 7 | id_key = "pin" 8 | 9 | def update(self, **kwargs): 10 | """ 11 | Update your Twilio Sandbox 12 | """ 13 | a = self.parent.update(**kwargs) 14 | self.load(a.__dict__) 15 | 16 | 17 | class Sandboxes(ListResource): 18 | 19 | name = "Sandbox" 20 | instance = Sandbox 21 | 22 | def get(self): 23 | """Request the specified instance resource""" 24 | return self.get_instance(self.uri) 25 | 26 | def update(self, **kwargs): 27 | """ 28 | Update your Twilio Sandbox 29 | """ 30 | resp, entry = self.request("POST", self.uri, 31 | body=transform_params(kwargs)) 32 | return self.create_instance(entry) 33 | -------------------------------------------------------------------------------- /tests/resources/authorized_connect_apps.json: -------------------------------------------------------------------------------- 1 | {"authorized_connect_apps":[{"connect_app_sid":"CN962cd12498b740a2a4c65d0e0774c2a9","account_sid":"AC8dfe2f2358cf421cb6134cf6f217c6a3","permissions":["get-all"],"connect_app_friendly_name":"YOUR MOM","connect_app_description":"alksjdfl;ajseifj;alsijfl;ajself;jasjfjas;lejflj","connect_app_company_name":"YOUR OTHER MOM","connect_app_homepage_url":"http:\/\/www.google.com","uri":"\/2010-04-01\/Accounts\/AC8dfe2f2358cf421cb6134cf6f217c6a3\/AuthorizedConnectApps\/CN962cd12498b740a2a4c65d0e0774c2a9.json"}],"page":0,"num_pages":1,"page_size":50,"total":1,"start":0,"end":0,"uri":"\/2010-04-01\/Accounts\/AC0a652c7f2c545a4f1fccc9b02d61b58b\/AuthorizedConnectApps.json","first_page_uri":"\/2010-04-01\/Accounts\/AC0a652c7f2c545a4f1fccc9b02d61b58b\/AuthorizedConnectApps.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts\/AC0a652c7f2c545a4f1fccc9b02d61b58b\/AuthorizedConnectApps.json?Page=0&PageSize=50"} 2 | -------------------------------------------------------------------------------- /tests/resources/process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import with_statement 3 | import re 4 | import os 5 | 6 | sid = "ed59a5733d2c1c1c69a83a28" 7 | 8 | 9 | def twilio_clean(contents): 10 | contents = re.sub(r"([A-Z]{2}\w{8})\w{24}", r"\1%s" % sid, contents) 11 | contents = re.sub(r"\"[a-z0-9]{32}\"", "\"AUTHTOKEN\"", contents) 12 | contents = re.sub(r"\+\d{10}", r"+14158675309", contents) 13 | contents = re.sub(r"[0-9\- \(\)]{14}", r"(415) 867-5309", contents) 14 | return contents 15 | 16 | 17 | def main(): 18 | for f in os.listdir(os.path.abspath(os.path.dirname(__file__))): 19 | path, ext = os.path.splitext(f) 20 | if ext == ".json": 21 | with open(f) as g: 22 | contents = g.read() 23 | contents = twilio_clean(contents) 24 | with open(f, "w") as g: 25 | g.write(contents.strip()) 26 | g.write("\n") 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /tests/resources/calls_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"CA47e13748ed59a5733d2c1c1c69a83a28","date_created":"Tue, 31 Aug 2010 20:36:28 +0000","date_updated":"Tue, 31 Aug 2010 20:36:44 +0000","parent_call_sid":null,"account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","to":"+141586753093","from":"+141586753091","phone_number_sid":"PN995e2937ed59a5733d2c1c1c69a83a28","status":"completed","start_time":"Tue, 31 Aug 2010 20:36:29 +0000","end_time":"Tue, 31 Aug 2010 20:36:44 +0000","duration":"15","price":"-0.03000","direction":"inbound","answered_by":null,"api_version":"2010-04-01","annotation":null,"forwarded_from":"+141586753093","group_sid":null,"caller_name":null,"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls\/CA47e13748ed59a5733d2c1c1c69a83a28.json","subresource_uris":{"notifications":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls\/CA47e13748ed59a5733d2c1c1c69a83a28\/Notifications.json","recordings":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls\/CA47e13748ed59a5733d2c1c1c69a83a28\/Recordings.json"}} 2 | -------------------------------------------------------------------------------- /docs/usage/transcriptions.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ================ 4 | Transcriptions 5 | ================ 6 | 7 | Transcriptions are generated from recordings via the 8 | `TwiML verb `_. 9 | Using the API, you can only read your transcription records. 10 | 11 | For more information, see the `Transcriptions REST Resource 12 | `_ documentation. 13 | 14 | 15 | Listing Your Transcriptions 16 | ---------------------------- 17 | 18 | The following code will print out the length of each :class:`Transcription`. 19 | 20 | .. code-block:: python 21 | 22 | from twilio.rest import TwilioRestClient 23 | 24 | # To find these visit https://www.twilio.com/user/account 25 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 26 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 27 | 28 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 29 | for transcription in client.transcriptions.list(): 30 | print transcription.duration 31 | 32 | -------------------------------------------------------------------------------- /tests/resources/participants_list.json: -------------------------------------------------------------------------------- 1 | {"page":0,"num_pages":1,"page_size":50,"total":1,"start":0,"end":0,"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants.json","first_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants.json?Page=0&PageSize=50","participants":[{"conference_sid":"CFf2fe8498ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","call_sid":"CAd31637cced59a5733d2c1c1c69a83a28","muted":false,"end_conference_on_exit":false,"start_conference_on_enter":true,"date_created":"Fri, 18 Feb 2011 21:07:19 +0000","date_updated":"Fri, 18 Feb 2011 21:07:19 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants\/CAd31637cced59a5733d2c1c1c69a83a28.json"}]} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Twilio, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests/test_accounts.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from mock import Mock, patch 4 | 5 | from tools import create_mock_json 6 | from twilio.rest.resources import Account 7 | 8 | 9 | class AccountTest(unittest.TestCase): 10 | 11 | @patch("twilio.rest.resources.base.make_twilio_request") 12 | def test_usage_records_subresource(self, request): 13 | resp = create_mock_json("tests/resources/usage_records_list.json") 14 | request.return_value = resp 15 | 16 | mock = Mock() 17 | mock.uri = "/base" 18 | account = Account(mock, 'AC123') 19 | account.load_subresources() 20 | records = account.usage_records.list() 21 | self.assertEquals(len(records), 2) 22 | 23 | @patch("twilio.rest.resources.base.make_twilio_request") 24 | def test_usage_triggers_subresource(self, request): 25 | resp = create_mock_json("tests/resources/usage_triggers_list.json") 26 | request.return_value = resp 27 | 28 | mock = Mock() 29 | mock.uri = "/base" 30 | account = Account(mock, 'AC123') 31 | account.load_subresources() 32 | triggers = account.usage_triggers.list() 33 | self.assertEquals(len(triggers), 2) 34 | -------------------------------------------------------------------------------- /docs/usage/caller-ids.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ================= 4 | Caller Ids 5 | ================= 6 | 7 | 8 | Validate a Phone Number 9 | ----------------------- 10 | 11 | Validating a phone number is quick and easy. 12 | 13 | .. code-block:: python 14 | 15 | from twilio.rest import TwilioRestClient 16 | 17 | # To find these visit https://www.twilio.com/user/account 18 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 19 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 20 | 21 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 22 | response = client.caller_ids.validate("+44 9876543212") 23 | print response.validation_code 24 | 25 | Twilio will call the provided number and wait for the validation code to be 26 | entered. 27 | 28 | Delete a Phone Number 29 | --------------------- 30 | 31 | Deleting a phone number is quick and easy. 32 | 33 | .. code-block:: python 34 | 35 | from twilio.rest import TwilioRestClient 36 | 37 | # To find these visit https://www.twilio.com/user/account 38 | account = "ACXXXXXXXXXXXXXXXXX" 39 | token = "YYYYYYYYYYYYYYYYYY" 40 | client = TwilioRestClient(account, token) 41 | 42 | response = client.caller_ids.list(phone_number="+15555555555") 43 | callerid = response[0] 44 | callerid.delete() 45 | -------------------------------------------------------------------------------- /tests/test_transcriptions.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from nose.tools import raises 3 | from twilio.rest.resources import Transcriptions 4 | from tools import create_mock_json 5 | 6 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 7 | ACCOUNT_SID = "AC123" 8 | AUTH = (ACCOUNT_SID, "token") 9 | 10 | transcriptions = Transcriptions(BASE_URI, AUTH) 11 | 12 | 13 | @patch("twilio.rest.resources.base.make_twilio_request") 14 | def test_paging(mock): 15 | resp = create_mock_json("tests/resources/transcriptions_list.json") 16 | mock.return_value = resp 17 | 18 | uri = "%s/Transcriptions" % (BASE_URI) 19 | transcriptions.list(page=2) 20 | 21 | mock.assert_called_with("GET", uri, params={"Page": 2}, auth=AUTH) 22 | 23 | 24 | @patch("twilio.rest.resources.base.make_twilio_request") 25 | def test_get(mock): 26 | resp = create_mock_json("tests/resources/transcriptions_instance.json") 27 | mock.return_value = resp 28 | 29 | uri = "%s/Transcriptions/TR123" % (BASE_URI) 30 | transcriptions.get("TR123") 31 | 32 | mock.assert_called_with("GET", uri, auth=AUTH) 33 | 34 | 35 | @raises(AttributeError) 36 | def test_create(): 37 | transcriptions.create 38 | 39 | 40 | @raises(AttributeError) 41 | def test_update(): 42 | transcriptions.update 43 | -------------------------------------------------------------------------------- /docs/_themes/kr/static/small_flask.css: -------------------------------------------------------------------------------- 1 | /* 2 | * small_flask.css_t 3 | * ~~~~~~~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2010 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | body { 10 | margin: 0; 11 | padding: 20px 30px; 12 | } 13 | 14 | div.documentwrapper { 15 | float: none; 16 | background: white; 17 | } 18 | 19 | div.sphinxsidebar { 20 | display: block; 21 | float: none; 22 | width: 102.5%; 23 | margin: 50px -30px -20px -30px; 24 | padding: 10px 20px; 25 | background: #333; 26 | color: white; 27 | } 28 | 29 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 30 | div.sphinxsidebar h3 a { 31 | color: white; 32 | } 33 | 34 | div.sphinxsidebar a { 35 | color: #aaa; 36 | } 37 | 38 | div.sphinxsidebar p.logo { 39 | display: none; 40 | } 41 | 42 | div.document { 43 | width: 100%; 44 | margin: 0; 45 | } 46 | 47 | div.related { 48 | display: block; 49 | margin: 0; 50 | padding: 10px 0 20px 0; 51 | } 52 | 53 | div.related ul, 54 | div.related ul li { 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | div.footer { 60 | display: none; 61 | } 62 | 63 | div.bodywrapper { 64 | margin: 0; 65 | } 66 | 67 | div.body { 68 | min-height: 0; 69 | padding: 0; 70 | } 71 | -------------------------------------------------------------------------------- /docs/api/twiml.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | :mod:`twilio.twiml` 3 | ==================== 4 | 5 | .. automodule:: twilio.twiml 6 | 7 | .. autoclass:: twilio.twiml.Response 8 | :members: 9 | 10 | 11 | Primary Verbs 12 | ~~~~~~~~~~~~~ 13 | 14 | .. autoclass:: twilio.twiml.Say 15 | :members: 16 | 17 | .. autoclass:: twilio.twiml.Play 18 | :members: 19 | 20 | .. autoclass:: twilio.twiml.Dial 21 | :members: 22 | 23 | .. autoclass:: twilio.twiml.Gather 24 | :members: 25 | 26 | .. autoclass:: twilio.twiml.Record 27 | :members: 28 | 29 | 30 | Secondary Verbs 31 | ~~~~~~~~~~~~~~~ 32 | 33 | .. autoclass:: twilio.twiml.Hangup 34 | :members: 35 | 36 | .. autoclass:: twilio.twiml.Redirect 37 | :members: 38 | 39 | .. autoclass:: twilio.twiml.Reject 40 | :members: 41 | 42 | .. autoclass:: twilio.twiml.Pause 43 | :members: 44 | 45 | .. autoclass:: twilio.twiml.Sms 46 | :members: 47 | 48 | .. autoclass:: twilio.twiml.Enqueue 49 | :members: 50 | 51 | .. autoclass:: twilio.twiml.Leave 52 | :members: 53 | 54 | 55 | Nouns 56 | ~~~~~~ 57 | 58 | .. autoclass:: twilio.twiml.Conference 59 | :members: 60 | 61 | .. autoclass:: twilio.twiml.Number 62 | :members: 63 | 64 | .. autoclass:: twilio.twiml.Client 65 | :members: 66 | 67 | .. autoclass:: twilio.twiml.Queue 68 | :members: 69 | 70 | -------------------------------------------------------------------------------- /tests/resources/accounts_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"kyle.j.conroy@gmail.com's Account","status":"active","date_created":"Sun, 15 Mar 2009 02:08:47 +0000","date_updated":"Wed, 25 Aug 2010 01:30:09 +0000","auth_token":"AUTHTOKEN","type":"Full","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28.json","subresource_uris":{"available_phone_numbers":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers.json","calls":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json","conferences":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences.json","incoming_phone_numbers":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","notifications":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Notifications.json","outgoing_caller_ids":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","recordings":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Recordings.json","sandbox":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Sandbox.json","sms_messages":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/SMS\/Messages.json","transcriptions":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Transcriptions.json"}} 2 | -------------------------------------------------------------------------------- /tests/test_sms_messages.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY3: 3 | import unittest 4 | else: 5 | import unittest2 as unittest 6 | 7 | from datetime import date 8 | from mock import Mock 9 | from twilio.rest.resources import SmsMessages 10 | 11 | DEFAULT = { 12 | 'From': None, 13 | 'DateSent<': None, 14 | 'DateSent>': None, 15 | 'DateSent': None, 16 | } 17 | 18 | 19 | class SmsTest(unittest.TestCase): 20 | 21 | def setUp(self): 22 | self.resource = SmsMessages("foo", ("sid", "token")) 23 | self.params = DEFAULT.copy() 24 | 25 | def test_list_on(self): 26 | self.resource.get_instances = Mock() 27 | self.resource.list(date_sent=date(2011, 1, 1)) 28 | self.params['DateSent'] = "2011-01-01" 29 | self.resource.get_instances.assert_called_with(self.params) 30 | 31 | def test_list_after(self): 32 | self.resource.get_instances = Mock() 33 | self.resource.list(after=date(2011, 1, 1)) 34 | self.params['DateSent>'] = "2011-01-01" 35 | self.resource.get_instances.assert_called_with(self.params) 36 | 37 | def test_list_before(self): 38 | self.resource.get_instances = Mock() 39 | self.resource.list(before=date(2011, 1, 1)) 40 | self.params['DateSent<'] = "2011-01-01" 41 | self.resource.get_instances.assert_called_with(self.params) 42 | -------------------------------------------------------------------------------- /twilio/rest/resources/connect_apps.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources import InstanceResource, ListResource 2 | from six import iteritems 3 | 4 | 5 | class ConnectApp(InstanceResource): 6 | """ An authorized connect app """ 7 | pass 8 | 9 | 10 | class ConnectApps(ListResource): 11 | """ A list of Connect App resources """ 12 | 13 | name = "ConnectApps" 14 | instance = ConnectApp 15 | key = "connect_apps" 16 | 17 | def list(self, **kwargs): 18 | """ 19 | Returns a page of :class:`ConnectApp` resources as a list. For paging 20 | informtion see :class:`ListResource` 21 | """ 22 | return self.get_instances(kwargs) 23 | 24 | 25 | class AuthorizedConnectApp(ConnectApp): 26 | """ An authorized connect app """ 27 | 28 | id_key = "connect_app_sid" 29 | 30 | def load(self, entries): 31 | """ Translate certain parameters into others""" 32 | result = {} 33 | 34 | for k, v in iteritems(entries): 35 | k = k.replace("connect_app_", "") 36 | result[k] = v 37 | 38 | super(AuthorizedConnectApp, self).load(result) 39 | 40 | 41 | class AuthorizedConnectApps(ConnectApps): 42 | """ A list of Authorized Connect App resources """ 43 | 44 | name = "AuthorizedConnectApps" 45 | instance = AuthorizedConnectApp 46 | key = "authorized_connect_apps" 47 | -------------------------------------------------------------------------------- /docs/_themes/kr/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 9 | {% endblock %} 10 | {%- block relbar2 %}{% endblock %} 11 | {%- block footer %} 12 | 15 | 16 | Fork me on GitHub 17 | 18 | 31 | 32 | {%- endblock %} 33 | -------------------------------------------------------------------------------- /tests/resources/queues_list.json: -------------------------------------------------------------------------------- 1 | {"first_page_uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues.json?Page=0&PageSize=50", "num_pages": 1, "previous_page_uri": null, "queues": [{"sid": "QU1b9faddec3d54ec18488f86c83019bf0", "friendly_name": "Support", "current_size": 0, "average_wait_time": 0, "max_size": 100, "date_created": "Tue, 07 Aug 2012 21:09:04 +0000", "date_updated": "Tue, 07 Aug 2012 21:09:04 +0000", "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QU1b9faddec3d54ec18488f86c83019bf0.json"}, {"sid": "QU6e2cd5a7c8074edc8b383a3b198b2f8b", "friendly_name": "CutieQueue", "current_size": 0, "average_wait_time": 0, "max_size": 100, "date_created": "Tue, 07 Aug 2012 21:08:51 +0000", "date_updated": "Tue, 07 Aug 2012 21:08:51 +0000", "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QU6e2cd5a7c8074edc8b383a3b198b2f8b.json"}, {"sid": "QUa8ce40f68d3f41958e2519646d55b989", "friendly_name": "Test1", "current_size": 0, "average_wait_time": 0, "max_size": 64, "date_created": "Tue, 07 Aug 2012 19:09:17 +0000", "date_updated": "Tue, 07 Aug 2012 19:09:17 +0000", "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues/QUa8ce40f68d3f41958e2519646d55b989.json"}], "uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues.json", "page_size": 50, "start": 0, "next_page_uri": null, "end": 2, "total": 3, "last_page_uri": "/2010-04-01/Accounts/ACf5af0ea0c9955b67b39c52fb11ee6e69/Queues.json?Page=0&PageSize=50", "page": 0} 2 | -------------------------------------------------------------------------------- /twilio/rest/resources/notifications.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources.util import normalize_dates 2 | from twilio.rest.resources import InstanceResource, ListResource 3 | 4 | 5 | class Notification(InstanceResource): 6 | 7 | def delete(self): 8 | """ 9 | Delete this notification 10 | """ 11 | return self.delete_instance() 12 | 13 | 14 | class Notifications(ListResource): 15 | 16 | name = "Notifications" 17 | instance = Notification 18 | 19 | @normalize_dates 20 | def list(self, before=None, after=None, **kwargs): 21 | """ 22 | Returns a page of :class:`Notification` resources as a list. 23 | For paging information see :class:`ListResource`. 24 | 25 | **NOTE**: Due to the potentially voluminous amount of data in a 26 | notification, the full HTTP request and response data is only returned 27 | in the Notification instance resource representation. 28 | 29 | :param date after: Only list notifications logged after this datetime 30 | :param date before: Only list notifications logger before this datetime 31 | :param log_level: If 1, only shows errors. If 0, only show warnings 32 | """ 33 | kwargs["MessageDate<"] = before 34 | kwargs["MessageDate>"] = after 35 | return self.get_instances(kwargs) 36 | 37 | def delete(self, sid): 38 | """ 39 | Delete a given Notificiation 40 | """ 41 | return self.delete_instance(sid) 42 | -------------------------------------------------------------------------------- /tests/test_conferences.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY3: 3 | import unittest 4 | else: 5 | import unittest2 as unittest 6 | 7 | from datetime import date 8 | from mock import Mock 9 | from twilio.rest.resources import Conferences 10 | 11 | DEFAULT = { 12 | 'DateUpdated<': None, 13 | 'DateUpdated>': None, 14 | 'DateUpdated': None, 15 | 'DateCreated<': None, 16 | 'DateCreated>': None, 17 | 'DateCreated': None, 18 | } 19 | 20 | 21 | class ConferenceTest(unittest.TestCase): 22 | 23 | def setUp(self): 24 | self.resource = Conferences("foo", ("sid", "token")) 25 | self.params = DEFAULT.copy() 26 | 27 | def test_list(self): 28 | self.resource.get_instances = Mock() 29 | self.resource.list() 30 | self.resource.get_instances.assert_called_with(self.params) 31 | 32 | def test_list_after(self): 33 | self.resource.get_instances = Mock() 34 | self.resource.list(created_after=date(2011, 1, 1)) 35 | self.params["DateCreated>"] = "2011-01-01" 36 | self.resource.get_instances.assert_called_with(self.params) 37 | 38 | def test_list_on(self): 39 | self.resource.get_instances = Mock() 40 | self.resource.list(created=date(2011, 1, 1)) 41 | self.params["DateCreated"] = "2011-01-01" 42 | self.resource.get_instances.assert_called_with(self.params) 43 | 44 | def test_list_before(self): 45 | self.resource.get_instances = Mock() 46 | self.resource.list(created_before=date(2011, 1, 1)) 47 | self.params["DateCreated<"] = "2011-01-01" 48 | self.resource.get_instances.assert_called_with(self.params) 49 | -------------------------------------------------------------------------------- /tests/test_members.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from tools import create_mock_json 3 | from twilio.rest.resources import Members 4 | 5 | QUEUE_SID = "QU1b9faddec3d54ec18488f86c83019bf0" 6 | ACCOUNT_SID = "AC123" 7 | AUTH = (ACCOUNT_SID, "token") 8 | CALL_SID = "CAaaf2e9ded94aba3e57c42a3d55be6ff2" 9 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123/Queues/%s" % ( 10 | QUEUE_SID) 11 | TWIML_URL = "example_twiml_url" 12 | 13 | list_resource = Members(BASE_URI, AUTH) 14 | 15 | 16 | @patch("twilio.rest.resources.base.make_twilio_request") 17 | def test_members_list(mock): 18 | resp = create_mock_json("tests/resources/members_list.json") 19 | mock.return_value = resp 20 | 21 | uri = "%s/Members" % (BASE_URI) 22 | list_resource.list() 23 | 24 | mock.assert_called_with("GET", uri, params={}, auth=AUTH) 25 | 26 | 27 | @patch("twilio.rest.resources.base.make_twilio_request") 28 | def test_members_dequeue_front(mock): 29 | resp = create_mock_json("tests/resources/members_instance.json") 30 | mock.return_value = resp 31 | 32 | uri = "%s/Members/Front" % (BASE_URI) 33 | list_resource.dequeue(TWIML_URL) 34 | 35 | mock.assert_called_with("POST", uri, data={"Url": TWIML_URL}, auth=AUTH) 36 | 37 | 38 | @patch("twilio.rest.resources.base.make_twilio_request") 39 | def test_members_dequeue_call(mock): 40 | resp = create_mock_json("tests/resources/members_instance.json") 41 | mock.return_value = resp 42 | 43 | uri = "%s/Members/%s" % (BASE_URI, CALL_SID) 44 | list_resource.dequeue(TWIML_URL, call_sid=CALL_SID) 45 | 46 | mock.assert_called_with("POST", uri, data={"Url": TWIML_URL}, auth=AUTH) 47 | -------------------------------------------------------------------------------- /twilio/rest/resources/__init__.py: -------------------------------------------------------------------------------- 1 | import re 2 | import datetime 3 | import logging 4 | import twilio 5 | from twilio import TwilioException, TwilioRestException 6 | 7 | from twilio.rest.resources.imports import ( 8 | parse_qs, json, httplib2 9 | ) 10 | 11 | from twilio.rest.resources.util import ( 12 | transform_params, format_name, parse_date, convert_boolean, convert_case, 13 | convert_keys, normalize_dates 14 | ) 15 | from twilio.rest.resources.base import ( 16 | Response, Resource, InstanceResource, ListResource, 17 | make_request, make_twilio_request 18 | ) 19 | from twilio.rest.resources.phone_numbers import ( 20 | AvailablePhoneNumber, AvailablePhoneNumbers, PhoneNumber, PhoneNumbers 21 | ) 22 | from twilio.rest.resources.recordings import ( 23 | Recording, Recordings, Transcription, Transcriptions 24 | ) 25 | from twilio.rest.resources.notifications import ( 26 | Notification, Notifications 27 | ) 28 | from twilio.rest.resources.connect_apps import ( 29 | ConnectApp, ConnectApps, AuthorizedConnectApp, AuthorizedConnectApps 30 | ) 31 | from twilio.rest.resources.calls import Call, Calls 32 | from twilio.rest.resources.caller_ids import CallerIds, CallerId 33 | from twilio.rest.resources.sandboxes import Sandbox, Sandboxes 34 | from twilio.rest.resources.sms_messages import ( 35 | Sms, SmsMessage, SmsMessages, ShortCode, ShortCodes) 36 | from twilio.rest.resources.conferences import ( 37 | Participant, Participants, Conference, Conferences 38 | ) 39 | from twilio.rest.resources.queues import ( 40 | Member, Members, Queue, Queues, 41 | ) 42 | from twilio.rest.resources.applications import ( 43 | Application, Applications 44 | ) 45 | from twilio.rest.resources.accounts import Account, Accounts 46 | 47 | from twilio.rest.resources.usage import Usage 48 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY3: 3 | import unittest 4 | else: 5 | import unittest2 as unittest 6 | 7 | from twilio.rest import TwilioRestClient, resources 8 | from mock import patch, Mock 9 | from tools import create_mock_json 10 | 11 | AUTH = ("ACCOUNT_SID", "AUTH_TOKEN") 12 | 13 | 14 | class RestClientTest(unittest.TestCase): 15 | 16 | def setUp(self): 17 | self.client = TwilioRestClient("ACCOUNT_SID", "AUTH_TOKEN") 18 | 19 | @patch("twilio.rest.make_request") 20 | def test_request(self, mock): 21 | self.client.request("2010-04-01", method="GET") 22 | mock.assert_called_with("GET", "https://api.twilio.com/2010-04-01", 23 | headers={"User-Agent": 'twilio-python'}, params={}, 24 | auth=AUTH, data=None) 25 | 26 | def test_connect_apps(self): 27 | self.assertIsInstance(self.client.connect_apps, 28 | resources.ConnectApps) 29 | 30 | def test_authorized_apps(self): 31 | self.assertIsInstance(self.client.authorized_connect_apps, 32 | resources.AuthorizedConnectApps) 33 | 34 | @patch("twilio.rest.resources.base.make_request") 35 | def test_conferences(self, mock): 36 | mock.return_value = Mock() 37 | mock.return_value.ok = True 38 | mock.return_value.content = '{"conferences": []}' 39 | self.client.conferences.list() 40 | 41 | @patch("twilio.rest.resources.base.make_twilio_request") 42 | def test_members(self, mock): 43 | resp = create_mock_json("tests/resources/members_list.json") 44 | mock.return_value = resp 45 | self.client.members("QU123").list() 46 | uri = "https://api.twilio.com/2010-04-01/Accounts/ACCOUNT_SID/Queues/QU123/Members" 47 | mock.assert_called_with("GET", uri, params={}, auth=AUTH) 48 | -------------------------------------------------------------------------------- /tests/test_validation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from six import b 3 | 4 | import six 5 | if six.PY3: 6 | import unittest 7 | else: 8 | import unittest2 as unittest 9 | from twilio.util import RequestValidator 10 | 11 | 12 | class ValidationTest(unittest.TestCase): 13 | 14 | def test_validation(self): 15 | token = "1c892n40nd03kdnc0112slzkl3091j20" 16 | validator = RequestValidator(token) 17 | 18 | uri = "http://www.postbin.org/1ed898x" 19 | params = { 20 | "AccountSid": "AC9a9f9392lad99kla0sklakjs90j092j3", 21 | "ApiVersion": "2010-04-01", 22 | "CallSid": "CAd800bb12c0426a7ea4230e492fef2a4f", 23 | "CallStatus": "ringing", 24 | "Called": "+15306384866", 25 | "CalledCity": "OAKLAND", 26 | "CalledCountry": "US", 27 | "CalledState": "CA", 28 | "CalledZip": "94612", 29 | "Caller": "+15306666666", 30 | "CallerCity": "SOUTH LAKE TAHOE", 31 | "CallerCountry": "US", 32 | "CallerName": "CA Wireless Call", 33 | "CallerState": "CA", 34 | "CallerZip": "89449", 35 | "Direction": "inbound", 36 | "From": "+15306666666", 37 | "FromCity": "SOUTH LAKE TAHOE", 38 | "FromCountry": "US", 39 | "FromState": "CA", 40 | "FromZip": "89449", 41 | "To": "+15306384866", 42 | "ToCity": "OAKLAND", 43 | "ToCountry": "US", 44 | "ToState": "CA", 45 | "ToZip": "94612", 46 | } 47 | 48 | expected = b("fF+xx6dTinOaCdZ0aIeNkHr/ZAA=") 49 | 50 | self.assertEquals(validator.compute_signature(uri, params), expected) 51 | self.assertTrue(validator.validate(uri, params, expected)) 52 | -------------------------------------------------------------------------------- /tests/resources/notifications_instance.json: -------------------------------------------------------------------------------- 1 | {"sid":"NO9329e5a4ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","call_sid":"CA40f819aaed59a5733d2c1c1c69a83a28","log":"0","error_code":"11200","more_info":"http:\/\/www.twilio.com\/docs\/errors\/11200","message_text":"EmailNotification=false&LogLevel=ERROR&sourceComponent=12000&Msg=&httpResponse=500&ErrorCode=11200&url=http%3A%2F%2Fvoiceforms4000.appspot.com%2Ftwiml","message_date":"Mon, 13 Sep 2010 20:02:00 +0000","response_body":"\n\n\n500 Server Error<\/title>\n<\/head>\n<body text=#000000 bgcolor=#ffffff>\n<h1>Error: Server Error<\/h1>\n<h2>The server encountered an error and could not complete your request.<p>If the problem persists, please <A HREF=\"http:\/\/code.google.com\/appengine\/community.html\">report<\/A> your problem and mention this error message and the query that caused it.<\/h2>\n<h2><\/h2>\n<\/body><\/html>\n","request_method":"get","request_url":"https:\/\/voiceforms4000.appspot.com\/twiml\/9436\/question\/0","request_variables":"AccountSid=AC4bf2dafbed59a5733d2c1c1c69a83a28&CallStatus=in-progress&ToZip=94937&ToCity=INVERNESS&ToState=CA&Called=%2B14156694923&To=%2B14156694923&ToCountry=US&CalledZip=94937&Direction=inbound&ApiVersion=2010-04-01&Caller=%2B17378742833&CalledCity=INVERNESS&CalledCountry=US&CallSid=CA40f819aaed59a5733d2c1c1c69a83a28&CalledState=CA&From=%2B17378742833","response_headers":"Date=Mon%2C+13+Sep+2010+20%3A02%3A00+GMT&Content-Length=466&Connection=close&Content-Type=text%2Fhtml%3B+charset%3DUTF-8&Server=Google+Frontend","date_created":"Mon, 13 Sep 2010 20:02:01 +0000","api_version":"2008-08-01","date_updated":"Mon, 13 Sep 2010 20:02:01 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Notifications\/NO9329e5a4ed59a5733d2c1c1c69a83a28.json"} 2 | -------------------------------------------------------------------------------- /tests/test_notifications.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from mock import patch 3 | from nose.tools import raises, assert_true 4 | from twilio.rest.resources import Notifications 5 | from tools import create_mock_json 6 | 7 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 8 | ACCOUNT_SID = "AC123" 9 | AUTH = (ACCOUNT_SID, "token") 10 | 11 | RE_SID = "RE19e96a31ed59a5733d2c1c1c69a83a28" 12 | 13 | list_resource = Notifications(BASE_URI, AUTH) 14 | 15 | 16 | @patch("twilio.rest.resources.base.make_twilio_request") 17 | def test_paging(mock): 18 | resp = create_mock_json("tests/resources/notifications_list.json") 19 | mock.return_value = resp 20 | 21 | uri = "%s/Notifications" % (BASE_URI) 22 | list_resource.list(before=date(2010, 12, 5)) 23 | exp_params = {'MessageDate<': '2010-12-05'} 24 | 25 | mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH) 26 | 27 | 28 | @patch("twilio.rest.resources.base.make_twilio_request") 29 | def test_get(mock): 30 | resp = create_mock_json("tests/resources/notifications_instance.json") 31 | mock.return_value = resp 32 | 33 | uri = "%s/Notifications/%s" % (BASE_URI, RE_SID) 34 | list_resource.get(RE_SID) 35 | 36 | mock.assert_called_with("GET", uri, auth=AUTH) 37 | 38 | 39 | @patch("twilio.rest.resources.base.make_twilio_request") 40 | def test_get2(mock): 41 | resp = create_mock_json("tests/resources/notifications_instance.json") 42 | resp.status_code = 204 43 | mock.return_value = resp 44 | 45 | uri = "%s/Notifications/%s" % (BASE_URI, RE_SID) 46 | r = list_resource.delete(RE_SID) 47 | 48 | mock.assert_called_with("DELETE", uri, auth=AUTH) 49 | assert_true(r) 50 | 51 | 52 | @raises(AttributeError) 53 | def test_create(): 54 | list_resource.create 55 | 56 | 57 | @raises(AttributeError) 58 | def test_update(): 59 | list_resource.update 60 | -------------------------------------------------------------------------------- /twilio/rest/resources/recordings.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources.util import normalize_dates 2 | 3 | from twilio.rest.resources import InstanceResource, ListResource 4 | 5 | 6 | class Transcription(InstanceResource): 7 | pass 8 | 9 | 10 | class Transcriptions(ListResource): 11 | 12 | name = "Transcriptions" 13 | instance = Transcription 14 | 15 | def list(self, **kwargs): 16 | """ 17 | Return a list of :class:`Transcription` resources 18 | """ 19 | return self.get_instances(kwargs) 20 | 21 | 22 | class Recording(InstanceResource): 23 | 24 | subresources = [Transcriptions] 25 | 26 | def __init__(self, *args, **kwargs): 27 | super(Recording, self).__init__(*args, **kwargs) 28 | self.formats = { 29 | "mp3": self.uri + ".mp3", 30 | "wav": self.uri + ".wav", 31 | } 32 | 33 | def delete(self): 34 | """ 35 | Delete this recording 36 | """ 37 | return self.delete_instance() 38 | 39 | 40 | class Recordings(ListResource): 41 | 42 | name = "Recordings" 43 | instance = Recording 44 | 45 | @normalize_dates 46 | def list(self, before=None, after=None, **kwargs): 47 | """ 48 | Returns a page of :class:`Recording` resources as a list. 49 | For paging information see :class:`ListResource`. 50 | 51 | :param date after: Only list recordings logged after this datetime 52 | :param date before: Only list recordings logger before this datetime 53 | :param call_sid: Only list recordings from this :class:`Call` 54 | """ 55 | kwargs["DateCreated<"] = before 56 | kwargs["DateCreated>"] = after 57 | return self.get_instances(kwargs) 58 | 59 | def delete(self, sid): 60 | """ 61 | Delete the given recording 62 | """ 63 | return self.delete_instance(sid) 64 | -------------------------------------------------------------------------------- /tests/test_connect_apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import six 3 | if six.PY3: 4 | import unittest 5 | else: 6 | import unittest2 as unittest 7 | 8 | from mock import Mock, patch 9 | from twilio.rest.resources import ConnectApps 10 | 11 | 12 | class ConnectAppTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.parent = Mock() 16 | self.uri = "/base" 17 | self.auth = ("AC123", "token") 18 | self.resource = ConnectApps(self.uri, self.auth) 19 | 20 | @patch("twilio.rest.resources.base.make_twilio_request") 21 | def test_get(self, mock): 22 | mock.return_value = Mock() 23 | mock.return_value.content = '{"sid": "SID"}' 24 | 25 | self.resource.get("SID") 26 | mock.assert_called_with("GET", "/base/ConnectApps/SID", 27 | auth=self.auth) 28 | 29 | @patch("twilio.rest.resources.base.make_twilio_request") 30 | def test_list_with_paging(self, mock): 31 | mock.return_value = Mock() 32 | mock.return_value.content = '{"connect_apps": []}' 33 | 34 | self.resource.list(page=1, page_size=50) 35 | mock.assert_called_with("GET", "/base/ConnectApps", 36 | params={"Page": 1, "PageSize": 50}, auth=self.auth) 37 | 38 | @patch("twilio.rest.resources.base.make_twilio_request") 39 | def test_list(self, mock): 40 | mock.return_value = Mock() 41 | mock.return_value.content = '{"connect_apps": []}' 42 | 43 | self.resource.list() 44 | mock.assert_called_with("GET", "/base/ConnectApps", 45 | params={}, auth=self.auth) 46 | 47 | def test_create(self): 48 | with self.assertRaises(AttributeError): 49 | self.resource.create() 50 | 51 | def test_delete(self): 52 | with self.assertRaises(AttributeError): 53 | self.resource.delete() 54 | 55 | def test_update(self): 56 | with self.assertRaises(AttributeError): 57 | self.resource.update() 58 | -------------------------------------------------------------------------------- /tests/test_recordings.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from mock import patch 3 | from nose.tools import raises, assert_equals, assert_true 4 | from twilio.rest.resources import Recordings 5 | from tools import create_mock_json 6 | 7 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 8 | ACCOUNT_SID = "AC123" 9 | AUTH = (ACCOUNT_SID, "token") 10 | 11 | RE_SID = "RE19e96a31ed59a5733d2c1c1c69a83a28" 12 | 13 | recordings = Recordings(BASE_URI, AUTH) 14 | 15 | 16 | @patch("twilio.rest.resources.base.make_twilio_request") 17 | def test_paging(mock): 18 | resp = create_mock_json("tests/resources/recordings_list.json") 19 | mock.return_value = resp 20 | 21 | uri = "%s/Recordings" % (BASE_URI) 22 | recordings.list(call_sid="CA123", before=date(2010, 12, 5)) 23 | exp_params = {'CallSid': 'CA123', 'DateCreated<': '2010-12-05'} 24 | 25 | mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH) 26 | 27 | 28 | @patch("twilio.rest.resources.base.make_twilio_request") 29 | def test_get(mock): 30 | resp = create_mock_json("tests/resources/recordings_instance.json") 31 | mock.return_value = resp 32 | 33 | uri = "%s/Recordings/%s" % (BASE_URI, RE_SID) 34 | r = recordings.get(RE_SID) 35 | 36 | mock.assert_called_with("GET", uri, auth=AUTH) 37 | 38 | truri = "%s/Recordings/%s/Transcriptions" % (BASE_URI, RE_SID) 39 | assert_equals(r.transcriptions.uri, truri) 40 | 41 | 42 | @patch("twilio.rest.resources.base.make_twilio_request") 43 | def test_get2(mock): 44 | resp = create_mock_json("tests/resources/recordings_instance.json") 45 | resp.status_code = 204 46 | mock.return_value = resp 47 | 48 | uri = "%s/Recordings/%s" % (BASE_URI, RE_SID) 49 | r = recordings.delete(RE_SID) 50 | 51 | mock.assert_called_with("DELETE", uri, auth=AUTH) 52 | assert_true(r) 53 | 54 | 55 | @raises(AttributeError) 56 | def test_create(): 57 | recordings.create 58 | 59 | 60 | @raises(AttributeError) 61 | def test_update(): 62 | recordings.update 63 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2011 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /tests/resources/conferences_list.json: -------------------------------------------------------------------------------- 1 | {"page":0,"num_pages":1,"page_size":50,"total":3,"start":0,"end":2,"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences.json","first_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences.json?Page=0&PageSize=50","conferences":[{"sid":"CFf2fe8498ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"AHH YEAH","status":"in-progress","date_created":"Fri, 18 Feb 2011 21:07:19 +0000","api_version":"2008-08-01","date_updated":"Fri, 18 Feb 2011 21:07:20 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28.json","subresource_uris":{"participants":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFf2fe8498ed59a5733d2c1c1c69a83a28\/Participants.json"}},{"sid":"CFefb79a9aed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"AHH YEAH","status":"completed","date_created":"Fri, 18 Feb 2011 20:29:34 +0000","api_version":"2008-08-01","date_updated":"Fri, 18 Feb 2011 21:02:17 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFefb79a9aed59a5733d2c1c1c69a83a28.json","subresource_uris":{"participants":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFefb79a9aed59a5733d2c1c1c69a83a28\/Participants.json"}},{"sid":"CFe3bb5bfbed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"AHH YEAH","status":"completed","date_created":"Fri, 18 Feb 2011 19:26:50 +0000","api_version":"2008-08-01","date_updated":"Fri, 18 Feb 2011 19:27:33 +0000","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFe3bb5bfbed59a5733d2c1c1c69a83a28.json","subresource_uris":{"participants":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences\/CFe3bb5bfbed59a5733d2c1c1c69a83a28\/Participants.json"}}]} 2 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Quickstart 3 | =========== 4 | 5 | Getting started with the Twilio API couldn't be easier. Create a Twilio REST 6 | client to get started. For example, the following code makes a call using the 7 | Twilio REST API. 8 | 9 | 10 | Make a Call 11 | =============== 12 | 13 | .. code-block:: python 14 | 15 | from twilio.rest import TwilioRestClient 16 | 17 | # To find these visit https://www.twilio.com/user/account 18 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 19 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 20 | 21 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 22 | call = client.calls.create(to="9991231234", from_="9991231234", 23 | url="http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient") 24 | print call.length 25 | print call.sid 26 | 27 | 28 | Send an SMS 29 | ================ 30 | 31 | .. code-block:: python 32 | 33 | from twilio.rest import TwilioRestClient 34 | 35 | account = "ACXXXXXXXXXXXXXXXXX" 36 | token = "YYYYYYYYYYYYYYYYYY" 37 | client = TwilioRestClient(account, token) 38 | 39 | message = client.sms.messages.create(to="+12316851234", 40 | from_="+15555555555", 41 | body="Hello there!") 42 | 43 | 44 | Generating TwiML 45 | ================= 46 | 47 | To control phone calls, your application needs to output `TwiML 48 | <http://www.twilio.com/docs/api/twiml/>`_. Use :class:`twilio.twiml.Response` 49 | to easily create such responses. 50 | 51 | .. code-block:: python 52 | 53 | from twilio import twiml 54 | 55 | r = twiml.Response() 56 | r.play("https://api.twilio.com/cowbell.mp3", loop=5) 57 | print str(r) 58 | 59 | .. code-block:: xml 60 | 61 | <?xml version="1.0" encoding="utf-8"?> 62 | <Response> 63 | <Play loop="5">https://api.twilio.com/cowbell.mp3</Play> 64 | <Response> 65 | 66 | 67 | Digging Deeper 68 | ======================== 69 | 70 | The full power of the Twilio API is at your fingertips. The :ref:`user-guide` 71 | explains all the awesome features available to use. 72 | 73 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from twilio import __version__ 3 | from setuptools import setup, find_packages 4 | 5 | # To install the twilio-python library, open a Terminal shell, then run this 6 | # file by typing: 7 | # 8 | # python setup.py install 9 | # 10 | # You need to have the setuptools module installed. Try reading the setuptools 11 | # documentation: http://pypi.python.org/pypi/setuptools 12 | REQUIRES = ["httplib2 >= 0.7", "six"] 13 | 14 | if sys.version_info < (2, 6): 15 | REQUIRES.append('simplejson') 16 | if sys.version_info >= (3,0): 17 | REQUIRES.append('unittest2py3k') 18 | else: 19 | REQUIRES.append('unittest2') 20 | 21 | setup( 22 | name = "twilio", 23 | version = __version__, 24 | description = "Twilio API client and TwiML generator", 25 | author = "Twilio", 26 | author_email = "help@twilio.com", 27 | url = "http://github.com/twilio/twilio-python/", 28 | keywords = ["twilio","twiml"], 29 | install_requires = REQUIRES, 30 | packages = find_packages(), 31 | classifiers = [ 32 | "Development Status :: 5 - Production/Stable", 33 | "Intended Audience :: Developers", 34 | "License :: OSI Approved :: MIT License", 35 | "Operating System :: OS Independent", 36 | "Programming Language :: Python", 37 | "Programming Language :: Python :: 2.5", 38 | "Programming Language :: Python :: 2.6", 39 | "Programming Language :: Python :: 2.7", 40 | "Programming Language :: Python :: 3.2", 41 | "Topic :: Software Development :: Libraries :: Python Modules", 42 | "Topic :: Communications :: Telephony", 43 | ], 44 | long_description = """\ 45 | Python Twilio Helper Library 46 | ---------------------------- 47 | 48 | DESCRIPTION 49 | The Twilio REST SDK simplifies the process of makes calls to the Twilio REST. 50 | The Twilio REST API lets to you initiate outgoing calls, list previous calls, 51 | and much more. See http://www.github.com/twilio/twilio-python for more information. 52 | 53 | LICENSE The Twilio Python Helper Library is distributed under the MIT 54 | License """ ) 55 | -------------------------------------------------------------------------------- /tests/resources/usage_triggers_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 0, 3 | "num_pages": 1, 4 | "page_size": 50, 5 | "total": 2, 6 | "start": 0, 7 | "end": 1, 8 | "uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Usage/Triggers.json?Page=0&PageSize=50", 9 | "previous_page_uri": null, 10 | "next_page_uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json?Page=1&PageSize=50", 11 | "last_page_uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json?Page=10&PageSize=50", 12 | "usage_triggers": [ 13 | { 14 | "sid": "UT33c6aeeba34e48f38d6899ea5b765ad4", 15 | "date_updated": "2012-09-29 12:47:54", 16 | "current_usage_value": "7", 17 | "friendly_name": "Trigger for calls at usage of 500", 18 | "uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Triggers/UT33c6aeeba34e48f38d6899ea5b765ad4/UT33c6aeeba34e48f38d6899ea5b765ad4.json", 19 | "usage_record_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Category=calls", 20 | "trigger_by": "usage", 21 | "callback_method": "POST", 22 | "date_created": "2012-09-29 12:45:43", 23 | "callback_url": "http://www.example.com/", 24 | "recurring": "daily", 25 | "usage_category": "calls", 26 | "trigger_value": "500.000000" 27 | }, { 28 | "sid": "UT33c6aeeba34e48f38d6899ea5b765ad5", 29 | "date_updated": "2012-09-30 12:47:54", 30 | "current_usage_value": "7", 31 | "friendly_name": "Trigger for calls at usage of 500", 32 | "uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Triggers/UT33c6aeeba34e48f38d6899ea5b765ad4/UT33c6aeeba34e48f38d6899ea5b765ad4.json", 33 | "usage_record_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Category=calls", 34 | "trigger_by": "usage", 35 | "callback_method": "POST", 36 | "date_created": "2012-09-30 12:45:43", 37 | "callback_url": "http://www.example.com/", 38 | "recurring": "daily", 39 | "usage_category": "calls", 40 | "trigger_value": "500.000000" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /tests/test_unicode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from mock import patch, Mock 3 | from six import u 4 | from twilio.rest import resources 5 | 6 | 7 | @patch("httplib2.Http") 8 | @patch("twilio.rest.resources.base.Response") 9 | def test_ascii_encode(resp_mock, mock): 10 | http = mock.return_value 11 | http.request.return_value = (Mock(), Mock()) 12 | 13 | data = { 14 | "body": "HeyHey".encode('utf-8') 15 | } 16 | 17 | resources.make_request("GET", "http://www.example.com", data=data) 18 | 19 | http.request.assert_called_with("http://www.example.com", "GET", 20 | headers=None, body="body=HeyHey") 21 | 22 | 23 | @patch("httplib2.Http") 24 | @patch("twilio.rest.resources.base.Response") 25 | def test_ascii(resp_mock, mock): 26 | http = mock.return_value 27 | http.request.return_value = (Mock(), Mock()) 28 | 29 | data = { 30 | "body": "HeyHey" 31 | } 32 | 33 | resources.make_request("GET", "http://www.example.com", data=data) 34 | 35 | http.request.assert_called_with("http://www.example.com", "GET", 36 | headers=None, body="body=HeyHey") 37 | 38 | 39 | @patch("httplib2.Http") 40 | @patch("twilio.rest.resources.base.Response") 41 | def test_double_encoding(resp_mock, mock): 42 | http = mock.return_value 43 | http.request.return_value = (Mock(), Mock()) 44 | 45 | body = u('Chlo\xe9\xf1') 46 | 47 | data = { 48 | "body": body.encode('utf-8'), 49 | } 50 | 51 | resources.make_request("GET", "http://www.example.com", data=data) 52 | 53 | http.request.assert_called_with("http://www.example.com", "GET", 54 | headers=None, body="body=Chlo%C3%A9%C3%B1") 55 | 56 | 57 | @patch("httplib2.Http") 58 | @patch("twilio.rest.resources.base.Response") 59 | def test_paging(resp_mock, mock): 60 | http = mock.return_value 61 | http.request.return_value = (Mock(), Mock()) 62 | 63 | data = { 64 | "body": u('Chlo\xe9\xf1'), 65 | } 66 | 67 | resources.make_request("GET", "http://www.example.com", data=data) 68 | 69 | http.request.assert_called_with("http://www.example.com", "GET", 70 | headers=None, body="body=Chlo%C3%A9%C3%B1") 71 | -------------------------------------------------------------------------------- /tests/test_applications.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY3: 3 | import unittest 4 | else: 5 | import unittest2 as unittest 6 | 7 | from mock import Mock 8 | from twilio.rest.resources import Applications 9 | 10 | 11 | class ApplicationsTest(unittest.TestCase): 12 | def setUp(self): 13 | self.parent = Mock() 14 | self.resource = Applications("http://api.twilio.com", ("user", "pass")) 15 | 16 | def test_create_appliation_sms_url_method(self): 17 | self.resource.create_instance = Mock() 18 | self.resource.create(sms_method="hey") 19 | self.resource.create_instance.assert_called_with({"sms_method": "hey"}) 20 | 21 | def test_create_appliation_sms_url(self): 22 | self.resource.create_instance = Mock() 23 | self.resource.create(sms_url="hey") 24 | self.resource.create_instance.assert_called_with({"sms_url": "hey"}) 25 | 26 | def test_update_appliation_sms_url_method(self): 27 | self.resource.update_instance = Mock() 28 | self.resource.update("123", sms_method="hey") 29 | self.resource.update_instance.assert_called_with( 30 | "123", {"sms_method": "hey"}) 31 | 32 | def test_update_appliation_sms_url(self): 33 | self.resource.update_instance = Mock() 34 | self.resource.update("123", sms_url="hey") 35 | self.resource.update_instance.assert_called_with( 36 | "123", {"sms_url": "hey"}) 37 | 38 | def test_update(self): 39 | request = Mock() 40 | request.return_value = (Mock(), {"sid": "123"}) 41 | self.resource.request = request 42 | 43 | self.resource.update("123", voice_url="hey") 44 | 45 | uri = "http://api.twilio.com/Applications/123" 46 | request.assert_called_with("POST", uri, data={"VoiceUrl": "hey"}) 47 | 48 | def test_create(self): 49 | request = Mock() 50 | request.return_value = (request, {"sid": "123"}) 51 | request.status_code = 201 52 | 53 | self.resource.request = request 54 | 55 | self.resource.create(friendly_name="hey") 56 | 57 | uri = "http://api.twilio.com/Applications" 58 | request.assert_called_with("POST", uri, data={"FriendlyName": "hey"}) 59 | -------------------------------------------------------------------------------- /docs/usage/notifications.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ==================== 4 | Notifications 5 | ==================== 6 | 7 | For more information, see the `Notifications REST Resource 8 | <http://www.twilio.com/docs/api/rest/notification>`_ documentation. 9 | 10 | 11 | Listing Your Notifications 12 | ---------------------------- 13 | 14 | The following code will print out additional information about each of your 15 | current :class:`Notification` resources. 16 | 17 | .. code-block:: python 18 | 19 | from twilio.rest import TwilioRestClient 20 | 21 | # To find these visit https://www.twilio.com/user/account 22 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 23 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 24 | 25 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 26 | for notification in client.notifications.list(): 27 | print notification.more_info 28 | 29 | You can filter transcriptions by :attr:`log` and :attr:`message_date`. 30 | The :attr:`log` value is 0 for `ERROR` and 1 for `WARNING`. 31 | 32 | .. code-block:: python 33 | 34 | from twilio.rest import TwilioRestClient 35 | 36 | # To find these visit https://www.twilio.com/user/account 37 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 38 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 39 | 40 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 41 | 42 | ERROR = 0 43 | 44 | for notification in client.notifications.list(log=ERROR): 45 | print notification.error_code 46 | 47 | .. note:: Due to the potentially voluminous amount of data in a notification, 48 | the full HTTP request and response data is only returned in the 49 | :class:`Notification` instance resource representation. 50 | 51 | 52 | Deleting Notifications 53 | ------------------------ 54 | 55 | Your account can sometimes generate an inordinate amount of 56 | :class:`Notification` resources. The :class:`Notifications` resource allows 57 | you to delete unnecessary notifications. 58 | 59 | .. code-block:: python 60 | 61 | from twilio.rest import TwilioRestClient 62 | 63 | # To find these visit https://www.twilio.com/user/account 64 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 65 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 66 | 67 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 68 | client.notifications.delete("NO123") 69 | 70 | -------------------------------------------------------------------------------- /docs/usage/messages.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources.sms_messages 2 | 3 | ============ 4 | SMS Messages 5 | ============ 6 | 7 | For more information, see the 8 | `SMS Message REST Resource <http://www.twilio.com/docs/api/rest/sms>`_ 9 | documentation. 10 | 11 | 12 | Sending a Text Message 13 | ---------------------- 14 | 15 | Send a text message in only a few lines of code. 16 | 17 | .. code-block:: python 18 | 19 | from twilio.rest import TwilioRestClient 20 | 21 | # To find these visit https://www.twilio.com/user/account 22 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 23 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 24 | 25 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 26 | 27 | message = client.sms.messages.create(to="+13216851234", 28 | from_="+15555555555", 29 | body="Hello!") 30 | 31 | 32 | .. note:: The message body must be less than 160 characters in length 33 | 34 | If you want to send a message from a `short code 35 | <http://www.twilio.com/api/sms/short-codes>`_ on Twilio, just set :attr:`from_` 36 | to your short code's number. 37 | 38 | 39 | Retrieving Sent Messages 40 | ------------------------- 41 | 42 | .. code-block:: python 43 | 44 | from twilio.rest import TwilioRestClient 45 | 46 | # To find these visit https://www.twilio.com/user/account 47 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 48 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 49 | 50 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 51 | 52 | for message in client.sms.messages.list(): 53 | print message.body 54 | 55 | 56 | Filtering Your Messages 57 | ------------------------- 58 | 59 | The :meth:`list` methods supports filtering on :attr:`to`, :attr:`from_`, 60 | and :attr:`date_sent`. 61 | The following will only show messages to "+5466758723" on January 1st, 2011. 62 | 63 | .. code-block:: python 64 | 65 | from datetime import date 66 | from twilio.rest import TwilioRestClient 67 | 68 | # To find these visit https://www.twilio.com/user/account 69 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 70 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 71 | 72 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 73 | 74 | messages = client.sms.messages.list(to="+5466758723", 75 | date_sent=date(2011,1,1)) 76 | 77 | for message in messages: 78 | print message.body 79 | 80 | -------------------------------------------------------------------------------- /docs/usage/twiml.rst: -------------------------------------------------------------------------------- 1 | .. _usage-twiml: 2 | 3 | .. module:: twilio.twiml 4 | 5 | ============== 6 | TwiML Creation 7 | ============== 8 | 9 | TwiML creation begins with the :class:`Response` verb. 10 | Each successive verb is created by calling various methods on the response, 11 | such as :meth:`say` or :meth:`play`. 12 | These methods return the verbs they create to ease creation of nested TwiML. 13 | To finish, call the :meth:`toxml` method on the :class:`Response`, 14 | which returns raw TwiML. 15 | 16 | .. code-block:: python 17 | 18 | from twilio import twiml 19 | 20 | r = twiml.Response() 21 | r.say("Hello") 22 | print str(r) 23 | 24 | .. code-block:: xml 25 | 26 | <?xml version="1.0" encoding="utf-8"?> 27 | <Response><Say>Hello</Say><Response> 28 | 29 | The verb methods (outlined in the :doc:`complete reference </api/twiml>`) 30 | take the body (only text) of the verb as the first argument. 31 | All attributes are keyword arguments. 32 | 33 | .. code-block:: python 34 | 35 | from twilio import twiml 36 | 37 | r = twiml.Response() 38 | r.play("https://api.twilio.com/cowbell.mp3", loop=5) 39 | print str(r) 40 | 41 | .. code-block:: xml 42 | 43 | <?xml version="1.0" encoding="utf-8"?> 44 | <Response> 45 | <Play loop="3">https://api.twilio.com/cowbell.mp3</Play> 46 | <Response> 47 | 48 | Python 2.6+ added the :const:`with` statement for context management. 49 | Using :const:`with`, the module can *almost* emulate Ruby blocks. 50 | 51 | .. code-block:: python 52 | 53 | from twilio import twiml 54 | 55 | r = twiml.Response() 56 | r.say("hello") 57 | with r.gather(finishOnKey=4) as g: 58 | g.say("world") 59 | print str(r) 60 | 61 | which returns the following 62 | 63 | .. code-block:: xml 64 | 65 | <?xml version="1.0" encoding="utf-8"?> 66 | <Response> 67 | <Say>Hello</Say> 68 | <Gather finishOnKey="4"><Say>World</Say></Gather> 69 | </Response> 70 | 71 | If you don't want the XML declaration in your output, 72 | use the :meth:`toxml` method 73 | 74 | .. code-block:: python 75 | 76 | from twilio import twiml 77 | 78 | r = twiml.Response() 79 | r.say("hello") 80 | with r.gather(finishOnKey=4) as g: 81 | g.say("world") 82 | print r.toxml(xml_declaration=False) 83 | 84 | .. code-block:: xml 85 | 86 | <Response> 87 | <Say>Hello</Say> 88 | <Gather finishOnKey="4"><Say>World</Say></Gather> 89 | </Response> 90 | 91 | -------------------------------------------------------------------------------- /tests/test_queues.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from tools import create_mock_json 3 | from twilio.rest.resources import Queues, Queue 4 | 5 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 6 | ACCOUNT_SID = "AC123" 7 | AUTH = (ACCOUNT_SID, "token") 8 | QUEUE_SID = "QU1b9faddec3d54ec18488f86c83019bf0" 9 | CALL_SID = "CAaaf2e9ded94aba3e57c42a3d55be6ff2" 10 | 11 | list_resource = Queues(BASE_URI, AUTH) 12 | instance_resource = Queue(list_resource, QUEUE_SID) 13 | 14 | 15 | @patch("twilio.rest.resources.base.make_twilio_request") 16 | def test_queues_list(mock): 17 | resp = create_mock_json("tests/resources/queues_list.json") 18 | mock.return_value = resp 19 | 20 | uri = "%s/Queues" % (BASE_URI) 21 | list_resource.list() 22 | 23 | mock.assert_called_with("GET", uri, params={}, auth=AUTH) 24 | 25 | 26 | @patch("twilio.rest.resources.base.make_twilio_request") 27 | def test_queues_create(mock): 28 | resp = create_mock_json("tests/resources/queues_instance.json") 29 | resp.status_code = 201 30 | mock.return_value = resp 31 | 32 | uri = "%s/Queues" % (BASE_URI) 33 | list_resource.create('test', max_size=9001) 34 | 35 | mock.assert_called_with("POST", uri, 36 | data={'FriendlyName': 'test', 'MaxSize': 9001}, 37 | auth=AUTH) 38 | 39 | 40 | @patch("twilio.rest.resources.base.make_twilio_request") 41 | def test_queues_get(mock): 42 | resp = create_mock_json("tests/resources/queues_instance.json") 43 | mock.return_value = resp 44 | 45 | uri = "%s/Queues/%s" % (BASE_URI, QUEUE_SID) 46 | list_resource.get(QUEUE_SID) 47 | mock.assert_called_with("GET", uri, auth=AUTH) 48 | 49 | 50 | @patch("twilio.rest.resources.base.make_twilio_request") 51 | def test_queue_update(mock): 52 | resp = create_mock_json("tests/resources/queues_instance.json") 53 | mock.return_value = resp 54 | 55 | uri = "%s/Queues/%s" % (BASE_URI, QUEUE_SID) 56 | instance_resource.update(friendly_name='QQ') 57 | 58 | mock.assert_called_with("POST", uri, 59 | data={'FriendlyName': 'QQ'}, auth=AUTH) 60 | 61 | 62 | @patch("twilio.rest.resources.base.make_twilio_request") 63 | def test_queue_delete(mock): 64 | resp = create_mock_json("tests/resources/queues_instance.json") 65 | mock.return_value = resp 66 | 67 | uri = "%s/Queues/%s" % (BASE_URI, QUEUE_SID) 68 | instance_resource.delete() 69 | 70 | mock.assert_called_with("DELETE", uri, auth=AUTH) 71 | -------------------------------------------------------------------------------- /docs/usage/accounts.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest 2 | 3 | =========== 4 | Accounts 5 | =========== 6 | 7 | Managing Twilio accounts is straightforward. 8 | Update your own account information or create and manage multiple subaccounts. 9 | 10 | For more information, see the 11 | `Account REST Resource <http://www.twilio.com/docs/api/rest/account>`_ 12 | documentation. 13 | 14 | 15 | Updating Account Information 16 | ---------------------------- 17 | 18 | Use the :meth:`Account.update` to modify one of your accounts. 19 | Right now the only valid attribute is `FriendlyName`. 20 | 21 | .. code-block:: python 22 | 23 | from twilio.rest import TwilioRestClient 24 | 25 | # To find these visit https://www.twilio.com/user/account 26 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 27 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 28 | 29 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 30 | account = client.accounts.get(ACCOUNT_SID) 31 | account.update(friendly_name="My Awesome Account") 32 | 33 | 34 | Creating Subaccounts 35 | ---------------------- 36 | 37 | Subaccounts are easy to make. 38 | 39 | .. code-block:: python 40 | 41 | from twilio.rest import TwilioRestClient 42 | 43 | # To find these visit https://www.twilio.com/user/account 44 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 45 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 46 | 47 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 48 | subaccount = client.accounts.create(name="My Awesome SubAccount") 49 | 50 | 51 | Managing Subaccounts 52 | ------------------------- 53 | 54 | Say you have a subaccount for Client X with an account sid `AC123` 55 | 56 | .. code-block:: python 57 | 58 | from twilio.rest import TwilioRestClient 59 | 60 | # To find these visit https://www.twilio.com/user/account 61 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 62 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 63 | 64 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 65 | 66 | # Client X's subaccount 67 | subaccount = client.accounts.get('AC123') 68 | 69 | Client X hasn't paid you recently, so let's suspend their account. 70 | 71 | .. code-block:: python 72 | 73 | subaccount.suspend() 74 | 75 | If it was just a misunderstanding, reenable their account. 76 | 77 | .. code-block:: python 78 | 79 | subaccount.activate() 80 | 81 | Otherwise, close their account permanently. 82 | 83 | .. code-block:: python 84 | 85 | subaccount.close() 86 | 87 | .. warning:: 88 | This action can't be undone. 89 | 90 | -------------------------------------------------------------------------------- /twilio/rest/resources/caller_ids.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources import transform_params 2 | from twilio.rest.resources import InstanceResource, ListResource 3 | 4 | 5 | class CallerId(InstanceResource): 6 | 7 | def delete(self): 8 | """ 9 | Deletes this caller ID from the account. 10 | """ 11 | return self.delete_instance() 12 | 13 | def update(self, **kwargs): 14 | """ 15 | Update the CallerId 16 | """ 17 | self.update_instance(**kwargs) 18 | 19 | 20 | class CallerIds(ListResource): 21 | """ A list of :class:`CallerId` resources """ 22 | 23 | name = "OutgoingCallerIds" 24 | key = "outgoing_caller_ids" 25 | instance = CallerId 26 | 27 | def delete(self, sid): 28 | """ 29 | Deletes a specific :class:`CallerId` from the account. 30 | """ 31 | self.delete_instance(sid) 32 | 33 | def list(self, **kwargs): 34 | """ 35 | :param phone_number: Show caller ids with this phone number. 36 | :param friendly_name: Show caller ids with this friendly name. 37 | """ 38 | return self.get_instances(kwargs) 39 | 40 | def update(self, sid, **kwargs): 41 | """ 42 | Update a specific :class:`CallerId` 43 | """ 44 | return self.update_instance(sid, kwargs) 45 | 46 | def validate(self, phone_number, **kwargs): 47 | """ 48 | Begin the validation process for the given number. 49 | 50 | Returns a dictionary with the following keys 51 | 52 | **account_sid**: 53 | The unique id of the Account to which the Validation Request belongs. 54 | 55 | **phone_number**: The incoming phone number being validated, 56 | formatted with a '+' and country code e.g., +16175551212 57 | 58 | **friendly_name**: The friendly name you provided, if any. 59 | 60 | **validation_code**: The 6 digit validation code that must be entered 61 | via the phone to validate this phone number for Caller ID. 62 | 63 | :param phone_number: The phone number to call and validate 64 | :param friendly_name: A description for the new caller ID 65 | :param call_delay: Number of seconds to delay the validation call. 66 | :param extension: Digits to dial after connecting the validation call. 67 | :returns: A response dictionary 68 | """ 69 | kwargs["phone_number"] = phone_number 70 | params = transform_params(kwargs) 71 | resp, validation = self.request("POST", self.uri, data=params) 72 | return validation 73 | -------------------------------------------------------------------------------- /tests/resources/available_phone_numbers_us_tollfree.json: -------------------------------------------------------------------------------- 1 | {"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers\/US\/TollFree.json","available_phone_numbers":[{"friendly_name":"(415) 867-5309","phone_number":"+141586753092","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753094","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753099","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753094","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753095","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753093","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753092","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753094","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753095","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753096","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753092","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753092","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753090","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753095","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753097","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753097","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753092","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753096","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753091","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753090","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753097","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753099","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753097","iso_country":"US"},{"friendly_name":"(415) 867-5309","phone_number":"+141586753094","iso_country":"US"}]} 2 | -------------------------------------------------------------------------------- /tests/test_make_request.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that make+request is making correct HTTP requests 3 | 4 | Uses the awesome httpbin.org to validate responses 5 | """ 6 | import twilio 7 | from nose.tools import raises 8 | from mock import patch, Mock 9 | from twilio import TwilioRestException 10 | from twilio.rest.resources.base import make_request, make_twilio_request 11 | 12 | get_headers = { 13 | "User-Agent": "twilio-python/%s" % (twilio.__version__), 14 | "Accept": "application/json", 15 | } 16 | 17 | post_headers = get_headers.copy() 18 | post_headers["Content-Type"] = "application/x-www-form-urlencoded" 19 | 20 | 21 | @patch('twilio.rest.resources.base.Response') 22 | @patch('httplib2.Http') 23 | def test_get_params(http_mock, response_mock): 24 | http = Mock() 25 | http.request.return_value = (Mock(), Mock()) 26 | http_mock.return_value = http 27 | make_request("GET", "http://httpbin.org/get", params={"hey": "you"}) 28 | http.request.assert_called_with("http://httpbin.org/get?hey=you", "GET", 29 | body=None, headers=None) 30 | 31 | 32 | @patch('twilio.rest.resources.base.Response') 33 | @patch('httplib2.Http') 34 | def test_get_extra_params(http_mock, response_mock): 35 | http = Mock() 36 | http.request.return_value = (Mock(), Mock()) 37 | http_mock.return_value = http 38 | make_request("GET", "http://httpbin.org/get?foo=bar", params={"hey": "you"}) 39 | http.request.assert_called_with("http://httpbin.org/get?foo=bar&hey=you", "GET", 40 | body=None, headers=None) 41 | 42 | 43 | @patch('twilio.rest.resources.base.Response') 44 | @patch('httplib2.Http') 45 | def test_resp_uri(http_mock, response_mock): 46 | http = Mock() 47 | http.request.return_value = (Mock(), Mock()) 48 | http_mock.return_value = http 49 | make_request("GET", "http://httpbin.org/get") 50 | http.request.assert_called_with("http://httpbin.org/get", "GET", 51 | body=None, headers=None) 52 | 53 | 54 | @patch('twilio.rest.resources.base.make_request') 55 | def test_make_twilio_request_headers(mock): 56 | url = "http://random/url" 57 | make_twilio_request("POST", url) 58 | mock.assert_called_with("POST", "http://random/url.json", 59 | headers=post_headers) 60 | 61 | 62 | @raises(TwilioRestException) 63 | @patch('twilio.rest.resources.base.make_request') 64 | def test_make_twilio_request_bad_data(mock): 65 | resp = Mock() 66 | resp.ok = False 67 | mock.return_value = resp 68 | 69 | url = "http://random/url" 70 | make_twilio_request("POST", url) 71 | mock.assert_called_with("POST", "http://random/url.json", 72 | headers=post_headers) 73 | -------------------------------------------------------------------------------- /tests/test_authorized_connect_apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import six 3 | if six.PY3: 4 | import unittest 5 | else: 6 | import unittest2 as unittest 7 | 8 | from mock import Mock, patch 9 | from twilio.rest.resources import AuthorizedConnectApps 10 | from twilio.rest.resources import AuthorizedConnectApp 11 | 12 | 13 | class AuthorizedConnectAppTest(unittest.TestCase): 14 | 15 | def setUp(self): 16 | self.parent = Mock() 17 | self.uri = "/base" 18 | self.auth = ("AC123", "token") 19 | self.resource = AuthorizedConnectApps(self.uri, self.auth) 20 | 21 | @patch("twilio.rest.resources.base.make_twilio_request") 22 | def test_get(self, mock): 23 | mock.return_value = Mock() 24 | mock.return_value.content = '{"connect_app_sid": "SID"}' 25 | 26 | self.resource.get("SID") 27 | mock.assert_called_with("GET", "/base/AuthorizedConnectApps/SID", 28 | auth=self.auth) 29 | 30 | @patch("twilio.rest.resources.base.make_twilio_request") 31 | def test_list(self, mock): 32 | mock.return_value = Mock() 33 | mock.return_value.content = '{"authorized_connect_apps": []}' 34 | 35 | self.resource.list() 36 | mock.assert_called_with("GET", "/base/AuthorizedConnectApps", 37 | params={}, auth=self.auth) 38 | 39 | def test_load(self): 40 | instance = AuthorizedConnectApp(Mock(), "sid") 41 | instance.load({ 42 | "connect_app_sid": "SID", 43 | "account_sid": "AC8dfe2f2358cf421cb6134cf6f217c6a3", 44 | "permissions": ["get-all"], 45 | "connect_app_friendly_name": "foo", 46 | "connect_app_description": "bat", 47 | "connect_app_company_name": "bar", 48 | "connect_app_homepage_url": "http://www.google.com", 49 | "uri": "/2010-04-01/Accounts/", 50 | }) 51 | 52 | self.assertEquals(instance.permissions, ["get-all"]) 53 | self.assertEquals(instance.sid, "SID") 54 | self.assertEquals(instance.friendly_name, "foo") 55 | self.assertEquals(instance.description, "bat") 56 | self.assertEquals(instance.homepage_url, "http://www.google.com") 57 | self.assertEquals(instance.company_name, "bar") 58 | 59 | def test_delete(self): 60 | with self.assertRaises(AttributeError): 61 | self.resource.delete() 62 | 63 | def test_create(self): 64 | with self.assertRaises(AttributeError): 65 | self.resource.create() 66 | 67 | def test_update(self): 68 | with self.assertRaises(AttributeError): 69 | self.resource.update() 70 | -------------------------------------------------------------------------------- /tests/test_usage.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from nose.tools import raises 3 | from tools import create_mock_json 4 | from twilio.rest.resources import Usage 5 | 6 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 7 | ACCOUNT_SID = "AC123" 8 | AUTH = (ACCOUNT_SID, "token") 9 | 10 | usage = Usage(BASE_URI, AUTH) 11 | 12 | 13 | @patch("twilio.rest.resources.base.make_twilio_request") 14 | def test_triggers_create(request): 15 | resp = create_mock_json("tests/resources/usage_triggers_instance.json") 16 | resp.status_code = 201 17 | request.return_value = resp 18 | 19 | usage.triggers.create( 20 | friendly_name="foo", 21 | usage_category="sms", 22 | trigger_by="count", 23 | recurring="price", 24 | trigger_value="10.00", 25 | callback_url="http://www.example.com", 26 | callback_method="POST" 27 | ) 28 | 29 | uri = "%s/Usage/Triggers" % BASE_URI 30 | request.assert_called_with("POST", uri, data={ 31 | "FriendlyName": "foo", 32 | "UsageCategory": "sms", 33 | "TriggerBy": "count", 34 | "Recurring": "price", 35 | "TriggerValue": "10.00", 36 | "CallbackUrl": "http://www.example.com", 37 | "CallbackMethod": "POST" 38 | }, auth=AUTH) 39 | 40 | 41 | @patch("twilio.rest.resources.base.make_twilio_request") 42 | def test_triggers_paging(request): 43 | resp = create_mock_json("tests/resources/usage_triggers_list.json") 44 | request.return_value = resp 45 | 46 | uri = "%s/Usage/Triggers" % BASE_URI 47 | usage.triggers.list( 48 | recurring="daily", 49 | usage_category="sms", 50 | trigger_by="count") 51 | 52 | request.assert_called_with("GET", uri, params={ 53 | "Recurring": "daily", 54 | "UsageCategory": "sms", 55 | "TriggerBy": "count" 56 | }, auth=AUTH) 57 | 58 | 59 | @patch("twilio.rest.resources.base.make_twilio_request") 60 | def test_records_paging(request): 61 | resp = create_mock_json("tests/resources/usage_records_list.json") 62 | request.return_value = resp 63 | 64 | uri = "%s/Usage/Records" % BASE_URI 65 | usage.records.list( 66 | start_date="2012-10-12", 67 | end_date="2012-10-13", 68 | category="sms") 69 | 70 | request.assert_called_with("GET", uri, params={ 71 | "StartDate": "2012-10-12", 72 | "EndDate": "2012-10-13", 73 | "Category": "sms" 74 | }, auth=AUTH) 75 | 76 | 77 | @raises(AttributeError) 78 | def test_records_create(): 79 | usage.records.all.create 80 | 81 | 82 | @raises(AttributeError) 83 | def test_records_delete(): 84 | usage.records.all.delete 85 | 86 | 87 | @raises(AttributeError) 88 | def test_records_get(): 89 | usage.records.all.get('abc') 90 | -------------------------------------------------------------------------------- /docs/usage/validation.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.util 2 | 3 | =========================== 4 | Validate Incoming Requests 5 | =========================== 6 | 7 | Twilio requires that your TwiML-serving web server be open to the public. This 8 | is necessary so that Twilio can retrieve TwiML from urls and POST data back to 9 | your server. 10 | 11 | However, there may be people out there trying to spoof the Twilio service. 12 | Luckily, there's an easy way to validate that incoming requests are from Twilio 13 | and Twilio alone. 14 | 15 | An in-depth guide to our security features can be `found in our online 16 | documentation <http://www.twilio.com/docs/security>`_. 17 | 18 | Before you can validate requests, you'll need four pieces of information: 19 | 20 | * your Twilio Auth Token (found in your `Dashboard 21 | <https://www.twilio.com/user/account>`_) 22 | * the POST data for the request 23 | * the requested URL 24 | * the X-Twilio-Signature header value 25 | 26 | Obtaining the last three pieces of information depends on the framework you are 27 | using to process requests. The below example assumes that you have the POST 28 | data as a dictionary and the url and X-Twilio-Signature as strings. 29 | 30 | The below example will print out a confirmation message if the request is 31 | actually from Twilio. 32 | 33 | .. code-block:: python 34 | 35 | from twilio.util import RequestValidator 36 | 37 | AUTH_TOKEN = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY' 38 | 39 | validator = RequestValidator(AUTH_TOKEN) 40 | 41 | # the callback URL you provided to Twilio 42 | url = "http://www.example.com/my/callback/url.xml" 43 | 44 | # the POST variables attached to the request (eg "From", "To") 45 | post_vars = {} 46 | 47 | # X-Twilio-Signature header value 48 | signature = "HpS7PBa1Agvt4OtO+wZp75IuQa0=" # will look something like that 49 | 50 | if validator.validate(url, post_vars, signature): 51 | print "Confirmed to have come from Twilio." 52 | else: 53 | print "NOT VALID. It might have been spoofed!" 54 | 55 | 56 | Trailing Slashes 57 | ================== 58 | 59 | If your URL uses an "index" page, such as index.php or index.html to handle 60 | the request, such as: https://mycompany.com/twilio where the real page 61 | is served from https://mycompany.com/twilio/index.php, then Apache or 62 | PHP may rewrite that URL a little bit so it's got a trailing slash, such as 63 | https://mycompany.com/twilio/ for example. 64 | 65 | Using the code above, or similar code in another language, you could 66 | end up with an incorrect hash because Twilio built the hash using 67 | https://mycompany.com/twilio and you may have built the hash using 68 | https://mycompany.com/twilio/. More information can be found in our 69 | documentation on validating requests. 70 | 71 | -------------------------------------------------------------------------------- /tests/resources/incoming_phone_numbers_list.json: -------------------------------------------------------------------------------- 1 | {"page":0,"num_pages":1,"page_size":50,"total":3,"start":0,"end":2,"uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","first_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json?Page=0&PageSize=50","incoming_phone_numbers":[{"sid":"PN81d4ed70ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"(415) 867-5309","phone_number":"+141586753096","voice_url":"","voice_method":"POST","voice_fallback_url":"","voice_fallback_method":"POST","voice_caller_id_lookup":true,"date_created":"Tue, 15 Feb 2011 01:04:42 +0000","date_updated":"Tue, 15 Feb 2011 01:36:01 +0000","sms_url":"","sms_method":"POST","sms_fallback_url":"","sms_fallback_method":"POST","capabilities":{"voice":true,"sms":true},"status_callback":null,"status_callback_method":null,"api_version":"2008-08-01","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers\/PN81d4ed70ed59a5733d2c1c1c69a83a28.json"},{"sid":"PN995e2937ed59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"The Twilio Welcome Demo","phone_number":"+141586753093","voice_url":"http:\/\/voiceforms4000.appspot.com\/twiml","voice_method":"GET","voice_fallback_url":"","voice_fallback_method":"POST","voice_caller_id_lookup":false,"date_created":"Mon, 28 Dec 2009 22:40:21 +0000","date_updated":"Tue, 31 Aug 2010 12:52:42 +0000","sms_url":"http:\/\/apps.dev.twilio.com\/~kjconroy\/sms\/sms-conversation.php","sms_method":"GET","sms_fallback_url":"","sms_fallback_method":"GET","capabilities":{"voice":true,"sms":true},"status_callback":"","status_callback_method":"POST","api_version":"2010-04-01","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers\/PN995e2937ed59a5733d2c1c1c69a83a28.json"},{"sid":"PNd2ae06cced59a5733d2c1c1c69a83a28","account_sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"An example Twilio Application","phone_number":"+141586753092","voice_url":"http:\/\/apps.dev.twilio.com\/~kjconroy\/mobilemusical\/index.php","voice_method":"POST","voice_fallback_url":"","voice_fallback_method":null,"voice_caller_id_lookup":false,"date_created":"Sat, 13 Jun 2009 01:00:39 +0000","date_updated":"Tue, 04 May 2010 09:33:02 +0000","sms_url":"","sms_method":null,"sms_fallback_url":"","sms_fallback_method":null,"capabilities":{"voice":true,"sms":false},"status_callback":null,"status_callback_method":null,"api_version":"2008-08-01","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers\/PNd2ae06cced59a5733d2c1c1c69a83a28.json"}]} 2 | -------------------------------------------------------------------------------- /twilio/jwt/__init__.py: -------------------------------------------------------------------------------- 1 | """ JSON Web Token implementation 2 | 3 | Minimum implementation based on this spec: 4 | http://self-issued.info/docs/draft-jones-json-web-token-01.html 5 | """ 6 | import base64 7 | import hashlib 8 | import hmac 9 | from six import text_type, b 10 | 11 | 12 | # default text to binary representation conversion 13 | def binary(txt): 14 | return txt.encode('utf-8') 15 | 16 | try: 17 | import json 18 | except ImportError: 19 | import simplejson as json 20 | 21 | 22 | __all__ = ['encode', 'decode', 'DecodeError'] 23 | 24 | 25 | class DecodeError(Exception): 26 | pass 27 | 28 | signing_methods = { 29 | 'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(), 30 | 'HS384': lambda msg, key: hmac.new(key, msg, hashlib.sha384).digest(), 31 | 'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest(), 32 | } 33 | 34 | 35 | def base64url_decode(input): 36 | input += b('=') * (4 - (len(input) % 4)) 37 | return base64.urlsafe_b64decode(input) 38 | 39 | 40 | def base64url_encode(input): 41 | return base64.urlsafe_b64encode(input).decode('utf-8').replace('=', '') 42 | 43 | 44 | def encode(payload, key, algorithm='HS256'): 45 | segments = [] 46 | header = {"typ": "JWT", "alg": algorithm} 47 | segments.append(base64url_encode(binary(json.dumps(header)))) 48 | segments.append(base64url_encode(binary(json.dumps(payload)))) 49 | sign_input = '.'.join(segments) 50 | try: 51 | signature = signing_methods[algorithm](binary(sign_input), binary(key)) 52 | except KeyError: 53 | raise NotImplementedError("Algorithm not supported") 54 | segments.append(base64url_encode(signature)) 55 | return '.'.join(segments) 56 | 57 | 58 | def decode(jwt, key='', verify=True): 59 | try: 60 | signing_input, crypto_segment = jwt.rsplit('.', 1) 61 | header_segment, payload_segment = signing_input.split('.', 1) 62 | except ValueError: 63 | raise DecodeError("Not enough segments") 64 | try: 65 | header_raw = base64url_decode(binary(header_segment)).decode('utf-8') 66 | payload_raw = base64url_decode(binary(payload_segment)).decode('utf-8') 67 | header = json.loads(header_raw) 68 | payload = json.loads(payload_raw) 69 | signature = base64url_decode(binary(crypto_segment)) 70 | except (ValueError, TypeError): 71 | raise DecodeError("Invalid segment encoding") 72 | if verify: 73 | try: 74 | method = signing_methods[header['alg']] 75 | if not signature == method(binary(signing_input), binary(key)): 76 | raise DecodeError("Signature verification failed") 77 | except KeyError: 78 | raise DecodeError("Algorithm not supported") 79 | return payload 80 | -------------------------------------------------------------------------------- /docs/usage/token-generation.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.util 2 | 3 | =========================== 4 | Generate Capability Tokens 5 | =========================== 6 | 7 | `Twilio Client <http://www.twilio.com/api/client>`_ allows you to make and 8 | receive connections in the browser. 9 | You can place a call to a phone on the PSTN network, 10 | all without leaving your browser. See the `Twilio Client Quickstart 11 | <http:/www.twilio.com/docs/quickstart/client>`_ to get up and running with 12 | Twilio Client. 13 | 14 | Capability tokens are used by `Twilio Client 15 | <http://www.twilio.com/api/client>`_ to provide connection 16 | security and authorization. The `Capability Token documentation 17 | <http://www.twilio.com/docs/tokens>`_ explains in depth the purpose and 18 | features of these tokens. 19 | 20 | :class:`TwilioCapability` is responsible for the creation of these capability 21 | tokens. You'll need your Twilio AccountSid and AuthToken. 22 | 23 | .. code-block:: python 24 | 25 | from twilio.util import TwilioCapability 26 | 27 | # Find these values at twilio.com/user/account 28 | account_sid = "AC123123" 29 | auth_token = "secret" 30 | 31 | capability = TwilioCapability(account_sid, auth_token) 32 | 33 | 34 | Allow Incoming Connections 35 | ============================== 36 | 37 | Before a device running `Twilio Client <http://www.twilio.com/api/client>`_ 38 | can recieve incoming connections, the instance must first register a name 39 | (such as "Alice" or "Bob"). 40 | The :meth:`allow_client_incoming` method adds the client name to the 41 | capability token. 42 | 43 | .. code-block:: python 44 | 45 | capability.allow_client_incoming("Alice") 46 | 47 | 48 | Allow Outgoing Connections 49 | ============================== 50 | 51 | To make an outgoing connection from a 52 | `Twilio Client <http://www.twilio.com/api/client>`_ device, 53 | you'll need to choose a 54 | `Twilio Application <http://www.twilio.com/docs/api/rest/applications>`_ 55 | to handle TwiML URLs. A Twilio Application is a collection of URLs responsible 56 | for outputting valid TwiML to control phone calls and SMS. 57 | 58 | .. code-block:: python 59 | 60 | # Twilio Application Sid 61 | application_sid = "APabe7650f654fc34655fc81ae71caa3ff" 62 | capability.allow_client_outgoing(application_sid) 63 | 64 | 65 | Generate a Token 66 | ================== 67 | 68 | .. code-block:: python 69 | 70 | token = capability.generate() 71 | 72 | By default, this token will expire in one hour. If you'd like to change the 73 | token expiration, :meth:`generate` takes an optional :attr:`expires` argument. 74 | 75 | .. code-block:: python 76 | 77 | token = capability.generate(expires=600) 78 | 79 | This token will now expire in 10 minutes. If you haven't guessed already, 80 | :attr:`expires` is expressed in seconds. 81 | 82 | -------------------------------------------------------------------------------- /tests/test_calls.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from mock import patch 3 | from nose.tools import raises, assert_true 4 | from twilio.rest.resources import Calls 5 | from tools import create_mock_json 6 | 7 | BASE_URI = "https://api.twilio.com/2010-04-01/Accounts/AC123" 8 | ACCOUNT_SID = "AC123" 9 | AUTH = (ACCOUNT_SID, "token") 10 | CALL_SID = "CA47e13748ed59a5733d2c1c1c69a83a28" 11 | 12 | list_resource = Calls(BASE_URI, AUTH) 13 | 14 | 15 | @patch("twilio.rest.resources.base.make_twilio_request") 16 | def test_create_call(mock): 17 | resp = create_mock_json("tests/resources/calls_instance.json") 18 | resp.status_code = 201 19 | mock.return_value = resp 20 | 21 | uri = "%s/Calls" % (BASE_URI) 22 | list_resource.create("TO", "FROM", "url", record=True, application_sid='APPSID') 23 | exp_params = { 24 | 'To': "TO", 25 | 'From': "FROM", 26 | 'Url': "url", 27 | 'Record': "true", 28 | 'ApplicationSid': 'APPSID', 29 | } 30 | 31 | mock.assert_called_with("POST", uri, data=exp_params, auth=AUTH) 32 | 33 | 34 | @patch("twilio.rest.resources.base.make_twilio_request") 35 | def test_paging(mock): 36 | resp = create_mock_json("tests/resources/calls_list.json") 37 | mock.return_value = resp 38 | 39 | uri = "%s/Calls" % (BASE_URI) 40 | list_resource.list(started_before=date(2010, 12, 5)) 41 | exp_params = {'StartTime<': '2010-12-05'} 42 | 43 | mock.assert_called_with("GET", uri, params=exp_params, auth=AUTH) 44 | 45 | 46 | @patch("twilio.rest.resources.base.make_twilio_request") 47 | def test_get(mock): 48 | resp = create_mock_json("tests/resources/calls_instance.json") 49 | mock.return_value = resp 50 | 51 | uri = "%s/Calls/%s" % (BASE_URI, CALL_SID) 52 | list_resource.get(CALL_SID) 53 | 54 | mock.assert_called_with("GET", uri, auth=AUTH) 55 | 56 | 57 | @patch("twilio.rest.resources.base.make_twilio_request") 58 | def test_hangup(mock): 59 | resp = create_mock_json("tests/resources/calls_instance.json") 60 | resp.status_code = 204 61 | mock.return_value = resp 62 | 63 | uri = "%s/Calls/%s" % (BASE_URI, CALL_SID) 64 | r = list_resource.hangup(CALL_SID) 65 | exp_data = {"Status": "completed"} 66 | 67 | mock.assert_called_with("POST", uri, data=exp_data, auth=AUTH) 68 | assert_true(r) 69 | 70 | 71 | @patch("twilio.rest.resources.base.make_twilio_request") 72 | def test_cancel(mock): 73 | resp = create_mock_json("tests/resources/calls_instance.json") 74 | resp.status_code = 204 75 | mock.return_value = resp 76 | 77 | uri = "%s/Calls/%s" % (BASE_URI, CALL_SID) 78 | r = list_resource.cancel(CALL_SID) 79 | exp_data = {"Status": "canceled"} 80 | 81 | mock.assert_called_with("POST", uri, data=exp_data, auth=AUTH) 82 | assert_true(r) 83 | 84 | 85 | @raises(AttributeError) 86 | def test_create(): 87 | list_resource.delete 88 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY3: 3 | import unittest 4 | else: 5 | import unittest2 as unittest 6 | from datetime import datetime 7 | from datetime import date 8 | from twilio.rest.resources import parse_date 9 | from twilio.rest.resources import transform_params 10 | from twilio.rest.resources import convert_keys 11 | from twilio.rest.resources import convert_case 12 | from twilio.rest.resources import convert_boolean 13 | from twilio.rest.resources import normalize_dates 14 | 15 | 16 | class CoreTest(unittest.TestCase): 17 | 18 | def test_date(self): 19 | d = date(2009, 10, 10) 20 | self.assertEquals(parse_date(d), "2009-10-10") 21 | 22 | def test_datetime(self): 23 | d = datetime(2009, 10, 10) 24 | self.assertEquals(parse_date(d), "2009-10-10") 25 | 26 | def test_string_date(self): 27 | d = "2009-10-10" 28 | self.assertEquals(parse_date(d), "2009-10-10") 29 | 30 | def test_string_date_none(self): 31 | d = None 32 | self.assertEquals(parse_date(d), None) 33 | 34 | def test_string_date_false(self): 35 | d = False 36 | self.assertEquals(parse_date(d), None) 37 | 38 | def test_fparam(self): 39 | d = {"HEY": None, "YOU": 3} 40 | ed = {"YOU": 3} 41 | self.assertEquals(transform_params(d), ed) 42 | 43 | def test_fparam_booleans(self): 44 | d = {"HEY": None, "YOU": 3, "Activated": False} 45 | ed = {"YOU": 3, "Activated": "false"} 46 | self.assertEquals(transform_params(d), ed) 47 | 48 | def test_normalize_dates(self): 49 | 50 | @normalize_dates 51 | def foo(on=None, before=None, after=None): 52 | return { 53 | "on": on, 54 | "before": before, 55 | "after": after, 56 | } 57 | 58 | d = foo(on="2009-10-10", before=date(2009, 10, 10), 59 | after=datetime(2009, 10, 10)) 60 | 61 | self.assertEquals(d["on"], "2009-10-10") 62 | self.assertEquals(d["after"], "2009-10-10") 63 | self.assertEquals(d["before"], "2009-10-10") 64 | 65 | def test_convert_case(self): 66 | self.assertEquals(convert_case("from_"), "From") 67 | self.assertEquals(convert_case("to"), "To") 68 | self.assertEquals(convert_case("friendly_name"), "FriendlyName") 69 | 70 | def test_convert_bool(self): 71 | self.assertEquals(convert_boolean(False), "false") 72 | self.assertEquals(convert_boolean(True), "true") 73 | self.assertEquals(convert_boolean(1), 1) 74 | 75 | def test_convert_keys(self): 76 | d = { 77 | "from_": 0, 78 | "to": 0, 79 | "friendly_name": 0, 80 | "ended": 0, 81 | } 82 | 83 | ed = { 84 | "From": 0, 85 | "To": 0, 86 | "FriendlyName": 0, 87 | "EndTime": 0, 88 | } 89 | 90 | self.assertEquals(ed, convert_keys(d)) 91 | -------------------------------------------------------------------------------- /twilio/rest/resources/util.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from six import iteritems 3 | 4 | 5 | def transform_params(parameters): 6 | """ 7 | Transform parameters, throwing away any None values 8 | and convert False and True values to strings 9 | 10 | Ex: 11 | {"record": true, "date_created": "2012-01-02"} 12 | 13 | becomes: 14 | {"Record": "true", "DateCreated": "2012-01-02"} 15 | """ 16 | transformed_parameters = {} 17 | 18 | for key, value in iteritems(parameters): 19 | if value is not None: 20 | transformed_parameters[format_name(key)] = convert_boolean(value) 21 | 22 | return transformed_parameters 23 | 24 | 25 | def format_name(word): 26 | if word.lower() == word: 27 | return convert_case(word) 28 | else: 29 | return word 30 | 31 | 32 | def parse_date(d): 33 | """ 34 | Return a string representation of a date that the Twilio API understands 35 | Format is YYYY-MM-DD. Returns None if d is not a string, datetime, or date 36 | """ 37 | if isinstance(d, datetime.datetime): 38 | return str(d.date()) 39 | elif isinstance(d, datetime.date): 40 | return str(d) 41 | elif isinstance(d, str): 42 | return d 43 | 44 | 45 | def convert_boolean(boolean): 46 | if isinstance(boolean, bool): 47 | return 'true' if boolean else 'false' 48 | return boolean 49 | 50 | 51 | def convert_case(s): 52 | """ 53 | Given a string in snake case, convert to CamelCase 54 | 55 | Ex: 56 | date_created -> DateCreated 57 | """ 58 | return ''.join([a.title() for a in s.split("_") if a]) 59 | 60 | 61 | def convert_keys(d): 62 | """ 63 | Return a dictionary with all keys converted from arguments 64 | """ 65 | special = { 66 | "started_before": "StartTime<", 67 | "started_after": "StartTime>", 68 | "started": "StartTime", 69 | "ended_before": "EndTime<", 70 | "ended_after": "EndTime>", 71 | "ended": "EndTime", 72 | "from_": "From", 73 | } 74 | 75 | result = {} 76 | 77 | for k, v in iteritems(d): 78 | if k in special: 79 | result[special[k]] = v 80 | else: 81 | result[convert_case(k)] = v 82 | 83 | return result 84 | 85 | 86 | def normalize_dates(myfunc): 87 | def inner_func(*args, **kwargs): 88 | for k, v in iteritems(kwargs): 89 | res = [True for s in ["after", "before", "on"] if s in k] 90 | if len(res): 91 | kwargs[k] = parse_date(v) 92 | return myfunc(*args, **kwargs) 93 | inner_func.__doc__ = myfunc.__doc__ 94 | inner_func.__repr__ = myfunc.__repr__ 95 | return inner_func 96 | 97 | 98 | def change_dict_key(d, from_key, to_key): 99 | """ 100 | Changes a dictionary's key from from_key to to_key. 101 | No-op if the key does not exist. 102 | 103 | :param d: Dictionary with key to change 104 | :param from_key: Old key 105 | :param to_key: New key 106 | :return: None 107 | """ 108 | try: 109 | d[to_key] = d.pop(from_key) 110 | except KeyError: 111 | pass 112 | -------------------------------------------------------------------------------- /docs/usage/recordings.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ================ 4 | Recordings 5 | ================ 6 | 7 | For more information, see the 8 | `Recordings REST Resource <http://www.twilio.com/docs/api/rest/recording>`_ 9 | documentation. 10 | 11 | 12 | Audio Formats 13 | ----------------- 14 | 15 | Each :class:`Recording` has a :attr:`formats` dictionary which lists the audio 16 | formats available for each recording. 17 | Below is an example :attr:`formats` dictionary. 18 | 19 | .. code-block:: python 20 | 21 | { 22 | "mp3": "https://api.twilio.com/cowbell.mp3", 23 | "wav": "http://www.dailywav.com/0112/noFateButWhatWeMake.wav", 24 | } 25 | 26 | 27 | Listing Your Recordings 28 | ---------------------------- 29 | 30 | The following code will print out the :attr:`duration` 31 | for each :class:`Recording`. 32 | 33 | .. code-block:: python 34 | 35 | from twilio.rest import TwilioRestClient 36 | 37 | # To find these visit https://www.twilio.com/user/account 38 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 39 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 40 | 41 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 42 | for recording in client.recordings.list(): 43 | print recording.duration 44 | 45 | You can filter recordings by CallSid by passing the Sid as :attr:`call`. 46 | Filter recordings using :attr:`before` and :attr:`after` dates. 47 | 48 | The following will only show recordings made before January 1, 2011. 49 | 50 | .. code-block:: python 51 | 52 | from datetime import date 53 | from twilio.rest import TwilioRestClient 54 | 55 | # To find these visit https://www.twilio.com/user/account 56 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 57 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 58 | 59 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 60 | for recording in client.recordings.list(before=date(2011,1,1)): 61 | print recording.duration 62 | 63 | 64 | Deleting Recordings 65 | --------------------- 66 | 67 | The :class:`Recordings` resource allows you to delete unnecessary recordings. 68 | 69 | .. code-block:: python 70 | 71 | from twilio.rest import TwilioRestClient 72 | 73 | # To find these visit https://www.twilio.com/user/account 74 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 75 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 76 | 77 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 78 | client.recordings.delete("RC123") 79 | 80 | 81 | Accessing Related Transcptions 82 | ------------------------------- 83 | 84 | The :class:`Recordings` allows you to retrieve associated transcriptions. 85 | The following prints out the text for each of the transcriptions associated 86 | with this recording. 87 | 88 | .. code-block:: python 89 | 90 | from twilio.rest import TwilioRestClient 91 | 92 | # To find these visit https://www.twilio.com/user/account 93 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 94 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 95 | 96 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 97 | recording = client.recordings.get("RC123") 98 | 99 | for transcription in recording.transcriptions.list(): 100 | print transcription.transcription_text 101 | 102 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | twilio-python Changelog 2 | ======================= 3 | 4 | Here you can see the full list of changes between each twilio-python release. 5 | 6 | Version 3.4.5 7 | ------------- 8 | 9 | Released on April 1, 2013 10 | 11 | Allow the Account object to access usage records and usage trigger data, in 12 | addition to the client. Reporter: Trenton McManus 13 | 14 | Version 3.4.4 15 | ------------- 16 | 17 | Adds support for Sip 18 | 19 | Version 3.4.3 20 | ------------- 21 | 22 | Adds correct dependencies to the `setup.py` file. 23 | 24 | Version 3.4.2 25 | ------------- 26 | 27 | Released on January 2, 2013 28 | 29 | Adds a convenience function to retrieve the members of a queue by running 30 | client.members("QU123"). 31 | 32 | Version 3.4.1 33 | ------------- 34 | 35 | Python3 support! 36 | 37 | Version 3.3.11 38 | -------------- 39 | 40 | - Fix a bug where participants could not be kicked from a Conference 41 | 42 | 43 | Version 3.3.10 44 | -------------- 45 | 46 | - Add support for Queue. Fix a bug where the library wouldn't work in Python 2.5 47 | 48 | 49 | Version 3.3.9 50 | ------------- 51 | 52 | - Fix an error introduced in 3.3.7 that prevented validation calls from 53 | succeeding. 54 | 55 | Version 3.3.8 56 | ------------- 57 | 58 | - Use next_page_uri when iterating over a list resource 59 | 60 | 61 | Version 3.3.7 62 | ------------- 63 | 64 | - Allow arbitrary keyword arguments on resource creation and listing 65 | 66 | Version 3.3.6 67 | ------------- 68 | 69 | - Remove doc/tmp directory which was preventing installation on certain Windows machines 70 | - Add Travis CI integration 71 | - Update httplib2 dependency 72 | 73 | Version 3.3.5 74 | ------------- 75 | 76 | Released on January 18, 2011 77 | 78 | - Fix a bug with the deprecated `TwilioRestClient.request` method 79 | - Fix a bug in filtering SMS messages by DateSent 80 | 81 | Version 3.3.4 82 | ------------- 83 | 84 | Released on December 16, 2011 85 | 86 | - Allow both unicode strings and encoded byte strings in request data 87 | - Add support for SMS and Voice application sids to phone number updating 88 | - Fix documentation error relating to phone number purchasing 89 | - Include doc string information for decorated functions 90 | 91 | Version 3.3.3 92 | ------------- 93 | 94 | Released on November 3, 2011 95 | 96 | - Support unicode characters when validating requests 97 | - Add support for Great Britain language on the Say verb 98 | - Set Sms Application, Voice Application, and/or a Friendly 99 | Name when purchasing a number 100 | - Add missing parameters for resource creation and update 101 | 102 | Version 3.3.2 103 | ------------- 104 | 105 | Released on September 29, 2011 106 | 107 | - TwiML verbs can now be used as context managers 108 | 109 | Version 3.3.1 110 | ------------- 111 | 112 | Released on September 27, 2011 113 | 114 | - Allow phone numbers to be transferred between accounts and subaccounts 115 | 116 | Version 3.3.0 117 | ------------- 118 | 119 | Released on September 21, 2011 120 | 121 | - Add support for Twilio Connect. Connect applications and authorized Connect 122 | applications are now availble via the REST client. 123 | - Fix a problem where date and datetimes weren't coverted to strings when 124 | querying list resources 125 | -------------------------------------------------------------------------------- /docs/usage/applications.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ================= 4 | Applications 5 | ================= 6 | 7 | An application inside of Twilio is just a set of URLs and other configuration 8 | data that tells Twilio how to behave when one of your Twilio numbers receives 9 | a call or SMS message. 10 | 11 | For more information, see the `Application REST Resource 12 | <http://www.twilio.com/docs/api/rest/applications>`_ documentation. 13 | 14 | 15 | Listing Your Applications 16 | -------------------------- 17 | 18 | The following code will print out the :attr:`friendly_name` for each :class:`Application`. 19 | 20 | .. code-block:: python 21 | 22 | from twilio.rest import TwilioRestClient 23 | 24 | # To find these visit https://www.twilio.com/user/account 25 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 26 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 27 | 28 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 29 | for app in client.applications.list(): 30 | print app.friendly_name 31 | 32 | 33 | Filtering Applications 34 | --------------------------- 35 | 36 | You can filter applications by FriendlyName 37 | 38 | .. code-block:: python 39 | 40 | from twilio.rest import TwilioRestClient 41 | 42 | # To find these visit https://www.twilio.com/user/account 43 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 44 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 45 | 46 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 47 | for app in client.applications.list(friendly_name="FOO"): 48 | print app.sid 49 | 50 | 51 | Creating an Application 52 | ------------------------- 53 | 54 | When creating an application, no fields are required. We create an application 55 | with only a :attr:`friendly_name`. The :meth:`Applications.create()` method 56 | accepts many other arguments for url configuration. 57 | 58 | .. code-block:: python 59 | 60 | from twilio.rest import TwilioRestClient 61 | 62 | # To find these visit https://www.twilio.com/user/account 63 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 64 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 65 | 66 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 67 | application = client.applications.create(friendly_name="My New App") 68 | 69 | 70 | Updating an Application 71 | ------------------------ 72 | 73 | .. code-block:: python 74 | 75 | from twilio.rest import TwilioRestClient 76 | 77 | # To find these visit https://www.twilio.com/user/account 78 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 79 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 80 | 81 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 82 | url = "http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient" 83 | app_sid = 'AP123' # the app you'd like to update 84 | application = client.applications.update(app_sid, voice_url=url) 85 | 86 | 87 | Deleting an Application 88 | ------------------------- 89 | 90 | .. code-block:: python 91 | 92 | from twilio.rest import TwilioRestClient 93 | 94 | # To find these visit https://www.twilio.com/user/account 95 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 96 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 97 | 98 | app_sid = 'AP123' # the app you'd like to delete 99 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 100 | client.applications.delete(app_sid) 101 | 102 | -------------------------------------------------------------------------------- /tests/test_available_phonenumber.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import six 4 | if six.PY3: 5 | import unittest 6 | else: 7 | import unittest2 as unittest 8 | 9 | from mock import Mock 10 | from twilio import TwilioException 11 | from twilio.rest.resources import AvailablePhoneNumber 12 | from twilio.rest.resources import AvailablePhoneNumbers 13 | from twilio.rest.resources import PhoneNumbers 14 | 15 | 16 | class AvailablePhoneNumberTest(unittest.TestCase): 17 | 18 | def setUp(self): 19 | self.parent = Mock() 20 | self.instance = AvailablePhoneNumber(self.parent) 21 | 22 | def test_init(self): 23 | self.assertEquals(self.instance.name, "") 24 | 25 | def test_purchase(self): 26 | self.instance.phone_number = "+123" 27 | self.instance.purchase(voice_url="http://www.google.com") 28 | 29 | self.parent.purchase.assert_called_with( 30 | voice_url="http://www.google.com", 31 | phone_number="+123") 32 | 33 | 34 | class AvailabePhoneNumbersTest(unittest.TestCase): 35 | 36 | def setUp(self): 37 | self.resource = AvailablePhoneNumbers("http://api.twilio.com", 38 | ("user", "pass"), Mock()) 39 | 40 | def test_get(self): 41 | with self.assertRaises(TwilioException): 42 | self.resource.get("PN123") 43 | 44 | def test_list(self): 45 | request = Mock() 46 | request.return_value = (Mock(), {"available_phone_numbers": []}) 47 | self.resource.request = request 48 | 49 | self.resource.list() 50 | 51 | uri = "http://api.twilio.com/AvailablePhoneNumbers/US/Local" 52 | request.assert_called_with("GET", uri, params={}) 53 | 54 | def test_load_instance(self): 55 | instance = self.resource.load_instance({"hey": "you"}) 56 | self.assertIsInstance(instance.parent, Mock) 57 | self.assertEquals(instance.hey, "you") 58 | 59 | def test_purchase_status_callback(self): 60 | request = Mock() 61 | request.return_value = (Mock(), {"available_phone_numbers": []}) 62 | self.resource.request = request 63 | 64 | self.resource.list() 65 | 66 | uri = "http://api.twilio.com/AvailablePhoneNumbers/US/Local" 67 | request.assert_called_with("GET", uri, params={}) 68 | 69 | 70 | class PhoneNumbersTest(unittest.TestCase): 71 | 72 | def setUp(self): 73 | self.resource = PhoneNumbers("http://api.twilio.com", 74 | ("user", "pass")) 75 | 76 | def test_reference(self): 77 | self.assertEquals(self.resource.available_phone_numbers.phone_numbers, 78 | self.resource) 79 | 80 | def test_purchase_status_callback(self): 81 | request = Mock() 82 | response = Mock() 83 | response.status_code = 201 84 | request.return_value = (response, {"sid": ""}) 85 | self.resource.request = request 86 | 87 | self.resource.purchase(area_code="530", status_callback_url="http://", 88 | status_callback_method="POST") 89 | 90 | uri = "http://api.twilio.com/IncomingPhoneNumbers" 91 | 92 | data = { 93 | "AreaCode": "530", 94 | "StatusCallback": "http://", 95 | "StatusCallbackMethod": "POST", 96 | } 97 | 98 | request.assert_called_with("POST", uri, data=data) 99 | -------------------------------------------------------------------------------- /twilio/rest/resources/queues.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources import InstanceResource, ListResource 2 | 3 | 4 | class Member(InstanceResource): 5 | id_key = "call_sid" 6 | 7 | 8 | class Members(ListResource): 9 | name = "Members" 10 | instance = Member 11 | key = "queue_members" 12 | 13 | def list(self, **kwargs): 14 | """ 15 | Returns a list of :class:`Member` resources in the given queue 16 | 17 | :param queue_sid: Queue this participant is part of 18 | """ 19 | return self.get_instances(kwargs) 20 | 21 | def dequeue(self, url, call_sid='Front', **kwargs): 22 | """ 23 | Dequeues a member from the queue and have the member's call 24 | begin executing the TwiML document at the url. 25 | 26 | :param call_sid: Call sid specifying the member, if not given, 27 | the member at the front of the queue will be used 28 | :param url: url of the TwiML document to be executed. 29 | """ 30 | kwargs['url'] = url 31 | return self.update_instance(call_sid, kwargs) 32 | 33 | 34 | class Queue(InstanceResource): 35 | 36 | subresources = [ 37 | Members 38 | ] 39 | 40 | def update(self, **kwargs): 41 | """ 42 | Update this queue 43 | 44 | :param friendly_name: A new friendly name for this queue 45 | :param max_size: A new max size. Changing a max size to less than the 46 | current size results in the queue rejecting incoming 47 | requests until it shrinks below the new max size 48 | """ 49 | return self.parent.update_instance(self.name, kwargs) 50 | 51 | def delete(self): 52 | """ 53 | Delete this queue. Can only be run on empty queues. 54 | """ 55 | return self.parent.delete_instance(self.name) 56 | 57 | 58 | class Queues(ListResource): 59 | name = "Queues" 60 | instance = Queue 61 | 62 | def list(self, **kwargs): 63 | """ 64 | Returns a page of :class:`Queue` resources as a list sorted 65 | by DateUpdated. For paging informtion see :class:`ListResource` 66 | """ 67 | return self.get_instances(kwargs) 68 | 69 | def create(self, name, **kwargs): 70 | """ Create an :class:`Queue` with any of these optional parameters. 71 | 72 | :param name: A human readable description of the application, 73 | with maximum length 64 characters. 74 | :param max_size: The limit on calls allowed into the queue (optional) 75 | """ 76 | kwargs['friendly_name'] = name 77 | return self.create_instance(kwargs) 78 | 79 | def update(self, sid, **kwargs): 80 | """ 81 | Update a :class:`Queue` 82 | 83 | :param sid: String identifier for a Queue resource 84 | :param friendly_name: A new friendly name for this queue 85 | :param max_size: A new max size. Changing a max size to less than the 86 | current size results in the queue rejecting incoming 87 | requests until it shrinks below the new max size 88 | """ 89 | return self.update_instance(sid, kwargs) 90 | 91 | def delete(self, sid): 92 | """ 93 | Delete a :class:`Queue`. Can only be run on empty queues. 94 | 95 | :param sid: String identifier for a Queue resource 96 | """ 97 | return self.delete_instance(sid) 98 | -------------------------------------------------------------------------------- /twilio/rest/resources/conferences.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources.util import parse_date, normalize_dates 2 | from twilio.rest.resources import InstanceResource, ListResource 3 | 4 | 5 | class Participant(InstanceResource): 6 | 7 | id_key = "call_sid" 8 | 9 | def mute(self): 10 | """ 11 | Mute the participant 12 | """ 13 | self.update_instance(muted="true") 14 | 15 | def unmute(self): 16 | """ 17 | Unmute the participant 18 | """ 19 | self.update_instance(muted="false") 20 | 21 | def kick(self): 22 | """ 23 | Remove the participant from the given conference 24 | """ 25 | self.delete_instance() 26 | 27 | 28 | class Participants(ListResource): 29 | 30 | name = "Participants" 31 | instance = Participant 32 | 33 | def list(self, **kwargs): 34 | """ 35 | Returns a list of :class:`Participant` resources in the given 36 | conference 37 | 38 | :param conference_sid: Conference this participant is part of 39 | :param boolean muted: If True, only show participants who are muted 40 | """ 41 | return self.get_instances(kwargs) 42 | 43 | def mute(self, call_sid): 44 | """ 45 | Mute the given participant 46 | """ 47 | return self.update(call_sid, muted=True) 48 | 49 | def unmute(self, call_sid): 50 | """ 51 | Unmute the given participant 52 | """ 53 | return self.update(call_sid, muted=False) 54 | 55 | def kick(self, call_sid): 56 | """ 57 | Remove the participant from the given conference 58 | """ 59 | return self.delete(call_sid) 60 | 61 | def delete(self, call_sid): 62 | """ 63 | Remove the participant from the given conference 64 | """ 65 | return self.delete_instance(call_sid) 66 | 67 | def update(self, sid, **kwargs): 68 | """ 69 | :param sid: Participant identifier 70 | :param boolean muted: If true, mute this participant 71 | """ 72 | return self.update_instance(sid, kwargs) 73 | 74 | 75 | class Conference(InstanceResource): 76 | 77 | subresources = [ 78 | Participants 79 | ] 80 | 81 | 82 | class Conferences(ListResource): 83 | 84 | name = "Conferences" 85 | instance = Conference 86 | 87 | @normalize_dates 88 | def list(self, updated_before=None, updated_after=None, created_after=None, 89 | created_before=None, updated=None, created=None, **kwargs): 90 | """ 91 | Return a list of :class:`Conference` resources 92 | 93 | :param status: Show conferences with this status 94 | :param friendly_name: Show conferences with this exact friendly_name 95 | :param date updated_after: List conferences updated after this date 96 | :param date updated_before: List conferences updated before this date 97 | :param date created_after: List conferences created after this date 98 | :param date created_before: List conferences created before this date 99 | """ 100 | kwargs["DateUpdated"] = parse_date(kwargs.get("date_updated", updated)) 101 | kwargs["DateCreated"] = parse_date(kwargs.get("date_created", created)) 102 | kwargs["DateUpdated<"] = updated_before 103 | kwargs["DateUpdated>"] = updated_after 104 | kwargs["DateCreated<"] = created_before 105 | kwargs["DateCreated>"] = created_after 106 | return self.get_instances(kwargs) 107 | -------------------------------------------------------------------------------- /docs/usage/conferences.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ============================== 4 | Conferences and Participants 5 | ============================== 6 | 7 | For more information, see the `Conference REST Resource <http://www.twilio.com/docs/api/rest/conference>`_ and `Participant REST Resource <http://www.twilio.com/docs/api/rest/participant>`_ documentation. 8 | 9 | 10 | Listing Conferences 11 | ----------------------- 12 | 13 | .. code-block:: python 14 | 15 | from twilio.rest import TwilioRestClient 16 | 17 | # To find these visit https://www.twilio.com/user/account 18 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 19 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 20 | 21 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 22 | conferences = client.conferences.list() 23 | 24 | for conference in conferences: 25 | print conference.sid 26 | 27 | 28 | Filtering Conferences 29 | ----------------------- 30 | 31 | The :meth:`Conferences.list` method supports filtering on :attr:`status`, :attr:`date_updated`, :attr:`date_created` and :attr:`friendly_name`. The following code will return a list of all in-progress conferences and print their friendly name. 32 | 33 | .. code-block:: python 34 | 35 | from twilio.rest import TwilioRestClient 36 | 37 | # To find these visit https://www.twilio.com/user/account 38 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 39 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 40 | 41 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 42 | conferences = client.conferences.list(status="in-progress") 43 | 44 | for conference in conferences: 45 | print conference.friendly_name 46 | 47 | 48 | Listing Participants 49 | ---------------------- 50 | 51 | Each :class:`Conference` has a :attr:`participants` instance which represents all current users in the conference 52 | 53 | .. code-block:: python 54 | 55 | from twilio.rest import TwilioRestClient 56 | 57 | # To find these visit https://www.twilio.com/user/account 58 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 59 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 60 | 61 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 62 | conference = client.conferences.get("CF123") 63 | 64 | for participant in conference.participants.list(): 65 | print participant.sid 66 | 67 | :class:`Conferences` and :class:`Participants` are subclasses of :class:`ListResource`. 68 | Therefore, their instances have the inherited methods such as :meth:`count`. 69 | 70 | 71 | Managing Participants 72 | ---------------------- 73 | 74 | Each :class:`Conference` has a :attr:`participants` function that returns a 75 | :class:`Participants` instance. This behavior differs from other list resources 76 | because :class:`Participants` needs a participant sid AND a conference sid to 77 | access the participants resource. 78 | 79 | Participants can be either muted or kicked out of the conference. The following 80 | code kicks out the first participant and mutes the rest. 81 | 82 | .. code-block:: python 83 | 84 | from twilio.rest import TwilioRestClient 85 | 86 | # To find these visit https://www.twilio.com/user/account 87 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 88 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 89 | 90 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 91 | participants = client.participants("CF123").list() 92 | 93 | if len(participants) == 0: 94 | return 95 | 96 | # Kick the first person out 97 | participants.pop().kick() 98 | 99 | # And mute the rest 100 | for participant in participants: 101 | participant.mute() 102 | 103 | -------------------------------------------------------------------------------- /tests/test_phone_numbers.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | try: 3 | import json 4 | except ImportError: 5 | import simplejson as json 6 | import six 7 | if six.PY3: 8 | import unittest 9 | else: 10 | import unittest2 as unittest 11 | from mock import Mock 12 | from twilio.rest.resources import PhoneNumbers 13 | from twilio.rest.resources import PhoneNumber 14 | 15 | 16 | class PhoneNumberTest(unittest.TestCase): 17 | 18 | def setUp(self): 19 | self.parent = Mock() 20 | self.uri = "/base" 21 | self.auth = ("AC123", "token") 22 | 23 | def test_update_rename_status_callback_url(self): 24 | mock = Mock() 25 | mock.uri = "/base" 26 | instance = PhoneNumber(mock, "SID") 27 | instance.update(status_callback_url="http://www.example.com") 28 | mock.update.assert_called_with("SID", status_callback="http://www.example.com") 29 | 30 | def test_update_instance_rename_status_callback_url(self): 31 | resource = PhoneNumbers(self.uri, self.auth) 32 | resource.update_instance = Mock() 33 | resource.update("SID", status_callback_url="http://www.example.com") 34 | resource.update_instance.assert_called_with("SID", {"status_callback": "http://www.example.com"}) 35 | 36 | def test_application_sid(self): 37 | resource = PhoneNumbers(self.uri, self.auth) 38 | resource.update_instance = Mock() 39 | resource.update("SID", application_sid="foo") 40 | resource.update_instance.assert_called_with( 41 | "SID", {"voice_application_sid": "foo", "sms_application_sid": "foo"}) 42 | 43 | def test_voice_application_sid(self): 44 | resource = PhoneNumbers(self.uri, self.auth) 45 | resource.update_instance = Mock() 46 | resource.update("SID", voice_application_sid="foo") 47 | resource.update_instance.assert_called_with( 48 | "SID", {"voice_application_sid": "foo"}) 49 | 50 | def test_sms_application_sid(self): 51 | resource = PhoneNumbers(self.uri, self.auth) 52 | resource.update_instance = Mock() 53 | resource.update("SID", sms_application_sid="foo") 54 | resource.update_instance.assert_called_with( 55 | "SID", {"sms_application_sid": "foo"}) 56 | 57 | def test_status_callback_url(self): 58 | resource = PhoneNumbers(self.uri, self.auth) 59 | resource.update_instance = Mock() 60 | resource.update("SID", status_callback="foo") 61 | resource.update_instance.assert_called_with( 62 | "SID", {"status_callback": "foo"}) 63 | 64 | def test_transfer(self): 65 | resource = PhoneNumbers(self.uri, self.auth) 66 | resource.update = Mock() 67 | resource.transfer("SID", "AC123") 68 | resource.update.assert_called_with("SID", account_sid="AC123") 69 | 70 | def test_instance_transfer(self): 71 | mock = Mock() 72 | mock.uri = "/base" 73 | instance = PhoneNumber(mock, "SID") 74 | instance.transfer("AC123") 75 | mock.transfer.assert_called_with("SID", "AC123") 76 | 77 | def test_base_uri(self): 78 | parent = Mock() 79 | parent.base_uri = ("https://api.twilio.com/2010-04-01/Accounts/" 80 | "AC123") 81 | resource = PhoneNumber(parent, "PNd2ae06cced59a5733d2c1c1c69a83a28") 82 | 83 | with open("tests/resources/incoming_phone_numbers_instance.json") as f: 84 | entry = json.load(f) 85 | resource.load(entry) 86 | 87 | self.assertEquals(resource.parent.base_uri, 88 | ("https://api.twilio.com/2010-04-01/Accounts/AC4bf2dafbed59a573" 89 | "3d2c1c1c69a83a28")) 90 | -------------------------------------------------------------------------------- /docs/usage/queues.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest.resources 2 | 3 | ============================== 4 | Queues and Members 5 | ============================== 6 | 7 | For more information, see the 8 | `Queue REST Resource <http://www.twilio.com/docs/api/rest/queue>`_ 9 | and `Member REST Resource <http://www.twilio.com/docs/api/rest/member>`_ 10 | documentation. 11 | 12 | 13 | Listing Queues 14 | ----------------------- 15 | 16 | .. code-block:: python 17 | 18 | from twilio.rest import TwilioRestClient 19 | 20 | # To find these visit https://www.twilio.com/user/account 21 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 22 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 23 | 24 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 25 | queues = client.queues.list() 26 | 27 | for queue in queues: 28 | print queue.sid 29 | 30 | 31 | Listing Queue Members 32 | ---------------------- 33 | 34 | Each :class:`Queue` has a :attr:`queue_members` instance which 35 | represents all current calls in the queue. 36 | 37 | .. code-block:: python 38 | 39 | from twilio.rest import TwilioRestClient 40 | 41 | # To find these visit https://www.twilio.com/user/account 42 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 43 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 44 | 45 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 46 | queue = client.queues.get("QU123") 47 | 48 | for member in queue.queue_members.list(): 49 | print member.call_sid 50 | 51 | 52 | Getting a specific Queue Member 53 | ------------------------------- 54 | 55 | To retrieve information about a specific member in the queue, each 56 | :class:`Members` has a :attr:`get` method. :attr:`get` accepts one 57 | argument. The argument can either be a `call_sid` thats in the queue, 58 | in which case :attr:`get` will return a :class:`Member` instance 59 | representing that call, or the argument can be 'Front', in which case 60 | :attr:`Get` will return a :class:`Member` instance representing the 61 | first call in the queue. 62 | 63 | .. code-block:: python 64 | 65 | from twilio.rest import TwilioRestClient 66 | 67 | # To find these visit https://www.twilio.com/user/account 68 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 69 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 70 | QUEUE_SID = "QUaaaaaaaaaaaaa" 71 | CALL_SID = "CAxxxxxxxxxxxxxx" 72 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 73 | members = client.queues.get(QUEUE_SID).queue_members 74 | 75 | # Get the first call in the queue 76 | print members.get('Front').date_enqueued 77 | 78 | # Get the call with the given call sid in the queue 79 | print members.get(CALL_SID).current_position 80 | 81 | 82 | Dequeueing Queue Members 83 | ------------------------ 84 | 85 | To dequeue a specific member from the queue, each 86 | :class:`Members` has a :attr:`dequeue` method. :attr:`dequeue` accepts an 87 | argument and two optional keyword arguments. The first argument is the 88 | url of the twiml document to be executed when the member is 89 | dequeued. The other two are :attr:`call_sid` and :attr:`method`, their 90 | default values are 'Front' and 'GET' 91 | 92 | .. code-block:: python 93 | 94 | from twilio.rest import TwilioRestClient 95 | 96 | # To find these visit https://www.twilio.com/user/account 97 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 98 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 99 | QUEUE_SID = "QUaaaaaaaaaaaaa" 100 | 101 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 102 | members = client.queues.get(QUEUE_SID).queue_members 103 | 104 | # Dequeue the first call in the queue 105 | print members.dequeue('http://www.twilio.com/welcome/call') 106 | 107 | -------------------------------------------------------------------------------- /tests/test_base_resource.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import with_statement 3 | import six 4 | if six.PY3: 5 | import unittest 6 | else: 7 | import unittest2 as unittest 8 | 9 | from mock import Mock 10 | from nose.tools import assert_equals 11 | from twilio.rest.resources import Resource 12 | from twilio.rest.resources import ListResource 13 | from twilio.rest.resources import InstanceResource 14 | from six import advance_iterator 15 | 16 | base_uri = "https://api.twilio.com/2010-04-01" 17 | account_sid = "AC123" 18 | auth = (account_sid, "token") 19 | 20 | 21 | def test_resource_init(): 22 | r = Resource(base_uri, auth) 23 | uri = "%s/%s" % (base_uri, r.name) 24 | 25 | assert_equals(r.base_uri, base_uri) 26 | assert_equals(r.auth, auth) 27 | assert_equals(r.uri, uri) 28 | 29 | 30 | def test_equivalence(): 31 | p = ListResource(base_uri, auth) 32 | r1 = p.load_instance({"sid": "AC123"}) 33 | r2 = p.load_instance({"sid": "AC123"}) 34 | assert_equals(r1, r2) 35 | 36 | 37 | class ListResourceTest(unittest.TestCase): 38 | 39 | def setUp(self): 40 | self.r = ListResource(base_uri, auth) 41 | 42 | def testListResourceInit(self): 43 | uri = "%s/%s" % (base_uri, self.r.name) 44 | self.assertEquals(self.r.uri, uri) 45 | 46 | def testKeyValueLower(self): 47 | self.assertEquals(self.r.key, self.r.name.lower()) 48 | 49 | def testIterNoKey(self): 50 | self.r.request = Mock() 51 | self.r.request.return_value = Mock(), {} 52 | 53 | with self.assertRaises(StopIteration): 54 | advance_iterator(self.r.iter()) 55 | 56 | def testRequest(self): 57 | self.r.request = Mock() 58 | self.r.request.return_value = Mock(), {self.r.key: [{'sid': 'foo'}]} 59 | advance_iterator(self.r.iter()) 60 | self.r.request.assert_called_with("GET", "https://api.twilio.com/2010-04-01/Resources", params={}) 61 | 62 | def testIterOneItem(self): 63 | self.r.request = Mock() 64 | self.r.request.return_value = Mock(), {self.r.key: [{'sid': 'foo'}]} 65 | 66 | items = self.r.iter() 67 | advance_iterator(items) 68 | 69 | with self.assertRaises(StopIteration): 70 | advance_iterator(items) 71 | 72 | def testIterNoNextPage(self): 73 | self.r.request = Mock() 74 | self.r.request.return_value = Mock(), {self.r.key: []} 75 | 76 | with self.assertRaises(StopIteration): 77 | advance_iterator(self.r.iter()) 78 | 79 | def testKeyValue(self): 80 | self.r.key = "Hey" 81 | self.assertEquals(self.r.key, "Hey") 82 | 83 | def testInstanceLoading(self): 84 | instance = self.r.load_instance({"sid": "foo"}) 85 | 86 | self.assertIsInstance(instance, InstanceResource) 87 | self.assertEquals(instance.sid, "foo") 88 | 89 | 90 | class testInstanceResourceInit(unittest.TestCase): 91 | 92 | def setUp(self): 93 | self.parent = ListResource(base_uri, auth) 94 | self.r = InstanceResource(self.parent, "123") 95 | self.uri = "%s/%s" % (self.parent.uri, "123") 96 | 97 | def testInit(self): 98 | self.assertEquals(self.r.uri, self.uri) 99 | 100 | def testLoad(self): 101 | self.r.load({"hey": "you"}) 102 | self.assertEquals(self.r.hey, "you") 103 | 104 | def testLoadWithUri(self): 105 | self.r.load({"hey": "you", "uri": "foobar"}) 106 | self.assertEquals(self.r.hey, "you") 107 | self.assertEquals(self.r.uri, self.uri) 108 | 109 | def testLoadWithFrom(self): 110 | self.r.load({"from": "foo"}) 111 | self.assertEquals(self.r.from_, "foo") 112 | 113 | def testLoadSubresources(self): 114 | m = Mock() 115 | self.r.subresources = [m] 116 | self.r.load_subresources() 117 | m.assert_called_with(self.r.uri, self.r.auth) 118 | -------------------------------------------------------------------------------- /docs/usage/basics.rst: -------------------------------------------------------------------------------- 1 | .. module:: twilio.rest 2 | 3 | ========================= 4 | Accessing REST Resources 5 | ========================= 6 | 7 | To access Twilio REST resources, you'll first need to instantiate a 8 | :class:`TwilioRestClient`. 9 | 10 | Authentication 11 | -------------------------- 12 | 13 | The :class:`TwilioRestClient` needs your Twilio credentials. While these can be 14 | passed in directly to the constructor, we suggest storing your credentials as 15 | environment variables. Why? You'll never have to worry about committing your 16 | credentials and accidentally posting them somewhere public. 17 | 18 | The :class:`TwilioRestClient` looks for :const:`TWILIO_ACCOUNT_SID` and 19 | :const:`TWILIO_AUTH_TOKEN` inside the current environment. 20 | 21 | With those two values set, create a new :class:`TwilioClient`. 22 | 23 | .. code-block:: python 24 | 25 | from twilio.rest import TwilioRestClient 26 | 27 | conn = TwilioRestClient() 28 | 29 | If you'd rather not use environment variables, pass your account credentials 30 | directly to the the constructor. 31 | 32 | .. code-block:: python 33 | 34 | from twilio.rest import TwilioRestClient 35 | 36 | ACCOUNT_SID = "AXXXXXXXXXXXXXXXXX" 37 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 38 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 39 | 40 | 41 | Listing Resources 42 | ------------------- 43 | 44 | The :class:`TwilioRestClient` gives you access to various list resources. 45 | :meth:`ListResource.list`, by default, returns the most recent 50 instance 46 | resources. 47 | 48 | .. code-block:: python 49 | 50 | from twilio.rest import TwilioRestClient 51 | 52 | # To find these visit https://www.twilio.com/user/account 53 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 54 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 55 | 56 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 57 | resources = client.calls.list() 58 | 59 | :meth:`resource.ListResource.list` accepts paging arguments. 60 | The following will return page 3 with page size of 25. 61 | 62 | .. code-block:: python 63 | 64 | from twilio.rest import TwilioRestClient 65 | 66 | # To find these visit https://www.twilio.com/user/account 67 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 68 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 69 | 70 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 71 | resources = client.calls.list(page=3, page_size=25) 72 | 73 | 74 | Listing All Resources 75 | ^^^^^^^^^^^^^^^^^^^^^^^ 76 | 77 | Sometimes you'd like to retrieve all records from a list resource. 78 | Instead of manually paging over the resource, 79 | the :class:`resources.ListResource.iter` method returns a generator. 80 | After exhausting the current page, 81 | the generator will request the next page of results. 82 | 83 | .. warning:: Accessing all your records can be slow. We suggest only doing so when you absolutely need all the records. 84 | 85 | .. code-block:: python 86 | 87 | from twilio.rest import TwilioRestClient 88 | 89 | # To find these visit https://www.twilio.com/user/account 90 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 91 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 92 | 93 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 94 | for number in client.phone_numbers.iter(): 95 | print number.friendly_name 96 | 97 | 98 | Get an Individual Resource 99 | ----------------------------- 100 | 101 | To get an individual instance resource, use 102 | :meth:`resources.ListResource.get`. 103 | Provide the :attr:`sid` of the resource you'd like to get. 104 | 105 | .. code-block:: python 106 | 107 | from twilio.rest import TwilioRestClient 108 | 109 | # To find these visit https://www.twilio.com/user/account 110 | ACCOUNT_SID = "ACXXXXXXXXXXXXXXXXX" 111 | AUTH_TOKEN = "YYYYYYYYYYYYYYYYYY" 112 | 113 | client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) 114 | 115 | call = client.calls.get("CA123") 116 | print call.to 117 | 118 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Twilio Python Helper Library 3 | ============================= 4 | 5 | .. _installation: 6 | 7 | Installation 8 | ================ 9 | 10 | Install from PyPi using `pip <http://www.pip-installer.org/en/latest/>`_, a 11 | package manager for Python. 12 | 13 | .. code-block:: bash 14 | 15 | $ pip install twilio 16 | 17 | Don't have pip installed? Try installing it, by running this from the command 18 | line: 19 | 20 | .. code-block:: bash 21 | 22 | $ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python 23 | 24 | Or, install the library by downloading 25 | `the source <https://github.com/twilio/twilio-python/zipball/master>`_, 26 | installing :data:`setuptools`, 27 | navigating in the Terminal to the folder containing the **twilio-python** 28 | library, and then running: 29 | 30 | .. code-block:: bash 31 | 32 | $ python setup.py install 33 | 34 | 35 | Getting Started 36 | ================ 37 | 38 | The :doc:`/getting-started` will get you up and running in a few quick minutes. 39 | This guide assumes you understand the core concepts of Twilio. 40 | If you've never used Twilio before, don't fret! Just read 41 | `about how Twilio works <http://www.twilio.com/api/>`_ and then jump in! 42 | 43 | 44 | .. _user-guide: 45 | 46 | User Guide 47 | ================== 48 | 49 | Functionality is split over three different sub-packages within 50 | **twilio-python**. Below are in-depth guides to specific portions of the 51 | library. 52 | 53 | 54 | REST API 55 | ---------- 56 | 57 | Query the Twilio REST API to create phone calls, send SMS messages and more! 58 | 59 | .. toctree:: 60 | :maxdepth: 1 61 | 62 | usage/basics 63 | usage/phone-calls 64 | usage/phone-numbers 65 | usage/messages 66 | usage/accounts 67 | usage/conferences 68 | usage/applications 69 | usage/notifications 70 | usage/recordings 71 | usage/transcriptions 72 | usage/caller-ids 73 | usage/queues 74 | 75 | 76 | TwiML 77 | --------- 78 | 79 | Generates valid TwiML for controlling and manipulating phone calls. 80 | 81 | .. toctree:: 82 | :maxdepth: 2 83 | 84 | usage/twiml 85 | 86 | 87 | Utilities 88 | ---------- 89 | 90 | Small functions useful for validating requests are coming from Twilio 91 | 92 | .. toctree:: 93 | :maxdepth: 1 94 | 95 | usage/validation 96 | usage/token-generation 97 | 98 | 99 | Upgrade Plan 100 | ================== 101 | 102 | `twilio-python` 3.0 introduced backwards-incompatible changes to the API. See 103 | the :doc:`/upgrade-guide` for step-by-step instructions for migrating to 3.0. 104 | In many cases, the same methods are still offered, just in different locations. 105 | 106 | 107 | API Reference 108 | ================== 109 | 110 | A complete guide to all public APIs found in `twilio-python`. Auto-generated, 111 | so only use when you really need to dive deep into the library. 112 | 113 | .. toctree:: 114 | :maxdepth: 2 115 | 116 | api 117 | 118 | 119 | Deploying to Google App Engine 120 | ============================== 121 | 122 | Want to run your app on Google App Engine? We've got a full guide to getting 123 | started here: 124 | 125 | .. toctree:: 126 | :maxdepth: 1 127 | 128 | appengine 129 | 130 | Frequently Asked Questions 131 | ========================== 132 | 133 | What to do if you get an ``ImportError``, and some advice about how to format 134 | phone numbers. 135 | 136 | .. toctree:: 137 | :maxdepth: 2 138 | 139 | faq 140 | 141 | 142 | Support and Development 143 | ========================== 144 | All development occurs over on 145 | `Github <https://github.com/twilio/twilio-python>`_. To checkout the source, 146 | 147 | .. code-block:: bash 148 | 149 | $ git clone git@github.com:twilio/twilio-python.git 150 | 151 | 152 | Report bugs using the Github 153 | `issue tracker <https://github.com/twilio/twilio-python/issues>`_. 154 | 155 | If you have questions that aren't answered by this documentation, 156 | ask the `#twilio IRC channel <irc://irc.freenode.net/#twilio>`_ 157 | 158 | See the :doc:`/changelog` for version history. 159 | 160 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Frequently Asked Questions 3 | ========================== 4 | 5 | Hopefully you can find an answer here to one of your questions. If not, please 6 | contact `help@twilio.com <mailto:help@twilio.com>`_. 7 | 8 | 9 | ImportError messages 10 | -------------------- 11 | 12 | If you get an error that looks like this: 13 | 14 | .. code-block:: python 15 | 16 | Traceback (most recent call last): 17 | File "twilio.py", line 1, in <module> 18 | from twilio.rest import TwilioRestClient 19 | File "/.../twilio-python/docs/twilio.py", line 1, in <module> 20 | from twilio.rest import TwilioRestClient 21 | ImportError: No module named rest 22 | 23 | Check to make sure that you don't have a file named ``twilio.py``; 24 | Python will try to load the Twilio library from your ``twilio.py`` file 25 | instead of from the Twilio library. 26 | 27 | If you get an error that looks like this: 28 | 29 | .. code-block:: python 30 | 31 | Traceback (most recent call last): 32 | File "test.py", line 1, in <module> 33 | import twilio.rest 34 | ImportError: No module named twilio.rest 35 | 36 | Your Python installation cannot find the library. 37 | 38 | Check which versions of ``pip`` and Python you are running with this command in 39 | the Terminal: 40 | 41 | .. code-block:: bash 42 | 43 | which -a python 44 | which -a pip 45 | 46 | ``pip`` needs to install the Twilio library to a path that your Python 47 | executable can read from. Sometimes there will be more than one version of pip, 48 | like pip-2.5, pip-2.7 etc. You can find all of them by running ``compgen -c | 49 | grep pip`` (works with Bash on \*nix machines). There can also be more than one 50 | version of Python, especially if you have Macports or homebrew. 51 | 52 | You also may be using an outdated version of the twilio-python library, which 53 | did not use a ``twilio.rest.TwilioRestClient`` object. Check which version of 54 | the twilio library you have installed by running this command: 55 | 56 | .. code-block:: bash 57 | 58 | $ pip freeze | grep twilio # Or pip-2.7 freeze etc. 59 | 60 | The latest version (as of January 2012) is 3.3. If you are running an outdated 61 | version, you can upgrade with this command: 62 | 63 | .. code-block:: bash 64 | 65 | $ pip install --upgrade twilio 66 | 67 | Note that if you have code that uses the older version of the library, it may 68 | break when you upgrade your site. 69 | 70 | 71 | Formatting phone numbers 72 | ------------------------ 73 | 74 | Twilio always returns phone numbers that are formatted in the `E.164 format 75 | <http://en.wikipedia.org/wiki/E.164>`_, like this: ``+12125551234``. However 76 | your users may enter them like this: ``(212) 555-1234``. This can lead to 77 | problems when, for example, Twilio makes a POST request to your server with the 78 | ``From`` phone number as ``+12125551234``, but you stored the phone number in 79 | your database as ``(212) 555-1234``, causing a database lookup to fail. 80 | 81 | We suggest that you convert the number to E.164 format 82 | before you store it in the database. The `phonenumbers 83 | <https://github.com/daviddrysdale/python-phonenumbers>`_ library is excellent 84 | for this purpose. Install it like this: 85 | 86 | .. code-block:: bash 87 | 88 | $ pip install phonenumbers 89 | 90 | Then you can convert user input to phone numbers like this: 91 | 92 | .. code-block:: python 93 | 94 | import phonenumbers 95 | 96 | def convert_to_e164(raw_phone): 97 | if not raw_phone: 98 | return 99 | 100 | if raw_phone[0] == '+': 101 | # Phone number may already be in E.164 format. 102 | parse_type = None 103 | else: 104 | # If no country code information present, assume it's a US number 105 | parse_type = "US" 106 | 107 | phone_representation = phonenumbers.parse(raw_phone, parse_type) 108 | return phonenumbers.format_number(phone_representation, 109 | phonenumbers.PhoneNumberFormat.E164) 110 | 111 | print convert_to_e164('212 555 1234') # prints +12125551234 112 | 113 | -------------------------------------------------------------------------------- /twilio/rest/resources/accounts.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources import InstanceResource, ListResource 2 | from twilio.rest.resources.applications import Applications 3 | from twilio.rest.resources.notifications import Notifications 4 | from twilio.rest.resources.recordings import Transcriptions, Recordings 5 | from twilio.rest.resources.calls import Calls 6 | from twilio.rest.resources.sms_messages import Sms 7 | from twilio.rest.resources.caller_ids import CallerIds 8 | from twilio.rest.resources.phone_numbers import PhoneNumbers 9 | from twilio.rest.resources.conferences import Conferences 10 | from twilio.rest.resources.connect_apps import ( 11 | ConnectApps, AuthorizedConnectApps 12 | ) 13 | from twilio.rest.resources.queues import Queues 14 | from twilio.rest.resources.usage import UsageRecords, UsageTriggers 15 | 16 | 17 | class Account(InstanceResource): 18 | """ An Account resource """ 19 | 20 | ACTIVE = "active" 21 | SUSPENDED = "suspended" 22 | CLOSED = "closed" 23 | 24 | subresources = [ 25 | Applications, 26 | Notifications, 27 | Transcriptions, 28 | Recordings, 29 | Calls, 30 | Sms, 31 | CallerIds, 32 | PhoneNumbers, 33 | Conferences, 34 | ConnectApps, 35 | Queues, 36 | AuthorizedConnectApps, 37 | UsageRecords, 38 | UsageTriggers, 39 | ] 40 | 41 | def update(self, **kwargs): 42 | """ 43 | :param friendly_name: Update the description of this account. 44 | :param status: Alter the status of this account 45 | 46 | Use :data:`CLOSED` to irreversibly close this account, 47 | :data:`SUSPENDED` to temporarily suspend it, or :data:`ACTIVE` 48 | to reactivate it. 49 | """ 50 | self.update_instance(**kwargs) 51 | 52 | def close(self): 53 | """ 54 | Permenently deactivate this account 55 | """ 56 | return self.update_instance(status=Account.CLOSED) 57 | 58 | def suspend(self): 59 | """ 60 | Temporarily suspend this account 61 | """ 62 | return self.update_instance(status=Account.SUSPENDED) 63 | 64 | def activate(self): 65 | """ 66 | Reactivate this account 67 | """ 68 | return self.update_instance(status=Account.ACTIVE) 69 | 70 | 71 | class Accounts(ListResource): 72 | """ A list of Account resources """ 73 | 74 | name = "Accounts" 75 | instance = Account 76 | 77 | def list(self, **kwargs): 78 | """ 79 | Returns a page of :class:`Account` resources as a list. For paging 80 | informtion see :class:`ListResource` 81 | 82 | :param date friendly_name: Only list accounts with this friendly name 83 | :param date status: Only list accounts with this status 84 | """ 85 | return self.get_instances(kwargs) 86 | 87 | def update(self, sid, **kwargs): 88 | """ 89 | :param sid: Account identifier 90 | :param friendly_name: Update the description of this account. 91 | :param status: Alter the status of this account 92 | 93 | Use :data:`CLOSED` to irreversibly close this account, 94 | :data:`SUSPENDED` to temporarily suspend it, or :data:`ACTIVE` 95 | to reactivate it. 96 | """ 97 | return self.update_instance(sid, kwargs) 98 | 99 | def close(self, sid): 100 | """ 101 | Permenently deactivate an account, Alias to update 102 | """ 103 | return self.update(sid, status=Account.CLOSED) 104 | 105 | def suspend(self, sid): 106 | """ 107 | Temporarily suspend an account, Alias to update 108 | """ 109 | return self.update(sid, status=Account.SUSPENDED) 110 | 111 | def activate(self, sid): 112 | """ 113 | Reactivate an account, Alias to update 114 | """ 115 | return self.update(sid, status=Account.ACTIVE) 116 | 117 | def create(self, **kwargs): 118 | """ 119 | Returns a newly created sub account resource. 120 | 121 | :param friendly_name: Update the description of this account. 122 | """ 123 | return self.create_instance(kwargs) 124 | -------------------------------------------------------------------------------- /twilio/rest/resources/applications.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources import InstanceResource, ListResource 2 | 3 | 4 | class Application(InstanceResource): 5 | """ An application resource """ 6 | 7 | def update(self, **kwargs): 8 | """ 9 | Update this application 10 | """ 11 | return self.parent.update(self.sid, **kwargs) 12 | 13 | def delete(self): 14 | """ 15 | Delete this application 16 | """ 17 | return self.parent.delete(self.sid) 18 | 19 | 20 | class Applications(ListResource): 21 | 22 | name = "Applications" 23 | instance = Application 24 | 25 | def list(self, **kwargs): 26 | """ 27 | Returns a page of :class:`Application` resources as a list. For paging 28 | informtion see :class:`ListResource` 29 | 30 | :param date friendly_name: List applications with this friendly name 31 | """ 32 | return self.get_instances(kwargs) 33 | 34 | def create(self, **kwargs): 35 | """ 36 | Create an :class:`Application` with any of these optional parameters. 37 | 38 | :param friendly_name: A human readable description of the application, 39 | with maximum length 64 characters. 40 | :param api_version: Requests to this application's URLs will start a 41 | new TwiML session with this API version. 42 | Either 2010-04-01 or 2008-08-01. 43 | :param voice_url: The URL that Twilio should request when somebody 44 | dials a phone number assigned to this application. 45 | :param voice_method: The HTTP method that should be used to request the 46 | VoiceUrl. Either GET or POST. 47 | :param voice_fallback_url: A URL that Twilio will request if an error 48 | occurs requesting or executing the TwiML 49 | defined by VoiceUrl. 50 | :param voice_fallback_method: The HTTP method that should be used to 51 | request the VoiceFallbackUrl. Either GET 52 | or POST. 53 | :param status_callback: The URL that Twilio will request to pass status 54 | parameters (such as call ended) to your 55 | application. 56 | :param status_callback_method: The HTTP method Twilio will use to make 57 | requests to the StatusCallback URL. 58 | Either GET or POST. 59 | :param voice_caller_id_lookup: Do a lookup of a caller's name from the 60 | CNAM database and post it to your app. 61 | Either true or false. 62 | :param sms_url: The URL that Twilio should request when somebody sends 63 | an SMS to a phone number assigned to this application. 64 | :param sms_method: The HTTP method that should be used to request the 65 | SmsUrl. Either GET or POST. 66 | :param sms_fallback_url: A URL that Twilio will request if an error 67 | occurs requesting or executing the TwiML 68 | defined by SmsUrl. 69 | :param sms_fallback_method: The HTTP method that should be used to 70 | request the SmsFallbackUrl. Either GET 71 | or POST. 72 | :param sms_status_callback: Twilio will make a POST request to this URL 73 | to pass status parameters (such as sent or 74 | failed) to your application if you specify 75 | this application's Sid as the 76 | ApplicationSid on an outgoing SMS request. 77 | """ 78 | return self.create_instance(kwargs) 79 | 80 | def update(self, sid, **kwargs): 81 | """ 82 | Update an :class:`Application` with the given parameters. 83 | 84 | All the parameters are describe above in :meth:`create` 85 | """ 86 | return self.update_instance(sid, kwargs) 87 | 88 | def delete(self, sid): 89 | """ 90 | Delete an :class:`Application` 91 | """ 92 | return self.delete_instance(sid) 93 | -------------------------------------------------------------------------------- /tests/resources/usage_records_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 0, 3 | "num_pages": 1, 4 | "page_size": 50, 5 | "total": 2, 6 | "start": 0, 7 | "end": 1, 8 | "uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Usage/Records.json?Page=0&PageSize=50", 9 | "previous_page_uri": null, 10 | "next_page_uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json?Page=1&PageSize=50", 11 | "last_page_uri": "\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json?Page=10&PageSize=50", 12 | "first_page_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Page=0&PageSize=50", 13 | "usage_records": [ 14 | { 15 | "category": "calleridlookups", 16 | "subresource_uris": [ 17 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/AllTime.json?Category=calleridlookups", 18 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Today.json?Category=calleridlookups", 19 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Yesterday.json?Category=calleridlookups", 20 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/ThisMonth.json?Category=calleridlookups", 21 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/LastMonth.json?Category=calleridlookups", 22 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/ThisYear.json?Category=calleridlookups", 23 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/LastYear.json?Category=calleridlookups", 24 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Daily.json?Category=calleridlookups", 25 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Monthly.json?Category=calleridlookups", 26 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Yearly.json?Category=calleridlookups" 27 | ], 28 | "description": "Caller ID lookups", 29 | "end_date": "2012-09-29", 30 | "usage_unit": "Lookups", 31 | "price": "0", 32 | "uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Category=calleridlookups&StartDate=2012-09-22&EndDate=2012-09-29", 33 | "number": "0", 34 | "account_sid": "ACed70abd024d3f57a4027b5dc2ca88d5b", 35 | "trigger_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Triggers.json?UsageCategory=calleridlookups", 36 | "usage": "0", 37 | "start_date": "2012-09-22" 38 | }, 39 | { 40 | "category": "recordings", 41 | "subresource_uris": [ 42 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/AllTime.json?Category=recordings", 43 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Today.json?Category=recordings", 44 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Yesterday.json?Category=recordings", 45 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/ThisMonth.json?Category=recordings", 46 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/LastMonth.json?Category=recordings", 47 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/ThisYear.json?Category=recordings", 48 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/LastYear.json?Category=recordings", 49 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Daily.json?Category=recordings", 50 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Monthly.json?Category=recordings", 51 | "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records/Yearly.json?Category=recordings" 52 | ], 53 | "description": "Call recordings", 54 | "end_date": "2012-09-29", 55 | "usage_unit": "Minutes", 56 | "price": "0", 57 | "uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Records.json?Category=recordings&StartDate=2012-09-22&EndDate=2012-09-29", 58 | "number": "0", 59 | "account_sid": "ACed70abd024d3f57a4027b5dc2ca88d5b", 60 | "trigger_uri": "/2010-04-01/Accounts/ACed70abd024d3f57a4027b5dc2ca88d5b/Usage/Triggers.json?UsageCategory=recordings", 61 | "usage": "0", 62 | "start_date": "2012-09-22" 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twilio-python 2 | 3 | [![Build Status](https://secure.travis-ci.org/twilio/twilio-python.png?branch=master)](http://travis-ci.org/twilio/twilio-python) 4 | 5 | A module for using the Twilio REST API and generating valid 6 | [TwiML](http://www.twilio.com/docs/api/twiml/ "TwiML - 7 | Twilio Markup Language"). [Click here to read the full 8 | documentation.](http://readthedocs.org/docs/twilio-python/en/latest/ "Twilio 9 | Python library documentation") 10 | 11 | ## Installation 12 | 13 | Install from PyPi using [pip](http://www.pip-installer.org/en/latest/), a 14 | package manager for Python. 15 | 16 | $ pip install twilio 17 | 18 | Don't have pip installed? Try installing it, by running this from the command 19 | line: 20 | 21 | $ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python 22 | 23 | Or, you can [download the source code 24 | (ZIP)](https://github.com/twilio/twilio-python/zipball/master "twilio-python 25 | source code") for `twilio-python`, and then run: 26 | 27 | $ python setup.py install 28 | 29 | You may need to run the above commands with `sudo`. 30 | 31 | ## Getting Started 32 | 33 | Getting started with the Twilio API couldn't be easier. Create a 34 | `TwilioRestClient` and you're ready to go. 35 | 36 | ### API Credentials 37 | 38 | The `TwilioRestClient` needs your Twilio credentials. You can either pass these 39 | directly to the constructor (see the code below) or via environment variables. 40 | 41 | ```python 42 | from twilio.rest import TwilioRestClient 43 | 44 | account = "ACXXXXXXXXXXXXXXXXX" 45 | token = "YYYYYYYYYYYYYYYYYY" 46 | client = TwilioRestClient(account, token) 47 | ``` 48 | 49 | Alternately, a `TwilioRestClient` constructor without these parameters will 50 | look for `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` variables inside the 51 | current environment. 52 | 53 | We suggest storing your credentials as environment variables. Why? You'll never 54 | have to worry about committing your credentials and accidentally posting them 55 | somewhere public. 56 | 57 | 58 | ```python 59 | from twilio.rest import TwilioRestClient 60 | client = TwilioRestClient() 61 | ``` 62 | 63 | ### Make a Call 64 | 65 | ```python 66 | from twilio.rest import TwilioRestClient 67 | 68 | account = "ACXXXXXXXXXXXXXXXXX" 69 | token = "YYYYYYYYYYYYYYYYYY" 70 | client = TwilioRestClient(account, token) 71 | 72 | call = client.calls.create(to="9991231234", 73 | from_="9991231234", 74 | url="http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient") 75 | print call.sid 76 | ``` 77 | 78 | ### Send an SMS 79 | 80 | ```python 81 | from twilio.rest import TwilioRestClient 82 | 83 | account = "ACXXXXXXXXXXXXXXXXX" 84 | token = "YYYYYYYYYYYYYYYYYY" 85 | client = TwilioRestClient(account, token) 86 | 87 | message = client.sms.messages.create(to="+12316851234", from_="+15555555555", 88 | body="Hello there!") 89 | ``` 90 | 91 | ### Handling a call using TwiML 92 | 93 | To control phone calls, your application needs to output 94 | [TwiML](http://www.twilio.com/docs/api/twiml/ "TwiML - Twilio Markup 95 | Language"). Use `twilio.twiml.Response` to easily create such responses. 96 | 97 | ```python 98 | from twilio import twiml 99 | 100 | r = twiml.Response() 101 | r.say("Welcome to twilio!") 102 | print str(r) 103 | ``` 104 | 105 | ```xml 106 | <?xml version="1.0" encoding="utf-8"?> 107 | <Response><Say>Welcome to twilio!</Say></Response> 108 | ``` 109 | 110 | ### Digging Deeper 111 | 112 | The full power of the Twilio API is at your fingertips. The [full 113 | documentation](http://readthedocs.org/docs/twilio-python/en/latest/ "Twilio 114 | Python library documentation") explains all the awesome features available to 115 | use. 116 | 117 | * [Retrieve Call Records][calls] 118 | * [Retrieve SMS Message Records][sms-messages] 119 | * [Search for a Phone Number][number] 120 | * [Buy a Number][number] 121 | * [Validate a Phone Number][validate] 122 | * [List Recordings][recordings] 123 | 124 | [number]: http://twilio-python.readthedocs.org/en/latest/usage/phone-numbers.html#searching-and-buying-a-number 125 | [validate]: http://twilio-python.readthedocs.org/en/latest/usage/caller-ids.html 126 | [recordings]: http://twilio-python.readthedocs.org/en/latest/usage/recordings.html#listing-your-recordings 127 | [sms-messages]: http://twilio-python.readthedocs.org/en/latest/usage/messages.html#retrieving-sent-messages 128 | [calls]: http://twilio-python.readthedocs.org/en/latest/usage/phone-calls.html#retrieve-a-call-record 129 | -------------------------------------------------------------------------------- /docs/appengine.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Deploying to Google App Engine 3 | ============================== 4 | 5 | Adding the `twilio-python` library to a Google App Engine project is a little 6 | tricky. Let's walk you through one way to do it. 7 | 8 | 9 | Laying Out Your Project 10 | ----------------------- 11 | 12 | The key is to lay out your project in a way that makes sense. 13 | 14 | #. Create a project folder called ``twilio-demo``. You can name the 15 | folder anything you like, but for the rest of this tutorial, we'll assume 16 | it's named ``twilio-demo``. At the command line, add a `virtualenv 17 | <http://www.virtualenv.org/en/latest/>`_ inside of that folder, by running: 18 | 19 | .. code-block:: bash 20 | 21 | mkdir twilio-demo # Creates a new twilio-demo folder 22 | cd twilio-demo # Move into that folder 23 | virtualenv . # Create a new virtual environment 24 | 25 | You should now have a directory structure that looks like this: 26 | 27 | .. code-block:: bash 28 | 29 | twilio-demo 30 | ├── bin 31 | ├── include 32 | └── lib 33 | 34 | We'll get to the Google App Engine part in a few steps. 35 | 36 | #. Now let's install the ``twilio-python`` library in our Virtualenv. If your 37 | current working directory is ``twilio-demo``, we want to source the 38 | ``activate`` file in the ``bin`` folder, then install the library with 39 | ``pip``. 40 | 41 | .. code-block:: bash 42 | 43 | source bin/activate # Activate our virtual environment 44 | pip install twilio # Install the twilio-python library 45 | 46 | #. Now let's add a new folder called ``src``. This is the folder that contains 47 | your ``app.yaml`` and your other Google App Engine files. You can add this 48 | at the command line. If your current directory is ``twilio-demo``: 49 | 50 | .. code-block:: bash 51 | 52 | mkdir src 53 | 54 | #. Create a basic ``app.yaml`` file in your ``src`` directory, per the 55 | instructions Google App Engine provides. Your folder structure should now 56 | look something like this: 57 | 58 | .. code-block:: bash 59 | 60 | twilio-demo 61 | ├── bin 62 | │   ├── activate 63 | │   └── ... about 20 files 64 | ├── include 65 | │   └── python2.7 -> /path/to/system/python-2.7 66 | ├── lib 67 | │   └── python2.7 # This folder contains a bunch of files 68 | └── src 69 | ├── app.yaml 70 | └── helloworld.py 71 | 72 | #. Link the twilio-python library into your ``src`` directory. We are going 73 | to use a symbolic link. Google will pick this up and import the library into 74 | the correct place. In the terminal, run these three commands from the ``src`` 75 | directory inside ``twilio-demo``: 76 | 77 | .. code-block:: bash 78 | 79 | ln -s ../lib/python2.7/site-packages/twilio . 80 | ln -s ../lib/python2.7/site-packages/httplib2 . 81 | ln -s ../lib/python2.7/site-packages/six.py . 82 | 83 | This should create a symbolic link inside the src directory to the 84 | ``twilio-python`` module. You can test the link as follows. Inside the 85 | ``twilio-demo/src`` folder, create a file called ``helloworld.py`` and put 86 | this inside it: 87 | 88 | .. code-block:: python 89 | 90 | import webapp2 91 | import twilio 92 | 93 | class MainPage(webapp2.RequestHandler): 94 | def get(self): 95 | self.response.headers['Content-Type'] = 'text/plain' 96 | self.response.write("The twilio version is " + twilio.__version__) 97 | 98 | app = webapp2.WSGIApplication([('/', MainPage)], 99 | debug=True) 100 | 101 | #. Finally, configure your app in Google App Engine and deploy it. Here are 102 | the settings you want in Google App Engine - Note the folder path ends with 103 | ``twilio-demo/src``. 104 | 105 | .. image:: https://www.evernote.com/shard/s265/sh/1b9407b0-c89b-464d-b352-dbf8fc7a7f41/f536b8e79747f43220fc12e0e0026ee2/res/5b2f83af-8a7f-451f-afba-db092c55aa44/skitch.png 106 | 107 | Once App Engine is running locally, in your browser, you should be able to 108 | navigate to ``http://localhost`` + the provided Port and view the twilio 109 | library version number, as well as deploy your app to Google. Once you have 110 | this set up, adding more complicated actions using the ``twilio`` library 111 | should be a snap. 112 | 113 | Hope that helps! If you have questions, we're always listening at 114 | `help@twilio.com <mailto:help@twilio.com>`_. 115 | -------------------------------------------------------------------------------- /tests/test_jwt.py: -------------------------------------------------------------------------------- 1 | import time 2 | from twilio import jwt 3 | import six 4 | if six.PY3: 5 | import unittest 6 | else: 7 | import unittest2 as unittest 8 | 9 | from twilio.util import TwilioCapability 10 | 11 | 12 | class JwtTest(unittest.TestCase): 13 | 14 | def assertIn(self, foo, bar, msg=None): 15 | """backport for 2.6""" 16 | return self.assertTrue(foo in bar, msg=(msg or "%s not found in %s" 17 | % (foo, bar))) 18 | 19 | def test_no_permissions(self): 20 | token = TwilioCapability("AC123", "XXXXX") 21 | payload = token.payload() 22 | self.assertEquals(len(payload), 1) 23 | self.assertEquals(payload["scope"], '') 24 | 25 | def test_inbound_permissions(self): 26 | token = TwilioCapability("AC123", "XXXXX") 27 | token.allow_client_incoming("andy") 28 | payload = token.payload() 29 | 30 | eurl = "scope:client:incoming?clientName=andy" 31 | self.assertEquals(len(payload), 1) 32 | self.assertEquals(payload['scope'], eurl) 33 | 34 | def test_outbound_permissions(self): 35 | token = TwilioCapability("AC123", "XXXXX") 36 | token.allow_client_outgoing("AP123") 37 | payload = token.payload() 38 | 39 | eurl = "scope:client:outgoing?appSid=AP123" 40 | 41 | self.assertEquals(len(payload), 1) 42 | self.assertIn(eurl, payload['scope']) 43 | 44 | def test_outbound_permissions_params(self): 45 | token = TwilioCapability("AC123", "XXXXX") 46 | token.allow_client_outgoing("AP123", foobar=3) 47 | payload = token.payload() 48 | 49 | eurl = "scope:client:outgoing?appParams=foobar%3D3&appSid=AP123" 50 | self.assertEquals(payload["scope"], eurl) 51 | 52 | def test_events(self): 53 | token = TwilioCapability("AC123", "XXXXX") 54 | token.allow_event_stream() 55 | payload = token.payload() 56 | 57 | event_uri = "scope:stream:subscribe?path=%2F2010-04-01%2FEvents" 58 | self.assertEquals(payload["scope"], event_uri) 59 | 60 | def test_events_with_filters(self): 61 | token = TwilioCapability("AC123", "XXXXX") 62 | token.allow_event_stream(foobar="hey") 63 | payload = token.payload() 64 | 65 | event_uri = "scope:stream:subscribe?params=foobar%3Dhey&path=%2F2010-04-01%2FEvents" 66 | self.assertEquals(payload["scope"], event_uri) 67 | 68 | def test_decode(self): 69 | token = TwilioCapability("AC123", "XXXXX") 70 | token.allow_client_outgoing("AP123", foobar=3) 71 | token.allow_client_incoming("andy") 72 | token.allow_event_stream() 73 | 74 | outgoing_uri = "scope:client:outgoing?appParams=foobar%3D3&appSid=AP123&clientName=andy" 75 | incoming_uri = "scope:client:incoming?clientName=andy" 76 | event_uri = "scope:stream:subscribe?path=%2F2010-04-01%2FEvents" 77 | 78 | result = jwt.decode(token.generate(), "XXXXX") 79 | scope = result["scope"].split(" ") 80 | 81 | self.assertIn(outgoing_uri, scope) 82 | self.assertIn(incoming_uri, scope) 83 | self.assertIn(event_uri, scope) 84 | 85 | def setUp(self): 86 | self.payload = {"iss": "jeff", "exp": int(time.time()), "claim": "insanity"} 87 | 88 | def test_encode_decode(self): 89 | secret = 'secret' 90 | jwt_message = jwt.encode(self.payload, secret) 91 | decoded_payload = jwt.decode(jwt_message, secret) 92 | self.assertEqual(decoded_payload, self.payload) 93 | 94 | def test_bad_secret(self): 95 | right_secret = 'foo' 96 | bad_secret = 'bar' 97 | jwt_message = jwt.encode(self.payload, right_secret) 98 | self.assertRaises(jwt.DecodeError, jwt.decode, jwt_message, bad_secret) 99 | 100 | def test_decodes_valid_jwt(self): 101 | example_payload = {"hello": "world"} 102 | example_secret = "secret" 103 | example_jwt = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" 104 | decoded_payload = jwt.decode(example_jwt, example_secret) 105 | self.assertEqual(decoded_payload, example_payload) 106 | 107 | def test_allow_skip_verification(self): 108 | right_secret = 'foo' 109 | jwt_message = jwt.encode(self.payload, right_secret) 110 | decoded_payload = jwt.decode(jwt_message, verify=False) 111 | self.assertEqual(decoded_payload, self.payload) 112 | 113 | def test_no_secret(self): 114 | right_secret = 'foo' 115 | jwt_message = jwt.encode(self.payload, right_secret) 116 | self.assertRaises(jwt.DecodeError, jwt.decode, jwt_message) 117 | 118 | def test_invalid_crypto_alg(self): 119 | self.assertRaises(NotImplementedError, jwt.encode, self.payload, "secret", "HS1024") 120 | -------------------------------------------------------------------------------- /twilio/rest/resources/sms_messages.py: -------------------------------------------------------------------------------- 1 | from twilio.rest.resources.util import normalize_dates, parse_date 2 | from twilio.rest.resources import InstanceResource, ListResource 3 | 4 | 5 | class ShortCode(InstanceResource): 6 | 7 | def update(self, **kwargs): 8 | return self.parent.update(self.name, **kwargs) 9 | 10 | 11 | class ShortCodes(ListResource): 12 | 13 | name = "ShortCodes" 14 | key = "short_codes" 15 | instance = ShortCode 16 | 17 | def list(self, **kwargs): 18 | """ 19 | Returns a page of :class:`ShortCode` resources as a list. For 20 | paging information see :class:`ListResource`. 21 | 22 | :param short_code: Only show the ShortCode resources that match this 23 | pattern. You can specify partial numbers and use '*' 24 | as a wildcard for any digit. 25 | :param friendly_name: Only show the ShortCode resources with friendly 26 | names that exactly match this name. 27 | """ 28 | return self.get_instances(kwargs) 29 | 30 | def update(self, sid, url=None, method=None, fallback_url=None, 31 | fallback_method=None, **kwargs): 32 | """ 33 | Update a specific :class:`ShortCode`, by specifying the sid. 34 | 35 | :param friendly_name: Description of the short code, with maximum 36 | length 64 characters. 37 | :param api_version: SMSs to this short code will start a new TwiML 38 | session with this API version. 39 | :param url: The URL that Twilio should request when somebody sends an 40 | SMS to the short code. 41 | :param method: The HTTP method that should be used to request the url. 42 | :param fallback_url: A URL that Twilio will request if an error occurs 43 | requesting or executing the TwiML at the url. 44 | :param fallback_method: The HTTP method that should be used to request 45 | the fallback_url. 46 | """ 47 | kwargs["sms_url"] = kwargs.get("sms_url", url) 48 | kwargs["sms_method"] = kwargs.get("sms_method", method) 49 | kwargs["sms_fallback_url"] = \ 50 | kwargs.get("sms_fallback_url", fallback_url) 51 | kwargs["sms_fallback_method"] = \ 52 | kwargs.get("sms_fallback_method", fallback_method) 53 | return self.update_instance(sid, kwargs) 54 | 55 | 56 | class Sms(object): 57 | """ 58 | Holds all the specific SMS list resources 59 | """ 60 | 61 | name = "SMS" 62 | key = "sms" 63 | 64 | def __init__(self, base_uri, auth): 65 | self.uri = "%s/SMS" % base_uri 66 | self.messages = SmsMessages(self.uri, auth) 67 | self.short_codes = ShortCodes(self.uri, auth) 68 | 69 | 70 | class SmsMessage(InstanceResource): 71 | pass 72 | 73 | 74 | class SmsMessages(ListResource): 75 | 76 | name = "Messages" 77 | key = "sms_messages" 78 | instance = SmsMessage 79 | 80 | def create(self, from_=None, **kwargs): 81 | """ 82 | Create and send a SMS Message. 83 | 84 | :param string to: The destination phone number. 85 | :param string `from_`: The phone number sending this message 86 | (must be a verified Twilio number) 87 | :param string body: The message you want to send, 88 | limited to 160 characters. 89 | :param status_callback: A URL that Twilio will POST to when 90 | your message is processed. 91 | :param string application_sid: The 34 character sid of the application 92 | Twilio should use to handle this phone call. 93 | """ 94 | kwargs["from"] = from_ 95 | return self.create_instance(kwargs) 96 | 97 | @normalize_dates 98 | def list(self, from_=None, before=None, after=None, date_sent=None, **kw): 99 | """ 100 | Returns a page of :class:`SMSMessage` resources as a list. For 101 | paging informtion see :class:`ListResource`. 102 | 103 | :param to: Only show SMS messages to this phone number. 104 | :param from_: Only show SMS messages from this phone number. 105 | :param date after: Only list SMS messages sent after this date. 106 | :param date before: Only list SMS message sent before this date. 107 | :param date date_sent: Only list SMS message sent on this date. 108 | :param `from_`: Only show SMS messages from this phone number. 109 | :param date after: Only list recordings logged after this datetime 110 | :param date before: Only list recordings logged before this datetime 111 | """ 112 | kw["From"] = from_ 113 | kw["DateSent<"] = before 114 | kw["DateSent>"] = after 115 | kw["DateSent"] = parse_date(date_sent) 116 | return self.get_instances(kw) 117 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make <target>' where <target> is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | pip install -r ../requirements.txt 41 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 42 | @echo 43 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 44 | 45 | dirhtml: 46 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 47 | @echo 48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 49 | 50 | singlehtml: 51 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 52 | @echo 53 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 54 | 55 | pickle: 56 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 57 | @echo 58 | @echo "Build finished; now you can process the pickle files." 59 | 60 | json: 61 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 62 | @echo 63 | @echo "Build finished; now you can process the JSON files." 64 | 65 | htmlhelp: 66 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 67 | @echo 68 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 69 | ".hhp project file in $(BUILDDIR)/htmlhelp." 70 | 71 | qthelp: 72 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 73 | @echo 74 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 75 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 76 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/twilio-python2.qhcp" 77 | @echo "To view the help file:" 78 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/twilio-python2.qhc" 79 | 80 | devhelp: 81 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 82 | @echo 83 | @echo "Build finished." 84 | @echo "To view the help file:" 85 | @echo "# mkdir -p $$HOME/.local/share/devhelp/twilio-python2" 86 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/twilio-python2" 87 | @echo "# devhelp" 88 | 89 | epub: 90 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 91 | @echo 92 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 93 | 94 | latex: 95 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 96 | @echo 97 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 98 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 99 | "(use \`make latexpdf' here to do that automatically)." 100 | 101 | latexpdf: 102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 103 | @echo "Running LaTeX files through pdflatex..." 104 | make -C $(BUILDDIR)/latex all-pdf 105 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 106 | 107 | text: 108 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 109 | @echo 110 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 111 | 112 | man: 113 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 114 | @echo 115 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 116 | 117 | changes: 118 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 119 | @echo 120 | @echo "The overview file is in $(BUILDDIR)/changes." 121 | 122 | linkcheck: 123 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 124 | @echo 125 | @echo "Link check complete; look for any errors in the above output " \ 126 | "or in $(BUILDDIR)/linkcheck/output.txt." 127 | 128 | doctest: 129 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 130 | @echo "Testing of doctests in the sources finished, look at the " \ 131 | "results in $(BUILDDIR)/doctest/output.txt." 132 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^<target^>` where ^<target^> is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\twilio-python2.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\twilio-python2.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /tests/resources/accounts_list.json: -------------------------------------------------------------------------------- 1 | {"page":0,"num_pages":1,"page_size":50,"total":4,"start":0,"end":3,"uri":"\/2010-04-01\/Accounts.json","first_page_uri":"\/2010-04-01\/Accounts.json?Page=0&PageSize=50","previous_page_uri":null,"next_page_uri":null,"last_page_uri":"\/2010-04-01\/Accounts.json?Page=0&PageSize=50","accounts":[{"sid":"ACea9234fced59a5733d2c1c1c69a83a28","friendly_name":"ChangeName","status":"active","auth_token":"AUTHTOKEN","date_created":"Thu, 17 Feb 2011 18:27:56 +0000","date_updated":"Thu, 17 Feb 2011 18:31:43 +0000","type":"Full","uri":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28.json","subresource_uris":{"available_phone_numbers":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers.json","calls":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Calls.json","conferences":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Conferences.json","incoming_phone_numbers":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","notifications":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Notifications.json","outgoing_caller_ids":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","recordings":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Recordings.json","sandbox":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Sandbox.json","sms_messages":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/SMS\/Messages.json","transcriptions":"\/2010-04-01\/Accounts\/ACea9234fced59a5733d2c1c1c69a83a28\/Transcriptions.json"}},{"sid":"ACc4d3e1b7ed59a5733d2c1c1c69a83a28","friendly_name":"HEY","status":"active","auth_token":"AUTHTOKEN","date_created":"Fri, 11 Feb 2011 03:43:04 +0000","date_updated":"Fri, 11 Feb 2011 03:43:04 +0000","type":"Full","uri":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28.json","subresource_uris":{"available_phone_numbers":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers.json","calls":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Calls.json","conferences":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Conferences.json","incoming_phone_numbers":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","notifications":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Notifications.json","outgoing_caller_ids":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","recordings":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Recordings.json","sandbox":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Sandbox.json","sms_messages":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/SMS\/Messages.json","transcriptions":"\/2010-04-01\/Accounts\/ACc4d3e1b7ed59a5733d2c1c1c69a83a28\/Transcriptions.json"}},{"sid":"AC2c016433ed59a5733d2c1c1c69a83a28","friendly_name":"SubAccount Created at(415) 867-5309:19 pm","status":"active","auth_token":"AUTHTOKEN","date_created":"Fri, 11 Feb 2011 00:19:37 +0000","date_updated":"Fri, 11 Feb 2011 00:19:37 +0000","type":"Full","uri":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28.json","subresource_uris":{"available_phone_numbers":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers.json","calls":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Calls.json","conferences":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Conferences.json","incoming_phone_numbers":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","notifications":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Notifications.json","outgoing_caller_ids":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","recordings":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Recordings.json","sandbox":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Sandbox.json","sms_messages":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/SMS\/Messages.json","transcriptions":"\/2010-04-01\/Accounts\/AC2c016433ed59a5733d2c1c1c69a83a28\/Transcriptions.json"}},{"sid":"AC4bf2dafbed59a5733d2c1c1c69a83a28","friendly_name":"kyle.j.conroy@gmail.com's Account","status":"active","auth_token":"AUTHTOKEN","date_created":"Sun, 15 Mar 2009 02:08:47 +0000","date_updated":"Wed, 25 Aug 2010 01:30:09 +0000","type":"Full","uri":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28.json","subresource_uris":{"available_phone_numbers":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/AvailablePhoneNumbers.json","calls":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Calls.json","conferences":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Conferences.json","incoming_phone_numbers":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/IncomingPhoneNumbers.json","notifications":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Notifications.json","outgoing_caller_ids":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/OutgoingCallerIds.json","recordings":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Recordings.json","sandbox":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Sandbox.json","sms_messages":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/SMS\/Messages.json","transcriptions":"\/2010-04-01\/Accounts\/AC4bf2dafbed59a5733d2c1c1c69a83a28\/Transcriptions.json"}}]} 2 | --------------------------------------------------------------------------------