Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
51c8507
Update python version exclusion to 3.15
Dev-iL Mar 11, 2026
4d64fb2
Add 3.14 metadata version classifiers and related constants
Dev-iL Mar 11, 2026
ad94588
Assorted workarounds to fix breeze image building
Dev-iL Mar 13, 2026
e66a3b0
Limit no-constraints fallback to bootstrap builds
Dev-iL Mar 14, 2026
e165508
Handle first constraints generation for new Python versions
Dev-iL Mar 14, 2026
ccb4e6d
Enable ignoring missing constraints on py3.14
Dev-iL Mar 14, 2026
e90bd46
Fix provider constraint generation on 3.14
Dev-iL Mar 15, 2026
74f2484
Exclude cassandra
Dev-iL Mar 15, 2026
000c2f8
Exclude amazon
Dev-iL Mar 15, 2026
147bb3e
Exclude google
Dev-iL Mar 15, 2026
115e268
Remove module-level import from `airflow.configuration`
Dev-iL Mar 15, 2026
8835daa
Avoid invoking truthiness on the `task` callable
Dev-iL Mar 15, 2026
05f0a99
CI: Only add pydantic extra to Airflow 2 migration tests
Dev-iL Mar 15, 2026
af5b69c
Disable DB migration tests for python 3.14
Dev-iL Mar 16, 2026
896328a
Enforce werkzeug 3.x for python 3.14
Dev-iL Mar 16, 2026
2c26ecf
Increase K8s executor test timeout for Python 3.14
Dev-iL Mar 16, 2026
00c2e19
Fix process_utils force-kill test race on Python 3.14
Dev-iL Mar 16, 2026
d491e36
Adapt LocalExecutor tests for Python 3.14 forkserver default
Dev-iL Mar 16, 2026
4cdc7d0
Bump dependencies to versions supporting 3.14
Dev-iL Mar 15, 2026
4c12e93
Fix PROD image build failing on Python 3.14 due to excluded providers
Dev-iL Mar 16, 2026
4c24186
Deselect DB tests at collection time instead of skipping at runtime
Dev-iL Mar 16, 2026
ddb0b49
Split core test types into 2 matrix groups to avoid OOM on Python 3.14
Dev-iL Mar 17, 2026
79e1071
Gracefully handle an already removed password file in fixture
Dev-iL Mar 17, 2026
555f13a
Fix OOM and flaky tests in test_process_utils
Dev-iL Mar 17, 2026
03c4df3
Add prek hook to validate python_version markers for excluded providers
Dev-iL Mar 17, 2026
914922c
Update `uv.lock`
Dev-iL Mar 15, 2026
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
12 changes: 10 additions & 2 deletions .github/actions/migration_tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ runs:
MIN_AIRFLOW_VERSION="$(
python ./scripts/ci/testing/get_min_airflow_version_for_python.py "${PYTHON_VERSION}"
)"
AIRFLOW_EXTRAS=""
if [[ "${MIN_AIRFLOW_VERSION}" =~ ^2\. ]]; then
AIRFLOW_EXTRAS="--airflow-extras pydantic"
fi
breeze shell "${AIRFLOW_2_CMD}" \
--use-airflow-version "${MIN_AIRFLOW_VERSION}" \
--airflow-extras pydantic \
${AIRFLOW_EXTRAS} \
--answer y &&
breeze shell "export AIRFLOW__DATABASE__EXTERNAL_DB_MANAGERS=${DB_MANGERS}
${AIRFLOW_3_CMD}" --no-db-cleanup
Expand Down Expand Up @@ -61,9 +65,13 @@ runs:
MIN_AIRFLOW_VERSION="$(
python ./scripts/ci/testing/get_min_airflow_version_for_python.py "${PYTHON_VERSION}"
)"
AIRFLOW_EXTRAS=""
if [[ "${MIN_AIRFLOW_VERSION}" =~ ^2\. ]]; then
AIRFLOW_EXTRAS="--airflow-extras pydantic"
fi
breeze shell "${AIRFLOW_2_CMD}" \
--use-airflow-version "${MIN_AIRFLOW_VERSION}" \
--airflow-extras pydantic \
${AIRFLOW_EXTRAS} \
--answer y &&
breeze shell "export AIRFLOW__DATABASE__EXTERNAL_DB_MANAGERS=${DB_MANGERS}
${AIRFLOW_3_CMD}" --no-db-cleanup
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/run-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ jobs:
uses: ./.github/actions/migration_tests
with:
python-version: ${{ matrix.python-version }}
if: inputs.run-migration-tests == 'true' && inputs.test-group == 'core'
# Any new python version should be disabled below via `&& matrix.python-version != '3.xx'` until the first
# Airflow version that supports it is released - otherwise there's nothing to migrate back to.
if: inputs.run-migration-tests == 'true' && inputs.test-group == 'core' && matrix.python-version != '3.14'
- name: >
${{ inputs.test-group }}:${{ inputs.test-scope }} Tests ${{ inputs.test-name }} ${{ matrix.backend-version }}
Py${{ matrix.python-version }}:${{ env.PARALLEL_TEST_TYPES }}
Expand Down
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ repos:
exclude: >
(?x)
^README\.md$|
^pyproject\.toml$|
^generated/PYPI_README\.md$|
^airflow-core/docs/.*commits\.rst$|
^airflow-core/newsfragments/41368\.significant\.rst$|
Expand Down Expand Up @@ -808,6 +809,17 @@ repos:
^providers/.*/provider\.yaml$
pass_filenames: false
require_serial: true
- id: check-excluded-provider-markers
name: Check excluded-provider python_version markers in pyproject.toml
language: python
entry: ./scripts/ci/prek/check_excluded_provider_markers.py
files: >
(?x)
^pyproject\.toml$|
^providers/.*/provider\.yaml$
pass_filenames: false
require_serial: true
additional_dependencies: ['packaging>=25', 'pyyaml', 'tomli>=2.0.1', 'rich>=13.6.0']
- id: update-reproducible-source-date-epoch
name: Update Source Date Epoch for reproducible builds
language: python
Expand Down
29 changes: 28 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,34 @@ function common::get_constraints_location() {
echo
echo "${COLOR_BLUE}Downloading constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} to ${HOME}/constraints.txt ${COLOR_RESET}"
echo
curl -sSf -o "${HOME}/constraints.txt" "${AIRFLOW_CONSTRAINTS_LOCATION}"
local http_code
if http_code=$(curl -sS -L -o "${HOME}/constraints.txt" -w "%{http_code}" "${AIRFLOW_CONSTRAINTS_LOCATION}"); then
if [[ ${http_code} == "200" ]]; then
return
fi
if [[ ${http_code} == "404" \
&& ${ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE:="false"} == "true" \
&& ${AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION:="false"} == "true" ]]; then
echo
echo "${COLOR_YELLOW}Constraints file not found at ${AIRFLOW_CONSTRAINTS_LOCATION}.${COLOR_RESET}"
echo "${COLOR_YELLOW}Using an empty constraints file because bootstrap mode was explicitly enabled.${COLOR_RESET}"
echo
AIRFLOW_CONSTRAINTS_LOCATION=""
: > "${HOME}/constraints.txt"
return
fi
echo
echo "${COLOR_RED}Failed to download constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} (HTTP ${http_code}).${COLOR_RESET}"
if [[ ${http_code} == "404" ]]; then
echo "${COLOR_RED}Only bootstrap builds should set both ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE=true and AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION=true.${COLOR_RESET}"
fi
echo
return 1
fi
echo
echo "${COLOR_RED}Failed to download constraints from ${AIRFLOW_CONSTRAINTS_LOCATION}.${COLOR_RESET}"
echo
return 1
else
echo
echo "${COLOR_BLUE}Copying constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} to ${HOME}/constraints.txt ${COLOR_RESET}"
Expand Down
32 changes: 31 additions & 1 deletion Dockerfile.ci
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,34 @@ function common::get_constraints_location() {
echo
echo "${COLOR_BLUE}Downloading constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} to ${HOME}/constraints.txt ${COLOR_RESET}"
echo
curl -sSf -o "${HOME}/constraints.txt" "${AIRFLOW_CONSTRAINTS_LOCATION}"
local http_code
if http_code=$(curl -sS -L -o "${HOME}/constraints.txt" -w "%{http_code}" "${AIRFLOW_CONSTRAINTS_LOCATION}"); then
if [[ ${http_code} == "200" ]]; then
return
fi
if [[ ${http_code} == "404" \
&& ${ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE:="false"} == "true" \
&& ${AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION:="false"} == "true" ]]; then
echo
echo "${COLOR_YELLOW}Constraints file not found at ${AIRFLOW_CONSTRAINTS_LOCATION}.${COLOR_RESET}"
echo "${COLOR_YELLOW}Using an empty constraints file because bootstrap mode was explicitly enabled.${COLOR_RESET}"
echo
AIRFLOW_CONSTRAINTS_LOCATION=""
: > "${HOME}/constraints.txt"
return
fi
echo
echo "${COLOR_RED}Failed to download constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} (HTTP ${http_code}).${COLOR_RESET}"
if [[ ${http_code} == "404" ]]; then
echo "${COLOR_RED}Only bootstrap builds should set both ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE=true and AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION=true.${COLOR_RESET}"
fi
echo
return 1
fi
echo
echo "${COLOR_RED}Failed to download constraints from ${AIRFLOW_CONSTRAINTS_LOCATION}.${COLOR_RESET}"
echo
return 1
else
echo
echo "${COLOR_BLUE}Copying constraints from ${AIRFLOW_CONSTRAINTS_LOCATION} to ${HOME}/constraints.txt ${COLOR_RESET}"
Expand Down Expand Up @@ -1680,6 +1707,7 @@ ARG CONSTRAINTS_GITHUB_REPOSITORY="apache/airflow"
ARG AIRFLOW_CONSTRAINTS_MODE="constraints-source-providers"
ARG AIRFLOW_CONSTRAINTS_REFERENCE=""
ARG AIRFLOW_CONSTRAINTS_LOCATION=""
ARG ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE="false"
ARG DEFAULT_CONSTRAINTS_BRANCH="constraints-main"
# By default fallback to installation without constraints because in CI image it should always be tried
ARG AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION="true"
Expand Down Expand Up @@ -1712,6 +1740,7 @@ ENV AIRFLOW_REPO=${AIRFLOW_REPO}\
AIRFLOW_CONSTRAINTS_MODE=${AIRFLOW_CONSTRAINTS_MODE} \
AIRFLOW_CONSTRAINTS_REFERENCE=${AIRFLOW_CONSTRAINTS_REFERENCE} \
AIRFLOW_CONSTRAINTS_LOCATION=${AIRFLOW_CONSTRAINTS_LOCATION} \
ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE=${ALLOW_MISSING_PREVIOUS_CONSTRAINTS_FILE} \
AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION=${AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION} \
DEFAULT_CONSTRAINTS_BRANCH=${DEFAULT_CONSTRAINTS_BRANCH} \
AIRFLOW_CI_BUILD_EPOCH=${AIRFLOW_CI_BUILD_EPOCH} \
Expand All @@ -1729,6 +1758,7 @@ ENV AIRFLOW_REPO=${AIRFLOW_REPO}\
AIRFLOW_VERSION_SPECIFICATION="" \
PIP_PROGRESS_BAR=${PIP_PROGRESS_BAR} \
ADDITIONAL_PIP_INSTALL_FLAGS=${ADDITIONAL_PIP_INSTALL_FLAGS} \
INCLUDE_PRE_RELEASE="true" \
CASS_DRIVER_BUILD_CONCURRENCY=${CASS_DRIVER_BUILD_CONCURRENCY} \
CASS_DRIVER_NO_CYTHON=${CASS_DRIVER_NO_CYTHON}

Expand Down
2 changes: 1 addition & 1 deletion airflow-core/docs/installation/prerequisites.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Prerequisites

Airflow® is tested with:

* Python: 3.10, 3.11, 3.12, 3.13
* Python: 3.10, 3.11, 3.12, 3.13, 3.14

* Databases:

Expand Down
24 changes: 13 additions & 11 deletions airflow-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ description = "Core packages for Apache Airflow, schedule and API server"
readme = { file = "README.md", content-type = "text/markdown" }
license = "Apache-2.0"
license-files = ["LICENSE", "NOTICE"]
# We know that it will take a while before we can support Python 3.14 because of all our dependencies
# It takes about 4-7 months after Python release before we can support it, so we limit it to <3.14
# proactively. This way we also have a chance to test it with Python 3.14 and bump the upper binding
# and manually mark providers that do not support it yet with !-3.14 - until they support it - which will
# also exclude resolving uv workspace dependencies for those providers.
requires-python = ">=3.10,!=3.14"
# Supporting new Python releases typically takes 4-7 months due to all our dependencies.
# We proactively exclude the next major version to avoid dependency conflicts, then test it and
# bump the upper binding once ready. Providers that don't support it yet are marked with
# != constraint - until they support it - which also excludes resolving uv workspace dependencies.
requires-python = ">=3.10,!=3.15"
authors = [
{ name = "Apache Software Foundation", email = "dev@airflow.apache.org" },
]
Expand All @@ -60,6 +59,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: System :: Monitoring",
]

Expand All @@ -80,7 +80,8 @@ dependencies = [
# The 1.13.0 of alembic marked some migration code as SQLAlchemy 2+ only so we limit it to 1.13.1
"alembic>=1.13.1, <2.0",
"argcomplete>=1.10",
"asgiref>=2.3.0",
"asgiref>=2.3.0; python_version < '3.14'",
"asgiref>=3.11.1; python_version >= '3.14'",
"attrs>=22.1.0, !=25.2.0",
"cadwyn>=6.0.4",
"colorlog>=6.8.2",
Expand All @@ -103,7 +104,8 @@ dependencies = [
"jinja2>=3.1.5",
"jsonschema>=4.19.1",
"lazy-object-proxy>=1.2.0",
'libcst >=1.8.2',
'libcst >=1.8.2; python_version < "3.14"',
'libcst >=1.8.6; python_version >= "3.14"',
"linkify-it-py>=2.0.0",
"lockfile>=0.12.2",
"methodtools>=0.4.7",
Expand Down Expand Up @@ -138,8 +140,7 @@ dependencies = [
"rich-argparse>=1.0.0",
"rich>=13.6.0",
"setproctitle>=1.3.3",
# SQLAlchemy >=2.0.36 fixes Python 3.13 TypingOnly import AssertionError caused by new typing attributes (__static_attributes__, __firstlineno__)
"sqlalchemy[asyncio]>=2.0.36",
"sqlalchemy[asyncio]>=2.0.48",
"svcs>=25.1.0",
"tabulate>=0.9.0",
"tenacity>=8.3.0",
Expand Down Expand Up @@ -173,7 +174,8 @@ dependencies = [
"async" = [
"eventlet>=0.37.0",
"gevent>=25.4.1",
"greenlet>=3.1.0",
"greenlet>=3.1.0; python_version < '3.14'",
"greenlet>=3.3.2; python_version >= '3.14'",
"greenback>=1.2.1",
]
"graphviz" = [
Expand Down
3 changes: 2 additions & 1 deletion airflow-core/src/airflow/models/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

from airflow._shared.module_loading import import_string
from airflow._shared.secrets_masker import mask_secret
from airflow.configuration import conf, ensure_secrets_loaded
from airflow.exceptions import AirflowException, AirflowNotFoundException
from airflow.models.base import ID_LEN, Base
from airflow.models.crypto import get_fernet
Expand Down Expand Up @@ -528,6 +527,8 @@ def get_connection_from_secrets(cls, conn_id: str, team_name: str | None = None)
raise AirflowNotFoundException(f"The conn_id `{conn_id}` isn't defined") from None
raise

from airflow.configuration import conf, ensure_secrets_loaded

if team_name and not conf.getboolean("core", "multi_team"):
raise ValueError(
"Multi-team mode is not configured in the Airflow environment but the task trying to access the connection belongs to a team"
Expand Down
1 change: 1 addition & 0 deletions airflow-core/src/airflow/typing_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
if sys.version_info >= (3, 11):
from typing import Self, Unpack, assert_never
else:
# TODO: Remove once Python 3.10 support is dropped (EOL 2026)
from typing_extensions import Self, Unpack, assert_never
3 changes: 3 additions & 0 deletions airflow-core/tests/unit/always/test_example_dags.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
IGNORE_EXAMPLE_DAGS: tuple[str, ...] = (
# These example dags require suspended providers, eg: google dataflow dependent on the Apache Beam provider,
# but it's in the suspended list, we can't import the dag
# Ray uses pydantic v1 internally, which fails to infer types in Python 3.14.
# TODO: remove once ray releases a version with Python 3.14 support.
"providers/google/tests/system/google/cloud/ray/example_ray_job.py",
"providers/google/tests/system/google/cloud/dataflow/example_dataflow_go.py",
"providers/google/tests/system/google/cloud/dataflow/example_dataflow_java_streaming.py",
"providers/google/tests/system/google/cloud/dataflow/example_dataflow_native_java.py",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from __future__ import annotations

import contextlib
import os

import pytest
Expand All @@ -33,7 +34,7 @@
@pytest.fixture
def auth_manager():
auth_manager = SimpleAuthManager()
if os.path.exists(auth_manager.get_generated_password_file()):
with contextlib.suppress(FileNotFoundError):
os.remove(auth_manager.get_generated_password_file())
return auth_manager

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async def lifespan_app(scope, receive, send):
import asyncio

with structlog.testing.capture_logs() as logs:
asyncio.get_event_loop().run_until_complete(middleware({"type": "lifespan"}, None, None))
asyncio.run(middleware({"type": "lifespan"}, None, None))

assert logs == []

Expand Down
Loading
Loading