This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 58c768d05bc Add helper to tie Airflow version to the min supported 
Python version (#63596)
58c768d05bc is described below

commit 58c768d05bcd41a851fb48fb7fd6b93adaadd808
Author: Dev-iL <[email protected]>
AuthorDate: Sat Mar 14 23:29:36 2026 +0200

    Add helper to tie Airflow version to the min supported Python version 
(#63596)
---
 .github/actions/migration_tests/action.yml         | 20 ++++++-
 .../testing/get_min_airflow_version_for_python.py  | 60 ++++++++++++++++++++
 scripts/tests/ci/testing/__init__.py               | 16 ++++++
 .../test_get_min_airflow_version_for_python.py     | 64 ++++++++++++++++++++++
 4 files changed, 157 insertions(+), 3 deletions(-)

diff --git a/.github/actions/migration_tests/action.yml 
b/.github/actions/migration_tests/action.yml
index c4fcd4853f1..ae05e647899 100644
--- a/.github/actions/migration_tests/action.yml
+++ b/.github/actions/migration_tests/action.yml
@@ -28,13 +28,20 @@ runs:
     - name: "Test migration file 2 to 3 migration: ${{env.BACKEND}}"
       shell: bash
       run: |
-        breeze shell "${AIRFLOW_2_CMD}" --use-airflow-version 2.11.0  
--airflow-extras pydantic --answer y &&
+        MIN_AIRFLOW_VERSION="$(
+          python ./scripts/ci/testing/get_min_airflow_version_for_python.py 
"${PYTHON_VERSION}"
+        )"
+        breeze shell "${AIRFLOW_2_CMD}" \
+          --use-airflow-version "${MIN_AIRFLOW_VERSION}" \
+          --airflow-extras pydantic \
+          --answer y &&
         breeze shell "export 
AIRFLOW__DATABASE__EXTERNAL_DB_MANAGERS=${DB_MANGERS}
                     ${AIRFLOW_3_CMD}" --no-db-cleanup
       env:
         COMPOSE_PROJECT_NAME: "docker-compose"
         DB_RESET: "false"
         DB_MANAGERS: 
"airflow.providers.fab.auth_manager.models.db.FABDBManager"
+        PYTHON_VERSION: "${{ inputs.python-version }}"
         AIRFLOW_2_CMD: >-
           airflow db reset --skip-init -y &&
           airflow db migrate --to-revision heads
@@ -50,14 +57,21 @@ runs:
         COMPOSE_PROJECT_NAME: "docker-compose"
     - name: "Test ORM migration 2 to 3: ${{env.BACKEND}}"
       shell: bash
-      run: >
-        breeze shell "${AIRFLOW_2_CMD}" --use-airflow-version 2.11.0 
--airflow-extras pydantic --answer y &&
+      run: |
+        MIN_AIRFLOW_VERSION="$(
+          python ./scripts/ci/testing/get_min_airflow_version_for_python.py 
"${PYTHON_VERSION}"
+        )"
+        breeze shell "${AIRFLOW_2_CMD}" \
+          --use-airflow-version "${MIN_AIRFLOW_VERSION}" \
+          --airflow-extras pydantic \
+          --answer y &&
         breeze shell "export 
AIRFLOW__DATABASE__EXTERNAL_DB_MANAGERS=${DB_MANGERS}
              ${AIRFLOW_3_CMD}" --no-db-cleanup
       env:
         COMPOSE_PROJECT_NAME: "docker-compose"
         DB_RESET: "false"
         DB_MANAGERS: 
"airflow.providers.fab.auth_manager.models.db.FABDBManager"
+        PYTHON_VERSION: "${{ inputs.python-version }}"
         AIRFLOW_2_CMD: >-
           airflow db reset -y
         AIRFLOW_3_CMD: >-
diff --git a/scripts/ci/testing/get_min_airflow_version_for_python.py 
b/scripts/ci/testing/get_min_airflow_version_for_python.py
new file mode 100644
index 00000000000..ae404935c7d
--- /dev/null
+++ b/scripts/ci/testing/get_min_airflow_version_for_python.py
@@ -0,0 +1,60 @@
+# 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 argparse
+
+MIN_AIRFLOW_VERSION_BY_PYTHON = {
+    "3.10": "2.11.0",
+    "3.13": "3.1.0",
+    # "3.14": "3.2.0",
+}
+
+
+def _version_key(version: str) -> tuple[int, ...]:
+    return tuple(int(part) for part in version.split("."))
+
+
+def get_min_airflow_version_for_python(python_version: str) -> str:
+    """
+    Return the minimum supported Airflow version for the given Python version.
+
+    Unknown future Python versions inherit the latest known minimum Airflow 
version so the
+    requirement never decreases when a new Python version is added to the DB 
test matrix.
+    """
+
+    matching_versions = [
+        version
+        for version in MIN_AIRFLOW_VERSION_BY_PYTHON
+        if _version_key(version) <= _version_key(python_version)
+    ]
+    if not matching_versions:
+        raise ValueError(f"No minimum Airflow version defined for Python 
{python_version}")
+    return MIN_AIRFLOW_VERSION_BY_PYTHON[max(matching_versions, 
key=_version_key)]
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description="Return the minimum supported Airflow version for a Python 
version."
+    )
+    parser.add_argument("python_version", help="Python major.minor version, 
for example 3.14")
+    args = parser.parse_args()
+    print(get_min_airflow_version_for_python(args.python_version))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/tests/ci/testing/__init__.py 
b/scripts/tests/ci/testing/__init__.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ b/scripts/tests/ci/testing/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git 
a/scripts/tests/ci/testing/test_get_min_airflow_version_for_python.py 
b/scripts/tests/ci/testing/test_get_min_airflow_version_for_python.py
new file mode 100644
index 00000000000..b6f9bf239dd
--- /dev/null
+++ b/scripts/tests/ci/testing/test_get_min_airflow_version_for_python.py
@@ -0,0 +1,64 @@
+# 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 importlib.util
+import sys
+from pathlib import Path
+
+import pytest
+
+MODULE_PATH = (
+    Path(__file__).resolve().parents[4]
+    / "scripts"
+    / "ci"
+    / "testing"
+    / "get_min_airflow_version_for_python.py"
+)
+
+
[email protected]
+def min_airflow_module():
+    module_name = "test_get_min_airflow_version_for_python_module"
+    sys.modules.pop(module_name, None)
+    spec = importlib.util.spec_from_file_location(module_name, MODULE_PATH)
+    module = importlib.util.module_from_spec(spec)
+    assert spec.loader is not None
+    sys.modules[module_name] = module
+    spec.loader.exec_module(module)
+    return module
+
+
[email protected](
+    ("python_version", "expected_airflow_version"),
+    [
+        ("3.10", "2.11.0"),
+        ("3.12", "2.11.0"),
+        ("3.13", "3.1.0"),
+        ("3.14", "3.2.0"),
+        ("3.15", "3.2.0"),
+    ],
+)
+def test_get_min_airflow_version_for_python_is_monotonic(
+    min_airflow_module, python_version, expected_airflow_version
+):
+    assert 
min_airflow_module.get_min_airflow_version_for_python(python_version) == 
expected_airflow_version
+
+
+def 
test_get_min_airflow_version_for_python_raises_for_older_python(min_airflow_module):
+    with pytest.raises(ValueError, match="No minimum Airflow version defined 
for Python 3.9"):
+        min_airflow_module.get_min_airflow_version_for_python("3.9")

Reply via email to