[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