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.
@@ -1,57 +1,73 @@
1
1
  import logging
2
2
  import multiprocessing
3
3
  import threading
4
- import time
5
- from datetime import datetime
6
- from typing import Any, Optional
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.task_manger.trigger import Trigger
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
- log_self: bool = True):
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.name = name
38
+ self._name = name
25
39
  self._workers: list[threading.Thread] = []
26
40
  self._tasks: list[Task] = []
27
- self._stopped: bool = False
28
- self._creation_time: datetime = datetime.now()
29
- self._keep_done_tasks = keep_done_tasks
30
- self.logger = logger or logging.getLogger(self.name)
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
- if worker_count - 2 < 1:
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.name}.Worker{i}", target=self.loop, daemon=daemon)
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 > 1:
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 not self.stopped:
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 stopped(self):
80
+ def state(self) -> TaskManagerStates:
65
81
  """
66
- Manager stopped flag.
82
+ Manager state
67
83
 
68
- :return: bool
84
+ :return: TaskManagerStates
69
85
  """
70
86
 
71
87
  with self.lock:
72
- stopped = self._stopped
73
- return stopped
88
+ return self._state
74
89
 
75
90
  @property
76
- def creation_time(self) -> datetime:
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
- creation_time = self._creation_time
85
- return creation_time
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.logger.debug(f"Starting manager ...")
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
- self.logger.debug(f"Manager started.")
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.logger.debug(f"Stopping manager ...")
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._stopped = True
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: Optional[bool] = None) -> None:
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 stay_in_loop is None:
133
- with self.lock:
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
- self.logger.debug(f"Running task '{current_task.name}' ...")
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
- if current_task.time_measurement_before_run:
157
- current_task.set_last_run()
158
- current_task.set_next_run()
159
-
160
- # run task
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
- with self.lock:
166
- if not current_task.time_measurement_before_run:
167
- current_task.set_last_run()
168
- current_task.set_next_run()
169
- if not current_task.is_done:
170
- self._tasks.append(current_task)
171
- else:
172
- self.logger.debug(f"Task '{current_task.name}' is done.")
173
- if self._keep_done_tasks:
174
- self._tasks.append(current_task)
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.init(self)
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
- with self.lock:
201
- self._tasks.remove(task)
202
- self.logger.debug(f"Task '{task.name}' removed.")
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
- trigger: Optional[Trigger] = None,
207
- time_measurement_before_run: bool = True,
208
- return_func: bool = True,
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 trigger: The trigger of the task.
216
- :param time_measurement_before_run: Time measurement before run flag.
217
- :param return_func: Return function flag. If True, the function will be returned instead of the task.
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 = Task(name=name,
225
- manager=self,
226
- trigger=trigger,
227
- time_measurement_before_run=time_measurement_before_run,
228
- payload=func,
229
- auto_add=True,
230
- *args,
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
-