feat(core): implement PEP 0810 explicit lazy imports in google-cloud-core - #18051
feat(core): implement PEP 0810 explicit lazy imports in google-cloud-core#18051hebaalazzeh wants to merge 12 commits into
Conversation
…-cert-source # Conflicts: # packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 # packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 # packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_compat.py.j2 # packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py # packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_compat.py # packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py # packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_compat.py # packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py # packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_compat.py # packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py # packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_compat.py # packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py # packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py # packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_compat.py # packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py # packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_compat.py # packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py # packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_compat.py # packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py # packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py # packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_compat.py
…core This PR implements PEP 0810 explicit lazy imports in google-cloud-core. On Python 3.15+, this defers loading of heavy inner modules and third-party dependencies (grpcio, cryptography, requests, and protobuf descriptor pools) to reduce serverless cold starts and memory footprints. On Python 3.14 and below, this falls back safely to eager execution with zero backwards-compatibility risk. ### Related Links - GAPIC Implementation PR: #17591 - google-api-core gapic_v1 PR: #17673 - google-api-core operations_v1 PR: #17724 - google-auth transport PR: #17679
There was a problem hiding this comment.
Code Review
This pull request refactors the client certificate source helper in the gapic-generator into a shared compat module and introduces PEP 0810 explicit lazy imports (lazy_modules) across several google-cloud-core modules. The review feedback highlights critical issues where adding conditionally imported modules (such as grpc, google.auth.api_key, and grpc._channel) to lazy_modules breaks historical graceful fallback behaviors; because deferred imports bypass the try-except blocks at module load time, they will cause unexpected runtime crashes. Additionally, using importlib.util.find_spec at the module level without error handling is unsafe and should be wrapped in a try-except block to prevent immediate import failures.
| _has_google_auth_api_key = ( | ||
| importlib.util.find_spec("google.auth.api_key") is not None | ||
| ) |
There was a problem hiding this comment.
Using importlib.util.find_spec("google.auth.api_key") at the module level is unsafe and defeats the purpose of lazy imports:
- Potential Crash (Correctness): If
googleorgoogle.authis not installed or fails to import,find_specwill raise aModuleNotFoundErrorimmediately at module load time. Since this is not wrapped in atry...exceptblock, it will crash the import ofgoogle.cloud.client, breaking backwards compatibility. - Defeats Lazy Imports (Efficiency): To resolve the spec of a submodule like
google.auth.api_key, Python's import system must eagerly import the parent packagegoogle.auth. This completely defeats the lazy import ofgoogle.auth(which is listed in__lazy_modules__to be deferred).
Recommendation:
Wrap the find_spec call in a try...except block to prevent crashes, and consider removing google.auth.api_key from __lazy_modules__ to avoid eager loading of google.auth.
| _has_google_auth_api_key = ( | |
| importlib.util.find_spec("google.auth.api_key") is not None | |
| ) | |
| try: | |
| _has_google_auth_api_key = ( | |
| importlib.util.find_spec("google.auth.api_key") is not None | |
| ) | |
| except ModuleNotFoundError: | |
| _has_google_auth_api_key = False |
References
- Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.
| if _has_google_auth_api_key: | ||
| __lazy_modules__.add("google.auth.api_key") |
There was a problem hiding this comment.
If google.auth.api_key is added to __lazy_modules__, any subsequent try...except ImportError block wrapping its import will be rendered useless because the import is deferred and won't raise an exception during module load. To preserve the graceful fallback behavior, it should be excluded from lazy imports.
# google.auth.api_key is excluded from lazy imports to preserve the try-except fallback behavior.References
- Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.
| if _has_grpc: | ||
| __lazy_modules__.update( | ||
| { | ||
| "grpc", | ||
| "google.auth.transport.grpc", | ||
| } | ||
| ) |
There was a problem hiding this comment.
Under PEP 810 (or any lazy import mechanism), if grpc and google.auth.transport.grpc are added to __lazy_modules__, the import statements inside the subsequent try...except ImportError block are deferred and will not actually execute during module load.
This introduces a major correctness and backwards-compatibility issue:
- Broken Graceful Fallback: If
google.auth.transport.grpcfails to import, theImportErrorwill not be caught by thetry...exceptblock. Instead, the exception will be raised later at runtime whengrpcor the transport is first accessed, causing unexpected crashes instead of gracefully falling back togrpc = None. - Redundant Try-Except: The
try...exceptblock is rendered completely useless because the deferred imports won't raise exceptions during its execution.
Recommendation:
Do not include modules that require conditional import checks and fallbacks (like grpc and google.auth.transport.grpc) in __lazy_modules__.
# grpc and its transport are excluded from lazy imports to preserve the try-except fallback behavior.References
- Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.
| if _has_grpc: | ||
| __lazy_modules__.update({"grpc", "grpc._channel"}) |
There was a problem hiding this comment.
Since grpc._channel is added to __lazy_modules__, the import from grpc._channel import _Rendezvous is deferred and will not execute during module load.
This breaks the graceful fallback behavior:
- Broken Fallback: If
_Rendezvouscannot be imported fromgrpc._channel, theImportErrorwill not be caught by thetry...exceptblock. Instead, it will be raised at runtime whenGrpcRendezvousis first accessed, bypassing the fallback toNoneand crashing the application. - Redundant Try-Except: The
try...exceptblock is useless because the deferred import won't raise any exception during module load.
Recommendation:
Do not include grpc._channel in __lazy_modules__ if it relies on a try...except block for fallback.
# grpc and grpc._channel are excluded from lazy imports to preserve the try-except fallback behavior.References
- Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.
This PR implements PEP 0810 explicit lazy imports in google-cloud-core. On Python 3.15+, this defers loading of heavy inner modules and third-party dependencies (grpcio, cryptography, requests, and protobuf descriptor pools) to reduce serverless cold starts and memory footprints. On Python 3.14 and below, this falls back safely to eager execution with zero backwards-compatibility risk.
Related Links
Design doc: go/sdk:python-lazy-loading