Source code for openforcefield.utils.serialization

#!/usr/bin/env python
"""
Serialization mix-in

.. todo ::

   Currently, the ``openforcefield`` toolkit package requires a number of dependencies to support all of these serialization protocols.
   Instead, should we not include these by default, and instead raise a helpful exception with installation instructions if one of the serialization schemes is called but the requisite library is not installed?

"""

#=============================================================================================
# GLOBAL IMPORTS
#=============================================================================================

import abc


#=============================================================================================
# SERIALIZATION MIX-IN
#=============================================================================================


[docs]class Serializable(abc.ABC): """Mix-in to add serialization and deserialization support via JSON, YAML, BSON, TOML, MessagePack, and XML. For more information on these formats, see: `JSON <https://www.json.org/>`_, `BSON <http://bsonspec.org/>`_, `YAML <http://yaml.org/>`_, `TOML <https://github.com/toml-lang/toml>`_, `MessagePack <https://msgpack.org/index.html>`_, and `XML <https://www.w3.org/XML/>`_. To use this mix-in, the class inheriting from this class must have implemented ``to_dict()`` and ``from_dict()`` methods that utilize dictionaries containing only serialiable Python objects. .. warning :: The serialization/deserialiation schemes used here place some strict constraints on what kinds of ``dict`` objects can be serialized. No effort is made to add further protection to ensure serialization is possible. Use with caution. Examples -------- Example class using :class:`Serializable` mix-in: >>> from openforcefield.utils.serialization import Serializable >>> class Thing(Serializable): ... def __init__(self, description): ... self.description = description ... ... def to_dict(self): ... return { 'description' : self.description } ... ... @classmethod ... def from_dict(cls, d): ... return cls(d['description']) ... >>> # Create an example object >>> thing = Thing('blorb') Get `JSON <https://www.json.org/>`_ representation: >>> json_thing = thing.to_json() Reconstruct an object from its `JSON <https://www.json.org/>`_ representation: >>> thing_from_json = Thing.from_json(json_thing) Get `BSON <http://bsonspec.org/>`_ representation: >>> bson_thing = thing.to_bson() Reconstruct an object from its `BSON <http://bsonspec.org/>`_ representation: >>> thing_from_bson = Thing.from_bson(bson_thing) Get `YAML <http://yaml.org/>`_ representation: >>> yaml_thing = thing.to_yaml() Reconstruct an object from its `YAML <http://yaml.org/>`_ representation: >>> thing_from_yaml = Thing.from_yaml(yaml_thing) Get `MessagePack <https://msgpack.org/index.html>`_ representation: >>> messagepack_thing = thing.to_messagepack() Reconstruct an object from its `MessagePack <https://msgpack.org/index.html>`_ representation: >>> thing_from_messagepack = Thing.from_messagepack(messagepack_thing) Get `XML <https://www.w3.org/XML/>`_ representation: >>> xml_thing = thing.to_xml() """ @abc.abstractmethod def to_dict(self): pass @classmethod @abc.abstractmethod def from_dict(cls, d): pass
[docs] def to_json(self, indent=None): """ Return a JSON serialized representation. Specification: https://www.json.org/ Parameters ---------- indent : int, optional, default=None If not None, will pretty-print with specified number of spaces for indentation Returns ------- serialized : str A JSON serialized representation of the object """ import json d = self.to_dict() return json.dumps(d, indent=indent)
[docs] @classmethod def from_json(cls, serialized): """ Instantiate an object from a JSON serialized representation. Specification: https://www.json.org/ Parameters ---------- serialized : str A JSON serialized representation of the object Returns ------- instance : cls An instantiated object """ import json d = json.loads(serialized) return cls.from_dict(d)
[docs] def to_bson(self): """ Return a BSON serialized representation. Specification: http://bsonspec.org/ Returns ------- serialized : bytes A BSON serialized representation of the objecft """ import bson d = self.to_dict() return bson.dumps(d)
[docs] @classmethod def from_bson(cls, serialized): """ Instantiate an object from a BSON serialized representation. Specification: http://bsonspec.org/ Parameters ---------- serialized : bytes A BSON serialized representation of the object Returns ------- instance : cls An instantiated object """ import bson d = bson.loads(serialized) return cls.from_dict(d)
[docs] def to_toml(self): """ Return a TOML serialized representation. Specification: https://github.com/toml-lang/toml Returns ------- serialized : str A TOML serialized representation of the object """ raise NotImplementedError() # TODO: This implementation currently discards dict keys associated to the None value. # See test_utils_serialization::TestUtilsSMIRNOFFSerialization::test_toml. import toml d = self.to_dict() return toml.dumps(d)
[docs] @classmethod def from_toml(cls, serialized): """ Instantiate an object from a TOML serialized representation. Specification: https://github.com/toml-lang/toml Parameters ---------- serlialized : str A TOML serialized representation of the object Returns ------- instance : cls An instantiated object """ import toml d = toml.loads(serialized) return cls.from_dict(d)
@staticmethod def _represent_odict(dump, tag, mapping, flow_style=None): """Like BaseRepresenter.represent_mapping, but does not issue the sort(). """ import yaml value = [] node = yaml.MappingNode(tag, value, flow_style=flow_style) if dump.alias_key is not None: dump.represented_objects[dump.alias_key] = node best_style = True if hasattr(mapping, 'items'): mapping = mapping.items() for item_key, item_value in mapping: node_key = dump.represent_data(item_key) node_value = dump.represent_data(item_value) if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): best_style = False if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): best_style = False value.append((node_key, node_value)) if flow_style is None: if dump.default_flow_style is not None: node.flow_style = dump.default_flow_style else: node.flow_style = best_style return node
[docs] def to_yaml(self): """ Return a YAML serialized representation. Specification: http://yaml.org/ Returns ------- serialized : str A YAML serialized representation of the object """ import yaml from collections import OrderedDict yaml.SafeDumper.add_representer(OrderedDict, lambda dumper, value: self._represent_odict(dumper, u'tag:yaml.org,2002:map', value)) d = self.to_dict() return yaml.safe_dump(d, width=180)
[docs] @classmethod def from_yaml(cls, serialized): """ Instantiate from a YAML serialized representation. Specification: http://yaml.org/ Parameters ---------- serialized : str A YAML serialized representation of the object Returns ------- instance : cls Instantiated object """ import yaml from collections import OrderedDict yaml.SafeDumper.add_representer(OrderedDict, lambda dumper, value: self._represent_odict(dumper, u'tag:yaml.org,2002:map', value)) d = yaml.safe_load(serialized) return cls.from_dict(d)
[docs] def to_messagepack(self): """ Return a MessagePack representation. Specification: https://msgpack.org/index.html Returns ------- serialized : bytes A MessagePack-encoded bytes serialized representation of the object """ import msgpack d = self.to_dict() return msgpack.dumps(d, use_bin_type=True)
[docs] @classmethod def from_messagepack(cls, serialized): """ Instantiate an object from a MessagePack serialized representation. Specification: https://msgpack.org/index.html Parameters ---------- serialized : bytes A MessagePack-encoded bytes serialized representation Returns ------- instance : cls Instantiated object. """ import msgpack d = msgpack.loads(serialized, raw=False) return cls.from_dict(d)
[docs] def to_xml(self, indent=2): """ Return an XML representation. Specification: https://www.w3.org/XML/ Parameters ---------- indent : int, optional, default=2 If not None, will pretty-print with specified number of spaces for indentation Returns ------- serialized : bytes A MessagePack-encoded bytes serialized representation. """ import xmltodict # An XML document requires one and only one root node. root_name = self.__class__.__name__ d = {root_name: self.to_dict()} # Configure indentation level. if indent is not None: pretty = True indent = ' ' * indent else: pretty = False # Convert data from dictionary to XML format. return xmltodict.unparse(d, pretty=pretty, indent=indent)
[docs] @classmethod def from_xml(cls, serialized): """ Instantiate an object from an XML serialized representation. Specification: https://www.w3.org/XML/ Parameters ---------- serialized : bytes An XML serialized representation Returns ------- instance : cls Instantiated object. """ raise NotImplementedError() # TODO: This implementation currently loads numbers as strings. # See test_utils_serialization::TestUtilsSerialization::test_xml. import xmltodict d = xmltodict.parse(serialized) root_name = cls.__name__ return cls.from_dict(d[root_name])
[docs] def to_pickle(self): """ Return a pickle serialized representation. .. warning :: This is not recommended for safe, stable storage since the pickle specification may change between Python versions. Returns ------- serialized : str A pickled representation of the object """ import pickle d = self.to_dict() return pickle.dumps(d)
[docs] @classmethod def from_pickle(cls, serialized): """ Instantiate an object from a pickle serialized representation. .. warning :: This is not recommended for safe, stable storage since the pickle specification may change between Python versions. Parameters ---------- serialized : str A pickled representation of the object Returns ------- instance : cls An instantiated object """ import pickle d = pickle.loads(serialized) return cls.from_dict(d)