Source code for pymixef.interoperability.base
"""Shared compatibility report types."""
from __future__ import annotations
from collections.abc import Iterable
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Generic, TypeVar
from .._contracts import CompatibilityIssue
from .._serialization import write_json
T = TypeVar("T")
_STATUSES = {"exact", "transformed", "approximated", "unsupported"}
[docs]
@dataclass(frozen=True, slots=True)
class CompatibilityReport:
"""Machine-readable accounting for every translated construct."""
source_format: str
target_format: str
issues: tuple[CompatibilityIssue, ...] = ()
metadata: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
invalid = [issue.status for issue in self.issues if issue.status not in _STATUSES]
if invalid:
raise ValueError(f"Unknown compatibility status values: {invalid}.")
@property
def supported(self) -> bool:
return not any(issue.status == "unsupported" for issue in self.issues)
[docs]
def by_status(self, status: str) -> tuple[CompatibilityIssue, ...]:
if status not in _STATUSES:
raise ValueError(f"Unknown compatibility status {status!r}.")
return tuple(issue for issue in self.issues if issue.status == status)
[docs]
def require_supported(self) -> None:
unsupported = self.by_status("unsupported")
if unsupported:
details = "; ".join(f"{item.construct}: {item.message}" for item in unsupported)
raise ValueError(
"Interchange refused because unsupported constructs were found: " + details
)
[docs]
def to_dict(self) -> dict[str, Any]:
return {
"source_format": self.source_format,
"target_format": self.target_format,
"supported": self.supported,
"issues": [issue.to_dict() for issue in self.issues],
"metadata": dict(self.metadata),
}
[docs]
def write(self, path: str | Path) -> Path:
return write_json(path, self.to_dict())
[docs]
@dataclass(frozen=True, slots=True)
class InterchangeResult(Generic[T]):
"""A translated value paired with its mandatory compatibility report."""
value: T
report: CompatibilityReport
[docs]
def require_supported(self) -> T:
self.report.require_supported()
return self.value
[docs]
def issues(status: str, constructs: Iterable[tuple[str, str]]) -> tuple[CompatibilityIssue, ...]:
"""Create compatibility issues that share one translation status.
Status validation is deferred to :class:`CompatibilityReport`, so callers
should attach the returned issues to a report before exposing a result.
"""
return tuple(
CompatibilityIssue(construct=construct, status=status, message=message)
for construct, message in constructs
)