Skip to content

feat(core): implement PEP 0810 explicit lazy imports in google-cloud-core - #18051

Closed
hebaalazzeh wants to merge 12 commits into
mainfrom
feat/lazy-imports-google-cloud-core
Closed

feat(core): implement PEP 0810 explicit lazy imports in google-cloud-core#18051
hebaalazzeh wants to merge 12 commits into
mainfrom
feat/lazy-imports-google-cloud-core

Conversation

@hebaalazzeh

Copy link
Copy Markdown
Contributor

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

…-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
@hebaalazzeh hebaalazzeh self-assigned this Aug 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +24 to +26
_has_google_auth_api_key = (
importlib.util.find_spec("google.auth.api_key") is not None
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using importlib.util.find_spec("google.auth.api_key") at the module level is unsafe and defeats the purpose of lazy imports:

  1. Potential Crash (Correctness): If google or google.auth is not installed or fails to import, find_spec will raise a ModuleNotFoundError immediately at module load time. Since this is not wrapped in a try...except block, it will crash the import of google.cloud.client, breaking backwards compatibility.
  2. 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 package google.auth. This completely defeats the lazy import of google.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.

Suggested change
_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
  1. Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.

Comment on lines +46 to +47
if _has_google_auth_api_key:
__lazy_modules__.add("google.auth.api_key")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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
  1. Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.

Comment on lines +46 to +52
if _has_grpc:
__lazy_modules__.update(
{
"grpc",
"google.auth.transport.grpc",
}
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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:

  1. Broken Graceful Fallback: If google.auth.transport.grpc fails to import, the ImportError will not be caught by the try...except block. Instead, the exception will be raised later at runtime when grpc or the transport is first accessed, causing unexpected crashes instead of gracefully falling back to grpc = None.
  2. Redundant Try-Except: The try...except block 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
  1. Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.

Comment on lines +39 to +40
if _has_grpc:
__lazy_modules__.update({"grpc", "grpc._channel"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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:

  1. Broken Fallback: If _Rendezvous cannot be imported from grpc._channel, the ImportError will not be caught by the try...except block. Instead, it will be raised at runtime when GrpcRendezvous is first accessed, bypassing the fallback to None and crashing the application.
  2. Redundant Try-Except: The try...except block 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
  1. Do not replace historical graceful fallback behaviors with exceptions if doing so would introduce breaking changes for downstream users and violate backwards compatibility.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant