Skip to content
Browse files

TelML Mostly working

  • Loading branch information...
1 parent 35c6a33 commit 9ca5bb1d4413b7011d5115dc9a02a1aac3626c07 @mattwilliamson mattwilliamson committed Jun 1, 2012
Showing with 287 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +27 −0 setup.py
  3. +1 −0 telapi/__init__.py
  4. +3 −0 telapi/rest/__init__.py
  5. +127 −0 telapi/telml/__init__.py
  6. +128 −0 tests/test_telml.py
View
1 .gitignore
@@ -25,3 +25,4 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
+ENV
View
27 setup.py
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+import os
+
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+ name = "telapi",
+ version = "0.0.1",
+ description = "TelAPI REST API client and TelML generator",
+ author = "TelAPI",
+ author_email = "help@telapi.com",
+ url = "https://github.com/teltechsystems/telapi-python/",
+ keywords = ["telapi", "telml"],
+ install_requires = ["requests"],
+ packages = find_packages(),
+ classifiers = [
+ "Programming Language :: Python",
+ "Operating System :: OS Independent",
+ "License :: OSI Approved :: MIT License",
+ "Intended Audience :: Developers",
+ "Development Status :: 3 - Alpha",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Communications :: Telephony"
+ ],
+ long_description = read('README.md'),
+)
View
1 telapi/__init__.py
@@ -0,0 +1 @@
+__all__ = ['rest', 'telml']
View
3 telapi/rest/__init__.py
@@ -0,0 +1,3 @@
+class Client(object):
+ """TelAPI REST Client"""
+ pass
View
127 telapi/telml/__init__.py
@@ -0,0 +1,127 @@
+from xml.sax.saxutils import escape
+
+class Response(object):
+ """Root XML element. Also, base class for all other TelML elements"""
+
+ _allowed_attributes = []
+ _allowed_children = ['Say', 'Play', 'Gather', 'Record', 'Dial']
+ _requires_body = False
+
+ def __init__(self, *args, **kwargs):
+ object.__init__(self)
+ self._element_name = self.__class__.__name__.split('.')[-1]
+ self._body = ''
+ self._attributes = {}
+ self._children = []
+
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+
+ def __str__(self):
+ return str(unicode(self))
+
+ def __unicode__(self):
+ if self._requires_body and not unicode(self._body).strip():
+ raise ValueError('Non-empty "body" property required for the "%s" element!' % self._element_name)
+
+ attribute_string = ''
+ body_string = ''.join([unicode(child) for child in self._children]) or self._body
+
+ if len(self._attributes):
+ attribute_string = ' ' + ' '.join(['%s="%s"' % (escape(unicode(k)), escape(unicode(v))) for k, v in self._attributes.items()])
+
+ return u"<%s%s>%s</%s>" % (self._element_name, attribute_string, body_string, self._element_name)
+
+ def _ensure_attribute(self, name):
+ if name not in self._allowed_attributes:
+ raise AttributeError('"%s" is not a valid attribute of the "%s" element!' % (name, self._element_name))
+
+ def _ensure_child(self, name):
+ if name not in self._allowed_children:
+ raise TypeError('"%s" is not a valid child element of the "%s" element!' % (name, self._element_name))
+
+ def __setattr__(self, name, value):
+ if name.startswith('_'):
+ object.__setattr__(self, name, value)
+ return
+
+ if name == 'body':
+ self._body = value
+
+ self._ensure_attribute(name)
+ self._attributes[name] = value
+
+ def __getattr__(self, name):
+ if name == 'body':
+ return self._body
+
+ self._ensure_attribute(name)
+
+ if name in self._attributes:
+ return self._attributes
+ else:
+ return None
+
+ def __delattr__(self, name):
+ self._ensure_attribute(name)
+
+ if name in self._attributes:
+ del self._attributes
+
+ def append(self, child):
+ self._ensure_child(child._element_name)
+ self._children.append(child)
+
+class Say(Response):
+ _allowed_children = []
+ _allowed_attributes = ['voice', 'loop']
+ _requires_body = True
+
+ def __init__(self, body, *args, **kwargs):
+ Response.__init__(self, *args, **kwargs)
+ self._body = body
+
+class Play(Say):
+ _allowed_attributes = ['loop']
+
+class Pause(Response):
+ _allowed_children = []
+ _allowed_attributes = ['length']
+
+class Gather(Response):
+ _allowed_children = ['Say', 'Play', 'Pause']
+ _allowed_attributes = ['action', 'method', 'timeout', 'finishOnKey', 'numDigits']
+
+ def __init__(self, *args, **kwargs):
+ # TODO: Check if a a child element is required or if gather can sit by itself
+ Response.__init__(self, *args, **kwargs)
+
+ for child in args:
+ self.append(child)
+
+class Record(Response):
+ _allowed_children = []
+ _allowed_attributes = ['action', 'method', 'timeout', 'finishOnKey', 'transcribe', 'transcribeCallback', 'playBeep', 'bothLegs', 'fileFormat']
+
+class Number(Say):
+ _allowed_attributes = ['sendDigits', 'method', 'url']
+
+class Conference(Say):
+ _allowed_attributes = ['muted', 'beep', 'startConferenceOnEnter', 'endConferenceOnExit', 'maxParticipants', 'waitUrl', 'waitMethod',
+ 'hangupOnStar', 'callbackUrl', 'method', 'waitSound', 'waitSoundMethod', 'digitsMatch', 'stayAlone']
+
+class Dial(Response):
+ def __init__(self, body='', *args, **kwargs):
+ Response.__init__(self, *args, **kwargs)
+
+ if isinstance(body, Number) or isinstance(body, Conference):
+ self._children.append(body)
+ self._body = True
+ else:
+ self._body = body
+
+ _requires_body = True
+ _allowed_attributes = ['action', 'method', 'timeout', 'hangupOnStar', 'timeLimit', 'callerId', 'hideCallerId', 'callerName', 'dialMusic',
+ 'callbackUrl', 'callbackMethod', 'confirmSound', 'digitsMatch', 'straightToVm', 'heartbeatUrl', 'heartbeatMethod', 'forwardedFrom']
+ _allowed_children = ['Number', 'Conference']
+
View
128 tests/test_telml.py
@@ -0,0 +1,128 @@
+import unittest
+import telapi.telml as tml
+
+class TestAllTelml(unittest.TestCase):
+ def setUp(self):
+ self.response = tml.Response()
+
+
+
+ # Response
+ def test_response(self):
+ self.assertEqual(str(self.response), '<Response></Response>')
+
+
+
+ # Say
+ def test_say_no_body(self):
+ with self.assertRaises(TypeError):
+ tml.Say()
+
+ def test_bad_child(self):
+ with self.assertRaises(TypeError):
+ say = tml.Say('Hi')
+ say.append(tml.Say("there"))
+
+ def test_say_body(self):
+ self.response.append(tml.Say('Hello there'))
+ self.assertEqual(str(self.response), '<Response><Say>Hello there</Say></Response>')
+
+ def test_say_bad_attributes(self):
+ with self.assertRaises(AttributeError):
+ tml.Say('hello', loops=5)
+
+ def test_say_all_attributes(self):
+ self.response.append(tml.Say('Hello there', voice='man', loop=1))
+ self.assertEqual(str(self.response), '<Response><Say voice="man" loop="1">Hello there</Say></Response>')
+
+
+
+ # Play
+ def test_play_body(self):
+ self.response.append(tml.Play('http://test.url.com/myfile.mp3', loop=1))
+ self.assertEqual(str(self.response), '<Response><Play loop="1">http://test.url.com/myfile.mp3</Play></Response>')
+
+
+
+ # Gather
+ def test_gather(self):
+ self.response.append(tml.Gather())
+ self.assertEqual(str(self.response), '<Response><Gather></Gather></Response>')
+
+ def test_gather_attributes(self):
+ self.response.append(tml.Gather(action="http://mysite.com/gather/", method="POST", timeout=10,
+ finishOnKey='true', numDigits=5))
+ self.assertEqual(str(self.response),
+ '<Response><Gather finishOnKey="true" action="http://mysite.com/gather/" method="POST" timeout="10" numDigits="5"></Gather></Response>')
+
+ def test_gather_bad_attributes(self):
+ with self.assertRaises(AttributeError):
+ self.response.append(tml.Gather(action="http://mysite.com/gather/", method="POST", timeout=10,
+ finishOnKey='true', numdigits=5))
+
+ def test_gather_children(self):
+ self.response.append(
+ tml.Gather(
+ tml.Say('Enter digits'),
+ tml.Play('http://test.com/pound.mp3'),
+ tml.Pause(),
+ tml.Say("Followed by pound key"),
+ timeout = 30
+ )
+ )
+ self.assertEqual(str(self.response),
+ '<Response><Gather timeout="30"><Say>Enter digits</Say><Play>http://test.com/pound.mp3</Play><Pause></Pause><Say>Followed by pound key</Say></Gather></Response>')
+
+
+
+ # Record
+ def test_record_attributes(self):
+ # No value validation yet, so just make sure all the attrs are assignable
+ record_attrs = dict([(attr, attr) for attr in ['action', 'method', 'timeout', 'finishOnKey', 'transcribe', 'transcribeCallback', 'playBeep', 'bothLegs', 'fileFormat']])
+ self.response.append(tml.Record(**record_attrs))
+ self.assertEqual(
+ str(self.response),
+ '<Response><Record finishOnKey="finishOnKey" transcribe="transcribe" playBeep="playBeep" transcribeCallback="transcribeCallback" fileFormat="fileFormat" bothLegs="bothLegs" timeout="timeout" action="action" method="method"></Record></Response>'
+ )
+
+
+ # Dial
+ def test_dial_simple(self):
+ self.response.append(tml.Dial('1-555-222-3456'))
+ self.assertEqual(str(self.response), '<Response><Dial>1-555-222-3456</Dial></Response>')
+
+ def test_dial_number(self):
+ self.response.append(tml.Dial(tml.Number('1-555-222-3456')))
+ self.assertEqual(
+ str(self.response),
+ '<Response><Dial><Number>1-555-222-3456</Number></Dial></Response>'
+ )
+
+ def test_dial_blank_number(self):
+ with self.assertRaises(TypeError):
+ self.response.append(tml.Dial(tml.Number()))
+
+ def test_dial_attributes(self):
+ # No value validation yet, so just make sure all the attrs are assignable
+ dial_attrs = dict([(attr, attr) for attr in ['action', 'method', 'timeout', 'hangupOnStar', 'timeLimit', 'callerId', 'hideCallerId', 'callerName', 'dialMusic',
+ 'callbackUrl', 'callbackMethod', 'confirmSound', 'digitsMatch', 'straightToVm', 'heartbeatUrl', 'heartbeatMethod', 'forwardedFrom']])
+ self.response.append(tml.Dial("1-555-222-3456", **dial_attrs))
+ self.assertEqual(
+ str(self.response),
+ '<Response><Dial hideCallerId="hideCallerId" heartbeatMethod="heartbeatMethod" callerName="callerName" callerId="callerId" confirmSound="confirmSound" digitsMatch="digitsMatch" forwardedFrom="forwardedFrom" callbackMethod="callbackMethod" timeLimit="timeLimit" dialMusic="dialMusic" timeout="timeout" heartbeatUrl="heartbeatUrl" action="action" straightToVm="straightToVm" hangupOnStar="hangupOnStar" method="method" callbackUrl="callbackUrl">1-555-222-3456</Dial></Response>'
+ )
+
+ def test_dial_blank_conference(self):
+ with self.assertRaises(TypeError):
+ self.response.append(tml.Dial(tml.Conference()))
+
+ def test_dial_conference(self):
+ self.response.append(tml.Say('You are about to enter the conference'))
+ self.response.append(tml.Dial(tml.Conference('Conference Room A')))
+ self.assertEqual(
+ str(self.response),
+ '<Response><Say>You are about to enter the conference</Say><Dial><Conference>Conference Room A</Conference></Dial></Response>'
+ )
+
+if __name__ == '__main__':
+ unittest.main()

0 comments on commit 9ca5bb1

Please sign in to comment.
Something went wrong with that request. Please try again.