diff --git a/tests/unit/test_twiml.py b/tests/unit/test_twiml.py
deleted file mode 100644
index 7bab2ad1e9..0000000000
--- a/tests/unit/test_twiml.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import with_statement
-import unittest
-import xml.etree.ElementTree as ET
-
-from nose.tools import assert_equal
-from six import u, text_type
-
-from twilio import twiml
-from twilio.twiml import TwimlException
-from twilio.twiml import Response
-
-
-class TwilioTest(unittest.TestCase):
- def strip(self, xml):
- return text_type(xml)
-
- def improperAppend(self, verb):
- self.assertRaises(TwimlException, verb.append, twiml.Say(""))
- self.assertRaises(TwimlException, verb.append, twiml.Gather())
- self.assertRaises(TwimlException, verb.append, twiml.Play(""))
- self.assertRaises(TwimlException, verb.append, twiml.Record())
- self.assertRaises(TwimlException, verb.append, twiml.Hangup())
- self.assertRaises(TwimlException, verb.append, twiml.Reject())
- self.assertRaises(TwimlException, verb.append, twiml.Redirect())
- self.assertRaises(TwimlException, verb.append, twiml.Dial())
- self.assertRaises(TwimlException, verb.append, twiml.Enqueue(""))
- self.assertRaises(TwimlException, verb.append, twiml.Queue(""))
- self.assertRaises(TwimlException, verb.append, twiml.Leave())
- self.assertRaises(TwimlException, verb.append, twiml.Conference(""))
- self.assertRaises(TwimlException, verb.append, twiml.Client(""))
- self.assertRaises(TwimlException, verb.append, twiml.Sms(""))
- self.assertRaises(TwimlException, verb.append, twiml.Pause())
-
-
-class TestResponse(TwilioTest):
-
- def testEmptyResponse(self):
- r = Response()
- assert_equal(self.strip(r), '')
-
- def testResponseAddAttribute(self):
- r = Response(foo="bar")
- assert_equal(self.strip(r), '')
-
-
-class TestSay(TwilioTest):
-
- def testEmptySay(self):
- """ should be a say with no text """
- r = Response()
- r.append(twiml.Say(""))
- assert_equal(self.strip(r), '')
-
- def testSayHelloWorld(self):
- """ should say hello monkey """
- r = Response()
- r.append(twiml.Say("Hello World"))
- r = self.strip(r)
- assert_equal(r, 'Hello World')
-
- def testSayFrench(self):
- """ should say hello monkey """
- r = Response()
- r.append(twiml.Say(u("n\xe9cessaire et d'autres"))) # it works on python 2.6 with the from __future__ import unicode_literal
- assert_equal(text_type(r),
- 'nécessaire et d\'autres')
-
- def testSayLoop(self):
- """ should say hello monkey and loop 3 times """
- r = Response()
- r.append(twiml.Say("Hello Monkey", loop=3))
- r = self.strip(r)
- assert_equal(r, 'Hello Monkey')
-
- def testSayLoopGreatBritian(self):
- """ should say have a woman say hello monkey and loop 3 times """
- r = Response()
- r.append(twiml.Say("Hello Monkey", language="en-gb"))
- r = self.strip(r)
- assert_equal(r, 'Hello Monkey')
-
- def testSayLoopWoman(self):
- """ should say have a woman say hello monkey and loop 3 times """
- r = Response()
- r.append(twiml.Say("Hello Monkey", loop=3, voice=twiml.Say.WOMAN))
- r = self.strip(r)
- assert_equal(r, 'Hello Monkey')
-
- def testSayConvienceMethod(self):
- """ convenience method: should say have a woman say hello monkey and loop 3 times and be in french """
- r = Response()
- r.addSay("Hello Monkey", loop=3, voice=twiml.Say.MAN, language=twiml.Say.FRENCH)
- r = self.strip(r)
- assert_equal(r, 'Hello Monkey')
-
- def testSayAddAttribute(self):
- """ add attribute """
- r = twiml.Say("", foo="bar")
- r = self.strip(r)
- assert_equal(r, '')
-
- def testSayBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Say(""))
-
-
-class TestPlay(TwilioTest):
-
- def testEmptyPlay(self):
- """ should play hello monkey """
- r = Response()
- r.append(twiml.Play(""))
- r = self.strip(r)
- self.assertEqual(r, '')
-
- def testPlayHello(self):
- """ should play hello monkey """
- r = Response()
- r.append(twiml.Play("http://hellomonkey.mp3"))
- r = self.strip(r)
- self.assertEqual(r, 'http://hellomonkey.mp3')
-
- def testPlayHelloLoop(self):
- """ should play hello monkey loop """
- r = Response()
- r.append(twiml.Play("http://hellomonkey.mp3", loop=3))
- r = self.strip(r)
- self.assertEqual(r, 'http://hellomonkey.mp3')
-
- def testPlayConvienceMethod(self):
- """ convenience method: should play hello monkey """
- r = Response()
- r.addPlay("http://hellomonkey.mp3", loop=3)
- r = self.strip(r)
- self.assertEqual(r, 'http://hellomonkey.mp3')
-
- def testPlayAddAttribute(self):
- """ add attribute """
- r = twiml.Play("", foo="bar")
- r = self.strip(r)
- assert_equal(r, '')
-
- def testPlayBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Play(""))
-
- def testPlayDigits(self):
- """ should play digits """
- r = Response()
- r.append(twiml.Play(digits='w123'))
- r = self.strip(r)
- self.assertEqual(r, '')
-
- def testUrlOrDigitsRequired(self):
- self.assertRaises(TwimlException, twiml.Play)
-
-
-class TestRecord(TwilioTest):
-
- def testRecordEmpty(self):
- """ should record """
- r = Response()
- r.append(twiml.Record())
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRecordActionMethod(self):
- """ should record with an action and a get method """
- r = Response()
- r.append(twiml.Record(action="example.com", method="GET"))
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRecordMaxlengthFinishTimeout(self):
- """ should record with an maxlength, finishonkey, and timeout """
- r = Response()
- r.append(twiml.Record(timeout=4, finishOnKey="#", maxLength=30))
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRecordTranscribeCallback(self):
- """ should record with a transcribe and transcribeCallback """
- r = Response()
- r.append(twiml.Record(transcribeCallback="example.com"))
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRecordAddAttribute(self):
- """ add attribute """
- r = twiml.Record(foo="bar")
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRecordBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Record())
-
-
-class TestRedirect(TwilioTest):
-
- def testRedirectEmpty(self):
- r = Response()
- r.append(twiml.Redirect())
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRedirectMethod(self):
- r = Response()
- r.append(twiml.Redirect(url="example.com", method="POST"))
- r = self.strip(r)
- assert_equal(r, 'example.com')
-
- def testRedirectMethodGetParams(self):
- r = Response()
- r.append(twiml.Redirect(url="example.com?id=34&action=hey", method="POST"))
- r = self.strip(r)
- assert_equal(r, 'example.com?id=34&action=hey')
-
- def testAddAttribute(self):
- """ add attribute """
- r = twiml.Redirect("", foo="bar")
- r = self.strip(r)
- assert_equal(r, '')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Redirect())
-
-
-class TestHangup(TwilioTest):
-
- def testHangup(self):
- """ convenience: should Hangup to a url via POST """
- r = Response()
- r.append(twiml.Hangup())
- r = self.strip(r)
- assert_equal(r, '')
-
- def testHangupConvience(self):
- """ should raises exceptions for wrong appending """
- r = Response()
- r.addHangup()
- r = self.strip(r)
- assert_equal(r, '')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Hangup())
-
-
-class TestLeave(TwilioTest):
-
- def testLeave(self):
- """ convenience: should Hangup to a url via POST """
- r = Response()
- r.append(twiml.Leave())
- r = self.strip(r)
- assert_equal(r, '')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Leave())
-
-
-class TestReject(TwilioTest):
-
- def testReject(self):
- """ should be a Reject with default reason """
- r = Response()
- r.append(twiml.Reject())
- r = self.strip(r)
- assert_equal(r, '')
-
- def testRejectConvenience(self):
- """ should be a Reject with reason Busy """
- r = Response()
- r.addReject(reason='busy')
- r = self.strip(r)
- assert_equal(r, '')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Reject())
-
-
-class TestSms(TwilioTest):
-
- def testEmpty(self):
- """ Test empty sms verb """
- r = Response()
- r.append(twiml.Sms(""))
- r = self.strip(r)
- assert_equal(r, '')
-
- def testBody(self):
- """ Test hello world """
- r = Response()
- r.append(twiml.Sms("Hello, World"))
- r = self.strip(r)
- assert_equal(r, 'Hello, World')
-
- def testToFromAction(self):
- """ Test the to, from, and status callback """
- r = Response()
- r.append(twiml.Sms("Hello, World", to=1231231234, sender=3453453456,
- statusCallback="example.com?id=34&action=hey"))
- r = self.strip(r)
- assert_equal(r, 'Hello, World')
-
- def testActionMethod(self):
- """ Test the action and method parameters on Sms """
- r = Response()
- r.append(twiml.Sms("Hello", method="POST", action="example.com?id=34&action=hey"))
- r = self.strip(r)
- assert_equal(r, 'Hello')
-
- def testConvience(self):
- """ should raises exceptions for wrong appending """
- r = Response()
- r.addSms("Hello")
- r = self.strip(r)
- assert_equal(r, 'Hello')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Sms("Hello"))
-
-
-class TestConference(TwilioTest):
-
- def setUp(self):
- r = Response()
- with r.dial() as dial:
- dial.conference("TestConferenceAttributes", beep=False, waitUrl="",
- startConferenceOnEnter=True, endConferenceOnExit=True)
- xml = r.toxml()
-
- # parse twiml XML string with Element Tree and inspect structure
- tree = ET.fromstring(xml)
- self.conf = tree.find(".//Conference")
-
- def test_conf_text(self):
- self.assertEqual(self.conf.text.strip(), "TestConferenceAttributes")
-
- def test_conf_beep(self):
- self.assertEqual(self.conf.get('beep'), "false")
-
- def test_conf_waiturl(self):
- self.assertEqual(self.conf.get('waitUrl'), "")
-
- def test_conf_start_conference(self):
- self.assertEqual(self.conf.get('startConferenceOnEnter'), "true")
-
- def test_conf_end_conference(self):
- self.assertEqual(self.conf.get('endConferenceOnExit'), "true")
-
-
-class TestQueue(TwilioTest):
-
- def setUp(self):
- r = Response()
- with r.dial() as dial:
- dial.queue("TestQueueAttribute", url="", method='GET')
- xml = r.toxml()
-
- # parse twiml XML string with Element Tree and inspect
- # structure
- tree = ET.fromstring(xml)
- self.conf = tree.find(".//Queue")
-
- def test_conf_text(self):
- self.assertEqual(self.conf.text.strip(), "TestQueueAttribute")
-
- def test_conf_waiturl(self):
- self.assertEqual(self.conf.get('url'), "")
-
- def test_conf_method(self):
- self.assertEqual(self.conf.get('method'), "GET")
-
-
-class TestEnqueue(TwilioTest):
-
- def setUp(self):
- r = Response()
- r.enqueue("TestEnqueueAttribute", action="act", method='GET',
- waitUrl='wait', waitUrlMethod='POST')
- xml = r.toxml()
-
- # parse twiml XML string with Element Tree and inspect
- # structure
- tree = ET.fromstring(xml)
- self.conf = tree.find("./Enqueue")
-
- def test_conf_text(self):
- self.assertEqual(self.conf.text.strip(), "TestEnqueueAttribute")
-
- def test_conf_waiturl(self):
- self.assertEqual(self.conf.get('waitUrl'), "wait")
-
- def test_conf_method(self):
- self.assertEqual(self.conf.get('method'), "GET")
-
- def test_conf_action(self):
- self.assertEqual(self.conf.get('action'), "act")
-
- def test_conf_waitmethod(self):
- self.assertEqual(self.conf.get('waitUrlMethod'), "POST")
-
-
-class TestDial(TwilioTest):
-
- def testDial(self):
- """ should redirect the call """
- r = Response()
- r.append(twiml.Dial("1231231234"))
- r = self.strip(r)
- assert_equal(r, '1231231234')
-
- def testSip(self):
- """ should redirect the call """
- r = Response()
- d = r.dial()
- d.sip('foo@example.com')
- r = self.strip(r)
- assert_equal(r, 'foo@example.com')
-
- def testSipUsernamePass(self):
- """ should redirect the call """
- r = Response()
- d = r.dial()
- d.sip('foo@example.com', username='foo', password='bar')
- r = self.strip(r)
- assert_equal(r, 'foo@example.com')
-
- def testSipUri(self):
- """ should redirect the call """
- r = Response()
- d = r.dial()
- s = d.sip()
- s.uri('foo@example.com')
- r = self.strip(r)
- assert_equal(r, 'foo@example.com')
-
- def testConvienceMethod(self):
- """ should dial to a url via post """
- r = Response()
- r.addDial()
- r = self.strip(r)
- assert_equal(r, '')
-
- def testAddNumber(self):
- """ add a number to a dial """
- r = Response()
- d = twiml.Dial()
- d.append(twiml.Number("1231231234"))
- r.append(d)
- r = self.strip(r)
- assert_equal(r, '1231231234')
-
- def testAddNumberStatusCallbackEvent(self):
- """ add a number to a dial with status callback events"""
- r = Response()
- d = twiml.Dial()
- d.append(twiml.Number("1231231234", statusCallback="http://example.com", statusCallbackEvent="initiated completed"))
- r.append(d)
- r = self.strip(r)
- assert_equal(r, '1231231234')
-
- def testAddNumberConvenience(self):
- """ add a number to a dial, convience method """
- r = Response()
- d = r.addDial()
- d.addNumber("1231231234")
- r = self.strip(r)
- assert_equal(r, '1231231234')
-
- def testAddNumberConvenienceStatusCallbackEvent(self):
- """ add a number to a dial, convience method """
- r = Response()
- d = r.addDial()
- d.addNumber("1231231234", statusCallback="http://example.com", statusCallbackEvent="initiated completed")
- r = self.strip(r)
- assert_equal(r, '1231231234')
-
- def testAddConference(self):
- """ add a conference to a dial """
- r = Response()
- d = twiml.Dial()
- d.append(twiml.Conference("My Room"))
- r.append(d)
- r = self.strip(r)
- assert_equal(r, 'My Room')
-
- def test_add_queue(self):
- """ add a queue to a dial """
- r = Response()
- d = r.dial()
- d.append(twiml.Queue("The Cute Queue"))
- r = self.strip(r)
- assert_equal(r, 'The Cute Queue')
-
- def test_add_empty_client(self):
- """ add an empty client to a dial """
- r = Response()
- d = r.dial()
- d.client("")
- assert_equal(str(r), '')
-
- def test_add_client(self):
- """ add a client to a dial """
- r = Response()
- d = r.dial()
- d.client("alice")
- assert_equal(str(r), 'alice')
-
- def testAddConferenceConvenceMethod(self):
- """ add a conference to a dial, conviently """
- r = Response()
- d = r.addDial()
- d.addConference("My Room")
- r = self.strip(r)
- assert_equal(r, 'My Room')
-
- def testAddAttribute(self):
- """ add attribute """
- r = twiml.Conference("MyRoom", foo="bar")
- r = self.strip(r)
- assert_equal(r, 'MyRoom')
-
- def testBadAppend(self):
- """ should raise exceptions for wrong appending """
- self.improperAppend(twiml.Conference("Hello"))
-
-
-class TestGather(TwilioTest):
-
- def testEmpty(self):
- """ a gather with nothing inside """
- r = Response()
- r.append(twiml.Gather())
- r = self.strip(r)
- assert_equal(r, '')
-
- def test_context_manager(self):
- with Response() as r:
- with r.gather() as g:
- g.say("Hello")
-
- assert_equal(str(r), 'Hello')
-
- def testNestedSayPlayPause(self):
- """ a gather with a say, play, and pause """
- r = Response()
- g = twiml.Gather()
- g.append(twiml.Say("Hey"))
- g.append(twiml.Play("hey.mp3"))
- g.append(twiml.Pause())
- r.append(g)
- r = self.strip(r)
- assert_equal(r, 'Heyhey.mp3')
-
- def testNestedSayPlayPauseConvience(self):
- """ a gather with a say, play, and pause """
- r = Response()
- g = r.addGather()
- g.addSay("Hey")
- g.addPlay("hey.mp3")
- g.addPause()
- r = self.strip(r)
- assert_equal(r, 'Heyhey.mp3')
-
- def testAddAttribute(self):
- """ add attribute """
- r = twiml.Gather(foo="bar")
- r = self.strip(r)
- assert_equal(r, '')
-
- def testNoDeclaration(self):
- """ add attribute """
- r = twiml.Gather(foo="bar")
- assert_equal(r.toxml(xml_declaration=False), '')
-
- def testImproperNesting(self):
- """ bad nesting """
- verb = twiml.Gather()
- self.assertRaises(TwimlException, verb.append, twiml.Gather())
- self.assertRaises(TwimlException, verb.append, twiml.Record())
- self.assertRaises(TwimlException, verb.append, twiml.Hangup())
- self.assertRaises(TwimlException, verb.append, twiml.Redirect())
- self.assertRaises(TwimlException, verb.append, twiml.Dial())
- self.assertRaises(TwimlException, verb.append, twiml.Conference(""))
- self.assertRaises(TwimlException, verb.append, twiml.Sms(""))
diff --git a/tests/unit/twiml/__init__.py b/tests/unit/twiml/__init__.py
new file mode 100644
index 0000000000..65873e0927
--- /dev/null
+++ b/tests/unit/twiml/__init__.py
@@ -0,0 +1,16 @@
+import unittest
+
+from nose.tools import raises
+from six import text_type
+
+from twilio.twiml import TwiMLException, TwiML
+
+
+class TwilioTest(unittest.TestCase):
+ def strip(self, xml):
+ return text_type(xml)
+
+ @raises(TwiMLException)
+ def test_append_fail(self):
+ t = TwiML()
+ t.append('foobar')
diff --git a/tests/unit/twiml/test_messaging_response.py b/tests/unit/twiml/test_messaging_response.py
new file mode 100644
index 0000000000..79dea317a2
--- /dev/null
+++ b/tests/unit/twiml/test_messaging_response.py
@@ -0,0 +1,78 @@
+from nose.tools import assert_equal
+from tests.unit.twiml import TwilioTest
+from twilio.twiml.messaging_response import MessagingResponse, Body, Media
+
+
+class TestResponse(TwilioTest):
+
+ def test_empty_response(self):
+ r = MessagingResponse()
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_response(self):
+ r = MessagingResponse()
+ r.message('Hello')
+ r.redirect(url='example.com')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello'
+ )
+
+ def test_response_chain(self):
+ r = MessagingResponse().message('Hello').redirect(url='example.com')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello'
+ )
+
+
+class TestMessage(TwilioTest):
+
+ def test_body(self):
+ r = MessagingResponse()
+ r.message('Hello')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello'
+ )
+
+ def test_nested_body(self):
+ b = Body('Hello World')
+
+ r = MessagingResponse()
+ r.append(b)
+
+ assert_equal(
+ self.strip(r),
+ 'Hello World'
+ )
+
+ def test_nested_body_media(self):
+ b = Body('Hello World')
+ m = Media('hey.jpg')
+
+ r = MessagingResponse()
+ r.append(b)
+ r.append(m)
+
+ assert_equal(
+ self.strip(r),
+ 'Hello Worldhey.jpg'
+ )
+
+
+class TestRedirect(TwilioTest):
+ def test_redirect(self):
+ r = MessagingResponse()
+ r.redirect(url='example.com')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
diff --git a/tests/unit/twiml/test_voice_response.py b/tests/unit/twiml/test_voice_response.py
new file mode 100644
index 0000000000..0ebd27ae25
--- /dev/null
+++ b/tests/unit/twiml/test_voice_response.py
@@ -0,0 +1,524 @@
+# -*- coding: utf-8 -*-
+from nose.tools import assert_equal
+from six import u
+from tests.unit.twiml import TwilioTest
+from twilio.twiml.voice_response import VoiceResponse, Dial, Gather
+
+
+class TestResponse(TwilioTest):
+
+ def test_empty_response(self):
+ r = VoiceResponse()
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_response(self):
+ r = VoiceResponse()
+ r.hangup()
+ r.leave()
+ r.sms(
+ 'twilio sms',
+ to='+11234567890',
+ from_='+10987654321'
+ )
+
+ assert_equal(
+ self.strip(r),
+ 'twilio sms'
+ )
+
+ def test_response_chain(self):
+ r = VoiceResponse().hangup().leave().sms(
+ 'twilio sms',
+ to='+11234567890',
+ from_='+10987654321'
+ )
+
+ assert_equal(
+ self.strip(r),
+ 'twilio sms'
+ )
+
+
+class TestSay(TwilioTest):
+
+ def test_empty_say(self):
+ """ should be a say with no text """
+ r = VoiceResponse()
+ r.say('')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_say_hello_world(self):
+ """ should say hello world """
+ r = VoiceResponse()
+ r.say('Hello World')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello World'
+ )
+
+ def test_say_french(self):
+ """ should say hello monkey """
+ r = VoiceResponse()
+ r.say(u('n\xe9cessaire et d\'autres'))
+
+ assert_equal(
+ self.strip(r),
+ 'nécessaire et d\'autres'
+ )
+
+ def test_say_loop(self):
+ """ should say hello monkey and loop 3 times """
+ r = VoiceResponse()
+ r.say('Hello Monkey', loop=3)
+
+ assert_equal(
+ self.strip(r),
+ 'Hello Monkey'
+ )
+
+ def test_say_loop_gb(self):
+ """ should say have a woman say hello monkey and loop 3 times """
+ r = VoiceResponse()
+ r.say('Hello Monkey', language='en-gb')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello Monkey'
+ )
+
+ def test_say_loop_woman(self):
+ """ should say have a woman say hello monkey and loop 3 times """
+ r = VoiceResponse()
+ r.say('Hello Monkey', loop=3, voice='woman')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello Monkey'
+ )
+
+ def test_say_all(self):
+ """ convenience method: should say have a woman say hello monkey and loop 3 times and be in french """
+ r = VoiceResponse()
+ r.say('Hello Monkey', loop=3, voice='man', language='fr')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello Monkey'
+ )
+
+
+class TestPlay(TwilioTest):
+
+ def test_empty_play(self):
+ """ should play hello monkey """
+ r = VoiceResponse()
+ r.play('')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_play_hello(self):
+ """ should play hello monkey """
+ r = VoiceResponse()
+ r.play('http://hellomonkey.mp3')
+
+ assert_equal(
+ self.strip(r),
+ 'http://hellomonkey.mp3'
+ )
+
+ def test_play_hello_loop(self):
+ """ should play hello monkey loop """
+ r = VoiceResponse()
+ r.play('http://hellomonkey.mp3', loop=3)
+
+ assert_equal(
+ self.strip(r),
+ 'http://hellomonkey.mp3'
+ )
+
+ def test_play_digits(self):
+ """ should play digits """
+ r = VoiceResponse()
+ r.play('', digits='w123')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+
+class TestRecord(TwilioTest):
+
+ def test_record_empty(self):
+ """ should record """
+ r = VoiceResponse()
+ r.record()
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_record_action_method(self):
+ """ should record with an action and a get method """
+ r = VoiceResponse()
+ r.record(action='example.com', method='GET')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_record_max_length_finish_timeout(self):
+ """ should record with an maxLength, finishOnKey, and timeout """
+ r = VoiceResponse()
+ r.record(timeout=4, finish_on_key='#', max_length=30)
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_record_transcribe(self):
+ """ should record with a transcribe and transcribeCallback """
+ r = VoiceResponse()
+ r.record(transcribe_callback='example.com')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+
+class TestRedirect(TwilioTest):
+
+ def test_redirect_empty(self):
+ r = VoiceResponse()
+ r.redirect('')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_redirect_method(self):
+ r = VoiceResponse()
+ r.redirect('example.com', method='POST')
+
+ assert_equal(
+ self.strip(r),
+ 'example.com'
+ )
+
+ def test_redirect_method_params(self):
+ r = VoiceResponse()
+ r.redirect('example.com?id=34&action=hey', method='POST')
+
+ assert_equal(
+ self.strip(r),
+ 'example.com?id=34&action=hey'
+ )
+
+
+class TestHangup(TwilioTest):
+
+ def test_hangup(self):
+ """ convenience: should Hangup to a url via POST """
+ r = VoiceResponse()
+ r.hangup()
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+
+class TestLeave(TwilioTest):
+
+ def test_leave(self):
+ """ convenience: should Hangup to a url via POST """
+ r = VoiceResponse()
+ r.leave()
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+
+class TestReject(TwilioTest):
+
+ def test_reject(self):
+ """ should be a Reject with default reason """
+ r = VoiceResponse()
+ r.reject()
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+
+class TestSms(TwilioTest):
+
+ def test_empty(self):
+ """ Test empty sms verb """
+ r = VoiceResponse()
+ r.sms('')
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_body(self):
+ """ Test hello world """
+ r = VoiceResponse()
+ r.sms('Hello, World')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello, World'
+ )
+
+ def test_to_from_action(self):
+ """ Test the to, from, and status callback """
+ r = VoiceResponse()
+ r.sms('Hello, World', to=1231231234, from_=3453453456, status_callback='example.com?id=34&action=hey')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello, World'
+ )
+
+ def test_action_method(self):
+ """ Test the action and method parameters on Sms """
+ r = VoiceResponse()
+ r.sms('Hello', method='POST', action='example.com?id=34&action=hey')
+
+ assert_equal(
+ self.strip(r),
+ 'Hello'
+ )
+
+
+class TestConference(TwilioTest):
+
+ def test_conference(self):
+ d = Dial()
+ d.conference(
+ 'TestConferenceAttributes',
+ beep=False,
+ wait_url='',
+ start_conference_on_enter=True,
+ end_conference_on_exit=True
+ )
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'TestConferenceAttributes'
+ )
+
+
+class TestQueue(TwilioTest):
+
+ def test_queue(self):
+ d = Dial()
+ d.queue('TestQueueAttribute', url='', method='GET')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'TestQueueAttribute'
+ )
+
+
+class TestEnqueue(TwilioTest):
+
+ def test_enqueue(self):
+ r = VoiceResponse()
+ r.enqueue(
+ 'TestEnqueueAttribute',
+ action='act',
+ method='GET',
+ wait_url='wait',
+ wait_url_method='POST'
+ )
+
+ assert_equal(
+ self.strip(r),
+ 'TestEnqueueAttribute'
+ )
+
+
+class TestDial(TwilioTest):
+
+ def test_dial(self):
+ """ should redirect the call """
+ r = VoiceResponse()
+ r.dial("1231231234")
+
+ assert_equal(
+ self.strip(r),
+ '1231231234'
+ )
+
+ def test_sip(self):
+ """ should redirect the call """
+ d = Dial()
+ d.sip('foo@example.com')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'foo@example.com'
+ )
+
+ def test_sip_username_password(self):
+ """ should redirect the call """
+ d = Dial()
+ d.sip('foo@example.com', username='foo', password='bar')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'foo@example.com'
+ )
+
+ def test_add_number(self):
+ """ add a number to a dial """
+ d = Dial()
+ d.number('1231231234')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ '1231231234'
+ )
+
+ def test_add_number_status_callback_event(self):
+ """ add a number to a dial with status callback events"""
+ d = Dial()
+ d.number('1231231234', status_callback='http://example.com', status_callback_event='initiated completed')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ '1231231234'
+ )
+
+ def test_add_conference(self):
+ """ add a conference to a dial """
+ d = Dial()
+ d.conference('My Room')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'My Room'
+ )
+
+ def test_add_queue(self):
+ """ add a queue to a dial """
+ d = Dial()
+ d.queue('The Cute Queue')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'The Cute Queue'
+ )
+
+ def test_add_empty_client(self):
+ """ add an empty client to a dial """
+ d = Dial()
+ d.client('')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_add_client(self):
+ """ add a client to a dial """
+ d = Dial()
+ d.client('alice')
+
+ r = VoiceResponse()
+ r.append(d)
+
+ assert_equal(
+ self.strip(r),
+ 'alice'
+ )
+
+
+class TestGather(TwilioTest):
+
+ def test_empty(self):
+ """ a gather with nothing inside """
+ r = VoiceResponse()
+ r.gather()
+
+ assert_equal(
+ self.strip(r),
+ ''
+ )
+
+ def test_gather_say(self):
+ g = Gather()
+ g.say('Hello')
+
+ r = VoiceResponse()
+ r.append(g)
+
+ assert_equal(
+ self.strip(r),
+ 'Hello'
+ )
+
+ def test_nested_say_play_pause(self):
+ """ a gather with a say, play, and pause """
+ g = Gather()
+ g.say('Hey')
+ g.play('hey.mp3')
+ g.pause()
+
+ r = VoiceResponse()
+ r.append(g)
+
+ assert_equal(
+ self.strip(r),
+ 'Heyhey.mp3'
+ )
diff --git a/twilio/base/exceptions.py b/twilio/base/exceptions.py
index 4f2d1ed0e4..8e2cf20b48 100644
--- a/twilio/base/exceptions.py
+++ b/twilio/base/exceptions.py
@@ -8,10 +8,6 @@ class TwilioException(Exception):
pass
-class TwimlException(Exception):
- pass
-
-
class TwilioRestException(TwilioException):
""" A generic 400 or 500 level exception from the Twilio API
diff --git a/twilio/twiml.py b/twilio/twiml.py
deleted file mode 100644
index d5a233613b..0000000000
--- a/twilio/twiml.py
+++ /dev/null
@@ -1,574 +0,0 @@
-"""
-Make sure to check out the TwiML overview and tutorial at
-https://www.twilio.com/docs/api/twiml
-"""
-import xml.etree.ElementTree as ET
-
-from twilio.base.exceptions import TwimlException
-
-
-class Verb(object):
- """Twilio basic verb object.
- """
- GET = "GET"
- POST = "POST"
- nestables = None
-
- def __init__(self, **kwargs):
- self.name = self.__class__.__name__
- self.body = None
- self.verbs = []
- self.attrs = {}
-
- if kwargs.get("waitMethod", "GET") not in ["GET", "POST"]:
- raise TwimlException("Invalid waitMethod parameter, "
- "must be 'GET' or 'POST'")
-
- if kwargs.get("method", "GET") not in ["GET", "POST"]:
- raise TwimlException("Invalid method parameter, "
- "must be 'GET' or 'POST'")
-
- for k, v in kwargs.items():
- if k == "sender":
- k = "from"
- if v is not None:
- self.attrs[k] = v
-
- def __str__(self):
- return self.toxml()
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- return False
-
- def toxml(self, xml_declaration=True):
- """
- Return the contents of this verb as an XML string
-
- :param bool xml_declaration: Include the XML declaration. Defaults to
- True
- """
- xml = ET.tostring(self.xml()).decode('utf-8')
-
- if xml_declaration:
- return '' + xml
- else:
- return xml
-
- def xml(self):
- el = ET.Element(self.name)
-
- keys = self.attrs.keys()
- keys = sorted(keys)
- for a in keys:
- value = self.attrs[a]
-
- if isinstance(value, bool):
- el.set(a, str(value).lower())
- else:
- el.set(a, str(value))
-
- if self.body:
- el.text = self.body
-
- for verb in self.verbs:
- el.append(verb.xml())
-
- return el
-
- def append(self, verb):
- if not self.nestables or verb.name not in self.nestables:
- raise TwimlException("%s is not nestable inside %s" %
- (verb.name, self.name))
- self.verbs.append(verb)
- return verb
-
-
-class Response(Verb):
- """Twilio response object."""
- nestables = [
- 'Say',
- 'Play',
- 'Gather',
- 'Record',
- 'Dial',
- 'Redirect',
- 'Pause',
- 'Hangup',
- 'Reject',
- 'Sms',
- 'Enqueue',
- 'Leave',
- 'Message',
- ]
-
- def __init__(self, **kwargs):
- """Version: Twilio API version e.g. 2008-08-01 """
- super(Response, self).__init__(**kwargs)
-
- def say(self, text, **kwargs):
- """Return a newly created :class:`Say` verb, nested inside this
- :class:`Response` """
- return self.append(Say(text, **kwargs))
-
- def play(self, url=None, digits=None, **kwargs):
- """Return a newly created :class:`Play` verb, nested inside this
- :class:`Response` """
- return self.append(Play(url=url, digits=digits, **kwargs))
-
- def pause(self, **kwargs):
- """Return a newly created :class:`Pause` verb, nested inside this
- :class:`Response` """
- return self.append(Pause(**kwargs))
-
- def redirect(self, url=None, **kwargs):
- """Return a newly created :class:`Redirect` verb, nested inside this
- :class:`Response` """
- return self.append(Redirect(url, **kwargs))
-
- def hangup(self, **kwargs):
- """Return a newly created :class:`Hangup` verb, nested inside this
- :class:`Response` """
- return self.append(Hangup(**kwargs))
-
- def reject(self, reason=None, **kwargs):
- """Return a newly created :class:`Hangup` verb, nested inside this
- :class:`Response` """
- return self.append(Reject(reason=reason, **kwargs))
-
- def gather(self, **kwargs):
- """Return a newly created :class:`Gather` verb, nested inside this
- :class:`Response` """
- return self.append(Gather(**kwargs))
-
- def dial(self, number=None, **kwargs):
- """Return a newly created :class:`Dial` verb, nested inside this
- :class:`Response` """
- return self.append(Dial(number, **kwargs))
-
- def enqueue(self, name, **kwargs):
- """Return a newly created :class:`Enqueue` verb, nested inside this
- :class:`Response` """
- return self.append(Enqueue(name, **kwargs))
-
- def leave(self, **kwargs):
- """Return a newly created :class:`Leave` verb, nested inside this
- :class:`Response` """
- return self.append(Leave(**kwargs))
-
- def record(self, **kwargs):
- """Return a newly created :class:`Record` verb, nested inside this
- :class:`Response` """
- return self.append(Record(**kwargs))
-
- def sms(self, msg, **kwargs):
- """Return a newly created :class:`Sms` verb, nested inside this
- :class:`Response` """
- return self.append(Sms(msg, **kwargs))
-
- def message(self, msg=None, **kwargs):
- """Return a newly created :class:`Message` verb, nested inside this
- :class:`Response`"""
- return self.append(Message(msg, **kwargs))
-
- # All add* methods are deprecated
- def addSay(self, *args, **kwargs):
- return self.say(*args, **kwargs)
-
- def addPlay(self, *args, **kwargs):
- return self.play(*args, **kwargs)
-
- def addPause(self, *args, **kwargs):
- return self.pause(*args, **kwargs)
-
- def addRedirect(self, *args, **kwargs):
- return self.redirect(*args, **kwargs)
-
- def addHangup(self, *args, **kwargs):
- return self.hangup(*args, **kwargs)
-
- def addReject(self, *args, **kwargs):
- return self.reject(*args, **kwargs)
-
- def addGather(self, *args, **kwargs):
- return self.gather(*args, **kwargs)
-
- def addDial(self, *args, **kwargs):
- return self.dial(*args, **kwargs)
-
- def addRecord(self, *args, **kwargs):
- return self.record(*args, **kwargs)
-
- def addSms(self, *args, **kwargs):
- return self.sms(*args, **kwargs)
-
-
-class Say(Verb):
- """The :class:`Say` verb converts text to speech that is read back to the
- caller.
-
- :param voice: allows you to choose a male or female voice to read text
- back.
-
- :param language: allows you pick a voice with a specific language's accent
- and pronunciations. Twilio currently supports languages
- 'en' (English), 'es' (Spanish), 'fr' (French), and 'de'
- (German), 'en-gb' (English Great Britain").
-
- :param loop: specifies how many times you'd like the text repeated.
- Specifying '0' will cause the the :class:`Say` verb to loop
- until the call is hung up. Defaults to 1.
- """
- MAN = 'man'
- WOMAN = 'woman'
-
- ENGLISH = 'en'
- BRITISH = 'en-gb'
- SPANISH = 'es'
- FRENCH = 'fr'
- GERMAN = 'de'
-
- def __init__(self, text, **kwargs):
- super(Say, self).__init__(**kwargs)
- self.body = text
-
-
-class Play(Verb):
- """Play DTMF digits or audio from a URL.
-
- :param str url: point to an audio file. The MIME type on the file must be
- set correctly. At least one of `url` and `digits` must be
- specified. If both are given, the digits will play prior
- to the audio from the URL.
-
- :param str digits: a string of digits to play. To pause before playing
- digits, use leading 'w' characters. Each 'w' will cause
- Twilio to wait 0.5 seconds instead of playing a digit.
- At least one of `url` and `digits` must be specified.
- If both are given, the digits will play first.
-
- :param int loop: specifies how many times you'd like the text repeated.
- Specifying '0' will cause the the :class:`Play` verb to loop
- until the call is hung up. Defaults to 1.
- """
- def __init__(self, url=None, digits=None, **kwargs):
- if url is None and digits is None:
- raise TwimlException(
- "Please specify either a url or digits to play.",
- )
-
- if digits is not None:
- kwargs['digits'] = digits
- super(Play, self).__init__(**kwargs)
- if url is not None:
- self.body = url
-
-
-class Pause(Verb):
- """Pause the call
-
- :param length: specifies how many seconds Twilio will wait silently before
- continuing on.
- """
-
-
-class Redirect(Verb):
- """Redirect call flow to another URL
-
- :param url: specifies the url which Twilio should query to retrieve new
- TwiML. The default is the current url
-
- :param method: specifies the HTTP method to use when retrieving the url
- """
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, url="", **kwargs):
- super(Redirect, self).__init__(**kwargs)
- self.body = url
-
-
-class Hangup(Verb):
- """Hangup the call
- """
-
-
-class Reject(Verb):
- """Hangup the call
-
- :param reason: not sure
- """
-
-
-class Gather(Verb):
- """Gather digits from the caller's keypad
-
- :param action: URL to which the digits entered will be sent
- :param method: submit to 'action' url using GET or POST
- :param numDigits: how many digits to gather before returning
- :param timeout: wait for this many seconds before returning
- :param finishOnKey: key that triggers the end of caller input
- """
- GET = 'GET'
- POST = 'POST'
- nestables = ['Say', 'Play', 'Pause']
-
- def __init__(self, **kwargs):
- super(Gather, self).__init__(**kwargs)
-
- def say(self, text, **kwargs):
- return self.append(Say(text, **kwargs))
-
- def play(self, url, **kwargs):
- return self.append(Play(url, **kwargs))
-
- def pause(self, **kwargs):
- return self.append(Pause(**kwargs))
-
- def addSay(self, *args, **kwargs):
- return self.say(*args, **kwargs)
-
- def addPlay(self, *args, **kwargs):
- return self.play(*args, **kwargs)
-
- def addPause(self, *args, **kwargs):
- return self.pause(*args, **kwargs)
-
-
-class Number(Verb):
- """Specify phone number in a nested Dial element.
-
- :param number: phone number to dial
- :param sendDigits: key to press after connecting to the number
- """
- def __init__(self, number, **kwargs):
- super(Number, self).__init__(**kwargs)
- self.body = number
-
-
-class Client(Verb):
- """Specify a client name to call in a nested Dial element.
-
- :param name: Client name to connect to
- """
- def __init__(self, name, **kwargs):
- super(Client, self).__init__(**kwargs)
- self.body = name
-
-
-class Sms(Verb):
- """ Send a Sms Message to a phone number
-
- :param to: whom to send message to
- :param sender: whom to send message from.
- :param action: url to request after the message is queued
- :param method: submit to 'action' url using GET or POST
- :param statusCallback: url to hit when the message is actually sent
- """
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, msg, **kwargs):
- super(Sms, self).__init__(**kwargs)
- self.body = msg
-
-
-class Message(Verb):
- """ Send an MMS Message to a phone number.
-
- :param to: whom to send message to
- :param sender: whom to send message from.
- :param action: url to request after the message is queued
- :param method: submit to 'action' url using GET or POST
- :param statusCallback: url to hit when the message is actually sent
- """
-
- GET = 'GET'
- POST = 'POST'
-
- nestables = ['Media', 'Body']
-
- def __init__(self, msg=None, **kwargs):
- super(Message, self).__init__(**kwargs)
- if msg is not None:
- self.append(Body(msg))
-
- def media(self, media_url, **kwargs):
- return self.append(Media(media_url, **kwargs))
-
-
-class Body(Verb):
- """ Specify a text body for a Message.
-
- :param msg: the text to use in the body.
- """
-
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, msg, **kwargs):
- super(Body, self).__init__(**kwargs)
- self.body = msg
-
-
-class Media(Verb):
- """Specify media to include in a Message.
-
- :param url: The URL of the media to include.
- """
-
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, url, **kwargs):
- super(Media, self).__init__(**kwargs)
- self.body = url
-
-
-class Conference(Verb):
- """Specify conference in a nested Dial element.
-
- :param name: friendly name of conference
- :param bool muted: keep this participant muted
- :param bool beep: play a beep when this participant enters/leaves
- :param bool startConferenceOnEnter: start conf when this participants joins
- :param bool endConferenceOnExit: end conf when this participants leaves
- :param waitUrl: TwiML url that executes before conference starts
- :param waitMethod: HTTP method for waitUrl GET/POST
- """
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, name, **kwargs):
- super(Conference, self).__init__(**kwargs)
- self.body = name
-
-
-class Dial(Verb):
- """Dial another phone number and connect it to this call
-
- :param action: submit the result of the dial to this URL
- :param method: submit to 'action' url using GET or POST
- :param int timeout: The number of seconds to waits for the called
- party to answer the call
- :param bool hangupOnStar: Allow the calling party to hang up on the
- called party by pressing the '*' key
- :param int timeLimit: The maximum duration of the Call in seconds
- :param callerId: The caller ID that will appear to the called party
- :param bool record: Record both legs of a call within this
- """
- GET = 'GET'
- POST = 'POST'
- nestables = ['Number', 'Conference', 'Client', 'Queue', 'Sip']
-
- def __init__(self, number=None, **kwargs):
- super(Dial, self).__init__(**kwargs)
- if number and len(number.split(',')) > 1:
- for n in number.split(','):
- self.append(Number(n.strip()))
- else:
- self.body = number
-
- def client(self, name, **kwargs):
- return self.append(Client(name, **kwargs))
-
- def number(self, number, **kwargs):
- return self.append(Number(number, **kwargs))
-
- def conference(self, name, **kwargs):
- return self.append(Conference(name, **kwargs))
-
- def queue(self, name, **kwargs):
- return self.append(Queue(name, **kwargs))
-
- def sip(self, sip_address=None, **kwargs):
- return self.append(Sip(sip_address, **kwargs))
-
- def addNumber(self, *args, **kwargs):
- return self.number(*args, **kwargs)
-
- def addConference(self, *args, **kwargs):
- return self.conference(*args, **kwargs)
-
-
-class Queue(Verb):
- """Specify queue in a nested Dial element.
-
- :param name: friendly name for the queue
- :param url: url to a twiml document that executes after a call is dequeued
- and before the call is connected
- :param method: HTTP method for url GET/POST
- """
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, name, **kwargs):
- super(Queue, self).__init__(**kwargs)
- self.body = name
-
-
-class Enqueue(Verb):
- """Enqueue the call into a specific queue.
-
- :param name: friendly name for the queue
- :param action: url to a twiml document that executes when the call
- leaves the queue. When dequeued via a verb,
- this url is executed after the bridged parties disconnect
- :param method: HTTP method for action GET/POST
- :param waitUrl: url to a twiml document that executes
- while the call is on the queue
- :param waitUrlMethod: HTTP method for waitUrl GET/POST
- """
- GET = 'GET'
- POST = 'POST'
-
- def __init__(self, name, **kwargs):
- super(Enqueue, self).__init__(**kwargs)
- self.body = name
-
-
-class Leave(Verb):
- """Signals the call to leave its queue
- """
- GET = 'GET'
- POST = 'POST'
-
-
-class Record(Verb):
- """Record audio from caller
-
- :param action: submit the result of the dial to this URL
- :param method: submit to 'action' url using GET or POST
- :param maxLength: maximum number of seconds to record
- :param timeout: seconds of silence before considering the recording done
- """
- GET = 'GET'
- POST = 'POST'
-
-
-class Sip(Verb):
- """Dial out to a SIP endpoint
-
- :param url: call screening URL none
- :param method: call screening method POST
- :param username: Username for SIP authentication
- :param password: Password for SIP authentication
- """
- nestables = ['Headers', 'Uri']
-
- def __init__(self, sip_address=None, **kwargs):
- super(Sip, self).__init__(**kwargs)
- if sip_address:
- self.body = sip_address
-
- def uri(self, uri, **kwargs):
- return self.append(Uri(uri, **kwargs))
-
-
-class Uri(Verb):
- """A uniform resource indentifier"""
- def __init__(self, uri, **kwargs):
- super(Uri, self).__init__(**kwargs)
- self.body = uri
diff --git a/twilio/twiml/__init__.py b/twilio/twiml/__init__.py
new file mode 100644
index 0000000000..68b3892f00
--- /dev/null
+++ b/twilio/twiml/__init__.py
@@ -0,0 +1,92 @@
+import xml.etree.ElementTree as ET
+
+
+def lower_camel(string):
+ result = "".join([x.title() for x in string.split('_')])
+ if not result:
+ return result
+
+ return result[0].lower() + result[1:]
+
+
+class TwiMLException(Exception):
+ pass
+
+
+class TwiML(object):
+ """
+ Twilio basic verb object.
+ """
+ MAP = {
+ 'from_': 'from'
+ }
+
+ def __init__(self, **kwargs):
+ self.name = self.__class__.__name__
+ self.body = None
+ self.verbs = []
+ self.attrs = {}
+
+ for k, v in kwargs.items():
+ if v is not None:
+ self.attrs[lower_camel(self.MAP.get(k, k))] = v
+
+ def __str__(self):
+ return self.to_xml()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ return False
+
+ def to_xml(self, xml_declaration=True):
+ """
+ Return the contents of this verb as an XML string
+
+ :param bool xml_declaration: Include the XML declaration. Defaults to
+ True
+ """
+ xml = ET.tostring(self.xml()).decode('utf-8')
+
+ if xml_declaration:
+ return '' + xml
+ else:
+ return xml
+
+ def append(self, verb):
+ """
+ Add a TwiML doc
+ :param verb: TwiML Document
+ :return:
+ """
+ if not isinstance(verb, TwiML):
+ raise TwiMLException('Only appending of TwiML is allowed')
+
+ self.verbs.append(verb)
+ return self
+
+ def xml(self):
+ """
+ Convert to XML
+ :return: Generated TwiML
+ """
+ el = ET.Element(self.name)
+
+ keys = self.attrs.keys()
+ keys = sorted(keys)
+ for a in keys:
+ value = self.attrs[a]
+
+ if isinstance(value, bool):
+ el.set(a, str(value).lower())
+ else:
+ el.set(a, str(value))
+
+ if self.body:
+ el.text = self.body
+
+ for verb in self.verbs:
+ el.append(verb.xml())
+
+ return el
diff --git a/twilio/twiml/messaging_response.py b/twilio/twiml/messaging_response.py
new file mode 100644
index 0000000000..388476f130
--- /dev/null
+++ b/twilio/twiml/messaging_response.py
@@ -0,0 +1,128 @@
+from twilio.twiml import TwiML
+
+
+class MessagingResponse(TwiML):
+ """
+ Messaging TwiML Response
+ """
+ def __init__(self):
+ """
+ Create a new
+ """
+ super(MessagingResponse, self).__init__()
+ self.name = 'Response'
+
+ def message(self,
+ body,
+ to=None,
+ from_=None,
+ method=None,
+ action=None,
+ status_callback=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param body: body of the message
+ :param to: number to send to
+ :param from_: number to send from
+ :param method: action HTTP method
+ :param action: action URL
+ :param status_callback: callback URL
+ :param kwargs: other attributes
+ :return: element
+ """
+ return self.append(Message(
+ body=body,
+ to=to,
+ from_=from_,
+ method=method,
+ action=action,
+ status_callback=status_callback,
+ **kwargs
+ ))
+
+ def redirect(self, method=None, url=None, **kwargs):
+ """
+ Add a element
+
+ :param method: HTTP method
+ :param url: URL to redirect to
+ :param kwargs: other attributes
+ :return: element
+ """
+ return self.append(Redirect(
+ method=method,
+ url=url,
+ **kwargs
+ ))
+
+
+class Message(TwiML):
+ """
+ element
+ """
+ def __init__(self, body=None, **kwargs):
+ """
+ Create a new element
+
+ :param body: body of message
+ :param kwargs: other attributes
+ """
+ super(Message, self).__init__(**kwargs)
+ if body:
+ self.body = body
+
+ def body(self, body):
+ """
+ Add a element
+
+ :param body: body of message
+ :return: element
+ """
+ return self.append(Body(body))
+
+ def media(self, url):
+ """
+ Add a element
+
+ :param url: media URL
+ :return: element
+ """
+ return self.append(Media(url))
+
+
+class Body(TwiML):
+ """
+ element
+ """
+ def __init__(self, body):
+ """
+ Create a new element
+
+ :param body: message body
+ """
+ super(Body, self).__init__()
+ self.body = body
+
+
+class Media(TwiML):
+ """
+ element
+ """
+ def __init__(self, url):
+ """
+ Create a new element
+
+ :param url: media URL location
+ """
+ super(Media, self).__init__()
+ self.body = url
+
+
+class Redirect(TwiML):
+ """
+ element
+ """
+ pass
+
diff --git a/twilio/twiml/voice_response.py b/twilio/twiml/voice_response.py
new file mode 100644
index 0000000000..bc2c62ccdd
--- /dev/null
+++ b/twilio/twiml/voice_response.py
@@ -0,0 +1,732 @@
+from twilio.twiml import TwiML
+
+
+class VoiceResponse(TwiML):
+ """
+ Voice TwiML Response
+ """
+ def __init__(self):
+ """
+ Create a new
+ """
+ super(VoiceResponse, self).__init__()
+ self.name = 'Response'
+
+ def dial(self,
+ number,
+ action=None,
+ method=None,
+ timeout=None,
+ hangup_on_star=None,
+ time_limit=None,
+ caller_id=None,
+ record=None,
+ trim=None,
+ recording_status_callback=None,
+ recording_status_callback_method=None,
+ **kwargs):
+ """
+ Create a element
+
+ :param number: phone number to dial
+ :param action: action URL
+ :param method: action HTTP method
+ :param timeout: time to wait for answer
+ :param hangup_on_star: hangup call on * press
+ :param time_limit: max time length
+ :param caller_id: caller ID to display
+ :param record: record the call
+ :param trim: trim the recording
+ :param recording_status_callback: status callback URL
+ :param recording_status_callback_method: status callback URL method
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Dial(
+ number=number,
+ action=action,
+ method=method,
+ timeout=timeout,
+ hangup_on_star=hangup_on_star,
+ time_limit=time_limit,
+ caller_id=caller_id,
+ record=record,
+ trim=trim,
+ recording_status_callback=recording_status_callback,
+ recording_status_callback_method=recording_status_callback_method,
+ **kwargs
+ ))
+
+ def enqueue(self,
+ name,
+ action=None,
+ method=None,
+ wait_url=None,
+ wait_url_method=None,
+ workflow_sid=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param name: friendly name
+ :param action: action URL
+ :param method: action URL method
+ :param wait_url: wait URL
+ :param wait_url_method: wait URL method
+ :param workflow_sid: TaskRouter workflow SID
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Enqueue(
+ name,
+ action=action,
+ method=method,
+ wait_url=wait_url,
+ wait_url_method=wait_url_method,
+ workflow_sid=workflow_sid,
+ **kwargs
+ ))
+
+ def gather(self,
+ action=None,
+ method=None,
+ timeout=None,
+ finish_on_key=None,
+ num_digits=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param action: action URL
+ :param method: action URL method
+ :param timeout: time to wait while gathering input
+ :param finish_on_key: finish on key press
+ :param num_digits: digits to collect
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Gather(
+ action=action,
+ method=method,
+ timeout=timeout,
+ finish_on_key=finish_on_key,
+ num_digits=num_digits,
+ ))
+
+ def hangup(self):
+ """
+ Add a new element
+
+ :return: element
+ """
+ return self.append(Hangup())
+
+ def leave(self):
+ """
+ Add a new element
+
+ :return: element
+ """
+ return self.append(Leave())
+
+ def pause(self, length=None):
+ """
+ Add a new element
+
+ :param length: time in seconds to pause
+ :return: element
+ """
+ return self.append(Pause(length=length))
+
+ def play(self,
+ url,
+ loop=None,
+ digits=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param url: url to play
+ :param loop: times to loop
+ :param digits: play DTMF tones during a call
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Play(
+ url,
+ loop=loop,
+ digits=digits,
+ **kwargs
+ ))
+
+ def record(self,
+ action=None,
+ method=None,
+ timeout=None,
+ finish_on_key=None,
+ max_length=None,
+ play_beep=None,
+ trim=None,
+ recording_status_callback=None,
+ recording_status_callback_method=None,
+ transcribe=None,
+ transcribe_callback=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param action: action URL
+ :param method: action URL method
+ :param timeout: timeout for recording
+ :param finish_on_key: finish recording on key
+ :param max_length: max length to record
+ :param play_beep: play beep
+ :param trim: trim the recording
+ :param recording_status_callback: status callback for the recordings
+ :param recording_status_callback_method: status callback method
+ :param transcribe: transcribe the recording
+ :param transcribe_callback: transcribe callback URL
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Record(
+ action=action,
+ method=method,
+ timeout=timeout,
+ finish_on_key=finish_on_key,
+ max_length=max_length,
+ play_beep=play_beep,
+ trim=trim,
+ recording_status_callback=recording_status_callback,
+ recording_status_callback_method=recording_status_callback_method,
+ transcribe=transcribe,
+ transcribe_callback=transcribe_callback,
+ **kwargs
+ ))
+
+ def redirect(self, url, method=None, **kwargs):
+ """
+ Add a element
+
+ :param url: redirect url
+ :param method: redirect method
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Redirect(url, method=method, **kwargs))
+
+ def reject(self, reason=None, **kwargs):
+ """
+ Add a element
+
+ :param reason: rejection reason
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Reject(reason=reason, **kwargs))
+
+ def say(self,
+ body,
+ loop=None,
+ language=None,
+ voice=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param body: message body
+ :param loop: times to loop
+ :param language: language of message
+ :param voice: voice to use
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Say(
+ body,
+ loop=loop,
+ language=language,
+ voice=voice,
+ **kwargs
+ ))
+
+ def sms(self,
+ body,
+ to=None,
+ from_=None,
+ method=None,
+ action=None,
+ status_callback=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param body: body of message
+ :param to: to phone number
+ :param from_: from phone number
+ :param method: action URL method
+ :param action: action URL
+ :param status_callback: status callback URL
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Sms(
+ body,
+ to=to,
+ from_=from_,
+ method=method,
+ action=action,
+ status_callback=status_callback,
+ **kwargs
+ ))
+
+
+class Dial(TwiML):
+ """
+ element
+ """
+ def __init__(self, number=None, **kwargs):
+ """
+ Create a new element
+
+ :param number: phone number to dial
+ :param kwargs: additional attributes
+ """
+ super(Dial, self).__init__(**kwargs)
+ if number:
+ self.body = number
+
+ def client(self,
+ name,
+ method=None,
+ url=None,
+ status_callback_event=None,
+ status_callback_method=None,
+ status_callback=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param name: name of client
+ :param method: action URL method
+ :param url: action URL
+ :param status_callback_event: events to call status callback
+ :param status_callback_method: status callback URL method
+ :param status_callback: status callback URL
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Client(
+ name,
+ method=method,
+ url=url,
+ status_callback_event=status_callback_event,
+ status_callback_method=status_callback_method,
+ status_callback=status_callback,
+ **kwargs
+ ))
+
+ def conference(self,
+ name,
+ muted=None,
+ start_conference_on_enter=None,
+ end_conference_on_exit=None,
+ max_participants=None,
+ beep=None,
+ record=None,
+ trim=None,
+ wait_method=None,
+ wait_url=None,
+ event_callback_url=None,
+ status_callback_event=None,
+ status_callback=None,
+ status_callback_method=None,
+ recording_status_callback=None,
+ recording_status_callback_method=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param name: name of conference
+ :param muted: join the conference muted
+ :param start_conference_on_enter: start the conference on enter
+ :param end_conference_on_exit: end the conference on exit
+ :param max_participants: max number of people in conference
+ :param beep: play beep when joining
+ :param record: record the conference
+ :param trim: trim the recording
+ :param wait_method: wait URL method
+ :param wait_url: wait URL to play
+ :param event_callback_url: event callback URL
+ :param status_callback_event: events to call status callback
+ :param status_callback: status callback URL
+ :param status_callback_method: status callback URL method
+ :param recording_status_callback: recording status callback URL
+ :param recording_status_callback_method: recording status callback URL method
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Conference(
+ name,
+ start_conference_on_enter=start_conference_on_enter,
+ end_conference_on_exit=end_conference_on_exit,
+ max_participants=max_participants,
+ beep=beep,
+ record=record,
+ trim=trim,
+ wait_method=wait_method,
+ wait_url=wait_url,
+ event_callback_url=event_callback_url,
+ status_callback_event=status_callback_event,
+ status_callback=status_callback,
+ status_callback_method=status_callback_method,
+ recording_status_callback=recording_status_callback,
+ recording_status_callback_method=recording_status_callback_method,
+ **kwargs
+ ))
+
+ def number(self,
+ number,
+ send_digits=None,
+ url=None,
+ method=None,
+ status_callback_event=None,
+ status_callback=None,
+ status_callback_method=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param number: phone number to dial
+ :param send_digits: play DTMF tones when the call is answered
+ :param url: TwiML URL
+ :param method: TwiML URL method
+ :param status_callback_event: events to call status callback
+ :param status_callback: status callback URL
+ :param status_callback_method: status callback URL method
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Number(
+ number,
+ send_digits=send_digits,
+ url=url,
+ method=method,
+ status_callback_event=status_callback_event,
+ status_callback=status_callback,
+ status_callback_method=status_callback_method,
+ **kwargs
+ ))
+
+ def queue(self,
+ queue_name,
+ url=None,
+ method=None,
+ reservation_sid=None,
+ post_work_activity_sid=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param queue_name: queue name
+ :param url: action URL
+ :param method: action URL method
+ :param reservation_sid: TaskRouter reservation SID
+ :param post_work_activity_sid: TaskRouter activity SID
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Queue(
+ queue_name,
+ url=url,
+ method=method,
+ reservation_sid=reservation_sid,
+ post_work_activity_sid=post_work_activity_sid,
+ **kwargs
+ ))
+
+ def sip(self,
+ uri,
+ username=None,
+ password=None,
+ url=None,
+ method=None,
+ status_callback_event=None,
+ status_callback=None,
+ status_callback_method=None,
+ **kwargs):
+ """
+ Add a element
+
+ :param uri: sip url
+ :param username: sip username
+ :param password: sip password
+ :param url: action URL
+ :param method: action URL method
+ :param status_callback_event: events to call status callback
+ :param status_callback: status callback URL
+ :param status_callback_method: status callback URL method
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Sip(
+ uri,
+ username=username,
+ password=password,
+ url=url,
+ method=method,
+ status_callback_event=status_callback_event,
+ status_callback=status_callback,
+ status_callback_method=status_callback_method,
+ **kwargs
+ ))
+
+
+class Client(TwiML):
+ """
+ element
+ """
+ def __init__(self, name, **kwargs):
+ """
+ Create a new element
+
+ :param name: name of client
+ :param kwargs: attributes
+ """
+ super(Client, self).__init__(**kwargs)
+ self.body = name
+
+
+class Conference(TwiML):
+ """
+ element
+ """
+ def __init__(self, name, **kwargs):
+ """
+ Create a new element
+
+ :param name: name of conference
+ :param kwargs: attributes
+ """
+ super(Conference, self).__init__(**kwargs)
+ self.body = name
+
+
+class Number(TwiML):
+ """
+ element
+ """
+ def __init__(self, number, **kwargs):
+ """
+ Create a new element
+
+ :param number: phone number
+ :param kwargs: attributes
+ """
+ super(Number, self).__init__(**kwargs)
+ self.body = number
+
+
+class Queue(TwiML):
+ """
+ element
+ """
+ def __init__(self, queue_name, **kwargs):
+ """
+ Create a new element
+
+ :param queue_name: name of queue
+ :param kwargs: attributes
+ """
+ super(Queue, self).__init__(**kwargs)
+ self.body = queue_name
+
+
+class Sip(TwiML):
+ """
+ element
+ """
+ def __init__(self, uri, **kwargs):
+ """
+ Create a new element
+
+ :param uri: sip url
+ :param kwargs: attributes
+ """
+ super(Sip, self).__init__(**kwargs)
+ self.body = uri
+
+
+class Enqueue(TwiML):
+ """
+ element
+ """
+ def __init__(self, name, **kwargs):
+ """
+ Create a new element
+
+ :param name: queue name
+ :param kwargs: attributes
+ """
+ super(Enqueue, self).__init__(**kwargs)
+ self.body = name
+
+
+class Gather(TwiML):
+ """
+ element
+ """
+ def __init__(self, **kwargs):
+ """
+ Create a new element
+ :param kwargs: attributes
+ """
+ super(Gather, self).__init__(**kwargs)
+
+ def say(self,
+ body,
+ loop=None,
+ language=None,
+ voice=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param body: message body
+ :param loop: times to loop
+ :param language: message language
+ :param voice: voice to use
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Say(
+ body,
+ loop=loop,
+ language=language,
+ voice=voice,
+ **kwargs
+ ))
+
+ def play(self,
+ url,
+ loop=None,
+ digits=None,
+ **kwargs):
+ """
+ Add a new element
+
+ :param url: media URL
+ :param loop: times to loop
+ :param digits: digits to simulate
+ :param kwargs: additional attributes
+ :return: element
+ """
+ return self.append(Play(
+ url,
+ loop=loop,
+ digits=digits,
+ **kwargs
+ ))
+
+ def pause(self, length=None):
+ """
+ Add a new element
+
+ :param length: time to pause
+ :return: element
+ """
+ return self.append(Pause(length=length))
+
+
+class Pause(TwiML):
+ """
+ element
+ """
+ pass
+
+
+class Play(TwiML):
+ """
+ element
+ """
+ def __init__(self, url, **kwargs):
+ """
+ Create a new element
+
+ :param url: media URL
+ :param kwargs: additional attributes
+ """
+ super(Play, self).__init__(**kwargs)
+ self.body = url
+
+
+class Say(TwiML):
+ """
+ element
+ """
+ def __init__(self, body, **kwargs):
+ """
+ Create a new element
+
+ :param body: message body
+ :param kwargs: attributes
+ """
+ super(Say, self).__init__(**kwargs)
+ self.body = body
+
+
+class Hangup(TwiML):
+ """
+ element
+ """
+ pass
+
+
+class Leave(TwiML):
+ """
+ element
+ """
+ pass
+
+
+class Record(TwiML):
+ """
+ element
+ """
+ pass
+
+
+class Redirect(TwiML):
+ """
+ element
+ """
+ def __init__(self, url, **kwargs):
+ """
+ Create a new element
+
+ :param url: TwiML URL
+ :param kwargs: attributes
+ """
+ super(Redirect, self).__init__(**kwargs)
+ self.body = url
+
+
+class Reject(TwiML):
+ """
+ element
+ """
+ pass
+
+
+class Sms(TwiML):
+ """
+ element
+ """
+ def __init__(self, body, **kwargs):
+ """
+ Create a new element
+
+ :param body: message body
+ :param kwargs: attributes
+ """
+ super(Sms, self).__init__(**kwargs)
+ self.body = body