[Twisted-Python] AMP with a long-lived connection - results and deferred?
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
Working off the example ampserver.py and ampclient.py examples, I wanted to build a client which maintains a single connection while allowing the passing of messages back and forth. However I'm stuck at one of the most basic steps, getting back the result (without 'completing' the connection). The ampclient.py example simply connects, does a callRemote on the resulting protocol, and adds callbacks to extract the result from a dictionary and print it. The deferred being used in this case is produced by connectProtocol. I'm trying to write a client (inheriting from amp.AMP) which is embedded in a kivy GUI, something which is quite possible using the _threadedselect reactor. On connection, I use the connectionMade method to save the 'self' (in this case, the client inheriting from amp.AMP) in my kivy app. I can then call a function which does a callRemote on this saved client, which indeed triggers the server appropriately. The callRemote returns a deferred (from reading docs online, a remote reference). I can't figure out what to do with it, specifically in terms of getting the result ('total', when calling Sum from ampserver.py). Assistance much appreciated.
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
One 'answer' that I've gotten so far is in this link - http://stackoverflow.com/questions/15640393/how-to-create-bi-directional-mes... - but that doesn't seem to achieve what I want, at least not in the way I want it. It does a callRemote from the server in the function called by the client (using callRemote). As I understand things, that's what deferred is meant to solve, and anyway this seems to invalidate the reason for using amp.Command inheritence anyway (as the response is required). To be clear, that way does 'work', but it doesn't seem to be doing the sort of bidirectional messaging AMP should be doing. In summary:- how do I see the response from client side without making a new connection every time I have something to send (as done in the ampclient.py example)? On Wed, Oct 28, 2015 at 2:24 PM, Oon-Ee Ng <ngoonee.talk@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/52cff2b66b9e291797f15f0c1b3491b8.jpg?s=120&d=mm&r=g)
On 10/28/2015 02:24 AM, Oon-Ee Ng wrote:
Basically, callRemote returns a deferred, that you can add callbacks and errbacks to, which will be called when the remote call succeeds or fails. On success, the callback will receive an argument equal to the return value of the remote callable that you called. On failure, the errback will receive an error argument. I have an old GUI (PyGTK, not Kivy) chat over AMP example at https://github.com/dripton/ampchat that might help you. (Though I think all of my commands return boring responses.) -- David Ripton dripton@ripton.net
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
Thanks David, but when I do something along the lines of:- def mycall(in): print(in) d = self.connection.boxReceiver.callRemote(Command, a='test') d.addCallBack(mycall) The print never actually happens. I'm not really sure why, it was one of the first things I tried. I tried the most basic thing, modifying the doMath() example, and mycall only triggers when the reactor stops (even if I run doMath multiple times with delays). On Thu, Oct 29, 2015 at 1:15 PM, David Ripton <dripton@ripton.net> wrote:
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
Hmm, I'm wondering whether this could be specific to the reactor I'm using (a _threadedselect reactor embedded in Kivy). I wrote up a minimal example of what I'm doing to demonstrate the issue, but this example works! Doesn't in my (admittedly quite a bit more complex) kivy example though. Let me try for a minimal example there and see if I can figure it out. from twisted.internet import reactor, protocol from twisted.internet.task import deferLater from twisted.protocols import amp from ampserver import Sum, Divide connection = None class MathClient(amp.AMP): def connectionMade(self): global connection connection = self class MathFactory(protocol.ReconnectingClientFactory): protocol = MathClient if __name__ == '__main__': reactor.connectTCP('127.0.0.1', 1234, MathFactory()) def simpleSum(): global connection d = connection.callRemote(Sum, a=1, b=5) def prin(result): print(result) d.addCallback(prin) return d deferLater(reactor, 1, simpleSum) deferLater(reactor, 3, simpleSum) deferLater(reactor, 6, simpleSum) deferLater(reactor, 9, simpleSum) deferLater(reactor, 12, simpleSum) deferLater(reactor, 15, simpleSum) deferLater(reactor, 18, simpleSum).addCallback(lambda _: reactor.stop()) reactor.run() On Thu, Oct 29, 2015 at 3:37 PM, Oon-Ee Ng <ngoonee.talk@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
And lo and behold it seems to work now even with Kivy. Strange. For posterity, here's the simple client code I was using for a minimal example. I'll expand upwards from here and see how far I can get. #install_twisted_rector must be called before importing the reactor from kivy.support import install_twisted_reactor install_twisted_reactor() from twisted.internet import reactor, protocol from twisted.internet.task import deferLater from ampserver import Sum, Divide from twisted.protocols import amp class EchoClient(amp.AMP): def connectionMade(self): self.factory.resetDelay() self.factory.app.on_connection(self) class EchoFactory(protocol.ReconnectingClientFactory): protocol = EchoClient def __init__(self, app): self.app = app from kivy.app import App from kivy.uix.label import Label from kivy.uix.textinput import TextInput from kivy.uix.boxlayout import BoxLayout # A simple kivy App, with a textbox to enter messages, and # a large label to display all the messages received from # the server class TwistedClientApp(App): connection = None def build(self): root = self.setup_gui() self.connect_to_server() return root def setup_gui(self): self.textbox = TextInput(size_hint_y=.1, multiline=False) self.textbox.bind(on_text_validate=self.send_message) self.label = Label(text='connecting...\n') self.layout = BoxLayout(orientation='vertical') self.layout.add_widget(self.label) self.layout.add_widget(self.textbox) return self.layout def connect_to_server(self): reactor.connectTCP('127.0.0.1', 1234, EchoFactory(self)) def on_connection(self, connection): self.print_message("connected succesfully!") self.connection = connection def send_message(self, *args): msg = self.textbox.text.encode('ascii') if self.connection: d = self.connection.boxReceiver.callRemote(Sum, a=3, b=8) def prin(result): print(result) d.addCallback(prin) def print_message(self, msg): self.label.text += str(msg) + "\n" if __name__ == '__main__': TwistedClientApp().run()
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
One 'answer' that I've gotten so far is in this link - http://stackoverflow.com/questions/15640393/how-to-create-bi-directional-mes... - but that doesn't seem to achieve what I want, at least not in the way I want it. It does a callRemote from the server in the function called by the client (using callRemote). As I understand things, that's what deferred is meant to solve, and anyway this seems to invalidate the reason for using amp.Command inheritence anyway (as the response is required). To be clear, that way does 'work', but it doesn't seem to be doing the sort of bidirectional messaging AMP should be doing. In summary:- how do I see the response from client side without making a new connection every time I have something to send (as done in the ampclient.py example)? On Wed, Oct 28, 2015 at 2:24 PM, Oon-Ee Ng <ngoonee.talk@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/52cff2b66b9e291797f15f0c1b3491b8.jpg?s=120&d=mm&r=g)
On 10/28/2015 02:24 AM, Oon-Ee Ng wrote:
Basically, callRemote returns a deferred, that you can add callbacks and errbacks to, which will be called when the remote call succeeds or fails. On success, the callback will receive an argument equal to the return value of the remote callable that you called. On failure, the errback will receive an error argument. I have an old GUI (PyGTK, not Kivy) chat over AMP example at https://github.com/dripton/ampchat that might help you. (Though I think all of my commands return boring responses.) -- David Ripton dripton@ripton.net
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
Thanks David, but when I do something along the lines of:- def mycall(in): print(in) d = self.connection.boxReceiver.callRemote(Command, a='test') d.addCallBack(mycall) The print never actually happens. I'm not really sure why, it was one of the first things I tried. I tried the most basic thing, modifying the doMath() example, and mycall only triggers when the reactor stops (even if I run doMath multiple times with delays). On Thu, Oct 29, 2015 at 1:15 PM, David Ripton <dripton@ripton.net> wrote:
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
Hmm, I'm wondering whether this could be specific to the reactor I'm using (a _threadedselect reactor embedded in Kivy). I wrote up a minimal example of what I'm doing to demonstrate the issue, but this example works! Doesn't in my (admittedly quite a bit more complex) kivy example though. Let me try for a minimal example there and see if I can figure it out. from twisted.internet import reactor, protocol from twisted.internet.task import deferLater from twisted.protocols import amp from ampserver import Sum, Divide connection = None class MathClient(amp.AMP): def connectionMade(self): global connection connection = self class MathFactory(protocol.ReconnectingClientFactory): protocol = MathClient if __name__ == '__main__': reactor.connectTCP('127.0.0.1', 1234, MathFactory()) def simpleSum(): global connection d = connection.callRemote(Sum, a=1, b=5) def prin(result): print(result) d.addCallback(prin) return d deferLater(reactor, 1, simpleSum) deferLater(reactor, 3, simpleSum) deferLater(reactor, 6, simpleSum) deferLater(reactor, 9, simpleSum) deferLater(reactor, 12, simpleSum) deferLater(reactor, 15, simpleSum) deferLater(reactor, 18, simpleSum).addCallback(lambda _: reactor.stop()) reactor.run() On Thu, Oct 29, 2015 at 3:37 PM, Oon-Ee Ng <ngoonee.talk@gmail.com> wrote:
![](https://secure.gravatar.com/avatar/5026480ce2d021886928e4b8842d6dd8.jpg?s=120&d=mm&r=g)
And lo and behold it seems to work now even with Kivy. Strange. For posterity, here's the simple client code I was using for a minimal example. I'll expand upwards from here and see how far I can get. #install_twisted_rector must be called before importing the reactor from kivy.support import install_twisted_reactor install_twisted_reactor() from twisted.internet import reactor, protocol from twisted.internet.task import deferLater from ampserver import Sum, Divide from twisted.protocols import amp class EchoClient(amp.AMP): def connectionMade(self): self.factory.resetDelay() self.factory.app.on_connection(self) class EchoFactory(protocol.ReconnectingClientFactory): protocol = EchoClient def __init__(self, app): self.app = app from kivy.app import App from kivy.uix.label import Label from kivy.uix.textinput import TextInput from kivy.uix.boxlayout import BoxLayout # A simple kivy App, with a textbox to enter messages, and # a large label to display all the messages received from # the server class TwistedClientApp(App): connection = None def build(self): root = self.setup_gui() self.connect_to_server() return root def setup_gui(self): self.textbox = TextInput(size_hint_y=.1, multiline=False) self.textbox.bind(on_text_validate=self.send_message) self.label = Label(text='connecting...\n') self.layout = BoxLayout(orientation='vertical') self.layout.add_widget(self.label) self.layout.add_widget(self.textbox) return self.layout def connect_to_server(self): reactor.connectTCP('127.0.0.1', 1234, EchoFactory(self)) def on_connection(self, connection): self.print_message("connected succesfully!") self.connection = connection def send_message(self, *args): msg = self.textbox.text.encode('ascii') if self.connection: d = self.connection.boxReceiver.callRemote(Sum, a=3, b=8) def prin(result): print(result) d.addCallback(prin) def print_message(self, msg): self.label.text += str(msg) + "\n" if __name__ == '__main__': TwistedClientApp().run()
participants (2)
-
David Ripton
-
Oon-Ee Ng