release
[pygobject.git] / gi / _propertyhelper.py
blobe81de2ed32dbef026e6b194d968137e164f4ca44
1 # -*- Mode: Python; py-indent-offset: 4 -*-
2 # pygobject - Python bindings for the GObject library
3 # Copyright (C) 2007 Johan Dahlin
5 # gi/_propertyhelper.py: GObject property wrapper/helper
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 import sys
21 import traceback
23 from . import _gi
25 from ._constants import \
26 TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, \
27 TYPE_BOOLEAN, TYPE_INT, TYPE_UINT, TYPE_LONG, \
28 TYPE_ULONG, TYPE_INT64, TYPE_UINT64, TYPE_ENUM, TYPE_FLAGS, \
29 TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, \
30 TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \
31 TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT
33 G_MAXFLOAT = _gi.G_MAXFLOAT
34 G_MAXDOUBLE = _gi.G_MAXDOUBLE
35 G_MININT = _gi.G_MININT
36 G_MAXINT = _gi.G_MAXINT
37 G_MAXUINT = _gi.G_MAXUINT
38 G_MINLONG = _gi.G_MINLONG
39 G_MAXLONG = _gi.G_MAXLONG
40 G_MAXULONG = _gi.G_MAXULONG
42 if sys.version_info >= (3, 0):
43 _basestring = str
44 _long = int
45 else:
46 _basestring = basestring
47 _long = long
50 class Property(object):
51 """Creates a new Property which when used in conjunction with
52 GObject subclass will create a Python property accessor for the
53 GObject ParamSpec.
55 :param callable getter:
56 getter to get the value of the property
57 :param callable setter:
58 setter to set the value of the property
59 :param type type:
60 type of property
61 :param default:
62 default value, must match the property type.
63 :param str nick:
64 short description
65 :param str blurb:
66 long description
67 :param GObject.ParamFlags flags:
68 parameter flags
69 :keyword minimum:
70 minimum allowed value (int, float, long only)
71 :keyword maximum:
72 maximum allowed value (int, float, long only)
74 .. code-block:: python
76 class MyObject(GObject.Object):
77 prop = GObject.Property(type=str)
79 obj = MyObject()
80 obj.prop = 'value'
82 obj.prop # now is 'value'
84 The API is similar to the builtin :py:func:`property`:
86 .. code-block:: python
88 class AnotherObject(GObject.Object):
89 value = 0
91 @GObject.Property
92 def prop(self):
93 'Read only property.'
94 return 1
96 @GObject.Property(type=int)
97 def propInt(self):
98 'Read-write integer property.'
99 return self.value
101 @propInt.setter
102 def propInt(self, value):
103 self.value = value
105 _type_from_pytype_lookup = {
106 # Put long_ first in case long_ and int are the same so int clobbers long_
107 _long: TYPE_LONG,
108 int: TYPE_INT,
109 bool: TYPE_BOOLEAN,
110 float: TYPE_DOUBLE,
111 str: TYPE_STRING,
112 object: TYPE_PYOBJECT,
115 _min_value_lookup = {
116 TYPE_UINT: 0,
117 TYPE_ULONG: 0,
118 TYPE_UINT64: 0,
119 # Remember that G_MINFLOAT and G_MINDOUBLE are something different.
120 TYPE_FLOAT: -G_MAXFLOAT,
121 TYPE_DOUBLE: -G_MAXDOUBLE,
122 TYPE_INT: G_MININT,
123 TYPE_LONG: G_MINLONG,
124 TYPE_INT64: -2 ** 63,
127 _max_value_lookup = {
128 TYPE_UINT: G_MAXUINT,
129 TYPE_ULONG: G_MAXULONG,
130 TYPE_INT64: 2 ** 63 - 1,
131 TYPE_UINT64: 2 ** 64 - 1,
132 TYPE_FLOAT: G_MAXFLOAT,
133 TYPE_DOUBLE: G_MAXDOUBLE,
134 TYPE_INT: G_MAXINT,
135 TYPE_LONG: G_MAXLONG,
138 _default_lookup = {
139 TYPE_INT: 0,
140 TYPE_UINT: 0,
141 TYPE_LONG: 0,
142 TYPE_ULONG: 0,
143 TYPE_INT64: 0,
144 TYPE_UINT64: 0,
145 TYPE_STRING: '',
146 TYPE_FLOAT: 0.0,
147 TYPE_DOUBLE: 0.0,
150 class __metaclass__(type):
151 def __repr__(self):
152 return "<class 'GObject.Property'>"
154 def __init__(self, getter=None, setter=None, type=None, default=None,
155 nick='', blurb='', flags=_gi.PARAM_READWRITE,
156 minimum=None, maximum=None):
157 self.name = None
159 if type is None:
160 type = object
161 self.type = self._type_from_python(type)
162 self.default = self._get_default(default)
163 self._check_default()
165 if not isinstance(nick, _basestring):
166 raise TypeError("nick must be a string")
167 self.nick = nick
169 if not isinstance(blurb, _basestring):
170 raise TypeError("blurb must be a string")
171 self.blurb = blurb
172 # Always clobber __doc__ with blurb even if blurb is empty because
173 # we don't want the lengthy Property class documentation showing up
174 # on instances.
175 self.__doc__ = blurb
176 self.flags = flags
178 # Call after setting blurb for potential __doc__ usage.
179 if getter and not setter:
180 setter = self._readonly_setter
181 elif setter and not getter:
182 getter = self._writeonly_getter
183 elif not setter and not getter:
184 getter = self._default_getter
185 setter = self._default_setter
186 self.getter(getter)
187 # do not call self.setter() here, as this defines the property name
188 # already
189 self.fset = setter
191 if minimum is not None:
192 if minimum < self._get_minimum():
193 raise TypeError(
194 "Minimum for type %s cannot be lower than %d" %
195 (self.type, self._get_minimum()))
196 else:
197 minimum = self._get_minimum()
198 self.minimum = minimum
199 if maximum is not None:
200 if maximum > self._get_maximum():
201 raise TypeError(
202 "Maximum for type %s cannot be higher than %d" %
203 (self.type, self._get_maximum()))
204 else:
205 maximum = self._get_maximum()
206 self.maximum = maximum
208 self._exc = None
210 def __repr__(self):
211 return '<GObject Property %s (%s)>' % (
212 self.name or '(uninitialized)',
213 _gi.type_name(self.type))
215 def __get__(self, instance, klass):
216 if instance is None:
217 return self
219 self._exc = None
221 # Simply return the result of fget directly, no need to go through GObject.
222 # See: https://bugzilla.gnome.org/show_bug.cgi?id=723872
223 # We catch and print any exception occurring within the fget for compatibility
224 # prior to the fast path addition from bug 723872, this should eventually
225 # be removed and exceptions raised directly to the caller as in:
226 # https://bugzilla.gnome.org/show_bug.cgi?id=575652
227 try:
228 value = self.fget(instance)
229 except Exception:
230 traceback.print_exc()
231 value = None
233 if self._exc:
234 exc = self._exc
235 self._exc = None
236 raise exc
238 return value
240 def __set__(self, instance, value):
241 if instance is None:
242 raise TypeError
244 self._exc = None
245 instance.set_property(self.name, value)
246 if self._exc:
247 exc = self._exc
248 self._exc = None
249 raise exc
251 def __call__(self, fget):
252 """Allows application of the getter along with init arguments."""
253 return self.getter(fget)
255 def getter(self, fget):
256 """Set the getter function to fget. For use as a decorator."""
257 if fget.__doc__:
258 # Always clobber docstring and blurb with the getter docstring.
259 self.blurb = fget.__doc__
260 self.__doc__ = fget.__doc__
261 self.fget = fget
262 return self
264 def setter(self, fset):
265 """Set the setter function to fset. For use as a decorator."""
266 self.fset = fset
267 # with a setter decorator, we must ignore the name of the method in
268 # install_properties, as this does not need to be a valid property name
269 # and does not define the property name. So set the name here.
270 if not self.name:
271 self.name = self.fget.__name__
272 return self
274 def _type_from_python(self, type_):
275 if type_ in self._type_from_pytype_lookup:
276 return self._type_from_pytype_lookup[type_]
277 elif (isinstance(type_, type) and
278 issubclass(type_, (_gi.GObject,
279 _gi.GEnum,
280 _gi.GFlags,
281 _gi.GBoxed,
282 _gi.GInterface))):
283 return type_.__gtype__
284 elif type_ in (TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR,
285 TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG,
286 TYPE_ULONG, TYPE_INT64, TYPE_UINT64,
287 TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER,
288 TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING,
289 TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT):
290 return type_
291 else:
292 raise TypeError("Unsupported type: %r" % (type_,))
294 def _get_default(self, default):
295 if default is not None:
296 return default
297 return self._default_lookup.get(self.type, None)
299 def _check_default(self):
300 ptype = self.type
301 default = self.default
302 if (ptype == TYPE_BOOLEAN and (default not in (True, False))):
303 raise TypeError(
304 "default must be True or False, not %r" % (default,))
305 elif ptype == TYPE_PYOBJECT:
306 if default is not None:
307 raise TypeError("object types does not have default values")
308 elif ptype == TYPE_GTYPE:
309 if default is not None:
310 raise TypeError("GType types does not have default values")
311 elif _gi.type_is_a(ptype, TYPE_ENUM):
312 if default is None:
313 raise TypeError("enum properties needs a default value")
314 elif not _gi.type_is_a(default, ptype):
315 raise TypeError("enum value %s must be an instance of %r" %
316 (default, ptype))
317 elif _gi.type_is_a(ptype, TYPE_FLAGS):
318 if not _gi.type_is_a(default, ptype):
319 raise TypeError("flags value %s must be an instance of %r" %
320 (default, ptype))
321 elif _gi.type_is_a(ptype, TYPE_STRV) and default is not None:
322 if not isinstance(default, list):
323 raise TypeError("Strv value %s must be a list" % repr(default))
324 for val in default:
325 if type(val) not in (str, bytes):
326 raise TypeError("Strv value %s must contain only strings" % str(default))
327 elif _gi.type_is_a(ptype, TYPE_VARIANT) and default is not None:
328 if not hasattr(default, '__gtype__') or not _gi.type_is_a(default, TYPE_VARIANT):
329 raise TypeError("variant value %s must be an instance of %r" %
330 (default, ptype))
332 def _get_minimum(self):
333 return self._min_value_lookup.get(self.type, None)
335 def _get_maximum(self):
336 return self._max_value_lookup.get(self.type, None)
339 # Getter and Setter
342 def _default_setter(self, instance, value):
343 setattr(instance, '_property_helper_' + self.name, value)
345 def _default_getter(self, instance):
346 return getattr(instance, '_property_helper_' + self.name, self.default)
348 def _readonly_setter(self, instance, value):
349 self._exc = TypeError("%s property of %s is read-only" % (
350 self.name, type(instance).__name__))
352 def _writeonly_getter(self, instance):
353 self._exc = TypeError("%s property of %s is write-only" % (
354 self.name, type(instance).__name__))
357 # Public API
360 def get_pspec_args(self):
361 ptype = self.type
362 if ptype in (TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG,
363 TYPE_INT64, TYPE_UINT64, TYPE_FLOAT, TYPE_DOUBLE):
364 args = self.minimum, self.maximum, self.default
365 elif (ptype == TYPE_STRING or ptype == TYPE_BOOLEAN or
366 ptype.is_a(TYPE_ENUM) or ptype.is_a(TYPE_FLAGS) or
367 ptype.is_a(TYPE_VARIANT)):
368 args = (self.default,)
369 elif ptype in (TYPE_PYOBJECT, TYPE_GTYPE):
370 args = ()
371 elif ptype.is_a(TYPE_OBJECT) or ptype.is_a(TYPE_BOXED):
372 args = ()
373 else:
374 raise NotImplementedError(ptype)
376 return (self.type, self.nick, self.blurb) + args + (self.flags,)
379 def install_properties(cls):
381 Scans the given class for instances of Property and merges them
382 into the classes __gproperties__ dict if it exists or adds it if not.
384 gproperties = cls.__dict__.get('__gproperties__', {})
386 props = []
387 for name, prop in cls.__dict__.items():
388 if isinstance(prop, Property): # not same as the built-in
389 # if a property was defined with a decorator, it may already have
390 # a name; if it was defined with an assignment (prop = Property(...))
391 # we set the property's name to the member name
392 if not prop.name:
393 prop.name = name
394 # we will encounter the same property multiple times in case of
395 # custom setter methods
396 if prop.name in gproperties:
397 if gproperties[prop.name] == prop.get_pspec_args():
398 continue
399 raise ValueError('Property %s was already found in __gproperties__' % prop.name)
400 gproperties[prop.name] = prop.get_pspec_args()
401 props.append(prop)
403 if not props:
404 return
406 cls.__gproperties__ = gproperties
408 if 'do_get_property' in cls.__dict__ or 'do_set_property' in cls.__dict__:
409 for prop in props:
410 if prop.fget != prop._default_getter or prop.fset != prop._default_setter:
411 raise TypeError(
412 "GObject subclass %r defines do_get/set_property"
413 " and it also uses a property with a custom setter"
414 " or getter. This is not allowed" %
415 (cls.__name__,))
417 def obj_get_property(self, pspec):
418 name = pspec.name.replace('-', '_')
419 return getattr(self, name, None)
420 cls.do_get_property = obj_get_property
422 def obj_set_property(self, pspec, value):
423 name = pspec.name.replace('-', '_')
424 prop = getattr(cls, name, None)
425 if prop:
426 prop.fset(self, value)
427 cls.do_set_property = obj_set_property