-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
This issue is very similar to #211. My team has an app-engine application that uses batch requests in the Gmail API. After an upgrade of this client library, we started seeing failures:
...
File "/base/data/home/apps/s~app-id/modname:version/path/to/my/code.py", line 241, in _user_method
return batch_request.execute(http=self._http)
File "/base/data/home/apps/s~app-id/modname:version/third_party/oauth2client/_helpers.py", line 133, in positional_wrapper
return wrapped(*args, **kwargs)
File "/base/data/home/apps/s~app-id/modname:version/third_party/googleapiclient/http.py", line 1417, in execute
self._execute(http, self._order, self._requests)
File "/base/data/home/apps/s~app-id/modname:version/third_party/googleapiclient/http.py", line 1333, in _execute
body = self._serialize_request(request)
File "/base/data/home/apps/s~app-id/modname:version/third_party/googleapiclient/http.py", line 1204, in _serialize_request
request.http.request.credentials.apply(headers)
File "/base/data/home/apps/s~app-id/modname:version/third_party/oauth2client/client.py", line 558, in apply
headers['Authorization'] = 'Bearer ' + self.access_token
TypeError: cannot concatenate 'str' and 'NoneType' objects
Our usage of this client library looks like the following:
from googleapiclient.discovery import build
gmail_service = build('gmail', 'v1') # Uses application credentials
...
authorized_http = user_credentials.authorize(httplib2.Http()) # Uses end user credentials
batch_request = gmail_service.new_batch_http_request()
batch_request.add(
gmail_service.users().threads().get(id=thread_id, userId='me'),
callback=callback,
request_id=thread_id)
batch_request.execute(http=authorized_http)The gmail_service is cached at the application level to avoid needing to do an API call for each request (which is why it ends up with the application credentials). What I believe is happening is that
gmail_service.users().threads().get(id=thread_id, userId='me')ends up creating an HttpRequest that uses the same http that was used to construct gmail_service (i.e. the application credentials) and I believe that the application credentials have no access_token. This results in the TypeError seen above. The fix in #232 won't help (the http that is getting passed to batch_request.execute is valid).
It seems to me that the application of credentials in _serialize_request should be conditional on the credentials actually having an access token:
if request.http is not None and hasattr(request.http.request,
'credentials'):
if request.http.request.credentials.access_token:
request.http.request.credentials.apply(headers)Otherwise, if the sub-request isn't authenticated then the authentication from the outer request should be used (at least, if the gmail docs are any indicator):
The HTTP headers for the outer batch request, except for the Content- headers such as Content-Type, apply to every request in the batch. If you specify a given HTTP header in both the outer request and an individual call, then the individual call header's value overrides the outer batch request header's value. The headers for an individual call apply only to that call.
For example, if you provide an Authorization header for a specific call, then that header applies only to that call. If you provide an Authorization header for the outer request, then that header applies to all of the individual calls unless they override it with Authorization headers of their own.
In the event that this behavior is actually working as intended or if my proposed fix isn't satisfactory and a real fix would be too difficult, a work-around to this issue is possible by updating the requests that you add to the batch with an appropriately authorized http instance. i.e changing the above to:
batch_request = gmail_service.new_batch_http_request()
request = gmail_service.users().threads().get(id=thread_id, userId='me')
request.http = authorized_http # explicitly set the authorization on the request.
batch_request.add(
request,
callback=callback,
request_id=thread_id)
batch_request.execute(http=authorized_http)Seems to fix the issue.