Source code for baybe.serialization.mixin
"""Serialization mixin class."""
from __future__ import annotations
import json
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeVar
from baybe.serialization.core import _add_type_to_dict, converter
_T = TypeVar("_T", bound="SerialMixin")
if TYPE_CHECKING:
from _typeshed import SupportsRead, SupportsWrite
[docs]
class SerialMixin:
"""A mixin class providing serialization functionality."""
# Use slots so that derived classes also remain slotted
# See also: https://www.attrs.org/en/stable/glossary.html#term-slotted-classes
__slots__ = ()
[docs]
def to_dict(self) -> dict:
"""Create an object's dictionary representation.
Returns:
The dictionary representation of the object.
"""
dct = converter.unstructure(self)
return _add_type_to_dict(dct, self.__class__.__name__)
[docs]
@classmethod
def from_dict(cls: type[_T], dictionary: dict) -> _T:
"""Create an object from its dictionary representation.
Args:
dictionary: The dictionary representation.
Returns:
The reconstructed object.
"""
return converter.structure(dictionary, cls)
[docs]
def to_json(
self,
sink: str | Path | SupportsWrite[str] | None = None,
/,
*,
overwrite: bool = False,
**kwargs: Any,
) -> str:
"""Create an object's JSON representation.
Args:
sink: The JSON sink. Can be:
- ``None`` (only returns the JSON string).
- A file path or ``Path`` object pointing to a location where to write
the JSON content.
- A file-like object with a ``write()`` method.
overwrite: Boolean flag indicating if to overwrite the file if it already
exists. Only relevant if ``sink`` is a file path or ``Path`` object.
**kwargs: Additional keyword arguments to pass to :func:`json.dumps`.
Raises:
FileExistsError: If ``sink`` points to an already existing file but
``overwrite`` is ``False``.
Returns:
The JSON representation as a string.
"""
string = json.dumps(self.to_dict(), **kwargs)
if isinstance(sink, str):
sink = Path(sink)
if isinstance(sink, Path):
if sink.is_file() and not overwrite:
raise FileExistsError(
f"The file '{sink}' already exists. If you want to overwrite it, "
f"explicitly set the 'overwrite' flag to 'True'."
)
sink.write_text(string)
elif sink is None:
pass
else:
sink.write(string)
return string
[docs]
@classmethod
def from_json(cls: type[_T], source: str | Path | SupportsRead[str], /) -> _T:
"""Create an object from its JSON representation.
Args:
source: The JSON source. Can be:
- A string containing JSON content.
- A file path or ``Path`` object pointing to a JSON file.
- A file-like object with a ``read()`` method.
Raises:
ValueError: If ``source`` is not one of the allowed types.
Returns:
The reconstructed object.
"""
if isinstance(source, Path):
string = source.read_text()
elif isinstance(source, str):
try:
string = Path(source).read_text()
except OSError:
string = source
else:
try:
string = source.read()
except Exception:
raise ValueError(
"The method argument must be a string containing valid JSON, "
"a string holding a file path to a JSON file / a corresponding "
"'Path' object, or a file-like object with a 'read()' method."
)
return cls.from_dict(json.loads(string))