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

ashb pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new fa7d9c01ff9 [v3-4-test] Migrate airflow-core to `httpx2` (#74244) 
(#75424)
fa7d9c01ff9 is described below

commit fa7d9c01ff9684e7458dbf65ab2032d24970e037
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 23 07:46:23 2026 +0100

    [v3-4-test] Migrate airflow-core to `httpx2` (#74244) (#75424)
---
 airflow-core/pyproject.toml                                   |  9 ++++++---
 airflow-core/src/airflow/api/client/local_client.py           |  6 +++---
 airflow-core/src/airflow/api_fastapi/auth/tokens.py           |  4 ++--
 .../src/airflow/api_fastapi/execution_api/in_process.py       |  1 -
 .../src/airflow/example_dags/example_dag_decorator.py         |  4 ++--
 airflow-core/tests/unit/api_fastapi/auth/test_tokens.py       | 10 +++++-----
 .../api_fastapi/core_api/routes/public/test_dag_sources.py    |  2 +-
 .../unit/api_fastapi/execution_api/versions/head/test_hitl.py | 11 +++++------
 uv.lock                                                       |  2 --
 9 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/airflow-core/pyproject.toml b/airflow-core/pyproject.toml
index 68ec71414a9..480707fd6cc 100644
--- a/airflow-core/pyproject.toml
+++ b/airflow-core/pyproject.toml
@@ -102,9 +102,6 @@ dependencies = [
     # and reports them as a 400. Below it, an unauthenticated form body to 
POST /auth/token is
     # parsed with no field-count or field-size bound at all.
     "starlette>=1.3.1",
-    "httpx>=0.25.0",
-    # Only for InProcessExecutionAPI's transports, which feed httpx2-based 
Task SDK clients.
-    # The rest of airflow-core is still on httpx; tracked at 
https://github.com/apache/airflow/issues/70522
     "httpx2>=2.0.0",
     'importlib_metadata>=6.5;python_version<"3.12"',
     'importlib_metadata>=7.0;python_version>="3.12"',
@@ -348,3 +345,9 @@ shared_distributions = [
     "apache-airflow-shared-plugins-manager",
     "apache-airflow-shared-providers-discovery",
 ]
+
+[tool.ruff]
+extend = "../pyproject.toml"
+
+[tool.ruff.lint.flake8-tidy-imports.extend-banned-api]
+"httpx".msg = "Use httpx2; httpx objects do not interoperate with the 
httpx2-based Task SDK client. See 
https://github.com/apache/airflow/issues/70522";
diff --git a/airflow-core/src/airflow/api/client/local_client.py 
b/airflow-core/src/airflow/api/client/local_client.py
index 057d6d99c7c..57b3ec64ed1 100644
--- a/airflow-core/src/airflow/api/client/local_client.py
+++ b/airflow-core/src/airflow/api/client/local_client.py
@@ -19,7 +19,7 @@
 
 from __future__ import annotations
 
-import httpx
+import httpx2
 
 from airflow.api.common import delete_dag, trigger_dag
 from airflow.exceptions import AirflowBadRequest, PoolNotFound
@@ -30,8 +30,8 @@ from airflow.utils.types import DagRunTriggeredByType
 class Client:
     """Local API client implementation."""
 
-    def __init__(self, auth=None, session: httpx.Client | None = None):
-        self._session: httpx.Client = session or httpx.Client()
+    def __init__(self, auth=None, session: httpx2.Client | None = None):
+        self._session: httpx2.Client = session or httpx2.Client()
         if auth:
             self._session.auth = auth
 
diff --git a/airflow-core/src/airflow/api_fastapi/auth/tokens.py 
b/airflow-core/src/airflow/api_fastapi/auth/tokens.py
index 29681eaefa0..dd6682b53c6 100644
--- a/airflow-core/src/airflow/api_fastapi/auth/tokens.py
+++ b/airflow-core/src/airflow/api_fastapi/auth/tokens.py
@@ -26,7 +26,7 @@ from datetime import datetime
 from typing import TYPE_CHECKING, Any, Literal, overload
 
 import attrs
-import httpx
+import httpx2
 import jwt
 import structlog
 from asgiref.sync import async_to_sync
@@ -107,7 +107,7 @@ class JWKS:
     fetched_at: float = 0
     last_fetch_attempt_at: float = 0
 
-    client: httpx.AsyncClient = attrs.field(factory=httpx.AsyncClient)
+    client: httpx2.AsyncClient = attrs.field(factory=httpx2.AsyncClient)
 
     _jwks: jwt.PyJWKSet | None = None
     refresh_jwks: bool = True
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/in_process.py 
b/airflow-core/src/airflow/api_fastapi/execution_api/in_process.py
index 8fd8d93e5ed..3807e3f7cc6 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/in_process.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/in_process.py
@@ -118,7 +118,6 @@ class InProcessExecutionAPI:
 
         return self._app
 
-    # httpx2, not httpx: the only consumers are Task SDK clients, which 
subclass httpx2.Client.
     @cached_property
     def transport(self) -> httpx2.WSGITransport:
         import httpx2
diff --git a/airflow-core/src/airflow/example_dags/example_dag_decorator.py 
b/airflow-core/src/airflow/example_dags/example_dag_decorator.py
index 452ad97c4b6..980e7f83bab 100644
--- a/airflow-core/src/airflow/example_dags/example_dag_decorator.py
+++ b/airflow-core/src/airflow/example_dags/example_dag_decorator.py
@@ -22,7 +22,7 @@ import ipaddress
 # [START dag_decorator_usage]
 from typing import TYPE_CHECKING, Any
 
-import httpx
+import httpx2
 import pendulum
 
 from airflow.providers.standard.operators.bash import BashOperator
@@ -42,7 +42,7 @@ class GetRequestOperator(BaseOperator):
         self.url = url
 
     def execute(self, context: Context):
-        return httpx.get(self.url).json()
+        return httpx2.get(self.url).json()
 
 
 @dag(
diff --git a/airflow-core/tests/unit/api_fastapi/auth/test_tokens.py 
b/airflow-core/tests/unit/api_fastapi/auth/test_tokens.py
index 394d96a72e8..cc43e0e06e4 100644
--- a/airflow-core/tests/unit/api_fastapi/auth/test_tokens.py
+++ b/airflow-core/tests/unit/api_fastapi/auth/test_tokens.py
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING
 from unittest.mock import patch
 
 import anyio
-import httpx
+import httpx2
 import jwt
 import pytest
 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
@@ -63,9 +63,9 @@ class TestJWKS:
         jwk_content = json.dumps({"keys": [key_to_jwk_dict(private_key, 
"kid")]})
 
         async def mock_transport(request):
-            return httpx.Response(status_code=200, content=jwk_content)
+            return httpx2.Response(status_code=200, content=jwk_content)
 
-        client = 
httpx.AsyncClient(transport=httpx.MockTransport(mock_transport))
+        client = 
httpx2.AsyncClient(transport=httpx2.MockTransport(mock_transport))
         jwks = JWKS(url="https://example.com/jwks.json";, client=client)
 
         # Test fetching JWKS
@@ -79,9 +79,9 @@ class TestJWKS:
         jwk_content = json.dumps({"keys": 
[key_to_jwk_dict(ed25519_private_key, "kid")]})
 
         async def mock_transport(request):
-            return httpx.Response(status_code=200, content=jwk_content)
+            return httpx2.Response(status_code=200, content=jwk_content)
 
-        client = 
httpx.AsyncClient(transport=httpx.MockTransport(mock_transport))
+        client = 
httpx2.AsyncClient(transport=httpx2.MockTransport(mock_transport))
         current = 1_000_000.0
 
         def mock_monotonic():
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_sources.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_sources.py
index 6c07e4d3892..a170443f780 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_sources.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_sources.py
@@ -22,7 +22,7 @@ from unittest import mock
 
 import pendulum
 import pytest
-from httpx import Response
+from httpx2 import Response
 from sqlalchemy import select
 
 from airflow.api_fastapi.core_api.routes.public.dag_sources import 
REDACTED_SOURCE
diff --git 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
index aeda1196886..c05fd5808f1 100644
--- 
a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
+++ 
b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
@@ -21,7 +21,6 @@ from typing import TYPE_CHECKING, Any
 
 import pytest
 import time_machine
-from httpx import Client
 from sqlalchemy import select
 from uuid6 import uuid7
 
@@ -187,7 +186,7 @@ def test_upsert_hitl_detail_with_empty_option(
 
 @time_machine.travel(datetime(2025, 7, 3, 0, 0, 0), tick=False)
 @pytest.mark.usefixtures("sample_hitl_detail")
-def test_update_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
+def test_update_hitl_detail(client: TestClient, sample_ti: TaskInstance) -> 
None:
     response = client.patch(
         f"/execution/hitlDetails/{sample_ti.id}",
         json={
@@ -206,7 +205,7 @@ def test_update_hitl_detail(client: Client, sample_ti: 
TaskInstance) -> None:
     }
 
 
-def test_update_hitl_detail_without_option(client: Client, sample_ti: 
TaskInstance) -> None:
+def test_update_hitl_detail_without_option(client: TestClient, sample_ti: 
TaskInstance) -> None:
     response = client.patch(
         f"/execution/hitlDetails/{sample_ti.id}",
         json={
@@ -218,7 +217,7 @@ def test_update_hitl_detail_without_option(client: Client, 
sample_ti: TaskInstan
     assert response.status_code == 422
 
 
-def test_update_hitl_detail_without_ti(client: Client) -> None:
+def test_update_hitl_detail_without_ti(client: TestClient) -> None:
     ti_id = str(uuid7())
     response = client.patch(
         f"/execution/hitlDetails/{ti_id}",
@@ -238,13 +237,13 @@ def test_update_hitl_detail_without_ti(client: Client) -> 
None:
 
 
 @pytest.mark.usefixtures("sample_hitl_detail")
-def test_get_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
+def test_get_hitl_detail(client: TestClient, sample_ti: TaskInstance) -> None:
     response = client.get(f"/execution/hitlDetails/{sample_ti.id}")
     assert response.status_code == 200
     assert response.json() == expected_empty_hitl_detail_response_part
 
 
-def test_get_hitl_detail_without_ti(client: Client) -> None:
+def test_get_hitl_detail_without_ti(client: TestClient) -> None:
     response = client.get(f"/execution/hitlDetails/{uuid7()}")
     assert response.status_code == 404
     assert response.json() == {
diff --git a/uv.lock b/uv.lock
index fd8707b3ccc..c62595f1b00 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2021,7 +2021,6 @@ dependencies = [
     { name = "deprecated", version = "3.0.0", source = { registry = 
"https://pypi.org/simple"; }, marker = "python_full_version >= '3.12'" },
     { name = "dill" },
     { name = "fastapi", extra = ["standard-no-fastapi-cloud-cli"] },
-    { name = "httpx" },
     { name = "httpx2" },
     { name = "importlib-metadata" },
     { name = "isoduration" },
@@ -2158,7 +2157,6 @@ requires-dist = [
     { name = "greenlet", marker = "python_full_version >= '3.14' and extra == 
'async'", specifier = ">=3.3.2" },
     { name = "greenlet", marker = "python_full_version < '3.14' and extra == 
'async'", specifier = ">=3.1.0" },
     { name = "gunicorn", marker = "extra == 'gunicorn'", specifier = 
">=23.0.0,!=25.1.0" },
-    { name = "httpx", specifier = ">=0.25.0" },
     { name = "httpx2", specifier = ">=2.0.0" },
     { name = "importlib-metadata", marker = "python_full_version < '3.12'", 
specifier = ">=6.5" },
     { name = "importlib-metadata", marker = "python_full_version >= '3.12'", 
specifier = ">=7.0" },

Reply via email to