import re from typing import Any _TAG_RE = re.compile(r"^[A-Za-z0-9_\-]{1,64}$") _DATASET_RE = re.compile(r"^[A-Za-z0-9_.\-]+\/[A-Za-z0-9_.\-]+$|^[A-Za-z0-9_.\-]+$") class ValidationError(ValueError): pass def safe_tag(s: str) -> str: if not isinstance(s, str): raise ValidationError("Experiment tag must be a string") if not _TAG_RE.match(s): raise ValidationError("Experiment tag must be 1–64 chars: letters, numbers, _ or -") return s def safe_dataset_id(s: str) -> str: if not isinstance(s, str): raise ValidationError("Dataset id must be a string") if not _DATASET_RE.match(s): raise ValidationError("Dataset must look like 'owner/name' or a public name") return s def safe_float(x: Any, lo: float, hi: float, name: str) -> float: try: v = float(x) except Exception: raise ValidationError(f"{name} must be a number") if not (lo <= v <= hi): raise ValidationError(f"{name} must be in [{lo}, {hi}]") return v def safe_int(x: Any, lo: int, hi: int, name: str) -> int: try: v = int(x) except Exception: raise ValidationError(f"{name} must be an integer") if not (lo <= v <= hi): raise ValidationError(f"{name} must be in [{lo}, {hi}]") return v