From 7c45a7a96b154ccda35e49ea9a483c294684c132 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 13 Oct 2015 17:53:59 +0200 Subject: [PATCH 1/4] Initial implementation of the support for default values and nullable traits. --- numtraits.py | 19 +++++++++++++++---- test_numtraits.py | 9 +++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/numtraits.py b/numtraits.py index 299ed3c..f0312d6 100644 --- a/numtraits.py +++ b/numtraits.py @@ -38,16 +38,23 @@ class NumericalTrait(TraitType): info_text = 'a numerical trait, either a scalar or a vector' def __init__(self, ndim=None, shape=None, domain=None, - default=None, convertible_to=None): - super(NumericalTrait, self).__init__() + default=None, convertible_to=None, nullable=False): + # We use the default value for the traitlet in the following cases: + # - default is not None, + # - default is None and the trait is nullable. + # In the other cases, we assume that a None default means that + # there is no default value defined. + if (default is None and nullable) or default is not None: + super(NumericalTrait, self).__init__(default_value=default) + else: + super(NumericalTrait, self).__init__() # Just store all the construction arguments. self.ndim = ndim self.shape = shape self.domain = domain - # TODO: traitlets supports a `default` argument in __init__(), we should - # probably link them together once we start using this. self.default = default + self.nullable = nullable self.target_unit = convertible_to if self.target_unit is not None: @@ -65,6 +72,10 @@ def _check_args(self): raise TraitError("shape={0} and ndim={1} are inconsistent".format(self.shape, self.ndim)) def validate(self, obj, value): + # If the trait is nullable and the value is None, + # then we have nothing to check. + if self.nullable and value is None: + return value # We proceed by checking whether Numpy tells us the value is a # scalar. If Numpy isscalar returns False, it could still be scalar diff --git a/test_numtraits.py b/test_numtraits.py index 5cd9ff7..60c1e81 100644 --- a/test_numtraits.py +++ b/test_numtraits.py @@ -13,6 +13,8 @@ class ScalarProperties(HasTraits): d = NumericalTrait(ndim=0, domain='negative') e = NumericalTrait(ndim=0, domain='strictly-negative') f = NumericalTrait(ndim=0, domain=(3, 4)) + g = NumericalTrait(ndim=0, nullable=True) + h = NumericalTrait(ndim=0, nullable=True, default=1.23) class TestScalar(object): @@ -79,6 +81,13 @@ def test_range(self): self.sp.f = 7 assert exc.value.args[0] == "f should be in the range [3:4]" + def test_nullable(self): + assert self.sp.g is None + assert self.sp.h is not None + self.sp.g = 1.2 + assert self.sp.g == 1.2 + self.sp.h = None + assert self.sp.h is None class ArrayProperties(HasTraits): From b0d7b41d28a99f5fdd9e315929e36b387f608b1b Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 13 Oct 2015 18:00:40 +0200 Subject: [PATCH 2/4] Some test additions. --- test_numtraits.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test_numtraits.py b/test_numtraits.py index 60c1e81..dbefb31 100644 --- a/test_numtraits.py +++ b/test_numtraits.py @@ -15,6 +15,7 @@ class ScalarProperties(HasTraits): f = NumericalTrait(ndim=0, domain=(3, 4)) g = NumericalTrait(ndim=0, nullable=True) h = NumericalTrait(ndim=0, nullable=True, default=1.23) + i = NumericalTrait(ndim=0, default=4.56) class TestScalar(object): @@ -81,13 +82,20 @@ def test_range(self): self.sp.f = 7 assert exc.value.args[0] == "f should be in the range [3:4]" - def test_nullable(self): + def test_nullable_default(self): assert self.sp.g is None - assert self.sp.h is not None + assert self.sp.h == 1.23 + assert self.sp.i == 4.56 self.sp.g = 1.2 assert self.sp.g == 1.2 self.sp.h = None assert self.sp.h is None + self.sp.i = 1.23 + assert self.sp.i == 1.23 + with pytest.raises(TraitError) as exc: + self.sp.i = None + assert exc.value.args[0] == "radius should be a scalar value" + class ArrayProperties(HasTraits): From 24b45de87675a0d54e067f8984e99a55d81e8dd5 Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 13 Oct 2015 18:10:57 +0200 Subject: [PATCH 3/4] Fix a test. --- test_numtraits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_numtraits.py b/test_numtraits.py index dbefb31..fc5739b 100644 --- a/test_numtraits.py +++ b/test_numtraits.py @@ -94,7 +94,7 @@ def test_nullable_default(self): assert self.sp.i == 1.23 with pytest.raises(TraitError) as exc: self.sp.i = None - assert exc.value.args[0] == "radius should be a scalar value" + assert exc.value.args[0] == "i should be a scalar value" class ArrayProperties(HasTraits): From b8d2180979384321fc16e2b0cbb80efd7714b3be Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Tue, 20 Oct 2015 15:09:08 +0200 Subject: [PATCH 4/4] Implement the nullable trait and the default value logic on top of the corresponding traitlets primitives. --- numtraits.py | 23 ++++------------------- test_numtraits.py | 6 +++--- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/numtraits.py b/numtraits.py index f0312d6..b01d019 100644 --- a/numtraits.py +++ b/numtraits.py @@ -25,7 +25,7 @@ from __future__ import print_function -from traitlets import TraitType, TraitError +from traitlets import TraitType, TraitError, Undefined import numpy as np @@ -38,23 +38,13 @@ class NumericalTrait(TraitType): info_text = 'a numerical trait, either a scalar or a vector' def __init__(self, ndim=None, shape=None, domain=None, - default=None, convertible_to=None, nullable=False): - # We use the default value for the traitlet in the following cases: - # - default is not None, - # - default is None and the trait is nullable. - # In the other cases, we assume that a None default means that - # there is no default value defined. - if (default is None and nullable) or default is not None: - super(NumericalTrait, self).__init__(default_value=default) - else: - super(NumericalTrait, self).__init__() + default_value=Undefined, convertible_to=None, allow_none=False): + super(NumericalTrait, self).__init__(default_value=default_value,allow_none=allow_none) - # Just store all the construction arguments. + # Store the construction arguments. self.ndim = ndim self.shape = shape self.domain = domain - self.default = default - self.nullable = nullable self.target_unit = convertible_to if self.target_unit is not None: @@ -72,11 +62,6 @@ def _check_args(self): raise TraitError("shape={0} and ndim={1} are inconsistent".format(self.shape, self.ndim)) def validate(self, obj, value): - # If the trait is nullable and the value is None, - # then we have nothing to check. - if self.nullable and value is None: - return value - # We proceed by checking whether Numpy tells us the value is a # scalar. If Numpy isscalar returns False, it could still be scalar # but be a Quantity with units, so we then extract the numerical diff --git a/test_numtraits.py b/test_numtraits.py index fc5739b..e13916b 100644 --- a/test_numtraits.py +++ b/test_numtraits.py @@ -13,9 +13,9 @@ class ScalarProperties(HasTraits): d = NumericalTrait(ndim=0, domain='negative') e = NumericalTrait(ndim=0, domain='strictly-negative') f = NumericalTrait(ndim=0, domain=(3, 4)) - g = NumericalTrait(ndim=0, nullable=True) - h = NumericalTrait(ndim=0, nullable=True, default=1.23) - i = NumericalTrait(ndim=0, default=4.56) + g = NumericalTrait(ndim=0, allow_none=True, default_value=None) + h = NumericalTrait(ndim=0, allow_none=True, default_value=1.23) + i = NumericalTrait(ndim=0, default_value=4.56) class TestScalar(object):