dbus/proxies.py: Give Interface some properties. Vastly simplify __getattr__ on Inter...
[dbus-python-phuang.git] / dbus / proxies.py
bloba645f3ec92a1d88e2df6c4acd2da9c35a89a011e
1 # Copyright (C) 2003, 2004, 2005, 2006, 2007 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006, 2007 Collabora Ltd. <http://www.collabora.co.uk/>
6 # Licensed under the Academic Free License version 2.1
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 import sys
23 import logging
25 try:
26 from threading import RLock
27 except ImportError:
28 from dummy_threading import RLock
30 import _dbus_bindings
31 from dbus._expat_introspect_parser import process_introspection_data
32 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
34 __docformat__ = 'restructuredtext'
37 _logger = logging.getLogger('dbus.proxies')
40 BUS_DAEMON_NAME = 'org.freedesktop.DBus'
41 BUS_DAEMON_PATH = '/org/freedesktop/DBus'
42 BUS_DAEMON_IFACE = BUS_DAEMON_NAME
44 # This is special in libdbus - the bus daemon will kick us off if we try to
45 # send any message to it :-/
46 LOCAL_PATH = '/org/freedesktop/DBus/Local'
49 class _ReplyHandler(object):
50 __slots__ = ('_on_error', '_on_reply', '_get_args_options')
51 def __init__(self, on_reply, on_error, **get_args_options):
52 self._on_error = on_error
53 self._on_reply = on_reply
54 self._get_args_options = get_args_options
56 def __call__(self, message):
57 if isinstance(message, _dbus_bindings.MethodReturnMessage):
58 self._on_reply(*message.get_args_list(**self._get_args_options))
59 elif isinstance(message, _dbus_bindings.ErrorMessage):
60 args = message.get_args_list()
61 if len(args) > 0:
62 self._on_error(DBusException(args[0]))
63 else:
64 self._on_error(DBusException())
65 else:
66 self._on_error(DBusException('Unexpected reply message type: %s'
67 % message))
70 class _DeferredMethod:
71 """A proxy method which will only get called once we have its
72 introspection reply.
73 """
74 def __init__(self, proxy_method, append, block):
75 self._proxy_method = proxy_method
76 # the test suite relies on the existence of this property
77 self._method_name = proxy_method._method_name
78 self._append = append
79 self._block = block
81 def __call__(self, *args, **keywords):
82 if keywords.has_key('reply_handler'):
83 # defer the async call til introspection finishes
84 self._append(self._proxy_method, args, keywords)
85 return None
86 else:
87 # we're being synchronous, so block
88 self._block()
89 return self._proxy_method(*args, **keywords)
92 class _ProxyMethod:
93 """A proxy method.
95 Typically a member of a ProxyObject. Calls to the
96 method produce messages that travel over the Bus and are routed
97 to a specific named Service.
98 """
99 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
100 if object_path == LOCAL_PATH:
101 raise DBusException('Methods may not be called on the reserved '
102 'path %s' % LOCAL_PATH)
104 # trust that the proxy, and the properties it had, are OK
105 self._proxy = proxy
106 self._connection = connection
107 self._named_service = named_service
108 self._object_path = object_path
109 # fail early if the method name is bad
110 _dbus_bindings.validate_member_name(method_name)
111 # the test suite relies on the existence of this property
112 self._method_name = method_name
113 # fail early if the interface name is bad
114 if iface is not None:
115 _dbus_bindings.validate_interface_name(iface)
116 self._dbus_interface = iface
118 def __call__(self, *args, **keywords):
119 timeout = -1
120 if keywords.has_key('timeout'):
121 timeout = keywords['timeout']
123 reply_handler = None
124 if keywords.has_key('reply_handler'):
125 reply_handler = keywords['reply_handler']
127 error_handler = None
128 if keywords.has_key('error_handler'):
129 error_handler = keywords['error_handler']
131 ignore_reply = False
132 if keywords.has_key('ignore_reply'):
133 ignore_reply = keywords['ignore_reply']
135 get_args_options = {}
136 if keywords.has_key('utf8_strings'):
137 get_args_options['utf8_strings'] = keywords['utf8_strings']
138 if keywords.has_key('byte_arrays'):
139 get_args_options['byte_arrays'] = keywords['byte_arrays']
141 if not(reply_handler and error_handler):
142 if reply_handler:
143 raise MissingErrorHandlerException()
144 elif error_handler:
145 raise MissingReplyHandlerException()
147 dbus_interface = self._dbus_interface
148 if keywords.has_key('dbus_interface'):
149 dbus_interface = keywords['dbus_interface']
151 tmp_iface = ''
152 if dbus_interface:
153 tmp_iface = dbus_interface + '.'
155 key = tmp_iface + self._method_name
157 introspect_sig = None
158 if self._proxy._introspect_method_map.has_key (key):
159 introspect_sig = self._proxy._introspect_method_map[key]
161 message = _dbus_bindings.MethodCallMessage(destination=None,
162 path=self._object_path,
163 interface=dbus_interface,
164 method=self._method_name)
165 message.set_destination(self._named_service)
167 # Add the arguments to the function
168 try:
169 message.append(signature=introspect_sig, *args)
170 except Exception, e:
171 _logger.error('Unable to set arguments %r according to '
172 'introspected signature %r: %s: %s',
173 args, introspect_sig, e.__class__, e)
174 raise
176 if ignore_reply:
177 self._connection.send_message(message)
178 return None
179 elif reply_handler:
180 self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1)
181 return None
182 else:
183 reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
184 args_list = reply_message.get_args_list(**get_args_options)
185 if len(args_list) == 0:
186 return None
187 elif len(args_list) == 1:
188 return args_list[0]
189 else:
190 return tuple(args_list)
193 class ProxyObject(object):
194 """A proxy to the remote Object.
196 A ProxyObject is provided by the Bus. ProxyObjects
197 have member functions, and can be called like normal Python objects.
199 ProxyMethodClass = _ProxyMethod
200 DeferredMethodClass = _DeferredMethod
202 INTROSPECT_STATE_DONT_INTROSPECT = 0
203 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
204 INTROSPECT_STATE_INTROSPECT_DONE = 2
206 def __init__(self, bus, named_service, object_path, introspect=True,
207 follow_name_owner_changes=False):
208 """Initialize the proxy object.
210 :Parameters:
211 `bus` : `dbus.Bus`
212 The bus on which to find this object
213 `named_service` : str
214 A bus name for the endpoint owning the object (need not
215 actually be a service name)
216 `object_path` : str
217 The object path at which the endpoint exports the object
218 `introspect` : bool
219 If true (default), attempt to introspect the remote
220 object to find out supported methods and their signatures
221 `follow_name_owner_changes` : bool
222 If true (default is false) and the `named_service` is a
223 well-known name, follow ownership changes for that name
225 if follow_name_owner_changes:
226 # we don't get the signals unless the Bus has a main loop
227 # XXX: using Bus internals
228 bus._require_main_loop()
230 self._bus = bus
232 if named_service is not None:
233 _dbus_bindings.validate_bus_name(named_service)
234 self._named_service = self._requested_bus_name = named_service
236 _dbus_bindings.validate_object_path(object_path)
237 self.__dbus_object_path__ = object_path
239 # XXX: assumes it's a bus daemon
240 if (named_service is not None and named_service[:1] != ':'
241 and named_service != BUS_DAEMON_NAME
242 and not follow_name_owner_changes):
243 bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
244 try:
245 self._named_service = bus_object.GetNameOwner(named_service,
246 dbus_interface=BUS_DAEMON_IFACE)
247 except DBusException, e:
248 # FIXME: detect whether it's NameHasNoOwner, but properly
249 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
250 # raise
251 # it might not exist: try to start it
252 bus_object.StartServiceByName(named_service,
253 _dbus_bindings.UInt32(0))
254 self._named_service = bus_object.GetNameOwner(named_service,
255 dbus_interface=BUS_DAEMON_IFACE)
257 #PendingCall object for Introspect call
258 self._pending_introspect = None
259 #queue of async calls waiting on the Introspect to return
260 self._pending_introspect_queue = []
261 #dictionary mapping method names to their input signatures
262 self._introspect_method_map = {}
264 # must be a recursive lock because block() is called while locked,
265 # and calls the callback which re-takes the lock
266 self._introspect_lock = RLock()
268 if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
269 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
270 else:
271 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
273 self._pending_introspect = self._Introspect()
275 bus_name = property(lambda self: self._named_service, None, None,
276 """The bus name to which this proxy is bound. (Read-only,
277 may change.)
279 If the proxy was instantiated using a unique name, this property
280 is that unique name.
282 If the proxy was instantiated with a well-known name and with
283 `follow_name_owner_changes` set false (the default), this
284 property is the unique name of the connection that owned that
285 well-known name when the proxy was instantiated, which might
286 not actually own the requested well-known name any more.
288 If the proxy was instantiated with a well-known name and with
289 `follow_name_owner_changes` set true, this property is that
290 well-known name.
291 """)
293 requested_bus_name = property(lambda self: self._requested_bus_name,
294 None, None,
295 """The bus name which was requested when this proxy was
296 instantiated.
297 """)
299 object_path = property(lambda self: self.__dbus_object_path__,
300 None, None,
301 """The object-path of this proxy.""")
303 # XXX: We don't currently support this because it's the signal receiver
304 # that's responsible for tracking name owner changes, but it
305 # seems a natural thing to add in future.
306 #unique_bus_name = property(lambda self: something, None, None,
307 # """The unique name of the connection to which this proxy is
308 # currently bound. (Read-only, may change.)
309 # """)
311 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
312 """Arrange for the given function to be called when the given signal
313 is received.
315 :Parameters:
316 `signal_name` : str
317 The name of the signal
318 `handler_function` : callable
319 A function to be called when the signal is emitted by
320 the remote object. Its positional arguments will be the
321 arguments of the signal; optionally, it may be given
322 keyword arguments as described below.
323 `dbus_interface` : str
324 Optional interface with which to qualify the signal name.
325 If None (the default) the handler will be called whenever a
326 signal of the given member name is received, whatever
327 its interface.
328 :Keywords:
329 `utf8_strings` : bool
330 If True, the handler function will receive any string
331 arguments as dbus.UTF8String objects (a subclass of str
332 guaranteed to be UTF-8). If False (default) it will receive
333 any string arguments as dbus.String objects (a subclass of
334 unicode).
335 `byte_arrays` : bool
336 If True, the handler function will receive any byte-array
337 arguments as dbus.ByteArray objects (a subclass of str).
338 If False (default) it will receive any byte-array
339 arguments as a dbus.Array of dbus.Byte (subclasses of:
340 a list of ints).
341 `sender_keyword` : str
342 If not None (the default), the handler function will receive
343 the unique name of the sending endpoint as a keyword
344 argument with this name
345 `destination_keyword` : str
346 If not None (the default), the handler function will receive
347 the bus name of the destination (or None if the signal is a
348 broadcast, as is usual) as a keyword argument with this name.
349 `interface_keyword` : str
350 If not None (the default), the handler function will receive
351 the signal interface as a keyword argument with this name.
352 `member_keyword` : str
353 If not None (the default), the handler function will receive
354 the signal name as a keyword argument with this name.
355 `path_keyword` : str
356 If not None (the default), the handler function will receive
357 the object-path of the sending object as a keyword argument
358 with this name
359 `message_keyword` : str
360 If not None (the default), the handler function will receive
361 the `dbus.lowlevel.SignalMessage` as a keyword argument with
362 this name.
363 `arg...` : unicode or UTF-8 str
364 If there are additional keyword parameters of the form
365 ``arg``\ *n*, match only signals where the *n*\ th argument
366 is the value given for that keyword parameter. As of this time
367 only string arguments can be matched (in particular,
368 object paths and signatures can't).
370 return \
371 self._bus.add_signal_receiver(handler_function,
372 signal_name=signal_name,
373 dbus_interface=dbus_interface,
374 named_service=self._named_service,
375 path=self.__dbus_object_path__,
376 **keywords)
378 def _Introspect(self):
379 message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
380 message.set_destination(self._named_service)
382 result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
383 return result
385 def _introspect_execute_queue(self):
386 # FIXME: potential to flood the bus
387 # We should make sure mainloops all have idle handlers
388 # and do one message per idle
389 for (proxy_method, args, keywords) in self._pending_introspect_queue:
390 proxy_method(*args, **keywords)
392 def _introspect_reply_handler(self, data):
393 self._introspect_lock.acquire()
394 try:
395 try:
396 self._introspect_method_map = process_introspection_data(data)
397 except IntrospectionParserException, e:
398 self._introspect_error_handler(e)
399 return
401 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
402 self._pending_introspect = None
403 self._introspect_execute_queue()
404 finally:
405 self._introspect_lock.release()
407 def _introspect_error_handler(self, error):
408 self._introspect_lock.acquire()
409 try:
410 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
411 self._pending_introspect = None
412 self._introspect_execute_queue()
413 sys.stderr.write("Introspect error: " + str(error) + "\n")
414 finally:
415 self._introspect_lock.release()
417 def _introspect_block(self):
418 self._introspect_lock.acquire()
419 try:
420 if self._pending_introspect is not None:
421 self._pending_introspect.block()
422 # else someone still has a _DeferredMethod from before we
423 # finished introspection: no need to do anything special any more
424 finally:
425 self._introspect_lock.release()
427 def _introspect_add_to_queue(self, callback, args, kwargs):
428 self._introspect_lock.acquire()
429 try:
430 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
431 self._pending_introspect_queue.append((callback, args, kwargs))
432 else:
433 # someone still has a _DeferredMethod from before we
434 # finished introspection
435 callback(*args, **kwargs)
436 finally:
437 self._introspect_lock.release()
439 def __getattr__(self, member):
440 if member.startswith('__') and member.endswith('__'):
441 raise AttributeError(member)
442 else:
443 return self.get_dbus_method(member)
445 def get_dbus_method(self, member, dbus_interface=None):
446 """Return a proxy method representing the given D-Bus method. The
447 returned proxy method can be called in the usual way. For instance, ::
449 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
451 is equivalent to::
453 proxy.Foo(123, dbus_interface='com.example.Bar')
455 or even::
457 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
459 However, using `get_dbus_method` is the only way to call D-Bus
460 methods with certain awkward names - if the author of a service
461 implements a method called ``connect_to_signal`` or even
462 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
464 For services which follow the D-Bus convention of CamelCaseMethodNames
465 this won't be a problem.
468 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
469 self._named_service,
470 self.__dbus_object_path__, member,
471 dbus_interface)
473 # this can be done without taking the lock - the worst that can
474 # happen is that we accidentally return a _DeferredMethod just after
475 # finishing introspection, in which case _introspect_add_to_queue and
476 # _introspect_block will do the right thing anyway
477 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
478 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
479 self._introspect_block)
481 return ret
483 def __repr__(self):
484 return '<ProxyObject wrapping %s %s %s at %#x>'%(
485 self._bus, self._named_service, self.__dbus_object_path__, id(self))
486 __str__ = __repr__
489 class Interface(object):
490 """An interface into a remote object.
492 An Interface can be used to wrap ProxyObjects
493 so that calls can be routed to their correct
494 D-Bus interface.
497 def __init__(self, object, dbus_interface):
498 """Construct a proxy for the given interface on the given object.
500 :Parameters:
501 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
502 The remote object or another of its interfaces
503 `dbus_interface` : str
504 An interface the `object` implements
506 if isinstance(object, Interface):
507 self._obj = object.proxy_object
508 else:
509 self._obj = object
510 self._dbus_interface = dbus_interface
512 object_path = property (lambda self: self._obj.object_path, None, None,
513 "The D-Bus object path of the underlying object")
514 __dbus_object_path__ = object_path
515 bus_name = property (lambda self: self._obj.bus_name, None, None,
516 "The bus name to which the underlying proxy object "
517 "is bound")
518 requested_bus_name = property (lambda self: self._obj.requested_bus_name,
519 None, None,
520 "The bus name which was requested when the "
521 "underlying object was created")
522 proxy_object = property (lambda self: self._obj, None, None,
523 """The underlying proxy object""")
524 dbus_interface = property (lambda self: self._dbus_interface, None, None,
525 """The D-Bus interface represented""")
527 def connect_to_signal(self, signal_name, handler_function,
528 dbus_interface=None, **keywords):
529 """Arrange for a function to be called when the given signal is
530 emitted.
532 The parameters and keyword arguments are the same as for
533 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
534 `dbus_interface` is None (the default), the D-Bus interface that
535 was passed to the `Interface` constructor is used.
537 if not dbus_interface:
538 dbus_interface = self._dbus_interface
540 return self._obj.connect_to_signal(signal_name, handler_function,
541 dbus_interface, **keywords)
543 def __getattr__(self, member):
544 if member.startswith('__') and member.endswith('__'):
545 raise AttributeError(member)
546 else:
547 return self._obj.get_dbus_method(member, self._dbus_interface)
549 def get_dbus_method(self, member, dbus_interface=None):
550 """Return a proxy method representing the given D-Bus method.
552 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
553 except that if `dbus_interface` is None (the default),
554 the D-Bus interface that was passed to the `Interface` constructor
555 is used.
557 if dbus_interface is None:
558 dbus_interface = self._dbus_interface
559 return self._obj.get_dbus_method(member, dbus_interface)
561 def __repr__(self):
562 return '<Interface %r implementing %r at %#x>'%(
563 self._obj, self._dbus_interface, id(self))
564 __str__ = __repr__