Old mod python
From FreeSWITCH Wiki
OUT OF DATE!!. All information on this page is out of date, and is only useful for people using an older version of Mod_python. See Mod_python for current information.
WARNNG - mod_python is NOT ready for production. One user has done extensive load testing with mod_python using real call traffic (6/10/08), and got segmentation faults. Your mileage will probably be the same. See Status section below for more discussion on this.
Unless you are willing to roll up our sleeves and help fix things, you are probably much better off either:
- Writing your code in lua and using mod_lua or
- Trying out the IronPython support in mod_mono.
Status
Despite grave disclaimers made above, mod_python is known to work with very good reliability:
- With an ancient version of freeswitch (revision 5834)
- Under a light/medium real world load (doesn't necessarily crash under heavy load, just not as much testing was done under a real load)
- Under heavy testing of a simulated load with sipp.
- Avoiding certain problematic API calls like setting hangup hooks (definitely) and DTMF callbacks (possibly).
mod_python supports approximately 90% of the API calls supported by mod_lua. Of the completed methods, several have slightly different API syntax compared to the Mod_lua version, and will be changed to match mod_lua.
There are known bugs that have cropped up during changes to the CoreSession object that happened during the creation of mod_lua, and so mod_python is less reliable with more recent versions of freeswitch until more testing/debugging can be done.
Getting Started
Building mod_python
Configure Script
The configure script will try to detect your existing python, if it cannot find it or it does not support multithreading, it will print a warning. Otherwise, it will write a Makefile that uses the detected version.
You can specify arguments to configure to make it use a particular version (you really only need to manually specify them if the default doesn't work for you). Both args need to be given:
- --with-python (eg, --with-python=/usr/bin/python-2.5)
- --with-python-config (eg, --with-python-config=/usr/bin/python-config-2.5)
NOTE: if you use custom locations you have to make sure python and python-config are from the same version
NOTE #2: As of svn revision 8455 / 1.0_rc6 --with-python-config has been removed!
Enable compilation in modules.conf
- edit modules.conf and uncomment languages/mod_python
- make installall
Enabling mod_python
Open up conf/modules.conf.xml and add an entry
<load module="mod_python"/>
PYTHONPATH
There are two different ways to tell the python interpreter how to find python modules. If you don't do either of these, the embedded python interpreter will have no way to find your python scripts.
Assuming you have
<action application="python" data="foo.bar"/>
This is telling python to load the bar module that lives in the foo package.
Copy or Symlink to site-packages directory
If the source file is in /usr/src/foo/bar.py:
cd /path/to/python/site-packages ln -s /usr/src/foo .
The same can be done via copying. Don't forget, the foo package directory will need an __init__.py.
Adding to PYTHONPATH environment variable
If the file is in /usr/src/foo/bar.py, add the following to your system environment startup
export PYTHONPATH=$PYTHONPATH:/usr/src
Don't forget, the foo package directory will need an __init__.py.
In the shell where freeswitch is started, this environment variable will need to be defined.
Invoking mod_python applications
To call a Python application from dialplan, you should probably be familiar with the Dialplan. You simply call it as an application similar to:
<action application="python" data="foo.bar"/>
The module is bar, in the foo package. See the Finding python modules section to tell the embedded python interpreter how to find this module.
If your module (say, test.py) is not in any package directory, then you would instead use:
<action application="python" data="test"/>
In both cases, you need to leave off the .py file extension otherwise it will not work. It expects a fully qualified module name only.
You would put this in your 'dialplan' if using the XML dialplan module with freeswitch. Dont forget your 'condition' tags and all that goodness.
Its posible to call python script from CLI with following format:
freeswitch> python foo.bar
NOTE: if you invoke it this way, your python handler() function will be called with no arguments.
Python module specification
Your python module must define a function called handler that takes a single string argument, uuid. Eg, def handler(uuid).
- If the script is called in the context of a live call, the uuid will have the uuid of the call, which can then be used to create a PySession -- the main interface to the freeswitch engine.
- If the script is called from the command line, the uuid will be None. It is up to your module to check for None and do the right thing.
Sample Python Scripts
Hello World via call
In the case that an extension has been mapped to the python module in the dialplan, here is what bar.py should look like:
from freeswitch import *
def handler(uuid):
session = PySession(uuid)
session.answer()
console_log("INFO", "Hello, world\n")
session.hangup("1")
Hello World via cmd line
In the case the script might be called from the command line, the script will need to be a bit smarter:
from freeswitch import *
def handler(uuid):
if not uuid:
console_log("INFO", "Hello, uuid-less world\n")
else:
session = PySession(uuid)
...
WARNING: Running python scripts from the FreeSWITCH command line or the python interactive console isn't well tested. Test it calling from the dialplan if you have any problems.
Sample IVR flow with python script
import sys, time
from freeswitch import *
def onDTMF(input, itype, funcargs):
console_log("1","\n\nonDTMF input: %s\n" % input)
if input == "5":
return "pause"
if input == "3":
return "seek:+60000"
if input == "1":
return "seek:-60000"
if input == "0":
return "stop"
return None # will make the streamfile audio stop
def handler(uuid):
console_log("1","... test from my python program\n")
session = PySession(uuid)
session.answer()
session.setDTMFCallback(onDTMF, "")
session.set_tts_parms("cepstral", "david")
session.streamFile("/path/to/test.mp3")
session.speak("Please enter telephone number with area code and press pound sign. ")
input = session.getDigits("", 11, "*#", "#", 10000)
console_log("1","result from get digits is %s\n" % input)
phone_number = session.playAndGetDigits(5, 11, 3, 10000, "*#",
"/sounds/test.gsm",
"/sounds/invalid.gsm",
"^17771112222$");
console_log("1","result from play_and_get_digits is %s\n" % phone_number)
session.transfer("1000", "XML", "default")
session.hangup("1")
More samples
FAQ
Does each script spawn a python interpreter?
No. A single python interpreter is spawned at module startup and used for the lifetime of the freeswitch process.
Are there thread safety issues?
Each thread swaps in its "thread state" before executing python code and then swaps it out when finished. Also during blocking calls into freeswitch, a thread will swap out its thread state in order to not block other threads, and then swap it in after the blocking call to freeswitch has finished.
I changed a module I'm importing, and nothing happened
Answer: assume you are importing a module called baz, change your entrypoint module to:
import baz reload(baz)
Does it spawn a python interpreter for every script?
Answer: No. One python interpreter is spawned when module is loaded and used throughout. It has a design as similar as possible to Apache's mod_python. By default, a single interpreter is used for a web application, which could be handling thousands of concurrent requests.
How do I pass arguments to the script?
This is possible using channel variables. In the dialplan:
<extension name="foo">
<condition field="destination_number" expression="^123$">
<action application="set" data="foo=bar"/>
<action application="python" data="mypackage.myscript"/>
</condition>
</extension>
and in the python script:
foo = session.getVariable("foo")
console_log("info", "\n\\n foo: %s\n\n" % foo)
Can I test scripts using python shell?
No, it will fail as follows when you try to import the python module:
>>> from freeswitch import *
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "/usr/lib/python2.4/site-packages/freeswitch.py", line 7, in ?
import _freeswitch
ImportError: No module named _freeswitch
Troubleshooting
Error:TypeError: CoreSession_playAndGetDigits() takes exactly 9 arguments (10 given)
You need to update your scripts for this change
Cannot import freeswitch
Copy freeswitch.py from the python directory installed by freeswitch to /usr/lib/python2.5/site-packages
NOTE: if you are getting this error trying to test on the python shell .. you will never get past it. The only way to test python IVR scripts is to define the script in the dialplan and call the number. There is no way to currently do any testing with mod_python scripts outside the context of an IVR running in freeswitch.
Cannot import time
This indicates a problem where lib-dynload is pointing to the wrong place. Try printing out "sys.path" from your python script and look for a situation where you have /opt/freeswitch-trunk/lib/python2.5/site-packages and /usr/lib/python2.5/lib-dynload but not /opt/freeswitch-trunk/lib/python2.5/lib-dynload
Workaround
Change your $PATH environment variable so it sees the python installed by freeswitch before the system python, eg, PATH=/opt/fs/bin:$PATH. More info
Workaround #2
You can try modifying the Makefile so that it uses the python already on your system.
CoreSession_setDTMFCallback() takes exactly 4 arguments ..
If you see:
TypeError: CoreSession_setDTMFCallback() takes exactly 4 arguments (2 given)
Indicates you are running an older release and need to update if you want this to work.
CoreSession_streamfile() takes exactly 3 arguments (4 given)
If you see
TypeError: CoreSession_streamfile() takes exactly 3 arguments (4 given)
it could mean that you are trying to use a dtmf callback that is a bound method of an object -- don't do that! The dtmf callback function should always be at the module scope and not take ("self") as an argument.
Error calling DTMF callback - wrong # of arguments
You may be trying to use a bound instance method to a class. (eg, takes self as first argument). This will not work. Instead what you can do is to have a nested method for your dtmf handler that can access instance's self name.
For example:
class YourIvr:
def __init__(self, session):
self.session=session
def run(self):
console_log("debug", "run method started")
def dtmf_handler(input, itype, funcargs):
console_log("INFO","\n\nDTMF itype: %s\n" % itype)
console_log("INFO","\n\nDTMF value: %s\n" % input)
console_log("INFO","\n\nsession.uuid: %s\n" % self.session.uuid)
return None
console_log("debug", "setting dtmf callback\n")
session.setDTMFCallback(dtmf_handler, "")
'console_log', argument 2 of type 'char *'
If you see error messages:
TypeError: in method 'console_log', argument 2 of type 'char *'
You just need to call str() on the variable before passing to console_log, which cannot deal with unicode strings at the present time.
Channels are not being cleaned up
This should not happen, if it does please report a bug with detailed instructions on how to reproduce. This has surfaced and been fixed a few times.
Avoid module-level global variables
If you find yourself using the globals keyword -- redesign your script. A good way to avoid having to pass the session around to each function is to just use classes. For example:
class Foo:
def __init__(self, session):
self.session = session
self.say_hello()
def say_hello(self):
self.session.speak("These aren't the droids you are looking for.")
def handler(uuid):
session = PySession(uuid)
session.answer()
foo = Foo(session)
Known Bugs
FIXED: Django database connections are not cleaned up
When using Django with mod_python, database connections are left open and will accumulate.
UPDATE: Fixed in revision 8193
python IVR's that execute nested python applications crash
If you have a python ivr script that runs
session.execute("python","package.script")
the entire freeswitch will crash with an error tstate mix-up.
Instead of calling the script via session.execute, just directly invoke the needed code from the current script, passing it the current session as a parameter.
from package import script script.dostuff(session)
If the other script being invoked does not have a method that takes a session as a parameter, then re-factor it.
transferring to an extension that runs a python IVR hangs
Currently, transferring to an extension that runs a python IVR does not work. You could use the workaround mentioned above in the "python IVR's that execute nested python applications crash" section.
Using originate() and bridge() results in the audio only flowing one direction
Workaround: instead of using orginate() and bridge() api calls, use
self.session.execute("bridge", 'sofia/mydomain.com/foo@bar.com')
Where you will need to replace mydomain.com to whatever you have set in the sofia configuration.
Strange errors on shutdown
2007-06-24 17:52:18 [CONSOLE] switch_loadable_module.c:1054 do_shutdown() Stopping: mod_python
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "/usr/lib/python2.4/atexit.py", line 24, in _run_exitfuncs
2007-06-24 17:52:18 [NOTICE] switch_loadable_module.c:485 switch_loadable_module_unprocess() Deleting Application 'python'
2007-06-24 17:52:18 [NOTICE] switch_loadable_module.c:503 switch_loadable_module_unprocess() Deleting API Function 'python'
func(*targs, **kargs)
File "/usr/lib/python2.4/threading.py", line 638, in __exitfunc
self._Thread__delete()
File "/usr/lib/python2.4/threading.py", line 522, in __delete
del _active[_get_ident()]
KeyError: -1211551072
Error in sys.exitfunc:
Traceback (most recent call last):
File "/usr/lib/python2.4/atexit.py", line 24, in _run_exitfuncs
func(*targs, **kargs)
File "/usr/lib/python2.4/threading.py", line 638, in __exitfunc
self._Thread__delete()
File "/usr/lib/python2.4/threading.py", line 522, in __delete
del _active[_get_ident()]
KeyError: -1211551072
Unloading and reloading the module does not seem to work
Zombie channels
If you see zombie channels being left over after calls, please report a bug and include instructions on how to reproduce the problem.
API
Implemented Methods
Some of these link to the Javascript docs because the method signatures are identical.
- console_log
- console_clean_log
- session.answer
- session.preAnswer
- session.transfer
- session.originate - There is a Bug Using originate() and bridge()
- session.streamFile
- session.playAndGetDigits
- session.setDTMFCallback
- session.recordFile
- session.set_tts_parms
- session.execute
- session.getDigits
- session.hangup
- session.ready
- session.speak
- session.getVariable
- session.setVariable
- session.flushEvents
- session.flushDigits
- session.setAutoHangup
- session.setHangupHook
- session.collectDigits (to be renamed to collectInput)
Methods not yet implemented
- session.waitForAnswer - not implemented in Mod_lua either.
- session.getEvent - not implemented in Mod_lua either.
- session.sayPhrase - not implemented in Mod_lua either. There is a phrase app can call session.exec() and invoke the phrase app.
See Also
Check mod python dev for more info.
