dbus/_dbus.py: in SignalMatch, use get_name_owner()
[dbus-python-phuang.git] / dbus / _dbus.py
blobfc070ae9191f31742f723d07f7d201c14518079d
1 """Implementation for dbus.Bus. Not to be imported directly."""
3 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
4 # Copyright (C) 2003 David Zeuthen
5 # Copyright (C) 2004 Rob Taylor
6 # Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
8 # Licensed under the Academic Free License version 2.1
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 from __future__ import generators
26 __all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
27 __docformat__ = 'reStructuredText'
29 import os
30 import logging
31 import sys
32 import weakref
33 from traceback import print_exc
35 from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH,\
36 BUS_DAEMON_IFACE, DBusException, UTF8String,\
37 validate_member_name, validate_interface_name,\
38 validate_bus_name, validate_object_path,\
39 BUS_SESSION, BUS_SYSTEM, BUS_STARTER,\
40 Connection as _Connection,\
41 DBUS_START_REPLY_SUCCESS, \
42 DBUS_START_REPLY_ALREADY_RUNNING, \
43 SignalMessage,\
44 HANDLER_RESULT_NOT_YET_HANDLED,\
45 HANDLER_RESULT_HANDLED
46 from dbus.bus import _BusDaemonMixin
47 from dbus.connection import _MethodCallMixin
48 from dbus.proxies import ProxyObject
50 try:
51 import thread
52 except ImportError:
53 import dummy_thread as thread
55 logger = logging.getLogger('dbus._dbus')
57 _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
58 "interface='%s',member='NameOwnerChanged',"
59 "path='%s',arg0='%%s'"
60 % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
61 BUS_DAEMON_PATH))
62 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
63 messages"""
66 class SignalMatch(object):
67 __slots__ = ('sender_unique', '_member', '_interface', '_sender',
68 '_path', '_handler', '_args_match', '_rule',
69 '_utf8_strings', '_byte_arrays', '_conn_weakref',
70 '_destination_keyword', '_interface_keyword',
71 '_message_keyword', '_member_keyword',
72 '_sender_keyword', '_path_keyword', '_int_args_match')
74 def __init__(self, conn, sender, object_path, dbus_interface,
75 member, handler, utf8_strings=False, byte_arrays=False,
76 sender_keyword=None, path_keyword=None,
77 interface_keyword=None, member_keyword=None,
78 message_keyword=None, destination_keyword=None,
79 **kwargs):
80 if member is not None:
81 validate_member_name(member)
82 if dbus_interface is not None:
83 validate_interface_name(dbus_interface)
84 if sender is not None:
85 validate_bus_name(sender)
86 if object_path is not None:
87 validate_object_path(object_path)
89 self._conn_weakref = weakref.ref(conn)
90 self._sender = sender
91 self._interface = dbus_interface
92 self._member = member
93 self._path = object_path
94 self._handler = handler
95 if (sender is not None and sender[:1] != ':'
96 and sender != BUS_DAEMON_NAME):
97 self.sender_unique = conn.get_name_owner(sender)
98 else:
99 self.sender_unique = sender
100 self._utf8_strings = utf8_strings
101 self._byte_arrays = byte_arrays
102 self._sender_keyword = sender_keyword
103 self._path_keyword = path_keyword
104 self._member_keyword = member_keyword
105 self._interface_keyword = interface_keyword
106 self._message_keyword = message_keyword
107 self._destination_keyword = destination_keyword
109 self._args_match = kwargs
110 if not kwargs:
111 self._int_args_match = None
112 else:
113 self._int_args_match = {}
114 for kwarg in kwargs:
115 if not kwarg.startswith('arg'):
116 raise TypeError('SignalMatch: unknown keyword argument %s'
117 % kwarg)
118 try:
119 index = int(kwarg[3:])
120 except ValueError:
121 raise TypeError('SignalMatch: unknown keyword argument %s'
122 % kwarg)
123 if index < 0 or index > 63:
124 raise TypeError('SignalMatch: arg match index must be in '
125 'range(64), not %d' % index)
126 self._int_args_match[index] = kwargs[kwarg]
128 # we're always going to have to calculate the match rule for
129 # the Bus's benefit, so this constructor might as well do the work
130 rule = ["type='signal'"]
131 if self._sender is not None:
132 rule.append("sender='%s'" % self._sender)
133 if self._path is not None:
134 rule.append("path='%s'" % self._path)
135 if self._interface is not None:
136 rule.append("interface='%s'" % self._interface)
137 if self._member is not None:
138 rule.append("member='%s'" % self._member)
139 for kwarg, value in kwargs.iteritems():
140 rule.append("%s='%s'" % (kwarg, value))
142 self._rule = ','.join(rule)
144 def __str__(self):
145 return self._rule
147 def __repr__(self):
148 return ('<%s at %x "%s" on conn %r>'
149 % (self.__class__, id(self), self._rule, self._conn_weakref()))
151 def matches_removal_spec(self, sender, object_path,
152 dbus_interface, member, handler, **kwargs):
153 if handler not in (None, self._handler):
154 return False
155 if sender != self._sender:
156 return False
157 if object_path != self._path:
158 return False
159 if dbus_interface != self._interface:
160 return False
161 if member != self._member:
162 return False
163 if kwargs != self._args_match:
164 return False
165 return True
167 def maybe_handle_message(self, message):
168 args = None
170 # these haven't been checked yet by the match tree
171 if self.sender_unique not in (None, message.get_sender()):
172 return False
173 if self._int_args_match is not None:
174 # extracting args with utf8_strings and byte_arrays is less work
175 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
176 for index, value in self._int_args_match.iteritems():
177 if (index >= len(args)
178 or not isinstance(args[index], UTF8String)
179 or args[index] != value):
180 return False
182 # these have likely already been checked by the match tree
183 if self._member not in (None, message.get_member()):
184 return False
185 if self._interface not in (None, message.get_interface()):
186 return False
187 if self._path not in (None, message.get_path()):
188 return False
190 try:
191 # minor optimization: if we already extracted the args with the
192 # right calling convention to do the args match, don't bother
193 # doing so again
194 if args is None or not self._utf8_strings or not self._byte_arrays:
195 args = message.get_args_list(utf8_strings=self._utf8_strings,
196 byte_arrays=self._byte_arrays)
197 kwargs = {}
198 if self._sender_keyword is not None:
199 kwargs[self._sender_keyword] = message.get_sender()
200 if self._destination_keyword is not None:
201 kwargs[self._destination_keyword] = message.get_destination()
202 if self._path_keyword is not None:
203 kwargs[self._path_keyword] = message.get_path()
204 if self._member_keyword is not None:
205 kwargs[self._member_keyword] = message.get_member()
206 if self._interface_keyword is not None:
207 kwargs[self._interface_keyword] = message.get_interface()
208 if self._message_keyword is not None:
209 kwargs[self._message_keyword] = message
210 self._handler(*args, **kwargs)
211 except:
212 # FIXME: need to decide whether dbus-python uses logging, or
213 # stderr, or what, and make it consistent
214 sys.stderr.write('Exception in handler for D-Bus signal:\n')
215 print_exc()
217 return True
219 def remove(self):
220 #logger.debug('%r: removing', self)
221 conn = self._conn_weakref()
222 # do nothing if the connection has already vanished
223 if conn is not None:
224 #logger.debug('%r: removing from connection %r', self, conn)
225 conn.remove_signal_receiver(self, self._member,
226 self._interface, self._sender,
227 self._path,
228 **self._args_match)
231 class Bus(_Connection, _MethodCallMixin, _BusDaemonMixin):
232 """A connection to a DBus daemon.
234 One of three possible standard buses, the SESSION, SYSTEM,
235 or STARTER bus
238 TYPE_SESSION = BUS_SESSION
239 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
241 TYPE_SYSTEM = BUS_SYSTEM
242 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
244 TYPE_STARTER = BUS_STARTER
245 """Represents the bus that started this service by activation (same as
246 the global dbus.BUS_STARTER)"""
248 ProxyObjectClass = ProxyObject
250 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
251 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
253 _shared_instances = {}
255 def __new__(cls, bus_type=TYPE_SESSION, private=False, mainloop=None):
256 """Constructor, returning an existing instance where appropriate.
258 The returned instance is actually always an instance of `SessionBus`,
259 `SystemBus` or `StarterBus`.
261 :Parameters:
262 `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
263 Connect to the appropriate bus
264 `private` : bool
265 If true, never return an existing shared instance, but instead
266 return a private connection
267 `mainloop` : dbus.mainloop.NativeMainLoop
268 The main loop to use. The default is to use the default
269 main loop if one has been set up, or raise an exception
270 if none has been.
271 :ToDo:
272 - There is currently no way to connect this class to a custom
273 address.
274 - Some of this functionality should be available on
275 peer-to-peer D-Bus connections too.
276 :Changed: in dbus-python 0.80:
277 converted from a wrapper around a Connection to a Connection
278 subclass.
280 if (not private and bus_type in cls._shared_instances):
281 return cls._shared_instances[bus_type]
283 # this is a bit odd, but we create instances of the subtypes
284 # so we can return the shared instances if someone tries to
285 # construct one of them (otherwise we'd eg try and return an
286 # instance of Bus from __new__ in SessionBus). why are there
287 # three ways to construct this class? we just don't know.
288 if bus_type == BUS_SESSION:
289 subclass = SessionBus
290 elif bus_type == BUS_SYSTEM:
291 subclass = SystemBus
292 elif bus_type == BUS_STARTER:
293 subclass = StarterBus
294 else:
295 raise ValueError('invalid bus_type %s' % bus_type)
297 bus = subclass._new_for_bus(bus_type, mainloop=mainloop)
299 bus._bus_type = bus_type
300 # _bus_names is used by dbus.service.BusName!
301 bus._bus_names = weakref.WeakValueDictionary()
303 bus._signal_recipients_by_object_path = {}
304 """Map from object path to dict mapping dbus_interface to dict
305 mapping member to list of SignalMatch objects."""
307 bus._signal_sender_matches = {}
308 """Map from sender well-known name to list of match rules for all
309 signal handlers that match on sender well-known name."""
311 bus._signals_lock = thread.allocate_lock()
312 """Lock used to protect signal data structures if doing two
313 removals at the same time (everything else is atomic, thanks to
314 the GIL)"""
316 bus.add_message_filter(bus.__class__._signal_func)
318 if not private:
319 cls._shared_instances[bus_type] = bus
321 return bus
323 def close(self):
324 t = self._bus_type
325 if self.__class__._shared_instances[t] is self:
326 del self.__class__._shared_instances[t]
327 _Connection.close(self)
329 def get_connection(self):
330 """(Deprecated - in new code, just use self)
332 Return self, for backwards compatibility with earlier dbus-python
333 versions where Bus was not a subclass of Connection.
335 return self
336 _connection = property(get_connection, None, None,
337 """self._connection == self, for backwards
338 compatibility with earlier dbus-python versions
339 where Bus was not a subclass of Connection.""")
341 def get_session(private=False):
342 """Static method that returns a connection to the session bus.
344 :Parameters:
345 `private` : bool
346 If true, do not return a shared connection.
348 return SessionBus(private=private)
350 get_session = staticmethod(get_session)
352 def get_system(private=False):
353 """Static method that returns a connection to the system bus.
355 :Parameters:
356 `private` : bool
357 If true, do not return a shared connection.
359 return SystemBus(private=private)
361 get_system = staticmethod(get_system)
364 def get_starter(private=False):
365 """Static method that returns a connection to the starter bus.
367 :Parameters:
368 `private` : bool
369 If true, do not return a shared connection.
371 return StarterBus(private=private)
373 get_starter = staticmethod(get_starter)
375 def get_object(self, named_service, object_path, introspect=True,
376 follow_name_owner_changes=False):
377 """Return a local proxy for the given remote object.
379 Method calls on the proxy are translated into method calls on the
380 remote object.
382 :Parameters:
383 `named_service` : str
384 A bus name (either the unique name or a well-known name)
385 of the application owning the object
386 `object_path` : str
387 The object path of the desired object
388 `introspect` : bool
389 If true (default), attempt to introspect the remote
390 object to find out supported methods and their signatures
391 `follow_name_owner_changes` : bool
392 If the object path is a well-known name and this parameter
393 is false (default), resolve the well-known name to the unique
394 name of its current owner and bind to that instead; if the
395 ownership of the well-known name changes in future,
396 keep communicating with the original owner.
397 This is necessary if the D-Bus API used is stateful.
399 If the object path is a well-known name and this parameter
400 is true, whenever the well-known name changes ownership in
401 future, bind to the new owner, if any.
403 If the given object path is a unique name, this parameter
404 has no effect.
406 :Returns: a `dbus.proxies.ProxyObject`
407 :Raises `DBusException`: if resolving the well-known name to a
408 unique name fails
410 if follow_name_owner_changes:
411 self._require_main_loop() # we don't get the signals otherwise
412 return self.ProxyObjectClass(self, named_service, object_path,
413 introspect=introspect,
414 follow_name_owner_changes=follow_name_owner_changes)
416 def add_signal_receiver(self, handler_function,
417 signal_name=None,
418 dbus_interface=None,
419 named_service=None,
420 path=None,
421 **keywords):
422 """Arrange for the given function to be called when a signal matching
423 the parameters is received.
425 :Parameters:
426 `handler_function` : callable
427 The function to be called. Its positional arguments will
428 be the arguments of the signal. By default it will receive
429 no keyword arguments, but see the description of
430 the optional keyword arguments below.
431 `signal_name` : str
432 The signal name; None (the default) matches all names
433 `dbus_interface` : str
434 The D-Bus interface name with which to qualify the signal;
435 None (the default) matches all interface names
436 `named_service` : str
437 A bus name for the sender, which will be resolved to a
438 unique name if it is not already; None (the default) matches
439 any sender
440 `path` : str
441 The object path of the object which must have emitted the
442 signal; None (the default) matches any object path
443 :Keywords:
444 `utf8_strings` : bool
445 If True, the handler function will receive any string
446 arguments as dbus.UTF8String objects (a subclass of str
447 guaranteed to be UTF-8). If False (default) it will receive
448 any string arguments as dbus.String objects (a subclass of
449 unicode).
450 `byte_arrays` : bool
451 If True, the handler function will receive any byte-array
452 arguments as dbus.ByteArray objects (a subclass of str).
453 If False (default) it will receive any byte-array
454 arguments as a dbus.Array of dbus.Byte (subclasses of:
455 a list of ints).
456 `sender_keyword` : str
457 If not None (the default), the handler function will receive
458 the unique name of the sending endpoint as a keyword
459 argument with this name.
460 `destination_keyword` : str
461 If not None (the default), the handler function will receive
462 the bus name of the destination (or None if the signal is a
463 broadcast, as is usual) as a keyword argument with this name.
464 `interface_keyword` : str
465 If not None (the default), the handler function will receive
466 the signal interface as a keyword argument with this name.
467 `member_keyword` : str
468 If not None (the default), the handler function will receive
469 the signal name as a keyword argument with this name.
470 `path_keyword` : str
471 If not None (the default), the handler function will receive
472 the object-path of the sending object as a keyword argument
473 with this name.
474 `message_keyword` : str
475 If not None (the default), the handler function will receive
476 the `dbus.lowlevel.SignalMessage` as a keyword argument with
477 this name.
478 `arg...` : unicode or UTF-8 str
479 If there are additional keyword parameters of the form
480 ``arg``\ *n*, match only signals where the *n*\ th argument
481 is the value given for that keyword parameter. As of this
482 time only string arguments can be matched (in particular,
483 object paths and signatures can't).
485 self._require_main_loop()
487 match = SignalMatch(self, named_service, path, dbus_interface,
488 signal_name, handler_function, **keywords)
489 by_interface = self._signal_recipients_by_object_path.setdefault(path,
491 by_member = by_interface.setdefault(dbus_interface, {})
492 matches = by_member.setdefault(signal_name, [])
493 # The bus daemon is special - its unique-name is org.freedesktop.DBus
494 # rather than starting with :
495 if (named_service is not None and named_service[:1] != ':'
496 and named_service != BUS_DAEMON_NAME):
497 notification = self._signal_sender_matches.setdefault(named_service,
499 if not notification:
500 self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
501 notification.append(match)
502 # make sure nobody is currently manipulating the list
503 self._signals_lock.acquire()
504 try:
505 matches.append(match)
506 finally:
507 self._signals_lock.release()
508 self.add_match_string(str(match))
509 return match
511 def _iter_easy_matches(self, path, dbus_interface, member):
512 if path is not None:
513 path_keys = (None, path)
514 else:
515 path_keys = (None,)
516 if dbus_interface is not None:
517 interface_keys = (None, dbus_interface)
518 else:
519 interface_keys = (None,)
520 if member is not None:
521 member_keys = (None, member)
522 else:
523 member_keys = (None,)
525 for path in path_keys:
526 by_interface = self._signal_recipients_by_object_path.get(path,
527 None)
528 if by_interface is None:
529 continue
530 for dbus_interface in interface_keys:
531 by_member = by_interface.get(dbus_interface, None)
532 if by_member is None:
533 continue
534 for member in member_keys:
535 matches = by_member.get(member, None)
536 if matches is None:
537 continue
538 for m in matches:
539 yield m
541 def _remove_name_owner_changed_for_match(self, named_service, match):
542 # The signals lock must be held.
543 notification = self._signal_sender_matches.get(named_service, False)
544 if notification:
545 try:
546 notification.remove(match)
547 except LookupError:
548 pass
549 if not notification:
550 self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
551 % named_service)
553 def remove_signal_receiver(self, handler_or_match,
554 signal_name=None,
555 dbus_interface=None,
556 named_service=None,
557 path=None,
558 **keywords):
559 #logger.debug('%r: removing signal receiver %r: member=%s, '
560 #'iface=%s, sender=%s, path=%s, kwargs=%r',
561 #self, handler_or_match, signal_name,
562 #dbus_interface, named_service, path, keywords)
563 #logger.debug('%r', self._signal_recipients_by_object_path)
564 by_interface = self._signal_recipients_by_object_path.get(path, None)
565 if by_interface is None:
566 return
567 by_member = by_interface.get(dbus_interface, None)
568 if by_member is None:
569 return
570 matches = by_member.get(signal_name, None)
571 if matches is None:
572 return
573 self._signals_lock.acquire()
574 #logger.debug(matches)
575 try:
576 new = []
577 for match in matches:
578 if (handler_or_match is match
579 or match.matches_removal_spec(named_service,
580 path,
581 dbus_interface,
582 signal_name,
583 handler_or_match,
584 **keywords)):
585 #logger.debug('Removing match string: %s', match)
586 self.remove_match_string(str(match))
587 self._remove_name_owner_changed_for_match(named_service,
588 match)
589 else:
590 new.append(match)
591 by_member[signal_name] = new
592 finally:
593 self._signals_lock.release()
595 def _signal_func(self, message):
596 """D-Bus filter function. Handle signals by dispatching to Python
597 callbacks kept in the match-rule tree.
600 #logger.debug('Incoming message %r with args %r', message,
601 #message.get_args_list())
603 if not isinstance(message, SignalMessage):
604 return HANDLER_RESULT_NOT_YET_HANDLED
606 # If it's NameOwnerChanged, we'll need to update our
607 # sender well-known name -> sender unique name mappings
608 if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
609 and message.has_sender(BUS_DAEMON_NAME)
610 and message.has_path(BUS_DAEMON_PATH)):
611 name, unused, new = message.get_args_list()
612 for match in self._signal_sender_matches.get(name, (None,))[1:]:
613 match.sender_unique = new
615 # See if anyone else wants to know
616 dbus_interface = message.get_interface()
617 path = message.get_path()
618 signal_name = message.get_member()
620 ret = HANDLER_RESULT_NOT_YET_HANDLED
621 for match in self._iter_easy_matches(path, dbus_interface,
622 signal_name):
623 if match.maybe_handle_message(message):
624 ret = HANDLER_RESULT_HANDLED
625 return ret
627 def __repr__(self):
628 if self._bus_type == BUS_SESSION:
629 name = 'SESSION'
630 elif self._bus_type == BUS_SYSTEM:
631 name = 'SYSTEM'
632 elif self._bus_type == BUS_STARTER:
633 name = 'STARTER'
634 else:
635 raise AssertionError('Unable to represent unknown bus type.')
637 return '<dbus.Bus on %s at %#x>' % (name, id(self))
638 __str__ = __repr__
641 # FIXME: Drop the subclasses here? I can't think why we'd ever want
642 # polymorphism
643 class SystemBus(Bus):
644 """The system-wide message bus."""
645 def __new__(cls, private=False, mainloop=None):
646 """Return a connection to the system bus.
648 :Parameters:
649 `private` : bool
650 If true, never return an existing shared instance, but instead
651 return a private connection.
652 `mainloop` : dbus.mainloop.NativeMainLoop
653 The main loop to use. The default is to use the default
654 main loop if one has been set up, or raise an exception
655 if none has been.
657 return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
658 private=private)
660 class SessionBus(Bus):
661 """The session (current login) message bus."""
662 def __new__(cls, private=False, mainloop=None):
663 """Return a connection to the session bus.
665 :Parameters:
666 `private` : bool
667 If true, never return an existing shared instance, but instead
668 return a private connection.
669 `mainloop` : dbus.mainloop.NativeMainLoop
670 The main loop to use. The default is to use the default
671 main loop if one has been set up, or raise an exception
672 if none has been.
674 return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
675 mainloop=mainloop)
677 class StarterBus(Bus):
678 """The bus that activated this process (only valid if
679 this process was launched by DBus activation).
681 def __new__(cls, private=False, mainloop=None):
682 """Return a connection to the bus that activated this process.
684 :Parameters:
685 `private` : bool
686 If true, never return an existing shared instance, but instead
687 return a private connection.
688 `mainloop` : dbus.mainloop.NativeMainLoop
689 The main loop to use. The default is to use the default
690 main loop if one has been set up, or raise an exception
691 if none has been.
693 return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
694 mainloop=mainloop)
697 _dbus_bindings_warning = DeprecationWarning("""\
698 The dbus_bindings module is deprecated and will go away soon.
700 dbus-python 0.80 provides only a partial emulation of the old
701 dbus_bindings, which was never meant to be public API.
703 Most uses of dbus_bindings are applications catching the exception
704 dbus.dbus_bindings.DBusException. You should use dbus.DBusException
705 instead (this is compatible with all dbus-python versions since 0.40.2).
707 If you need additional public API, please contact the maintainers via
708 <dbus@lists.freedesktop.org>.
709 """)
711 if 'DBUS_PYTHON_NO_DEPRECATED' not in os.environ:
713 class _DBusBindingsEmulation:
714 """A partial emulation of the dbus_bindings module."""
715 _module = None
716 def __str__(self):
717 return '_DBusBindingsEmulation()'
718 def __repr__(self):
719 return '_DBusBindingsEmulation()'
720 def __getattr__(self, attr):
721 if self._module is None:
722 from warnings import warn as _warn
723 _warn(_dbus_bindings_warning, DeprecationWarning, stacklevel=2)
725 import dbus.dbus_bindings as m
726 self._module = m
727 return getattr(self._module, attr)
729 dbus_bindings = _DBusBindingsEmulation()
730 """Deprecated, don't use."""