Skip to content

Commit

Permalink
Overriding the name Parameter is taken into account at the class an…
Browse files Browse the repository at this point in the history
…d instance level (#740)
  • Loading branch information
maximlt authored Jun 21, 2023
1 parent a92835d commit a8b64bf
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 7 deletions.
44 changes: 39 additions & 5 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -2844,8 +2844,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 @@ -2898,6 +2897,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 @@ -3038,14 +3068,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 @@ -3363,7 +3394,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 @@ -111,15 +111,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

0 comments on commit a8b64bf

Please sign in to comment.