The GitHub Actions job "Tests (AMD)" on airflow.git/main has failed.
Run started by GitHub user potiuk (triggered by potiuk).

Head commit for run:
3733e62d6a4262a4e4e11759ebc201d25e9a9142 / Anurag Pappula 
<[email protected]>
Type asset_expression in the REST API so the UI does not cast through unknown 
(#67725)

* Type asset_expression in the REST API instead of an untyped dict

asset_expression was declared as `dict | None` on the REST API response
models, so the OpenAPI generator emitted an opaque `{ [k: string]: unknown }`
and the UI hand-maintained an ExpressionType union and cast the value through
it with no runtime check. A change to the server shape would not be caught by
the TypeScript build.

This adds a structured, recursive AssetExpression model in
datamodels/common.py: a discriminated union over the five shapes
BaseAsset.as_expression() produces (asset, alias, asset_ref, any, all),
following the existing Annotated[Union[...], Discriminator] pattern. It is
used on every response that serves the field (DAG details, DAGs with latest
runs, and both partitioned dag run responses), types the next_run_assets UI
endpoint with a NextRunAssetsResponse model, regenerates the OpenAPI spec and
the TypeScript client, and removes the now-redundant hand-written union and
casts in the UI.

The asset leaf carries the optional id that DagModelOperation injects on
persistence; id is optional so a row written before id-enrichment, or
migrated from the pre-3.0 dataset format, still validates. For expressions
produced by current code the emitted JSON is unchanged, so this is not a
breaking change for API consumers; only the declared schema becomes precise.

The test_get_dags fixtures previously set asset_expression to a simplified
`{"any": [{"uri": ...}]}` shape that as_expression() never produces. That was
harmless against an untyped dict but the structured model correctly rejects
the bare leaf, so the fixtures now use the real `{"any": [{"asset": {...}}]}`
shape with each asset's name and group.

* Derive ExpressionType from generated types, drop manual aliases

ExpressionType is now NonNullable<DAGDetailsResponse["asset_expression"]>
instead of a hand-maintained union that could drift from the server shape.
Remove AssetSummary and inline its union at the only use site (AssetNode),
and drop the explanatory comment. Per review feedback.

* Remove unused NextRunEvent type from AssetExpression

* Apply ruff-format to partitioned_dag_runs route

Wrap the over-length created_at/updated_at ternaries so the ruff-format 
pre-commit hook passes. No behaviour change.

* Degrade legacy asset_expression shapes to null instead of 500ing the API

The newly-typed asset_expression field rejects pre-3.0 dataset shapes (a bare 
uri string, {"any": [<strings>]}, {"alias": "<name>"}) that the 2.x->3.x column 
rename carried over verbatim, which would surface as an HTTP 500 on the 
dag-detail, dag-list and next_run_assets endpoints until the dag is re-parsed. 
MaybeAssetExpression wraps the union with a BeforeValidator that coerces any 
unrecognized stored shape to null, reproducing the blank render the UI showed 
while the field was an untyped dict. Adds round-trip and coercion tests.

* Log a warning when an unrecognized asset_expression shape is dropped

Per review feedback: the tolerant validator silently coerced any unrecognized 
shape to None, which also masked genuine server/UI shape drift this PR guards 
against. Emit a warning when a non-None value is dropped so the drift stays 
observable. Graceful degradation behaviour is unchanged.

* Extract response dict into a named variable before model_validate

Per review feedback: pull the dict passed to .model_validate() into a 
model_data local in the UI routes that build responses from a dict, which reads 
better for the larger payloads. No behaviour change.

* Annotate extracted model_data dicts as dict[str, Any]

The dict literals were inferred with a narrow value type once pulled into a 
local, so mypy rejected heterogeneous entries (e.g. pending_partition_count: 
int | None). Declare them as dict[str, Any], which is what model_validate 
accepts, annotating the first occurrence per function scope to avoid no-redef.

* Add route test for legacy asset_expression served as null

Per review: a pre-3.0 dataset-format asset_expression (string leaves) is served 
as null on /dags/{id}/details instead of 500ing the endpoint, exercising 
MaybeAssetExpression end-to-end.

Report URL: https://github.com/apache/airflow/actions/runs/28245758792

With regards,
GitHub Actions via GitBox


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to