httpx.Response does not have .ok attribute (that's requests.Response).
Async migration missed this: _llm_async_http.py used response.ok in two
places, causing 502 errors when LLM API responded.
Fix: response.ok -> response.is_success
Root cause: AsyncAPIClient.request() passes its parameter directly
to httpx.AsyncClient.request(json=data). When callers pass a pre-serialized
JSON string (data=json.dumps(dict)), httpx re-encodes it via json.dumps(),
resulting in a double-encoded JSON string body instead of a JSON object.
This caused ALL POST/PUT requests with string data to fail — Superset received
a JSON string instead of a JSON object, returning GENERIC_BACKEND_ERROR
('dictionary update sequence element #0 has length 1; 2 is required').
Fix: if data is a string, pass it via httpx parameter (raw body);
if it's a dict/list, pass via for automatic encoding.
Affected callers (6 files) now correctly send JSON objects:
- preview_executor.py: chart data requests
- superset_executor.py
- _run_source.py
- _datasets.py: update_dataset
- _datasets_preview.py: compile_dataset_preview
- _dashboards_write.py
Also simplified preview_executor.fetch_sample_rows back to single-strategy
(chart data API only) since the root cause is now fixed.
1. preview_translation route: missing await on async preview_rows()
- preview_rows() is async def, called without await
- returned coroutine object instead of result -> 'coroutine not iterable' error
2. MultiSelect.svelte: opt.label -> opt.name
- option type is {code, name} but template used {opt.label}
- rendered empty spans instead of language names
debug.py: _test_db_api and _get_dataset_structure were already async def
but called SupersetClient methods (authenticate, get_databases, get_dataset)
without await. Added await to 4 calls.
task_logger.py: _add_log callback is async def but _log() called it without
await, silently dropping all task log messages (RuntimeWarning: coroutine
never awaited). Changed to fire-and-forget via asyncio.ensure_future when
a running event loop is available, drops gracefully otherwise.
executor.execute_run() is async def but was called without await in
TranslationExecutionEngine.execute_run(). This returned a coroutine
instead of a TranslationRun, causing:
'coroutine' object has no attribute 'status'
This made every translation run fail in the background execute path.
run_translation was def (sync FastAPI handler runs in thread pool with no event
loop), but its body calls asyncio.create_task(_background_execute()) which
requires a running event loop. Changed to async def so FastAPI runs it on the
event loop directly.
Error: 'Run failed: no running event loop'
get_dashboards_page_async() no longer exists — the sync/async split was
removed and the method is now simply get_dashboards_page() (already async).
Calls in dashboard slug resolution and git helpers were using the old name.
This caused AttributeError at runtime, making all slug-based dashboard
lookups fail with 'Dashboard not found'.
SupersetClientBase was missing aclose() method. AsyncSupersetClient had it
via override, but code creating SupersetClient directly (e.g. dashboard tasks
history route) would fail with AttributeError on await client.aclose().
All test methods calling validate_target_table_schema now async def + await.
Mocked async methods (resolve_database_id, execute_and_poll) use AsyncMock
instead of MagicMock since await on MagicMock raises TypeError.
_prime_dashboard_meta_cache was sync but called async get_dashboards_summary().
Parent get_health_summary was already async def. Made child async + added awaits.
db_name and backend were initialized inside the try block but referenced
in the except handler. If an exception occurred before their assignment
(e.g. in resolve_database_id), the except block would raise:
cannot access local variable 'db_name' where it is not associated
Moved initialization before try with safe defaults.
Three sync functions called async SupersetClient methods without await:
- get_datasource_columns(): get_dataset_detail + get_database (async)
- fetch_available_datasources(): get_datasets (async)
- Route handlers: both calls missing await
Converted to async functions + added awaits. Both endpoints now return
proper data instead of coroutine errors.
Svelte 5 does not allow duplicate class: directives on the same element.
The two class:border-warning (and two class:bg-warning-light) conditions
were mutually exclusive but Svelte rejected them at compile time.
Merged into single || condition.
git_service.get_status() is async def, but was called without await,
returning a coroutine object instead of dict. The coroutine was then
used as a dict value in RepoStatusBatchResponse, causing Pydantic
ValidationError (dict type mismatch).
get_dataset_linked_dashboard_count is async (coroutine function), but was
passed to asyncio.to_thread() which expects a sync callable. This returned
a coroutine object instead of an int, causing:
'>' not supported between instances of 'coroutine' and 'int'
Fix: await the async method directly inside asyncio.wait_for().
Found 3 missing 'await' keywords causing 'coroutine object is not iterable':
- get_dashboards_summary() (line 57)
- get_dashboards_summary_page() (line 114)
- get_datasets_summary() (line 306)
All three were calling async methods in sync context — returned coroutine
objects instead of lists/dicts, causing iteration failures.
HIGH: 'bodyClass' and display logic updated — 503/504 errors now show
warning (yellow) styling and 'Could not verify' message instead of
destructive (red) 'Table not found'. Backend now returns 503 on pool
exhaustion and 504 on upstream timeout after async refactoring.
All routes (assistant, migration, datasets, git) now use AsyncSupersetClient.
_helpers.py sync->async for dashboard ref resolution.
_detail_routes.py import fixed.
Known residual: MigrationDryRunService and IdMappingService still sync.
Add L1 invariant tests for all Screen Models:
- DashboardDetailModel: test load, delete, pagination, column filter
- DashboardHubModel: test load, environment switching, selection, git actions
- DatasetDetailModel: test load, edit, delete
- DatasetsHubModel: test load, filter, pagination
- DictionaryDetailModel: test load entries, add, edit, delete, search
- LLMReportModel: test load, filter, report generation
- TranslateHistoryModel: test load runs, filter, pagination
- ValidationRunDetailModel: test load details, records
- ValidationTasksListModel: test load tasks, status transitions
Per semantics-testing protocol: L1 tests verify model invariants without
DOM rendering.