Exemple #1
0
    def start(self, print_next_run=True):
        """
        Starts the scheduler as per the configurations and parameters. Calling this method more than once has
        no effect if ``shutdown`` requested or already started.
        If ``separate_thread`` is true, then scheduler will run in separate thread, ``main`` thread otherwise.
        If ``run_continuous`` is true, then job will be run continuously, once otherwise

        Parameters
        ----------
        print_next_run : bool
                         if ``True`` it will keep printing next rnu schedule at every ``pulse_seconds``.
                         Default every 5 seconds

        """
        if self._shutdown_requested or self._started:
            return

        print('Starting scheduler > ')

        self._print_etr = print_next_run

        audit_params(Sc.OPERATION_START_JOBS, Sc.STATUS_STARTING,
                     'Starting scheduled jobs')

        if self._separate_thread:
            self._stop_event = self._schedule_in_separate_thread()
        else:
            self._schedule_in_main_thread()
Exemple #2
0
def notify(notifier: Notifier):
    """
    This module allows to send a notification based on the custom implementation of the
    ``Notifier`` abstract class. A developer needs to provide implementation for the notifier.

    The notifier dependency will be injected by the caller as per the use case. This behaviour
    allows to plug in either existing notifier or custom notifiers.

    This method also captures general audit log.

    Parameters
    ----------
    notifier : Notifier
                an instance of custom implementation of ``Notifier``

    """
    _start = current_time_in_millis()
    audit_params(Sc.OPERATION_NOTIFICATION,
                 Sc.STATUS_PROCESSING,
                 'Sending notification')

    notifier.notify()

    audit_params(Sc.OPERATION_NOTIFICATION,
                 Sc.STATUS_COMPLETE,
                 'Notification sent' + time_taken(_start))
Exemple #3
0
    def shutdown(self, force: bool = False):
        """
        Shuts down the scheduler. When requested, calling cancel any job has no effect.

        Parameters
        ----------
        force : bool
                if ``True`` it will not wait until the jobs complete, otherwise will wait until
                all jobs complete and then safely shutdown

        """

        if inspect.stack()[1].function != '_shut_it_down':
            return

        print(Sc.MSG_SHUTTING_DOWN_SCHEDULER)
        audit_params(Sc.OPERATION_SHUTDOWN, Sc.STATUS_STARTING,
                     Sc.MSG_SHUTTING_DOWN_SCHEDULER + ' Force=' + str(force))

        if self._run_continuous:
            self.__wait = True
            self._shutdown_requested = True
            if self._separate_thread:
                self._stop_event.set()
            else:
                self._run_continuous = False

            if not force:
                self._wait_until_safely_shutdown()
        schedule.clear()

        audit_params(Sc.OPERATION_SHUTDOWN, Sc.STATUS_COMPLETE,
                     Sc.MSG_SCHEDULER_SHUTDOWN_COMPLETE)

        print(Sc.MSG_SCHEDULER_SHUTDOWN_COMPLETE)
Exemple #4
0
def download(downloader: Downloader) -> list:
    """
    The caller should inject the appropriate implementation to perform
    download operation as per use case

    Parameters
    ----------
    downloader: Downloader
                instance of custom downloader implementation which has overridden
                download() function

    Returns
    ----------
    list
        list of downloaded files ordered by last modified time,
        earlier will be the first element

    """
    _start = current_time_in_millis()
    audit_params(Sc.OPERATION_DOWNLOAD, Sc.STATUS_PROCESSING, 'Files are downloading from source to destination')

    files = downloader.download()

    audit_params(Sc.OPERATION_DOWNLOAD, Sc.STATUS_COMPLETE, 'Files are downloaded to destination' + time_taken(_start))

    return files
Exemple #5
0
def upload(uploader: Uploader) -> list:
    """
    The caller should inject the appropriate implementation to perform
    upload operation as per use case

    Parameters
    ----------
    uploader: Uploader
              instance of custom uploader implementation which has overridden
              upload() function

    Returns
    --------
    list
        a list of uploaded files

    """
    _start = current_time_in_millis()
    audit_params(Sc.OPERATION_UPLOAD, Sc.STATUS_PROCESSING, 'Files are uploading from source to destination')

    files = uploader.upload()

    audit_params(Sc.OPERATION_UPLOAD, Sc.STATUS_COMPLETE, 'Files are uploaded to destination' + time_taken(_start))

    return files
