Convert _BusDaemonMixin and _MethodCallMixin into base classes BusConnection and...
[dbus-python-phuang.git] / dbus / _dbus.py
blob48c623bb5bce9704f7c5b24b1ca57c4e05a9ff94
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 DBUS_START_REPLY_SUCCESS, \
41 DBUS_START_REPLY_ALREADY_RUNNING, \
42 SignalMessage,\
43 HANDLER_RESULT_NOT_YET_HANDLED,\
44 HANDLER_RESULT_HANDLED
45 from dbus.bus import BusConnection
46 from dbus.proxies import ProxyObject
48 try:
49 import thread
50 except ImportError:
51 import dummy_thread as thread
53 logger = logging.getLogger('dbus._dbus')
55 _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
56 "interface='%s',member='NameOwnerChanged',"
57 "path='%s',arg0='%%s'"
58 % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
59 BUS_DAEMON_PATH))
60 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
61 messages"""
64 class SignalMatch(object):
65 __slots__ = ('sender_unique', '_member', '_interface', '_sender',
66 '_path', '_handler', '_args_match', '_rule',
67 '_utf8_strings', '_byte_arrays', '_conn_weakref',
68 '_destination_keyword', '_interface_keyword',
69 '_message_keyword', '_member_keyword',
70 '_sender_keyword', '_path_keyword', '_int_args_match')
72 def __init__(self, conn, sender, object_path, dbus_interface,
73 member, handler, utf8_strings=False, byte_arrays=False,
74 sender_keyword=None, path_keyword=None,
75 interface_keyword=None, member_keyword=None,
76 message_keyword=None, destination_keyword=None,
77 **kwargs):
78 if member is not None:
79 validate_member_name(member)
80 if dbus_interface is not None:
81 validate_interface_name(dbus_interface)
82 if sender is not None:
83 validate_bus_name(sender)
84 if object_path is not None:
85 validate_object_path(object_path)
87 self._conn_weakref = weakref.ref(conn)
88 self._sender = sender
89 self._interface = dbus_interface
90 self._member = member
91 self._path = object_path
92 self._handler = handler
93 if (sender is not None and sender[:1] != ':'
94 and sender != BUS_DAEMON_NAME):
95 self.sender_unique = conn.get_name_owner(sender)
96 else:
97 self.sender_unique = sender
98 self._utf8_strings = utf8_strings
99 self._byte_arrays = byte_arrays
100 self._sender_keyword = sender_keyword
101 self._path_keyword = path_keyword
102 self._member_keyword = member_keyword
103 self._interface_keyword = interface_keyword
104 self._message_keyword = message_keyword
105 self._destination_keyword = destination_keyword
107 self._args_match = kwargs
108 if not kwargs:
109 self._int_args_match = None
110 else:
111 self._int_args_match = {}
112 for kwarg in kwargs:
113 if not kwarg.startswith('arg'):
114 raise TypeError('SignalMatch: unknown keyword argument %s'
115 % kwarg)
116 try:
117 index = int(kwarg[3:])
118 except ValueError:
119 raise TypeError('SignalMatch: unknown keyword argument %s'
120 % kwarg)
121 if index < 0 or index > 63:
122 raise TypeError('SignalMatch: arg match index must be in '
123 'range(64), not %d' % index)
124 self._int_args_match[index] = kwargs[kwarg]
126 # we're always going to have to calculate the match rule for
127 # the Bus's benefit, so this constructor might as well do the work
128 rule = ["type='signal'"]
129 if self._sender is not None:
130 rule.append("sender='%s'" % self._sender)
131 if self._path is not None:
132 rule.append("path='%s'" % self._path)
133 if self._interface is not None:
134 rule.append("interface='%s'" % self._interface)
135 if self._member is not None:
136 rule.append("member='%s'" % self._member)
137 for kwarg, value in kwargs.iteritems():
138 rule.append("%s='%s'" % (kwarg, value))
140 self._rule = ','.join(rule)
142 def __str__(self):
143 return self._rule
145 def __repr__(self):
146 return ('<%s at %x "%s" on conn %r>'
147 % (self.__class__, id(self), self._rule, self._conn_weakref()))
149 def matches_removal_spec(self, sender, object_path,
150 dbus_interface, member, handler, **kwargs):
151 if handler not in (None, self._handler):
152 return False
153 if sender != self._sender:
154 return False
155 if object_path != self._path:
156 return False
157 if dbus_interface != self._interface:
158 return False
159 if member != self._member:
160 return False
161 if kwargs != self._args_match:
162 return False
163 return True
165 def maybe_handle_message(self, message):
166 args = None
168 # these haven't been checked yet by the match tree
169 if self.sender_unique not in (None, message.get_sender()):
170 return False
171 if self._int_args_match is not None:
172 # extracting args with utf8_strings and byte_arrays is less work
173 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
174 for index, value in self._int_args_match.iteritems():
175 if (index >= len(args)
176 or not isinstance(args[index], UTF8String)
177 or args[index] != value):
178 return False
180 # these have likely already been checked by the match tree
181 if self._member not in (None, message.get_member()):
182 return False
183 if self._interface not in (None, message.get_interface()):
184 return False
185 if self._path not in (None, message.get_path()):
186 return False
188 try:
189 # minor optimization: if we already extracted the args with the
190 # right calling convention to do the args match, don't bother
191 # doing so again
192 if args is None or not self._utf8_strings or not self._byte_arrays:
193 args = message.get_args_list(utf8_strings=self._utf8_strings,
194 byte_arrays=self._byte_arrays)
195 kwargs = {}
196 if self._sender_keyword is not None:
197 kwargs[self._sender_keyword] = message.get_sender()
198 if self._destination_keyword is not None:
199 kwargs[self._destination_keyword] = message.get_destination()
200 if self._path_keyword is not None:
201 kwargs[self._path_keyword] = message.get_path()
202 if self._member_keyword is not None:
203 kwargs[self._member_keyword] = message.get_member()
204 if self._interface_keyword is not None:
205 kwargs[self._interface_keyword] = message.get_interface()
206 if self._message_keyword is not None:
207 kwargs[self._message_keyword] = message
208 self._handler(*args, **kwargs)
209 except:
210 # FIXME: need to decide whether dbus-python uses logging, or
211 # stderr, or what, and make it consistent
212 sys.stderr.write('Exception in handler for D-Bus signal:\n')
213 print_exc()
215 return True
217 def remove(self):
218 #logger.debug('%r: removing', self)
219 conn = self._conn_weakref()
220 # do nothing if the connection has already vanished
221 if conn is not None:
222 #logger.debug('%r: removing from connection %r', self, conn)
223 conn.remove_signal_receiver(self, self._member,
224 self._interface, self._sender,
225 self._path,
226 **self._args_match)
229 class Bus(BusConnection):
230 """A connection to a DBus daemon.
232 One of three possible standard buses, the SESSION, SYSTEM,
233 or STARTER bus
236 TYPE_SESSION = BUS_SESSION
237 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
239 TYPE_SYSTEM = BUS_SYSTEM
240 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
242 TYPE_STARTER = BUS_STARTER
243 """Represents the bus that started this service by activation (same as
244 the global dbus.BUS_STARTER)"""
246 ProxyObjectClass = ProxyObject
248 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
249 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
251 _shared_instances = {}
253 def __new__(cls, bus_type=TYPE_SESSION, private=False, mainloop=None):
254 """Constructor, returning an existing instance where appropriate.
256 The returned instance is actually always an instance of `SessionBus`,
257 `SystemBus` or `StarterBus`.
259 :Parameters:
260 `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
261 Connect to the appropriate bus
262 `private` : bool
263 If true, never return an existing shared instance, but instead
264 return a private connection
265 `mainloop` : dbus.mainloop.NativeMainLoop
266 The main loop to use. The default is to use the default
267 main loop if one has been set up, or raise an exception
268 if none has been.
269 :ToDo:
270 - There is currently no way to connect this class to a custom
271 address.
272 - Some of this functionality should be available on
273 peer-to-peer D-Bus connections too.
274 :Changed: in dbus-python 0.80:
275 converted from a wrapper around a Connection to a Connection
276 subclass.
278 if (not private and bus_type in cls._shared_instances):
279 return cls._shared_instances[bus_type]
281 # this is a bit odd, but we create instances of the subtypes
282 # so we can return the shared instances if someone tries to
283 # construct one of them (otherwise we'd eg try and return an
284 # instance of Bus from __new__ in SessionBus). why are there
285 # three ways to construct this class? we just don't know.
286 if bus_type == BUS_SESSION:
287 subclass = SessionBus
288 elif bus_type == BUS_SYSTEM:
289 subclass = SystemBus
290 elif bus_type == BUS_STARTER:
291 subclass = StarterBus
292 else:
293 raise ValueError('invalid bus_type %s' % bus_type)
295 bus = subclass._new_for_bus(bus_type, mainloop=mainloop)
297 bus._bus_type = bus_type
298 # _bus_names is used by dbus.service.BusName!
299 bus._bus_names = weakref.WeakValueDictionary()
301 bus._signal_recipients_by_object_path = {}
302 """Map from object path to dict mapping dbus_interface to dict
303 mapping member to list of SignalMatch objects."""
305 bus._signal_sender_matches = {}
306 """Map from sender well-known name to list of match rules for all
307 signal handlers that match on sender well-known name."""
309 bus._signals_lock = thread.allocate_lock()
310 """Lock used to protect signal data structures if doing two
311 removals at the same time (everything else is atomic, thanks to
312 the GIL)"""
314 bus.add_message_filter(bus.__class__._signal_func)
316 if not private:
317 cls._shared_instances[bus_type] = bus
319 return bus
321 def close(self):
322 t = self._bus_type
323 if self.__class__._shared_instances[t] is self:
324 del self.__class__._shared_instances[t]
325 super(BusConnection, self).close()
327 def get_connection(self):
328 """(Deprecated - in new code, just use self)
330 Return self, for backwards compatibility with earlier dbus-python
331 versions where Bus was not a subclass of Connection.
333 return self
334 _connection = property(get_connection, None, None,
335 """self._connection == self, for backwards
336 compatibility with earlier dbus-python versions
337 where Bus was not a subclass of Connection.""")
339 def get_session(private=False):
340 """Static method that returns a connection to the session bus.
342 :Parameters:
343 `private` : bool
344 If true, do not return a shared connection.
346 return SessionBus(private=private)
348 get_session = staticmethod(get_session)
350 def get_system(private=False):
351 """Static method that returns a connection to the system bus.
353 :Parameters:
354 `private` : bool
355 If true, do not return a shared connection.
357 return SystemBus(private=private)
359 get_system = staticmethod(get_system)
362 def get_starter(private=False):
363 """Static method that returns a connection to the starter bus.
365 :Parameters:
366 `private` : bool
367 If true, do not return a shared connection.
369 return StarterBus(private=private)
371 get_starter = staticmethod(get_starter)
373 def get_object(self, named_service, object_path, introspect=True,
374 follow_name_owner_changes=False):
375 """Return a local proxy for the given remote object.
377 Method calls on the proxy are translated into method calls on the
378 remote object.
380 :Parameters:
381 `named_service` : str
382 A bus name (either the unique name or a well-known name)
383 of the application owning the object
384 `object_path` : str
385 The object path of the desired object
386 `introspect` : bool
387 If true (default), attempt to introspect the remote
388 object to find out supported methods and their signatures
389 `follow_name_owner_changes` : bool
390 If the object path is a well-known name and this parameter
391 is false (default), resolve the well-known name to the unique
392 name of its current owner and bind to that instead; if the
393 ownership of the well-known name changes in future,
394 keep communicating with the original owner.
395 This is necessary if the D-Bus API used is stateful.
397 If the object path is a well-known name and this parameter
398 is true, whenever the well-known name changes ownership in
399 future, bind to the new owner, if any.
401 If the given object path is a unique name, this parameter
402 has no effect.
404 :Returns: a `dbus.proxies.ProxyObject`
405 :Raises `DBusException`: if resolving the well-known name to a
406 unique name fails
408 if follow_name_owner_changes:
409 self._require_main_loop() # we don't get the signals otherwise
410 return self.ProxyObjectClass(self, named_service, object_path,
411 introspect=introspect,
412 follow_name_owner_changes=follow_name_owner_changes)
414 def add_signal_receiver(self, handler_function,
415 signal_name=None,
416 dbus_interface=None,
417 named_service=None,
418 path=None,
419 **keywords):
420 """Arrange for the given function to be called when a signal matching
421 the parameters is received.
423 :Parameters:
424 `handler_function` : callable
425 The function to be called. Its positional arguments will
426 be the arguments of the signal. By default it will receive
427 no keyword arguments, but see the description of
428 the optional keyword arguments below.
429 `signal_name` : str
430 The signal name; None (the default) matches all names
431 `dbus_interface` : str
432 The D-Bus interface name with which to qualify the signal;
433 None (the default) matches all interface names
434 `named_service` : str
435 A bus name for the sender, which will be resolved to a
436 unique name if it is not already; None (the default) matches
437 any sender
438 `path` : str
439 The object path of the object which must have emitted the
440 signal; None (the default) matches any object path
441 :Keywords:
442 `utf8_strings` : bool
443 If True, the handler function will receive any string
444 arguments as dbus.UTF8String objects (a subclass of str
445 guaranteed to be UTF-8). If False (default) it will receive
446 any string arguments as dbus.String objects (a subclass of
447 unicode).
448 `byte_arrays` : bool
449 If True, the handler function will receive any byte-array
450 arguments as dbus.ByteArray objects (a subclass of str).
451 If False (default) it will receive any byte-array
452 arguments as a dbus.Array of dbus.Byte (subclasses of:
453 a list of ints).
454 `sender_keyword` : str
455 If not None (the default), the handler function will receive
456 the unique name of the sending endpoint as a keyword
457 argument with this name.
458 `destination_keyword` : str
459 If not None (the default), the handler function will receive
460 the bus name of the destination (or None if the signal is a
461 broadcast, as is usual) as a keyword argument with this name.
462 `interface_keyword` : str
463 If not None (the default), the handler function will receive
464 the signal interface as a keyword argument with this name.
465 `member_keyword` : str
466 If not None (the default), the handler function will receive
467 the signal name as a keyword argument with this name.
468 `path_keyword` : str
469 If not None (the default), the handler function will receive
470 the object-path of the sending object as a keyword argument
471 with this name.
472 `message_keyword` : str
473 If not None (the default), the handler function will receive
474 the `dbus.lowlevel.SignalMessage` as a keyword argument with
475 this name.
476 `arg...` : unicode or UTF-8 str
477 If there are additional keyword parameters of the form
478 ``arg``\ *n*, match only signals where the *n*\ th argument
479 is the value given for that keyword parameter. As of this
480 time only string arguments can be matched (in particular,
481 object paths and signatures can't).
483 self._require_main_loop()
485 match = SignalMatch(self, named_service, path, dbus_interface,
486 signal_name, handler_function, **keywords)
487 by_interface = self._signal_recipients_by_object_path.setdefault(path,
489 by_member = by_interface.setdefault(dbus_interface, {})
490 matches = by_member.setdefault(signal_name, [])
491 # The bus daemon is special - its unique-name is org.freedesktop.DBus
492 # rather than starting with :
493 if (named_service is not None and named_service[:1] != ':'
494 and named_service != BUS_DAEMON_NAME):
495 notification = self._signal_sender_matches.setdefault(named_service,
497 if not notification:
498 self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
499 notification.append(match)
500 # make sure nobody is currently manipulating the list
501 self._signals_lock.acquire()
502 try:
503 matches.append(match)
504 finally:
505 self._signals_lock.release()
506 self.add_match_string(str(match))
507 return match
509 def _iter_easy_matches(self, path, dbus_interface, member):
510 if path is not None:
511 path_keys = (None, path)
512 else:
513 path_keys = (None,)
514 if dbus_interface is not None:
515 interface_keys = (None, dbus_interface)
516 else:
517 interface_keys = (None,)
518 if member is not None:
519 member_keys = (None, member)
520 else:
521 member_keys = (None,)
523 for path in path_keys:
524 by_interface = self._signal_recipients_by_object_path.get(path,
525 None)
526 if by_interface is None:
527 continue
528 for dbus_interface in interface_keys:
529 by_member = by_interface.get(dbus_interface, None)
530 if by_member is None:
531 continue
532 for member in member_keys:
533 matches = by_member.get(member, None)
534 if matches is None:
535 continue
536 for m in matches:
537 yield m
539 def _remove_name_owner_changed_for_match(self, named_service, match):
540 # The signals lock must be held.
541 notification = self._signal_sender_matches.get(named_service, False)
542 if notification:
543 try:
544 notification.remove(match)
545 except LookupError:
546 pass
547 if not notification:
548 self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
549 % named_service)
551 def remove_signal_receiver(self, handler_or_match,
552 signal_name=None,
553 dbus_interface=None,
554 named_service=None,
555 path=None,
556 **keywords):
557 #logger.debug('%r: removing signal receiver %r: member=%s, '
558 #'iface=%s, sender=%s, path=%s, kwargs=%r',
559 #self, handler_or_match, signal_name,
560 #dbus_interface, named_service, path, keywords)
561 #logger.debug('%r', self._signal_recipients_by_object_path)
562 by_interface = self._signal_recipients_by_object_path.get(path, None)
563 if by_interface is None:
564 return
565 by_member = by_interface.get(dbus_interface, None)
566 if by_member is None:
567 return
568 matches = by_member.get(signal_name, None)
569 if matches is None:
570 return
571 self._signals_lock.acquire()
572 #logger.debug(matches)
573 try:
574 new = []
575 for match in matches:
576 if (handler_or_match is match
577 or match.matches_removal_spec(named_service,
578 path,
579 dbus_interface,
580 signal_name,
581 handler_or_match,
582 **keywords)):
583 #logger.debug('Removing match string: %s', match)
584 self.remove_match_string(str(match))
585 self._remove_name_owner_changed_for_match(named_service,
586 match)
587 else:
588 new.append(match)
589 by_member[signal_name] = new
590 finally:
591 self._signals_lock.release()
593 def _signal_func(self, message):
594 """D-Bus filter function. Handle signals by dispatching to Python
595 callbacks kept in the match-rule tree.
598 #logger.debug('Incoming message %r with args %r', message,
599 #message.get_args_list())
601 if not isinstance(message, SignalMessage):
602 return HANDLER_RESULT_NOT_YET_HANDLED
604 # If it's NameOwnerChanged, we'll need to update our
605 # sender well-known name -> sender unique name mappings
606 if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
607 and message.has_sender(BUS_DAEMON_NAME)
608 and message.has_path(BUS_DAEMON_PATH)):
609 name, unused, new = message.get_args_list()
610 for match in self._signal_sender_matches.get(name, (None,))[1:]:
611 match.sender_unique = new
613 # See if anyone else wants to know
614 dbus_interface = message.get_interface()
615 path = message.get_path()
616 signal_name = message.get_member()
618 ret = HANDLER_RESULT_NOT_YET_HANDLED
619 for match in self._iter_easy_matches(path, dbus_interface,
620 signal_name):
621 if match.maybe_handle_message(message):
622 ret = HANDLER_RESULT_HANDLED
623 return ret
625 def __repr__(self):
626 if self._bus_type == BUS_SESSION:
627 name = 'SESSION'
628 elif self._bus_type == BUS_SYSTEM:
629 name = 'SYSTEM'
630 elif self._bus_type == BUS_STARTER:
631 name = 'STARTER'
632 else:
633 raise AssertionError('Unable to represent unknown bus type.')
635 return '<dbus.Bus on %s at %#x>' % (name, id(self))
636 __str__ = __repr__
639 # FIXME: Drop the subclasses here? I can't think why we'd ever want
640 # polymorphism
641 class SystemBus(Bus):
642 """The system-wide message bus."""
643 def __new__(cls, private=False, mainloop=None):
644 """Return a connection to the system bus.
646 :Parameters:
647 `private` : bool
648 If true, never return an existing shared instance, but instead
649 return a private connection.
650 `mainloop` : dbus.mainloop.NativeMainLoop
651 The main loop to use. The default is to use the default
652 main loop if one has been set up, or raise an exception
653 if none has been.
655 return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
656 private=private)
658 class SessionBus(Bus):
659 """The session (current login) message bus."""
660 def __new__(cls, private=False, mainloop=None):
661 """Return a connection to the session bus.
663 :Parameters:
664 `private` : bool
665 If true, never return an existing shared instance, but instead
666 return a private connection.
667 `mainloop` : dbus.mainloop.NativeMainLoop
668 The main loop to use. The default is to use the default
669 main loop if one has been set up, or raise an exception
670 if none has been.
672 return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
673 mainloop=mainloop)
675 class StarterBus(Bus):
676 """The bus that activated this process (only valid if
677 this process was launched by DBus activation).
679 def __new__(cls, private=False, mainloop=None):
680 """Return a connection to the bus that activated this process.
682 :Parameters:
683 `private` : bool
684 If true, never return an existing shared instance, but instead
685 return a private connection.
686 `mainloop` : dbus.mainloop.NativeMainLoop
687 The main loop to use. The default is to use the default
688 main loop if one has been set up, or raise an exception
689 if none has been.
691 return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
692 mainloop=mainloop)
695 _dbus_bindings_warning = DeprecationWarning("""\
696 The dbus_bindings module is deprecated and will go away soon.
698 dbus-python 0.80 provides only a partial emulation of the old
699 dbus_bindings, which was never meant to be public API.
701 Most uses of dbus_bindings are applications catching the exception
702 dbus.dbus_bindings.DBusException. You should use dbus.DBusException
703 instead (this is compatible with all dbus-python versions since 0.40.2).
705 If you need additional public API, please contact the maintainers via
706 <dbus@lists.freedesktop.org>.
707 """)
709 if 'DBUS_PYTHON_NO_DEPRECATED' not in os.environ:
711 class _DBusBindingsEmulation:
712 """A partial emulation of the dbus_bindings module."""
713 _module = None
714 def __str__(self):
715 return '_DBusBindingsEmulation()'
716 def __repr__(self):
717 return '_DBusBindingsEmulation()'
718 def __getattr__(self, attr):
719 if self._module is None:
720 from warnings import warn as _warn
721 _warn(_dbus_bindings_warning, DeprecationWarning, stacklevel=2)
723 import dbus.dbus_bindings as m
724 self._module = m
725 return getattr(self._module, attr)
727 dbus_bindings = _DBusBindingsEmulation()
728 """Deprecated, don't use."""