[PATCH 03/17] Move the client method-call machinery from dbus.proxies to dbus.connection._MethodCallMixin.

Simon McVittie simon.mcvittie at collabora.co.uk
Mon Apr 30 03:31:22 PDT 2007


This makes proxy methods much simpler, and allows the _BusDaemonMixin to bypass
the proxies module completely (since the signatures are already known, so
we don't need to introspect anything).

diff --git a/dbus/Makefile.am b/dbus/Makefile.am
index 53efa89..271c899 100644
--- a/dbus/Makefile.am
+++ b/dbus/Makefile.am
@@ -1,6 +1,7 @@
 pythondbusdir = $(pythondir)/dbus
 
 nobase_pythondbus_PYTHON = bus.py \
+			   connection.py \
 			   dbus_bindings.py \
 			   _dbus.py \
 			   _version.py \
diff --git a/dbus/_dbus.py b/dbus/_dbus.py
index 6812127..3eba4b2 100644
--- a/dbus/_dbus.py
+++ b/dbus/_dbus.py
@@ -36,9 +36,10 @@ import sys
 import weakref
 from traceback import print_exc
 
+from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE
 from dbus.bus import _BusDaemonMixin
-from dbus.proxies import ProxyObject, BUS_DAEMON_NAME, BUS_DAEMON_PATH, \
-        BUS_DAEMON_IFACE
+from dbus.connection import _MethodCallMixin
+from dbus.proxies import ProxyObject
 
 try:
     import thread
@@ -221,7 +222,7 @@ class SignalMatch(object):
                                         **self._args_match)
 
 
-class Bus(_dbus_bindings.Connection, _BusDaemonMixin):
+class Bus(_dbus_bindings.Connection, _MethodCallMixin, _BusDaemonMixin):
     """A connection to a DBus daemon.
 
     One of three possible standard buses, the SESSION, SYSTEM,
diff --git a/dbus/bus.py b/dbus/bus.py
index 20bc97f..053666d 100644
--- a/dbus/bus.py
+++ b/dbus/bus.py
@@ -18,21 +18,19 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from dbus import UInt32, UTF8String
-from dbus.proxies import BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE
+BUS_DAEMON_NAME = 'org.freedesktop.DBus'
+BUS_DAEMON_PATH = '/org/freedesktop/DBus'
+BUS_DAEMON_IFACE = BUS_DAEMON_NAME
 
 from _dbus_bindings import validate_interface_name, validate_member_name,\
                            validate_bus_name, validate_object_path,\
                            validate_error_name
 
-def _noop(*args, **kwargs):
-    """A universal no-op function"""
 
 class _BusDaemonMixin(object):
-    """This mixin must be mixed-in with something with a get_object method
-    (obviously, it's meant to be the dbus.Bus). It provides simple blocking
-    wrappers for various methods on the org.freedesktop.DBus bus-daemon
-    object, to reduce the amount of C code we need.
+    """This mixin provides simple blocking wrappers for various methods on
+    the org.freedesktop.DBus bus-daemon object, to reduce the amount of C
+    code we need.
     """
 
     def get_unix_user(self, bus_name):
@@ -44,9 +42,9 @@ class _BusDaemonMixin(object):
         :Returns: a `dbus.UInt32`
         """
         validate_bus_name(bus_name)
-        return self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).GetConnectionUnixUser(bus_name,
-                        dbus_interface=BUS_DAEMON_IFACE)
+        return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                                  BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
+                                  's', (bus_name,))
 
     def start_service_by_name(self, bus_name, flags=0):
         """Start a service which will implement the given bus name on this Bus.
@@ -65,10 +63,10 @@ class _BusDaemonMixin(object):
         :Raises DBusException: if the service could not be started.
         """
         validate_bus_name(bus_name)
-        ret = self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).StartServiceByName(bus_name, UInt32(flags),
-                        dbus_interface=BUS_DAEMON_IFACE)
-        return (True, ret)
+        return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                                         BUS_DAEMON_IFACE,
+                                         'StartServiceByName',
+                                         'su', (bus_name, flags)))
 
     # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
     # but this would not be backwards-compatible
@@ -91,9 +89,9 @@ class _BusDaemonMixin(object):
             returns an error.
         """
         validate_bus_name(name, allow_unique=False)
-        return self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).RequestName(name, UInt32(flags),
-                        dbus_interface=BUS_DAEMON_IFACE)
+        return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                                  BUS_DAEMON_IFACE, 'RequestName',
+                                  'su', (name, flags))
 
     def release_name(self, name):
         """Release a bus name.
@@ -108,9 +106,9 @@ class _BusDaemonMixin(object):
             returns an error.
         """
         validate_bus_name(name, allow_unique=False)
