pax_global_header00006660000000000000000000000064122455735210014520gustar00rootroot0000000000000052 comment=dc5da871ae920593424348b0512e6507afd73768 python-pyst-0.6.50/000075500000000000000000000000001224557352100141245ustar00rootroot00000000000000python-pyst-0.6.50/ChangeLog000064400000000000000000000050651224557352100157040ustar00rootroot000000000000002007-01-26 Matthew Nicholson * asterisk/manager.py: Make get_header() functions work like dict.get(). * UPGRADE: Updated. 2007-01-16 Matthew Nicholson * asterisk/manager.py: Fix support for Manager.command(). Patch from Karl Putland . 2007-01-02 Matthew Nicholson * asterisk/agi.py (AGI.set_autohangup): Fixed syntax error. 2006-11-28 Matthew Nicholson * UPGRADE: Tweaked formatting. 2006-10-30 Matthew Nicholson * ChangeLog: Fixed previous entry. 2006-10-30 Matthew Nicholson * TODO: Updated. * asterisk/agi.py (AGI.control_stream_file): Changed default skipms and quoted arguments. 2006-10-24 Matthew Nicholson * asterisk/agi.py: Added get_variable_full command. 2006-10-18 Matthew Nicholson * asterisk/agitb.py: Make error output default to sys.stderr instead of sys.stdout. 2006-09-19 Matthew Nicholson * debian/control: Removed XS-Python-Versions header to make it default to all python versions. 2006-09-19 Matthew Nicholson * setup.py: Updated version. 2006-09-19 Matthew Nicholson * debian/rules: Changed to use pysupport. * debian/control: Changed to use pysupport and changed arch to all. 2006-09-19 Matthew Nicholson * MANIFEST.in: Added NEWS to manifest. 2006-09-19 Matthew Nicholson * debian/rules: Updated to reflect new python policy. * debian/control: Updated to reflect new python policy. * debian/changelog: Updated. 2006-08-23 Matthew Nicholson * UPGRADE: Updated. 2006-08-23 Matthew Nicholson * asterisk/manager.py (unregister_event): Added. 2006-08-23 Matthew Nicholson * NEWS: Added. 2006-07-14 Matthew Nicholson * asterisk/agi.py (wait_for_digit): Only catch ValueError, not all exceptions. 2006-07-14 Matthew Nicholson * TODO: Updated. * asterisk/agi.py (set_variable): Documentation changes. * asterisk/agi.py (get_variable): Changed to return and empty string instead of throwing an exception when a channel variable is not set. * UPGRADE: Added. 2006-07-14 Matthew Nicholson * ChangeLog: Added. * TODO: Added. * MANIFEST.in: Added ChangeLog and TODO. python-pyst-0.6.50/MANIFEST.in000064400000000000000000000003571224557352100156670ustar00rootroot00000000000000include debian/watch include debian/rules include debian/changelog include debian/control include debian/compat include debian/copyright include rpm/python-pyst.spec include ChangeLog include README include README.html include MANIFEST.in python-pyst-0.6.50/PKG-INFO000064400000000000000000000270731224557352100152320ustar00rootroot00000000000000Metadata-Version: 1.0 Name: pyst Version: 0.6.50 Summary: A Python Interface to Asterisk Home-page: http://www.sourceforge.net/projects/pyst/ Author: Ralf Schlatterbeck Author-email: rsc@runtux.com License: Python Software Foundation License, GNU Library or Lesser General Public License (LGPL) Download-URL: http://downloads.sourceforge.net/project/pyst/pyst/0.6.50/pyst-0.6.50.tar.gz Description: pyst: A Python Interface to Asterisk ==================================== Pyst consists of a set of interfaces and libraries to allow programming of Asterisk from python. The library currently supports AGI, AMI, and the parsing of Asterisk configuration files. The library also includes debugging facilities for AGI. A note on maintenance and forks: The current maintainer is Ralf Schlatterbeck. I've contacted maintainers of forks to try to join forces. For any questions, please contact me via rsc@runtux.com or my sourceforge user. Download from `Sourceforge project page`_. .. _`Sourceforge project page`: http://sourceforge.net/projects/pyst/ Installation is the standard python install:: tar xvf pyst.tar.gz cd pyst python setup.py install --prefix=/usr/local Documentation is currently only in python docstrings, you can use pythons built-in help facility:: import asterisk help (asterisk) import asterisk.agi help (asterisk.agi) import asterisk.manager help (asterisk.manager) import asterisk.config help (asterisk.config) Some notes on platforms: We now specify "platforms = 'Any'" in ``setup.py``. This means, the manager part of the package will probably run on any platform. The agi scripts on the other hand are called directly on the host where Asterisk is running. Since Asterisk doesn't run on windows platforms (and probably never will) the agi part of the package can only be run on Asterisk platforms. Credits ------- Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested. Thanks also to the people in the sourceforge project and those who just report bugs: Antoine Brenner, Max Nesterov, Sven Uebelacker ... and to unnamed contributors to earlier releases. Things to do for pyst --------------------- This is the original changelog merged into the readme file. I'm not so sure I really want to change all these things (in particular the threaded implementation looks good to me). I will maintain a section summarizing the changes in this README, the ChangeLog won't be maintained any longer. Detailed changes will be available in the version control tool (currently svn). * ChangeLog: The ChangeLog needs to be updated from the monotone logs. * Documentation: All of pyst's inline documentation needs to be updated. * manager.py: This should be converted to be single threaded. Also there is a race condition when a user calls manager.logoff() followed by manager.close(). The close() function may still call logoff again if the socket thread has not yet cleared the _connected flag. A class should be made for each manager action rather than having a function in a manager class. The manager class should be adapted to have a send method that know the general format of the classes. Matthew Nicholson writes on the mailinglist (note that I'm not sure I'll do this, I'm currently satisfied with the threaded implementation): For pyst 0.3 I am planning to clean up the manager.py. There are several know issues with the code. No one has actually reported these as problems, but I have personally had trouble with these. Currently manager.py runs in several threads, the main program thread, a thread to read from the network, and an event distribution thread. This causes problems with non thread safe code such as the MySQLdb libraries. This design also causes problems when an event handler throws an exception that causes the event processing thread to terminate. The second problem is with the way actions are sent. Each action has a specific function associated with it in the manager object that takes all possible arguments that may ever be passed to that action. This makes the api somewhat rigid and the Manager object cluttered. To solve these problems I am basically going to copy the design of my Astxx manager library (written in c++) and make it more python like. Each action will be a different object with certain methods to handle various tasks, with one function in the actual Manager class to send the action. This will make the Manager class much smaller and much more flexible. The current code will be consolidated into a single threaded design with hooks to have the library process events and such. These hooks will be called from the host application's main loop. Upgrading from older versions ----------------------------- If upgrading from... * 0.2: - ``get_header()`` methods in ``manager.py`` now work like ``dict.get()`` instead of ``dict[key]`` * 0.1.0: - ``agi.get_variable`` no longer throws an exception, instead it returns an empty string when a channel variable is not set. - ``manager.quit()`` has be renamed to ``manager.close()`` Source Code Repository Access ----------------------------- The current versions are kept in a Subversion repository on Sourceforge. You can check out the trunk with:: svn co svn://svn.code.sf.net/p/pyst/svn/pyst/trunk pyst There is also a 0.2 branch in:: svn://svn.code.sf.net/p/pyst/svn/pyst/branches/0.2 which contains unreleased changes after 0.2 (which have been merged into trunk *after* changing how manager commands to asterisk are parsed). Released versions are in:: https://pyst.svn.sourceforge.net/svnroot/pyst/pyst/tags For versions prior to the 0.2 release when Matthew Nicholson was maintaining pyst, the changes are kept in a `monotone`_ repository (monotone is a free distributed version control system). Please contact Matthew via Sourceforge if you're interested in intermediate versions. .. _`monotone`: http://monotone.ca/ prior to that the sources are in the CVS repository on sourceforge. Changes ------- Version 0.6: Minor feature enhancements The asterisk management interface emulator asterisk/astemu now can be used for unit-tests of applications using asterisk.manager. We're using this in the regression test (see test directory). But this way it is usable by others. - Factor asterisk emulator from regression test into own module Version 0.5: Small install change Fix setup.py to include download_url. This makes it installable using intall tools like pip. - Add download_url to setup.py - Fix svn url after SourceForge upgrade Version 0.4: Minor feature enhancements Small feature extensions to AGI and Manager modules. Add a regression test which now covers some aspects of the manager API. - Handle events with several fields with the same name (e.g. 'Variable' in the 'AgentCalled' event. Thanks to Max Nesterov for the suggestion, implementation differs from the suggestion in SF patch 3290869. For a use-case see the give SF patch and the regression test case test_agent_event. - Allow to use AGI module in FastAGI way via TCP connection. This change allows you to specify the socket streams instead sys.stdin/sys.stdout streams. Thanks to Max Nesterov for the patch. Applies SF patch 3047290. - Add regression test framework and some test cases for manager API. - The generated ActionID for the manager interface now includes the process-ID, this allows several concurrent processes using the manager API. Version 0.3: Minor feature enhancements New maintainer Ralf Schlatterbeck, this is my first release, please report any problems via the Sourceforge Bug-Tracker or email me directly. Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested. The parsing of answers from asterisk was completely rewritten. This should fix problems people were having with commands returning embedded '/' or empty lines. Some new manager commands added. - Add playdtmf manager command - add sippeers and sipshowpeer manager commands - rewritten manager communication - should no longer choke on '/' in answers returned from a manager command (fixes SF Bug 2947866) - should now correctly parse multi-line output with embedded empty lines, e.g. ``mgr.command('dialplan show')`` - Bug-fix for list manipulation in ``event_dispatch``, thanks to Jan Mueller, see mailinglist comment from 2008-04-18 - Merge unreleased changes from repository of Matthew Nicholson in particular a typo in ``agi.py`` for ``set_autohangup``, and change of ``get_header`` methods (see Upgrading instructions). The fixed ``manager.command`` support is already in (with a different solution). The unreleased changes are also on the 0.2 branch in the subversion repository in case somebody is interested. See the ChangeLog for older changes. Platform: Any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Other Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Telecommunications Industry Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Topic :: Communications :: Internet Phone Classifier: Topic :: Communications :: Telephony Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) python-pyst-0.6.50/README000064400000000000000000000214151224557352100150070ustar00rootroot00000000000000.. image:: http://sflogo.sourceforge.net/sflogo.php?group_id=134329&type=7 :height: 62 :width: 210 :alt: SourceForge.net Logo :target: http://sourceforge.net/projects/pyst/ pyst: A Python Interface to Asterisk ==================================== Pyst consists of a set of interfaces and libraries to allow programming of Asterisk from python. The library currently supports AGI, AMI, and the parsing of Asterisk configuration files. The library also includes debugging facilities for AGI. A note on maintenance and forks: The current maintainer is Ralf Schlatterbeck. I've contacted maintainers of forks to try to join forces. For any questions, please contact me via rsc@runtux.com or my sourceforge user. Download from `Sourceforge project page`_. .. _`Sourceforge project page`: http://sourceforge.net/projects/pyst/ Installation is the standard python install:: tar xvf pyst.tar.gz cd pyst python setup.py install --prefix=/usr/local Documentation is currently only in python docstrings, you can use pythons built-in help facility:: import asterisk help (asterisk) import asterisk.agi help (asterisk.agi) import asterisk.manager help (asterisk.manager) import asterisk.config help (asterisk.config) Some notes on platforms: We now specify "platforms = 'Any'" in ``setup.py``. This means, the manager part of the package will probably run on any platform. The agi scripts on the other hand are called directly on the host where Asterisk is running. Since Asterisk doesn't run on windows platforms (and probably never will) the agi part of the package can only be run on Asterisk platforms. Credits ------- Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested. Thanks also to the people in the sourceforge project and those who just report bugs: Antoine Brenner, Max Nesterov, Sven Uebelacker ... and to unnamed contributors to earlier releases. Things to do for pyst --------------------- This is the original changelog merged into the readme file. I'm not so sure I really want to change all these things (in particular the threaded implementation looks good to me). I will maintain a section summarizing the changes in this README, the ChangeLog won't be maintained any longer. Detailed changes will be available in the version control tool (currently svn). * ChangeLog: The ChangeLog needs to be updated from the monotone logs. * Documentation: All of pyst's inline documentation needs to be updated. * manager.py: This should be converted to be single threaded. Also there is a race condition when a user calls manager.logoff() followed by manager.close(). The close() function may still call logoff again if the socket thread has not yet cleared the _connected flag. A class should be made for each manager action rather than having a function in a manager class. The manager class should be adapted to have a send method that know the general format of the classes. Matthew Nicholson writes on the mailinglist (note that I'm not sure I'll do this, I'm currently satisfied with the threaded implementation): For pyst 0.3 I am planning to clean up the manager.py. There are several know issues with the code. No one has actually reported these as problems, but I have personally had trouble with these. Currently manager.py runs in several threads, the main program thread, a thread to read from the network, and an event distribution thread. This causes problems with non thread safe code such as the MySQLdb libraries. This design also causes problems when an event handler throws an exception that causes the event processing thread to terminate. The second problem is with the way actions are sent. Each action has a specific function associated with it in the manager object that takes all possible arguments that may ever be passed to that action. This makes the api somewhat rigid and the Manager object cluttered. To solve these problems I am basically going to copy the design of my Astxx manager library (written in c++) and make it more python like. Each action will be a different object with certain methods to handle various tasks, with one function in the actual Manager class to send the action. This will make the Manager class much smaller and much more flexible. The current code will be consolidated into a single threaded design with hooks to have the library process events and such. These hooks will be called from the host application's main loop. Upgrading from older versions ----------------------------- If upgrading from... * 0.2: - ``get_header()`` methods in ``manager.py`` now work like ``dict.get()`` instead of ``dict[key]`` * 0.1.0: - ``agi.get_variable`` no longer throws an exception, instead it returns an empty string when a channel variable is not set. - ``manager.quit()`` has be renamed to ``manager.close()`` Source Code Repository Access ----------------------------- The current versions are kept in a Subversion repository on Sourceforge. You can check out the trunk with:: svn co svn://svn.code.sf.net/p/pyst/svn/pyst/trunk pyst There is also a 0.2 branch in:: svn://svn.code.sf.net/p/pyst/svn/pyst/branches/0.2 which contains unreleased changes after 0.2 (which have been merged into trunk *after* changing how manager commands to asterisk are parsed). Released versions are in:: https://pyst.svn.sourceforge.net/svnroot/pyst/pyst/tags For versions prior to the 0.2 release when Matthew Nicholson was maintaining pyst, the changes are kept in a `monotone`_ repository (monotone is a free distributed version control system). Please contact Matthew via Sourceforge if you're interested in intermediate versions. .. _`monotone`: http://monotone.ca/ prior to that the sources are in the CVS repository on sourceforge. Changes ------- Version 0.6: Minor feature enhancements The asterisk management interface emulator asterisk/astemu now can be used for unit-tests of applications using asterisk.manager. We're using this in the regression test (see test directory). But this way it is usable by others. - Factor asterisk emulator from regression test into own module Version 0.5: Small install change Fix setup.py to include download_url. This makes it installable using intall tools like pip. - Add download_url to setup.py - Fix svn url after SourceForge upgrade Version 0.4: Minor feature enhancements Small feature extensions to AGI and Manager modules. Add a regression test which now covers some aspects of the manager API. - Handle events with several fields with the same name (e.g. 'Variable' in the 'AgentCalled' event. Thanks to Max Nesterov for the suggestion, implementation differs from the suggestion in SF patch 3290869. For a use-case see the give SF patch and the regression test case test_agent_event. - Allow to use AGI module in FastAGI way via TCP connection. This change allows you to specify the socket streams instead sys.stdin/sys.stdout streams. Thanks to Max Nesterov for the patch. Applies SF patch 3047290. - Add regression test framework and some test cases for manager API. - The generated ActionID for the manager interface now includes the process-ID, this allows several concurrent processes using the manager API. Version 0.3: Minor feature enhancements New maintainer Ralf Schlatterbeck, this is my first release, please report any problems via the Sourceforge Bug-Tracker or email me directly. Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested. The parsing of answers from asterisk was completely rewritten. This should fix problems people were having with commands returning embedded '/' or empty lines. Some new manager commands added. - Add playdtmf manager command - add sippeers and sipshowpeer manager commands - rewritten manager communication - should no longer choke on '/' in answers returned from a manager command (fixes SF Bug 2947866) - should now correctly parse multi-line output with embedded empty lines, e.g. ``mgr.command('dialplan show')`` - Bug-fix for list manipulation in ``event_dispatch``, thanks to Jan Mueller, see mailinglist comment from 2008-04-18 - Merge unreleased changes from repository of Matthew Nicholson in particular a typo in ``agi.py`` for ``set_autohangup``, and change of ``get_header`` methods (see Upgrading instructions). The fixed ``manager.command`` support is already in (with a different solution). The unreleased changes are also on the 0.2 branch in the subversion repository in case somebody is interested. See the ChangeLog for older changes. python-pyst-0.6.50/README.html000064400000000000000000000373171224557352100157620ustar00rootroot00000000000000
SourceForge.net Logo

