Classes to make adaptors more readable.
[cnetworkmanager.git] / dbusclient / __init__.py
blobd1b1f13c0318275c765cda3b065793af47249845
1 "Convenience wrappers around dbus-python"
3 import dbus
4 import functools
6 # TODO rename to adaptors
7 from func import Adaptor, MethodAdaptor, PropertyAdaptor, SignalAdaptor
9 def object_path(o):
10 """Return the object path of o.
12 If o is a proxy object, use its appropriate attribute.
13 Otherwise assume that o already is an object path.
14 """
15 if isinstance(o, dbus.proxies.ProxyObject):
16 return o.object_path
17 # hope it is ok
18 return o
20 class DBusMio(dbus.proxies.ProxyObject):
21 """Multi-interface object.
23 Will look into introspection data to find which interface
24 to use for a method or a property, obviating the need for
25 dbus.proxies.Interface.
26 If introspection is not available, provide default_interface
27 to the constructor.
29 BUGS: 1st method call will block with introspection"""
31 def __init__(self, conn=None, bus_name=None, object_path=None, introspect=True, follow_name_owner_changes=False, **kwargs):
32 """Constructor.
34 kwargs may contain default_interface, to be used
35 if introspection does not provide it for a method/property
36 """
38 # FIXME common for this class, all classes?
39 self.__default_interface = kwargs.pop("default_interface", None)
40 super(DBusMio, self).__init__(conn, bus_name, object_path, introspect, follow_name_owner_changes, **kwargs)
42 def __getattr__(self, name):
43 """Proxied DBus methods.
45 Uses introspection or default_interface to find the interface.
46 """
47 # TODO cache
48 # iface = self._interface_cache.get(name)
49 # if iface == None:
50 iface = self.__default_interface
51 # _introspect_method_map comes from ProxyObject
52 # But it will be empty until the async introspection finishes
53 self._introspect_block() # FIXME makeit work with async methods
54 methods = self._introspect_method_map.keys()
55 for im in methods:
56 (i, m) = im.rsplit(".", 1)
57 if m == name:
58 iface = i
59 # print "METHOD %s INTERFACE %s" %(name, iface)
60 callable = super(DBusMio, self).__getattr__(name)
61 return functools.partial(callable, dbus_interface=iface)
63 # properties
64 def __getitem__(self, key):
65 """Proxies DBus properties as dictionary items.
67 a = DBusMio(...)
68 p = a["Prop"]
70 Uses default_interface (because dbus.proxies.ProxyObject
71 does not store introspection data for properties, boo. TODO.)
72 """
74 iface = self.__default_interface # TODO cache
75 # TODO _introspect_property_map
76 pmi = dbus.Interface(self, "org.freedesktop.DBus.Properties")
77 return pmi.Get(iface, key)
79 def __setitem__(self, key, value):
80 """Proxies DBus properties as dictionary items.
82 a = DBusMio(...)
83 a["Prop"] = "Hello"
85 Uses default_interface (because dbus.proxies.ProxyObject
86 does not store introspection data for properties, boo. TODO.)
87 """
89 iface = self.__default_interface # TODO cache
90 # TODO _introspect_property_map
91 pmi = dbus.Interface(self, "org.freedesktop.DBus.Properties")
92 return pmi.Set(iface, key, value)
94 def _mklist(x):
95 """Return a list.
97 Tuples are made into lists, everything else a singleton list.
98 """
99 if isinstance(x, list):
100 return x
101 elif isinstance(x, tuple):
102 return [i for i in x]
103 else:
104 return [x]
106 class DBusClient(DBusMio):
109 _adaptors = {
110 "methods": {},
111 "signals": {},
112 "properties": {},
116 @classmethod
117 def _get_adaptor(cls, kind, name):
118 # print "GET", cls, kind, name
119 try:
120 a = cls._adaptors[kind][name]
121 # print ">", a
122 # TODO cache somehow?
123 return a
124 except KeyError:
125 scls = cls.__mro__[1] # can use "super"? how?
126 try:
127 return scls._get_adaptor(kind, name)
128 except AttributeError: # no _get_adaptor there
129 raise KeyError(":".join((kind, name)))
131 @classmethod
132 def _add_adaptor(cls, kind, name, adaptor):
133 # print "ADD", cls, kind, name, adaptor
134 if isinstance(adaptor, Adaptor):
135 cls._adaptors[kind][name] = adaptor
136 return
138 # this adjusts the (too) flexible list syntax
139 adaptor = _mklist(adaptor)
140 ret = adaptor[0]
141 try:
142 args = adaptor[1]
143 except:
144 args = []
145 args = _mklist(args)
146 try:
147 kwargs = adaptor[2]
148 except:
149 kwargs = {}
151 if kind == "methods":
152 adaptor = MethodAdaptor(ret, *args)
153 elif kind == "properties":
154 setter = args[0] if len(args) else None
155 adaptor = PropertyAdaptor(ret, setter)
156 elif kind == "signals":
157 adaptor = SignalAdaptor(*args)
158 cls._adaptors[kind][name] = adaptor
161 @classmethod
162 def _add_adaptors(cls, *args, **kwargs):
164 a nested dictionary of kind:name:adaptor,
165 either as kwargs (new) or as a single dict arg (old, newest)
167 if not cls.__dict__.has_key("_adaptors"):
168 # do not use inherited attribute
169 cls._adaptors = {"methods":{}, "properties":{}, "signals":{}}
170 if len(args) != 0:
171 assert len(kwargs) == 0
172 assert len(args) == 1
173 kwargs = args[0]
175 for section in cls._adaptors.keys():
176 secsource = kwargs.pop(section, {})
177 for name, adaptor in secsource.iteritems():
178 cls._add_adaptor(section, name, adaptor)
179 assert len(kwargs) == 0
180 # print "AA", cls, cls._adaptors
182 @classmethod
183 def _add_adaptors2(cls, **kwargs):
184 """kwargs: a *flat* dictionary of name: adaptor"""
185 adict = {"methods":{}, "properties":{}, "signals":{}}
186 for k, v in kwargs.iteritems():
187 if isinstance(v, MethodAdaptor):
188 adict["methods"][k] = v
189 elif isinstance(v, PropertyAdaptor):
190 adict["properties"][k] = v
191 elif isinstance(v, SignalAdaptor):
192 adict["signals"][k] = v
193 cls._add_adaptors(adict)
195 def __getattr__(self, name):
196 "Wrap return values"
198 callable = super(DBusClient, self).__getattr__(name)
199 try:
200 adaptor = self._get_adaptor("methods", name)
201 return adaptor.adapt(callable)
202 except KeyError:
203 return callable
205 # properties
206 def __getitem__(self, key):
207 value = super(DBusClient, self).__getitem__(key)
208 try:
209 adaptor = self._get_adaptor("properties", key)
210 return adaptor.adapt(value)
211 except KeyError:
212 return value
214 def __setitem__(self, key, value):
215 try:
216 adaptor = self._get_adaptor("properties", key)
217 value = adaptor.adapt_write(value)
218 except KeyError:
219 pass
220 return super(DBusClient, self).__setitem__(key, value)
223 # signals
224 # overrides a ProxyObject method
225 def _connect_to_signal(self, signame, handler, interface=None, **kwargs):
226 "Wrap signal handler, with arg adaptors"
228 # TODO also demarshal kwargs
229 adaptor = self._get_adaptor("signals", signame)
230 wrap_handler = adaptor.adapt(handler)
231 return self.connect_to_signal(signame, wrap_handler, interface, **kwargs)