traits icon indicating copy to clipboard operation
traits copied to clipboard

minlen not present on TraitInstance

Open jgostick opened this issue 3 years ago • 5 comments

Loving traits! I have created a wrapper class that does some pretty printing among other things. I'm getting the following error when of my classes attributes is a List():


  File "C:\Users\jeff\anaconda3\envs\dev\lib\site-packages\traits\trait_list_object.py", line 580, in __init__
    self._validate_length(len(value))

  File "C:\Users\jeff\anaconda3\envs\dev\lib\site-packages\traits\trait_list_object.py", line 889, in _validate_length
    if not trait.minlen <= new_length <= trait.maxlen:

AttributeError: 'TraitInstance' object has no attribute 'minlen'

It's probably something I'm doing wrong, so let me know if you need more information from me. Thanks!

jgostick avatar Nov 05 '21 04:11 jgostick

It's probably something I'm doing wrong, so let me know if you need more information from me

You alluded to a class attribute being a list in your comment

I'm getting the following error when of my classes attributes is a List():

Can you give us an example using which we can reproduce this issue?

rahulporuri avatar Nov 05 '21 04:11 rahulporuri

Hi @jgostick. There's not a lot we can do with this issue without more information: this isn't a common error in Traits-using projects, and I'm not able to guess what sort of code might provoke that error. Do you have a reproducible example that you could share?

It seems likely that this is a bug in your code rather than in Traits itself, but it would still be useful to know in that case, since it may be an indication of a documentation bug, or there may be API changes that could be made to fix potentially misleading APIs.

If there are no further updates, I'll close this issue in a few days - I'm afraid that as it stands there's nothing actionable here.

mdickinson avatar Nov 12 '21 11:11 mdickinson

HI @mdickinson and @rahulporuri I have actually decided to roll my own 'traits' class since I really only need the one feature (forcing a dataclass attribute to only accept a given type after instantiation). However, I can describe what I was doing that cause this error, for your reference. The code snippet below is my attempt at a 'minimal' example to recreate the error...not exactly minimal though, sorry:

from traits.api import HasTraits, Int, List, Str


class Sub1(HasTraits):
    a = Int(3)
    b = List(Str())
    
    
class Sub2(HasTraits):
    a = Int(5)
    c = Str('test')
    

class SettingsWrapper:
    r"""
    This is a wrapper class around HasTraits that allows one to set and get
    attributes with a clean namespace (without all the methods from the 
    traits class). HasTraits is a stored in a hidden attribute called 
    _settings.
    """
    
    def __init__(self, *args):
        super().__setattr__('_settings', HasTraits())
            
    def __getattr__(self, attr):
        r"""
        Catch attribute assignments to the class and push to the HasTraits 
        object _settings
        """
        return getattr(self._settings, attr)
    
    def __setattr__(self, attr, value):
        r"""
        Catch attribute retrivals from the class and fetch from HasTraits 
        object _settings
        """
        setattr(self._settings, attr, value)
            
    def update(self, settings):
        r"""
        Add the attributes from ``settings`` to self, to combine settings
        from different locations on a single object
        """
        # Fetch list of user-added attributes on the HasTraits instance
        attrs = list(set(dir(settings)).difference(set(dir(HasTraits()))))
        # Assign them to the _setting
        for item in attrs:
            self._settings.add_trait(item, getattr(settings, item))
            
    def __dir__(self):
        r"""
        Overload the __dir__ attr so that the user-specified attrs show up
        in the IDE
        """       
        attrs = list(set(dir(self._settings)).difference(set(dir(HasTraits()))))
        return attrs
    

if __name__ == "__main__":
    a = Sub1()
    b = Sub2()
    sets = SettingsWrapper()
    sets.update(a)
    sets.update(b)
    assert sets.a == 5
    assert sets.c == 'test'
    try:
        sets.b
    except AttributeError as e:
        print(e)
    

jgostick avatar Nov 12 '21 16:11 jgostick

@jgostick Thanks for the update. Here's your example reduced to something more minimal:

from traits.api import HasTraits, List

class Sub(HasTraits):
    b = List()

settings = HasTraits()
settings.add_trait("b", Sub().b)
settings.b

On my machine, this fails with the following traceback:

(traits) mdickinson@mirzakhani Desktop % python test.py
Traceback (most recent call last):
  File "/Users/mdickinson/Desktop/test.py", line 8, in <module>
    settings.b
  File "/Users/mdickinson/Enthought/ETS/traits/traits/trait_list_object.py", line 580, in __init__
    self._validate_length(len(value))
  File "/Users/mdickinson/Enthought/ETS/traits/traits/trait_list_object.py", line 889, in _validate_length
    if not trait.minlen <= new_length <= trait.maxlen:
AttributeError: 'TraitInstance' object has no attribute 'minlen'

The main problem here is that add_trait expects a TraitType, or a cTrait, or at least something that can be converted to a cTrait, as its second argument, and we're passing an instance of TraitList instead. Ideally this would be an error, but there's ancient machinery still in play that tries to interpret arbitrary objects as cTraits in weird and wonderful ways. It's not really surprising that this fails, but it may still be worth digging in to find the exact cause - this could be a symptom of an underlying problem.

mdickinson avatar Nov 30 '21 16:11 mdickinson

FWIW, this is a regression introduced at some point between Traits 5.2.0 and Traits 6.0.0. Under Traits 5.2.0, the code I posted above works fine.

mdickinson avatar Dec 01 '21 12:12 mdickinson