Source code for grad300_client.models

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any


[docs] class ScanType(str, Enum): """Enumeration of supported scan types. Each member is also a plain ``str`` so it can be used directly as an API parameter value (e.g. ``"TPI"``). """ TPI = "TPI" """Total-power integration scans.""" IMAGES = "Images" """On-the-fly imaging scans.""" SPECTRUM = "Spectrum" """Spectral-line scans.""" ONOFF = "OnOff" """On-Off switching scans."""
def normalize_scan_type(scan_type: ScanType | str) -> ScanType: """Normalise a raw string to a :class:`ScanType` enum member. Accepts case-insensitive aliases such as ``"image"``, ``"on_off"``, etc. Parameters ---------- scan_type : ScanType | str A ``ScanType`` member or a compatible string alias. Returns ------- ScanType Matching scan type. Raises ------ ValueError If ``scan_type`` does not match any known alias. """ if isinstance(scan_type, ScanType): return scan_type key = scan_type.strip().lower() aliases = { "tpi": ScanType.TPI, "image": ScanType.IMAGES, "images": ScanType.IMAGES, "spectrum": ScanType.SPECTRUM, "onoff": ScanType.ONOFF, "on_off": ScanType.ONOFF, } if key in aliases: return aliases[key] valid = ", ".join(item.value for item in ScanType) raise ValueError(f"Unknown scan type '{scan_type}'. Expected one of: {valid}") def parse_datetime(value: Any) -> datetime | None: """Parse a value into a :class:`~datetime.datetime` or ``None``. Parameters ---------- value : Any ISO-8601 string, :class:`datetime.datetime` instance, or ``None``. Returns ------- datetime | None Parsed datetime, or ``None`` if ``value`` is ``None``. Raises ------ TypeError If ``value`` is not a supported type. """ if value is None: return None if isinstance(value, datetime): return value if isinstance(value, str): return datetime.fromisoformat(value.replace("Z", "+00:00")) raise TypeError(f"Expected datetime-compatible value, got: {type(value)!r}")
[docs] @dataclass class ScanRecord: """Structured scan record returned by the backend API.""" id: int scan_type: ScanType file_name: str file_path: str size: int source: str ra: float dec: float date: datetime date_obs: datetime | None date_end: datetime | None gain: int | None comment: str | None metadata: dict[str, Any] = field(default_factory=dict)
[docs] @classmethod def from_api(cls, scan_type: ScanType, payload: dict[str, Any]) -> "ScanRecord": """Build a :class:`ScanRecord` from a raw API response dict. Parameters ---------- scan_type : ScanType Type of the record. payload : dict[str, Any] One scan object as returned by the backend API. Returns ------- ScanRecord Populated record instance. Raises ------ ValueError If required fields are missing or have unexpected types. """ try: scan_id = int(payload["id"]) file_name = str(payload["file_name"]) file_path = str(payload["file_path"]) size = int(payload["size"]) source = str(payload["object"]) ra = float(payload["ra"]) dec = float(payload["dec"]) date = parse_datetime(payload["date"]) if date is None: raise TypeError("date is required") except (KeyError, TypeError, ValueError) as exc: raise ValueError(f"Invalid scan payload: {payload}") from exc known_keys = { "id", "file_name", "file_path", "size", "object", "ra", "dec", "date", "date_obs", "date_end", "gain", "comment", } metadata = {k: v for k, v in payload.items() if k not in known_keys} return cls( id=scan_id, scan_type=scan_type, file_name=file_name, file_path=file_path, size=size, source=source, ra=ra, dec=dec, date=date, date_obs=parse_datetime(payload.get("date_obs")), date_end=parse_datetime(payload.get("date_end")), gain=payload.get("gain"), comment=payload.get("comment"), metadata=metadata, )