txdbus icon indicating copy to clipboard operation
txdbus copied to clipboard

Simplify/eliminate callRemote

Open micolous opened this issue 10 years ago • 2 comments

I was thinking it may be better to eliminate the use of callRemote in txdbus. This would mean that D-Bus remote objects are first-class Python objects, which makes RPC interfaces much simpler, and don't need any glue to talk over the D-Bus.

How I've implemented this in my own code is with something like this:

class DBusRemoteWrapperMethod(object):
    """
    Wrapper for methods for interface.callRemote
    """
    def __init__(self, obj, methname):
        self._obj = obj
        self._methname = methname


    def __call__(self, *args, **kwargs):
        return self._obj.callRemote(self._methname, *args, **kwargs)


class DBusRemoteWrapper(object):
    """
    Wrapper for interfaces that makes everything a callRemote.

    """
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        return DBusRemoteWrapperMethod(self._obj, name)

# (setup reactor here, omitted for simplicity)

@defer.inlineCallbacks
def main():
    conn = yield client.connect(reactor, 'session')
    obj = yield conn.getRemoteObject(DBUS_SERVICE, DBUS_PATH)

    # Add callRemote wrapper
    api = DBusRemoteWrapper(obj)

    # Now we can execute methods directly on the DBus object as if it was
    # a normal Python class...
    yield api.my_method()
    result = yield api.do_other_things('bar', 123)

There's a some limitations to my implementation as it stands:

  • Members are generated dynamically, and it does not check or expose the org.freedesktop.DBus.Introspection interface in order to be smart about this (and populate things like __all__.
  • This doesn't support wrapping org.freedesktop.DBus.Properties to Python properties.
  • This doesn't support handling other interfaces of the object.

There's one major limitation to my implementation as it stands is that it can't handle org.freedesktop.DBus.Properties interface wrapping to a Python property. This would require a little bit more glue in order to handle this.

With o.fd.DB.Properties implementation, it would allow even more interaction like a normal Python object:

api.MyProperty = 6
while api.BottleCount > 0:
  api.BottleCount -= 1

This would make txdbus work a little more like python-dbus, and other RPC mechanisms like Python's XMLRPC module in the standard library (ServiceProxy).

micolous avatar Apr 26 '14 06:04 micolous

Michael,

You are quite correct that callRemote could be eliminated in favor of providing wrapper objects that simulate native object usage. It was, however, an intentional design decision to not do this. There are two primary reasons for not doing so in txdbus. First is that I was attempting to provide an interface similar to that of Twisted's long-standing Perspective Broker API. The second, and more important reason, is that I agree with Perspective Broker's rationale for intentionally avoiding the native-looking APIs for remote objects.

Remote methods do not behave in the same manner as native methods. If nothing else, network connection failures can occur at any time and robust code must account for the fact that all remote calls can fail with TimeOuts and/or ConnectionLost exceptions. The visual distinction between "o.callRemote('foo', 1,2,3)" and "o.foo(1,2,3)" helps to remind readers of your code that you're dealing with remote objects and that the rules are different. Admittedly, it's somewhat less convenient during the initial implementation but the maintenance benefits, in my opinion, more than make up for it.

Cheers,

Tom

On Sat, Apr 26, 2014 at 1:25 AM, Michael Farrell [email protected]:

I was thinking it may be better to eliminate the use of callRemote in txdbus. This would mean that D-Bus remote objects are first-class Python objects, which makes RPC interfaces much simpler, and don't need any glue to talk over the D-Bus.

How I've implemented this in my own code is with something like this:

class DBusRemoteWrapperMethod(object): """ Wrapper for methods for interface.callRemote """ def init(self, obj, methname): self._obj = obj self._methname = methname

def __call__(self, *args, **kwargs):
    return self._obj.callRemote(self._methname, *args, **kwargs)

class DBusRemoteWrapper(object): """ Wrapper for interfaces that makes everything a callRemote. """ def init(self, obj): self._obj = obj

def __getattr__(self, name):
    return DBusRemoteWrapperMethod(self._obj, name)

(setup reactor here, omitted for simplicity)

@defer.inlineCallbacksdef main(): conn = yield client.connect(reactor, 'session') obj = yield conn.getRemoteObject(DBUS_SERVICE, DBUS_PATH)

# Add callRemote wrapper
api = DBusRemoteWrapper(obj)

# Now we can execute methods directly on the DBus object as if it was
# a normal Python class...
yield api.my_method()
result = yield api.do_other_things('bar', 123)

There's a some limitations to my implementation as it stands:

  • Members are generated dynamically, and it does not check or expose the org.freedesktop.DBus.Introspection interface in order to be smart about this (and populate things like all.
  • This doesn't support wrapping org.freedesktop.DBus.Properties to Python properties.
  • This doesn't support handling other interfaces of the object.

There's one major limitation to my implementation as it stands is that it can't handle org.freedesktop.DBus.Properties interface wrapping to a Python property. This would require a little bit more glue in order to handle this.

With o.fd.DB.Properties implementation, it would allow even more interaction like a normal Python object:

api.MyProperty = 6while api.BottleCount > 0: api.BottleCount -= 1

This would make txdbus work a little more like python-dbus, and other RPC mechanisms like Python's XMLRPC module in the standard library ( ServiceProxy).

— Reply to this email directly or view it on GitHubhttps://github.com/cocagne/txdbus/issues/10 .

cocagne avatar Apr 28 '14 18:04 cocagne

@micolous, you may simplify your code using return functools.partial(self._obj.callRemote, name) instead of return DBusRemoteWrapperMethod(self._obj, name) and also I recommend you to check name.startswith('_') and raise appropriate error

socketpair avatar Nov 20 '14 21:11 socketpair