From d33eb3b455dbda7ec32f361c3e81d4ebffd4146e Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Wed, 13 Sep 2017 20:50:08 -0400 Subject: [PATCH] googleapiclient 1.6.3 --- src/googleapiclient/__init__.py | 2 +- src/googleapiclient/_auth.py | 45 +++++++++++++++++++++++++++++++-- src/googleapiclient/errors.py | 11 +++++++- src/googleapiclient/http.py | 42 +++++++++++++++++------------- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/src/googleapiclient/__init__.py b/src/googleapiclient/__init__.py index 4af0c3c2..97977bb7 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.6.2" +__version__ = "1.6.3" # Set default logging handler to avoid "No handler found" warnings. import logging diff --git a/src/googleapiclient/_auth.py b/src/googleapiclient/_auth.py index 87d37095..92f2796f 100644 --- a/src/googleapiclient/_auth.py +++ b/src/googleapiclient/_auth.py @@ -14,6 +14,8 @@ """Helpers for authentication using oauth2client or google-auth.""" +import httplib2 + try: import google.auth import google.auth.credentials @@ -29,8 +31,6 @@ try: except ImportError: # pragma: NO COVER HAS_OAUTH2CLIENT = False -from googleapiclient.http import build_http - def default_credentials(): """Returns Application Default Credentials.""" @@ -84,9 +84,50 @@ def authorized_http(credentials): Union[httplib2.Http, google_auth_httplib2.AuthorizedHttp]: An authorized http client. """ + from googleapiclient.http import build_http + if HAS_GOOGLE_AUTH and isinstance( credentials, google.auth.credentials.Credentials): return google_auth_httplib2.AuthorizedHttp(credentials, http=build_http()) else: return credentials.authorize(build_http()) + + +def refresh_credentials(credentials): + # Refresh must use a new http instance, as the one associated with the + # credentials could be a AuthorizedHttp or an oauth2client-decorated + # Http instance which would cause a weird recursive loop of refreshing + # and likely tear a hole in spacetime. + refresh_http = httplib2.Http() + if HAS_GOOGLE_AUTH and isinstance( + credentials, google.auth.credentials.Credentials): + request = google_auth_httplib2.Request(refresh_http) + return credentials.refresh(request) + else: + return credentials.refresh(refresh_http) + + +def apply_credentials(credentials, headers): + # oauth2client and google-auth have the same interface for this. + return credentials.apply(headers) + + +def is_valid(credentials): + if HAS_GOOGLE_AUTH and isinstance( + credentials, google.auth.credentials.Credentials): + return credentials.valid + else: + return not credentials.access_token_expired + + +def get_credentials_from_http(http): + if http is None: + return None + elif hasattr(http.request, 'credentials'): + return http.request.credentials + elif (hasattr(http, 'credentials') + and not isinstance(http.credentials, httplib2.Credentials)): + return http.credentials + else: + return None diff --git a/src/googleapiclient/errors.py b/src/googleapiclient/errors.py index 6a741f86..bab14189 100644 --- a/src/googleapiclient/errors.py +++ b/src/googleapiclient/errors.py @@ -46,6 +46,7 @@ class HttpError(Error): raise TypeError("HTTP content should be bytes") self.content = content self.uri = uri + self.error_details = '' def _get_reason(self): """Calculate the reason for the error from the response content.""" @@ -54,9 +55,13 @@ class HttpError(Error): data = json.loads(self.content.decode('utf-8')) if isinstance(data, dict): reason = data['error']['message'] + if 'details' in data['error']: + self.error_details = data['error']['details'] elif isinstance(data, list) and len(data) > 0: first_error = data[0] reason = first_error['error']['message'] + if 'details' in first_error['error']: + self.error_details = first_error['error']['details'] except (ValueError, KeyError, TypeError): pass if reason is None: @@ -64,7 +69,11 @@ class HttpError(Error): return reason def __repr__(self): - if self.uri: + reason = self._get_reason() + if self.error_details: + return '' % \ + (self.resp.status, self.uri, reason.strip(), self.error_details) + elif self.uri: return '' % ( self.resp.status, self.uri, self._get_reason().strip()) else: diff --git a/src/googleapiclient/http.py b/src/googleapiclient/http.py index 4330f26e..f5d08a19 100644 --- a/src/googleapiclient/http.py +++ b/src/googleapiclient/http.py @@ -62,6 +62,7 @@ try: except ImportError: from oauth2client import _helpers as util +from googleapiclient import _auth from googleapiclient import mimeparse from googleapiclient.errors import BatchError from googleapiclient.errors import HttpError @@ -203,7 +204,7 @@ class MediaUploadProgress(object): the percentage complete as a float, returning 0.0 if the total size of the upload is unknown. """ - if self.total_size is not None: + if self.total_size is not None and self.total_size != 0: return float(self.resumable_progress) / float(self.total_size) else: return 0.0 @@ -229,7 +230,7 @@ class MediaDownloadProgress(object): the percentage complete as a float, returning 0.0 if the total size of the download is unknown. """ - if self.total_size is not None: + if self.total_size is not None and self.total_size != 0: return float(self.resumable_progress) / float(self.total_size) else: return 0.0 @@ -1126,21 +1127,25 @@ class BatchHttpRequest(object): # If there is no http per the request then refresh the http passed in # via execute() creds = None - if request.http is not None and hasattr(request.http.request, - 'credentials'): - creds = request.http.request.credentials - elif http is not None and hasattr(http.request, 'credentials'): - creds = http.request.credentials + request_credentials = False + + if request.http is not None: + creds = _auth.get_credentials_from_http(request.http) + request_credentials = True + + if creds is None and http is not None: + creds = _auth.get_credentials_from_http(http) + if creds is not None: if id(creds) not in self._refreshed_credentials: - creds.refresh(http) + _auth.refresh_credentials(creds) self._refreshed_credentials[id(creds)] = 1 # Only apply the credentials if we are using the http object passed in, # otherwise apply() will get called during _serialize_request(). - if request.http is None or not hasattr(request.http.request, - 'credentials'): - creds.apply(request.headers) + if request.http is None or not request_credentials: + _auth.apply_credentials(creds, request.headers) + def _id_to_header(self, id_): """Convert an id to a Content-ID header value. @@ -1200,9 +1205,10 @@ class BatchHttpRequest(object): msg = MIMENonMultipart(major, minor) headers = request.headers.copy() - if request.http is not None and hasattr(request.http.request, - 'credentials'): - request.http.request.credentials.apply(headers) + if request.http is not None: + credentials = _auth.get_credentials_from_http(request.http) + if credentials is not None: + _auth.apply_credentials(credentials, headers) # MIMENonMultipart adds its own Content-Type header. if 'content-type' in headers: @@ -1409,11 +1415,11 @@ class BatchHttpRequest(object): # Special case for OAuth2Credentials-style objects which have not yet been # refreshed with an initial access_token. - if getattr(http.request, 'credentials', None) is not None: - creds = http.request.credentials - if not getattr(creds, 'access_token', None): + creds = _auth.get_credentials_from_http(http) + if creds is not None: + if not _auth.is_valid(creds): LOGGER.info('Attempting refresh to obtain initial access_token') - creds.refresh(http) + _auth.refresh_credentials(creds) self._execute(http, self._order, self._requests)