Skip to content

Commit b2ec761

Browse files
authored
fix(google-auth): add aiohttp bound for Python 3.14 (#17654)
Adds a lower-bound requirement of aiohttp >= 3.9.0 for Python 3.14 in setup.py and updates async test response mocks for Python 3.14 compatibility.
1 parent 4087828 commit b2ec761

5 files changed

Lines changed: 83 additions & 65 deletions

File tree

packages/google-auth/setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030

3131
requests_extra_require = ["requests >= 2.30.0, < 3.0.0"]
3232

33-
aiohttp_extra_require = ["aiohttp >= 3.8.0, < 4.0.0", *requests_extra_require]
33+
aiohttp_extra_require = [
34+
"aiohttp >= 3.8.0, < 4.0.0; python_version < '3.14'",
35+
"aiohttp >= 3.9.0, < 4.0.0; python_version >= '3.14'",
36+
*requests_extra_require,
37+
]
3438

3539
pyjwt_extra_require = ["pyjwt>=2.0"]
3640

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# Lower-bound constraints for Python 3.14 core dependencies.
2-
# Used during CI unit tests to verify minimum supported package versions.
1+
# This constraints file is used to check that lower bounds
2+
# are correct in setup.py
3+
# List *all* library dependencies and extras in this file.
4+
# Pin the version to the lower bound.
5+
#
6+
# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
7+
# Then this file should have foo==1.14.0
38
pyasn1-modules==0.2.1
49
cryptography==41.0.5
10+
aiohttp==3.9.0

packages/google-auth/tests/transport/aio/conftest.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import inspect
16-
from unittest.mock import Mock
15+
from tests.transport.aioresponses_compat import patch_aioresponses
1716

18-
import aiohttp
19-
from aioresponses.core import RequestMatch # type: ignore
20-
21-
22-
class _CompatClientResponse(aiohttp.ClientResponse):
23-
"""ClientResponse subclass for aioresponses compatibility across all aiohttp versions."""
24-
25-
def __init__(self, *args, **kwargs):
26-
writer = kwargs.pop("writer", None)
27-
stream_writer = kwargs.pop("stream_writer", None)
28-
writer_obj = stream_writer or writer or Mock()
29-
sig = inspect.signature(super().__init__)
30-
if "stream_writer" in sig.parameters:
31-
kwargs["stream_writer"] = writer_obj
32-
if "writer" in sig.parameters:
33-
kwargs["writer"] = writer_obj
34-
super().__init__(*args, **kwargs)
35-
36-
37-
_orig_request_match_init = RequestMatch.__init__
38-
39-
40-
def _request_match_init(self, *args, **kwargs):
41-
if kwargs.get("response_class") is None:
42-
kwargs["response_class"] = _CompatClientResponse
43-
_orig_request_match_init(self, *args, **kwargs)
44-
45-
46-
RequestMatch.__init__ = _request_match_init
17+
patch_aioresponses()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import inspect
16+
17+
import aiohttp
18+
from aioresponses.core import RequestMatch # type: ignore
19+
20+
21+
class _DummyWriter:
22+
output_size = 0
23+
24+
def done(self):
25+
return True
26+
27+
def add_done_callback(self, fn, *args, **kwargs):
28+
pass
29+
30+
def remove_done_callback(self, fn):
31+
pass
32+
33+
def __await__(self):
34+
if False:
35+
yield
36+
return None
37+
38+
39+
class _CompatClientResponse(aiohttp.ClientResponse):
40+
"""ClientResponse subclass for aioresponses compatibility across all aiohttp versions."""
41+
42+
def __init__(self, *args, **kwargs):
43+
writer = kwargs.pop("writer", None)
44+
stream_writer = kwargs.pop("stream_writer", None)
45+
writer_obj = stream_writer or writer or _DummyWriter()
46+
sig = inspect.signature(super().__init__)
47+
if "stream_writer" in sig.parameters:
48+
kwargs["stream_writer"] = writer_obj
49+
if "writer" in sig.parameters:
50+
kwargs["writer"] = writer_obj
51+
super().__init__(*args, **kwargs)
52+
53+
54+
def patch_aioresponses():
55+
"""Patch RequestMatch.__init__ to use _CompatClientResponse by default."""
56+
if getattr(RequestMatch, "_is_compat_patched", False):
57+
return
58+
orig_init = RequestMatch.__init__
59+
60+
def _request_match_init(self, *args, **kwargs):
61+
if kwargs.get("response_class") is None:
62+
kwargs["response_class"] = _CompatClientResponse
63+
orig_init(self, *args, **kwargs)
64+
65+
RequestMatch.__init__ = _request_match_init
66+
RequestMatch._is_compat_patched = True

packages/google-auth/tests_async/transport/conftest.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import inspect
16-
from unittest.mock import Mock
15+
from tests.transport.aioresponses_compat import patch_aioresponses
1716

18-
import aiohttp
19-
from aioresponses.core import RequestMatch # type: ignore
20-
21-
22-
class _CompatClientResponse(aiohttp.ClientResponse):
23-
"""ClientResponse subclass for aioresponses compatibility across all aiohttp versions."""
24-
25-
def __init__(self, *args, **kwargs):
26-
writer = kwargs.pop("writer", None)
27-
stream_writer = kwargs.pop("stream_writer", None)
28-
writer_obj = stream_writer or writer or Mock()
29-
sig = inspect.signature(super().__init__)
30-
if "stream_writer" in sig.parameters:
31-
kwargs["stream_writer"] = writer_obj
32-
if "writer" in sig.parameters:
33-
kwargs["writer"] = writer_obj
34-
super().__init__(*args, **kwargs)
35-
36-
37-
_orig_request_match_init = RequestMatch.__init__
38-
39-
40-
def _request_match_init(self, *args, **kwargs):
41-
if kwargs.get("response_class") is None:
42-
kwargs["response_class"] = _CompatClientResponse
43-
_orig_request_match_init(self, *args, **kwargs)
44-
45-
46-
RequestMatch.__init__ = _request_match_init
17+
patch_aioresponses()

0 commit comments

Comments
 (0)