php to python dynamic dispatch example


T

todddeluca

I am posting code for calling almost any python function from php,
because it seems generally useful. Please feel free to suggest
improvements or tell me this has already been done better somewhere
else, etc. My limited searching turned up nothing.

I work in a heterogeneous environment with php web pages and python
modules/scripts. This code requires no no creation of an ad hoc
command line interface to the python module and/or ad hoc serialization
of inputs and outputs.

Essentially it requires python's ability to call functions with keyword
arguments, dynamic importing of modules, and a serialization protocol
shared by php and python. In this case I use php serialization, but
perhaps YAML or JSON could be used instead.

Below is the php function. It simply serializes an array of keyword
parameter values, invokes the python module which will call the
function (passing in the keywords) and unserializes the output.
runCommand() is a helper function that simply invokes the command,
passes it stdin, and returns stdout, stderr, and the exit code of the
command.

function python_dispatch($fullyQualifiedFuncName, $keywords) {
$stdin = serialize($keywords);
$cmd = PYTHON_ROOT."/php_dispatch.py
".escapeshellarg($fullyQualifiedFuncName);
list($exitcode, $stdout, $stderr) = runCommand($cmd, $stdin);
$retval = unserialize($stdout);
return array($retval, $exitcode);
}


The python code is not complicated either. It depends on
PHPSerialize.py and PHPUnserialize.py by Scott Hurring
(http://hurring.com/code/python/serialize/). There are two functions
for dynamically importing the right module and traversing the
components of a complete function path, like
'mypackage.mymodule.myclass.myfunc'.

# this function is from http://docs.python.org/lib/built-in-funcs.html
def _dispatch_import(name):
mod = __import__(name)
components = name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return mod


def _dispatch_lookup_function(name, namespace, prefix=''):
# last name in component
if name.find('.') == -1:
return namespace[name]
else:
# get next component in name, and its fully qualified component
name

first, rest = name.split('.', 1)
if prefix:
modName = '.'.join([prefix, first])
else:
modName = first

if first in namespace:
# if component is in namespace, look for the function in
the namespace of the component
return _dispatch_lookup_function(prefix=modName, name=rest,
namespace=vars(namespace[first]))
else:
# else import component (using fully qualified name) and
look for function in the imported component.
module = _dispatch_import(modName)
return _dispatch_lookup_function(prefix=modName, name=rest,
namespace=vars(module))


def main():
import sys
modAndFuncName = sys.argv[1]
func = _dispatch_lookup_function(modAndFuncName, globals())

# reconstruct function keyword arguments from serialized input


keywords =
walkPHPArrayConvertHeuristically(PHPUnserialize.PHPUnserialize().unserialize(sys.stdin.read()))
sys.stdin.close()

retval = PHPSerialize.PHPSerialize().serialize(func(**keywords))

sys.stdout.write(retval)


The last piece of the puzzle is walkPHPArrayConvertHeuristically()
which converts unserialized dicts to lists if they should be lists.
This hack gets around the fact that everything is a dict (a.k.a. array)
in php, so using php serialization to go between php and python is not
isomorphic. This might be a good reason to use JSON or YAML for
serialization, but I do not have any experience with them.

def walkPHPArrayConvertHeuristically(data):
if type(data) is types.DictType:
if isPHPDataListGuess(data):
lst = convertPHPArrayToList(data)
return [walkPHPArrayConvertHeuristically(item) for item in
lst]
else:
for k in data:
data[k] = walkPHPArrayConvertHeuristically(data[k])
return data
else:
return data


def convertPHPArrayToList(array):
keys = array.keys()
keys.sort()
return [array[k] for k in keys]


def isPHPDataListGuess(data):
if type(data) is types.DictType:
i = 0
for k in data.keys():
if k != i:
return False
i += 1
return True
else:
return False


I hope you find this code useful or interesting.

Cheers,
Todd
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top