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