diff --git a/param/parameterized.py b/param/parameterized.py
index 398deb2cf..3622cfbc2 100644
--- a/param/parameterized.py
+++ b/param/parameterized.py
@@ -17,6 +17,7 @@
import operator
import typing
import warnings
+import html
# Allow this file to be used standalone if desired, albeit without JSON serialization
try:
@@ -3612,45 +3613,62 @@ def type_script_repr(type_,imports,prefix,settings):
dbprint_prefix=None
-def _name_if_set(parameterized):
- """Return the name of this Parameterized if explicitly set to other than the default"""
- class_name = parameterized.__class__.__name__
- default_name = re.match('^'+class_name+'[0-9]+$', parameterized.name)
- return '' if default_name else parameterized.name
+def truncate(str_, maxlen = 30):
+ """Return HTML-safe truncated version of given string"""
+ rep = (str_[:(maxlen-2)] + '..') if (len(str_) > (maxlen-2)) else str_
+ return html.escape(rep)
-def _get_param_repr(key, val, p, truncate=40):
+def _get_param_repr(key, val, p, vallen=30, doclen=40):
"""HTML representation for a single Parameter object and its value"""
if isinstance(val, Parameterized) or (type(val) is type and issubclass(val, Parameterized)):
value = val.param._repr_html_(open=False)
elif hasattr(val, "_repr_html_"):
value = val._repr_html_()
else:
- rep = repr(val)
- value = (rep[:truncate] + '..') if len(rep) > truncate else rep
+ value = truncate(repr(val), vallen)
- modes = []
- if p.constant:
- modes.append('constant')
- if p.readonly:
- modes.append('read-only')
- if getattr(p, 'allow_None', False):
- modes.append('nullable')
- mode = ' | '.join(modes)
if hasattr(p, 'bounds'):
- bounds = p.bounds
+ if p.bounds is None:
+ range_ = ''
+ elif hasattr(p,'inclusive_bounds'):
+ # Numeric bounds use ( and [ to indicate exclusive and inclusive
+ bl,bu = p.bounds
+ il,iu = p.inclusive_bounds
+
+ lb = '' if bl is None else ('>=' if il else '>') + str(bl)
+ ub = '' if bu is None else ('<=' if iu else '<') + str(bu)
+ range_ = lb + (', ' if lb and bu else '') + ub
+ else:
+ range_ = repr(p.bounds)
elif hasattr(p, 'objects') and p.objects:
- bounds = ', '.join(list(map(repr, p.objects)))
+ range_ = ', '.join(list(map(repr, p.objects)))
+ elif hasattr(p, 'class_'):
+ if isinstance(p.class_, tuple):
+ range_ = ' | '.join(kls.__name__ for kls in p.class_)
+ else:
+ range_ = p.class_.__name__
+ elif hasattr(p, 'regex') and p.regex is not None:
+ range_ = f'regex({p.regex})'
else:
- bounds = ''
+ range_ = ''
+
+ if p.readonly:
+ range_ = ' '.join(s for s in ['read-only', range_] if s)
+ elif p.constant:
+ range_ = ' '.join(s for s in ['constant', range_] if s)
+
+ if getattr(p, 'allow_None', False):
+ range_ = ' '.join(s for s in ['nullable', range_] if s)
+
tooltip = f' class="param-doc-tooltip" data-tooltip="{escape(p.doc.strip())}"' if p.doc else ''
+
return (
f'
'
- f' | {key} | '
- f' {p.__class__.__name__} | '
- f' {value} | '
- f' {bounds} | '
- f' {mode} | '
+ f' {key} | '
+ f' {value} | '
+ f' {p.__class__.__name__} | '
+ f' {range_} | '
f'
\n'
)
@@ -3659,7 +3677,7 @@ def _parameterized_repr_html(p, open):
"""HTML representation for a Parameterized object"""
if isinstance(p, Parameterized):
cls = p.__class__
- title = cls.name + "() " + _name_if_set(p)
+ title = cls.name + "()"
value_field = 'Value'
else:
cls = p
@@ -3669,12 +3687,12 @@ def _parameterized_repr_html(p, open):
tooltip_css = """
.param-doc-tooltip{
position: relative;
+ cursor: help;
}
.param-doc-tooltip:hover:after{
content: attr(data-tooltip);
background-color: black;
color: #fff;
- text-align: center;
border-radius: 3px;
padding: 10px;
position: absolute;
@@ -3682,8 +3700,7 @@ def _parameterized_repr_html(p, open):
top: -5px;
left: 100%;
margin-left: 10px;
- min-width: 100px;
- min-width: 150px;
+ min-width: 250px;
}
.param-doc-tooltip:hover:before {
content: "";
@@ -3708,7 +3725,7 @@ def _parameterized_repr_html(p, open):
' \n'
' \n'
'
\n'
- f' | Name | Type | {value_field} | Bounds/Objects | Mode |
\n'
+ f' | Name | {value_field} | Type | Range |
\n'
f'{contents}\n'
'
\n
\n\n'
)
diff --git a/tests/testparameterizedrepr.py b/tests/testparameterizedrepr.py
index 470914c94..c5c01b528 100644
--- a/tests/testparameterizedrepr.py
+++ b/tests/testparameterizedrepr.py
@@ -252,48 +252,3 @@ class T(P): pass
)
assert t.param.pprint() == 'T()'
-
-
-# HTML repr
-
-class TestHTMLRepr:
-
- @pytest.fixture
- def Sub(self):
- class Sub(param.Parameterized):
- xsub = param.Parameter()
-
- return Sub
-
- @pytest.fixture
- def P(self, Sub):
- class P(param.Parameterized):
- constant = param.Parameter(constant=True)
- readonly = param.Parameter(readonly=True)
- allow_None = param.Parameter(1, allow_None=False)
- bounds = param.Number(bounds=(-10, 10))
- objects_list = param.Selector(objects=[1, 2])
- objects_dict = param.Selector(objects=dict(a=1, b=2))
- sub = param.ClassSelector(default=Sub(xsub=1), class_=Sub)
-
- return P
-
- def test_repr_in_place(self):
- assert hasattr(param.Parameterized.param, '_repr_html_')
- assert hasattr(param.Parameterized().param, '_repr_html_')
-
- def test_html_repr_class_str(self, P):
- html = P.param._repr_html_()
- assert isinstance(html, str)
-
- def test_html_repr_inst_str(self, P):
- html = P().param._repr_html_()
- assert isinstance(html, str)
-
- def test_html_repr_class_sub(self, P):
- html = P.param._repr_html_()
- assert '\n \n Sub' in html
-
- def test_html_repr_inst_sub(self, P):
- html = P().param._repr_html_()
- assert '\n \n Sub' in html
diff --git a/tests/testreprhtml.py b/tests/testreprhtml.py
new file mode 100644
index 000000000..d989eb0a1
--- /dev/null
+++ b/tests/testreprhtml.py
@@ -0,0 +1,62 @@
+import param
+import pytest
+
+from param.parameterized import _parameterized_repr_html
+
+
+class TestHTMLRepr:
+
+ @pytest.fixture
+ def Sub(self):
+ class Sub(param.Parameterized):
+ xsub = param.Parameter()
+
+ return Sub
+
+ @pytest.fixture
+ def P(self, Sub):
+ class P(param.Parameterized):
+ constant = param.Parameter(constant=True)
+ readonly = param.Parameter(readonly=True)
+ allow_None = param.Parameter(1, allow_None=False)
+ bounds = param.Number(bounds=(-10, 10))
+ objects_list = param.Selector(objects=[1, 2])
+ objects_dict = param.Selector(objects=dict(a=1, b=2))
+ sub = param.ClassSelector(default=Sub(xsub=1), class_=Sub)
+
+ return P
+
+ def test_repr_in_place(self):
+ assert hasattr(param.Parameterized.param, '_repr_html_')
+ assert hasattr(param.Parameterized().param, '_repr_html_')
+
+ def test_html_repr_class_str(self, P):
+ html = P.param._repr_html_()
+ assert isinstance(html, str)
+
+ def test_html_repr_inst_str(self, P):
+ html = P().param._repr_html_()
+ assert isinstance(html, str)
+
+ def test_html_repr_class_sub(self, P):
+ html = P.param._repr_html_()
+ assert '\n \n Sub' in html
+
+ def test_html_repr_inst_sub(self, P):
+ html = P().param._repr_html_()
+ assert '\n \n Sub' in html
+
+ def test_html_repr_ClassSelector_tuple(self):
+ class P(param.Parameterized):
+ c = param.ClassSelector(class_=(str, int))
+
+ rhtml = _parameterized_repr_html(P, True)
+ assert 'str | int' in rhtml
+
+ def test_html_repr_title_class(self, P):
+ html = P.param._repr_html_()
+ assert 'P' in html
+
+ def test_html_repr_title_instance(self, P):
+ html = P().param._repr_html_()
+ assert 'P()' in html