Source code for feets.io

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2017-2024, Cabral, Juan
# Copyright (c) 2025, QuatroPe; ClariĆ”, Felipe
# License: MIT
# Full Text:
#     https://github.com/quatrope/feets/blob/master/LICENSE

# This code was ripped of from scikit-neuromsi on 06-nov-2024.
# https://github.com/renatoparedes/scikit-neuromsi/blob/f197a3c/skneuromsi/utils/custom_json.py
# Copyright (c) 2021-2022, Renato Paredes; Cabral, Juan
# License: BSD 3-Clause (https://tldrlegal.com/license/bsd-3-clause-license-(revised))
# All rights reserved.


# =============================================================================
# DOCS
# =============================================================================

"""Serialize and deserialize `feets.FeatureSpace` objects."""

# =============================================================================
# IMPORTS
# =============================================================================

import contextlib
import datetime as dt
import io
import json
import pathlib

import numpy as np

import yaml

from .core import FeatureSpace

# =============================================================================
# CUSTOM JSON ENCODER
# =============================================================================


[docs] class CustomJSONEncoder(json.JSONEncoder): """Custom JSON <https://json.org> encoder for `feets.FeatureSpace` objects. This class extends the `json.JSONEncoder` to add support for the following objects and types: +----------------------------------------------------+---------------+ | Python | JSON | +====================================================+===============+ | tuple, set, frozenset, np.ndarray | array | +----------------------------------------------------+---------------+ | datetime | string | +----------------------------------------------------+---------------+ | np.integer, np.floating, np.complexfloating | number | +----------------------------------------------------+---------------+ | np.true | true | +----------------------------------------------------+---------------+ | np.false | false | +----------------------------------------------------+---------------+ Attributes ---------- CONVERTERS : dict A dictionary mapping data types to their corresponding converter functions. See Also -------- json.JSONEncoder : Extensible JSON https://json.org encoder for Python data structures. """ CONVERTERS = ( (tuple, list), (set, list), (frozenset, list), (dt.datetime, dt.datetime.isoformat), (np.integer, int), (np.floating, float), (np.complexfloating, complex), (np.bool_, bool), (np.ndarray, np.ndarray.tolist), )
[docs] def default(self, obj): """Serialize an object to a JSON-serializable format. This method overrides the default method of the `json.JSONEncoder` class to provide custom serialization for the data structures defined in the `CONVERTERS` attribute, or calls the base implementation for any other object. Returns ------- object The JSON-serializable representation of the object. Raises ------ TypeError If the object does not match any of the types in `CONVERTERS`. """ for cls, converter in self.CONVERTERS: if isinstance(obj, cls): return converter(obj) return super(CustomJSONEncoder, self).default(obj)
# ============================================================================= # API # =============================================================================
[docs] @contextlib.contextmanager def none_open_or_buffer(path_or_buffer, mode): """Context manager to handle file paths or buffers as file-like objects. This function provides a unified way to handle file paths, buffers, or in-memory buffers, and yields a file-like object for reading or writing. Parameters ---------- path_or_buffer : str, pathlib.Path, file-like object or None - If `str` or `pathlib.Path`, the file at this given path is opened with the specified `mode`. - If a file-like object, it is yielded directly. - If `None`, an `io.StringIO` in-memory buffer is created and yielded. mode : str The mode in which to open the file (e.g., 'r', 'w'). This is ignored if `path_or_buffer` is not a path. Yields ------ file-like object An open, ready-to-use file-like object. """ if path_or_buffer is None: yield io.StringIO() elif isinstance(path_or_buffer, (str, pathlib.Path)): with open(path_or_buffer, mode) as fp: yield fp else: yield path_or_buffer
[docs] def store_json(fspace, path_or_buffer=None, **kwargs): """Serialize a `feets.FeatureSpace` to a JSON formatted string or file. Parameters ---------- fspace : feets.FeatureSpace The `feets.FeatureSpace` object to serialize. This object must implement a `to_dict` method that returns a serializable representation. path_or_buffer : str, pathlib.Path, file-like object or None, default=None The file path, buffer, or stream to write the JSON data to. If `None`, the JSON data is returned as a string. **kwargs Additional keyword arguments passed to `json.dump` when serializing the feature space. Returns ------- str or None If `path_or_buffer` is `None`, returns a JSON formatted string representing the feature space. Otherwise, writes the JSON data to the specified file or buffer and returns `None`. Raises ------ TypeError If the provided feature space contains non-serializable objects. See Also -------- feets.FeatureSpace : Class to select and extract features from a time series. read_json json.dump """ data = fspace.to_dict() kwargs.setdefault("indent", 2) with none_open_or_buffer(path_or_buffer, "w") as fp: json.dump(data, fp=fp, cls=CustomJSONEncoder, **kwargs) if path_or_buffer is None: return fp.getvalue()
[docs] def store_yaml(fspace, path_or_buffer=None, **kwargs): """Serialize a `feets.FeatureSpace` to a YAML formatted string or file. Parameters ---------- fspace : feets.FeatureSpace The `feets.FeatureSpace` object to serialize. This object must implement a `to_dict` method that returns a serializable representation. path_or_buffer : str, pathlib.Path, file-like object or None, default=None The file path, buffer, or stream to write the YAML data to. If `None`, the YAML data is returned as a string. **kwargs Additional keyword arguments passed to `yaml.safe_dump` when serializing the feature space. Returns ------- str or None If `path_or_buffer` is `None`, returns a YAML formatted string representing the feature space. Otherwise, writes the YAML data to the specified file or buffer and returns `None`. Raises ------ TypeError If the provided feature space contains non-serializable objects. See Also -------- feets.FeatureSpace : Class to select and extract features from a time series. read_yaml yaml.safe_dump """ json_str = store_json(fspace, path_or_buffer=None, indent=None) data = json.loads(json_str) with none_open_or_buffer(path_or_buffer, "w") as fp: yaml.safe_dump(data, stream=fp, **kwargs) if path_or_buffer is None: return fp.getvalue()
[docs] def read_json(path_or_buffer): """Deserialize a JSON formatted string or file to `feets.FeatureSpace`. Parameters ---------- path_or_buffer : str, pathlib.Path or file-like object The file path, buffer, or stream to read the JSON data from. Returns ------- feets.FeatureSpace A `feets.FeatureSpace` object containing the deserialized data. See Also -------- feets.FeatureSpace : Class to select and extract features from a time series. store_json """ with none_open_or_buffer(path_or_buffer, "r") as fp: data = json.load(fp) return FeatureSpace.from_dict(data)
[docs] def read_yaml(path_or_buffer): """Deserialize a YAML formatted string or file to `feets.FeatureSpace`. Parameters ---------- path_or_buffer : str, pathlib.Path or file-like object The file path, buffer, or stream to read the YAML data from. Returns ------- feets.FeatureSpace A `feets.FeatureSpace` object containing the deserialized data. See Also -------- feets.FeatureSpace : Class to select and extract features from a time series. store_yaml """ with none_open_or_buffer(path_or_buffer, "r") as fp: data = yaml.safe_load(fp) return FeatureSpace.from_dict(data)