Pushkal-Gupta opened a new pull request, #74027:
URL: https://github.com/apache/airflow/pull/74027

   Follow-up to #71647, which added `check-openapi-exception-doc-in-sync` but 
only inspected statuses a handler raised in its own body. Review on that PR 
asked for nested resolution, since most route code reaches its error paths 
through a helper:
   
   ```
   route.handler -> common.validate -> common.not_found raises 
HTTPException(404)
   ```
   
   Those are exactly the statuses a client is most likely to hit and least 
likely to have a model for. Following helper calls found six still undeclared:
   
   | Endpoint | Undeclared | Raised by |
   | --- | --- | --- |
   | `PUT 
/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key}` 
| `400` | `_resolve_expires_at` — a negative `default_retention_days` |
   | `GET /ui/calendar/{dag_id}` | `404` | `get_latest_version_of_dag` — the 
Dag does not exist |
   | `GET /execution/hitlDetails/{task_instance_id}` | `404` | 
`_check_hitl_detail_exists` |
   | `PATCH /execution/hitlDetails/{task_instance_id}` | `404` | 
`_check_hitl_detail_exists` |
   | `GET /execution/task-instances/count` | `404` | `_get_group_tasks` — 
unknown task group |
   | `GET /execution/task-instances/states` | `404` | `_get_group_tasks` — 
unknown task group |
   
   The first one is worth calling out: `_resolve_expires_at`'s own docstring 
already says "Negative values raise HTTP 400", so that response was documented 
in prose and absent from the spec at the same time.
   
   Two public/UI endpoints gain error models their clients had no type for; the 
execution API changes have no committed spec artifact.
   
   ### Why plain `ast` and not LibCST
   
   Review suggested LibCST's `FullyQualifiedNameProvider`. I measured its two 
advantages against this package before adding the dependency, and neither 
applies here:
   
   - **Relative imports:** `grep -rE "^from \.+" 
airflow-core/src/airflow/api_fastapi` returns nothing. There are none to 
resolve.
   - **Aliases:** present, but only for model classes (`TaskInstance as TI`), 
never for functions that raise `HTTPException`. `ast.ImportFrom` already 
exposes `asname`, which is what this resolver keys on.
   
   Meanwhile `libcst.parse_module` is **17.8x slower** than `ast.parse` over 
the 68 route files (599 ms vs 34 ms), before counting the imported modules a 
call graph also has to parse, and `FullyQualifiedNameProvider` needs a 
`MetadataWrapper` with repository-level resolution on top of that. For a hook 
that runs on every commit that cost buys no additional resolution power in this 
package.
   
   Both approaches find the same six statuses. I went with `ast` and kept the 
dependency surface unchanged. Happy to switch if relative imports or aliased 
helpers show up here later — the resolver is one function, and the tests pin 
the behaviour either way.
   
   ### About the check
   
   The conservative posture from #71647 is unchanged — it gates CI, so it 
under-reports rather than over-reports:
   
   - Only calls in a handler's **body** are followed. This matters more than it 
sounds: a route decorator's `dependencies=[Depends(requires_access_dag(...))]` 
resolves to functions that raise `400` on an invalid identifier, and treating 
those as the route's own would have produced nine false positives. The router 
supplies those statuses, not the route.
   - Imports are followed only from the route module itself; inside an imported 
module only that module's own definitions are visible.
   - Chains stop at `MAX_CALL_DEPTH` (3), and a repeated name terminates the 
walk, so mutual recursion cannot loop.
   - `401`, `403` and `422` are still never required, and anything unresolvable 
is skipped rather than guessed at.
   
   ### Verification
   
   - `uv run --project scripts pytest 
scripts/tests/ci/prek/test_check_openapi_exception_doc_in_sync.py` — 36 passed
   - `prek run check-openapi-exception-doc-in-sync --all-files` — Passed, 0 
violations remaining
   - `prek run --from-ref main --stage pre-commit` — exit 0 (mypy included)
   - `pytest airflow-core/tests/unit/api_fastapi/execution_api` — 669 passed, 4 
skipped (postgres/mysql-only)
   - Calendar, HITL and task-state-store route tests — 158 passed
   - Specs and the UI TypeScript client regenerated; insertions only, no churn
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes — Claude Code (Opus 5)


-- 
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