Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
tasks.py388 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 copy14import logging15 16from s3transfer.utils import get_callbacks17 18logger = logging.getLogger(__name__)19 20 21class Task:22    """A task associated to a TransferFuture request23 24    This is a base class for other classes to subclass from. All subclassed25    classes must implement the main() method.26    """27 28    def __init__(29        self,30        transfer_coordinator,31        main_kwargs=None,32        pending_main_kwargs=None,33        done_callbacks=None,34        is_final=False,35    ):36        """37        :type transfer_coordinator: s3transfer.futures.TransferCoordinator38        :param transfer_coordinator: The context associated to the39            TransferFuture for which this Task is associated with.40 41        :type main_kwargs: dict42        :param main_kwargs: The keyword args that can be immediately supplied43            to the _main() method of the task44 45        :type pending_main_kwargs: dict46        :param pending_main_kwargs: The keyword args that are depended upon47            by the result from a dependent future(s). The result returned by48            the future(s) will be used as the value for the keyword argument49            when _main() is called. The values for each key can be:50                * a single future - Once completed, its value will be the51                  result of that single future52                * a list of futures - Once all of the futures complete, the53                  value used will be a list of each completed future result54                  value in order of when they were originally supplied.55 56        :type done_callbacks: list of callbacks57        :param done_callbacks: A list of callbacks to call once the task is58            done completing. Each callback will be called with no arguments59            and will be called no matter if the task succeeds or an exception60            is raised.61 62        :type is_final: boolean63        :param is_final: True, to indicate that this task is the final task64            for the TransferFuture request. By setting this value to True, it65            will set the result of the entire TransferFuture to the result66            returned by this task's main() method.67        """68        self._transfer_coordinator = transfer_coordinator69 70        self._main_kwargs = main_kwargs71        if self._main_kwargs is None:72            self._main_kwargs = {}73 74        self._pending_main_kwargs = pending_main_kwargs75        if pending_main_kwargs is None:76            self._pending_main_kwargs = {}77 78        self._done_callbacks = done_callbacks79        if self._done_callbacks is None:80            self._done_callbacks = []81 82        self._is_final = is_final83 84    def __repr__(self):85        # These are the general main_kwarg parameters that we want to86        # display in the repr.87        params_to_display = [88            'bucket',89            'key',90            'part_number',91            'final_filename',92            'transfer_future',93            'offset',94            'extra_args',95        ]96        main_kwargs_to_display = self._get_kwargs_with_params_to_include(97            self._main_kwargs, params_to_display98        )99        return '{}(transfer_id={}, {})'.format(100            self.__class__.__name__,101            self._transfer_coordinator.transfer_id,102            main_kwargs_to_display,103        )104 105    @property106    def transfer_id(self):107        """The id for the transfer request that the task belongs to"""108        return self._transfer_coordinator.transfer_id109 110    def _get_kwargs_with_params_to_include(self, kwargs, include):111        filtered_kwargs = {}112        for param in include:113            if param in kwargs:114                filtered_kwargs[param] = kwargs[param]115        return filtered_kwargs116 117    def _get_kwargs_with_params_to_exclude(self, kwargs, exclude):118        filtered_kwargs = {}119        for param, value in kwargs.items():120            if param in exclude:121                continue122            filtered_kwargs[param] = value123        return filtered_kwargs124 125    def __call__(self):126        """The callable to use when submitting a Task to an executor"""127        try:128            # Wait for all of futures this task depends on.129            self._wait_on_dependent_futures()130            # Gather up all of the main keyword arguments for main().131            # This includes the immediately provided main_kwargs and132            # the values for pending_main_kwargs that source from the return133            # values from the task's dependent futures.134            kwargs = self._get_all_main_kwargs()135            # If the task is not done (really only if some other related136            # task to the TransferFuture had failed) then execute the task's137            # main() method.138            if not self._transfer_coordinator.done():139                return self._execute_main(kwargs)140        except Exception as e:141            self._log_and_set_exception(e)142        finally:143            # Run any done callbacks associated to the task no matter what.144            for done_callback in self._done_callbacks:145                done_callback()146 147            if self._is_final:148                # If this is the final task announce that it is done if results149                # are waiting on its completion.150                self._transfer_coordinator.announce_done()151 152    def _execute_main(self, kwargs):153        # Do not display keyword args that should not be printed, especially154        # if they are going to make the logs hard to follow.155        params_to_exclude = ['data']156        kwargs_to_display = self._get_kwargs_with_params_to_exclude(157            kwargs, params_to_exclude158        )159        # Log what is about to be executed.160        logger.debug(f"Executing task {self} with kwargs {kwargs_to_display}")161 162        return_value = self._main(**kwargs)163        # If the task is the final task, then set the TransferFuture's164        # value to the return value from main().165        if self._is_final:166            self._transfer_coordinator.set_result(return_value)167        return return_value168 169    def _log_and_set_exception(self, exception):170        # If an exception is ever thrown than set the exception for the171        # entire TransferFuture.172        logger.debug("Exception raised.", exc_info=True)173        self._transfer_coordinator.set_exception(exception)174 175    def _main(self, **kwargs):176        """The method that will be ran in the executor177 178        This method must be implemented by subclasses from Task. main() can179        be implemented with any arguments decided upon by the subclass.180        """181        raise NotImplementedError('_main() must be implemented')182 183    def _wait_on_dependent_futures(self):184        # Gather all of the futures into that main() depends on.185        futures_to_wait_on = []186        for _, future in self._pending_main_kwargs.items():187            # If the pending main keyword arg is a list then extend the list.188            if isinstance(future, list):189                futures_to_wait_on.extend(future)190            # If the pending main keyword arg is a future append it to the list.191            else:192                futures_to_wait_on.append(future)193        # Now wait for all of the futures to complete.194        self._wait_until_all_complete(futures_to_wait_on)195 196    def _wait_until_all_complete(self, futures):197        # This is a basic implementation of the concurrent.futures.wait()198        #199        # concurrent.futures.wait() is not used instead because of this200        # reported issue: https://bugs.python.org/issue20319.201        # The issue would occasionally cause multipart uploads to hang202        # when wait() was called. With this approach, it avoids the203        # concurrency bug by removing any association with concurrent.futures204        # implementation of waiters.205        logger.debug(206            '%s about to wait for the following futures %s', self, futures207        )208        for future in futures:209            try:210                logger.debug('%s about to wait for %s', self, future)211                future.result()212            except Exception:213                # result() can also produce exceptions. We want to ignore214                # these to be deferred to error handling down the road.215                pass216        logger.debug('%s done waiting for dependent futures', self)217 218    def _get_all_main_kwargs(self):219        # Copy over all of the kwargs that we know is available.220        kwargs = copy.copy(self._main_kwargs)221 222        # Iterate through the kwargs whose values are pending on the result223        # of a future.224        for key, pending_value in self._pending_main_kwargs.items():225            # If the value is a list of futures, iterate though the list226            # appending on the result from each future.227            if isinstance(pending_value, list):228                result = []229                for future in pending_value:230                    result.append(future.result())231            # Otherwise if the pending_value is a future, just wait for it.232            else:233                result = pending_value.result()234            # Add the retrieved value to the kwargs to be sent to the235            # main() call.236            kwargs[key] = result237        return kwargs238 239 240class SubmissionTask(Task):241    """A base class for any submission task242 243    Submission tasks are the top-level task used to submit a series of tasks244    to execute a particular transfer.245    """246 247    def _main(self, transfer_future, **kwargs):248        """249        :type transfer_future: s3transfer.futures.TransferFuture250        :param transfer_future: The transfer future associated with the251            transfer request that tasks are being submitted for252 253        :param kwargs: Any additional kwargs that you may want to pass254            to the _submit() method255        """256        try:257            self._transfer_coordinator.set_status_to_queued()258 259            # Before submitting any tasks, run all of the on_queued callbacks260            on_queued_callbacks = get_callbacks(transfer_future, 'queued')261            for on_queued_callback in on_queued_callbacks:262                on_queued_callback()263 264            # Once callbacks have been ran set the status to running.265            self._transfer_coordinator.set_status_to_running()266 267            # Call the submit method to start submitting tasks to execute the268            # transfer.269            self._submit(transfer_future=transfer_future, **kwargs)270        except BaseException as e:271            # If there was an exception raised during the submission of task272            # there is a chance that the final task that signals if a transfer273            # is done and too run the cleanup may never have been submitted in274            # the first place so we need to account accordingly.275            #276            # Note that BaseException is caught, instead of Exception, because277            # for some implementations of executors, specifically the serial278            # implementation, the SubmissionTask is directly exposed to279            # KeyboardInterupts and so needs to cleanup and signal done280            # for those as well.281 282            # Set the exception, that caused the process to fail.283            self._log_and_set_exception(e)284 285            # Wait for all possibly associated futures that may have spawned286            # from this submission task have finished before we announce the287            # transfer done.288            self._wait_for_all_submitted_futures_to_complete()289 290            # Announce the transfer as done, which will run any cleanups291            # and done callbacks as well.292            self._transfer_coordinator.announce_done()293 294    def _submit(self, transfer_future, **kwargs):295        """The submission method to be implemented296 297        :type transfer_future: s3transfer.futures.TransferFuture298        :param transfer_future: The transfer future associated with the299            transfer request that tasks are being submitted for300 301        :param kwargs: Any additional keyword arguments you want to be passed302            in303        """304        raise NotImplementedError('_submit() must be implemented')305 306    def _wait_for_all_submitted_futures_to_complete(self):307        # We want to wait for all futures that were submitted to308        # complete as we do not want the cleanup callbacks or done callbacks309        # to be called to early. The main problem is any task that was310        # submitted may have submitted even more during its process and so311        # we need to account accordingly.312 313        # First get all of the futures that were submitted up to this point.314        submitted_futures = self._transfer_coordinator.associated_futures315        while submitted_futures:316            # Wait for those futures to complete.317            self._wait_until_all_complete(submitted_futures)318            # However, more futures may have been submitted as we waited so319            # we need to check again for any more associated futures.320            possibly_more_submitted_futures = (321                self._transfer_coordinator.associated_futures322            )323            # If the current list of submitted futures is equal to the324            # the list of associated futures for when after the wait completes,325            # we can ensure no more futures were submitted in waiting on326            # the current list of futures to complete ultimately meaning all327            # futures that may have spawned from the original submission task328            # have completed.329            if submitted_futures == possibly_more_submitted_futures:330                break331            submitted_futures = possibly_more_submitted_futures332 333 334class CreateMultipartUploadTask(Task):335    """Task to initiate a multipart upload"""336 337    def _main(self, client, bucket, key, extra_args):338        """339        :param client: The client to use when calling CreateMultipartUpload340        :param bucket: The name of the bucket to upload to341        :param key: The name of the key to upload to342        :param extra_args: A dictionary of any extra arguments that may be343            used in the initialization.344 345        :returns: The upload id of the multipart upload346        """347        # Create the multipart upload.348        response = client.create_multipart_upload(349            Bucket=bucket, Key=key, **extra_args350        )351        upload_id = response['UploadId']352 353        # Add a cleanup if the multipart upload fails at any point.354        self._transfer_coordinator.add_failure_cleanup(355            client.abort_multipart_upload,356            Bucket=bucket,357            Key=key,358            UploadId=upload_id,359        )360        return upload_id361 362 363class CompleteMultipartUploadTask(Task):364    """Task to complete a multipart upload"""365 366    def _main(self, client, bucket, key, upload_id, parts, extra_args):367        """368        :param client: The client to use when calling CompleteMultipartUpload369        :param bucket: The name of the bucket to upload to370        :param key: The name of the key to upload to371        :param upload_id: The id of the upload372        :param parts: A list of parts to use to complete the multipart upload::373 374            [{'Etag': etag_value, 'PartNumber': part_number}, ...]375 376            Each element in the list consists of a return value from377            ``UploadPartTask.main()``.378        :param extra_args:  A dictionary of any extra arguments that may be379            used in completing the multipart transfer.380        """381        client.complete_multipart_upload(382            Bucket=bucket,383            Key=key,384            UploadId=upload_id,385            MultipartUpload={'Parts': parts},386            **extra_args,387        )388 
codekingpro/portable-devtools · Team Ai