Merge pull request #201 from toabctl/support-oauth2client-v1-and-v2

Allow using oauth2client versions < 2.
diff --git a/googleapiclient/__init__.py b/googleapiclient/__init__.py
index ab076a4..f1d54a9 100644
--- a/googleapiclient/__init__.py
+++ b/googleapiclient/__init__.py
@@ -13,3 +13,15 @@
 # limitations under the License.
 
 __version__ = "1.5.0"
+
+# Set default logging handler to avoid "No handler found" warnings.
+import logging
+
+try:  # Python 2.7+
+    from logging import NullHandler
+except ImportError:
+    class NullHandler(logging.Handler):
+        def emit(self, record):
+            pass
+
+logging.getLogger(__name__).addHandler(NullHandler())
diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
index cee5628..ecb75fa 100644
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -82,6 +82,9 @@
 VARNAME = re.compile('[a-zA-Z0-9_-]+')
 DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
                  '{api}/{apiVersion}/rest')
+V1_DISCOVERY_URI = DISCOVERY_URI
+V2_DISCOVERY_URI = ('https://{api}.googleapis.com/$discovery/rest?'
+                    'version={apiVersion}')
 DEFAULT_METHOD_DOC = 'A description of how to use this function'
 HTTP_PAYLOAD_METHODS = frozenset(['PUT', 'POST', 'PATCH'])
 _MEDIA_SIZE_BIT_SHIFTS = {'KB': 10, 'MB': 20, 'GB': 30, 'TB': 40}
@@ -196,21 +199,23 @@
   if http is None:
     http = httplib2.Http()
 
-  requested_url = uritemplate.expand(discoveryServiceUrl, params)
+  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)
-  except HttpError as e:
-    if e.resp.status == http_client.NOT_FOUND:
-      raise UnknownApiNameOrVersion("name: %s  version: %s" % (serviceName,
-                                                               version))
-    else:
-      raise e
+    try:
+      content = _retrieve_discovery_doc(requested_url, http, cache_discovery,
+                                        cache)
+      return build_from_document(content, base=discovery_url, http=http,
+          developerKey=developerKey, model=model, requestBuilder=requestBuilder,
+          credentials=credentials)
+    except HttpError as e:
+      if e.resp.status == http_client.NOT_FOUND:
+        continue
+      else:
+        raise e
 
-  return build_from_document(content, base=discoveryServiceUrl, http=http,
-      developerKey=developerKey, model=model, requestBuilder=requestBuilder,
-      credentials=credentials)
+  raise UnknownApiNameOrVersion(
+        "name: %s  version: %s" % (serviceName, version))
 
 
 def _retrieve_discovery_doc(url, http, cache_discovery, cache=None):
diff --git a/googleapiclient/discovery_cache/__init__.py b/googleapiclient/discovery_cache/__init__.py
index c56fd65..f86a06d 100644
--- a/googleapiclient/discovery_cache/__init__.py
+++ b/googleapiclient/discovery_cache/__init__.py
@@ -19,6 +19,9 @@
 import logging
 import datetime
 
+
+LOGGER = logging.getLogger(__name__)
+
 DISCOVERY_DOC_MAX_AGE = 60 * 60 * 24  # 1 day
 
 
@@ -38,5 +41,5 @@
       from . import file_cache
       return file_cache.cache
     except Exception as e:
-      logging.warning(e, exc_info=True)
+      LOGGER.warning(e, exc_info=True)
       return None
diff --git a/googleapiclient/discovery_cache/appengine_memcache.py b/googleapiclient/discovery_cache/appengine_memcache.py
index a521fc3..7e43e66 100644
--- a/googleapiclient/discovery_cache/appengine_memcache.py
+++ b/googleapiclient/discovery_cache/appengine_memcache.py
@@ -23,6 +23,9 @@
 from . import base
 from ..discovery_cache import DISCOVERY_DOC_MAX_AGE
 
+
+LOGGER = logging.getLogger(__name__)
+
 NAMESPACE = 'google-api-client'
 
 
@@ -41,12 +44,12 @@
     try:
       return memcache.get(url, namespace=NAMESPACE)
     except Exception as e:
-      logging.warning(e, exc_info=True)
+      LOGGER.warning(e, exc_info=True)
 
   def set(self, url, content):
     try:
       memcache.set(url, content, time=int(self._max_age), namespace=NAMESPACE)
     except Exception as e:
-      logging.warning(e, exc_info=True)
+      LOGGER.warning(e, exc_info=True)
 
 cache = Cache(max_age=DISCOVERY_DOC_MAX_AGE)
diff --git a/googleapiclient/discovery_cache/file_cache.py b/googleapiclient/discovery_cache/file_cache.py
index 7526ed9..31434db 100644
--- a/googleapiclient/discovery_cache/file_cache.py
+++ b/googleapiclient/discovery_cache/file_cache.py
@@ -38,7 +38,7 @@
 from . import base
 from ..discovery_cache import DISCOVERY_DOC_MAX_AGE
 
-logger = logging.getLogger(__name__)
+LOGGER = logging.getLogger(__name__)
 
 FILENAME = 'google-api-python-client-discovery-doc.cache'
 EPOCH = datetime.datetime.utcfromtimestamp(0)
@@ -88,7 +88,7 @@
         # If we can not obtain the lock, other process or thread must
         # have initialized the file.
       except Exception as e:
-        logging.warning(e, exc_info=True)
+        LOGGER.warning(e, exc_info=True)
       finally:
         f.unlock_and_close()
 
@@ -104,10 +104,10 @@
             return content
         return None
       else:
-        logger.debug('Could not obtain a lock for the cache file.')
+        LOGGER.debug('Could not obtain a lock for the cache file.')
         return None
     except Exception as e:
-      logger.warning(e, exc_info=True)
+      LOGGER.warning(e, exc_info=True)
     finally:
       f.unlock_and_close()
 
@@ -126,9 +126,9 @@
         f.file_handle().seek(0)
         json.dump(cache, f.file_handle())
       else:
-        logger.debug('Could not obtain a lock for the cache file.')
+        LOGGER.debug('Could not obtain a lock for the cache file.')
     except Exception as e:
-      logger.warning(e, exc_info=True)
+      LOGGER.warning(e, exc_info=True)
     finally:
       f.unlock_and_close()
 
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
index 2245e8d..ef72e4f 100644
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -57,6 +57,8 @@
 from oauth2client import util
 
 
+LOGGER = logging.getLogger(__name__)
+
 DEFAULT_CHUNK_SIZE = 512*1024
 
 MAX_URI_LENGTH = 2048
@@ -85,7 +87,7 @@
   for retry_num in range(num_retries + 1):
     if retry_num > 0:
       sleep(rand() * 2**retry_num)
-      logging.warning(
+      LOGGER.warning(
           'Retry #%d for %s: %s %s%s' % (retry_num, req_type, method, uri,
           ', following status: %d' % resp.status if resp else ''))
 
@@ -882,7 +884,7 @@
     for retry_num in range(num_retries + 1):
       if retry_num > 0:
         self._sleep(self._rand() * 2**retry_num)
-        logging.warning(
+        LOGGER.warning(
             'Retry #%d for media upload: %s %s, following status: %d'
             % (retry_num, self.method, self.uri, resp.status))
 
@@ -1632,7 +1634,7 @@
       headers = {}
     if method == 'PATCH':
       if 'oauth_token' in headers.get('authorization', ''):
-        logging.warning(
+        LOGGER.warning(
             'OAuth 1.0 request made with Credentials after tunnel_patch.')
       headers['x-http-method-override'] = "PATCH"
       method = 'POST'
diff --git a/googleapiclient/model.py b/googleapiclient/model.py
index e8afb63..dded04e 100644
--- a/googleapiclient/model.py
+++ b/googleapiclient/model.py
@@ -33,6 +33,8 @@
 from googleapiclient.errors import HttpError
 
 
+LOGGER = logging.getLogger(__name__)
+
 dump_request_response = False
 
 
@@ -105,18 +107,18 @@
   def _log_request(self, headers, path_params, query, body):
     """Logs debugging information about the request if requested."""
     if dump_request_response:
-      logging.info('--request-start--')
-      logging.info('-headers-start-')
+      LOGGER.info('--request-start--')
+      LOGGER.info('-headers-start-')
       for h, v in six.iteritems(headers):
-        logging.info('%s: %s', h, v)
-      logging.info('-headers-end-')
-      logging.info('-path-parameters-start-')
+        LOGGER.info('%s: %s', h, v)
+      LOGGER.info('-headers-end-')
+      LOGGER.info('-path-parameters-start-')
       for h, v in six.iteritems(path_params):
-        logging.info('%s: %s', h, v)
-      logging.info('-path-parameters-end-')
-      logging.info('body: %s', body)
-      logging.info('query: %s', query)
-      logging.info('--request-end--')
+        LOGGER.info('%s: %s', h, v)
+      LOGGER.info('-path-parameters-end-')
+      LOGGER.info('body: %s', body)
+      LOGGER.info('query: %s', query)
+      LOGGER.info('--request-end--')
 
   def request(self, headers, path_params, query_params, body_value):
     """Updates outgoing requests with a serialized body.
@@ -176,12 +178,12 @@
   def _log_response(self, resp, content):
     """Logs debugging information about the response if requested."""
     if dump_request_response:
-      logging.info('--response-start--')
+      LOGGER.info('--response-start--')
       for h, v in six.iteritems(resp):
-        logging.info('%s: %s', h, v)
+        LOGGER.info('%s: %s', h, v)
       if content:
-        logging.info(content)
-      logging.info('--response-end--')
+        LOGGER.info(content)
+      LOGGER.info('--response-end--')
 
   def response(self, resp, content):
     """Convert the response wire format into a Python object.
@@ -206,7 +208,7 @@
         return self.no_content_response
       return self.deserialize(content)
     else:
-      logging.debug('Content from bad request was: %s' % content)
+      LOGGER.debug('Content from bad request was: %s' % content)
       raise HttpError(resp, content)
 
   def serialize(self, body_value):
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 0181bbb..dea5631 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -352,6 +352,7 @@
   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()),
       ])
       with self.assertRaises(UnknownApiNameOrVersion):
         plus = build('plus', 'v1', http=http, cache_discovery=False)
