Source code for abinslib.io

"""I/O and serialization utilities for abinslib data structures."""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Mapping
import json
from pathlib import Path
from typing import Any, Self
import warnings

from packaging.version import parse as parse_version

from .util import get_version


[docs] class JSONMixin(ABC): """Mixin providing JSON serialization and deserialization methods."""
[docs] @abstractmethod def to_dict(self) -> Mapping[str, Any]: """Convert object to a dictionary."""
[docs] @classmethod @abstractmethod def from_dict(cls, data_dict: Any) -> Self: """Instantiate object from a dictionary."""
[docs] def to_json(self) -> str: """Convert object representation to a JSON string.""" data_dict = dict(self.to_dict()) data_dict["__abinslib_class__"] = type(self).__name__ data_dict["__abinslib_version__"] = get_version() return json.dumps(data_dict, indent=4, sort_keys=True)
@staticmethod def _validate_class(file_class: str, expected_class: str) -> None: """Ensure JSON class metadata matches expected target class name.""" if file_class != expected_class: raise ValueError( f"JSON data class '{file_class}' does not match " f"expected class '{expected_class}'." ) @staticmethod def _validate_version(file_version: str) -> None: """Issue warnings if JSON version is DEVELOPMENT or newer than installed.""" current_version = get_version() if current_version == "DEVELOPMENT": return if file_version == "DEVELOPMENT": warnings.warn( "JSON data was generated with a DEVELOPMENT version of abinslib.", UserWarning, stacklevel=3, ) return if parse_version(file_version) > parse_version(current_version): warnings.warn( f"JSON data version ({file_version}) is newer " f"than current abinslib version ({current_version}).", UserWarning, stacklevel=3, )
[docs] @classmethod def from_json(cls, json_str: str) -> Self: """Instantiate object from a JSON string.""" data_dict = json.loads(json_str) try: file_class = data_dict.pop("__abinslib_class__") except KeyError: raise ValueError("JSON data is missing required '__abinslib_class__' key.") cls._validate_class(file_class, cls.__name__) try: file_version = data_dict.pop("__abinslib_version__") except KeyError: raise ValueError( "JSON data is missing required '__abinslib_version__' key." ) cls._validate_version(file_version) return cls.from_dict(data_dict)
[docs] def to_json_file(self, filename: Path | str) -> None: """Write object representation to a JSON file.""" Path(filename).write_text(self.to_json())
[docs] @classmethod def from_json_file(cls, filename: Path | str) -> Self: """Read object representation from a JSON file.""" return cls.from_json(Path(filename).read_text())