Repository: google/google-api-python-client (python)

Issue:
Change from 1.5.5 to 1.6.2 results in authentication failure.
We have some code that successfully talks to a Google API using v1.5.5 but breaks under v1.6.2 with the following error:

   "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential"

Our build statement looks like:

     return build(
         'monitoring',
         'v3',
         discoveryServiceUrl=discovery_url,
         developerKey=self.api_key,
         credentials=credentials)

I'm wondering if the following commit may be culprit here:

    https://github.com/google/google-api-python-client/commit/068eb35c9a70e31a42f04d5cb69c36d59e4a2a69

Because we supply both "credentials" and "developerKey" and expect the bearer token / other context from the credentials to be propagated regardless of the presence of "developerKey" (it was my understanding that "developerKey" simply adds a "key=<api-key>" to the discovery URL and to the API requests, but the headers implied by the credentials should still be provided in this case). 

Can you fix this? Thanks!

Pull request:
Remove mutual exclusivity check for developerKey and credentials
Resolves #357

Patch:
diff --git a/docs/epy/googleapiclient.http-pysrc.html b/docs/epy/googleapiclient.http-pysrc.html
index efaa79981..3506be86b 100644
--- a/docs/epy/googleapiclient.http-pysrc.html
+++ b/docs/epy/googleapiclient.http-pysrc.html
@@ -844,7 +844,7 @@ googleapiclient.http.MediaUpload.chunksize" class="py-name" href="#" onclick="re
 <a name="L655"></a><tt class="py-lineno"> 655</tt>  <tt class="py-line"><tt class="py-docstring">    Returns:</tt> </tt>
 <a name="L656"></a><tt class="py-lineno"> 656</tt>  <tt class="py-line"><tt class="py-docstring">      (status, done): (MediaDownloadStatus, boolean)</tt> </tt>
 <a name="L657"></a><tt class="py-lineno"> 657</tt>  <tt class="py-line"><tt class="py-docstring">         The value of 'done' will be True when the media has been fully</tt> </tt>
-<a name="L658"></a><tt class="py-lineno"> 658</tt>  <tt class="py-line"><tt class="py-docstring">         downloaded.</tt> </tt>
+<a name="L658"></a><tt class="py-lineno"> 658</tt>  <tt class="py-line"><tt class="py-docstring">         downloaded or the total size of the media is unknown.</tt> </tt>
 <a name="L659"></a><tt class="py-lineno"> 659</tt>  <tt class="py-line"><tt class="py-docstring"></tt> </tt>
 <a name="L660"></a><tt class="py-lineno"> 660</tt>  <tt class="py-line"><tt class="py-docstring">    Raises:</tt> </tt>
 <a name="L661"></a><tt class="py-lineno"> 661</tt>  <tt class="py-line"><tt class="py-docstring">      googleapiclient.errors.HttpError if the response was not a 2xx.</tt> </tt>
@@ -873,7 +873,7 @@ googleapiclient.http.MediaUpload.chunksize" class="py-name" href="#" onclick="re
 <a name="L684"></a><tt class="py-lineno"> 684</tt>  <tt class="py-line">      <tt class="py-keyword">elif</tt> <tt class="py-string">'content-length'</tt> <tt class="py-keyword">in</tt> <tt class="py-name">resp</tt><tt class="py-op">:</tt> </tt>
 <a name="L685"></a><tt class="py-lineno"> 685</tt>  <tt class="py-line">        <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_total_size</tt> <tt class="py-op">=</tt> <tt class="py-name">int</tt><tt class="py-op">(</tt><tt class="py-name">resp</tt><tt class="py-op">[</tt><tt class="py-string">'content-length'</tt><tt class="py-op">]</tt><tt class="py-op">)</tt> </tt>
 <a name="L686"></a><tt class="py-lineno"> 686</tt>  <tt class="py-line"> </tt>
-<a name="L687"></a><tt class="py-lineno"> 687</tt>  <tt class="py-line">      <tt class="py-keyword">if</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_progress</tt> <tt class="py-op">==</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_total_size</tt><tt class="py-op">:</tt> </tt>
+<a name="L687"></a><tt class="py-lineno"> 687</tt>  <tt class="py-line">      <tt class="py-keyword">if</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_total_size</tt> <tt class="py-keyword">is</tt> <tt class="py-name">None</tt> <tt class="py-keyword">or</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_progress</tt> <tt class="py-op">==</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_total_size</tt><tt class="py-op">:</tt> </tt>
 <a name="L688"></a><tt class="py-lineno"> 688</tt>  <tt class="py-line">        <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_done</tt> <tt class="py-op">=</tt> <tt class="py-name">True</tt> </tt>
 <a name="L689"></a><tt class="py-lineno"> 689</tt>  <tt class="py-line">      <tt class="py-keyword">return</tt> <tt id="link-86" class="py-name" targets="Class googleapiclient.http.MediaDownloadProgress=googleapiclient.http.MediaDownloadProgress-class.html"><a title="googleapiclient.http.MediaDownloadProgress" class="py-name" href="#" onclick="return doclink('link-86', 'MediaDownloadProgress', 'link-86');">MediaDownloadProgress</a></tt><tt class="py-op">(</tt><tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_progress</tt><tt class="py-op">,</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_total_size</tt><tt class="py-op">)</tt><tt class="py-op">,</tt> <tt class="py-name">self</tt><tt class="py-op">.</tt><tt class="py-name">_done</tt> </tt>
 <a name="L690"></a><tt class="py-lineno"> 690</tt>  <tt class="py-line">    <tt class="py-keyword">else</tt><tt class="py-op">:</tt> </tt>