-        return self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).ReleaseName(name,
-                        dbus_interface=BUS_DAEMON_IFACE)
+        return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                                  BUS_DAEMON_IFACE, 'ReleaseName',
+                                  's', (name,))
 
     def name_has_owner(self, bus_name):
         """Return True iff the given bus name has an owner on this bus.
@@ -120,12 +118,9 @@ class _BusDaemonMixin(object):
                 The bus name to look up
         :Returns: a `bool`
         """
-        return bool(self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).NameHasOwner(bus_name,
-                        dbus_interface=BUS_DAEMON_IFACE))
-
-    # AddMatchString is not bound here
-    # RemoveMatchString either
+        return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                                       BUS_DAEMON_IFACE, 'NameHasOwner',
+                                       's', (bus_name,)))
 
     def add_match_string(self, rule):
         """Arrange for this application to receive messages on the bus that
@@ -136,12 +131,10 @@ class _BusDaemonMixin(object):
                 The match rule
         :Raises: `DBusException` on error.
         """
-        self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).AddMatch(rule,
-                        dbus_interface=BUS_DAEMON_IFACE)
+        self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                           BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
 
     # FIXME: add an async success/error handler capability?
-    # FIXME: tell the bus daemon not to bother sending us a reply
     # (and the same for remove_...)
     def add_match_string_non_blocking(self, rule):
         """Arrange for this application to receive messages on the bus that
@@ -154,11 +147,9 @@ class _BusDaemonMixin(object):
                 The match rule
         :Raises: `DBusException` on error.
         """
-        self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).AddMatch(rule,
-                        dbus_interface=BUS_DAEMON_IFACE,
-                        reply_handler=_noop,
-                        error_handler=_noop)
+        self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                        BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
+                        None, None)
 
     def remove_match_string(self, rule):
         """Arrange for this application to receive messages on the bus that
@@ -169,9 +160,8 @@ class _BusDaemonMixin(object):
                 The match rule
         :Raises: `DBusException` on error.
         """
-        self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).RemoveMatch(rule,
-                        dbus_interface=BUS_DAEMON_IFACE)
+        self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                           BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
 
     def remove_match_string_non_blocking(self, rule):
         """Arrange for this application to receive messages on the bus that
@@ -184,8 +174,6 @@ class _BusDaemonMixin(object):
                 The match rule
         :Raises: `DBusException` on error.
         """
