[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