@@ -2189,7 +2189,7 @@ expandto(location.href);
 <table border="0" cellpadding="0" cellspacing="0" width="100%%">
   <tr>
     <td align="left" class="footer">
-    Generated by Epydoc 3.0.1 on Mon Jun  5 13:38:40 2017
+    Generated by Epydoc 3.0.1 on Wed Jun 14 20:18:21 2017
     </td>
     <td align="right" class="footer">
       <a target="mainFrame" href="http://epydoc.sourceforge.net"
diff --git a/docs/epy/googleapiclient.http.MediaIoBaseDownload-class.html b/docs/epy/googleapiclient.http.MediaIoBaseDownload-class.html
index 06f378178..3bf1a5752 100644
--- a/docs/epy/googleapiclient.http.MediaIoBaseDownload-class.html
+++ b/docs/epy/googleapiclient.http.MediaIoBaseDownload-class.html
@@ -266,7 +266,7 @@ Args:
 Returns:
   (status, done): (MediaDownloadStatus, boolean)
      The value of 'done' will be True when the media has been fully
-     downloaded.
+     downloaded or the total size of the media is unknown.
 
 Raises:
   googleapiclient.errors.HttpError if the response was not a 2xx.
@@ -308,7 +308,7 @@ Raises:
 <table border="0" cellpadding="0" cellspacing="0" width="100%%">
   <tr>
     <td align="left" class="footer">
-    Generated by Epydoc 3.0.1 on Mon Jun  5 13:38:40 2017
+    Generated by Epydoc 3.0.1 on Wed Jun 14 20:18:20 2017
     </td>
     <td align="right" class="footer">
       <a target="mainFrame" href="http://epydoc.sourceforge.net"
diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
index c8956f43f..fe19022c9 100644
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -334,10 +334,6 @@ def build_from_document(
   if http is not None and credentials is not None:
     raise ValueError('Arguments http and credentials are mutually exclusive.')
 
-  if developerKey is not None and credentials is not None:
-    raise ValueError(
-      'Arguments developerKey and credentials are mutually exclusive.')
-
   if isinstance(service, six.string_types):
     service = json.loads(service)
 
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
index eb464490b..ee8d6e3c3 100644
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -658,7 +658,7 @@ class MediaIoBaseDownload(object):
     Returns:
       (status, done): (MediaDownloadProgress, boolean)
          The value of 'done' will be True when the media has been fully
-         downloaded.
+         downloaded or the total size of the media is unknown.
 
     Raises:
       googleapiclient.errors.HttpError if the response was not a 2xx.
@@ -687,7 +687,7 @@ class MediaIoBaseDownload(object):
       elif 'content-length' in resp:
         self._total_size = int(resp['content-length'])
 
-      if self._progress == self._total_size:
+      if self._total_size is None or self._progress == self._total_size:
         self._done = True
       return MediaDownloadProgress(self._progress, self._total_size), self._done
     else:


Test patch:
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index fd1b9ae93..94027e3de 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -375,13 +375,6 @@ class DiscoveryErrors(unittest.TestCase):
       build(
         'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
 
-  def test_credentials_and_developer_key_mutually_exclusive(self):
-    http = HttpMock(datafile('plus.json'), {'status': '200'})
-    with self.assertRaises(ValueError):
-      build(
-        'plus', 'v1', credentials=mock.sentinel.credentials,
-        developerKey=mock.sentinel.credentials)
-
 
 class DiscoveryFromDocument(unittest.TestCase):
   MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)
diff --git a/tests/test_http.py b/tests/test_http.py
index 04e11429a..9d482663f 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -576,6 +576,29 @@ class TestMediaIoBaseDownload(unittest.TestCase):
     self.assertEqual(0, download._total_size)
     self.assertEqual(0, status.progress())
 
+  def test_media_io_base_download_unknown_media_size(self):
+    self.request.http = HttpMockSequence([
+      ({'status': '200'}, b'123')
+    ])
+
+    download = MediaIoBaseDownload(
+      fd=self.fd, request=self.request, chunksize=3)
+
+    self.assertEqual(self.fd, download._fd)
+    self.assertEqual(0, download._progress)
+    self.assertEqual(None, download._total_size)
+    self.assertEqual(False, download._done)
+    self.assertEqual(self.request.uri, download._uri)
+
+    status, done = download.next_chunk()
+
+    self.assertEqual(self.fd.getvalue(), b'123')
+    self.assertEqual(True, done)
+    self.assertEqual(3, download._progress)
+    self.assertEqual(None, download._total_size)
+    self.assertEqual(0, status.progress())
+
+
 EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
 Content-Type: application/json
 MIME-Version: 1.0