Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/resumable_media/requests/download.py: 13%

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

175 statements  

1# Copyright 2017 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"""Support for downloading media from Google APIs.""" 

16 

17import http 

18 

19import urllib3.response # type: ignore 

20 

21from google.resumable_media import _download, _helpers, common 

22from google.resumable_media.requests import _request_helpers 

23 

24_CHECKSUM_MISMATCH = """\ 

25Checksum mismatch while downloading: 

26 

27 {} 

28 

29The X-Goog-Hash header indicated an {checksum_type} checksum of: 

30 

31 {} 

32 

33but the actual {checksum_type} checksum of the downloaded contents was: 

34 

35 {} 

36""" 

37 

38_STREAM_SEEK_ERROR = """\ 

39Incomplete download for: 

40{} 

41Error writing to stream while handling a gzip-compressed file download. 

42Please restart the download. 

43""" 

44 

45_RESPONSE_HEADERS_INFO = """\ 

46 

47The X-Goog-Stored-Content-Length is {}. The X-Goog-Stored-Content-Encoding is {}. 

48 

49The download request read {} bytes of data. 

50If the download was incomplete, please check the network connection and restart the download. 

51""" 

52 

53 

54class Download(_request_helpers.RequestsMixin, _download.Download): 

55 """Helper to manage downloading a resource from a Google API. 

56 

57 "Slices" of the resource can be retrieved by specifying a range 

58 with ``start`` and / or ``end``. However, in typical usage, neither 

59 ``start`` nor ``end`` is expected to be provided. 

60 

61 Args: 

62 media_url (str): The URL containing the media to be downloaded. 

63 stream (IO[bytes]): A write-able stream (i.e. file-like object) that 

64 the downloaded resource can be written to. 

65 start (int): The first byte in a range to be downloaded. If not 

66 provided, but ``end`` is provided, will download from the 

67 beginning to ``end`` of the media. 

68 end (int): The last byte in a range to be downloaded. If not 

69 provided, but ``start`` is provided, will download from the 

70 ``start`` to the end of the media. 

71 headers (Optional[Mapping[str, str]]): Extra headers that should 

72 be sent with the request, e.g. headers for encrypted data. 

73 checksum Optional([str]): The type of checksum to compute to verify 

74 the integrity of the object. The response headers must contain 

75 a checksum of the requested type. If the headers lack an 

76 appropriate checksum (for instance in the case of transcoded or 

77 ranged downloads where the remote service does not know the 

78 correct checksum) an INFO-level log will be emitted. Supported 

79 values are "md5", "crc32c" and None. The default is "md5". 

80 

81 Attributes: 

82 media_url (str): The URL containing the media to be downloaded. 

83 start (Optional[int]): The first byte in a range to be downloaded. 

84 end (Optional[int]): The last byte in a range to be downloaded. 

85 """ 

86 

87 def _write_to_stream(self, response): 

88 """Write response body to a write-able stream. 

89 

90 .. note: 

91 

92 This method assumes that the ``_stream`` attribute is set on the 

93 current download. 

94 

95 Args: 

96 response (~requests.Response): The HTTP response object. 

97 

98 Raises: 

99 ~google.resumable_media.common.DataCorruption: If the download's 

100 checksum doesn't agree with server-computed checksum. 

101 """ 

102 

103 # Retrieve the expected checksum only once for the download request, 

104 # then compute and validate the checksum when the full download completes. 

105 # Retried requests are range requests, and there's no way to detect 

106 # data corruption for that byte range alone. 

107 if self._expected_checksum is None and self._checksum_object is None: 

108 # `_get_expected_checksum()` may return None even if a checksum was 

109 # requested, in which case it will emit an info log _MISSING_CHECKSUM. 

110 # If an invalid checksum type is specified, this will raise ValueError. 

111 expected_checksum, checksum_object = _helpers._get_expected_checksum( 

112 response, self._get_headers, self.media_url, checksum_type=self.checksum 

113 ) 

114 self._expected_checksum = expected_checksum 

