From c5c373bc8311d9adc76ecb1e1a7f9d7169d39e9b Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Aug 2017 12:00:07 +0200 Subject: [PATCH 1/5] test overriding `_trait_default` --- traitlets/tests/test_traitlets.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py index 778bbf88..69cdb0a3 100644 --- a/traitlets/tests/test_traitlets.py +++ b/traitlets/tests/test_traitlets.py @@ -2547,3 +2547,26 @@ def __init__(__self, cls, self): pass x = X(cls=None, self=None) + + +def test_override_default(): + class C(HasTraits): + a = Unicode('hard default') + def _a_default(self): + return 'default method' + + C._a_default = lambda self: 'overridden' + c = C() + assert c.a == 'overridden' + +def test_override_default_decorator(): + class C(HasTraits): + a = Unicode('hard default') + @default('a') + def _a_default(self): + return 'default method' + + C._a_default = lambda self: 'overridden' + c = C() + assert c.a == 'overridden' + From cb3522915addf8cf0dcf864a584db32f71ba96ed Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Aug 2017 12:00:52 +0200 Subject: [PATCH 2/5] fix has_trait check in trait_defaults `has_trait` function is not defined --- traitlets/traitlets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 2d4ae73b..cecd7450 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -1519,13 +1519,14 @@ def trait_defaults(self, *names, **metadata): ----- Dynamically generated default values may depend on the current state of the object.""" - if len(names) == 1 and len(metadata) == 0: - return self._get_trait_default_generator(names[0])(self) - for n in names: - if not has_trait(self, n): + if not self.has_trait(n): raise TraitError("'%s' is not a trait of '%s' " "instances" % (n, type(self).__name__)) + + if len(names) == 1 and len(metadata) == 0: + return self._get_trait_default_generator(names[0])(self) + trait_names = self.trait_names(**metadata) trait_names.extend(names) From 2ecd463989dc3aea32616a60de118c48b223fd1b Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Aug 2017 12:01:39 +0200 Subject: [PATCH 3/5] don't register default trait generators Prevents modifying classes from behaving as expected - magic method has highest priority, if defined - truncate mro walk at trait.this_trait --- traitlets/tests/test_traitlets.py | 6 ------ traitlets/traitlets.py | 22 ++++++++++------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py index 69cdb0a3..c68ed732 100644 --- a/traitlets/tests/test_traitlets.py +++ b/traitlets/tests/test_traitlets.py @@ -2527,12 +2527,6 @@ class AB(A, B): class BA(B, A): pass - assert 'trait' in Base._trait_default_generators - assert 'trait' not in A._trait_default_generators - assert 'trait' in B._trait_default_generators - assert 'trait' not in AB._trait_default_generators - assert 'trait' not in BA._trait_default_generators - assert A().trait == 'base' assert A().attr == 'base' assert BA().trait == 'B' diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index cecd7450..5a8fe33e 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -417,16 +417,6 @@ class TraitType(BaseDescriptor): info_text = 'any value' default_value = Undefined - def class_init(self, cls, name): - super(TraitType, self).class_init(cls, name) - if self.name is not None and self.name not in cls._trait_default_generators: - cls._trait_default_generators[self.name] = self.default - - def subclass_init(self, cls): - if '_%s_default' % self.name in cls.__dict__: - method = getattr(cls, '_%s_default' % self.name) - cls._trait_default_generators[self.name] = method - def __init__(self, default_value=Undefined, allow_none=False, read_only=None, help=None, config=None, **kwargs): """Declare a traitlet. @@ -1507,10 +1497,18 @@ def _get_trait_default_generator(cls, name): Walk the MRO to resolve the correct default generator according to inheritance. """ - for c in cls.mro(): + trait = getattr(cls, name) + assert isinstance(trait, TraitType) + # truncate mro to the class on which the trait is defined + mro = cls.mro() + mro = mro[:mro.index(trait.this_class) + 1] + for c in mro: + method_name = '_%s_default' % name + if method_name in c.__dict__: + return getattr(c, '_%s_default' % name) if name in c.__dict__.get('_trait_default_generators', {}): return c._trait_default_generators[name] - raise KeyError("No default generator for trait %r found in %r" % (name, cls.mro())) + return trait.default def trait_defaults(self, *names, **metadata): """Return a trait's default value or a dictionary of them From c770170d9b5ba76aac8836e932856ca31d58bd07 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Aug 2017 12:54:08 +0200 Subject: [PATCH 4/5] handle `_trait_default` defined on the instance --- traitlets/tests/test_traitlets.py | 11 +++++++++++ traitlets/traitlets.py | 10 ++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py index c68ed732..2087955a 100644 --- a/traitlets/tests/test_traitlets.py +++ b/traitlets/tests/test_traitlets.py @@ -2564,3 +2564,14 @@ def _a_default(self): c = C() assert c.a == 'overridden' +def test_override_default_instance(): + class C(HasTraits): + a = Unicode('hard default') + @default('a') + def _a_default(self): + return 'default method' + + c = C() + c._a_default = lambda self: 'overridden' + assert c.a == 'overridden' + diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 5a8fe33e..4985422e 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -1491,21 +1491,23 @@ def trait_values(self, **metadata): """ return {name: getattr(self, name) for name in self.trait_names(**metadata)} - @classmethod - def _get_trait_default_generator(cls, name): + def _get_trait_default_generator(self, name): """Return default generator for a given trait Walk the MRO to resolve the correct default generator according to inheritance. """ + method_name = '_%s_default' % name + if method_name in self.__dict__: + return getattr(self, method_name) + cls = self.__class__ trait = getattr(cls, name) assert isinstance(trait, TraitType) # truncate mro to the class on which the trait is defined mro = cls.mro() mro = mro[:mro.index(trait.this_class) + 1] for c in mro: - method_name = '_%s_default' % name if method_name in c.__dict__: - return getattr(c, '_%s_default' % name) + return getattr(c, method_name) if name in c.__dict__.get('_trait_default_generators', {}): return c._trait_default_generators[name] return trait.default From 036a9096ffb4ec00a4298fb00b14c02250be8f71 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Aug 2017 13:16:00 +0200 Subject: [PATCH 5/5] handle this_class not in mro --- traitlets/traitlets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 4985422e..803d047e 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -1504,7 +1504,11 @@ def _get_trait_default_generator(self, name): assert isinstance(trait, TraitType) # truncate mro to the class on which the trait is defined mro = cls.mro() - mro = mro[:mro.index(trait.this_class) + 1] + try: + mro = mro[:mro.index(trait.this_class) + 1] + except ValueError: + # this_class not in mro + pass for c in mro: if method_name in c.__dict__: return getattr(c, method_name)