1fanwang opened a new pull request, #66888:
URL: https://github.com/apache/airflow/pull/66888

   Triggering a Dag run with an oversized `conf` payload (and a whole class of 
similarly-shaped writes across the API) currently produces a generic `500 
Internal Server Error`. The SQL error surfaces deep in SQLAlchemy as `(1406, 
"Data too long for column 'conf' at row 1")` on MySQL, the caller has no signal 
that payload size was the cause, and every write endpoint that touches a 
length-capped column has the same shape today — `Connection.extra`, 
`Variable.val`, `XCom.value`, `TaskInstance.note`, HITL fields, and so on.
   
   This adds a single FastAPI exception handler for `sqlalchemy.exc.DataError` 
and registers it on both the public REST API and the task-execution API. `Data 
too long` / `too large` / `too big` errors map to `413 Content Too Large`; 
other DataErrors (out-of-range, numeric overflow) map to `422 Unprocessable 
Entity`. The response body carries the original DB error plus an actionable 
hint pointing at either reducing the payload or widening the column type on 
MySQL.
   
   Every existing and future write endpoint inherits the translation 
automatically. Postgres deployments never hit it (`JSONB` has no length cap); 
MySQL deployments get a clear 4xx + remediation hint instead of a generic 500.
   
   This replaces #66787, which proposed a config-knob + per-route validator + 
new exception class for the same problem. Closing that one in favour of this 
minimal, generalised version.
   
   ## Reproducer
   
   A real MySQL 8.0 container reproduces the literal `(1406, ...)` DataError, 
and the same exception driven through a FastAPI `TestClient` shows the response 
transition.
   
   ```
   $ docker run --rm -d --name mysql-66787 \
       -e MYSQL_ROOT_PASSWORD=test -e MYSQL_DATABASE=airflow_test \
       -p 3309:3306 mysql:8.0
   $ python /tmp/66787_dataerror_repro.py
   ```
   
   The script inserts a 70 KB JSON payload into a `dag_run`-shaped `TEXT conf` 
column to surface the real DataError, then drives the same error through 
TestClient with and without `_DataErrorHandler` registered.
   
   ### Before this PR
   
   ```
   === Step 1: real MySQL DataError (oversized conf into TEXT column) ===
     exception class: sqlalchemy.exc.DataError
     orig:            (1406, "Data too long for column 'conf' at row 1")
   
   === Step 2: drive the same DataError through FastAPI TestClient ===
   
   --- 2a: upstream/main behavior (no DataError handler registered) ---
     upstream/main: HTTP 500
     body: Internal Server Error
   
   --- 2b: this PR (DataError -> 413 via _DataErrorHandler) ---
   ImportError: cannot import name '_DataErrorHandler' from 
'airflow.api_fastapi.common.exceptions'
   ```
   
   ### After this PR
   
   ```
   === Step 1: real MySQL DataError (oversized conf into TEXT column) ===
     exception class: sqlalchemy.exc.DataError
     orig:            (1406, "Data too long for column 'conf' at row 1")
   
   === Step 2: drive the same DataError through FastAPI TestClient ===
   
   --- 2a: upstream/main behavior (no DataError handler registered) ---
     upstream/main: HTTP 500
     body: Internal Server Error
   
   --- 2b: this PR (DataError -> 413 via _DataErrorHandler) ---
     this PR     : HTTP 413
     body: {"detail":{"reason":"Payload exceeded database column 
limit","orig_error":"(1406, \"Data too long for column 'conf' at row 
1\")","message":"Database rejected the payload. Reduce the field size, or your 
operator may widen the column type (e.g. MEDIUMTEXT / LONGTEXT on MySQL)."}}
   ```
   
   Same DataError, same `orig_error` field; the difference is the HTTP response 
the API caller actually sees.
   
   ## Tests
   
   
`airflow-core/tests/unit/api_fastapi/common/test_exceptions.py::TestDataErrorHandler`
 covers:
   
   - five parametrised dialect-error shapes — MySQL 1406 (`Data too long`), 
Postgres `value too long for type ...`, SQLite `string or blob too big`, MySQL 
1264 (`Out of range`), Postgres `numeric field overflow` — asserting 413 for 
the "too large" family and 422 for the "out of range" family, the original DB 
error round-trips in the `orig_error` field, and the `MEDIUMTEXT` hint is in 
the message;
   - an end-to-end dispatch test that registers `ERROR_HANDLERS` on a `FastAPI` 
app, raises `DataError` from a route, and asserts the client receives the 413 
with the structured body.
   
   Run on this branch:
   
   ```
   $ uv run --project airflow-core pytest 
airflow-core/tests/unit/api_fastapi/common/test_exceptions.py -v
   ...
   
TestDataErrorHandler::test_dataerror_translates_to_actionable_http_response[mysql-1406-data-too-long]
 PASSED
   
TestDataErrorHandler::test_dataerror_translates_to_actionable_http_response[postgres-value-too-long]
 PASSED
   
TestDataErrorHandler::test_dataerror_translates_to_actionable_http_response[sqlite-blob-too-big]
 PASSED
   
TestDataErrorHandler::test_dataerror_translates_to_actionable_http_response[mysql-1264-out-of-range]
 PASSED
   
TestDataErrorHandler::test_dataerror_translates_to_actionable_http_response[postgres-numeric-field-overflow]
 PASSED
   TestDataErrorHandler::test_dataerror_dispatched_through_fastapi_app PASSED
   ... 16 passed, 6 skipped
   ```
   
   On `main` (handler not present) the same suite errors out at import-time 
with `ImportError: cannot import name '_DataErrorHandler'`.
   
   ## Scope
   
   Strictly `DataError`. `IntegrityError` translation (unique violations are 
already handled; FK / NOT NULL violations aren't) is intentionally left for a 
follow-up so this stays small and the failure-class is one specific shape — the 
database telling the API server "this value didn't fit", which is always a 
client-input problem.
   
   Closes #66779.
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes — Claude Code (Opus 4.7)
   
   Generated-by: Claude Code (Opus 4.7) following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to