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

   If prek runs the permission-doc hooks on a Python older than 3.11, they 
quietly delete 84 lines from `api_permissions_ref.rst` and another 84 from the 
FAB `access-control.rst`, and then tell you it worked.
   
   ### What's going on
   
   Two hooks regenerate those docs, `generate-api-permissions-doc` and 
`generate-fab-permissions-doc`. Both statically parse the route files under 
`api_fastapi/core_api/routes/public/` with `ast`, which means the interpreter 
running the hook has to be at least as new as the code it is reading. Two route 
files use PEP 646 star-unpacking in a subscript (`assets.py:217`, 
`dag_stats.py:78`), so they need 3.11 to parse at all. That's fine, since every 
`pyproject.toml` in the repo sets a Python floor of 3.11.
   
   Neither script carries a PEP 723 block though, so there is nothing telling 
prek which interpreter they need and it falls back to 
`default_language_version: python: python3`. Here that resolved to a managed 
3.10.20, which cannot parse those two lines, and this is what came out:
   
   ```
   Generate REST API permission reference 
documentation.......................Failed
     [WARN] Could not parse assets.py: invalid syntax (<unknown>, line 217)
     [WARN] Could not parse dag_stats.py: invalid syntax (<unknown>, line 78)
     [OK] Written 116 entries to .../api_permissions_ref.rst
   
    airflow-core/docs/security/api_permissions_ref.rst | 84 
----------------------
    providers/fab/docs/auth-manager/access-control.rst | 84 
----------------------
    2 files changed, 168 deletions(-)
   ```
   
   137 entries on 3.11, 116 on 3.10. The script exits 0 and prints `[OK]`. 
Running it straight from the shell with a 3.9.6 `python3` gives the same 116.
   
   The reason it degrades instead of stopping is `extract_from_file`: it 
catches `(OSError, SyntaxError)`, warns on stderr, and returns an empty list. 
The caller has no way to tell that apart from a route file that genuinely 
declares no routes, so the endpoints just vanish and the run still succeeds. 
`fab_permissions_doc.py` imports the same helper, which is why both docs shrink 
together.
   
   ### How much is affected
   
   CI is fine, because CI's `python3` resolves to 3.11+. That is luck rather 
than design though: nothing in the hook definitions requires it, which is how 
my machine ended up on 3.10. I think that is exactly why this has survived: the 
hook rewrites the doc short, you see two docs you never touched show up in `git 
diff`, and the hook output says `[OK]`. I nearly committed the 168-line 
deletion myself while working on an unrelated PR.
   
   It is also an odd way for one of these scripts to behave. Plenty of hooks in 
`scripts/ci/prek/` read the repo's own source with `ast`, and most of them pin 
the Python they need, so they never meet an interpreter that cannot parse it. 
Six do not pin, and of those six `extract_permissions.py` is the only one that 
responds to a parse failure by carrying on regardless. 
`check_registry_types_json_sync.py` and `check_applies_to_roots_in_sync.py` 
have no handler at all and just traceback, 
`check_contextmanager_class_decorators.py` reports the parse error as a 
finding, and `check_secrets_search_path_sync.py` returns `None` so its caller 
bails.
   
   ### The fix
   
   Two small changes.
   
   Pin both scripts to `requires-python = ">=3.11"` with a PEP 723 block, which 
is how 112 of the other scripts in `scripts/ci/prek/` already declare their 
interpreter. prek then picks a conforming Python and the hooks simply work.
   
   Then make a parse failure fatal rather than silent. The pin removes today's 
trigger, but the generator's job is to produce a *complete* table, so any 
future unparseable or unreadable file should stop the run instead of shrinking 
a security reference nobody is going to re-count by hand.
   
   After the change both hooks pass and leave the docs byte-identical. With a 
route file deliberately broken, the script now says `[FAIL] Could not parse 
<path>: invalid syntax`, exits 1, and leaves the doc alone instead of rewriting 
it short.
   
   ### Tests
   
   The existing suite had `test_syntax_error_returns_empty_list`, which 
asserted the old behaviour directly (`entries == []`, `"WARN" in 
captured.err`), so it had to change. It's now 
`test_syntax_error_aborts_instead_of_dropping_entries`, and I added 
`test_unreadable_file_aborts` to cover the `OSError` arm of the same `except`.
   
   Both new tests fail against unpatched `extract_permissions.py`; I checked by 
stashing just that file. `scripts/tests/` passes at 1678, and ruff check and 
format are clean on all three files.
   
   No newsfragment, since this is dev tooling with no user-facing change.
   
   ### One open question
   
   I pinned `>=3.11` rather than `>=3.11,<3.12` like most siblings use, because 
for these two scripts the floor is what matters and a ceiling would only get 
stale. Happy to match the majority if you'd rather these look like the rest.
   
   The swallow came in with #67606 and was inherited by #72230. It reads like a 
reasonable choice at the time, when a warning on stderr would plausibly be 
seen, and it only turns into silent data loss because prek routes hook output 
the way it does.
   
   ---
   
   ##### 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