diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 7482038589a3..d2165df0ab39 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,6 +17,7 @@ from __future__ import absolute_import import logging +from typing import Set from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -32,6 +33,8 @@ _LOGGER = logging.getLogger(__name__) +__lazy_modules__: Set[str] = {"grpc"} + class AuthMetadataPlugin(grpc.AuthMetadataPlugin): """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index eef0384652a6..483f3a0b1fe9 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -21,7 +21,7 @@ import logging import numbers import time -from typing import Optional +from typing import Optional, Set try: import requests @@ -42,6 +42,8 @@ import google.auth.transport._mtls_helper from google.oauth2 import service_account +__lazy_modules__: Set[str] = {"requests", "requests.adapters", "requests.exceptions"} + _LOGGER = logging.getLogger(__name__) _DEFAULT_TIMEOUT = 120 # in seconds diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 1b0fac7c342f..3f56029794d9 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -16,8 +16,10 @@ from __future__ import absolute_import + import http.client as http_client import logging +from typing import Set import warnings # Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle @@ -56,6 +58,14 @@ from google.auth.transport import _mtls_helper from google.oauth2 import service_account +__lazy_modules__: Set[str] = { + "urllib3", + "urllib3.exceptions", + "certifi", + "packaging", + "packaging.version", +} + if version.parse(urllib3.__version__) >= version.parse("2.0.0"): # pragma: NO COVER RequestMethods = urllib3._request_methods.RequestMethods # type: ignore else: # pragma: NO COVER diff --git a/packages/google-auth/tests/transport/test_lazy_imports.py b/packages/google-auth/tests/transport/test_lazy_imports.py new file mode 100644 index 000000000000..bd472440e61e --- /dev/null +++ b/packages/google-auth/tests/transport/test_lazy_imports.py @@ -0,0 +1,77 @@ +# 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 subprocess +import sys + +import pytest + +SCRIPT_PYTHON_315 = """ +import sys +import google.auth.transport.requests +import google.auth.transport.urllib3 +import google.auth.transport.grpc + +# The heavy underlying modules should NOT be loaded yet +HEAVY_MODULES = ["requests", "urllib3", "grpc"] + +for mod in HEAVY_MODULES: + if mod in sys.modules: + print(f"FAILED: {mod} was eagerly loaded into sys.modules") + sys.exit(1) + +# Trigger reification +import requests +_ = requests.__name__ + +if "requests" not in sys.modules: + print("FAILED: requests was not lazily loaded upon access") + sys.exit(2) + +sys.exit(0) +""" + +SCRIPT_PRE_315 = """ +import sys +import google.auth.transport.requests +import google.auth.transport.urllib3 +import google.auth.transport.grpc + +HEAVY_MODULES = ["requests", "urllib3", "grpc"] + +for mod in HEAVY_MODULES: + if mod not in sys.modules: + print(f"FAILED: {mod} was not eagerly loaded into sys.modules") + sys.exit(1) + +sys.exit(0) +""" + + +@pytest.mark.skipif(sys.version_info < (3, 15), reason="PEP 810 requires Python 3.15+") +def test_lazy_imports_on_python_315(): + result = subprocess.run( + [sys.executable, "-c", SCRIPT_PYTHON_315], capture_output=True, text=True + ) + assert result.returncode == 0, f"Subprocess failed: {result.stdout}" + + +@pytest.mark.skipif( + sys.version_info >= (3, 15), reason="Testing fallback behavior on < 3.15" +) +def test_fallback_eager_imports_pre_315(): + result = subprocess.run( + [sys.executable, "-c", SCRIPT_PRE_315], capture_output=True, text=True + ) + assert result.returncode == 0, f"Subprocess failed: {result.stdout}"