feat: lazy imports google auth#17679
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a __lazy_modules__ set in packages/google-auth/google/auth/transport/__init__.py to support lazy loading of transport modules. The review feedback correctly identifies an incorrect module name (_aiohttp_requests instead of aiohttp_requests) that would prevent lazy importing in Python 3.15+.
parthea
left a comment
There was a problem hiding this comment.
LGTM but holding off on formal approval until we have tests. We should assert that on Python 3.15+ the modules are indeed absent from sys.modules until accessed, and that on pre-3.15 environments they fallback cleanly to standard eager imports
import sys
import pytest
# List of modules we expect to be lazy
LAZY_MODULES = [
"google.auth.transport.requests",
"google.auth.transport.urllib3",
"google.auth.transport.grpc",
]
def clean_sys_modules():
"""Helper to ensure we start with a clean slate for import testing."""
for mod in LAZY_MODULES:
sys.modules.pop(mod, None)
@pytest.mark.skipif(sys.version_info < (3, 15), reason="PEP 810 requires Python 3.15+")
def test_lazy_imports_on_python_315():
clean_sys_modules()
# 1. Import the transport package
import google.auth.transport
# 2. Assert that none of the lazy modules have been eagerly loaded into sys.modules
for mod in LAZY_MODULES:
assert mod not in sys.modules
# 3. Access an attribute to trigger reification
from google.auth.transport import requests
_ = requests.__name__ # Trigger first-use reification
# 4. Assert that the module has now been reified and loaded
assert "google.auth.transport.requests" in sys.modules
@pytest.mark.skipif(sys.version_info >= (3, 15), reason="Testing fallback behavior on < 3.15")
def test_fallback_eager_imports_pre_315():
clean_sys_modules()
# On older Python, __lazy_modules__ is safely ignored, meaning they should eager-load
import google.auth.transport
for mod in LAZY_MODULES:
assert mod in sys.modules
|
I see the tests are failing. We also need to add |
|
Disregard my last comment.
We should only add From https://docs.python.org/3.15/reference/simple_stmts.html#lazy-imports
|
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
6e6fe6e to
eef11dc
Compare
…it__ to fully address parthea's feedback
|
addressed all comments |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces lazy import support for Python 3.15+ by defining __lazy_modules__ in several transport modules (grpc.py, requests.py, and urllib3.py) and adding corresponding integration tests. However, the feedback highlights that the lazy loading mechanism is currently defeated in grpc.py due to inheriting from grpc.AuthMetadataPlugin at the module level, and in urllib3.py due to a module-level version check on urllib3.__version__.
parthea
left a comment
There was a problem hiding this comment.
Please can you address the feedback from GCA?
|
\gemini |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces lazy loading support for heavy transport modules (grpc, requests, and urllib3) to optimize import times, particularly targeting Python 3.15+ (PEP 810), with fallback behavior for older versions. Key changes include defining __lazy_modules__, decoupling classes from eager parent imports, and adding a test suite to verify the lazy loading behavior. Feedback focuses on optimizing a performance bottleneck in urllib3.py's dynamic attribute resolution, addressing a bypassed ImportError check in requests.py due to deferred imports, and adding explanatory comments in grpc.py to prevent accidental regressions.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for PEP 0810 (Explicit Lazy Imports) in Python 3.15+ across the grpc, requests, and urllib3 transports. This is achieved by defining lazy_modules, removing eager base class inheritance, and lazily resolving attributes (such as RequestMethods in AuthorizedHttp). A test suite is also added to verify this behavior. The reviewer suggests improving the getattr implementation in AuthorizedHttp by dynamically checking for attributes on the underlying RequestMethods class rather than hardcoding specific method names, and lazily importing version to avoid eagerly loading the packaging module.
| class AuthorizedHttp(object): | ||
| """A urllib3 HTTP class with credentials. |
There was a problem hiding this comment.
Changing AuthorizedHttp from subclassing urllib3.request.RequestMethods (or urllib3._request_methods.RequestMethods) to object is a breaking change across all Python versions (3.9–3.15+). Any downstream code that checks isinstance(authed_http, urllib3.request.RequestMethods) (or urllib3.PoolManager) will now return False.
| def __getattr__(self, name): | ||
| if name in ("request", "request_encode_url", "request_encode_body"): | ||
| import types | ||
|
|
||
| if AuthorizedHttp._request_methods_class is None: | ||
| if version.parse(urllib3.__version__) >= version.parse("2.0.0"): | ||
| AuthorizedHttp._request_methods_class = ( | ||
| urllib3._request_methods.RequestMethods | ||
| ) | ||
| else: | ||
| AuthorizedHttp._request_methods_class = ( | ||
| urllib3.request.RequestMethods | ||
| ) | ||
|
|
||
| method = getattr(AuthorizedHttp._request_methods_class, name) | ||
| bound_method = types.MethodType(method, self) | ||
| setattr(self, name, bound_method) |
There was a problem hiding this comment.
If we revert class AuthorizedHttp(object): back to inheriting from RequestMethods to avoid the breaking isinstance() change, this __getattr__ method and the _request_methods_class lookup won't be needed and should be removed to keep the implementation simple and safe.
| # Inheriting from object instead of grpc.AuthMetadataPlugin is intentional. | ||
| # This prevents eagerly loading grpc at module import time, preserving lazy loading support. | ||
| class AuthMetadataPlugin(object): |
There was a problem hiding this comment.
Similar to AuthorizedHttp in urllib3.py, changing AuthMetadataPlugin from subclassing grpc.AuthMetadataPlugin to object introduces a breaking change across all Python versions for any downstream code checking isinstance(plugin, grpc.AuthMetadataPlugin).
| __lazy_modules__: Set[str] = {"requests", "requests.adapters", "requests.exceptions"} | ||
|
|
There was a problem hiding this comment.
should this be declared before the import statements for it to actually work?
| :class:`requests.exceptions.Timeout`. | ||
| """ | ||
|
|
||
| def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): |
There was a problem hiding this comment.
Default parameter expressions in function signatures (requests.exceptions.Timeout) are evaluated when the function is defined at module import time. This immediately reifies requests.exceptions (and requests) when requests.py is imported.
| @@ -0,0 +1,77 @@ | |||
| # Copyright 2026 Google LLC | |||
There was a problem hiding this comment.
We should remove test_lazy_imports.py entirely rather than maintaining subprocess-based tests:
- Testing Python's Internal implementation:
__lazy_modules__is a standard declarative hint for PEP 0810. We don't need to maintain custom subprocess unit tests to verify Python 3.15's built-in module reification behavior.
Description
This PR initiates the rollout of our PEP 0810 lazy-loading architecture to
google-auth, starting with the transport module.By applying Python 3.15's native explicit lazy imports directly inside our transport wrappers (
requests.py,urllib3.py, andgrpc.py), we defer the eager parsing and compilation of the heavy third-party networking libraries (requests,urllib3, andgrpc). This acts as the first step in addressing the high initialization latency and peak memory footprints we're seeing on Serverless cold-starts.Older Python runtimes (3.14 and below) safely ignore the
__lazy_modules__set and fall back to standard eager execution, meaning this introduces zero backwards compatibility risk.Related Documents
Note: This PR was kept intentionally small for review speed. We will be executing an iterative rollout, extending this pattern to other heavy modules like
oauth2andcompute_enginein fast follow-up PRs once this structural pattern is approved.