Giter Site home page Giter Site logo

enthought / traits Goto Github PK

View Code? Open in Web Editor NEW
415.0 66.0 85.0 53.46 MB

Observable typed attributes for Python classes

License: Other

Python 91.41% C 8.56% CSS 0.04%
python dataclasses observer-pattern types gui attributes runtime-typechecking

traits's Introduction

Traits: observable typed attributes for Python classes

http://docs.enthought.com/traits

The Traits project is at the center of all Enthought Tool Suite development and has changed the mental model used at Enthought for programming in the already extremely efficient Python programming language. We encourage everyone to join us in enjoying the productivity gains from using such a powerful approach.

The Traits project allows Python programmers to use a special kind of type definition called a trait, which gives object attributes some additional characteristics:

  • Initialization: A trait has a default value, which is automatically set as the initial value of an attribute before its first use in a program.
  • Validation: The type of a trait attribute is explicitly declared. The type is evident in the code, and only values that meet a programmer-specified set of criteria (i.e., the trait definition) can be assigned to that attribute.
  • Delegation: The value of a trait attribute can be contained either in the defining object or in another object delegated to by the trait.
  • Notification: Setting the value of a trait attribute can notify other parts of the program that the value has changed.
  • Visualization: User interfaces that allow a user to interactively modify the value of a trait attribute can be automatically constructed using the trait's definition. (This feature requires that a supported GUI toolkit be installed. If this feature is not used, the Traits project does not otherwise require GUI support.)

A class can freely mix trait-based attributes with normal Python attributes, or can opt to allow the use of only a fixed or open set of trait attributes within the class. Trait attributes defined by a class are automatically inherited by any subclass derived from the class.

Dependencies

Traits requires Python >= 3.8.

Traits has the following optional dependencies:

  • NumPy to support the trait types for arrays.
  • TraitsUI to support GUI Views.

To build the full documentation one needs:

  • Sphinx version 2.1 or later.
  • The Enthought Sphinx Theme. (A version of the documentation can be built without this, but some formatting may be incorrect.)

traits's People

Contributors

aaronayres35 avatar burnpanck avatar cfarrow avatar corranwebster avatar dependabot[bot] avatar dpinte avatar ferdonline avatar ievacerny avatar itziakos avatar jcorson avatar jjenthought avatar johntyree avatar jvkersch avatar jwiggins avatar k2bd avatar kitchoi avatar martinrenou avatar mchilvers avatar mdickinson avatar midhun-pm avatar notmatthancock avatar pradyunsg avatar rahulporuri avatar rkern avatar shoeb-github avatar sjagoe avatar stefanoborini avatar stevenjkern avatar timdiller avatar tmreay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

traits's Issues

Changing a dict in a List(Dict) trait raises a TraitError

from traits.api import *

class A(HasTraits):
    foo = List(Dict)
    bar = Property(List(Str), depends_on='foo[]')

    def _get_bar(self):
        return sorted([sorted(d.keys())[0] for d in self.foo])

a = A()
a.foo.append(dict(x=10))
a.foo[0]['x'] = 20

Raises the following TraitError

Traceback (most recent call last):
  File "/home/punchagan/tmp/foo.py", line 12, in <module>
    a.foo[0]['x'] = 20
  File "/home/punchagan/work/traits/traits/trait_handlers.py", line 3159, in __setitem__
    raise excp
traits.trait_errors.TraitError: Each value of the 'foo_items' trait of an A instance must be an implementor of, or can be adapted to implement, TraitListEvent or None, but a value of <traits.trait_handlers.TraitDictEvent object at 0x13046d0> <class 'traits.trait_handlers.TraitDictEvent'> was specified.

ProtototypedFrom issue using ColorTrait (maybe all the mapped traits)

Here is a simple test case ::

from traits.api import HasTraits, PrototypedFrom, Instance
from enable.api import ColorTrait

class Configuration (HasTraits):

    line_color = ColorTrait('red')

class Plot(HasTraits):

    config = Instance(Configuration, ())
    line_color = PrototypedFrom('config')


if __name__ == '__main__':

    s = Plot()

    print s.line_color

    # raises a KeyError on line_color_
    s.line_color =  'black'

Executing the code leads to the following traceback ::

dpinte:Downloads dpinte$ python bug_delagated_mapped_traits.py 
red
Traceback (most recent call last):
  File "bug_delagated_mapped_traits.py", line 21, in <module>
    s.line_color =  'black'
  File "/Users/dpinte/projects/ets/traits/traits/trait_handlers.py", line 2124, in _post_setattr
    setattr( object, name + '_', value )
  File "/Users/dpinte/projects/ets/traits/traits/has_traits.py", line 3703, in _remove_trait_delegate_listener
    name, self.__class__.__listener_traits__[ name ][1] ),
KeyError: 'line_color_'

traits 3.6.0 + deepcopy

In [57]: class A(traits.HasTraits):
foo = traits.List(traits.Dict)

In [60]: a = A()

In [61]: a.foo = [{'a':[1,2,3]}, {'b':[4,5,6]}]

In [62]: a.foo
Out[62]: [{'a': [1, 2, 3]}, {'b': [4, 5, 6]}]

In [63]: deepcopy(a).foo
Out[63]: []

TypeError in RangeEditor when mixed literal and trait name for Range trait

The problem is exemplified in the following code:

import traits.api as tr
class rangetest(tr.HasTraits):
    low = tr.Int(0)
    high = tr.Int(10)
