Discussion:
[Twisted-Python] AMP with a long-lived connection - results and deferred?
Oon-Ee Ng
2015-10-28 06:24:24 UTC
Permalink
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.
Oon-Ee Ng
2015-10-28 06:33:58 UTC
Permalink
One 'answer' that I've gotten so far is in this link -
http://stackoverflow.com/questions/15640393/how-to-create-bi-directional-messaging-using-amp-in-twisted-python
- 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)?
Post by Oon-Ee Ng
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.
David Ripton
2015-10-29 05:15:24 UTC
Permalink
Post by Oon-Ee Ng
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.
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 ***@ripton.net
Oon-Ee Ng
2015-10-29 07:37:20 UTC
Permalink
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).
Post by David Ripton
Post by Oon-Ee Ng
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.
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.)
--
_______________________________________________
Twisted-Python mailing list
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
Oon-Ee Ng
2015-10-29 08:03:50 UTC
Permalink
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()
Post by Oon-Ee Ng
Thanks David, but when I do something along the lines of:-
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).
Post by David Ripton
Post by Oon-Ee Ng
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.
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.)
--
_______________________________________________
Twisted-Python mailing list
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
Oon-Ee Ng
2015-10-29 08:16:38 UTC
Permalink
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()

Loading...