115 self._checksum_object = checksum_object 

116 else: 

117 expected_checksum = self._expected_checksum 

118 checksum_object = self._checksum_object 

119 

120 with response: 

121 # NOTE: In order to handle compressed streams gracefully, we try 

122 # to insert our checksum object into the decompression stream. If 

123 # the stream is indeed compressed, this will delegate the checksum 

124 # object to the decoder and return a _DoNothingHash here. 

125 local_checksum_object = _add_decoder(response.raw, checksum_object) 

126 body_iter = response.iter_content( 

127 chunk_size=_request_helpers._SINGLE_GET_CHUNK_SIZE, decode_unicode=False 

128 ) 

129 for chunk in body_iter: 

130 self._stream.write(chunk) 

131 self._bytes_downloaded += len(chunk) 

132 local_checksum_object.update(chunk) 

133 

134 # Don't validate the checksum for partial responses. 

135 if ( 

136 expected_checksum is not None 

137 and response.status_code != http.client.PARTIAL_CONTENT 

138 ): 

139 actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest()) 

140 

141 if actual_checksum != expected_checksum: 

142 headers = self._get_headers(response) 

143 x_goog_encoding = headers.get("x-goog-stored-content-encoding") 

144 x_goog_length = headers.get("x-goog-stored-content-length") 

145 content_length_msg = _RESPONSE_HEADERS_INFO.format( 

146 x_goog_length, x_goog_encoding, self._bytes_downloaded 

147 ) 

148 if ( 

149 x_goog_length 

150 and self._bytes_downloaded < int(x_goog_length) 

151 and x_goog_encoding != "gzip" 

152 ): 

153 # The library will attempt to trigger a retry by raising a ConnectionError, if 

154 # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and 

155 # (b) the object is not gzip-compressed when stored in Cloud Storage. 

156 raise ConnectionError(content_length_msg) 

157 else: 

158 msg = _CHECKSUM_MISMATCH.format( 

159 self.media_url, 

160 expected_checksum, 

161 actual_checksum, 

162 checksum_type=self.checksum.upper(), 

163 ) 

164 msg += content_length_msg 

165 raise common.DataCorruption(response, msg) 

166 

167 def consume( 

168 self, 

169 transport, 

170 timeout=( 

171 _request_helpers._DEFAULT_CONNECT_TIMEOUT, 

172 _request_helpers._DEFAULT_READ_TIMEOUT, 

173 ), 

174 ): 

175 """Consume the resource to be downloaded. 

176 

177 If a ``stream`` is attached to this download, then the downloaded 

178 resource will be written to the stream. 

179 

180 Args: 

181 transport (~requests.Session): A ``requests`` object which can 

182 make authenticated requests. 

183 timeout (Optional[Union[float, Tuple[float, float]]]): 

184 The number of seconds to wait for the server response. 

185 Depending on the retry strategy, a request may be repeated 

186 several times using the same timeout each time. 

187 

188 Can also be passed as a tuple (connect_timeout, read_timeout). 

189 See :meth:`requests.Session.request` documentation for details. 

190 

191 Returns: 

192 ~requests.Response: The HTTP response returned by ``transport``. 

193 

194 Raises: 

195 ~google.resumable_media.common.DataCorruption: If the download's 

196 checksum doesn't agree with server-computed checksum. 

197 ValueError: If the current :class:`Download` has already 

198 finished. 

199 """ 

200 method, _, payload, headers = self._prepare_request() 

201 # NOTE: We assume "payload is None" but pass it along anyway. 

202 request_kwargs = { 

203 "data": payload, 

204 "headers": headers, 

205 "timeout": timeout, 

206 } 

207 if self._stream is not None: 

208 request_kwargs["stream"] = True 

209 

210 # Assign object generation if generation is specified in the media url. 

211 if self._object_generation is None: 

212 self._object_generation = _helpers._get_generation_from_url(self.media_url) 

213 

214 # Wrap the request business logic in a function to be retried. 

215 def retriable_request(): 

216 url = self.media_url 

