wiederverwendbar 0.9.1__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 +8 -7
- 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-0.9.2.dist-info/METADATA +726 -0
- {wiederverwendbar-0.9.1.dist-info → wiederverwendbar-0.9.2.dist-info}/RECORD +13 -11
- wiederverwendbar-0.9.2.dist-info/licenses/LICENSE +674 -0
- wiederverwendbar-0.9.1.dist-info/METADATA +0 -52
- {wiederverwendbar-0.9.1.dist-info → wiederverwendbar-0.9.2.dist-info}/WHEEL +0 -0
- {wiederverwendbar-0.9.1.dist-info → wiederverwendbar-0.9.2.dist-info}/entry_points.txt +0 -0
wiederverwendbar/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
__title__ = "wiederverwendbar"
|
2
2
|
__description__ = "A collection of scripts, classes and tools they are \\\"wiederverwendbar\\\"."
|
3
|
-
__version__ = "0.9.
|
3
|
+
__version__ = "0.9.2"
|
4
4
|
__author__ = "Julius Koenig"
|
5
5
|
__author_email__ = "info@bastelquartier.de"
|
6
6
|
__license__ = "GPL-3.0"
|
@@ -23,13 +23,14 @@ class BrandingSettings(BaseModel):
|
|
23
23
|
module_data = self.get_attributes(main_module.__dict__)
|
24
24
|
if module_data is None:
|
25
25
|
# ToDO: this code is not working for pyprojects.toml [project.scripts]
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
if hasattr(main_module, "__file__"):
|
27
|
+
init_file = Path(main_module.__file__).parent / "__init__.py"
|
28
|
+
if init_file.is_file():
|
29
|
+
init_file_module_spec = importlib.util.spec_from_file_location("__main__.__init__", init_file)
|
30
|
+
init_file_module = importlib.util.module_from_spec(init_file_module_spec)
|
31
|
+
sys.modules["__main__.__init__"] = init_file_module
|
32
|
+
init_file_module_spec.loader.exec_module(init_file_module)
|
33
|
+
module_data = self.get_attributes(init_file_module.__dict__)
|
33
34
|
if module_data is None:
|
34
35
|
module_data = {}
|
35
36
|
|
@@ -172,13 +172,13 @@ class SqlalchemyDb:
|
|
172
172
|
else:
|
173
173
|
connection_string += self.password
|
174
174
|
if self.host is None:
|
175
|
-
raise RuntimeError(f"No host specified for {self}")
|
175
|
+
raise RuntimeError(f"No host specified for {self.__class__.__name__}")
|
176
176
|
connection_string += f"@{self.host}"
|
177
177
|
if self.port is None:
|
178
|
-
raise RuntimeError(f"No port specified for {self}")
|
178
|
+
raise RuntimeError(f"No port specified for {self.__class__.__name__}")
|
179
179
|
connection_string += f":{self.port}"
|
180
180
|
if self.name is None:
|
181
|
-
raise RuntimeError(f"No name specified for {self}")
|
181
|
+
raise RuntimeError(f"No name specified for {self.__class__.__name__}")
|
182
182
|
connection_string += f"/{self.name}"
|
183
183
|
return connection_string
|
184
184
|
|
@@ -1,4 +1,17 @@
|
|
1
|
-
from wiederverwendbar.task_manger.task_manager import TaskManager
|
2
|
-
from wiederverwendbar.task_manger.singleton import ManagerSingleton
|
3
|
-
from wiederverwendbar.task_manger.task import Task
|
4
|
-
from wiederverwendbar.task_manger.trigger import
|
1
|
+
from wiederverwendbar.task_manger.task_manager import (TaskManager)
|
2
|
+
from wiederverwendbar.task_manger.singleton import (ManagerSingleton)
|
3
|
+
from wiederverwendbar.task_manger.task import (Task)
|
4
|
+
from wiederverwendbar.task_manger.trigger import (Trigger,
|
5
|
+
Interval,
|
6
|
+
EverySeconds,
|
7
|
+
EveryMinutes,
|
8
|
+
EveryHours,
|
9
|
+
EveryDays,
|
10
|
+
EveryWeeks,
|
11
|
+
EveryMonths,
|
12
|
+
EveryYears,
|
13
|
+
At,
|
14
|
+
AtDatetime,
|
15
|
+
AtNow,
|
16
|
+
AtManagerCreation,
|
17
|
+
AtManagerStart)
|
@@ -1,104 +1,147 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from
|
1
|
+
from datetime import datetime as _datetime
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Any, Optional, Callable, Union, TYPE_CHECKING
|
3
4
|
|
4
|
-
from wiederverwendbar.
|
5
|
+
from wiederverwendbar.functions.is_coroutine_function import is_coroutine_function
|
6
|
+
from wiederverwendbar.task_manger.trigger import Trigger
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from wiederverwendbar.task_manger.task_manager import TaskManager
|
5
10
|
|
6
11
|
|
7
12
|
class Task:
|
13
|
+
class TimeMeasurement(str, Enum):
|
14
|
+
START = "START"
|
15
|
+
END = "END"
|
16
|
+
|
8
17
|
def __init__(self,
|
9
|
-
payload,
|
10
|
-
|
18
|
+
payload: Callable[..., None],
|
19
|
+
*triggers: Trigger,
|
11
20
|
name: Optional[str] = None,
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# set task name
|
21
|
+
time_measurement: Optional[TimeMeasurement] = None,
|
22
|
+
task_args: Optional[Union[list, tuple]] = None,
|
23
|
+
task_kwargs: Optional[dict] = None):
|
24
|
+
self._manager = None
|
25
|
+
|
26
|
+
# set the task name
|
18
27
|
if name is None:
|
19
28
|
name = payload.__name__
|
20
|
-
self.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
self._name = name
|
30
|
+
|
31
|
+
# set task triggers
|
32
|
+
self._triggers = []
|
33
|
+
for trigger in triggers:
|
34
|
+
if not isinstance(trigger, Trigger):
|
35
|
+
raise ValueError("Trigger must be an instance of Trigger.")
|
36
|
+
if trigger.task is not None:
|
37
|
+
raise ValueError("Trigger already assigned to a task.")
|
38
|
+
trigger._task = self
|
39
|
+
self._triggers.append(trigger)
|
40
|
+
self._triggers = tuple(self._triggers)
|
28
41
|
|
29
42
|
# set task payload
|
30
43
|
if not callable(payload):
|
31
44
|
raise ValueError("Payload must be callable.")
|
45
|
+
if is_coroutine_function(payload):
|
46
|
+
raise ValueError("Coroutine functions are not supported.")
|
32
47
|
self._payload = payload
|
33
48
|
|
49
|
+
# indicates when the last run time should be measured
|
50
|
+
if time_measurement is None:
|
51
|
+
time_measurement = self.TimeMeasurement.START
|
52
|
+
if not isinstance(time_measurement, self.TimeMeasurement):
|
53
|
+
raise ValueError("Time measurement must be an instance of TaskTimeMeasurement.")
|
54
|
+
self._time_measurement = time_measurement
|
55
|
+
|
34
56
|
# set payload args and kwargs
|
35
|
-
|
36
|
-
|
37
|
-
if not iter(
|
38
|
-
raise ValueError("
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
self._last_run
|
48
|
-
|
49
|
-
|
50
|
-
# indicate if task is done
|
57
|
+
if task_args is None:
|
58
|
+
task_args = []
|
59
|
+
if not iter(task_args):
|
60
|
+
raise ValueError("Task args must be iterable.")
|
61
|
+
self._task_args = tuple(task_args)
|
62
|
+
if task_kwargs is None:
|
63
|
+
task_kwargs = {}
|
64
|
+
if not isinstance(task_kwargs, dict):
|
65
|
+
raise ValueError("Task kwargs must be dict.")
|
66
|
+
self._task_kwargs = task_kwargs
|
67
|
+
|
68
|
+
# indicate last run
|
69
|
+
self._last_run = None
|
70
|
+
|
71
|
+
# indicate if the task is done
|
51
72
|
self._done = False
|
52
73
|
|
53
|
-
|
54
|
-
|
55
|
-
|
74
|
+
def __str__(self):
|
75
|
+
return (f"{self.__class__.__name__}("
|
76
|
+
f"name={self.name}, "
|
77
|
+
f"trigger=[{', '.join([str(trigger) for trigger in self._triggers])}], "
|
78
|
+
f"last_run={self.last_run})")
|
56
79
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
80
|
+
def __call__(self, *args, **kwargs) -> bool:
|
81
|
+
if self.manager is None:
|
82
|
+
raise ValueError(f"Task {self} is not assigned to a manager.")
|
83
|
+
for trigger in self.triggers:
|
84
|
+
if trigger():
|
85
|
+
return True
|
86
|
+
return False
|
61
87
|
|
62
|
-
|
63
|
-
|
64
|
-
self.
|
65
|
-
|
88
|
+
@property
|
89
|
+
def manager(self) -> Optional["TaskManager"]:
|
90
|
+
return self._manager
|
91
|
+
|
92
|
+
@manager.setter
|
93
|
+
def manager(self, manager: "TaskManager") -> None:
|
94
|
+
if manager is None:
|
95
|
+
if self._manager is None:
|
96
|
+
return
|
97
|
+
manager = self._manager
|
98
|
+
with manager.lock:
|
99
|
+
# noinspection PyProtectedMember
|
100
|
+
manager._tasks.remove(self)
|
101
|
+
self.manager_removed()
|
102
|
+
else:
|
103
|
+
if self._manager is not None:
|
104
|
+
raise ValueError(f"Task {self} is already assigned to manager {self._manager}.")
|
105
|
+
self._manager = manager
|
66
106
|
|
67
|
-
|
68
|
-
|
107
|
+
with manager.lock:
|
108
|
+
# noinspection PyProtectedMember
|
109
|
+
manager._tasks.append(self)
|
110
|
+
self.manager_added()
|
69
111
|
|
70
112
|
@property
|
71
|
-
def
|
72
|
-
|
73
|
-
return datetime.fromtimestamp(0)
|
74
|
-
return self._last_run
|
113
|
+
def name(self) -> str:
|
114
|
+
return self._name
|
75
115
|
|
76
116
|
@property
|
77
|
-
def
|
78
|
-
|
79
|
-
return None
|
80
|
-
return self._next_run
|
117
|
+
def triggers(self) -> tuple[Trigger, ...]:
|
118
|
+
return self._triggers
|
81
119
|
|
82
120
|
@property
|
83
|
-
def
|
84
|
-
return self.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
121
|
+
def time_measurement(self) -> TimeMeasurement:
|
122
|
+
return self._time_measurement
|
123
|
+
|
124
|
+
@property
|
125
|
+
def task_args(self) -> tuple[Any]:
|
126
|
+
return self._task_args
|
127
|
+
|
128
|
+
@property
|
129
|
+
def task_kwargs(self) -> dict[str, Any]:
|
130
|
+
return self._task_kwargs
|
131
|
+
|
132
|
+
@property
|
133
|
+
def last_run(self) -> Optional[_datetime]:
|
134
|
+
return self._last_run
|
96
135
|
|
97
|
-
def
|
98
|
-
|
136
|
+
def manager_added(self) -> None:
|
137
|
+
for trigger in self.triggers:
|
138
|
+
trigger.manager_added()
|
139
|
+
self.manager.logger.debug(f"{self.manager} -> Task {self} added.")
|
99
140
|
|
100
|
-
def
|
101
|
-
self.
|
141
|
+
def manager_removed(self) -> None:
|
142
|
+
for trigger in self.triggers:
|
143
|
+
trigger.manager_removed()
|
144
|
+
self.manager.logger.debug(f"{self.manager} -> Task {self} removed.")
|
102
145
|
|
103
|
-
def payload(self):
|
104
|
-
self._payload(*self.
|
146
|
+
def payload(self) -> None:
|
147
|
+
self._payload(*self.task_args, **self.task_kwargs)
|
@@ -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
|
-
|