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

John (J5) Palmieri johnp at redhat.com
Mon Apr 30 14:40:39 PDT 2007


Ah, ok.  Disregard my reply to the first patch.  This fixes that.  Looks
good.

On Mon, 2007-04-30 at 11:31 +0100, Simon McVittie wrote:
> 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
> _______________________________________________
> dbus mailing list
> dbus at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dbus
-- 
John (J5) Palmieri <johnp at redhat.com>



More information about the dbus mailing list