feat: catalog Record model and validation
This commit is contained in:
@@ -1 +1,72 @@
|
||||
"""Catalog data model, validation, and JSONL IO."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
MODES = frozenset({"audio", "video", "av"})
|
||||
LICENSES = frozenset({"public_domain", "cc0", "cc_by", "cc_by_nc"})
|
||||
REVIEW_STATUSES = frozenset({"proposed", "approved"})
|
||||
COORD_FIELDS = ("left", "right", "dark", "light")
|
||||
COORD_MIN = 0
|
||||
COORD_MAX = 4
|
||||
ATTRIBUTION_LICENSES = frozenset({"cc_by", "cc_by_nc"})
|
||||
|
||||
|
||||
class CatalogError(ValueError):
|
||||
"""Raised when a catalog record is structurally invalid."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class Record:
|
||||
id: str
|
||||
title: str
|
||||
source_url: str
|
||||
source_archive: str
|
||||
license: str
|
||||
mode: str
|
||||
left: int
|
||||
right: int
|
||||
dark: int
|
||||
light: int
|
||||
duration_s: int
|
||||
file_path: str
|
||||
review_status: str = "proposed"
|
||||
attribution: str = ""
|
||||
resolution: str = ""
|
||||
dominant_color: str = ""
|
||||
rationale: str = ""
|
||||
reviewed_at: Optional[str] = None
|
||||
notes: str = ""
|
||||
|
||||
|
||||
def validate(record: Record) -> None:
|
||||
"""Raise CatalogError if the record is structurally invalid."""
|
||||
if not record.id:
|
||||
raise CatalogError("record id must be non-empty")
|
||||
if record.mode not in MODES:
|
||||
raise CatalogError(
|
||||
f"invalid mode {record.mode!r}; expected one of {sorted(MODES)}"
|
||||
)
|
||||
if record.license not in LICENSES:
|
||||
raise CatalogError(
|
||||
f"invalid license {record.license!r}; expected one of {sorted(LICENSES)}"
|
||||
)
|
||||
if record.review_status not in REVIEW_STATUSES:
|
||||
raise CatalogError(
|
||||
f"invalid review_status {record.review_status!r}; "
|
||||
f"expected one of {sorted(REVIEW_STATUSES)}"
|
||||
)
|
||||
for axis in COORD_FIELDS:
|
||||
value = getattr(record, axis)
|
||||
if isinstance(value, bool) or not isinstance(value, int):
|
||||
raise CatalogError(f"coordinate {axis} must be an int, got {value!r}")
|
||||
if not (COORD_MIN <= value <= COORD_MAX):
|
||||
raise CatalogError(
|
||||
f"coordinate {axis}={value} out of range {COORD_MIN}..{COORD_MAX}"
|
||||
)
|
||||
if record.duration_s < 0:
|
||||
raise CatalogError("duration_s must be non-negative")
|
||||
if record.license in ATTRIBUTION_LICENSES and not record.attribution:
|
||||
raise CatalogError(f"license {record.license} requires attribution")
|
||||
|
||||
Reference in New Issue
Block a user