SKILL.md
3,251 tokens · o200k_base · 12,628 bytes
Source excerpt starting at line 1.---name: upgrade-fab-providerdescription: > Upgrade the pinned Flask-AppBuilder (FAB) dependency in the Apache Airflow FAB provider (`providers/fab/`). Bumps the exact `flask-appbuilder==` pin and its mirror constant, regenerates `uv.lock`, drives the `test_fab_alignment.py` drift tripwire, reviews the vendored security-manager `override.py` against the new upstream FAB, and conditionally re-vendors static assets / DB migrations. Use when asked to "upgrade FAB", "bump flask-appbuilder", or move the FAB provider to a newer Flask-AppBuilder release.license: Apache-2.0--- <!-- SPDX-License-Identifier: Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0 --> # upgrade-fab-provider Airflow's FAB provider is **tightly coupled** to a specific Flask-AppBuilderrelease because it vendors-in and subclasses large parts of FAB's securitymanager. A version bump is therefore never "just change the pin" — it must bereconciled against the vendored code, and that reconciliation is enforced by apytest **alignment test**, not a prek hook. The canonical reference for a real bump is PR **#66841** ("Bump flask-appbuilderto 5.2.1 and mirror new auth event hooks") — commit `c72b6613fd`. Read its difffirst when in doubt: `git show c72b6613fd`. ## The coupling — why this is not a one-line change - `providers/fab/pyproject.toml:75-80` explains it: Airflow vendored FAB's security-manager code into `providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py` (~2700 lines) as `FabAirflowSecurityManagerOverride`. Every bump must review that class against upstream FAB for new / changed / removed methods.- `test_fab_alignment.py` mechanically detects drift between the **installed** FAB package and the vendored override, and **fails CI** until the developer reconciles it. ## Inputs - **Target version** — the FAB version to move to. If not given, use the latest release on PyPI (`https://pypi.org/pypi/flask-appbuilder/json` → `info.version`). Confirm the target with the user if it is a **major or minor** bump (higher reconciliation risk); a **patch** bump can proceed. ## The files a bump touches **Always:** 1. `providers/fab/pyproject.toml` — line ~80, the `flask-appbuilder==X.Y.Z` pin (the **only** real dependency pin in the repo).2. `providers/fab/tests/unit/fab/auth_manager/security_manager/test_fab_alignment.py` — `EXPECTED_FAB_VERSION = "X.Y.Z"` (line ~43). Must move in lockstep with the pin.3. `providers/fab/docs/index.rst` — the dependency table row ``` ``flask-appbuilder`` ``==X.Y.Z`` ``` (line ~114).4. `providers/fab/README.rst` — the Requirements table row (line ~60). **Do not hand-edit** — it is auto-generated. Regenerate it from the bumped `pyproject.toml` with the `sync-provider-readme` prek hook (Step 8); the hook re-renders the table whenever `pyproject.toml` changes. (Pre-existing bumps that predate this hook left it to release-time regeneration; today the hook is per-commit, so CI flags the drift — run it.)5. `uv.lock` — regenerated (see Step 4 for the pinned-uv caveat). **Conditionally:** 6. `providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py` — transplant any relevant upstream FAB changes (new auth hooks, changed signatures, ported fixes). The reference bump added +37 lines here. **Note:** a green alignment test does not prove the transplant is unnecessary — a security fix may live inside a method Airflow vendors (see the 5.2.2 worked example below).7. `providers/fab/src/airflow/providers/fab/www/**` + `providers/fab/www-hash.txt` — only if the new FAB ships changed static assets / templates that are re-vendored (see Step 6).8. `providers/fab/src/airflow/providers/fab/migrations/versions/**` — only if the new FAB version ships DB migrations (see Step 7). **Never:** no newsfragment, and do **not** hand-edit `providers/fab/docs/changelog.rst`— providers are released from `main` and the release manager regenerates thechangelog from `git log` (per `providers/AGENTS.md`). The commit subject is thechangelog entry. ## Procedure ### Step 1 — Determine the target version and current state - Read the current pin: `grep flask-appbuilder== providers/fab/pyproject.toml`.- Resolve the target (PyPI latest, or the user's requested version).- Classify the jump: patch / minor / major. For minor/major, skim the FAB release notes (`https://github.com/dpgaspar/Flask-AppBuilder/releases`) for security-manager / model / template changes before editing. ### Step 2 — Bump the three source pins Edit in lockstep: - `providers/fab/pyproject.toml` — the `flask-appbuilder==X.Y.Z` line (keep the trailing `# Whenever updating the version, run test_fab_alignment.py to verify.` comment).- `test_fab_alignment.py` — `EXPECTED_FAB_VERSION = "X.Y.Z"`. It is the tripwire; editing it now is fine — you will keep re-running the test until it and the other three tests pass.- `providers/fab/docs/index.rst` — the dependency-table `==X.Y.Z` row. ### Step 3 — Install the new FAB into the provider venv `uv --project providers/fab sync` installs the new pin. Confirm the installedversion: ```bashuv run --project providers/fab python -c \ "import importlib.metadata as m; print(m.version('flask-appbuilder'))"``` ### Step 4 — Regenerate uv.lock (pinned uv!) The lock **must** be regenerated with the repo's pinned uv version, or it driftshundreds of unrelated marker lines: ```bashAIRFLOW_UV_VERSION=$(grep -oE 'AIRFLOW_UV_VERSION=[0-9.]+' Dockerfile.ci | head -1 | cut -d= -f2)uvx --from uv==$AIRFLOW_UV_VERSION uv lock``` If a conflict is irrecoverable, delete `uv.lock` and re-run `uv lock` with thepinned version. Confirm the diff is limited to the FAB bump, not a wholesalemarker rewrite. ### Step 5 — Run the alignment test and reconcile override.py ```bashuv run --project providers/fab pytest \ providers/fab/tests/unit/fab/auth_manager/security_manager/test_fab_alignment.py -xvs``` Under the host sandbox the test needs a writable `AIRFLOW_HOME` and thererun-failures socket disabled — if it errors on a socket `bind` or on`~/airflow`, run it as: ```bashAIRFLOW_HOME="$TMPDIR/fab_home" uv run --project providers/fab pytest \ providers/fab/tests/.../test_fab_alignment.py -q -p no:rerunfailures``` The four tests and how to fix each: 1. **`test_fab_version_matches_expected`** — trips on the version mismatch. It passes once `EXPECTED_FAB_VERSION` == installed version, but only after you have done the review below.2. **`test_no_unaudited_fab_methods`** — a new FAB public method exists that is neither implemented in `override.py` nor listed in `AUDITED_EXCLUSIONS`. Fix: either implement/override it in `override.py`, or add it to `AUDITED_EXCLUSIONS` **with a justification comment**.3. **`test_no_stale_exclusions`** — `AUDITED_EXCLUSIONS` lists a method the new FAB no longer has. Fix: remove that entry.4. **`test_shared_method_signatures_compatible`** — FAB changed a method signature (new required param). Fix: update the `override.py` signature, or add to `KNOWN_SIGNATURE_DEVIATIONS` if the divergence is intentional. **The manual review that the test cannot fully automate:** diff the vendored`override.py` against the new FAB's `flask_appbuilder/security/sqla/manager.py`and `BaseSecurityManager` and **transplant behavioural changes** (bug fixes, newauth event hooks), not just signatures — the test only checks method *presence*and *required params*. Locate the installed source: ```bashuv run --project providers/fab python -c \ "import flask_appbuilder.security.sqla.manager as m; print(m.__file__)"``` ### Step 6 — Re-vendor static assets / templates (only if changed) If the new FAB changed frontend assets that Airflow vendors under`providers/fab/src/airflow/providers/fab/www/` (templates in`templates/appbuilder/`, static JS/CSS), re-vendor them, then regenerate thefingerprint: ```bashprek run compile-fab-assets --all-files``` This runs `scripts/ci/prek/compile_provider_assets.py fab` (pnpm build over`www/`) and rewrites `providers/fab/www-hash.txt`. A patch bump usually does**not** touch assets — skip this step unless FAB's templates/static changed.Commit the regenerated `www-hash.txt` if it changed. ### Step 7 — DB migrations (only if FAB ships them) If the new FAB adds/changes security-model tables, add the correspondingmigration under `providers/fab/src/airflow/providers/fab/migrations/versions/`and run: ```bashprek run update-migration-references-fab check-revision-heads-map-fab --all-files``` Patch bumps normally have no migrations — skip unless the release notes mentionschema changes. ### Step 8 — Static checks + tests ```bashprek run --from-ref main --stage pre-commituv run --project providers/fab pytest providers/fab/tests/unit/fab/auth_manager -xvs``` The pre-commit stage runs `sync-provider-readme` (regenerating `README.rst`) andother FAB hooks. Re-run the alignment test until all four tests pass. The fullprovider suite is `breeze testing providers-tests --test-type "Providers[fab]"`. ### Step 9 — Self-review and commit - `git diff main...HEAD` — verify only the intended files changed, and `uv.lock` is a clean FAB-scoped diff.- Commit subject in imperative mood, plain prose, **no** Conventional-Commits prefix, e.g. `Bump flask-appbuilder to X.Y.Z in FAB provider`. Body explains *why* (what upstream changes were mirrored), not what.- No newsfragment, no changelog edit (provider release manager regenerates from git log).- Prepend your PR to the history list in `providers/fab/CONTRIBUTING.rst` so the FAB-upgrade record stays current.- Push to `origin` and open the PR per the repo's PR conventions. ## Gotchas - **`EXPECTED_FAB_VERSION` is a second pin.** Forgetting it makes `test_fab_alignment.py` fail even when everything else is correct.- **`uv.lock` marker drift.** Always use the pinned uv (Step 4) — a bare `uv lock` rewrites hundreds of environment-marker lines and buries the real diff.- **The alignment test uses AST, not import**, to read FAB's `SecurityManager` (to avoid SQLAlchemy model-registry collisions with Airflow's vendored models). A green test proves *structural* alignment; it does **not** prove behavioural parity — Step 5's manual transplant review is still required.- **`docs/index.rst` dependency table** may look auto-generated but the reference PR edited it by hand; if a docs-regen prek hook rewrites it, let the hook win.- **`README.rst` is generated, but the sync hook is per-commit.** Don't hand-edit it; run `prek run sync-provider-readme` (or the full pre-commit stage) after bumping `pyproject.toml` — CI fails on the drift otherwise.- **Don't touch `providers/fab/docs/upgrading.rst`** — that is end-user guidance for upgrading the *provider package* in a deployment, not the developer bump workflow. ## Worked example — 5.2.1 to 5.2.2 (patch) A patch bump that needed **no `override.py` change**, but only after a realbehavioural review — the green alignment test alone was not sufficient evidence: - FAB 5.2.2 shipped three security-manager fixes: LDAP search-filter escaping, OAuth email-allowlist regex anchoring (`email + "$"`), and API-login provider validation.- All three touch methods Airflow *vendors* (`_search_ldap`, `auth_user_ldap`, `auth_user_oauth`) — so the alignment test passing did **not** mean "nothing to do". Each had to be checked by hand: - **LDAP escaping** — Airflow's vendored `_search_ldap` **already** escapes via `ldap.filter.escape_filter_chars` (and adds filter-parenthesis validation); it was ahead of FAB. No transplant. - **OAuth allowlist anchoring** — the match lives in FAB's `AuthOAuthView` (`views.py`), and Airflow's `CustomAuthOAuthView.oauth_authorized` delegates via `super().oauth_authorized()`, so the fix is inherited from the installed FAB 5.2.2. No transplant. - **API-login validation / Azure-JWT warning / uuid4** — in FAB core Airflow doesn't vendor. Inherited.- Net change set: `pyproject.toml`, `test_fab_alignment.py`, `docs/index.rst`, `README.rst` (via hook), `uv.lock`. Commit: `Bump flask-appbuilder to 5.2.2 in FAB provider`. The lesson the skill encodes: **for every security/behavioural fix in the FABrelease notes, locate the method and check whether Airflow vendors it** — thealignment test guards structure, you guard behaviour.
Discovery context
Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.