pyst: A Python Interface to Asterisk

Pyst consists of a set of interfaces and libraries to allow programming of Asterisk from python. The library currently supports AGI, AMI, and the parsing of Asterisk configuration files. The library also includes debugging facilities for AGI.

A note on maintenance and forks: The current maintainer is Ralf Schlatterbeck. I've contacted maintainers of forks to try to join forces. For any questions, please contact me via rsc@runtux.com or my sourceforge user.

Download from Sourceforge project page.

Installation is the standard python install:

tar xvf pyst.tar.gz
cd pyst
python setup.py install --prefix=/usr/local

Documentation is currently only in python docstrings, you can use pythons built-in help facility:

import asterisk
help (asterisk)
import asterisk.agi
help (asterisk.agi)
import asterisk.manager
help (asterisk.manager)
import asterisk.config
help (asterisk.config)

Some notes on platforms: We now specify "platforms = 'Any'" in setup.py. This means, the manager part of the package will probably run on any platform. The agi scripts on the other hand are called directly on the host where Asterisk is running. Since Asterisk doesn't run on windows platforms (and probably never will) the agi part of the package can only be run on Asterisk platforms.

Credits

Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested.

Thanks also to the people in the sourceforge project and those who just report bugs: Antoine Brenner, Max Nesterov, Sven Uebelacker

... and to unnamed contributors to earlier releases.

Things to do for pyst

This is the original changelog merged into the readme file. I'm not so sure I really want to change all these things (in particular the threaded implementation looks good to me). I will maintain a section summarizing the changes in this README, the ChangeLog won't be maintained any longer. Detailed changes will be available in the version control tool (currently svn).

  • ChangeLog: The ChangeLog needs to be updated from the monotone logs.

  • Documentation: All of pyst's inline documentation needs to be updated.

  • manager.py: This should be converted to be single threaded. Also there is a race condition when a user calls manager.logoff() followed by manager.close(). The close() function may still call logoff again if the socket thread has not yet cleared the _connected flag.

    A class should be made for each manager action rather than having a function in a manager class. The manager class should be adapted to have a send method that know the general format of the classes.

