This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 44273cebaef [v3-3-test] Keep README stable Airflow version pins in
sync with releases (#73786) (#73817)
44273cebaef is described below
commit 44273cebaef64f7a86bb559ba50a47c239ce5d7d
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Sep 28 18:48:35 2026 +0200
[v3-3-test] Keep README stable Airflow version pins in sync with releases
(#73786) (#73817)
The 3.3.2 release updated supported_versions.py, which regenerated the
lifecycle table, but the README "Stable version" header and the pip
install/constraints examples stayed on 3.3.0 because updating them was a
separate manual release step that is easy to miss. Deriving them from
SUPPORTED_VERSIONS in the same hook removes that step.
(cherry picked from commit ebe3bddae798cf6fb9cb1a1ad98e8ff9faedc49e)
Generated-by: Claude Opus 5
Co-authored-by: Jarek Potiuk <[email protected]>
---
dev/README_RELEASE_AIRFLOW.md | 6 +-
scripts/ci/prek/supported_versions.py | 34 ++++++++++++
scripts/tests/ci/prek/test_supported_versions.py | 71 ++++++++++++++++++++++++
3 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/dev/README_RELEASE_AIRFLOW.md b/dev/README_RELEASE_AIRFLOW.md
index 3fd3cf6e89f..6e9c30a8ed3 100644
--- a/dev/README_RELEASE_AIRFLOW.md
+++ b/dev/README_RELEASE_AIRFLOW.md
@@ -461,7 +461,8 @@ still works but is no longer recommended.
- Update the Task SDK version `>=` part in `pyproject.toml` to `==`
TASK_SDK_VERSION without RC
- Run `git commit` without a message to update versions in `docs`.
- Add supported Airflow version to `./scripts/ci/prek/supported_versions.py`
and let prek do the job again.
-- Replace the versions in `README.md` about installation and verify that
installation instructions work fine.
+ The `update-supported-versions` hook also rewrites the "Stable version"
header and the installation
+ `pip install` / constraints pins in `README.md` - verify that installation
instructions work fine.
- Update the build status badge in `README.md` to point to the new `vX-Y-test`
branch (the `3.x` row in the
build status table). The `uv run dev/update_github_branch_config.py X Y`
script does this automatically.
- Add entry for default python version to
`PROVIDERS_COMPATIBILITY_TESTS_MATRIX` in
`src/airflow_breeze/global_constants.py`
@@ -1657,7 +1658,8 @@ If you don't have access to the account ask a PMC member
to post.
This includes:
-- Modify `./scripts/ci/prek/supported_versions.py` and let prek do the job.
+- Modify `./scripts/ci/prek/supported_versions.py` and let prek do the job (it
also updates the stable
+ version pins in the `README.md` installation instructions).
- For major/minor release, update version in `airflow/__init__.py` and
`docker-stack-docs/` to the next likely major version release.
- New version should be, current major release + 1.0
- Sync `RELEASE_NOTES.rst` (including deleting relevant `newsfragments`) and
`README.md` changes.
diff --git a/scripts/ci/prek/supported_versions.py
b/scripts/ci/prek/supported_versions.py
index 2e218381c1f..6e8e578bd50 100755
--- a/scripts/ci/prek/supported_versions.py
+++ b/scripts/ci/prek/supported_versions.py
@@ -24,6 +24,7 @@
from __future__ import annotations
+import re
from pathlib import Path
from common_prek_utils import AIRFLOW_ROOT_PATH
@@ -48,6 +49,20 @@ SUPPORTED_VERSIONS = (
)
+# The README pins the latest stable release in the Requirements table header
and in the
+# ``pip install`` examples; these are not covered by the generated table, so
they are rewritten
+# here to keep a release PR that only edits SUPPORTED_VERSIONS from leaving
them stale.
+STABLE_VERSION_HEADER_PATTERN = re.compile(r"Stable version
\((?P<version>[^)]*)\)(?P<padding> *)")
+STABLE_VERSION_PIN_PATTERNS = (
+ re.compile(r"(?P<prefix>pip install
'apache-airflow(?:\[[^\]]*\])?==)[^']*(?P<suffix>')"),
+
re.compile(r"(?P<prefix>/apache/airflow/constraints-)[0-9][^/]*(?P<suffix>/constraints-)"),
+)
+
+
+def get_stable_version() -> str:
+ return SUPPORTED_VERSIONS[0][1]
+
+
def replace_text_between(file: Path, start: str, end: str, replacement_text:
str):
original_text = file.read_text()
leading_text = original_text.split(start)[0]
@@ -55,7 +70,26 @@ def replace_text_between(file: Path, start: str, end: str,
replacement_text: str
file.write_text(leading_text + start + replacement_text + end +
trailing_text)
+def update_stable_version_in_readme(text: str, stable_version: str) -> str:
+ def replace_header(match: re.Match) -> str:
+ # Keep the markdown table cell width unchanged whenever the padding
allows it.
+ width = len(match.group(0))
+ header = f"Stable version ({stable_version})"
+ return header + " " * max(width - len(header), 1 if
match.group("padding") else 0)
+
+ if not STABLE_VERSION_HEADER_PATTERN.search(text):
+ raise RuntimeError(f"Pattern {STABLE_VERSION_HEADER_PATTERN.pattern!r}
not found in README.md")
+ text = STABLE_VERSION_HEADER_PATTERN.sub(replace_header, text)
+ for pattern in STABLE_VERSION_PIN_PATTERNS:
+ if not pattern.search(text):
+ raise RuntimeError(f"Pattern {pattern.pattern!r} not found in
README.md")
+ text = pattern.sub(rf"\g<prefix>{stable_version}\g<suffix>", text)
+ return text
+
+
if __name__ == "__main__":
+ readme = AIRFLOW_ROOT_PATH / "README.md"
+ readme.write_text(update_stable_version_in_readme(readme.read_text(),
get_stable_version()))
replace_text_between(
file=AIRFLOW_ROOT_PATH / "README.md",
start="<!-- Beginning of auto-generated table -->\n",
diff --git a/scripts/tests/ci/prek/test_supported_versions.py
b/scripts/tests/ci/prek/test_supported_versions.py
new file mode 100644
index 00000000000..e6e4d3de3b7
--- /dev/null
+++ b/scripts/tests/ci/prek/test_supported_versions.py
@@ -0,0 +1,71 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import textwrap
+
+import pytest
+from ci.prek import supported_versions as hook
+
+README = textwrap.dedent(
+ """\
+ | | Main version (dev) | Stable version (3.3.0) | Deprecate
version (2.11.2) |
+
|------------|---------------------|--------------------------|------------------------------|
+ files in the orphan `constraints-main` and `constraints-2-0` branches.
+
+ pip install 'apache-airflow==3.3.0' \\
+ --constraint
"https://raw.githubusercontent.com/apache/airflow/constraints-3.3.0/constraints-3.10.txt"
+
+ pip install 'apache-airflow[postgres,google]==3.3.0' \\
+ --constraint
"https://raw.githubusercontent.com/apache/airflow/constraints-3.3.0/constraints-3.10.txt"
+ """
+)
+
+
+class TestUpdateStableVersionInReadme:
+ def test_updates_header_and_pins(self):
+ result = hook.update_stable_version_in_readme(README, "3.3.2")
+
+ assert result == README.replace("3.3.0", "3.3.2")
+
+ def test_keeps_table_cell_width(self):
+ result = hook.update_stable_version_in_readme(README, "3.3.10")
+
+ header, separator = result.splitlines()[:2]
+ assert "| Stable version (3.3.10) |" in header
+ assert len(header) == len(separator)
+
+ def test_is_idempotent(self):
+ once = hook.update_stable_version_in_readme(README, "3.3.2")
+
+ assert hook.update_stable_version_in_readme(once, "3.3.2") == once
+
+ @pytest.mark.parametrize(
+ "removed",
+ [
+ pytest.param(["Stable version (3.3.0)"], id="header"),
+ pytest.param(["'apache-airflow==3.3.0'",
"'apache-airflow[postgres,google]==3.3.0'"], id="pip"),
+ pytest.param(["/apache/airflow/constraints-3.3.0/"],
id="constraints"),
+ ],
+ )
+ def test_raises_when_a_pin_site_disappears(self, removed):
+ text = README
+ for snippet in removed:
+ text = text.replace(snippet, "")
+
+ with pytest.raises(RuntimeError, match="not found in README.md"):
+ hook.update_stable_version_in_readme(text, "3.3.2")