Source code for iiisight.lint
"""Consumer-oriented lint over the diagnostics a normalization produced.
This is the byproduct the tolerance design unlocks: because
:func:`~iiisight.normalize.normalize` records every repair it made, a lint is just
a reporting view over ``document.diagnostics`` — *"what did a real client have to
guess or fix to read this document?"* It is client-compatibility feedback, not a
strict spec-conformance check.
"""
from collections import Counter
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any
from .diagnostics import Diagnostic, Severity
from .model import Collection, Manifest
from .normalize import normalize
[docs]
@dataclass(frozen=True)
class LintReport:
"""The diagnostics from normalizing a document, organized for reporting."""
diagnostics: tuple[Diagnostic, ...]
@property
def errors(self) -> tuple[Diagnostic, ...]:
"""Diagnostics where information was dropped or skipped."""
return tuple(d for d in self.diagnostics if d.severity is Severity.ERROR)
@property
def warnings(self) -> tuple[Diagnostic, ...]:
"""Diagnostics for deviations that were repaired without loss."""
return tuple(d for d in self.diagnostics if d.severity is Severity.WARNING)
@property
def infos(self) -> tuple[Diagnostic, ...]:
"""Diagnostics for spec-sanctioned normalizations."""
return tuple(d for d in self.diagnostics if d.severity is Severity.INFO)
@property
def ok(self) -> bool:
"""True when nothing beyond informational normalization occurred."""
return not self.errors and not self.warnings
[docs]
def counts(self) -> dict[Severity, int]:
"""Return the diagnostic count for every severity (zero-filled)."""
counter = Counter(d.severity for d in self.diagnostics)
return {severity: counter.get(severity, 0) for severity in Severity}
[docs]
def summary(self) -> str:
"""A one-line human summary, e.g. ``"1 error, 2 warnings, 3 info"``."""
counts = self.counts()
return (
f"{counts[Severity.ERROR]} error(s), "
f"{counts[Severity.WARNING]} warning(s), "
f"{counts[Severity.INFO]} info"
)
[docs]
def lint(document: Manifest | Collection | Mapping[str, Any]) -> LintReport:
"""Lint a IIIF document.
Args:
document: An already-normalized :class:`Manifest`/:class:`Collection`, or a
raw parsed JSON ``dict`` (which is normalized first).
Returns:
A :class:`LintReport` over the document's diagnostics.
"""
if isinstance(document, (Manifest, Collection)):
diagnostics = document.diagnostics
else:
diagnostics = normalize(document).diagnostics
return LintReport(diagnostics=tuple(diagnostics))