Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google_auth_httplib2.py: 69%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

89 statements  

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. 

14 

15"""Transport adapter for httplib2.""" 

16 

17from __future__ import absolute_import 

18 

19import http.client 

20import logging 

21 

22from google.auth import exceptions 

23from google.auth import transport 

24import httplib2 

25 

26 

27_LOGGER = logging.getLogger(__name__) 

28# Properties present in file-like streams / buffers. 

29_STREAM_PROPERTIES = ("read", "seek", "tell") 

30 

31 

32class _Response(transport.Response): 

33 """httplib2 transport response adapter. 

34 

35 Args: 

36 response (httplib2.Response): The raw httplib2 response. 

37 data (bytes): The response body. 

38 """ 

39 

40 def __init__(self, response, data): 

41 self._response = response 

42 self._data = data 

43 

44 @property 

45 def status(self): 

46 """int: The HTTP status code.""" 

47 return self._response.status 

48 

49 @property 

50 def headers(self): 

51 """Mapping[str, str]: The HTTP response headers.""" 

52 return dict(self._response) 

53 

54 @property 

55 def data(self): 

56 """bytes: The response body.""" 

57 return self._data 

58 

59 

60class Request(transport.Request): 

61 """httplib2 request adapter. 

62 

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. 

66 

67 This class can be useful if you want to manually refresh a 

68 :class:`~google.auth.credentials.Credentials` instance:: 

69 

70 import google_auth_httplib2 

71 import httplib2 

72 

73 http = httplib2.Http() 

74 request = google_auth_httplib2.Request(http) 

75 

76 credentials.refresh(request) 

77 

78 Args: 

79 http (httplib2.Http): The underlying http object to use to make 

80 requests. 

81 """ 

82 

83 def __init__(self, http): 

84 self.http = http 

85 

86 def __call__( 

87 self, url, method="GET", body=None, headers=None, timeout=None, **kwargs 

88 ): 

89 """Make an HTTP request using httplib2. 

90 

91 Args: 

92 url (str): The URI to be requested. 

93 method (str): The HTTP method to use for the request. Defaults 

94 to 'GET'. 

95 body (bytes): The payload / body in HTTP request. 

96 headers (Mapping[str, str]): Request headers. 

97 timeout (Optional[int]): The number of seconds to wait for a 

98 response from the server. This is ignored by httplib2 and will 

99 issue a warning. 

100 kwargs: Additional arguments passed throught to the underlying 

101 :meth:`httplib2.Http.request` method. 

102 

103 Returns: 

104 google.auth.transport.Response: The HTTP response. 

105 

106 Raises: 

107 google.auth.exceptions.TransportError: If any exception occurred. 

108 """ 

109 if timeout is not None: 

110 _LOGGER.warning( 

111 "httplib2 transport does not support per-request timeout. " 

112 "Set the timeout when constructing the httplib2.Http instance." 

113 ) 

114 

115 try: 

116 _LOGGER.debug("Making request: %s %s", method, url) 

117 response, data = self.http.request( 

118 url, method=method, body=body, headers=headers, **kwargs 

119 ) 

120 return _Response(response, data) 

121 # httplib2 should catch the lower http error, this is a bug and 

122 # needs to be fixed there. Catch the error for the meanwhile. 

123 except (httplib2.HttpLib2Error, http.client.HTTPException) as exc: 

124 raise exceptions.TransportError(exc) 

125 

126 

127def _make_default_http(): 

128 """Returns a default httplib2.Http instance.""" 

129 return httplib2.Http() 

130 

131 

132class AuthorizedHttp(object): 

133 """A httplib2 HTTP class with credentials. 

134 

135 This class is used to perform requests to API endpoints that require 

136 authorization:: 

137 

138 from google.auth.transport._httplib2 import AuthorizedHttp 

139 

140 authed_http = AuthorizedHttp(credentials) 

141 

142 response = authed_http.request( 

143 'https://www.googleapis.com/storage/v1/b') 

144 

145 This class implements :meth:`request` in the same way as 

146 :class:`httplib2.Http` and can usually be used just like any other 

147 instance of :class:`httplib2.Http`. 

148 

149 The underlying :meth:`request` implementation handles adding the 

150 credentials' headers to the request and refreshing credentials as needed. 

151 """ 

152 

153 def __init__( 

154 self, 

155 credentials, 

156 http=None, 

157 refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, 

158 max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, 

159 ): 

