#!/usr/bin/env python
#
# properties.py - Python descriptor framework.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""Python descriptor framework.
This module defines the :class:`PropertyBase`, :class:`ListPropertyBase`,
which form the basis for defining class properties; and the
:class:`HasProperties` class, which is intended to be sub-classed by
application code.
.. autosummary::
:nosignatures:
HasProperties
PropertyBase
ListPropertyBase
"""
import weakref
import logging
import warnings
from . import properties_value
from . import bindable
from . import serialise
log = logging.getLogger(__name__)
class _InstanceData:
"""An ``_InstanceData`` object is created for every ``PropertyBase``
object of a ``HasProperties`` instance. It stores references to the
instance and the associated :class:`.PropertyValue` instance.
"""
def __init__(self, instance, propVal):
self.instance = weakref.ref(instance)
self.propVal = propVal
[docs]
class DisabledError(Exception):
"""A ``DisabledError`` is raised when an attempt is made to assign
a value to a disabled property. See the :meth:`PropertyBase.enable`
and :meth:`PropertyBase.disable` methods.
"""
pass
[docs]
class PropertyBase:
"""The base class for properties.
For every ``HasProperties`` instance which has this ``PropertyBase``
object as a property, one ``_InstanceData`` object is created and attached
as an attribute of the ``HasProperties`` object.
One important point to note is that a ``PropertyBase`` object may exist
without being bound to a ``HasProperties`` instance (in which case it will
not create or manage any ``PropertyValue`` instances). This is useful
if you just want validation functionality via the :meth:`validate`,
:meth:`getAttribute` and :meth:`setAttribute` methods, passing in
``None`` for the instance parameter. Nothing else will work properly
though.
Several subclasses are defined in the :mod:`.properties_types` module.
All subclasses should:
- Ensure that the superclass :meth:`__init__` is called.
- Override the :meth:`validate` method to implement any built in
validation rules, ensuring that the the superclass implementation
is called first (see the :class:`.Number` property for an example).
- Override the :meth:`cast` method for implicit casting/conversion logic
(see the :class:`.Boolean` property for an example).
"""
def __init__(self,
default=None,
validateFunc=None,
equalityFunc=None,
required=False,
allowInvalid=True,
**atts):
"""Define a ``PropertyBase`` property.
:param default: Default/initial value.
:param bool required: Boolean determining whether or not this
property must have a value. May alternately
be a function which accepts one parameter,
the owning ``HasProperties`` instance,
and returns ``True`` or ``False``.
:param validateFunc: Custom validation function. Must accept three
parameters: a reference to the ``HasProperties``
instance, the owner of this property; a
dictionary containing the attributes for this
property; and the new property value. Should
return ``True`` if the property value is valid,
``False`` otherwise.
:param equalityFunc: Function for testing equality of two values.
:param allowInvalid: If ``False``, a :exc:`ValueError` will be
raised on all attempts to set this property
to an invalid value. This does not guarantee
that the property value will never be
invalid - see caveats in the
:class:`.PropertyValue` documentation.
:param atts: Type specific attributes used to test
validity - passed to the
:meth:`.PropertyValue.__init__` method as
its ``attributes``.
"""
atts['default'] = default
atts['enabled'] = atts.get('enabled', True)
# A _label is added to this dict by
# HasProperties.__new__ class when
# the first instance of that class
# is created
self._label = {}
self._required = required
self._validateFunc = validateFunc
self._equalityFunc = equalityFunc
self._allowInvalid = allowInvalid
self._defaultAttributes = atts
def __copy__(self):
"""Returns a copy of this :class:`PropertyBase` instance.
NOTE: This has not been rigorously tested.
"""
newProp = type(self)()
newProp.__dict__.update(self.__dict__)
# This is the critical step - make sure
# that the class labels from this instance
# are not copied across to the new instance
newProp._label = {}
# Give the new object an independent
# defaultAttributes dictionary
newProp._defaultAttributes = dict(newProp._defaultAttributes)
return newProp
def _setLabel(self, cls, label):
"""Sets the property label for the given class. A :exc:`RuntimeError`
is raised if a label already exists for the given class.
"""
if cls in self._label:
raise RuntimeError('The {} instance assigned to {}.{} is '
'already present as {}.{}. A PropertyBase '
'instance may only be present on a class '
'once. Declare a new instance.'.format(
self.__class__.__name__,
cls.__name__,
label,
cls.__name__,
self._label[cls]))
self._label[cls] = label
[docs]
def getLabel(self, instance):
"""Returns the property label for the given instance (more
specifically, for the class of the given instance), or ``None``
if no such label has been defined.
"""
if instance is None: return None
return self._label.get(type(instance), None)
[docs]
def enable(self, instance):
"""Enables this property for the given :class:`HasProperties`
instance.
See the :meth:`disable` method for more details.
"""
propVal = self.getPropVal(instance)
propVal.setAttribute('enabled', True)
[docs]
def disable(self, instance):
"""Disables this property for the given :class:`HasProperties`
instance.
An attempt to set the value of a disabled property will result in a
:class:`DisabledError`. This behaviour can be circumvented by dealing
directly with the underlying :class:`.PropertyValue` object.
Changes to the enabled state of a property may be detected by
registering an attribute listener (see
:meth:`.PropertyValue.addAttributeListener`) and listening for changes
to the ``enabled`` attribute.
"""
propVal = self.getPropVal(instance)
propVal.setAttribute('enabled', False)
[docs]
def isEnabled(self, instance):
"""Returns ``True`` if this property is enabled for the given
:class:`HasProperties` instance, ``False`` otherwise.
See the :meth:`disable` method for more details.
"""
propVal = self.getPropVal(instance)
return propVal.getAttribute('enabled')
[docs]
def addListener(self, instance, *args, **kwargs):
"""Register a listener with the ``PropertyValue`` object managed
by this property. See :meth:`.PropertyValue.addListener`.
:param instance: The ``HasProperties`` instance on which the
listener is to be registered.
"""
self._getInstanceData(instance).propVal.addListener(*args, **kwargs)
[docs]
def removeListener(self, instance, name):
"""De-register the named listener from the ``PropertyValue`` object
managed by this property.
"""
instData = self._getInstanceData(instance)
if instData is None: return
else: instData.propVal.removeListener(name)
[docs]
def getConstraint(self, instance, constraint):
"""See :meth:`getAttribute`. """
warnings.warn('getConstraint is deprecated - use getAttribute instead',
category=DeprecationWarning,
stacklevel=2)
return self.getAttribute(instance, constraint)
[docs]
def setConstraint(self, instance, constraint, value):
"""See :meth:`setAttribute`. """
warnings.warn('setConstraint is deprecated - use setAttribute instead',
category=DeprecationWarning,
stacklevel=2)
return self.setAttribute(instance, constraint, value)
[docs]
def getAttribute(self, instance, att, *arg):
"""Returns the value of the named attribute for the specified
``HasProperties`` instance, or the default attribute value if
instance is ``None``. See :meth:`.PropertiesValue.getAttribute`.
"""
instData = self._getInstanceData(instance)
nodefault = len(arg) == 0
if instData is None:
if nodefault: return self._defaultAttributes[att]
else: return self._defaultAttributes.get(att, arg[0])
else:
return instData.propVal.getAttribute(att, *arg)
[docs]
def setAttribute(self, instance, att, value):
"""Sets the value of the named attribute for the specified
``HasProperties`` instance,or the default value if instance
is ``None``. See :meth:`.PropertiesValue.setAttribute`.
"""
instData = self._getInstanceData(instance)
oldVal = self.getAttribute(instance, att, None)
if value == oldVal: return
log.debug('Changing {} attribute on {}: {} = {}'.format(
'' if instance is None else self.getLabel(instance),
'default' if instance is None else 'instance',
att,
value))
if instData is None: self._defaultAttributes[att] = value
else: instData.propVal.setAttribute(att, value)
[docs]
def getPropVal(self, instance):
"""Return the :class:`.PropertyValue` instance(s) for this property,
associated with the given ``HasProperties`` instance, or ``None``
if there is no value for the given instance.
"""
instData = self._getInstanceData(instance)
if instData is None: return None
return instData.propVal
def _getInstanceData(self, instance):
"""Returns the :class:`_InstanceData` object for the given
``HasProperties`` instance, or ``None`` if there is no
``_InstanceData`` for the given instance. An ``_InstanceData``
object, which provides a binding between a ``PropertyBase``
object and a ``HasProperties`` instance, is created by that
``HasProperties`` instance when it is created (see
:meth:`HasProperties.__new__`).
"""
if instance is None: return None
return instance.__dict__.get(self.getLabel(instance), None)
def _makePropVal(self, instance):
"""Creates and returns a ``PropertyValue`` object for the given
``HasProperties`` instance.
"""
default = self._defaultAttributes.get('default', None)
return properties_value.PropertyValue(instance,
name=self.getLabel(instance),
value=default,
castFunc=self.cast,
validateFunc=self.validate,
equalityFunc=self._equalityFunc,
allowInvalid=self._allowInvalid,
**self._defaultAttributes)
[docs]
def validate(self, instance, attributes, value):
"""Called when an attempt is made to set the property value on the
given instance.
Called by ``PropertyValue`` objects when their value changes. The sole
purpose of this method is to determine whether a given value is valid
or invalid; it should not do anything else. In particular, it should
not modify any other property values on the instance, as bad things
will probably happen.
If the given value is invalid, subclass implementations should raise a
:exc:`ValueError` containing a useful message as to why the value is
invalid. Otherwise, they should not return any value. The default
implementation does nothing, unless a custom validate function, and/or
``required=True``, was passed to the constructor. If ``required`` is
``True``, and the value is ``None``, a :exc:`ValueError` is raised. If
a custom validate function was set, it is called and, if it returns
``False``, a :exc:`ValueError` is raised. It may also raise a
:exc:`ValueError` of its own for invalid values.
Subclasses which override this method should therefore call this
superclass implementation in addition to performing their own
validation.
:param instance: The ``HasProperties`` instance which
owns this ``PropertyBase`` instance,
or ``None`` for an unbound property value.
:param dict attributes: Attributes of the ``PropertyValue`` object,
which are used to store type-specific
attributes for ``PropertyBase`` subclasses.
:param value: The value to be validated.
"""
# a value is required
if (self._required is not None) and (value is None):
# required may either be a boolean value
if isinstance(self._required, bool):
if self._required:
raise ValueError('A value is required')
# or a function
elif self._required(instance):
raise ValueError('A value is required')
# a custom validation function has been provided
if self._validateFunc is not None:
if not self._validateFunc(instance, attributes, value):
raise ValueError('Value does not meet custom validation rules')
[docs]
def cast(self, instance, attributes, value):
"""This method is called when a value is assigned to this
``PropertyBase`` instance through a ``HasProperties`` attribute
access. The default implementaton just returns the given value.
Subclasses may override this method to perform any required implicit
casting or conversion rules.
"""
return value
[docs]
def revalidate(self, instance):
"""Forces validation of this property value, for the current instance.
This will result in any registered listeners being notified, but only
if the validity of the value has changed.
"""
propVal = self.getPropVal(instance)
propVal.revalidate()
def __get__(self, instance, owner):
"""If called on the ``HasProperties`` class, and not on an instance,
returns this ``PropertyBase`` object. Otherwise, returns the value
contained in the ``PropertyValue`` object which is attached to the
instance.
"""
if instance is None:
return self
instData = self._getInstanceData(instance)
return instData.propVal.get()
def __set__(self, instance, value):
"""Set the value of this property, as attached to the given instance,
to the given value.
"""
propVal = self.getPropVal(instance)
if not propVal.getAttribute('enabled'):
raise DisabledError('Property {}.{} is disabled'.format(
instance.__class__.__name__,
self.getLabel(instance)))
propVal.set(value)
[docs]
class ListPropertyBase(PropertyBase):
"""A :class:`PropertyBase` for properties which encapsulate more than
one value.
"""
def __init__(self, listType, **kwargs):
"""Define a ``ListPropertyBase`` property.
:param listType: An unbound ``PropertyBase`` instance, defining
the type of value allowed in the list. This is
optional; if not provided, values of any type will be
allowed in the list, but no validation or casting
will be performed.
"""
PropertyBase.__init__(self, **kwargs)
self._listType = listType
[docs]
def getListType(self):
"""Returns a reference to the ``PropertyBase`` instance which defines
the value types allowsed in this ``ListPropertyBase``. This may be
``None``.
"""
return self._listType
def _makePropVal(self, instance):
"""Creates and returns a :class:`.PropertyValueList` object to be
associated with the given ``HasProperties`` instance.
"""
if self._listType is not None:
itemCastFunc = self._listType.cast
itemValidateFunc = self._listType.validate
itemEqualityFunc = self._listType._equalityFunc
itemAllowInvalid = self._listType._allowInvalid
itemAttributes = self._listType._defaultAttributes
else:
itemCastFunc = None
itemValidateFunc = None
itemEqualityFunc = None
itemAllowInvalid = True
itemAttributes = None
default = self._defaultAttributes.get('default', None)
return properties_value.PropertyValueList(
instance,
name=self.getLabel(instance),
values=default,
itemCastFunc=itemCastFunc,
itemValidateFunc=itemValidateFunc,
itemEqualityFunc=itemEqualityFunc,
listValidateFunc=self.validate,
itemAllowInvalid=itemAllowInvalid,
listAttributes=self._defaultAttributes,
itemAttributes=itemAttributes)
[docs]
def enableItem(self, instance, index):
"""Enables the item at the given ``index``. See :meth:`disableItem`.
"""
self.getPropValList(instance)[index].setAttribute('enabled', True)
[docs]
def disableItem(self, instance, index):
"""Disables the item at the given ``index``. See
:meth:`PropertyBase.disable`.
.. note:: ``ListPropertyBase`` items cannot actually be disabled
at this point in time - all this method does is set the
``'enabled'`` attribute of the item to ``False``.
"""
self.getPropValList(instance)[index].setAttribute('enabled', False)
[docs]
def getPropValList(self, instance):
"""Returns the list of ``PropertyValue`` objects which represent the
items stored in this list.
Note that this is a list of ``PropertyValue`` instances; it is not the
``PropertyValueList`` instance. The latter can be accessed through
the owning ``HasProperties`` instance with a simple attribute access.
"""
propVal = self.getPropVal(instance)
if propVal is not None: return propVal.getPropertyValueList()
else: return None
[docs]
class PropertyOwner(type):
"""Deprecated. """
def __new__(cls, name, bases, attrs):
warnings.warn('PropertyOwner is deprecated and is no longer used',
category=DeprecationWarning,
stacklevel=2)
return super(PropertyOwner, cls).__new__(cls, name, bases, attrs)
[docs]
class HasProperties:
"""Base class for classes which contain ``PropertyBase`` instances. All
classes which contain ``PropertyBase`` objects must subclass this
class.
.. note:: ``HasProperties`` is also available via an alias called
:attr:`HasProps`.
"""
def __new__(cls, *args, **kwargs):
"""Here we create a new ``HasProperties`` instance, and loop
through all of its ``PropertyBase`` properties to ensure that
they are initialised.
"""
instance = super(HasProperties, cls).__new__(cls)
# By default, when a property changes,
# all other properties are not validated.
# This behaviour can be changed by passing
# validateOnChange=True to __init__.
instance.__validateOnChange = False
# Helper to return *all* attributes of a
# class, including those of its base classes
def allAttrs(cls):
atts = dict(cls.__dict__.items())
if hasattr(cls, '__bases__'):
for base in cls.__bases__:
batts = dict(allAttrs(base))
batts = {n : v for n, v in batts.items() if n not in atts}
atts.update(batts)
return atts
# Add each class level PropertyBase
# object as a property of the new
# HasProperties instance
for propName, propObj in allAttrs(cls).items():
if not isinstance(propObj, PropertyBase):
continue
# A PropertyBase instance maintains labels
# for each class, so this will only need to
# be done for the first instance that gets
# created.
if propObj.getLabel(instance) is None:
propObj._setLabel(cls, propName)
instance.addProperty(propName, propObj)
return instance
def __init__(self, validateOnChange=False, **kwargs):
"""Create a ``HasProperties`` instance.
If no arguments need to be passed in, ``HasProperties.__init__`` does
not need to be called.
:arg validateOnChange: Defaults to ``False``. If set to ``True``,
whenever any property value is changed,
the value of *every* property is re-validated.
This functionality is accomplished by using
the *preNotify* listener on all
``PropertyValue`` instances - see the
:meth:`.PropertyValue.setPreNotifyFunction`
method.
:arg kwargs: All other arguments are assumed to be
``name=value`` pairs, containing initial values
for the properties defined on this
``HasProperties`` instance.
.. note:: The ``validateOnChange`` argument warrants some explanation.
The point of validating all other properties when one property
changes is to handle the scenario where the validity of one
property is dependent upon the values of other properties.
Currently, the only option is to enable this globally;
i.e. whenever the value of any property changes, all other
properties are validated.
At some stage, I may allow more fine grained control;
e.g. validation could only occur when specific properties change,
and/or only specific properties are validated. This should be
fairly straightforward - we could just maintain a dict of
``{propName : [propNames ..]}`` mappings, where the key is the name
of a property that should trigger validation, and the value is a
list of properties that need to be validated when that property
changes.
"""
self.__validateOnChange = validateOnChange
# The prenotify function is added to new properties
# in the addProperty method, but not for properties
# defined at the class level (because the
# __validateOnChange attribute is initially set to
# false in __new__). So here we make sure that the
# prenotify is set if needed.
if validateOnChange:
propNames, props = self.getAllProperties()
for prop, propName in zip(propNames, props):
propVal = prop.getPropVal(self)
propVal.setPreNotifyFunction(self.__valueChanged)
# Initial values
for name, value in kwargs.items():
setattr(self, name, value)
def __copy__(self):
"""Default copy operator.
Creates a new instance of this type, and copies all property values
across.
If a no-arguments constructor is not available, an error will
be raised.
Subclasses which require arguments on initialisation, or which have
more complex copy semantics, will need to implement their own
``__copy__`` operator if this one does not suffice.
"""
copy = type(self)()
# TODO Is this going to crash for List properties?
# If it does, make it not crash.
for propName in self.getAllProperties()[0]:
setattr(copy, propName, getattr(self, propName))
return copy
[docs]
def bindProps(self, *args, **kwargs):
"""See :func:`.bindable.bindProps`. """
bindable.bindProps(self, *args, **kwargs)
[docs]
def unbindProps(self, *args, **kwargs):
"""See :func:`.bindable.unbindProps`. """
bindable.unbindProps(self, *args, **kwargs)
[docs]
def bind(self, *args, **kwargs):
"""Alias for :meth:`bindProps`. """
self.bindProps(*args, **kwargs)
[docs]
def unbind(self, *args, **kwargs):
"""Alias for :meth:`unbindProps`. """
self.unbindProps(*args, **kwargs)
[docs]
def isBound(self, *args, **kwargs):
"""See :func:`.bindable.isBound`. """
bindable.isBound(self, *args, **kwargs)
[docs]
def addProperty(self, propName, propObj):
"""Add the given `PropertyBase`` instance as an attribute of this
``HasProperties`` instance. """
if not isinstance(propObj, PropertyBase):
raise ValueError('propObj must be a PropertyBase instance')
# If this property does not exist on the class,
# add it. This is a bit hacky, as the labels
# for all the properties that exist in the class
# definition are handled by the metaclass. What's
# stopping me from throwing out the metaclass,
# and doing everything in HasProps.__new__, and
# this method? Related - is there a reason why
# PropertyBase labels are tied to the HasProps
# class, rather than to the instance?
if not hasattr(self.__class__, propName):
setattr( self.__class__, propName, propObj)
propObj._setLabel(self.__class__, propName)
# Create a PropertyValue and an _InstanceData
# object, which bind the PropertyBase object
# to this HasProperties instance.
propVal = propObj._makePropVal(self)
instData = _InstanceData(self, propVal)
log.debug('Adding property to {}.{} [{}] ({})'.format(
self.__class__.__name__,
propName,
id(self),
propObj.__class__.__name__))
# Store the _InstanceData object
# on this instance itself
self.__dict__[propName] = instData
# validate other properties when
# this property changes - does
# nothing if validation is enabled
if self.__validateOnChange:
propVal.setPreNotifyFunction(self.__valueChanged)
def __valueChanged(self, ctx, value, valid, name):
"""This method is only called if ``validateOnChange`` was set
to true in :meth:`__init__`. It is registered as the ``preNotify``
listener on all ``PropertyValue`` instances. See the note in
:meth:`__init__`.
"""
if not self.__validateOnChange:
return
# Force validation for all other properties of the instance, and
# notification of their registered listeners, This is done because the
# validity of some properties may be dependent upon the values of this
# one. So when the value of this property changes, it may have changed
# the validity of another property, meaning that the listeners of the
# latter property need to be notified of this change in validity.
log.debug('Revalidating all instance properties '
'(due to {} change)'.format(name))
propNames, props = self.getAllProperties()
for propName, prop in zip(propNames, props):
if propName is not name:
prop.revalidate(self)
[docs]
@classmethod
def getAllProperties(cls):
"""Returns two lists, the first containing the names of all properties
of this object, and the second containing the corresponding
``PropertyBase`` objects.
Properties which have a name beginning with an underscore are not
returned by this method
"""
propNames = []
props = []
for attName in dir(cls):
att = getattr(cls, attName)
if isinstance(att, PropertyBase) and (not attName.startswith('_')):
propNames.append(attName)
props .append(att)
return propNames, props
[docs]
@classmethod
def getProp(cls, propName):
"""Return the ``PropertyBase`` object for the given property."""
return getattr(cls, propName)
[docs]
def getPropVal(self, propName):
"""Return the ``PropertyValue`` object(s) for the given property.
"""
return self.getProp(propName).getPropVal(self)
[docs]
def getLastValue(self, propName):
"""Returns the most recent value of the specified property before its
current one.
See the :meth:`.PropertyValue.getLast` method.
"""
return self.getPropVal(propName).getLast()
[docs]
def serialise(self, propName):
"""Returns a string containing the value of the named property,
serialsied via the :mod:`.serialise` module.
"""
return serialise.serialise(self, propName)
[docs]
def deserialise(self, propName, value):
"""Deserialises the given value (assumed to have been serialised
via the :mod:`.serialise` module), and sets the named property
to the deserialised value.
"""
value = serialise.deserialise(self, propName, value)
setattr(self, propName, value)
[docs]
def enableNotification(self, propName, *args, **kwargs):
"""Enables notification of listeners on the given property.
See the :meth:`.PropertyValue.enableNotification` method.
"""
self.getPropVal(propName).enableNotification(*args, **kwargs)
[docs]
def disableNotification(self, propName, *args, **kwargs):
"""Disables notification of listeners on the given property.
See the :meth:`.PropertyValue.disableNotification` method.
"""
self.getPropVal(propName).disableNotification(*args, **kwargs)
[docs]
def getNotificationState(self, propName):
"""Returns the notification state of the given property.
See the :meth:`.PropertyValue.getNotificationState` method.
"""
return self.getPropVal(propName).getNotificationState()
[docs]
def setNotificationState(self, propName, value):
"""Sets the notification state of the given property.
See the :meth:`.PropertyValue.setNotificationState` method.
"""
self.getPropVal(propName).setNotificationState(value)
[docs]
def enableAllNotification(self):
"""Enables notification of listeners on all properties."""
propNames, props = self.getAllProperties()
for propName in propNames:
self.enableNotification(propName)
[docs]
def disableAllNotification(self):
"""Disables notification of listeners on all properties."""
propNames, props = self.getAllProperties()
for propName in propNames:
self.disableNotification(propName)
[docs]
def enableProperty(self, propName, index=None):
"""Enables the given property.
If an ``index`` is provided, it is assumed that the property is a
list property (a :class:`ListPropertyBase`).
See :meth:`PropertyBase.enable` and
:meth:`ListPropertyBase.enableItem`.
"""
if index is not None: self.getProp(propName).enableItem(self, index)
else: self.getProp(propName).enable( self)
[docs]
def disableProperty(self, propName, index=None):
"""Disables the given property - see :meth:`PropertyBase.disable`.
If an ``index`` is provided, it is assumed that the property is a
list property (a :class:`ListPropertyBase`).
See :meth:`PropertyBase.disable` and
:meth:`ListPropertyBase.disableItem`.
"""
if index is not None: self.getProp(propName).disableItem(self, index)
else: self.getProp(propName).disable( self)
[docs]
def propertyIsEnabled(self, propName):
"""Returns the enabled state of the given property - see
:meth:`PropertyBase.isEnabled`.
"""
return self.getProp(propName).isEnabled(self)
[docs]
def propNotify(self, propName):
"""Force notification of listeners on the given property. This will
have no effect if notification for the property is disabled.
See the :meth:`.PropertyValue.propNotify` method.
"""
self.getPropVal(propName).propNotify()
[docs]
def getConstraint(self, propName, constraint):
"""See :meth:`getAttribute`. """
warnings.warn('getConstraint is deprecated - use getAttribute instead',
category=DeprecationWarning,
stacklevel=2)
return self.getAttribute(propName, constraint)
[docs]
def setConstraint(self, propName, constraint, value):
"""See :meth:`setAttribute`. """
warnings.warn('setConstraint is deprecated - use setAttribute instead',
category=DeprecationWarning,
stacklevel=2)
return self.setAttribute(propName, constraint, value)
[docs]
def getAttribute(self, propName, *args):
"""Convenience method, returns the value of the named attributes for
the named property. See :meth:`PropertyBase.getAttribute`.
"""
return self.getProp(propName).getAttribute(self, *args)
[docs]
def setAttribute(self, propName, *args):
"""Convenience method, sets the value of the named attribute for
the named property. See :meth:`PropertyBase.setAttribute`.
"""
return self.getProp(propName).setAttribute(self, *args)
[docs]
def setatt(self, *args, **kwargs):
"""Alias for :meth:`setAttribute`. """
return self.setAttribute(*args, **kwargs)
[docs]
def getatt(self, *args, **kwargs):
"""Alias for :meth:`getAttribute`. """
return self.getAttribute(*args, **kwargs)
[docs]
def listen(self, *args, **kwargs):
"""Alias for :meth:`addListener`. """
self.addListener(*args, **kwargs)
[docs]
def ilisten(self, *args, **kwargs):
"""Calls ``addListener(immediate=True)``. """
self.addListener(*args, immediate=True, **kwargs)
[docs]
def wlisten(self, *args, **kwargs):
"""Calls ``addListener(weak=False)``. """
self.addListener(*args, weak=False, **kwargs)
[docs]
def remove(self, *args, **kwargs):
"""Alias for :meth:`removeListener`. """
self.removeListener(*args, **kwargs)
[docs]
def addListener(self, propName, *args, **kwargs):
"""Convenience method, adds the specified listener to the specified
property. See :meth:`PropertyValue.addListener`.
"""
self.getPropVal(propName).addListener(*args, **kwargs)
[docs]
def removeListener(self, propName, listenerName):
"""Convenience method, removes the specified listener from the
specified property. See :meth:`PropertyValue.removeListener`.
"""
self.getPropVal(propName).removeListener(listenerName)
[docs]
def enableListener(self, propName, name):
"""(Re-)Enables the listener on the specified property with the
specified ``name``.
"""
self.getPropVal(propName).enableListener(name)
[docs]
def disableListener(self, propName, name):
"""Disables the listener on the specified property with the specified
``name``, but does not remove it from the list of listeners.
"""
self.getPropVal(propName).disableListener(name)
[docs]
def getListenerState(self, propName, name):
"""See :meth:`.PropertyValue.getListenerState`. """
return self.getPropVal(propName).getListenerState(name)
[docs]
def setListenerState(self, propName, name, state):
"""See :meth:`.PropertyValue.setListenerState`. """
self.getPropVal(propName).setListenerState(name, state)
[docs]
def hasListener(self, propName, name):
"""Returns ``True`` if a listener is registered on the given property,
``False`` otherwise.
"""
return self.getPropVal(propName).hasListener(name)
[docs]
def addGlobalListener(self, *args, **kwargs):
"""Registers the given listener so that it will be notified of
changes to any of the properties of this HasProperties instance.
"""
propNames, props = self.getAllProperties()
for propName in propNames:
self.getPropVal(propName).addListener(*args, **kwargs)
[docs]
def removeGlobalListener(self, listenerName):
"""De-registers the specified global listener (see
:meth:`addGlobalListener`).
"""
propNames, props = self.getAllProperties()
for propName in propNames:
self.getPropVal(propName).removeListener(listenerName)
[docs]
def isValid(self, propName):
"""Returns ``True`` if the current value of the specified property is
valid, ``False`` otherwise.
"""
prop = self.getProp(propName)
propVal = prop.getPropVal(self)
try: prop.validate(self, propVal.getAttributes(), propVal.get())
except ValueError: return False
return True
[docs]
def validateAll(self):
"""Validates all of the properties of this :class:`HasProperties`
object. A list of tuples is returned, with each tuple containing
a property name, and an associated error string. The error string
is a message about the property which failed validation. If all
property values are valid, the returned list will be empty.
"""
names, props = self.getAllProperties()
errors = []
for name, prop in zip(names, props):
propVal = prop.getPropVal(self)
try:
prop.validate(self, propVal.getAttributes(), propVal.get())
except ValueError as e:
errors.append((name, e))
return errors
def __str__(self):
"""Returns a multi-line string containing the names and values of
all the properties of this object.
"""
clsname = self.__class__.__name__
propNames, props = self.getAllProperties()
if len(propNames) == 0:
return clsname
propVals = []
for propName in propNames:
val = getattr(self, propName)
if val is self: propVals.append('<self>')
else: propVals.append(str(val))
maxNameLength = max(map(len, propNames))
lines = [clsname]
for propName, propVal in zip(propNames, propVals):
fmtStr = ' {:>' + str(maxNameLength) + '} = {}'
lines.append(fmtStr.format(propName, propVal))
return '\n'.join(lines)
HasProps = HasProperties
"""``HasProps`` is simply an alias for :class:`HasProperties`. """
Props = HasProperties
"""``Props`` is simply an alias for :class:`HasProperties`. """