Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions packages/google-api-core/google/api_core/gapic_v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,51 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from google.api_core.gapic_v1 import client_info
from google.api_core.gapic_v1 import config
from google.api_core.gapic_v1 import config_async
from google.api_core.gapic_v1 import method
from google.api_core.gapic_v1 import method_async
from google.api_core.gapic_v1 import routing_header
import importlib.util
from typing import Set

__all__ = [
"client_info",
"config",
"config_async",
"method",
"method_async",
"routing_header",
]
_has_grpc = importlib.util.find_spec("grpc") is not None

# PEP 0810: Explicit Lazy Imports
# Python 3.15+ natively intercepts and defers these imports.
# Developers can disable this behavior and force eager imports.
# For more information, see:
# https://docs.python.org/3.15/library/sys.html#sys.set_lazy_imports_filter
# Older Python versions safely ignore this variable.
__lazy_modules__: Set[str] = {
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.routing_header",
}

if _has_grpc:
__lazy_modules__.update(
{
"google.api_core.gapic_v1.config",
"google.api_core.gapic_v1.config_async",
"google.api_core.gapic_v1.method",
"google.api_core.gapic_v1.method_async",
}
)

from google.api_core.gapic_v1 import client_info # noqa: E402
from google.api_core.gapic_v1 import routing_header # noqa: E402

if _has_grpc:
from google.api_core.gapic_v1 import config # noqa: F401
from google.api_core.gapic_v1 import config_async # noqa: F401
from google.api_core.gapic_v1 import method # noqa: F401
from google.api_core.gapic_v1 import method_async # noqa: F401
Comment on lines +41 to +48

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.

why do we need these # noqa: E402 and # noqa: F401?


__all__ = [
"client_info",
"routing_header",
"config",
"config_async",
"method",
"method_async",
]
else:
__all__ = [
"client_info",
"routing_header",
]
Comment thread
hebaalazzeh marked this conversation as resolved.
Comment on lines +26 to +62

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.

Suggested change
__lazy_modules__: Set[str] = {
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.routing_header",
}
if _has_grpc:
__lazy_modules__.update(
{
"google.api_core.gapic_v1.config",
"google.api_core.gapic_v1.config_async",
"google.api_core.gapic_v1.method",
"google.api_core.gapic_v1.method_async",
}
)
from google.api_core.gapic_v1 import client_info # noqa: E402
from google.api_core.gapic_v1 import routing_header # noqa: E402
if _has_grpc:
from google.api_core.gapic_v1 import config # noqa: F401
from google.api_core.gapic_v1 import config_async # noqa: F401
from google.api_core.gapic_v1 import method # noqa: F401
from google.api_core.gapic_v1 import method_async # noqa: F401
__all__ = [
"client_info",
"routing_header",
"config",
"config_async",
"method",
"method_async",
]
else:
__all__ = [
"client_info",
"routing_header",
]
__lazy_modules__: Set[str] = {
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.routing_header",
}
__all__ = ["client_info", "routing_header"]
if _has_grpc:
__lazy_modules__.update({
"google.api_core.gapic_v1.config",
"google.api_core.gapic_v1.config_async",
"google.api_core.gapic_v1.method",
"google.api_core.gapic_v1.method_async",
})
from google.api_core.gapic_v1 import client_info # noqa: E402
from google.api_core.gapic_v1 import routing_header # noqa: E402
if _has_grpc:
from google.api_core.gapic_v1 import config # noqa: F401
from google.api_core.gapic_v1 import config_async # noqa: F401
from google.api_core.gapic_v1 import method # noqa: F401
from google.api_core.gapic_v1 import method_async # noqa: F401
__all__.extend(["config", "config_async", "method", "method_async"])

97 changes: 97 additions & 0 deletions packages/google-api-core/tests/unit/gapic/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# 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 = """

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.

Is there a better way to test this instead of using this script within a test file? Do we even want to test this?

import sys
import google.api_core.gapic_v1

# The inner gapic_v1 modules should NOT be loaded yet
HEAVY_MODULES = [
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.config",
"google.api_core.gapic_v1.config_async",
"google.api_core.gapic_v1.method",
"google.api_core.gapic_v1.method_async",
"google.api_core.gapic_v1.routing_header",
]

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 google.api_core.gapic_v1.client_info
_ = google.api_core.gapic_v1.client_info.__name__

if "google.api_core.gapic_v1.client_info" not in sys.modules:
print("FAILED: google.api_core.gapic_v1.client_info was not lazily loaded upon access")
sys.exit(2)

sys.exit(0)
"""

SCRIPT_PRE_315 = """
import sys
import google.api_core.gapic_v1

try:
import grpc
has_grpc = True
except ImportError:
has_grpc = False

HEAVY_MODULES = [
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.routing_header",
]

if has_grpc:
HEAVY_MODULES.extend([
"google.api_core.gapic_v1.config",
"google.api_core.gapic_v1.config_async",
"google.api_core.gapic_v1.method",
"google.api_core.gapic_v1.method_async",
])

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}"
Comment thread
hebaalazzeh marked this conversation as resolved.
Loading