From d26cb9af779d658f8abaf9024c80fc7d27fd181c Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 13 May 2021 20:57:18 +0200 Subject: [PATCH 1/8] Update FileSelector when setting path --- param/__init__.py | 4 ++++ param/parameterized.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index ba637e46d..5bd8f30ae 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1818,6 +1818,10 @@ def __init__(self, default=None, path="", **kwargs): self.path = path self.update() + def _on_set(self, attribute, old, new): + if attribute == 'path': + self.update() + def update(self): self.objects = sorted(glob.glob(self.path)) if self.default in self.objects: diff --git a/param/parameterized.py b/param/parameterized.py index 4ffe4a8ed..a5ecd8fe6 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -851,18 +851,19 @@ def _set_instantiate(self,instantiate): self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201 - # TODO: quick trick to allow subscription to the setting of - # parameter metadata. ParameterParameter? + def _on_set(self, attribute, old, value): + """Called when a parameter attribute is changed""" - # Note that unlike with parameter value setting, there's no access - # to the Parameterized instance, so no per-instance subscription. - def __setattr__(self,attribute,value): - implemented = (attribute!="default" and hasattr(self,'watchers') and attribute in self.watchers) + def __setattr__(self, attribute, value): + implemented = (attribute != "default" and hasattr(self,'watchers') and attribute in self.watchers) + slot_attribute = attribute in self.__slots__ try: - old = getattr(self,attribute) if implemented else NotImplemented + old = getattr(self, attribute) if implemented else NotImplemented + if slot_attribute: + self._on_set(attribute, old, value) except AttributeError as e: - if attribute in self.__slots__: + if slot_attribute: # If Parameter slot is defined but an AttributeError was raised # we are in __setstate__ and watchers should not be triggered old = NotImplemented @@ -905,7 +906,7 @@ def __get__(self,obj,objtype): # pylint: disable-msg=W0613 @instance_descriptor - def __set__(self,obj,val): + def __set__(self, obj, val): """ Set the value for this Parameter. From ccdb8e81080039c24d2673eb276a6d06e9bf23b2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 14 May 2021 12:54:14 +0200 Subject: [PATCH 2/8] Refactor parameter attribute validation --- param/__init__.py | 473 +++++++++++++++++++++-------------------- param/parameterized.py | 50 +++-- 2 files changed, 273 insertions(+), 250 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 5bd8f30ae..ef8d2e2b0 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -764,32 +764,23 @@ def __init__(self,default=0.0,bounds=None,softbounds=None, self.set_hook = identity_hook self.bounds = bounds self.inclusive_bounds = inclusive_bounds - self._softbounds = softbounds + self.softbounds = softbounds self.step = step self._validate(default) - - def __get__(self,obj,objtype): + def __get__(self, obj, objtype): """ Same as the superclass's __get__, but if the value was dynamically generated, check the bounds. """ - result = super(Number,self).__get__(obj,objtype) + result = super(Number, self).__get__(obj, objtype) # CEBALERT: results in extra lookups (_value_is_dynamic() is # also looking up 'result' - should just pass it in). Note # that this method is called often. - if self._value_is_dynamic(obj,objtype): self._validate(result) + if self._value_is_dynamic(obj, objtype): + self._validate(result) return result - # Allow softbounds to be used like a normal attribute, as it - # probably should have been already (not _softbounds) - @property - def softbounds(self): return self._softbounds - - @softbounds.setter - def softbounds(self,value): self._softbounds = value - - def set_in_bounds(self,obj,val): """ Set to the given value, but cropped to be within the legal bounds. @@ -800,12 +791,11 @@ def set_in_bounds(self,obj,val): bounded_val = self.crop_to_bounds(val) else: bounded_val = val - super(Number,self).__set__(obj,bounded_val) - + super(Number, self).__set__(obj, bounded_val) # CEBERRORALERT: doesn't take account of exclusive bounds; see # https://github.com/ioam/param/issues/80. - def crop_to_bounds(self,val): + def crop_to_bounds(self, val): """ Return the given value cropped to be within the hard bounds for this parameter. @@ -837,61 +827,65 @@ def crop_to_bounds(self,val): else: # non-numeric value sent in: reverts to default value - return self.default + return self.default return val + def _validate_bounds(self, val, bounds, inclusive_bounds): + if bounds is None: + return + vmin, vmax = bounds + incmin, incmax = inclusive_bounds + if vmax is not None: + if incmax is True: + if not val <= vmax: + raise ValueError("Parameter %r must be at most %s, " + "not %s." % (self.name, vmax, val)) + else: + if not val < vmax: + raise ValueError("Parameter %r must be less than %s, " + "not %s." % (self.name, vmax, val)) + + if vmin is not None: + if incmin is True: + if not val >= vmin: + raise ValueError("Parameter %r must be at least %s, " + "not %s." % (self.name, vmin, val)) + else: + if not val > vmin: + raise ValueError("Parameter %r must be greater than %s, " + "not %s." % (self.name, vmin, val)) - def _checkBounds(self, val): - - if self.bounds is not None: - vmin,vmax = self.bounds - incmin,incmax = self.inclusive_bounds - - # Could simplify: see https://github.com/ioam/param/issues/83 - if vmax is not None: - if incmax is True: - if not val <= vmax: - raise ValueError("Parameter '%s' must be at most %s, " - "not %s." % (self.name, vmax, val)) - else: - if not val < vmax: - raise ValueError("Parameter '%s' must be less than %s, " - "not %s." % (self.name, vmax, val)) - - if vmin is not None: - if incmin is True: - if not val >= vmin: - raise ValueError("Parameter '%s' must be at least %s, " - "not %s." % (self.name, vmin, val)) - else: - if not val > vmin: - raise ValueError("Parameter '%s' must be greater than %s, " - "not %s." % (self.name, vmin, val)) + def _validate_value(self, val, allow_None): + if (allow_None and val is None) or callable(val): + return + if not _is_number(val): + raise ValueError("Parameter %r only takes numeric values, " + "not type %r." % (self.name, type(val))) + def _validate_step(self, val, step): + if step is not None and not _is_number(step): + raise ValueError("Step parameter can only be None or a " + "numeric value, not type %r." % type(step)) def _validate(self, val): """ Checks that the value is numeric and that it is within the hard bounds; if not, an exception is raised. """ - if callable(val): - return val + self._validate_value(val, self.allow_None) + self._validate_step(val, self.step) + self._validate_bounds(val, self.bounds, self.inclusive_bounds) - if self.allow_None and val is None: + def _on_set(self, attribute, old, new): + if self.owner is None: return - - if not _is_number(val): - raise ValueError("Parameter %r only takes numeric values, " - "not type %r." % (self.name, type(val))) - - if self.step is not None and not _is_number(self.step): - raise ValueError("Step parameter can only be None or a " - "numeric value, not type %r." % type(val)) - - self._checkBounds(val) - + if attribute == 'inclusive_bounds': + return self._validate_bounds(getattr(self.owner, self.name), self.bounds, new) + elif attribute == 'bounds': + return self._validate_bounds(getattr(self.owner, self.name), new, self.inclusive_bounds) + super(Number, self)._on_set(attribute, old, new) def get_soft_bounds(self): """ @@ -900,15 +894,14 @@ def get_soft_bounds(self): defaults to the hard bound. The hard bound could still be None. """ if self.bounds is None: - hl,hu=(None,None) + hl, hu = (None, None) else: - hl,hu=self.bounds + hl, hu= self.bounds - if self._softbounds is None: - sl,su=(None,None) + if self.softbounds is None: + sl, su = (None, None) else: - sl,su=self._softbounds - + sl, su = self.softbounds if sl is None: l = hl else: l = sl @@ -916,14 +909,13 @@ def get_soft_bounds(self): if su is None: u = hu else: u = su - return (l,u) - + return (l, u) def __setstate__(self,state): if 'step' not in state: state['step'] = None - super(Number,self).__setstate__(state) + super(Number, self).__setstate__(state) @@ -933,21 +925,21 @@ class Integer(Number): def __init__(self, default=0, **params): Number.__init__(self, default=default, **params) - def _validate(self, val): - if callable(val): return + def _validate_value(self, val, allow_None): + if callable(val): + return - if self.allow_None and val is None: + if allow_None and val is None: return if not isinstance(val, int): - raise ValueError("Parameter %r must be an integer, not type %r." - % (self.name, type(val))) + raise ValueError("Integer parameter %r must be an integer, " + "not type %r." % (self.name, type(val))) - if self.step is not None and not isinstance(self.step, int): + def _validate_step(self, val, step): + if step is not None and not isinstance(step, int): raise ValueError("Step parameter can only be None or an " - "integer value, not type %r" % type(self.step)) - - self._checkBounds(val) + "integer value, not type %r" % type(step)) @@ -969,8 +961,8 @@ def __init__(self, default=False, bounds=(0,1), **params): self.bounds = bounds super(Boolean, self).__init__(default=default, **params) - def _validate(self, val): - if self.allow_None: + def _validate_value(self, val, allow_None): + if allow_None: if not isinstance(val, bool) and val is not None: raise ValueError("Boolean parameter %r only takes a " "Boolean value or None, not %s." @@ -978,7 +970,6 @@ def _validate(self, val): elif not isinstance(val, bool): raise ValueError("Boolean parameter %r must be True or False, " "not %s." % (self.name, val)) - super(Boolean, self)._validate(val) @@ -987,14 +978,14 @@ class Tuple(Parameter): __slots__ = ['length'] - def __init__(self,default=(0,0),length=None,**params): + def __init__(self, default=(0,0), length=None, **params): """ Initialize a tuple parameter with a fixed length (number of elements). The length is determined by the initial default value, if any, and must be supplied explicitly otherwise. The length is not allowed to change after instantiation. """ - super(Tuple,self).__init__(default=default,**params) + super(Tuple,self).__init__(default=default, **params) if length is None and default is not None: self.length = len(default) elif length is None and default is None: @@ -1004,18 +995,23 @@ def __init__(self,default=(0,0),length=None,**params): self.length = length self._validate(default) - def _validate(self, val): - if val is None and self.allow_None: + def _validate_value(self, val, allow_None): + if val is None and allow_None: return if not isinstance(val, tuple): raise ValueError("Tuple parameter %r only takes a tuple value, " "not %r." % (self.name, type(val))) - if not len(val) == self.length: + def _validate_length(self, val, length): + if not len(val) == length: raise ValueError("Tuple parameter %r is not of the correct " "length (%d instead of %d)." % - (self.name, len(val), self.length)) + (self.name, len(val), length)) + + def _validate(self, val): + self._validate_value(val, self.allow_None) + self._validate_length(val, self.length) @classmethod def serialize(cls, value): @@ -1029,9 +1025,9 @@ def deserialize(cls, value): class NumericTuple(Tuple): """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length.""" - def _validate(self, val): - super(NumericTuple, self)._validate(val) - if self.allow_None and val is None: + def _validate_value(self, val, allow_None): + super(NumericTuple, self)._validate_value(val, allow_None) + if allow_None and val is None: return for n in val: if _is_number(n): @@ -1040,7 +1036,6 @@ def _validate(self, val): "values, not type %r." % (self.name, type(n))) - class XYCoordinates(NumericTuple): """A NumericTuple for an X,Y coordinate.""" @@ -1048,7 +1043,6 @@ def __init__(self, default=(0.0, 0.0), **params): super(XYCoordinates,self).__init__(default=default, length=2, **params) - class Callable(Parameter): """ Parameter holding a value that is a callable object, such as a function. @@ -1059,17 +1053,14 @@ class Callable(Parameter): 2.4, so instantiate must be False for those values. """ - def _validate(self, val): - super(Callable, self)._validate(val) - - if (self.allow_None and val is None) or callable(val): + def _validate_value(self, val, allow_None): + if (allow_None and val is None) or callable(val): return raise ValueError("Callable parameter %r only takes a callable object, " "not objects of type %r." % (self.name, type(val))) - class Action(Callable): """ A user-provided function that can be invoked like a class or object method using (). @@ -1112,33 +1103,40 @@ class Composite(Parameter): attributes. """ - __slots__=['attribs','objtype'] + __slots__ = ['attribs', 'objtype'] - def __init__(self,attribs=None,**kw): + def __init__(self, attribs=None, **kw): if attribs is None: attribs = [] - super(Composite,self).__init__(default=None,**kw) + super(Composite, self).__init__(default=None, **kw) self.attribs = attribs - def __get__(self,obj,objtype): + def __get__(self, obj, objtype): """ Return the values of all the attribs, as a list. """ if obj is None: - return [getattr(objtype,a) for a in self.attribs] + return [getattr(objtype, a) for a in self.attribs] else: - return [getattr(obj,a) for a in self.attribs] + return [getattr(obj, a) for a in self.attribs] + + def _validate_attribs(self, val, attribs): + if len(val) == len(attribs): + return + raise ValueError("Compound parameter %r got the wrong number " + "of values (needed %d, but got %d)." % + (self.name, len(attribs), len(val))) def _validate(self, val): - assert len(val) == len(self.attribs),"Compound parameter '%s' got the wrong number of values (needed %d, but got %d)." % (self.name,len(self.attribs),len(val)) + self._validate_attribs(val, self.attribs) def _post_setter(self, obj, val): if obj is None: - for a,v in zip(self.attribs,val): - setattr(self.objtype,a,v) + for a, v in zip(self.attribs, val): + setattr(self.objtype, a, v) else: - for a,v in zip(self.attribs,val): - setattr(obj,a,v) + for a, v in zip(self.attribs, val): + setattr(obj, a, v) class SelectorBase(Parameter): @@ -1195,20 +1193,19 @@ def __init__(self, default=None, objects=None, instantiate=False, self.compute_default_fn = compute_default_fn if check_on_set is not None: - self.check_on_set=check_on_set + self.check_on_set = check_on_set elif len(objects) == 0: - self.check_on_set=False + self.check_on_set = False else: - self.check_on_set=True + self.check_on_set = True - super(ObjectSelector,self).__init__(default=default, instantiate=instantiate, - **params) + super(ObjectSelector,self).__init__( + default=default, instantiate=instantiate, **params) # Required as Parameter sets allow_None=True if default is None self.allow_None = allow_None if default is not None and self.check_on_set is True: self._validate(default) - # CBNOTE: if the list of objects is changed, the current value for # this parameter in existing POs could be out of the new range. @@ -1221,11 +1218,10 @@ def compute_default(self): no longer None). """ if self.default is None and callable(self.compute_default_fn): - self.default=self.compute_default_fn() + self.default = self.compute_default_fn() if self.default not in self.objects: self.objects.append(self.default) - def _validate(self, val): """ val must be None or one of the objects in self.objects. @@ -1315,7 +1311,7 @@ class ClassSelector(SelectorBase): for is_instance=True. """ - __slots__ = ['class_','is_instance'] + __slots__ = ['class_', 'is_instance'] def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params): self.class_ = class_ @@ -1323,22 +1319,36 @@ def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params) self._validate(default) - def _validate(self,val): - """val must be None, an instance of self.class_ if self.is_instance=True or a subclass of self_class if self.is_instance=False""" - if isinstance(self.class_, tuple): - class_name = ('(%s)' % ', '.join(cl.__name__ for cl in self.class_)) + def _on_set(self, attribute, old, new): + if self.owner is None: + return + if attribute == 'class_': + return self._validate_class_(getattr(self.owner, self.name), new, self.is_instance) + elif attribute == 'is_instance': + return self._validate_class_(getattr(self.owner, self.name), self.class_, new) + super(ClassSelector, self)._on_set(attribute, old, new) + + def _validate(self, val): + super(ClassSelector, self)._validate(val) + self._validate_class_(val, self.class_, self.is_instance) + + def _validate_class_(self, val, class_, is_instance): + if (val is None and self.allow_None): + return + if isinstance(class_, tuple): + class_name = ('(%s)' % ', '.join(cl.__name__ for cl in class_)) else: - class_name = self.class_.__name__ - if self.is_instance: - if not (isinstance(val,self.class_)) and not (val is None and self.allow_None): + class_name = class_.__name__ + if is_instance: + if not (isinstance(val, class_)): raise ValueError( - "Parameter %r value must be an instance of %s, not '%s'." % + "ClassSelector parameter %r value must be an instance of %s, not %r." % (self.name, class_name, val)) else: - if not (val is None and self.allow_None) and not (issubclass(val,self.class_)): + if not (issubclass(val, class_)): raise ValueError( - "Parameter '%s' must be a subclass of %s, not '%s'" % - (val.__name__, class_name, val.__class__.__name__)) + "ClassSelector parameter %r must be a subclass of %s, not %r." % + (self.name, class_name, val.__name__)) def get_range(self): """ @@ -1354,9 +1364,9 @@ def get_range(self): all_classes = {} for cls in classes: all_classes.update(concrete_descendents(cls)) - d = OrderedDict((name,class_) for name,class_ in all_classes.items()) + d = OrderedDict((name, class_) for name,class_ in all_classes.items()) if self.allow_None: - d['None']=None + d['None'] = None return d @@ -1371,52 +1381,48 @@ class List(Parameter): __slots__ = ['bounds', 'item_type', 'class_'] - def __init__(self,default=[],class_=None,item_type=None,instantiate=True, - bounds=(0,None),**params): + def __init__(self, default=[], class_=None, item_type=None, + instantiate=True, bounds=(0, None), **params): self.item_type = item_type or class_ self.class_ = self.item_type self.bounds = bounds - Parameter.__init__(self,default=default,instantiate=instantiate, + Parameter.__init__(self, default=default, instantiate=instantiate, **params) self._validate(default) - def _validate(self, val): - """ - Checks that the list is of the right length and has the right contents. - Otherwise, an exception is raised. - """ - if self.allow_None and val is None: + def _validate_bounds(self, val, bounds): + "Checks that the list is of the right length and has the right contents." + if bounds is None: + return + min_length, max_length = bounds + l = len(val) + if min_length is not None and max_length is not None: + if not (min_length <= l <= max_length): + raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length)) + elif min_length is not None: + if not min_length <= l: + raise ValueError("%s: list length must be at least %s." + % (self.name, min_length)) + elif max_length is not None: + if not l <= max_length: + raise ValueError("%s: list length must be at most %s." + % (self.name, max_length)) + + def _validate_value(self, val, allow_None): + if allow_None and val is None: return - if not isinstance(val, list): - raise ValueError("List '%s' must be a list, not an object of type %s." + raise ValueError("List parameter %r must be a list, not an object of type %s." % (self.name, type(val))) - if self.bounds is not None: - min_length,max_length = self.bounds - l=len(val) - if min_length is not None and max_length is not None: - if not (min_length <= l <= max_length): - raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length)) - elif min_length is not None: - if not min_length <= l: - raise ValueError("%s: list length must be at least %s." - % (self.name, min_length)) - elif max_length is not None: - if not l <= max_length: - raise ValueError("%s: list length must be at most %s." - % (self.name, max_length)) - - self._check_type(val) - - def _check_type(self,val): - if self.item_type is None: + def _validate_item_type(self, val, item_type): + if item_type is None or (self.allow_None and val is None): return for v in val: - if isinstance(v, self.item_type): + if isinstance(v, item_type): continue raise TypeError("List parameter %r items must be instances " - "of type %r, not %r." % (self.name, self.item_type, val)) + "of type %r, not %r." % (self.name, item_type, val)) class HookList(List): @@ -1427,9 +1433,12 @@ class HookList(List): for users to register a set of commands to be called at a specified place in some sequence of processing steps. """ - __slots__ = ['class_','bounds'] + __slots__ = ['class_', 'bounds'] - def _check_type(self, val): + def _validate_value(self, val, allow_None): + super(HookList, self)._validate_value(val, allow_None) + if allow_None and val is None: + return for v in val: if callable(v): continue @@ -1556,8 +1565,16 @@ class Series(ClassSelector): which may be a number or an integer bounds tuple to constrain the allowable number of rows. """ + __slots__ = ['rows'] + def __init__(self, default=None, rows=None, allow_None=False, **params): + from pandas import Series as pdSeries + self.rows = rows + super(Series,self).__init__(pdSeries, default=default, allow_None=allow_None, + **params) + self._validate(self.default) + def _length_bounds_check(self, bounds, length, name): message = '{name} length {length} does not match declared bounds of {bounds}' if not isinstance(bounds, tuple): @@ -1571,13 +1588,6 @@ def _length_bounds_check(self, bounds, length, name): if failure: raise ValueError(message.format(name=name,length=length, bounds=bounds)) - def __init__(self, default=None, rows=None, allow_None=False, **params): - from pandas import Series as pdSeries - self.rows = rows - super(Series,self).__init__(pdSeries, default=default, allow_None=allow_None, - **params) - self._validate(self.default) - def _validate(self, val): super(Series, self)._validate(val) @@ -1819,6 +1829,7 @@ def __init__(self, default=None, path="", **kwargs): self.update() def _on_set(self, attribute, old, new): + super(FileSelector, self)._on_set(attribute, new, old) if attribute == 'path': self.update() @@ -1880,7 +1891,7 @@ class Date(Number): def __init__(self, default=None, **kwargs): super(Date, self).__init__(default=default, **kwargs) - def _validate(self, val): + def _validate_value(self, val, allow_None): """ Checks that the value is numeric and that it is within the hard bounds; if not, an exception is raised. @@ -1888,14 +1899,13 @@ def _validate(self, val): if self.allow_None and val is None: return - if not isinstance(val, dt_types) and not (self.allow_None and val is None): - raise ValueError("Date '%s' only takes datetime and date types."%self.name) + if not isinstance(val, dt_types) and not (allow_None and val is None): + raise ValueError("Date parameter %r only takes datetime and date types." % self.name) - if self.step is not None and not isinstance(self.step, dt_types): + def _validate_step(self, val, step): + if step is not None and not isinstance(step, dt_types): raise ValueError("Step parameter can only be None, a datetime or datetime type") - self._checkBounds(val) - @classmethod def serialize(cls, value): if not isinstance(value, (dt.datetime, dt.date)): # i.e np.datetime64 @@ -1915,7 +1925,7 @@ class CalendarDate(Number): def __init__(self, default=None, **kwargs): super(CalendarDate, self).__init__(default=default, **kwargs) - def _validate(self, val): + def _validate_value(self, val, allow_None): """ Checks that the value is numeric and that it is within the hard bounds; if not, an exception is raised. @@ -1923,13 +1933,12 @@ def _validate(self, val): if self.allow_None and val is None: return - if not isinstance(val, dt.date) and not (self.allow_None and val is None): - raise ValueError("CalendarDate '%s' only takes datetime types."%self.name) - - if self.step is not None and not isinstance(self.step, dt.date): - raise ValueError("Step parameter can only be None or a date type") + if not isinstance(val, dt.date) and not (allow_None and val is None): + raise ValueError("CalendarDate parameter %r only takes datetime types." % self.name) - self._checkBounds(val) + def _validate_step(self, val, step): + if step is not None and not isinstance(step, dt.date): + raise ValueError("Step parameter can only be None or a date type.") @classmethod def serialize(cls, value): @@ -1991,11 +2000,17 @@ def __init__(self, default=None, allow_named=True, **kwargs): self._validate(default) def _validate(self, val): - if (self.allow_None and val is None): + self._validate_value(val, self.allow_None) + self._validate_allow_named(val, self.allow_named) + + def _validate_value(self, val, allow_None): + if (allow_None and val is None): return if not isinstance(val, basestring): - raise ValueError("Color '%s' only takes a string value, " - "received %s." % (self.name, type(val))) + raise ValueError("Color parameter %r expects a string value, " + "not an object of type %s." % (self.name, type(val))) + + def _validate_allow_named(self, val, allow_named): is_hex = re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val) if self.allow_named: if not is_hex and val not in self._named_colors: @@ -2006,7 +2021,6 @@ def _validate(self, val): "codes, received '%s'." % (self.name, val)) - class Range(NumericTuple): "A numeric range with optional bounds and softbounds" @@ -2021,18 +2035,30 @@ def __init__(self,default=None, bounds=None, softbounds=None, self.step = step super(Range,self).__init__(default=default,length=2,**params) + def _on_set(self, attribute, old, new): + if self.owner is None: + return + if attribute == 'inclusive_bounds': + return self._validate_bounds(getattr(self.owner, self.name), self.bounds, new) + elif attribute == 'bounds': + return self._validate_bounds(getattr(self.owner, self.name), new, self.inclusive_bounds) + super(Range, self)._on_set(attribute, old, new) def _validate(self, val): - """ - Checks that the value is numeric and that it is within the hard - bounds; if not, an exception is raised. - """ - if self.allow_None and val is None: - return super(Range, self)._validate(val) + self._validate_bounds(val, self.bounds, self.inclusive_bounds) - self._checkBounds(val) - + def _validate_bounds(self, val, bounds, inclusive_bounds): + if bounds is None or (val is None and self.allow_None): + return + vmin, vmax = bounds + incmin, incmax = inclusive_bounds + for bound, v in zip(['lower', 'upper'], val): + too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin) + too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax) + if too_low or too_high: + raise ValueError("Range parameter %r's %s bound must be in range %s." + % (self.name, bound, self.rangestr())) def get_soft_bounds(self): """ @@ -2040,15 +2066,14 @@ def get_soft_bounds(self): then it is returned, otherwise it defaults to the hard bound. The hard bound could still be None. """ if self.bounds is None: - hl,hu=(None,None) + hl, hu = (None, None) else: - hl,hu=self.bounds + hl, hu = self.bounds if self.softbounds is None: - sl,su=(None,None) + sl, su = (None, None) else: - sl,su=self.softbounds - + sl, su = self.softbounds if sl is None: l = hl else: l = sl @@ -2056,8 +2081,7 @@ def get_soft_bounds(self): if su is None: u = hu else: u = su - return (l,u) - + return (l, u) def rangestr(self): vmin, vmax = self.bounds @@ -2067,62 +2091,49 @@ def rangestr(self): return '%s%s, %s%s' % (incmin, vmin, vmax, incmax) - def _checkBounds(self, val): - if self.bounds is not None: - vmin,vmax = self.bounds - incmin,incmax = self.inclusive_bounds - for bound, v in zip(['lower', 'upper'], val): - too_low = (vmin is not None) and (v < vmin if incmin else v <= vmin) - too_high = (vmax is not None) and (v > vmax if incmax else v >= vmax) - if too_low or too_high: - raise ValueError("Parameter '%s' %s bound must be in range %s" - % (self.name, bound, self.rangestr())) - - class DateRange(Range): """ A datetime or date range specified as (start, end). Bounds must be specified as datetime or date types (see param.dt_types). """ - def _validate(self, val): - if self.allow_None and val is None: + + def _validate_value(self, val, allow_None): + if allow_None and val is None: return for n in val: - if not isinstance(n, dt_types): - raise ValueError("DateRange '%s' only takes datetime types: %s"%(self.name,val)) + if isinstance(n, dt_types): + continue + raise ValueError("DateRange parameter %r only takes datetime " + "types, not %s." % (self.name, val)) start, end = val if not end >= start: - raise ValueError("DateRange '%s': end datetime %s is before start datetime %s."%(self.name,val[1],val[0])) + raise ValueError("DateRange parameter %r's end datetime %s " + "is before start datetime %s." % + (self.name,val[1],val[0])) - # Calling super(DateRange, self)._check(val) would also check - # values are numeric, which is redundant, so just call - # _checkBounds(). - self._checkBounds(val) class CalendarDateRange(Range): """ A date range specified as (start_date, end_date). """ - def _validate(self, val): - if self.allow_None and val is None: + def _validate_value(self, val, allow_None): + if allow_None and val is None: return for n in val: if not isinstance(n, dt.date): - raise ValueError("CalendarDateRange '%s' only takes date types: %s"%(self.name,val)) + raise ValueError("CalendarDateRange parameter %r only " + "takes date types, not %s." % (self.name, val)) start, end = val if not end >= start: - raise ValueError("CalendarDateRange '%s': end date %s is before start date %s."%(self.name,val[1],val[0])) - - # Calling super(CalendarDateRange, self)._check(val) would also check - # values are numeric, which is redundant, so just call - # _checkBounds(). - self._checkBounds(val) + raise ValueError("CalendarDateRange parameter %r's end date " + "%s is before start date %s." % + (self.name, val[1], val[0])) class Event(Boolean): diff --git a/param/parameterized.py b/param/parameterized.py index a5ecd8fe6..229493bb6 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -850,11 +850,6 @@ def _set_instantiate(self,instantiate): else: self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201 - - def _on_set(self, attribute, old, value): - """Called when a parameter attribute is changed""" - - def __setattr__(self, attribute, value): implemented = (attribute != "default" and hasattr(self,'watchers') and attribute in self.watchers) slot_attribute = attribute in self.__slots__ @@ -882,8 +877,7 @@ def __setattr__(self, attribute, value): if not self.owner.param._BATCH_WATCH: self.owner.param._batch_call_watchers() - - def __get__(self,obj,objtype): # pylint: disable-msg=W0613 + def __get__(self, obj, objtype): # pylint: disable-msg=W0613 """ Return the value for this Parameter. @@ -904,7 +898,6 @@ def __get__(self,obj,objtype): # pylint: disable-msg=W0613 result = obj.__dict__.get(self._internal_name,self.default) return result - @instance_descriptor def __set__(self, obj, val): """ @@ -986,19 +979,28 @@ def __set__(self, obj, val): if not obj.param._BATCH_WATCH: obj.param._batch_call_watchers() + def _on_set(self, attribute, old, new): + if self.owner is None: + return + if attribute == 'allow_None': + attribute = 'value' + validator = '_validate_%s' % attribute + if hasattr(self, validator): + getattr(self, validator)(getattr(self.owner, self.name), new) - def _validate(self, val): - """Implements validation for the parameter""" + def _validate_value(self, value, allow_None): + """Implements validation for parameter value""" + def _validate(self, val): + """Implements validation for the parameter value and attributes""" + self._validate_value(val, self.allow_None) def _post_setter(self, obj, val): """Called after the parameter value has been validated and set""" - def __delete__(self,obj): raise TypeError("Cannot delete '%s': Parameters deletion not allowed." % self.name) - def _set_names(self, attrib_name): if None not in (self.owner, self.name) and attrib_name != self.name: raise AttributeError('The %s parameter %r has already been ' @@ -1012,7 +1014,6 @@ def _set_names(self, attrib_name): self.name = attrib_name self._internal_name = "_%s_param_value" % attrib_name - def __getstate__(self): """ All Parameters have slots, not a dict, so we have to support @@ -1055,7 +1056,6 @@ def __init__(self, default="0.0.0.0", allow_None=False, **kwargs): ip_regex = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' super(IPAddress, self).__init__(default=default, regex=ip_regex, **kwargs) - """ __slots__ = ['regex'] @@ -1066,17 +1066,29 @@ def __init__(self, default="", regex=None, allow_None=False, **kwargs): self.allow_None = (default is None or allow_None) self._validate(default) - def _validate(self, val): - if self.allow_None and val is None: + def _on_set(self, attribute, old, new): + if self.owner is None: return + if attribute == 'regex': + self._validate_regex(getattr(self.owner, self.name), new) + elif attribute == 'allow_None': + self._validate_value(getattr(self.owner, self.name), new) + def _validate_regex(self, val, regex): + if regex is not None and re.match(regex, val) is None: + raise ValueError("String parameter %r value %r does not match regex %r." + % (self.name, val, regex)) + + def _validate_value(self, val, allow_None): + if allow_None and val is None: + return if not isinstance(val, basestring): raise ValueError("String parameter %r only takes a string value, " "not value of type %s." % (self.name, type(val))) - if self.regex is not None and re.match(self.regex, val) is None: - raise ValueError("String parameter %r value %r does not match regex %r." - % (self.name, val, self.regex)) + def _validate(self, val): + self._validate_value(val, self.allow_None) + self._validate_regex(val, self.regex) class shared_parameters(object): From a3fa7741baafcfaa67e21f09c96d7af2e3ce2448 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 14 May 2021 13:19:40 +0200 Subject: [PATCH 3/8] Various fixes --- param/__init__.py | 23 ++++++++++++++--------- param/parameterized.py | 2 ++ tests/API0/testclassselector.py | 8 ++++---- tests/API1/testclassselector.py | 10 +++++----- tests/API1/testpandas.py | 2 +- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index ef8d2e2b0..5e66dae43 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -750,16 +750,16 @@ class Number(Dynamic): """ - __slots__ = ['bounds','_softbounds','inclusive_bounds','set_hook', 'step'] + __slots__ = ['bounds', 'softbounds', 'inclusive_bounds', 'set_hook', 'step'] - def __init__(self,default=0.0,bounds=None,softbounds=None, + def __init__(self, default=0.0, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, **params): """ Initialize this parameter object and store the bounds. Non-dynamic default values are checked against the bounds. """ - super(Number,self).__init__(default=default,**params) + super(Number,self).__init__(default=default, **params) self.set_hook = identity_hook self.bounds = bounds @@ -832,7 +832,7 @@ def crop_to_bounds(self, val): return val def _validate_bounds(self, val, bounds, inclusive_bounds): - if bounds is None: + if bounds is None or (val is None and self.allow_None): return vmin, vmax = bounds incmin, incmax = inclusive_bounds @@ -1004,6 +1004,9 @@ def _validate_value(self, val, allow_None): "not %r." % (self.name, type(val))) def _validate_length(self, val, length): + if val is None and self.allow_None: + return + if not len(val) == length: raise ValueError("Tuple parameter %r is not of the correct " "length (%d instead of %d)." % @@ -1339,16 +1342,17 @@ def _validate_class_(self, val, class_, is_instance): class_name = ('(%s)' % ', '.join(cl.__name__ for cl in class_)) else: class_name = class_.__name__ + param_cls = self.__class__.__name__ if is_instance: if not (isinstance(val, class_)): raise ValueError( - "ClassSelector parameter %r value must be an instance of %s, not %r." % - (self.name, class_name, val)) + "%s parameter %r value must be an instance of %s, not %r." % + (param_cls, self.name, class_name, val)) else: if not (issubclass(val, class_)): raise ValueError( - "ClassSelector parameter %r must be a subclass of %s, not %r." % - (self.name, class_name, val.__name__)) + "%s parameter %r must be a subclass of %s, not %r." % + (param_cls, self.name, class_name, val.__name__)) def get_range(self): """ @@ -2011,6 +2015,8 @@ def _validate_value(self, val, allow_None): "not an object of type %s." % (self.name, type(val))) def _validate_allow_named(self, val, allow_named): + if (val is None and self.allow_None): + return is_hex = re.match('^#?(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$', val) if self.allow_named: if not is_hex and val not in self._named_colors: @@ -2026,7 +2032,6 @@ class Range(NumericTuple): __slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step'] - def __init__(self,default=None, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, **params): self.bounds = bounds diff --git a/param/parameterized.py b/param/parameterized.py index 229493bb6..079a707ef 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -1075,6 +1075,8 @@ def _on_set(self, attribute, old, new): self._validate_value(getattr(self.owner, self.name), new) def _validate_regex(self, val, regex): + if (val is None and self.allow_None): + return if regex is not None and re.match(regex, val) is None: raise ValueError("String parameter %r value %r does not match regex %r." % (self.name, val, regex)) diff --git a/tests/API0/testclassselector.py b/tests/API0/testclassselector.py index 9323dd25d..63385ff82 100644 --- a/tests/API0/testclassselector.py +++ b/tests/API0/testclassselector.py @@ -25,7 +25,7 @@ def test_single_class_instance_constructor(self): self.assertEqual(p.e, 6) def test_single_class_instance_error(self): - exception = "Parameter 'e' value must be an instance of int, not 'a'" + exception = "ClassSelector parameter 'e' value must be an instance of int, not 'a'." with self.assertRaisesRegexp(ValueError, exception): self.P(e='a') @@ -34,7 +34,7 @@ def test_single_class_type_constructor(self): self.assertEqual(p.f, float) def test_single_class_type_error(self): - exception = "Parameter 'str' must be a subclass of Number, not 'type'" + exception = "ClassSelector parameter 'f' must be a subclass of Number, not 'str'." with self.assertRaisesRegexp(ValueError, exception): self.P(f=str) @@ -47,7 +47,7 @@ def test_multiple_class_instance_constructor2(self): self.assertEqual(p.g, 'A') def test_multiple_class_instance_error(self): - exception = "Parameter 'g' value must be an instance of \(int, str\), not '3.0'" + exception = "ClassSelector parameter 'g' value must be an instance of \(int, str\), not 3.0." with self.assertRaisesRegexp(ValueError, exception): self.P(g=3.0) @@ -60,6 +60,6 @@ def test_multiple_class_type_constructor2(self): self.assertEqual(p.h, str) def test_multiple_class_type_error(self): - exception = "Parameter 'float' must be a subclass of \(int, str\), not 'type'" + exception = "ClassSelector parameter 'h' must be a subclass of \(int, str\), not 'float'." with self.assertRaisesRegexp(ValueError, exception): self.P(h=float) diff --git a/tests/API1/testclassselector.py b/tests/API1/testclassselector.py index c70a656f7..1668a1fcf 100644 --- a/tests/API1/testclassselector.py +++ b/tests/API1/testclassselector.py @@ -26,7 +26,7 @@ def test_single_class_instance_constructor(self): self.assertEqual(p.e, 6) def test_single_class_instance_error(self): - exception = "Parameter 'e' value must be an instance of int, not 'a'" + exception = "ClassSelector parameter 'e' value must be an instance of int, not 'a'." with self.assertRaisesRegexp(ValueError, exception): self.P(e='a') @@ -35,7 +35,7 @@ def test_single_class_type_constructor(self): self.assertEqual(p.f, float) def test_single_class_type_error(self): - exception = "Parameter 'str' must be a subclass of Number, not 'type'" + exception = "ClassSelector parameter 'f' must be a subclass of Number, not 'str'." with self.assertRaisesRegexp(ValueError, exception): self.P(f=str) @@ -48,7 +48,7 @@ def test_multiple_class_instance_constructor2(self): self.assertEqual(p.g, 'A') def test_multiple_class_instance_error(self): - exception = "Parameter 'g' value must be an instance of \(int, str\), not '3.0'" + exception = "ClassSelector parameter 'g' value must be an instance of \(int, str\), not 3.0." with self.assertRaisesRegexp(ValueError, exception): self.P(g=3.0) @@ -67,7 +67,7 @@ def test_class_selector_get_range(self): self.assertIn('str', classes) def test_multiple_class_type_error(self): - exception = "Parameter 'float' must be a subclass of \(int, str\), not 'type'" + exception = "ClassSelector parameter 'h' must be a subclass of \(int, str\), not 'float'." with self.assertRaisesRegexp(ValueError, exception): self.P(h=float) @@ -92,6 +92,6 @@ class Test(param.Parameterized): items = param.Dict(valid_dict) test = Test() - exception = "Parameter 'items' value must be an instance of dict, not '3'" + exception = "Dict parameter 'items' value must be an instance of dict, not 3." with self.assertRaisesRegexp(ValueError, exception): test.items = 3 diff --git a/tests/API1/testpandas.py b/tests/API1/testpandas.py index 8bb54c751..6a6de1659 100644 --- a/tests/API1/testpandas.py +++ b/tests/API1/testpandas.py @@ -52,7 +52,7 @@ class Test(param.Parameterized): df = param.DataFrame(default=empty) test = Test() - exception = "Parameter 'df' value must be an instance of DataFrame, not '3'" + exception = "DataFrame parameter 'df' value must be an instance of DataFrame, not 3." with self.assertRaisesRegexp(ValueError, exception): test.df = 3 From fb5efeae3a75d7bf0a736a087dc0f44b611fa0ad Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 14 May 2021 13:52:25 +0200 Subject: [PATCH 4/8] Fixes for dynamic params --- param/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 5e66dae43..70cb21167 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -832,7 +832,7 @@ def crop_to_bounds(self, val): return val def _validate_bounds(self, val, bounds, inclusive_bounds): - if bounds is None or (val is None and self.allow_None): + if bounds is None or (val is None and self.allow_None) or callable(val): return vmin, vmax = bounds incmin, incmax = inclusive_bounds @@ -879,7 +879,7 @@ def _validate(self, val): self._validate_bounds(val, self.bounds, self.inclusive_bounds) def _on_set(self, attribute, old, new): - if self.owner is None: + if self.owner is None or self.time_fn: return if attribute == 'inclusive_bounds': return self._validate_bounds(getattr(self.owner, self.name), self.bounds, new) From 3b2fcc21ba02824e9d80b04a504dd3fd5033ffb3 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 31 May 2021 12:34:06 +0200 Subject: [PATCH 5/8] Removed on_set validators --- param/__init__.py | 41 +++++++---------------------------------- param/parameterized.py | 17 ----------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 70cb21167..921c1a7be 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -878,15 +878,6 @@ def _validate(self, val): self._validate_step(val, self.step) self._validate_bounds(val, self.bounds, self.inclusive_bounds) - def _on_set(self, attribute, old, new): - if self.owner is None or self.time_fn: - return - if attribute == 'inclusive_bounds': - return self._validate_bounds(getattr(self.owner, self.name), self.bounds, new) - elif attribute == 'bounds': - return self._validate_bounds(getattr(self.owner, self.name), new, self.inclusive_bounds) - super(Number, self)._on_set(attribute, old, new) - def get_soft_bounds(self): """ For each soft bound (upper and lower), if there is a defined @@ -1281,8 +1272,9 @@ class Selector(ObjectSelector): first argument is now objects, this can be passed in as a positional argument which sufficient in many common use cases. """ - def __init__(self,objects=None, default=None, instantiate=False, - compute_default_fn=None,check_on_set=None,allow_None=None,**params): + def __init__(self, objects=None, default=None, instantiate=False, + compute_default_fn=None, check_on_set=None, + allow_None=None, **params): if is_ordered_dict(objects): autodefault = list(objects.values())[0] @@ -1299,11 +1291,10 @@ def __init__(self,objects=None, default=None, instantiate=False, default = autodefault if default is None else default - super(Selector,self).__init__(default=default, objects=objects, - instantiate=instantiate, - compute_default_fn=compute_default_fn, - check_on_set=check_on_set, - allow_None=allow_None, **params) + super(Selector,self).__init__( + default=default, objects=objects, instantiate=instantiate, + compute_default_fn=compute_default_fn, + check_on_set=check_on_set, allow_None=allow_None, **params) class ClassSelector(SelectorBase): @@ -1322,15 +1313,6 @@ def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params) self._validate(default) - def _on_set(self, attribute, old, new): - if self.owner is None: - return - if attribute == 'class_': - return self._validate_class_(getattr(self.owner, self.name), new, self.is_instance) - elif attribute == 'is_instance': - return self._validate_class_(getattr(self.owner, self.name), self.class_, new) - super(ClassSelector, self)._on_set(attribute, old, new) - def _validate(self, val): super(ClassSelector, self)._validate(val) self._validate_class_(val, self.class_, self.is_instance) @@ -2040,15 +2022,6 @@ def __init__(self,default=None, bounds=None, softbounds=None, self.step = step super(Range,self).__init__(default=default,length=2,**params) - def _on_set(self, attribute, old, new): - if self.owner is None: - return - if attribute == 'inclusive_bounds': - return self._validate_bounds(getattr(self.owner, self.name), self.bounds, new) - elif attribute == 'bounds': - return self._validate_bounds(getattr(self.owner, self.name), new, self.inclusive_bounds) - super(Range, self)._on_set(attribute, old, new) - def _validate(self, val): super(Range, self)._validate(val) self._validate_bounds(val, self.bounds, self.inclusive_bounds) diff --git a/param/parameterized.py b/param/parameterized.py index 079a707ef..6cb24ce6c 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -979,15 +979,6 @@ def __set__(self, obj, val): if not obj.param._BATCH_WATCH: obj.param._batch_call_watchers() - def _on_set(self, attribute, old, new): - if self.owner is None: - return - if attribute == 'allow_None': - attribute = 'value' - validator = '_validate_%s' % attribute - if hasattr(self, validator): - getattr(self, validator)(getattr(self.owner, self.name), new) - def _validate_value(self, value, allow_None): """Implements validation for parameter value""" @@ -1066,14 +1057,6 @@ def __init__(self, default="", regex=None, allow_None=False, **kwargs): self.allow_None = (default is None or allow_None) self._validate(default) - def _on_set(self, attribute, old, new): - if self.owner is None: - return - if attribute == 'regex': - self._validate_regex(getattr(self.owner, self.name), new) - elif attribute == 'allow_None': - self._validate_value(getattr(self.owner, self.name), new) - def _validate_regex(self, val, regex): if (val is None and self.allow_None): return From a7afa40cb67f9c62b5a0dc4bcb2afb7b7b7ce5e6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 31 May 2021 12:38:41 +0200 Subject: [PATCH 6/8] Fix formatting --- param/parameterized.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/param/parameterized.py b/param/parameterized.py index 6cb24ce6c..cef9cd97a 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -706,9 +706,9 @@ class Foo(Bar): # attributes. Using __slots__ requires special support for # operations to copy and restore Parameters (e.g. for Python # persistent storage pickling); see __getstate__ and __setstate__. - __slots__ = ['name','_internal_name','default','doc', - 'precedence','instantiate','constant','readonly', - 'pickle_default_value','allow_None', 'per_instance', + __slots__ = ['name', '_internal_name', 'default', 'doc', + 'precedence', 'instantiate', 'constant', 'readonly', + 'pickle_default_value', 'allow_None', 'per_instance', 'watchers', 'owner', '_label'] # Note: When initially created, a Parameter does not know which @@ -717,7 +717,7 @@ class Foo(Bar): # class is created, owner, name, and _internal_name are # set. - _serializers = {'json':serializer.JSONSerialization} + _serializers = {'json': serializer.JSONSerialization} def __init__(self,default=None, doc=None, label=None, precedence=None, # pylint: disable-msg=R0913 instantiate=False, constant=False, readonly=False, @@ -796,14 +796,14 @@ class hierarchy (see ParameterizedMetaclass). """ self.name = None - self._internal_name = None self.owner = None - self._label = label self.precedence = precedence self.default = default self.doc = doc self.constant = constant or readonly # readonly => constant self.readonly = readonly + self._label = label + self._internal_name = None self._set_instantiate(instantiate) self.pickle_default_value = pickle_default_value self.allow_None = (default is None or allow_None) From a0387b56c39a1402aec9b5670b8f4897b6540e63 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 31 May 2021 12:46:09 +0200 Subject: [PATCH 7/8] Fix trailing whitespace --- param/parameterized.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/param/parameterized.py b/param/parameterized.py index cef9cd97a..95c609b69 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -748,7 +748,7 @@ def __init__(self,default=None, doc=None, label=None, precedence=None, # pylint False, so that a user can choose to change the value at the Parameterized instance level (affecting only that instance) or at the Parameterized class or superclass level (affecting all - existing and future instances of that class or superclass). For + existing and future instances of that class or superclass). For a mutable Parameter value, the default of False is also appropriate if you want all instances to share the same value state, e.g. if they are each simply referring to a single global object like From ef1b5e8bce86df08fb818e82bb0c345dd7b1c25b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 31 May 2021 12:49:58 +0200 Subject: [PATCH 8/8] Fix MultiFileSelector update --- param/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/param/__init__.py b/param/__init__.py index 921c1a7be..508e890db 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1859,6 +1859,11 @@ def __init__(self, default=None, path="", **kwargs): self.path = path self.update() + def _on_set(self, attribute, old, new): + super(MultiFileSelector, self)._on_set(attribute, new, old) + if attribute == 'path': + self.update() + def update(self): self.objects = sorted(glob.glob(self.path)) if self.default and all([o in self.objects for o in self.default]):