# Copyright (c) 2007 - ALBAR (Toulouse, FRANCE). # mailto:barthe@albar.fr # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Use epydoc (http://epydoc.sourceforge.net) to produce the documentation. # $Id: perlprocess.py 8 2008-06-02 16:40:43Z albar $ """This module provides a way to execute perl statements in a parallel perl process. Quick start =========== >>> import perlprocess >>> p = perlprocess.perlprocess('use File::Basename;') >>> p.run('"The perl script is " . basename $0') 'The perl script is tmpyOA1fR.pl' >>> p.run('$a = 0; $b = 54') '54' >>> p.run('$b/$a') Traceback (most recent call last): File "", line 1, in ? File "/home/albar_musclor/workspaces/python/perlprocess.py", line 142, in run if error: raise PerlRuntimeError(error) perlprocess.PerlRuntimeError: Illegal division by zero Description =========== When a I{perlprocess} object is created, a separate process is launched, running a small perl script that expects commands on STDIN. If a string is given as parameter to the I{perlprocess} constructor, it is used as a prolog for the perl script. It is usefull mainly to declare B{use} clauses or some useful functions. When invoking the I{run(command)} method of the I{perlprocess} object, the command is sent to the perl process as a string containing eventually several perl statements, eventually among several lines. This string is evaluated, the result is printed on stdout. If an error occurs (perl variable C{$@} not empty), B{PerlRuntimeError} is raised. If you define a perl variable of any kind in the perl process, this variable remains alive until you undefine it. Caveats ======= - The eval in the perl script is performed in a scalar context. So if the result of an eval is a list or a dictionary, either the number of elements or the first element are returned by the I{run} method. To deal with lists and dictionaries, use the perl functions B{printlist} and B{printdict}. These functions are embedded in the I{perlprocess} script. Example: >>> p.run('glob("/media/*")') '/media/cdrom' # Wrong: firlst element only >>> p.run('printlist(glob("/media/*"))') # Right: I get directly a list ['/media/cdrom', '/media/cdrom0', '/media/floppy', '/media/floppy0'] Functions B{printlist} and B{printdict} accept also references to lists or dicts. - Values are always returned as strings (scalars, list and dict elements). If you are waiting for a numeric value, you have to convert it yourself. - When several statements are evluated, the resulting value is the evaluation of the last executed statement. >>> p.run('$a = "Hello"; print $a') # Wrong 'Hello1' # The evaluated value is '1' >>> p.run('$a') # Right 'Hello' # The evaluated value is 'Hello' Note that printed values are also taken as result. - The commands evluated by the perl process must never use: - B{STDIN} as it disturbs the communication with perl. - B{STDERR} because it is interpreted as an error and B{PerlRuntimeError} is raised. Usage ===== A priori, it is a strange idea to execute pieces of perl code from a python program, unless you have to use an existing perl API and you want to write as less perl code as possible. In this case (and only it, in my opinion), I{perlprocess} may help you. See L{VMPython} as an example of use. @requires: a perl interpreter in the PATH. @version: 0.1 @status: beta @author: A. Barthe @copyright: 2007 - ALBAR (Toulouse, FRANCE) @contact: mailto:barthe@albar.fr @group Exception: PerlInitialisationError, PerlRuntimeError """ import os, sys, tempfile, re __all__ = ['perlprocess', 'PerlInitialisationError', 'PerlRuntimeError'] ERR_COOKIE = "*** EOSTDERR ***\n" #: STDERR cookie OUT_COOKIE = "*** EOSTDOUT ***\n" #: STDOUT cookie IN_COOKIE = "*** EOSTDIN ***\n" #: STDIN cookie EXIT_COOKIE = "*** EOPROG ***\n" #: End of execution cookie EVAL_COOKIE = "*** EVAL ***\n" #: The result is evaluated class PerlInitialisationError(Exception): """It occurs when the prolog contains an error (syntax error, package not found, etc). """ class PerlRuntimeError(Exception): "It occurs a command evaluation fail." #def __init__(self, errmsg): print errmsg, sys.exc_info() class perlprocess(object): "The main class" def __init__(self, prolog='', debug=False): """@param prolog: piece of perl code to be executed at the very beginning. @param debug: if True, prints out the perl code""" self.debug = debug desc, script = tempfile.mkstemp('.pl') os.write(desc, prolog + ';\n' + PERL_SCRIPT) os.close(desc) self.stdin, self.stdout, self.stderr = os.popen3('perl %s' % script) error = self.stderr.readline() os.remove(script) if error != ERR_COOKIE: raise PerlInitialisationError(error) def run(self, command): """Evaluates the command, raise a B{PerlRuntimeError} if the evaluation gives an error (perl variable C{$@} not empty). @param command: perl statement(s) to be evaluated as a string @return: what the command printed on B{STDOUT}, plus the result of the last statement evaluation. """ if self.debug: print "Evaluating command: %s" % command self.stdin.write('\n' + command + '\n' + IN_COOKIE) self.stdin.flush() output = self._readlines(self.stdout, OUT_COOKIE) error = self._readlines(self.stderr, ERR_COOKIE) if error: error = re.sub('\s*at \(eval.*$', # Remove perl traceback ' while evaluating string: ', error) error += command raise PerlRuntimeError(error) return output def _readlines(self, desc, cookie): result = '' line = desc.readline() if line == EVAL_COOKIE: evaluate = True line = desc.readline() else: evaluate = False while line != cookie: result += line line = desc.readline() result = result[0:-1] # remove trailing '\n' if evaluate: result = eval(result) return result def __del__(self): "Terminates the perl process and remove the script" self.stdin.write(EXIT_COOKIE + IN_COOKIE) PERL_SCRIPT = """ $EVAL_COOKIE = "%s"; $ERR_COOKIE = "%s"; $OUT_COOKIE = "%s"; $IN_COOKIE = "%s"; $EXIT_COOKIE = "%s"; $| = 1; sub printlist { my @list = @_ == 1 && ref($_[0]) ? @{$_[0]} : @_; my $result = $EVAL_COOKIE . '['; $result .= '"' . join('", "', @list) . '"' if @list; return $result . ']'; } sub printdict { my @list = @_ == 1 && ref($_[0]) ? %%{$_[0]} : @_; my $result = $EVAL_COOKIE . '{'; for (my $i=1; $i<@list; $i+=2) { $result .= '"' . $list[$i-1] . '": "' . $list[$i] . '",'; } $result .= '}'; return $result; } warn $ERR_COOKIE; while (1) { while (my $line = ) { last if $line eq $IN_COOKIE; $cmd .= $line; } last if $cmd eq $EXIT_COOKIE; my $output = eval $cmd; # Trailing '\\n' is needed for communication to work print $output, "\\n" if $output; print $OUT_COOKIE; warn $@ if $@; warn "\\n$ERR_COOKIE"; undef $cmd; } """ % (EVAL_COOKIE, ERR_COOKIE, OUT_COOKIE, IN_COOKIE, EXIT_COOKIE) "The perl code"