Skip to content

Commit

Permalink
Replace ObjectSelector with Selector (#497)
Browse files Browse the repository at this point in the history
* Swapped Selector and ObjectSelector and deprecated ObjectSelector
* Updated tests to use Selector, not ObjectSelector
* Made Selector autodefault configurable
* Allow FileSelector and MultiFileSelector to accept initial values
* Made autodefault ignore empty objects lists
  • Loading branch information
jbednar authored Jul 2, 2021
1 parent e638a6c commit 5dbbf17
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 52 deletions.
93 changes: 49 additions & 44 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,10 +1156,14 @@ def get_range(self):
raise NotImplementedError("get_range() must be implemented in subclasses.")


class ObjectSelector(SelectorBase):
class Selector(SelectorBase):
"""
Parameter whose value must be one object from a list of possible objects.
By default, if no default is specfied, picks the first object from
the provided set of objects, as long as the objects are in an ordered
data collection.
check_on_set restricts the value to be among the current list of
objects. By default, if objects are initially supplied,
check_on_set is True, whereas if no objects are initially
Expand All @@ -1181,11 +1185,27 @@ class ObjectSelector(SelectorBase):

__slots__ = ['objects', 'compute_default_fn', 'check_on_set', 'names']

# ObjectSelector is usually used to allow selection from a list of
# Selector is usually used to allow selection from a list of
# existing objects, therefore instantiate is False by default.
def __init__(self, default=None, objects=None, instantiate=False,
def __init__(self, objects=None, default=None, instantiate=False,
compute_default_fn=None, check_on_set=None,
allow_None=None, **params):
allow_None=None, empty_default=False, **params):

autodefault = None
if objects:
if is_ordered_dict(objects):
autodefault = list(objects.values())[0]
elif isinstance(objects, dict):
main.param.warning("Parameter default value is arbitrary due to "
"dictionaries prior to Python 3.6 not being "
"ordered; should use an ordered dict or "
"supply an explicit default value.")
autodefault = list(objects.values())[0]
elif isinstance(objects, list):
autodefault = objects[0]

default = autodefault if (not empty_default and default is None) else default

if objects is None:
objects = []
if isinstance(objects, collections_abc.Mapping):
Expand All @@ -1203,7 +1223,7 @@ def __init__(self, default=None, objects=None, instantiate=False,
else:
self.check_on_set = True

super(ObjectSelector,self).__init__(
super(Selector,self).__init__(
default=default, instantiate=instantiate, **params)
# Required as Parameter sets allow_None=True if default is None
self.allow_None = allow_None
Expand Down Expand Up @@ -1235,11 +1255,11 @@ def _validate(self, val):
return

if not (val in self.objects or (self.allow_None and val is None)):
# CEBALERT: can be called before __init__ has called
# super's __init__, i.e. before attrib_name has been set.
try:
attrib_name = self.name
except AttributeError:
# This method can be called before __init__ has called
# super's __init__, so there may not be any name set yet.
if (hasattr(self, "name") and self.name):
attrib_name = " " + self.name
else:
attrib_name = ""

items = []
Expand All @@ -1254,7 +1274,7 @@ def _validate(self, val):
limiter = ', ...]'
break
items = '[' + ', '.join(items) + limiter
raise ValueError("%s not in Parameter %s's list of possible objects, "
raise ValueError("%s not in parameter%s's list of possible objects, "
"valid options include %s" % (val, attrib_name, items))

def _ensure_value_is_in_objects(self,val):
Expand All @@ -1275,36 +1295,14 @@ def get_range(self):
return named_objs(self.objects, self.names)


class Selector(ObjectSelector):
class ObjectSelector(Selector):
"""
A more user friendly ObjectSelector that picks the first object for
the default (by default) given an ordered data collection. As the
first argument is now objects, this can be passed in as a positional
argument which sufficient in many common use cases.
Deprecated. Same as Selector, but with a different constructor for
historical reasons.
"""
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]
elif isinstance(objects, dict):
main.param.warning("Parameter default value is arbitrary due to "
"dictionaries prior to Python 3.6 not being "
"ordered; should use an ordered dict or "
"supply an explicit default value.")
autodefault = list(objects.values())[0]
elif isinstance(objects, list):
autodefault = objects[0]
else:
autodefault = None

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)
def __init__(self, default=None, objects=None, **kwargs):
super(ObjectSelector,self).__init__(objects=objects, default=default,
empty_default=True, **kwargs)


class ClassSelector(SelectorBase):
Expand Down Expand Up @@ -1811,16 +1809,18 @@ def abbreviate_paths(pathspec,named_paths):



class FileSelector(ObjectSelector):
class FileSelector(Selector):
"""
Given a path glob, allows one file to be selected from those matching.
"""
__slots__ = ['path']

def __init__(self, default=None, path="", **kwargs):
super(FileSelector, self).__init__(default, **kwargs)
self.default = default
self.path = path
self.update()
super(FileSelector, self).__init__(default=default, objects=self.objects,
empty_default=True, **kwargs)

def _on_set(self, attribute, old, new):
super(FileSelector, self)._on_set(attribute, new, old)
Expand All @@ -1837,12 +1837,16 @@ def get_range(self):
return abbreviate_paths(self.path,super(FileSelector, self).get_range())


class ListSelector(ObjectSelector):
class ListSelector(Selector):
"""
Variant of ObjectSelector where the value can be multiple objects from
Variant of Selector where the value can be multiple objects from
a list of possible objects.
"""

def __init__(self, default=None, objects=None, **kwargs):
super(ListSelector,self).__init__(
objects=objects, default=default, empty_default=True, **kwargs)

def compute_default(self):
if self.default is None and callable(self.compute_default_fn):
self.default = self.compute_default_fn()
Expand All @@ -1863,9 +1867,10 @@ class MultiFileSelector(ListSelector):
__slots__ = ['path']

def __init__(self, default=None, path="", **kwargs):
super(MultiFileSelector, self).__init__(default, **kwargs)
self.default = default
self.path = path
self.update()
super(MultiFileSelector, self).__init__(default=default, objects=self.objects, **kwargs)

def _on_set(self, attribute, old, new):
super(MultiFileSelector, self)._on_set(attribute, new, old)
Expand Down
36 changes: 28 additions & 8 deletions tests/API1/testobjectselector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class TestObjectSelectorParameters(API1TestCase):
def setUp(self):
super(TestObjectSelectorParameters, self).setUp()
class P(param.Parameterized):
e = param.ObjectSelector(default=5,objects=[5,6,7])
f = param.ObjectSelector(default=10)
h = param.ObjectSelector(default=None)
g = param.ObjectSelector(default=None,objects=[7,8])
i = param.ObjectSelector(default=7,objects=[9],check_on_set=False)
s = param.ObjectSelector(default=3,objects=OrderedDict(one=1,two=2,three=3))
d = param.ObjectSelector(default=opts['B'],objects=opts)
e = param.Selector(default=5,objects=[5,6,7])
f = param.Selector(default=10)
h = param.Selector(default=None)
g = param.Selector(default=None,objects=[7,8])
i = param.Selector(default=7,objects=[9],check_on_set=False)
s = param.Selector(default=3,objects=OrderedDict(one=1,two=2,three=3))
d = param.Selector(default=opts['B'],objects=opts)

self.P = P

Expand Down Expand Up @@ -98,14 +98,34 @@ def test_set_object_setattr_post_error(self):
def test_initialization_out_of_bounds(self):
try:
class Q(param.Parameterized):
q = param.ObjectSelector(5,objects=[4])
q = param.Selector(default=5,objects=[4])
except ValueError:
pass
else:
raise AssertionError("ObjectSelector created outside range.")


def test_initialization_no_bounds(self):
try:
class Q(param.Parameterized):
q = param.Selector(default=5,objects=10)
except TypeError:
pass
else:
raise AssertionError("ObjectSelector created without range.")


def test_initialization_out_of_bounds_objsel(self):
try:
class Q(param.Parameterized):
q = param.ObjectSelector(5,objects=[4])
except ValueError:
pass
else:
raise AssertionError("ObjectSelector created outside range.")


def test_initialization_no_bounds_objsel(self):
try:
class Q(param.Parameterized):
q = param.ObjectSelector(5,objects=10)
Expand Down

0 comments on commit 5dbbf17

Please sign in to comment.