Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Selector to be a user friendly ObjectSelector #316

Merged
merged 7 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ def as_unicode(obj):
return unicode(obj)


def is_ordered_dict(d):
"""
Predicate checking for ordered dictionaries. OrderedDict is always
ordered, an vanilla Python dictionaries are ordered for Python 3.6+
jlstevens marked this conversation as resolved.
Show resolved Hide resolved
"""
py3_ordered_dicts = (sys.version_info.major == 3) and (sys.version_info.minor >= 6)
vanilla_odicts = (sys.version_info.major > 3) or py3_ordered_dicts
return isinstance(d, (OrderedDict))or (vanilla_odicts and isinstance(d, dict))


def hashable(x):
"""
Return a hashable version of the given object x, with lists and
Expand Down Expand Up @@ -981,7 +991,7 @@ def _post_setter(self, obj, val):
setattr(obj,a,v)


class Selector(Parameter):
class SelectorBase(Parameter):
"""
Parameter whose value must be chosen from a list of possibilities.

Expand All @@ -994,7 +1004,7 @@ def get_range(self):
raise NotImplementedError("get_range() must be implemented in subclasses.")


class ObjectSelector(Selector):
class ObjectSelector(SelectorBase):
"""
Parameter whose value must be one object from a list of possible objects.

Expand Down Expand Up @@ -1114,7 +1124,38 @@ def get_range(self):
return named_objs(self.objects, self.names)


class ClassSelector(Selector):
class Selector(ObjectSelector):
"""
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.
"""
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 = default if default else autodefault

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):
"""
Parameter whose value is a specified class or an instance of that class.
By default, requires an instance, but if is_instance=False, accepts a class instead.
Expand Down
120 changes: 120 additions & 0 deletions tests/API1/testselector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
Unit test for object selector parameters.

Originally implemented as doctests in Topographica in the file
testEnumerationParameter.txt
"""

import param
from . import API1TestCase
from collections import OrderedDict


opts=dict(A=[1,2],B=[3,4],C=dict(a=1,b=2))


class TestSelectorParameters(API1TestCase):

def setUp(self):
super(TestSelectorParameters, self).setUp()
class P(param.Parameterized):
e = param.Selector([5,6,7])
f = param.Selector(default=10)
h = param.Selector(default=None)
g = param.Selector([7,8])
i = param.Selector([9],default=7, check_on_set=False)
s = param.Selector(OrderedDict(one=1,two=2,three=3), default=3)
d = param.Selector(opts, default=opts['B'])

self.P = P

def test_set_object_constructor(self):
p = self.P(e=6)
self.assertEqual(p.e, 6)

def test_get_range_list(self):
r = self.P.param.params("g").get_range()
self.assertEqual(r['7'],7)
self.assertEqual(r['8'],8)

def test_get_range_dict(self):
r = self.P.param.params("s").get_range()
self.assertEqual(r['one'],1)
self.assertEqual(r['two'],2)

def test_get_range_mutable(self):
r = self.P.param.params("d").get_range()
self.assertEqual(r['A'],opts['A'])
self.assertEqual(r['C'],opts['C'])
self.d=opts['A']
self.d=opts['C']
self.d=opts['B']

def test_set_object_outside_bounds(self):
p = self.P(e=6)
try:
p.e = 9
except ValueError:
pass
else:
raise AssertionError("Object set outside range.")

def test_set_object_setattr(self):
p = self.P(e=6)
p.f = 9
self.assertEqual(p.f, 9)
p.g = 7
self.assertEqual(p.g, 7)
p.i = 12
self.assertEqual(p.i, 12)


def test_set_object_not_None(self):
p = self.P(e=6)
p.g = 7
try:
p.g = None
except ValueError:
pass
else:
raise AssertionError("Object set outside range.")

def test_set_object_setattr_post_error(self):
p = self.P(e=6)
p.f = 9
self.assertEqual(p.f, 9)
p.g = 7
try:
p.g = None
except ValueError:
pass
else:
raise AssertionError("Object set outside range.")

self.assertEqual(p.g, 7)
p.i = 12
self.assertEqual(p.i, 12)

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


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


if __name__ == "__main__":
import nose
nose.runmodule()