diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a9fe43da8468..1dfb9ee734ff 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,11 @@ requests_extra_require = ["requests >= 2.30.0, < 3.0.0"] -aiohttp_extra_require = ["aiohttp >= 3.8.0, < 4.0.0", *requests_extra_require] +aiohttp_extra_require = [ + "aiohttp >= 3.8.0, < 4.0.0; python_version < '3.14'", + "aiohttp >= 3.9.0, < 4.0.0; python_version >= '3.14'", + *requests_extra_require, +] pyjwt_extra_require = ["pyjwt>=2.0"] diff --git a/packages/google-auth/testing/constraints-3.14.txt b/packages/google-auth/testing/constraints-3.14.txt index abdcf9de1e42..64212e65e4df 100644 --- a/packages/google-auth/testing/constraints-3.14.txt +++ b/packages/google-auth/testing/constraints-3.14.txt @@ -1,4 +1,10 @@ -# Lower-bound constraints for Python 3.14 core dependencies. -# Used during CI unit tests to verify minimum supported package versions. +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List *all* library dependencies and extras in this file. +# Pin the version to the lower bound. +# +# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", +# Then this file should have foo==1.14.0 pyasn1-modules==0.2.1 cryptography==41.0.5 +aiohttp==3.9.0 diff --git a/packages/google-auth/tests/transport/aio/conftest.py b/packages/google-auth/tests/transport/aio/conftest.py index 6b415af5aaf0..126819af6a45 100644 --- a/packages/google-auth/tests/transport/aio/conftest.py +++ b/packages/google-auth/tests/transport/aio/conftest.py @@ -12,35 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import inspect -from unittest.mock import Mock +from tests.transport.aioresponses_compat import patch_aioresponses -import aiohttp -from aioresponses.core import RequestMatch # type: ignore - - -class _CompatClientResponse(aiohttp.ClientResponse): - """ClientResponse subclass for aioresponses compatibility across all aiohttp versions.""" - - def __init__(self, *args, **kwargs): - writer = kwargs.pop("writer", None) - stream_writer = kwargs.pop("stream_writer", None) - writer_obj = stream_writer or writer or Mock() - sig = inspect.signature(super().__init__) - if "stream_writer" in sig.parameters: - kwargs["stream_writer"] = writer_obj - if "writer" in sig.parameters: - kwargs["writer"] = writer_obj - super().__init__(*args, **kwargs) - - -_orig_request_match_init = RequestMatch.__init__ - - -def _request_match_init(self, *args, **kwargs): - if kwargs.get("response_class") is None: - kwargs["response_class"] = _CompatClientResponse - _orig_request_match_init(self, *args, **kwargs) - - -RequestMatch.__init__ = _request_match_init +patch_aioresponses() diff --git a/packages/google-auth/tests/transport/aioresponses_compat.py b/packages/google-auth/tests/transport/aioresponses_compat.py new file mode 100644 index 000000000000..84493d7ffe3d --- /dev/null +++ b/packages/google-auth/tests/transport/aioresponses_compat.py @@ -0,0 +1,66 @@ +# Copyright 2026 Google LLC +# +# Licensed 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. + +import inspect + +import aiohttp +from aioresponses.core import RequestMatch # type: ignore + + +class _DummyWriter: + output_size = 0 + + def done(self): + return True + + def add_done_callback(self, fn, *args, **kwargs): + pass + + def remove_done_callback(self, fn): + pass + + def __await__(self): + if False: + yield + return None + + +class _CompatClientResponse(aiohttp.ClientResponse): + """ClientResponse subclass for aioresponses compatibility across all aiohttp versions.""" + + def __init__(self, *args, **kwargs): + writer = kwargs.pop("writer", None) + stream_writer = kwargs.pop("stream_writer", None) + writer_obj = stream_writer or writer or _DummyWriter() + sig = inspect.signature(super().__init__) + if "stream_writer" in sig.parameters: + kwargs["stream_writer"] = writer_obj + if "writer" in sig.parameters: + kwargs["writer"] = writer_obj + super().__init__(*args, **kwargs) + + +def patch_aioresponses(): + """Patch RequestMatch.__init__ to use _CompatClientResponse by default.""" + if getattr(RequestMatch, "_is_compat_patched", False): + return + orig_init = RequestMatch.__init__ + + def _request_match_init(self, *args, **kwargs): + if kwargs.get("response_class") is None: + kwargs["response_class"] = _CompatClientResponse + orig_init(self, *args, **kwargs) + + RequestMatch.__init__ = _request_match_init + RequestMatch._is_compat_patched = True diff --git a/packages/google-auth/tests_async/transport/conftest.py b/packages/google-auth/tests_async/transport/conftest.py index 6b415af5aaf0..126819af6a45 100644 --- a/packages/google-auth/tests_async/transport/conftest.py +++ b/packages/google-auth/tests_async/transport/conftest.py @@ -12,35 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import inspect -from unittest.mock import Mock +from tests.transport.aioresponses_compat import patch_aioresponses -import aiohttp -from aioresponses.core import RequestMatch # type: ignore - - -class _CompatClientResponse(aiohttp.ClientResponse): - """ClientResponse subclass for aioresponses compatibility across all aiohttp versions.""" - - def __init__(self, *args, **kwargs): - writer = kwargs.pop("writer", None) - stream_writer = kwargs.pop("stream_writer", None) - writer_obj = stream_writer or writer or Mock() - sig = inspect.signature(super().__init__) - if "stream_writer" in sig.parameters: - kwargs["stream_writer"] = writer_obj - if "writer" in sig.parameters: - kwargs["writer"] = writer_obj - super().__init__(*args, **kwargs) - - -_orig_request_match_init = RequestMatch.__init__ - - -def _request_match_init(self, *args, **kwargs): - if kwargs.get("response_class") is None: - kwargs["response_class"] = _CompatClientResponse - _orig_request_match_init(self, *args, **kwargs) - - -RequestMatch.__init__ = _request_match_init +patch_aioresponses()