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 <[email protected]>
+
+- Remove unneeded BuildRequires.
+- Refreshed patches:
+ * python-google-api-core-no-mock.patch
+
+-------------------------------------------------------------------
+Thu Sep 26 13:32:08 UTC 2024 - John Paul Adrian Glaubitz
<[email protected]>
+
+- 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)
+
+
[email protected]
+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()
+
+
[email protected]
[email protected](
+ "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
+
+
[email protected]
[email protected](
+ "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)
+
+
[email protected]
[email protected](
+ "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
+
+
[email protected]
[email protected](
+ "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)
+
+
[email protected]
[email protected]("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()
+
+
[email protected]
[email protected]("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()
+
+
[email protected]
[email protected]("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()
+
+
[email protected]
[email protected](
+ "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__()
+
+
[email protected]
[email protected]("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()
+
+
[email protected]
+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 <[email protected]>
+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