wiederverwendbar 0.9.0__py3-none-any.whl → 0.9.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wiederverwendbar/__init__.py +1 -1
- wiederverwendbar/branding/settings.py +9 -7
- wiederverwendbar/fastapi/app.py +4 -1
- wiederverwendbar/sqlalchemy/db.py +3 -3
- wiederverwendbar/task_manger/__init__.py +17 -4
- wiederverwendbar/task_manger/task.py +118 -75
- wiederverwendbar/task_manger/task_manager.py +127 -104
- wiederverwendbar/task_manger/trigger.py +297 -115
- wiederverwendbar/timer.py +61 -0
- wiederverwendbar/typer/app.py +5 -1
- wiederverwendbar-0.9.2.dist-info/METADATA +726 -0
- {wiederverwendbar-0.9.0.dist-info → wiederverwendbar-0.9.2.dist-info}/RECORD +15 -13
- wiederverwendbar-0.9.2.dist-info/licenses/LICENSE +674 -0
- wiederverwendbar-0.9.0.dist-info/METADATA +0 -52
- {wiederverwendbar-0.9.0.dist-info → wiederverwendbar-0.9.2.dist-info}/WHEEL +0 -0
- {wiederverwendbar-0.9.0.dist-info → wiederverwendbar-0.9.2.dist-info}/entry_points.txt +0 -0
@@ -1,57 +1,73 @@
|
|
1
1
|
import logging
|
2
2
|
import multiprocessing
|
3
3
|
import threading
|
4
|
-
import
|
5
|
-
from
|
6
|
-
from typing import
|
4
|
+
from datetime import datetime as _datetime
|
5
|
+
from enum import Enum
|
6
|
+
from typing import Optional, Union, TYPE_CHECKING
|
7
7
|
|
8
8
|
from wiederverwendbar.task_manger.task import Task
|
9
|
-
from wiederverwendbar.
|
9
|
+
from wiederverwendbar.timer import timer_loop
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from wiederverwendbar.task_manger.trigger import Trigger
|
13
|
+
|
14
|
+
|
15
|
+
class TaskManagerStates(str, Enum):
|
16
|
+
INITIAL = "INITIAL"
|
17
|
+
RUNNING = "RUNNING"
|
18
|
+
STOPPED = "STOPPED"
|
19
|
+
|
10
20
|
|
11
21
|
class TaskManager:
|
22
|
+
class States(str, Enum):
|
23
|
+
INITIAL = "INITIAL"
|
24
|
+
RUNNING = "RUNNING"
|
25
|
+
STOPPED = "STOPPED"
|
26
|
+
|
12
27
|
lock = threading.Lock()
|
13
28
|
|
14
29
|
def __init__(self,
|
15
30
|
name: Optional[str] = None,
|
16
31
|
worker_count: Optional[int] = None,
|
17
32
|
daemon: bool = False,
|
18
|
-
keep_done_tasks: bool = False,
|
19
33
|
loop_delay: Optional[float] = None,
|
20
|
-
logger: Optional[logging.Logger] = None
|
21
|
-
|
34
|
+
logger: Optional[logging.Logger] = None):
|
35
|
+
self._id = id(self)
|
22
36
|
if name is None:
|
23
37
|
name = self.__class__.__name__
|
24
|
-
self.
|
38
|
+
self._name = name
|
25
39
|
self._workers: list[threading.Thread] = []
|
26
40
|
self._tasks: list[Task] = []
|
27
|
-
self.
|
28
|
-
self._creation_time:
|
29
|
-
self.
|
30
|
-
self.logger = logger or logging.getLogger(self.
|
41
|
+
self._state: TaskManagerStates = TaskManagerStates.INITIAL
|
42
|
+
self._creation_time: _datetime = _datetime.now()
|
43
|
+
self._start_time: Optional[_datetime] = None
|
44
|
+
self.logger = logger or logging.getLogger(self._name)
|
31
45
|
|
32
46
|
# create workers
|
33
47
|
if worker_count is None:
|
34
48
|
worker_count = multiprocessing.cpu_count()
|
35
|
-
|
49
|
+
worker_count = round(worker_count / 2)
|
50
|
+
if worker_count < 1:
|
36
51
|
worker_count = 1
|
37
|
-
if worker_count > 4:
|
38
|
-
worker_count = 4
|
39
52
|
for i in range(worker_count):
|
40
|
-
worker = threading.Thread(name=f"{self.
|
53
|
+
worker = threading.Thread(name=f"{self._name}.Worker{i}", target=self.loop, daemon=daemon)
|
41
54
|
self._workers.append(worker)
|
42
55
|
|
43
56
|
# set loop delay
|
44
57
|
if loop_delay is None:
|
45
|
-
if self.worker_count
|
58
|
+
if self.worker_count >= 1:
|
46
59
|
loop_delay = 0.001
|
47
60
|
self._loop_delay = loop_delay
|
48
61
|
|
62
|
+
def __str__(self):
|
63
|
+
return f"{self.__class__.__name__}(name={self._name}, id={self._id}, state={self._state.value})"
|
64
|
+
|
49
65
|
def __del__(self):
|
50
|
-
if
|
66
|
+
if self.state == TaskManagerStates.RUNNING:
|
51
67
|
self.stop()
|
52
68
|
|
53
69
|
@property
|
54
|
-
def worker_count(self):
|
70
|
+
def worker_count(self) -> int:
|
55
71
|
"""
|
56
72
|
Number of workers.
|
57
73
|
|
@@ -61,19 +77,18 @@ class TaskManager:
|
|
61
77
|
return len(self._workers)
|
62
78
|
|
63
79
|
@property
|
64
|
-
def
|
80
|
+
def state(self) -> TaskManagerStates:
|
65
81
|
"""
|
66
|
-
Manager
|
82
|
+
Manager state
|
67
83
|
|
68
|
-
:return:
|
84
|
+
:return: TaskManagerStates
|
69
85
|
"""
|
70
86
|
|
71
87
|
with self.lock:
|
72
|
-
|
73
|
-
return stopped
|
88
|
+
return self._state
|
74
89
|
|
75
90
|
@property
|
76
|
-
def creation_time(self) ->
|
91
|
+
def creation_time(self) -> _datetime:
|
77
92
|
"""
|
78
93
|
Manager creation time.
|
79
94
|
|
@@ -81,8 +96,18 @@ class TaskManager:
|
|
81
96
|
"""
|
82
97
|
|
83
98
|
with self.lock:
|
84
|
-
|
85
|
-
|
99
|
+
return self._creation_time
|
100
|
+
|
101
|
+
@property
|
102
|
+
def start_time(self) -> Optional[_datetime]:
|
103
|
+
"""
|
104
|
+
Manager start time.
|
105
|
+
|
106
|
+
:return: datetime or None
|
107
|
+
"""
|
108
|
+
|
109
|
+
with self.lock:
|
110
|
+
return self._start_time
|
86
111
|
|
87
112
|
def start(self) -> None:
|
88
113
|
"""
|
@@ -91,14 +116,24 @@ class TaskManager:
|
|
91
116
|
:return: None
|
92
117
|
"""
|
93
118
|
|
94
|
-
self.
|
119
|
+
if self.state != TaskManagerStates.INITIAL:
|
120
|
+
raise ValueError(f"Manager '{self._name}' is not in state '{TaskManagerStates.INITIAL.value}'.")
|
121
|
+
|
122
|
+
self.logger.debug(f"Starting manager {self} ...")
|
123
|
+
|
124
|
+
with self.lock:
|
125
|
+
self._state = TaskManagerStates.RUNNING
|
95
126
|
|
96
127
|
# start workers
|
97
128
|
for worker in self._workers:
|
98
|
-
self.logger.debug(f"Starting worker '{worker.name}' ...")
|
129
|
+
self.logger.debug(f"{self} -> Starting worker '{worker.name}' ...")
|
99
130
|
worker.start()
|
100
131
|
|
101
|
-
|
132
|
+
# set the start time
|
133
|
+
with self.lock:
|
134
|
+
self._start_time = _datetime.now()
|
135
|
+
|
136
|
+
self.logger.debug(f"Manager {self} started.")
|
102
137
|
|
103
138
|
def stop(self) -> None:
|
104
139
|
"""
|
@@ -107,21 +142,24 @@ class TaskManager:
|
|
107
142
|
:return: None
|
108
143
|
"""
|
109
144
|
|
110
|
-
self.
|
145
|
+
if self.state != TaskManagerStates.RUNNING:
|
146
|
+
raise ValueError(f"Manager {self} is not in state '{TaskManagerStates.RUNNING.value}'.")
|
147
|
+
|
148
|
+
self.logger.debug(f"Stopping manager {self} ...")
|
111
149
|
|
112
150
|
# set stopped flag
|
113
151
|
with self.lock:
|
114
|
-
self.
|
152
|
+
self._state = TaskManagerStates.STOPPED
|
115
153
|
|
116
154
|
# wait for workers to finish
|
117
155
|
for worker in self._workers:
|
118
156
|
if worker.is_alive():
|
119
|
-
self.logger.debug(f"Waiting for worker '{worker.name}' to finish ...")
|
157
|
+
self.logger.debug(f"{self} -> Waiting for worker '{worker.name}' to finish ...")
|
120
158
|
worker.join()
|
121
159
|
|
122
|
-
self.logger.debug(f"Manager stopped.")
|
160
|
+
self.logger.debug(f"Manager {self} stopped.")
|
123
161
|
|
124
|
-
def loop(self, stay_in_loop:
|
162
|
+
def loop(self, stay_in_loop: bool = True) -> None:
|
125
163
|
"""
|
126
164
|
Manager loop. All workers run this loop. If worker_count is 0, you can run this loop manually.
|
127
165
|
|
@@ -129,54 +167,48 @@ class TaskManager:
|
|
129
167
|
:return: None
|
130
168
|
"""
|
131
169
|
|
132
|
-
if
|
133
|
-
|
134
|
-
stay_in_loop = bool(self._loop_delay)
|
135
|
-
while not self.stopped:
|
136
|
-
now = datetime.now()
|
137
|
-
current_task = None
|
138
|
-
with self.lock:
|
139
|
-
for i, task in enumerate(self._tasks):
|
140
|
-
if task.next_run is None:
|
141
|
-
continue
|
142
|
-
if task.next_run > now:
|
143
|
-
continue
|
144
|
-
current_task = self._tasks.pop(i)
|
145
|
-
break
|
146
|
-
if current_task is None:
|
147
|
-
with self.lock:
|
148
|
-
loop_delay = self._loop_delay
|
149
|
-
if loop_delay:
|
150
|
-
time.sleep(loop_delay)
|
151
|
-
continue
|
170
|
+
if self.state != TaskManagerStates.RUNNING:
|
171
|
+
raise ValueError(f"Manager {self} is not in state '{TaskManagerStates.RUNNING.value}'.")
|
152
172
|
|
153
|
-
|
173
|
+
# check if running in a worker thread
|
174
|
+
with self.lock:
|
175
|
+
if threading.current_thread() not in self._workers:
|
176
|
+
if len(self._workers) > 0:
|
177
|
+
raise ValueError(f"{self} -> Running manager loop outside of worker thread is not allowed, if worker_count > 0.")
|
154
178
|
|
179
|
+
while self.state == TaskManagerStates.RUNNING:
|
180
|
+
# get the next task from the list
|
181
|
+
task = None
|
155
182
|
with self.lock:
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
current_task.payload()
|
162
|
-
|
163
|
-
self.logger.debug(f"Task '{current_task.name}' successfully run.")
|
183
|
+
for i, _task in enumerate(self._tasks):
|
184
|
+
if not _task():
|
185
|
+
continue
|
186
|
+
task = self._tasks.pop(i)
|
187
|
+
break
|
164
188
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
self.logger.debug(f"Task
|
173
|
-
|
174
|
-
|
189
|
+
# if a task to run available, run the task
|
190
|
+
if task is not None:
|
191
|
+
self.logger.debug(f"{self} -> Running task {task} ...")
|
192
|
+
if task.time_measurement == Task.TimeMeasurement.START:
|
193
|
+
task._last_run = _datetime.now()
|
194
|
+
try:
|
195
|
+
task.payload()
|
196
|
+
self.logger.debug(f"{self} -> Task {task} successfully run.")
|
197
|
+
except Exception as e:
|
198
|
+
self.logger.error(f"{self} -> Task {task} failed: {e}")
|
199
|
+
if task.time_measurement == Task.TimeMeasurement.END:
|
200
|
+
task._last_run = _datetime.now()
|
201
|
+
|
202
|
+
# put the task back to list
|
203
|
+
with self.lock:
|
204
|
+
self._tasks.append(task)
|
175
205
|
|
176
206
|
if not stay_in_loop:
|
177
207
|
break
|
208
|
+
if self._loop_delay:
|
209
|
+
timer_loop(name=f"{self._name}_{self._id}_LOOP", seconds=self._loop_delay, loop_delay=self._loop_delay)
|
178
210
|
|
179
|
-
def add_task(self, task: Task):
|
211
|
+
def add_task(self, task: Task) -> None:
|
180
212
|
"""
|
181
213
|
Add task to manager.
|
182
214
|
|
@@ -184,12 +216,9 @@ class TaskManager:
|
|
184
216
|
:return:
|
185
217
|
"""
|
186
218
|
|
187
|
-
task.
|
188
|
-
with self.lock:
|
189
|
-
self._tasks.append(task)
|
190
|
-
self.logger.debug(f"Task '{task.name}' added.")
|
219
|
+
task.manager = self
|
191
220
|
|
192
|
-
def remove_task(self, task: Task):
|
221
|
+
def remove_task(self, task: Task) -> None:
|
193
222
|
"""
|
194
223
|
Remove task from manager.
|
195
224
|
|
@@ -197,40 +226,34 @@ class TaskManager:
|
|
197
226
|
:return:
|
198
227
|
"""
|
199
228
|
|
200
|
-
|
201
|
-
self.
|
202
|
-
|
229
|
+
if task.manager is not self:
|
230
|
+
raise ValueError(f"Task {task} is not assigned to manager {self}.")
|
231
|
+
task.manager = None
|
203
232
|
|
204
233
|
def task(self,
|
234
|
+
*triggers: "Trigger",
|
205
235
|
name: Optional[str] = None,
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
*args,
|
210
|
-
**kwargs) -> Any:
|
236
|
+
time_measurement: Optional[Task.TimeMeasurement] = None,
|
237
|
+
task_args: Optional[Union[list, tuple]] = None,
|
238
|
+
task_kwargs: Optional[dict] = None):
|
211
239
|
"""
|
212
240
|
Task decorator.
|
213
241
|
|
242
|
+
:param triggers: The trigger of the task.
|
214
243
|
:param name: The name of the task.
|
215
|
-
:param
|
216
|
-
:param
|
217
|
-
:param
|
218
|
-
:param args: Args for the task payload.
|
219
|
-
:param kwargs: Kwargs for the task payload.
|
244
|
+
:param time_measurement: Time measurement for the task.
|
245
|
+
:param task_args: Args for the task payload.
|
246
|
+
:param task_kwargs: Kwargs for the task payload.
|
220
247
|
:return: Task or function
|
221
248
|
"""
|
222
249
|
|
223
250
|
def decorator(func):
|
224
|
-
task
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
**kwargs)
|
232
|
-
return task if not return_func else func
|
251
|
+
self.add_task(task=Task(func,
|
252
|
+
*triggers,
|
253
|
+
name=name,
|
254
|
+
time_measurement=time_measurement,
|
255
|
+
task_args=task_args,
|
256
|
+
task_kwargs=task_kwargs))
|
257
|
+
return func
|
233
258
|
|
234
259
|
return decorator
|
235
|
-
|
236
|
-
|