"""Warehouse schema retrieval. Physical layer via SQLAlchemy's Inspector against the active dataset's Postgres schema; semantic layer (table/column descriptions, metric catalog) from YAML files under `api/datasets//`. Cached on first call — the schema is stable for a process's lifetime. Restart the api to pick up a dataset switch or YAML edits. """ from __future__ import annotations from functools import lru_cache from typing import Any from sqlalchemy import inspect from api.config import get_settings from api.datasets import read_yaml from api.tools.db import get_engine from api.tools.types import Column, Metric, SchemaContext, Table @lru_cache(maxsize=1) def load_schema() -> SchemaContext: dataset = get_settings().dataset docs = read_yaml(dataset, "schema_docs.yaml") metrics_yaml = read_yaml(dataset, "metrics.yaml") # Postgres schema name comes from the dataset id; the schema_docs file # can override it, but normally they match. schema_name: str = docs.get("schema", dataset) table_descs: dict[str, dict[str, Any]] = docs.get("tables", {}) or {} insp = inspect(get_engine()) tables: dict[str, Table] = { name: Table( name=name, description=(desc := table_descs.get(name, {})).get("description"), columns=[ Column.from_inspector(c, (desc.get("columns") or {}).get(c["name"])) for c in insp.get_columns(name, schema=schema_name) ], ) for name in insp.get_table_names(schema=schema_name) } metrics: dict[str, Metric] = { name: Metric.from_spec(name, spec) for name, spec in (metrics_yaml.get("metrics", {}) or {}).items() } return SchemaContext(schema=schema_name, tables=tables, metrics=metrics) def get_metric(name: str) -> Metric | None: return load_schema().metrics.get(name)