From 83e57eafba205cc8b4384e8d2328b150188c8a5e Mon Sep 17 00:00:00 2001 From: "James A. Bednar" Date: Thu, 17 Aug 2023 04:55:52 -0500 Subject: [PATCH] Listselectordefault (#817) --- numbergen/__init__.py | 10 ++++----- param/__init__.py | 47 +++++++++++++++++++++++++-------------- tests/testlistselector.py | 15 +++++++------ 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/numbergen/__init__.py b/numbergen/__init__.py index 6b356586c..ee1dfbf74 100644 --- a/numbergen/__init__.py +++ b/numbergen/__init__.py @@ -320,7 +320,7 @@ class TimeAwareRandomState(TimeAware): random_generator = param.Parameter( default=random.Random(c_size_t(hash((500,500))).value), doc= """ - Random state used by the object. This may may be an instance + Random state used by the object. This may be an instance of random.Random from the Python standard library or an instance of numpy.random.RandomState. @@ -458,9 +458,9 @@ class UniformRandom(RandomDistribution): See the random module for further details. """ - lbound = param.Number(default=0.0,doc="inclusive lower bound") + lbound = param.Number(default=0.0,doc="Inclusive lower bound.") - ubound = param.Number(default=1.0,doc="exclusive upper bound") + ubound = param.Number(default=1.0,doc="Exclusive upper bound.") def __call__(self): @@ -500,8 +500,8 @@ class UniformRandomInt(RandomDistribution): See the randint function in the random module for further details. """ - lbound = param.Number(default=0,doc="inclusive lower bound") - ubound = param.Number(default=1000,doc="inclusive upper bound") + lbound = param.Number(default=0,doc="Inclusive lower bound.") + ubound = param.Number(default=1000,doc="Inclusive upper bound.") def __call__(self): diff --git a/param/__init__.py b/param/__init__.py index 748cce7ed..ffd95d5b3 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1666,7 +1666,7 @@ def sig(self): class _SignatureSelector(Parameter): - + # Needs docstring; why is this a separate mixin? _slot_defaults = _dict_update( SelectorBase._slot_defaults, _objects=_compute_selector_default, compute_default_fn=None, check_on_set=_compute_selector_checking_default, @@ -1748,17 +1748,13 @@ def __init__(self, *, objects=Undefined, default=Undefined, instantiate=Undefine self.allow_None = self._slot_defaults['allow_None'] else: self.allow_None = allow_None - if self.default is not None and self.check_on_set is True: - self._validate(self.default) + if self.default is not None: + self._validate_value(self.default) self._update_state() def _update_state(self): - if ( - self.check_on_set is False - and self.default is not None - and self.default not in self.objects - ): - self.objects.append(self.default) + if self.check_on_set is False and self.default is not None: + self._ensure_value_is_in_objects(self.default) @property def objects(self): @@ -1786,18 +1782,17 @@ def compute_default(self): """ if self.default is None and callable(self.compute_default_fn): self.default = self.compute_default_fn() - if self.default not in self.objects: - self.objects.append(self.default) + self._ensure_value_is_in_objects(self.default) def _validate(self, val): - """ - val must be None or one of the objects in self.objects. - """ if not self.check_on_set: self._ensure_value_is_in_objects(val) return - if not (val in self.objects or (self.allow_None and val is None)): + self._validate_value(val) + + def _validate_value(self, val): + if self.check_on_set and not (self.allow_None and val is None) and val not in self.objects: items = [] limiter = ']' length = 0 @@ -2623,16 +2618,34 @@ def compute_default(self): self.objects.append(o) def _validate(self, val): + self._validate_type(val) + + if self.check_on_set: + self._validate_value(val) + else: + self._ensure_value_is_in_objects(val) + + + def _validate_type(self, val): if (val is None and self.allow_None): return + if not isinstance(val, list): raise ValueError( f"{_validate_error_prefix(self)} only takes list types, " f"not {val!r}." ) - for o in val: - super()._validate(o) + def _validate_value(self, val): + self._validate_type(val) + if val is not None: + for o in val: + super()._validate_value(o) + + def _update_state(self): + if self.check_on_set is False and self.default is not None: + for o in self.default: + self._ensure_value_is_in_objects(o) class MultiFileSelector(ListSelector): diff --git a/tests/testlistselector.py b/tests/testlistselector.py index c737333c4..e61e052d5 100644 --- a/tests/testlistselector.py +++ b/tests/testlistselector.py @@ -18,7 +18,7 @@ def setUp(self): super().setUp() class P(param.Parameterized): e = param.ListSelector(default=[5],objects=[5,6,7]) - f = param.ListSelector(default=10) + f = param.ListSelector(default=[10]) h = param.ListSelector(default=None) g = param.ListSelector(default=None,objects=[7,8]) i = param.ListSelector(default=[7],objects=[9],check_on_set=False) @@ -173,16 +173,17 @@ class Q(param.Parameterized): r = param.ListSelector(default=[6]) ########################## - # CEBALERT: not sure it makes sense for ListSelector to be set to - # a non-iterable value (except None). I.e. I think this first test - # should fail. def test_default_not_checked_to_be_iterable(self): - class Q(param.Parameterized): - r = param.ListSelector(default=6) + with pytest.raises( + ValueError, + match=re.escape("ListSelector parameter 'r' only takes list types, not 6."), + ): + class Q(param.Parameterized): + r = param.ListSelector(default=6) def test_set_checked_to_be_iterable(self): class Q(param.Parameterized): - r = param.ListSelector(default=6,check_on_set=False) + r = param.ListSelector(default=[6],check_on_set=False) with self.assertRaises(ValueError): Q.r = 6