Récemment, j’ai dû réfléchir à une solution de moteur de script dans le cadre d’un projet au boulot. Le but est d’enregistrer l’exécution d’un code python à la volée et de générer le script équivalent, pouvant se rejouer seul.

J’ai pour cela utilisé Spring Python qui est la version Python du fameux Spring pour Java, ainsi que l’introspection fournie par Python.

J’ai pris un exemple simpliste dont le sens des classes a peu d’intérêt, l’important étant d’avoir plusieurs classes pour générer des noms d’instances uniques par type. Le code source est disponible ici.

On crée deux classes de base qui encapsule des entiers (Chiffre et Nombre) et une classe qui possède des méthodes statiques afin d’instancier et d’additionner un Chiffre et un Nombre.

class Nombre:
    def __init__(self, nombre):
        self.nombre = nombre
    def toInt(self):
        return self.nombre
    def printOut(self):
        print self.toInt()


class Chiffre:
    def __init__(self, chiffre):
        self.chiffre = chiffre
    def toInt(self):
        return self.chiffre


from Nombre import Nombre
from Chiffre import Chiffre
class LibCalcul:
@staticmethod
def getNombre(nombre):
return Nombre(nombre)
@staticmethod
def getChiffre(chiffre):
return Chiffre(chiffre)
@staticmethod
def additionne(nombre, chiffre):
return Nombre(nombre.toInt() + chiffre.toInt())
@staticmethod
def printNombre(nombre):
print nombre.toInt()


Il faut ensuite définir un intercepteur qui sera appelé lorsque qu’une méthode de la classe LibCalcul sera appelée. Le point d’entrée de cette classe est la méthode “invoke”.

from springpython.aop import MethodInterceptor
class ScriptifyInterceptor(MethodInterceptor):
    _sessionIds = {}
    _sessionIdGenerated = {}
    def __init__(self, outputFilename):
        self._outputFilename = outputFilename
    def getClassNameForObject(self, objectTarget):
        return objectTarget.__class__.__name__
    def getGeneratedIdForObject(self, objectTarget):
        objectClassname = self.getClassNameForObject(objectTarget)
        if not objectClassname in ScriptifyInterceptor._sessionIdGenerated.keys():
            ScriptifyInterceptor._sessionIdGenerated[objectClassname] = 0
        ScriptifyInterceptor._sessionIdGenerated[objectClassname] += 1
        return ScriptifyInterceptor._sessionIdGenerated[objectClassname]
    def getIdForObject(self, objectTarget):
        objectId = id(objectTarget)
        if not objectId in ScriptifyInterceptor._sessionIds.keys():
            ScriptifyInterceptor._sessionIds[objectId] = self.getGeneratedIdForObject(objectTarget)
        return ScriptifyInterceptor._sessionIds[objectId]
    def getVariableForObject(self, objectTarget):
        return self.getClassNameForObject(objectTarget).lower() + "_" + str(self.getIdForObject(objectTarget))
    def invoke(self, invocation):
        lstArgs = []
        for arg in invocation.args:
            if not isinstance(arg, int):
                lstArgs.append(self.getVariableForObject(arg))
            else:
                lstArgs.append(str(arg))
        result = self.getClassNameForObject(invocation.instance) + "." + invocation.method_name + "(" + str(", ").join(lstArgs) + ")"
        objectReturned = invocation.proceed()
        if objectReturned:
            result = self.getVariableForObject(objectReturned) + " = " +  result
        with open(self._outputFilename, 'a') as script:
            script.write(result+"\n")
        return objectReturned


On met en place le système dans un script.

from springpython.aop import ProxyFactory
from ScriptifyInterceptor import ScriptifyInterceptor
from LibCalcul import LibCalcul
scriptFilename = "testScriptify.py"
with open(scriptFilename, 'w') as script:
    script.write("from LibCalcul import LibCalcul\n\n")
factory = ProxyFactory()
factory.target = LibCalcul()
factory.interceptors.append(ScriptifyInterceptor(scriptFilename))
service = factory.getProxy()
nombre1 = service.getNombre(10)
chiffre1 = service.getChiffre(5)
nombre2 = service.additionne(nombre1, chiffre1)
service.printNombre(nombre2)


Voilà le résulat.
from LibCalcul import LibCalcul
nombre_1 = LibCalcul.getNombre(10)
chiffre_1 = LibCalcul.getChiffre(5)
nombre_2 = LibCalcul.additionne(nombre_1, chiffre_1)
LibCalcul.printNombre(nombre_2)