217 

218 # To restart an interrupted download, read from the offset of last byte 

219 # received using a range request, and set object generation query param. 

220 if self._bytes_downloaded > 0: 

221 _download.add_bytes_range( 

222 (self.start or 0) + self._bytes_downloaded, self.end, self._headers 

223 ) 

224 request_kwargs["headers"] = self._headers 

225 

226 # Set object generation query param to ensure the same object content is requested. 

227 if ( 

228 self._object_generation is not None 

229 and _helpers._get_generation_from_url(self.media_url) is None 

230 ): 

231 query_param = {"generation": self._object_generation} 

232 url = _helpers.add_query_parameters(self.media_url, query_param) 

233 

234 result = transport.request(method, url, **request_kwargs) 

235 

236 # If a generation hasn't been specified, and this is the first response we get, let's record the 

237 # generation. In future requests we'll specify the generation query param to avoid data races. 

238 if self._object_generation is None: 

239 self._object_generation = _helpers._parse_generation_header( 

240 result, self._get_headers 

241 ) 

242 

243 self._process_response(result) 

244 

245 # With decompressive transcoding, GCS serves back the whole file regardless of the range request, 

246 # thus we reset the stream position to the start of the stream. 

247 # See: https://cloud.google.com/storage/docs/transcoding#range 

248 if self._stream is not None: 

249 if _helpers._is_decompressive_transcoding(result, self._get_headers): 

250 try: 

251 self._stream.seek(0) 

252 except Exception as exc: 

253 msg = _STREAM_SEEK_ERROR.format(url) 

254 raise Exception(msg) from exc 

255 self._bytes_downloaded = 0 

256 

257 self._write_to_stream(result) 

258 

259 return result 

260 

261 return _request_helpers.wait_and_retry( 

262 retriable_request, self._get_status_code, self._retry_strategy 

263 ) 

264 

265 

266class RawDownload(_request_helpers.RawRequestsMixin, _download.Download): 

267 """Helper to manage downloading a raw resource from a Google API. 

268 

269 "Slices" of the resource can be retrieved by specifying a range 

270 with ``start`` and / or ``end``. However, in typical usage, neither 

271 ``start`` nor ``end`` is expected to be provided. 

272 

273 Args: 

274 media_url (str): The URL containing the media to be downloaded. 

275 stream (IO[bytes]): A write-able stream (i.e. file-like object) that 

276 the downloaded resource can be written to. 

277 start (int): The first byte in a range to be downloaded. If not 

278 provided, but ``end`` is provided, will download from the 

279 beginning to ``end`` of the media. 

280 end (int): The last byte in a range to be downloaded. If not 

281 provided, but ``start`` is provided, will download from the 

282 ``start`` to the end of the media. 

283 headers (Optional[Mapping[str, str]]): Extra headers that should 

284 be sent with the request, e.g. headers for encrypted data. 

285 checksum Optional([str]): The type of checksum to compute to verify 

286 the integrity of the object. The response headers must contain 

287 a checksum of the requested type. If the headers lack an 

288 appropriate checksum (for instance in the case of transcoded or 

289 ranged downloads where the remote service does not know the 

290 correct checksum) an INFO-level log will be emitted. Supported 

291 values are "md5", "crc32c" and None. The default is "md5". 

292 Attributes: 

293 media_url (str): The URL containing the media to be downloaded. 

294 start (Optional[int]): The first byte in a range to be downloaded. 

295 end (Optional[int]): The last byte in a range to be downloaded. 

296 """ 

297 

298 def _write_to_stream(self, response): 

299 """Write response body to a write-able stream. 

300 

301 .. note: 

302 

303 This method assumes that the ``_stream`` attribute is set on the 

304 current download. 

305 

306 Args: 

307 response (~requests.Response): The HTTP response object. 

308 

309 Raises: 

310 ~google.resumable_media.common.DataCorruption: If the download's 

311 checksum doesn't agree with server-computed checksum. 

312 """ 

313 # Retrieve the expected checksum only once for the download request, 

