Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-google-api-core for openSUSE:Factory checked in at 2023-12-07 19:11:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-google-api-core (Old) and /work/SRC/openSUSE:Factory/.python-google-api-core.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-google-api-core" Thu Dec 7 19:11:34 2023 rev:28 rq:1131630 version:2.14.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-google-api-core/python-google-api-core.changes 2023-10-24 20:08:52.049637685 +0200 +++ /work/SRC/openSUSE:Factory/.python-google-api-core.new.25432/python-google-api-core.changes 2023-12-07 19:13:19.685727905 +0100 @@ -1,0 +2,14 @@ +Thu Dec 7 13:49:02 UTC 2023 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 2.14.0 + * Support with_call for wrapped rpcs (#550) +- from version 2.13.1 + * Update async client to use async retry (#544) +- from version 2.13.0 + * Add caching to routing header calculation (#526) + * Add warning to retry target to avoid incorrect usage (#543) + * Drop usage of distutils (#541) + * Ensure exception is available when BackgroundConsumer + open stream fails (#357) + +------------------------------------------------------------------- Old: ---- google-api-core-2.12.0.tar.gz New: ---- google-api-core-2.14.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-google-api-core.spec ++++++ --- /var/tmp/diff_new_pack.37IJhy/_old 2023-12-07 19:13:20.141744728 +0100 +++ /var/tmp/diff_new_pack.37IJhy/_new 2023-12-07 19:13:20.145744875 +0100 @@ -27,7 +27,7 @@ %define skip_python2 1 %{?sle15_python_module_pythons} Name: python-google-api-core -Version: 2.12.0 +Version: 2.14.0 Release: 0 Summary: Google API client core library License: Apache-2.0 ++++++ google-api-core-2.12.0.tar.gz -> google-api-core-2.14.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/PKG-INFO new/google-api-core-2.14.0/PKG-INFO --- old/google-api-core-2.12.0/PKG-INFO 2023-09-25 18:31:13.247293500 +0200 +++ new/google-api-core-2.14.0/PKG-INFO 2023-11-10 00:16:17.662312300 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-core -Version: 2.12.0 +Version: 2.14.0 Summary: Google API client core library Home-page: https://github.com/googleapis/python-api-core Author: Google LLC @@ -20,10 +20,20 @@ Classifier: Operating System :: OS Independent Classifier: Topic :: Internet Requires-Python: >=3.7 +License-File: LICENSE +Requires-Dist: googleapis-common-protos<2.0.dev0,>=1.56.2 +Requires-Dist: protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0.dev0,>=3.19.5 +Requires-Dist: google-auth<3.0.dev0,>=2.14.1 +Requires-Dist: requests<3.0.0.dev0,>=2.18.0 Provides-Extra: grpc +Requires-Dist: grpcio<2.0dev,>=1.33.2; extra == "grpc" +Requires-Dist: grpcio<2.0dev,>=1.49.1; python_version >= "3.11" and extra == "grpc" +Requires-Dist: grpcio-status<2.0.dev0,>=1.33.2; extra == "grpc" +Requires-Dist: grpcio-status<2.0.dev0,>=1.49.1; python_version >= "3.11" and extra == "grpc" Provides-Extra: grpcgcp +Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcgcp" Provides-Extra: grpcio-gcp -License-File: LICENSE +Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcio-gcp" Core Library for Google Client Libraries ======================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/bidi.py new/google-api-core-2.14.0/google/api_core/bidi.py --- old/google-api-core-2.12.0/google/api_core/bidi.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/bidi.py 2023-11-10 00:14:15.000000000 +0100 @@ -92,10 +92,7 @@ # Note: there is a possibility that this starts *before* the call # property is set. So we have to check if self.call is set before # seeing if it's active. - if self.call is not None and not self.call.is_active(): - return False - else: - return True + return self.call is not None and self.call.is_active() def __iter__(self): if self._initial_request is not None: @@ -265,6 +262,10 @@ self._callbacks.append(callback) def _on_call_done(self, future): + # This occurs when the RPC errors or is successfully terminated. + # Note that grpc's "future" here can also be a grpc.RpcError. + # See note in https://github.com/grpc/grpc/issues/10885#issuecomment-302651331 + # that `grpc.RpcError` is also `grpc.call`. for callback in self._callbacks: callback(future) @@ -276,7 +277,13 @@ request_generator = _RequestQueueGenerator( self._request_queue, initial_request=self._initial_request ) - call = self._start_rpc(iter(request_generator), metadata=self._rpc_metadata) + try: + call = self._start_rpc(iter(request_generator), metadata=self._rpc_metadata) + except exceptions.GoogleAPICallError as exc: + # The original `grpc.RpcError` (which is usually also a `grpc.Call`) is + # available from the ``response`` property on the mapped exception. + self._on_call_done(exc.response) + raise request_generator.call = call diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/gapic_v1/method.py new/google-api-core-2.14.0/google/api_core/gapic_v1/method.py --- old/google-api-core-2.12.0/google/api_core/gapic_v1/method.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/gapic_v1/method.py 2023-11-10 00:14:15.000000000 +0100 @@ -137,6 +137,8 @@ default_timeout=None, default_compression=None, client_info=client_info.DEFAULT_CLIENT_INFO, + *, + with_call=False, ): """Wrap an RPC method with common behavior. @@ -216,6 +218,10 @@ passed as gRPC metadata to the method. If unspecified, then a sane default will be used. If ``None``, then no user agent metadata will be provided to the RPC method. + with_call (bool): If True, wrapped grpc.UnaryUnaryMulticallables will + return a tuple of (response, grpc.Call) instead of just the response. + This is useful for extracting trailing metadata from unary calls. + Defaults to False. Returns: Callable: A new callable that takes optional ``retry``, ``timeout``, @@ -223,6 +229,13 @@ arguments and applies the common error mapping, retry, timeout, compression, and metadata behavior to the low-level RPC method. """ + if with_call: + try: + func = func.with_call + except AttributeError as exc: + raise ValueError( + "with_call=True is only supported for unary calls." + ) from exc func = grpc_helpers.wrap_errors(func) if client_info is not None: user_agent_metadata = [client_info.to_grpc_metadata()] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/gapic_v1/routing_header.py new/google-api-core-2.14.0/google/api_core/gapic_v1/routing_header.py --- old/google-api-core-2.12.0/google/api_core/gapic_v1/routing_header.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/gapic_v1/routing_header.py 2023-11-10 00:14:15.000000000 +0100 @@ -20,17 +20,22 @@ Generally, these headers are specified as gRPC metadata. """ +import functools from enum import Enum from urllib.parse import urlencode ROUTING_METADATA_KEY = "x-goog-request-params" +# This is the value for the `maxsize` argument of @functools.lru_cache +# https://docs.python.org/3/library/functools.html#functools.lru_cache +# This represents the number of recent function calls to store. +ROUTING_PARAM_CACHE_SIZE = 32 def to_routing_header(params, qualified_enums=True): """Returns a routing header string for the given request parameters. Args: - params (Mapping[str, Any]): A dictionary containing the request + params (Mapping[str, str | bytes | Enum]): A dictionary containing the request parameters used for routing. qualified_enums (bool): Whether to represent enum values as their type-qualified symbol names instead of as their @@ -38,19 +43,11 @@ Returns: str: The routing header string. - """ + tuples = params.items() if isinstance(params, dict) else params if not qualified_enums: - if isinstance(params, dict): - tuples = params.items() - else: - tuples = params - params = [(x[0], x[1].name) if isinstance(x[1], Enum) else x for x in tuples] - return urlencode( - params, - # Per Google API policy (go/api-url-encoding), / is not encoded. - safe="/", - ) + tuples = [(x[0], x[1].name) if isinstance(x[1], Enum) else x for x in tuples] + return "&".join([_urlencode_param(*t) for t in tuples]) def to_grpc_metadata(params, qualified_enums=True): @@ -58,7 +55,7 @@ request parameters. Args: - params (Mapping[str, Any]): A dictionary containing the request + params (Mapping[str, str | bytes | Enum]): A dictionary containing the request parameters used for routing. qualified_enums (bool): Whether to represent enum values as their type-qualified symbol names instead of as their @@ -69,3 +66,22 @@ and value. """ return (ROUTING_METADATA_KEY, to_routing_header(params, qualified_enums)) + + +# use caching to avoid repeated computation +@functools.lru_cache(maxsize=ROUTING_PARAM_CACHE_SIZE) +def _urlencode_param(key, value): + """Cacheable wrapper over urlencode + + Args: + key (str): The key of the parameter to encode. + value (str | bytes | Enum): The value of the parameter to encode. + + Returns: + str: The encoded parameter. + """ + return urlencode( + {key: value}, + # Per Google API policy (go/api-url-encoding), / is not encoded. + safe="/", + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/operations_v1/abstract_operations_client.py new/google-api-core-2.14.0/google/api_core/operations_v1/abstract_operations_client.py --- old/google-api-core-2.12.0/google/api_core/operations_v1/abstract_operations_client.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/operations_v1/abstract_operations_client.py 2023-11-10 00:14:15.000000000 +0100 @@ -14,7 +14,6 @@ # limitations under the License. # from collections import OrderedDict -from distutils import util import os import re from typing import Dict, Optional, Sequence, Tuple, Type, Union @@ -294,13 +293,16 @@ client_options = client_options_lib.ClientOptions() # Create SSL credentials for mutual TLS if needed. - use_client_cert = bool( - util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) - ) - + use_client_cert = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) client_cert_source_func = None is_mtls = False - if use_client_cert: + if use_client_cert == "true": if client_options.client_cert_source: is_mtls = True client_cert_source_func = client_options.client_cert_source diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/operations_v1/operations_async_client.py new/google-api-core-2.14.0/google/api_core/operations_v1/operations_async_client.py --- old/google-api-core-2.12.0/google/api_core/operations_v1/operations_async_client.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/operations_v1/operations_async_client.py 2023-11-10 00:14:15.000000000 +0100 @@ -26,7 +26,7 @@ from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1, page_iterator_async -from google.api_core import retry as retries +from google.api_core import retry_async as retries from google.api_core import timeout as timeouts from google.longrunning import operations_pb2 from grpc import Compression @@ -48,7 +48,7 @@ # Create the gRPC client stub with gRPC AsyncIO channel. self.operations_stub = operations_pb2.OperationsStub(channel) - default_retry = retries.Retry( + default_retry = retries.AsyncRetry( initial=0.1, # seconds maximum=60.0, # seconds multiplier=1.3, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/retry.py new/google-api-core-2.14.0/google/api_core/retry.py --- old/google-api-core-2.12.0/google/api_core/retry.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/retry.py 2023-11-10 00:14:15.000000000 +0100 @@ -62,6 +62,8 @@ import random import sys import time +import inspect +import warnings from typing import Any, Callable, TypeVar, TYPE_CHECKING import requests.exceptions @@ -84,6 +86,7 @@ _DEFAULT_MAXIMUM_DELAY = 60.0 # seconds _DEFAULT_DELAY_MULTIPLIER = 2.0 _DEFAULT_DEADLINE = 60.0 * 2.0 # seconds +_ASYNC_RETRY_WARNING = "Using the synchronous google.api_core.retry.Retry with asynchronous calls may lead to unexpected results. Please use google.api_core.retry_async.AsyncRetry instead." def if_exception_type( @@ -201,7 +204,10 @@ for sleep in sleep_generator: try: - return target() + result = target() + if inspect.isawaitable(result): + warnings.warn(_ASYNC_RETRY_WARNING) + return result # pylint: disable=broad-except # This function explicitly must deal with broad exceptions. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google/api_core/version.py new/google-api-core-2.14.0/google/api_core/version.py --- old/google-api-core-2.12.0/google/api_core/version.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/google/api_core/version.py 2023-11-10 00:14:15.000000000 +0100 @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.12.0" +__version__ = "2.14.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/google_api_core.egg-info/PKG-INFO new/google-api-core-2.14.0/google_api_core.egg-info/PKG-INFO --- old/google-api-core-2.12.0/google_api_core.egg-info/PKG-INFO 2023-09-25 18:31:13.000000000 +0200 +++ new/google-api-core-2.14.0/google_api_core.egg-info/PKG-INFO 2023-11-10 00:16:17.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-core -Version: 2.12.0 +Version: 2.14.0 Summary: Google API client core library Home-page: https://github.com/googleapis/python-api-core Author: Google LLC @@ -20,10 +20,20 @@ Classifier: Operating System :: OS Independent Classifier: Topic :: Internet Requires-Python: >=3.7 +License-File: LICENSE +Requires-Dist: googleapis-common-protos<2.0.dev0,>=1.56.2 +Requires-Dist: protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0.dev0,>=3.19.5 +Requires-Dist: google-auth<3.0.dev0,>=2.14.1 +Requires-Dist: requests<3.0.0.dev0,>=2.18.0 Provides-Extra: grpc +Requires-Dist: grpcio<2.0dev,>=1.33.2; extra == "grpc" +Requires-Dist: grpcio<2.0dev,>=1.49.1; python_version >= "3.11" and extra == "grpc" +Requires-Dist: grpcio-status<2.0.dev0,>=1.33.2; extra == "grpc" +Requires-Dist: grpcio-status<2.0.dev0,>=1.49.1; python_version >= "3.11" and extra == "grpc" Provides-Extra: grpcgcp +Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcgcp" Provides-Extra: grpcio-gcp -License-File: LICENSE +Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcio-gcp" Core Library for Google Client Libraries ======================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/tests/unit/gapic/test_method.py new/google-api-core-2.14.0/tests/unit/gapic/test_method.py --- old/google-api-core-2.12.0/tests/unit/gapic/test_method.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/tests/unit/gapic/test_method.py 2023-11-10 00:14:15.000000000 +0100 @@ -201,3 +201,24 @@ assert result == 42 method.assert_called_once_with(timeout=22, metadata=mock.ANY) + + +def test_wrap_method_with_call(): + method = mock.Mock() + mock_call = mock.Mock() + method.with_call.return_value = 42, mock_call + + wrapped_method = google.api_core.gapic_v1.method.wrap_method(method, with_call=True) + result = wrapped_method() + assert len(result) == 2 + assert result[0] == 42 + assert result[1] == mock_call + + +def test_wrap_method_with_call_not_supported(): + """Raises an error if wrapped callable doesn't have with_call method.""" + method = lambda: None # noqa: E731 + + with pytest.raises(ValueError) as exc_info: + google.api_core.gapic_v1.method.wrap_method(method, with_call=True) + assert "with_call=True is only supported for unary calls" in str(exc_info.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/tests/unit/gapic/test_routing_header.py new/google-api-core-2.14.0/tests/unit/gapic/test_routing_header.py --- old/google-api-core-2.12.0/tests/unit/gapic/test_routing_header.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/tests/unit/gapic/test_routing_header.py 2023-11-10 00:14:15.000000000 +0100 @@ -70,3 +70,34 @@ params = [("name", "meep"), ("book.read", "1")] metadata = routing_header.to_grpc_metadata(params) assert metadata == (routing_header.ROUTING_METADATA_KEY, "name=meep&book.read=1") + + +@pytest.mark.parametrize( + "key,value,expected", + [ + ("book.read", "1", "book.read=1"), + ("name", "me/ep", "name=me/ep"), + ("\\", "=", "%5C=%3D"), + (b"hello", "world", "hello=world"), + ("âï¸", "âï¸", "%E2%9C%94%EF%B8%8F=%E2%9C%8C%EF%B8%8F"), + ], +) +def test__urlencode_param(key, value, expected): + result = routing_header._urlencode_param(key, value) + assert result == expected + + +def test__urlencode_param_caching_performance(): + import time + + key = "key" * 100 + value = "value" * 100 + # time with empty cache + start_time = time.perf_counter() + routing_header._urlencode_param(key, value) + duration = time.perf_counter() - start_time + second_start_time = time.perf_counter() + routing_header._urlencode_param(key, value) + second_duration = time.perf_counter() - second_start_time + # second call should be approximately 10 times faster + assert second_duration < duration / 10 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/tests/unit/test_bidi.py new/google-api-core-2.14.0/tests/unit/test_bidi.py --- old/google-api-core-2.12.0/tests/unit/test_bidi.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/tests/unit/test_bidi.py 2023-11-10 00:14:15.000000000 +0100 @@ -804,6 +804,21 @@ while consumer.is_active: pass + def test_rpc_callback_fires_when_consumer_start_fails(self): + expected_exception = exceptions.InvalidArgument( + "test", response=grpc.StatusCode.INVALID_ARGUMENT + ) + callback = mock.Mock(spec=["__call__"]) + + rpc, _ = make_rpc() + bidi_rpc = bidi.BidiRpc(rpc) + bidi_rpc.add_done_callback(callback) + bidi_rpc._start_rpc.side_effect = expected_exception + + consumer = bidi.BackgroundConsumer(bidi_rpc, on_response=None) + consumer.start() + assert callback.call_args.args[0] == grpc.StatusCode.INVALID_ARGUMENT + def test_consumer_expected_error(self, caplog): caplog.set_level(logging.DEBUG) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-core-2.12.0/tests/unit/test_retry.py new/google-api-core-2.14.0/tests/unit/test_retry.py --- old/google-api-core-2.12.0/tests/unit/test_retry.py 2023-09-25 18:29:07.000000000 +0200 +++ new/google-api-core-2.14.0/tests/unit/test_retry.py 2023-11-10 00:14:15.000000000 +0100 @@ -129,6 +129,26 @@ sleep.assert_not_called() +@mock.patch("asyncio.sleep", autospec=True) +@mock.patch( + "google.api_core.datetime_helpers.utcnow", + return_value=datetime.datetime.min, + autospec=True, +) +@pytest.mark.asyncio +async def test_retry_target_warning_for_retry(utcnow, sleep): + predicate = retry.if_exception_type(ValueError) + target = mock.AsyncMock(spec=["__call__"]) + + with pytest.warns(Warning) as exc_info: + # Note: predicate is just a filler and doesn't affect the test + retry.retry_target(target, predicate, range(10), None) + + assert len(exc_info) == 2 + assert str(exc_info[0].message) == retry._ASYNC_RETRY_WARNING + sleep.assert_not_called() + + @mock.patch("time.sleep", autospec=True) @mock.patch("google.api_core.datetime_helpers.utcnow", autospec=True) def test_retry_target_deadline_exceeded(utcnow, sleep):