fix(renderer): tolerate missing optional tables; document tzinfo
This commit is contained in:
parent
51f287a81a
commit
4449623af8
2 changed files with 49 additions and 12 deletions
|
|
@ -47,11 +47,14 @@ def _load_default_template() -> str:
|
|||
|
||||
|
||||
def _list_tables(conn: duckdb.DuckDBPyConnection) -> list[dict[str, Any]]:
|
||||
rows = conn.execute(
|
||||
"""SELECT name, description, query_mode
|
||||
FROM table_registry
|
||||
ORDER BY name"""
|
||||
).fetchall()
|
||||
try:
|
||||
rows = conn.execute(
|
||||
"""SELECT name, description, query_mode
|
||||
FROM table_registry
|
||||
ORDER BY name"""
|
||||
).fetchall()
|
||||
except duckdb.CatalogException:
|
||||
return []
|
||||
return [
|
||||
{"name": r[0], "description": r[1] or "", "query_mode": r[2] or "local"}
|
||||
for r in rows
|
||||
|
|
@ -81,18 +84,24 @@ def _marketplaces_for_user(
|
|||
Results are grouped by marketplace slug; display names are fetched
|
||||
from marketplace_registry in a single query.
|
||||
"""
|
||||
allowed = resolve_allowed_plugins(conn, user)
|
||||
try:
|
||||
allowed = resolve_allowed_plugins(conn, user)
|
||||
except duckdb.CatalogException:
|
||||
return []
|
||||
if not allowed:
|
||||
return []
|
||||
|
||||
# Build slug → display name lookup from registry
|
||||
slugs = list({p["marketplace_slug"] for p in allowed})
|
||||
placeholders = ",".join(["?"] * len(slugs))
|
||||
name_rows = conn.execute(
|
||||
f"SELECT id, name FROM marketplace_registry WHERE id IN ({placeholders})",
|
||||
slugs,
|
||||
).fetchall()
|
||||
slug_to_name: dict[str, str] = {r[0]: r[1] for r in name_rows}
|
||||
try:
|
||||
name_rows = conn.execute(
|
||||
f"SELECT id, name FROM marketplace_registry WHERE id IN ({placeholders})",
|
||||
slugs,
|
||||
).fetchall()
|
||||
slug_to_name: dict[str, str] = {r[0]: r[1] for r in name_rows}
|
||||
except duckdb.CatalogException:
|
||||
slug_to_name = {}
|
||||
|
||||
grouped: dict[str, dict[str, Any]] = {}
|
||||
for plugin in allowed:
|
||||
|
|
@ -116,7 +125,12 @@ def build_context(
|
|||
user: dict[str, Any],
|
||||
server_url: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Compose the Jinja2 render context. Pure, no side effects."""
|
||||
"""Compose the Jinja2 render context. Pure, no side effects.
|
||||
|
||||
Note: ``now`` is tz-aware UTC; DB-sourced timestamps elsewhere in the
|
||||
codebase are naive (DuckDB stores ``TIMESTAMP``, not ``TIMESTAMPTZ``).
|
||||
Don't subtract or compare them inside templates without normalising.
|
||||
"""
|
||||
now = datetime.now(timezone.utc)
|
||||
parsed = urlparse(server_url)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -54,3 +54,26 @@ def test_context_exposes_documented_keys(conn):
|
|||
for top in ("instance", "server", "sync_interval", "data_source",
|
||||
"tables", "metrics", "marketplaces", "user", "now", "today"):
|
||||
assert top in ctx, f"missing top-level key: {top}"
|
||||
|
||||
|
||||
def test_render_tolerates_missing_optional_tables(tmp_path, monkeypatch):
|
||||
"""A bare DuckDB without table_registry / marketplace_registry must still render."""
|
||||
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||
db_path = tmp_path / "bare.duckdb"
|
||||
bare = duckdb.connect(str(db_path))
|
||||
# Only seed the welcome_template singleton manually; no other tables.
|
||||
bare.execute(
|
||||
"""CREATE TABLE welcome_template (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
content TEXT,
|
||||
updated_at TIMESTAMP,
|
||||
updated_by VARCHAR
|
||||
)"""
|
||||
)
|
||||
bare.execute("INSERT INTO welcome_template (id, content) VALUES (1, NULL)")
|
||||
|
||||
out = render_welcome(bare, user=_user(), server_url="https://example.com")
|
||||
bare.close()
|
||||
assert "AI Data Analyst" in out # default template still renders
|
||||
# No tables → "_No tables registered yet_" branch from the default template
|
||||
assert "No tables registered yet" in out
|
||||
|
|
|
|||
Loading…
Reference in a new issue