Picon
Gravatar

Re: [Traits] dirty trait metadata

Prabhu Ramachandran wrote:
> This automatically builds the shadow _x trait and always calls back 
> _x_changed.  The ShadowProperty can be made smarter so it optionally 
> checks to see if the value changed and only then fire the handler but 
> thats easy to do.  I'll likely do it.  I think this is a common enough 

Now done with docs etc.  I'll check it in for the time being into mayavi 
somewhere (maybe e.mayavi.core.traits).

cheers,
prabhu

from enthought.traits.api import TraitType
from enthought.traits.traits import trait_cast
import numpy

##########################################################################
# `ShadowProperty` trait type.
##########################################################################
class ShadowProperty(TraitType):

    # Not really necessary but specifies the attribute up front.
    trait_type = None

    # Call the notifiers smartly only when the value has really changed.
    # If this is set to False, the notification will always occur.
    smart_notify = True

    def __init__(self, trait_type, **metadata):
        """Defines a shadow property trait that is best explained by
        example::

            class Thing(HasTraits):
                x = ShadowProperty(Float, smart_notify=False)
                def _x_changed(self, value):
                    print value

        In this example, the actual value of the property (`x`) will be
        stored in `_x` and `_x_changed` will be called regardless
        whether the value actually changed or not.  If `smart_notify` is
        set to `True` then the handler is called only if the value has
        actually changed.

        Note that the validation uses the validation of the specified
        `trait_type` parameter.
        """
        self.trait_type = trait_cast(trait_type)
        if 'smart_notify' in metadata:
            self.smart_notify = metadata.pop('smart_notify')
        super(ShadowProperty, self).__init__(**metadata)

    def validate(self, object, name, value):
        """Validates that a specified value is valid for this trait.
        """
        trt = self.trait_type
        if trt is not None and hasattr(trt, 'validate'):
            value = trt.validate(object, name, value)
        return value

    def get(self, object, name):
        """Get the value of the trait."""
        shadow = self._get_shadow(name)
        d = object.__dict__
        if shadow in d:
            return d[shadow]
        else:
            return None

    def set(self, object, name, value):
        """Set the value of the trait."""
        old = self.get(object, name)
        shadow = self._get_shadow(name)
        object.__dict__[shadow] = value
        # Fire a trait property changed.
        fire = True
        if self.smart_notify:
            if old is value:
                fire = False
        if fire:
            object.trait_property_changed(name, old, value)

    def _get_shadow(self, name):
        """Get the shadow attribute name to use."""
        return '_' + name

# -------------- test --------------------
from enthought.traits.api import HasTraits, Either, Array, Any, TraitError
import unittest

ArrayOrNone = Either(None, Array)
class DataNotSmart(HasTraits):
    x = ShadowProperty(ArrayOrNone, smart_notify=False)
    # Test attribute.
    _test = Any
    def _x_changed(self, value):
        self._test = value.copy()

class DataSmart(HasTraits):
    x = ShadowProperty(ArrayOrNone, smart_notify=True)
    # Test attribute.
    _test = Any
    def _x_changed(self, value):
        self._test = value.copy()

class TestShadowProperty(unittest.TestCase):
    def test_shadow_property_smart(self):
        "Test if the shadow property trait type works correctly."
        x = numpy.linspace(0, 1)
        d = DataSmart(x=x)
        self.assertEqual(numpy.all(d.x == x), True)
        self.assertEqual(numpy.all(d._x == x), True)
        self.assertEqual(numpy.all(d._test == x), True)
        old = x.copy()
        x *= 2
        d.x = x
        self.assertEqual(numpy.all(d.x == x), True)
        self.assertEqual(numpy.all(d._x == x), True)
        # Notifier shouldn't be called.
        self.assertEqual(numpy.all(d._test == old), True)

    def test_shadow_property_not_smart(self):
        "Test if the shadow property trait type works correctly."
        x = numpy.linspace(0, 1)
        d = DataNotSmart(x=x)
        self.assertEqual(numpy.all(d.x == x), True)
        self.assertEqual(numpy.all(d._x == x), True)
        self.assertEqual(numpy.all(d._test == x), True)
        x *= 2
        d.x = x
        self.assertEqual(numpy.all(d.x == x), True)
        self.assertEqual(numpy.all(d._x == x), True)
        self.assertEqual(numpy.all(d._test == x), True)

    def test_type_checking(self):
        "Test if the validation works correctly."
        x = numpy.linspace(0, 1)
        d = DataNotSmart(x=x)
        self.assertRaises(TraitError, d.__setattr__, 'x', 'hey')

if __name__ == '__main__':
    unittest.main()

_______________________________________________
Enthought-dev mailing list
Enthought-dev@...
https://mail.enthought.com/mailman/listinfo/enthought-dev

Gmane