Skip to content

Commit

Permalink
JSON serialization and schema generation (#414)
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed Jul 13, 2020
1 parent a8e34b2 commit b89310a
Show file tree
Hide file tree
Showing 5 changed files with 544 additions and 4 deletions.
42 changes: 41 additions & 1 deletion param/__init__.py
Expand Up @@ -1014,6 +1014,10 @@ def _validate(self, val):
(self.name,len(val),self.length))


@classmethod
def deserialize(cls, value):
return tuple(value) # As JSON has no tuple representation


class NumericTuple(Tuple):
"""A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length."""
Expand Down Expand Up @@ -1426,6 +1430,15 @@ def __init__(self, default=None, **params):
from numpy import ndarray
super(Array,self).__init__(ndarray, allow_None=True, default=default, **params)

@classmethod
def serialize(cls, value):
return value.tolist()

@classmethod
def deserialize(cls, value):
from numpy import asarray
return asarray(value)


class DataFrame(ClassSelector):
"""
Expand All @@ -1452,7 +1465,7 @@ def __init__(self, default=None, rows=None, columns=None, ordered=None, **params
self.rows = rows
self.columns = columns
self.ordered = ordered
super(DataFrame,self).__init__(pdDFrame, default=default, allow_None=True, **params)
super(DataFrame,self).__init__(pdDFrame, default=default, **params)
self._validate(self.default)


Expand Down Expand Up @@ -1497,6 +1510,15 @@ def _validate(self, val):
if self.rows is not None:
self._length_bounds_check(self.rows, len(val), 'Row')

@classmethod
def serialize(cls, value):
return value.to_dict('records')

@classmethod
def deserialize(cls, value):
from pandas import DataFrame as pdDFrame
return pdDFrame(value)


class Series(ClassSelector):
"""
Expand Down Expand Up @@ -1838,6 +1860,16 @@ def _validate(self, val):

self._checkBounds(val)

@classmethod
def serialize(cls, value):
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")


class CalendarDate(Number):
"""
Expand All @@ -1863,6 +1895,14 @@ def _validate(self, val):

self._checkBounds(val)

@classmethod
def serialize(cls, value):
return value.strftime("%Y-%m-%d")

@classmethod
def deserialize(cls, value):
return dt.datetime.strptime(value, "%Y-%m-%d").date()


class Color(Parameter):
"""
Expand Down
53 changes: 51 additions & 2 deletions param/parameterized.py
Expand Up @@ -11,6 +11,13 @@
import numbers
import operator

# Allow this file to be used standalone if desired, albeit without JSON serialization
try:
from . import serializer
except ImportError:
serializer = None


from collections import defaultdict, namedtuple, OrderedDict
from operator import itemgetter,attrgetter
from types import FunctionType
Expand Down Expand Up @@ -687,6 +694,8 @@ class Foo(Bar):
# class is created, owner, name, and _internal_name are
# set.

_serializers = {'json':serializer.JSONSerialization}

def __init__(self,default=None,doc=None,label=None,precedence=None, # pylint: disable-msg=R0913
instantiate=False,constant=False,readonly=False,
pickle_default_value=True, allow_None=False,
Expand Down Expand Up @@ -731,6 +740,25 @@ class hierarchy (see ParameterizedMetaclass).
self.per_instance = per_instance


@classmethod
def serialize(cls, value):
"Given the parameter value, return a Python value suitable for serialization"
return value

@classmethod
def deserialize(cls, value):
"Given a serializable Python value, return a value that the parameter can be set to"
return value

def schema(self, safe=False, subset=None, mode='json'):
if serializer is None:
raise ImportError('Cannot import serializer.py needed to generate schema')
if mode not in self._serializers:
raise KeyError('Mode %r not in available serialization formats %r'
% (mode, list(self._serializers.keys())))
return self._serializers[mode].parameter_schema(self.__class__.__name__, self,
safe=safe, subset=subset)

@property
def label(self):
if self.name and self._label is None:
Expand Down Expand Up @@ -1136,7 +1164,7 @@ def __setstate__(self, state):
self_or_cls._parameters_state[k] = state.pop(key)
for k, v in state.items():
setattr(self, k, v)

def __getitem__(self_, key):
"""
Returns the class or instance parameter
Expand Down Expand Up @@ -1566,6 +1594,27 @@ class or instance that contains an iterable collection of
for obj in sublist:
obj.param.set_dynamic_time_fn(time_fn,sublistattr)

def serialize_parameters(self_, subset=None, mode='json'):
self_or_cls = self_.self_or_cls
if mode not in Parameter._serializers:
raise KeyError('Mode %r not in available serialization formats %r'
% (mode, list(Parameter._serializers.keys())))
serializer = Parameter._serializers[mode]
return serializer.serialize_parameters(self_or_cls, subset=subset)

def deserialize_parameters(self_, serialization, subset=None, mode='json'):
self_or_cls = self_.self_or_cls
serializer = Parameter._serializers[mode]
return serializer.deserialize_parameters(self_or_cls, serialization, subset=subset)

def schema(self_, safe=False, subset=None, mode='json'):
self_or_cls = self_.self_or_cls
if mode not in Parameter._serializers:
raise KeyError('Mode %r not in available serialization formats %r'
% (mode, list(Parameter._serializers.keys())))
serializer = Parameter._serializers[mode]
return serializer.schema(self_or_cls, safe=safe, subset=subset)

def get_param_values(self_,onlychanged=False):
"""
Return a list of name,value pairs for all Parameters of this
Expand Down Expand Up @@ -1934,7 +1983,7 @@ def __init__(mcs,name,bases,dict_):
"watchers": [] # Queue of batched watchers
}
mcs._param = Parameters(mcs)


# All objects (with their names) of type Parameter that are
# defined in this class
Expand Down

0 comments on commit b89310a

Please sign in to comment.