Source code for compages._structure

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any, TypeVar

from ._common import ExtendedType, GeneratorStack, Result, get_lookup_order
from .path import PathElem


[docs] class StructuringError(Exception): """ An error during structuring. Accumulates possible nested errors. """ def __init__(self, message: str, inner_errors: list[tuple[PathElem, "StructuringError"]] = []): super().__init__(message) self.message = message self.inner_errors = inner_errors def __str__(self) -> str: messages = collect_messages([], self) _, msg = messages[0] message_strings = [msg] + [ " " * len(path) + ".".join(str(elem) for elem in path) + f": {msg}" for path, msg in messages[1:] ] return "\n".join(message_strings)
def collect_messages( path: list[PathElem], exc: StructuringError ) -> list[tuple[list[PathElem], str]]: result = [(path, exc.message)] for path_elem, inner_exc in exc.inner_errors: result.extend(collect_messages([*path, path_elem], inner_exc)) return result _T = TypeVar("_T")
[docs] @dataclass class StructurerContext: """A context object passed to handlers during structuring.""" structurer: "Structurer" """The current structurer.""" structure_into: ExtendedType[Any] """ The requested return value type. Can be a regular type, a newtype, or a generic type. """ user_context: Any """ The custom object the user passed to :py:meth:`Unstructurer.unstructure_as`. """
[docs] def nested_structure_into(self, structure_into: ExtendedType[_T], val: Any) -> _T: """ Calls :py:meth:`Structurer.structure_into` of ``self.structurer`` passing on the user context. """ return self.structurer.structure_into(structure_into, val, user_context=self.user_context)
[docs] class StructureHandler: """A base class for structuring logic attached to a type."""
[docs] def structure( self, context: StructurerContext, # noqa: ARG002 value: Any, ) -> Any: """ Structures the given ``value`` returning an instance of ``context.structure_into``. If not defined, falls back to :py:meth:`simple_structure`. """ return self.simple_structure(value)
[docs] def simple_structure(self, value: Any) -> Any: """ Structures the given ``value``. Use for the cases where the information from ``context`` is not needed. If :py:meth:`structure` is not defined, this method must be defined. """ raise NotImplementedError( "`StructureHandler` must implement either `structure()` or `simple_structure()`" )
[docs] class Structurer: def __init__(self, handlers: Mapping[Any, StructureHandler] = {}): self._handlers = dict(handlers)
[docs] def structure_into( self, structure_into: ExtendedType[_T], val: Any, user_context: Any = None ) -> _T: """ Structures (deserializes) the given ``value`` into the type ``structure_into`` with an optional ``user_context`` (which will be passed to the handlers). Raises :py:class:`StructuringError` for any structuring-related error. """ context = StructurerContext( structurer=self, structure_into=structure_into, user_context=user_context ) stack = GeneratorStack[StructurerContext, _T](context, val) lookup_order = get_lookup_order(structure_into) for tp in lookup_order: handler = self._handlers.get(tp, None) result = stack.push(handler.structure if handler else None) if result is not Result.UNDEFINED: return result if stack.is_empty(): raise StructuringError( f"No handlers registered to structure `{val}` into {structure_into}" ) raise StructuringError( f"Could not find a non-generator handler to structure into {structure_into}" )