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

Reply via email to