Exemple #6
0
    def _wait_until_safely_shutdown(self):
        """
        If not force shutdown, then this method will ensure all jobs will complete before shutdown safely.

        """
        audit_params(Sc.OPERATION_SHUTDOWN, Sc.STATUS_WAITING,
                     Sc.MSG_WAIT_UNTIL_SAFE_SHUTDOWN)
        while self.__wait:
            print("Please wait...")
            time.sleep(2)
    def upload(self) -> list:
        """
        Connects to SFTP host and transfers files from a remote source to destination directory

        Returns
        -------
        list
            a list of files transferred

        """

        _sftp = None
        try:
            if self._sftpSecret.find(os.sep) == -1:
                _sftp = pysftp.Connection(self._sftpHost,
                                          username=self._sftpUsername,
                                          password=self._sftpSecret,
                                          port=self._sftpPort,
                                          cnopts=cnopts)
            else:
                _sftp = pysftp.Connection(self._sftpHost,
                                          username=self._sftpUsername,
                                          private_key=self._sftpSecret,
                                          port=self._sftpPort,
                                          cnopts=cnopts)
            _sftp.chdir(self._srcDir)

            for file in self._files:
                _src = os.path.join(self._srcDir, os.path.basename(file))
                _dest = os.path.join(self._destDir, os.path.basename(file))
                _start = current_time_in_millis()
                file_comments = '({}) ==> ({}) transfer'.format(_src, _dest)

                audit_params(operation=Sc.OPERATION_FILE_TRANSFER,
                             status=Sc.STATUS_PROCESSING,
                             comments=file_comments + 'ing...')

                _sftp.rename(_src, _dest)

                audit_params(operation=Sc.OPERATION_FILE_TRANSFER,
                             status=Sc.STATUS_COMPLETE,
                             comments=file_comments + 'ed' +
                             time_taken(_start))

        finally:
            if _sftp is not None:
                _sftp.close()

        return self._files
Exemple #8
0
def cleanup(cleaner: Cleaner):
    """
    The caller should inject the appropriate implementation to perform
    cleanup operation as per use case

    Parameters
    ----------
    cleaner: Cleaner
             instance of custom cleaner implementation which has overridden
             clean() function

    """
    _start = current_time_in_millis()
    audit_params(Sc.OPERATION_FILE_DELETE, Sc.STATUS_PROCESSING, 'Cleaning proecssed files')

    cleaner.clean()

    audit_params(Sc.OPERATION_FILE_DELETE, Sc.STATUS_COMPLETE, 'Processed files are cleaned' + time_taken(_start))
Exemple #9
0
    def download(self) -> list:
        """
        Copy files from source directory to destination.
        Only files will be considered which are matches with the extension mentioned in the configurations.

        Returns
        --------
        list
            a list of copied files

        """
        files = []

        # scans source directory for files and filters out with extension and then copy
        with os.scandir(self._srcDir) as it:
            for entry in it:
                if self._isfile(entry) and self._is_file_asked(entry.name):
                    files.append(entry)
                    dest_path = os.path.join(self._destDir, entry.name)
                    file_comments = '({}) ==> ({}) transfer'.format(
                        entry.path, dest_path)
                    _start = current_time_in_millis()
                    audit_params(operation=Sc.OPERATION_FILE_TRANSFER,
                                 status=Sc.STATUS_PROCESSING,
                                 comments=file_comments + 'ing...')

                    shutil.copy2(entry.path, dest_path)

                    audit_params(operation=Sc.OPERATION_FILE_TRANSFER,
                                 status=Sc.STATUS_COMPLETE,
                                 comments=file_comments + 'ed' +
                                 time_taken(_start))

        # sorts file entries by modified time. I
        # t is sorted by earliest modified time first and latest at the last
        if len(files) > 0:
            files.sort(key=lambda f: f.stat().st_mtime)
            print(files)
            return list(
                map(lambda f: os.path.join(self._destDir, f.name), files))
        else:
            return []