#    range = tr.Range(low='low', high='high', value=2)   # ok
#    range = tr.Range(low=0, high=10, value=2)           # ok
    range = tr.Range(low=0, high='high', value=2)       # TypeError
#    range = tr.Range(low='low', high=10, value=2)       # TypeError


if __name__ == '__main__':
    rangetest().configure_traits()

Traits v. 4.2.0. TypeError on trauitsui\editors\range_editor.py:216:
if (not is_float) and (abs(high - low) > 1000000000L):
TypeError: unsupported operand type(s) for -: 'int' and 'code'

Race condition in TraitChangeNotifyWrapper.

There's a race condition in TraitChangeNotifyWrapper than can lead to exceptions like the following:

Exception occurred in traits notification handler for object: <__main__.A object at 0x547cf0>, trait: foo, old value: 1517402, new value: 1517403
Traceback (most recent call last):
  File "/Users/mdickinson/Enthought/ETS/traits/traits/trait_notifiers.py", line 519, in rebind_call_1
    self.dispatch( getattr( self.object(), self.name ), new )
TypeError: 'NoneType' object is not callable

This can occur when generating trait change events on one thread at the same time as removing the listener for those trait change events on a different thread (the main thread in the application that triggered this). Here's a short script that generates the above exceptions for me:

import itertools
import threading
import time

from traits.api import HasTraits, Int


class A(HasTraits):
    foo = Int

    def foo_changed_handler(self):
        pass


def foo_writer(a, stop_event):
    natural_number = itertools.count()
    while not stop_event.is_set():
        a.foo = next(natural_number)


def test_listener_thread_safety():
    a = A()
    stop_event = threading.Event()

    t = threading.Thread(target=foo_writer, args=(a, stop_event))
    t.start()

    for _ in xrange(100):
        a.on_trait_change(a.foo_changed_handler, 'foo')
        time.sleep(0.01)
        a.on_trait_change(a.foo_changed_handler, 'foo', remove=True)
        time.sleep(0.01)

    stop_event.set()
    t.join()


test_listener_thread_safety()

I plan to work on a fix at some point, if no-one beats me to it.


Edited 2013-05-13 20:24 to give a simpler script.

Unreachable code in Dict.validate

The Dict.validate contains an if statement that cannot be reached.

def validate ( self, object, name, value ):
        """ Validates that the value is a valid dictionary.
        """
        if isinstance( value, dict ):
            if value is None:
                return value
            return TraitDictObject( self, object, name, value )

        self.error( object, name, value )

The inner if block will not be called since if value will always return True.

It is possible that the bug is due to a typo and is should be changed into if object is None: Similar to how the Set.validate is defined.

Remove '__subclass_traits__' attribute of HasTraits classes

MetaHasTraits, in its __new__ , sets the class attribute __subclass_traits__ of HasTraits subclasses and adds newly created classes to the __subclass_traits__ attribute of all its base classes.
All this logic is not really needed since it can now be substituted with the standard __subclasses__ method of new-style classes which returns immediate subclasses of the class, using weak references so that class garbage collection is not prevented as is the case with using the strong references in __subclass_traits__

on_trait_change decorator raises exception for embedded trait

This is in traits 3.

I'm using the on_traits_change decorator to respond to changes in 'trace_plot.plot.components.index_mapper.range+'
trace_plot is an instance of a direct subclass of HasTraits,
plot is an instance of VPlotContainer,
components is a LinePlot,
index_mapper and range are the standard for LinePlot.

When the class with using the on_traits_change decorator is instantiated, the following exception is raised:

Exception occurred in traits notification handler.
Please check the log file for details.
Exception occurred in traits notification handler for object: <zoom_plot.ZoomPlot object at 0x24299bd0>, trait: plot, old value: , new value: <enthought.chaco.plot_containers.VPlotContainer object at 0x242e60f0>
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/trait_notifiers.py", line 610, in rebind_call_4
object, trait_name, old, new )
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/trait_notifiers.py", line 448, in dispatch
handler( *args )
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/traits_listener.py", line 467, in handle_simple
self.next.register( new )
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/traits_listener.py", line 433, in register
value = getattr( self, type )( new, name, False )
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/traits_listener.py", line 666, in _register_simple
return next.register( getattr( object, name ) )
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/site-packages/enthought/traits/traits_listener.py", line 344, in register
if (new is None) or (new is Undefined) or (new in self.active):
File "/Library/Frameworks/Python.framework/Versions/7.0/lib/python2.7/weakref.py", line 298, in contains
return wr in self.data
TypeError: unhashable type: 'TraitListObject'

See a71c93750d82ec267bee3dc1aaee3a164c6a4d86 of ripe project - _update_line method.

Segfault when a default method for a Str returns None

The following code causes a segfault after raising the expected TraitError exception (Traits 4.2.0 (from EPD 7.3) in Ubuntu 64 bit):

from traits.api import HasTraits, Str, List
from traitsui.api import View, Item, VGroup, EnumEditor


class Foo(HasTraits):

    value = Str
    choices = List(Str)

    # If the following is uncommented, the code raises the TraitError
    # exception (as it should) and does not segfault.
    #traits_view = View(Item('value', editor=EnumEditor(name='choices')))

    def _value_default(self):
        if len(self.choices) == 0:
            result = ''
        else:
            result = self.choices[0]
        # Intentional bug: missing return value, so None is returned.
        #return result

    def _choices_default(self):
        return []

if __name__ == "__main__":
    f = Foo()
    f.configure_traits()

WeakRef trait type creates reference cycles.

Code to reproduce:

import gc
from traits.api import HasTraits, Str, WeakRef

