mirror of
https://github.com/zvx-echo6/central.git
synced 2026-05-21 18:14:44 +02:00
Surfaced during the 2026-05-20 NaviBackend activation: toggling
config.enrichment.backend_class to NoOpBackend while backend_settings still
held {"base_url": ...} crashed _rebuild_enrichers with
`TypeError: NoOpBackend() takes no arguments`, BEFORE invalidate() ran. Fixed
by mirroring the SourceAdapter.settings_schema pattern: each backend declares a
Pydantic settings_schema; validation happens at write-time (GUI POST) and
read-time (supervisor). A mismatch is now a clean ValidationError, never a
constructor TypeError.
Backends — each gets a `<Name>BackendSettings(BaseModel, extra="forbid")` +
`settings_schema` class attr, mirroring __init__ defaults EXACTLY (note:
timeout_s stays 10.0 — the brief's "5.0" was a transcription slip; preserve the
production default):
NoOpBackend -> NoOpBackendSettings (no fields)
NaviBackend -> NaviBackendSettings (base_url, timeout_s, headers, warmup)
PhotonBackend -> PhotonBackendSettings (base_url, timeout_s, headers)
NominatimBackend-> NominatimBackendSettings (base_url, user_agent, rate_limit_per_sec, timeout_s)
GeocoderBackend Protocol (in geocoder.py, where the base actually lives — not
base.py, which only has Enricher) gains `settings_schema: type[BaseModel]`.
supervisor:
- build_enrichers validates backend_cls.settings_schema.model_validate(
backend_settings) before instantiating, and constructs from the validated
.model_dump(). ValidationError (not TypeError) on mismatch.
- _rebuild_enrichers builds into locals and commits to instance state only on
success — a ValidationError leaves the previously-active enrichers/config/
cache untouched.
- _handle_enrichment_change wraps the rebuild in try/except ValidationError:
logs and returns, keeping the previous backend running (supervisor stays up;
operator fixes the row; next NOTIFY applies cleanly). No cache invalidation
on a failed change.
GUI /enrichment:
- GET skips the outer EnrichmentConfig.backend_settings field and renders a
separate <fieldset> from describe_fields(backend_cls.settings_schema, ...)
for the row's current backend_class. Backend fields namespaced bs_<name>.
- POST reassembles bs_<name> inputs into a backend_settings dict, validates it
against the SUBMITTED backend_class's schema (so errors attach to the right
fields when an operator is mid-switch), then validates the outer
EnrichmentConfig. DB row written only if both pass; otherwise re-renders with
field-level errors against the submitted backend.
- backend_class stays a plain text field (no <select>, no client-side reshape).
form_descriptors: generic `float -> "number"` widget (2 lines, mirrors K.5's
`dict -> "json"`), needed because backend schemas have float fields
(timeout_s, rate_limit_per_sec). Benefits any float field codebase-wide.
DB schema unchanged: backend_settings stays JSONB; validation moved to
use-site. _BACKEND_REGISTRY / _ENRICHER_REGISTRY unchanged beyond schema lookup.
Tests (test_backend_settings_schema.py, 11): schemas exist + extra='forbid';
Navi schema preserves defaults (timeout_s == 10.0); NoOp has zero fields;
build_enrichers raises ValidationError-not-TypeError for the exact 2026-05-20
case; supervisor keeps previous backend on a bad NOTIFY (the incident
scenario); valid NoOp-with-empty-settings applies + invalidates; GUI POST
rejects bad backend_settings without writing + re-renders against submitted
backend; GUI POST writes on valid settings. test_enrichment_config_plumbing
updated for the new context shape (outer_fields/backend_fields).
Verification: full pytest 546 passed, 1 skipped (was 535; +11). grep
subject_for_event/_ADAPTER_REGISTRY and grep 100.64.0./192.168.1. in src both
empty.
Does NOT touch PR L scope (events tab, remaining adapter enrichment_locations),
the DB schema, or the registries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| fixtures | ||
| __init__.py | ||
| conftest.py | ||
| README.md | ||
| test_adapters.py | ||
| test_api_key_resolver.py | ||
| test_api_keys.py | ||
| test_archive_multi_stream.py | ||
| test_audit.py | ||
| test_auth.py | ||
| test_backend_settings_schema.py | ||
| test_bootstrap_config.py | ||
| test_config_source.py | ||
| test_config_store.py | ||
| test_consumer_doc.py | ||
| test_crypto.py | ||
| test_csrf_handler.py | ||
| test_csrf_race_condition.py | ||
| test_dashboard.py | ||
| test_enrichment_config_plumbing.py | ||
| test_enrichment_framework.py | ||
| test_eonet.py | ||
| test_events_adapter_column.py | ||
| test_events_feed.py | ||
| test_events_feed_frontend.py | ||
| test_firms.py | ||
| test_form_descriptors.py | ||
| test_gdacs.py | ||
| test_geocoder_enricher.py | ||
| test_gui_scaffold.py | ||
| test_inciweb.py | ||
| test_models.py | ||
| test_navi_backend.py | ||
| test_nominatim_backend.py | ||
| test_nwis.py | ||
| test_nws_normalization.py | ||
| test_photon_backend.py | ||
| test_preview_hook.py | ||
| test_producer_doc.py | ||
| test_region_picker.py | ||
| test_requires_api_key.py | ||
| test_session_auth.py | ||
| test_setup_gate.py | ||
| test_stream_registry.py | ||
| test_streams.py | ||
| test_supervisor_hotreload.py | ||
| test_supervisor_integration.py | ||
| test_swpc.py | ||
| test_usgs_quake.py | ||
| test_wfigs.py | ||
| test_wizard.py | ||
Central Tests
Test Database
Some tests (notably test_config_store.py) require a real PostgreSQL database.
By default, tests connect to:
postgresql://central_test:testpass@localhost/central_test
If your test database uses different credentials, set the CENTRAL_TEST_DB_DSN
environment variable:
export CENTRAL_TEST_DB_DSN="postgresql://myuser:mypass@localhost/mydb"
uv run pytest tests/test_config_store.py