Search code examples
pythontwistedperspective-broker

Why is the perspective argument in a pb.Viewable passed as None?


I am trying to understand how to find out how to allow a server to know which client is making remote requests in twisted's perspective broker. I think I'm supposed to use twisted.spread.pb.Viewable for this, but when I try the perspective argument in the Viewable's view_* methods is None.

I run this server

import twisted.spread.pb as pb
import twisted.internet.reactor as reactor

class Server(pb.Root):
    def __init__(self):
        self.v = MyViewable()

    def remote_getViewable(self):
        return self.v

class MyViewable(pb.Viewable):
    def view_foo(self, perspective):
        print ("Perspective %s"%perspective)


if __name__ == "__main__":
    reactor.listenTCP(54321, pb.PBServerFactory(Server()))
    print("Starting reactor")
    reactor.run()

and this client

import twisted.spread.pb as pb
import twisted.internet.reactor as reactor
from twisted.internet.defer import inlineCallbacks

@inlineCallbacks
def gotRoot(root):
    v1 = yield root.callRemote("getViewable")
    v2 = yield root.callRemote("getViewable")
    print(v1)
    print(v2)
    yield v1.callRemote("foo")
    yield v2.callRemote("foo")

factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 54321, factory)
d = factory.getRootObject()
d.addCallback(gotRoot)
reactor.run()

The output from the server is

Starting reactor
Perspective None
Perspective None

Why are the perspective arguments None?


Solution

  • Through experimentation I believe I have determined the answer.

    In order for remote invocations of a view_* method on a pb.Viewable to properly receive the perspective argument, the reference to that Viewable held by the client must have been obtained as the return value from a perspective_* method called on an instance of pb.Avatar (or subclass). The perspective argument passed into the view_* methods then corresponds to the Avatar that originally gave the client the reference to the Viewable.

    The example code in the original posting doesn't work properly because the remote references to the Viewable are passed to the client from a pb.Root, not as return values from a perspective_* method on a pb.Avatar.

    I note here that while this information is implied by the way the examples in the official documents are written, it does not seem to be explicitly stated there.

    EDIT: I've figured out the right way to do this. One of the arguments to the Realm's requstAvatar method is the user's mind. All you have to do is set mind.perspective to the new Avatar instance and all subsequent remote calls work how you'd expect. For example:

    class SimpleRealm:
    implements(IRealm)
    
    def requestAvatar(self, avatarId, mind, *interfaces):
        avatar = MyAvatarSubclass()
        mind.perspective = avatar
        return pb.IPerspective, avatar, avatar.logout
    

    OLD EDIT: A (crummy) way to make this work is to explicitly contruct a pb.ViewPoint and pass that as an argument to the remote client. For example if p is an instance of an Avatar subclass and v is a viewable on the server side, we can do this on the server

    referenceToClient.callRemote("take", ViewPoint(p, v))
    

    where on the client side we have something like

    def remote_take(self, objToReceive):
        self.myView = objToReceive
    

    Subsequent invocations of self.myView.callRemote(...) by the client will work properly