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