<p>Corey Farrell has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/8990">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Fix pep8 and flake8 issues.<br><br>* Update examples to be compatible with python3.<br>* Address pep8 and flake8 issues doc/, starpy/ and examples/.<br>* Fix undefined name keytree in fastagi:FastAGIProtocol.databaseDeltree.<br>* Set maximum line length to 90.<br><br>Change-Id: I293635427a48e66298042c3fc1e600e47854d3ed<br>---<br>M doc/pydoc/builddocs.py<br>M doc/pydoc/pydoc2.py<br>M examples/amicommand.py<br>M examples/autosurvey/frontend.py<br>M examples/calldurationcallback.py<br>M examples/connecttoivr.py<br>M examples/connecttoivrapp.py<br>M examples/fastagisetvariable.py<br>M examples/getvariable.py<br>M examples/hellofastagi.py<br>M examples/hellofastagiapp.py<br>M examples/menu.py<br>M examples/menutest.py<br>M examples/priexhaustion.py<br>M examples/priexhaustionbare.py<br>M examples/readingdigits.py<br>M examples/timestamp.py<br>M examples/timestampapp.py<br>M examples/utilapplication.py<br>M starpy/fastagi.py<br>M starpy/manager.py<br>M tox.ini<br>22 files changed, 1,496 insertions(+), 1,343 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/starpy refs/changes/90/8990/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/doc/pydoc/builddocs.py b/doc/pydoc/builddocs.py<br>index 080f7c6..9372eb1 100755<br>--- a/doc/pydoc/builddocs.py<br>+++ b/doc/pydoc/builddocs.py<br>@@ -2,26 +2,25 @@<br> import pydoc2<br> <br> if __name__ == "__main__":<br>- excludes = [<br>- "Numeric",<br>- "_tkinter",<br>- "Tkinter",<br>- "math",<br>- "string",<br>- "twisted",<br>- ]<br>- stops = [<br>- ]<br>+ excludes = [<br>+ "Numeric",<br>+ "_tkinter",<br>+ "Tkinter",<br>+ "math",<br>+ "string",<br>+ "twisted",<br>+ ]<br>+ stops = [<br>+ ]<br> <br>- modules = [<br>- 'starpy',<br>- 'starpy.examples',<br>- '__builtin__',<br>- ] <br>- pydoc2.PackageDocumentationGenerator(<br>- baseModules = modules,<br>- destinationDirectory = ".",<br>- exclusions = excludes,<br>- recursionStops = stops,<br>- ).process ()<br>- <br>+ modules = [<br>+ 'starpy',<br>+ 'starpy.examples',<br>+ '__builtin__',<br>+ ]<br>+ pydoc2.PackageDocumentationGenerator(<br>+ baseModules=modules,<br>+ destinationDirectory=".",<br>+ exclusions=excludes,<br>+ recursionStops=stops,<br>+ ).process()<br>diff --git a/doc/pydoc/pydoc2.py b/doc/pydoc/pydoc2.py<br>index 8fc4d33..cc85c6a 100644<br>--- a/doc/pydoc/pydoc2.py<br>+++ b/doc/pydoc/pydoc2.py<br>@@ -1,463 +1,462 @@<br> """Pydoc sub-class for generating documentation for entire packages"""<br>-import pydoc, inspect, os, string<br>-import sys, imp, os, stat, re, types, inspect<br>-from repr import Repr<br>-from string import expandtabs, find, join, lower, split, strip, rfind, rstrip<br>+import pydoc<br>+import inspect<br>+import os<br>+import string<br>+import sys<br>+from string import join, split, strip<br>+<br> <br> def classify_class_attrs(cls):<br>- """Return list of attribute-descriptor tuples.<br>+ """Return list of attribute-descriptor tuples.<br> <br>- For each name in dir(cls), the return list contains a 4-tuple<br>- with these elements:<br>+ For each name in dir(cls), the return list contains a 4-tuple<br>+ with these elements:<br> <br>- 0. The name (a string).<br>+ 0. The name (a string).<br> <br>- 1. The kind of attribute this is, one of these strings:<br>- 'class method' created via classmethod()<br>- 'static method' created via staticmethod()<br>- 'property' created via property()<br>- 'method' any other flavor of method<br>- 'data' not a method<br>+ 1. The kind of attribute this is, one of these strings:<br>+ 'class method' created via classmethod()<br>+ 'static method' created via staticmethod()<br>+ 'property' created via property()<br>+ 'method' any other flavor of method<br>+ 'data' not a method<br> <br>- 2. The class which defined this attribute (a class).<br>+ 2. The class which defined this attribute (a class).<br> <br>- 3. The object as obtained directly from the defining class's<br>- __dict__, not via getattr. This is especially important for<br>- data attributes: C.data is just a data object, but<br>- C.__dict__['data'] may be a data descriptor with additional<br>- info, like a __doc__ string.<br>- <br>- Note: This version is patched to work with Zope Interface-bearing objects<br>- """<br>+ 3. The object as obtained directly from the defining class's<br>+ __dict__, not via getattr. This is especially important for<br>+ data attributes: C.data is just a data object, but<br>+ C.__dict__['data'] may be a data descriptor with additional<br>+ info, like a __doc__ string.<br> <br>- mro = inspect.getmro(cls)<br>- names = dir(cls)<br>- result = []<br>- for name in names:<br>- # Get the object associated with the name.<br>- # Getting an obj from the __dict__ sometimes reveals more than<br>- # using getattr. Static and class methods are dramatic examples.<br>- if name in cls.__dict__:<br>- obj = cls.__dict__[name]<br>- else:<br>- try:<br>- obj = getattr(cls, name)<br>- except AttributeError, err:<br>- continue<br>+ Note: This version is patched to work with Zope Interface-bearing objects<br>+ """<br> <br>- # Figure out where it was defined.<br>- homecls = getattr(obj, "__objclass__", None)<br>- if homecls is None:<br>- # search the dicts.<br>- for base in mro:<br>- if name in base.__dict__:<br>- homecls = base<br>- break<br>+ mro = inspect.getmro(cls)<br>+ names = dir(cls)<br>+ result = []<br>+ for name in names:<br>+ # Get the object associated with the name.<br>+ # Getting an obj from the __dict__ sometimes reveals more than<br>+ # using getattr. Static and class methods are dramatic examples.<br>+ if name in cls.__dict__:<br>+ obj = cls.__dict__[name]<br>+ else:<br>+ try:<br>+ obj = getattr(cls, name)<br>+ except AttributeError as err:<br>+ continue<br> <br>- # Get the object again, in order to get it from the defining<br>- # __dict__ instead of via getattr (if possible).<br>- if homecls is not None and name in homecls.__dict__:<br>- obj = homecls.__dict__[name]<br>+ # Figure out where it was defined.<br>+ homecls = getattr(obj, "__objclass__", None)<br>+ if homecls is None:<br>+ # search the dicts.<br>+ for base in mro:<br>+ if name in base.__dict__:<br>+ homecls = base<br>+ break<br> <br>- # Also get the object via getattr.<br>- obj_via_getattr = getattr(cls, name)<br>+ # Get the object again, in order to get it from the defining<br>+ # __dict__ instead of via getattr (if possible).<br>+ if homecls is not None and name in homecls.__dict__:<br>+ obj = homecls.__dict__[name]<br> <br>- # Classify the object.<br>- if isinstance(obj, staticmethod):<br>- kind = "static method"<br>- elif isinstance(obj, classmethod):<br>- kind = "class method"<br>- elif isinstance(obj, property):<br>- kind = "property"<br>- elif (inspect.ismethod(obj_via_getattr) or<br>- inspect.ismethoddescriptor(obj_via_getattr)):<br>- kind = "method"<br>- else:<br>- kind = "data"<br>+ # Also get the object via getattr.<br>+ obj_via_getattr = getattr(cls, name)<br> <br>- result.append((name, kind, homecls, obj))<br>+ # Classify the object.<br>+ if isinstance(obj, staticmethod):<br>+ kind = "static method"<br>+ elif isinstance(obj, classmethod):<br>+ kind = "class method"<br>+ elif isinstance(obj, property):<br>+ kind = "property"<br>+ elif (inspect.ismethod(obj_via_getattr) or<br>+ inspect.ismethoddescriptor(obj_via_getattr)):<br>+ kind = "method"<br>+ else:<br>+ kind = "data"<br> <br>- return result<br>+ result.append((name, kind, homecls, obj))<br>+<br>+ return result<br> inspect.classify_class_attrs = classify_class_attrs<br> <br> <br> class DefaultFormatter(pydoc.HTMLDoc):<br>- def docmodule(self, object, name=None, mod=None, packageContext = None, *ignored):<br>- """Produce HTML documentation for a module object."""<br>- name = object.__name__ # ignore the passed-in name<br>- parts = split(name, '.')<br>- links = []<br>- for i in range(len(parts)-1):<br>- links.append(<br>- '<a href="%s.html"><font color="#ffffff">%s</font></a>' %<br>- (join(parts[:i+1], '.'), parts[i]))<br>- linkedname = join(links + parts[-1:], '.')<br>- head = '<big><big><strong>%s</strong></big></big>' % linkedname<br>- try:<br>- path = inspect.getabsfile(object)<br>- url = path<br>- if sys.platform == 'win32':<br>- import nturl2path<br>- url = nturl2path.pathname2url(path)<br>- filelink = '<a href="file:%s">%s</a>' % (url, path)<br>- except TypeError:<br>- filelink = '(built-in)'<br>- info = []<br>- if hasattr(object, '__version__'):<br>- version = str(object.__version__)<br>- if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':<br>- version = strip(version[11:-1])<br>- info.append('version %s' % self.escape(version))<br>- if hasattr(object, '__date__'):<br>- info.append(self.escape(str(object.__date__)))<br>- if info:<br>- head = head + ' (%s)' % join(info, ', ')<br>- result = self.heading(<br>- head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)<br>+ def docmodule(self, object, name=None, mod=None, packageContext=None, *ignored):<br>+ """Produce HTML documentation for a module object."""<br>+ name = object.__name__ # ignore the passed-in name<br>+ parts = split(name, '.')<br>+ links = []<br>+ for i in range(len(parts)-1):<br>+ links.append(<br>+ '<a href="%s.html"><font color="#ffffff">%s</font></a>' %<br>+ (join(parts[:i+1], '.'), parts[i]))<br>+ linkedname = join(links + parts[-1:], '.')<br>+ head = '<big><big><strong>%s</strong></big></big>' % linkedname<br>+ try:<br>+ path = inspect.getabsfile(object)<br>+ url = path<br>+ if sys.platform == 'win32':<br>+ import nturl2path<br>+ url = nturl2path.pathname2url(path)<br>+ filelink = '<a href="file:%s">%s</a>' % (url, path)<br>+ except TypeError:<br>+ filelink = '(built-in)'<br>+ info = []<br>+ if hasattr(object, '__version__'):<br>+ version = str(object.__version__)<br>+ if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':<br>+ version = strip(version[11:-1])<br>+ info.append('version %s' % self.escape(version))<br>+ if hasattr(object, '__date__'):<br>+ info.append(self.escape(str(object.__date__)))<br>+ if info:<br>+ head = head + ' (%s)' % join(info, ', ')<br>+ result = self.heading(<br>+ head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)<br> <br>- modules = inspect.getmembers(object, inspect.ismodule)<br>+ modules = inspect.getmembers(object, inspect.ismodule)<br> <br>- classes, cdict = [], {}<br>- for key, value in inspect.getmembers(object, inspect.isclass):<br>- if (inspect.getmodule(value) or object) is object:<br>- classes.append((key, value))<br>- cdict[key] = cdict[value] = '#' + key<br>- for key, value in classes:<br>- for base in value.__bases__:<br>- key, modname = base.__name__, base.__module__<br>- module = sys.modules.get(modname)<br>- if modname != name and module and hasattr(module, key):<br>- if getattr(module, key) is base:<br>- if not cdict.has_key(key):<br>- cdict[key] = cdict[base] = modname + '.html#' + key<br>- funcs, fdict = [], {}<br>- for key, value in inspect.getmembers(object, inspect.isroutine):<br>- if inspect.isbuiltin(value) or inspect.getmodule(value) is object:<br>- funcs.append((key, value))<br>- fdict[key] = '#-' + key<br>- if inspect.isfunction(value): fdict[value] = fdict[key]<br>- data = []<br>- for key, value in inspect.getmembers(object, pydoc.isdata):<br>- if key not in ['__builtins__', '__doc__']:<br>- data.append((key, value))<br>+ classes, cdict = [], {}<br>+ for key, value in inspect.getmembers(object, inspect.isclass):<br>+ if (inspect.getmodule(value) or object) is object:<br>+ classes.append((key, value))<br>+ cdict[key] = cdict[value] = '#' + key<br>+ for key, value in classes:<br>+ for base in value.__bases__:<br>+ key, modname = base.__name__, base.__module__<br>+ module = sys.modules.get(modname)<br>+ if modname != name and module and hasattr(module, key):<br>+ if getattr(module, key) is base:<br>+ if key not in cdict:<br>+ cdict[key] = cdict[base] = modname + '.html#' + key<br>+ funcs, fdict = [], {}<br>+ for key, value in inspect.getmembers(object, inspect.isroutine):<br>+ if inspect.isbuiltin(value) or inspect.getmodule(value) is object:<br>+ funcs.append((key, value))<br>+ fdict[key] = '#-' + key<br>+ if inspect.isfunction(value):<br>+ fdict[value] = fdict[key]<br>+ data = []<br>+ for key, value in inspect.getmembers(object, pydoc.isdata):<br>+ if key not in ['__builtins__', '__doc__']:<br>+ data.append((key, value))<br> <br>- doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict)<br>- doc = doc and '<tt>%s</tt>' % doc<br>- result = result + '<p>%s</p>\n' % doc<br>+ doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict)<br>+ doc = doc and '<tt>%s</tt>' % doc<br>+ result = result + '<p>%s</p>\n' % doc<br> <br>- packageContext.clean ( classes, object )<br>- packageContext.clean ( funcs, object )<br>- packageContext.clean ( data, object )<br>- <br>- if hasattr(object, '__path__'):<br>- modpkgs = []<br>- modnames = []<br>- for file in os.listdir(object.__path__[0]):<br>- path = os.path.join(object.__path__[0], file)<br>- modname = inspect.getmodulename(file)<br>- if modname and modname not in modnames:<br>- modpkgs.append((modname, name, 0, 0))<br>- modnames.append(modname)<br>- elif pydoc.ispackage(path):<br>- modpkgs.append((file, name, 1, 0))<br>- modpkgs.sort()<br>- contents = self.multicolumn(modpkgs, self.modpkglink)<br>-## result = result + self.bigsection(<br>-## 'Package Contents', '#ffffff', '#aa55cc', contents)<br>- result = result + self.moduleSection( object, packageContext)<br>- elif modules:<br>- contents = self.multicolumn(<br>- modules, lambda (key, value), s=self: s.modulelink(value))<br>- result = result + self.bigsection(<br>- 'Modules', '#fffff', '#aa55cc', contents)<br>+ packageContext.clean(classes, object)<br>+ packageContext.clean(funcs, object)<br>+ packageContext.clean(data, object)<br> <br>- <br>- if classes:<br>-## print classes<br>-## import pdb<br>-## pdb.set_trace()<br>- classlist = map(lambda (key, value): value, classes)<br>- contents = [<br>- self.formattree(inspect.getclasstree(classlist, 1), name)]<br>- for key, value in classes:<br>- contents.append(self.document(value, key, name, fdict, cdict))<br>- result = result + self.bigsection(<br>- 'Classes', '#ffffff', '#ee77aa', join(contents))<br>- if funcs:<br>- contents = []<br>- for key, value in funcs:<br>- contents.append(self.document(value, key, name, fdict, cdict))<br>- result = result + self.bigsection(<br>- 'Functions', '#ffffff', '#eeaa77', join(contents))<br>- if data:<br>- contents = []<br>- for key, value in data:<br>- try:<br>- contents.append(self.document(value, key))<br>- except Exception, err:<br>- pass<br>- result = result + self.bigsection(<br>- 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))<br>- if hasattr(object, '__author__'):<br>- contents = self.markup(str(object.__author__), self.preformat)<br>- result = result + self.bigsection(<br>- 'Author', '#ffffff', '#7799ee', contents)<br>- if hasattr(object, '__credits__'):<br>- contents = self.markup(str(object.__credits__), self.preformat)<br>- result = result + self.bigsection(<br>- 'Credits', '#ffffff', '#7799ee', contents)<br>+ if hasattr(object, '__path__'):<br>+ modpkgs = []<br>+ modnames = []<br>+ for file in os.listdir(object.__path__[0]):<br>+ path = os.path.join(object.__path__[0], file)<br>+ modname = inspect.getmodulename(file)<br>+ if modname and modname not in modnames:<br>+ modpkgs.append((modname, name, 0, 0))<br>+ modnames.append(modname)<br>+ elif pydoc.ispackage(path):<br>+ modpkgs.append((file, name, 1, 0))<br>+ modpkgs.sort()<br>+ contents = self.multicolumn(modpkgs, self.modpkglink)<br>+ result = result + self.moduleSection(object, packageContext)<br>+ elif modules:<br>+ contents = self.multicolumn(<br>+ modules, lambda kvp, s=self: s.modulelink(kvp[1]))<br>+ result = result + self.bigsection(<br>+ 'Modules', '#fffff', '#aa55cc', contents)<br> <br>- return result<br>+ if classes:<br>+ classlist = map(lambda kvp: kvp[1], classes)<br>+ contents = [<br>+ self.formattree(inspect.getclasstree(classlist, 1), name)]<br>+ for key, value in classes:<br>+ contents.append(self.document(value, key, name, fdict, cdict))<br>+ result = result + self.bigsection(<br>+ 'Classes', '#ffffff', '#ee77aa', join(contents))<br>+ if funcs:<br>+ contents = []<br>+ for key, value in funcs:<br>+ contents.append(self.document(value, key, name, fdict, cdict))<br>+ result = result + self.bigsection(<br>+ 'Functions', '#ffffff', '#eeaa77', join(contents))<br>+ if data:<br>+ contents = []<br>+ for key, value in data:<br>+ try:<br>+ contents.append(self.document(value, key))<br>+ except Exception as err:<br>+ pass<br>+ result = result + self.bigsection(<br>+ 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))<br>+ if hasattr(object, '__author__'):<br>+ contents = self.markup(str(object.__author__), self.preformat)<br>+ result = result + self.bigsection(<br>+ 'Author', '#ffffff', '#7799ee', contents)<br>+ if hasattr(object, '__credits__'):<br>+ contents = self.markup(str(object.__credits__), self.preformat)<br>+ result = result + self.bigsection(<br>+ 'Credits', '#ffffff', '#7799ee', contents)<br> <br>- def classlink(self, object, modname):<br>- """Make a link for a class."""<br>- name, module = object.__name__, sys.modules.get(object.__module__)<br>- if hasattr(module, name) and getattr(module, name) is object:<br>- return '<a href="%s.html#%s">%s</a>' % (<br>- module.__name__, name, name<br>- )<br>- return pydoc.classname(object, modname)<br>- <br>- def moduleSection( self, object, packageContext ):<br>- """Create a module-links section for the given object (module)"""<br>- modules = inspect.getmembers(object, inspect.ismodule)<br>- packageContext.clean ( modules, object )<br>- packageContext.recurseScan( modules )<br>+ return result<br> <br>- if hasattr(object, '__path__'):<br>- modpkgs = []<br>- modnames = []<br>- for file in os.listdir(object.__path__[0]):<br>- path = os.path.join(object.__path__[0], file)<br>- modname = inspect.getmodulename(file)<br>- if modname and modname not in modnames:<br>- modpkgs.append((modname, object.__name__, 0, 0))<br>- modnames.append(modname)<br>- elif pydoc.ispackage(path):<br>- modpkgs.append((file, object.__name__, 1, 0))<br>- modpkgs.sort()<br>- # do more recursion here...<br>- for (modname, name, ya,yo) in modpkgs:<br>- packageContext.addInteresting( join( (object.__name__, modname), '.'))<br>- items = []<br>- for (modname, name, ispackage,isshadowed) in modpkgs:<br>- try:<br>- # get the actual module object...<br>-## if modname == "events":<br>-## import pdb<br>-## pdb.set_trace()<br>- module = pydoc.safeimport( "%s.%s"%(name,modname) )<br>- description, documentation = pydoc.splitdoc( inspect.getdoc( module ))<br>- if description:<br>- items.append(<br>- """%s -- %s"""% (<br>- self.modpkglink( (modname, name, ispackage, isshadowed) ),<br>- description,<br>- )<br>- )<br>- else:<br>- items.append(<br>- self.modpkglink( (modname, name, ispackage, isshadowed) )<br>- )<br>- except:<br>- items.append(<br>- self.modpkglink( (modname, name, ispackage, isshadowed) )<br>- )<br>- contents = string.join( items, '<br>')<br>- result = self.bigsection(<br>- 'Package Contents', '#ffffff', '#aa55cc', contents)<br>- elif modules:<br>- contents = self.multicolumn(<br>- modules, lambda (key, value), s=self: s.modulelink(value))<br>- result = self.bigsection(<br>- 'Modules', '#fffff', '#aa55cc', contents)<br>- else:<br>- result = ""<br>- return result<br>- <br>- <br>+ def classlink(self, object, modname):<br>+ """Make a link for a class."""<br>+ name, module = object.__name__, sys.modules.get(object.__module__)<br>+ if hasattr(module, name) and getattr(module, name) is object:<br>+ return '<a href="%s.html#%s">%s</a>' % (<br>+ module.__name__, name, name<br>+ )<br>+ return pydoc.classname(object, modname)<br>+<br>+ def moduleSection(self, object, packageContext):<br>+ """Create a module-links section for the given object (module)"""<br>+ modules = inspect.getmembers(object, inspect.ismodule)<br>+ packageContext.clean(modules, object)<br>+ packageContext.recurseScan(modules)<br>+<br>+ if hasattr(object, '__path__'):<br>+ modpkgs = []<br>+ modnames = []<br>+ for file in os.listdir(object.__path__[0]):<br>+ path = os.path.join(object.__path__[0], file)<br>+ modname = inspect.getmodulename(file)<br>+ if modname and modname not in modnames:<br>+ modpkgs.append((modname, object.__name__, 0, 0))<br>+ modnames.append(modname)<br>+ elif pydoc.ispackage(path):<br>+ modpkgs.append((file, object.__name__, 1, 0))<br>+ modpkgs.sort()<br>+ # do more recursion here...<br>+ for (modname, name, ya, yo) in modpkgs:<br>+ packageContext.addInteresting(join((object.__name__, modname), '.'))<br>+ items = []<br>+ for (modname, name, ispackage, isshadowed) in modpkgs:<br>+ try:<br>+ # get the actual module object...<br>+ module = pydoc.safeimport("%s.%s" % (name, modname))<br>+ description, documentation = pydoc.splitdoc(inspect.getdoc(module))<br>+ if description:<br>+ items.append(<br>+ """%s -- %s""" % (<br>+ self.modpkglink((modname, name, ispackage, isshadowed)),<br>+ description,<br>+ )<br>+ )<br>+ else:<br>+ items.append(<br>+ self.modpkglink((modname, name, ispackage, isshadowed))<br>+ )<br>+ except:<br>+ items.append(<br>+ self.modpkglink((modname, name, ispackage, isshadowed))<br>+ )<br>+ contents = string.join(items, '<br>')<br>+ result = self.bigsection(<br>+ 'Package Contents', '#ffffff', '#aa55cc', contents)<br>+ elif modules:<br>+ contents = self.multicolumn(<br>+ modules, lambda kvp, s=self: s.modulelink(kvp[1]))<br>+ result = self.bigsection(<br>+ 'Modules', '#fffff', '#aa55cc', contents)<br>+ else:<br>+ result = ""<br>+ return result<br>+<br>+<br> class AlreadyDone(Exception):<br>- pass<br>- <br>+ pass<br> <br> <br> class PackageDocumentationGenerator:<br>- """A package document generator creates documentation<br>- for an entire package using pydoc's machinery.<br>+ """A package document generator creates documentation<br>+ for an entire package using pydoc's machinery.<br> <br>- baseModules -- modules which will be included<br>- and whose included and children modules will be<br>- considered fair game for documentation<br>- destinationDirectory -- the directory into which<br>- the HTML documentation will be written<br>- recursion -- whether to add modules which are<br>- referenced by and/or children of base modules<br>- exclusions -- a list of modules whose contents will<br>- not be shown in any other module, commonly<br>- such modules as OpenGL.GL, wxPython.wx etc.<br>- recursionStops -- a list of modules which will<br>- explicitly stop recursion (i.e. they will never<br>- be included), even if they are children of base<br>- modules.<br>- formatter -- allows for passing in a custom formatter<br>- see DefaultFormatter for sample implementation.<br>- """<br>- def __init__ (<br>- self, baseModules, destinationDirectory = ".",<br>- recursion = 1, exclusions = (),<br>- recursionStops = (),<br>- formatter = None<br>- ):<br>- self.destinationDirectory = os.path.abspath( destinationDirectory)<br>- self.exclusions = {}<br>- self.warnings = []<br>- self.baseSpecifiers = {}<br>- self.completed = {}<br>- self.recursionStops = {}<br>- self.recursion = recursion<br>- for stop in recursionStops:<br>- self.recursionStops[ stop ] = 1<br>- self.pending = []<br>- for exclusion in exclusions:<br>- try:<br>- self.exclusions[ exclusion ]= pydoc.locate ( exclusion)<br>- except pydoc.ErrorDuringImport, value:<br>- self.warn( """Unable to import the module %s which was specified as an exclusion module"""% (repr(exclusion)))<br>- self.formatter = formatter or DefaultFormatter()<br>- for base in baseModules:<br>- self.addBase( base )<br>- def warn( self, message ):<br>- """Warnings are used for recoverable, but not necessarily ignorable conditions"""<br>- self.warnings.append (message)<br>- def info (self, message):<br>- """Information/status report"""<br>- print message<br>- def addBase(self, specifier):<br>- """Set the base of the documentation set, only children of these modules will be documented"""<br>- try:<br>- self.baseSpecifiers [specifier] = pydoc.locate ( specifier)<br>- self.pending.append (specifier)<br>- except pydoc.ErrorDuringImport, value:<br>- self.warn( """Unable to import the module %s which was specified as a base module"""% (repr(specifier)))<br>- def addInteresting( self, specifier):<br>- """Add a module to the list of interesting modules"""<br>- if self.checkScope( specifier):<br>-## print "addInteresting", specifier<br>- self.pending.append (specifier)<br>- else:<br>- self.completed[ specifier] = 1<br>- def checkScope (self, specifier):<br>- """Check that the specifier is "in scope" for the recursion"""<br>- if not self.recursion:<br>- return 0<br>- items = string.split (specifier, ".")<br>- stopCheck = items [:]<br>- while stopCheck:<br>- name = string.join(items, ".")<br>- if self.recursionStops.get( name):<br>- return 0<br>- elif self.completed.get (name):<br>- return 0<br>- del stopCheck[-1]<br>- while items:<br>- if self.baseSpecifiers.get( string.join(items, ".")):<br>- return 1<br>- del items[-1]<br>- # was not within any given scope<br>- return 0<br>+ baseModules -- modules which will be included<br>+ and whose included and children modules will be<br>+ considered fair game for documentation<br>+ destinationDirectory -- the directory into which<br>+ the HTML documentation will be written<br>+ recursion -- whether to add modules which are<br>+ referenced by and/or children of base modules<br>+ exclusions -- a list of modules whose contents will<br>+ not be shown in any other module, commonly<br>+ such modules as OpenGL.GL, wxPython.wx etc.<br>+ recursionStops -- a list of modules which will<br>+ explicitly stop recursion (i.e. they will never<br>+ be included), even if they are children of base<br>+ modules.<br>+ formatter -- allows for passing in a custom formatter<br>+ see DefaultFormatter for sample implementation.<br>+ """<br>+ def __init__(<br>+ self, baseModules, destinationDirectory=".",<br>+ recursion=1, exclusions=(),<br>+ recursionStops=(),<br>+ formatter=None<br>+ ):<br>+ self.destinationDirectory = os.path.abspath(destinationDirectory)<br>+ self.exclusions = {}<br>+ self.warnings = []<br>+ self.baseSpecifiers = {}<br>+ self.completed = {}<br>+ self.recursionStops = {}<br>+ self.recursion = recursion<br>+ for stop in recursionStops:<br>+ self.recursionStops[stop] = 1<br>+ self.pending = []<br>+ for exclusion in exclusions:<br>+ try:<br>+ self.exclusions[exclusion] = pydoc.locate(exclusion)<br>+ except pydoc.ErrorDuringImport as value:<br>+ self.warn(<br>+ ("Unable to import the module %s which "<br>+ "was specified as an exclusion module") % (repr(exclusion)))<br>+ self.formatter = formatter or DefaultFormatter()<br>+ for base in baseModules:<br>+ self.addBase(base)<br> <br>- def process( self ):<br>- """Having added all of the base and/or interesting modules,<br>- proceed to generate the appropriate documentation for each<br>- module in the appropriate directory, doing the recursion<br>- as we go."""<br>- try:<br>- while self.pending:<br>- try:<br>- if self.completed.has_key( self.pending[0] ):<br>- raise AlreadyDone( self.pending[0] )<br>- self.info( """Start %s"""% (repr(self.pending[0])))<br>- object = pydoc.locate ( self.pending[0] )<br>- self.info( """ ... found %s"""% (repr(object.__name__)))<br>- except AlreadyDone:<br>- pass<br>- except pydoc.ErrorDuringImport, value:<br>- self.info( """ ... FAILED %s"""% (repr( value)))<br>- self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))<br>- except (SystemError, SystemExit), value:<br>- self.info( """ ... FAILED %s"""% (repr( value)))<br>- self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))<br>- except Exception, value:<br>- self.info( """ ... FAILED %s"""% (repr( value)))<br>- self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))<br>- else:<br>- page = self.formatter.page(<br>- pydoc.describe(object),<br>- self.formatter.docmodule(<br>- object,<br>- object.__name__,<br>- packageContext = self,<br>- )<br>- )<br>- file = open (<br>- os.path.join(<br>- self.destinationDirectory,<br>- self.pending[0] + ".html",<br>- ),<br>- 'w',<br>- )<br>- file.write(page)<br>- file.close()<br>- self.completed[ self.pending[0]] = object<br>- del self.pending[0]<br>- finally:<br>- for item in self.warnings:<br>- print item<br>- <br>- def clean (self, objectList, object):<br>- """callback from the formatter object asking us to remove<br>- those items in the key, value pairs where the object is<br>- imported from one of the excluded modules"""<br>- for key, value in objectList[:]:<br>- for excludeObject in self.exclusions.values():<br>- if hasattr( excludeObject, key ) and excludeObject is not object:<br>- if (<br>- getattr( excludeObject, key) is value or<br>- (hasattr( excludeObject, '__name__') and<br>- excludeObject.__name__ == "Numeric"<br>- )<br>- ):<br>- objectList[:] = [ (k,o) for k,o in objectList if k != key ]<br>- def recurseScan(self, objectList):<br>- """Process the list of modules trying to add each to the<br>- list of interesting modules"""<br>- for key, value in objectList:<br>- self.addInteresting( value.__name__ )<br>+ def warn(self, message):<br>+ """Warnings are used for recoverable, but not necessarily ignorable conditions"""<br>+ self.warnings.append(message)<br> <br>+ def info(self, message):<br>+ """Information/status report"""<br>+ print(message)<br>+<br>+ def addBase(self, specifier):<br>+ """Set the base of the documentation set, only children of these modules will<br>+ be documented"""<br>+ try:<br>+ self.baseSpecifiers[specifier] = pydoc.locate(specifier)<br>+ self.pending.append(specifier)<br>+ except pydoc.ErrorDuringImport as value:<br>+ self.warn(<br>+ "Unable to import the module %s which was specified as a base module" %<br>+ (repr(specifier)))<br>+<br>+ def addInteresting(self, specifier):<br>+ """Add a module to the list of interesting modules"""<br>+ if self.checkScope(specifier):<br>+ self.pending.append(specifier)<br>+ else:<br>+ self.completed[specifier] = 1<br>+<br>+ def checkScope(self, specifier):<br>+ """Check that the specifier is "in scope" for the recursion"""<br>+ if not self.recursion:<br>+ return 0<br>+ items = string.split(specifier, ".")<br>+ stopCheck = items[:]<br>+ while stopCheck:<br>+ name = string.join(items, ".")<br>+ if self.recursionStops.get(name):<br>+ return 0<br>+ elif self.completed.get(name):<br>+ return 0<br>+ del stopCheck[-1]<br>+ while items:<br>+ if self.baseSpecifiers.get(string.join(items, ".")):<br>+ return 1<br>+ del items[-1]<br>+ # was not within any given scope<br>+ return 0<br>+<br>+ def process(self):<br>+ """Having added all of the base and/or interesting modules,<br>+ proceed to generate the appropriate documentation for each<br>+ module in the appropriate directory, doing the recursion<br>+ as we go."""<br>+ try:<br>+ while self.pending:<br>+ try:<br>+ if self.pending[0] in self.completed:<br>+ raise AlreadyDone(self.pending[0])<br>+ self.info("""Start %s""" % (repr(self.pending[0])))<br>+ object = pydoc.locate(self.pending[0])<br>+ self.info(""" ... found %s""" % (repr(object.__name__)))<br>+ except AlreadyDone:<br>+ pass<br>+ except pydoc.ErrorDuringImport as value:<br>+ self.info(" ... FAILED %s" % (repr(value)))<br>+ self.warn("Unable to import the module %s" % (repr(self.pending[0])))<br>+ except (SystemError, SystemExit) as value:<br>+ self.info(" ... FAILED %s" % (repr(value)))<br>+ self.warn("Unable to import the module %s" % (repr(self.pending[0])))<br>+ except Exception as value:<br>+ self.info(" ... FAILED %s" % (repr(value)))<br>+ self.warn("Unable to import the module %s" % (repr(self.pending[0])))<br>+ else:<br>+ page = self.formatter.page(<br>+ pydoc.describe(object),<br>+ self.formatter.docmodule(<br>+ object,<br>+ object.__name__,<br>+ packageContext=self,<br>+ )<br>+ )<br>+ file = open(<br>+ os.path.join(<br>+ self.destinationDirectory,<br>+ self.pending[0] + ".html",<br>+ ),<br>+ 'w',<br>+ )<br>+ file.write(page)<br>+ file.close()<br>+ self.completed[self.pending[0]] = object<br>+ del self.pending[0]<br>+ finally:<br>+ for item in self.warnings:<br>+ print(item)<br>+<br>+ def clean(self, objectList, object):<br>+ """callback from the formatter object asking us to remove<br>+ those items in the key, value pairs where the object is<br>+ imported from one of the excluded modules"""<br>+ for key, value in objectList[:]:<br>+ for excludeObject in self.exclusions.values():<br>+ if hasattr(excludeObject, key) and excludeObject is not object:<br>+ if (<br>+ getattr(excludeObject, key) is value or<br>+ (hasattr(excludeObject, '__name__') and<br>+ excludeObject.__name__ == "Numeric"<br>+ )<br>+ ):<br>+ objectList[:] = [(k, o) for k, o in objectList if k != key]<br>+<br>+ def recurseScan(self, objectList):<br>+ """Process the list of modules trying to add each to the<br>+ list of interesting modules"""<br>+ for key, value in objectList:<br>+ self.addInteresting(value.__name__)<br> <br> <br> if __name__ == "__main__":<br>- excludes = [<br>- "OpenGL.GL",<br>- "OpenGL.GLU",<br>- "OpenGL.GLUT",<br>- "OpenGL.GLE",<br>- "OpenGL.GLX",<br>- "wxPython.wx",<br>- "Numeric",<br>- "_tkinter",<br>- "Tkinter",<br>- ]<br>+ excludes = [<br>+ "OpenGL.GL",<br>+ "OpenGL.GLU",<br>+ "OpenGL.GLUT",<br>+ "OpenGL.GLE",<br>+ "OpenGL.GLX",<br>+ "wxPython.wx",<br>+ "Numeric",<br>+ "_tkinter",<br>+ "Tkinter",<br>+ ]<br> <br>- modules = [<br>- "OpenGLContext.debug",<br>-## "wxPython.glcanvas",<br>-## "OpenGL.Tk",<br>-## "OpenGL",<br>- ] <br>- PackageDocumentationGenerator(<br>- baseModules = modules,<br>- destinationDirectory = "z:\\temp",<br>- exclusions = excludes,<br>- ).process ()<br>- <br>+ modules = [<br>+ "OpenGLContext.debug",<br>+ ]<br>+ PackageDocumentationGenerator(<br>+ baseModules=modules,<br>+ destinationDirectory="z:\\temp",<br>+ exclusions=excludes,<br>+ ).process()<br>diff --git a/examples/amicommand.py b/examples/amicommand.py<br>index ee0e7b1..04ac913 100644<br>--- a/examples/amicommand.py<br>+++ b/examples/amicommand.py<br>@@ -1,35 +1,38 @@<br> #! /usr/bin/env python<br> """Test/sample to call "show database" command<br> """<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi<br>+from twisted.internet import reactor<br>+from starpy import manager<br> import utilapplication<br>-import menu<br>-import os, logging, pprint, time<br>+import logging<br> <br>-log = logging.getLogger( 'callduration' )<br>+log = logging.getLogger('callduration')<br> APPLICATION = utilapplication.UtilApplication()<br> <br>+<br> def main():<br>- def onConnect( ami ):<br>- def onResult( result ):<br>- print 'Result', result<br>+<br>+ def onConnect(ami):<br>+<br>+ def onResult(result):<br>+ print('Result', result)<br> return ami.logoff()<br>- def onError( reason ):<br>- print reason.getTraceback()<br>+<br>+ def onError(reason):<br>+ print(reason.getTraceback())<br> return reason<br>- def onFinished( result ):<br>+<br>+ def onFinished(result):<br> reactor.stop()<br>- df = ami.command( 'database show' )<br>- df.addCallbacks( onResult, onError )<br>- df.addCallbacks( onFinished, onFinished )<br>+ df = ami.command('database show')<br>+ df.addCallbacks(onResult, onError)<br>+ df.addCallbacks(onFinished, onFinished)<br> return df<br>- amiDF = APPLICATION.amiSpecifier.login(<br>- ).addCallback( onConnect )<br>+ APPLICATION.amiSpecifier.login().addCallback(onConnect)<br>+<br> <br> if __name__ == "__main__":<br> logging.basicConfig()<br>- manager.log.setLevel( logging.DEBUG )<br>- reactor.callWhenRunning( main )<br>+ manager.log.setLevel(logging.DEBUG)<br>+ reactor.callWhenRunning(main)<br> reactor.run()<br>diff --git a/examples/autosurvey/frontend.py b/examples/autosurvey/frontend.py<br>index da50378..7843a34 100644<br>--- a/examples/autosurvey/frontend.py<br>+++ b/examples/autosurvey/frontend.py<br>@@ -1,167 +1,176 @@<br> """Simple HTTP Server using twisted.web2"""<br>-from nevow import rend, appserver, inevow, tags, loaders<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>+from nevow import rend, appserver, inevow, loaders<br>+from twisted.application import internet<br>+from twisted.internet import reactor<br> from starpy import manager, fastagi, utilapplication<br> from basicproperty import common, basic, propertied, weak<br>-import os, logging, pprint, time<br>+import logging<br>+import random<br>+import sys<br> <br>-log = logging.getLogger( 'autosurvey' )<br>-<br>-class Application( utilapplication.UtilApplication ):<br>- """Services provided at the application level"""<br>- surveys = common.DictionaryProperty(<br>- "surveys", """Set of surveys indexed by survey/extension number""",<br>- )<br>+log = logging.getLogger('autosurvey')<br> <br> <br>+class Application(utilapplication.UtilApplication):<br>+ """Services provided at the application level"""<br>+ surveys = common.DictionaryProperty(<br>+ "surveys", """Set of surveys indexed by survey/extension number""",<br>+ )<br> <br>-class Survey( propertied.Propertied ):<br>- """Models a single survey to be completed"""<br>- surveyId = common.IntegerProperty(<br>- "surveyId", """Unique identifier for this survey""",<br>- )<br>- owner = basic.BasicProperty(<br>- "owner", """Owner's phone number to which to connect""",<br>- )<br>- questions = common.ListProperty(<br>- "questions", """Set of questions which make up the survey""",<br>- )<br>- YOU_CURRENTLY_HAVE = 'vm-youhave'<br>- QUESTIONS_IN_YOUR_SURVEY = 'vm-messages'<br>- QUESTION_IN_YOUR_SURVEY = 'vm-message'<br>- TO_LISTEN_TO_SURVEY_QUESTION = 'to-listen-to-it'<br>- TO_RECORD_A_NEW_SURVEY_QUESTION = 'to-rerecord-it'<br>- TO_FINISH_SURVEY_SETUP = 'vm-helpexit'<br>- def setupSurvey( self, agi ):<br>- """AGI application to allow the user to set up the survey<br>- <br>- Screen 1:<br>- You have # questions.<br>- To listen to a question, press the number of the question.<br>- To record a new question, press pound.<br>- To finish setup, press star.<br>- """<br>- seq = fastagi.InSequence( )<br>- seq.append( agi.wait, 2 )<br>- base = """You currently have %s question%s.<br>- To listen to a question press the number of the question.<br>- To record a new question, press pound.<br>- To finish survey setup, press star.<br>- """%(<br>- len(self.questions),<br>- ['','s'][len(self.questions)==1],<br>- )<br>- if len(base) != 1:<br>- base += 's'<br>- base = " ".join(base.split())<br>- seq.append( agi.execute, 'Festival', base )<br>- seq.append( agi.finish, )<br>- return seq()<br>- seq.append( agi.streamFile, self.YOU_CURRENTLY_HAVE )<br>- seq.append( agi.sayNumber, len(self.questions))<br>- if len(self.questions) == 1:<br>- seq.append( agi.streamFile, self.QUESTION_IN_YOUR_SURVEY )<br>- else:<br>- seq.append( agi.streamFile, self.QUESTIONS_IN_YOUR_SURVEY )<br>- seq.append( agi.streamFile, self.TO_LISTEN_TO_SURVEY_QUESTION )<br>- seq.append( agi.streamFile, self.TO_RECORD_A_NEW_SURVEY_QUESTION )<br>- seq.append( agi.streamFile, self.TO_FINISH_SURVEY_SETUP )<br>- seq.append( agi.finish, )<br>- return seq()<br>- def newQuestionId( self ):<br>- """Return a new, unique, question id"""<br>- import random, sys<br>- bad = True<br>- while bad:<br>- bad = False<br>- id = random.randint(0,sys.maxint)<br>- for question in self.questions:<br>- if id == question.__dict__.get('questionId'):<br>- bad = True<br>- return id<br>-class Question( propertied.Propertied ):<br>- survey = weak.WeakProperty(<br>- "survey", """Our survey object""",<br>- )<br>- questionId = common.IntegerProperty(<br>- "questionId", """Unique identifier for our question""",<br>- defaultFunction = lambda prop,client: client.survey.newQuestionId(),<br>- )<br>- def recordQuestion( self, agi, number=None ):<br>- """Record a question (number)"""<br>- return agi.recordFile( <br>- '%s.%s'%(self.survey.surveyId,self.questionId),<br>- 'gsm',<br>- '#*',<br>- timeout=60,<br>- beep = True,<br>- silence=5,<br>- ).addCallback( <br>- self.onRecorded, agi=agi<br>- ).addErrback(self.onRecordAborted, agi=agi )<br>- def onRecorded( self, result, agi ):<br>- """Handle recording of the question"""<br>- <br> <br>-def getManagerAPI( username, password, server='127.0.0.1', port=5038 ):<br>- """Retrieve a logged-in manager API"""<br>+class Survey(propertied.Propertied):<br>+ """Models a single survey to be completed"""<br>+ surveyId = common.IntegerProperty(<br>+ "surveyId", """Unique identifier for this survey""",<br>+ )<br>+ owner = basic.BasicProperty(<br>+ "owner", """Owner's phone number to which to connect""",<br>+ )<br>+ questions = common.ListProperty(<br>+ "questions", """Set of questions which make up the survey""",<br>+ )<br>+ YOU_CURRENTLY_HAVE = 'vm-youhave'<br>+ QUESTIONS_IN_YOUR_SURVEY = 'vm-messages'<br>+ QUESTION_IN_YOUR_SURVEY = 'vm-message'<br>+ TO_LISTEN_TO_SURVEY_QUESTION = 'to-listen-to-it'<br>+ TO_RECORD_A_NEW_SURVEY_QUESTION = 'to-rerecord-it'<br>+ TO_FINISH_SURVEY_SETUP = 'vm-helpexit'<br>+<br>+ def setupSurvey(self, agi):<br>+ """AGI application to allow the user to set up the survey<br>+<br>+ Screen 1:<br>+ You have # questions.<br>+ To listen to a question, press the number of the question.<br>+ To record a new question, press pound.<br>+ To finish setup, press star.<br>+ """<br>+ seq = fastagi.InSequence()<br>+ seq.append(agi.wait, 2)<br>+ base = """You currently have %s question%s.<br>+ To listen to a question press the number of the question.<br>+ To record a new question, press pound.<br>+ To finish survey setup, press star.<br>+ """ % (<br>+ len(self.questions),<br>+ ['', 's'][len(self.questions) == 1],<br>+ )<br>+ if len(base) != 1:<br>+ base += 's'<br>+ base = " ".join(base.split())<br>+ seq.append(agi.execute, 'Festival', base)<br>+ seq.append(agi.finish,)<br>+ return seq()<br>+ seq.append(agi.streamFile, self.YOU_CURRENTLY_HAVE)<br>+ seq.append(agi.sayNumber, len(self.questions))<br>+ if len(self.questions) == 1:<br>+ seq.append(agi.streamFile, self.QUESTION_IN_YOUR_SURVEY)<br>+ else:<br>+ seq.append(agi.streamFile, self.QUESTIONS_IN_YOUR_SURVEY)<br>+ seq.append(agi.streamFile, self.TO_LISTEN_TO_SURVEY_QUESTION)<br>+ seq.append(agi.streamFile, self.TO_RECORD_A_NEW_SURVEY_QUESTION)<br>+ seq.append(agi.streamFile, self.TO_FINISH_SURVEY_SETUP)<br>+ seq.append(agi.finish,)<br>+ return seq()<br>+<br>+ def newQuestionId(self):<br>+ """Return a new, unique, question id"""<br>+ bad = True<br>+ while bad:<br>+ bad = False<br>+ id = random.randint(0, sys.maxint)<br>+ for question in self.questions:<br>+ if id == question.__dict__.get('questionId'):<br>+ bad = True<br>+ return id<br>+<br>+<br>+class Question(propertied.Propertied):<br>+ survey = weak.WeakProperty(<br>+ "survey", """Our survey object""",<br>+ )<br>+ questionId = common.IntegerProperty(<br>+ "questionId", """Unique identifier for our question""",<br>+ defaultFunction=lambda prop, client: client.survey.newQuestionId(),<br>+ )<br>+<br>+ def recordQuestion(self, agi, number=None):<br>+ """Record a question (number)"""<br>+ return agi.recordFile(<br>+ '%s.%s' % (self.survey.surveyId, self.questionId),<br>+ 'gsm',<br>+ '#*',<br>+ timeout=60,<br>+ beep=True,<br>+ silence=5,<br>+ ).addCallback(<br>+ self.onRecorded, agi=agi<br>+ ).addErrback(self.onRecordAborted, agi=agi)<br>+<br>+ def onRecorded(self, result, agi):<br>+ """Handle recording of the question"""<br>+ pass<br>+<br>+<br>+def getManagerAPI(username, password, server='127.0.0.1', port=5038):<br>+ """Retrieve a logged-in manager API"""<br>+<br> <br> class SurveySetup(rend.Page):<br>- """Page displaying the survey setup"""<br>- addSlash = True<br>- docFactory = loaders.htmlfile( 'index.html' )<br>-<br>-class RecordFunction( rend.Page ):<br>- """Page/application to record survey via call to user"""<br>- def renderHTTP( self, ctx ):<br>- """Process rendering of the request"""<br>- # process request parameters...<br>- request = inevow.IRequest( ctx )<br>- # XXX sanitise and check value...<br>- channel = 'SIP/%s'%( request.args['ownerName'][0], )<br>- <br>- df = APPLICATION.amiSpecifier.login()<br>- def onLogin( ami ):<br>- # Note that the connect comes in *before* the originate returns,<br>- # so we need to wait for the call before we even send it...<br>- userConnectDF = APPLICATION.waitForCallOn( '23', timeout=15 )<br>- APPLICATION.surveys['23'] = survey = Survey()<br>- userConnectDF.addCallback( <br>- survey.setupSurvey, <br>- )<br>- def onComplete( result ):<br>- return ami.logoff()<br>- ami.originate(# don't wait for this to complete...<br>- # XXX handle case where the originate fails differently<br>- # from the case where we just don't get a connection?<br>- channel,<br>- APPLICATION.agiSpecifier.context,<br>- '23',<br>- '1',<br>- timeout=14,<br>- ).addCallbacks( onComplete, onComplete )<br>- return userConnectDF<br>- return df.addCallback( onLogin )<br>+ """Page displaying the survey setup"""<br>+ addSlash = True<br>+ docFactory = loaders.htmlfile('index.html')<br> <br> <br>+class RecordFunction(rend.Page):<br>+ """Page/application to record survey via call to user"""<br>+ def renderHTTP(self, ctx):<br>+ """Process rendering of the request"""<br>+ # process request parameters...<br>+ request = inevow.IRequest(ctx)<br>+ # XXX sanitise and check value...<br>+ channel = 'SIP/%s' % (request.args['ownerName'][0],)<br>+<br>+ df = APPLICATION.amiSpecifier.login()<br>+<br>+ def onLogin(ami):<br>+ # Note that the connect comes in *before* the originate returns,<br>+ # so we need to wait for the call before we even send it...<br>+ userConnectDF = APPLICATION.waitForCallOn('23', timeout=15)<br>+ APPLICATION.surveys['23'] = survey = Survey()<br>+ userConnectDF.addCallback(survey.setupSurvey,)<br>+<br>+ def onComplete(result):<br>+ return ami.logoff()<br>+ ami.originate(<br>+ # don't wait for this to complete...<br>+ # XXX handle case where the originate fails differently<br>+ # from the case where we just don't get a connection?<br>+ channel,<br>+ APPLICATION.agiSpecifier.context,<br>+ '23',<br>+ '1',<br>+ timeout=14,<br>+ ).addCallbacks(onComplete, onComplete)<br>+ return userConnectDF<br>+ return df.addCallback(onLogin)<br> <br> <br> def main():<br>- """Create the web-site"""<br>- s = SurveySetup()<br>- s.putChild( 'record', RecordFunction() )<br>- site = appserver.NevowSite(s)<br>- webServer = internet.TCPServer(8080, site)<br>- webServer.startService()<br>+ """Create the web-site"""<br>+ s = SurveySetup()<br>+ s.putChild('record', RecordFunction())<br>+ site = appserver.NevowSite(s)<br>+ webServer = internet.TCPServer(8080, site)<br>+ webServer.startService()<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- log.setLevel( logging.DEBUG )<br>- manager.log.setLevel( logging.DEBUG )<br>- fastagi.log.setLevel( logging.DEBUG )<br>- APPLICATION = Application()<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- from twisted.internet import reactor<br>- reactor.callWhenRunning( main )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ log.setLevel(logging.DEBUG)<br>+ manager.log.setLevel(logging.DEBUG)<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ APPLICATION = Application()<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br>+ reactor.callWhenRunning(main)<br>+ reactor.run()<br>diff --git a/examples/calldurationcallback.py b/examples/calldurationcallback.py<br>index 00c8260..6ed6cc3 100644<br>--- a/examples/calldurationcallback.py<br>+++ b/examples/calldurationcallback.py<br>@@ -6,26 +6,29 @@<br> for the end of the call, then call them back with the<br> duration message.<br> """<br>-from twisted.application import service, internet<br> from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi<br>+from starpy import fastagi<br> import utilapplication<br> import menu<br>-import os, logging, pprint, time<br>+import logging<br>+import time<br> <br>-log = logging.getLogger( 'callduration' )<br>+log = logging.getLogger('callduration')<br> <br>-class Application( utilapplication.UtilApplication ):<br>+<br>+class Application(utilapplication.UtilApplication):<br> """Application for the call duration callback mechanism"""<br>- def onS( self, agi ):<br>+<br>+ def onS(self, agi):<br> """Incoming AGI connection to the "s" extension (start operation)"""<br>- log.info( """New call tracker""" )<br>+ log.info("""New call tracker""")<br> c = CallTracker()<br>- return c.recordChannelInfo( agi ).addErrback(<br>+ return c.recordChannelInfo(agi).addErrback(<br> agi.jumpOnError, difference=100,<br> )<br> <br>-class CallTracker( object ):<br>+<br>+class CallTracker(object):<br> """Object which tracks duration of a single call<br> <br> This object encapsulates the entire interaction with the user, from<br>@@ -37,7 +40,8 @@<br> as all numeric extensions.<br> """<br> ourContext = 'callduration'<br>- def __init__( self ):<br>+<br>+ def __init__(self):<br> """Initialise the tracker object"""<br> self.uniqueChannelId = None<br> self.currentChannel = None<br>@@ -47,135 +51,151 @@<br> self.ami = None<br> self.startTime = None<br> self.stopTime = None<br>- def recordChannelInfo( self, agi ):<br>+<br>+ def recordChannelInfo(self, agi):<br> """Records relevant channel information, creates manager watcher"""<br> self.uniqueChannelId = agi.variables['agi_uniqueid']<br> self.currentChannel = currentChannel = agi.variables['agi_channel']<br>- # XXX everything up to the last - is normally our local caller's "address"<br>+ # XXX everything up to the last - normally our local caller's "address"<br> # this is not, however, a great way to decide who to call back...<br>- self.callbackChannel = currentChannel.rsplit( '-', 1)[0]<br>+ self.callbackChannel = currentChannel.rsplit('-', 1)[0]<br> # Ask user for the account number...<br> df = menu.CollectDigits(<br>- soundFile = 'your-account',<br>- maxDigits = 7,<br>- minDigits = 3,<br>- timeout = 5,<br>- )( agi ).addCallback(<br>- self.onAccountInput,agi=agi,<br>+ soundFile='your-account',<br>+ maxDigits=7,<br>+ minDigits=3,<br>+ timeout=5,<br>+ )(agi).addCallback(<br>+ self.onAccountInput, agi=agi,<br> )<br> # XXX handle AMI login failure...<br> amiDF = APPLICATION.amiSpecifier.login(<br>- ).addCallback( self.onAMIConnect )<br>- dl = defer.DeferredList( [df, amiDF] )<br>- return dl.addCallback( self.onConnectAndAccount )<br>- def onAccountInput( self, result, agi, retries=2):<br>+ ).addCallback(self.onAMIConnect)<br>+ dl = defer.DeferredList([df, amiDF])<br>+ return dl.addCallback(self.onConnectAndAccount)<br>+<br>+ def onAccountInput(self, result, agi, retries=2):<br> """Allow user to enter again if timed out"""<br> self.account = result[0][1]<br> self.startTime = time.time()<br>- agi.finish() # let the user go about their business...<br>+ agi.finish() # let the user go about their business...<br> return agi<br>- def cleanUp( self, agi=None ):<br>+<br>+ def cleanUp(self, agi=None):<br> """Cleanup on error as much as possible"""<br> items = []<br> if self.ami:<br>- items.append( self.ami.logoff())<br>+ items.append(self.ami.logoff())<br> self.ami = None<br> if items:<br>- return defer.DeferredList( items )<br>+ return defer.DeferredList(items)<br> else:<br>- return defer.succeed( False )<br>- def onAMIConnect( self, ami ):<br>+ return defer.succeed(False)<br>+<br>+ def onAMIConnect(self, ami):<br> """We have successfully connected to the AMI"""<br>- log.debug( "AMI login complete" )<br>+ log.debug("AMI login complete")<br> if not self.cancelled:<br> self.ami = ami<br> return ami<br> else:<br> return self.ami.logoff()<br>- def onConnectAndAccount( self, results ):<br>+<br>+ def onConnectAndAccount(self, results):<br> """We have connected and retrieved an account"""<br>- log.info( """AMI Connected and account information gathered: %s""", self.uniqueChannelId )<br>+ log.info("""AMI Connected and account information gathered: %s""",<br>+ self.uniqueChannelId)<br> df = defer.Deferred()<br>- def onChannelHangup( ami, event ):<br>+<br>+ def onChannelHangup(ami, event):<br> """Deal with the hangup of an event"""<br> if event['uniqueid'] == self.uniqueChannelId:<br>- log.info( """AMI Detected close of our channel: %s""", self.uniqueChannelId )<br>+ log.info("""AMI Detected close of our channel: %s""",<br>+ self.uniqueChannelId)<br> self.stopTime = time.time()<br> # give the user a few seconds to put down the hand-set<br>- reactor.callLater( 2, df.callback, event )<br>- self.ami.deregisterEvent( 'Hangup', onChannelHangup )<br>- log.debug( 'event:', event )<br>+ reactor.callLater(2, df.callback, event)<br>+ self.ami.deregisterEvent('Hangup', onChannelHangup)<br>+ log.debug('event:', event)<br> if not self.cancelled:<br>- self.ami.registerEvent( 'Hangup', onChannelHangup )<br>- return df.addCallback( self.onHangup, callbacks=5 )<br>- def onHangup( self, event, callbacks=5 ):<br>+ self.ami.registerEvent('Hangup', onChannelHangup)<br>+ return df.addCallback(self.onHangup, callbacks=5)<br>+<br>+ def onHangup(self, event, callbacks=5):<br> """Okay, the call is finished, time to inform the user"""<br>- log.debug( 'onHangup %s %s', event, callbacks )<br>- def ignoreResult( result ):<br>+ log.debug('onHangup %s %s', event, callbacks)<br>+<br>+ def ignoreResult(result):<br> """Since we're using an equal timeout waiting for a connect<br> we don't care *how* this fails/succeeds"""<br> pass<br> self.ami.originate(<br> self.callbackChannel,<br> self.ourContext, id(self), 1,<br>- timeout = 15,<br>- ).addCallbacks( ignoreResult, ignoreResult )<br>- df = APPLICATION.waitForCallOn( id(self), 15 )<br>+ timeout=15,<br>+ ).addCallbacks(ignoreResult, ignoreResult)<br>+ df = APPLICATION.waitForCallOn(id(self), 15)<br> df.addCallbacks(<br> self.onUserReconnected, self.onUserReconnectFail,<br>- errbackKeywords = { 'event': event, 'callbacks': callbacks-1 },<br>+ errbackKeywords={'event': event, 'callbacks': callbacks-1},<br> )<br>- def onUserReconnectFail( self, reason, event, callbacks ):<br>+<br>+ def onUserReconnectFail(self, reason, event, callbacks):<br> """Wait for bit, then retry..."""<br> if callbacks:<br> # XXX really want something like a decaying back-off in frequency<br> # with final values of e.g. an hour...<br>- log.info( """Failure connecting: will retry in 30 seconds""" )<br>- reactor.callLater( 30, self.onHangup, event, callbacks )<br>+ log.info("""Failure connecting: will retry in 30 seconds""")<br>+ reactor.callLater(30, self.onHangup, event, callbacks)<br> else:<br>- log.error( """Unable to connect to user, giving up""" )<br>- return self.cleanUp( None )<br>- def onUserReconnected( self, agi ):<br>+ log.error("""Unable to connect to user, giving up""")<br>+ return self.cleanUp(None)<br>+<br>+ def onUserReconnected(self, agi):<br> """Handle the user interaction after they've re-connected"""<br>- log.info( """Connection re-established with the user""" )<br>+ log.info("""Connection re-established with the user""")<br> # XXX should handle unexpected failures in here...<br> delta = self.stopTime - self.startTime<br>- minutes, seconds = divmod( delta, 60 )<br>+ minutes, seconds = divmod(delta, 60)<br> seconds = int(seconds)<br>- hours, minutes = divmod( minutes, 60 )<br>+ hours, minutes = divmod(minutes, 60)<br> duration = []<br> if hours:<br>- duration.append( '%s hour%s'%(hours,['','s'][hours!=1]))<br>+ duration.append('%s hour%s' % (hours, ['', 's'][hours != 1]))<br> if minutes:<br>- duration.append( '%s second%s'%(minutes,['','s'][minutes!=1]))<br>+ duration.append('%s second%s' % (minutes, ['', 's'][minutes != 1]))<br> if seconds:<br>- duration.append( '%s second%s'%(seconds,['','s'][seconds!=1]))<br>+ duration.append('%s second%s' % (seconds, ['', 's'][seconds != 1]))<br> if not duration:<br> duration = '0'<br> else:<br>- duration = " ".join( duration )<br>- seq = fastagi.InSequence( )<br>- seq.append( agi.wait, 1 )<br>- seq.append( agi.execute, "Festival", "Call to account %r took %s"%(self.account,duration) )<br>- seq.append( agi.wait, 1 )<br>- seq.append( agi.execute, "Festival", "Repeating, call to account %r took %s"%(self.account,duration) )<br>- seq.append( agi.wait, 1 )<br>- seq.append( agi.finish )<br>- def logSuccess( ):<br>- log.debug( """Finished successfully!""" )<br>- return defer.succeed( True )<br>- seq.append( logSuccess )<br>- seq.append( self.cleanUp, agi )<br>+ duration = " ".join(duration)<br>+ seq = fastagi.InSequence()<br>+ seq.append(agi.wait, 1)<br>+ seq.append(agi.execute, "Festival", "Call to account %r took %s" % (<br>+ self.account, duration))<br>+ seq.append(agi.wait, 1)<br>+ seq.append(agi.execute, "Festival",<br>+ "Repeating, call to account %r took %s" % (<br>+ self.account, duration))<br>+ seq.append(agi.wait, 1)<br>+ seq.append(agi.finish)<br>+<br>+ def logSuccess():<br>+ log.debug("""Finished successfully!""")<br>+ return defer.succeed(True)<br>+ seq.append(logSuccess)<br>+ seq.append(self.cleanUp, agi)<br> return seq()<br>+<br> <br> APPLICATION = Application()<br> <br> if __name__ == "__main__":<br> logging.basicConfig()<br>- log.setLevel( logging.DEBUG )<br>- #manager.log.setLevel( logging.DEBUG )<br>- #fastagi.log.setLevel( logging.DEBUG )<br>- APPLICATION.handleCallsFor( 's', APPLICATION.onS )<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- from twisted.internet import reactor<br>+ log.setLevel(logging.DEBUG)<br>+ # manager.log.setLevel(logging.DEBUG)<br>+ # fastagi.log.setLevel(logging.DEBUG)<br>+ APPLICATION.handleCallsFor('s', APPLICATION.onS)<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br> reactor.run()<br>diff --git a/examples/connecttoivr.py b/examples/connecttoivr.py<br>index 24dcf63..3790390 100644<br>--- a/examples/connecttoivr.py<br>+++ b/examples/connecttoivr.py<br>@@ -1,38 +1,46 @@<br> """Example script to generate a call to connect a remote channel to an IVR"""<br> from starpy import manager<br> from twisted.internet import reactor<br>-import sys, logging<br>+import sys<br>+import logging<br> <br>-def main( channel = 'sip/20035@aci.on.ca', connectTo=('outgoing','s','1') ):<br>- f = manager.AMIFactory(sys.argv[1], sys.argv[2])<br>- df = f.login()<br>- def onLogin( protocol ):<br>- """On Login, attempt to originate the call"""<br>- context, extension, priority = connectTo<br>- df = protocol.originate(<br>- channel,<br>- context,extension,priority,<br>- )<br>- def onFinished( result ):<br>- df = protocol.logoff()<br>- def onLogoff( result ):<br>- reactor.stop()<br>- return df.addCallbacks( onLogoff, onLogoff )<br>- def onFailure( reason ):<br>- print reason.getTraceback()<br>- return reason <br>- df.addErrback( onFailure )<br>- df.addCallbacks( onFinished, onFinished )<br>- return df <br>- def onFailure( reason ):<br>- """Unable to log in!"""<br>- print reason.getTraceback()<br>- reactor.stop()<br>- df.addCallbacks( onLogin, onFailure )<br>- return df<br>+<br>+def main(channel='sip/20035@aci.on.ca', connectTo=('outgoing', 's', '1')):<br>+ f = manager.AMIFactory(sys.argv[1], sys.argv[2])<br>+ df = f.login()<br>+<br>+ def onLogin(protocol):<br>+ """On Login, attempt to originate the call"""<br>+ context, extension, priority = connectTo<br>+ df = protocol.originate(<br>+ channel,<br>+ context, extension, priority,<br>+ )<br>+<br>+ def onFinished(result):<br>+ df = protocol.logoff()<br>+<br>+ def onLogoff(result):<br>+ reactor.stop()<br>+ return df.addCallbacks(onLogoff, onLogoff)<br>+<br>+ def onFailure(reason):<br>+ print(reason.getTraceback())<br>+ return reason<br>+ df.addErrback(onFailure)<br>+ df.addCallbacks(onFinished, onFinished)<br>+ return df<br>+<br>+ def onFailure(reason):<br>+ """Unable to log in!"""<br>+ print(reason.getTraceback())<br>+ reactor.stop()<br>+ df.addCallbacks(onLogin, onFailure)<br>+ return df<br>+<br> <br> if __name__ == "__main__":<br>- manager.log.setLevel( logging.DEBUG )<br>- logging.basicConfig()<br>- reactor.callWhenRunning( main )<br>- reactor.run()<br>+ manager.log.setLevel(logging.DEBUG)<br>+ logging.basicConfig()<br>+ reactor.callWhenRunning(main)<br>+ reactor.run()<br>diff --git a/examples/connecttoivrapp.py b/examples/connecttoivrapp.py<br>index aeed61d..829ab66 100644<br>--- a/examples/connecttoivrapp.py<br>+++ b/examples/connecttoivrapp.py<br>@@ -3,35 +3,41 @@<br> This version of the script uses the utilapplication framework and is<br> pared down for presentation on a series of slides<br> """<br>-from starpy import manager<br> import utilapplication<br> from twisted.internet import reactor<br>-import sys, logging<br>+import logging<br>+<br> APPLICATION = utilapplication.UtilApplication()<br> <br>-def main( channel = 'sip/4167290048@testout', connectTo=('outgoing','s','1') ):<br>- df = APPLICATION.amiSpecifier.login()<br>- def onLogin( protocol ):<br>- """We've logged into the manager, generate a call and log off"""<br>- context, extension, priority = connectTo<br>- df = protocol.originate(<br>- channel,<br>- context,extension,priority,<br>- )<br>- def onFinished( result ):<br>- return protocol.logoff()<br>- df.addCallbacks( onFinished, onFinished )<br>- return df <br>- def onFailure( reason ):<br>- print reason.getTraceback()<br>- def onFinished( result ):<br>- reactor.stop()<br>- df.addCallbacks( <br>- onLogin, onFailure <br>- ).addCallbacks( onFinished, onFinished )<br>- return df<br>+<br>+def main(channel='sip/4167290048@testout', connectTo=('outgoing', 's', '1')):<br>+ df = APPLICATION.amiSpecifier.login()<br>+<br>+ def onLogin(protocol):<br>+ """We've logged into the manager, generate a call and log off"""<br>+ context, extension, priority = connectTo<br>+ df = protocol.originate(<br>+ channel,<br>+ context, extension, priority,<br>+ )<br>+<br>+ def onFinished(result):<br>+ return protocol.logoff()<br>+ df.addCallbacks(onFinished, onFinished)<br>+ return df<br>+<br>+ def onFailure(reason):<br>+ print(reason.getTraceback())<br>+<br>+ def onFinished(result):<br>+ reactor.stop()<br>+ df.addCallbacks(<br>+ onLogin, onFailure<br>+ ).addCallbacks(onFinished, onFinished)<br>+ return df<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- reactor.callWhenRunning( main )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ reactor.callWhenRunning(main)<br>+ reactor.run()<br>diff --git a/examples/fastagisetvariable.py b/examples/fastagisetvariable.py<br>index 44c3da7..3a4b774 100644<br>--- a/examples/fastagisetvariable.py<br>+++ b/examples/fastagisetvariable.py<br>@@ -3,26 +3,31 @@<br> from twisted.internet import reactor<br> from starpy import fastagi<br> import utilapplication<br>-import logging, time<br>+import logging<br> <br>-log = logging.getLogger( 'hellofastagi' )<br>+log = logging.getLogger('hellofastagi')<br> <br>-def testFunction( agi ):<br>- """Demonstrate simplistic use of the AGI interface with sequence of actions"""<br>- log.debug( 'testFunction' )<br>- def setX( ):<br>- return agi.setVariable( 'this"toset', 'That"2set' )<br>- def getX( result ):<br>- return agi.getVariable( 'this"toset' )<br>- def onX( value ):<br>- print 'Retrieved value', value <br>- reactor.stop()<br>- return setX().addCallback( getX ).addCallbacks( onX, onX )<br>+<br>+def testFunction(agi):<br>+ """Demonstrate simple use of the AGI interface with sequence of actions"""<br>+ log.debug('testFunction')<br>+<br>+ def setX():<br>+ return agi.setVariable('this"toset', 'That"2set')<br>+<br>+ def getX(result):<br>+ return agi.getVariable('this"toset')<br>+<br>+ def onX(value):<br>+ print('Retrieved value', value)<br>+ reactor.stop()<br>+ return setX().addCallback(getX).addCallbacks(onX, onX)<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.DEBUG )<br>- APPLICATION = utilapplication.UtilApplication()<br>- APPLICATION.handleCallsFor( 's', testFunction )<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ APPLICATION = utilapplication.UtilApplication()<br>+ APPLICATION.handleCallsFor('s', testFunction)<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br>+ reactor.run()<br>diff --git a/examples/getvariable.py b/examples/getvariable.py<br>index 6929240..43a8e60 100644<br>--- a/examples/getvariable.py<br>+++ b/examples/getvariable.py<br>@@ -4,56 +4,63 @@<br> from twisted.internet import reactor<br> from starpy import fastagi<br> import utilapplication<br>-import logging, time, pprint<br>+import logging<br>+import pprint<br> <br>-log = logging.getLogger( 'hellofastagi' )<br>+log = logging.getLogger('hellofastagi')<br> <br>-def envVars( agi ):<br>- """Print out channel variables for display"""<br>- vars = [<br>- x.split( ' -- ' )[0].strip() <br>- for x in agi.getVariable.__doc__.splitlines()<br>- if len(x.split( ' -- ' )) == 2<br>- ]<br>- for var in vars:<br>- yield var <br> <br>-def printVar( result, agi, vars ):<br>- """Print out the variables produced by envVars"""<br>- def doPrint( result, var ):<br>- print '%r -- %r'%( var, result )<br>- def notAvailable( reason, var ):<br>- print '%r -- UNDEFINED'%( var, )<br>- try:<br>- var = vars.next()<br>- except StopIteration, err:<br>- return None<br>- else:<br>- return agi.getVariable( var ).addCallback( doPrint, var ).addErrback(<br>- notAvailable, var,<br>- ).addCallback(<br>- printVar, agi, vars,<br>- )<br>- <br>+def envVars(agi):<br>+ """Print out channel variables for display"""<br>+ vars = [<br>+ x.split(' -- ')[0].strip()<br>+ for x in agi.getVariable.__doc__.splitlines()<br>+ if len(x.split(' -- ')) == 2<br>+ ]<br>+ for var in vars:<br>+ yield var<br> <br>-def testFunction( agi ):<br>- """Print out known AGI variables"""<br>- log.debug( 'testFunction' )<br>- print 'AGI Variables'<br>- pprint.pprint( agi.variables )<br>- print 'Channel Variables'<br>- sequence = fastagi.InSequence()<br>- sequence.append( printVar, None, agi, envVars(agi) )<br>- sequence.append( agi.finish )<br>- def onFailure( reason ):<br>- log.error( "Failure: %s", reason.getTraceback())<br>- agi.finish()<br>- return sequence().addErrback( onFailure )<br>+<br>+def printVar(result, agi, vars):<br>+ """Print out the variables produced by envVars"""<br>+<br>+ def doPrint(result, var):<br>+ print('%r -- %r' % (var, result))<br>+<br>+ def notAvailable(reason, var):<br>+ print('%r -- UNDEFINED' % (var,))<br>+ try:<br>+ var = vars.next()<br>+ except StopIteration as err:<br>+ return None<br>+ else:<br>+ return agi.getVariable(var).addCallback(doPrint, var).addErrback(<br>+ notAvailable, var,<br>+ ).addCallback(<br>+ printVar, agi, vars,<br>+ )<br>+<br>+<br>+def testFunction(agi):<br>+ """Print out known AGI variables"""<br>+ log.debug('testFunction')<br>+ print('AGI Variables')<br>+ pprint.pprint(agi.variables)<br>+ print('Channel Variables')<br>+ sequence = fastagi.InSequence()<br>+ sequence.append(printVar, None, agi, envVars(agi))<br>+ sequence.append(agi.finish)<br>+<br>+ def onFailure(reason):<br>+ log.error("Failure: %s", reason.getTraceback())<br>+ agi.finish()<br>+ return sequence().addErrback(onFailure)<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- #fastagi.log.setLevel( logging.DEBUG )<br>- APPLICATION = utilapplication.UtilApplication()<br>- APPLICATION.handleCallsFor( 's', testFunction )<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ # fastagi.log.setLevel(logging.DEBUG)<br>+ APPLICATION = utilapplication.UtilApplication()<br>+ APPLICATION.handleCallsFor('s', testFunction)<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br>+ reactor.run()<br>diff --git a/examples/hellofastagi.py b/examples/hellofastagi.py<br>index 1434dad..f491ca4 100644<br>--- a/examples/hellofastagi.py<br>+++ b/examples/hellofastagi.py<br>@@ -2,24 +2,30 @@<br> """Simple FastAGI server using starpy"""<br> from twisted.internet import reactor<br> from starpy import fastagi<br>-import logging, time<br>+import logging<br>+import time<br> <br>-log = logging.getLogger( 'hellofastagi' )<br> <br>-def testFunction( agi ):<br>- """Demonstrate simplistic use of the AGI interface with sequence of actions"""<br>- log.debug( 'testFunction' )<br>- sequence = fastagi.InSequence()<br>- sequence.append( agi.sayDateTime, time.time() )<br>- sequence.append( agi.finish )<br>- def onFailure( reason ):<br>- log.error( "Failure: %s", reason.getTraceback())<br>- agi.finish()<br>- return sequence().addErrback( onFailure )<br>+log = logging.getLogger('hellofastagi')<br>+<br>+<br>+def testFunction(agi):<br>+ """Demonstrate simple use of the AGI interface with sequence of actions"""<br>+ log.debug('testFunction')<br>+ sequence = fastagi.InSequence()<br>+ sequence.append(agi.sayDateTime, time.time())<br>+ sequence.append(agi.finish)<br>+<br>+ def onFailure(reason):<br>+ log.error("Failure: %s", reason.getTraceback())<br>+ agi.finish()<br>+ return sequence().addErrback(onFailure)<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.DEBUG )<br>- f = fastagi.FastAGIFactory(testFunction)<br>- reactor.listenTCP(4573, f, 50, '127.0.0.1') # only binding on local interface<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ f = fastagi.FastAGIFactory(testFunction)<br>+ # only binding on local interface<br>+ reactor.listenTCP(4573, f, 50, '127.0.0.1')<br>+ reactor.run()<br>diff --git a/examples/hellofastagiapp.py b/examples/hellofastagiapp.py<br>index 51bc06c..6ed80be 100644<br>--- a/examples/hellofastagiapp.py<br>+++ b/examples/hellofastagiapp.py<br>@@ -7,25 +7,29 @@<br> from twisted.internet import reactor<br> from starpy import fastagi<br> import utilapplication<br>-import logging, time<br>+import logging<br>+import time<br> <br>-log = logging.getLogger( 'hellofastagi' )<br>+log = logging.getLogger('hellofastagi')<br> <br>-def testFunction( agi ):<br>- """Demonstrate simplistic use of the AGI interface with sequence of actions"""<br>- log.debug( 'testFunction' )<br>- sequence = fastagi.InSequence()<br>- sequence.append( agi.sayDateTime, time.time() )<br>- sequence.append( agi.finish )<br>- def onFailure( reason ):<br>- log.error( "Failure: %s", reason.getTraceback())<br>- agi.finish()<br>- return sequence().addErrback( onFailure )<br>+<br>+def testFunction(agi):<br>+ """Demonstrate simple use of the AGI interface with sequence of actions"""<br>+ log.debug('testFunction')<br>+ sequence = fastagi.InSequence()<br>+ sequence.append(agi.sayDateTime, time.time())<br>+ sequence.append(agi.finish)<br>+<br>+ def onFailure(reason):<br>+ log.error("Failure: %s", reason.getTraceback())<br>+ agi.finish()<br>+ return sequence().addErrback(onFailure)<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.DEBUG )<br>- APPLICATION = utilapplication.UtilApplication()<br>- APPLICATION.handleCallsFor( 's', testFunction )<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ APPLICATION = utilapplication.UtilApplication()<br>+ APPLICATION.handleCallsFor('s', testFunction)<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br>+ reactor.run()<br>diff --git a/examples/menu.py b/examples/menu.py<br>index a847c2d..847e255 100644<br>--- a/examples/menu.py<br>+++ b/examples/menu.py<br>@@ -1,6 +1,6 @@<br> #<br> # StarPy -- Asterisk Protocols for Twisted<br>-# <br>+#<br> # Copyright (c) 2006, Michael C. Fletcher<br> #<br> # Michael C. Fletcher <mcfletch@vrplumber.com><br>@@ -29,34 +29,38 @@<br> XXX add the reject/accept menus to the CollectDigits (requires soundfiles<br> in standard locations on the server, complicates install)<br> """<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi, error<br>-import utilapplication<br>-import os, logging, pprint, time<br>+from twisted.internet import defer<br>+from starpy import error<br>+import os<br>+import sys<br>+import logging<br> from basicproperty import common, propertied, basic<br> <br> log = logging.getLogger('menu')<br> log.setLevel(logging.DEBUG)<br>+<br> <br> class Interaction(propertied.Propertied):<br> """Base class for user-interaction operations"""<br> ALL_DIGITS = '0123456789*#'<br> timeout = common.FloatProperty(<br> "timeout", """Duration to wait for response before repeating message""",<br>- defaultValue = 5,<br>+ defaultValue=5,<br> )<br> maxRepetitions = common.IntegerProperty(<br> "maxRepetitions", """Maximum number of times to play before failure""",<br>- defaultValue = 5,<br>+ defaultValue=5,<br> )<br> onSuccess = basic.BasicProperty(<br>- "onSuccess", """Optional callback for success with signature method( result, runner )""",<br>+ "onSuccess",<br>+ """Optional callback for success with signature method( result, runner )""",<br> )<br> onFailure = basic.BasicProperty(<br>- "onFailure", """Optional callback for failure with signature method( result, runner )""",<br>+ "onFailure",<br>+ """Optional callback for failure with signature method( result, runner )""",<br> )<br> runnerClass = None<br>+<br> def __call__(self, agi, *args, **named):<br> """Initiate AGI-based interaction with the user"""<br> return self.runnerClass(model=self, agi=agi)(*args, **named)<br>@@ -67,6 +71,7 @@<br> agi = basic.BasicProperty(<br> "agi", """The AGI instance we use to communicate with the user""",<br> )<br>+<br> def defaultFinalDF(prop, client):<br> """Produce the default finalDF with onSuccess/onFailure support"""<br> df = defer.Deferred()<br>@@ -80,13 +85,13 @@<br> return df<br> finalDF = basic.BasicProperty(<br> "finalDF", """Final deferred we will callback/errback on success/failure""",<br>- defaultFunction = defaultFinalDF,<br>+ defaultFunction=defaultFinalDF,<br> )<br> del defaultFinalDF<br> <br> alreadyRepeated = common.IntegerProperty(<br> "alreadyRepeated", """Number of times we've repeated the message...""",<br>- defaultValue = 0,<br>+ defaultValue=0,<br> )<br> model = basic.BasicProperty(<br> "model", """The data-model that we are presenting to the user (e.g. Menu)""",<br>@@ -94,7 +99,7 @@<br> <br> def returnResult(self, result):<br> """Return result of deferred to our original caller"""<br>- log.debug('returnResult: %s %s', self.model,result)<br>+ log.debug('returnResult: %s %s', self.model, result)<br> if not self.finalDF.called:<br> self.finalDF.debug = True<br> self.finalDF.callback(result)<br>@@ -117,20 +122,23 @@<br> """Take set of prompt-compatible objects and produce a PromptRunner for them"""<br> realPrompt = []<br> for p in prompt:<br>- if isinstance(p, (str, unicode)):<br>+ if isinstance(p, str):<br>+ p = AudioPrompt(p)<br>+ elif sys.version_info <= (3, 0) and isinstance(p, unicode): # noqa<br> p = AudioPrompt(p)<br> elif isinstance(p, int):<br> p = NumberPrompt(p)<br> elif not isinstance(p, Prompt):<br>- raise TypeError( """Unknown prompt element type on %r: %s"""%(<br>- p, p.__class__,<br>- ))<br>+ raise TypeError(<br>+ """Unknown prompt element type on %r: %s""" % (<br>+ p, p.__class__, )<br>+ )<br> realPrompt.append(p)<br> return PromptRunner(<br>- elements = realPrompt,<br>- escapeDigits = self.escapeDigits,<br>- agi = self.agi,<br>- timeout = self.model.timeout,<br>+ elements=realPrompt,<br>+ escapeDigits=self.escapeDigits,<br>+ agi=self.agi,<br>+ timeout=self.model.timeout,<br> )<br> <br> <br>@@ -151,7 +159,7 @@<br> # easiest possibility, just read out the file...<br> return self.agi.getData(<br> soundFile, timeout=self.model.timeout,<br>- maxDigits = getattr(self.model, 'maxDigits', None),<br>+ maxDigits=getattr(self.model, 'maxDigits', None),<br> ).addCallback(self.onReadDigits).addErrback(self.returnError)<br> else:<br> raise NotImplemented("""Haven't got non-soundfile menus working yet""")<br>@@ -166,8 +174,9 @@<br> return False, 'Too few digits'<br> return True, None<br> <br>- def onReadDigits(self, (digits,timeout)):<br>+ def onReadDigits(self, values):<br> """Deal with succesful result from reading digits"""<br>+ (digits, timeout) = values<br> log.info("""onReadDigits: %r, %s""", digits, timeout)<br> valid, reason = self.validEntry(digits)<br> if (not digits) and (not timeout):<br>@@ -183,7 +192,8 @@<br> pass<br> self.alreadyRepeated += 1<br> if self.alreadyRepeated >= self.model.maxRepetitions:<br>- log.warn("""User did not complete digit-entry for %s, timing out""", self.model)<br>+ log.warn("""User did not complete digit-entry for %s, timing out""",<br>+ self.model)<br> raise error.MenuTimeout(<br> self.model,<br> """User did not finish digit-entry in %s passes of collection""" % (<br>@@ -201,6 +211,7 @@<br> expected = common.StringLocaleProperty(<br> "expected", """The value expected/required from the user for this run""",<br> )<br>+<br> def __call__(self, expected, *args, **named):<br> """Begin the AGI processing for the menu"""<br> self.expected = expected<br>@@ -223,9 +234,10 @@<br> """Audio-collection runner, records user audio to a file on the asterisk server"""<br> escapeDigits = common.StringLocaleProperty(<br> "escapeDigits", """Set of digits which escape from recording""",<br>- defaultFunction = lambda prop, client: client.model.escapeDigits,<br>- setDefaultOnGet = False,<br>+ defaultFunction=lambda prop, client: client.model.escapeDigits,<br>+ setDefaultOnGet=False,<br> )<br>+<br> def __call__(self, *args, **named):<br> """Begin the AGI processing for the menu"""<br> self.readPrompt()<br>@@ -252,7 +264,7 @@<br> else:<br> return self.collectAudio()<br> <br>- def collectAudio( self ):<br>+ def collectAudio(self):<br> """We're supposed to record audio from the user with our model's parameters"""<br> # XXX use a temporary file for recording the audio, then move to final destination<br> log.debug('collectAudio')<br>@@ -280,8 +292,8 @@<br> digits, typeOfExit, endpos = result<br> if typeOfExit in ('hangup', 'timeout'):<br> # expected common-case for recording...<br>- return self.returnResult((self,(digits,typeOfExit,endpos)))<br>- elif typeOfExit =='dtmf':<br>+ return self.returnResult((self, (digits, typeOfExit, endpos)))<br>+ elif typeOfExit == 'dtmf':<br> raise error.MenuExit(<br> self.model,<br> """User cancelled entry of audio""",<br>@@ -297,7 +309,7 @@<br> """Failure collecting audio for CollectAudio instance %s: %s""",<br> self.model, reason.getTraceback(),<br> )<br>- return reason # re-raise the error...<br>+ return reason # re-raise the error...<br> <br> def moveToFinal(self, result):<br> """On succesful recording, move temporaryFile to final file"""<br>@@ -305,13 +317,12 @@<br> 'Moving recorded audio %r to final destination %r',<br> self.model.temporaryFile, self.model.filename<br> )<br>- import os<br> try:<br> os.rename(<br> '%s.%s' % (self.model.temporaryFile, self.model.format),<br> '%s.%s' % (self.model.filename, self.model.format),<br> )<br>- except (OSError, IOError), err:<br>+ except (OSError, IOError) as err:<br> log.error(<br> """Unable to move temporary recording file %r to target file %r: %s""",<br> self.model.temporaryFile, self.model.filename,<br>@@ -324,6 +335,7 @@<br> <br> class MenuRunner(Runner):<br> """User's single interaction with a given menu"""<br>+<br> def defaultEscapeDigits(prop, client):<br> """Return the default escape digits for the given client"""<br> if client.model.tellInvalid:<br>@@ -333,9 +345,9 @@<br> return escapeDigits<br> escapeDigits = common.StringLocaleProperty(<br> "escapeDigits", """Set of digits which escape from prompts to choose option""",<br>- defaultFunction = defaultEscapeDigits,<br>+ defaultFunction=defaultEscapeDigits,<br> )<br>- del defaultEscapeDigits # clean up namespace<br>+ del defaultEscapeDigits # clean up namespace<br> <br> def __call__(self, *args, **named):<br> """Begin the AGI processing for the menu"""<br>@@ -353,7 +365,8 @@<br> if not pressed:<br> self.alreadyRepeated += 1<br> if self.alreadyRepeated >= self.model.maxRepetitions:<br>- log.warn("""User did not complete menu selection for %s, timing out""", self.model)<br>+ log.warn("""User did not complete menu selection for %s, timing out""",<br>+ self.model)<br> if not self.finalDF.called:<br> raise error.MenuTimeout(<br> self.model,<br>@@ -374,15 +387,16 @@<br> self.returnResult, self.returnError<br> )<br> elif hasattr(option, 'onSuccess'):<br>- return defer.maybeDeferred(option.onSuccess, pressed, self).addCallbacks(<br>- self.returnResult, self.returnError<br>- )<br>+ return defer.maybeDeferred(option.onSuccess, pressed, self) \<br>+ .addCallbacks(self.returnResult, self.returnError)<br> else:<br>- return self.returnResult([(option,pressed),])<br>+ return self.returnResult([(option, pressed), ])<br> # but it wasn't anything we expected...<br> if not self.model.tellInvalid:<br> raise error.MenuUnexpectedOption(<br>- self.model, """User somehow selected %r, which isn't a recognised option?""" % (pressed,),<br>+ self.model,<br>+ "User somehow selected %r, which isn't a recognised option?" % (<br>+ pressed,),<br> )<br> else:<br> return self.agi.getOption(<br>@@ -424,8 +438,9 @@<br> "options", """Set of options the user may select""",<br> )<br> tellInvalid = common.IntegerProperty(<br>- "tellInvalid", """Whether to tell the user that their selection is unrecognised""",<br>- defaultValue = True,<br>+ "tellInvalid",<br>+ """Whether to tell the user that their selection is unrecognised""",<br>+ defaultValue=True,<br> )<br> runnerClass = MenuRunner<br> <br>@@ -442,11 +457,12 @@<br> menu = basic.BasicProperty(<br> "menu", """The sub-menu we are presenting to the user""",<br> )<br>+<br> def __call__(self, pressed, parent):<br> """Get result from the sub-menu, add ourselves into the result"""<br> def onResult(result):<br> log.debug("""Child menu %s result: %s""", self.menu, result)<br>- result.insert(0, (self,pressed))<br>+ result.insert(0, (self, pressed))<br> return result<br> <br> def onFailure(reason):<br>@@ -460,6 +476,7 @@<br> <br> class ExitOn(Option):<br> """An option which exits from the current menu level"""<br>+<br> def __call__(self, pressed, parent):<br> """Raise a MenuExit error"""<br> raise error.MenuExit(<br>@@ -477,18 +494,21 @@<br> )<br> readBack = common.BooleanProperty(<br> "readBack", """Whether to read the entered value back to the user""",<br>- defaultValue = False,<br>+ defaultValue=False,<br> )<br> minDigits = common.IntegerProperty(<br>- "minDigits", """Minimum number of digits to collect (only restricted if specified)""",<br>+ "minDigits",<br>+ """Minimum number of digits to collect (only restricted if specified)""",<br> )<br> maxDigits = common.IntegerProperty(<br>- "maxDigits", """Maximum number of digits to collect (only restricted if specified)""",<br>+ "maxDigits",<br>+ """Maximum number of digits to collect (only restricted if specified)""",<br> )<br> runnerClass = CollectDigitsRunner<br> tellInvalid = common.IntegerProperty(<br>- "tellInvalid", """Whether to tell the user that their selection is unrecognised""",<br>- defaultValue = True,<br>+ "tellInvalid",<br>+ """Whether to tell the user that their selection is unrecognised""",<br>+ defaultValue=True,<br> )<br> <br> <br>@@ -497,11 +517,11 @@<br> runnerClass = CollectPasswordRunner<br> escapeDigits = common.StringLocaleProperty(<br> "escapeDigits", """Set of digits which escape from password entry""",<br>- defaultValue = '',<br>+ defaultValue='',<br> )<br> soundFile = common.StringLocaleProperty(<br> "soundFile", """File (name) for the pre-recorded blurb""",<br>- defaultValue = 'vm-password',<br>+ defaultValue='vm-password',<br> )<br> <br> <br>@@ -517,30 +537,31 @@<br> "textPrompt", """Textual prompt describing the option""",<br> )<br> temporaryFile = common.StringLocaleProperty(<br>- "temporaryFile", """Temporary file into which to record the audio before moving to filename""",<br>+ "temporaryFile",<br>+ """Temporary file into which to record the audio before moving to filename""",<br> )<br> filename = common.StringLocaleProperty(<br> "filename", """Final filename into which to record the file...""",<br> )<br> deleteOnFail = common.BooleanProperty(<br> "deleteOnFail", """Whether to delete failed attempts to record a file""",<br>- defaultValue = True<br>+ defaultValue=True<br> )<br> escapeDigits = common.StringLocaleProperty(<br> "escapeDigits", """Set of digits which escape from recording the file""",<br>- defaultValue = '#*0123456789',<br>+ defaultValue='#*0123456789',<br> )<br> timeout = common.FloatProperty(<br> "timeout", """Duration to wait for recording (maximum record time)""",<br>- defaultValue = 60,<br>+ defaultValue=60,<br> )<br> silence = common.FloatProperty(<br> "silence", """Duration to wait for recording (maximum record time)""",<br>- defaultValue = 5,<br>+ defaultValue=5,<br> )<br> beep = common.BooleanProperty(<br> "beep", """Whether to play a "beep" sound at beginning of recording""",<br>- defaultValue = True,<br>+ defaultValue=True,<br> )<br> runnerClass = CollectAudioRunner<br> <br>@@ -560,6 +581,7 @@<br> timeout = common.FloatProperty(<br> "timeout", """Timeout on data-entry after completed reading""",<br> )<br>+<br> def __call__(self):<br> """Return a deferred that chains all of the sub-prompts in order<br> <br>@@ -575,7 +597,7 @@<br> return result<br> try:<br> element = self.elements[index]<br>- except IndexError, err:<br>+ except IndexError as err:<br> # okay, do a waitForDigit from timeout seconds...<br> return self.agi.waitForDigit(self.timeout).addCallback(<br> self.processKey<br>@@ -592,7 +614,7 @@<br> # getOption result...<br> if result[1] == 0:<br> # failure during load of the file...<br>- log.warn("""Apparent failure during load of audio file: %s""", self.value)<br>+ log.warn("Apparent failure during load of audio file: %s", self.value)<br> result = 0<br> else:<br> result = result[0]<br>@@ -601,7 +623,7 @@<br> result = ord(result)<br> else:<br> result = 0<br>- if result: # None or 0<br>+ if result: # None or 0<br> # User pressed a key during the reading...<br> key = chr(result)<br> if key in self.escapeDigits:<br>@@ -613,7 +635,7 @@<br> # completed reading without any escape digits, continue reading<br> return None<br> <br>- def processLast(self,result):<br>+ def processLast(self, result):<br> if result is None:<br> result = ''<br> return result<br>@@ -624,6 +646,7 @@<br> value = basic.BasicProperty(<br> "value", """Filename to be read to the user""",<br> )<br>+<br> def __init__(self, value, **named):<br> named['value'] = value<br> super(Prompt, self).__init__(**named)<br>@@ -648,6 +671,7 @@<br> value = common.IntegerProperty(<br> "value", """Integer numeral to read""",<br> )<br>+<br> def read(self, agi, escapeDigits):<br> """Read the audio prompt to the user"""<br> return agi.sayNumber(self.value, escapeDigits)<br>@@ -671,8 +695,9 @@<br> """Prompt that reads a date/time as a date"""<br> format = basic.BasicProperty(<br> "format", """Format in which to read the date to the user""",<br>- defaultValue = None<br>+ defaultValue=None<br> )<br>+<br> def read(self, agi, escapeDigits):<br> """Read the audio prompt to the user"""<br> return agi.sayDateTime(self.value, escapeDigits, format=self.format)<br>diff --git a/examples/menutest.py b/examples/menutest.py<br>index 55d6726..e7a1ab0 100644<br>--- a/examples/menutest.py<br>+++ b/examples/menutest.py<br>@@ -1,81 +1,80 @@<br> #! /usr/bin/env python<br> """Sample application to test the menuing utility classes"""<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi, error<br>+from twisted.internet import reactor<br>+from starpy import fastagi<br> import utilapplication<br> import menu<br>-import os, logging, pprint, time<br>+import logging<br> <br>-log = logging.getLogger( 'menutest' )<br>+log = logging.getLogger('menutest')<br> <br> mainMenu = menu.Menu(<br>- prompt = '/home/mcfletch/starpydemo/soundfiles/menutest-toplevel',<br>- #prompt = 'houston',<br>- textPrompt = '''Top level of the menu test example<br>- <br>- Pressing Star will exit this menu at any time.<br>- Options zero and pound will exit with those options selected.<br>- Option one will start a submenu.<br>- Option two will start a digit-collecting sub-menu.<br>- We'll tell you if you make an invalid selection here.''',<br>- options = [<br>- menu.Option( option='0' ),<br>- menu.Option( option='#' ),<br>- menu.ExitOn( option='*' ),<br>- menu.SubMenu( <br>- option='1',<br>- menu = menu.Menu(<br>- prompt = '/home/mcfletch/starpydemo/soundfiles/menutest-secondlevel',<br>- #prompt = 'atlantic',<br>- textPrompt = '''A second-level menu in the menu test example<br>- <br>- Pressing Star will exit this menu at any time.<br>- Options zero and pound will exit the whole menu with those options selected.<br>- We won't tell you if you make an invalid selection here.<br>- ''',<br>- tellInvalid = False, # don't report incorrect selections<br>- options = [<br>- menu.Option( option='0' ),<br>- menu.Option( option='#' ),<br>- menu.ExitOn( option='*' ),<br>- ],<br>- ),<br>- ),<br>- menu.SubMenu(<br>- option='2',<br>- menu = menu.CollectDigits(<br>- textPrompt = '''Digit collection example,<br>- Please enter three to 5 digits.<br>- ''',<br>- soundFile = '/home/mcfletch/starpydemo/soundfiles/menutest-digits',<br>- #soundFile = 'extension',<br>- maxDigits = 5,<br>- minDigits = 3,<br>- ),<br>- ),<br>- ],<br>+ prompt='/home/mcfletch/starpydemo/soundfiles/menutest-toplevel',<br>+ textPrompt='''Top level of the menu test example<br>+<br>+ Pressing Star will exit this menu at any time.<br>+ Options zero and pound will exit with those options selected.<br>+ Option one will start a submenu.<br>+ Option two will start a digit-collecting sub-menu.<br>+ We'll tell you if you make an invalid selection here.''',<br>+ options=[<br>+ menu.Option(option='0'),<br>+ menu.Option(option='#'),<br>+ menu.ExitOn(option='*'),<br>+ menu.SubMenu(<br>+ option='1',<br>+ menu=menu.Menu(<br>+ prompt='/home/mcfletch/starpydemo/soundfiles/menutest-secondlevel',<br>+ textPrompt='''A second-level menu in the menu test example<br>+<br>+ Pressing Star will exit this menu at any time. Options zero<br>+ and pound will exit the whole menu with those options selected.<br>+ We won't tell you if you make an invalid selection here.<br>+ ''',<br>+ tellInvalid=False, # don't report incorrect selections<br>+ options=[<br>+ menu.Option(option='0'),<br>+ menu.Option(option='#'),<br>+ menu.ExitOn(option='*'),<br>+ ],<br>+ ),<br>+ ),<br>+ menu.SubMenu(<br>+ option='2',<br>+ menu=menu.CollectDigits(<br>+ textPrompt='''Digit collection example,<br>+ Please enter three to 5 digits.<br>+ ''',<br>+ soundFile='/home/mcfletch/starpydemo/soundfiles/menutest-digits',<br>+ maxDigits=5,<br>+ minDigits=3,<br>+ ),<br>+ ),<br>+ ],<br> )<br> <br>-class Application( utilapplication.UtilApplication ):<br>- """Application for the call duration callback mechanism"""<br>- def onS( self, agi ):<br>- """Incoming AGI connection to the "s" extension (start operation)"""<br>- log.info( """New call tracker""" )<br>- def onComplete( result ):<br>- log.info( """Final result: %r""", result )<br>- agi.finish()<br>- return mainMenu( agi ).addCallbacks( onComplete, onComplete )<br>+<br>+class Application(utilapplication.UtilApplication):<br>+ """Application for the call duration callback mechanism"""<br>+<br>+ def onS(self, agi):<br>+ """Incoming AGI connection to the "s" extension (start operation)"""<br>+ log.info("""New call tracker""")<br>+<br>+ def onComplete(result):<br>+ log.info("""Final result: %r""", result)<br>+ agi.finish()<br>+ return mainMenu(agi).addCallbacks(onComplete, onComplete)<br>+<br> <br> APPLICATION = Application()<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- log.setLevel( logging.DEBUG )<br>- #manager.log.setLevel( logging.DEBUG )<br>- fastagi.log.setLevel( logging.DEBUG )<br>- menu.log.setLevel( logging.DEBUG )<br>- APPLICATION.handleCallsFor( 's', APPLICATION.onS )<br>- APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )<br>- from twisted.internet import reactor<br>- reactor.run()<br>+ logging.basicConfig()<br>+ log.setLevel(logging.DEBUG)<br>+ # manager.log.setLevel(logging.DEBUG)<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ menu.log.setLevel(logging.DEBUG)<br>+ APPLICATION.handleCallsFor('s', APPLICATION.onS)<br>+ APPLICATION.agiSpecifier.run(APPLICATION.dispatchIncomingCall)<br>+ reactor.run()<br>diff --git a/examples/priexhaustion.py b/examples/priexhaustion.py<br>index 6f70cae..167f0cd 100644<br>--- a/examples/priexhaustion.py<br>+++ b/examples/priexhaustion.py<br>@@ -2,99 +2,104 @@<br> """Sample application to watch for PRI exhaustion<br> <br> This script watches for events on the AMI interface, tracking the identity of<br>-open channels in order to track how many channels are being used. This would <br>-be used to send messages to an administrator when network capacity is being <br>+open channels in order to track how many channels are being used. This would<br>+be used to send messages to an administrator when network capacity is being<br> approached.<br> <br>-Similarly, you could watch for spare capacity on the network and use that <br>+Similarly, you could watch for spare capacity on the network and use that<br> to decide whether to allow low-priority calls, such as peering framework or<br> free-world-dialup calls to go through.<br> """<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi<br>+from twisted.internet import reactor<br> import utilapplication<br>-import menu<br>-import os, logging, pprint, time<br>-from basicproperty import common, propertied, basic<br>+import logging<br>+from basicproperty import common, propertied<br> <br>-log = logging.getLogger( 'priexhaustion' )<br>-log.setLevel( logging.INFO )<br>+log = logging.getLogger('priexhaustion')<br>+log.setLevel(logging.INFO)<br> <br>-class ChannelTracker( propertied.Propertied ):<br>- """Track open channels on the Asterisk server"""<br>- channels = common.DictionaryProperty(<br>- "channels", """Set of open channels on the system""",<br>- )<br>- thresholdCount = common.IntegerProperty(<br>- "thresholdCount", """Storage of threshold below which we don't warn user""",<br>- defaultValue = 20,<br>- )<br>- def main( self ):<br>- """Main operation for the channel-tracking demo"""<br>- amiDF = APPLICATION.amiSpecifier.login( <br>- ).addCallback( self.onAMIConnect )<br>- # XXX do something useful on failure to login...<br>- def onAMIConnect( self, ami ):<br>- """Register for AMI events"""<br>- # XXX should do an initial query to populate channels...<br>- # XXX should handle asterisk reboots (at the moment the AMI <br>- # interface will just stop generating events), not a practical<br>- # problem at the moment, but should have a periodic check to be sure<br>- # the interface is still up, and if not, should close and restart<br>- log.debug( 'onAMIConnect' )<br>- ami.status().addCallback( self.onStatus, ami=ami )<br>- ami.registerEvent( 'Hangup', self.onChannelHangup )<br>- ami.registerEvent( 'Newchannel', self.onChannelNew )<br>- def interestingEvent( self, event, ami=None ):<br>- """Decide whether this channel event is interesting <br>- <br>- Real-world application would want to take only Zap channels, or only<br>- channels from a given context, or whatever other filter you want in <br>- order to capture *just* the scarce resource (such as PRI lines).<br>- <br>- Keep in mind that an "interesting" event must show up as interesting <br>- for *both* Newchannel and Hangup events or you will leak <br>- references/channels or have unknown channels hanging up.<br>- """<br>- return True<br>- def onStatus( self, events, ami=None ):<br>- """Integrate the current status into our set of channels"""<br>- log.debug( """Initial channel status retrieved""" )<br>- for event in events:<br>- self.onChannelNew( ami, event )<br>- def onChannelNew( self, ami, event ):<br>- """Handle creation of a new channel"""<br>- log.debug( """Start on channel %s""", event )<br>- if self.interestingEvent( event, ami ):<br>- opening = not self.channels.has_key( event['uniqueid'] )<br>- self.channels[ event['uniqueid'] ] = event <br>- if opening:<br>- self.onChannelChange( ami, event, opening = opening )<br>- def onChannelHangup( self, ami, event ):<br>- """Handle hangup of an existing channel"""<br>- if self.interestingEvent( event, ami ):<br>- try:<br>- del self.channels[ event['uniqueid']]<br>- except KeyError, err:<br>- log.warn( """Hangup on unknown channel %s""", event )<br>- else:<br>- log.debug( """Hangup on channel %s""", event )<br>- self.onChannelChange( ami, event, opening = False )<br>- def onChannelChange( self, ami, event, opening=False ):<br>- """Channel count has changed, do something useful like enforcing limits"""<br>- if opening and len(self.channels) > self.thresholdCount:<br>- log.warn( """Current channel count: %s""", len(self.channels ) )<br>- else:<br>- log.info( """Current channel count: %s""", len(self.channels ) )<br>+<br>+class ChannelTracker(propertied.Propertied):<br>+ """Track open channels on the Asterisk server"""<br>+ channels = common.DictionaryProperty(<br>+ "channels", """Set of open channels on the system""",<br>+ )<br>+ thresholdCount = common.IntegerProperty(<br>+ "thresholdCount", """Storage of threshold below which we don't warn user""",<br>+ defaultValue=20,<br>+ )<br>+<br>+ def main(self):<br>+ """Main operation for the channel-tracking demo"""<br>+ APPLICATION.amiSpecifier.login().addCallback(self.onAMIConnect)<br>+ # XXX do something useful on failure to login...<br>+<br>+ def onAMIConnect(self, ami):<br>+ """Register for AMI events"""<br>+ # XXX should do an initial query to populate channels...<br>+ # XXX should handle asterisk reboots (at the moment the AMI<br>+ # interface will just stop generating events), not a practical<br>+ # problem at the moment, but should have a periodic check to be sure<br>+ # the interface is still up, and if not, should close and restart<br>+ log.debug('onAMIConnect')<br>+ ami.status().addCallback(self.onStatus, ami=ami)<br>+ ami.registerEvent('Hangup', self.onChannelHangup)<br>+ ami.registerEvent('Newchannel', self.onChannelNew)<br>+<br>+ def interestingEvent(self, event, ami=None):<br>+ """Decide whether this channel event is interesting<br>+<br>+ Real-world application would want to take only Zap channels, or only<br>+ channels from a given context, or whatever other filter you want in<br>+ order to capture *just* the scarce resource (such as PRI lines).<br>+<br>+ Keep in mind that an "interesting" event must show up as interesting<br>+ for *both* Newchannel and Hangup events or you will leak<br>+ references/channels or have unknown channels hanging up.<br>+ """<br>+ return True<br>+<br>+ def onStatus(self, events, ami=None):<br>+ """Integrate the current status into our set of channels"""<br>+ log.debug("""Initial channel status retrieved""")<br>+ for event in events:<br>+ self.onChannelNew(ami, event)<br>+<br>+ def onChannelNew(self, ami, event):<br>+ """Handle creation of a new channel"""<br>+ log.debug("""Start on channel %s""", event)<br>+ if self.interestingEvent(event, ami):<br>+ opening = event['uniqueid'] not in self.channels<br>+ self.channels[event['uniqueid']] = event<br>+ if opening:<br>+ self.onChannelChange(ami, event, opening=opening)<br>+<br>+ def onChannelHangup(self, ami, event):<br>+ """Handle hangup of an existing channel"""<br>+ if self.interestingEvent(event, ami):<br>+ try:<br>+ del self.channels[event['uniqueid']]<br>+ except KeyError as err:<br>+ log.warn("""Hangup on unknown channel %s""", event)<br>+ else:<br>+ log.debug("""Hangup on channel %s""", event)<br>+ self.onChannelChange(ami, event, opening=False)<br>+<br>+ def onChannelChange(self, ami, event, opening=False):<br>+ """Channel count has changed, do something useful like enforcing limits"""<br>+ if opening and len(self.channels) > self.thresholdCount:<br>+ log.warn("""Current channel count: %s""", len(self.channels))<br>+ else:<br>+ log.info("""Current channel count: %s""", len(self.channels))<br>+<br> <br> APPLICATION = utilapplication.UtilApplication()<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- #log.setLevel( logging.DEBUG )<br>- #manager.log.setLevel( logging.DEBUG )<br>- #fastagi.log.setLevel( logging.DEBUG )<br>- tracker = ChannelTracker()<br>- reactor.callWhenRunning( tracker.main )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ # log.setLevel(logging.DEBUG)<br>+ # manager.log.setLevel(logging.DEBUG)<br>+ # fastagi.log.setLevel(logging.DEBUG)<br>+ tracker = ChannelTracker()<br>+ reactor.callWhenRunning(tracker.main)<br>+ reactor.run()<br>diff --git a/examples/priexhaustionbare.py b/examples/priexhaustionbare.py<br>index 4e0290b..450bf02 100644<br>--- a/examples/priexhaustionbare.py<br>+++ b/examples/priexhaustionbare.py<br>@@ -1,64 +1,69 @@<br> #! /usr/bin/env python<br>-from twisted.application import service, internet<br>-from twisted.internet import reactor, defer<br>-from starpy import manager, fastagi<br>+from twisted.internet import reactor<br> import utilapplication<br>-import menu<br>-import os, logging, pprint, time<br>-from basicproperty import common, propertied, basic<br>+import logging<br>+from basicproperty import common, propertied<br> <br>-log = logging.getLogger( 'priexhaustion' )<br>-log.setLevel( logging.INFO )<br>+log = logging.getLogger('priexhaustion')<br>+log.setLevel(logging.INFO)<br> <br>-class ChannelTracker( propertied.Propertied ):<br>- """Track open channels on the Asterisk server"""<br>- channels = common.DictionaryProperty(<br>- "channels", """Set of open channels on the system""",<br>- )<br>- thresholdCount = common.IntegerProperty(<br>- "thresholdCount", """Storage of threshold below which we don't warn user""",<br>- defaultValue = 20,<br>- )<br>- def main( self ):<br>- """Main operation for the channel-tracking demo"""<br>- amiDF = APPLICATION.amiSpecifier.login( <br>- ).addCallback( self.onAMIConnect )<br>- def onAMIConnect( self, ami ):<br>- ami.status().addCallback( self.onStatus, ami=ami )<br>- ami.registerEvent( 'Hangup', self.onChannelHangup )<br>- ami.registerEvent( 'Newchannel', self.onChannelNew )<br>- def onStatus( self, events, ami=None ):<br>- """Integrate the current status into our set of channels"""<br>- log.debug( """Initial channel status retrieved""" )<br>- for event in events:<br>- self.onChannelNew( ami, event )<br>- def onChannelNew( self, ami, event ):<br>- """Handle creation of a new channel"""<br>- log.debug( """Start on channel %s""", event )<br>- opening = not self.channels.has_key( event['uniqueid'] )<br>- self.channels[ event['uniqueid'] ] = event <br>- if opening:<br>- self.onChannelChange( ami, event, opening = opening )<br>- def onChannelHangup( self, ami, event ):<br>- """Handle hangup of an existing channel"""<br>- try:<br>- del self.channels[ event['uniqueid']]<br>- except KeyError, err:<br>- log.warn( """Hangup on unknown channel %s""", event )<br>- else:<br>- log.debug( """Hangup on channel %s""", event )<br>- self.onChannelChange( ami, event, opening = False )<br>- def onChannelChange( self, ami, event, opening=False ):<br>- """Channel count has changed, do something useful like enforcing limits"""<br>- if opening and len(self.channels) > self.thresholdCount:<br>- log.warn( """Current channel count: %s""", len(self.channels ) )<br>- else:<br>- log.info( """Current channel count: %s""", len(self.channels ) )<br>+<br>+class ChannelTracker(propertied.Propertied):<br>+ """Track open channels on the Asterisk server"""<br>+ channels = common.DictionaryProperty(<br>+ "channels", """Set of open channels on the system""",<br>+ )<br>+ thresholdCount = common.IntegerProperty(<br>+ "thresholdCount", """Storage of threshold below which we don't warn user""",<br>+ defaultValue=20,<br>+ )<br>+<br>+ def main(self):<br>+ """Main operation for the channel-tracking demo"""<br>+ APPLICATION.amiSpecifier.login().addCallback(self.onAMIConnect)<br>+<br>+ def onAMIConnect(self, ami):<br>+ ami.status().addCallback(self.onStatus, ami=ami)<br>+ ami.registerEvent('Hangup', self.onChannelHangup)<br>+ ami.registerEvent('Newchannel', self.onChannelNew)<br>+<br>+ def onStatus(self, events, ami=None):<br>+ """Integrate the current status into our set of channels"""<br>+ log.debug("""Initial channel status retrieved""")<br>+ for event in events:<br>+ self.onChannelNew(ami, event)<br>+<br>+ def onChannelNew(self, ami, event):<br>+ """Handle creation of a new channel"""<br>+ log.debug("""Start on channel %s""", event)<br>+ opening = event['uniqueid'] not in self.channels<br>+ self.channels[event['uniqueid']] = event<br>+ if opening:<br>+ self.onChannelChange(ami, event, opening=opening)<br>+<br>+ def onChannelHangup(self, ami, event):<br>+ """Handle hangup of an existing channel"""<br>+ try:<br>+ del self.channels[event['uniqueid']]<br>+ except KeyError as err:<br>+ log.warn("""Hangup on unknown channel %s""", event)<br>+ else:<br>+ log.debug("""Hangup on channel %s""", event)<br>+ self.onChannelChange(ami, event, opening=False)<br>+<br>+ def onChannelChange(self, ami, event, opening=False):<br>+ """Channel count has changed, do something useful like enforcing limits"""<br>+ if opening and len(self.channels) > self.thresholdCount:<br>+ log.warn("""Current channel count: %s""", len(self.channels))<br>+ else:<br>+ log.info("""Current channel count: %s""", len(self.channels))<br>+<br> <br> APPLICATION = utilapplication.UtilApplication()<br> <br>+<br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- tracker = ChannelTracker()<br>- reactor.callWhenRunning( tracker.main )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ tracker = ChannelTracker()<br>+ reactor.callWhenRunning(tracker.main)<br>+ reactor.run()<br>diff --git a/examples/readingdigits.py b/examples/readingdigits.py<br>index 093f1be..5f2895b 100644<br>--- a/examples/readingdigits.py<br>+++ b/examples/readingdigits.py<br>@@ -1,60 +1,70 @@<br> #! /usr/bin/env python<br> """Read digits from the user in various ways..."""<br>-from twisted.internet import reactor, defer<br>-from starpy import fastagi, error<br>-import logging, time<br>+from twisted.internet import reactor<br>+from starpy import fastagi<br>+import logging<br> <br>-log = logging.getLogger( 'hellofastagi' )<br>+log = logging.getLogger('hellofastagi')<br> <br>-class DialPlan( object ):<br>- """Stupid little application to report how many times it's been accessed"""<br>- def __init__( self ):<br>- self.count = 0<br>- def __call__( self, agi ):<br>- """Store the AGI instance for later usage, kick off our operations"""<br>- self.agi = agi <br>- return self.start()<br>- def start( self ):<br>- """Begin the dial-plan-like operations"""<br>- return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )<br>- def answerFailure( self, reason ):<br>- """Deal with a failure to answer"""<br>- log.warn( <br>- """Unable to answer channel %r: %s""", <br>- self.agi.variables['agi_channel'], reason.getTraceback(),<br>- )<br>- self.agi.finish()<br>- def onAnswered( self, resultLine ):<br>- """We've managed to answer the channel, yay!"""<br>- self.count += 1<br>- return self.agi.wait( 2.0 ).addCallback( self.onWaited )<br>- def onWaited( self, result ):<br>- """We've finished waiting, tell the user the number"""<br>- return self.agi.sayNumber( self.count, '*' ).addErrback(<br>- self.onNumberFailed,<br>- ).addCallbacks(<br>- self.onFinished, self.onFinished,<br>- )<br>- def onFinished( self, resultLine ):<br>- """We said the number correctly, hang up on the user"""<br>- return self.agi.finish()<br>- def onNumberFailed( self, reason ):<br>- """We were unable to read the number to the user"""<br>- log.warn( <br>- """Unable to read number to user on channel %r: %s""",<br>- self.agi.variables['agi_channel'], reason.getTraceback(),<br>- )<br>- <br>- def onHangupFailure( self, reason ):<br>- """Failed trying to hang up"""<br>- log.warn( <br>- """Unable to hang up channel %r: %s""", <br>- self.agi.variables['agi_channel'], reason.getTraceback(),<br>- )<br>+<br>+class DialPlan(object):<br>+ """Stupid little application to report how many times it's been accessed"""<br>+<br>+ def __init__(self):<br>+ self.count = 0<br>+<br>+ def __call__(self, agi):<br>+ """Store the AGI instance for later usage, kick off our operations"""<br>+ self.agi = agi<br>+ return self.start()<br>+<br>+ def start(self):<br>+ """Begin the dial-plan-like operations"""<br>+ return self.agi.answer().addCallbacks(self.onAnswered, self.answerFailure)<br>+<br>+ def answerFailure(self, reason):<br>+ """Deal with a failure to answer"""<br>+ log.warn(<br>+ """Unable to answer channel %r: %s""",<br>+ self.agi.variables['agi_channel'], reason.getTraceback(),<br>+ )<br>+ self.agi.finish()<br>+<br>+ def onAnswered(self, resultLine):<br>+ """We've managed to answer the channel, yay!"""<br>+ self.count += 1<br>+ return self.agi.wait(2.0).addCallback(self.onWaited)<br>+<br>+ def onWaited(self, result):<br>+ """We've finished waiting, tell the user the number"""<br>+ return self.agi.sayNumber(self.count, '*').addErrback(<br>+ self.onNumberFailed,<br>+ ).addCallbacks(<br>+ self.onFinished, self.onFinished,<br>+ )<br>+<br>+ def onFinished(self, resultLine):<br>+ """We said the number correctly, hang up on the user"""<br>+ return self.agi.finish()<br>+<br>+ def onNumberFailed(self, reason):<br>+ """We were unable to read the number to the user"""<br>+ log.warn(<br>+ """Unable to read number to user on channel %r: %s""",<br>+ self.agi.variables['agi_channel'], reason.getTraceback(),<br>+ )<br>+<br>+ def onHangupFailure(self, reason):<br>+ """Failed trying to hang up"""<br>+ log.warn(<br>+ """Unable to hang up channel %r: %s""",<br>+ self.agi.variables['agi_channel'], reason.getTraceback(),<br>+ )<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.DEBUG )<br>- f = fastagi.FastAGIFactory(DialPlan())<br>- reactor.listenTCP(4573, f, 50, '127.0.0.1') # only binding on local interface<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.DEBUG)<br>+ f = fastagi.FastAGIFactory(DialPlan())<br>+ reactor.listenTCP(4573, f, 50, '127.0.0.1') # only binding on local interface<br>+ reactor.run()<br>diff --git a/examples/timestamp.py b/examples/timestamp.py<br>index e66e832..1793d6b 100644<br>--- a/examples/timestamp.py<br>+++ b/examples/timestamp.py<br>@@ -2,44 +2,52 @@<br> """Provide a trivial date-and-time service"""<br> from twisted.internet import reactor<br> from starpy import fastagi<br>-import logging, time<br>+import logging<br>+import time<br> <br>-log = logging.getLogger( 'dateandtime' )<br>+log = logging.getLogger('dateandtime')<br> <br>-def testFunction( agi ):<br>- """Give time for some time a bit in the future"""<br>- log.debug( 'testFunction' )<br>- df = agi.streamFile( 'at-tone-time-exactly' )<br>- def onFailed( reason ):<br>- log.error( "Failure: %s", reason.getTraceback())<br>- return None<br>- def cleanup( result ):<br>- agi.finish()<br>- return result<br>- def onSaid( resultLine ):<br>- """Having introduced, actually read the time"""<br>- t = time.time()<br>- t2 = t+20.0<br>- df = agi.sayDateTime( t2, format='HM' )<br>- def onDateFinished( resultLine ):<br>- # now need to sleep until .5 seconds before the time <br>- df = agi.wait( t2-.5-time.time() )<br>- def onDoBeep( result ):<br>- df = agi.streamFile( 'beep' )<br>- return df<br>- return df.addCallback( onDoBeep )<br>- return df.addCallback( onDateFinished )<br>- return df.addCallback( <br>- onSaid <br>- ).addErrback( <br>- onFailed <br>- ).addCallbacks(<br>- cleanup, cleanup,<br>- )<br>+<br>+def testFunction(agi):<br>+ """Give time for some time a bit in the future"""<br>+ log.debug('testFunction')<br>+ df = agi.streamFile('at-tone-time-exactly')<br>+<br>+ def onFailed(reason):<br>+ log.error("Failure: %s", reason.getTraceback())<br>+ return None<br>+<br>+ def cleanup(result):<br>+ agi.finish()<br>+ return result<br>+<br>+ def onSaid(resultLine):<br>+ """Having introduced, actually read the time"""<br>+ t = time.time()<br>+ t2 = t+20.0<br>+ df = agi.sayDateTime(t2, format='HM')<br>+<br>+ def onDateFinished(resultLine):<br>+ # now need to sleep until .5 seconds before the time<br>+ df = agi.wait(t2-.5-time.time())<br>+<br>+ def onDoBeep(result):<br>+ df = agi.streamFile('beep')<br>+ return df<br>+ return df.addCallback(onDoBeep)<br>+ return df.addCallback(onDateFinished)<br>+ return df.addCallback(<br>+ onSaid<br>+ ).addErrback(<br>+ onFailed<br>+ ).addCallbacks(<br>+ cleanup, cleanup,<br>+ )<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.INFO )<br>- f = fastagi.FastAGIFactory(testFunction)<br>- reactor.listenTCP(4574, f, 50, '127.0.0.1') # only binding on local interface<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.INFO)<br>+ f = fastagi.FastAGIFactory(testFunction)<br>+ reactor.listenTCP(4574, f, 50, '127.0.0.1') # only binding on local interface<br>+ reactor.run()<br>diff --git a/examples/timestampapp.py b/examples/timestampapp.py<br>index 0585155..dba94b6 100644<br>--- a/examples/timestampapp.py<br>+++ b/examples/timestampapp.py<br>@@ -3,46 +3,55 @@<br> from twisted.internet import reactor<br> from starpy import fastagi<br> import utilapplication<br>-import logging, time<br>+import logging<br>+import time<br> <br>-log = logging.getLogger( 'dateandtime' )<br>+log = logging.getLogger('dateandtime')<br> <br>-def testFunction( agi ):<br>- """Give time for some time a bit in the future"""<br>- log.debug( 'testFunction' )<br>- df = agi.streamFile( 'at-tone-time-exactly' )<br>- def onFailed( reason ):<br>- log.error( "Failure: %s", reason.getTraceback())<br>- return None<br>- def cleanup( result ):<br>- agi.finish()<br>- return result<br>- def onSaid( resultLine ):<br>- """Having introduced, actually read the time"""<br>- t = time.time()<br>- t2 = t+7.0<br>- df = agi.sayDateTime( t2, format='HMS' )<br>- def onDateFinished( resultLine ):<br>- # now need to sleep until .05 seconds before the time <br>- df = agi.wait( t2-.05-time.time() )<br>- def onDoBeep( result ):<br>- df = agi.streamFile( 'beep' )<br>- return df<br>- def waitTwo( result ):<br>- return agi.streamFile( 'thank-you-for-calling' )<br>- return df.addCallback( onDoBeep ).addCallback( waitTwo )<br>- return df.addCallback( onDateFinished )<br>- return df.addCallback( <br>- onSaid <br>- ).addErrback( <br>- onFailed <br>- ).addCallbacks(<br>- cleanup, cleanup,<br>- )<br>+<br>+def testFunction(agi):<br>+ """Give time for some time a bit in the future"""<br>+ log.debug('testFunction')<br>+ df = agi.streamFile('at-tone-time-exactly')<br>+<br>+ def onFailed(reason):<br>+ log.error("Failure: %s", reason.getTraceback())<br>+ return None<br>+<br>+ def cleanup(result):<br>+ agi.finish()<br>+ return result<br>+<br>+ def onSaid(resultLine):<br>+ """Having introduced, actually read the time"""<br>+ t = time.time()<br>+ t2 = t+7.0<br>+ df = agi.sayDateTime(t2, format='HMS')<br>+<br>+ def onDateFinished(resultLine):<br>+ # now need to sleep until .05 seconds before the time<br>+ df = agi.wait(t2-.05-time.time())<br>+<br>+ def onDoBeep(result):<br>+ df = agi.streamFile('beep')<br>+ return df<br>+<br>+ def waitTwo(result):<br>+ return agi.streamFile('thank-you-for-calling')<br>+ return df.addCallback(onDoBeep).addCallback(waitTwo)<br>+ return df.addCallback(onDateFinished)<br>+ return df.addCallback(<br>+ onSaid<br>+ ).addErrback(<br>+ onFailed<br>+ ).addCallbacks(<br>+ cleanup, cleanup,<br>+ )<br>+<br> <br> if __name__ == "__main__":<br>- logging.basicConfig()<br>- fastagi.log.setLevel( logging.INFO )<br>- APPLICATION = utilapplication.UtilApplication()<br>- reactor.callWhenRunning( APPLICATION.agiSpecifier.run, testFunction )<br>- reactor.run()<br>+ logging.basicConfig()<br>+ fastagi.log.setLevel(logging.INFO)<br>+ APPLICATION = utilapplication.UtilApplication()<br>+ reactor.callWhenRunning(APPLICATION.agiSpecifier.run, testFunction)<br>+ reactor.run()<br>diff --git a/examples/utilapplication.py b/examples/utilapplication.py<br>index 0e51901..e7fc6ed 100644<br>--- a/examples/utilapplication.py<br>+++ b/examples/utilapplication.py<br>@@ -1,6 +1,6 @@<br> #<br> # StarPy -- Asterisk Protocols for Twisted<br>-# <br>+#<br> # Copyright (c) 2006, Michael C. Fletcher<br> #<br> # Michael C. Fletcher <mcfletch@vrplumber.com><br>@@ -15,15 +15,17 @@<br> # details.<br> <br> """Class providing utility applications with common support code"""<br>-from basicproperty import common, propertied, basic, weak<br>+from basicproperty import common, propertied, basic<br> from ConfigParser import ConfigParser<br> from starpy import fastagi, manager<br> from twisted.internet import defer, reactor<br>-import logging,os<br>+import logging<br>+import os<br> <br>-log = logging.getLogger( 'app' )<br>+log = logging.getLogger('app')<br> <br>-class UtilApplication( propertied.Propertied ):<br>+<br>+class UtilApplication(propertied.Propertied):<br> """Utility class providing simple application-level operations<br> <br> FastAGI entry points are waitForCallOn and handleCallsFor, which allow<br>@@ -32,82 +34,93 @@<br> (as specified in self.configFiles).<br> """<br> amiSpecifier = basic.BasicProperty(<br>- "amiSpecifier", """AMI connection specifier for the application see AMISpecifier""",<br>- defaultFunction = lambda prop,client: AMISpecifier()<br>+ "amiSpecifier",<br>+ """AMI connection specifier for the application see AMISpecifier""",<br>+ defaultFunction=lambda prop, client: AMISpecifier()<br> )<br> agiSpecifier = basic.BasicProperty(<br>- "agiSpecifier", """FastAGI server specifier for the application see AGISpecifier""",<br>- defaultFunction = lambda prop,client: AGISpecifier()<br>+ "agiSpecifier",<br>+ """FastAGI server specifier for the application see AGISpecifier""",<br>+ defaultFunction=lambda prop, client: AGISpecifier()<br> )<br> extensionWaiters = common.DictionaryProperty(<br> "extensionWaiters", """Set of deferreds waiting for incoming extensions""",<br> )<br> extensionHandlers = common.DictionaryProperty(<br>- "extensionHandlers", """Set of permanant callbacks waiting for incoming extensions""",<br>+ "extensionHandlers",<br>+ """Set of permanant callbacks waiting for incoming extensions""",<br> )<br>- configFiles = configFiles=('starpy.conf','~/.starpy.conf')<br>- def __init__( self ):<br>+ configFiles = ('starpy.conf', '~/.starpy.conf')<br>+<br>+ def __init__(self):<br> """Initialise the application from options in configFile"""<br> self.loadConfigurations()<br>- def loadConfigurations( self ):<br>- parser = self._loadConfigFiles( self.configFiles )<br>- self._copyPropertiesFrom( parser, 'AMI', self.amiSpecifier )<br>- self._copyPropertiesFrom( parser, 'FastAGI', self.agiSpecifier )<br>+<br>+ def loadConfigurations(self):<br>+ parser = self._loadConfigFiles(self.configFiles)<br>+ self._copyPropertiesFrom(parser, 'AMI', self.amiSpecifier)<br>+ self._copyPropertiesFrom(parser, 'FastAGI', self.agiSpecifier)<br> return parser<br>- def _loadConfigFiles( self, configFiles ):<br>+<br>+ def _loadConfigFiles(self, configFiles):<br> """Load options from configuration files given (if present)"""<br>- parser = ConfigParser( )<br>+ parser = ConfigParser()<br> filenames = [<br>- os.path.abspath( os.path.expandvars( os.path.expanduser( file ) ))<br>+ os.path.abspath(os.path.expandvars(os.path.expanduser(file)))<br> for file in configFiles<br> ]<br>- log.info( "Possible configuration files:\n\t%s", "\n\t".join(filenames) or None)<br>+ log.info("Possible configuration files:\n\t%s", "\n\t".join(filenames) or None)<br> filenames = [<br> file for file in filenames<br> if os.path.isfile(file)<br> ]<br>- log.info( "Actual configuration files:\n\t%s", "\n\t".join(filenames) or None)<br>- parser.read( filenames )<br>+ log.info("Actual configuration files:\n\t%s", "\n\t".join(filenames) or None)<br>+ parser.read(filenames)<br> return parser<br>- def _copyPropertiesFrom( self, parser, section, client, properties=None ):<br>+<br>+ def _copyPropertiesFrom(self, parser, section, client, properties=None):<br> """Copy properties from the config-parser's given section into client"""<br> if properties is None:<br> properties = client.getProperties()<br> for property in properties:<br>- if parser.has_option( section, property.name ):<br>+ if parser.has_option(section, property.name):<br> try:<br>- value = parser.get( section, property.name )<br>- setattr( client, property.name, value )<br>- except (TypeError,ValueError,AttributeError,NameError), err:<br>- log( """Unable to set property %r of %r to config-file value %r: %s"""%(<br>- property.name, client, parser.get( section, property.name, 1), err,<br>+ value = parser.get(section, property.name)<br>+ setattr(client, property.name, value)<br>+ except (TypeError, ValueError, AttributeError, NameError) as err:<br>+ log('Unable to set property %r of %r to config-file value %r: %s' % (<br>+ property.name, client, parser.get(section, property.name, 1), err,<br> ))<br> return client<br>- def dispatchIncomingCall( self, agi ):<br>+<br>+ def dispatchIncomingCall(self, agi):<br> """Handle an incoming call (dispatch to the appropriate registered handler)"""<br> extension = agi.variables['agi_extension']<br>- log.info( """AGI connection with extension: %r""", extension )<br>+ log.info("""AGI connection with extension: %r""", extension)<br> try:<br>- df = self.extensionWaiters.pop( extension )<br>- except KeyError, err:<br>+ df = self.extensionWaiters.pop(extension)<br>+ except KeyError as err:<br> try:<br>- callback = self.extensionHandlers[ extension ]<br>- except KeyError, err:<br>+ callback = self.extensionHandlers[extension]<br>+ except KeyError as err:<br> try:<br>- callback = self.extensionHandlers[ None ]<br>- except KeyError, err:<br>- log.warn( """Unexpected connection to extension %r: %s""", extension, agi.variables )<br>+ callback = self.extensionHandlers[None]<br>+ except KeyError as err:<br>+ log.warn("""Unexpected connection to extension %r: %s""",<br>+ extension, agi.variables)<br> agi.finish()<br> return<br> try:<br>- return callback( agi )<br>- except Exception, err:<br>- log.error( """Failure during callback %s for agi %s: %s""", callback, agi.variables, err )<br>+ return callback(agi)<br>+ except Exception as err:<br>+ log.error("""Failure during callback %s for agi %s: %s""",<br>+ callback, agi.variables, err)<br> # XXX return a -1 here<br> else:<br> if not df.called:<br>- df.callback( agi )<br>- def waitForCallOn( self, extension, timeout=15 ):<br>+ df.callback(agi)<br>+<br>+ def waitForCallOn(self, extension, timeout=15):<br> """Wait for an AGI call on extension given<br> <br> extension -- string extension for which to wait<br>@@ -122,17 +135,19 @@<br> returns deferred returning connected FastAGIProtocol or an error<br> """<br> extension = str(extension)<br>- log.info( 'Waiting for extension %r for %s seconds', extension, timeout )<br>- df = defer.Deferred( )<br>- self.extensionWaiters[ extension ] = df<br>- def onTimeout( ):<br>+ log.info('Waiting for extension %r for %s seconds', extension, timeout)<br>+ df = defer.Deferred()<br>+ self.extensionWaiters[extension] = df<br>+<br>+ def onTimeout():<br> if not df.called:<br>- df.errback( defer.TimeoutError(<br>- """Timeout waiting for call on extension: %r"""%(extension,)<br>+ df.errback(defer.TimeoutError(<br>+ """Timeout waiting for call on extension: %r""" % (extension,)<br> ))<br>- reactor.callLater( timeout, onTimeout )<br>+ reactor.callLater(timeout, onTimeout)<br> return df<br>- def handleCallsFor( self, extension, callback ):<br>+<br>+ def handleCallsFor(self, extension, callback):<br> """Register permanant handler for given extension<br> <br> extension -- string extension for which to wait or None to define<br>@@ -150,9 +165,10 @@<br> """<br> if extension is not None:<br> extension = str(extension)<br>- self.extensionHandlers[ extension ] = callback<br>+ self.extensionHandlers[extension] = callback<br> <br>-class AMISpecifier( propertied.Propertied ):<br>+<br>+class AMISpecifier(propertied.Propertied):<br> """Manager interface setup/specifier"""<br> username = common.StringLocaleProperty(<br> "username", """Login username for the manager interface""",<br>@@ -163,36 +179,39 @@<br> password = secret<br> server = common.StringLocaleProperty(<br> "server", """Server IP address to which to connect""",<br>- defaultValue = '127.0.0.1',<br>+ defaultValue='127.0.0.1',<br> )<br> port = common.IntegerProperty(<br> "port", """Server IP port to which to connect""",<br>- defaultValue = 5038,<br>+ defaultValue=5038,<br> )<br> timeout = common.FloatProperty(<br> "timeout", """Timeout in seconds for an AMI connection timeout""",<br>- defaultValue = 5.0,<br>+ defaultValue=5.0,<br> )<br>- def login( self ):<br>+<br>+ def login(self):<br> """Login to the specified manager via the AMI"""<br> theManager = manager.AMIFactory(self.username, self.secret)<br> return theManager.login(self.server, self.port, timeout=self.timeout)<br> <br>-class AGISpecifier( propertied.Propertied ):<br>+<br>+class AGISpecifier(propertied.Propertied):<br> """Specifier of where we send the user to connect to our AGI"""<br> port = common.IntegerProperty(<br> "port", """IP port on which to listen""",<br>- defaultValue = 4573,<br>+ defaultValue=4573,<br> )<br> interface = common.StringLocaleProperty(<br> "interface", """IP interface on which to listen (local only by default)""",<br>- defaultValue = '127.0.0.1',<br>+ defaultValue='127.0.0.1',<br> )<br> context = common.StringLocaleProperty(<br> "context", """Asterisk context to which to connect incoming calls""",<br>- defaultValue = 'survey',<br>+ defaultValue='survey',<br> )<br>- def run( self, mainFunction ):<br>+<br>+ def run(self, mainFunction):<br> """Start up the AGI server with the given mainFunction"""<br> f = fastagi.FastAGIFactory(mainFunction)<br> return reactor.listenTCP(self.port, f, 50, self.interface)<br>diff --git a/starpy/fastagi.py b/starpy/fastagi.py<br>index 7e26284..85de054 100644<br>--- a/starpy/fastagi.py<br>+++ b/starpy/fastagi.py<br>@@ -28,7 +28,6 @@<br> from twisted.internet import protocol, reactor, defer<br> from twisted.internet import error as tw_error<br> from twisted.protocols import basic<br>-import socket<br> import logging<br> import time<br> from starpy import error<br>@@ -236,9 +235,10 @@<br> if endpos == skipMS:<br> # "likely" an error according to the wiki,<br> # we'll raise an error...<br>- raise error.AGICommandFailure(FAILURE_CODE,<br>- "End position %s == original position, "<br>- "result code %s" % (endpos, digit))<br>+ raise error.AGICommandFailure(<br>+ FAILURE_CODE,<br>+ "End position %s == original position, "<br>+ "result code %s" % (endpos, digit))<br> return digit, endpos<br> raise ValueError("Unexpected result on streaming completion: %r" %<br> resultLine)<br>@@ -267,8 +267,8 @@<br> sequence = InSequence()<br> sequence.append(self.setContext, self.variables['agi_context'])<br> sequence.append(self.setExtension, self.variables['agi_extension'])<br>- sequence.append(self.setPriority, int(self.variables['agi_priority'])<br>- + difference)<br>+ sequence.append(self.setPriority,<br>+ int(self.variables['agi_priority']) + difference)<br> sequence.append(self.finish)<br> return sequence()<br> <br>@@ -326,7 +326,7 @@<br> """<br> parts = resultLine.split(' ', 1)<br> result = int(parts[0])<br>- endpos = None # Default if endpos isn't specified<br>+ endpos = None # Default if endpos isn't specified<br> if len(parts) == 2:<br> endposStuff = parts[1].strip()<br> if endposStuff.startswith('endpos='):<br>@@ -379,7 +379,7 @@<br> """<br> command = 'DATABASE DELTREE "%s"' % (family,)<br> if keyTree:<br>- command += ' "%s"' % (keytree,)<br>+ command += ' "%s"' % (keyTree,)<br> return self.sendCommand(command).addCallback(<br> self.checkFailure, failure='0',<br> ).addCallback(self.resultAsInt)<br>@@ -629,8 +629,7 @@<br> <br> def recordFile(<br> self, filename, format, escapeDigits, timeout=-1,<br>- offsetSamples=None, beep=True, silence=None,<br>- ):<br>+ offsetSamples=None, beep=True, silence=None,):<br> """Record channel to given filename until escapeDigits or silence<br> <br> filename -- filename on the server to which to save<br>diff --git a/starpy/manager.py b/starpy/manager.py<br>index 6876f74..7adac62 100644<br>--- a/starpy/manager.py<br>+++ b/starpy/manager.py<br>@@ -46,11 +46,13 @@<br> """A subclass of defer.Deferred that adds a registerError method<br> to handle function callback when an Error response happens"""<br> _errorRespCallback = None<br>- def registerError(self, function ):<br>+<br>+ def registerError(self, function):<br> """Add function for Error response callback"""<br> self._errorRespCallback = function<br> log.debug('Registering function %s to handle Error response'<br> % (function))<br>+<br> <br> class AMIProtocol(basic.LineOnlyReceiver):<br> """Protocol for the interfacing with the Asterisk Manager Interface (AMI)<br>@@ -239,10 +241,10 @@<br> if line:<br> if line.endswith(self.END_DATA):<br> # multi-line command results...<br>- message.setdefault(' ', []).extend([<br>- l for l in line.split('\n')<br>- if (l and l != self.END_DATA)<br>- ])<br>+ message.setdefault(' ', []).extend(<br>+ [l for l in line.split('\n')<br>+ if (l and l != self.END_DATA)]<br>+ )<br> else:<br> # regular line...<br> if line.startswith(self.VERSION_PREFIX):<br>@@ -319,8 +321,9 @@<br> <br> def checkErrorResponse(self, result, actionid, df):<br> """Check for error response and callback"""<br>- self.cleanup( result, actionid)<br>- if isinstance(result, dict) and result.get('response') == 'Error' and df._errorRespCallback:<br>+ self.cleanup(result, actionid)<br>+ if isinstance(result, dict) and result.get('response') == 'Error' \<br>+ and df._errorRespCallback:<br> df._errorRespCallback(result)<br> return result<br> <br>@@ -398,7 +401,7 @@<br> raise error.AMICommandFailure(message)<br> return message<br> <br>- ## End-user API<br>+ # End-user API<br> def absoluteTimeout(self, channel, timeout):<br> """Set timeout value for the given channel (in seconds)"""<br> message = {<br>@@ -461,10 +464,9 @@<br> <br> def action(self, action, **action_args):<br> """Sends an arbitrary action to the AMI"""<br>- #action_args will be ar least an empty dict so we build the message from it.<br>+ # action_args will be at least an empty dict so we build the message from it.<br> action_args['action'] = action<br> return self.sendDeferred(action_args).addCallback(self.errorUnlessResponse)<br>-<br> <br> def dbDel(self, family, key):<br> """Delete key value in the AstDB database"""<br>@@ -493,7 +495,8 @@<br> value = event['val']<br> self.deregisterEvent("DBGetResponse", extractValue)<br> return df.callback(value)<br>- def errorResponse( message ):<br>+<br>+ def errorResponse(message):<br> self.deregisterEvent("DBGetResponse", extractValue)<br> return df.callback(None)<br> message = {<br>@@ -612,14 +615,16 @@<br> def loginChallengeResponse(self):<br> """Log into the AMI interface with challenge-response.<br> <br>- Follows the same approach as self.login() using factory.username and factory.secret.<br>- Also done automatically on connection: will be called instead of self.login() if<br>- factory.plaintext_login is False: see AMIFactory constructor.<br>+ Follows the same approach as self.login() using factory.username and<br>+ factory.secret. Also done automatically on connection: will be called<br>+ instead of self.login() if factory.plaintext_login is False: see<br>+ AMIFactory constructor.<br> """<br> def sendResponse(challenge):<br>- if not type(challenge) is dict or not 'challenge' in challenge:<br>+ if not type(challenge) is dict or 'challenge' not in challenge:<br> raise error.AMICommandFailure(challenge)<br>- key_value = md5('%s%s' % (challenge['challenge'], self.factory.secret)).hexdigest()<br>+ key_value = md5('%s%s' % (challenge['challenge'], self.factory.secret)) \<br>+ .hexdigest()<br> return self.sendDeferred({<br> 'action': 'Login',<br> 'authtype': 'MD5',<br>@@ -712,8 +717,7 @@<br> self, channel, context=None, exten=None, priority=None,<br> timeout=None, callerid=None, account=None, application=None,<br> data=None, variable={}, async=False, channelid=None,<br>- otherchannelid=None<br>- ):<br>+ otherchannelid=None):<br> """Originate call to connect channel to given context/exten/priority<br> <br> channel -- the outgoing channel to which will be dialed<br>@@ -886,7 +890,7 @@<br> message = {<br> 'action': 'queues'<br> }<br>- #return self.collectDeferred(message, 'QueueStatusEnd')<br>+ # return self.collectDeferred(message, 'QueueStatusEnd')<br> return self.sendDeferred(message).addCallback(self.errorUnlessResponse)<br> <br> def queueStatus(self, queue=None, member=None):<br>@@ -1101,7 +1105,8 @@<br> """<br> protocol = AMIProtocol<br> <br>- def __init__(self, username, secret, id=None, plaintext_login=True, on_reconnect=None):<br>+ def __init__(self, username, secret, id=None, plaintext_login=True,<br>+ on_reconnect=None):<br> self.username = username<br> self.secret = secret<br> self.id = id<br>diff --git a/tox.ini b/tox.ini<br>index a6c1ede..dd04c12 100644<br>--- a/tox.ini<br>+++ b/tox.ini<br>@@ -1,6 +1,9 @@<br> [tox]<br> envlist = py26,py27,py32,py33,py34,p35,pep8<br> <br>+[pep8]<br>+max-line-length=90<br>+<br> [testenv]<br> deps = -r{toxinidir}/tools/pip-requires<br> -r{toxinidir}/tools/test-requires<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8990">change 8990</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/8990"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: starpy </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I293635427a48e66298042c3fc1e600e47854d3ed </div>
<div style="display:none"> Gerrit-Change-Number: 8990 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Corey Farrell <git@cfware.com> </div>