def _init():
    """
    Initialise and load all configurations from file. This method also validates configurations for errors.

    """
    print(Sc.MSG_CONFIG_LOADING)

    audit_params(Sc.OPERATION_CONFIGURATION, Sc.STATUS_LOADING,
                 'Loading configurations from file')

    _working_dir = os.path.dirname(__file__)
    _scheduler_config_file = _working_dir + Sc.CONFIG_FILE

    # config_file_path = scheduler_config_file

    with open(_scheduler_config_file) as jf:
        __box.all_configs = yaml.load(jf)

    audit_params(Sc.OPERATION_CONFIGURATION, Sc.STATUS_LOADED,
                 'Loaded configurations from file')

    errors = _validate()
    __box.valid = _empty_arr(errors)

    if not __box.valid:
        audit_params(Sc.OPERATION_CONFIGURATION, Sc.STATUS_INVALID,
                     'Invalid configurations found')

    print(Sc.MSG_CONFIG_LOADED)
    def upload(self) -> list:
        """
        Copy or move files to destination

        Returns
        --------
        list
            a list of files transferred to destination

        """
        _done = []
        for entry in self._files:
            if self._isfile(entry) and self._is_file_asked(entry):
                dest_path = os.path.join(self._destDir,
                                         os.path.basename(entry))
                src_path = os.path.join(self._srcDir, os.path.basename(entry))
                file_comments = '({}) ==> ({}) transfer'.format(
                    src_path, dest_path)
                _start = current_time_in_millis()

                audit_params(operation=Sc.OPERATION_UPLOAD,
                             status=Sc.STATUS_PROCESSING,
                             comments=file_comments + 'ing...')

                if self._move:
                    shutil.move(src=src_path, dst=dest_path)
                else:
                    shutil.copy2(src_path, dest_path)

                audit_params(operation=Sc.OPERATION_UPLOAD,
                             status=Sc.STATUS_COMPLETE,
                             comments=file_comments + 'ed' +
                             time_taken(_start))

                _done.append(dest_path)

        return _done
    def upload(self) -> list:
        """
        Connects to SFTP host and transfers files passed as an input

        Returns
        --------
        list
            a list of uploaded files

        """
        _sftp = None
        try:
            if self._sftpSecret.find(os.sep) == -1:
                _sftp = pysftp.Connection(self._sftpHost,
                                          username=self._sftpUsername,
                                          password=self._sftpSecret,
                                          port=self._sftpPort,
                                          cnopts=cnopts)
            else:
                _sftp = pysftp.Connection(self._sftpHost,
                                          username=self._sftpUsername,
                                          private_key=self._sftpSecret,
                                          port=self._sftpPort,
                                          cnopts=cnopts)
            _sftp.chdir(self._destDir)

            _start = current_time_in_millis()
            # audit_params(operation=Sc.OPERATION_UPLOAD,
            #              status=Sc.STATUS_PROCESSING,
            #              comments=Sc.MSG_FILES_UPLOADING)

            for file in self._files:
                _sftp.put(localpath=file,
                          callback=lambda transfered, size: audit_params(operation=Sc.OPERATION_UPLOAD,
                                                                         status=Sc.STATUS_COMPLETE,
                                                                         comments="{} {} ({})% ".format('<--<<', file, str("%.2f" % (100 * (int(transfered) / int(size)))))))

            # audit_params(operation=Sc.OPERATION_UPLOAD,
            #              status=Sc.STATUS_COMPLETE,
            #              comments=Sc.MSG_FILES_UPLOADED + time_taken(_start))
        finally:
            if _sftp is not None:
                _sftp.close()

        return self._files
Exemple #13
0
    def _download_them(self, _sftp: pysftp.Connection):
        """
        This method does the hard work downloading files. It also preserves the modified time
        after downloaded from the SFTP host.
        The call back lambda function prints the % byes downloaded,
        which is based on the size of the file.

        Parameters
        ----------
        _sftp : pysftp.Connection
                a sftp connection object

        """
        entries = _sftp.listdir(self._srcDir)

        for entry in entries:
            if _sftp.isfile(entry) and self._is_file_asked(entry):

                _sftp.get(os.path.join(self._srcDir, entry),
                          os.path.join(self._destDir, entry),
                          callback=lambda transfered, size: audit_params(operation=Sc.OPERATION_DOWNLOAD,
                                                                         status=Sc.STATUS_COMPLETE,
                                                                         comments="{} {} ({})%".format('>>-->', entry, str("%.2f" % (100*(int(transfered)/int(size)))))),
                          preserve_mtime=True)
Exemple #14
0
    def _schedule_in_main_thread(self):
        """
        Run scheduler in the main thread.
        If ``run_continuous`` is true then it will continuously run the jobs on schedule.
        Otherwise run all jobs at once.

        """

        if self._run_continuous:
            audit_params(Sc.OPERATION_START_JOBS, Sc.STATUS_STARTED,
                         Sc.MSG_JOB_STARTED.format(str(self._print_etr)))
            self._started = True
            while True:
                try:
                    if not self._run_continuous:
                        print(Sc.MSG_SHUTDOWN_SCHEDULER_RUNNING_ALL)
                        schedule.run_all()
                        self.__wait = False
                        self._shutdown_requested = False
                        self._started = False
                        break

                    self._next_run = schedule.next_run()
                    self._idle_seconds = schedule.idle_seconds()
                    self._log_etr()

                    schedule.run_pending()

                    time.sleep(self._pulse)
                except KeyboardInterrupt:
                    audit_params(operation=Sc.OPERATION_SHUTDOWN,
                                 status=Sc.STATUS_INTERRUPTED,
                                 comments=Sc.MSG_SCHEDULER_INTERRUPTED)
                    schedule.run_all()
                    self.__wait = False
                    self._shutdown_requested = False
                    self._started = False
                    break
        else:
            audit_params(Sc.OPERATION_START_JOBS, Sc.STATUS_STARTED,
                         Sc.MSG_JOB_STARTED.format(str(self._print_etr)))
            schedule.run_all()
