Otra de las grandes cosas de Twisted es que tiene varias opciones de RPC. De éstas, la que me parece más pythonesca es la que dieron en llamar Perspective Broker. Con esta parte del framework sólo tenemos que heredar de un par de clases, usar un par de factories ya listas para usar, y ponerle remote_ a los métodos que queremos exportar:

from twisted.spread import pb
from twisted.internet import reactor, defer

class MyServer (pb.Root):
    # ...
    def remote_hello (self, greeting):
        print "client says %s" % greeting
        return "why, hello there!"

s= MyServer ()
reactor.listenTCP (port, pb.PBServerFactory (s))
reactor.run ()

Éste es un server que exporta un método remoto hello. El cliente no es mucho más difícil:

def answered (answer):
    print "server said %s" % answer

def connected (root):
    root.callRemote ('hello', 'hi server!').addCallback (answered)

factory= pb.PBClientFactory ()
reactor.connectTCP (name, port, factory)
factory.getRootObject ().addCallback (connected)

Lo primero que tenemos que pedir antes de poder hacer nada es el "objeto raíz". Éste es el objeto que nos va a permitir acceder a todos los servicios remotos ofrecidos por el server. Este objecto es el s que creamos en el server y que pasamos como objeto raíz a la pb.PBServerFactory. getRootObject() nos devuelve un Deferred.

Una vez obtenido el objeto raíz, podemos empezar a llamarle métodos remotos. Notar que callRemote() recibe el nombre del método remoto como un string, y el resto de los parámetros que pasamos son los parámetros con los que va a ser llamado el método remoto en el server. Como es de esperarse, una llamada a un método remoto no vuelve inmediatamente, sino que nos da también un Deferred.

Ahora bien, vemos que los Deferreds son la estrella del framework, y que los usamos en todos lados... ¿Qué pasa cuando un método remoto no puede devolver inmediatamente el resultado? Pues, aunque parezca raro y obvio al mismo tiempo, ¡devuelve un Deferred!:

    # ... en MyServer
    def bye (self):
        return defer.succeed (True)

Acá estoy usando defer.suceed(), que es una función que devuelve un Deferred con su valor ya disponible. Esto es útil para cuando en algunos casos podemos calcular el valor de la respuesta ya, pero en otros casos los tenemos que ir a buscar en otro lado. También existe defer.fail().

Ahora bien, acá entra un poco la magia de Twisted. Es obvio que este Deferred no es el que es devuelto por callRemote(), porque ése es el de haber llamado a un método remoto, no el que devuelve ese método por no tener la respuesta inmediatamente. Lo que no es tan obvio es que este Deferred nunca viaja por la red. Perspective Broker se da cuenta de que el valor no está disponible aún y por lo tanto no retorna nada por la red. Esto es posible porque del otro lado ya hay un Deferred "esperando". Cuando el resultado esté disponible en el server, podemos llamar al método callback() de la promesa con el valor calculado y recién entonces se manda un mensaje por la red y se resuelve la promesa del lado del cliente:

    # ... MyServer
    mxCalc= relaymanager.MXCalculator ()

    def remote_resolveMX (self, hostname):
        promise= defer.Deferred ()
        mxCalc.getMX (hostname).addCalback (self.resolvedMX, promise)
        return promise

    def resolvedMX (self, mxRecord, promise):
        if mxRecord is not None:
            answer= mwRecord.name
        else:
            answer= None
        promise.callback (answer)

Acá estoy usando otro detalle que creo que no había presentado antes: estoy llamando a addCallback() no sólo con un callback, sino que le agrego otro parámetro, una referencia a la promesa que devolví. Cuando getMX() resuelve su valor, el callback es llamado con ese resultado como primer parámetro y los otros parámetros que pasé a addCallback() después. Esto me permite juntar el resiltado con la promesa devuelta anteriormente.

Hay varias cosas más en el PB. Hay objetos que se pueden referenciar remotamente, que se pueden copiar (hay dos tipos, pero aún no les agarro la mano) y un par de cosas más. Además, Twisted permite RPC usando XML-RPC (buzzword!) y también acceso a/servido de webservices (buzzword!) a través de SOAP y REST (buzzword, buzzword!).

Vamos viendo que Twisted tiene muchas cosas pensadas, y que los conceptos que maneja no son muchos ni muy complicados. Si puedo decir que en la forma en que se van 'partiendo' las soluciones en métodos (estados) no ayuda a la legibilidad posterior del código. Por ejemplo, vean cómo empezamos a tener métodos que representan estados y que son varios para algo que de otra forma nos parecería más sencillo:

    # ... MyServer
    def remote_resolveMX (self, hostname):
        return mxCalc.getMX (hostname)
    # ...

# ... cliente
root= factory.getRootObject ()
mx= root.callRemote ('resolveMX', 'decode.com.ar')
# mandar un mail a pyar...

Tengo algunas ideas de cómo simplificar estas cosas. Básicamente se basa en partir el conjunto de máquinas de estados representado en una sola clase a una clase maestra que coordina todo y varias clasesitas que manipulan las distintas maquinitas de estados. Después les cuento bien.

twisted python