`_ 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 |
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\nError: Server Error<\/h1>\nThe server encountered an error and could not complete your request.
If the problem persists, please report<\/A> your problem and mention this error message and the query that caused it.<\/h2>\n<\/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 | `_. 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 |
62 |
63 | https://api.twilio.com/cowbell.mp3
64 |
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 | `_ 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 `_
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 | `_ 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 |
27 | Hello
28 |
29 | The verb methods (outlined in the :doc:`complete reference `)
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 |
44 |
45 | https://api.twilio.com/cowbell.mp3
46 |
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 |
66 |
67 | Hello
68 | World
69 |
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 |
87 | Hello
88 | World
89 |
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 `_
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 `_.
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 | `_)
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 `_ 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 | `_ to get up and running with
12 | Twilio Client.
13 |
14 | Capability tokens are used by `Twilio Client
15 | `_ to provide connection
16 | security and authorization. The `Capability Token documentation
17 | `_ 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 `_
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 `_ device,
53 | you'll need to choose a
54 | `Twilio Application `_
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 `_
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 | `_ 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 `_ and `Participant REST Resource `_ 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 `_
9 | and `Member REST Resource `_
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 `_, 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 `_,
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 `_ 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 `_. 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 `_.
154 |
155 | If you have questions that aren't answered by this documentation,
156 | ask the `#twilio IRC channel `_
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 `_.
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
18 | from twilio.rest import TwilioRestClient
19 | File "/.../twilio-python/docs/twilio.py", line 1, in
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
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 | `_, 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 | `_ 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 | [](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 |
107 | Welcome to twilio!
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 | `_ 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 `_.
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 ' where 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 ^` where ^ 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 |
--------------------------------------------------------------------------------