Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/actions/migration_tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: >-
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ testing = ["dev", "providers.tests", "tests_common", "tests", "system", "unit",
"providers/**/tests/*" = ["D", "TID253", "S101", "TRY002"]
"performance/tests/*" = ["S101"]
"dev/registry/tests/*" = ["S101"]
"scripts/tests/*" = ["S101"]

# Shared distributions SHOULD use relative imports when referencing each other
# This one disables 'ban-relative-imports'.
Expand Down
60 changes: 60 additions & 0 deletions scripts/ci/testing/get_min_airflow_version_for_python.py
Original file line number Diff line number Diff line change
@@ -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()
16 changes: 16 additions & 0 deletions scripts/tests/ci/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions scripts/tests/ci/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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"
)


@pytest.fixture
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


@pytest.mark.parametrize(
("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")
Loading