@@ -425,6 +426,13 @@
     except HttpError as e:
       self.assertEqual(e.uri, 'http://example.com')
 
+  def test_discovery_loading_from_v2_discovery_uri(self):
+      http = HttpMockSequence([
+        ({'status': '404'}, 'Not found'),
+        ({'status': '200'}, open(datafile('zoo.json'), 'rb').read()),
+      ])
+      zoo = build('zoo', 'v1', http=http, cache_discovery=False)
+      self.assertTrue(hasattr(zoo, 'animals'))
 
 class DiscoveryFromAppEngineCache(unittest.TestCase):
   def test_appengine_memcache(self):
diff --git a/tests/test_json_model.py b/tests/test_json_model.py
index 1784f8e..0d1f283 100644
--- a/tests/test_json_model.py
+++ b/tests/test_json_model.py
@@ -219,8 +219,8 @@
         self.status = items['status']
         for key, value in six.iteritems(items):
           self[key] = value
-    old_logging = googleapiclient.model.logging
-    googleapiclient.model.logging = MockLogging()
+    old_logging = googleapiclient.model.LOGGER
+    googleapiclient.model.LOGGER = MockLogging()
     googleapiclient.model.dump_request_response = True
     model = JsonModel()
     request_body = {
@@ -236,18 +236,18 @@
                 'response_field_2': 'response_value_2'}
     response_body = model.response(MockResponse(response), body_string)
     self.assertEqual(request_body, response_body)
-    self.assertEqual(googleapiclient.model.logging.info_record[:2],
+    self.assertEqual(googleapiclient.model.LOGGER.info_record[:2],
                      ['--request-start--',
                       '-headers-start-'])
     self.assertTrue('response_field_1: response_value_1' in
-                    googleapiclient.model.logging.info_record)
+                    googleapiclient.model.LOGGER.info_record)
     self.assertTrue('response_field_2: response_value_2' in
-                    googleapiclient.model.logging.info_record)
-    self.assertEqual(json.loads(googleapiclient.model.logging.info_record[-2]),
+                    googleapiclient.model.LOGGER.info_record)
+    self.assertEqual(json.loads(googleapiclient.model.LOGGER.info_record[-2]),
                      request_body)
-    self.assertEqual(googleapiclient.model.logging.info_record[-1],
+    self.assertEqual(googleapiclient.model.LOGGER.info_record[-1],
                      '--response-end--')
-    googleapiclient.model.logging = old_logging
+    googleapiclient.model.LOGGER = old_logging
 
   def test_no_data_wrapper_deserialize(self):
     model = JsonModel(data_wrapper=False)