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 2024-10-02 21:33:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-google-api-core (Old) and /work/SRC/openSUSE:Factory/.python-google-api-core.new.19354 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-google-api-core" Wed Oct 2 21:33:40 2024 rev:35 rq:1205028 version:2.20.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-google-api-core/python-google-api-core.changes 2024-09-04 13:23:38.823313686 +0200 +++ /work/SRC/openSUSE:Factory/.python-google-api-core.new.19354/python-google-api-core.changes 2024-10-02 21:34:03.156048106 +0200 @@ -1,0 +2,15 @@ +Wed Oct 2 04:03:30 UTC 2024 - Steve Kowalik <steven.kowa...@suse.com> + +- Remove unneeded BuildRequires. +- Refreshed patches: + * python-google-api-core-no-mock.patch + +------------------------------------------------------------------- +Thu Sep 26 13:32:08 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 2.20.0 + * Add async unsupported paramater exception (#694) + * Add support for asynchronous rest streaming (#686) + * Add support for creating exceptions from an asynchronous response (#688) + +------------------------------------------------------------------- Old: ---- google_api_core-2.19.2.tar.gz New: ---- google_api_core-2.20.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-google-api-core.spec ++++++ --- /var/tmp/diff_new_pack.Ybg4ZR/_old 2024-10-02 21:34:03.796074714 +0200 +++ /var/tmp/diff_new_pack.Ybg4ZR/_new 2024-10-02 21:34:03.796074714 +0200 @@ -26,23 +26,17 @@ %endif %{?sle15_python_module_pythons} Name: python-google-api-core -Version: 2.19.2 +Version: 2.20.0 Release: 0 Summary: Google API client core library License: Apache-2.0 URL: https://github.com/googleapis/python-api-core Source: https://files.pythonhosted.org/packages/source/g/google_api_core/google_api_core-%{version}.tar.gz -# https://github.com/googleapis/python-api-core/issues/377 +# PATCH-FIX-UPSTREAM Based on gh#googleapis/python-api-core#713 Patch0: python-google-api-core-no-mock.patch -BuildRequires: %{python_module google-auth >= 2.14.1} +# Needed for python_sitelib/google BuildRequires: %{python_module googleapis-common-protos >= 1.53.0} -BuildRequires: %{python_module grpcio >= 1.33.2} -BuildRequires: %{python_module grpcio-status >= 1.33.2} BuildRequires: %{python_module pip} -BuildRequires: %{python_module proto-plus >= 1.22.3 with %python-proto-plus < 2.0.0dev0} -BuildRequires: %{python_module protobuf >= 3.19.5 with %python-protobuf < 5.0.0.dev0} -BuildRequires: %{python_module pytz} -BuildRequires: %{python_module requests >= 2.18.0} BuildRequires: %{python_module setuptools >= 40.3.0} BuildRequires: %{python_module wheel} %if 0%{?sle_version} >= 150400 @@ -50,7 +44,7 @@ %endif # START TESTING SECTION %if %{with test} -BuildRequires: %{python_module google-api-core >= %{version}} +BuildRequires: %{python_module google-api-core = %{version}} BuildRequires: %{python_module proto-plus} BuildRequires: %{python_module pytest-asyncio} BuildRequires: %{python_module pytest} @@ -60,11 +54,9 @@ BuildRequires: python-rpm-macros Requires: python-google-auth >= 2.14.1 Requires: python-googleapis-common-protos >= 1.53.0 -Requires: python-grpcio >= 1.33.2 -Requires: python-grpcio-status >= 1.33.2 -Requires: python-pytz +Requires: python-grpcio >= 1.49.1 +Requires: python-grpcio-status >= 1.49.1 Requires: python-requests >= 2.18.0 -Requires: python-setuptools >= 40.3.0 Requires: (python-proto-plus >= 1.22.3 with python-proto-plus < 2.0.0dev0) Requires: (python-protobuf >= 3.19.5 with python-protobuf < 5.0.0.dev0) BuildArch: noarch @@ -90,13 +82,11 @@ %pytest %endif -%clean - %if !%{with test} %files %{python_files} %license LICENSE %doc README.rst %{python_sitelib}/google/api_core -%{python_sitelib}/google_api_core-%{version}*-info +%{python_sitelib}/google_api_core-%{version}.dist-info %endif ++++++ google_api_core-2.19.2.tar.gz -> google_api_core-2.20.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/PKG-INFO new/google_api_core-2.20.0/PKG-INFO --- old/google_api_core-2.19.2/PKG-INFO 2024-08-27 22:59:16.198442000 +0200 +++ new/google_api_core-2.20.0/PKG-INFO 2024-09-19 20:22:39.577738800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-core -Version: 2.19.2 +Version: 2.20.0 Summary: Google API client core library Home-page: https://github.com/googleapis/python-api-core Author: Google LLC diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/_rest_streaming_base.py new/google_api_core-2.20.0/google/api_core/_rest_streaming_base.py --- old/google_api_core-2.19.2/google/api_core/_rest_streaming_base.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google_api_core-2.20.0/google/api_core/_rest_streaming_base.py 2024-09-19 20:20:48.000000000 +0200 @@ -0,0 +1,118 @@ +# Copyright 2024 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. + +"""Helpers for server-side streaming in REST.""" + +from collections import deque +import string +from typing import Deque, Union +import types + +import proto +import google.protobuf.message +from google.protobuf.json_format import Parse + + +class BaseResponseIterator: + """Base Iterator over REST API responses. This class should not be used directly. + + Args: + response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response + class expected to be returned from an API. + + Raises: + ValueError: If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`. + """ + + def __init__( + self, + response_message_cls: Union[proto.Message, google.protobuf.message.Message], + ): + self._response_message_cls = response_message_cls + # Contains a list of JSON responses ready to be sent to user. + self._ready_objs: Deque[str] = deque() + # Current JSON response being built. + self._obj = "" + # Keeps track of the nesting level within a JSON object. + self._level = 0 + # Keeps track whether HTTP response is currently sending values + # inside of a string value. + self._in_string = False + # Whether an escape symbol "\" was encountered. + self._escape_next = False + + self._grab = types.MethodType(self._create_grab(), self) + + def _process_chunk(self, chunk: str): + if self._level == 0: + if chunk[0] != "[": + raise ValueError( + "Can only parse array of JSON objects, instead got %s" % chunk + ) + for char in chunk: + if char == "{": + if self._level == 1: + # Level 1 corresponds to the outermost JSON object + # (i.e. the one we care about). + self._obj = "" + if not self._in_string: + self._level += 1 + self._obj += char + elif char == "}": + self._obj += char + if not self._in_string: + self._level -= 1 + if not self._in_string and self._level == 1: + self._ready_objs.append(self._obj) + elif char == '"': + # Helps to deal with an escaped quotes inside of a string. + if not self._escape_next: + self._in_string = not self._in_string + self._obj += char + elif char in string.whitespace: + if self._in_string: + self._obj += char + elif char == "[": + if self._level == 0: + self._level += 1 + else: + self._obj += char + elif char == "]": + if self._level == 1: + self._level -= 1 + else: + self._obj += char + else: + self._obj += char + self._escape_next = not self._escape_next if char == "\\" else False + + def _create_grab(self): + if issubclass(self._response_message_cls, proto.Message): + + def grab(this): + return this._response_message_cls.from_json( + this._ready_objs.popleft(), ignore_unknown_fields=True + ) + + return grab + elif issubclass(self._response_message_cls, google.protobuf.message.Message): + + def grab(this): + return Parse(this._ready_objs.popleft(), this._response_message_cls()) + + return grab + else: + raise ValueError( + "Response message class must be a subclass of proto.Message or google.protobuf.message.Message." + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/exceptions.py new/google_api_core-2.20.0/google/api_core/exceptions.py --- old/google_api_core-2.19.2/google/api_core/exceptions.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/google/api_core/exceptions.py 2024-09-19 20:20:48.000000000 +0200 @@ -22,7 +22,7 @@ from __future__ import unicode_literals import http.client -from typing import Dict +from typing import Optional, Dict from typing import Union import warnings @@ -442,6 +442,12 @@ grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None +class AsyncRestUnsupportedParameterError(NotImplementedError): + """Raised when an unsupported parameter is configured against async rest transport.""" + + pass + + def exception_class_for_http_status(status_code): """Return the exception class for a specific HTTP status code. @@ -476,22 +482,37 @@ return error -def from_http_response(response): - """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`. +def _format_rest_error_message(error, method, url): + method = method.upper() if method else None + message = "{method} {url}: {error}".format( + method=method, + url=url, + error=error, + ) + return message + + +# NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared +# to `format_http_response_error` which expects a more abstract response from google.auth and is +# compatible with both sync and async response types. +# TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response. +def format_http_response_error( + response, method: str, url: str, payload: Optional[Dict] = None +): + """Create a :class:`GoogleAPICallError` from a google auth rest response. Args: - response (requests.Response): The HTTP response. + response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response. + method Optional(str): The HTTP request method. + url Optional(str): The HTTP request url. + payload Optional(dict): The HTTP response payload. If not passed in, it is read from response for a response type of google.auth.transport.Response. Returns: GoogleAPICallError: An instance of the appropriate subclass of :class:`GoogleAPICallError`, with the message and errors populated from the response. """ - try: - payload = response.json() - except ValueError: - payload = {"error": {"message": response.text or "unknown error"}} - + payload = {} if not payload else payload error_message = payload.get("error", {}).get("message", "unknown error") errors = payload.get("error", {}).get("errors", ()) # In JSON, details are already formatted in developer-friendly way. @@ -504,12 +525,7 @@ ) ) error_info = error_info[0] if error_info else None - - message = "{method} {url}: {error}".format( - method=response.request.method, - url=response.request.url, - error=error_message, - ) + message = _format_rest_error_message(error_message, method, url) exception = from_http_status( response.status_code, @@ -522,6 +538,26 @@ return exception +def from_http_response(response): + """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`. + + Args: + response (requests.Response): The HTTP response. + + Returns: + GoogleAPICallError: An instance of the appropriate subclass of + :class:`GoogleAPICallError`, with the message and errors populated + from the response. + """ + try: + payload = response.json() + except ValueError: + payload = {"error": {"message": response.text or "unknown error"}} + return format_http_response_error( + response, response.request.method, response.request.url, payload + ) + + def exception_class_for_grpc_status(status_code): """Return the exception class for a specific :class:`grpc.StatusCode`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/gapic_v1/method_async.py new/google_api_core-2.20.0/google/api_core/gapic_v1/method_async.py --- old/google_api_core-2.19.2/google/api_core/gapic_v1/method_async.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/google/api_core/gapic_v1/method_async.py 2024-09-19 20:20:48.000000000 +0200 @@ -25,6 +25,8 @@ from google.api_core.gapic_v1.method import DEFAULT # noqa: F401 from google.api_core.gapic_v1.method import USE_DEFAULT_METADATA # noqa: F401 +_DEFAULT_ASYNC_TRANSPORT_KIND = "grpc_asyncio" + def wrap_method( func, @@ -32,6 +34,7 @@ default_timeout=None, default_compression=None, client_info=client_info.DEFAULT_CLIENT_INFO, + kind=_DEFAULT_ASYNC_TRANSPORT_KIND, ): """Wrap an async RPC method with common behavior. @@ -40,7 +43,8 @@ and ``compression`` arguments and applies the common error mapping, retry, timeout, metadata, and compression behavior to the low-level RPC method. """ - func = grpc_helpers_async.wrap_errors(func) + if kind == _DEFAULT_ASYNC_TRANSPORT_KIND: + func = grpc_helpers_async.wrap_errors(func) metadata = [client_info.to_grpc_metadata()] if client_info is not None else None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/rest_streaming.py new/google_api_core-2.20.0/google/api_core/rest_streaming.py --- old/google_api_core-2.19.2/google/api_core/rest_streaming.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/google/api_core/rest_streaming.py 2024-09-19 20:20:48.000000000 +0200 @@ -14,17 +14,15 @@ """Helpers for server-side streaming in REST.""" -from collections import deque -import string -from typing import Deque, Union +from typing import Union import proto import requests import google.protobuf.message -from google.protobuf.json_format import Parse +from google.api_core._rest_streaming_base import BaseResponseIterator -class ResponseIterator: +class ResponseIterator(BaseResponseIterator): """Iterator over REST API responses. Args: @@ -33,7 +31,8 @@ class expected to be returned from an API. Raises: - ValueError: If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`. + ValueError: + - If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`. """ def __init__( @@ -42,68 +41,16 @@ response_message_cls: Union[proto.Message, google.protobuf.message.Message], ): self._response = response - self._response_message_cls = response_message_cls # Inner iterator over HTTP response's content. self._response_itr = self._response.iter_content(decode_unicode=True) - # Contains a list of JSON responses ready to be sent to user. - self._ready_objs: Deque[str] = deque() - # Current JSON response being built. - self._obj = "" - # Keeps track of the nesting level within a JSON object. - self._level = 0 - # Keeps track whether HTTP response is currently sending values - # inside of a string value. - self._in_string = False - # Whether an escape symbol "\" was encountered. - self._escape_next = False + super(ResponseIterator, self).__init__( + response_message_cls=response_message_cls + ) def cancel(self): """Cancel existing streaming operation.""" self._response.close() - def _process_chunk(self, chunk: str): - if self._level == 0: - if chunk[0] != "[": - raise ValueError( - "Can only parse array of JSON objects, instead got %s" % chunk - ) - for char in chunk: - if char == "{": - if self._level == 1: - # Level 1 corresponds to the outermost JSON object - # (i.e. the one we care about). - self._obj = "" - if not self._in_string: - self._level += 1 - self._obj += char - elif char == "}": - self._obj += char - if not self._in_string: - self._level -= 1 - if not self._in_string and self._level == 1: - self._ready_objs.append(self._obj) - elif char == '"': - # Helps to deal with an escaped quotes inside of a string. - if not self._escape_next: - self._in_string = not self._in_string - self._obj += char - elif char in string.whitespace: - if self._in_string: - self._obj += char - elif char == "[": - if self._level == 0: - self._level += 1 - else: - self._obj += char - elif char == "]": - if self._level == 1: - self._level -= 1 - else: - self._obj += char - else: - self._obj += char - self._escape_next = not self._escape_next if char == "\\" else False - def __next__(self): while not self._ready_objs: try: @@ -115,18 +62,5 @@ raise e return self._grab() - def _grab(self): - # Add extra quotes to make json.loads happy. - if issubclass(self._response_message_cls, proto.Message): - return self._response_message_cls.from_json( - self._ready_objs.popleft(), ignore_unknown_fields=True - ) - elif issubclass(self._response_message_cls, google.protobuf.message.Message): - return Parse(self._ready_objs.popleft(), self._response_message_cls()) - else: - raise ValueError( - "Response message class must be a subclass of proto.Message or google.protobuf.message.Message." - ) - def __iter__(self): return self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/rest_streaming_async.py new/google_api_core-2.20.0/google/api_core/rest_streaming_async.py --- old/google_api_core-2.19.2/google/api_core/rest_streaming_async.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google_api_core-2.20.0/google/api_core/rest_streaming_async.py 2024-09-19 20:20:48.000000000 +0200 @@ -0,0 +1,83 @@ +# Copyright 2024 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. + +"""Helpers for asynchronous server-side streaming in REST.""" + +from typing import Union + +import proto + +try: + import google.auth.aio.transport +except ImportError as e: # pragma: NO COVER + raise ImportError( + "google-auth>=2.35.0 is required to use asynchronous rest streaming." + ) from e + +import google.protobuf.message +from google.api_core._rest_streaming_base import BaseResponseIterator + + +class AsyncResponseIterator(BaseResponseIterator): + """Asynchronous Iterator over REST API responses. + + Args: + response (google.auth.aio.transport.Response): An API response object. + response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response + class expected to be returned from an API. + + Raises: + ValueError: + - If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`. + """ + + def __init__( + self, + response: google.auth.aio.transport.Response, + response_message_cls: Union[proto.Message, google.protobuf.message.Message], + ): + self._response = response + self._chunk_size = 1024 + self._response_itr = self._response.content().__aiter__() + super(AsyncResponseIterator, self).__init__( + response_message_cls=response_message_cls + ) + + async def __aenter__(self): + return self + + async def cancel(self): + """Cancel existing streaming operation.""" + await self._response.close() + + async def __anext__(self): + while not self._ready_objs: + try: + chunk = await self._response_itr.__anext__() + chunk = chunk.decode("utf-8") + self._process_chunk(chunk) + except StopAsyncIteration as e: + if self._level > 0: + raise ValueError("i Unfinished stream: %s" % self._obj) + raise e + except ValueError as e: + raise e + return self._grab() + + def __aiter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + """Cancel existing async streaming operation.""" + await self._response.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google/api_core/version.py new/google_api_core-2.20.0/google/api_core/version.py --- old/google_api_core-2.19.2/google/api_core/version.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/google/api_core/version.py 2024-09-19 20:20:48.000000000 +0200 @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.19.2" +__version__ = "2.20.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google_api_core.egg-info/PKG-INFO new/google_api_core-2.20.0/google_api_core.egg-info/PKG-INFO --- old/google_api_core-2.19.2/google_api_core.egg-info/PKG-INFO 2024-08-27 22:59:16.000000000 +0200 +++ new/google_api_core-2.20.0/google_api_core.egg-info/PKG-INFO 2024-09-19 20:22:39.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-core -Version: 2.19.2 +Version: 2.20.0 Summary: Google API client core library Home-page: https://github.com/googleapis/python-api-core Author: Google LLC diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/google_api_core.egg-info/SOURCES.txt new/google_api_core-2.20.0/google_api_core.egg-info/SOURCES.txt --- old/google_api_core-2.19.2/google_api_core.egg-info/SOURCES.txt 2024-08-27 22:59:16.000000000 +0200 +++ new/google_api_core-2.20.0/google_api_core.egg-info/SOURCES.txt 2024-09-19 20:22:39.000000000 +0200 @@ -4,6 +4,7 @@ setup.cfg setup.py google/api_core/__init__.py +google/api_core/_rest_streaming_base.py google/api_core/bidi.py google/api_core/client_info.py google/api_core/client_options.py @@ -23,6 +24,7 @@ google/api_core/py.typed google/api_core/rest_helpers.py google/api_core/rest_streaming.py +google/api_core/rest_streaming_async.py google/api_core/retry_async.py google/api_core/timeout.py google/api_core/universe.py @@ -62,10 +64,12 @@ google_api_core.egg-info/requires.txt google_api_core.egg-info/top_level.txt tests/__init__.py +tests/helpers.py tests/asyncio/__init__.py tests/asyncio/test_grpc_helpers_async.py tests/asyncio/test_operation_async.py tests/asyncio/test_page_iterator_async.py +tests/asyncio/test_rest_streaming_async.py tests/asyncio/future/__init__.py tests/asyncio/future/test_async_future.py tests/asyncio/gapic/test_config_async.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/tests/asyncio/gapic/test_method_async.py new/google_api_core-2.20.0/tests/asyncio/gapic/test_method_async.py --- old/google_api_core-2.19.2/tests/asyncio/gapic/test_method_async.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/tests/asyncio/gapic/test_method_async.py 2024-09-19 20:20:48.000000000 +0200 @@ -252,3 +252,14 @@ assert result == 42 method.assert_called_once_with(timeout=22, metadata=mock.ANY) + + +@pytest.mark.asyncio +async def test_wrap_method_without_wrap_errors(): + fake_call = mock.AsyncMock() + + wrapped_method = gapic_v1.method_async.wrap_method(fake_call, kind="rest") + with mock.patch("google.api_core.grpc_helpers_async.wrap_errors") as method: + await wrapped_method() + + method.assert_not_called() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/tests/asyncio/test_rest_streaming_async.py new/google_api_core-2.20.0/tests/asyncio/test_rest_streaming_async.py --- old/google_api_core-2.19.2/tests/asyncio/test_rest_streaming_async.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google_api_core-2.20.0/tests/asyncio/test_rest_streaming_async.py 2024-09-19 20:20:48.000000000 +0200 @@ -0,0 +1,378 @@ +# Copyright 2024 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. + +# TODO: set random.seed explicitly in each test function. +# See related issue: https://github.com/googleapis/python-api-core/issues/689. + +import pytest # noqa: I202 +import mock + +import datetime +import logging +import random +import time +from typing import List, AsyncIterator + +import proto + +try: + from google.auth.aio.transport import Response + + AUTH_AIO_INSTALLED = True +except ImportError: + AUTH_AIO_INSTALLED = False + +if not AUTH_AIO_INSTALLED: # pragma: NO COVER + pytest.skip( + "google-auth>=2.35.0 is required to use asynchronous rest streaming.", + allow_module_level=True, + ) + +from google.api_core import rest_streaming_async +from google.api import http_pb2 +from google.api import httpbody_pb2 + + +from ..helpers import Composer, Song, EchoResponse, parse_responses + + +__protobuf__ = proto.module(package=__name__) +SEED = int(time.time()) +logging.info(f"Starting async rest streaming tests with random seed: {SEED}") +random.seed(SEED) + + +async def mock_async_gen(data, chunk_size=1): + for i in range(0, len(data)): # pragma: NO COVER + chunk = data[i : i + chunk_size] + yield chunk.encode("utf-8") + + +class ResponseMock(Response): + class _ResponseItr(AsyncIterator[bytes]): + def __init__(self, _response_bytes: bytes, random_split=False): + self._responses_bytes = _response_bytes + self._idx = 0 + self._random_split = random_split + + def __aiter__(self): + return self + + async def __anext__(self): + if self._idx >= len(self._responses_bytes): + raise StopAsyncIteration + if self._random_split: + n = random.randint(1, len(self._responses_bytes[self._idx :])) + else: + n = 1 + x = self._responses_bytes[self._idx : self._idx + n] + self._idx += n + return x + + def __init__( + self, + responses: List[proto.Message], + response_cls, + random_split=False, + ): + self._responses = responses + self._random_split = random_split + self._response_message_cls = response_cls + + def _parse_responses(self): + return parse_responses(self._response_message_cls, self._responses) + + @property + async def headers(self): + raise NotImplementedError() + + @property + async def status_code(self): + raise NotImplementedError() + + async def close(self): + raise NotImplementedError() + + async def content(self, chunk_size=None): + itr = self._ResponseItr( + self._parse_responses(), random_split=self._random_split + ) + async for chunk in itr: + yield chunk + + async def read(self): + raise NotImplementedError() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "random_split,resp_message_is_proto_plus", + [(False, True), (False, False)], +) +async def test_next_simple(random_split, resp_message_is_proto_plus): + if resp_message_is_proto_plus: + response_type = EchoResponse + responses = [EchoResponse(content="hello world"), EchoResponse(content="yes")] + else: + response_type = httpbody_pb2.HttpBody + responses = [ + httpbody_pb2.HttpBody(content_type="hello world"), + httpbody_pb2.HttpBody(content_type="yes"), + ] + + resp = ResponseMock( + responses=responses, random_split=random_split, response_cls=response_type + ) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + idx = 0 + async for response in itr: + assert response == responses[idx] + idx += 1 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "random_split,resp_message_is_proto_plus", + [ + (True, True), + (False, True), + (True, False), + (False, False), + ], +) +async def test_next_nested(random_split, resp_message_is_proto_plus): + if resp_message_is_proto_plus: + response_type = Song + responses = [ + Song(title="some song", composer=Composer(given_name="some name")), + Song(title="another song", date_added=datetime.datetime(2021, 12, 17)), + ] + else: + # Although `http_pb2.HttpRule`` is used in the response, any response message + # can be used which meets this criteria for the test of having a nested field. + response_type = http_pb2.HttpRule + responses = [ + http_pb2.HttpRule( + selector="some selector", + custom=http_pb2.CustomHttpPattern(kind="some kind"), + ), + http_pb2.HttpRule( + selector="another selector", + custom=http_pb2.CustomHttpPattern(path="some path"), + ), + ] + resp = ResponseMock( + responses=responses, random_split=random_split, response_cls=response_type + ) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + idx = 0 + async for response in itr: + assert response == responses[idx] + idx += 1 + assert idx == len(responses) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "random_split,resp_message_is_proto_plus", + [ + (True, True), + (False, True), + (True, False), + (False, False), + ], +) +async def test_next_stress(random_split, resp_message_is_proto_plus): + n = 50 + if resp_message_is_proto_plus: + response_type = Song + responses = [ + Song(title="title_%d" % i, composer=Composer(given_name="name_%d" % i)) + for i in range(n) + ] + else: + response_type = http_pb2.HttpRule + responses = [ + http_pb2.HttpRule( + selector="selector_%d" % i, + custom=http_pb2.CustomHttpPattern(path="path_%d" % i), + ) + for i in range(n) + ] + resp = ResponseMock( + responses=responses, random_split=random_split, response_cls=response_type + ) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + idx = 0 + async for response in itr: + assert response == responses[idx] + idx += 1 + assert idx == n + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "random_split,resp_message_is_proto_plus", + [ + (True, True), + (False, True), + (True, False), + (False, False), + ], +) +async def test_next_escaped_characters_in_string( + random_split, resp_message_is_proto_plus +): + if resp_message_is_proto_plus: + response_type = Song + composer_with_relateds = Composer() + relateds = ["Artist A", "Artist B"] + composer_with_relateds.relateds = relateds + + responses = [ + Song( + title='ti"tle\nfoo\tbar{}', composer=Composer(given_name="name\n\n\n") + ), + Song( + title='{"this is weird": "totally"}', + composer=Composer(given_name="\\{}\\"), + ), + Song(title='\\{"key": ["value",]}\\', composer=composer_with_relateds), + ] + else: + response_type = http_pb2.Http + responses = [ + http_pb2.Http( + rules=[ + http_pb2.HttpRule( + selector='ti"tle\nfoo\tbar{}', + custom=http_pb2.CustomHttpPattern(kind="name\n\n\n"), + ) + ] + ), + http_pb2.Http( + rules=[ + http_pb2.HttpRule( + selector='{"this is weird": "totally"}', + custom=http_pb2.CustomHttpPattern(kind="\\{}\\"), + ) + ] + ), + http_pb2.Http( + rules=[ + http_pb2.HttpRule( + selector='\\{"key": ["value",]}\\', + custom=http_pb2.CustomHttpPattern(kind="\\{}\\"), + ) + ] + ), + ] + resp = ResponseMock( + responses=responses, random_split=random_split, response_cls=response_type + ) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + idx = 0 + async for response in itr: + assert response == responses[idx] + idx += 1 + assert idx == len(responses) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody]) +async def test_next_not_array(response_type): + + data = '{"hello": 0}' + with mock.patch.object( + ResponseMock, "content", return_value=mock_async_gen(data) + ) as mock_method: + resp = ResponseMock(responses=[], response_cls=response_type) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + with pytest.raises(ValueError): + await itr.__anext__() + mock_method.assert_called_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody]) +async def test_cancel(response_type): + with mock.patch.object( + ResponseMock, "close", new_callable=mock.AsyncMock + ) as mock_method: + resp = ResponseMock(responses=[], response_cls=response_type) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + await itr.cancel() + mock_method.assert_called_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody]) +async def test_iterator_as_context_manager(response_type): + with mock.patch.object( + ResponseMock, "close", new_callable=mock.AsyncMock + ) as mock_method: + resp = ResponseMock(responses=[], response_cls=response_type) + async with rest_streaming_async.AsyncResponseIterator(resp, response_type): + pass + mock_method.assert_called_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "response_type,return_value", + [ + (EchoResponse, bytes('[{"content": "hello"}, {', "utf-8")), + (httpbody_pb2.HttpBody, bytes('[{"content_type": "hello"}, {', "utf-8")), + ], +) +async def test_check_buffer(response_type, return_value): + with mock.patch.object( + ResponseMock, + "_parse_responses", + return_value=return_value, + ): + resp = ResponseMock(responses=[], response_cls=response_type) + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + with pytest.raises(ValueError): + await itr.__anext__() + await itr.__anext__() + + +@pytest.mark.asyncio +@pytest.mark.parametrize("response_type", [EchoResponse, httpbody_pb2.HttpBody]) +async def test_next_html(response_type): + + data = "<!DOCTYPE html><html></html>" + with mock.patch.object( + ResponseMock, "content", return_value=mock_async_gen(data) + ) as mock_method: + resp = ResponseMock(responses=[], response_cls=response_type) + + itr = rest_streaming_async.AsyncResponseIterator(resp, response_type) + with pytest.raises(ValueError): + await itr.__anext__() + mock_method.assert_called_once() + + +@pytest.mark.asyncio +async def test_invalid_response_class(): + class SomeClass: + pass + + resp = ResponseMock(responses=[], response_cls=SomeClass) + with pytest.raises( + ValueError, + match="Response message class must be a subclass of proto.Message or google.protobuf.message.Message", + ): + rest_streaming_async.AsyncResponseIterator(resp, SomeClass) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/tests/helpers.py new/google_api_core-2.20.0/tests/helpers.py --- old/google_api_core-2.19.2/tests/helpers.py 1970-01-01 01:00:00.000000000 +0100 +++ new/google_api_core-2.20.0/tests/helpers.py 2024-09-19 20:20:48.000000000 +0200 @@ -0,0 +1,71 @@ +# Copyright 2024 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. + +"""Helpers for tests""" + +import logging +from typing import List + +import proto + +from google.protobuf import duration_pb2 +from google.protobuf import timestamp_pb2 +from google.protobuf.json_format import MessageToJson + + +class Genre(proto.Enum): + GENRE_UNSPECIFIED = 0 + CLASSICAL = 1 + JAZZ = 2 + ROCK = 3 + + +class Composer(proto.Message): + given_name = proto.Field(proto.STRING, number=1) + family_name = proto.Field(proto.STRING, number=2) + relateds = proto.RepeatedField(proto.STRING, number=3) + indices = proto.MapField(proto.STRING, proto.STRING, number=4) + + +class Song(proto.Message): + composer = proto.Field(Composer, number=1) + title = proto.Field(proto.STRING, number=2) + lyrics = proto.Field(proto.STRING, number=3) + year = proto.Field(proto.INT32, number=4) + genre = proto.Field(Genre, number=5) + is_five_mins_longer = proto.Field(proto.BOOL, number=6) + score = proto.Field(proto.DOUBLE, number=7) + likes = proto.Field(proto.INT64, number=8) + duration = proto.Field(duration_pb2.Duration, number=9) + date_added = proto.Field(timestamp_pb2.Timestamp, number=10) + + +class EchoResponse(proto.Message): + content = proto.Field(proto.STRING, number=1) + + +def parse_responses(response_message_cls, all_responses: List[proto.Message]) -> bytes: + # json.dumps returns a string surrounded with quotes that need to be stripped + # in order to be an actual JSON. + json_responses = [ + ( + response_message_cls.to_json(response).strip('"') + if issubclass(response_message_cls, proto.Message) + else MessageToJson(response).strip('"') + ) + for response in all_responses + ] + logging.info(f"Sending JSON stream: {json_responses}") + ret_val = "[{}]".format(",".join(json_responses)) + return bytes(ret_val, "utf-8") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google_api_core-2.19.2/tests/unit/test_rest_streaming.py new/google_api_core-2.20.0/tests/unit/test_rest_streaming.py --- old/google_api_core-2.19.2/tests/unit/test_rest_streaming.py 2024-08-27 22:57:23.000000000 +0200 +++ new/google_api_core-2.20.0/tests/unit/test_rest_streaming.py 2024-09-19 20:20:48.000000000 +0200 @@ -26,48 +26,16 @@ from google.api_core import rest_streaming from google.api import http_pb2 from google.api import httpbody_pb2 -from google.protobuf import duration_pb2 -from google.protobuf import timestamp_pb2 -from google.protobuf.json_format import MessageToJson + +from ..helpers import Composer, Song, EchoResponse, parse_responses __protobuf__ = proto.module(package=__name__) SEED = int(time.time()) -logging.info(f"Starting rest streaming tests with random seed: {SEED}") +logging.info(f"Starting sync rest streaming tests with random seed: {SEED}") random.seed(SEED) -class Genre(proto.Enum): - GENRE_UNSPECIFIED = 0 - CLASSICAL = 1 - JAZZ = 2 - ROCK = 3 - - -class Composer(proto.Message): - given_name = proto.Field(proto.STRING, number=1) - family_name = proto.Field(proto.STRING, number=2) - relateds = proto.RepeatedField(proto.STRING, number=3) - indices = proto.MapField(proto.STRING, proto.STRING, number=4) - - -class Song(proto.Message): - composer = proto.Field(Composer, number=1) - title = proto.Field(proto.STRING, number=2) - lyrics = proto.Field(proto.STRING, number=3) - year = proto.Field(proto.INT32, number=4) - genre = proto.Field(Genre, number=5) - is_five_mins_longer = proto.Field(proto.BOOL, number=6) - score = proto.Field(proto.DOUBLE, number=7) - likes = proto.Field(proto.INT64, number=8) - duration = proto.Field(duration_pb2.Duration, number=9) - date_added = proto.Field(timestamp_pb2.Timestamp, number=10) - - -class EchoResponse(proto.Message): - content = proto.Field(proto.STRING, number=1) - - class ResponseMock(requests.Response): class _ResponseItr: def __init__(self, _response_bytes: bytes, random_split=False): @@ -97,27 +65,15 @@ self._random_split = random_split self._response_message_cls = response_cls - def _parse_responses(self, responses: List[proto.Message]) -> bytes: - # json.dumps returns a string surrounded with quotes that need to be stripped - # in order to be an actual JSON. - json_responses = [ - ( - self._response_message_cls.to_json(r).strip('"') - if issubclass(self._response_message_cls, proto.Message) - else MessageToJson(r).strip('"') - ) - for r in responses - ] - logging.info(f"Sending JSON stream: {json_responses}") - ret_val = "[{}]".format(",".join(json_responses)) - return bytes(ret_val, "utf-8") + def _parse_responses(self): + return parse_responses(self._response_message_cls, self._responses) def close(self): raise NotImplementedError() def iter_content(self, *args, **kwargs): return self._ResponseItr( - self._parse_responses(self._responses), + self._parse_responses(), random_split=self._random_split, ) @@ -333,9 +289,8 @@ pass resp = ResponseMock(responses=[], response_cls=SomeClass) - response_iterator = rest_streaming.ResponseIterator(resp, SomeClass) with pytest.raises( ValueError, match="Response message class must be a subclass of proto.Message or google.protobuf.message.Message", ): - response_iterator._grab() + rest_streaming.ResponseIterator(resp, SomeClass) ++++++ python-google-api-core-no-mock.patch ++++++ --- /var/tmp/diff_new_pack.Ybg4ZR/_old 2024-10-02 21:34:03.916079703 +0200 +++ /var/tmp/diff_new_pack.Ybg4ZR/_new 2024-10-02 21:34:03.920079869 +0200 @@ -1,102 +1,183 @@ -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/future/test_async_future.py google-api-core-2.19.0/tests/asyncio/future/test_async_future.py ---- google-api-core-2.19.0.orig/tests/asyncio/future/test_async_future.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/future/test_async_future.py 2024-05-16 16:03:34.320181259 +0200 -@@ -14,7 +14,7 @@ +From 5f28e210306de47ff5a9fa3b4bf48ac6ceb13e85 Mon Sep 17 00:00:00 2001 +From: Steve Kowalik <ste...@wedontsleep.org> +Date: Wed, 2 Oct 2024 13:50:33 +1000 +Subject: [PATCH] test: Switch to unittest.mock from mock + +Now that the minimum supported version of Python is 3.7, we can stop +using the external mock requirement, and import it from unittest. I have +also attempted to keep imports ordered. + +Fixes #377 +--- + noxfile.py | 2 -- + tests/asyncio/future/test_async_future.py | 2 +- + tests/asyncio/gapic/test_method_async.py | 2 +- + tests/asyncio/operations_v1/test_operations_async_client.py | 3 ++- + tests/asyncio/retry/test_retry_streaming_async.py | 4 ++-- + tests/asyncio/retry/test_retry_unary_async.py | 2 +- + tests/asyncio/test_grpc_helpers_async.py | 3 ++- + tests/asyncio/test_operation_async.py | 3 ++- + tests/asyncio/test_page_iterator_async.py | 2 +- + tests/asyncio/test_rest_streaming_async.py | 6 +++--- + tests/unit/future/test__helpers.py | 2 +- + tests/unit/future/test_polling.py | 2 +- + tests/unit/gapic/test_method.py | 2 +- + tests/unit/operations_v1/test_operations_rest_client.py | 2 +- + tests/unit/retry/test_retry_base.py | 2 +- + tests/unit/retry/test_retry_streaming.py | 2 +- + tests/unit/retry/test_retry_unary.py | 2 +- + tests/unit/test_bidi.py | 2 +- + tests/unit/test_exceptions.py | 2 +- + tests/unit/test_extended_operation.py | 2 +- + tests/unit/test_grpc_helpers.py | 3 ++- + tests/unit/test_operation.py | 3 ++- + tests/unit/test_page_iterator.py | 2 +- + tests/unit/test_path_template.py | 2 +- + tests/unit/test_timeout.py | 3 +-- + 25 files changed, 32 insertions(+), 30 deletions(-) + +diff --git a/tests/asyncio/future/test_async_future.py b/tests/asyncio/future/test_async_future.py +index 0cfe6773..659f41cf 100644 +--- a/tests/asyncio/future/test_async_future.py ++++ b/tests/asyncio/future/test_async_future.py +@@ -13,8 +13,8 @@ + # limitations under the License. import asyncio ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/gapic/test_method_async.py google-api-core-2.19.0/tests/asyncio/gapic/test_method_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/gapic/test_method_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/gapic/test_method_async.py 2024-05-16 16:03:34.310181235 +0200 -@@ -14,7 +14,7 @@ +diff --git a/tests/asyncio/gapic/test_method_async.py b/tests/asyncio/gapic/test_method_async.py +index f64157b4..026993e2 100644 +--- a/tests/asyncio/gapic/test_method_async.py ++++ b/tests/asyncio/gapic/test_method_async.py +@@ -13,8 +13,8 @@ + # limitations under the License. import datetime ++from unittest import mock -import mock -+from unittest import mock import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/operations_v1/test_operations_async_client.py google-api-core-2.19.0/tests/asyncio/operations_v1/test_operations_async_client.py ---- google-api-core-2.19.0.orig/tests/asyncio/operations_v1/test_operations_async_client.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/operations_v1/test_operations_async_client.py 2024-05-16 16:03:34.300181212 +0200 -@@ -12,7 +12,7 @@ +diff --git a/tests/asyncio/operations_v1/test_operations_async_client.py b/tests/asyncio/operations_v1/test_operations_async_client.py +index 19ac9b56..e5b20dcd 100644 +--- a/tests/asyncio/operations_v1/test_operations_async_client.py ++++ b/tests/asyncio/operations_v1/test_operations_async_client.py +@@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +from unittest import mock ++ import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/retry/test_retry_streaming_async.py google-api-core-2.19.0/tests/asyncio/retry/test_retry_streaming_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/retry/test_retry_streaming_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/retry/test_retry_streaming_async.py 2024-05-16 16:03:34.296847871 +0200 -@@ -16,7 +16,7 @@ +diff --git a/tests/asyncio/retry/test_retry_streaming_async.py b/tests/asyncio/retry/test_retry_streaming_async.py +index 28ae6ff1..a54fc125 100644 +--- a/tests/asyncio/retry/test_retry_streaming_async.py ++++ b/tests/asyncio/retry/test_retry_streaming_async.py +@@ -12,11 +12,11 @@ + # See the License for the specific language governing permissions and + # limitations under the License. + ++import asyncio + import datetime import re - import asyncio +-import asyncio ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/retry/test_retry_unary_async.py google-api-core-2.19.0/tests/asyncio/retry/test_retry_unary_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/retry/test_retry_unary_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/retry/test_retry_unary_async.py 2024-05-16 16:03:34.293514530 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/asyncio/retry/test_retry_unary_async.py b/tests/asyncio/retry/test_retry_unary_async.py +index fc2f572b..032bab75 100644 +--- a/tests/asyncio/retry/test_retry_unary_async.py ++++ b/tests/asyncio/retry/test_retry_unary_async.py +@@ -14,8 +14,8 @@ + import datetime import re ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/test_grpc_helpers_async.py google-api-core-2.19.0/tests/asyncio/test_grpc_helpers_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/test_grpc_helpers_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/test_grpc_helpers_async.py 2024-05-16 16:03:34.286847847 +0200 -@@ -12,7 +12,7 @@ +diff --git a/tests/asyncio/test_grpc_helpers_async.py b/tests/asyncio/test_grpc_helpers_async.py +index 1a408ccd..a53de083 100644 +--- a/tests/asyncio/test_grpc_helpers_async.py ++++ b/tests/asyncio/test_grpc_helpers_async.py +@@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +from unittest import mock ++ import pytest # noqa: I202 try: -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/test_operation_async.py google-api-core-2.19.0/tests/asyncio/test_operation_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/test_operation_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/test_operation_async.py 2024-05-16 16:03:34.316847918 +0200 -@@ -13,7 +13,7 @@ +diff --git a/tests/asyncio/test_operation_async.py b/tests/asyncio/test_operation_async.py +index 127ba634..f7ebad8a 100644 +--- a/tests/asyncio/test_operation_async.py ++++ b/tests/asyncio/test_operation_async.py +@@ -13,7 +13,8 @@ # limitations under the License. -import mock +from unittest import mock ++ import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/asyncio/test_page_iterator_async.py google-api-core-2.19.0/tests/asyncio/test_page_iterator_async.py ---- google-api-core-2.19.0.orig/tests/asyncio/test_page_iterator_async.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/asyncio/test_page_iterator_async.py 2024-05-16 16:03:34.283514506 +0200 -@@ -14,7 +14,7 @@ +diff --git a/tests/asyncio/test_page_iterator_async.py b/tests/asyncio/test_page_iterator_async.py +index 75f9e1cf..e661bd26 100644 +--- a/tests/asyncio/test_page_iterator_async.py ++++ b/tests/asyncio/test_page_iterator_async.py +@@ -13,8 +13,8 @@ + # limitations under the License. import inspect ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import page_iterator_async -diff -Nru google-api-core-2.19.0.orig/tests/unit/future/test__helpers.py google-api-core-2.19.0/tests/unit/future/test__helpers.py ---- google-api-core-2.19.0.orig/tests/unit/future/test__helpers.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/future/test__helpers.py 2024-05-16 16:03:34.433514858 +0200 +diff --git a/tests/asyncio/test_rest_streaming_async.py b/tests/asyncio/test_rest_streaming_async.py +index da5b1c8d..f726cbee 100644 +--- a/tests/asyncio/test_rest_streaming_async.py ++++ b/tests/asyncio/test_rest_streaming_async.py +@@ -15,14 +15,14 @@ + # TODO: set random.seed explicitly in each test function. + # See related issue: https://github.com/googleapis/python-api-core/issues/689. + +-import pytest # noqa: I202 +-import mock +- + import datetime + import logging + import random + import time + from typing import List, AsyncIterator ++from unittest import mock ++ ++import pytest # noqa: I202 + + import proto + +diff --git a/tests/unit/future/test__helpers.py b/tests/unit/future/test__helpers.py +index 98afc599..a37efdd4 100644 +--- a/tests/unit/future/test__helpers.py ++++ b/tests/unit/future/test__helpers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. @@ -106,169 +187,197 @@ from google.api_core.future import _helpers -diff -Nru google-api-core-2.19.0.orig/tests/unit/future/test_polling.py google-api-core-2.19.0/tests/unit/future/test_polling.py ---- google-api-core-2.19.0.orig/tests/unit/future/test_polling.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/future/test_polling.py 2024-05-16 16:03:34.433514858 +0200 -@@ -16,7 +16,7 @@ +diff --git a/tests/unit/future/test_polling.py b/tests/unit/future/test_polling.py +index f5d9b4f1..2f66f230 100644 +--- a/tests/unit/future/test_polling.py ++++ b/tests/unit/future/test_polling.py +@@ -15,8 +15,8 @@ + import concurrent.futures import threading import time ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions, retry -diff -Nru google-api-core-2.19.0.orig/tests/unit/gapic/test_method.py google-api-core-2.19.0/tests/unit/gapic/test_method.py ---- google-api-core-2.19.0.orig/tests/unit/gapic/test_method.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/gapic/test_method.py 2024-05-16 16:03:34.413514811 +0200 -@@ -14,7 +14,7 @@ +diff --git a/tests/unit/gapic/test_method.py b/tests/unit/gapic/test_method.py +index d966f478..87aa6390 100644 +--- a/tests/unit/gapic/test_method.py ++++ b/tests/unit/gapic/test_method.py +@@ -13,8 +13,8 @@ + # limitations under the License. import datetime ++from unittest import mock -import mock -+from unittest import mock import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/unit/operations_v1/test_operations_rest_client.py google-api-core-2.19.0/tests/unit/operations_v1/test_operations_rest_client.py ---- google-api-core-2.19.0.orig/tests/unit/operations_v1/test_operations_rest_client.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/operations_v1/test_operations_rest_client.py 2024-05-16 16:03:34.393514764 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py +index 4ab4f1f7..26f34c40 100644 +--- a/tests/unit/operations_v1/test_operations_rest_client.py ++++ b/tests/unit/operations_v1/test_operations_rest_client.py +@@ -14,8 +14,8 @@ + # limitations under the License. # import os ++from unittest import mock -import mock -+from unittest import mock import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/unit/retry/test_retry_base.py google-api-core-2.19.0/tests/unit/retry/test_retry_base.py ---- google-api-core-2.19.0.orig/tests/unit/retry/test_retry_base.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/retry/test_retry_base.py 2024-05-16 16:03:34.380181399 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/retry/test_retry_base.py b/tests/unit/retry/test_retry_base.py +index a0c6776b..212c4293 100644 +--- a/tests/unit/retry/test_retry_base.py ++++ b/tests/unit/retry/test_retry_base.py +@@ -14,8 +14,8 @@ + import itertools import re ++from unittest import mock -import mock -+from unittest import mock import pytest import requests.exceptions -diff -Nru google-api-core-2.19.0.orig/tests/unit/retry/test_retry_streaming.py google-api-core-2.19.0/tests/unit/retry/test_retry_streaming.py ---- google-api-core-2.19.0.orig/tests/unit/retry/test_retry_streaming.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/retry/test_retry_streaming.py 2024-05-16 16:03:34.376848059 +0200 -@@ -14,7 +14,7 @@ +diff --git a/tests/unit/retry/test_retry_streaming.py b/tests/unit/retry/test_retry_streaming.py +index 01f35327..82b60b05 100644 +--- a/tests/unit/retry/test_retry_streaming.py ++++ b/tests/unit/retry/test_retry_streaming.py +@@ -13,8 +13,8 @@ + # limitations under the License. import re ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/unit/retry/test_retry_unary.py google-api-core-2.19.0/tests/unit/retry/test_retry_unary.py ---- google-api-core-2.19.0.orig/tests/unit/retry/test_retry_unary.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/retry/test_retry_unary.py 2024-05-16 16:03:34.386848082 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/retry/test_retry_unary.py b/tests/unit/retry/test_retry_unary.py +index 7dcd8dd6..b018fa96 100644 +--- a/tests/unit/retry/test_retry_unary.py ++++ b/tests/unit/retry/test_retry_unary.py +@@ -14,8 +14,8 @@ + import datetime import re ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_bidi.py google-api-core-2.19.0/tests/unit/test_bidi.py ---- google-api-core-2.19.0.orig/tests/unit/test_bidi.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_bidi.py 2024-05-16 16:03:34.326847941 +0200 -@@ -17,7 +17,7 @@ +diff --git a/tests/unit/test_bidi.py b/tests/unit/test_bidi.py +index 84ac9dc5..08e80afd 100644 +--- a/tests/unit/test_bidi.py ++++ b/tests/unit/test_bidi.py +@@ -16,8 +16,8 @@ + import logging import queue import threading ++from unittest import mock -import mock -+from unittest import mock import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_exceptions.py google-api-core-2.19.0/tests/unit/test_exceptions.py ---- google-api-core-2.19.0.orig/tests/unit/test_exceptions.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_exceptions.py 2024-05-16 16:03:34.403514788 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py +index 07a36817..e3f8f909 100644 +--- a/tests/unit/test_exceptions.py ++++ b/tests/unit/test_exceptions.py +@@ -14,8 +14,8 @@ + import http.client import json ++from unittest import mock -import mock -+from unittest import mock import pytest import requests -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_extended_operation.py google-api-core-2.19.0/tests/unit/test_extended_operation.py ---- google-api-core-2.19.0.orig/tests/unit/test_extended_operation.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_extended_operation.py 2024-05-16 16:03:34.353514670 +0200 -@@ -16,7 +16,7 @@ +diff --git a/tests/unit/test_extended_operation.py b/tests/unit/test_extended_operation.py +index 53af5204..ab550662 100644 +--- a/tests/unit/test_extended_operation.py ++++ b/tests/unit/test_extended_operation.py +@@ -15,8 +15,8 @@ + import dataclasses import enum import typing ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import exceptions -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_grpc_helpers.py google-api-core-2.19.0/tests/unit/test_grpc_helpers.py ---- google-api-core-2.19.0.orig/tests/unit/test_grpc_helpers.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_grpc_helpers.py 2024-05-16 16:03:34.373514717 +0200 -@@ -12,7 +12,7 @@ +diff --git a/tests/unit/test_grpc_helpers.py b/tests/unit/test_grpc_helpers.py +index 59442d43..8de9d8c0 100644 +--- a/tests/unit/test_grpc_helpers.py ++++ b/tests/unit/test_grpc_helpers.py +@@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +from unittest import mock ++ import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_operation.py google-api-core-2.19.0/tests/unit/test_operation.py ---- google-api-core-2.19.0.orig/tests/unit/test_operation.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_operation.py 2024-05-16 16:03:34.340181306 +0200 -@@ -13,7 +13,7 @@ +diff --git a/tests/unit/test_operation.py b/tests/unit/test_operation.py +index f029866c..80680720 100644 +--- a/tests/unit/test_operation.py ++++ b/tests/unit/test_operation.py +@@ -13,7 +13,8 @@ # limitations under the License. -import mock +from unittest import mock ++ import pytest try: -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_page_iterator.py google-api-core-2.19.0/tests/unit/test_page_iterator.py ---- google-api-core-2.19.0.orig/tests/unit/test_page_iterator.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_page_iterator.py 2024-05-16 16:03:34.333514624 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/test_page_iterator.py b/tests/unit/test_page_iterator.py +index cf43aedf..560722c5 100644 +--- a/tests/unit/test_page_iterator.py ++++ b/tests/unit/test_page_iterator.py +@@ -14,8 +14,8 @@ + import math import types ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api_core import page_iterator -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_path_template.py google-api-core-2.19.0/tests/unit/test_path_template.py ---- google-api-core-2.19.0.orig/tests/unit/test_path_template.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_path_template.py 2024-05-16 16:03:34.333514624 +0200 -@@ -14,7 +14,7 @@ +diff --git a/tests/unit/test_path_template.py b/tests/unit/test_path_template.py +index 808b36f3..c34dd0f3 100644 +--- a/tests/unit/test_path_template.py ++++ b/tests/unit/test_path_template.py +@@ -13,8 +13,8 @@ + # limitations under the License. from __future__ import unicode_literals ++from unittest import mock -import mock -+from unittest import mock import pytest from google.api import auth_pb2 -diff -Nru google-api-core-2.19.0.orig/tests/unit/test_timeout.py google-api-core-2.19.0/tests/unit/test_timeout.py ---- google-api-core-2.19.0.orig/tests/unit/test_timeout.py 2024-04-30 19:00:19.000000000 +0200 -+++ google-api-core-2.19.0/tests/unit/test_timeout.py 2024-05-16 16:03:34.426848175 +0200 -@@ -15,7 +15,7 @@ +diff --git a/tests/unit/test_timeout.py b/tests/unit/test_timeout.py +index 0bcf07f0..60a2e65d 100644 +--- a/tests/unit/test_timeout.py ++++ b/tests/unit/test_timeout.py +@@ -14,8 +14,7 @@ + import datetime import itertools - +- -import mock +from unittest import mock