123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- #!/usr/bin/env python
- # -*- coding: UTF-8 -*-
-
- """
- task (Task Module)
- ==================
-
- **Author:**
-
- * Dirk Alders <sudo-dirk@mount-mockery.de>
-
- **Description:**
-
- This Module supports helpfull classes for queues, tasks, ...
-
- **Submodules:**
-
- * :class:`task.crontab`
- * :class:`task.delayed`
- * :class:`task.periodic`
- * :class:`task.queue`
- * :class:`task.threaded_queue`
-
- **Unittest:**
-
- See also the :download:`unittest <task/_testresults_/unittest.pdf>` documentation.
-
- **Module Documentation:**
-
- """
- __DEPENDENCIES__ = []
-
- import logging
- import sys
- import threading
- import time
- if sys.version_info >= (3, 0):
- from queue import PriorityQueue
- from queue import Empty
- else:
- from Queue import PriorityQueue
- from Queue import Empty
-
- try:
- from config import APP_NAME as ROOT_LOGGER_NAME
- except ImportError:
- ROOT_LOGGER_NAME = 'root'
- logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
-
- __DESCRIPTION__ = """The Module {\\tt %s} is designed to help with task issues like periodic tasks, delayed tasks, queues, threaded queues and crontabs.
- For more Information read the documentation.""" % __name__.replace('_', '\_')
- """The Module Description"""
- __INTERPRETER__ = (2, 3)
- """The Tested Interpreter-Versions"""
-
-
- class queue(object):
- """
- Class to execute queued callbacks.
-
- :param bool expire: The default value for expire. See also :py:func:`expire`.
-
- **Example:**
-
- .. literalinclude:: task/_examples_/tqueue.py
-
- Will result to the following output:
-
- .. literalinclude:: task/_examples_/tqueue.log
- """
- class job(object):
- def __init__(self, priority, callback, *args, **kwargs):
- self.time = time.time()
- self.priority = priority
- self.callback = callback
- self.args = args
- self.kwargs = kwargs
-
- def run(self, queue):
- self.callback(queue, *self.args, **self.kwargs)
-
- def __lt__(self, other):
- if self.priority != other.priority:
- return self.priority < other.priority
- else:
- return self.time < other.time
-
- def __init__(self, expire=True):
- self.__expire = expire
- self.__stop = False
- self.queue = PriorityQueue()
-
- def clean_queue(self):
- """
- This Methods removes all jobs from the queue.
-
- .. note:: Be aware that already running jobs will not be terminated.
- """
- while not self.queue.empty():
- try:
- self.queue.get(False)
- except Empty: # This block is hard to reach for a testcase, but is
- continue # needed, if the thread runs dry while cleaning the queue.
- self.queue.task_done()
-
- def enqueue(self, priority, callback, *args, **kwargs):
- """
- This enqueues a given callback.
-
- :param number priority: The priority indication number of this task. The lowest value will be queued first.
- :param callback callback: Callback to be executed
- :param args args: Arguments to be given to callback
- :param kwargs kwargs: Keword Arguments to be given to callback
-
- .. note:: Callback will get this instance as first argument, followed by :py:data:`args` und :py:data:`kwargs`.
- """
- self.queue.put(self.job(priority, callback, *args, **kwargs))
-
- def qsize(self):
- return self.queue.qsize()
-
- def run(self):
- """
- This starts the execution of the queued callbacks.
- """
- self.__stop = False
- while not self.__stop:
- try:
- self.queue.get(timeout=0.1).run(self)
- except Empty:
- if self.__expire:
- break
- if type(self) is threaded_queue:
- self.thread = None
-
- def expire(self):
- """
- This sets the expire flag. That means that the process will stop after queue gets empty.
- """
- self.__expire = True
-
- def stop(self):
- """
- This sets the stop flag. That means that the process will stop after finishing the active task.
- """
- self.__stop = True
-
-
- class threaded_queue(queue):
- """Class to execute queued callbacks in a background thread (See also parent :py:class:`queue`).
-
- :param bool expire: The default value for expire. See also :py:func:`queue.expire`.
-
- **Example:**
-
- .. literalinclude:: task/_examples_/threaded_queue.py
-
- Will result to the following output:
-
- .. literalinclude:: task/_examples_/threaded_queue.log
- """
-
- def __init__(self, expire=False):
- queue.__init__(self, expire=expire)
- self.thread = None
-
- def run(self):
- if self.thread is None:
- self.thread = threading.Thread(target=self._start, args=(), daemon=True)
- self.thread.daemon = True # Daemonize thread
- self.thread.start() # Start the execution
-
- def join(self):
- """
- This blocks till the queue is empty.
-
- .. note:: If the queue does not run dry, join will block till the end of the days.
- """
- self.expire()
- if self.thread is not None:
- self.thread.join()
-
- def stop(self):
- queue.stop(self)
- self.join()
-
- def _start(self):
- queue.run(self)
-
-
- class periodic(object):
- """
- Class to execute a callback cyclicly.
-
- :param float cycle_time: Cycle time in seconds -- callback will be executed every *cycle_time* seconds
- :param callback callback: Callback to be executed
- :param args args: Arguments to be given to the callback
- :param kwargs kwargs: Keword Arguments to be given to callback
-
- .. note:: The Callback will get this instance as first argument, followed by :py:data:`args` und :py:data:`kwargs`.
-
- **Example:**
-
- .. literalinclude:: task/_examples_/periodic.py
-
- Will result to the following output:
-
- .. literalinclude:: task/_examples_/periodic.log
- """
-
- def __init__(self, cycle_time, callback, *args, **kwargs):
- self._lock = threading.Lock()
- self._timer = None
- self.callback = callback
- self.cycle_time = cycle_time
- self.args = args
- self.kwargs = kwargs
- self._stopped = True
- self._last_tm = None
- self.dt = None
-
- def join(self):
- """
- This blocks till the cyclic task is terminated.
-
- .. note:: Using join means that somewhere has to be a condition calling :py:func:`stop` to terminate. Otherwise :func:`task.join` will never return.
- """
- while not self._stopped:
- time.sleep(.1)
-
- def run(self):
- """
- This starts the cyclic execution of the given callback.
- """
- if self._stopped:
- self._set_timer(force_now=True)
-
- def stop(self):
- """
- This stops the execution of any further task.
- """
- self._lock.acquire()
- self._stopped = True
- if self._timer is not None:
- self._timer.cancel()
- self._lock.release()
-
- def _set_timer(self, force_now=False):
- """
- This sets the timer for the execution of the next task.
- """
- self._lock.acquire()
- self._stopped = False
- if force_now:
- self._timer = threading.Timer(0, self._start)
- else:
- self._timer = threading.Timer(self.cycle_time, self._start)
- self._timer.daemon = True
- self._timer.start()
- self._lock.release()
-
- def _start(self):
- tm = time.time()
- if self._last_tm is not None:
- self.dt = tm - self._last_tm
- self._set_timer(force_now=False)
- self.callback(self, *self.args, **self.kwargs)
- self._last_tm = tm
-
-
- class delayed(periodic):
- """Class to execute a callback a given time in the future. See also parent :py:class:`periodic`.
-
- :param float time: Delay time for execution of the given callback
- :param callback callback: Callback to be executed
- :param args args: Arguments to be given to callback
- :param kwargs kwargs: Keword Arguments to be given to callback
-
- **Example:**
-
- .. literalinclude:: task/_examples_/delayed.py
-
- Will result to the following output:
-
- .. literalinclude:: task/_examples_/delayed.log
- """
-
- def run(self):
- """
- This starts the timer for the delayed execution.
- """
- self._set_timer(force_now=False)
-
- def _start(self):
- self.callback(*self.args, **self.kwargs)
- self.stop()
-
-
- class crontab(periodic):
- """Class to execute a callback at the specified time conditions. See also parent :py:class:`periodic`.
-
- :param accuracy: Repeat time in seconds for background task checking event triggering. This time is the maximum delay between specified time condition and the execution.
- :type accuracy: float
-
- **Example:**
-
- .. literalinclude:: task/_examples_/crontab.py
-
- Will result to the following output:
-
- .. literalinclude:: task/_examples_/crontab.log
- """
- ANY = '*'
- """Constant for matching every condition."""
-
- class cronjob(object):
- """Class to handle cronjob parameters and cronjob changes.
-
- :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
- :type minute: int, list, str
- :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
- :type hour: int, list, str
- :param day_of_month: Day of Month for execution. Either 0...31, [0...31, 0...31, ...] or :py:const:`crontab.ANY` for every Day of Month.
- :type day_of_month: int, list, str
- :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
- :type month: int, list, str
- :param day_of_week: Day of Week for execution. Either 0...6, [0...6, 0...6, ...] or :py:const:`crontab.ANY` for every Day of Week.
- :type day_of_week: int, list, str
- :param callback: The callback to be executed. The instance of :py:class:`cronjob` will be given as the first, args and kwargs as the following parameters.
- :type callback: func
-
- .. note:: This class should not be used stand alone. An instance will be created by adding a cronjob by using :py:func:`crontab.add_cronjob()`.
- """
- class all_match(set):
- """Universal set - match everything"""
-
- def __contains__(self, item):
- (item)
- return True
-
- def __init__(self, minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs):
- self.set_trigger_conditions(minute or crontab.ANY, hour or crontab.ANY,
- day_of_month or crontab.ANY, month or crontab.ANY, day_of_week or crontab.ANY)
- self.callback = callback
- self.args = args
- self.kwargs = kwargs
- self.__last_cron_check_time__ = None
- self.__last_execution__ = None
-
- def set_trigger_conditions(self, minute=None, hour=None, day_of_month=None, month=None, day_of_week=None):
- """This Method changes the execution parameters.
-
- :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
- :type minute: int, list, str
- :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
- :type hour: int, list, str
- :param day_of_month: Day of Month for execution. Either 0...31, [0...31, 0...31, ...] or :py:const:`crontab.ANY` for every Day of Month.
- :type day_of_month: int, list, str
- :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
- :type month: int, list, str
- :param day_of_week: Day of Week for execution. Either 0...6, [0...6, 0...6, ...] or :py:const:`crontab.ANY` for every Day of Week.
- :type day_of_week: int, list, str
- """
- if minute is not None:
- self.minute = self.__conv_to_set__(minute)
- if hour is not None:
- self.hour = self.__conv_to_set__(hour)
- if day_of_month is not None:
- self.day_of_month = self.__conv_to_set__(day_of_month)
- if month is not None:
- self.month = self.__conv_to_set__(month)
- if day_of_week is not None:
- self.day_of_week = self.__conv_to_set__(day_of_week)
-
- def __conv_to_set__(self, obj):
- if obj is crontab.ANY:
- return self.all_match()
- elif isinstance(obj, (int, long) if sys.version_info < (3, 0) else (int)):
- return set([obj])
- else:
- return set(obj)
-
- def __execution_needed_for__(self, minute, hour, day_of_month, month, day_of_week):
- if self.__last_execution__ != [minute, hour, day_of_month, month, day_of_week]:
- if minute in self.minute and hour in self.hour and day_of_month in self.day_of_month and month in self.month and day_of_week in self.day_of_week:
- return True
- return False
-
- def __store_execution_reminder__(self, minute, hour, day_of_month, month, day_of_week):
- self.__last_execution__ = [minute, hour, day_of_month, month, day_of_week]
-
- def cron_execution(self, tm):
- """This Methods executes the Cron-Callback, if a execution is needed for the given time (depending on the parameters on initialisation)
-
- :param tm: (Current) Time Value to be checked. The time needs to be given in seconds since 1970 (e.g. generated by int(time.time())).
- :type tm: int
- """
- if self.__last_cron_check_time__ is None:
- self.__last_cron_check_time__ = tm - 1
- #
- for t in range(self.__last_cron_check_time__ + 1, tm + 1):
- lt = time.localtime(t)
- if self.__execution_needed_for__(lt[4], lt[3], lt[2], lt[1], lt[6]):
- self.callback(self, *self.args, **self.kwargs)
- self.__store_execution_reminder__(lt[4], lt[3], lt[2], lt[1], lt[6])
- break
- self.__last_cron_check_time__ = tm
-
- def __init__(self, accuracy=30):
- periodic.__init__(self, accuracy, self.__periodic__)
- self.__crontab__ = []
-
- def __periodic__(self, rt):
- (rt)
- tm = int(time.time())
- for cronjob in self.__crontab__:
- cronjob.cron_execution(tm)
-
- def add_cronjob(self, minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs):
- """This Method adds a cronjob to be executed.
-
- :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
- :type minute: int, list, str
- :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
- :type hour: int, list, str
- :param day_of_month: Day of Month for execution. Either 0...31, [0...31, 0...31, ...] or :py:const:`crontab.ANY` for every Day of Month.
- :type day_of_month: int, list, str
- :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
- :type month: int, list, str
- :param day_of_week: Day of Week for execution. Either 0...6, [0...6, 0...6, ...] or :py:const:`crontab.ANY` for every Day of Week.
- :type day_of_week: int, list, str
- :param callback: The callback to be executed. The instance of :py:class:`cronjob` will be given as the first, args and kwargs as the following parameters.
- :type callback: func
-
- .. note:: The ``callback`` will be executed with it's instance of :py:class:`cronjob` as the first parameter.
- The given Arguments (:data:`args`) and keyword Arguments (:data:`kwargs`) will be stored in that object.
- """
- self.__crontab__.append(self.cronjob(minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs))
|