Source code for jsonype.basic_to_json_converters

from abc import ABC, abstractmethod
from collections.abc import Callable, Mapping, Sequence
from inspect import Parameter, signature
from typing import Any, Generic, TypeVar, get_args

from jsonype.base_types import Json, JsonNull, JsonSimple

SourceType_contra = TypeVar("SourceType_contra", contravariant=True)
JsonType_co = TypeVar("JsonType_co", bound=JsonSimple, covariant=True)


[docs] class ToJsonConversionError(ValueError):
[docs] def __init__(self, o: Any, reason: str | None = None) -> None: super().__init__(f"Cannot convert {o} to JSON {f': {reason}' if reason else ''}")
[docs] class UnsupportedSourceTypeError(ValueError):
[docs] def __init__(self, o: Any) -> None: super().__init__(f"Converting objects of type {type(o)} to JSON not supported")
[docs] class ToJsonConverter(ABC, Generic[SourceType_contra]): """The base-class for converters that convert to objects representing JSON. Converters that convert objects of their specific type ``T`` to objects representing JSON have to implement the two abstract methods defined in this base-class. SourceType_contra: The type of the object that shall be converted into an object representing JSON. """
[docs] @abstractmethod def can_convert(self, o: Any) -> bool: """Return if this converter can convert the given object to an object representing JSON. Args: o: the object to be converted to an object representing JSON Returns: ``True`` if this converter can convert the given object into an object representing JSON, ``False`` otherwise. """
[docs] @abstractmethod def convert(self, o: SourceType_contra, to_json: Callable[[Any], Json]) -> Json: """Convert the given object of type ``SourceType_contra`` to an object representing JSON. Args: o: the object to convert to_json: If this converter converts container types like :class:`typing.Sequence` this function is used to convert the contained objects into their corresponding objects representing JSON. Returns: the converted object representing JSON. Raises: ValueError: If the object cannot be converted to an object representing JSON. """
[docs] class FunctionBasedToSimpleJsonConverter(ToJsonConverter[SourceType_contra]): # noinspection GrazieInspection # pylint: disable=wrong-spelling-in-docstring """A function based :class:`ToJsonConverter`. Creates a ``ToJsonConverter`` from a function that maps a source type to a simple JSON type. Args: f: A function that maps a source type into a simple JSON type (int, float, str, bool). input_type: None, if the source type can be derived from the function signature (using :func:`inspect.signature`) or the concrete source type if this is not possible. Example FunctionBasedToSimpleJsonConverter: >>> from typing import Sequence >>> from jsonype import FunctionBasedToSimpleJsonConverter >>> >>> def abbreviate_str(s: str) -> str: ... return s if len(s) < 8 else f"{s[:2]}...{s[-2:]}" >>> >>> converter = FunctionBasedToSimpleJsonConverter(abbreviate_str) >>> print(converter.convert( ... "Long String", ... lambda a: None ... )) Lo...ng >>> # if the function signature is untyped, the input type can be provided explicitly: >>> >>> converter2 = FunctionBasedToSimpleJsonConverter( ... lambda s: s if len(s) < 8 else f"{s[:2]}...{s[-2:]}", str) """
[docs] def __init__(self, f: Callable[[SourceType_contra], JsonType_co], input_type: type[SourceType_contra] | None = None) -> None: self._f = f if input_type: self._input_type = input_type return sig = signature(f) assert len(sig.parameters) == 1 input_parameter = next(iter(sig.parameters.values())) assert input_parameter.annotation != Parameter.empty self._input_type = input_parameter.annotation
[docs] def can_convert(self, o: Any) -> bool: return isinstance(o, self._input_type)
[docs] def convert(self, o: SourceType_contra, _to_json: Callable[[Any], Json]) -> Json: try: return self._f(o) except ValueError as e: raise ToJsonConversionError(o, str(e)) from e
[docs] class FromNone(ToJsonConverter[JsonNull]): """Converts a ``None`` instance. A ``None`` is converted to ``None``. """
[docs] def can_convert(self, o: Any) -> bool: return o is None
[docs] def convert(self, o: JsonNull, to_json: Callable[[Any], Json]) -> JsonNull: return None
[docs] class FromSimple(ToJsonConverter[JsonSimple]): """Converts simple objects of type ``int``, ``float``, ``str``, ``bool``. The conversion simply returns the given object. """
[docs] def can_convert(self, o: Any) -> bool: return isinstance(o, get_args(JsonSimple))
[docs] def convert(self, o: JsonSimple, to_json: Callable[[Any], Json]) -> JsonSimple: return o
[docs] class FromSequence(ToJsonConverter[Sequence[Any]]): """Converts objects of type :class:`typing.Sequence`. A :class:`typing.Sequence` is converted to a :class:`list` with all elements being converted with their respective :class:`ToJsonConverter`. """
[docs] def can_convert(self, o: Any) -> bool: return isinstance(o, Sequence)
[docs] def convert(self, o: Sequence[Any], to_json: Callable[[Any], Json]) -> Json: return [to_json(e) for e in o]
[docs] class FromMapping(ToJsonConverter[Mapping[str, Any]]): """Converts objects of type :class:`typing.Mapping`. A :class:`typing.Mapping` with ``str`` typed keys is converted to a ``dict`` with all values being converted with their respective :class:`ToJsonConverter`. """
[docs] def can_convert(self, o: Any) -> bool: return isinstance(o, Mapping)
[docs] def convert(self, o: Mapping[str, Any], to_json: Callable[[Any], Json]) -> Json: """Convert the given object of type :class:`typing.Mapping` to an object representing JSON. Raises: ValueError: If the :class:`typing.Mapping` contains none-``str`` keys. """ def ensure_str(k: Any) -> str: if isinstance(k, str): return k raise ToJsonConversionError(o, f"Contains non str key: {k}") return {ensure_str(k): to_json(v) for k, v in o.items()}