This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 522e916e14a [v3-2-test] API: Return 400 instead of 500 from
materialize_asset for invalid validation input (#67445) (#67526)
522e916e14a is described below
commit 522e916e14aca4c70fb7e6776bc55c2b80c38ca4
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Wed May 27 16:43:08 2026 +0800
[v3-2-test] API: Return 400 instead of 500 from materialize_asset for
invalid validation input (#67445) (#67526)
The materialize_asset POST endpoint passed user input (dag_run_id,
logical_date/data_interval pairing, partition_key) straight through to
MaterializeAssetBody.validate_context() and dag.create_dagrun(), which raise
ValueError / ParamValidationError on invalid input (e.g. dag_run_id
containing
'..', invalid partition_key type, logical_date/data_interval mismatch).
Those
exceptions escaped uncaught and were returned as 500 Internal Server Error,
even though the route's OpenAPI spec already documents 400 for this case.
Wrap the validate_context / create_dagrun calls in try/except and re-raise
as
HTTPException(400) with the validator's message in the detail, matching the
sibling pattern in dag_run.trigger_dag_run.
Regression test asserts dag_run_id='bad..id' returns 400, not 500.
(cherry picked from commit 0120ba7fd5be8b6d5e7e4184209fb0479127f2d8)
Co-authored-by: Deepak kumar <[email protected]>
---
.../api_fastapi/core_api/routes/public/assets.py | 34 ++++++++++++----------
.../core_api/routes/public/test_assets.py | 13 +++++++++
2 files changed, 32 insertions(+), 15 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
index e5d9d4bf228..411d94de6cb 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
@@ -72,6 +72,7 @@ from airflow.api_fastapi.core_api.security import (
)
from airflow.api_fastapi.logging.decorators import action_logging
from airflow.assets.manager import asset_manager
+from airflow.exceptions import ParamValidationError
from airflow.models.asset import (
AssetAliasModel,
AssetDagRunQueue,
@@ -435,21 +436,24 @@ def materialize_asset(
f"Dag with dag_id: '{dag_id}' does not allow asset materialization
runs",
)
- params = (body or MaterializeAssetBody()).validate_context(dag)
- return dag.create_dagrun(
- run_id=params["run_id"],
- logical_date=params["logical_date"],
- data_interval=params["data_interval"],
- run_after=params["run_after"],
- conf=params["conf"],
- run_type=DagRunType.ASSET_MATERIALIZATION,
- triggered_by=DagRunTriggeredByType.REST_API,
- triggering_user_name=user.get_name(),
- state=DagRunState.QUEUED,
- partition_key=params["partition_key"],
- note=params["note"],
- session=session,
- )
+ try:
+ params = (body or MaterializeAssetBody()).validate_context(dag)
+ return dag.create_dagrun(
+ run_id=params["run_id"],
+ logical_date=params["logical_date"],
+ data_interval=params["data_interval"],
+ run_after=params["run_after"],
+ conf=params["conf"],
+ run_type=DagRunType.ASSET_MATERIALIZATION,
+ triggered_by=DagRunTriggeredByType.REST_API,
+ triggering_user_name=user.get_name(),
+ state=DagRunState.QUEUED,
+ partition_key=params["partition_key"],
+ note=params["note"],
+ session=session,
+ )
+ except (ParamValidationError, ValueError) as e:
+ raise HTTPException(status.HTTP_400_BAD_REQUEST, str(e)) from e
@assets_router.get(
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
index 26fe24888ac..ba6a4200959 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
@@ -1551,6 +1551,19 @@ class TestPostAssetMaterialize(TestAssets):
user=mock.ANY,
)
+ @pytest.mark.usefixtures("configure_git_connection_for_dag_bundle")
+ def test_should_respond_400_on_invalid_dag_run_id(self, test_client):
+ """A dag_run_id failing DagRun.validate_run_id triggers ValueError.
+
+ It must surface as 400 BAD_REQUEST, not 500 INTERNAL_SERVER_ERROR.
+ """
+ response = test_client.post(
+ "/assets/1/materialize",
+ json={"dag_run_id": "bad id"},
+ )
+ assert response.status_code == 400
+ assert "does not match regex pattern" in response.json()["detail"]
+
class TestGetAssetQueuedEvents(TestQueuedEventEndpoint):
@pytest.mark.usefixtures("time_freezer")