google-api-core 2.34.0__tar.gz → 2.36.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {google_api_core-2.34.0/google_api_core.egg-info → google_api_core-2.36.0}/PKG-INFO +6 -1
- google_api_core-2.36.0/google/api_core/_observability.py +135 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/_python_package_support.py +27 -3
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/client_options.py +8 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/exceptions.py +2 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/grpc_helpers.py +68 -1
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/path_template.py +57 -4
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/rest_helpers.py +2 -1
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/version.py +1 -1
- {google_api_core-2.34.0 → google_api_core-2.36.0/google_api_core.egg-info}/PKG-INFO +6 -1
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google_api_core.egg-info/SOURCES.txt +2 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google_api_core.egg-info/requires.txt +7 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/pyproject.toml +9 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/test_grpc_helpers_async.py +9 -3
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_client_options.py +4 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_exceptions.py +25 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_feature_gating_helpers.py +1 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_grpc_helpers.py +109 -2
- google_api_core-2.36.0/tests/unit/test_observability.py +255 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_path_template.py +325 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_python_package_support.py +51 -3
- {google_api_core-2.34.0 → google_api_core-2.36.0}/LICENSE +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/MANIFEST.in +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/README.rst +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/_feature_gating_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/_python_version_support.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/_rest_streaming_base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/bidi.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/bidi_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/bidi_base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/client_info.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/client_logging.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/datetime_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/extended_operation.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/future/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/future/_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/future/async_future.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/future/base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/future/polling.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/client_info.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/config.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/config_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/method.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/method_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/requests.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/gapic_v1/routing_header.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/general_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/grpc_helpers_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/iam.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operation.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operation_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/abstract_operations_base_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/abstract_operations_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/operations_async_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/operations_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/operations_client_config.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/operations_rest_client_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/pagers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/pagers_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/pagers_base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/transports/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/transports/base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/transports/rest.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/operations_v1/transports/rest_asyncio.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/page_iterator.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/page_iterator_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/protobuf_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/py.typed +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/rest_streaming.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/rest_streaming_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/retry_base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/retry_streaming.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/retry_streaming_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/retry_unary.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry/retry_unary_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/retry_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/timeout.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/universe.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/version_header.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google_api_core.egg-info/dependency_links.txt +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/google_api_core.egg-info/top_level.txt +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/setup.cfg +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/setup.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/future/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/future/test_async_future.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/gapic/test_config_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/gapic/test_method_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/operations_v1/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/operations_v1/test_operations_async_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/retry/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/retry/test_retry_streaming_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/retry/test_retry_unary_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/test_bidi_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/test_operation_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/test_page_iterator_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/asyncio/test_rest_streaming_async.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/conftest.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/future/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/future/test__helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/future/test_polling.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/gapic/test_client_info.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/gapic/test_config.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/gapic/test_method.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/gapic/test_requests.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/gapic/test_routing_header.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/operations_v1/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/operations_v1/test_operations_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/operations_v1/test_operations_rest_client.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/retry/__init__.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/retry/test_retry_base.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/retry/test_retry_imports.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/retry/test_retry_streaming.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/retry/test_retry_unary.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_bidi.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_client_info.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_client_logging.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_datetime_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_extended_operation.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_iam.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_operation.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_packaging.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_page_iterator.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_protobuf_helpers.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_python_version_support.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_rest_helpers.py +1 -1
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_rest_streaming.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_timeout.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_universe.py +0 -0
- {google_api_core-2.34.0 → google_api_core-2.36.0}/tests/unit/test_version_header.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: google-api-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.36.0
|
|
4
4
|
Summary: Google API client core library
|
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -27,6 +27,7 @@ Requires-Dist: protobuf<8.0.0,>=6.33.5
|
|
|
27
27
|
Requires-Dist: proto-plus<2.0.0,>=1.26.1
|
|
28
28
|
Requires-Dist: google-auth<3.0.0,>=2.14.1
|
|
29
29
|
Requires-Dist: requests<3.0.0,>=2.33.0
|
|
30
|
+
Requires-Dist: opentelemetry-api<2.0.0,>=1.44.0
|
|
30
31
|
Provides-Extra: async-rest
|
|
31
32
|
Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.14.1; extra == "async-rest"
|
|
32
33
|
Requires-Dist: aiohttp>=3.13.4; extra == "async-rest"
|
|
@@ -35,6 +36,10 @@ Requires-Dist: grpcio<2.0.0,>=1.59.0; extra == "grpc"
|
|
|
35
36
|
Requires-Dist: grpcio<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
|
|
36
37
|
Requires-Dist: grpcio-status<2.0.0,>=1.59.0; extra == "grpc"
|
|
37
38
|
Requires-Dist: grpcio-status<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
|
|
39
|
+
Provides-Extra: tracing
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-grpc<1.0.0,>=0.65b0; extra == "tracing"
|
|
41
|
+
Provides-Extra: testing
|
|
42
|
+
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.44.0; extra == "testing"
|
|
38
43
|
Dynamic: license-file
|
|
39
44
|
|
|
40
45
|
Core Library for Google Client Libraries
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
"""OpenTelemetry helpers for resolving and instantiating interceptors."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Callable, Sequence
|
|
22
|
+
|
|
23
|
+
from google.api_core import _feature_gating_helpers
|
|
24
|
+
from google.api_core.client_options import ClientOptions
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
# flake8: grpc, trace, and ClientInterceptor are imported only for static analysis and type annotations
|
|
28
|
+
# The `# noqa: F401` comment avoids flake8 "imported but not used" errors.
|
|
29
|
+
import grpc # noqa: F401
|
|
30
|
+
import opentelemetry.trace # noqa: F401
|
|
31
|
+
|
|
32
|
+
from google.api_core.grpc_helpers import ClientInterceptor # noqa: F401
|
|
33
|
+
|
|
34
|
+
_TRACER_PROVIDER = "tracer_provider"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def is_otel_capabilities_enabled(
|
|
38
|
+
client_options: ClientOptions | dict[str, Any] | None = None,
|
|
39
|
+
env_var: str = "GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED",
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""Checks if OTel capabilities are enabled and installed.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
client_options: The client options object or dictionary.
|
|
45
|
+
env_var: The environment variable to check for enablement.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
bool: True if enabled and installed, False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
is_tracing_enabled = _feature_gating_helpers.resolve_feature_flags(
|
|
51
|
+
env_var=env_var,
|
|
52
|
+
feature_key=_TRACER_PROVIDER,
|
|
53
|
+
configuration=client_options,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if is_tracing_enabled:
|
|
57
|
+
try:
|
|
58
|
+
import opentelemetry.instrumentation.grpc as otel_grpc # type: ignore[import-not-found] # noqa: F401
|
|
59
|
+
|
|
60
|
+
return True
|
|
61
|
+
except ImportError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_tracer_provider(
|
|
68
|
+
client_options: ClientOptions | dict[str, Any] | None = None,
|
|
69
|
+
) -> opentelemetry.trace.TracerProvider | None:
|
|
70
|
+
"""Extracts the OpenTelemetry tracer provider from client options if present.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
client_options: The client options object or dictionary.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
opentelemetry.trace.TracerProvider | None: The tracer provider if present,
|
|
77
|
+
None otherwise.
|
|
78
|
+
"""
|
|
79
|
+
if isinstance(client_options, dict):
|
|
80
|
+
return client_options.get(_TRACER_PROVIDER)
|
|
81
|
+
elif client_options is not None:
|
|
82
|
+
return getattr(client_options, _TRACER_PROVIDER, None)
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_otel_interceptor(
|
|
87
|
+
client_options: ClientOptions | dict[str, Any] | None = None,
|
|
88
|
+
) -> Callable[[grpc.Channel], grpc.Channel] | None:
|
|
89
|
+
"""Returns an interceptor callable that wraps a sync gRPC channel with OpenTelemetry tracing.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
client_options: The client options object or dictionary used for feature gating
|
|
93
|
+
and extracting the tracer provider.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Callable[[grpc.Channel], grpc.Channel] | None: An interceptor callable if OpenTelemetry
|
|
97
|
+
tracing is enabled and installed, None otherwise.
|
|
98
|
+
"""
|
|
99
|
+
if not is_otel_capabilities_enabled(client_options):
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
import opentelemetry.instrumentation.grpc as otel_grpc # type: ignore[import-not-found]
|
|
103
|
+
|
|
104
|
+
interceptor: ClientInterceptor = otel_grpc.client_interceptor(
|
|
105
|
+
tracer_provider=_get_tracer_provider(client_options)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def otel_interceptor(channel: grpc.Channel) -> grpc.Channel:
|
|
109
|
+
return otel_grpc.intercept_channel(channel, interceptor)
|
|
110
|
+
|
|
111
|
+
return otel_interceptor
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_otel_async_interceptor(
|
|
115
|
+
client_options: ClientOptions | dict[str, Any] | None = None,
|
|
116
|
+
) -> Sequence[grpc.aio.ClientInterceptor] | None:
|
|
117
|
+
"""Returns async gRPC client interceptors for OpenTelemetry tracing.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
client_options: The client options object or dictionary used for feature gating
|
|
121
|
+
and extracting the tracer provider.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Sequence[grpc.aio.ClientInterceptor] | None: Instantiated OpenTelemetry async
|
|
125
|
+
client interceptors if tracing is enabled and installed, None otherwise.
|
|
126
|
+
"""
|
|
127
|
+
if not is_otel_capabilities_enabled(client_options):
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
# Ignored by mypy: Optional dependency only loaded if early-return is skipped
|
|
131
|
+
import opentelemetry.instrumentation.grpc as otel_grpc # type: ignore[import-not-found]
|
|
132
|
+
|
|
133
|
+
return otel_grpc.aio_client_interceptors(
|
|
134
|
+
tracer_provider=_get_tracer_provider(client_options)
|
|
135
|
+
)
|
{google_api_core-2.34.0 → google_api_core-2.36.0}/google/api_core/_python_package_support.py
RENAMED
|
@@ -30,14 +30,37 @@ ParsedVersion = Tuple[int, ...]
|
|
|
30
30
|
# about deprecated and unsupported versions.
|
|
31
31
|
DependencyConstraint = namedtuple(
|
|
32
32
|
"DependencyConstraint",
|
|
33
|
-
[
|
|
33
|
+
[
|
|
34
|
+
"package_name",
|
|
35
|
+
"minimum_fully_supported_version",
|
|
36
|
+
"recommended_version",
|
|
37
|
+
"message_template",
|
|
38
|
+
],
|
|
39
|
+
defaults=(None, None),
|
|
34
40
|
)
|
|
41
|
+
|
|
42
|
+
PQC_GRPC_WARNING_TEMPLATE = (
|
|
43
|
+
"Package {consumer_package} depends on {dependency_package}, currently installed at version {version_used_string}. "
|
|
44
|
+
"grpcio < 1.83.0 does not support Post-Quantum Cryptography (PQC). "
|
|
45
|
+
"Support for non-PQC environments is deprecated. In October 2026, "
|
|
46
|
+
"Google Cloud Python packages will raise their minimum requirements "
|
|
47
|
+
"(including google-api-core, grpcio, and grpcio-status) to enforce grpcio >= 1.83.0. "
|
|
48
|
+
"For more details on Google Cloud's post-quantum security migration, visit: "
|
|
49
|
+
"https://cloud.google.com/security/resources/post-quantum-cryptography"
|
|
50
|
+
)
|
|
51
|
+
|
|
35
52
|
_PACKAGE_DEPENDENCY_WARNINGS = [
|
|
36
53
|
DependencyConstraint(
|
|
37
54
|
"google.protobuf",
|
|
38
55
|
minimum_fully_supported_version="6.33.5",
|
|
39
56
|
recommended_version="6.x",
|
|
40
|
-
)
|
|
57
|
+
),
|
|
58
|
+
DependencyConstraint(
|
|
59
|
+
"grpcio",
|
|
60
|
+
minimum_fully_supported_version="1.83.0",
|
|
61
|
+
recommended_version="1.83.x",
|
|
62
|
+
message_template=PQC_GRPC_WARNING_TEMPLATE,
|
|
63
|
+
),
|
|
41
64
|
]
|
|
42
65
|
|
|
43
66
|
|
|
@@ -162,7 +185,7 @@ def warn_deprecation_for_versions_less_than(
|
|
|
162
185
|
) = _get_distribution_and_import_packages(consumer_import_package)
|
|
163
186
|
|
|
164
187
|
recommendation = (
|
|
165
|
-
" (we recommend {recommended_version})" if recommended_version else ""
|
|
188
|
+
f" (we recommend {recommended_version})" if recommended_version else ""
|
|
166
189
|
)
|
|
167
190
|
message_template = message_template or _flatten_message(
|
|
168
191
|
"""
|
|
@@ -222,4 +245,5 @@ def check_dependency_versions(
|
|
|
222
245
|
package_info.package_name,
|
|
223
246
|
package_info.minimum_fully_supported_version,
|
|
224
247
|
recommended_version=package_info.recommended_version,
|
|
248
|
+
message_template=package_info.message_template,
|
|
225
249
|
)
|
|
@@ -48,9 +48,13 @@ You can also pass a mapping object.
|
|
|
48
48
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
+
import typing
|
|
51
52
|
import warnings
|
|
52
53
|
from typing import Callable, Mapping, Optional, Sequence, Tuple
|
|
53
54
|
|
|
55
|
+
if typing.TYPE_CHECKING:
|
|
56
|
+
import opentelemetry.trace
|
|
57
|
+
|
|
54
58
|
from google.api_core import general_helpers
|
|
55
59
|
|
|
56
60
|
|
|
@@ -98,6 +102,8 @@ class ClientOptions(object):
|
|
|
98
102
|
`googleapis.com`. If both `api_endpoint` and `universe_domain` are set,
|
|
99
103
|
then `api_endpoint` is used as the service endpoint. If `api_endpoint` is
|
|
100
104
|
not specified, the format will be `{service}.{universe_domain}`.
|
|
105
|
+
tracer_provider (Optional["opentelemetry.trace.TracerProvider"]): The OpenTelemetry tracer provider to use
|
|
106
|
+
for tracing in supported libraries.
|
|
101
107
|
|
|
102
108
|
Raises:
|
|
103
109
|
ValueError: If both ``client_cert_source`` and ``client_encrypted_cert_source``
|
|
@@ -117,6 +123,7 @@ class ClientOptions(object):
|
|
|
117
123
|
api_key: Optional[str] = None,
|
|
118
124
|
api_audience: Optional[str] = None,
|
|
119
125
|
universe_domain: Optional[str] = None,
|
|
126
|
+
tracer_provider: Optional["opentelemetry.trace.TracerProvider"] = None,
|
|
120
127
|
):
|
|
121
128
|
if credentials_file is not None:
|
|
122
129
|
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)
|
|
@@ -136,6 +143,7 @@ class ClientOptions(object):
|
|
|
136
143
|
self.api_key = api_key
|
|
137
144
|
self.api_audience = api_audience
|
|
138
145
|
self.universe_domain = universe_domain
|
|
146
|
+
self.tracer_provider = tracer_provider
|
|
139
147
|
|
|
140
148
|
def __repr__(self) -> str:
|
|
141
149
|
return "ClientOptions: " + repr(self.__dict__)
|
|
@@ -510,6 +510,8 @@ def format_http_response_error(
|
|
|
510
510
|
:class:`GoogleAPICallError`, with the message and errors populated
|
|
511
511
|
from the response.
|
|
512
512
|
"""
|
|
513
|
+
if isinstance(payload, list):
|
|
514
|
+
payload = next((item for item in payload if isinstance(item, dict)), {})
|
|
513
515
|
payload = {} if not payload else payload
|
|
514
516
|
error_message = payload.get("error", {}).get("message", "unknown error")
|
|
515
517
|
errors = payload.get("error", {}).get("errors", ())
|
|
@@ -17,7 +17,17 @@
|
|
|
17
17
|
import collections
|
|
18
18
|
import functools
|
|
19
19
|
import warnings
|
|
20
|
-
from typing import
|
|
20
|
+
from typing import (
|
|
21
|
+
Callable,
|
|
22
|
+
Generic,
|
|
23
|
+
Iterator,
|
|
24
|
+
Optional,
|
|
25
|
+
Sequence,
|
|
26
|
+
TypeAlias,
|
|
27
|
+
TypeVar,
|
|
28
|
+
cast,
|
|
29
|
+
get_args,
|
|
30
|
+
)
|
|
21
31
|
|
|
22
32
|
import google.auth
|
|
23
33
|
import google.auth.credentials
|
|
@@ -34,6 +44,17 @@ _STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCal
|
|
|
34
44
|
# denotes the proto response type for grpc calls
|
|
35
45
|
P = TypeVar("P")
|
|
36
46
|
|
|
47
|
+
# Type alias representing any client-side gRPC interceptor
|
|
48
|
+
ClientInterceptor: TypeAlias = (
|
|
49
|
+
grpc.UnaryUnaryClientInterceptor
|
|
50
|
+
| grpc.UnaryStreamClientInterceptor
|
|
51
|
+
| grpc.StreamUnaryClientInterceptor
|
|
52
|
+
| grpc.StreamStreamClientInterceptor
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Runtime tuple of gRPC client interceptor base classes for isinstance checks
|
|
56
|
+
_CLIENT_INTERCEPTOR_CLASSES = get_args(ClientInterceptor)
|
|
57
|
+
|
|
37
58
|
|
|
38
59
|
def _patch_callable_name(callable_):
|
|
39
60
|
"""Fix-up gRPC callable attributes.
|
|
@@ -419,6 +440,52 @@ def _modify_target_for_direct_path(target: str) -> str:
|
|
|
419
440
|
return target
|
|
420
441
|
|
|
421
442
|
|
|
443
|
+
def apply_channel_interceptors(
|
|
444
|
+
channel: grpc.Channel,
|
|
445
|
+
interceptors: (
|
|
446
|
+
Sequence[ClientInterceptor | Callable[[grpc.Channel], grpc.Channel]] | None
|
|
447
|
+
) = None,
|
|
448
|
+
) -> grpc.Channel:
|
|
449
|
+
"""Applies client interceptors or channel-intercepting callables to a gRPC channel.
|
|
450
|
+
|
|
451
|
+
Executes in reverse order so the first interceptor in the sequence becomes the
|
|
452
|
+
outermost layer on outbound requests and the innermost layer on inbound responses,
|
|
453
|
+
aligning with the behavior of ``grpc.intercept_channel``.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
channel (grpc.Channel): The channel to intercept.
|
|
457
|
+
interceptors (Optional[Sequence[Union[ClientInterceptor, Callable[[grpc.Channel], grpc.Channel]]]]):
|
|
458
|
+
Additional interceptors (or callables that apply interceptors) to apply to the gRPC channel.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
grpc.Channel: The intercepted channel, or the original channel if no
|
|
462
|
+
interceptors were provided.
|
|
463
|
+
|
|
464
|
+
Raises:
|
|
465
|
+
TypeError: If an item in ``interceptors`` is neither a gRPC ClientInterceptor
|
|
466
|
+
nor a Callable[[Channel], Channel].
|
|
467
|
+
"""
|
|
468
|
+
if not interceptors:
|
|
469
|
+
return channel
|
|
470
|
+
|
|
471
|
+
modified_channel = channel
|
|
472
|
+
# Reverse the inputs to align with the behavior of grpc.create_channel(*interceptors)
|
|
473
|
+
for interceptor in reversed(list(interceptors)):
|
|
474
|
+
if isinstance(interceptor, _CLIENT_INTERCEPTOR_CLASSES):
|
|
475
|
+
modified_channel = grpc.intercept_channel(modified_channel, interceptor)
|
|
476
|
+
elif callable(interceptor):
|
|
477
|
+
interceptor_callable = cast(
|
|
478
|
+
Callable[[grpc.Channel], grpc.Channel], interceptor
|
|
479
|
+
)
|
|
480
|
+
modified_channel = interceptor_callable(modified_channel)
|
|
481
|
+
else:
|
|
482
|
+
raise TypeError(
|
|
483
|
+
f"Expected ClientInterceptor or Callable[[Channel], Channel], got {type(interceptor).__name__}"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
return modified_channel
|
|
487
|
+
|
|
488
|
+
|
|
422
489
|
_MethodCall = collections.namedtuple(
|
|
423
490
|
"_MethodCall", ("request", "timeout", "metadata", "credentials", "compression")
|
|
424
491
|
)
|
|
@@ -28,6 +28,7 @@ from __future__ import unicode_literals
|
|
|
28
28
|
import copy
|
|
29
29
|
import functools
|
|
30
30
|
import re
|
|
31
|
+
import urllib.parse
|
|
31
32
|
from collections import deque
|
|
32
33
|
|
|
33
34
|
# Regular expression for extracting variable parts from a path template.
|
|
@@ -62,6 +63,41 @@ _SINGLE_SEGMENT_PATTERN = r"([^/]+)"
|
|
|
62
63
|
_MULTI_SEGMENT_PATTERN = r"(.+)"
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
def _extract_and_validate_wildcards(
|
|
67
|
+
val: str, template_str: str | None, property_name: str = "property"
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Extract and validate wildcard variables against path traversal.
|
|
70
|
+
|
|
71
|
+
Ensures that values matched against single-segment wildcards ('*') are
|
|
72
|
+
not '.' or '..', and values matched against multi-segment wildcards ('**')
|
|
73
|
+
do not contain '.' or '..' segments.
|
|
74
|
+
|
|
75
|
+
If the value does not match the sub-template structure, validation is
|
|
76
|
+
deferred to allow subsequent bindings to be evaluated.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
val (str): The raw string value to validate.
|
|
80
|
+
template_str (str | None): The template string of the variable (e.g.
|
|
81
|
+
'projects/*/locations/*').
|
|
82
|
+
property_name (str): The name of the property being validated, for use
|
|
83
|
+
in error messages.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: If a wildcard value contains invalid dot segments.
|
|
87
|
+
"""
|
|
88
|
+
tmpl = template_str or "*"
|
|
89
|
+
m = re.fullmatch(_generate_pattern_for_template(tmpl), val)
|
|
90
|
+
if m is not None:
|
|
91
|
+
groups = m.groups()
|
|
92
|
+
for g in groups:
|
|
93
|
+
if "." in g and any(seg in (".", "..") for seg in g.split("/")):
|
|
94
|
+
if "**" in tmpl or "/" in tmpl:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Value for {property_name} must not contain segments that are exactly . or .. ."
|
|
97
|
+
)
|
|
98
|
+
raise ValueError(f"Invalid value {g} for {property_name}.")
|
|
99
|
+
|
|
100
|
+
|
|
65
101
|
def _expand_variable_match(positional_vars, named_vars, match):
|
|
66
102
|
"""Expand a matched variable with its value.
|
|
67
103
|
|
|
@@ -81,9 +117,13 @@ def _expand_variable_match(positional_vars, named_vars, match):
|
|
|
81
117
|
"""
|
|
82
118
|
positional = match.group("positional")
|
|
83
119
|
name = match.group("name")
|
|
120
|
+
template = match.group("template")
|
|
121
|
+
|
|
84
122
|
if name is not None:
|
|
85
123
|
try:
|
|
86
|
-
|
|
124
|
+
val = str(named_vars[name])
|
|
125
|
+
_extract_and_validate_wildcards(val, template, name)
|
|
126
|
+
return val
|
|
87
127
|
except KeyError:
|
|
88
128
|
raise ValueError(
|
|
89
129
|
"Named variable '{}' not specified and needed by template "
|
|
@@ -91,7 +131,9 @@ def _expand_variable_match(positional_vars, named_vars, match):
|
|
|
91
131
|
)
|
|
92
132
|
elif positional is not None:
|
|
93
133
|
try:
|
|
94
|
-
|
|
134
|
+
val = str(positional_vars.pop(0))
|
|
135
|
+
_extract_and_validate_wildcards(val, positional, "positional variable")
|
|
136
|
+
return val
|
|
95
137
|
except IndexError:
|
|
96
138
|
raise ValueError(
|
|
97
139
|
"Positional variable not specified and needed by template "
|
|
@@ -159,6 +201,7 @@ def _replace_variable_with_pattern(match):
|
|
|
159
201
|
raise ValueError("Unknown template expression {}".format(match.group(0)))
|
|
160
202
|
|
|
161
203
|
|
|
204
|
+
@functools.lru_cache(maxsize=256)
|
|
162
205
|
def _generate_pattern_for_template(tmpl):
|
|
163
206
|
"""Generate a pattern that can validate a path template.
|
|
164
207
|
|
|
@@ -172,12 +215,17 @@ def _generate_pattern_for_template(tmpl):
|
|
|
172
215
|
return _VARIABLE_RE.sub(_replace_variable_with_pattern, tmpl)
|
|
173
216
|
|
|
174
217
|
|
|
175
|
-
def get_field(request, field):
|
|
218
|
+
def get_field(request, field, encode=False):
|
|
176
219
|
"""Get the value of a field from a given dictionary.
|
|
177
220
|
|
|
178
221
|
Args:
|
|
179
222
|
request (dict | Message): A dictionary or a Message object.
|
|
180
223
|
field (str): The key to the request in dot notation.
|
|
224
|
+
encode (bool): Whether to percent-encode the field value. If enabled,
|
|
225
|
+
will encode all characters except `[-_.~/0-9a-zA-Z]` for URI path
|
|
226
|
+
variable parts per
|
|
227
|
+
https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44-L312.
|
|
228
|
+
Defaults to False.
|
|
181
229
|
|
|
182
230
|
Returns:
|
|
183
231
|
The value of the field.
|
|
@@ -192,6 +240,8 @@ def get_field(request, field):
|
|
|
192
240
|
value = value.get(part)
|
|
193
241
|
if isinstance(value, dict):
|
|
194
242
|
return
|
|
243
|
+
if encode and value is not None:
|
|
244
|
+
return urllib.parse.quote(str(value), safe="/")
|
|
195
245
|
return value
|
|
196
246
|
|
|
197
247
|
|
|
@@ -284,7 +334,10 @@ def transcode(http_options, message=None, **request_kwargs):
|
|
|
284
334
|
]
|
|
285
335
|
bindings.append((uri_template, fields))
|
|
286
336
|
|
|
287
|
-
path_args = {
|
|
337
|
+
path_args = {
|
|
338
|
+
field: get_field(transcoded_value, field, encode=True)
|
|
339
|
+
for field, _ in fields
|
|
340
|
+
}
|
|
288
341
|
request["uri"] = expand(uri_template, **path_args)
|
|
289
342
|
|
|
290
343
|
if not validate(uri_template, request["uri"]) or not all(path_args.values()):
|
|
@@ -19,9 +19,10 @@ import json
|
|
|
19
19
|
import operator
|
|
20
20
|
from typing import Any, Dict, List, Optional, Tuple
|
|
21
21
|
|
|
22
|
-
from google.api_core import path_template
|
|
23
22
|
from google.protobuf import json_format
|
|
24
23
|
|
|
24
|
+
from google.api_core import path_template
|
|
25
|
+
|
|
25
26
|
__all__ = ["flatten_query_params", "transcode", "transcode_request"]
|
|
26
27
|
|
|
27
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: google-api-core
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.36.0
|
|
4
4
|
Summary: Google API client core library
|
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -27,6 +27,7 @@ Requires-Dist: protobuf<8.0.0,>=6.33.5
|
|
|
27
27
|
Requires-Dist: proto-plus<2.0.0,>=1.26.1
|
|
28
28
|
Requires-Dist: google-auth<3.0.0,>=2.14.1
|
|
29
29
|
Requires-Dist: requests<3.0.0,>=2.33.0
|
|
30
|
+
Requires-Dist: opentelemetry-api<2.0.0,>=1.44.0
|
|
30
31
|
Provides-Extra: async-rest
|
|
31
32
|
Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.14.1; extra == "async-rest"
|
|
32
33
|
Requires-Dist: aiohttp>=3.13.4; extra == "async-rest"
|
|
@@ -35,6 +36,10 @@ Requires-Dist: grpcio<2.0.0,>=1.59.0; extra == "grpc"
|
|
|
35
36
|
Requires-Dist: grpcio<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
|
|
36
37
|
Requires-Dist: grpcio-status<2.0.0,>=1.59.0; extra == "grpc"
|
|
37
38
|
Requires-Dist: grpcio-status<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
|
|
39
|
+
Provides-Extra: tracing
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-grpc<1.0.0,>=0.65b0; extra == "tracing"
|
|
41
|
+
Provides-Extra: testing
|
|
42
|
+
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.44.0; extra == "testing"
|
|
38
43
|
Dynamic: license-file
|
|
39
44
|
|
|
40
45
|
Core Library for Google Client Libraries
|
|
@@ -6,6 +6,7 @@ setup.cfg
|
|
|
6
6
|
setup.py
|
|
7
7
|
google/api_core/__init__.py
|
|
8
8
|
google/api_core/_feature_gating_helpers.py
|
|
9
|
+
google/api_core/_observability.py
|
|
9
10
|
google/api_core/_python_package_support.py
|
|
10
11
|
google/api_core/_python_version_support.py
|
|
11
12
|
google/api_core/_rest_streaming_base.py
|
|
@@ -104,6 +105,7 @@ tests/unit/test_extended_operation.py
|
|
|
104
105
|
tests/unit/test_feature_gating_helpers.py
|
|
105
106
|
tests/unit/test_grpc_helpers.py
|
|
106
107
|
tests/unit/test_iam.py
|
|
108
|
+
tests/unit/test_observability.py
|
|
107
109
|
tests/unit/test_operation.py
|
|
108
110
|
tests/unit/test_packaging.py
|
|
109
111
|
tests/unit/test_page_iterator.py
|
|
@@ -3,6 +3,7 @@ protobuf<8.0.0,>=6.33.5
|
|
|
3
3
|
proto-plus<2.0.0,>=1.26.1
|
|
4
4
|
google-auth<3.0.0,>=2.14.1
|
|
5
5
|
requests<3.0.0,>=2.33.0
|
|
6
|
+
opentelemetry-api<2.0.0,>=1.44.0
|
|
6
7
|
|
|
7
8
|
[async_rest]
|
|
8
9
|
google-auth[aiohttp]<3.0.0,>=2.14.1
|
|
@@ -15,3 +16,9 @@ grpcio-status<2.0.0,>=1.59.0
|
|
|
15
16
|
[grpc:python_version >= "3.14"]
|
|
16
17
|
grpcio<2.0.0,>=1.75.1
|
|
17
18
|
grpcio-status<2.0.0,>=1.75.1
|
|
19
|
+
|
|
20
|
+
[testing]
|
|
21
|
+
opentelemetry-sdk<2.0.0,>=1.44.0
|
|
22
|
+
|
|
23
|
+
[tracing]
|
|
24
|
+
opentelemetry-instrumentation-grpc<1.0.0,>=0.65b0
|
|
@@ -48,6 +48,7 @@ dependencies = [
|
|
|
48
48
|
"proto-plus >= 1.26.1, < 2.0.0",
|
|
49
49
|
"google-auth >= 2.14.1, < 3.0.0",
|
|
50
50
|
"requests >= 2.33.0, < 3.0.0",
|
|
51
|
+
"opentelemetry-api >= 1.44.0, < 2.0.0",
|
|
51
52
|
]
|
|
52
53
|
dynamic = ["version"]
|
|
53
54
|
|
|
@@ -64,6 +65,12 @@ grpc = [
|
|
|
64
65
|
"grpcio-status >= 1.59.0, < 2.0.0",
|
|
65
66
|
"grpcio-status >= 1.75.1, < 2.0.0; python_version >= '3.14'",
|
|
66
67
|
]
|
|
68
|
+
tracing = [
|
|
69
|
+
"opentelemetry-instrumentation-grpc >= 0.65b0, < 1.0.0",
|
|
70
|
+
]
|
|
71
|
+
testing = [
|
|
72
|
+
"opentelemetry-sdk >= 1.44.0, < 2.0.0",
|
|
73
|
+
]
|
|
67
74
|
|
|
68
75
|
|
|
69
76
|
[tool.setuptools.dynamic]
|
|
@@ -85,6 +92,8 @@ filterwarnings = [
|
|
|
85
92
|
"error",
|
|
86
93
|
# Prevent Python version warnings from interfering with tests
|
|
87
94
|
"ignore:.* Python version .*:FutureWarning",
|
|
95
|
+
# TODO: Remove once b/544782314 is resolved (when grpcio >= 1.83.0 is enforced)
|
|
96
|
+
"ignore:.*does not support Post-Quantum Cryptography.*:FutureWarning",
|
|
88
97
|
# Remove once https://github.com/pytest-dev/pytest-cov/issues/621 is fixed
|
|
89
98
|
"ignore:.*The --rsyncdir command line argument and rsyncdirs config variable are deprecated:DeprecationWarning",
|
|
90
99
|
# Remove once https://github.com/protocolbuffers/protobuf/issues/12186 is fixed
|
|
@@ -421,9 +421,15 @@ def test_create_channel_implicit_with_default_host(
|
|
|
421
421
|
assert channel is grpc_secure_channel.return_value
|
|
422
422
|
|
|
423
423
|
google_auth_default.assert_called_once_with(scopes=None, default_scopes=None)
|
|
424
|
-
auth_metadata_plugin.
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
assert auth_metadata_plugin.call_count == 1
|
|
425
|
+
assert auth_metadata_plugin.call_args.args == (
|
|
426
|
+
mock.sentinel.credentials,
|
|
427
|
+
mock.sentinel.Request,
|
|
428
|
+
)
|
|
429
|
+
assert auth_metadata_plugin.call_args.kwargs["default_host"] == default_host
|
|
430
|
+
# google-auth >= 2.56.3 supports suppress_metrics_header; older versions omit it during fallback
|
|
431
|
+
if "suppress_metrics_header" in auth_metadata_plugin.call_args.kwargs:
|
|
432
|
+
assert auth_metadata_plugin.call_args.kwargs["suppress_metrics_header"] is True
|
|
427
433
|
grpc_secure_channel.assert_called_once_with(
|
|
428
434
|
expected_target, composite_creds, compression=None
|
|
429
435
|
)
|
|
@@ -30,6 +30,7 @@ def get_client_encrypted_cert():
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def test_constructor():
|
|
33
|
+
mock_tracer_provider = object()
|
|
33
34
|
with warn_deprecated_credentials_file():
|
|
34
35
|
options = client_options.ClientOptions(
|
|
35
36
|
api_endpoint="foo.googleapis.com",
|
|
@@ -42,6 +43,7 @@ def test_constructor():
|
|
|
42
43
|
],
|
|
43
44
|
api_audience="foo2.googleapis.com",
|
|
44
45
|
universe_domain="googleapis.com",
|
|
46
|
+
tracer_provider=mock_tracer_provider,
|
|
45
47
|
)
|
|
46
48
|
|
|
47
49
|
assert options.api_endpoint == "foo.googleapis.com"
|
|
@@ -54,6 +56,7 @@ def test_constructor():
|
|
|
54
56
|
]
|
|
55
57
|
assert options.api_audience == "foo2.googleapis.com"
|
|
56
58
|
assert options.universe_domain == "googleapis.com"
|
|
59
|
+
assert options.tracer_provider is mock_tracer_provider
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
def test_constructor_with_encrypted_cert_source():
|
|
@@ -162,6 +165,7 @@ def test_repr():
|
|
|
162
165
|
"scopes",
|
|
163
166
|
"api_key",
|
|
164
167
|
"api_audience",
|
|
168
|
+
"tracer_provider",
|
|
165
169
|
]
|
|
166
170
|
)
|
|
167
171
|
options = client_options.ClientOptions(api_endpoint="foo.googleapis.com")
|
|
@@ -132,6 +132,31 @@ def test_from_http_response_json_content():
|
|
|
132
132
|
assert exception.errors == ["1", "2"]
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
def test_from_http_response_json_list_content():
|
|
136
|
+
response = make_response(
|
|
137
|
+
json.dumps(
|
|
138
|
+
[{"error": {"message": "json message", "errors": ["1", "2"]}}]
|
|
139
|
+
).encode("utf-8")
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
exception = exceptions.from_http_response(response)
|
|
143
|
+
|
|
144
|
+
assert isinstance(exception, exceptions.NotFound)
|
|
145
|
+
assert exception.code == http.client.NOT_FOUND
|
|
146
|
+
assert exception.message == "POST https://example.com/: json message"
|
|
147
|
+
assert exception.errors == ["1", "2"]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_from_http_response_json_list_content_without_error_dict():
|
|
151
|
+
response = make_response(json.dumps(["error message"]).encode("utf-8"))
|
|
152
|
+
|
|
153
|
+
exception = exceptions.from_http_response(response)
|
|
154
|
+
|
|
155
|
+
assert isinstance(exception, exceptions.NotFound)
|
|
156
|
+
assert exception.code == http.client.NOT_FOUND
|
|
157
|
+
assert exception.message == "POST https://example.com/: unknown error"
|
|
158
|
+
|
|
159
|
+
|
|
135
160
|
def test_from_http_response_bad_json_content():
|
|
136
161
|
response = make_response(json.dumps({"meep": "moop"}).encode("utf-8"))
|
|
137
162
|
|