class Eggs(HasTraits):
    name = Str


class Spam(HasTraits):
    eggs = WeakRef(Eggs)


eggs = Eggs(name='boris')


gc.disable()
gc.collect()

for _ in xrange(10):
    spam = Spam(eggs=eggs)
    del spam
    print gc.collect()    

Output:

iwasawa:Desktop mdickinson$ python weakref_bug.py 
5
5
5
5
5
5
5
5
5
5

I was expecting to see 0 output instead of 5.

Here's the cycle that's being created:

weakref

Crash when calling `reset_traits` on specific properties.

Here's an example script that crashes an interpreter. I have not dug in to determine the nature of the crash.

from traits.api import HasTraits, Int, Property


class CrashMe(HasTraits):
    """ Crashes when reset_traits(['a']) is called.

    This works with Float, Int, Str.
    Not with Any.

    """
    a = Property(Int)

    def crash(self):
        self.reset_traits(['a'])


CrashMe().crash()
print "Hey, I didn't crash!"

`traits.adapts` confused when run repeatedly in an interactive session.

Using traits.adapts adds an adapter class to the adapter registry. If the same class is redefined, then traits can't choose between them. For example, try running the following example repeatedly in an ipython session (using %run): Adapted_tree_editor_demo.py

The first time should run fine (this adds the class to the registry). The second time should fail (this adds the same class to the registry, and then it can't decide between the repeated entries).

One solution maybe to check the name and __module__ of the class added to the registry. If those match something in the registry, then replace the existing entry (since you might update the class before the second run).

Dynamic and decorated listeners fail unpredictably but deterministically

This sporadic bug was detected in April-May 2011, on three separate customer applications by different developers (@jdmarch, @jwiggins, @Terrel), and confirmed by @corranwebster and @rkern.

There are some rare situations (apparently mostly involving catching Chaco or Enable events, but we are not yet sure) where some dynamic and decorated listeners fail to be triggered when they should be. The workaround is to use (undecorated) specially named listener methods when you can.

This bug is deterministic in the sense that the same code always fails on the same machine and IIRC on multiple machines. It persisted when switching to an older version of Traits (Sept 210). It is unpredictable in the sense that a given use of the decorator will work fine, then begin to fail after another decorated listener is added elsewhere in the application.

@rkern speculated that this unpredictability was due to changes in the order of listeners in a traits internal dictionary.

There is no minimal working example, as the bug only appeared in fairly complex applications.

items tutorial causes bus error

The tutorial entitled Extended Traits UI Item and Editor References causes a bus error when run from the tutor application.
This is running all of ETS from the master on OSX 10.6.

Start tutor.py under traits/examples/tutorials and click on the mentioned tutorial.

Feature request: Allow longs to be passed to an Int trait.

Currently, the validator for an Int trait doesn't allow something of Python type long to be used as the trait value:

>>> from traits.api import *
>>> class A(HasTraits):
...     foo = Int
...     bar = CInt
... 
>>> a = A()
>>> a.foo = 2L
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mdickinson/Enthought/ETS/traits/traits/trait_handlers.py", line 169, in error
    value )
traits.trait_errors.TraitError: The 'foo' trait of an A instance must be an integer, but a value of 2L <type 'long'> was specified.

