From daded08f0dd16a41932e19acf867f4155482362b Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 11:56:40 +0000 Subject: [PATCH 1/7] Made Date and CalendarDate schemas nullable --- param/serializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/param/serializer.py b/param/serializer.py index 1f36b3c38..6035eb084 100644 --- a/param/serializer.py +++ b/param/serializer.py @@ -179,11 +179,11 @@ def dict_schema(cls, p, safe=False): @classmethod def date_schema(cls, p, safe=False): - return {'type': 'string', 'format': 'date-time'} + return JSONNullable({'type': 'string', 'format': 'date-time'}) @classmethod def calendardate_schema(cls, p, safe=False): - return {'type': 'string', 'format': 'date'} + return JSONNullable({'type': 'string', 'format': 'date'}) @classmethod def tuple_schema(cls, p, safe=False): From 75d9275de01a576b0a545426af931249c37bb523 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 11:57:27 +0000 Subject: [PATCH 2/7] Updated Date and CalendarDate serialization methods to support None --- param/__init__.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index d56531cff..b7b5c0b01 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1925,13 +1925,18 @@ def _validate_step(self, val, step): @classmethod def serialize(cls, value): + if value is None: + return 'null' if not isinstance(value, (dt.datetime, dt.date)): # i.e np.datetime64 value = value.astype(dt.datetime) return value.strftime("%Y-%m-%dT%H:%M:%S.%f") @classmethod def deserialize(cls, value): - return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") + if value == 'null': + return None + else: + return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") class CalendarDate(Number): @@ -1959,11 +1964,17 @@ def _validate_step(self, val, step): @classmethod def serialize(cls, value): - return value.strftime("%Y-%m-%d") + if value is None: + return 'null' + else: + return value.strftime("%Y-%m-%d") @classmethod def deserialize(cls, value): - return dt.datetime.strptime(value, "%Y-%m-%d").date() + if value == 'null': + return None + else: + return dt.datetime.strptime(value, "%Y-%m-%d").date() class Color(Parameter): From 8d3da07445699769a1b83eb295208f324f06cd15 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 11:58:06 +0000 Subject: [PATCH 3/7] Added unit tests for None valued Dates and CalendarDates --- tests/API1/testjsonserialization.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/API1/testjsonserialization.py b/tests/API1/testjsonserialization.py index 82015405e..ca67fc002 100644 --- a/tests/API1/testjsonserialization.py +++ b/tests/API1/testjsonserialization.py @@ -68,6 +68,8 @@ class TestSet(param.Parameterized): {'A':[1,2,3], 'B':[1.1,2.2,3.3]}), columns=(1,4), rows=(2,5)) u = None if pd is None else param.DataFrame(default=df2, columns=['A', 'B']) v = param.Dict({'1':2}) + w = param.Date(default=None, allow_None=True) + x = param.CalendarDate(default=None, allow_None=True) test = TestSet(a=29) From 21678311cd482afc3fa6f5feeaaf22b7e66b27b5 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 15:32:51 +0000 Subject: [PATCH 4/7] Reverted unnecessary extra wrapping of JSONNullable --- param/serializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/param/serializer.py b/param/serializer.py index 6035eb084..1f36b3c38 100644 --- a/param/serializer.py +++ b/param/serializer.py @@ -179,11 +179,11 @@ def dict_schema(cls, p, safe=False): @classmethod def date_schema(cls, p, safe=False): - return JSONNullable({'type': 'string', 'format': 'date-time'}) + return {'type': 'string', 'format': 'date-time'} @classmethod def calendardate_schema(cls, p, safe=False): - return JSONNullable({'type': 'string', 'format': 'date'}) + return {'type': 'string', 'format': 'date'} @classmethod def tuple_schema(cls, p, safe=False): From 8c525fbbdaf0f0446c39603a33e79956f663e017 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 16:11:43 +0000 Subject: [PATCH 5/7] Added None and null support for Tuple, Array and DataFrame --- param/__init__.py | 21 ++++++++++++++++++--- tests/API1/testjsonserialization.py | 7 +++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index b7b5c0b01..1d2404663 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1017,10 +1017,15 @@ def _validate(self, val): @classmethod def serialize(cls, value): - return list(value) # As JSON has no tuple representation + if value is None: + return 'null' + else: + return list(value) # As JSON has no tuple representation @classmethod def deserialize(cls, value): + if value == 'null': + return None return tuple(value) # As JSON has no tuple representation @@ -1474,10 +1479,15 @@ def __init__(self, default=None, **params): @classmethod def serialize(cls, value): - return value.tolist() + if value is None: + return 'null' + else: + return value.tolist() @classmethod def deserialize(cls, value): + if value == 'null': + return None from numpy import asarray return asarray(value) @@ -1557,10 +1567,15 @@ def _validate(self, val): @classmethod def serialize(cls, value): - return value.to_dict('records') + if value is None: + return 'null' + else: + return value.to_dict('records') @classmethod def deserialize(cls, value): + if value == 'null': + return None from pandas import DataFrame as pdDFrame return pdDFrame(value) diff --git a/tests/API1/testjsonserialization.py b/tests/API1/testjsonserialization.py index ca67fc002..4f938beaf 100644 --- a/tests/API1/testjsonserialization.py +++ b/tests/API1/testjsonserialization.py @@ -41,8 +41,8 @@ class TestSet(param.Parameterized): __test__ = False - numpy_params = ['r'] - pandas_params = ['s','t','u'] + numpy_params = ['r','y'] + pandas_params = ['s','t','u','z'] conditionally_unsafe = ['f', 'o'] a = param.Integer(default=5, doc='Example doc', bounds=(2,30), inclusive_bounds=(True, False)) @@ -70,6 +70,9 @@ class TestSet(param.Parameterized): v = param.Dict({'1':2}) w = param.Date(default=None, allow_None=True) x = param.CalendarDate(default=None, allow_None=True) + y = None if np is None else param.Array(default=None) + z = None if pd is None else param.DataFrame(default=None, allow_None=True) + aa = param.Tuple(default=None, allow_None=True, length=1) test = TestSet(a=29) From 5cd27c0d5a79770403fa02e1a64d50a3fb73f568 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 16:12:20 +0000 Subject: [PATCH 6/7] Fixed support for None in pandas equality comparison --- tests/API1/testjsonserialization.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/API1/testjsonserialization.py b/tests/API1/testjsonserialization.py index 4f938beaf..f4b6d070f 100644 --- a/tests/API1/testjsonserialization.py +++ b/tests/API1/testjsonserialization.py @@ -189,7 +189,10 @@ def test_pandas_instance_serialization(self): serialized = test.param.serialize_parameters(subset=test.pandas_params, mode=self.mode) deserialized = TestSet.param.deserialize_parameters(serialized, mode=self.mode) for pname in test.pandas_params: - self.assertTrue(getattr(test, pname).equals(deserialized[pname])) + if getattr(test, pname) is None: + self.assertTrue(deserialized[pname] is None) + else: + self.assertTrue(getattr(test, pname).equals(deserialized[pname])) From a3e5777784a2a0a9321747b6c31fef61c5726aba Mon Sep 17 00:00:00 2001 From: jlstevens Date: Wed, 15 Dec 2021 19:32:54 +0000 Subject: [PATCH 7/7] Consistently using early return style --- param/__init__.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 1d2404663..b0f509321 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -1019,8 +1019,7 @@ def _validate(self, val): def serialize(cls, value): if value is None: return 'null' - else: - return list(value) # As JSON has no tuple representation + return list(value) # As JSON has no tuple representation @classmethod def deserialize(cls, value): @@ -1481,8 +1480,7 @@ def __init__(self, default=None, **params): def serialize(cls, value): if value is None: return 'null' - else: - return value.tolist() + return value.tolist() @classmethod def deserialize(cls, value): @@ -1569,8 +1567,7 @@ def _validate(self, val): def serialize(cls, value): if value is None: return 'null' - else: - return value.to_dict('records') + return value.to_dict('records') @classmethod def deserialize(cls, value): @@ -1950,8 +1947,7 @@ def serialize(cls, value): def deserialize(cls, value): if value == 'null': return None - else: - return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") + return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") class CalendarDate(Number): @@ -1981,15 +1977,13 @@ def _validate_step(self, val, step): def serialize(cls, value): if value is None: return 'null' - else: - return value.strftime("%Y-%m-%d") + return value.strftime("%Y-%m-%d") @classmethod def deserialize(cls, value): if value == 'null': return None - else: - return dt.datetime.strptime(value, "%Y-%m-%d").date() + return dt.datetime.strptime(value, "%Y-%m-%d").date() class Color(Parameter):