Skip to content

Commit d0ca3c1

Browse files
committed
fix: resolve lazy load issue by dynamic base class resolution using metaclass
1 parent 8454b17 commit d0ca3c1

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

packages/google-auth/google/auth/_helpers.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,35 @@ def response_log(logger: logging.Logger, response: Any) -> None:
534534
if is_logging_enabled(logger):
535535
json_response = _parse_response(response)
536536
_response_log_base(logger, json_response)
537+
538+
539+
class LazyBasesMeta(type):
540+
"""Metaclass that allows dynamic lazy resolution of class bases.
541+
542+
This metaclass avoids eager loading of heavy dependencies during import
543+
time by allowing classes to inherit from a dummy class initially.
544+
The real base classes are dynamically resolved and swapped in when the
545+
class is first instantiated, subclassed, or has its attributes inspected.
546+
"""
547+
548+
def __call__(cls, *args, **kwargs):
549+
type(cls)._resolve_bases(cls)
550+
return super(LazyBasesMeta, cls).__call__(*args, **kwargs)
551+
552+
def __getattribute__(cls, name):
553+
type(cls)._resolve_bases(cls)
554+
return super(LazyBasesMeta, cls).__getattribute__(name)
555+
556+
def _resolve_bases(cls):
557+
pass
558+
559+
560+
class HeapDummy(object):
561+
"""A heap-allocated dummy class.
562+
563+
This class serves as a safe initial base class for classes using LazyBasesMeta.
564+
Inheriting from HeapDummy instead of built-in `object` ensures that Python's
565+
memory layout check passes when we swap bases to another heap-allocated class.
566+
"""
567+
pass
568+

packages/google-auth/google/auth/transport/requests.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@
5353
import google.auth.transport._mtls_helper
5454
from google.oauth2 import service_account
5555

56+
57+
class _LazyBasesMeta(_helpers.LazyBasesMeta):
58+
def _resolve_bases(cls):
59+
current_bases = type.__getattribute__(cls, "__bases__")
60+
if current_bases == (_helpers.HeapDummy,):
61+
cls_name = type.__getattribute__(cls, "__name__")
62+
if cls_name in ("_MutualTlsAdapter", "_MutualTlsOffloadAdapter"):
63+
cls.__bases__ = (requests.adapters.HTTPAdapter,)
64+
elif cls_name == "AuthorizedSession":
65+
cls.__bases__ = (requests.Session,)
66+
67+
5668
_LOGGER = logging.getLogger(__name__)
5769

5870
_DEFAULT_TIMEOUT = 120 # in seconds
@@ -208,7 +220,7 @@ def __call__(
208220
raise new_exc from caught_exc
209221

210222

211-
class _MutualTlsAdapter(requests.adapters.HTTPAdapter):
223+
class _MutualTlsAdapter(_helpers.HeapDummy, metaclass=_LazyBasesMeta):
212224
"""
213225
A TransportAdapter that enables mutual TLS.
214226
@@ -274,7 +286,7 @@ def proxy_manager_for(self, *args, **kwargs):
274286
return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs)
275287

276288

277-
class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter):
289+
class _MutualTlsOffloadAdapter(_helpers.HeapDummy, metaclass=_LazyBasesMeta):
278290
"""
279291
A TransportAdapter that enables mutual TLS and offloads the client side
280292
signing operation to the signing library.
@@ -324,7 +336,7 @@ def proxy_manager_for(self, *args, **kwargs):
324336
return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs)
325337

326338

327-
class AuthorizedSession(requests.Session):
339+
class AuthorizedSession(_helpers.HeapDummy, metaclass=_LazyBasesMeta):
328340
"""A Requests Session class with credentials.
329341
330342
This class is used to perform requests to API endpoints that require

packages/google-auth/google/auth/transport/urllib3.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,38 @@
5151
) from caught_exc
5252

5353

54+
from typing import Set
55+
5456
from google.auth import _helpers
5557
from google.auth import exceptions
5658
from google.auth import transport
5759
from google.auth.transport import _mtls_helper
5860
from google.oauth2 import service_account
5961

60-
if version.parse(urllib3.__version__) >= version.parse("2.0.0"): # pragma: NO COVER
61-
RequestMethods = urllib3._request_methods.RequestMethods # type: ignore
62-
else: # pragma: NO COVER
63-
RequestMethods = urllib3.request.RequestMethods # type: ignore
62+
# PEP 0810: Explicit Lazy Imports
63+
# Python 3.15+ natively intercepts and defers these imports.
64+
# Developers can disable this behavior and force eager imports.
65+
# For more information, see:
66+
# https://docs.python.org/3.15/library/sys.html#sys.set_lazy_imports_filter
67+
# Older Python versions safely ignore this variable.
68+
__lazy_modules__: Set[str] = {
69+
"urllib3",
70+
"urllib3.exceptions",
71+
"certifi",
72+
"packaging",
73+
"packaging.version",
74+
}
75+
76+
77+
class _LazyBasesMeta(_helpers.LazyBasesMeta):
78+
def _resolve_bases(cls):
79+
current_bases = type.__getattribute__(cls, "__bases__")
80+
if current_bases == (_helpers.HeapDummy,):
81+
if version.parse(urllib3.__version__) >= version.parse("2.0.0"):
82+
request_methods_class = urllib3._request_methods.RequestMethods # type: ignore
83+
else:
84+
request_methods_class = urllib3.request.RequestMethods # type: ignore
85+
cls.__bases__ = (request_methods_class,)
6486

6587

6688
_LOGGER = logging.getLogger(__name__)
@@ -205,7 +227,7 @@ def _make_mutual_tls_http(cert, key):
205227
return http
206228

207229

208-
class AuthorizedHttp(RequestMethods): # type: ignore
230+
class AuthorizedHttp(_helpers.HeapDummy, metaclass=_LazyBasesMeta):
209231
"""A urllib3 HTTP class with credentials.
210232
211233
This class is used to perform requests to API endpoints that require

0 commit comments

Comments
 (0)