pythontwistedtwisted.client

twisted client call from a library function


I'm trying to implement a function that will act as a Twisted client. It's called from code I don't have control over. I tried something like (this taken from the pbsimpleclient.py example code):

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


from twisted.spread import pb
from twisted.internet import reactor
from twisted.python import util

def remcall(**kw):
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8789, factory)
    d = factory.getRootObject()
    # kw here is what's passed in via remcall
    d.addCallback(lambda object: object.callRemote("echo", kw))
    d.addCallback(lambda echo: 'server echoed: '+repr(echo))
    d.addErrback(lambda reason: 'error: '+str(reason.value))
    d.addCallback(util.println)
    d.addCallback(lambda _: reactor.stop())
    reactor.run()

And the caller would make a call like:

remcall(hello=1, world=2)
remcall(hi=3, there=4)

But as you may have guessed, it gives a "twisted.internet.error.ReactorNotRestartable" error.

What's the best way to do this? I'm not so worried about getting a response from the remote end, but I should know if it's failing and why.


Solution

  • The answer is to use crochet.

    # Copyright (c) Twisted Matrix Laboratories.
    # See LICENSE for details.
    
    ## Add these two lines
    from crochet import setup, wait_for
    setup()
    
    from twisted.spread import pb
    from twisted.internet import reactor
    from twisted.python import util
    
    ## Add a wait_for decorator
    @wait_for(timeout=5.0)
    def remcall(**kw):
        factory = pb.PBClientFactory()
        reactor.connectTCP("localhost", 8789, factory)
        d = factory.getRootObject()
        # kw here is what's passed in via remcall
        d.addCallback(lambda object: object.callRemote("echo", kw))
        d.addCallback(lambda echo: 'server echoed: '+repr(echo))
        d.addErrback(lambda reason: 'error: '+str(reason.value))
        d.addCallback(util.println)
    ## Get rid of the reactor calls, and return d
    #    d.addCallback(lambda _: reactor.stop())
    #    reactor.run()
        return d
    

    Then the caller just calls

    remcall(hello=1, world=2)
    remcall(hi=3, there=4)
    

    and crochet's @wait_for handles running remcall inside the reactor thread.