Dirk Alders 4年前
コミット
d3fbc59340
3個のファイルの変更21434行の追加0行の削除
  1. 422
    0
      __init__.py
  2. 21012
    0
      _testresults_/unittest.json
  3. バイナリ
      _testresults_/unittest.pdf

+ 422
- 0
__init__.py ファイルの表示

@@ -0,0 +1,422 @@
1
+#!/usr/bin/env python
2
+# -*- coding: UTF-8 -*-
3
+
4
+"""
5
+task (Task Module)
6
+==================
7
+
8
+**Author:**
9
+
10
+* Dirk Alders <sudo-dirk@mount-mockery.de>
11
+
12
+**Description:**
13
+
14
+    This Module supports helpfull classes for queues, tasks, ...
15
+
16
+**Submodules:**
17
+
18
+* :class:`task.crontab`
19
+* :class:`task.delayed`
20
+* :class:`task.periodic`
21
+* :class:`task.queue`
22
+* :class:`task.threaded_queue`
23
+
24
+**Unittest:**
25
+
26
+        See also the :download:`unittest <../../task/_testresults_/unittest.pdf>` documentation.
27
+"""
28
+__DEPENDENCIES__ = []
29
+
30
+import logging
31
+import sys
32
+import threading
33
+import time
34
+if sys.version_info >= (3, 0):
35
+    from queue import PriorityQueue
36
+    from queue import Empty
37
+else:
38
+    from Queue import PriorityQueue
39
+    from Queue import Empty
40
+
41
+logger_name = 'TASK'
42
+logger = logging.getLogger(logger_name)
43
+
44
+__DESCRIPTION__ = """The Module {\\tt %s} is designed to help with task issues like periodic tasks, delayed tasks, queues, threaded queues and crontabs.
45
+For more Information read the documentation.""" % __name__.replace('_', '\_')
46
+"""The Module Description"""
47
+__INTERPRETER__ = (2, 3)
48
+"""The Tested Interpreter-Versions"""
49
+
50
+
51
+class queue(object):
52
+    """Class to execute queued methods.
53
+
54
+    :param bool expire: The default value for expire. See also :py:func:`expire`.
55
+
56
+    **Example:**
57
+
58
+    .. literalinclude:: ../../task/_examples_/queue.py
59
+
60
+    Will result to the following output:
61
+
62
+    .. literalinclude:: ../../task/_examples_/queue.log
63
+    """
64
+    class job(object):
65
+        def __init__(self, priority, callback, *args, **kwargs):
66
+            self.priority = priority
67
+            self.callback = callback
68
+            self.args = args
69
+            self.kwargs = kwargs
70
+
71
+        def run(self, queue):
72
+            self.callback(queue, *self.args, **self.kwargs)
73
+
74
+        def __lt__(self, other):
75
+            return self.priority < other.priority
76
+
77
+    def __init__(self, expire=True):
78
+        self.__expire = expire
79
+        self.__stop = False
80
+        self.queue = PriorityQueue()
81
+
82
+    def clean_queue(self):
83
+        """
84
+        This Methods removes all jobs from the queue.
85
+
86
+        .. note:: Be aware that already runnung jobs will not be terminated.
87
+        """
88
+        while not self.queue.empty():
89
+            try:
90
+                self.queue.get(False)
91
+            except Empty:         # This block is hard to reach for a testcase, but is
92
+                continue          # needed, if the thread runs dry while cleaning the queue.
93
+            self.queue.task_done()
94
+
95
+    def enqueue(self, priority, method, *args, **kwargs):
96
+        """
97
+        This enqueues a given callback.
98
+
99
+        :param number priority: The priority indication number of this task. The lowest value will be queued first.
100
+        :param method method: Method to be executed
101
+        :param args args: Arguments to be given to method
102
+        :param kwargs kwargs: Kewordsarguments to be given to method
103
+
104
+        .. note:: Called method will get this instance as first argument, followed by :py:data:`args` und :py:data:`kwargs`.
105
+        """
106
+        self.queue.put(self.job(priority, method, *args, **kwargs))
107
+
108
+    def qsize(self):
109
+        return self.queue.qsize()
110
+
111
+    def run(self):
112
+        """
113
+        This starts the execution of the queued methods.
114
+        """
115
+        self.__stop = False
116
+        while not self.__stop:
117
+            try:
118
+                self.queue.get(timeout=0.1).run(self)
119
+            except Empty:
120
+                if self.__expire:
121
+                    break
122
+        if type(self) is threaded_queue:
123
+            self.thread = None
124
+
125
+    def expire(self):
126
+        """
127
+        This sets the expire flag. That means that the process will stop after queue gets empty.
128
+        """
129
+        self.__expire = True
130
+
131
+    def stop(self):
132
+        """
133
+        This sets the stop flag. That means that the process will stop after finishing the active task.
134
+        """
135
+        self.__stop = True
136
+
137
+
138
+class threaded_queue(queue):
139
+    """Class to execute queued methods in a background thread (See also parent :py:class:`queue`).
140
+
141
+    :param bool expire: The default value for expire. See also :py:func:`queue.expire`.
142
+
143
+    **Example:**
144
+
145
+    .. literalinclude:: ../../task/_examples_/threaded_queue.py
146
+
147
+    Will result to the following output:
148
+
149
+    .. literalinclude:: ../../task/_examples_/threaded_queue.log
150
+    """
151
+    def __init__(self, expire=False):
152
+        queue.__init__(self, expire=expire)
153
+        self.thread = None
154
+
155
+    def run(self):
156
+        if self.thread is None:
157
+            self.thread = threading.Thread(target=self._start, args=())
158
+            self.thread.daemon = True    # Daemonize thread
159
+            self.thread.start()          # Start the execution
160
+
161
+    def join(self):
162
+        """
163
+        This blocks till the queue is empty.
164
+
165
+        .. note:: If the queue does not run dry, join will block till the end of the days.
166
+        """
167
+        self.expire()
168
+        if self.thread is not None:
169
+            self.thread.join()
170
+
171
+    def stop(self):
172
+        queue.stop(self)
173
+        self.join()
174
+
175
+    def _start(self):
176
+        queue.run(self)
177
+
178
+
179
+class periodic(object):
180
+    """
181
+    :param float cycle_time: Cycle time in seconds -- method will be executed every *cycle_time* seconds
182
+    :param method method: Method to be executed
183
+    :param args args: Arguments to be given to method
184
+    :param kwargs kwargs: Kewordsarguments to be given to method
185
+
186
+    Class to execute a method cyclicly.
187
+
188
+    .. note:: Called method will get this instance as first argument, followed by :py:data:`args` und :py:data:`kwargs`.
189
+
190
+    **Example:**
191
+
192
+    .. literalinclude:: ../../task/_examples_/periodic.py
193
+
194
+    Will result to the following output:
195
+
196
+    .. literalinclude:: ../../task/_examples_/periodic.log
197
+    """
198
+    def __init__(self, cycle_time, method, *args, **kwargs):
199
+        self._lock = threading.Lock()
200
+        self._timer = None
201
+        self.method = method
202
+        self.cycle_time = cycle_time
203
+        self.args = args
204
+        self.kwargs = kwargs
205
+        self._stopped = True
206
+        self._last_tm = None
207
+        self.dt = None
208
+
209
+    def join(self, timeout=0.1):
210
+        """
211
+        This blocks till the cyclic task is terminated.
212
+
213
+        :param float timeout: Cycle time for checking if task is stopped
214
+
215
+        .. note:: Using join means that somewhere has to be a condition calling :py:func:`stop` to terminate.
216
+        """
217
+        while not self._stopped:
218
+            time.sleep(timeout)
219
+
220
+    def run(self):
221
+        """
222
+        This starts the cyclic execution of the given method.
223
+        """
224
+        if self._stopped:
225
+            self._set_timer(force_now=True)
226
+
227
+    def stop(self):
228
+        """
229
+        This stops the execution of any following task.
230
+        """
231
+        self._lock.acquire()
232
+        self._stopped = True
233
+        if self._timer is not None:
234
+            self._timer.cancel()
235
+        self._lock.release()
236
+
237
+    def _set_timer(self, force_now=False):
238
+        """
239
+        This sets the timer for the execution of the next task.
240
+        """
241
+        self._lock.acquire()
242
+        self._stopped = False
243
+        if force_now:
244
+            self._timer = threading.Timer(0, self._start)
245
+        else:
246
+            self._timer = threading.Timer(self.cycle_time, self._start)
247
+        self._timer.start()
248
+        self._lock.release()
249
+
250
+    def _start(self):
251
+        tm = time.time()
252
+        if self._last_tm is not None:
253
+            self.dt = tm - self._last_tm
254
+        self._set_timer(force_now=False)
255
+        self.method(self, *self.args, **self.kwargs)
256
+        self._last_tm = tm
257
+
258
+
259
+class delayed(periodic):
260
+    """Class to execute a method a given time in the future. See also parent :py:class:`periodic`.
261
+
262
+    :param float time: Delay time for execution of the given method
263
+    :param method method: Method to be executed
264
+    :param args args: Arguments to be given to method
265
+    :param kwargs kwargs: Kewordsarguments to be given to method
266
+
267
+    **Example:**
268
+
269
+    .. literalinclude:: ../../task/_examples_/delayed.py
270
+
271
+    Will result to the following output:
272
+
273
+    .. literalinclude:: ../../task/_examples_/delayed.log
274
+    """
275
+    def run(self):
276
+        """
277
+        This starts the timer for the delayed execution.
278
+        """
279
+        self._set_timer(force_now=False)
280
+
281
+    def _start(self):
282
+        self.method(*self.args, **self.kwargs)
283
+        self.stop()
284
+
285
+
286
+class crontab(periodic):
287
+    """Class to execute a callback at the specified time conditions. See also parent :py:class:`periodic`.
288
+
289
+    :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.
290
+    :type accuracy: float
291
+
292
+    **Example:**
293
+
294
+    .. literalinclude:: ../../task/_examples_/crontab.py
295
+
296
+    Will result to the following output:
297
+
298
+    .. literalinclude:: ../../task/_examples_/crontab.log
299
+    """
300
+    ANY = '*'
301
+    """Constant for matching every condition."""
302
+
303
+    class cronjob(object):
304
+        """Class to handle cronjob parameters and cronjob changes.
305
+
306
+        :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
307
+        :type minute: int, list, str
308
+        :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
309
+        :type hour: int, list, str
310
+        :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.
311
+        :type day_of_month: int, list, str
312
+        :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
313
+        :type month: int, list, str
314
+        :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.
315
+        :type day_of_week: int, list, str
316
+        :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.
317
+        :type callback: func
318
+
319
+        .. 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()`.
320
+        """
321
+        class all_match(set):
322
+            """Universal set - match everything"""
323
+            def __contains__(self, item):
324
+                (item)
325
+                return True
326
+
327
+        def __init__(self, minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs):
328
+            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)
329
+            self.callback = callback
330
+            self.args = args
331
+            self.kwargs = kwargs
332
+            self.__last_cron_check_time__ = None
333
+            self.__last_execution__ = None
334
+
335
+        def set_trigger_conditions(self, minute=None, hour=None, day_of_month=None, month=None, day_of_week=None):
336
+            """This Method changes the execution parameters.
337
+
338
+            :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
339
+            :type minute: int, list, str
340
+            :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
341
+            :type hour: int, list, str
342
+            :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.
343
+            :type day_of_month: int, list, str
344
+            :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
345
+            :type month: int, list, str
346
+            :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.
347
+            :type day_of_week: int, list, str
348
+            """
349
+            if minute is not None:
350
+                self.minute = self.__conv_to_set__(minute)
351
+            if hour is not None:
352
+                self.hour = self.__conv_to_set__(hour)
353
+            if day_of_month is not None:
354
+                self.day_of_month = self.__conv_to_set__(day_of_month)
355
+            if month is not None:
356
+                self.month = self.__conv_to_set__(month)
357
+            if day_of_week is not None:
358
+                self.day_of_week = self.__conv_to_set__(day_of_week)
359
+
360
+        def __conv_to_set__(self, obj):
361
+            if obj is crontab.ANY:
362
+                return self.all_match()
363
+            elif isinstance(obj, (int, long) if sys.version_info < (3,0) else (int)):
364
+                return set([obj])
365
+            else:
366
+                return set(obj)
367
+
368
+        def __execution_needed_for__(self, minute, hour, day_of_month, month, day_of_week):
369
+            if self.__last_execution__ != [minute, hour, day_of_month, month, day_of_week]:
370
+                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:
371
+                    return True
372
+            return False
373
+
374
+        def __store_execution_reminder__(self, minute, hour, day_of_month, month, day_of_week):
375
+            self.__last_execution__ = [minute, hour, day_of_month, month, day_of_week]
376
+
377
+        def cron_execution(self, tm):
378
+            """This Methods executes the Cron-Callback, if a execution is needed for the given time (depending on the parameters on initialisation)
379
+
380
+            :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())).
381
+            :type tm: int
382
+            """
383
+            if self.__last_cron_check_time__ is None:
384
+                self.__last_cron_check_time__ = tm - 1
385
+            #
386
+            for t in range(self.__last_cron_check_time__ + 1, tm + 1):
387
+                lt = time.localtime(t)
388
+                if self.__execution_needed_for__(lt[4], lt[3], lt[2], lt[1], lt[6]):
389
+                    self.callback(self, *self.args, **self.kwargs)
390
+                    self.__store_execution_reminder__(lt[4], lt[3], lt[2], lt[1], lt[6])
391
+                    break
392
+            self.__last_cron_check_time__ = tm
393
+
394
+    def __init__(self, accuracy=30):
395
+        periodic.__init__(self, accuracy, self.__periodic__)
396
+        self.__crontab__ = []
397
+
398
+    def __periodic__(self, rt):
399
+        (rt)
400
+        tm = int(time.time())
401
+        for cronjob in self.__crontab__:
402
+            cronjob.cron_execution(tm)
403
+
404
+    def add_cronjob(self, minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs):
405
+        """This Method adds a cronjob to be executed.
406
+
407
+        :param minute: Minute for execution. Either 0...59, [0...59, 0...59, ...] or :py:const:`crontab.ANY` for every Minute.
408
+        :type minute: int, list, str
409
+        :param hour: Hour for execution. Either 0...23, [0...23, 0...23, ...] or :py:const:`crontab.ANY` for every Hour.
410
+        :type hour: int, list, str
411
+        :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.
412
+        :type day_of_month: int, list, str
413
+        :param month: Month for execution. Either 0...12, [0...12, 0...12, ...] or :py:const:`crontab.ANY` for every Month.
414
+        :type month: int, list, str
415
+        :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.
416
+        :type day_of_week: int, list, str
417
+        :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.
418
+        :type callback: func
419
+
420
+        .. note:: The ``callback`` will be executed with it's instance of :py:class:`cronjob` as the first parameter.
421
+        """
422
+        self.__crontab__.append(self.cronjob(minute, hour, day_of_month, month, day_of_week, callback, *args, **kwargs))

+ 21012
- 0
_testresults_/unittest.json
ファイル差分が大きすぎるため省略します
ファイルの表示


バイナリ
_testresults_/unittest.pdf ファイルの表示


読み込み中…
キャンセル
保存