Skip to content

Commit

Permalink
improve backward-compatibility for Container subclasses with default_…
Browse files Browse the repository at this point in the history
…value=None
  • Loading branch information
minrk committed Oct 15, 2020
1 parent 20b64f5 commit 872b77a
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 11 deletions.
20 changes: 20 additions & 0 deletions traitlets/tests/test_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,26 @@ class C(HasTraits):
assert c.t == default_value


@pytest.mark.parametrize(
"Trait, default_value",
((List, []), (Tuple, ()), (Set, set())),
)
def test_subclass_default_value(Trait, default_value):
"""Test deprecated default_value=None behavior for Container subclass traits"""

class SubclassTrait(Trait):
def __init__(self, default_value=None):
super().__init__(default_value=default_value)

class C(HasTraits):
t = SubclassTrait()

# test default value
c = C()
assert type(c.t) is type(default_value)
assert c.t == default_value


class CRegExpTrait(HasTraits):

value = CRegExp(r'')
Expand Down
63 changes: 52 additions & 11 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,7 @@ class Container(Instance):
To be subclassed by overriding klass.
"""

klass = None
_cast_types = ()
_valid_defaults = SequenceTypes
Expand Down Expand Up @@ -2441,11 +2442,25 @@ def __init__(self, trait=None, default_value=Undefined, **kwargs):
further keys for extensions to the Trait (e.g. config)
"""

# allow List([values]):
if trait is not None and default_value is Undefined and not is_trait(trait):
default_value = trait
trait = None

if default_value is None and not kwargs.get("allow_none", False):
# improve backward-compatibility for possible subclasses
# specifying default_value=None as default,
# keeping 'unspecified' behavior (i.e. empty container)
warn(
f"Specifying {self.__class__.__name__}(default_value=None)"
" for no default is deprecated in traitlets 5.0.5."
" Use default_value=Undefined",
DeprecationWarning,
stacklevel=2,
)
default_value = Undefined

if default_value is Undefined:
args = ()
elif default_value is None:
Expand All @@ -2455,16 +2470,23 @@ def __init__(self, trait=None, default_value=Undefined, **kwargs):
elif isinstance(default_value, self._valid_defaults):
args = (default_value,)
else:
raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
raise TypeError(
"default value of %s was %s" % (self.__class__.__name__, default_value)
)

if is_trait(trait):
if isinstance(trait, type):
warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
" Passing types is deprecated in traitlets 4.1.",
DeprecationWarning, stacklevel=3)
warn(
"Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
" Passing types is deprecated in traitlets 4.1.",
DeprecationWarning,
stacklevel=3,
)
self._trait = trait() if isinstance(trait, type) else trait
elif trait is not None:
raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
raise TypeError(
"`trait` must be a Trait or None, got %s" % repr_type(trait)
)

super(Container, self).__init__(klass=self.klass, args=args, **kwargs)

Expand Down Expand Up @@ -2671,6 +2693,7 @@ def default_value_repr(self):

class Tuple(Container):
"""An instance of a Python tuple."""

klass = tuple
_cast_types = (list,)

Expand Down Expand Up @@ -2702,12 +2725,25 @@ def __init__(self, *traits, **kwargs):
will be cast to a tuple. If ``traits`` are specified,
``default_value`` must conform to the shape and type they specify.
"""
default_value = kwargs.pop('default_value', Undefined)
default_value = kwargs.pop("default_value", Undefined)
# allow Tuple((values,)):
if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
default_value = traits[0]
traits = ()

if default_value is None and not kwargs.get("allow_none", False):
# improve backward-compatibility for possible subclasses
# specifying default_value=None as default,
# keeping 'unspecified' behavior (i.e. empty container)
warn(
f"Specifying {self.__class__.__name__}(default_value=None)"
" for no default is deprecated in traitlets 5.0.5."
" Use default_value=Undefined",
DeprecationWarning,
stacklevel=2,
)
default_value = Undefined

if default_value is Undefined:
args = ()
elif default_value is None:
Expand All @@ -2717,18 +2753,23 @@ def __init__(self, *traits, **kwargs):
elif isinstance(default_value, self._valid_defaults):
args = (default_value,)
else:
raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
raise TypeError(
"default value of %s was %s" % (self.__class__.__name__, default_value)
)

self._traits = []
for trait in traits:
if isinstance(trait, type):
warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
" Passing types is deprecated in traitlets 4.1.",
DeprecationWarning, stacklevel=2)
warn(
"Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
" Passing types is deprecated in traitlets 4.1.",
DeprecationWarning,
stacklevel=2,
)
trait = trait()
self._traits.append(trait)

if self._traits and default_value is None:
if self._traits and (default_value is None or default_value is Undefined):
# don't allow default to be an empty container if length is specified
args = None
super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
Expand Down

0 comments on commit 872b77a

Please sign in to comment.