-        self.get_object(BUS_DAEMON_NAME,
-                BUS_DAEMON_PATH).RemoveMatch(rule,
-                        dbus_interface=BUS_DAEMON_IFACE,
-                        reply_handler=_noop,
-                        error_handler=_noop)
+        self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+                        BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
+                        None, None)
diff --git a/dbus/connection.py b/dbus/connection.py
new file mode 100644
index 0000000..ef6f984
--- /dev/null
+++ b/dbus/connection.py
@@ -0,0 +1,139 @@
+"""Method-call mixin for use within dbus-python only.
+See `_MethodCallMixin`.
+"""
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Licensed under the Academic Free License version 2.1
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import logging
+
+from _dbus_bindings import Connection, ErrorMessage, \
+                           MethodCallMessage, MethodReturnMessage, \
+                           DBusException
+
+
+# This is special in libdbus - the bus daemon will kick us off if we try to
+# send any message to it :-/
+LOCAL_PATH = '/org/freedesktop/DBus/Local'
+
+
+_logger = logging.getLogger('dbus.methods')
+
+
+def _noop(*args, **kwargs):
+    pass
+
+
+class _MethodCallMixin(object):
+
+    def call_async(self, bus_name, object_path, dbus_interface, method,
+                   signature, args, reply_handler, error_handler,
+                   timeout=-1.0, utf8_strings=False, byte_arrays=False,
+                   require_main_loop=True):
+        """Call the given method, asynchronously.
+
+        If the reply_handler is None, successful replies will be ignored.
+        If the error_handler is None, failures will be ignored. If both
+        are None, the implementation may request that no reply is sent.
+
+        :Returns: The dbus.lowlevel.PendingCall.
+        """
+        if object_path == LOCAL_PATH:
+            raise DBusException('Methods may not be called on the reserved '
+                                'path %s' % LOCAL_PATH)
+        # no need to validate other args - MethodCallMessage ctor will do
+
+        get_args_opts = {'utf8_strings': utf8_strings,
+                         'byte_arrays': byte_arrays}
+
+        message = MethodCallMessage(destination=bus_name,
+                                    path=object_path,
+                                    interface=dbus_interface,
+                                    method=method)
+        # Add the arguments to the function
+        try:
+            message.append(signature=signature, *args)
+        except Exception, e:
+            _logger.error('Unable to set arguments %r according to '
+                          'signature %r: %s: %s',
+                          args, signature, e.__class__, e)
+            raise
+
+        if reply_handler is None and error_handler is None:
+            # we don't care what happens, so just send it
+            self.send_message(message)
+            return
+
+        if reply_handler is None:
+            reply_handler = _noop
+        if error_handler is None:
+            error_handler = _noop
+
+        def msg_reply_handler(message):
+            if isinstance(message, MethodReturnMessage):
+                reply_handler(*message.get_args_list(**get_args_opts))
+            elif isinstance(message, ErrorMessage):
+                args = message.get_args_list()
+                # FIXME: should we do something with the rest?
+                if len(args) > 0:
+                    error_handler(DBusException(args[0]))
+                else:
+                    error_handler(DBusException())
+            else:
+                error_handler(TypeError('Unexpected type for reply '
+                                        'message: %r' % message))
+        return self.send_message_with_reply(message, msg_reply_handler,
+                                        timeout/1000.0,
+                                        require_main_loop=require_main_loop)
+
+    def call_blocking(self, bus_name, object_path, dbus_interface, method,
+                      signature, args, timeout=-1.0, utf8_strings=False,
+                      byte_arrays=False):
+        """Call the given method, synchronously.
+        """
+        if object_path == LOCAL_PATH:
+            raise DBusException('Methods may not be called on the reserved '
+                                'path %s' % LOCAL_PATH)
+        # no need to validate other args - MethodCallMessage ctor will do
+
+        get_args_opts = {'utf8_strings': utf8_strings,
+                         'byte_arrays': byte_arrays}
+
+        message = MethodCallMessage(destination=bus_name,
+                                    path=object_path,
+                                    interface=dbus_interface,
+                                    method=method)
+        # Add the arguments to the function
+        try:
+            message.append(signature=signature, *args)
+        except Exception, e:
+            _logger.error('Unable to set arguments %r according to '
+                          'signature %r: %s: %s',
+                          args, signature, e.__class__, e)
+            raise
+
+        # make a blocking call
+        reply_message = self.send_message_with_reply_and_block(
+            message, timeout)
+        args_list = reply_message.get_args_list(**get_args_opts)
+        if len(args_list) == 0:
+            return None
+        elif len(args_list) == 1:
+            return args_list[0]
+        else:
+            return tuple(args_list)
diff --git a/dbus/proxies.py b/dbus/proxies.py
index a645f3e..027d99d 100644
--- a/dbus/proxies.py
+++ b/dbus/proxies.py
@@ -36,35 +36,10 @@ __docformat__ = 'restructuredtext'
 
 _logger = logging.getLogger('dbus.proxies')
 
+from _dbus_bindings import LOCAL_PATH, \
+                           BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE
 
-BUS_DAEMON_NAME = 'org.freedesktop.DBus'
-BUS_DAEMON_PATH = '/org/freedesktop/DBus'
-BUS_DAEMON_IFACE = BUS_DAEMON_NAME
-
-# This is special in libdbus - the bus daemon will kick us off if we try to
-# send any message to it :-/
-LOCAL_PATH = '/org/freedesktop/DBus/Local'
-
-
-class _ReplyHandler(object):
-    __slots__ = ('_on_error', '_on_reply', '_get_args_options')
-    def __init__(self, on_reply, on_error, **get_args_options):
-        self._on_error = on_error
-        self._on_reply = on_reply
-        self._get_args_options = get_args_options
-
-    def __call__(self, message):
-        if isinstance(message, _dbus_bindings.MethodReturnMessage):
-            self._on_reply(*message.get_args_list(**self._get_args_options))
-        elif isinstance(message, _dbus_bindings.ErrorMessage):
-            args = message.get_args_list()
-            if len(args) > 0:
-                self._on_error(DBusException(args[0]))
-            else:
-                self._on_error(DBusException())
-        else:
-            self._on_error(DBusException('Unexpected reply message type: %s'
-                                        % message))
+INTROSPECTABLE_IFACE = 'org.freedesktop.DBus.Introspectable'
 
 
 class _DeferredMethod:
@@ -88,6 +63,9 @@ class _DeferredMethod:
             self._block()
             return self._proxy_method(*args, **keywords)
 
