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 resumable uploads.
16
17Also supported here are simple (media) uploads and multipart
18uploads that contain both metadata and a small file as payload.
19"""
20
21
22from google.cloud.storage._media import _upload
23from google.cloud.storage._media.requests import _request_helpers
24
25
26class SimpleUpload(_request_helpers.RequestsMixin, _upload.SimpleUpload):
27 """Upload a resource to a Google API.
28
29 A **simple** media upload sends no metadata and completes the upload
30 in a single request.
31
32 Args:
33 upload_url (str): The URL where the content will be uploaded.
34 headers (Optional[Mapping[str, str]]): Extra headers that should
35 be sent with the request, e.g. headers for encrypted data.
36
37 Attributes:
38 upload_url (str): The URL where the content will be uploaded.
39 """
40
41 def transmit(
42 self,
43 transport,
44 data,
45 content_type,
46 timeout=(
47 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
48 _request_helpers._DEFAULT_READ_TIMEOUT,
49 ),
50 ):
51 """Transmit the resource to be uploaded.
52
53 Args:
54 transport (~requests.Session): A ``requests`` object which can
55 make authenticated requests.
56 data (bytes): The resource content to be uploaded.
57 content_type (str): The content type of the resource, e.g. a JPEG
58 image has content type ``image/jpeg``.
59 timeout (Optional[Union[float, Tuple[float, float]]]):
60 The number of seconds to wait for the server response.
61 Depending on the retry strategy, a request may be repeated
62 several times using the same timeout each time.
63
64 Can also be passed as a tuple (connect_timeout, read_timeout).
65 See :meth:`requests.Session.request` documentation for details.
66
67 Returns:
68 ~requests.Response: The HTTP response returned by ``transport``.
69 """
70 method, url, payload, headers = self._prepare_request(data, content_type)
71
72 # Wrap the request business logic in a function to be retried.
73 def retriable_request():
74 result = transport.request(
75 method, url, data=payload, headers=headers, timeout=timeout
76 )
77
78 self._process_response(result)
79
80 return result
81
82 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
83
84
85class MultipartUpload(_request_helpers.RequestsMixin, _upload.MultipartUpload):
86 """Upload a resource with metadata to a Google API.
87
88 A **multipart** upload sends both metadata and the resource in a single
89 (multipart) request.
90
91 Args:
92 upload_url (str): The URL where the content will be uploaded.
93 headers (Optional[Mapping[str, str]]): Extra headers that should
94 be sent with the request, e.g. headers for encrypted data.
95 checksum Optional([str]): The type of checksum to compute to verify
96 the integrity of the object. The request metadata will be amended
97 to include the computed value. Using this option will override a
98 manually-set checksum value. Supported values are "md5",
99 "crc32c", "auto", and None. The default is "auto", which will try
100 to detect if the C extension for crc32c is installed and fall back
101 to md5 otherwise.
102 retry (Optional[google.api_core.retry.Retry]): How to retry the
103 RPC. A None value will disable retries. A
104 google.api_core.retry.Retry value will enable retries, and the
105 object will configure backoff and timeout options.
106
107 See the retry.py source code and docstrings in this package
108 (google.cloud.storage.retry) for information on retry types and how
109 to configure them.
110
111 Attributes:
112 upload_url (str): The URL where the content will be uploaded.
113 """
114
115 def transmit(
116 self,
117 transport,
118 data,
119 metadata,
120 content_type,
121 timeout=(
122 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
123 _request_helpers._DEFAULT_READ_TIMEOUT,
124 ),
125 ):
126 """Transmit the resource to be uploaded.
127
128 Args:
129 transport (~requests.Session): A ``requests`` object which can
130 make authenticated requests.
131 data (bytes): The resource content to be uploaded.
132 metadata (Mapping[str, str]): The resource metadata, such as an
133 ACL list.
134 content_type (str): The content type of the resource, e.g. a JPEG
135 image has content type ``image/jpeg``.
136 timeout (Optional[Union[float, Tuple[float, float]]]):
137 The number of seconds to wait for the server response.
138 Depending on the retry strategy, a request may be repeated
139 several times using the same timeout each time.
140
141 Can also be passed as a tuple (connect_timeout, read_timeout).
142 See :meth:`requests.Session.request` documentation for details.
143
144 Returns:
145 ~requests.Response: The HTTP response returned by ``transport``.
146 """
147 method, url, payload, headers = self._prepare_request(
148 data, metadata, content_type
149 )
150
151 # Wrap the request business logic in a function to be retried.
152 def retriable_request():
153 result = transport.request(
154 method, url, data=payload, headers=headers, timeout=timeout
155 )
156
157 self._process_response(result)
158
159 return result
160
161 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
162
163
164class ResumableUpload(_request_helpers.RequestsMixin, _upload.ResumableUpload):
165 """Initiate and fulfill a resumable upload to a Google API.
166
167 A **resumable** upload sends an initial request with the resource metadata
168 and then gets assigned an upload ID / upload URL to send bytes to.
169 Using the upload URL, the upload is then done in chunks (determined by
170 the user) until all bytes have been uploaded.
171
172 When constructing a resumable upload, only the resumable upload URL and
173 the chunk size are required:
174
175 .. testsetup:: resumable-constructor
176
177 bucket = 'bucket-foo'
178
179 .. doctest:: resumable-constructor
180
181 >>> from google.cloud.storage._media.requests import ResumableUpload
182 >>>
183 >>> url_template = (
184 ... 'https://www.googleapis.com/upload/storage/v1/b/{bucket}/o?'
185 ... 'uploadType=resumable')
186 >>> upload_url = url_template.format(bucket=bucket)
187 >>>
188 >>> chunk_size = 3 * 1024 * 1024 # 3MB
189 >>> upload = ResumableUpload(upload_url, chunk_size)
190
191 When initiating an upload (via :meth:`initiate`), the caller is expected
192 to pass the resource being uploaded as a file-like ``stream``. If the size
193 of the resource is explicitly known, it can be passed in directly:
194
195 .. testsetup:: resumable-explicit-size
196
197 import os
198 import tempfile
199
200 import mock
201 import requests
202 import http.client
203
204 from google.cloud.storage._media.requests import ResumableUpload
205
206 upload_url = 'http://test.invalid'
207 chunk_size = 3 * 1024 * 1024 # 3MB
208 upload = ResumableUpload(upload_url, chunk_size)
209
210 file_desc, filename = tempfile.mkstemp()
211 os.close(file_desc)
212
213 data = b'some bytes!'
214 with open(filename, 'wb') as file_obj:
215 file_obj.write(data)
216
217 fake_response = requests.Response()
218 fake_response.status_code = int(http.client.OK)
219 fake_response._content = b''
220 resumable_url = 'http://test.invalid?upload_id=7up'
221 fake_response.headers['location'] = resumable_url
222
223 post_method = mock.Mock(return_value=fake_response, spec=[])
224 transport = mock.Mock(request=post_method, spec=['request'])
225
226 .. doctest:: resumable-explicit-size
227
228 >>> import os
229 >>>
230 >>> upload.total_bytes is None
231 True
232 >>>
233 >>> stream = open(filename, 'rb')
234 >>> total_bytes = os.path.getsize(filename)
235 >>> metadata = {'name': filename}
236 >>> response = upload.initiate(
237 ... transport, stream, metadata, 'text/plain',
238 ... total_bytes=total_bytes)
239 >>> response
240 <Response [200]>
241 >>>
242 >>> upload.total_bytes == total_bytes
243 True
244
245 .. testcleanup:: resumable-explicit-size
246
247 os.remove(filename)
248
249 If the stream is in a "final" state (i.e. it won't have any more bytes
250 written to it), the total number of bytes can be determined implicitly
251 from the ``stream`` itself:
252
253 .. testsetup:: resumable-implicit-size
254
255 import io
256
257 import mock
258 import requests
259 import http.client
260
261 from google.cloud.storage._media.requests import ResumableUpload
262
263 upload_url = 'http://test.invalid'
264 chunk_size = 3 * 1024 * 1024 # 3MB
265 upload = ResumableUpload(upload_url, chunk_size)
266
267 fake_response = requests.Response()
268 fake_response.status_code = int(http.client.OK)
269 fake_response._content = b''
270 resumable_url = 'http://test.invalid?upload_id=7up'
271 fake_response.headers['location'] = resumable_url
272
273 post_method = mock.Mock(return_value=fake_response, spec=[])
274 transport = mock.Mock(request=post_method, spec=['request'])
275
276 data = b'some MOAR bytes!'
277 metadata = {'name': 'some-file.jpg'}
278 content_type = 'image/jpeg'
279
280 .. doctest:: resumable-implicit-size
281
282 >>> stream = io.BytesIO(data)
283 >>> response = upload.initiate(
284 ... transport, stream, metadata, content_type)
285 >>>
286 >>> upload.total_bytes == len(data)
287 True
288
289 If the size of the resource is **unknown** when the upload is initiated,
290 the ``stream_final`` argument can be used. This might occur if the
291 resource is being dynamically created on the client (e.g. application
292 logs). To use this argument:
293
294 .. testsetup:: resumable-unknown-size
295
296 import io
297
298 import mock
299 import requests
300 import http.client
301
302 from google.cloud.storage._media.requests import ResumableUpload
303
304 upload_url = 'http://test.invalid'
305 chunk_size = 3 * 1024 * 1024 # 3MB
306 upload = ResumableUpload(upload_url, chunk_size)
307
308 fake_response = requests.Response()
309 fake_response.status_code = int(http.client.OK)
310 fake_response._content = b''
311 resumable_url = 'http://test.invalid?upload_id=7up'
312 fake_response.headers['location'] = resumable_url
313
314 post_method = mock.Mock(return_value=fake_response, spec=[])
315 transport = mock.Mock(request=post_method, spec=['request'])
316
317 metadata = {'name': 'some-file.jpg'}
318 content_type = 'application/octet-stream'
319
320 stream = io.BytesIO(b'data')
321
322 .. doctest:: resumable-unknown-size
323
324 >>> response = upload.initiate(
325 ... transport, stream, metadata, content_type,
326 ... stream_final=False)
327 >>>
328 >>> upload.total_bytes is None
329 True
330
331 Args:
332 upload_url (str): The URL where the resumable upload will be initiated.
333 chunk_size (int): The size of each chunk used to upload the resource.
334 headers (Optional[Mapping[str, str]]): Extra headers that should
335 be sent with the :meth:`initiate` request, e.g. headers for
336 encrypted data. These **will not** be sent with
337 :meth:`transmit_next_chunk` or :meth:`recover` requests.
338 checksum Optional([str]): The type of checksum to compute to verify
339 the integrity of the object. After the upload is complete, the
340 server-computed checksum of the resulting object will be checked
341 and google.cloud.storage.exceptions.DataCorruption will be raised on
342 a mismatch. The corrupted file will not be deleted from the remote
343 host automatically. Supported values are "md5", "crc32c", "auto",
344 and None. The default is "auto", which will try to detect if the C
345 extension for crc32c is installed and fall back to md5 otherwise.
346 retry (Optional[google.api_core.retry.Retry]): How to retry the
347 RPC. A None value will disable retries. A
348 google.api_core.retry.Retry value will enable retries, and the
349 object will configure backoff and timeout options.
350
351 See the retry.py source code and docstrings in this package
352 (google.cloud.storage.retry) for information on retry types and how
353 to configure them.
354
355 Attributes:
356 upload_url (str): The URL where the content will be uploaded.
357
358 Raises:
359 ValueError: If ``chunk_size`` is not a multiple of
360 :data:`.UPLOAD_CHUNK_SIZE`.
361 """
362
363 def initiate(
364 self,
365 transport,
366 stream,
367 metadata,
368 content_type,
369 total_bytes=None,
370 stream_final=True,
371 timeout=(
372 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
373 _request_helpers._DEFAULT_READ_TIMEOUT,
374 ),
375 ):
376 """Initiate a resumable upload.
377
378 By default, this method assumes your ``stream`` is in a "final"
379 state ready to transmit. However, ``stream_final=False`` can be used
380 to indicate that the size of the resource is not known. This can happen
381 if bytes are being dynamically fed into ``stream``, e.g. if the stream
382 is attached to application logs.
383
384 If ``stream_final=False`` is used, :attr:`chunk_size` bytes will be
385 read from the stream every time :meth:`transmit_next_chunk` is called.
386 If one of those reads produces strictly fewer bites than the chunk
387 size, the upload will be concluded.
388
389 Args:
390 transport (~requests.Session): A ``requests`` object which can
391 make authenticated requests.
392 stream (IO[bytes]): The stream (i.e. file-like object) that will
393 be uploaded. The stream **must** be at the beginning (i.e.
394 ``stream.tell() == 0``).
395 metadata (Mapping[str, str]): The resource metadata, such as an
396 ACL list.
397 content_type (str): The content type of the resource, e.g. a JPEG
398 image has content type ``image/jpeg``.
399 total_bytes (Optional[int]): The total number of bytes to be
400 uploaded. If specified, the upload size **will not** be
401 determined from the stream (even if ``stream_final=True``).
402 stream_final (Optional[bool]): Indicates if the ``stream`` is
403 "final" (i.e. no more bytes will be added to it). In this case
404 we determine the upload size from the size of the stream. If
405 ``total_bytes`` is passed, this argument will be ignored.
406 timeout (Optional[Union[float, Tuple[float, float]]]):
407 The number of seconds to wait for the server response.
408 Depending on the retry strategy, a request may be repeated
409 several times using the same timeout each time.
410
411 Can also be passed as a tuple (connect_timeout, read_timeout).
412 See :meth:`requests.Session.request` documentation for details.
413
414 Returns:
415 ~requests.Response: The HTTP response returned by ``transport``.
416 """
417 method, url, payload, headers = self._prepare_initiate_request(
418 stream,
419 metadata,
420 content_type,
421 total_bytes=total_bytes,
422 stream_final=stream_final,
423 )
424
425 # Wrap the request business logic in a function to be retried.
426 def retriable_request():
427 result = transport.request(
428 method, url, data=payload, headers=headers, timeout=timeout
429 )
430
431 self._process_initiate_response(result)
432
433 return result
434
435 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
436
437 def transmit_next_chunk(
438 self,
439 transport,
440 timeout=(
441 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
442 _request_helpers._DEFAULT_READ_TIMEOUT,
443 ),
444 ):
445 """Transmit the next chunk of the resource to be uploaded.
446
447 If the current upload was initiated with ``stream_final=False``,
448 this method will dynamically determine if the upload has completed.
449 The upload will be considered complete if the stream produces
450 fewer than :attr:`chunk_size` bytes when a chunk is read from it.
451
452 In the case of failure, an exception is thrown that preserves the
453 failed response:
454
455 .. testsetup:: bad-response
456
457 import io
458
459 import mock
460 import requests
461 import http.client
462
463 from google.cloud.storage import _media
464 import google.cloud.storage._media.requests.upload as upload_mod
465
466 transport = mock.Mock(spec=['request'])
467 fake_response = requests.Response()
468 fake_response.status_code = int(http.client.BAD_REQUEST)
469 transport.request.return_value = fake_response
470
471 upload_url = 'http://test.invalid'
472 upload = upload_mod.ResumableUpload(
473 upload_url, _media.UPLOAD_CHUNK_SIZE)
474 # Fake that the upload has been initiate()-d
475 data = b'data is here'
476 upload._stream = io.BytesIO(data)
477 upload._total_bytes = len(data)
478 upload._resumable_url = 'http://test.invalid?upload_id=nope'
479
480 .. doctest:: bad-response
481 :options: +NORMALIZE_WHITESPACE
482
483 >>> error = None
484 >>> try:
485 ... upload.transmit_next_chunk(transport)
486 ... except _media.InvalidResponse as caught_exc:
487 ... error = caught_exc
488 ...
489 >>> error
490 InvalidResponse('Request failed with status code', 400,
491 'Expected one of', <HTTPStatus.OK: 200>, <HTTPStatus.PERMANENT_REDIRECT: 308>)
492 >>> error.response
493 <Response [400]>
494
495 Args:
496 transport (~requests.Session): A ``requests`` object which can
497 make authenticated requests.
498 timeout (Optional[Union[float, Tuple[float, float]]]):
499 The number of seconds to wait for the server response.
500 Depending on the retry strategy, a request may be repeated
501 several times using the same timeout each time.
502
503 Can also be passed as a tuple (connect_timeout, read_timeout).
504 See :meth:`requests.Session.request` documentation for details.
505
506 Returns:
507 ~requests.Response: The HTTP response returned by ``transport``.
508
509 Raises:
510 ~google.cloud.storage.exceptions.InvalidResponse: If the status
511 code is not 200 or http.client.PERMANENT_REDIRECT.
512 ~google.cloud.storage.exceptions.DataCorruption: If this is the final
513 chunk, a checksum validation was requested, and the checksum
514 does not match or is not available.
515 """
516 method, url, payload, headers = self._prepare_request()
517
518 # Wrap the request business logic in a function to be retried.
519 def retriable_request():
520 result = transport.request(
521 method, url, data=payload, headers=headers, timeout=timeout
522 )
523
524 self._process_resumable_response(result, len(payload))
525
526 return result
527
528 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
529
530 def recover(self, transport):
531 """Recover from a failure and check the status of the current upload.
532
533 This will verify the progress with the server and make sure the
534 current upload is in a valid state before :meth:`transmit_next_chunk`
535 can be used again. See https://cloud.google.com/storage/docs/performing-resumable-uploads#status-check
536 for more information.
537
538 This method can be used when a :class:`ResumableUpload` is in an
539 :attr:`~ResumableUpload.invalid` state due to a request failure.
540
541 Args:
542 transport (~requests.Session): A ``requests`` object which can
543 make authenticated requests.
544
545 Returns:
546 ~requests.Response: The HTTP response returned by ``transport``.
547 """
548 timeout = (
549 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
550 _request_helpers._DEFAULT_READ_TIMEOUT,
551 )
552
553 method, url, payload, headers = self._prepare_recover_request()
554 # NOTE: We assume "payload is None" but pass it along anyway.
555
556 # Wrap the request business logic in a function to be retried.
557 def retriable_request():
558 result = transport.request(
559 method, url, data=payload, headers=headers, timeout=timeout
560 )
561
562 self._process_recover_response(result)
563
564 return result
565
566 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
567
568
569class XMLMPUContainer(_request_helpers.RequestsMixin, _upload.XMLMPUContainer):
570 """Initiate and close an upload using the XML MPU API.
571
572 An XML MPU sends an initial request and then receives an upload ID.
573 Using the upload ID, the upload is then done in numbered parts and the
574 parts can be uploaded concurrently.
575
576 In order to avoid concurrency issues with this container object, the
577 uploading of individual parts is handled separately, by XMLMPUPart objects
578 spawned from this container class. The XMLMPUPart objects are not
579 necessarily in the same process as the container, so they do not update the
580 container automatically.
581
582 MPUs are sometimes referred to as "Multipart Uploads", which is ambiguous
583 given the JSON multipart upload, so the abbreviation "MPU" will be used
584 throughout.
585
586 See: https://cloud.google.com/storage/docs/multipart-uploads
587
588 Args:
589 upload_url (str): The URL of the object (without query parameters). The
590 initiate, PUT, and finalization requests will all use this URL, with
591 varying query parameters.
592 headers (Optional[Mapping[str, str]]): Extra headers that should
593 be sent with the :meth:`initiate` request, e.g. headers for
594 encrypted data. These headers will be propagated to individual
595 XMLMPUPart objects spawned from this container as well.
596 retry (Optional[google.api_core.retry.Retry]): How to retry the
597 RPC. A None value will disable retries. A
598 google.api_core.retry.Retry value will enable retries, and the
599 object will configure backoff and timeout options.
600
601 See the retry.py source code and docstrings in this package
602 (google.cloud.storage.retry) for information on retry types and how
603 to configure them.
604
605 Attributes:
606 upload_url (str): The URL where the content will be uploaded.
607 upload_id (Optional(int)): The ID of the upload from the initialization
608 response.
609 """
610
611 def initiate(
612 self,
613 transport,
614 content_type,
615 timeout=(
616 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
617 _request_helpers._DEFAULT_READ_TIMEOUT,
618 ),
619 ):
620 """Initiate an MPU and record the upload ID.
621
622 Args:
623 transport (object): An object which can make authenticated
624 requests.
625 content_type (str): The content type of the resource, e.g. a JPEG
626 image has content type ``image/jpeg``.
627 timeout (Optional[Union[float, Tuple[float, float]]]):
628 The number of seconds to wait for the server response.
629 Depending on the retry strategy, a request may be repeated
630 several times using the same timeout each time.
631
632 Can also be passed as a tuple (connect_timeout, read_timeout).
633 See :meth:`requests.Session.request` documentation for details.
634
635 Returns:
636 ~requests.Response: The HTTP response returned by ``transport``.
637 """
638
639 method, url, payload, headers = self._prepare_initiate_request(
640 content_type,
641 )
642
643 # Wrap the request business logic in a function to be retried.
644 def retriable_request():
645 result = transport.request(
646 method, url, data=payload, headers=headers, timeout=timeout
647 )
648
649 self._process_initiate_response(result)
650
651 return result
652
653 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
654
655 def finalize(
656 self,
657 transport,
658 timeout=(
659 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
660 _request_helpers._DEFAULT_READ_TIMEOUT,
661 ),
662 ):
663 """Finalize an MPU request with all the parts.
664
665 Args:
666 transport (object): An object which can make authenticated
667 requests.
668 timeout (Optional[Union[float, Tuple[float, float]]]):
669 The number of seconds to wait for the server response.
670 Depending on the retry strategy, a request may be repeated
671 several times using the same timeout each time.
672
673 Can also be passed as a tuple (connect_timeout, read_timeout).
674 See :meth:`requests.Session.request` documentation for details.
675
676 Returns:
677 ~requests.Response: The HTTP response returned by ``transport``.
678 """
679 method, url, payload, headers = self._prepare_finalize_request()
680
681 # Wrap the request business logic in a function to be retried.
682 def retriable_request():
683 result = transport.request(
684 method, url, data=payload, headers=headers, timeout=timeout
685 )
686
687 self._process_finalize_response(result)
688
689 return result
690
691 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
692
693 def cancel(
694 self,
695 transport,
696 timeout=(
697 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
698 _request_helpers._DEFAULT_READ_TIMEOUT,
699 ),
700 ):
701 """Cancel an MPU request and permanently delete any uploaded parts.
702
703 This cannot be undone.
704
705 Args:
706 transport (object): An object which can make authenticated
707 requests.
708 timeout (Optional[Union[float, Tuple[float, float]]]):
709 The number of seconds to wait for the server response.
710 Depending on the retry strategy, a request may be repeated
711 several times using the same timeout each time.
712
713 Can also be passed as a tuple (connect_timeout, read_timeout).
714 See :meth:`requests.Session.request` documentation for details.
715
716 Returns:
717 ~requests.Response: The HTTP response returned by ``transport``.
718 """
719 method, url, payload, headers = self._prepare_cancel_request()
720
721 # Wrap the request business logic in a function to be retried.
722 def retriable_request():
723 result = transport.request(
724 method, url, data=payload, headers=headers, timeout=timeout
725 )
726
727 self._process_cancel_response(result)
728
729 return result
730
731 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)
732
733
734class XMLMPUPart(_request_helpers.RequestsMixin, _upload.XMLMPUPart):
735 def upload(
736 self,
737 transport,
738 timeout=(
739 _request_helpers._DEFAULT_CONNECT_TIMEOUT,
740 _request_helpers._DEFAULT_READ_TIMEOUT,
741 ),
742 ):
743 """Upload the part.
744
745 Args:
746 transport (object): An object which can make authenticated
747 requests.
748 timeout (Optional[Union[float, Tuple[float, float]]]):
749 The number of seconds to wait for the server response.
750 Depending on the retry strategy, a request may be repeated
751 several times using the same timeout each time.
752
753 Can also be passed as a tuple (connect_timeout, read_timeout).
754 See :meth:`requests.Session.request` documentation for details.
755
756 Returns:
757 ~requests.Response: The HTTP response returned by ``transport``.
758 """
759 method, url, payload, headers = self._prepare_upload_request()
760
761 # Wrap the request business logic in a function to be retried.
762 def retriable_request():
763 result = transport.request(
764 method, url, data=payload, headers=headers, timeout=timeout
765 )
766
767 self._process_upload_response(result)
768
769 return result
770
771 return _request_helpers.wait_and_retry(retriable_request, self._retry_strategy)