Add special case to serialization: objects with a __dbus_object_path__ attribute...
[dbus-python-phuang.git] / dbus / proxies.py
blob5c72398b680c9eb1efcb4c195bf29b09e5ee05ca
1 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006 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 # 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 import sys
25 import logging
27 import _dbus_bindings
28 from dbus._expat_introspect_parser import process_introspection_data
29 from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
31 __docformat__ = 'restructuredtext'
34 _logger = logging.getLogger('dbus.proxies')
37 class _ReplyHandler(object):
38 __slots__ = ('_on_error', '_on_reply', '_get_args_options')
39 def __init__(self, on_reply, on_error, **get_args_options):
40 self._on_error = on_error
41 self._on_reply = on_reply
42 self._get_args_options = get_args_options
44 def __call__(self, message):
45 if isinstance(message, _dbus_bindings.MethodReturnMessage):
46 self._on_reply(*message.get_args_list(**self._get_args_options))
47 elif isinstance(message, _dbus_bindings.ErrorMessage):
48 args = message.get_args_list()
49 if len(args) > 0:
50 self._on_error(DBusException(args[0]))
51 else:
52 self._on_error(DBusException())
53 else:
54 self._on_error(DBusException('Unexpected reply message type: %s'
55 % message))
58 class DeferedMethod:
59 """A DeferedMethod
61 This is returned instead of ProxyMethod when we are defering DBus calls
62 while waiting for introspection data to be returned
63 """
64 def __init__(self, proxy_method):
65 self._proxy_method = proxy_method
66 self._method_name = proxy_method._method_name
68 def __call__(self, *args, **keywords):
69 reply_handler = None
70 if keywords.has_key('reply_handler'):
71 reply_handler = keywords['reply_handler']
73 #block for now even on async
74 # FIXME: put ret in async queue in future if we have a reply handler
76 self._proxy_method._proxy._pending_introspect.block()
77 ret = self._proxy_method (*args, **keywords)
79 return ret
81 class ProxyMethod:
82 """A proxy Method.
84 Typically a member of a ProxyObject. Calls to the
85 method produce messages that travel over the Bus and are routed
86 to a specific named Service.
87 """
88 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
89 self._proxy = proxy
90 self._connection = connection
91 self._named_service = named_service
92 self._object_path = object_path
93 self._method_name = method_name
94 self._dbus_interface = iface
96 def __call__(self, *args, **keywords):
97 timeout = -1
98 if keywords.has_key('timeout'):
99 timeout = keywords['timeout']
101 reply_handler = None
102 if keywords.has_key('reply_handler'):
103 reply_handler = keywords['reply_handler']
105 error_handler = None
106 if keywords.has_key('error_handler'):
107 error_handler = keywords['error_handler']
109 ignore_reply = False
110 if keywords.has_key('ignore_reply'):
111 ignore_reply = keywords['ignore_reply']
113 get_args_options = {}
114 if keywords.has_key('utf8_strings'):
115 get_args_options['utf8_strings'] = keywords['utf8_strings']
116 if keywords.has_key('byte_arrays'):
117 get_args_options['byte_arrays'] = keywords['byte_arrays']
119 if not(reply_handler and error_handler):
120 if reply_handler:
121 raise MissingErrorHandlerException()
122 elif error_handler:
123 raise MissingReplyHandlerException()
125 dbus_interface = self._dbus_interface
126 if keywords.has_key('dbus_interface'):
127 dbus_interface = keywords['dbus_interface']
129 tmp_iface = ''
130 if dbus_interface:
131 tmp_iface = dbus_interface + '.'
133 key = tmp_iface + self._method_name
135 introspect_sig = None
136 if self._proxy._introspect_method_map.has_key (key):
137 introspect_sig = self._proxy._introspect_method_map[key]
139 message = _dbus_bindings.MethodCallMessage(destination=None,
140 path=self._object_path,
141 interface=dbus_interface,
142 method=self._method_name)
143 message.set_destination(self._named_service)
145 # Add the arguments to the function
146 try:
147 message.append(signature=introspect_sig, *args)
148 except Exception, e:
149 _logger.error('Unable to set arguments %r according to '
150 'introspected signature %r: %s: %s',
151 args, introspect_sig, e.__class__, e)
152 raise
154 if ignore_reply:
155 self._connection.send_message(message)
156 return None
157 elif reply_handler:
158 result = self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0)
159 return None
160 else:
161 reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
162 args_list = reply_message.get_args_list(**get_args_options)
163 if len(args_list) == 0:
164 return None
165 elif len(args_list) == 1:
166 return args_list[0]
167 else:
168 return tuple(args_list)
171 class ProxyObject:
172 """A proxy to the remote Object.
174 A ProxyObject is provided by the Bus. ProxyObjects
175 have member functions, and can be called like normal Python objects.
177 ProxyMethodClass = ProxyMethod
178 DeferedMethodClass = DeferedMethod
180 INTROSPECT_STATE_DONT_INTROSPECT = 0
181 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
182 INTROSPECT_STATE_INTROSPECT_DONE = 2
184 def __init__(self, bus, named_service, object_path, introspect=True):
185 """Initialize the proxy object.
187 :Parameters:
188 `bus` : `dbus.Bus`
189 The bus on which to find this object
190 `named_service` : str
191 A bus name for the endpoint owning the object (need not
192 actually be a service name)
193 `object_path` : str
194 The object path at which the endpoint exports the object
195 `introspect` : bool
196 If true (default), attempt to introspect the remote
197 object to find out supported methods and their signatures
199 self._bus = bus
200 self._named_service = named_service
201 self.__dbus_object_path__ = object_path
203 #PendingCall object for Introspect call
204 self._pending_introspect = None
205 #queue of async calls waiting on the Introspect to return
206 self._pending_introspect_queue = []
207 #dictionary mapping method names to their input signatures
208 self._introspect_method_map = {}
210 if not introspect:
211 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
212 else:
213 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
215 self._pending_introspect = self._Introspect()
218 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
219 """Arrange for the given function to be called when the given signal
220 is received.
222 FIXME: this binds to the unique name regardless of whether this is a
223 by-unique-name or by-well-known-name proxy.
225 :Parameters:
226 `signal_name` : str
227 The name of the signal
228 `handler_function` : callable
229 A function to be called when the signal is emitted by
230 the remote object. Its positional arguments will be the
231 arguments of the signal; optionally, it may be given
232 keyword arguments as described below.
233 `dbus_interface` : str
234 Optional interface with which to qualify the signal name.
235 If None (the default) the handler will be called whenever a
236 signal of the given member name is received, whatever
237 its interface.
238 :Keywords:
239 `utf8_strings` : bool
240 If True, the handler function will receive any string
241 arguments as dbus.UTF8String objects (a subclass of str
242 guaranteed to be UTF-8). If False (default) it will receive
243 any string arguments as dbus.String objects (a subclass of
244 unicode).
245 `byte_arrays` : bool
246 If True, the handler function will receive any byte-array
247 arguments as dbus.ByteArray objects (a subclass of str).
248 If False (default) it will receive any byte-array
249 arguments as a dbus.Array of dbus.Byte (subclasses of:
250 a list of ints).
251 `sender_keyword` : str
252 If not None (the default), the handler function will receive
253 the unique name of the sending endpoint as a keyword
254 argument with this name
255 `destination_keyword` : str
256 If not None (the default), the handler function will receive
257 the bus name of the destination (or None if the signal is a
258 broadcast, as is usual) as a keyword argument with this name.
259 `interface_keyword` : str
260 If not None (the default), the handler function will receive
261 the signal interface as a keyword argument with this name.
262 `member_keyword` : str
263 If not None (the default), the handler function will receive
264 the signal name as a keyword argument with this name.
265 `path_keyword` : str
266 If not None (the default), the handler function will receive
267 the object-path of the sending object as a keyword argument
268 with this name
269 `message_keyword` : str
270 If not None (the default), the handler function will receive
271 the `dbus.lowlevel.SignalMessage` as a keyword argument with
272 this name.
273 `arg...` : unicode or UTF-8 str
274 If there are additional keyword parameters of the form
275 ``arg``\ *n*, match only signals where the *n*\ th argument
276 is the value given for that keyword parameter. As of this time
277 only string arguments can be matched (in particular,
278 object paths and signatures can't).
280 return \
281 self._bus.add_signal_receiver(handler_function,
282 signal_name=signal_name,
283 dbus_interface=dbus_interface,
284 named_service=self._named_service,
285 path=self.__dbus_object_path__,
286 **keywords)
288 def _Introspect(self):
289 message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
290 message.set_destination(self._named_service)
292 result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
293 return result
295 def _introspect_execute_queue(self):
296 for call in self._pending_introspect_queue:
297 (member, iface, args, keywords) = call
299 introspect_sig = None
301 tmp_iface = ''
302 if iface:
303 tmp_iface = iface + '.'
305 key = tmp_iface + '.' + member
306 if self._introspect_method_map.has_key (key):
307 introspect_sig = self._introspect_method_map[key]
310 call_object = self.ProxyMethodClass(self._bus.get_connection(),
311 self._named_service,
312 self.__dbus_object_path__,
313 iface,
314 member,
315 introspect_sig)
317 call_object(args, keywords)
319 def _introspect_reply_handler(self, data):
320 try:
321 self._introspect_method_map = process_introspection_data(data)
322 except IntrospectionParserException, e:
323 self._introspect_error_handler(e)
324 return
326 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
327 #self._introspect_execute_queue()
329 def _introspect_error_handler(self, error):
330 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
331 self._introspect_execute_queue()
332 sys.stderr.write("Introspect error: " + str(error) + "\n")
334 def __getattr__(self, member, dbus_interface=None):
335 if member == '__call__':
336 return object.__call__
337 elif member.startswith('__') and member.endswith('__'):
338 raise AttributeError(member)
339 else:
340 return self.get_dbus_method(member, dbus_interface)
342 def get_dbus_method(self, member, dbus_interface=None):
343 """Return a proxy method representing the given D-Bus method. The
344 returned proxy method can be called in the usual way. For instance, ::
346 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
348 is equivalent to::
350 proxy.Foo(123, dbus_interface='com.example.Bar')
352 or even::
354 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
356 However, using `get_dbus_method` is the only way to call D-Bus
357 methods with certain awkward names - if the author of a service
358 implements a method called ``connect_to_signal`` or even
359 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
361 For services which follow the D-Bus convention of CamelCaseMethodNames
362 this won't be a problem.
365 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
366 self._named_service,
367 self.__dbus_object_path__, member,
368 dbus_interface)
370 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
371 ret = self.DeferedMethodClass(ret)
373 return ret
375 def __repr__(self):
376 return '<ProxyObject wrapping %s %s %s at %#x>'%(
377 self._bus, self._named_service, self.__dbus_object_path__, id(self))
378 __str__ = __repr__