From 570b121276185da6528045d69847f07ed0a0c988 Mon Sep 17 00:00:00 2001 From: zvx Date: Tue, 19 May 2026 17:55:39 +0000 Subject: [PATCH] fix(2-G.5): preview_for_settings contract in adapter docstring + distinguish [] from None MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixup 1 — Contract section appended to SourceAdapter.preview_for_settings's docstring. Override authors read adapter.py, not routes.py, so the contract (pure function of settings; open your own short-lived aiohttp session; None vs [] semantics) belongs on the base method, not on the GUI stub class. Fixup 2 — _adapter_preview.html distinguishes [] from None. Previously the elif test was truthiness (`elif preview_rows`) which collapsed both into "render nothing". Now uses `elif preview_rows is not none` and special-cases the empty-list case inside: legend "Preview (0 rows)" with no table; None still renders nothing at all. Lets adapters signal "query ran, matched zero" distinctly from "preview not meaningful". Tests +1: - test_partial_renders_empty_list — [] yields "Preview (0 rows)" legend, no table, no headers. Distinct from the existing None case. Acceptance: - 27/27 targeted (preview_hook +1 new, nwis, stream_registry). - 458/458 full suite. - (b) framework GUI dir still has zero adapter-name branches. --- src/central/adapter.py | 11 +++++++++++ src/central/gui/templates/_adapter_preview.html | 4 +++- tests/test_preview_hook.py | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/central/adapter.py b/src/central/adapter.py index 2a408b0..76ab04e 100644 --- a/src/central/adapter.py +++ b/src/central/adapter.py @@ -85,5 +85,16 @@ class SourceAdapter(ABC): Return list[dict] (framework renders as a generic table; columns come from the first dict's keys, in insertion order). Return None to skip preview. Raise to surface an error banner — framework catches at the route boundary. + + Contract: + - Preview is a pure function of `settings`. Do NOT access + self._config_store or cursor_db state — the framework may instantiate + adapters with a stub config_store solely to call this method. + - Network preview implementations must open their own short-lived + aiohttp session (the adapter's polling session may not exist; the GUI + process never calls startup()). + - Return None when preview is not meaningful (e.g., required settings + like region are unset). Return [] explicitly if the query ran and + matched zero rows — the framework renders that distinctly from None. """ return None diff --git a/src/central/gui/templates/_adapter_preview.html b/src/central/gui/templates/_adapter_preview.html index ff9ef0c..ab8a7f1 100644 --- a/src/central/gui/templates/_adapter_preview.html +++ b/src/central/gui/templates/_adapter_preview.html @@ -2,9 +2,10 @@
{{ preview_error }}
-{% elif preview_rows %} +{% elif preview_rows is not none %}
Preview ({{ preview_rows|length }} rows) + {% if preview_rows %} @@ -19,5 +20,6 @@ {% endfor %}
+ {% endif %}
{% endif %} diff --git a/tests/test_preview_hook.py b/tests/test_preview_hook.py index e9fbd30..88b9b1b 100644 --- a/tests/test_preview_hook.py +++ b/tests/test_preview_hook.py @@ -106,3 +106,15 @@ def test_partial_renders_nothing_when_both_none(): assert "Preview Unavailable" not in out # Either empty or only whitespace/newlines from the template. assert "Preview (" not in out + + +def test_partial_renders_empty_list(): + """Empty list -> legend with (0 rows), no table. + + Distinct from None (which renders nothing at all). Lets adapters signal + 'query ran, matched zero rows' separately from 'preview not meaningful'. + """ + out = _render_partial(preview_rows=[], preview_error=None) + assert "Preview (0 rows)" in out + assert "" not in out