Matthew Nicholson writes on the mailinglist (note that I'm not sure I'll do this, I'm currently satisfied with the threaded implementation):

For pyst 0.3 I am planning to clean up the manager.py. There are several know issues with the code. No one has actually reported these as problems, but I have personally had trouble with these. Currently manager.py runs in several threads, the main program thread, a thread to read from the network, and an event distribution thread. This causes problems with non thread safe code such as the MySQLdb libraries. This design also causes problems when an event handler throws an exception that causes the event processing thread to terminate.

The second problem is with the way actions are sent. Each action has a specific function associated with it in the manager object that takes all possible arguments that may ever be passed to that action. This makes the api somewhat rigid and the Manager object cluttered.

To solve these problems I am basically going to copy the design of my Astxx manager library (written in c++) and make it more python like. Each action will be a different object with certain methods to handle various tasks, with one function in the actual Manager class to send the action. This will make the Manager class much smaller and much more flexible. The current code will be consolidated into a single threaded design with hooks to have the library process events and such. These hooks will be called from the host application's main loop.

Upgrading from older versions

If upgrading from...

  • 0.2:

    • get_header() methods in manager.py now work like dict.get() instead of dict[key]
  • 0.1.0:

    • agi.get_variable no longer throws an exception, instead it returns an empty string when a channel variable is not set.
    • manager.quit() has be renamed to manager.close()

Source Code Repository Access

The current versions are kept in a Subversion repository on Sourceforge. You can check out the trunk with:

svn co svn://svn.code.sf.net/p/pyst/svn/pyst/trunk pyst

There is also a 0.2 branch in:

svn://svn.code.sf.net/p/pyst/svn/pyst/branches/0.2

which contains unreleased changes after 0.2 (which have been merged into trunk after changing how manager commands to asterisk are parsed).

Released versions are in:

https://pyst.svn.sourceforge.net/svnroot/pyst/pyst/tags

For versions prior to the 0.2 release when Matthew Nicholson was maintaining pyst, the changes are kept in a monotone repository (monotone is a free distributed version control system). Please contact Matthew via Sourceforge if you're interested in intermediate versions.

prior to that the sources are in the CVS repository on sourceforge.

Changes

Version 0.6: Minor feature enhancements

The asterisk management interface emulator asterisk/astemu now can be used for unit-tests of applications using asterisk.manager. We're using this in the regression test (see test directory). But this way it is usable by others.

  • Factor asterisk emulator from regression test into own module

Version 0.5: Small install change

Fix setup.py to include download_url. This makes it installable using intall tools like pip.

  • Add download_url to setup.py
  • Fix svn url after SourceForge upgrade

Version 0.4: Minor feature enhancements

Small feature extensions to AGI and Manager modules. Add a regression test which now covers some aspects of the manager API.

  • Handle events with several fields with the same name (e.g. 'Variable' in the 'AgentCalled' event. Thanks to Max Nesterov for the suggestion, implementation differs from the suggestion in SF patch 3290869. For a use-case see the give SF patch and the regression test case test_agent_event.
  • Allow to use AGI module in FastAGI way via TCP connection. This change allows you to specify the socket streams instead sys.stdin/sys.stdout streams. Thanks to Max Nesterov for the patch. Applies SF patch 3047290.
  • Add regression test framework and some test cases for manager API.
  • The generated ActionID for the manager interface now includes the process-ID, this allows several concurrent processes using the manager API.

Version 0.3: Minor feature enhancements

New maintainer Ralf Schlatterbeck, this is my first release, please report any problems via the Sourceforge Bug-Tracker or email me directly. Thanks to Karl Putland for writing the original package. Thanks to Matthew Nicholson for maintaining the package for some years and for handing over maintenance when he was no longer interested. The parsing of answers from asterisk was completely rewritten. This should fix problems people were having with commands returning embedded '/' or empty lines. Some new manager commands added.

  • Add playdtmf manager command
  • add sippeers and sipshowpeer manager commands
  • rewritten manager communication
  • should no longer choke on '/' in answers returned from a manager command (fixes SF Bug 2947866)
  • should now correctly parse multi-line output with embedded empty lines, e.g. mgr.command('dialplan show')
  • Bug-fix for list manipulation in event_dispatch, thanks to Jan Mueller, see mailinglist comment from 2008-04-18
  • Merge unreleased changes from repository of Matthew Nicholson in particular a typo in agi.py for set_autohangup, and change of get_header methods (see Upgrading instructions). The fixed manager.command support is already in (with a different solution). The unreleased changes are also on the 0.2 branch in the subversion repository in case somebody is interested.

See the ChangeLog for older changes.

python-pyst-0.6.50/asterisk/000075500000000000000000000000001224557352100157515ustar00rootroot00000000000000python-pyst-0.6.50/asterisk/Version.py000064400000000000000000000000211224557352100177410ustar00rootroot00000000000000VERSION="0.6.50" python-pyst-0.6.50/asterisk/__init__.py000064400000000000000000000007131224557352100200630ustar00rootroot00000000000000""" pyst - A set of interfaces and libraries to allow programming of asterisk from python. The pyst project includes several python modules to assist in programming asterisk with python: agi - python wrapper for agi agitb - a module to assist in agi debugging, like cgitb config - a module for parsing asterisk config files manager - a module for interacting with the asterisk manager interface """ __all__ = ['agi', 'agitb', 'config', 'manager'] python-pyst-0.6.50/asterisk/agi.py000064400000000000000000000632641224557352100170760ustar00rootroot00000000000000#!/usr/bin/env python2 # vim: set et sw=4: """agi This module contains functions and classes to implment AGI scripts in python. pyvr {'agi_callerid' : 'mars.putland.int', 'agi_channel' : 'IAX[kputland@kputland]/119', 'agi_context' : 'default', 'agi_dnid' : '666', 'agi_enhanced' : '0.0', 'agi_extension': '666', 'agi_language' : 'en', 'agi_priority' : '1', 'agi_rdnis' : '', 'agi_request' : 'pyst', 'agi_type' : 'IAX'} """ import sys, pprint, re from types import ListType import signal DEFAULT_TIMEOUT = 2000 # 2sec timeout used as default for functions that take timeouts DEFAULT_RECORD = 20000 # 20sec record time re_code = re.compile(r'(^\d*)\s*(.*)') re_kv = re.compile(r'(?P\w+)=(?P[^\s]+)\s*(?:\((?P.*)\))*') class AGIException(Exception): pass class AGIError(AGIException): pass class AGIUnknownError(AGIError): pass class AGIAppError(AGIError): pass # there are several different types of hangups we can detect # they all are derrived from AGIHangup class AGIHangup(AGIAppError): pass class AGISIGHUPHangup(AGIHangup): pass class AGISIGPIPEHangup(AGIHangup): pass class AGIResultHangup(AGIHangup): pass class AGIDBError(AGIAppError): pass class AGIUsageError(AGIError): pass class AGIInvalidCommand(AGIError): pass class AGI: """ This class encapsulates communication between Asterisk an a python script. It handles encoding commands to Asterisk and parsing responses from Asterisk. """ def __init__(self,stdin=sys.stdin,stdout=sys.stdout,stderr=sys.stderr): self.stdin=stdin self.stdout=stdout self.stderr=stderr self._got_sighup = False signal.signal(signal.SIGHUP, self._handle_sighup) # handle SIGHUP self.stderr.write('ARGS: ') self.stderr.write(str(sys.argv)) self.stderr.write('\n') self.env = {} self._get_agi_env() def _get_agi_env(self): while 1: line = self.stdin.readline().strip() self.stderr.write('ENV LINE: ') self.stderr.write(line) self.stderr.write('\n') if line == '': #blank line signals end break key,data = line.split(':')[0], ':'.join(line.split(':')[1:]) key = key.strip() data = data.strip() if key <> '': self.env[key] = data self.stderr.write('class AGI: self.env = ') self.stderr.write(pprint.pformat(self.env)) self.stderr.write('\n') def _quote(self, string): return ''.join(['"', str(string), '"']) def _handle_sighup(self, signum, frame): """Handle the SIGHUP signal""" self._got_sighup = True def test_hangup(self): """This function throws AGIHangup if we have recieved a SIGHUP""" if self._got_sighup: raise AGISIGHUPHangup("Received SIGHUP from Asterisk") def execute(self, command, *args): self.test_hangup() try: self.send_command(command, *args) return self.get_result() except IOError,e: if e.errno == 32: # Broken Pipe * let us go raise AGISIGPIPEHangup("Received SIGPIPE") else: raise def send_command(self, command, *args): """Send a command to Asterisk""" command = command.strip() command = '%s %s' % (command, ' '.join(map(str,args))) command = command.strip() if command[-1] != '\n': command += '\n' self.stderr.write(' COMMAND: %s' % command) self.stdout.write(command) self.stdout.flush() def get_result(self): """Read the result of a command from Asterisk""" code = 0 result = {'result':('','')} line = self.stdin.readline().strip() self.stderr.write(' RESULT_LINE: %s\n' % line) m = re_code.search(line) if m: code, response = m.groups() code = int(code) if code == 200: for key,value,data in re_kv.findall(response): result[key] = (value, data) # If user hangs up... we get 'hangup' in the data if data == 'hangup': raise AGIResultHangup("User hungup during execution") if key == 'result' and value == '-1': raise AGIAppError("Error executing application, or hangup") self.stderr.write(' RESULT_DICT: %s\n' % pprint.pformat(result)) return result elif code == 510: raise AGIInvalidCommand(response) elif code == 520: usage = [line] line = self.stdin.readline().strip() while line[:3] != '520': usage.append(line) line = self.stdin.readline().strip() usage.append(line) usage = '%s\n' % '\n'.join(usage) raise AGIUsageError(usage) else: raise AGIUnknownError(code, 'Unhandled code or undefined response') def _process_digit_list(self, digits): if type(digits) == ListType: digits = ''.join(map(str, digits)) return self._quote(digits) def answer(self): """agi.answer() --> None Answer channel if not already in answer state. """ self.execute('ANSWER')['result'][0] def wait_for_digit(self, timeout=DEFAULT_TIMEOUT): """agi.wait_for_digit(timeout=DEFAULT_TIMEOUT) --> digit Waits for up to 'timeout' milliseconds for a channel to receive a DTMF digit. Returns digit dialed Throws AGIError on channel falure """ res = self.execute('WAIT FOR DIGIT', timeout)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except ValueError: raise AGIError('Unable to convert result to digit: %s' % res) def send_text(self, text=''): """agi.send_text(text='') --> None Sends the given text on a channel. Most channels do not support the transmission of text. Throws AGIError on error/hangup """ self.execute('SEND TEXT', self._quote(text))['result'][0] def receive_char(self, timeout=DEFAULT_TIMEOUT): """agi.receive_char(timeout=DEFAULT_TIMEOUT) --> chr Receives a character of text on a channel. Specify timeout to be the maximum time to wait for input in milliseconds, or 0 for infinite. Most channels do not support the reception of text. """ res = self.execute('RECEIVE CHAR', timeout)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def tdd_mode(self, mode='off'): """agi.tdd_mode(mode='on'|'off') --> None Enable/Disable TDD transmission/reception on a channel. Throws AGIAppError if channel is not TDD-capable. """ res = self.execute('TDD MODE', mode)['result'][0] if res == '0': raise AGIAppError('Channel %s is not TDD-capable') def stream_file(self, filename, escape_digits='', sample_offset=0): """agi.stream_file(filename, escape_digits='', sample_offset=0) --> digit Send the given file, allowing playback to be interrupted by the given digits, if any. escape_digits is a string '12345' or a list of ints [1,2,3,4,5] or strings ['1','2','3'] or mixed [1,'2',3,'4'] If sample offset is provided then the audio will seek to sample offset before play starts. Returns digit if one was pressed. Throws AGIError if the channel was disconnected. Remember, the file extension must not be included in the filename. """ escape_digits = self._process_digit_list(escape_digits) response = self.execute('STREAM FILE', filename, escape_digits, sample_offset) res = response['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def control_stream_file(self, filename, escape_digits='', skipms=3000, fwd='', rew='', pause=''): """ Send the given file, allowing playback to be interrupted by the given digits, if any. escape_digits is a string '12345' or a list of ints [1,2,3,4,5] or strings ['1','2','3'] or mixed [1,'2',3,'4'] If sample offset is provided then the audio will seek to sample offset before play starts. Returns digit if one was pressed. Throws AGIError if the channel was disconnected. Remember, the file extension must not be included in the filename. """ escape_digits = self._process_digit_list(escape_digits) response = self.execute('CONTROL STREAM FILE', self._quote(filename), escape_digits, self._quote(skipms), self._quote(fwd), self._quote(rew), self._quote(pause)) res = response['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def send_image(self, filename): """agi.send_image(filename) --> None Sends the given image on a channel. Most channels do not support the transmission of images. Image names should not include extensions. Throws AGIError on channel failure """ res = self.execute('SEND IMAGE', filename)['result'][0] if res != '0': raise AGIAppError('Channel falure on channel %s' % self.env.get('agi_channel','UNKNOWN')) def say_digits(self, digits, escape_digits=''): """agi.say_digits(digits, escape_digits='') --> digit Say a given digit string, returning early if any of the given DTMF digits are received on the channel. Throws AGIError on channel failure """ digits = self._process_digit_list(digits) escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY DIGITS', digits, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_number(self, number, escape_digits=''): """agi.say_number(number, escape_digits='') --> digit Say a given digit string, returning early if any of the given DTMF digits are received on the channel. Throws AGIError on channel failure """ number = self._process_digit_list(number) escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY NUMBER', number, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_alpha(self, characters, escape_digits=''): """agi.say_alpha(string, escape_digits='') --> digit Say a given character string, returning early if any of the given DTMF digits are received on the channel. Throws AGIError on channel failure """ characters = self._process_digit_list(characters) escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY ALPHA', characters, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_phonetic(self, characters, escape_digits=''): """agi.say_phonetic(string, escape_digits='') --> digit Phonetically say a given character string, returning early if any of the given DTMF digits are received on the channel. Throws AGIError on channel failure """ characters = self._process_digit_list(characters) escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY PHONETIC', characters, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_date(self, seconds, escape_digits=''): """agi.say_date(seconds, escape_digits='') --> digit Say a given date, returning early if any of the given DTMF digits are pressed. The date should be in seconds since the UNIX Epoch (Jan 1, 1970 00:00:00) """ escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY DATE', seconds, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_time(self, seconds, escape_digits=''): """agi.say_time(seconds, escape_digits='') --> digit Say a given time, returning early if any of the given DTMF digits are pressed. The time should be in seconds since the UNIX Epoch (Jan 1, 1970 00:00:00) """ escape_digits = self._process_digit_list(escape_digits) res = self.execute('SAY TIME', seconds, escape_digits)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def say_datetime(self, seconds, escape_digits='', format='', zone=''): """agi.say_datetime(seconds, escape_digits='', format='', zone='') --> digit Say a given date in the format specfied (see voicemail.conf), returning early if any of the given DTMF digits are pressed. The date should be in seconds since the UNIX Epoch (Jan 1, 1970 00:00:00). """ escape_digits = self._process_digit_list(escape_digits) if format: format = self._quote(format) res = self.execute('SAY DATETIME', seconds, escape_digits, format, zone)['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def get_data(self, filename, timeout=DEFAULT_TIMEOUT, max_digits=255): """agi.get_data(filename, timeout=DEFAULT_TIMEOUT, max_digits=255) --> digits Stream the given file and receive dialed digits """ result = self.execute('GET DATA', filename, timeout, max_digits) res, value = result['result'] return res def get_option(self, filename, escape_digits='', timeout=0): """agi.get_option(filename, escape_digits='', timeout=0) --> digit Send the given file, allowing playback to be interrupted by the given digits, if any. escape_digits is a string '12345' or a list of ints [1,2,3,4,5] or strings ['1','2','3'] or mixed [1,'2',3,'4'] Returns digit if one was pressed. Throws AGIError if the channel was disconnected. Remember, the file extension must not be included in the filename. """ escape_digits = self._process_digit_list(escape_digits) if timeout: response = self.execute('GET OPTION', filename, escape_digits, timeout) else: response = self.execute('GET OPTION', filename, escape_digits) res = response['result'][0] if res == '0': return '' else: try: return chr(int(res)) except: raise AGIError('Unable to convert result to char: %s' % res) def set_context(self, context): """agi.set_context(context) Sets the context for continuation upon exiting the application. No error appears to be produced. Does not set exten or priority Use at your own risk. Ensure that you specify a valid context. """ self.execute('SET CONTEXT', context) def set_extension(self, extension): """agi.set_extension(extension) Sets the extension for continuation upon exiting the application. No error appears to be produced. Does not set context or priority Use at your own risk. Ensure that you specify a valid extension. """ self.execute('SET EXTENSION', extension) def set_priority(self, priority): """agi.set_priority(priority) Sets the priority for continuation upon exiting the application. No error appears to be produced. Does not set exten or context Use at your own risk. Ensure that you specify a valid priority. """ self.execute('set priority', priority) def goto_on_exit(self, context='', extension='', priority=''): context = context or self.env['agi_context'] extension = extension or self.env['agi_extension'] priority = priority or self.env['agi_priority'] self.set_context(context) self.set_extension(extension) self.set_priority(priority) def record_file(self, filename, format='gsm', escape_digits='#', timeout=DEFAULT_RECORD, offset=0, beep='beep'): """agi.record_file(filename, format, escape_digits, timeout=DEFAULT_TIMEOUT, offset=0, beep='beep') --> None Record to a file until a given dtmf digit in the sequence is received The format will specify what kind of file will be recorded. The timeout is the maximum record time in milliseconds, or -1 for no timeout. Offset samples is optional, and if provided will seek to the offset without exceeding the end of the file """ escape_digits = self._process_digit_list(escape_digits) res = self.execute('RECORD FILE', self._quote(filename), format, escape_digits, timeout, offset, beep)['result'][0] try: return chr(int(res)) except: raise AGIError('Unable to convert result to digit: %s' % res) def set_autohangup(self, secs): """agi.set_autohangup(secs) --> None Cause the channel to automatically hangup at seconds in the future. Of course it can be hungup before then as well. Setting to 0 will cause the autohangup feature to be disabled on this channel. """ self.execute('SET AUTOHANGUP', secs) def hangup(self, channel=''): """agi.hangup(channel='') Hangs up the specified channel. If no channel name is given, hangs up the current channel """ self.execute('HANGUP', channel) def appexec(self, application, options=''): """agi.appexec(application, options='') Executes with given . Returns whatever the application returns, or -2 on failure to find application """ result = self.execute('EXEC', application, self._quote(options)) res = result['result'][0] if res == '-2': raise AGIAppError('Unable to find application: %s' % application) return res def set_callerid(self, number): """agi.set_callerid(number) --> None Changes the callerid of the current channel. """ self.execute('SET CALLERID', number) def channel_status(self, channel=''): """agi.channel_status(channel='') --> int Returns the status of the specified channel. If no channel name is given the returns the status of the current channel. Return values: 0 Channel is down and available 1 Channel is down, but reserved 2 Channel is off hook 3 Digits (or equivalent) have been dialed 4 Line is ringing 5 Remote end is ringing 6 Line is up 7 Line is busy """ try: result = self.execute('CHANNEL STATUS', channel) except AGIHangup: raise except AGIAppError: result = {'result': ('-1','')} return int(result['result'][0]) def set_variable(self, name, value): """Set a channel variable. """ self.execute('SET VARIABLE', self._quote(name), self._quote(value)) def get_variable(self, name): """Get a channel variable. This function returns the value of the indicated channel variable. If the variable is not set, an empty string is returned. """ try: result = self.execute('GET VARIABLE', self._quote(name)) except AGIResultHangup: result = {'result': ('1', 'hangup')} res, value = result['result'] return value def get_full_variable(self, name, channel = None): """Get a channel variable. This function returns the value of the indicated channel variable. If the variable is not set, an empty string is returned. """ try: if channel: result = self.execute('GET FULL VARIABLE', self._quote(name), self._quote(channel)) else: result = self.execute('GET FULL VARIABLE', self._quote(name)) except AGIResultHangup: result = {'result': ('1', 'hangup')} res, value = result['result'] return value def verbose(self, message, level=1): """agi.verbose(message='', level=1) --> None Sends to the console via verbose message system. is the the verbose level (1-4) """ self.execute('VERBOSE', self._quote(message), level) def database_get(self, family, key): """agi.database_get(family, key) --> str Retrieves an entry in the Asterisk database for a given family and key. Returns 0 if is not set. Returns 1 if is set and returns the variable in parenthesis example return code: 200 result=1 (testvariable) """ family = '"%s"' % family key = '"%s"' % key result = self.execute('DATABASE GET', self._quote(family), self._quote(key)) res, value = result['result'] if res == '0': raise AGIDBError('Key not found in database: family=%s, key=%s' % (family, key)) elif res == '1': return value else: raise AGIError('Unknown exception for : family=%s, key=%s, result=%s' % (family, key, pprint.pformat(result))) def database_put(self, family, key, value): """agi.database_put(family, key, value) --> None Adds or updates an entry in the Asterisk database for a given family, key, and value. """ result = self.execute('DATABASE PUT', self._quote(family), self._quote(key), self._quote(value)) res, value = result['result'] if res == '0': raise AGIDBError('Unable to put vaule in databale: family=%s, key=%s, value=%s' % (family, key, value)) def database_del(self, family, key): """agi.database_del(family, key) --> None Deletes an entry in the Asterisk database for a given family and key. """ result = self.execute('DATABASE DEL', self._quote(family), self._quote(key)) res, value = result['result'] if res == '0': raise AGIDBError('Unable to delete from database: family=%s, key=%s' % (family, key)) def database_deltree(self, family, key=''): """agi.database_deltree(family, key='') --> None Deletes a family or specific keytree with in a family in the Asterisk database. """ result = self.execute('DATABASE DELTREE', self._quote(family), self._quote(key)) res, value = result['result'] if res == '0': raise AGIDBError('Unable to delete tree from database: family=%s, key=%s' % (family, key)) def noop(self): """agi.noop() --> None Does nothing """ self.execute('NOOP') if __name__=='__main__': agi = AGI() #agi.appexec('festival','Welcome to Klass Technologies. Thank you for calling.') #agi.appexec('festival','This is a test of the text to speech engine.') #agi.appexec('festival','Press 1 for sales ') #agi.appexec('festival','Press 2 for customer support ') #agi.hangup() #agi.goto_on_exit(extension='1234', priority='1') #sys.exit(0) #agi.say_digits('123', [4,'5',6]) #agi.say_digits([4,5,6]) #agi.say_number('1234') #agi.say_number('01234') # 668 #agi.say_number('0xf5') # 245 agi.get_data('demo-congrats') agi.hangup() sys.exit(0) #agi.record_file('pyst-test') #FAILS #agi.stream_file('demo-congrats', [1,2,3,4,5,6,7,8,9,0,'#','*']) #agi.appexec('background','demo-congrats') try: agi.appexec('backgrounder','demo-congrats') except AGIAppError: self.stderr.write("Handled exception for missing application backgrounder\n") agi.set_variable('foo','bar') agi.get_variable('foo') try: agi.get_variable('foobar') except AGIAppError: self.stderr.write("Handled exception for missing variable foobar\n") try: agi.database_put('foo', 'bar', 'foobar') agi.database_put('foo', 'baz', 'foobaz') agi.database_put('foo', 'bat', 'foobat') v = agi.database_get('foo', 'bar') self.stderr.write('DBVALUE foo:bar = %s\n' % v) v = agi.database_get('bar', 'foo') self.stderr.write('DBVALUE foo:bar = %s\n' % v) agi.database_del('foo', 'bar') agi.database_deltree('foo') except AGIDBError: self.stderr.write("Handled exception for missing database entry bar:foo\n") agi.hangup() python-pyst-0.6.50/asterisk/agitb.py000064400000000000000000000173631224557352100174230ustar00rootroot00000000000000"""More comprehensive traceback formatting for Python scripts. To enable this module, do: import asterisk.agitb, asterisk.agi asterisk.agitb.enable(display = False, logdir = '/var/log/asterisk/') agi = asterisk.agi.AGI() asterisk.agitb.enable(agi, False, '/var/log/asterisk') at the top of your script. The optional arguments to enable() are: agi - the agi handle to write verbose messages to display - if true, tracebacks are displayed on the asterisk console (used with the agi option) logdir - if set, tracebacks are written to files in this directory context - number of lines of source code to show for each stack frame By default, tracebacks are displayed but not saved, and the context is 5 lines. You may want to add a logdir if you call agitb.enable() before you have an agi.AGI() handle. Alternatively, if you have caught an exception and want agitb to display it for you, call agitb.handler(). The optional argument to handler() is a 3-item tuple (etype, evalue, etb) just like the value of sys.exc_info(). If you do not pass anything to handler() it will use sys.exc_info(). This script was adapted from Ka-Ping Yee's cgitb. """ __author__ = 'Matthew Nicholson' __version__ = '0.1.0' import sys __UNDEF__ = [] # a special sentinel object def lookup(name, frame, locals): """Find the value for a given name in the given environment.""" if name in locals: return 'local', locals[name] if name in frame.f_globals: return 'global', frame.f_globals[name] if '__builtins__' in frame.f_globals: builtins = frame.f_globals['__builtins__'] if type(builtins) is type({}): if name in builtins: return 'builtin', builtins[name] else: if hasattr(builtins, name): return 'builtin', getattr(builtins, name) return None, __UNDEF__ def scanvars(reader, frame, locals): """Scan one logical line of Python and look up values of variables used.""" import tokenize, keyword vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__ for ttype, token, start, end, line in tokenize.generate_tokens(reader): if ttype == tokenize.NEWLINE: break if ttype == tokenize.NAME and token not in keyword.kwlist: if lasttoken == '.': if parent is not __UNDEF__: value = getattr(parent, token, __UNDEF__) vars.append((prefix + token, prefix, value)) else: where, value = lookup(token, frame, locals) vars.append((token, where, value)) elif token == '.': prefix += lasttoken + '.' parent = value else: parent, prefix = None, '' lasttoken = token return vars def text((etype, evalue, etb), context=5): """Return a plain text document describing a given traceback.""" import os, types, time, traceback, linecache, inspect, pydoc if type(etype) is types.ClassType: etype = etype.__name__ pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable date = time.ctime(time.time()) head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + ''' A problem occurred in a Python script. Here is the sequence of function calls leading up to the error, in the order they occurred. ''' frames = [] records = inspect.getinnerframes(etb, context) for frame, file, lnum, func, lines, index in records: file = file and os.path.abspath(file) or '?' args, varargs, varkw, locals = inspect.getargvalues(frame) call = '' if func != '?': call = 'in ' + func + \ inspect.formatargvalues(args, varargs, varkw, locals, formatvalue=lambda value: '=' + pydoc.text.repr(value)) highlight = {} def reader(lnum=[lnum]): highlight[lnum[0]] = 1 try: return linecache.getline(file, lnum[0]) finally: lnum[0] += 1 vars = scanvars(reader, frame, locals) rows = [' %s %s' % (file, call)] if index is not None: i = lnum - index for line in lines: num = '%5d ' % i rows.append(num+line.rstrip()) i += 1 done, dump = {}, [] for name, where, value in vars: if name in done: continue done[name] = 1 if value is not __UNDEF__: if where == 'global': name = 'global ' + name elif where == 'local': name = name else: name = where + name.split('.')[-1] dump.append('%s = %s' % (name, pydoc.text.repr(value))) else: dump.append(name + ' undefined') rows.append('\n'.join(dump)) frames.append('\n%s\n' % '\n'.join(rows)) exception = ['%s: %s' % (str(etype), str(evalue))] if type(evalue) is types.InstanceType: for name in dir(evalue): value = pydoc.text.repr(getattr(evalue, name)) exception.append('\n%s%s = %s' % (" "*4, name, value)) import traceback return head + ''.join(frames) + ''.join(exception) + ''' The above is a description of an error in a Python program. Here is the original traceback: %s ''' % ''.join(traceback.format_exception(etype, evalue, etb)) class Hook: """A hook to replace sys.excepthook that shows tracebacks in HTML.""" def __init__(self, display=1, logdir=None, context=5, file=None, agi=None): self.display = display # send tracebacks to browser if true self.logdir = logdir # log tracebacks to files if not None self.context = context # number of source code lines per frame self.file = file or sys.stderr # place to send the output self.agi = agi def __call__(self, etype, evalue, etb): self.handle((etype, evalue, etb)) def handle(self, info=None): info = info or sys.exc_info() try: doc = text(info, self.context) except: # just in case something goes wrong import traceback doc = ''.join(traceback.format_exception(*info)) if self.display: if self.agi: # print to agi for line in doc.split('\n'): self.agi.verbose(line, 4) else: self.file.write(doc + '\n') if self.agi: self.agi.verbose('A problem occured in a python script', 4) else: self.file.write('A problem occured in a python script\n') if self.logdir is not None: import os, tempfile (fd, path) = tempfile.mkstemp(suffix='.txt', dir=self.logdir) try: file = os.fdopen(fd, 'w') file.write(doc) file.close() msg = '%s contains the description of this error.' % path except: msg = 'Tried to save traceback to %s, but failed.' % path if self.agi: self.agi.verbose(msg, 4) else: self.file.write(msg + '\n') try: self.file.flush() except: pass handler = Hook().handle def enable(agi=None, display=1, logdir=None, context=5): """Install an exception handler that formats tracebacks as HTML. The optional argument 'display' can be set to 0 to suppress sending the traceback to the browser, and 'logdir' can be set to a directory to cause tracebacks to be written to files there.""" except_hook = Hook(display=display, logdir=logdir, context=context, agi=agi) sys.excepthook = except_hook global handler handler = except_hook.handle python-pyst-0.6.50/asterisk/astemu.py000064400000000000000000000110161224557352100176200ustar00rootroot00000000000000import socket from signal import SIGTERM from os import fork, kill, waitpid from time import sleep class Event(dict): """ Events are encoded as dicts with a header fieldname to content-list map. Normally (for all typical asterisk events) the content-list only has one element. For multiple elements multiple lines with the same header (but different content) are sent. This tests cases where asterisk events contain multiple instances of the same header. The key 'CONTENT' is special, it denotes text that is appended to an event (e.g. for testing the output of the command action) """ sort_order = dict ((x, n) for n, x in enumerate (( 'Event' , 'Response' , 'Username' , 'Privilege' , 'Secret' , 'Command' , 'Channel' , 'ChannelState' , 'ChannelStateDesc' , 'CallerIDNum' , 'CallerIDName' , 'AccountCode' , 'Context' , 'Exten' , 'Reason' , 'Uniqueid' , 'ActionID' , 'OldAccountCode' , 'Cause' , 'Cause-txt' ))) sort_order ['CONTENT'] = 100000 def sort(self, x): return self.sort_order.get(x[0], 10000) def as_string(self, id): ret = [] if 'Response' in self: self ['ActionID'] = [id] for k,v in sorted(self.iteritems(), key=self.sort): if k == 'CONTENT': ret.append(v) else : if isinstance(v, str): ret.append (": ".join ((k, v))) else: for x in v: ret.append (": ".join ((k, x))) ret.append ('') ret.append ('') return '\r\n'.join (ret) @property def name(self): return self.get('Event','') @property def headers(self): return self class AsteriskEmu(object): """ Emulator for asterisk management interface. Used for unittests of asterisk.manager. Now factored into a standalone module for others to use in unittests of programs that build on pyst's asterisk.manager. By default let the operating system decide the port number to bind to, resulting port is stored in self.port. """ default_events = dict \ ( Login = ( Event ( Response = ('Success',) , Message = ('Authentication accepted',) ) , ) , Logoff = ( Event ( Response = ('Goodbye',) , Message = ('Thanks for all the fish.',) ) , ) ) def __init__(self, chatscript, port = 0): s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', port)) s.listen(1) pid = fork() if not pid: # won't return self.asterisk_emu(s, chatscript) self.childpid = pid host, self.port = s.getsockname() s.close() def asterisk_emu(self, sock, chatscript): """ Emulate asterisk management interface on a socket. Chatscript is a dict of command names to event list mapping. The event list contains events to send when the given command is recognized. """ while True: conn, addr = sock.accept() f = conn.makefile('rw') conn.close() f.write('Asterisk Call Manager/1.1\r\n') f.flush() cmd = lastid = '' try: for l in f: if l.startswith ('ActionID:'): lastid = l.split(':', 1)[1].strip() elif l.startswith ('Action:'): cmd = l.split(':', 1)[1].strip() elif not l.strip(): for d in chatscript, self.default_events: if cmd in d: for event in d[cmd]: f.write(event.as_string(id = lastid)) f.flush() if cmd == 'Logoff': f.close() break except: pass sleep(10000) # wait for being killed def close(self): if self.childpid: kill(self.childpid, SIGTERM) waitpid(self.childpid, 0) self.childpid = None python-pyst-0.6.50/asterisk/compat.py000064400000000000000000000012011224557352100176000ustar00rootroot00000000000000#!/usr/bin/python """ Compatibility of different python versions. Goal for now is to run on 2.6, 2.7 and 3.3 onwards. We skip versions prior to 2.6 and 3.1, 3.2, see Armin Ronachers blog post at http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/ """ import sys PY2 = sys.version_info[0] == 2 # Queue in python3 has moved: try: from Queue import Queue except ImportError: from queue import Queue # String types, stolen from Armin Ronacher above if not PY2: text_type = str string_types = (str,) unichr = chr else: text_type = unicode string_types = (str, unicode) unichr = unichr python-pyst-0.6.50/asterisk/config.py000064400000000000000000000114421224557352100175720ustar00rootroot00000000000000#!/usr/bin/env python # vim: set expandtab: """ Parse Asterisk configuration files. This module provides parsing functionality for asterisk config files. import asterisk.config import sys # load and parse the config file try: config = asterisk.config.Config('/etc/asterisk/extensions.conf') except asterisk.config.ParseError, (line, reason): print "Parse Error line: %s: %s" % (line, reason) sys.exit(1) except IOError, reason: print "Error opening file: %s" % reason sys.exit(1) # print our parsed output for category in config.categories: print '[%s]' % category.name # print the current category for item in category.items: print ' %s = %s' % (item.name, item.value) """ import sys class ParseError(Exception): pass class Line(object): def __init__(self, line, number): self.line = '' self.comment = '' line = line.strip() # I guess we don't preserve indentation self.number = number parts = line.split(';') if len(parts) >= 2: self.line = parts[0].strip() self.comment = ';'.join(parts[1:]) #Just in case the comment contained ';' else: self.line = line def __str__(self): return self.get_line() def get_line(self): if self.comment and self.line: return '%s\t;%s' % (self.line, self.comment) elif self.comment and not self.line: return ';%s' % self.comment return self.line class Category(Line): def __init__(self, line='', num=-1, name=None): Line.__init__(self, line, num) if self.line: if (self.line[0] != '[' or self.line[-1] != ']'): raise ParseError(self.number, "Missing '[' or ']' in category definition") self.name = self.line[1:-1] elif name: self.name = name else: raise Exception("Must provide name or line representing a category") self.items = [] self.comments = [] def get_line(self): if self.comment: return '[%s]\t;%s' % (self.name, self.comment) return '[%s]' % self.name def append(self, item): self.items.append(item) def insert(self, index, item): self.items.insert(index, item) def pop(self, index=-1): self.items.pop(index) def remove(self, item): self.items.remove(item) class Item(Line): def __init__(self, line='', num=-1, name=None, value=None): Line.__init__(self, line, num) self.style = '' if self.line: self.parse() elif (name and value): self.name = name self.value = value else: raise Exception("Must provide name or value representing an item") def parse(self): try: name, value = self.line.split('=', 1) except ValueError: if self.line.strip()[-1] == ']': raise ParseError(self.number, "Category name missing '['") else: raise ParseError(self.number, "Item must be in name = value pairs") if value and value[0] == '>': self.style = '>' #preserve the style of the original value = value[1:].strip() self.name = name.strip() self.value = value def get_line(self): if self.comment: return '%s =%s %s\t;%s' % (self.name, self.style, self.value, self.comment) return '%s =%s %s' % (self.name, self.style, self.value) class Config(object): def __init__(self, filename): self.filename = filename self.raw_lines = [] # Holds the raw strings self.lines = [] # Holds things in order self.categories = [] # load and parse the file self.load() self.parse() def load(self): self.raw_lines = open(self.filename).readlines() #try: #self.raw_lines = open(self.filename).readlines() #except IOError: #sys.stderr.write('WARNING: error opening filename: %s No data read. Starting new file?' % self.filename) #self.raw_lines = [] def parse(self): cat = None num = 0 for line in self.raw_lines: num += 1 line = line.strip() if not line or line[0] == ';': item = Line(line or '', num) self.lines.append(item) if cat: cat.comments.append(item) continue elif line[0] == '[': cat = Category(line, num) self.lines.append(cat) self.categories.append(cat) continue else: item = Item(line, num) self.lines.append(item) if cat: cat.append(item) continue python-pyst-0.6.50/asterisk/manager.py000064400000000000000000000516601224557352100177450ustar00rootroot00000000000000#!/usr/bin/env python # vim: set expandtab shiftwidth=4: from __future__ import absolute_import from __future__ import print_function """ Python Interface for Asterisk Manager This module provides a Python API for interfacing with the asterisk manager. import asterisk.manager import sys def handle_shutdown(event, manager): print ("Received shutdown event") manager.close() # we could analize the event and reconnect here def handle_event(event, manager): print ("Received event: %s" % event.name) manager = asterisk.manager.Manager() try: # connect to the manager try: manager.connect('host') manager.login('user', 'secret') # register some callbacks manager.register_event('Shutdown', handle_shutdown) # shutdown manager.register_event('*', handle_event) # catch all # get a status report response = manager.status() manager.logoff() except asterisk.manager.ManagerSocketException as err: errno, reason = err print ("Error connecting to the manager: %s" % reason) sys.exit(1) except asterisk.manager.ManagerAuthException as reason: print ("Error logging in to the manager: %s" % reason) sys.exit(1) except asterisk.manager.ManagerException as reason: print ("Error: %s" % reason) sys.exit(1) finally: # remember to clean up manager.close() Remember all header, response, and event names are case sensitive. Not all manager actions are implmented as of yet, feel free to add them and submit patches. """ import sys,os import socket import threading import re from io import StringIO from time import sleep from asterisk.compat import Queue, string_types EOL = '\r\n' class _Msg(object): def has_header(self, hname): """Check for a header""" return self.headers.has_key(hname) def get_header(self, hname, defval = None): """Return the specified header""" return self.headers.get(hname, defval) def __getitem__(self, hname): """Return the specified header""" return self.headers[hname] def __repr__(self): return self.headers['Response'] class ManagerMsg(_Msg): """A manager interface message""" def __init__(self, response): # the raw response, straight from the horse's mouth: self.response = response self.data = '' self.headers = {} self.multiheaders = {} # parse the response self.parse(response) # This is an unknown message, may happen if a command (notably # 'dialplan show something') contains a \n\r\n sequence in the # middle of output. We hope this happens only *once* during a # misbehaved command *and* the command ends with --END COMMAND-- # in that case we return an Event. Otherwise we asume it is # from a misbehaving command not returning a proper header (e.g. # IAXnetstats in Asterisk 1.4.X) # A better solution is probably to retain some knowledge of # commands sent and their expected return syntax. In that case # we could wait for --END COMMAND-- for 'command'. # B0rken in asterisk. This should be parseable without context. if 'Event' not in self.headers and 'Response' not in self.headers: # there are commands that return the ActionID but not # 'Response', e.g., IAXpeers in Asterisk 1.4.X if self.has_header('ActionID'): self.headers['Response'] = 'Generated Header' self.multiheaders ['Response'] = ['Generated Header'] elif '--END COMMAND--' in self.data: self.headers['Event'] = 'NoClue' self.multiheaders ['Event'] = ['NoClue'] else: self.headers['Response'] = 'Generated Header' self.multiheaders ['Response'] = ['Generated Header'] def parse(self, response): """Parse a manager message""" data = [] for n, line in enumerate (response): # all valid header lines end in \r\n if not line.endswith ('\r\n'): data.extend(response[n:]) break try: k, v = (x.strip() for x in line.split(':',1)) if k not in self.multiheaders: self.multiheaders[k]=[] self.headers[k] = v self.multiheaders[k].append(v) except ValueError: # invalid header, start of multi-line data response data.extend(response[n:]) break self.data = ''.join(data) class Event(_Msg): """Manager interface Events, __init__ expects and 'Event' message""" def __init__(self, message): # store all of the event data self.message = message self.data = message.data self.headers = message.headers self.multiheaders = message.multiheaders # if this is not an event message we have a problem if not message.has_header('Event'): raise ManagerException('Trying to create event from non event message') # get the event name self.name = message.get_header('Event') def __repr__(self): return self.headers['Event'] def get_action_id(self): return self.headers.get('ActionID',0000) class Manager(object): def __init__(self): self._sock = None # our socket self.title = None # set by received greeting self._connected = threading.Event() self._running = threading.Event() # our hostname self.hostname = socket.gethostname() # pid -- used for unique naming of ActionID self.pid = os.getpid () # our queues self._message_queue = Queue() self._response_queue = Queue() self._event_queue = Queue() # callbacks for events self._event_callbacks = {} self._reswaiting = [] # who is waiting for a response # sequence stuff self._seqlock = threading.Lock() self._seq = 0 # some threads self.message_thread = threading.Thread(target=self.message_loop) self.event_dispatch_thread = threading.Thread(target=self.event_dispatch) self.message_thread.setDaemon(True) self.event_dispatch_thread.setDaemon(True) def __del__(self): self.close() def connected(self): """ Check if we are connected or not. """ return self._connected.isSet() def next_seq(self): """Return the next number in the sequence, this is used for ActionID""" self._seqlock.acquire() try: return self._seq finally: self._seq += 1 self._seqlock.release() def send_action(self, cdict={}, **kwargs): """ Send a command to the manager If a list is passed to the cdict argument, each item in the list will be sent to asterisk under the same header in the following manner: cdict = {"Action": "Originate", "Variable": ["var1=value", "var2=value"]} send_action(cdict) ... Action: Originate Variable: var1=value Variable: var2=value """ if not self._connected.isSet(): raise ManagerException("Not connected") # fill in our args cdict.update(kwargs) # set the action id if not cdict.has_key('ActionID'): cdict['ActionID'] = '%s-%04s-%08x' % (self.hostname, self.pid, self.next_seq()) clist = [] # generate the command for key, value in cdict.items(): if isinstance(value, list): for item in value: item = tuple([key, item]) clist.append('%s: %s' % item) else: item = tuple([key, value]) clist.append('%s: %s' % item) clist.append(EOL) command = EOL.join(clist) # lock the socket and send our command try: self._sock.write(command) self._sock.flush() except socket.error as err: errno, reason = err raise ManagerSocketException(errno, reason) self._reswaiting.insert(0,1) response = self._response_queue.get() self._reswaiting.pop(0) if not response: raise ManagerSocketException(0, 'Connection Terminated') return response def _receive_data(self): """ Read the response from a command. """ multiline = False wait_for_marker = False eolcount = 0 # loop while we are sill running and connected while self._running.isSet() and self._connected.isSet(): try: lines = [] for line in self._sock : # check to see if this is the greeting line if not self.title and '/' in line and not ':' in line: # store the title of the manager we are connecting to: self.title = line.split('/')[0].strip() # store the version of the manager we are connecting to: self.version = line.split('/')[1].strip() # fake message header lines.append ('Response: Generated Header\r\n') lines.append (line) break # If the line is EOL marker we have a complete message. # Some commands are broken and contain a \n\r\n # sequence, in the case wait_for_marker is set, we # have such a command where the data ends with the # marker --END COMMAND--, so we ignore embedded # newlines until we see that marker if line == EOL and not wait_for_marker : multiline = False if lines or not self._connected.isSet(): break # ignore empty lines at start continue lines.append(line) # line not ending in \r\n or without ':' isn't a # valid header and starts multiline response if not line.endswith('\r\n') or ':' not in line: multiline = True # Response: Follows indicates we should wait for end # marker --END COMMAND-- if not multiline and line.startswith('Response') and \ line.split(':', 1)[1].strip() == 'Follows': wait_for_marker = True # same when seeing end of multiline response if multiline and line.startswith('--END COMMAND--'): wait_for_marker = False multiline = False if not self._connected.isSet(): break else: # EOF during reading self._sock.close() self._connected.clear() # if we have a message append it to our queue if lines and self._connected.isSet(): self._message_queue.put(lines) else: self._message_queue.put(None) except socket.error: self._sock.close() self._connected.clear() self._message_queue.put(None) def register_event(self, event, function): """ Register a callback for the specfied event. If a callback function returns True, no more callbacks for that event will be executed. """ # get the current value, or an empty list # then add our new callback current_callbacks = self._event_callbacks.get(event, []) current_callbacks.append(function) self._event_callbacks[event] = current_callbacks def unregister_event(self, event, function): """ Unregister a callback for the specified event. """ current_callbacks = self._event_callbacks.get(event, []) current_callbacks.remove(function) self._event_callbacks[event] = current_callbacks def message_loop(self): """ The method for the event thread. This actually recieves all types of messages and places them in the proper queues. """ # start a thread to recieve data t = threading.Thread(target=self._receive_data) t.setDaemon(True) t.start() try: # loop getting messages from the queue while self._running.isSet(): # get/wait for messages data = self._message_queue.get() # if we got None as our message we are done if not data: # notify the other queues self._event_queue.put(None) for waiter in self._reswaiting: self._response_queue.put(None) break # parse the data message = ManagerMsg(data) # check if this is an event message if message.has_header('Event'): self._event_queue.put(Event(message)) # check if this is a response elif message.has_header('Response'): self._response_queue.put(message) else: print ('No clue what we got\n%s' % message.data) finally: # wait for our data receiving thread to exit t.join() def event_dispatch(self): """This thread is responsible for dispatching events""" # loop dispatching events while self._running.isSet(): # get/wait for an event ev = self._event_queue.get() # if we got None as an event, we are finished if not ev: break # dispatch our events # first build a list of the functions to execute callbacks = (self._event_callbacks.get(ev.name, []) + self._event_callbacks.get('*', [])) # now execute the functions for callback in callbacks: if callback(ev, self): break def connect(self, host, port=5038): """Connect to the manager interface""" if self._connected.isSet(): raise ManagerException('Already connected to manager') # make sure host is a string assert isinstance (host, string_types) port = int(port) # make sure port is an int # create our socket and connect try: _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) _sock.connect((host,port)) self._sock = _sock.makefile () _sock.close () except socket.error as err: errno, reason = err raise ManagerSocketException(errno, reason) # we are connected and running self._connected.set() self._running.set() # start the event thread self.message_thread.start() # start the event dispatching thread self.event_dispatch_thread.start() # get our initial connection response return self._response_queue.get() def close(self): """Shutdown the connection to the manager""" # if we are still running, logout if self._running.isSet() and self._connected.isSet(): self.logoff() if self._running.isSet(): # put None in the message_queue to kill our threads self._message_queue.put(None) # wait for the event thread to exit self.message_thread.join() # make sure we do not join our self (when close is called from event handlers) if threading.currentThread() != self.event_dispatch_thread: # wait for the dispatch thread to exit self.event_dispatch_thread.join() self._running.clear() # Manager actions def login(self, username, secret): """Login to the manager, throws ManagerAuthException when login falis""" cdict = {'Action':'Login'} cdict['Username'] = username cdict['Secret'] = secret response = self.send_action(cdict) if response.get_header('Response') == 'Error': raise ManagerAuthException(response.get_header('Message')) return response def ping(self): """Send a ping action to the manager""" cdict = {'Action':'Ping'} response = self.send_action(cdict) return response def logoff(self): """Logoff from the manager""" cdict = {'Action':'Logoff'} response = self.send_action(cdict) return response def hangup(self, channel): """Hangup the specified channel""" cdict = {'Action':'Hangup'} cdict['Channel'] = channel response = self.send_action(cdict) return response def status(self, channel = ''): """Get a status message from asterisk""" cdict = {'Action':'Status'} cdict['Channel'] = channel response = self.send_action(cdict) return response def redirect(self, channel, exten, priority='1', extra_channel='', context=''): """Redirect a channel""" cdict = {'Action':'Redirect'} cdict['Channel'] = channel cdict['Exten'] = exten cdict['Priority'] = priority if context: cdict['Context'] = context if extra_channel: cdict['ExtraChannel'] = extra_channel response = self.send_action(cdict) return response def originate(self, channel, exten, context='', priority='', timeout='', caller_id='', async=False, account='', variables={}): """Originate a call""" cdict = {'Action':'Originate'} cdict['Channel'] = channel cdict['Exten'] = exten if context: cdict['Context'] = context if priority: cdict['Priority'] = priority if timeout: cdict['Timeout'] = timeout if caller_id: cdict['CallerID'] = caller_id if async: cdict['Async'] = 'yes' if account: cdict['Account'] = account # join dict of vairables together in a string in the form of 'key=val|key=val' # with the latest CVS HEAD this is no longer necessary # if variables: cdict['Variable'] = '|'.join(['='.join((str(key), str(value))) for key, value in variables.items()]) if variables: cdict['Variable'] = ['='.join((str(key), str(value))) for key, value in variables.items()] response = self.send_action(cdict) return response def mailbox_status(self, mailbox): """Get the status of the specfied mailbox""" cdict = {'Action':'MailboxStatus'} cdict['Mailbox'] = mailbox response = self.send_action(cdict) return response def command(self, command): """Execute a command""" cdict = {'Action':'Command'} cdict['Command'] = command response = self.send_action(cdict) return response def extension_state(self, exten, context): """Get the state of an extension""" cdict = {'Action':'ExtensionState'} cdict['Exten'] = exten cdict['Context'] = context response = self.send_action(cdict) return response def playdtmf (self, channel, digit) : """Plays a dtmf digit on the specified channel""" cdict = {'Action':'PlayDTMF'} cdict['Channel'] = channel cdict['Digit'] = digit response = self.send_action(cdict) return response def absolute_timeout(self, channel, timeout): """Set an absolute timeout on a channel""" cdict = {'Action':'AbsoluteTimeout'} cdict['Channel'] = channel cdict['Timeout'] = timeout response = self.send_action(cdict) return response def mailbox_count(self, mailbox): cdict = {'Action':'MailboxCount'} cdict['Mailbox'] = mailbox response = self.send_action(cdict) return response def sippeers(self): cdict = {'Action' : 'Sippeers'} response = self.send_action(cdict) return response def sipshowpeer(self, peer): cdict = {'Action' : 'SIPshowpeer'} cdict['Peer'] = peer response = self.send_action(cdict) return response class ManagerException(Exception): pass class ManagerSocketException(ManagerException): pass class ManagerAuthException(ManagerException): pass python-pyst-0.6.50/rpm/000075500000000000000000000000001224557352100147225ustar00rootroot00000000000000python-pyst-0.6.50/rpm/python-pyst.spec000064400000000000000000000024661224557352100201240ustar00rootroot00000000000000Summary: An interface to AGI Name: python-pyst Version: 0.0.5 Release: 2.centos4.0 Source0: http://prdownloads.sourceforge.net/pyst/pyst-%{version}.tar.gz License: LGPL Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-buildroot URL: http://sourceforge.net/projects/pyst Requires: python BuildRequires: python-devel BuildRequires: python Distutils %description Pyst consists of a set of interfaces and libraries to allow programming of Asterisk from python. The library currently supports AGI, AMI, and the parsing of Asterisk configuration files. The library also includes debugging facilities for AGI. %prep %setup -q -n pyst-%{version} %build CFLAGS="$RPM_OPT_FLAGS" python setup.py build %install python setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES mkdir -p $RPM_BUILD_ROOT/usr/share/doc/python-pyst cp debian/copyright $RPM_BUILD_ROOT/usr/share/doc/python-pyst %clean rm -rf $RPM_BUILD_ROOT %files -f INSTALLED_FILES %defattr(-,root,root) %doc /usr/share/doc/python-pyst/* %{_libdir}/python*/site-packages/asterisk/ %changelog * Tue Mar 21 2006 Matthew Nicholson el4.3 - Bumped version number. * Thu Feb 23 2006 Antoine Brenner el4.2 - Fixed source0 line * Tue Feb 9 2006 Antoine Brenner el4.1 - Initial Package python-pyst-0.6.50/setup.py000064400000000000000000000034651224557352100156460ustar00rootroot00000000000000#!/usr/bin/env python from distutils.core import setup try : from asterisk.Version import VERSION except : VERSION = None description = [] f = open ('README') logo_stripped = False for line in f : if not logo_stripped and line.strip () : continue logo_stripped = True description.append (line) licenses = ( 'Python Software Foundation License' , 'GNU Library or Lesser General Public License (LGPL)' ) download = "http://downloads.sourceforge.net/project/pyst/pyst" setup \ ( name = 'pyst' , version = VERSION , description = 'A Python Interface to Asterisk' , long_description = ''.join (description) , author = 'Karl Putland' , author_email = 'kputland@users.sourceforge.net' , maintainer = 'Ralf Schlatterbeck' , maintainer_email = 'rsc@runtux.com' , url = 'http://www.sourceforge.net/projects/pyst/' , packages = ['asterisk'] , license = ', '.join (licenses) , platforms = 'Any' , download_url = \ "%(download)s/%(VERSION)s/pyst-%(VERSION)s.tar.gz" % locals () , classifiers = [ 'Development Status :: 5 - Production/Stable' , 'Environment :: Other Environment' , 'Intended Audience :: Developers' , 'Intended Audience :: Telecommunications Industry' , 'Operating System :: OS Independent' , 'Programming Language :: Python' , 'Programming Language :: Python :: 2.4' , 'Programming Language :: Python :: 2.5' , 'Programming Language :: Python :: 2.6' , 'Programming Language :: Python :: 2.7' , 'Topic :: Communications :: Internet Phone' , 'Topic :: Communications :: Telephony' , 'Topic :: Software Development :: Libraries :: Python Modules' ] + ['License :: OSI Approved :: ' + l for l in licenses] ) python-pyst-0.6.50/test/000075500000000000000000000000001224557352100151035ustar00rootroot00000000000000python-pyst-0.6.50/test/test_base.py000064400000000000000000000543311224557352100174340ustar00rootroot00000000000000import sys import socket import unittest from asterisk.manager import Manager from asterisk.compat import Queue from asterisk.astemu import Event, AsteriskEmu class Test_Manager(unittest.TestCase): """ Test the asterisk management interface. """ default_events = AsteriskEmu.default_events def close(self): if self.manager: self.manager.close() self.manager = None self.astemu.close() def setUp(self): self.manager = None self.childpid = None self.events = [] self.evcount = 0 self.queue = Queue() def tearDown(self): self.close() def handler(self, event, manager): self.events.append(event) self.queue.put(self.evcount) self.evcount += 1 def run_manager(self, chatscript): self.astemu = AsteriskEmu (chatscript) self.port = self.astemu.port self.manager = Manager() self.manager.connect('localhost', port = self.port) self.manager.register_event ('*', self.handler) def compare_result(self, r_event, event): for k, v in event.iteritems(): if k == 'CONTENT': self.assertEqual(r_event.data, v) elif isinstance(v, str): self.assertEqual(r_event[k], v) else: self.assertEqual(r_event[k], v[-1]) self.assertEqual(sorted(r_event.multiheaders[k]), sorted(list(v))) def test_login(self): self.run_manager({}) r = self.manager.login('account', 'geheim') self.compare_result(r, self.default_events['Login'][0]) self.close() self.assertEqual(self.events, []) def test_command(self): d = dict events = dict \ ( Command = ( Event ( Response = ('Follows',) , Privilege = ('Command',) , CONTENT = """Channel Location State Application(Data) lcr/556 s@attendoparse:9 Up Read(dtmf,,30,noanswer,,2) 1 active channel 1 active call 372 calls processed --END COMMAND--\r """ ) , ) ) self.run_manager(events) r = self.manager.command ('core show channels') self.assertEqual(self.events, []) self.compare_result(r, events['Command'][0]) def test_redirect(self): d = dict events = dict \ ( Redirect = ( Event ( Response = ('Success',) , Message = ('Redirect successful',) ) , ) ) self.run_manager(events) r = self.manager.redirect \ ('lcr/556', 'generic', 'Bye', context='attendo') self.assertEqual(self.events, []) self.compare_result(r, events['Redirect'][0]) def test_originate(self): d = dict events = dict \ ( Originate = ( Event ( Response = ('Success',) , Message = ('Originate successfully queued',) ) , Event ( Event = ('Newchannel',) , Privilege = ('call,all',) , Channel = ('lcr/557',) , ChannelState = ('1',) , ChannelStateDesc = ('Rsrvd',) , CallerIDNum = ('',) , CallerIDName = ('',) , AccountCode = ('',) , Exten = ('',) , Context = ('',) , Uniqueid = ('1332366541.558',) ) , Event ( Event = ('NewAccountCode',) , Privilege = ('call,all',) , Channel = ('lcr/557',) , Uniqueid = ('1332366541.558',) , AccountCode = ('4019946397',) , OldAccountCode = ('',) ) , Event ({ 'Event' : ('NewCallerid',) , 'Privilege' : ('call,all',) , 'Channel' : ('lcr/557',) , 'CallerIDNum' : ('',) , 'CallerIDName' : ('',) , 'Uniqueid' : ('1332366541.558',) , 'CID-CallingPres' : ('0 (Presentation Allowed, Not Screened)',) }) , Event ( Event = ('Newchannel',) , Privilege = ('call,all',) , Channel = ('lcr/558',) , ChannelState = ('1',) , ChannelStateDesc = ('Rsrvd',) , CallerIDNum = ('',) , CallerIDName = ('',) , AccountCode = ('',) , Exten = ('',) , Context = ('',) , Uniqueid = ('1332366541.559',) ) , Event ( Event = ('Newstate',) , Privilege = ('call,all',) , Channel = ('lcr/558',) , ChannelState = ('4',) , ChannelStateDesc = ('Ring',) , CallerIDNum = ('0000000000',) , CallerIDName = ('',) , Uniqueid = ('1332366541.559',) ) , Event ( Event = ('Newstate',) , Privilege = ('call,all',) , Channel = ('lcr/558',) , ChannelState = ('7',) , ChannelStateDesc = ('Busy',) , CallerIDNum = ('0000000000',) , CallerIDName = ('',) , Uniqueid = ('1332366541.559',) ) , Event ({ 'Event' : ('Hangup',) , 'Privilege' : ('call,all',) , 'Channel' : ('lcr/558',) , 'Uniqueid' : ('1332366541.559',) , 'CallerIDNum' : ('0000000000',) , 'CallerIDName' : ('',) , 'Cause' : ('16',) , 'Cause-txt' : ('Normal Clearing',) }) , Event ({ 'Event' : ('Hangup',) , 'Privilege' : ('call,all',) , 'Channel' : ('lcr/557',) , 'Uniqueid' : ('1332366541.558',) , 'CallerIDNum' : ('',) , 'CallerIDName' : ('',) , 'Cause' : ('17',) , 'Cause-txt' : ('User busy',) }) , Event ( Event = ('OriginateResponse',) , Privilege = ('call,all',) , Response = ('Failure',) , Channel = ('LCR/Ext1/0000000000',) , Context = ('linecheck',) , Exten = ('1',) , Reason = ('1',) , Uniqueid = ('',) , CallerIDNum = ('',) , CallerIDName = ('',) ) ) ) self.run_manager(events) r = self.manager.originate \ ('LCR/Ext1/0000000000', '1' , context = 'linecheck' , priority = '1' , account = '4019946397' , variables = {'CALL_DELAY' : '1', 'SOUND' : 'abandon-all-hope'} ) self.compare_result(r, events['Originate'][0]) for k in events['Originate'][1:]: n = self.queue.get() self.compare_result(self.events[n], events['Originate'][n+1]) def test_misc_events(self): d = dict # Events from SF bug 3470641 # http://sourceforge.net/tracker/ # ?func=detail&aid=3470641&group_id=76162&atid=546272 # But we fail to reproduce the bug. events = dict \ ( Login = ( self.default_events['Login'][0] , Event ({ 'AppData' : '0?begin2' , 'Extension' : 'zap2dahdi' , 'Uniqueid' : '1325950970.698' , 'Priority' : '9' , 'Application': 'GotoIf' , 'Context' : 'macro-dial-one' , 'Privilege' : 'dialplan,all' , 'Event' : 'Newexten' , 'Channel' : 'Local/102@from-queue-a8ca;2' }) , Event ({ 'Value' : '2' , 'Variable' : 'MACRO_DEPTH' , 'Uniqueid' : '1325950970.698' , 'Privilege' : 'dialplan,all' , 'Event' : 'VarSet' , 'Channel' : 'Local/102@from-queue-a8ca;2' }) , Event ({'Privilege': 'dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 9\r\n' 'Application: GotoIf\r\n' 'AppData: 0?begin2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 10\r\n' 'Application: Set\r\n' 'AppData: THISDIAL=SIP/102\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: THISDIAL\r\n' 'Value: SIP/102\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 11\r\n' 'Application: Return\r\n' 'AppData: \r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: GOSUB_RETVAL\r\n' 'Value: \r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: dstring\r\n' 'Priority: 9\r\n' 'Application: Set\r\n' 'AppData: DSTRING=SIP/102&\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: DSTRING\r\n' 'Value: SIP/102&\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: dstring\r\n' 'Priority: 10\r\n' 'Application: Set\r\n' 'AppData: ITER=2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 6\r\n' 'Application: ExecIf\r\n' 'AppData: 0?Set(THISPART2=DAHDI/101)\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: ITER\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/102@from-queue-a8ca;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.698\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 7\r\n' 'Application: Set\r\n' 'AppData: NEWDIAL=SIP/101&\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: NEWDIAL\r\n' 'Value: SIP/101&\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 8\r\n' 'Application: Set\r\n' 'AppData: ITER2=2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: ITER2\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 9\r\n' 'Application: GotoIf\r\n' 'AppData: 0?begin2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: MACRO_DEPTH\r\n' 'Value: 2\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: Newexten\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Context: macro-dial-one\r\n' 'Extension: zap2dahdi\r\n' 'Priority: 10\r\n' 'Application: Set\r\n' 'AppData: THISDIAL=SIP/101\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2\r\n' 'Variable: THISDIAL\r\n' 'Value: SIP/101\r\n' 'Uniqueid: 1325950970.696\r\n' '\r\n' 'Event: VarSet\r\n' 'Privilege: dialplan,all\r\n' 'Channel: Local/101@from-queue-4406;2' , 'Variable': 'MACRO_DEPTH' , 'Event': 'VarSet' , 'Value': '2' , 'Uniqueid': '1325950970.696' }) ) ) self.run_manager(events) r = self.manager.login('account', 'geheim') self.compare_result(r, events['Login'][0]) evnames = [] for s in events['Login'][3]['Privilege'].split('\r\n'): if s.startswith('Event:'): evnames.append(s.split(':')[1].strip()) for k in xrange(30): n = self.queue.get() e = self.events[n] if n < 2: self.compare_result(e, events['Login'][n+1]) elif n == 2: self.assertEqual(e['Event'], 'VarSet') else: self.assertEqual(e['Event'], evnames[n-3]) self.assertEqual(len(self.events), 30) def test_agent_event(self): d = dict # Events from SF bug 3470641 # http://sourceforge.net/tracker/ # ?func=detail&aid=3470641&group_id=76162&atid=546272 # But we fail to reproduce the bug. events = dict \ ( Login = ( self.default_events['Login'][0] , Event ( Event = ('AgentCalled',) , Privilege = ('agent,all',) , Queue = ('test',) , AgentCalled = ('SIP/s394000',) , AgentName = ('910567',) , ChannelCalling = ('SIP/multifon-00000006',) , DestinationChannel = ('SIP/s394000-00000007',) , CallerIDNum = ('394000',) , CallerIDName = ('Agent',) , Context = ('from-multifon',) , Extension = ('7930456789',) , Priority = ('3',) , Uniqueid = ('1302010429.6',) , Variable = ('data1=456789', 'data2=test') ) ) ) self.run_manager(events) r = self.manager.login('account', 'geheim') self.compare_result(r, events['Login'][0]) for k in events['Login'][1:]: n = self.queue.get() self.compare_result(self.events[n], events['Login'][n+1]) def test_suite(): suite = unittest.TestSuite() suite.addTest (unittest.makeSuite (Test_Manager)) return suite if __name__ == '__main__': unittest.main()