Hello community, here is the log from the commit of package python-google-api-python-client for openSUSE:Factory checked in at 2020-10-15 13:48:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-google-api-python-client (Old) and /work/SRC/openSUSE:Factory/.python-google-api-python-client.new.3486 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-google-api-python-client" Thu Oct 15 13:48:48 2020 rev:16 rq:833488 version:1.11.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-google-api-python-client/python-google-api-python-client.changes 2020-06-25 15:11:49.602193418 +0200 +++ /work/SRC/openSUSE:Factory/.python-google-api-python-client.new.3486/python-google-api-python-client.changes 2020-10-15 13:49:23.453274035 +0200 @@ -1,0 +2,28 @@ +Thu Sep 10 14:05:18 UTC 2020 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to version 1.11.0 + * add support for mtls env variables (#1008) +- from version 1.10.1 + * discovery uses V2 when version is None (#975), closes (#971) + * fix deprecation warnings due to invalid escape sequences. (#996), closes (#995) + * fix link to service accounts documentation (#986) + * update generated docs (#981) +- from version 1.10.0 + * allow to use 'six.moves.collections_abc.Mapping' in 'client_options.from_dict()' (#943) + * Build universal wheels (#948) + * discovery supports retries (#967), closes (#848) + * consolidating and updating the Contribution Guide (#964), closes (#963) +- from version 1.9.3 + * update GOOGLE_API_USE_MTLS values (#940) +- from version 1.9.2 + * bump api-core version (#936) +- from version 1.9.1 + * fix python-api-core dependency issue (#931) +- from version 1.9.0 + * add mtls feature (#917) + * add templates for python samples projects (#506), (#924) +- Refresh patches for new version + + python-google-api-python-client-no-unittest2.patch +- Update BuildRequires and Requires from setup.py + +------------------------------------------------------------------- Old: ---- google-api-python-client-1.8.4.tar.gz New: ---- google-api-python-client-1.11.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-google-api-python-client.spec ++++++ --- /var/tmp/diff_new_pack.ucJiIH/_old 2020-10-15 13:49:24.229274359 +0200 +++ /var/tmp/diff_new_pack.ucJiIH/_new 2020-10-15 13:49:24.233274361 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-google-api-python-client -Version: 1.8.4 +Version: 1.11.0 Release: 0 Summary: Google APIs Python Client License: Apache-2.0 @@ -27,20 +27,21 @@ Source: https://files.pythonhosted.org/packages/source/g/google-api-python-client/google-api-python-client-%{version}.tar.gz # https://github.com/googleapis/google-api-python-client/pull/929 Patch0: python-google-api-python-client-no-unittest2.patch -BuildRequires: %{python_module google-api-core >= 1.13.0} -BuildRequires: %{python_module google-auth >= 1.4.1} +BuildRequires: %{python_module google-api-core >= 1.18.0} +BuildRequires: %{python_module google-auth >= 1.16.0} BuildRequires: %{python_module google-auth-httplib2 >= 0.0.3} BuildRequires: %{python_module httplib2 >= 0.9.2} BuildRequires: %{python_module mock} BuildRequires: %{python_module oauth2client} +BuildRequires: %{python_module parameterized} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module six >= 1.6.1} BuildRequires: %{python_module uritemplate >= 3.0.0} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-google-api-core >= 1.13.0 -Requires: python-google-auth >= 1.4.1 +Requires: python-google-api-core >= 1.18.0 +Requires: python-google-auth >= 1.16.0 Requires: python-google-auth-httplib2 >= 0.0.3 Requires: python-httplib2 >= 0.9.2 Requires: python-six >= 1.6.1 ++++++ google-api-python-client-1.8.4.tar.gz -> google-api-python-client-1.11.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/PKG-INFO new/google-api-python-client-1.11.0/PKG-INFO --- old/google-api-python-client-1.8.4/PKG-INFO 2020-05-26 22:19:38.414078000 +0200 +++ new/google-api-python-client-1.11.0/PKG-INFO 2020-08-27 23:35:31.385575800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-python-client -Version: 1.8.4 +Version: 1.11.0 Summary: Google API Client Library for Python Home-page: https://github.com/googleapis/google-api-python-client/ Author: Google LLC @@ -74,7 +74,9 @@ ## Contributing - Please see the [contributing page](http://google.github.io/google-api-python-client/contributing.html) for more information. In particular, we love pull requests - but please make sure to sign the contributor license agreement. + Please see our [Contribution Guide](CONTRIBUTING.rst). + In particular, we love pull requests - but please make sure to sign + the contributor license agreement. Keywords: google api client Platform: UNKNOWN diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/README.md new/google-api-python-client-1.11.0/README.md --- old/google-api-python-client-1.8.4/README.md 2020-05-26 22:17:52.000000000 +0200 +++ new/google-api-python-client-1.11.0/README.md 2020-08-27 23:33:50.000000000 +0200 @@ -66,4 +66,6 @@ ## Contributing -Please see the [contributing page](http://google.github.io/google-api-python-client/contributing.html) for more information. In particular, we love pull requests - but please make sure to sign the contributor license agreement. +Please see our [Contribution Guide](CONTRIBUTING.rst). +In particular, we love pull requests - but please make sure to sign +the contributor license agreement. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/google_api_python_client.egg-info/PKG-INFO new/google-api-python-client-1.11.0/google_api_python_client.egg-info/PKG-INFO --- old/google-api-python-client-1.8.4/google_api_python_client.egg-info/PKG-INFO 2020-05-26 22:19:38.000000000 +0200 +++ new/google-api-python-client-1.11.0/google_api_python_client.egg-info/PKG-INFO 2020-08-27 23:35:31.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-api-python-client -Version: 1.8.4 +Version: 1.11.0 Summary: Google API Client Library for Python Home-page: https://github.com/googleapis/google-api-python-client/ Author: Google LLC @@ -74,7 +74,9 @@ ## Contributing - Please see the [contributing page](http://google.github.io/google-api-python-client/contributing.html) for more information. In particular, we love pull requests - but please make sure to sign the contributor license agreement. + Please see our [Contribution Guide](CONTRIBUTING.rst). + In particular, we love pull requests - but please make sure to sign + the contributor license agreement. Keywords: google api client Platform: UNKNOWN diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/google_api_python_client.egg-info/SOURCES.txt new/google-api-python-client-1.11.0/google_api_python_client.egg-info/SOURCES.txt --- old/google-api-python-client-1.8.4/google_api_python_client.egg-info/SOURCES.txt 2020-05-26 22:19:38.000000000 +0200 +++ new/google-api-python-client-1.11.0/google_api_python_client.egg-info/SOURCES.txt 2020-08-27 23:35:31.000000000 +0200 @@ -1,6 +1,7 @@ LICENSE MANIFEST.in README.md +setup.cfg setup.py apiclient/__init__.py google_api_python_client.egg-info/PKG-INFO @@ -36,6 +37,8 @@ tests/test_model.py tests/test_protobuf_model.py tests/test_schema.py +tests/data/500.json +tests/data/503.json tests/data/bad_request.json tests/data/bigquery.json tests/data/certs.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/google_api_python_client.egg-info/requires.txt new/google-api-python-client-1.11.0/google_api_python_client.egg-info/requires.txt --- old/google-api-python-client-1.8.4/google_api_python_client.egg-info/requires.txt 2020-05-26 22:19:38.000000000 +0200 +++ new/google-api-python-client-1.11.0/google_api_python_client.egg-info/requires.txt 2020-08-27 23:35:31.000000000 +0200 @@ -1,6 +1,6 @@ httplib2<1dev,>=0.9.2 -google-auth>=1.4.1 +google-auth>=1.16.0 google-auth-httplib2>=0.0.3 -google-api-core<2dev,>=1.13.0 +google-api-core<2dev,>=1.18.0 six<2dev,>=1.6.1 uritemplate<4dev,>=3.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/googleapiclient/discovery.py new/google-api-python-client-1.11.0/googleapiclient/discovery.py --- old/google-api-python-client-1.8.4/googleapiclient/discovery.py 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/googleapiclient/discovery.py 2020-08-27 23:33:51.000000000 +0200 @@ -29,7 +29,7 @@ # Standard library imports import copy - +from collections import OrderedDict try: from email.generator import BytesGenerator except ImportError: @@ -47,6 +47,13 @@ import httplib2 import uritemplate import google.api_core.client_options +from google.auth.transport import mtls +from google.auth.exceptions import MutualTLSChannelError + +try: + import google_auth_httplib2 +except ImportError: # pragma: NO COVER + google_auth_httplib2 = None # Local imports from googleapiclient import _auth @@ -110,6 +117,10 @@ } _PAGE_TOKEN_NAMES = ("pageToken", "nextPageToken") +# Parameters controlling mTLS behavior. See https://google.aip.dev/auth/4114. +GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE" +GOOGLE_API_USE_MTLS_ENDPOINT = "GOOGLE_API_USE_MTLS_ENDPOINT" + # Parameters accepted by the stack, but not visible via discovery. # TODO(dhermes): Remove 'userip' in 'v2'. STACK_QUERY_PARAMETERS = frozenset(["trace", "pp", "userip", "strict"]) @@ -132,7 +143,7 @@ Returns: The name with '_' appended if the name is a reserved word and '$' and '-' - replaced with '_'. + replaced with '_'. """ name = name.replace("$", "_").replace("-", "_") if keyword.iskeyword(name) or name in RESERVED_WORDS: @@ -178,6 +189,9 @@ cache_discovery=True, cache=None, client_options=None, + adc_cert_path=None, + adc_key_path=None, + num_retries=1, ): """Construct a Resource for interacting with an API. @@ -204,11 +218,40 @@ cache_discovery: Boolean, whether or not to cache the discovery doc. cache: googleapiclient.discovery_cache.base.CacheBase, an optional cache object for the discovery documents. - client_options: Dictionary or google.api_core.client_options, Client options to set user - options on the client. API endpoint should be set through client_options. + client_options: Mapping object or google.api_core.client_options, client + options to set user options on the client. + (1) The API endpoint should be set through client_options. If API endpoint + is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used + to control which endpoint to use. + (2) client_cert_source is not supported, client cert should be provided using + client_encrypted_cert_source instead. In order to use the provided client + cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be + set to `true`. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 + adc_cert_path: str, client certificate file path to save the application + default client certificate for mTLS. This field is required if you want to + use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be set to `true` in order to use this field, + otherwise this field doesn't nothing. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 + adc_key_path: str, client encrypted private key file path to save the + application default client encrypted private key for mTLS. This field is + required if you want to use the default client certificate. + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to + `true` in order to use this field, otherwise this field doesn't nothing. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 + num_retries: Integer, number of times to retry discovery with + randomized exponential backoff in case of intermittent/connection issues. Returns: A Resource object with methods for interacting with the service. + + Raises: + google.auth.exceptions.MutualTLSChannelError: if there are any problems + setting up mutual TLS channel. """ params = {"api": serviceName, "apiVersion": version} @@ -217,12 +260,14 @@ else: discovery_http = http - for discovery_url in (discoveryServiceUrl, V2_DISCOVERY_URI): + for discovery_url in \ + _discovery_service_uri_options(discoveryServiceUrl, version): requested_url = uritemplate.expand(discovery_url, params) try: content = _retrieve_discovery_doc( - requested_url, discovery_http, cache_discovery, cache, developerKey + requested_url, discovery_http, cache_discovery, cache, + developerKey, num_retries=num_retries ) return build_from_document( content, @@ -232,7 +277,9 @@ model=model, requestBuilder=requestBuilder, credentials=credentials, - client_options=client_options + client_options=client_options, + adc_cert_path=adc_cert_path, + adc_key_path=adc_key_path, ) except HttpError as e: if e.resp.status == http_client.NOT_FOUND: @@ -243,7 +290,31 @@ raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version)) -def _retrieve_discovery_doc(url, http, cache_discovery, cache=None, developerKey=None): +def _discovery_service_uri_options(discoveryServiceUrl, version): + """ + Returns Discovery URIs to be used for attemnting to build the API Resource. + + Args: + discoveryServiceUrl: + string, the Original Discovery Service URL preferred by the customer. + version: + string, API Version requested + + Returns: + A list of URIs to be tried for the Service Discovery, in order. + """ + + urls = [discoveryServiceUrl, V2_DISCOVERY_URI] + # V1 Discovery won't work if the requested version is None + if discoveryServiceUrl == V1_DISCOVERY_URI and version is None: + logger.warning( + "Discovery V1 does not support empty versions. Defaulting to V2...") + urls.pop(0) + return list(OrderedDict.fromkeys(urls)) + + +def _retrieve_discovery_doc(url, http, cache_discovery, + cache=None, developerKey=None, num_retries=1): """Retrieves the discovery_doc from cache or the internet. Args: @@ -253,13 +324,16 @@ cache_discovery: Boolean, whether or not to cache the discovery doc. cache: googleapiclient.discovery_cache.base.Cache, an optional cache object for the discovery documents. + developerKey: string, Key for controlling API usage, generated + from the API Console. + num_retries: Integer, number of times to retry discovery with + randomized exponential backoff in case of intermittent/connection issues. Returns: A unicode string representation of the discovery document. """ if cache_discovery: from . import discovery_cache - from .discovery_cache import base if cache is None: cache = discovery_cache.autodetect() @@ -279,10 +353,10 @@ actual_url = _add_query_parameter(url, "key", developerKey) logger.debug("URL being requested: GET %s", actual_url) - resp, content = http.request(actual_url) - - if resp.status >= 400: - raise HttpError(resp, content, uri=actual_url) + # Execute this request with retries build into HttpRequest + # Note that it will already raise an error if we don't get a 2xx response + req = HttpRequest(http, HttpRequest.null_postproc, actual_url) + resp, content = req.execute(num_retries=num_retries) try: content = content.decode("utf-8") @@ -309,7 +383,9 @@ model=None, requestBuilder=HttpRequest, credentials=None, - client_options=None + client_options=None, + adc_cert_path=None, + adc_key_path=None, ): """Create a Resource for interacting with an API. @@ -334,11 +410,38 @@ credentials: oauth2client.Credentials or google.auth.credentials.Credentials, credentials to be used for authentication. - client_options: Dictionary or google.api_core.client_options, Client options to set user - options on the client. API endpoint should be set through client_options. + client_options: Mapping object or google.api_core.client_options, client + options to set user options on the client. + (1) The API endpoint should be set through client_options. If API endpoint + is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used + to control which endpoint to use. + (2) client_cert_source is not supported, client cert should be provided using + client_encrypted_cert_source instead. In order to use the provided client + cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be + set to `true`. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 + adc_cert_path: str, client certificate file path to save the application + default client certificate for mTLS. This field is required if you want to + use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be set to `true` in order to use this field, + otherwise this field doesn't nothing. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 + adc_key_path: str, client encrypted private key file path to save the + application default client encrypted private key for mTLS. This field is + required if you want to use the default client certificate. + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to + `true` in order to use this field, otherwise this field doesn't nothing. + More details on the environment variables are here: + https://google.aip.dev/auth/4114 Returns: A Resource object with methods for interacting with the service. + + Raises: + google.auth.exceptions.MutualTLSChannelError: if there are any problems + setting up mutual TLS channel. """ if http is not None and credentials is not None: @@ -349,7 +452,7 @@ elif isinstance(service, six.binary_type): service = json.loads(service.decode("utf-8")) - if "rootUrl" not in service and (isinstance(http, (HttpMock, HttpMockSequence))): + if "rootUrl" not in service and isinstance(http, (HttpMock, HttpMockSequence)): logger.error( "You are using HttpMock or HttpMockSequence without" + "having the service discovery doc in cache. Try calling " @@ -359,12 +462,10 @@ raise InvalidJsonError() # If an API Endpoint is provided on client options, use that as the base URL - base = urljoin(service['rootUrl'], service["servicePath"]) + base = urljoin(service["rootUrl"], service["servicePath"]) if client_options: - if type(client_options) == dict: - client_options = google.api_core.client_options.from_dict( - client_options - ) + if isinstance(client_options, six.moves.collections_abc.Mapping): + client_options = google.api_core.client_options.from_dict(client_options) if client_options.api_endpoint: base = client_options.api_endpoint @@ -400,6 +501,62 @@ else: http = build_http() + # Obtain client cert and create mTLS http channel if cert exists. + client_cert_to_use = None + use_client_cert = os.getenv(GOOGLE_API_USE_CLIENT_CERTIFICATE, "false") + if not use_client_cert in ("true", "false"): + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false" + ) + if client_options and client_options.client_cert_source: + raise MutualTLSChannelError( + "ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source." + ) + if use_client_cert == "true": + if ( + client_options + and hasattr(client_options, "client_encrypted_cert_source") + and client_options.client_encrypted_cert_source + ): + client_cert_to_use = client_options.client_encrypted_cert_source + elif adc_cert_path and adc_key_path and mtls.has_default_client_cert_source(): + client_cert_to_use = mtls.default_client_encrypted_cert_source( + adc_cert_path, adc_key_path + ) + if client_cert_to_use: + cert_path, key_path, passphrase = client_cert_to_use() + + # The http object we built could be google_auth_httplib2.AuthorizedHttp + # or httplib2.Http. In the first case we need to extract the wrapped + # httplib2.Http object from google_auth_httplib2.AuthorizedHttp. + http_channel = ( + http.http + if google_auth_httplib2 + and isinstance(http, google_auth_httplib2.AuthorizedHttp) + else http + ) + http_channel.add_certificate(key_path, cert_path, "", passphrase) + + # If user doesn't provide api endpoint via client options, decide which + # api endpoint to use. + if "mtlsRootUrl" in service and ( + not client_options or not client_options.api_endpoint + ): + mtls_endpoint = urljoin(service["mtlsRootUrl"], service["servicePath"]) + use_mtls_endpoint = os.getenv(GOOGLE_API_USE_MTLS_ENDPOINT, "auto") + + if not use_mtls_endpoint in ("never", "auto", "always"): + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Switch to mTLS endpoint, if environment variable is "always", or + # environment varibable is "auto" and client cert exists. + if use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_to_use + ): + base = mtls_endpoint + if model is None: features = service.get("features", []) model = JsonModel("dataWrapper" in features) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/googleapiclient/http.py new/google-api-python-client-1.11.0/googleapiclient/http.py --- old/google-api-python-client-1.8.4/googleapiclient/http.py 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/googleapiclient/http.py 2020-08-27 23:33:51.000000000 +0200 @@ -1118,6 +1118,10 @@ resumable=d["resumable"], ) + @staticmethod + def null_postproc(resp, contents): + return resp, contents + class BatchHttpRequest(object): """Batches multiple HttpRequest objects into a single HTTP request. @@ -1168,7 +1172,7 @@ batch_uri = _LEGACY_BATCH_URI if batch_uri == _LEGACY_BATCH_URI: - LOGGER.warn( + LOGGER.warning( "You have constructed a BatchHttpRequest using the legacy batch " "endpoint %s. This endpoint will be turned down on August 12, 2020. " "Please provide the API-specific endpoint or use " @@ -1416,7 +1420,7 @@ http: httplib2.Http, an http object to be used to make the request with. order: list, list of request ids in the order they were added to the batch. - request: list, list of request objects to send. + requests: list, list of request objects to send. Raises: httplib2.HttpLib2Error if a transport error has occurred. @@ -1690,9 +1694,8 @@ if headers is None: headers = {"status": "200"} if filename: - f = open(filename, "rb") - self.data = f.read() - f.close() + with open(filename, "rb") as f: + self.data = f.read() else: self.data = None self.response_headers = headers @@ -1749,6 +1752,7 @@ """ self._iterable = iterable self.follow_redirects = True + self.request_sequence = list() def request( self, @@ -1759,6 +1763,8 @@ redirections=1, connection_type=None, ): + # Remember the request so after the fact this mock can be examined + self.request_sequence.append((uri, method, body, headers)) resp, content = self._iterable.pop(0) content = six.ensure_binary(content) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/setup.cfg new/google-api-python-client-1.11.0/setup.cfg --- old/google-api-python-client-1.8.4/setup.cfg 2020-05-26 22:19:38.414078000 +0200 +++ new/google-api-python-client-1.11.0/setup.cfg 2020-08-27 23:35:31.385575800 +0200 @@ -1,3 +1,6 @@ +[bdist_wheel] +universal = 1 + [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/setup.py new/google-api-python-client-1.11.0/setup.py --- old/google-api-python-client-1.8.4/setup.py 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/setup.py 2020-08-27 23:33:51.000000000 +0200 @@ -39,9 +39,9 @@ # currently upgrade their httplib2 version. # Please see https://github.com/googleapis/google-api-python-client/pull/841 "httplib2>=0.9.2,<1dev", - "google-auth>=1.4.1", + "google-auth>=1.16.0", "google-auth-httplib2>=0.0.3", - "google-api-core>=1.13.0,<2dev", + "google-api-core>=1.18.0,<2dev", "six>=1.6.1,<2dev", "uritemplate>=3.0.0,<4dev", ] @@ -52,7 +52,7 @@ with io.open(readme_filename, encoding="utf-8") as readme_file: readme = readme_file.read() -version = "1.8.4" +version = "1.11.0" setup( name="google-api-python-client", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/500.json new/google-api-python-client-1.11.0/tests/data/500.json --- old/google-api-python-client-1.8.4/tests/data/500.json 1970-01-01 01:00:00.000000000 +0100 +++ new/google-api-python-client-1.11.0/tests/data/500.json 2020-08-27 23:33:51.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "error": { + "errors": [ + { + "domain": "global", + "reason": "internalError", + "message": "We encountered an internal error. Please try again using truncated exponential backoff." + } + ], + "code": 500, + "message": "Internal Server Error" + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/503.json new/google-api-python-client-1.11.0/tests/data/503.json --- old/google-api-python-client-1.8.4/tests/data/503.json 1970-01-01 01:00:00.000000000 +0100 +++ new/google-api-python-client-1.11.0/tests/data/503.json 2020-08-27 23:33:51.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "error": { + "errors": [ + { + "domain": "global", + "reason": "backendError", + "message": "We encountered an internal error. Please try again using truncated exponential backoff." + } + ], + "code": 503, + "message": "Service Unavailable" + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/bigquery.json new/google-api-python-client-1.11.0/tests/data/bigquery.json --- old/google-api-python-client-1.8.4/tests/data/bigquery.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/bigquery.json 2020-08-27 23:33:51.000000000 +0200 @@ -19,6 +19,7 @@ "baseUrl": "https://www.googleapis.com/bigquery/v2/", "basePath": "/bigquery/v2/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "bigquery/v2/", "batchPath": "batch", "parameters": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/drive.json new/google-api-python-client-1.11.0/tests/data/drive.json --- old/google-api-python-client-1.8.4/tests/data/drive.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/drive.json 2020-08-27 23:33:51.000000000 +0200 @@ -19,6 +19,7 @@ "baseUrl": "https://www.googleapis.com/drive/v3/", "basePath": "/drive/v3/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "drive/v3/", "batchPath": "batch", "parameters": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/latitude.json new/google-api-python-client-1.11.0/tests/data/latitude.json --- old/google-api-python-client-1.8.4/tests/data/latitude.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/latitude.json 2020-08-27 23:33:51.000000000 +0200 @@ -14,6 +14,7 @@ "protocol": "rest", "basePath": "/latitude/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "latitude/v1/", "auth": { "oauth2": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/logging.json new/google-api-python-client-1.11.0/tests/data/logging.json --- old/google-api-python-client-1.8.4/tests/data/logging.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/logging.json 2020-08-27 23:33:51.000000000 +0200 @@ -2086,5 +2086,6 @@ "ownerName": "Google", "version": "v2", "rootUrl": "https://logging.googleapis.com/", + "mtlsRootUrl": "https://logging.mtls.googleapis.com/", "kind": "discovery#restDescription" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/plus.json new/google-api-python-client-1.11.0/tests/data/plus.json --- old/google-api-python-client-1.8.4/tests/data/plus.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/plus.json 2020-08-27 23:33:51.000000000 +0200 @@ -16,6 +16,7 @@ "protocol": "rest", "basePath": "/plus/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "plus/v1/", "parameters": { "alt": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/tasks.json new/google-api-python-client-1.11.0/tests/data/tasks.json --- old/google-api-python-client-1.8.4/tests/data/tasks.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/tasks.json 2020-08-27 23:33:51.000000000 +0200 @@ -16,6 +16,7 @@ "protocol": "rest", "basePath": "/tasks/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "tasks/v1/", "parameters": { "alt": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/data/zoo.json new/google-api-python-client-1.11.0/tests/data/zoo.json --- old/google-api-python-client-1.8.4/tests/data/zoo.json 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/data/zoo.json 2020-08-27 23:33:51.000000000 +0200 @@ -6,6 +6,7 @@ "basePath": "/zoo/", "batchPath": "batchZoo", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "zoo/v1/", "rpcPath": "/rpc", "parameters": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/test_discovery.py new/google-api-python-client-1.11.0/tests/test_discovery.py --- old/google-api-python-client-1.8.4/tests/test_discovery.py 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/test_discovery.py 2020-08-27 23:33:51.000000000 +0200 @@ -38,10 +38,14 @@ import re import sys import unittest2 as unittest +from collections import defaultdict +from parameterized import parameterized import mock import google.auth.credentials +from google.auth.transport import mtls +from google.auth.exceptions import MutualTLSChannelError import google_auth_httplib2 from googleapiclient.discovery import _fix_up_media_upload from googleapiclient.discovery import _fix_up_method_description @@ -56,6 +60,8 @@ from googleapiclient.discovery import ResourceMethodParameters from googleapiclient.discovery import STACK_QUERY_PARAMETERS from googleapiclient.discovery import STACK_QUERY_PARAMETER_DEFAULT_VALUE +from googleapiclient.discovery import V1_DISCOVERY_URI +from googleapiclient.discovery import V2_DISCOVERY_URI from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE from googleapiclient.discovery_cache.base import Cache from googleapiclient.errors import HttpError @@ -104,10 +110,35 @@ testcase.assertEqual(expected_query[name], actual_query[name]) +def assert_discovery_uri(testcase, actual, service_name, version, discovery): + """Assert that discovery URI used was the one that was expected + for a given service and version.""" + params = {"api": service_name, "apiVersion": version} + expanded_requested_uri = uritemplate.expand(discovery, params) + assertUrisEqual(testcase, expanded_requested_uri, actual) + + +def validate_discovery_requests(testcase, http_mock, service_name, + version, discovery): + """Validates that there have > 0 calls to Http Discovery + and that LAST discovery URI used was the one that was expected + for a given service and version.""" + testcase.assertTrue(len(http_mock.request_sequence) > 0) + if len(http_mock.request_sequence) > 0: + actual_uri = http_mock.request_sequence[-1][0] + assert_discovery_uri(testcase, + actual_uri, service_name, version, discovery) + + def datafile(filename): return os.path.join(DATA_DIR, filename) +def read_datafile(filename, mode='r'): + with open(datafile(filename), mode=mode) as f: + return f.read() + + class SetupHttplib2(unittest.TestCase): def test_retries(self): # Merely loading googleapiclient.discovery should set the RETRIES to 1. @@ -116,8 +147,7 @@ class Utilities(unittest.TestCase): def setUp(self): - with open(datafile("zoo.json"), "r") as fh: - self.zoo_root_desc = json.loads(fh.read()) + self.zoo_root_desc = json.loads(read_datafile("zoo.json", "r")) self.zoo_get_method_desc = self.zoo_root_desc["methods"]["query"] self.zoo_animals_resource = self.zoo_root_desc["resources"]["animals"] self.zoo_insert_method_desc = self.zoo_animals_resource["methods"]["insert"] @@ -224,7 +254,11 @@ final_max_size, final_media_path_url, ): - fake_root_desc = {"rootUrl": "http://root/", "servicePath": "fake/"} + fake_root_desc = { + "rootUrl": "http://root/", + "servicePath": "fake/", + "mtlsRootUrl": "http://root/", + } fake_path_url = "fake-path/" accept, max_size, media_path_url = _fix_up_media_upload( @@ -422,8 +456,8 @@ def test_unknown_api_name_or_version(self): http = HttpMockSequence( [ - ({"status": "404"}, open(datafile("zoo.json"), "rb").read()), - ({"status": "404"}, open(datafile("zoo.json"), "rb").read()), + ({"status": "404"}, read_datafile("zoo.json", "rb")), + ({"status": "404"}, read_datafile("zoo.json", "rb")), ] ) with self.assertRaises(UnknownApiNameOrVersion): @@ -439,28 +473,28 @@ MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) def test_can_build_from_local_document(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") plus = build_from_document( discovery, base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS, ) - self.assertTrue(plus is not None) + self.assertIsNotNone(plus) self.assertTrue(hasattr(plus, "activities")) def test_can_build_from_local_deserialized_document(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") discovery = json.loads(discovery) plus = build_from_document( discovery, base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS, ) - self.assertTrue(plus is not None) + self.assertIsNotNone(plus) self.assertTrue(hasattr(plus, "activities")) def test_building_with_base_remembers_base(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") base = "https://www.example.com/" plus = build_from_document( @@ -469,7 +503,7 @@ self.assertEqual("https://www.googleapis.com/plus/v1/", plus._baseUrl) def test_building_with_optional_http_with_authorization(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") plus = build_from_document( discovery, base="https://www.googleapis.com/", @@ -483,7 +517,7 @@ self.assertGreater(plus._http.http.timeout, 0) def test_building_with_optional_http_with_no_authorization(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") # Cleanup auth field, so we would use plain http client discovery = json.loads(discovery) discovery["auth"] = {} @@ -499,14 +533,14 @@ def test_building_with_explicit_http(self): http = HttpMock() - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") plus = build_from_document( discovery, base="https://www.googleapis.com/", http=http ) self.assertEqual(plus._http, http) def test_building_with_developer_key_skips_adc(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") plus = build_from_document( discovery, base="https://www.googleapis.com/", developerKey="123" ) @@ -516,31 +550,262 @@ self.assertNotIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp) def test_api_endpoint_override_from_client_options(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") api_endpoint = "https://foo.googleapis.com/" options = google.api_core.client_options.ClientOptions( api_endpoint=api_endpoint ) plus = build_from_document( - discovery, - client_options=options, - credentials=self.MOCK_CREDENTIALS + discovery, client_options=options, credentials=self.MOCK_CREDENTIALS + ) + + self.assertEqual(plus._baseUrl, api_endpoint) + + def test_api_endpoint_override_from_client_options_mapping_object(self): + + discovery = read_datafile("plus.json") + api_endpoint = "https://foo.googleapis.com/" + mapping_object = defaultdict(str) + mapping_object['api_endpoint'] = api_endpoint + plus = build_from_document( + discovery, client_options=mapping_object ) self.assertEqual(plus._baseUrl, api_endpoint) def test_api_endpoint_override_from_client_options_dict(self): - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") api_endpoint = "https://foo.googleapis.com/" plus = build_from_document( - discovery, + discovery, client_options={"api_endpoint": api_endpoint}, - credentials=self.MOCK_CREDENTIALS + credentials=self.MOCK_CREDENTIALS, ) self.assertEqual(plus._baseUrl, api_endpoint) +REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/" +MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/" + + +class DiscoveryFromDocumentMutualTLS(unittest.TestCase): + MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) + ADC_CERT_PATH = "adc_cert_path" + ADC_KEY_PATH = "adc_key_path" + ADC_PASSPHRASE = "adc_passphrase" + + def check_http_client_cert(self, resource, has_client_cert="false"): + if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp): + certs = list(resource._http.http.certificates.iter("")) + else: + certs = list(resource._http.certificates.iter("")) + if has_client_cert == "true": + self.assertEqual(len(certs), 1) + self.assertEqual( + certs[0], (self.ADC_KEY_PATH, self.ADC_CERT_PATH, self.ADC_PASSPHRASE) + ) + else: + self.assertEqual(len(certs), 0) + + def client_encrypted_cert_source(self): + return self.ADC_CERT_PATH, self.ADC_KEY_PATH, self.ADC_PASSPHRASE + + @parameterized.expand( + [ + ("never", "true"), + ("auto", "true"), + ("always", "true"), + ("never", "false"), + ("auto", "false"), + ("always", "false"), + ] + ) + def test_mtls_not_trigger_if_http_provided(self, use_mtls_env, use_client_cert): + discovery = read_datafile("plus.json") + + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + plus = build_from_document(discovery, http=httplib2.Http()) + self.assertIsNotNone(plus) + self.assertEqual(plus._baseUrl, REGULAR_ENDPOINT) + self.check_http_client_cert(plus, has_client_cert="false") + + @parameterized.expand( + [ + ("never", "true"), + ("auto", "true"), + ("always", "true"), + ("never", "false"), + ("auto", "false"), + ("always", "false"), + ] + ) + def test_exception_with_client_cert_source(self, use_mtls_env, use_client_cert): + discovery = read_datafile("plus.json") + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + with self.assertRaises(MutualTLSChannelError): + build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={"client_cert_source": mock.Mock()}, + ) + + @parameterized.expand( + [ + ("never", "true", REGULAR_ENDPOINT), + ("auto", "true", MTLS_ENDPOINT), + ("always", "true", MTLS_ENDPOINT), + ("never", "false", REGULAR_ENDPOINT), + ("auto", "false", REGULAR_ENDPOINT), + ("always", "false", MTLS_ENDPOINT), + ] + ) + def test_mtls_with_provided_client_cert( + self, use_mtls_env, use_client_cert, base_url + ): + discovery = read_datafile("plus.json") + + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "client_encrypted_cert_source": self.client_encrypted_cert_source + }, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=use_client_cert) + self.assertEqual(plus._baseUrl, base_url) + + @parameterized.expand( + [ + ("never", "true"), + ("auto", "true"), + ("always", "true"), + ("never", "false"), + ("auto", "false"), + ("always", "false"), + ] + ) + def test_endpoint_not_switch(self, use_mtls_env, use_client_cert): + # Test endpoint is not switched if user provided api endpoint + discovery = read_datafile("plus.json") + + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "api_endpoint": "https://foo.googleapis.com", + "client_encrypted_cert_source": self.client_encrypted_cert_source, + }, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=use_client_cert) + self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") + + @parameterized.expand( + [ + ("never", "true", REGULAR_ENDPOINT), + ("auto", "true", MTLS_ENDPOINT), + ("always", "true", MTLS_ENDPOINT), + ("never", "false", REGULAR_ENDPOINT), + ("auto", "false", REGULAR_ENDPOINT), + ("always", "false", MTLS_ENDPOINT), + ] + ) + @mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", autospec=True + ) + @mock.patch( + "google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True + ) + def test_mtls_with_default_client_cert( + self, + use_mtls_env, + use_client_cert, + base_url, + default_client_encrypted_cert_source, + has_default_client_cert_source, + ): + has_default_client_cert_source.return_value = True + default_client_encrypted_cert_source.return_value = ( + self.client_encrypted_cert_source + ) + discovery = read_datafile("plus.json") + + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=use_client_cert) + self.assertEqual(plus._baseUrl, base_url) + + @parameterized.expand( + [ + ("never", "true", REGULAR_ENDPOINT), + ("auto", "true", REGULAR_ENDPOINT), + ("always", "true", MTLS_ENDPOINT), + ("never", "false", REGULAR_ENDPOINT), + ("auto", "false", REGULAR_ENDPOINT), + ("always", "false", MTLS_ENDPOINT), + ] + ) + @mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", autospec=True + ) + def test_mtls_with_no_client_cert( + self, use_mtls_env, use_client_cert, base_url, has_default_client_cert_source + ): + has_default_client_cert_source.return_value = False + discovery = read_datafile("plus.json") + + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_MTLS_ENDPOINT": use_mtls_env} + ): + with mock.patch.dict( + "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert="false") + self.assertEqual(plus._baseUrl, base_url) + + class DiscoveryFromHttp(unittest.TestCase): def setUp(self): self.old_environ = os.environ.copy() @@ -554,7 +819,7 @@ os.environ["REMOTE_ADDR"] = "10.0.0.1" try: http = HttpMockSequence( - [({"status": "400"}, open(datafile("zoo.json"), "rb").read())] + [({"status": "400"}, read_datafile("zoo.json", "rb"))] ) zoo = build( "zoo", @@ -572,7 +837,7 @@ # out of the raised exception. try: http = HttpMockSequence( - [({"status": "400"}, open(datafile("zoo.json"), "rb").read())] + [({"status": "400"}, read_datafile("zoo.json", "rb"))] ) zoo = build( "zoo", @@ -590,7 +855,7 @@ # out of the raised exception. try: http = HttpMockSequence( - [({"status": "400"}, open(datafile("zoo.json"), "rb").read())] + [({"status": "400"}, read_datafile("zoo.json", "rb"))] ) zoo = build( "zoo", @@ -607,7 +872,7 @@ http = HttpMockSequence( [ ({"status": "404"}, "Not found"), - ({"status": "200"}, open(datafile("zoo.json"), "rb").read()), + ({"status": "200"}, read_datafile("zoo.json", "rb")), ] ) zoo = build("zoo", "v1", http=http, cache_discovery=False) @@ -617,7 +882,7 @@ http = HttpMockSequence( [ ({"status": "404"}, "Not found"), - ({"status": "200"}, open(datafile("zoo.json"), "rb").read()), + ({"status": "200"}, read_datafile("zoo.json", "rb")), ] ) api_endpoint = "https://foo.googleapis.com/" @@ -633,7 +898,7 @@ http = HttpMockSequence( [ ({"status": "404"}, "Not found"), - ({"status": "200"}, open(datafile("zoo.json"), "rb").read()), + ({"status": "200"}, read_datafile("zoo.json", "rb")), ] ) api_endpoint = "https://foo.googleapis.com/" @@ -646,9 +911,103 @@ ) self.assertEqual(zoo._baseUrl, api_endpoint) + def test_discovery_with_empty_version_uses_v2(self): + http = HttpMockSequence( + [ + ({"status": "200"}, read_datafile("zoo.json", "rb")), + ] + ) + build("zoo", version=None, http=http, cache_discovery=False) + validate_discovery_requests(self, http, "zoo", None, V2_DISCOVERY_URI) -class DiscoveryFromAppEngineCache(unittest.TestCase): + def test_discovery_with_empty_version_preserves_custom_uri(self): + http = HttpMockSequence( + [ + ({"status": "200"}, read_datafile("zoo.json", "rb")), + ] + ) + custom_discovery_uri = "https://foo.bar/$discovery" + build( + "zoo", version=None, http=http, + cache_discovery=False, discoveryServiceUrl=custom_discovery_uri) + validate_discovery_requests( + self, http, "zoo", None, custom_discovery_uri) + + def test_discovery_with_valid_version_uses_v1(self): + http = HttpMockSequence( + [ + ({"status": "200"}, read_datafile("zoo.json", "rb")), + ] + ) + build("zoo", version="v123", http=http, cache_discovery=False) + validate_discovery_requests(self, http, "zoo", "v123", V1_DISCOVERY_URI) + + +class DiscoveryRetryFromHttp(unittest.TestCase): + def test_repeated_500_retries_and_fails(self): + http = HttpMockSequence( + [ + ({"status": "500"}, read_datafile("500.json", "rb")), + ({"status": "503"}, read_datafile("503.json", "rb")), + ] + ) + with self.assertRaises(HttpError): + with mock.patch("time.sleep") as mocked_sleep: + build("zoo", "v1", http=http, cache_discovery=False) + + mocked_sleep.assert_called_once() + # We also want to verify that we stayed with v1 discovery + validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI) + + def test_v2_repeated_500_retries_and_fails(self): + http = HttpMockSequence( + [ + ({"status": "404"}, "Not found"), # last v1 discovery call + ({"status": "500"}, read_datafile("500.json", "rb")), + ({"status": "503"}, read_datafile("503.json", "rb")), + ] + ) + with self.assertRaises(HttpError): + with mock.patch("time.sleep") as mocked_sleep: + build("zoo", "v1", http=http, cache_discovery=False) + + mocked_sleep.assert_called_once() + # We also want to verify that we switched to v2 discovery + validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI) + + def test_single_500_retries_and_succeeds(self): + http = HttpMockSequence( + [ + ({"status": "500"}, read_datafile("500.json", "rb")), + ({"status": "200"}, read_datafile("zoo.json", "rb")), + ] + ) + with mock.patch("time.sleep") as mocked_sleep: + zoo = build("zoo", "v1", http=http, cache_discovery=False) + self.assertTrue(hasattr(zoo, "animals")) + mocked_sleep.assert_called_once() + # We also want to verify that we stayed with v1 discovery + validate_discovery_requests(self, http, "zoo", "v1", V1_DISCOVERY_URI) + + def test_single_500_then_404_retries_and_succeeds(self): + http = HttpMockSequence( + [ + ({"status": "500"}, read_datafile("500.json", "rb")), + ({"status": "404"}, "Not found"), # last v1 discovery call + ({"status": "200"}, read_datafile("zoo.json", "rb")), + ] + ) + with mock.patch("time.sleep") as mocked_sleep: + zoo = build("zoo", "v1", http=http, cache_discovery=False) + + self.assertTrue(hasattr(zoo, "animals")) + mocked_sleep.assert_called_once() + # We also want to verify that we switched to v2 discovery + validate_discovery_requests(self, http, "zoo", "v1", V2_DISCOVERY_URI) + + +class DiscoveryFromAppEngineCache(unittest.TestCase): def setUp(self): self.old_environ = os.environ.copy() os.environ["APPENGINE_RUNTIME"] = "python27" @@ -685,8 +1044,7 @@ ) # memcache.set is called once - with open(datafile("plus.json")) as f: - content = f.read() + content = read_datafile("plus.json") self.mocked_api.memcache.set.assert_called_once_with( url, content, time=DISCOVERY_DOC_MAX_AGE, namespace=namespace ) @@ -743,8 +1101,7 @@ cache.get.assert_called_once_with(url) # cache.set is called once - with open(datafile("plus.json")) as f: - content = f.read() + content = read_datafile("plus.json") cache.set.assert_called_once_with(url, content) # Make sure there is a cache entry for the plus v1 discovery doc. @@ -905,7 +1262,7 @@ def test_tunnel_patch(self): http = HttpMockSequence( [ - ({"status": "200"}, open(datafile("zoo.json"), "rb").read()), + ({"status": "200"}, read_datafile("zoo.json", "rb")), ({"status": "200"}, "echo_request_headers_as_json"), ] ) @@ -925,13 +1282,13 @@ credentials = mock.Mock(spec=GoogleCredentials) credentials.create_scoped_required.return_value = False - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") service = build_from_document(discovery, credentials=credentials) self.assertEqual(service._http, credentials.authorize.return_value) def test_google_auth_credentials(self): credentials = mock.Mock(spec=google.auth.credentials.Credentials) - discovery = open(datafile("plus.json")).read() + discovery = read_datafile("plus.json") service = build_from_document(discovery, credentials=credentials) self.assertIsInstance(service._http, google_auth_httplib2.AuthorizedHttp) @@ -939,7 +1296,7 @@ def test_no_scopes_no_credentials(self): # Zoo doesn't have scopes - discovery = open(datafile("zoo.json")).read() + discovery = read_datafile("zoo.json") service = build_from_document(discovery) # Should be an ordinary httplib2.Http instance and not AuthorizedHttp. self.assertIsInstance(service._http, httplib2.Http) @@ -1069,8 +1426,7 @@ request = zoo.animals().insert(media_body=datafile("small.png"), body={}) self.assertTrue(request.headers["content-type"].startswith("multipart/related")) - with open(datafile("small.png"), "rb") as f: - contents = f.read() + contents = read_datafile("small.png", "rb") boundary = re.match(b"--=+([^=]+)", request.body).group(1) self.assertEqual( request.body.rstrip(b"\n"), # Python 2.6 does not add a trailing \n @@ -1116,7 +1472,7 @@ self.assertEqual("image/png", request.resumable.mimetype()) - self.assertNotEquals(request.body, None) + self.assertNotEqual(request.body, None) self.assertEqual(request.resumable_uri, None) http = HttpMockSequence( @@ -1580,8 +1936,7 @@ # instances upon un-pickling def _dummy_zoo_request(self): - with open(os.path.join(DATA_DIR, "zoo.json"), "rU") as fh: - zoo_contents = fh.read() + zoo_contents = read_datafile("zoo.json") zoo_uri = uritemplate.expand(DISCOVERY_URI, {"api": "zoo", "apiVersion": "v1"}) if "REMOTE_ADDR" in os.environ: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/google-api-python-client-1.8.4/tests/test_http.py new/google-api-python-client-1.11.0/tests/test_http.py --- old/google-api-python-client-1.8.4/tests/test_http.py 2020-05-26 22:17:53.000000000 +0200 +++ new/google-api-python-client-1.11.0/tests/test_http.py 2020-08-27 23:33:51.000000000 +0200 @@ -1097,6 +1097,11 @@ request.execute() request._sleep.assert_not_called() + def test_null_postproc(self): + resp, content = HttpRequest.null_postproc("foo", "bar") + self.assertEqual(resp, "foo") + self.assertEqual(content, "bar") + class TestBatch(unittest.TestCase): def setUp(self): ++++++ python-google-api-python-client-no-unittest2.patch ++++++ --- /var/tmp/diff_new_pack.ucJiIH/_old 2020-10-15 13:49:24.345274408 +0200 +++ /var/tmp/diff_new_pack.ucJiIH/_new 2020-10-15 13:49:24.345274408 +0200 @@ -1,8 +1,7 @@ -Index: google-api-python-client-1.8.4/tests/test__auth.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test__auth.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test__auth.py 2020-06-01 15:48:23.340280191 +0200 -@@ -18,7 +18,7 @@ import google.auth.credentials +diff -Nru google-api-python-client-1.11.0.orig/tests/test__auth.py google-api-python-client-1.11.0/tests/test__auth.py +--- google-api-python-client-1.11.0.orig/tests/test__auth.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test__auth.py 2020-09-10 15:54:13.243410474 +0200 +@@ -18,7 +18,7 @@ import google_auth_httplib2 import httplib2 import oauth2client.client @@ -11,11 +10,10 @@ from googleapiclient import _auth -Index: google-api-python-client-1.8.4/tests/test_channel.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_channel.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_channel.py 2020-06-01 15:48:45.200411976 +0200 -@@ -3,7 +3,7 @@ from __future__ import absolute_import +diff -Nru google-api-python-client-1.11.0.orig/tests/test_channel.py google-api-python-client-1.11.0/tests/test_channel.py +--- google-api-python-client-1.11.0.orig/tests/test_channel.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_channel.py 2020-09-10 15:54:13.243410474 +0200 +@@ -3,7 +3,7 @@ __author__ = "jcgrego...@google.com (Joe Gregorio)" @@ -24,23 +22,9 @@ import datetime from googleapiclient import channel -Index: google-api-python-client-1.8.4/tests/test_discovery.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_discovery.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_discovery.py 2020-06-01 15:49:06.480540264 +0200 -@@ -37,7 +37,7 @@ import os - import pickle - import re - import sys --import unittest2 as unittest -+import unittest - - import mock - -Index: google-api-python-client-1.8.4/tests/test_discovery_cache.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_discovery_cache.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_discovery_cache.py 2020-06-01 15:49:21.664631801 +0200 +diff -Nru google-api-python-client-1.11.0.orig/tests/test_discovery_cache.py google-api-python-client-1.11.0/tests/test_discovery_cache.py +--- google-api-python-client-1.11.0.orig/tests/test_discovery_cache.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_discovery_cache.py 2020-09-10 15:54:13.243410474 +0200 @@ -18,7 +18,7 @@ """Discovery document cache tests.""" @@ -50,11 +34,22 @@ import mock -Index: google-api-python-client-1.8.4/tests/test_errors.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_errors.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_errors.py 2020-06-01 15:49:32.728698502 +0200 -@@ -21,7 +21,7 @@ from __future__ import absolute_import +diff -Nru google-api-python-client-1.11.0.orig/tests/test_discovery.py google-api-python-client-1.11.0/tests/test_discovery.py +--- google-api-python-client-1.11.0.orig/tests/test_discovery.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_discovery.py 2020-09-10 15:54:40.883760261 +0200 +@@ -37,7 +37,7 @@ + import pickle + import re + import sys +-import unittest2 as unittest ++import unittest + from collections import defaultdict + + from parameterized import parameterized +diff -Nru google-api-python-client-1.11.0.orig/tests/test_errors.py google-api-python-client-1.11.0/tests/test_errors.py +--- google-api-python-client-1.11.0.orig/tests/test_errors.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_errors.py 2020-09-10 15:54:13.247410524 +0200 +@@ -21,7 +21,7 @@ __author__ = "afs...@google.com (Ali Afshar)" @@ -63,11 +58,10 @@ import httplib2 -Index: google-api-python-client-1.8.4/tests/test_http.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_http.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_http.py 2020-06-01 15:49:53.456823460 +0200 -@@ -35,7 +35,7 @@ import io +diff -Nru google-api-python-client-1.11.0.orig/tests/test_http.py google-api-python-client-1.11.0/tests/test_http.py +--- google-api-python-client-1.11.0.orig/tests/test_http.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_http.py 2020-09-10 15:54:13.247410524 +0200 +@@ -35,7 +35,7 @@ import logging import mock import os @@ -76,11 +70,10 @@ import random import socket import ssl -Index: google-api-python-client-1.8.4/tests/test_json_model.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_json_model.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_json_model.py 2020-06-01 15:50:08.064911523 +0200 -@@ -28,7 +28,7 @@ import json +diff -Nru google-api-python-client-1.11.0.orig/tests/test_json_model.py google-api-python-client-1.11.0/tests/test_json_model.py +--- google-api-python-client-1.11.0.orig/tests/test_json_model.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_json_model.py 2020-09-10 15:54:13.247410524 +0200 +@@ -28,7 +28,7 @@ import os import pkg_resources import platform @@ -89,11 +82,10 @@ import httplib2 import googleapiclient.model -Index: google-api-python-client-1.8.4/tests/test_mocks.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_mocks.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_mocks.py 2020-06-01 15:50:21.572991364 +0200 -@@ -24,7 +24,7 @@ __author__ = "jcgrego...@google.com (Joe +diff -Nru google-api-python-client-1.11.0.orig/tests/test_mocks.py google-api-python-client-1.11.0/tests/test_mocks.py +--- google-api-python-client-1.11.0.orig/tests/test_mocks.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_mocks.py 2020-09-10 15:54:13.247410524 +0200 +@@ -24,7 +24,7 @@ import httplib2 import os @@ -102,11 +94,10 @@ from googleapiclient.errors import HttpError from googleapiclient.errors import UnexpectedBodyError -Index: google-api-python-client-1.8.4/tests/test_model.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_model.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_model.py 2020-06-01 15:50:35.321072029 +0200 -@@ -23,7 +23,7 @@ from __future__ import absolute_import +diff -Nru google-api-python-client-1.11.0.orig/tests/test_model.py google-api-python-client-1.11.0/tests/test_model.py +--- google-api-python-client-1.11.0.orig/tests/test_model.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_model.py 2020-09-10 15:54:13.247410524 +0200 +@@ -23,7 +23,7 @@ __author__ = "jcgrego...@google.com (Joe Gregorio)" @@ -115,11 +106,10 @@ from googleapiclient.model import BaseModel from googleapiclient.model import makepatch -Index: google-api-python-client-1.8.4/tests/test_protobuf_model.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_protobuf_model.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_protobuf_model.py 2020-06-01 15:50:53.605179328 +0200 -@@ -22,7 +22,7 @@ from __future__ import absolute_import +diff -Nru google-api-python-client-1.11.0.orig/tests/test_protobuf_model.py google-api-python-client-1.11.0/tests/test_protobuf_model.py +--- google-api-python-client-1.11.0.orig/tests/test_protobuf_model.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_protobuf_model.py 2020-09-10 15:54:13.247410524 +0200 +@@ -22,7 +22,7 @@ __author__ = "mmcdon...@google.com (Matt McDonald)" @@ -128,11 +118,10 @@ import httplib2 import googleapiclient.model -Index: google-api-python-client-1.8.4/tests/test_schema.py -=================================================================== ---- google-api-python-client-1.8.4.orig/tests/test_schema.py 2020-05-26 22:17:53.000000000 +0200 -+++ google-api-python-client-1.8.4/tests/test_schema.py 2020-06-01 15:51:08.885268956 +0200 -@@ -19,7 +19,7 @@ __author__ = "jcgrego...@google.com (Joe +diff -Nru google-api-python-client-1.11.0.orig/tests/test_schema.py google-api-python-client-1.11.0/tests/test_schema.py +--- google-api-python-client-1.11.0.orig/tests/test_schema.py 2020-08-27 23:33:51.000000000 +0200 ++++ google-api-python-client-1.11.0/tests/test_schema.py 2020-09-10 15:54:13.247410524 +0200 +@@ -19,7 +19,7 @@ import json import os