Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google_auth_httplib2.py: 48%
88 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Transport adapter for httplib2."""
17from __future__ import absolute_import
19import http.client
20import logging
22from google.auth import exceptions
23from google.auth import transport
24import httplib2
27_LOGGER = logging.getLogger(__name__)
28# Properties present in file-like streams / buffers.
29_STREAM_PROPERTIES = ("read", "seek", "tell")
32class _Response(transport.Response):
33 """httplib2 transport response adapter.
35 Args:
36 response (httplib2.Response): The raw httplib2 response.
37 data (bytes): The response body.
38 """
40 def __init__(self, response, data):
41 self._response = response
42 self._data = data
44 @property
45 def status(self):
46 """int: The HTTP status code."""
47 return self._response.status
49 @property
50 def headers(self):
51 """Mapping[str, str]: The HTTP response headers."""
52 return dict(self._response)
54 @property
55 def data(self):
56 """bytes: The response body."""
57 return self._data
60class Request(transport.Request):
61 """httplib2 request adapter.
63 This class is used internally for making requests using various transports
64 in a consistent way. If you use :class:`AuthorizedHttp` you do not need
65 to construct or use this class directly.
67 This class can be useful if you want to manually refresh a
68 :class:`~google.auth.credentials.Credentials` instance::
70 import google_auth_httplib2
71 import httplib2
73 http = httplib2.Http()
74 request = google_auth_httplib2.Request(http)
76 credentials.refresh(request)
78 Args:
79 http (httplib2.Http): The underlying http object to use to make
80 requests.
82 .. automethod:: __call__
83 """
85 def __init__(self, http):
86 self.http = http
88 def __call__(
89 self, url, method="GET", body=None, headers=None, timeout=None, **kwargs
90 ):
91 """Make an HTTP request using httplib2.
93 Args:
94 url (str): The URI to be requested.
95 method (str): The HTTP method to use for the request. Defaults
96 to 'GET'.
97 body (bytes): The payload / body in HTTP request.
98 headers (Mapping[str, str]): Request headers.
99 timeout (Optional[int]): The number of seconds to wait for a
100 response from the server. This is ignored by httplib2 and will
101 issue a warning.
102 kwargs: Additional arguments passed throught to the underlying
103 :meth:`httplib2.Http.request` method.
105 Returns:
106 google.auth.transport.Response: The HTTP response.
108 Raises:
109 google.auth.exceptions.TransportError: If any exception occurred.
110 """
111 if timeout is not None:
112 _LOGGER.warning(
113 "httplib2 transport does not support per-request timeout. "
114 "Set the timeout when constructing the httplib2.Http instance."
115 )
117 try:
118 _LOGGER.debug("Making request: %s %s", method, url)
119 response, data = self.http.request(
120 url, method=method, body=body, headers=headers, **kwargs
121 )
122 return _Response(response, data)
123 # httplib2 should catch the lower http error, this is a bug and
124 # needs to be fixed there. Catch the error for the meanwhile.
125 except (httplib2.HttpLib2Error, http.client.HTTPException) as exc:
126 raise exceptions.TransportError(exc)
129def _make_default_http():
130 """Returns a default httplib2.Http instance."""
131 return httplib2.Http()
134class AuthorizedHttp(object):
135 """A httplib2 HTTP class with credentials.
137 This class is used to perform requests to API endpoints that require
138 authorization::
140 from google.auth.transport._httplib2 import AuthorizedHttp
142 authed_http = AuthorizedHttp(credentials)
144 response = authed_http.request(
145 'https://www.googleapis.com/storage/v1/b')
147 This class implements :meth:`request` in the same way as
148 :class:`httplib2.Http` and can usually be used just like any other
149 instance of :class:``httplib2.Http`.
151 The underlying :meth:`request` implementation handles adding the
152 credentials' headers to the request and refreshing credentials as needed.
153 """
155 def __init__(
156 self,
157 credentials,
158 http=None,
159 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES,
160 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS,
161 ):
162 """
163 Args:
164 credentials (google.auth.credentials.Credentials): The credentials
165 to add to the request.
166 http (httplib2.Http): The underlying HTTP object to
167 use to make requests. If not specified, a
168 :class:`httplib2.Http` instance will be constructed.
169 refresh_status_codes (Sequence[int]): Which HTTP status codes
170 indicate that credentials should be refreshed and the request
171 should be retried.
172 max_refresh_attempts (int): The maximum number of times to attempt
173 to refresh the credentials and retry the request.
174 """
176 if http is None:
177 http = _make_default_http()
179 self.http = http
180 self.credentials = credentials
181 self._refresh_status_codes = refresh_status_codes
182 self._max_refresh_attempts = max_refresh_attempts
183 # Request instance used by internal methods (for example,
184 # credentials.refresh).
185 self._request = Request(self.http)
187 def close(self):
188 """Calls httplib2's Http.close"""
189 self.http.close()
191 def request(
192 self,
193 uri,
194 method="GET",
195 body=None,
196 headers=None,
197 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
198 connection_type=None,
199 **kwargs
200 ):
201 """Implementation of httplib2's Http.request."""
203 _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0)
205 # Make a copy of the headers. They will be modified by the credentials
206 # and we want to pass the original headers if we recurse.
207 request_headers = headers.copy() if headers is not None else {}
209 self.credentials.before_request(self._request, method, uri, request_headers)
211 # Check if the body is a file-like stream, and if so, save the body
212 # stream position so that it can be restored in case of refresh.
213 body_stream_position = None
214 if all(getattr(body, stream_prop, None) for stream_prop in _STREAM_PROPERTIES):
215 body_stream_position = body.tell()
217 # Make the request.
218 response, content = self.http.request(
219 uri,
220 method,
221 body=body,
222 headers=request_headers,
223 redirections=redirections,
224 connection_type=connection_type,
225 **kwargs
226 )
228 # If the response indicated that the credentials needed to be
229 # refreshed, then refresh the credentials and re-attempt the
230 # request.
231 # A stored token may expire between the time it is retrieved and
232 # the time the request is made, so we may need to try twice.
233 if (
234 response.status in self._refresh_status_codes
235 and _credential_refresh_attempt < self._max_refresh_attempts
236 ):
238 _LOGGER.info(
239 "Refreshing credentials due to a %s response. Attempt %s/%s.",
240 response.status,
241 _credential_refresh_attempt + 1,
242 self._max_refresh_attempts,
243 )
245 self.credentials.refresh(self._request)
247 # Restore the body's stream position if needed.
248 if body_stream_position is not None:
249 body.seek(body_stream_position)
251 # Recurse. Pass in the original headers, not our modified set.
252 return self.request(
253 uri,
254 method,
255 body=body,
256 headers=headers,
257 redirections=redirections,
258 connection_type=connection_type,
259 _credential_refresh_attempt=_credential_refresh_attempt + 1,
260 **kwargs
261 )
263 return response, content
265 def add_certificate(self, key, cert, domain, password=None):
266 """Proxy to httplib2.Http.add_certificate."""
267 self.http.add_certificate(key, cert, domain, password=password)
269 @property
270 def connections(self):
271 """Proxy to httplib2.Http.connections."""
272 return self.http.connections
274 @connections.setter
275 def connections(self, value):
276 """Proxy to httplib2.Http.connections."""
277 self.http.connections = value
279 @property
280 def follow_redirects(self):
281 """Proxy to httplib2.Http.follow_redirects."""
282 return self.http.follow_redirects
284 @follow_redirects.setter
285 def follow_redirects(self, value):
286 """Proxy to httplib2.Http.follow_redirects."""
287 self.http.follow_redirects = value
289 @property
290 def timeout(self):
291 """Proxy to httplib2.Http.timeout."""
292 return self.http.timeout
294 @timeout.setter
295 def timeout(self, value):
296 """Proxy to httplib2.Http.timeout."""
297 self.http.timeout = value
299 @property
300 def redirect_codes(self):
301 """Proxy to httplib2.Http.redirect_codes."""
302 return self.http.redirect_codes
304 @redirect_codes.setter
305 def redirect_codes(self, value):
306 """Proxy to httplib2.Http.redirect_codes."""
307 self.http.redirect_codes = value