314 # then compute and validate the checksum when the full download completes. 

315 # Retried requests are range requests, and there's no way to detect 

316 # data corruption for that byte range alone. 

317 if self._expected_checksum is None and self._checksum_object is None: 

318 # `_get_expected_checksum()` may return None even if a checksum was 

319 # requested, in which case it will emit an info log _MISSING_CHECKSUM. 

320 # If an invalid checksum type is specified, this will raise ValueError. 

321 expected_checksum, checksum_object = _helpers._get_expected_checksum( 

322 response, self._get_headers, self.media_url, checksum_type=self.checksum 

323 ) 

324 self._expected_checksum = expected_checksum 

325 self._checksum_object = checksum_object 

326 else: 

327 expected_checksum = self._expected_checksum 

328 checksum_object = self._checksum_object 

329 

330 with response: 

331 body_iter = response.raw.stream( 

332 _request_helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False 

333 ) 

334 for chunk in body_iter: 

335 self._stream.write(chunk) 

336 self._bytes_downloaded += len(chunk) 

337 checksum_object.update(chunk) 

338 response._content_consumed = True 

339 

340 # Don't validate the checksum for partial responses. 

341 if ( 

342 expected_checksum is not None 

343 and response.status_code != http.client.PARTIAL_CONTENT 

344 ): 

345 actual_checksum = _helpers.prepare_checksum_digest(checksum_object.digest()) 

346 

347 if actual_checksum != expected_checksum: 

348 headers = self._get_headers(response) 

349 x_goog_encoding = headers.get("x-goog-stored-content-encoding") 

350 x_goog_length = headers.get("x-goog-stored-content-length") 

351 content_length_msg = _RESPONSE_HEADERS_INFO.format( 

352 x_goog_length, x_goog_encoding, self._bytes_downloaded 

353 ) 

354 if ( 

355 x_goog_length 

356 and self._bytes_downloaded < int(x_goog_length) 

357 and x_goog_encoding != "gzip" 

358 ): 

359 # The library will attempt to trigger a retry by raising a ConnectionError, if 

360 # (a) bytes_downloaded is less than response header x-goog-stored-content-length, and 

361 # (b) the object is not gzip-compressed when stored in Cloud Storage. 

362 raise ConnectionError(content_length_msg) 

363 else: 

364 msg = _CHECKSUM_MISMATCH.format( 

365 self.media_url, 

366 expected_checksum, 

367 actual_checksum, 

368 checksum_type=self.checksum.upper(), 

369 ) 

370 msg += content_length_msg 

371 raise common.DataCorruption(response, msg) 

372 

373 def consume( 

374 self, 

375 transport, 

376 timeout=( 

377 _request_helpers._DEFAULT_CONNECT_TIMEOUT, 

378 _request_helpers._DEFAULT_READ_TIMEOUT, 

379 ), 

380 ): 

381 """Consume the resource to be downloaded. 

382 

383 If a ``stream`` is attached to this download, then the downloaded 

384 resource will be written to the stream. 

385 

386 Args: 

387 transport (~requests.Session): A ``requests`` object which can 

388 make authenticated requests. 

389 timeout (Optional[Union[float, Tuple[float, float]]]): 

390 The number of seconds to wait for the server response. 

391 Depending on the retry strategy, a request may be repeated 

392 several times using the same timeout each time. 

393 

394 Can also be passed as a tuple (connect_timeout, read_timeout). 

395 See :meth:`requests.Session.request` documentation for details. 

396 

397 Returns: 

398 ~requests.Response: The HTTP response returned by ``transport``. 

399 

400 Raises: 

401 ~google.resumable_media.common.DataCorruption: If the download's 

402 checksum doesn't agree with server-computed checksum. 

403 ValueError: If the current :class:`Download` has already 

404 finished. 

405 """ 

406 method, _, payload, headers = self._prepare_request() 

407 # NOTE: We assume "payload is None" but pass it along anyway. 