160 """ 

161 Args: 

162 credentials (google.auth.credentials.Credentials): The credentials 

163 to add to the request. 

164 http (httplib2.Http): The underlying HTTP object to 

165 use to make requests. If not specified, a 

166 :class:`httplib2.Http` instance will be constructed. 

167 refresh_status_codes (Sequence[int]): Which HTTP status codes 

168 indicate that credentials should be refreshed and the request 

169 should be retried. 

170 max_refresh_attempts (int): The maximum number of times to attempt 

171 to refresh the credentials and retry the request. 

172 """ 

173 

174 if http is None: 

175 http = _make_default_http() 

176 

177 self.http = http 

178 self.credentials = credentials 

179 self._refresh_status_codes = refresh_status_codes 

180 self._max_refresh_attempts = max_refresh_attempts 

181 # Request instance used by internal methods (for example, 

182 # credentials.refresh). 

183 self._request = Request(self.http) 

184 

185 def close(self): 

186 """Calls httplib2's Http.close""" 

187 self.http.close() 

188 

189 def request( 

190 self, 

191 uri, 

192 method="GET", 

193 body=None, 

194 headers=None, 

195 redirections=httplib2.DEFAULT_MAX_REDIRECTS, 

196 connection_type=None, 

197 **kwargs 

198 ): 

199 """Implementation of httplib2's Http.request.""" 

200 

201 _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) 

202 

203 # Make a copy of the headers. They will be modified by the credentials 

204 # and we want to pass the original headers if we recurse. 

205 request_headers = headers.copy() if headers is not None else {} 

206 

207 self.credentials.before_request(self._request, method, uri, request_headers) 

208 

209 # Check if the body is a file-like stream, and if so, save the body 

210 # stream position so that it can be restored in case of refresh. 

211 body_stream_position = None 

212 if all(getattr(body, stream_prop, None) for stream_prop in _STREAM_PROPERTIES): 

213 body_stream_position = body.tell() 

214 

215 # Make the request. 

216 response, content = self.http.request( 

217 uri, 

218 method, 

219 body=body, 

220 headers=request_headers, 

221 redirections=redirections, 

222 connection_type=connection_type, 

223 **kwargs 

224 ) 

225 

226 # If the response indicated that the credentials needed to be 

227 # refreshed, then refresh the credentials and re-attempt the 

228 # request. 

229 # A stored token may expire between the time it is retrieved and 

230 # the time the request is made, so we may need to try twice. 

231 if ( 

232 response.status in self._refresh_status_codes 

233 and _credential_refresh_attempt < self._max_refresh_attempts 

234 ): 

235 _LOGGER.info( 

236 "Refreshing credentials due to a %s response. Attempt %s/%s.", 

237 response.status, 

238 _credential_refresh_attempt + 1, 

239 self._max_refresh_attempts, 

240 ) 

241 

242 self.credentials.refresh(self._request) 

243 

244 # Restore the body's stream position if needed. 

245 if body_stream_position is not None: 

246 body.seek(body_stream_position) 

247 

248 # Recurse. Pass in the original headers, not our modified set. 

249 return self.request( 

250 uri, 

251 method, 

252 body=body, 

253 headers=headers, 

254 redirections=redirections, 

255 connection_type=connection_type, 

256 _credential_refresh_attempt=_credential_refresh_attempt + 1, 

257 **kwargs 

258 ) 

259 

260 return response, content 

261 

262 def add_certificate(self, key, cert, domain, password=None): 

263 """Proxy to httplib2.Http.add_certificate.""" 

264 self.http.add_certificate(key, cert, domain, password=password) 

265 

266 @property 

267 def connections(self): 

268 """Proxy to httplib2.Http.connections.""" 

269 return self.http.connections 

270 

271 @connections.setter 

272 def connections(self, value): 

273 """Proxy to httplib2.Http.connections.""" 

274 self.http.connections = value 

275 

276 @property 

277 def follow_redirects(self): 

278 """Proxy to httplib2.Http.follow_redirects.""" 

279 return self.http.follow_redirects 

280 

281 @follow_redirects.setter 

282 def follow_redirects(self, value): 

283 """Proxy to httplib2.Http.follow_redirects.""" 

284 self.http.follow_redirects = value 

285 

286 @property 

287 def timeout(self): 

288 """Proxy to httplib2.Http.timeout.""" 

289 return self.http.timeout 

290 

291 @timeout.setter 

292 def timeout(self, value): 

293 """Proxy to httplib2.Http.timeout.""" 

294 self.http.timeout = value 

295 

296 @property 

297 def redirect_codes(self): 

298 """Proxy to httplib2.Http.redirect_codes.""" 

299 return self.http.redirect_codes 

300 

301 @redirect_codes.setter 

302 def redirect_codes(self, value): 

303 """Proxy to httplib2.Http.redirect_codes.""" 

304 self.http.redirect_codes = value