From 9339a8272b1399a8628258dc42825f701d214c36 Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 6 Jul 2026 18:52:20 +0200 Subject: [PATCH] feat(tht): schema columns reader (name/description/type/pk) from catalog --- harness/tests/test_schema_columns_cmd.py | 40 ++++++++++++++++++++++++ harness/tht/cli/schema_cmd.py | 40 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 harness/tests/test_schema_columns_cmd.py diff --git a/harness/tests/test_schema_columns_cmd.py b/harness/tests/test_schema_columns_cmd.py new file mode 100644 index 00000000..1847b3e7 --- /dev/null +++ b/harness/tests/test_schema_columns_cmd.py @@ -0,0 +1,40 @@ +import json +from datetime import datetime + +from typer.testing import CliRunner + +from tht.cli import app # the root Typer app that mounts schema_app as "schema" +from tht.mschema.models import ColumnPhysical, PhysicalSchema, TablePhysical + + +def _write_catalog(tmp_path): + phys = PhysicalSchema( + database="d", schema="s", introspected_at=datetime(2026, 1, 1), + tables={ + "dim_patient": TablePhysical( + comment="Anagrafica", + columns={ + "cod_paz": ColumnPhysical(type="bigint", pk=True, comment="Codice paziente"), + "nome": ColumnPhysical(type="text", comment="Nome"), + }, + ) + }, + ) + out = tmp_path / "artifacts" / "mschema" / "physical.yaml" + phys.to_yaml(out) + return out + + +def test_schema_columns_json(tmp_path, monkeypatch): + _write_catalog(tmp_path) + cfg = tmp_path / "workspace.yaml" + cfg.write_text( + "database: {database: d, schema: s, user: u, password: p, transport: direct}\n" + f"paths: {{artifacts: {tmp_path/'artifacts'}, indexes: {tmp_path/'i'}, sessions: {tmp_path/'s'}}}\n" + ) + res = CliRunner().invoke(app, ["schema", "columns", "dim_patient", "--json", "-c", str(cfg)]) + assert res.exit_code == 0, res.output + data = json.loads(res.output) + assert data["table"] == "dim_patient" + assert data["description"] == "Anagrafica" + assert {"name": "cod_paz", "description": "Codice paziente", "type": "bigint", "pk": True} in data["columns"] diff --git a/harness/tht/cli/schema_cmd.py b/harness/tht/cli/schema_cmd.py index 0770ca38..41d933c3 100644 --- a/harness/tht/cli/schema_cmd.py +++ b/harness/tht/cli/schema_cmd.py @@ -156,3 +156,43 @@ def render_cmd( typer.secho(f"OK: scritto {output}", fg=typer.colors.GREEN) else: typer.echo(out) + + +@schema_app.command("columns") +def columns_cmd( + table: str = typer.Argument(..., help="Nome tabella (chiave in physical.yaml)."), + json_out: bool = typer.Option(False, "--json", help="Emetti JSON puro su stdout."), + config: Path = CONFIG_OPT, +) -> None: + """Elenca nome/descrizione/tipo/pk delle colonne di una tabella dal catalogo.""" + import json as _json + + from tht.mschema.models import PhysicalSchema + + cfg = _load_config_or_exit(config) + phys_file = physical_path(cfg) + if not phys_file.exists(): + typer.secho( + f"ERRORE: {phys_file} non trovato. Esegui prima `tht schema introspect`.", + fg=typer.colors.RED, err=True, + ) + raise typer.Exit(code=1) + physical = PhysicalSchema.from_yaml(phys_file) + tbl = physical.tables.get(table) + if tbl is None: + typer.secho(f"ERRORE: tabella non nel catalogo: {table}", fg=typer.colors.RED, err=True) + raise typer.Exit(code=1) + payload = { + "table": table, + "description": tbl.comment, + "columns": [ + {"name": name, "description": col.comment, "type": col.type, "pk": col.pk} + for name, col in tbl.columns.items() + ], + } + if json_out: + typer.echo(_json.dumps(payload, ensure_ascii=False)) + return + typer.echo(f"{table}: {tbl.comment}") + for c in payload["columns"]: + typer.echo(f" {'*' if c['pk'] else ' '} {c['name']} ({c['type']}) — {c['description']}")