408 request_kwargs = { 

409 "data": payload, 

410 "headers": headers, 

411 "timeout": timeout, 

412 "stream": True, 

413 } 

414 

415 # Assign object generation if generation is specified in the media url. 

416 if self._object_generation is None: 

417 self._object_generation = _helpers._get_generation_from_url(self.media_url) 

418 

419 # Wrap the request business logic in a function to be retried. 

420 def retriable_request(): 

421 url = self.media_url 

422 

423 # To restart an interrupted download, read from the offset of last byte 

424 # received using a range request, and set object generation query param. 

425 if self._bytes_downloaded > 0: 

426 _download.add_bytes_range( 

427 (self.start or 0) + self._bytes_downloaded, self.end, self._headers 

428 ) 

429 request_kwargs["headers"] = self._headers 

430 

431 # Set object generation query param to ensure the same object content is requested. 

432 if ( 

433 self._object_generation is not None 

434 and _helpers._get_generation_from_url(self.media_url) is None 

435 ): 

436 query_param = {"generation": self._object_generation} 

437 url = _helpers.add_query_parameters(self.media_url, query_param) 

438 

439 result = transport.request(method, url, **request_kwargs) 

440 

441 # If a generation hasn't been specified, and this is the first response we get, let's record the 

442 # generation. In future requests we'll specify the generation query param to avoid data races. 

443 if self._object_generation is None: 

444 self._object_generation = _helpers._parse_generation_header( 

445 result, self._get_headers 

446 ) 

447 

448 self._process_response(result) 

449 

450 # With decompressive transcoding, GCS serves back the whole file regardless of the range request, 

451 # thus we reset the stream position to the start of the stream. 

452 # See: https://cloud.google.com/storage/docs/transcoding#range 

453 if self._stream is not None: 

454 if _helpers._is_decompressive_transcoding(result, self._get_headers): 

455 try: 

456 self._stream.seek(0) 

457 except Exception as exc: 

458 msg = _STREAM_SEEK_ERROR.format(url) 

459 raise Exception(msg) from exc 

460 self._bytes_downloaded = 0 

461 

462 self._write_to_stream(result) 

463 

464 return result 

465 

466 return _request_helpers.wait_and_retry( 

467 retriable_request, self._get_status_code, self._retry_strategy 

468 ) 

469 

470 

471class ChunkedDownload(_request_helpers.RequestsMixin, _download.ChunkedDownload): 

472 """Download a resource in chunks from a Google API. 

473 

474 Args: 

475 media_url (str): The URL containing the media to be downloaded. 

476 chunk_size (int): The number of bytes to be retrieved in each 

477 request. 

478 stream (IO[bytes]): A write-able stream (i.e. file-like object) that 

479 will be used to concatenate chunks of the resource as they are 

480 downloaded. 

481 start (int): The first byte in a range to be downloaded. If not 

482 provided, defaults to ``0``. 

483 end (int): The last byte in a range to be downloaded. If not 

484 provided, will download to the end of the media. 

485 headers (Optional[Mapping[str, str]]): Extra headers that should 

486 be sent with each request, e.g. headers for data encryption 

487 key headers. 

488 

489 Attributes: 

490 media_url (str): The URL containing the media to be downloaded. 

491 start (Optional[int]): The first byte in a range to be downloaded. 

492 end (Optional[int]): The last byte in a range to be downloaded. 

493 chunk_size (int): The number of bytes to be retrieved in each request. 

494 

495 Raises: 

496 ValueError: If ``start`` is negative. 

497 """ 

498 

499 def consume_next_chunk( 

500 self, 

501 transport, 

502 timeout=( 

503 _request_helpers._DEFAULT_CONNECT_TIMEOUT, 

504 _request_helpers._DEFAULT_READ_TIMEOUT, 

505 ), 

506 ): 

