fix(2-G.5): preview_for_settings contract in adapter docstring + distinguish [] from None

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.
This commit is contained in:
zvx 2026-05-19 17:55:39 +00:00
commit 570b121276
3 changed files with 26 additions and 1 deletions

View file

@ -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

View file

@ -2,9 +2,10 @@
<article aria-label="Preview Unavailable" style="background-color: var(--pico-del-color); margin-bottom: 1rem;">
<strong>{{ preview_error }}</strong>
</article>
{% elif preview_rows %}
{% elif preview_rows is not none %}
<fieldset>
<legend>Preview ({{ preview_rows|length }} rows)</legend>
{% if preview_rows %}
<table class="preview-table" role="grid">
<thead>
<tr>
@ -19,5 +20,6 @@
{% endfor %}
</tbody>
</table>
{% endif %}
</fieldset>
{% endif %}