"""
Defines free functions to serialize / deserialize bands-inspect objects to HDF5.
"""
from functools import singledispatch, lru_cache
import h5py
from fsc.export import export
from ._subscribe import SERIALIZE_MAPPING, TYPE_TAG_KEY
__all__ = ['save', 'load']
@lru_cache(maxsize=None)
def _get_entrypoint_mapping(group):
"""
Helper function to get the entry point mapping corresponding to a
given group name
"""
import pkg_resources
return {ep.name: ep for ep in pkg_resources.iter_entry_points(group=group)}
def _try_loading_parts(*, identifier, entry_point_mapping):
"""
Helper function to load an entrypoint corresponding to the given
identifier. If an exact match for the identifier is not found, the
colon-separated "parent identifiers" will be checked.
For example, for an entry point named 'nobody.expects.the',
it will check 'nobody.expects.the', 'nobody.expects', 'nobody', in
that order.
:param identifier: The identifier for which an entrypoint is to be loaded.
:type identifier: str
:param entry_point_mapping: A mapping between entrypoint names and the entrypoints themselves.
:type entry_point_mapping: dict
:returns: The name of the entry point that was loaded (if any), or False.
"""
split_identifier = identifier.split('.')
for partial_identifier_length in range(len(split_identifier), 0, -1):
partial_identifier = '.'.join(
split_identifier[:partial_identifier_length]
)
if partial_identifier in entry_point_mapping:
entry_point_mapping[partial_identifier].load()
return partial_identifier
return False
[docs]@export
def from_hdf5(hdf5_handle):
"""
Deserializes the given HDF5 handle into an object.
:param hdf5_handle: HDF5 location where the serialized object is stored.
:type hdf5_handle: :py:class:`h5py.File<File>` or :py:class:`h5py.Group<Group>`.
"""
try:
type_tag = hdf5_handle[TYPE_TAG_KEY][()]
except KeyError as err:
raise ValueError(
"HDF5 object '{}' cannot be de-serialized: No type information given."
.format(hdf5_handle.name)
) from err
try:
obj_class = SERIALIZE_MAPPING[type_tag]
except KeyError as err:
partial_tag = _try_loading_parts(
identifier=type_tag,
entry_point_mapping=_get_entrypoint_mapping('fsc.hdf5_io.load')
)
if not partial_tag:
raise KeyError(
"Unknown {} '{}'. The module defining this class has not been imported, and no matching entry point was found."
.format(TYPE_TAG_KEY, type_tag)
) from err
try:
obj_class = SERIALIZE_MAPPING[type_tag]
except KeyError as err2:
raise KeyError(
"Unknown {} '{}'. The module defining this class has not been imported, even after loading entry point {}."
.format(TYPE_TAG_KEY, type_tag, partial_tag)
) from err2
return obj_class.from_hdf5(hdf5_handle)
[docs]@export
def to_hdf5(obj, hdf5_handle):
"""
Serializes a given object to HDF5 format.
:param obj: Object to serialize.
:param hdf5_handle: HDF5 location where the serialized object gets stored.
:type hdf5_handle: :py:class:`h5py.File<File>` or :py:class:`h5py.Group<Group>`.
"""
if hasattr(obj, 'to_hdf5'):
obj.to_hdf5(hdf5_handle)
else:
try:
to_hdf5_singledispatch(obj, hdf5_handle)
except SerializerNotFound:
objtype = type(obj)
objmodule = objtype.__module__
if objmodule is None:
fullname = objtype.__qualname__
else:
fullname = objmodule + '.' + objtype.__qualname__
if not _try_loading_parts(
identifier=fullname,
entry_point_mapping=_get_entrypoint_mapping(
'fsc.hdf5_io.save'
)
):
raise TypeError(
"Cannot serialize object of type '{}', and no corresponding entry point found."
.format(fullname)
)
to_hdf5_singledispatch(obj, hdf5_handle)
class SerializerNotFound(TypeError):
"""
Error to raise when the singledispatch for serializing an object to
HDF5 format fails to find a matching function.
"""
@export
@singledispatch
def to_hdf5_singledispatch(obj, hdf5_handle):
"""
Singledispatch function which is called to serialize and object when it does not have a ``to_hdf5`` method.
:param obj: Object to serialize.
:param hdf5_handle: HDF5 location where the serialized object gets stored.
:type hdf5_handle: :py:class:`h5py.File<File>` or :py:class:`h5py.Group<Group>`.
"""
raise SerializerNotFound(
"Cannot serialize object '{}' of type '{}'".format(obj, type(obj))
)
[docs]@export
def from_hdf5_file(hdf5_file):
"""
Loads the object from a file in HDF5 format.
:param hdf5_file: Path of the file.
:type hdf5_file: str
"""
with h5py.File(hdf5_file, 'r') as f:
return from_hdf5(f)
load = from_hdf5_file # pylint: disable=invalid-name
load.__doc__ = """Alias for :func:`from_hdf5_file`."""
[docs]@export
def to_hdf5_file(obj, hdf5_file):
"""
Saves the object to a file, in HDF5 format.
:param obj: The object to be saved.
:param hdf5_file: Path of the file.
:type hdf5_file: str
"""
with h5py.File(hdf5_file, 'w') as f:
to_hdf5(obj, f)
save = to_hdf5_file # pylint: disable=invalid-name
save.__doc__ = """Alias for :func:`to_hdf5_file`."""