Source code for soma.serialization
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import sys
import six
'''
Framework to serialize/deserialize Python objects in JSON. The main
feature of this framework is that deserialization of a JSON value requires
no other information than the JSON itself. It is not necessary to know the
class of the object to deserialize. This imposes that the serialization
contains a reference to a factory that is responsible to build the Python
object given its JSON.
'''
[docs]class JSONSerializable(object):
'''
Instances of classes deriving from `JSONSerializable` can be serialized
in a JSON compatible object with :meth:`to_json` method. This JSON object
contains a reference to a factory as well as the full state of the
instance. Calling the factory with the state of the instance makes it
possible to recreate the instance (this is what function :func:`from_json`
is doing). A typical usage of this serialization system is to store the
JSON serialization of an instance in a configuration file (or database)
and latter (possibly in another Python instance) recreate the same
instance. This is an alternative to pickle allowing to use a standard
representation format (JSON) and to have full control on instances
creation.
'''
[docs] def to_json(self):
'''
Return a JSON serialization of self. The returned object can be given
to :func:`from_json` to create another instance that is eqivalent to
self.
Here, equivalent means that all attributes values are the same and the
methods called with the same parameters give the same results.
See :func:`from_json` to have insight of what is a JSON serialization
object.
'''
raise NotImplementedError()
[docs]def to_json(object):
'''
Return a JSON serialization of an object.
Standard types (None, bool, int, float, str, etc.) are using
json.dumps whereas other type of object must define a to_json
method returning the serialization.
'''
serializer = getattr(object, 'to_json', None)
if serializer is None:
if object is None:
return None
raise ValueError('Cannot serialize in JSON object of type {0}'.format(type(object)))
else:
return serializer()
[docs]def from_json(json_serialization):
'''
Takes a JSON serialization object (typically created by a
:meth:`JSONSerializable.to_json` method) and create and return the
corresponding instance.
A JSON serialization object can have one of the following structures:
- A simple string containing a factory reference
- A list with one, two or three of the following items:
- factory: a mandatory item containing a reference to a factory
(see :func:`find_factory`)
- args: an optional item containing a list of parameters values
for the factory
- kwargs: an optional item containing a dictionary of parameters
for the factory
A reference to a factory identifies a :class:`callable` (e.g a function or
a class) in a Python module. It is a string containing the module name
and the callable name separated by a dot. For instance
``'catidb.data_models.catidb_3_4'`` would identify the *catidb_3_4*
callable in the *catidb.data_models* module.
'''
if json_serialization is None:
return None
elif isinstance(json_serialization, six.string_types):
callable = find_factory(json_serialization)
return callable()
elif isinstance(json_serialization, list):
if len(json_serialization) == 3:
callable, args, kwargs = json_serialization
callable = find_factory(callable)
return callable(*args, **kwargs)
elif len(json_serialization) == 2:
callable, args_or_kwargs = json_serialization
if isinstance(args_or_kwargs, list):
callable = find_factory(callable)
return callable(*args_or_kwargs)
elif isinstance(args_or_kwargs, dict):
callable = find_factory(callable)
return callable(**args_or_kwargs)
elif len(json_serialization) == 1:
callable = find_factory(json_serialization[0])
return callable()
raise ValueError('Object is not a valid JSON serialization: %s' % repr(json_serialization))
[docs]def find_factory(reference):
'''
Finds a factory callable given its reference. The reference is simply
a string containing a module name and the name of fatory in that module
separated by a dot. For instance ``'my_packages.my_module.my_factory'``
would refer to the item *my_factory* in the module
``my_packages.my_module``.
This funcion simply loads the module and returns the module attribute with
the factory name.
:class:`exceptions.ValueError` is raised if the factory cannot be found.
'''
split = reference.rsplit('.', 1)
if len(split) != 2:
raise ValueError('%s is not a valid reference to a factory' % reference)
module_name, item_name = split
try:
__import__(module_name)
except ImportError as e:
raise ValueError('{0} is not a valid reference to a factory: {1}'.format(reference, e))
module = sys.modules[module_name]
factory = getattr(module, item_name, None)
if factory is None:
raise ValueError('{0} is not a valid reference to a factory: module {1} has no attribute {2}'.format(reference, module_name, item_name))
return factory