For the most Python use-cases, programmers shouldn't care about int versus long. Moreover, the distinction presents a portability problem: something that's an int on one platform can end up being a long on another (even if it's still within the range of an int).

I propose that the Int trait type should accept Python longs, passing them through the int constructor so that integer values that are within the range [-sys.maxint-1, sys.maxint] are still stored and retrieved as something of type int, and so that integers outside that range are stored as something of type long. In other words, for integral types, Int should behave like CInt:

>>> a.bar = 2L
>>> a.bar
2
>>> a.bar = 7**100
>>> a.bar
3234476509624757991344647769100216810857203198904625400933895331391691459636928060001L

I did briefly consider the idea that only values that are within the range [-sys.maxint-1, sys.maxint] should be storable (and values outside that range should raise an error), but that still retains the portability problems mentioned above. Moreover, we're already able to store out-of-range values if they're numpy ints.

>>> import numpy
>>> from traits.api import *
>>> class A(HasTraits):
...     foo = Int
... 
>>> a = A()
>>> a.foo = numpy.uint64(2**64-1)
>>> a.foo
18446744073709551615
>>> type(a.foo)
<type 'numpy.uint64'>

If there's agreement on this change I'll work on a patch.

update slider within a loop

Hello,

I want to create an animation with traits and mayavi
but related to that I want to show the current object represented with a slider.
So I have a loop that give different values to my slider (beween 1 and 5)
but I can not see the different state of the slider : only the first one and the last one.

I want to see the slider at 1, then at 2, then at 3 ....

Do you have any idea ?

Here is my simple code:
!!!!!!!!!!!!!
from traits.api import HasTraits, Range, Int, Button, Float
from traitsui.api import View, Item
import time

class TestSlider(HasTraits):

bounds = Range(1,5,value=1)
play_butt = Button("play")

traits_view = View(Item('bounds'),
Item('play_butt'))

def _play_butt_fired(self):
for ii in range(5):
self.bounds= ii+1
time.sleep(0.25)

TestSlider().configure_traits()

Mlab and traitsui!

Hi !

I am trying to make a slider in mlab window using traitsui. I am able to view the content that i want but i was trying to understand the traitsui and make a simple slider to change its opacity. Could you give me any support?

This is the code

from mayavi import mlab
pkl_file = open('data.pkl', 'rb')
data1 = pickle.load(pkl_file)
pprint.pprint(data1)
pkl_file.close()
mlab.contour3d(data1,colormap='PuRd',opacity=.05,contours=5)
mlab.show()

The only parameter i want to change its opacity, it is hard to find the tutorial to do this and i am new to traits.

Thanks!

Proposal: Lazy Property

I propose to add "lazy property" to the existing trait types. The purpose is to avoid unnecessary computations of property values and redundant trait notifications.

Consider first a standard property trait. Suppose that there are traits (call them โ€œancestorsโ€) that the property depends on (which is the usual case for a non-trivial property), and there are also traits (โ€œdescendantsโ€) that depend on the considered property. In short, we suppose that the property is an intermediate node in the traits notification network.

My concern is that each time such a property receives a notification that one of its ancestors has changed its value, it automatically recomputes its own value whether or not this value is going to be used.

I propose to add "lazy property" to the existing trait types. The purpose is to avoid unnecessary computations of property values and redundant trait notifications.

Consider first a standard property trait. Suppose that there are traits (call them โ€œancestorsโ€) that the property depends on (which is the usual case for a non-trivial property), and there are also traits (โ€œdescendantsโ€) that depend on the considered property. In short, we suppose that the property is an intermediate node in the traits notification network.

My concern is that each time such a property receives a notification that one of its ancestors has changed its value, it automatically recomputes its own value whether or not this value is going to be used.

In contrast, a โ€œlazy propertyโ€ should be such property that when it receives a notification, it does not update its value immediately, but postpones the updating until the value is actually requested (by a getattr method or alike). This "lazy evaluation" can be complemented with "lazy notification", which means that if the โ€œlazy propertyโ€ has already sent a notification, it does not send further notifications until its value is recomputed.

Let us now recall why the โ€œusualโ€ property with descendants automatically recomputes its value. This happens because the property must propagate the received notification, telling its descendants that its value is being updated, and the current implementation of Traits is such that the new value must be part of the notification. But is it really necessary to include the new value in the notification? Would it not suffice just to notify the descendants that the value is not valid anymore? More specifically, it appears to be no important reason to include both the old and the new values in the trait_change event. Posting only the old value would be sufficient, at least in most cases; the new value can be always readily computed by explicitly accessing the trait. However, to conform to the existing Traits framework, one can simply include the โ€œUndefinedโ€ object in the trait notification instead of the real new value.

Where could "lazy properties" be useful? Clearly, the Traits properties offer an elegant syntax to implement a function as a composition of other functions (with an unlimited number of "intermediate" decomposition layers). This is useful in coding many mathematical models (in particular, in the context of Chaco visualization). But what can be said about the computational efficiency of this framework? A function coded as a
"network" of properties reacts to any change of its inputs. This is acceptable if one wishes to trace the changes in function values caused by each change in each individual input. But suppose that we don't need the function value be recomputed after each change. We may wish to change a subset of the inputs, and evaluate the function after that. Here the standard properties become inefficient, resulting in unnecessary computations and notifications. Let me refer to my code below (or script lazy_property.py) for an illustration.

This ticket follows the discussion on the Enthought-Dev mailing list (see subject [Traits] Proposal: lazy property, initiated by Anton Tyutin on February 7, 2011). You can also find a link to script lazy_property.py there, which is a simple implementation of the "lazy evaluation" feature of โ€œlazy propertiesโ€. This code is cited below for completeness.

from __future__ import print_function

from enthought.traits.api import Undefined, Property
from enthought.traits.has_traits import weak_arg


def enable_lazy_properties(cls):
    """ Decorator that enables class cls to have 'lazy' Property traits by
        overriding method _init_trait_property_listener. The class cls is 
        expected to be a subclass of class HasTraits (defined in 
        enthought.traits.has_traits).

        A 'lazy' property is a Property trait with 'depends_on' metadata, and,
        additionally, 'lazy' metadata set to True. When a 'lazy' property 
        receives a notification from a trait it depends on, it sends the
        Undefined object as its new value to its listeners; a 'lazy' property 
        does not compute its actual value when receiving notifications, only its
        cached value is invalidated, if the latter exists.
    """

    # define new method    
    def _init_trait_property_listener ( self, name, kind, cached, pattern ):
        """ Sets up the listener for a property with 'depends_on' metadata.
        """
        property_trait = self.__class_traits__[name]
        if property_trait.__dict__.get('lazy', False):
            def my_trait_property_changed(self, name, old):
#                print(name) ## Uncomment this line if you wish to trace notifications
                return self.trait_property_changed( name, old, Undefined )
        else:
            def my_trait_property_changed(self, name, old):
                return self.trait_property_changed( name, old )

        if cached is None:
            @weak_arg(self)
            def notify ( self ):
                my_trait_property_changed(self, name, None)
        else:
            cached_old = cached + ':old'
            @weak_arg(self)
            def pre_notify ( self ):
                dict = self.__dict__
                old  = dict.get( cached_old, Undefined )
                if old is Undefined:
                    dict[ cached_old ] = dict.pop( cached, None )
            self.on_trait_change( pre_notify, pattern, priority = True, target=self )

            @weak_arg(self)
            def notify ( self ):
                old = self.__dict__.pop( cached_old, Undefined )
                if old is not Undefined:
                    my_trait_property_changed(self, name, old)

        self.on_trait_change( notify, pattern, target=self )

    # override the method
    cls._init_trait_property_listener = _init_trait_property_listener

    # return the modified class
    return cls


def LazyProperty(lazily_depends_on, *args, **kwdargs):
    """ Shortcut to a lazy property constructor.
    """
    return Property(*args, depends_on=lazily_depends_on, lazy=True, **kwdargs)


def ReadOnlyElement(prop_name, ind):
    """ 
    """
    return LazyProperty(prop_name, 
                        fget = lambda self: getattr(self, prop_name)[ind])


if __name__ == "__main__":
    # testing example

    from enthought.traits.api import HasTraits, Int, Property, cached_property

    print("\nCompute z = y * x3, where y = x1 + x2, using lazy properties:")

    @enable_lazy_properties
    class A(HasTraits):

        x1 = Int
        x2 = Int
        x3 = Int

        y = LazyProperty('x1, x2')
        @cached_property
        def _get_y(self):
            print("_get_y called")
            return self.x1 + self.x2

        z = LazyProperty('y, x3')
        @cached_property
        def _get_z(self):
            print("_get_z called")
            return self.y * self.x3

    a = A()

    print("")
    print("x1 set")
    a.x1 = 2
    print("x2 set")
    a.x2 = 3
    print("x3 set")
    a.x3 = 4
    print("z accessed")
    print("z =", a.z)

    print("")
    print("x3 set")
    a.x3 = 8
    print("z accessed")
    print("z =", a.z)

    print("")
    print("x1 set")
    a.x1 = 6
    print("x2 set")
    a.x2 = 4
    print("z accessed")
    print("z =", a.z)


    print("\n\nNow do analogous computations using traditional properties:")

#    @enable_lazy_properties  ## You may uncomment this line: nothing should change in the output
    class A0(HasTraits):

        x1 = Int
        x2 = Int
        x3 = Int

        y = Property(depends_on='x1, x2')
        @cached_property
        def _get_y(self):
            print("_get_y called")
            return self.x1 + self.x2

        z = Property(depends_on='y, x3')
        @cached_property
        def _get_z(self):
            print("_get_z called")
            return self.y * self.x3

    a = A0()

    print("")
    print("x1 set")
    a.x1 = 2
    print("x2 set")
    a.x2 = 3
    print("x3 set")
    a.x3 = 4
    print("z accessed")
    print("z =", a.z)

    print("")
    print("x3 set")
    a.x3 = 8
    print("z accessed")
    print("z =", a.z)

    print("")
    print("x1 set")
    a.x1 = 6
    print("x2 set")
    a.x2 = 4
    print("z accessed")
    print("z =", a.z)

on_trait_change causes reference cycles that keeps the handler alive

The code below causes two reference cycles.

The cycle involving TraitChangeNotifyWrapper._notify_method_listener will keep the SomeClass instance alive until the cycle is garbage collected.

import refcycle


from traits.api import HasTraits, Str, on_trait_change


class SomeClass(HasTraits):

    attribute = Str

    @on_trait_change('attribute')
    def _on_attribute_changed(self):
        pass


def a_function():
    pass


def test():
    c = SomeClass()
    c.on_trait_change(a_function, 'attribute')


cycles = refcycle.cycles_created_by(test).strongly_connected_components()
for cycle in cycles:
    print cycle.to_dot()

0 dot

1 dot

Detect multiple calls to a Traits default constructor.

When using Traits in a multithreaded environment, one of the (many) possible things that can go wrong is accidentally invoking a Traits default constructor simultaneously from more than one thread. That's usually an error in the application code rather than in Traits itself; nevertheless, it would be nice if there were some way to easily detect and warn about this situation.

It's not clear to me right now whether there's a cheap solution to this. It's critical that any solution doesn't slow down traits attribute access in general, though I imagine a small performance hit to the default constructor would be acceptable.

Here's some code that demonstrates the issue:

import threading

from traits.api import HasTraits, List


class A(HasTraits):
    cheeses = List

    def add_new_item(self, event):
        event.wait()
        self.cheeses.append("gorgonzola")


for _ in xrange(1000):
    a = A()
    event = threading.Event()
    t = threading.Thread(target=a.add_new_item, args=(event,))
    t.start()
    event.set()
    a.cheeses.append("brie")
    t.join()
    assert len(a.cheeses) == 2  # Will occasionally fail.

sync_traits creates reference cycles.

The sync_traits method on HasTraits objects creates reference cycles. In the script below, the create_cycles function creates two cycles that won't get collected until the cyclic garbage collector runs. See the attached image for the details of the cycles.

import gc

import refcycle

from traits.api import HasStrictTraits, HasTraits, Instance, Str


class Eggs(HasStrictTraits):
    spam = Str


class Sandwich(HasStrictTraits):
    spam = Str


def create_cycles():
    sandwich = Sandwich()
    eggs = Eggs()
    sandwich.sync_trait('spam', eggs)


def main():
    gc.disable()
    gc.collect()
    create_cycles()
    garbage = refcycle.garbage()
    print "garbage: ", garbage
    with open('garbage.gv', 'w') as f:
        f.write(garbage.to_dot())


if __name__ == '__main__':
    main()

garbage

AdaptationError should contain a message saying what adaptation failed

The AdaptationError exception does not indicate which adaptation failed. This can make it difficult to track down what is actually going wrong.

  File "C:\Python27\lib\site-packages\traits\adaptation\adaptation_manager.py", line 123, in adapt
    raise AdaptationError
AdaptationError

Exception occurs when deleting synched trait

The following traceback occurs when the code below is executed:

Exception occurred in traits notification handler.
Please check the log file for details.
Exception occurred in traits notification handler for object: <__main__.A object at 0x10741a
ef0>, trait: t, old value: 0, new value: 42                                                
Traceback (most recent call last):
  File "/Users/jvkersch/Desktop/Enthought Projects/traits/traits/trait_notifiers.py", line 5
20, in _dispatch_change_event                                                              
    self.dispatch( handler, *args )
  File "/Users/jvkersch/Desktop/Enthought Projects/traits/traits/trait_notifiers.py", line 4
83, in dispatch                                                                            
    handler( *args )
  File "/Users/jvkersch/Desktop/Enthought Projects/traits/traits/has_traits.py", line 2680, 
in _sync_trait_modified                                                                    
    for object, object_name in info[ name ].values():
KeyError: 't'
from traits.api import HasTraits, Int

class A(HasTraits):
    t = Int

a = A()
b = A()

a.sync_trait('t', b, mutual=False)

del b

a.t = 42

Pickling or copying dynamic traits makes them loose their type and metadata.

pickling

a = HasTraits()
a.add_trait('foo', File(exists=True))
a.foo = Undefined

print a.traits()
print a.traits()['foo'].trait_type

inp = pickle.dumps(a, -1)
b = pickle.loads(inp)


print b.traits()
print b.traits()['foo'].trait_type

b.foo = 'idonotexist'
a.foo = 'idonotexist'

copying

In [68]: a = HasTraits()

In [69]: a.add_trait('foo', File(exists=True))

In [70]: a.foo = Undefined

In [71]: a.traits()['foo'].trait_type
Out[71]: <enthought.traits.trait_types.File object at 0x104809650>

In [72]: b = deepcopy(a)

In [73]: b.traits()['foo'].trait_type
Out[73]: <enthought.traits.trait_types.Python object at 0x10272b950>

In [74]: b.foo = 'idonotexist'

In [75]: a.foo = 'idonotexist'

Regular and extended notifications behave differently

When listening to changes in a dictionary or a list (and probably other sequence types as well), the leaf listener of an extended notifier behaves differently to a single notifier (not extended) on the leaf object. In the example below, the change handlers on the leaf object (Foo) are not fired when the dictionary or list are mutated (as expected), but the listeners on the root object are fired (not expected).

from traits.api import *


class Foo(HasTraits):
    dict_trait = Dict()

    list_trait = List()

    @on_trait_change('dict_trait')
    def _on_i_changed(self):
        print 'dict_trait'

    @on_trait_change('list_trait')
    def _on_list_trait(self):
        print 'list_trait'


class Bar(HasTraits):

    foo = Instance(Foo)

    @on_trait_change('foo:dict_trait')
    def _on_foo_dict_trait(self):
        print 'foo:dict_trait'

    @on_trait_change('foo:list_trait')
    def _on_foo_list_trait(self):
        print 'foo:list_trait'


foo = Foo()
bar = Bar(foo=foo)

foo.dict_trait[1] = 1
foo.list_trait.append(1)

Output is:

foo:dict_trait
foo:list_trait

HasTraits.trait_set does not properly handle recursion

The current body of HasTraits.trait_set looks like this:

def trait_set ( self, trait_change_notify = True, **traits ):
    if not trait_change_notify:
        self._trait_change_notify( False )
        try:
            for name, value in traits.items():
                setattr( self, name, value )
        finally:
            self._trait_change_notify( True )
    else:
        for name, value in traits.items():
            setattr( self, name, value )

    return self

The problem with this is that if trait_change_notify is False, and the call to setattr(...) somehow triggers another call to trait_set with trait_change_notify = False (like through a property), then _trait_change_notify( True ) will be called by the recursive path, incorrectly turning on notifications for the first setattr(...) call. This happens because _trait_change_notify just sets a bit flag on the HasTraits object at the C level; it does not keep a stack context.

I've got a workaround that overrides trait_set with the following:

    _trait_change_notify_flag = Bool(True)
    def trait_set(self, trait_change_notify=True, **traits):
        last = self._trait_change_notify_flag
        self._trait_change_notify_flag = trait_change_notify
        self._trait_change_notify(trait_change_notify)
        try:
            for name, value in traits.iteritems():
                setattr(self, name, value)
        finally:
            self._trait_change_notify_flag = last
            self._trait_change_notify(last)
        return self

This works, but it's not ideal. It would be better if the C api exposed the flag as read-write instead of write-only.

tutorial needs traitsui removal

The tutorial in examples/tutorials/traits_4.0/extended_trait_change/properties.py is shows traits content, but uses traitsui in the example. To avoid confusion, the tutorial example should be redone without traitsui.

Failed assignment to HasStrictTraits instance modifies the class.

I encountered the following behaviour while (foolishly) trying to monkeypatch a HasStrictTraits subclass. The fact that the monkeypatching doesn't work isn't really surprising. What is suprising is that the attempt to monkeypatch one instance of the class breaks the class itself.

>>> from traits.api import *
>>> class A(HasStrictTraits):
...     def start(self): print "Starting"
... 
>>> try:
...     A().start = 3.2  # Monkey-patching fails;  too bad.
... except Exception:
...     pass
... 
>>> A().start  # Surprise: the failure above affects new A instances!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'start'

What appears to be happening is that the assignment to A().start creates a new class trait start for A and puts it into the A.__class_traits__ dictionary, before the failure occurs. After the failure, that new class trait still exists.

`@on_trait_change` decorator is not lazy (and a special case of the same issue)

The @on_trait_change decorator is not lazy, which means that even if we provide a default value for a trait, the _traitname_default method is called on intialization.

I have two examples:

  1. Consider the class
class B(HasTraits):
    a = Any
    def _a_default(self):
        print 'INIT a'
        return 42

>>> y=B(a=1)

This, as I expected, doesn't output anything.

Now if we add a on_trait_change decorator:

class A(HasTraits):
     a = Any
     def _a_default(self):
         print 'INIT a'
         return 42

     @on_trait_change('a')
     def _do_something(self):
         print 'a CHANGED'

>>> x = A(a=1)
INIT a
a CHANGED

Here the 'a' default initializer is called.

  1. A variant of the above behavior:
class X(HasTraits):
     x = Float(12)


class A(HasTraits):
     a = Instance(X)

     def _a_default(self):
         print 'INIT a'
         return X()

     @on_trait_change('a:x')
     def _do_something(self):
         print 'a:x CHANGED'


y = X(x=1)
b = A(a=y)
INIT a

The _a_default method is still called, even though the listener itself is not called.

Presence of Delegate trait breaks indirect trait notifiers

This seems to be a rather obscure bug. Trait change notifications for doubly nested attributes do not get delivered when there is also a delegate to the intermediate trait:

from traits.api import HasTraits,Int,Instance,on_trait_change,DelegatesTo

class C(HasTraits):
    x = Int

class B(HasTraits):
    y = Int
    c = Instance(C,())

class A1(HasTraits):
    b = Instance(B,())

    @on_trait_change('b.c.x')
    def _bcx_changed(self,value):
        print(type(self).__name__,'.b.c.x changed to',value)

    @on_trait_change('b.y')
    def _by_changed(self,value):
        print(type(self).__name__,'.b.y changed to',value)


class A2(HasTraits):
    b = Instance(B,())
    c = DelegatesTo('b')

    @on_trait_change('b.c.x')
    def _bcx_changed(self,value):
        print(type(self).__name__,'.b.c.x changed to',value)

    @on_trait_change('b.y')
    def _by_changed(self,value):
        print(type(self).__name__,'.b.y changed to',value)

class A3(HasTraits):
    c = Instance(C,())
    x = DelegatesTo('c')

    @on_trait_change('c.x')
    def _cx_changed(self,value):
        print(type(self).__name__,'.c.x changed to',value)

a = A1()
a.b.c.x = 1
a.b.y = 2

a = A2()
a.b.c.x = 3
a.b.y = 4
a.c.x = 5

a = A3()
a.c.x = 6
a.x = 7

On traits 4.3.0 (and on my python 3 branch), there will be no change notification for the instance of class A2, i,e, cases 3-5. Note that A1 and A2 differ only by a DelegatesTo trait. However, as seen in A3, for a single level of indirection, there is no problem in that case.

KeyError inside `_default` methods should not be caught by traits.

If a KeyError is raised inside a trait _default method the error is caught and converted to AttributeError.

The following example demonstrates the problem

from traits.api import HasStrictTraits, Float

class MyClass(HasStrictTraits):

    alpha = Float

    beta = Float

    gamma = Float

    def _alpha_default(self):
        return 4.5

    def _beta_default(self):
        1 / 0

    def _gamma_default(self):
        raise KeyError()

my_class = MyClass()

executing:

  • my_class.alpha return 4.5 (all is well).
  • my_class.beta raises a ZeroDivisionError with the right traceback (all is well again).
  • my_class.gamma raises an AttributeError claiming that the gamma attribute has not been defined (which is at least confusing).

It turns out that ctraits is checking for KeyError on trait access and raise an AtttributeError for missing traits, which makes sense. However, in the case where accessing the trait will cause the _default method to be executed, the KeyError check is including the user code. Catching and converting errors that take place inside the _default method does not sound right and can lead to difficult to find and debug issues.

Subclasses of ndarray pass Array validation and are not coerced

When using an Array trait, an object that is a subclass of ndarray will pass validation, but not be converted to a true ndarray, since isinstance(ndarray) is True. Examples of common subclasses of ndarray include matrix and pandas.Series (versions <= 0.12). This can lead to unexpected behavior in the API. For example, you would get matrix multiplication instead of elementwise in the matrix case, or force index alignment for operations in the Series case.

Tangentially related, there isn't much difference in behavior between Array and CArray as far as I can tell, since casting to an array occurs even if the coerce flag is False (in the case where isinstance(ndarray) == False). A simple fix for the above issue would be to use asarray to coerce all objects, but I'm not sure whether the present behavior of doing the coercion for both Array and CArray should be extended to this case as well.

License issue

There is no license file included in traits-4.4.0.tar.gz. Source files have:

# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license.  The license
# is also available online at http://www.enthought.com/licenses/BSD.txt

but there is no enthought/LICENSE.txt and http://www.enthought.com/licenses/BSD.txt returns 404 not found.

Extended Property depends_on References tutorial bus error

The tutorial entitled Extended Property depends_on References causes a bus error when run from the tutor application.
This is running all of ETS from the master on OSX 10.6.

Start tutor.py under traits/examples/tutorials and click on the mentioned tutorial.

A bus error also occurs in a traitsui tutorial, as reported here: enthought/traitsui#3

add_trait doesn't make traits "immediately available"

Starting using the add_trait method, I noticed a strange behavior. (using traits 4.3.0)

When adding a new trait with add_trait, it is not accessible before "visiting it"

class A(HasTraits):
param1 = Float(1)

a = A()
print(a.get())
# -> {'param1': 1}

# Add a trait
a.add_trait('param2', Float(2))
print(a.get())
# -> {'param1': 1}
# -> 'param2' is missing !

# Visit the trait:
a.param2
# Now param2 is visible
print(a.get())
# -> {'param2': 2, 'param1': 1}

(see https://gist.github.com/pierre-haessig/6870346 for complete script)

Is this an expected behavior or a bug ? Should I call some kind of "do register my trait" method ?

best,
Pierre

Copying a Dict trait doesn't work correctly

from traits.api import HasTraits, Dict
import copy

class Foo(HasTraits):
    d = Dict

foo = Foo()
foo.d['foo'] = 1
d = copy.copy(foo.d)
d.update({'bar': 3})

This results in the following error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
C:\dev\projects\ in ()
----> 1 d.update({'bar': 3})

traits-4.0.1-py2.6-win32.egg\traits\trait_handlers.py in update(self, dic)
   3177     def update ( self, dic ):
   3178         if len( dic ) > 0:
-> 3179             new_dic = self._validate_dic( dic )
   3180
   3181             if self.name_items is not None:

traits-4.0.1-py2.6-win32.egg\traits\trait_handlers.py in _validate_dic(self, dic)
   3261         new_dic = {}
   3262
-> 3263         key_validate = self.trait.key_trait.handler.validate
   3264         if key_validate is None:
   3265             key_validate = lambda object, name, key: key

AttributeError: 'TraitDictObject' object has no attribute 'trait'

Traits ListEditor popup menus broken

When running a simple example of a list editor, clicking on the popup menu button causes an error as follows:

File "/Users/cwebster/src/ets/traitsui/traitsui/qt4/list_editor.py", line 280, in popup_menu
    proxy    = sender.proxy
AttributeError: 'NoneType' object has no attribute 'proxy'

Example code which tweaks this:

class SchoolClass(HasTraits):
    # List of the students in the class
    students = List(Str)

    view = View('
             Group(
                  Item('students', 
                       style='custom',
                       editor=ListEditor(rows=5),
                       show_label=False,
                  ),
                  show_border=True,
                  label='Students'
            ),      
            title = 'Class',
            width=300,
            height=200,
            resizable=True
        )

Support dispatch option in `on_trait_change` decorator

It would be convenient if the on_trait_change decorator supported a dispatch keyword so that code like this.

@on_trait_change('trait')
def _dispatch_to_real_handler(self, value):
     ui_dispatch(self.real_handler, value)

def _real_handler(self, value):
    ...

Could be written as:

@on_trait_change('trait', dispatch='ui')
def _real_handler(self , value):
      ...

Bad behavior of inherited traits with _ = Disallow

This works as expected:

In [11]: class Foo(HasTraits):
   ....:     pass
   ....: 

In [12]: class Bar(Foo):
   ....:     _ = Disallow
   ....:     

In [13]: f = Foo()

In [14]: f.a = 12

In [15]: b = Bar()

In [16]: b.a = 12
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)
/Users/chris/Development/git_repos/enaml/<ipython-input-16-a15ba401819f> in <module>()
----> 1 b.a = 12

TraitError: Cannot set the undefined 'a' attribute of a 'Bar' object.

But this is bad behavior with an instance defining behavior of the entire class and subclasses. It seems that adding an instance trait also gets added to the class traits dict. Not sure how deep down the rabbit hole fixing this would take us.

In [17]: class Foo(HasTraits):
   ....:     pass
   ....: 

In [18]: f = Foo()

In [19]: f.a = 12

In [20]: class Bar(Foo):
   ....:     _ = Disallow
   ....:     

In [21]: b = Bar()

In [22]: b.a = 12

dynamic traits lose type information/validation when pickled or copied

@dpinte: finally got back to this. the example below demonstrates our issue. we use dynamic traits a lot and then pickle and send our objects to remote execution nodes. so this bug affects us a fair bit. we currently work around this by not using the validation.

code

def test_dynamic_traits():
    from nose.tools import assert_raises
    from traits.api import HasTraits, Int
    from cPickle import dumps, loads
    a = HasTraits()
    a.add_trait('foo', Int)
    a.foo = 1
    assign_a = lambda : setattr(a, 'foo', 'a')
    assert_raises(Exception, assign_a)
    pkld_a = dumps(a)
    unpkld_a = loads(pkld_a)
    assign_a_again = lambda : setattr(unpkld_a, 'foo', 'a')    
    assert_raises(Exception, assign_a_again)

test

test_dynamic_traits()
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/<ipython-input-2-deb613781b32> in <module>()
----> 1 test_dynamic_traits()

/<ipython-input-1-3ed06dbc8b75> in test_dynamic_traits()
     11     unpkld_a = loads(pkld_a)
     12     assign_a_again = lambda : setattr(unpkld_a, 'foo', 'a')
---> 13     assert_raises(Exception, assign_a_again)
     14 

/Library/Frameworks/EPD64.framework/Versions/7.1/lib/python2.7/unittest/case.pyc in assertRaises(self, excClass, callableObj, *args, **kwargs)
    469             return context
    470         with context:
--> 471             callableObj(*args, **kwargs)
    472 
    473     def _getAssertEqualityFunc(self, first, second):

/Library/Frameworks/EPD64.framework/Versions/7.1/lib/python2.7/unittest/case.pyc in __exit__(self, exc_type, exc_value, tb)
    113                 exc_name = str(self.expected)
    114             raise self.failureException(
--> 115                 "{0} not raised".format(exc_name))
    116         if not issubclass(exc_type, self.expected):
    117             # let unexpected exceptions pass through


AssertionError: Exception not raised

traits version

import traits

traits.__version__
Out[4]: '4.1.1'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.