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

Overriding the name Parameter is taken into account at the class and instance level #740

Merged
merged 6 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 39 additions & 5 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -2680,8 +2680,7 @@ def __init__(mcs, name, bases, dict_):
"""
type.__init__(mcs, name, bases, dict_)

# Give Parameterized classes a useful 'name' attribute.
mcs.name = name
mcs.__set_name(name, dict_)

mcs._parameters_state = {
"BATCH_WATCH": False, # If true, Event and watcher objects are queued.
Expand Down Expand Up @@ -2734,6 +2733,37 @@ def __init__(mcs, name, bases, dict_):
if docstring_signature:
mcs.__class_docstring_signature()

def __set_name(mcs, name, dict_):
"""
Give Parameterized classes a useful 'name' attribute that is by
default the class name, unless a class in the hierarchy has defined
a `name` String Parameter with a defined `default` value, in which case
that value is used to set the class name.
"""
mcs.__renamed = False
name_param = dict_.get("name", None)
if name_param is not None:
if not type(name_param) is String:
raise TypeError(
f"Parameterized class {name!r} cannot override "
f"the 'name' Parameter with type {type(name_param)}. "
"Overriding 'name' is only allowed with a 'String' Parameter."
)
if name_param.default:
mcs.name = name_param.default
mcs.__renamed = True
else:
mcs.name = name
else:
classes = classlist(mcs)[::-1]
found_renamed = False
for c in classes:
if getattr(c, "_ParameterizedMetaclass__renamed", False):
found_renamed = True
break
if not found_renamed:
mcs.name = name

def __class_docstring_signature(mcs, max_repr_len=15):
"""
Autogenerate a keyword signature in the class docstring for
Expand Down Expand Up @@ -2894,14 +2924,15 @@ def __param_inheritance(mcs,param_name,param):
setattr(param,'objtype',mcs)
del slots['objtype']

supers = classlist(mcs)[::-1]

# instantiate is handled specially
for superclass in classlist(mcs)[::-1]:
for superclass in supers:
super_param = superclass.__dict__.get(param_name)
if isinstance(super_param, Parameter) and super_param.instantiate is True:
param.instantiate=True
del slots['instantiate']

supers = classlist(mcs)[::-1]
callables = {}
for slot in slots.keys():
superclasses = iter(supers)
Expand Down Expand Up @@ -3227,7 +3258,10 @@ def __init__(self, **params):
self._param_watchers = {}
self._dynamic_watchers = defaultdict(list)

self.param._generate_name()
# Skip generating a custom instance name when a class in the hierarchy
# has overriden the default of the `name` Parameter.
if self.param.name.default == self.__class__.__name__:
self.param._generate_name()
self.param._setup_params(**params)
object_count += 1

Expand Down
147 changes: 145 additions & 2 deletions tests/testparameterizedobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,158 @@ class P(param.Parameterized):

p = P()

match = re.fullmatch(r'P\w{5}', p.name)
assert match is not None
assert p.name == 'Other'

def test_parameter_name_fixed(self):
testpo = TestPO()

with pytest.raises(AttributeError):
testpo.param.const.name = 'notconst'

def test_name_overriden(self):
class P(param.Parameterized):
name = param.String(default='other')

assert P.name == 'other'

p = P()

assert p.name == 'other'

def test_name_overriden_without_default(self):
class A(param.Parameterized):
pass
class B(param.Parameterized):
name = param.String(doc='some help')

class C(B):
pass

assert B.name == 'B'
assert B.param.name.doc == 'some help'
assert C.name == 'C'
assert C.param.name.doc == 'some help'

def test_name_overriden_constructor(self):
class P(param.Parameterized):
name = param.String(default='other')

p = P(name='another')

assert p.name == 'another'

def test_name_overriden_subclasses(self):
class P(param.Parameterized):
name = param.String(default='other')

class Q(P):
pass

class R(Q):
name = param.String(default='yetanother')

assert Q.name == 'other'

q1 = Q()

assert q1.name == 'other'

q2 = Q(name='another')

assert q2.name == 'another'

assert R.name == 'yetanother'

r1 = R()

assert r1.name == 'yetanother'

r2 = R(name='last')

assert r2.name == 'last'


def test_name_overriden_subclasses_name_set(self):
class P(param.Parameterized):
name = param.String(default='other')

class Q(P):
pass

P.name = 'another'

assert Q.name == 'another'

Q.name = 'yetanother'

assert Q.name == 'yetanother'

q = Q()

assert q.name == 'yetanother'

def test_name_overriden_error_not_String(self):

msg = "Parameterized class 'P' cannot override the 'name' Parameter " \
"with type <class 'str'>. Overriding 'name' is only allowed with " \
"a 'String' Parameter."

with pytest.raises(TypeError, match=msg):
class P(param.Parameterized):
name = 'other'

msg = "Parameterized class 'P' cannot override the 'name' Parameter " \
"with type <class 'param.parameterized.Parameter'>. Overriding 'name' " \
"is only allowed with a 'String' Parameter."

with pytest.raises(TypeError, match=msg):
class P(param.Parameterized):
name = param.Parameter(default='other')

def test_name_complex_hierarchy(self):
class Mixin1: pass
class Mixin2: pass
class Mixin3(param.Parameterized): pass

class A(param.Parameterized, Mixin1): pass
class B(A): pass
class C(B, Mixin2): pass
class D(C, Mixin3): pass

assert A.name == 'A'
assert B.name == 'B'
assert C.name == 'C'
assert D.name == 'D'

def test_name_overriden_complex_hierarchy(self):
class Mixin1: pass
class Mixin2: pass
class Mixin3(param.Parameterized): pass

class A(param.Parameterized, Mixin1): pass
class B(A):
name = param.String(default='other')

class C(B, Mixin2):
name = param.String(default='another')

class D(C, Mixin3): pass

assert A.name == 'A'
assert B.name == 'other'
assert C.name == 'another'
assert D.name == 'another'

def test_name_overriden_multiple(self):
class A(param.Parameterized):
name = param.String(default='AA')
class B(param.Parameterized):
name = param.String(default='BB')

class C(A, B): pass

assert C.name == 'AA'

def test_constant_parameter(self):
"""Test that you can't set a constant parameter after construction."""
testpo = TestPO(const=17)
Expand Down