mirror of
https://github.com/apache/airflow.git
synced 2026-11-04 07:12:48 +00:00
Fix test infrastructure for Python-version-excluded providers (#63793)
* Skip provider tests when all test directories are excluded When running Providers[google] or Providers[amazon] on Python 3.14, generate_args_for_pytest removes the test folders for excluded providers, but the skip check in _run_test only triggered when the --ignore filter itself removed something. Since the folders were already removed upstream, the guard condition was never met, leaving pytest with only flags and no test directories — causing it to crash on unrecognized custom arguments. Remove the overly strict guard so the skip fires whenever no test directories remain in the args. * Fix PROD image docker tests for Python-version-excluded providers The docker tests expected all providers from prod_image_installed_providers.txt to be present, but providers like google and amazon declare excluded-python-versions in their provider.yaml. On Python 3.14, these providers are correctly excluded from the PROD image at build time, but the tests didn't account for this. Read provider.yaml exclusions and filter expected providers/imports based on the Docker image's Python version. * Skip Python-incompatible provider wheels during PROD image build get_distribution_specs.py now reads Requires-Python metadata from each wheel and skips wheels that are incompatible with the running interpreter. This prevents excluded providers (e.g. amazon on 3.14) from being passed to pip/uv and installed despite their exclusion. Also fix the requires-python specifier generation in packages.py: !=3.14 per PEP 440 only excludes 3.14.0, not 3.14.2. Use !=3.14.* wildcard to exclude the entire minor version.
This commit is contained in:
1 parent
210158e670
commit
48ec002f4e
7 files changed
+273
-13
No files matched your search
@@ -21,6 +21,7 @@ import os
|
||||
from importlib.util import find_spec
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from python_on_whales import DockerException
|
||||
|
||||
from docker_tests.constants import AIRFLOW_ROOT_PATH
|
||||
@@ -57,6 +58,39 @@ REGULAR_IMAGE_PROVIDERS = [
|
||||
testing_slim_image = os.environ.get("TEST_SLIM_IMAGE", str(False)).lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
def _get_provider_python_exclusions() -> dict[str, list[str]]:
|
||||
"""Return mapping of provider_id -> list of excluded Python minor versions from provider.yaml."""
|
||||
exclusions: dict[str, list[str]] = {}
|
||||
providers_root = AIRFLOW_ROOT_PATH / "providers"
|
||||
for line in PROD_IMAGE_PROVIDERS_FILE_PATH.read_text().splitlines():
|
||||
provider_id = line.split(">=")[0].strip()
|
||||
if not provider_id or provider_id.startswith("#"):
|
||||
continue
|
||||
provider_yaml_path = providers_root / provider_id.replace(".", "/") / "provider.yaml"
|
||||
if not provider_yaml_path.exists():
|
||||
continue
|
||||
with open(provider_yaml_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
excluded = data.get("excluded-python-versions", [])
|
||||
if excluded:
|
||||
exclusions[provider_id] = [str(v) for v in excluded]
|
||||
return exclusions
|
||||
|
||||
|
||||
PROVIDER_PYTHON_EXCLUSIONS = _get_provider_python_exclusions()
|
||||
|
||||
|
||||
def _get_python_minor_version(default_docker_image: str) -> str:
|
||||
"""Get Python minor version (e.g. '3.14') from the Docker image."""
|
||||
python_version = run_bash_in_docker("python --version", image=default_docker_image)
|
||||
return ".".join(python_version.strip().split()[1].split(".")[:2])
|
||||
|
||||
|
||||
def _get_excluded_provider_ids(python_minor: str) -> set[str]:
|
||||
"""Return set of provider IDs excluded for the given Python minor version."""
|
||||
return {pid for pid, versions in PROVIDER_PYTHON_EXCLUSIONS.items() if python_minor in versions}
|
||||
|
||||
|
||||
class TestCommands:
|
||||
def test_without_command(self, default_docker_image):
|
||||
"""Checking the image without a command. It should return non-zero exit code."""
|
||||
@@ -91,7 +125,10 @@ class TestPythonPackages:
|
||||
if testing_slim_image:
|
||||
packages_to_install = set(SLIM_IMAGE_PROVIDERS)
|
||||
else:
|
||||
packages_to_install = set(REGULAR_IMAGE_PROVIDERS)
|
||||
python_minor = _get_python_minor_version(default_docker_image)
|
||||
excluded_ids = _get_excluded_provider_ids(python_minor)
|
||||
excluded_packages = {f"apache-airflow-providers-{pid.replace('.', '-')}" for pid in excluded_ids}
|
||||
packages_to_install = set(REGULAR_IMAGE_PROVIDERS) - excluded_packages
|
||||
assert len(packages_to_install) != 0
|
||||
output = run_bash_in_docker(
|
||||
"airflow providers list --output json",
|
||||
@@ -197,15 +234,19 @@ class TestPythonPackages:
|
||||
def test_check_dependencies_imports(
|
||||
self, package_name: str, import_names: list[str], default_docker_image: str
|
||||
):
|
||||
python_minor = _get_python_minor_version(default_docker_image)
|
||||
excluded_ids = _get_excluded_provider_ids(python_minor)
|
||||
# Skip individual provider test cases if the provider is excluded for this Python version
|
||||
if package_name in excluded_ids:
|
||||
pytest.skip(f"Provider {package_name} is excluded for Python {python_minor}")
|
||||
if package_name == "providers":
|
||||
python_version = run_bash_in_docker(
|
||||
"python --version",
|
||||
image=default_docker_image,
|
||||
)
|
||||
if python_version.startswith("Python 3.13"):
|
||||
if "airflow.providers.fab" in import_names:
|
||||
import_names.remove("airflow.providers.fab")
|
||||
run_python_in_docker(f"import {','.join(import_names)}", image=default_docker_image)
|
||||
excluded_imports = {f"airflow.providers.{pid}" for pid in excluded_ids}
|
||||
import_names = [name for name in import_names if name not in excluded_imports]
|
||||
# FAB provider has import issues on Python 3.13
|
||||
if python_minor == "3.13":
|
||||
import_names = [name for name in import_names if name != "airflow.providers.fab"]
|
||||
if import_names:
|
||||
run_python_in_docker(f"import {','.join(import_names)}", image=default_docker_image)
|
||||
|
||||
def test_there_is_no_opt_airflow_airflow_folder(self, default_docker_image):
|
||||
output = run_bash_in_docker(
|
||||
|
||||
Reference in new issue
Block a user