+    def call_async(self, *args, **keywords):
+        self._append(self._proxy_method, args, keywords)
+
 
 class _ProxyMethod:
     """A proxy method.
@@ -116,78 +94,67 @@ class _ProxyMethod:
         self._dbus_interface = iface
 
     def __call__(self, *args, **keywords):
-        timeout = -1
-        if keywords.has_key('timeout'):
-            timeout = keywords['timeout']
-
-        reply_handler = None
-        if keywords.has_key('reply_handler'):
-            reply_handler = keywords['reply_handler']
+        reply_handler = keywords.pop('reply_handler', None)
+        error_handler = keywords.pop('error_handler', None)
+        ignore_reply = keywords.pop('ignore_reply', False)
 
-        error_handler = None
-        if keywords.has_key('error_handler'):
-            error_handler = keywords['error_handler']
-
-        ignore_reply = False
-        if keywords.has_key('ignore_reply'):
-            ignore_reply = keywords['ignore_reply']
-
-        get_args_options = {}
-        if keywords.has_key('utf8_strings'):
-            get_args_options['utf8_strings'] = keywords['utf8_strings']
-        if keywords.has_key('byte_arrays'):
-            get_args_options['byte_arrays'] = keywords['byte_arrays']
-
-        if not(reply_handler and error_handler):
-            if reply_handler:
+        if reply_handler is not None or error_handler is not None:
+            if reply_handler is None:
                 raise MissingErrorHandlerException()
-            elif error_handler:
+            elif error_handler is None:
                 raise MissingReplyHandlerException()
+            elif ignore_reply:
+                raise TypeError('ignore_reply and reply_handler cannot be '
+                                'used together')
 
-        dbus_interface = self._dbus_interface
-        if keywords.has_key('dbus_interface'):
-            dbus_interface = keywords['dbus_interface']
-
-        tmp_iface = ''
-        if dbus_interface:
-            tmp_iface = dbus_interface + '.'
+        dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
 
-        key = tmp_iface + self._method_name
+        if dbus_interface is None:
+            key = self._method_name
+        else:
+            key = dbus_interface + '.' + self._method_name
+        introspect_sig = self._proxy._introspect_method_map.get(key, None)
+
+        if ignore_reply or reply_handler is not None:
+            self._connection.call_async(self._named_service,
+                                        self._object_path,
+                                        dbus_interface,
+                                        self._method_name,
+                                        introspect_sig,
+                                        args,
+                                        reply_handler,
+                                        error_handler,
+                                        **keywords)
+        else:
+            return self._connection.call_blocking(self._named_service,
+                                                  self._object_path,
+                                                  dbus_interface,
+                                                  self._method_name,
+                                                  introspect_sig,
+                                                  args,
+                                                  **keywords)
 
-        introspect_sig = None
-        if self._proxy._introspect_method_map.has_key (key):
-            introspect_sig = self._proxy._introspect_method_map[key]
+    def call_async(self, *args, **keywords):
+        reply_handler = keywords.pop('reply_handler', None)
+        error_handler = keywords.pop('error_handler', None)
 
-        message = _dbus_bindings.MethodCallMessage(destination=None,
-                                                   path=self._object_path,
-                                                   interface=dbus_interface,
-                                                   method=self._method_name)
-        message.set_destination(self._named_service)
+        dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
 
-        # Add the arguments to the function
-        try:
-            message.append(signature=introspect_sig, *args)
-        except Exception, e:
-            _logger.error('Unable to set arguments %r according to '
-                          'introspected signature %r: %s: %s',
-                          args, introspect_sig, e.__class__, e)
-            raise
-
-        if ignore_reply:
-            self._connection.send_message(message)
-            return None
-        elif reply_handler:
-            self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1)
-            return None
+        if dbus_interface:
+            key = dbus_interface + '.' + self._method_name
         else:
-            reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
-            args_list = reply_message.get_args_list(**get_args_options)
-            if len(args_list) == 0:
-                return None
-            elif len(args_list) == 1:
-                return args_list[0]
-            else:
-                return tuple(args_list)
+            key = self._method_name
+        introspect_sig = self._proxy._introspect_method_map.get(key, None)
+
+        self._connection.call_async(self._named_service,
+                                    self._object_path,
+                                    dbus_interface,
+                                    self._method_name,
+                                    introspect_sig,
+                                    args,
+                                    reply_handler,
+                                    error_handler,
+                                    **keywords)
 
 
 class ProxyObject(object):
@@ -376,11 +343,13 @@ class ProxyObject(object):
                                       **keywords)
 
     def _Introspect(self):
-        message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
-        message.set_destination(self._named_service)
-
-        result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
-        return result
+        return self._bus.call_async(self._named_service,
+                                    self.__dbus_object_path__,
+                                    INTROSPECTABLE_IFACE, 'Introspect', '', (),
+                                    self._introspect_reply_handler,
+                                    self._introspect_error_handler,
+                                    utf8_strings=True,
+                                    require_main_loop=False)
 
     def _introspect_execute_queue(self):
         # FIXME: potential to flood the bus


More information about the dbus mailing list