Exemple #15
0
            def run(cls):
                if self._run_continuous:
                    audit_params(
                        Sc.OPERATION_START_JOBS, Sc.STATUS_STARTED,
                        Sc.MSG_JOB_STARTED.format(str(self._print_etr)))
                    self._started = True
                    while True:
                        try:
                            if stop_continuous_run.is_set():
                                print(Sc.MSG_SHUTDOWN_SCHEDULER_RUNNING_ALL)
                                schedule.run_all()
                                self.__wait = False
                                self._shutdown_requested = False
                                self._started = False
                                break

                            self._next_run = schedule.next_run()
                            self._idle_seconds = schedule.idle_seconds()
                            self._log_etr()

                            schedule.run_pending()

                            time.sleep(self._pulse)
                        except KeyboardInterrupt:
                            audit_params(operation=Sc.OPERATION_SHUTDOWN,
                                         status=Sc.STATUS_INTERRUPTED,
                                         comments=Sc.MSG_SCHEDULER_INTERRUPTED)
                            self._stop_event.set()
                            schedule.run_all()
                            self.__wait = False
                            self._shutdown_requested = False
                            self._started = False
                            break
                else:
                    audit_params(
                        Sc.OPERATION_START_JOBS, Sc.STATUS_STARTED,
                        Sc.MSG_JOB_STARTED.format(str(self._print_etr)))
                    schedule.run_all()
Exemple #16
0
    def schedule_job(self,
                     job: Job,
                     schedule_config: ScheduleConfig = None,
                     run_continuous: bool = False,
                     execute_parallel: bool = False,
                     pulse_seconds: int = Sc.DEFAULT_PULSE):
        """
        Schedules a job with ``name`` and user can specify whether the job should run once or should run continuously.

        Parameters
        ----------
        job : Job
              an instance of custom implementation of the ``Job`` module. Default ``None``
        schedule_config : ScheduleConfig
                        if user wants to change the ``every`` or ``at`` and ``time_unit`` before job schedule,
                        then user can create an instance on ScheduleConfig and specify new values
                        for schedule
        run_continuous : bool
                        if ``True`` the scheduler will continuously run jobs as per schedule,
                        run jobs once otherwise
        execute_parallel : bool
                            if ``True`` the job will be scheduled to execute parallel by spawning a thread,
                            sequential otherwise
        pulse_seconds : int
                       seconds to keep checking next schedule. Default 5 seconds.

                       Examples
                       --------
                        1. ``pulse_seconds=10`` will check next scheduler every 10 seconds
                        2. ``pulse_seconds=30`` will check next scheduler every 30 seconds

        """

        if not is_valid_implementation(job, Job):
            raise Exception(Sc.MSG_EX_ILLEGAL_JOB)

        if not isnone(schedule_config):
            self._override_schedule(schedule_config)

        self._run_continuous = run_continuous
        self._pulse = pulse_seconds

        audit_params(Sc.OPERATION_SCHEDULE, Sc.STATUS_SCHEDULING,
                     'Scheduling a job')

        if not isnone(self._every):
            evaluation_str = 'schedule.every(int(self._every)).' + self._unit
        else:
            evaluation_str = 'schedule.every().' + self._unit

        job_name = job.name()

        evaluation_str += self._at()
        if execute_parallel:
            evaluation_str += '.do(self._spawn_thread, job.goal).tag(job_name)'
        else:
            evaluation_str += '.do(job.goal).tag(job_name)'

        eval(evaluation_str)

        comments = StringBuilder(', ')
        comments.append('A job scheduled. Summary (Every=' + str(self._every)) \
            .append('TimeUnit=' + str(self._unit)) \
            .append('At=' + str(self._at_time)) \
            .append('SeparateThread=' + str(self._separate_thread)) \
            .append('RunningContinuously=' + str(self._run_continuous)) \
            .append('Pulse=' + str(self._pulse) + ')')

        audit_params(Sc.OPERATION_SCHEDULE, Sc.STATUS_SCHEDULED,
                     comments.to_string())

        print(Sc.MSG_JOB_SCHEDULED)