Skip to content

Commit 4dd72b6

Browse files
author
Jon Wayne Parrott
authored
Remove httplib2, replace with Requests (#3674)
* Core: remove httplib2, replace with Requests Additionally remove make_exception in favor of from_http_status and from_http_response. * Datastore: replace httplib2 with Requests * DNS: replace httplib2 with Requests * Error Reporting: replace httplib2 with requests * Language: replace httplib2 with Requests * Logging: replace httplib2 with requests * Monitoring: replace httplib2 with Requests * Pubsub: replace httplib2 with Requests * Resource Manager: replace httplib2 with Requests * Runtimeconfig: replace httplib2 with Requests * Speech: replace httplib2 with Requests * Storage: replace httplib2 with Requests * BigQuery: replace httplib2 with Requests * Translate: replace httplib2 with Requests * Vision: replace httplib2 with Requests
1 parent 21f1fb4 commit 4dd72b6

File tree

8 files changed

+307
-441
lines changed

8 files changed

+307
-441
lines changed

packages/google-cloud-core/google/cloud/_helpers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@
2525
import re
2626
from threading import local as Local
2727

28-
import google_auth_httplib2
29-
import httplib2
3028
import six
3129
from six.moves import http_client
3230

3331
import google.auth
32+
import google.auth.transport.requests
3433
from google.protobuf import duration_pb2
3534
from google.protobuf import timestamp_pb2
3635

@@ -550,7 +549,7 @@ def make_secure_channel(credentials, user_agent, host, extra_options=()):
550549
:returns: gRPC secure channel with credentials attached.
551550
"""
552551
target = '%s:%d' % (host, http_client.HTTPS_PORT)
553-
http_request = google_auth_httplib2.Request(http=httplib2.Http())
552+
http_request = google.auth.transport.requests.Request()
554553

555554
user_agent_option = ('grpc.primary_user_agent', user_agent)
556555
options = (user_agent_option,) + extra_options

packages/google-cloud-core/google/cloud/_http.py

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
import platform
1919

2020
from pkg_resources import get_distribution
21-
import six
2221
from six.moves.urllib.parse import urlencode
2322

24-
from google.cloud.exceptions import make_exception
23+
from google.cloud import exceptions
2524

2625

2726
API_BASE_URL = 'https://www.googleapis.com'
@@ -67,8 +66,9 @@ def credentials(self):
6766
def http(self):
6867
"""A getter for the HTTP transport used in talking to the API.
6968
70-
:rtype: :class:`httplib2.Http`
71-
:returns: A Http object used to transport data.
69+
Returns:
70+
google.auth.transport.requests.AuthorizedSession:
71+
A :class:`requests.Session` instance.
7272
"""
7373
return self._client._http
7474

@@ -168,23 +168,13 @@ def _make_request(self, method, url, data=None, content_type=None,
168168
custom behavior, for example, to defer an HTTP request and complete
169169
initialization of the object at a later time.
170170
171-
:rtype: tuple of ``response`` (a dictionary of sorts)
172-
and ``content`` (a string).
173-
:returns: The HTTP response object and the content of the response,
174-
returned by :meth:`_do_request`.
171+
:rtype: :class:`requests.Response`
172+
:returns: The HTTP response.
175173
"""
176174
headers = headers or {}
177175
headers.update(self._EXTRA_HEADERS)
178176
headers['Accept-Encoding'] = 'gzip'
179177

180-
if data:
181-
content_length = len(str(data))
182-
else:
183-
content_length = 0
184-
185-
# NOTE: str is intended, bytes are sufficient for headers.
186-
headers['Content-Length'] = str(content_length)
187-
188178
if content_type:
189179
headers['Content-Type'] = content_type
190180

@@ -215,12 +205,11 @@ def _do_request(self, method, url, headers, data,
215205
(Optional) Unused ``target_object`` here but may be used by a
216206
superclass.
217207
218-
:rtype: tuple of ``response`` (a dictionary of sorts)
219-
and ``content`` (a string).
220-
:returns: The HTTP response object and the content of the response.
208+
:rtype: :class:`requests.Response`
209+
:returns: The HTTP response.
221210
"""
222-
return self.http.request(uri=url, method=method, headers=headers,
223-
body=data)
211+
return self.http.request(
212+
url=url, method=method, headers=headers, data=data)
224213

225214
def api_request(self, method, path, query_params=None,
226215
data=None, content_type=None, headers=None,
@@ -281,7 +270,7 @@ def api_request(self, method, path, query_params=None,
281270
282271
:raises ~google.cloud.exceptions.GoogleCloudError: if the response code
283272
is not 200 OK.
284-
:raises TypeError: if the response content type is not JSON.
273+
:raises ValueError: if the response content type is not JSON.
285274
:rtype: dict or str
286275
:returns: The API response payload, either as a raw string or
287276
a dictionary if the response is valid JSON.
@@ -296,21 +285,14 @@ def api_request(self, method, path, query_params=None,
296285
data = json.dumps(data)
297286
content_type = 'application/json'
298287

299-
response, content = self._make_request(
288+
response = self._make_request(
300289
method=method, url=url, data=data, content_type=content_type,
301290
headers=headers, target_object=_target_object)
302291

303-
if not 200 <= response.status < 300:
304-
raise make_exception(response, content,
305-
error_info=method + ' ' + url)
292+
if not 200 <= response.status_code < 300:
293+
raise exceptions.from_http_response(response)
306294

307-
string_or_bytes = (six.binary_type, six.text_type)
308-
if content and expect_json and isinstance(content, string_or_bytes):
309-
content_type = response.get('content-type', '')
310-
if not content_type.startswith('application/json'):
311-
raise TypeError('Expected JSON, got %s' % content_type)
312-
if isinstance(content, six.binary_type):
313-
content = content.decode('utf-8')
314-
return json.loads(content)
315-
316-
return content
295+
if expect_json and response.content:
296+
return response.json()
297+
else:
298+
return response.content

packages/google-cloud-core/google/cloud/client.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
import json
1919
from pickle import PicklingError
2020

21-
import google_auth_httplib2
2221
import six
2322

2423
import google.auth
2524
import google.auth.credentials
25+
import google.auth.transport.requests
2626
from google.cloud._helpers import _determine_default_project
2727
from google.oauth2 import service_account
2828

@@ -87,36 +87,23 @@ class Client(_ClientFactoryMixin):
8787
Stores ``credentials`` and an HTTP object so that subclasses
8888
can pass them along to a connection class.
8989
90-
If no value is passed in for ``_http``, a :class:`httplib2.Http` object
90+
If no value is passed in for ``_http``, a :class:`requests.Session` object
9191
will be created and authorized with the ``credentials``. If not, the
9292
``credentials`` and ``_http`` need not be related.
9393
9494
Callers and subclasses may seek to use the private key from
9595
``credentials`` to sign data.
9696
97-
A custom (non-``httplib2``) HTTP object must have a ``request`` method
98-
which accepts the following arguments:
99-
100-
* ``uri``
101-
* ``method``
102-
* ``body``
103-
* ``headers``
104-
105-
In addition, ``redirections`` and ``connection_type`` may be used.
106-
107-
A custom ``_http`` object will also need to be able to add a bearer token
108-
to API requests and handle token refresh on 401 errors.
109-
11097
:type credentials: :class:`~google.auth.credentials.Credentials`
11198
:param credentials: (Optional) The OAuth2 Credentials to use for this
11299
client. If not passed (and if no ``_http`` object is
113100
passed), falls back to the default inferred from the
114101
environment.
115102
116-
:type _http: :class:`~httplib2.Http`
103+
:type _http: :class:`~requests.Session`
117104
:param _http: (Optional) HTTP object to make requests. Can be any object
118105
that defines ``request()`` with the same interface as
119-
:meth:`~httplib2.Http.request`. If not passed, an
106+
:meth:`requests.Session.request`. If not passed, an
120107
``_http`` object is created that is bound to the
121108
``credentials`` for the current object.
122109
This parameter should be considered private, and could
@@ -151,12 +138,13 @@ def __getstate__(self):
151138
def _http(self):
152139
"""Getter for object used for HTTP transport.
153140
154-
:rtype: :class:`~httplib2.Http`
141+
:rtype: :class:`~requests.Session`
155142
:returns: An HTTP object.
156143
"""
157144
if self._http_internal is None:
158-
self._http_internal = google_auth_httplib2.AuthorizedHttp(
159-
self._credentials)
145+
self._http_internal = (
146+
google.auth.transport.requests.AuthorizedSession(
147+
self._credentials))
160148
return self._http_internal
161149

162150

@@ -204,10 +192,10 @@ class ClientWithProject(Client, _ClientProjectMixin):
204192
passed), falls back to the default inferred from the
205193
environment.
206194
207-
:type _http: :class:`~httplib2.Http`
195+
:type _http: :class:`~requests.Session`
208196
:param _http: (Optional) HTTP object to make requests. Can be any object
209197
that defines ``request()`` with the same interface as
210-
:meth:`~httplib2.Http.request`. If not passed, an
198+
:meth:`~requests.Session.request`. If not passed, an
211199
``_http`` object is created that is bound to the
212200
``credentials`` for the current object.
213201
This parameter should be considered private, and could

packages/google-cloud-core/google/cloud/exceptions.py

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from __future__ import absolute_import
2222

2323
import copy
24-
import json
2524

2625
import six
2726

@@ -186,56 +185,55 @@ class GatewayTimeout(ServerError):
186185
code = 504
187186

188187

189-
def make_exception(response, content, error_info=None, use_json=True):
190-
"""Factory: create exception based on HTTP response code.
188+
def from_http_status(status_code, message, errors=()):
189+
"""Create a :class:`GoogleCloudError` from an HTTP status code.
191190
192-
:type response: :class:`httplib2.Response` or other HTTP response object
193-
:param response: A response object that defines a status code as the
194-
status attribute.
191+
Args:
192+
status_code (int): The HTTP status code.
193+
message (str): The exception message.
194+
errors (Sequence[Any]): A list of additional error information.
195+
196+
Returns:
197+
GoogleCloudError: An instance of the appropriate subclass of
198+
:class:`GoogleCloudError`.
199+
"""
200+
error_class = _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleCloudError)
201+
error = error_class(message, errors)
202+
203+
if error.code is None:
204+
error.code = status_code
205+
206+
return error
195207

196-
:type content: str or dictionary
197-
:param content: The body of the HTTP error response.
198208

199-
:type error_info: str
200-
:param error_info: Optional string giving extra information about the
201-
failed request.
209+
def from_http_response(response):
210+
"""Create a :class:`GoogleCloudError` from a :class:`requests.Response`.
202211
203-
:type use_json: bool
204-
:param use_json: Flag indicating if ``content`` is expected to be JSON.
212+
Args:
213+
response (requests.Response): The HTTP response.
205214
206-
:rtype: instance of :class:`GoogleCloudError`, or a concrete subclass.
207-
:returns: Exception specific to the error response.
215+
Returns:
216+
GoogleCloudError: An instance of the appropriate subclass of
217+
:class:`GoogleCloudError`, with the message and errors populated
218+
from the response.
208219
"""
209-
if isinstance(content, six.binary_type):
210-
content = content.decode('utf-8')
211-
212-
if isinstance(content, six.string_types):
213-
payload = None
214-
if use_json:
215-
try:
216-
payload = json.loads(content)
217-
except ValueError:
218-
# Expected JSON but received something else.
219-
pass
220-
if payload is None:
221-
payload = {'error': {'message': content}}
222-
else:
223-
payload = content
224-
225-
message = payload.get('error', {}).get('message', '')
220+
try:
221+
payload = response.json()
222+
except ValueError:
223+
payload = {'error': {'message': response.text or 'unknown error'}}
224+
225+
error_message = payload.get('error', {}).get('message', 'unknown error')
226226
errors = payload.get('error', {}).get('errors', ())
227227

228-
if error_info is not None:
229-
message += ' (%s)' % (error_info,)
228+
message = '{method} {url}: {error}'.format(
229+
method=response.request.method,
230+
url=response.request.url,
231+
error=error_message)
230232

231-
try:
232-
klass = _HTTP_CODE_TO_EXCEPTION[response.status]
233-
except KeyError:
234-
error = GoogleCloudError(message, errors)
235-
error.code = response.status
236-
else:
237-
error = klass(message, errors)
238-
return error
233+
exception = from_http_status(
234+
response.status_code, message, errors=errors)
235+
exception.response = response
236+
return exception
239237

240238

241239
def _walk_subclasses(klass):

packages/google-cloud-core/setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,10 @@
5151

5252

5353
REQUIREMENTS = [
54-
'httplib2 >= 0.9.1',
5554
'googleapis-common-protos >= 1.3.4',
5655
'protobuf >= 3.0.0',
5756
'google-auth >= 0.4.0, < 2.0.0dev',
58-
'google-auth-httplib2',
57+
'requests >= 2.4.0, < 3.0.0dev',
5958
'six',
6059
'tenacity >= 4.0.0, <5.0.0dev'
6160
]

0 commit comments

Comments
 (0)