Source code for pyradtran.data.manifest

"""Asset manifest: the registry of data files bundled with pyRadtran.

The manifest maps logical (category, name) references to on-disk relative
paths under the data root (bundled or external libRadtran). All paths are
relative to the libRadtran data-files root so the same manifest works against
either the bundled subset or a full external libRadtran install.
"""

from __future__ import annotations

import tomllib
from dataclasses import dataclass
from pathlib import Path

_MANIFEST_PATH = Path(__file__).resolve().parent / "MANIFEST.toml"


[docs] @dataclass(frozen=True) class Asset: """A single bundled data asset. Attributes: category: Logical group (atmosphere_profile, solar_flux, ckd, ...). name: The user-facing reference value for the consuming uvspec keyword (e.g. "US-standard", "kurudz_1.0nm.dat", "reptran coarse"). uvspec_keyword: The uvspec keyword that consumes this asset. paths: One or more paths relative to the data root. """ category: str name: str uvspec_keyword: str paths: tuple[str, ...]
[docs] @dataclass(frozen=True) class ValidationIssue: """A single data-reference problem found when validating a Scene. Attributes: severity: "warning" (default) or "error". category: Manifest category of the missing/referenced asset. name: The user-facing reference value that could not be satisfied. message: Human-readable explanation. """ severity: str category: str name: str message: str
[docs] def load_manifest() -> list[Asset]: """Load bundled MANIFEST.toml into a list of Asset objects. Returns an empty list when the manifest contains no ``[[assets]]`` entries. """ if not _MANIFEST_PATH.is_file(): return [] with open(_MANIFEST_PATH, "rb") as f: doc = tomllib.load(f) assets: list[Asset] = [] for entry in doc.get("assets", []): path = entry.get("path") files = entry.get("files") if path is not None: paths = (path,) elif files is not None: paths = tuple(files) else: raise ValueError(f"Manifest asset {entry.get('name')!r} has neither 'path' nor 'files'") assets.append( Asset( category=entry["category"], name=entry["name"], uvspec_keyword=entry["uvspec_keyword"], paths=paths, ) ) return assets
def check_consistency(assets: list[Asset], assets_dir: Path) -> list[str]: """Return a list of human-readable inconsistency messages. Consistent == every manifest path exists on disk AND every file under assets_dir is claimed by some manifest asset. An empty assets_dir and an empty manifest are consistent. """ messages: list[str] = [] # Every manifest path must exist on disk. declared: set[str] = set() for a in assets: for p in a.paths: declared.add(p) if not (assets_dir / p).is_file(): messages.append(f"manifest path missing on disk: {p}") # Every on-disk file must be declared (ignore .gitkeep). on_disk: set[str] = set() if assets_dir.is_dir(): for f in assets_dir.rglob("*"): if f.is_file() and f.name != ".gitkeep": on_disk.add(str(f.relative_to(assets_dir))) orphaned = on_disk - declared for p in sorted(orphaned): messages.append(f"file on disk not in manifest: {p}") return messages