Source code for httk.core.views.coercion

"""General best-effort coercion into registered view or value classes."""

from collections.abc import Callable, Sequence
from typing import Any, cast

[docs] type Coercer = Callable[[Any, type], Any | None]
_coercers: list[Coercer] = []
[docs] def register_coercer(coercer: Coercer) -> None: """Append ``coercer`` to the registry, preserving registration order.""" _coercers.append(coercer)
[docs] def view_class_coercer(view_classes: Sequence[type]) -> Coercer: """Return a coercer that tries matching view classes in ``view_classes`` order. A ``TypeError``, ``ValueError``, or ``OverflowError`` from a view constructor means that view cannot represent the value, so construction continues with the next matching class. """ def coerce_view(value: Any, target: type) -> Any | None: for cls in view_classes: if issubclass(cls, target): try: return cast(Any, cls)(value) except (TypeError, ValueError, OverflowError): pass return None return coerce_view
[docs] def coerce(value: Any, target: Any) -> Any: """ Coerce ``value`` to a target class or prototype instance. The exact string ``"natural"`` is a documented sentinel that returns ``value`` unchanged. Otherwise, a class target is used directly and an instance target is treated as a prototype, using its type. Values already matching the target are returned unchanged. Registered coercers are then tried in registration order, and the first non-``None`` result wins. If none succeeds, ``TypeError`` is raised naming the value type and target. Coercion is best effort and favors lossless view wrapping; individual coercers document any deliberately lossy conversion. """ if isinstance(target, str) and target == "natural": return value tcls = target if isinstance(target, type) else type(target) if isinstance(value, tcls): return value for coercer in _coercers: result = coercer(value, tcls) if result is not None: return result raise TypeError(f"Cannot coerce {type(value)} to {target!r}")