Hi @dumbledad
That is a good question. I’ve often thought about how to define a param.Parameter
of a complex type. Here are my thoughts and observations.
param.Dict
constructor has no class_
argument
The param.Dict
class inherits from the param.ClassSelector
and as such has already set the class_
argument to dict
.
The above is all the code defining param.Dict
.
The inheritance chain is Dict
<- ClassSelector
<- SelectorBase
<- Parameter
.
The validation is done in the _validate
function.
Parameter
has a _validate
function that ClassSelector
implements. This function you could override.
Then the question is which Class to override: Dict
, ClassSelector
, SelectorBase
or Parameter
.
For not knowing better I would choose the simplest solution Parameter
and look at how String
is implemented.
How to test for Dict[str, List[str]]?
You can get inspiration from https://stackoverflow.com/questions/58985396/python-check-if-dictionary-value-is-made-of-list-of-strings
Example Implementation
import param
class DictWithStringKeyAndListOfStringsValues(param.Parameter):
def __init__(self, default={}, allow_None=False, **kwargs):
super().__init__(default=default, allow_None=allow_None, **kwargs)
# I can see that the `Parameter` __init__ does not `_validate` the default
# Is that a bug?
self._validate(self.default)
def _validate(self, val):
if self.allow_None and val is None:
return
if not isinstance(val, dict):
raise ValueError(f"Error. The value {val} is not of type Dict[str, List]!")
for key in val.keys():
if not isinstance(key, str):
raise ValueError(f"Error. The key {key} is not of type str!")
for value in val.values():
if not isinstance(value, list):
raise ValueError(f"Error. The dictionary value {value} is not of type list!")
for list_val in value:
if not isinstance(list_val, str):
raise ValueError(f"Error. The list value {list_val} is not of type str!")
Test of Example Implementation
If I add the below tests and run Pytest I get
import pytest
class UnivariateConfig(param.Parameterized):
model_types = DictWithStringKeyAndListOfStringsValues(default={})
@pytest.mark.parametrize(["value"], [
({}, ),
({"a": []}, ),
({"a": ["b"]}, ),
])
def test_does_not_raise_error_when_correct_type(value):
config = UnivariateConfig(model_types=value)
assert config.model_types == value
@pytest.mark.parametrize(["value"], [
(None, ),
("str", ),
({"a": [None]}, ),
])
def test_raises_value_error_if_not_correct_type(value):
with pytest.raises(ValueError):
UnivariateConfig(model_types=value)
Test session starts (platform: win32, Python 3.7.6, pytest 5.4.2, pytest-sugar 0.9.3)
cachedir: .pytest_cache
rootdir: C:\repos\private\awesome-panel, inifile: pytest.ini
plugins: cov-2.8.1, mock-3.1.0, sugar-0.9.3
collecting ...
scripts\issue_param_class.py::test_does_not_raise_error_when_correct_type[value0] ✓ 17% █▋
scripts\issue_param_class.py::test_does_not_raise_error_when_correct_type[value1] ✓ 33% ███▍
scripts\issue_param_class.py::test_does_not_raise_error_when_correct_type[value2] ✓ 50% █████
scripts\issue_param_class.py::test_raises_value_error_if_not_correct_type[None] ✓ 67% ██████▋
scripts\issue_param_class.py::test_raises_value_error_if_not_correct_type[str] ✓ 83% ████████▍
scripts\issue_param_class.py::test_raises_value_error_if_not_correct_type[value2] ✓ 100% ██████████
Results (0.23s):
6 passed
Final Reflections
The above validation might slow down your code, so it depends on your use case if you would like that extra validation. Maybe it’s best just to go with param.Dict
as @Leonidas says?