507 """Consume the next chunk of the resource to be downloaded. 

508 

509 Args: 

510 transport (~requests.Session): A ``requests`` object which can 

511 make authenticated requests. 

512 timeout (Optional[Union[float, Tuple[float, float]]]): 

513 The number of seconds to wait for the server response. 

514 Depending on the retry strategy, a request may be repeated 

515 several times using the same timeout each time. 

516 

517 Can also be passed as a tuple (connect_timeout, read_timeout). 

518 See :meth:`requests.Session.request` documentation for details. 

519 

520 Returns: 

521 ~requests.Response: The HTTP response returned by ``transport``. 

522 

523 Raises: 

524 ValueError: If the current download has finished. 

525 """ 

526 method, url, payload, headers = self._prepare_request() 

527 

528 # Wrap the request business logic in a function to be retried. 

529 def retriable_request(): 

530 # NOTE: We assume "payload is None" but pass it along anyway. 

531 result = transport.request( 

532 method, 

533 url, 

534 data=payload, 

535 headers=headers, 

536 timeout=timeout, 

537 ) 

538 self._process_response(result) 

539 return result 

540 

541 return _request_helpers.wait_and_retry( 

542 retriable_request, self._get_status_code, self._retry_strategy 

543 ) 

544 

545 

546class RawChunkedDownload(_request_helpers.RawRequestsMixin, _download.ChunkedDownload): 

547 """Download a raw resource in chunks from a Google API. 

548 

549 Args: 

550 media_url (str): The URL containing the media to be downloaded. 

551 chunk_size (int): The number of bytes to be retrieved in each 

552 request. 

553 stream (IO[bytes]): A write-able stream (i.e. file-like object) that 

554 will be used to concatenate chunks of the resource as they are 

555 downloaded. 

556 start (int): The first byte in a range to be downloaded. If not 

557 provided, defaults to ``0``. 

558 end (int): The last byte in a range to be downloaded. If not 

559 provided, will download to the end of the media. 

560 headers (Optional[Mapping[str, str]]): Extra headers that should 

561 be sent with each request, e.g. headers for data encryption 

562 key headers. 

563 

564 Attributes: 

565 media_url (str): The URL containing the media to be downloaded. 

566 start (Optional[int]): The first byte in a range to be downloaded. 

567 end (Optional[int]): The last byte in a range to be downloaded. 

568 chunk_size (int): The number of bytes to be retrieved in each request. 

569 

570 Raises: 

571 ValueError: If ``start`` is negative. 

572 """ 

573 

574 def consume_next_chunk( 

575 self, 

576 transport, 

577 timeout=( 

578 _request_helpers._DEFAULT_CONNECT_TIMEOUT, 

579 _request_helpers._DEFAULT_READ_TIMEOUT, 

580 ), 

581 ): 

582 """Consume the next chunk of the resource to be downloaded. 

583 

584 Args: 

585 transport (~requests.Session): A ``requests`` object which can 

586 make authenticated requests. 

587 timeout (Optional[Union[float, Tuple[float, float]]]): 

588 The number of seconds to wait for the server response. 

589 Depending on the retry strategy, a request may be repeated 

590 several times using the same timeout each time. 

591 

592 Can also be passed as a tuple (connect_timeout, read_timeout). 

593 See :meth:`requests.Session.request` documentation for details. 

594 

595 Returns: 

596 ~requests.Response: The HTTP response returned by ``transport``. 

597 

598 Raises: 

599 ValueError: If the current download has finished. 

600 """ 

601 method, url, payload, headers = self._prepare_request() 

602 

603 # Wrap the request business logic in a function to be retried. 

604 def retriable_request(): 

605 # NOTE: We assume "payload is None" but pass it along anyway. 

606 result = transport.request( 

607 method, 

608 url, 

609 data=payload, 

610 headers=headers, 

611 stream=True, 

612 timeout=timeout, 

613 ) 

614 self._process_response(result) 

615 return result 

616 

617 return _request_helpers.wait_and_retry( 

618 retriable_request, self._get_status_code, self._retry_strategy 

619 ) 

620 

621 

622def _add_decoder(response_raw, checksum): 

623 """Patch the ``_decoder`` on a ``urllib3`` response. 

624 

625 This is so that we can intercept the compressed bytes before they are 

626 decoded. 

627 

628 Only patches if the content encoding is ``gzip`` or ``br``. 

629 

630 Args: 

631 response_raw (urllib3.response.HTTPResponse): The raw response for 

632 an HTTP request. 

633 checksum (object): 

634 A checksum which will be updated with compressed bytes. 

635 

636 Returns: 

637 object: Either the original ``checksum`` if ``_decoder`` is not 

638 patched, or a ``_DoNothingHash`` if the decoder is patched, since the 

639 caller will no longer need to hash to decoded bytes. 

640 """ 

641 encoding = response_raw.headers.get("content-encoding", "").lower() 

642 if encoding == "gzip": 

643 response_raw._decoder = _GzipDecoder(checksum) 

644 return _helpers._DoNothingHash() 

645 # Only activate if brotli is installed 

646 elif encoding == "br" and _BrotliDecoder: # type: ignore 

647 response_raw._decoder = _BrotliDecoder(checksum) 

648 return _helpers._DoNothingHash() 

649 else: 

650 return checksum 

651 

652 

653class _GzipDecoder(urllib3.response.GzipDecoder): 

654 """Custom subclass of ``urllib3`` decoder for ``gzip``-ed bytes. 

655 

656 Allows a checksum function to see the compressed bytes before they are 

657 decoded. This way the checksum of the compressed value can be computed. 

658 

659 Args: 

660 checksum (object): 

661 A checksum which will be updated with compressed bytes. 

662 """ 

663 

664 def __init__(self, checksum): 

665 super().__init__() 

666 self._checksum = checksum 

667 

668 def decompress(self, data, max_length=-1): 

669 """Decompress the bytes. 

670 

671 Args: 

672 data (bytes): The compressed bytes to be decompressed. 

673 max_length (int): Maximum number of bytes to return. -1 for no 

674 limit. Forwarded to the underlying decoder when supported. 

675 

676 Returns: 

677 bytes: The decompressed bytes from ``data``. 

678 """ 

679 self._checksum.update(data) 

680 try: 

681 return super().decompress(data, max_length=max_length) 

682 except TypeError: 

683 return super().decompress(data) 

684 

685 

686# urllib3.response.BrotliDecoder might not exist depending on whether brotli is 

687# installed. 

688if hasattr(urllib3.response, "BrotliDecoder"): 

689 

690 class _BrotliDecoder: 

691 """Handler for ``brotli`` encoded bytes. 

692 

693 Allows a checksum function to see the compressed bytes before they are 

694 decoded. This way the checksum of the compressed value can be computed. 

695 

696 Because BrotliDecoder's decompress method is dynamically created in 

697 urllib3, a subclass is not practical. Instead, this class creates a 

698 captive urllib3.requests.BrotliDecoder instance and acts as a proxy. 

699 

700 Args: 

701 checksum (object): 

702 A checksum which will be updated with compressed bytes. 

703 """ 

704 

705 def __init__(self, checksum): 

706 self._decoder = urllib3.response.BrotliDecoder() 

707 self._checksum = checksum 

708 

709 def decompress(self, data, max_length=-1): 

710 """Decompress the bytes. 

711 

712 Args: 

713 data (bytes): The compressed bytes to be decompressed. 

714 max_length (int): Maximum number of bytes to return. -1 for no 

715 limit. Forwarded to the underlying decoder when supported. 

716 

717 Returns: 

718 bytes: The decompressed bytes from ``data``. 

719 """ 

720 self._checksum.update(data) 

721 try: 

722 return self._decoder.decompress(data, max_length=max_length) 

723 except TypeError: 

724 return self._decoder.decompress(data) 

725 

726 @property 

727 def has_unconsumed_tail(self): 

728 try: 

729 return self._decoder.has_unconsumed_tail 

730 except AttributeError: 

731 return False 

732 

733 def flush(self): 

734 return self._decoder.flush() 

735 

736else: # pragma: NO COVER 

737 _BrotliDecoder = None # type: ignore # pragma: NO COVER