Repository: googleapis/google-cloud-python

Issue:
DISCUSSION: Allow http as an argument to Connection so libraries other than httplib2 can be used
This way we can support people using `Twisted` (for example) or other frameworks without having to write any other code. Though docs could be nice? We could supply an abstract class for them to subclass?

---

As of 12ac983fe613785c998aeeb1d8b981f4b5b347d9:

```
$ git log -1 --pretty=%H
12ac983fe613785c998aeeb1d8b981f4b5b347d9
$ git grep '\.http' -- gcloud/ | egrep -v 'test'
gcloud/datastore/connection.py:        :raises: :class:`six.moves.http_client.HTTPException` if the response
gcloud/datastore/connection.py:        headers, content = self.http.request(
gcloud/datastore/connection.py:            raise six.moves.http_client.HTTPException(message)
gcloud/storage/connection.py:        return self.http.request(uri=url, method=method, headers=headers,
gcloud/storage/key.py:        download.InitializeDownload(request, self.connection.http)
gcloud/storage/key.py:        upload.InitializeUpload(request, conn.http)
gcloud/storage/key.py:            http_wrapper.MakeRequest(conn.http, request, retries=num_retries)
```

We only actively use the `httplib2.Http()` instance twice. Once in `storage.connection.Connection.make_request`:

``` python
        return self.http.request(uri=url, method=method, headers=headers,
                                 body=data)
```

and once in `datastore.connection.Connection._request`:

``` python
        headers, content = self.http.request(
            uri=self.build_api_url(dataset_id=dataset_id, method=method),
            method='POST', headers=headers, body=data)
```

---

In either case the object only needs to be able to accept
- `uri`
- `method`
- `headers`
- `body`

and needs to [append](https://github.com/google/oauth2client/blob/2864ebc7a52832f0fda3820d6aeb8723c0d09597/oauth2client/client.py#L584-L590) a

```
Authorization: Bearer ya29.some-OAUTH-TOKEN...
```

header to `headers`. (See the [source](https://github.com/google/oauth2client/blob/2864ebc7a52832f0fda3820d6aeb8723c0d09597/oauth2client/client.py#L525-L556), the user agent is also updated and the request is retried if a [401 Unauthorized](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error) is encountered, but an external client need not do either of those.)

---

ASIDE: Should we add an auth label?



Discussion:
Other things to note:
1.  As seen above, the `httplib2.Http()` instance is passed along to our vendored in `apitools` code.
2.  The `credentials` object is used in [`storage.Connection.generate_signed_url`](https://github.com/GoogleCloudPlatform/gcloud-python/blob/12ac983fe613785c998aeeb1d8b981f4b5b347d9/gcloud/storage/connection.py#L512-L557) (and nowhere else):
   
   ```
   $ git grep -n '\.credentials' | egrep -v 'test'
   gcloud/storage/connection.py:550:        query_params = _get_signed_query_params(self.credentials,
   ```


Resolving patch:
diff --git a/gcloud/connection.py b/gcloud/connection.py
--- a/gcloud/connection.py
+++ b/gcloud/connection.py
@@ -24,6 +24,34 @@ class Connection(object):
 
     Subclasses should understand only the basic types in method arguments,
     however they should be capable of returning advanced types.
+
+    If no value is passed in for ``http``, a :class:`httplib2.Http` object
+    will be created and authorized with the ``credentials``. If not, the
+    ``credentials`` and ``http`` need not be related.
+
+    Subclasses may seek to use the private key from ``credentials`` to sign
+    data.
+
+    A custom (non-``httplib2``) HTTP object must have a ``request`` method
+    which accepts the following arguments:
+
+    * ``uri``
+    * ``method``
+    * ``body``
+    * ``headers``
+
+    In addition, ``redirections`` and ``connection_type`` may be used.
+
+    Without the use of ``credentials.authorize(http)``, a custom ``http``
+    object will also need to be able to add a bearer token to API
+    requests and handle token refresh on 401 errors.
+
+    :type credentials: :class:`oauth2client.client.OAuth2Credentials` or
+                       :class:`NoneType`
+    :param credentials: The OAuth2 Credentials to use for this connection.
+
+    :type http: :class:`httplib2.Http` or class that defines ``request()``.
+    :param http: An optional HTTP object to make requests.
     """
 
     API_BASE_URL = 'https://www.googleapis.com'
@@ -35,14 +63,8 @@ class Connection(object):
     USER_AGENT = "gcloud-python/{0}".format(get_distribution('gcloud').version)
     """The user agent for gcloud-python requests."""
 
-    def __init__(self, credentials=None):
-        """Constructor for Connection.
-
-        :type credentials: :class:`oauth2client.client.OAuth2Credentials` or
-                           :class:`NoneType`
-        :param credentials: The OAuth2 Credentials to use for this connection.
-        """
-        self._http = None
+    def __init__(self, credentials=None, http=None):
+        self._http = http
         self._credentials = credentials
 
     @property


Test patch:
diff --git a/gcloud/test_connection.py b/gcloud/test_connection.py
--- a/gcloud/test_connection.py
+++ b/gcloud/test_connection.py
@@ -32,6 +32,13 @@ def test_ctor_explicit(self):
         creds = object()
         conn = self._makeOne(creds)
         self.assertTrue(conn.credentials is creds)
+        self.assertEqual(conn._http, None)
+
+    def test_ctor_explicit_http(self):
+        http = object()
+        conn = self._makeOne(http=http)
+        self.assertEqual(conn.credentials, None)
+        self.assertTrue(conn.http is http)
 
     def test_http_w_existing(self):
         conn = self._makeOne()