Skip to content
Open
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
6 changes: 6 additions & 0 deletions param/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,9 @@ def _validate_value(self, value: t.Any, allow_None: bool) -> None:
if self.allow_None and value is None:
return

if callable(value) and not inspect.isgeneratorfunction(value):
return

if not isinstance(value, _dt_types) and not (allow_None and value is None):
raise ValueError(
f"{_validate_error_prefix(self)} only takes datetime and "
Expand Down Expand Up @@ -1324,6 +1327,9 @@ def _validate_value(self, value, allow_None):
if self.allow_None and value is None:
return

if callable(value) and not inspect.isgeneratorfunction(value):
return

if (not isinstance(value, dt.date) or isinstance(value, dt.datetime)) and not (allow_None and value is None):
raise ValueError(
f"{_validate_error_prefix(self)} only takes date types."
Expand Down
44 changes: 44 additions & 0 deletions tests/testdynamicparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,50 @@ def test_dynamic_shared_numbergen(self):
self.assertNotEqual(call_1, t12.x)


class TestDynamicCallableAcrossNumberSiblings(unittest.TestCase):
"""
Number, Integer, Date and CalendarDate all subclass Dynamic, whose
contract is that any such parameter may be set to a callable that is
invoked to produce the value on get. Every sibling must accept a callable
uniformly; Date and CalendarDate used to reject it at validation time.
"""

def setUp(self):
super().setUp()
param.Dynamic.time_dependent = False

import datetime as dt

self.cases = [
(param.Number, lambda: 5, 5),
(param.Integer, lambda: 5, 5),
(param.Date, lambda: dt.datetime(2021, 5, 5), dt.datetime(2021, 5, 5)),
(param.CalendarDate, lambda: dt.date(2021, 5, 5), dt.date(2021, 5, 5)),
]

def test_set_callable_and_get_resolves(self):
for ptype, gen, expected in self.cases:
with self.subTest(ptype=ptype.__name__):

class P(param.Parameterized):
v = ptype()

p = P()
# Setting a callable must not raise for any Dynamic sibling.
p.v = gen
# Getting resolves the dynamic value by calling the callable.
self.assertEqual(p.v, expected)

def test_callable_as_constructor_default(self):
for ptype, gen, expected in self.cases:
with self.subTest(ptype=ptype.__name__):

class P(param.Parameterized):
v = ptype(default=gen)

self.assertEqual(P().v, expected)


# Commented out block in the original doctest version.
# Maybe these are features originally planned but never implemented

Expand Down