From b06b8608d0620e8f1a2760513a6dc3d4fff4c585 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Tue, 24 Jan 2017 14:03:03 -0500 Subject: [PATCH] googleapiclient 1.6.1 --- src/googleapiclient/__init__.py | 2 +- src/googleapiclient/_auth.py | 91 ++++++++++++++++++++++++++++++++ src/googleapiclient/discovery.py | 67 ++++++++++++----------- src/googleapiclient/http.py | 6 ++- 4 files changed, 133 insertions(+), 33 deletions(-) create mode 100644 src/googleapiclient/_auth.py diff --git a/src/googleapiclient/__init__.py b/src/googleapiclient/__init__.py index e80f4475..1d2baf16 100644 --- a/src/googleapiclient/__init__.py +++ b/src/googleapiclient/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.5.5" +__version__ = "1.6.1" # Set default logging handler to avoid "No handler found" warnings. import logging diff --git a/src/googleapiclient/_auth.py b/src/googleapiclient/_auth.py new file mode 100644 index 00000000..044ed12d --- /dev/null +++ b/src/googleapiclient/_auth.py @@ -0,0 +1,91 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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 authentication using oauth2client or google-auth.""" + +import httplib2 + +try: + import google.auth + import google.auth.credentials + import google_auth_httplib2 + HAS_GOOGLE_AUTH = True +except ImportError: # pragma: NO COVER + HAS_GOOGLE_AUTH = False + +try: + import oauth2client + import oauth2client.client + HAS_OAUTH2CLIENT = True +except ImportError: # pragma: NO COVER + HAS_OAUTH2CLIENT = False + + +def default_credentials(): + """Returns Application Default Credentials.""" + if HAS_GOOGLE_AUTH: + credentials, _ = google.auth.default() + return credentials + elif HAS_OAUTH2CLIENT: + return oauth2client.client.GoogleCredentials.get_application_default() + else: + raise EnvironmentError( + 'No authentication library is available. Please install either ' + 'google-auth or oauth2client.') + + +def with_scopes(credentials, scopes): + """Scopes the credentials if necessary. + + Args: + credentials (Union[ + google.auth.credentials.Credentials, + oauth2client.client.Credentials]): The credentials to scope. + scopes (Sequence[str]): The list of scopes. + + Returns: + Union[google.auth.credentials.Credentials, + oauth2client.client.Credentials]: The scoped credentials. + """ + if HAS_GOOGLE_AUTH and isinstance( + credentials, google.auth.credentials.Credentials): + return google.auth.credentials.with_scopes_if_required( + credentials, scopes) + else: + try: + if credentials.create_scoped_required(): + return credentials.create_scoped(scopes) + else: + return credentials + except AttributeError: + return credentials + + +def authorized_http(credentials): + """Returns an http client that is authorized with the given credentials. + + Args: + credentials (Union[ + google.auth.credentials.Credentials, + oauth2client.client.Credentials]): The credentials to use. + + Returns: + Union[httplib2.Http, google_auth_httplib2.AuthorizedHttp]: An + authorized http client. + """ + if HAS_GOOGLE_AUTH and isinstance( + credentials, google.auth.credentials.Credentials): + return google_auth_httplib2.AuthorizedHttp(credentials) + else: + return credentials.authorize(httplib2.Http()) diff --git a/src/googleapiclient/discovery.py b/src/googleapiclient/discovery.py index 4e7b7363..74f0a09e 100644 --- a/src/googleapiclient/discovery.py +++ b/src/googleapiclient/discovery.py @@ -53,6 +53,7 @@ import httplib2 import uritemplate # Local imports +from googleapiclient import _auth from googleapiclient import mimeparse from googleapiclient.errors import HttpError from googleapiclient.errors import InvalidJsonError @@ -197,7 +198,8 @@ def build(serviceName, model: googleapiclient.Model, converts to and from the wire format. requestBuilder: googleapiclient.http.HttpRequest, encapsulator for an HTTP request. - credentials: oauth2client.Credentials, credentials to be used for + credentials: oauth2client.Credentials or + google.auth.credentials.Credentials, credentials to be used for authentication. cache_discovery: Boolean, whether or not to cache the discovery doc. cache: googleapiclient.discovery_cache.base.CacheBase, an optional @@ -211,15 +213,14 @@ def build(serviceName, 'apiVersion': version } - if http is None: - http = httplib2.Http() + discovery_http = http if http is not None else httplib2.Http() for discovery_url in (discoveryServiceUrl, V2_DISCOVERY_URI,): requested_url = uritemplate.expand(discovery_url, params) try: - content = _retrieve_discovery_doc(requested_url, http, cache_discovery, - cache) + content = _retrieve_discovery_doc( + requested_url, discovery_http, cache_discovery, cache) return build_from_document(content, base=discovery_url, http=http, developerKey=developerKey, model=model, requestBuilder=requestBuilder, credentials=credentials) @@ -316,17 +317,16 @@ def build_from_document( model: Model class instance that serializes and de-serializes requests and responses. requestBuilder: Takes an http request and packages it up to be executed. - credentials: object, credentials to be used for authentication. + credentials: oauth2client.Credentials or + google.auth.credentials.Credentials, credentials to be used for + authentication. Returns: A Resource object with methods for interacting with the service. """ - if http is None: - http = httplib2.Http() - - # future is no longer used. - future = {} + if http is not None and credentials is not None: + raise ValueError('Arguments http and credentials are mutually exclusive.') if isinstance(service, six.string_types): service = json.loads(service) @@ -342,31 +342,36 @@ def build_from_document( base = urljoin(service['rootUrl'], service['servicePath']) schema = Schemas(service) - if credentials: - # If credentials were passed in, we could have two cases: - # 1. the scopes were specified, in which case the given credentials - # are used for authorizing the http; - # 2. the scopes were not provided (meaning the Application Default - # Credentials are to be used). In this case, the Application Default - # Credentials are built and used instead of the original credentials. - # If there are no scopes found (meaning the given service requires no - # authentication), there is no authorization of the http. - if (isinstance(credentials, GoogleCredentials) and - credentials.create_scoped_required()): - scopes = service.get('auth', {}).get('oauth2', {}).get('scopes', {}) - if scopes: - credentials = credentials.create_scoped(list(scopes.keys())) - else: - # No need to authorize the http object - # if the service does not require authentication. - credentials = None + # If the http client is not specified, then we must construct an http client + # to make requests. If the service has scopes, then we also need to setup + # authentication. + if http is None: + # Does the service require scopes? + scopes = list( + service.get('auth', {}).get('oauth2', {}).get('scopes', {}).keys()) - if credentials: - http = credentials.authorize(http) + # If so, then the we need to setup authentication. + if scopes: + # If the user didn't pass in credentials, attempt to acquire application + # default credentials. + if credentials is None: + credentials = _auth.default_credentials() + + # The credentials need to be scoped. + credentials = _auth.with_scopes(credentials, scopes) + + # Create an authorized http instance + http = _auth.authorized_http(credentials) + + # If the service doesn't require scopes then there is no need for + # authentication. + else: + http = httplib2.Http() if model is None: features = service.get('features', []) model = JsonModel('dataWrapper' in features) + return Resource(http=http, baseUrl=base, model=model, developerKey=developerKey, requestBuilder=requestBuilder, resourceDesc=service, rootDesc=service, schema=schema) diff --git a/src/googleapiclient/http.py b/src/googleapiclient/http.py index 14580f0c..0ef10b95 100644 --- a/src/googleapiclient/http.py +++ b/src/googleapiclient/http.py @@ -996,7 +996,11 @@ class HttpRequest(object): elif resp.status == 308: self._in_error_state = False # A "308 Resume Incomplete" indicates we are not done. - self.resumable_progress = int(resp['range'].split('-')[1]) + 1 + try: + self.resumable_progress = int(resp['range'].split('-')[1]) + 1 + except KeyError: + # If resp doesn't contain range header, resumable progress is 0 + self.resumable_progress = 0 if 'location' in resp: self.resumable_uri = resp['location'] else: