Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
upload.py809 linesDownload Raw Back to s3transfer
1# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License"). You4# may not use this file except in compliance with the License. A copy of5# the License is located at6#7# http://aws.amazon.com/apache2.0/8#9# or in the "license" file accompanying this file. This file is10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF11# ANY KIND, either express or implied. See the License for the specific12# language governing permissions and limitations under the License.13import math14from io import BytesIO15 16from s3transfer.compat import readable, seekable17from s3transfer.futures import IN_MEMORY_UPLOAD_TAG18from s3transfer.tasks import (19    CompleteMultipartUploadTask,20    CreateMultipartUploadTask,21    SubmissionTask,22    Task,23)24from s3transfer.utils import (25    ChunksizeAdjuster,26    DeferredOpenFile,27    get_callbacks,28    get_filtered_dict,29)30 31 32class AggregatedProgressCallback:33    def __init__(self, callbacks, threshold=1024 * 256):34        """Aggregates progress updates for every provided progress callback35 36        :type callbacks: A list of functions that accepts bytes_transferred37            as a single argument38        :param callbacks: The callbacks to invoke when threshold is reached39 40        :type threshold: int41        :param threshold: The progress threshold in which to take the42            aggregated progress and invoke the progress callback with that43            aggregated progress total44        """45        self._callbacks = callbacks46        self._threshold = threshold47        self._bytes_seen = 048 49    def __call__(self, bytes_transferred):50        self._bytes_seen += bytes_transferred51        if self._bytes_seen >= self._threshold:52            self._trigger_callbacks()53 54    def flush(self):55        """Flushes out any progress that has not been sent to its callbacks"""56        if self._bytes_seen > 0:57            self._trigger_callbacks()58 59    def _trigger_callbacks(self):60        for callback in self._callbacks:61            callback(bytes_transferred=self._bytes_seen)62        self._bytes_seen = 063 64 65class InterruptReader:66    """Wrapper that can interrupt reading using an error67 68    It uses a transfer coordinator to propagate an error if it notices69    that a read is being made while the file is being read from.70 71    :type fileobj: file-like obj72    :param fileobj: The file-like object to read from73 74    :type transfer_coordinator: s3transfer.futures.TransferCoordinator75    :param transfer_coordinator: The transfer coordinator to use if the76        reader needs to be interrupted.77    """78 79    def __init__(self, fileobj, transfer_coordinator):80        self._fileobj = fileobj81        self._transfer_coordinator = transfer_coordinator82 83    def read(self, amount=None):84        # If there is an exception, then raise the exception.85        # We raise an error instead of returning no bytes because for86        # requests where the content length and md5 was sent, it will87        # cause md5 mismatches and retries as there was no indication that88        # the stream being read from encountered any issues.89        if self._transfer_coordinator.exception:90            raise self._transfer_coordinator.exception91        return self._fileobj.read(amount)92 93    def seek(self, where, whence=0):94        self._fileobj.seek(where, whence)95 96    def tell(self):97        return self._fileobj.tell()98 99    def close(self):100        self._fileobj.close()101 102    def __enter__(self):103        return self104 105    def __exit__(self, *args, **kwargs):106        self.close()107 108 109class UploadInputManager:110    """Base manager class for handling various types of files for uploads111 112    This class is typically used for the UploadSubmissionTask class to help113    determine the following:114 115        * How to determine the size of the file116        * How to determine if a multipart upload is required117        * How to retrieve the body for a PutObject118        * How to retrieve the bodies for a set of UploadParts119 120    The answers/implementations differ for the various types of file inputs121    that may be accepted. All implementations must subclass and override122    public methods from this class.123    """124 125    def __init__(self, osutil, transfer_coordinator, bandwidth_limiter=None):126        self._osutil = osutil127        self._transfer_coordinator = transfer_coordinator128        self._bandwidth_limiter = bandwidth_limiter129 130    @classmethod131    def is_compatible(cls, upload_source):132        """Determines if the source for the upload is compatible with manager133 134        :param upload_source: The source for which the upload will pull data135            from.136 137        :returns: True if the manager can handle the type of source specified138            otherwise returns False.139        """140        raise NotImplementedError('must implement _is_compatible()')141 142    def stores_body_in_memory(self, operation_name):143        """Whether the body it provides are stored in-memory144 145        :type operation_name: str146        :param operation_name: The name of the client operation that the body147            is being used for. Valid operation_names are ``put_object`` and148            ``upload_part``.149 150        :rtype: boolean151        :returns: True if the body returned by the manager will be stored in152            memory. False if the manager will not directly store the body in153            memory.154        """155        raise NotImplementedError('must implement store_body_in_memory()')156 157    def provide_transfer_size(self, transfer_future):158        """Provides the transfer size of an upload159 160        :type transfer_future: s3transfer.futures.TransferFuture161        :param transfer_future: The future associated with upload request162        """163        raise NotImplementedError('must implement provide_transfer_size()')164 165    def requires_multipart_upload(self, transfer_future, config):166        """Determines where a multipart upload is required167 168        :type transfer_future: s3transfer.futures.TransferFuture169        :param transfer_future: The future associated with upload request170 171        :type config: s3transfer.manager.TransferConfig172        :param config: The config associated to the transfer manager173 174        :rtype: boolean175        :returns: True, if the upload should be multipart based on176            configuration and size. False, otherwise.177        """178        raise NotImplementedError('must implement requires_multipart_upload()')179 180    def get_put_object_body(self, transfer_future):181        """Returns the body to use for PutObject182 183        :type transfer_future: s3transfer.futures.TransferFuture184        :param transfer_future: The future associated with upload request185 186        :type config: s3transfer.manager.TransferConfig187        :param config: The config associated to the transfer manager188 189        :rtype: s3transfer.utils.ReadFileChunk190        :returns: A ReadFileChunk including all progress callbacks191            associated with the transfer future.192        """193        raise NotImplementedError('must implement get_put_object_body()')194 195    def yield_upload_part_bodies(self, transfer_future, chunksize):196        """Yields the part number and body to use for each UploadPart197 198        :type transfer_future: s3transfer.futures.TransferFuture199        :param transfer_future: The future associated with upload request200 201        :type chunksize: int202        :param chunksize: The chunksize to use for this upload.203 204        :rtype: int, s3transfer.utils.ReadFileChunk205        :returns: Yields the part number and the ReadFileChunk including all206            progress callbacks associated with the transfer future for that207            specific yielded part.208        """209        raise NotImplementedError('must implement yield_upload_part_bodies()')210 211    def _wrap_fileobj(self, fileobj):212        fileobj = InterruptReader(fileobj, self._transfer_coordinator)213        if self._bandwidth_limiter:214            fileobj = self._bandwidth_limiter.get_bandwith_limited_stream(215                fileobj, self._transfer_coordinator, enabled=False216            )217        return fileobj218 219    def _get_progress_callbacks(self, transfer_future):220        callbacks = get_callbacks(transfer_future, 'progress')221        # We only want to be wrapping the callbacks if there are callbacks to222        # invoke because we do not want to be doing any unnecessary work if223        # there are no callbacks to invoke.224        if callbacks:225            return [AggregatedProgressCallback(callbacks)]226        return []227 228    def _get_close_callbacks(self, aggregated_progress_callbacks):229        return [callback.flush for callback in aggregated_progress_callbacks]230 231 232class UploadFilenameInputManager(UploadInputManager):233    """Upload utility for filenames"""234 235    @classmethod236    def is_compatible(cls, upload_source):237        return isinstance(upload_source, str)238 239    def stores_body_in_memory(self, operation_name):240        return False241 242    def provide_transfer_size(self, transfer_future):243        transfer_future.meta.provide_transfer_size(244            self._osutil.get_file_size(transfer_future.meta.call_args.fileobj)245        )246 247    def requires_multipart_upload(self, transfer_future, config):248        return transfer_future.meta.size >= config.multipart_threshold249 250    def get_put_object_body(self, transfer_future):251        # Get a file-like object for the given input252        fileobj, full_size = self._get_put_object_fileobj_with_full_size(253            transfer_future254        )255 256        # Wrap fileobj with interrupt reader that will quickly cancel257        # uploads if needed instead of having to wait for the socket258        # to completely read all of the data.259        fileobj = self._wrap_fileobj(fileobj)260 261        callbacks = self._get_progress_callbacks(transfer_future)262        close_callbacks = self._get_close_callbacks(callbacks)263        size = transfer_future.meta.size264        # Return the file-like object wrapped into a ReadFileChunk to get265        # progress.266        return self._osutil.open_file_chunk_reader_from_fileobj(267            fileobj=fileobj,268            chunk_size=size,269            full_file_size=full_size,270            callbacks=callbacks,271            close_callbacks=close_callbacks,272        )273 274    def yield_upload_part_bodies(self, transfer_future, chunksize):275        full_file_size = transfer_future.meta.size276        num_parts = self._get_num_parts(transfer_future, chunksize)277        for part_number in range(1, num_parts + 1):278            callbacks = self._get_progress_callbacks(transfer_future)279            close_callbacks = self._get_close_callbacks(callbacks)280            start_byte = chunksize * (part_number - 1)281            # Get a file-like object for that part and the size of the full282            # file size for the associated file-like object for that part.283            fileobj, full_size = self._get_upload_part_fileobj_with_full_size(284                transfer_future.meta.call_args.fileobj,285                start_byte=start_byte,286                part_size=chunksize,287                full_file_size=full_file_size,288            )289 290            # Wrap fileobj with interrupt reader that will quickly cancel291            # uploads if needed instead of having to wait for the socket292            # to completely read all of the data.293            fileobj = self._wrap_fileobj(fileobj)294 295            # Wrap the file-like object into a ReadFileChunk to get progress.296            read_file_chunk = self._osutil.open_file_chunk_reader_from_fileobj(297                fileobj=fileobj,298                chunk_size=chunksize,299                full_file_size=full_size,300                callbacks=callbacks,301                close_callbacks=close_callbacks,302            )303            yield part_number, read_file_chunk304 305    def _get_deferred_open_file(self, fileobj, start_byte):306        fileobj = DeferredOpenFile(307            fileobj, start_byte, open_function=self._osutil.open308        )309        return fileobj310 311    def _get_put_object_fileobj_with_full_size(self, transfer_future):312        fileobj = transfer_future.meta.call_args.fileobj313        size = transfer_future.meta.size314        return self._get_deferred_open_file(fileobj, 0), size315 316    def _get_upload_part_fileobj_with_full_size(self, fileobj, **kwargs):317        start_byte = kwargs['start_byte']318        full_size = kwargs['full_file_size']319        return self._get_deferred_open_file(fileobj, start_byte), full_size320 321    def _get_num_parts(self, transfer_future, part_size):322        return int(math.ceil(transfer_future.meta.size / float(part_size)))323 324 325class UploadSeekableInputManager(UploadFilenameInputManager):326    """Upload utility for an open file object"""327 328    @classmethod329    def is_compatible(cls, upload_source):330        return readable(upload_source) and seekable(upload_source)331 332    def stores_body_in_memory(self, operation_name):333        if operation_name == 'put_object':334            return False335        else:336            return True337 338    def provide_transfer_size(self, transfer_future):339        fileobj = transfer_future.meta.call_args.fileobj340        # To determine size, first determine the starting position341        # Seek to the end and then find the difference in the length342        # between the end and start positions.343        start_position = fileobj.tell()344        fileobj.seek(0, 2)345        end_position = fileobj.tell()346        fileobj.seek(start_position)347        transfer_future.meta.provide_transfer_size(348            end_position - start_position349        )350 351    def _get_upload_part_fileobj_with_full_size(self, fileobj, **kwargs):352        # Note: It is unfortunate that in order to do a multithreaded353        # multipart upload we cannot simply copy the filelike object354        # since there is not really a mechanism in python (i.e. os.dup355        # points to the same OS filehandle which causes concurrency356        # issues). So instead we need to read from the fileobj and357        # chunk the data out to separate file-like objects in memory.358        data = fileobj.read(kwargs['part_size'])359        # We return the length of the data instead of the full_file_size360        # because we partitioned the data into separate BytesIO objects361        # meaning the BytesIO object has no knowledge of its start position362        # relative the input source nor access to the rest of the input363        # source. So we must treat it as its own standalone file.364        return BytesIO(data), len(data)365 366    def _get_put_object_fileobj_with_full_size(self, transfer_future):367        fileobj = transfer_future.meta.call_args.fileobj368        # The current position needs to be taken into account when retrieving369        # the full size of the file.370        size = fileobj.tell() + transfer_future.meta.size371        return fileobj, size372 373 374class UploadNonSeekableInputManager(UploadInputManager):375    """Upload utility for a file-like object that cannot seek."""376 377    def __init__(self, osutil, transfer_coordinator, bandwidth_limiter=None):378        super().__init__(osutil, transfer_coordinator, bandwidth_limiter)379        self._initial_data = b''380 381    @classmethod382    def is_compatible(cls, upload_source):383        return readable(upload_source)384 385    def stores_body_in_memory(self, operation_name):386        return True387 388    def provide_transfer_size(self, transfer_future):389        # No-op because there is no way to do this short of reading the entire390        # body into memory.391        return392 393    def requires_multipart_upload(self, transfer_future, config):394        # If the user has set the size, we can use that.395        if transfer_future.meta.size is not None:396            return transfer_future.meta.size >= config.multipart_threshold397 398        # This is tricky to determine in this case because we can't know how399        # large the input is. So to figure it out, we read data into memory400        # up until the threshold and compare how much data was actually read401        # against the threshold.402        fileobj = transfer_future.meta.call_args.fileobj403        threshold = config.multipart_threshold404        self._initial_data = self._read(fileobj, threshold, False)405        if len(self._initial_data) < threshold:406            return False407        else:408            return True409 410    def get_put_object_body(self, transfer_future):411        callbacks = self._get_progress_callbacks(transfer_future)412        close_callbacks = self._get_close_callbacks(callbacks)413        fileobj = transfer_future.meta.call_args.fileobj414 415        body = self._wrap_data(416            self._initial_data + fileobj.read(), callbacks, close_callbacks417        )418 419        # Zero out the stored data so we don't have additional copies420        # hanging around in memory.421        self._initial_data = None422        return body423 424    def yield_upload_part_bodies(self, transfer_future, chunksize):425        file_object = transfer_future.meta.call_args.fileobj426        part_number = 0427 428        # Continue reading parts from the file-like object until it is empty.429        while True:430            callbacks = self._get_progress_callbacks(transfer_future)431            close_callbacks = self._get_close_callbacks(callbacks)432            part_number += 1433            part_content = self._read(file_object, chunksize)434            if not part_content:435                break436            part_object = self._wrap_data(437                part_content, callbacks, close_callbacks438            )439 440            # Zero out part_content to avoid hanging on to additional data.441            part_content = None442            yield part_number, part_object443 444    def _read(self, fileobj, amount, truncate=True):445        """446        Reads a specific amount of data from a stream and returns it. If there447        is any data in initial_data, that will be popped out first.448 449        :type fileobj: A file-like object that implements read450        :param fileobj: The stream to read from.451 452        :type amount: int453        :param amount: The number of bytes to read from the stream.454 455        :type truncate: bool456        :param truncate: Whether or not to truncate initial_data after457            reading from it.458 459        :return: Generator which generates part bodies from the initial data.460        """461        # If the the initial data is empty, we simply read from the fileobj462        if len(self._initial_data) == 0:463            return fileobj.read(amount)464 465        # If the requested number of bytes is less than the amount of466        # initial data, pull entirely from initial data.467        if amount <= len(self._initial_data):468            data = self._initial_data[:amount]469            # Truncate initial data so we don't hang onto the data longer470            # than we need.471            if truncate:472                self._initial_data = self._initial_data[amount:]473            return data474 475        # At this point there is some initial data left, but not enough to476        # satisfy the number of bytes requested. Pull out the remaining477        # initial data and read the rest from the fileobj.478        amount_to_read = amount - len(self._initial_data)479        data = self._initial_data + fileobj.read(amount_to_read)480 481        # Zero out initial data so we don't hang onto the data any more.482        if truncate:483            self._initial_data = b''484        return data485 486    def _wrap_data(self, data, callbacks, close_callbacks):487        """488        Wraps data with the interrupt reader and the file chunk reader.489 490        :type data: bytes491        :param data: The data to wrap.492 493        :type callbacks: list494        :param callbacks: The callbacks associated with the transfer future.495 496        :type close_callbacks: list497        :param close_callbacks: The callbacks to be called when closing the498            wrapper for the data.499 500        :return: Fully wrapped data.501        """502        fileobj = self._wrap_fileobj(BytesIO(data))503        return self._osutil.open_file_chunk_reader_from_fileobj(504            fileobj=fileobj,505            chunk_size=len(data),506            full_file_size=len(data),507            callbacks=callbacks,508            close_callbacks=close_callbacks,509        )510 511 512class UploadSubmissionTask(SubmissionTask):513    """Task for submitting tasks to execute an upload"""514 515    UPLOAD_PART_ARGS = [516        'ChecksumAlgorithm',517        'SSECustomerKey',518        'SSECustomerAlgorithm',519        'SSECustomerKeyMD5',520        'RequestPayer',521        'ExpectedBucketOwner',522    ]523 524    COMPLETE_MULTIPART_ARGS = [525        'SSECustomerKey',526        'SSECustomerAlgorithm',527        'SSECustomerKeyMD5',528        'RequestPayer',529        'ExpectedBucketOwner',530    ]531 532    def _get_upload_input_manager_cls(self, transfer_future):533        """Retrieves a class for managing input for an upload based on file type534 535        :type transfer_future: s3transfer.futures.TransferFuture536        :param transfer_future: The transfer future for the request537 538        :rtype: class of UploadInputManager539        :returns: The appropriate class to use for managing a specific type of540            input for uploads.541        """542        upload_manager_resolver_chain = [543            UploadFilenameInputManager,544            UploadSeekableInputManager,545            UploadNonSeekableInputManager,546        ]547 548        fileobj = transfer_future.meta.call_args.fileobj549        for upload_manager_cls in upload_manager_resolver_chain:550            if upload_manager_cls.is_compatible(fileobj):551                return upload_manager_cls552        raise RuntimeError(553            'Input {} of type: {} is not supported.'.format(554                fileobj, type(fileobj)555            )556        )557 558    def _submit(559        self,560        client,561        config,562        osutil,563        request_executor,564        transfer_future,565        bandwidth_limiter=None,566    ):567        """568        :param client: The client associated with the transfer manager569 570        :type config: s3transfer.manager.TransferConfig571        :param config: The transfer config associated with the transfer572            manager573 574        :type osutil: s3transfer.utils.OSUtil575        :param osutil: The os utility associated to the transfer manager576 577        :type request_executor: s3transfer.futures.BoundedExecutor578        :param request_executor: The request executor associated with the579            transfer manager580 581        :type transfer_future: s3transfer.futures.TransferFuture582        :param transfer_future: The transfer future associated with the583            transfer request that tasks are being submitted for584        """585        upload_input_manager = self._get_upload_input_manager_cls(586            transfer_future587        )(osutil, self._transfer_coordinator, bandwidth_limiter)588 589        # Determine the size if it was not provided590        if transfer_future.meta.size is None:591            upload_input_manager.provide_transfer_size(transfer_future)592 593        # Do a multipart upload if needed, otherwise do a regular put object.594        if not upload_input_manager.requires_multipart_upload(595            transfer_future, config596        ):597            self._submit_upload_request(598                client,599                config,600                osutil,601                request_executor,602                transfer_future,603                upload_input_manager,604            )605        else:606            self._submit_multipart_request(607                client,608                config,609                osutil,610                request_executor,611                transfer_future,612                upload_input_manager,613            )614 615    def _submit_upload_request(616        self,617        client,618        config,619        osutil,620        request_executor,621        transfer_future,622        upload_input_manager,623    ):624        call_args = transfer_future.meta.call_args625 626        # Get any tags that need to be associated to the put object task627        put_object_tag = self._get_upload_task_tag(628            upload_input_manager, 'put_object'629        )630 631        # Submit the request of a single upload.632        self._transfer_coordinator.submit(633            request_executor,634            PutObjectTask(635                transfer_coordinator=self._transfer_coordinator,636                main_kwargs={637                    'client': client,638                    'fileobj': upload_input_manager.get_put_object_body(639                        transfer_future640                    ),641                    'bucket': call_args.bucket,642                    'key': call_args.key,643                    'extra_args': call_args.extra_args,644                },645                is_final=True,646            ),647            tag=put_object_tag,648        )649 650    def _submit_multipart_request(651        self,652        client,653        config,654        osutil,655        request_executor,656        transfer_future,657        upload_input_manager,658    ):659        call_args = transfer_future.meta.call_args660 661        # Submit the request to create a multipart upload.662        create_multipart_future = self._transfer_coordinator.submit(663            request_executor,664            CreateMultipartUploadTask(665                transfer_coordinator=self._transfer_coordinator,666                main_kwargs={667                    'client': client,668                    'bucket': call_args.bucket,669                    'key': call_args.key,670                    'extra_args': call_args.extra_args,671                },672            ),673        )674 675        # Submit requests to upload the parts of the file.676        part_futures = []677        extra_part_args = self._extra_upload_part_args(call_args.extra_args)678 679        # Get any tags that need to be associated to the submitted task680        # for upload the data681        upload_part_tag = self._get_upload_task_tag(682            upload_input_manager, 'upload_part'683        )684 685        size = transfer_future.meta.size686        adjuster = ChunksizeAdjuster()687        chunksize = adjuster.adjust_chunksize(config.multipart_chunksize, size)688        part_iterator = upload_input_manager.yield_upload_part_bodies(689            transfer_future, chunksize690        )691 692        for part_number, fileobj in part_iterator:693            part_futures.append(694                self._transfer_coordinator.submit(695                    request_executor,696                    UploadPartTask(697                        transfer_coordinator=self._transfer_coordinator,698                        main_kwargs={699                            'client': client,700                            'fileobj': fileobj,701                            'bucket': call_args.bucket,702                            'key': call_args.key,703                            'part_number': part_number,704                            'extra_args': extra_part_args,705                        },706                        pending_main_kwargs={707                            'upload_id': create_multipart_future708                        },709                    ),710                    tag=upload_part_tag,711                )712            )713 714        complete_multipart_extra_args = self._extra_complete_multipart_args(715            call_args.extra_args716        )717        # Submit the request to complete the multipart upload.718        self._transfer_coordinator.submit(719            request_executor,720            CompleteMultipartUploadTask(721                transfer_coordinator=self._transfer_coordinator,722                main_kwargs={723                    'client': client,724                    'bucket': call_args.bucket,725                    'key': call_args.key,726                    'extra_args': complete_multipart_extra_args,727                },728                pending_main_kwargs={729                    'upload_id': create_multipart_future,730                    'parts': part_futures,731                },732                is_final=True,733            ),734        )735 736    def _extra_upload_part_args(self, extra_args):737        # Only the args in UPLOAD_PART_ARGS actually need to be passed738        # onto the upload_part calls.739        return get_filtered_dict(extra_args, self.UPLOAD_PART_ARGS)740 741    def _extra_complete_multipart_args(self, extra_args):742        return get_filtered_dict(extra_args, self.COMPLETE_MULTIPART_ARGS)743 744    def _get_upload_task_tag(self, upload_input_manager, operation_name):745        tag = None746        if upload_input_manager.stores_body_in_memory(operation_name):747            tag = IN_MEMORY_UPLOAD_TAG748        return tag749 750 751class PutObjectTask(Task):752    """Task to do a nonmultipart upload"""753 754    def _main(self, client, fileobj, bucket, key, extra_args):755        """756        :param client: The client to use when calling PutObject757        :param fileobj: The file to upload.758        :param bucket: The name of the bucket to upload to759        :param key: The name of the key to upload to760        :param extra_args: A dictionary of any extra arguments that may be761            used in the upload.762        """763        with fileobj as body:764            client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)765 766 767class UploadPartTask(Task):768    """Task to upload a part in a multipart upload"""769 770    def _main(771        self, client, fileobj, bucket, key, upload_id, part_number, extra_args772    ):773        """774        :param client: The client to use when calling PutObject775        :param fileobj: The file to upload.776        :param bucket: The name of the bucket to upload to777        :param key: The name of the key to upload to778        :param upload_id: The id of the upload779        :param part_number: The number representing the part of the multipart780            upload781        :param extra_args: A dictionary of any extra arguments that may be782            used in the upload.783 784        :rtype: dict785        :returns: A dictionary representing a part::786 787            {'Etag': etag_value, 'PartNumber': part_number}788 789            This value can be appended to a list to be used to complete790            the multipart upload.791        """792        with fileobj as body:793            response = client.upload_part(794                Bucket=bucket,795                Key=key,796                UploadId=upload_id,797                PartNumber=part_number,798                Body=body,799                **extra_args,800            )801        etag = response['ETag']802        part_metadata = {'ETag': etag, 'PartNumber': part_number}803        if 'ChecksumAlgorithm' in extra_args:804            algorithm_name = extra_args['ChecksumAlgorithm'].upper()805            checksum_member = f'Checksum{algorithm_name}'806            if checksum_member in response:807                part_metadata[checksum_member] = response[checksum_member]808        return part_metadata809 
codekingpro/portable-devtools · Team Ai