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,24 +1,43 @@
1
+ import threading
1
2
  from abc import ABC, abstractmethod
2
- from datetime import datetime, timedelta
3
- from typing import Optional
3
+ from datetime import datetime as _datetime, timedelta
4
+ from typing import Optional, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from wiederverwendbar.task_manger.task import Task
4
8
 
5
9
 
6
10
  class Trigger(ABC):
7
11
  def __init__(self):
8
- self.manager = None
12
+ self.lock = threading.Lock()
13
+ self._task = None
9
14
 
10
- @abstractmethod
11
- def init(self, manager):
15
+ def __str__(self):
16
+ return f"{self.__class__.__name__}()"
17
+
18
+ def __call__(self):
19
+ if self.task is None:
20
+ raise ValueError(f"Trigger {self} is not assigned to a task.")
21
+ return self.check()
22
+
23
+ def manager_added(self) -> None:
12
24
  ...
13
25
 
26
+ def manager_removed(self) -> None:
27
+ ...
28
+
29
+ @property
30
+ def task(self) -> Optional["Task"]:
31
+ return self._task
32
+
14
33
  @abstractmethod
15
- def next(self, *args, **kwargs) -> datetime:
34
+ def check(self) -> bool:
16
35
  ...
17
36
 
18
37
 
19
38
  class Interval(Trigger):
20
39
  def __init__(self,
21
- seconds: int = 0,
40
+ seconds: float = 0,
22
41
  minutes: int = 0,
23
42
  hours: int = 0,
24
43
  days: int = 0,
@@ -27,30 +46,18 @@ class Interval(Trigger):
27
46
  years: int = 0):
28
47
  super().__init__()
29
48
 
30
- self.interval: Optional[int] = None
31
- self.seconds: int = seconds
32
- self.minutes: int = minutes
33
- self.hours: int = hours
34
- self.days: int = days
35
- self.weeks: int = weeks
36
- self.months: int = months
37
- self.years: int = years
38
-
39
- def init(self, manager):
40
- self.manager = manager
41
-
42
- # calculate interval in seconds
43
- self.interval = self.seconds
44
- self.interval += self.minutes * 60
45
- self.interval += self.hours * 60 * 60
46
- self.interval += self.days * 60 * 60 * 24
47
- self.interval += self.weeks * 60 * 60 * 24 * 7
48
- self.interval += self.months * 60 * 60 * 24 * 30
49
- self.interval += self.years * 60 * 60 * 24 * 365
49
+ # calculate the interval in seconds
50
+ self._interval = float(seconds)
51
+ self._interval += minutes * 60
52
+ self._interval += hours * 60 * 60
53
+ self._interval += days * 60 * 60 * 24
54
+ self._interval += weeks * 60 * 60 * 24 * 7
55
+ self._interval += months * 60 * 60 * 24 * 30
56
+ self._interval += years * 60 * 60 * 24 * 365
50
57
 
51
58
  def __str__(self):
52
59
  interval = self.interval
53
- interval_str = "Every "
60
+ interval_str = f"{self.__class__.__name__}(Every "
54
61
  if interval >= 60 * 60 * 24 * 365:
55
62
  years = interval // (60 * 60 * 24 * 365)
56
63
  interval_str += f"{years}y "
@@ -77,24 +84,40 @@ class Interval(Trigger):
77
84
  interval -= minutes * 60
78
85
  if interval > 0:
79
86
  interval_str += f"{interval}s "
80
- interval_str = interval_str.strip()
87
+ interval_str = interval_str.strip() + ")"
81
88
  return interval_str
82
89
 
83
90
  def __int__(self):
91
+ return round(self.interval)
92
+
93
+ def __float__(self):
84
94
  return self.interval
85
95
 
86
96
  def __add__(self, other):
87
97
  if isinstance(other, (Interval, int, float)):
88
- return Interval(seconds=int(self) + int(other))
89
- else:
90
- raise ValueError(f"Can't add Interval and '{type(other)}'.")
98
+ return Interval(seconds=float(self) + float(other))
99
+ raise ValueError("Only Interval, int and float are supported.")
91
100
 
92
- def next(self, last: datetime) -> datetime:
93
- return last + timedelta(seconds=int(self))
101
+ @property
102
+ def interval(self) -> float:
103
+ with self.lock:
104
+ return self._interval
105
+
106
+ @interval.setter
107
+ def interval(self, value: float):
108
+ with self.lock:
109
+ self._interval = value
110
+
111
+ def check(self) -> bool:
112
+ if self.task.last_run is None:
113
+ return True
114
+ if _datetime.now() - self.task.last_run > timedelta(seconds=self.interval):
115
+ return True
116
+ return False
94
117
 
95
118
 
96
119
  class EverySeconds(Interval):
97
- def __init__(self, seconds: int):
120
+ def __init__(self, seconds: float):
98
121
  super().__init__(seconds=seconds)
99
122
 
100
123
 
@@ -135,28 +158,80 @@ class At(Trigger):
135
158
  hour: Optional[int] = None,
136
159
  day: Optional[int] = None,
137
160
  month: Optional[int] = None,
138
- year: Optional[int] = None,
139
- delay_for_seconds: int = 0,
140
- delay_for_minutes: int = 0,
141
- delay_for_hours: int = 0,
142
- delay_for_days: int = 0):
161
+ year: Optional[int] = None):
143
162
  super().__init__()
144
163
 
145
- self.second: Optional[int] = second
146
- self.minute: Optional[int] = minute
147
- self.hour: Optional[int] = hour
148
- self.day: Optional[int] = day
149
- self.month: Optional[int] = month
150
- self.year: Optional[int] = year
151
- self.delay_for_seconds: int = delay_for_seconds
152
- self.delay_for_minutes: int = delay_for_minutes
153
- self.delay_for_hours: int = delay_for_hours
154
- self.delay_for_days: int = delay_for_days
155
-
156
- def init(self, manager):
157
- self.manager = manager
158
-
159
- # check if some of the values are set
164
+ self._second: Optional[int] = second
165
+ self._minute: Optional[int] = minute
166
+ self._hour: Optional[int] = hour
167
+ self._day: Optional[int] = day
168
+ self._month: Optional[int] = month
169
+ self._year: Optional[int] = year
170
+
171
+ @property
172
+ def second(self) -> Optional[int]:
173
+ with self.lock:
174
+ return self._second
175
+
176
+ @second.setter
177
+ def second(self, value: Optional[int]):
178
+ with self.lock:
179
+ self._second = value
180
+
181
+ @property
182
+ def minute(self) -> Optional[int]:
183
+ with self.lock:
184
+ return self._minute
185
+
186
+ @minute.setter
187
+ def minute(self, value: Optional[int]):
188
+ with self.lock:
189
+ self._minute = value
190
+
191
+ @property
192
+ def hour(self) -> Optional[int]:
193
+ with self.lock:
194
+ return self._hour
195
+
196
+ @hour.setter
197
+ def hour(self, value: Optional[int]):
198
+ with self.lock:
199
+ self._hour = value
200
+
201
+ @property
202
+ def day(self) -> Optional[int]:
203
+ with self.lock:
204
+ return self._day
205
+
206
+ @day.setter
207
+ def day(self, value: Optional[int]):
208
+ with self.lock:
209
+ self._day = value
210
+
211
+ @property
212
+ def month(self) -> Optional[int]:
213
+ with self.lock:
214
+ return self._month
215
+
216
+ @month.setter
217
+ def month(self, value: Optional[int]):
218
+ with self.lock:
219
+ self._month = value
220
+
221
+ @property
222
+ def year(self) -> Optional[int]:
223
+ with self.lock:
224
+ return self._year
225
+
226
+ @year.setter
227
+ def year(self, value: Optional[int]):
228
+ with self.lock:
229
+ self._year = value
230
+
231
+ def manager_added(self) -> None:
232
+ super().manager_added()
233
+
234
+ # check if some values are set
160
235
  if not any([self.second is not None, self.minute is not None, self.hour is not None, self.day is not None, self.month is not None, self.year is not None]):
161
236
  raise ValueError("At least one of the values must be set.")
162
237
 
@@ -179,108 +254,215 @@ class At(Trigger):
179
254
  if self.year is not None:
180
255
  at_str += f"year {self.year:04} "
181
256
  if self.month is not None:
182
- if at_str:
257
+ if at_str != "At ":
183
258
  at_str += "and "
184
259
  at_str += f"month {self.month:02} "
185
260
  if self.day is not None:
186
- if at_str:
261
+ if at_str != "At ":
187
262
  at_str += "and "
188
263
  at_str += f"day {self.day:02} "
189
264
  if self.hour is not None:
190
- if at_str:
265
+ if at_str != "At ":
191
266
  at_str += "and "
192
267
  at_str += f"hour {self.hour:02} "
193
268
  if self.minute is not None:
194
- if at_str:
269
+ if at_str != "At ":
195
270
  at_str += "and "
196
271
  at_str += f"minute {self.minute:02} "
197
272
  if self.second is not None:
198
- if at_str:
273
+ if at_str != "At ":
199
274
  at_str += "and "
200
275
  at_str += f"second {self.second:02} "
201
276
  at_str = at_str.strip()
202
- return at_str
277
+ return f"{self.__class__.__name__}({at_str})"
278
+
279
+ def check(self) -> bool:
280
+ next_run = _datetime.now().replace(microsecond=0)
203
281
 
204
- def next(self) -> datetime:
205
- n = datetime.now().replace(microsecond=0)
206
282
  second = 0
207
283
  if self.second is not None:
208
284
  second = self.second
209
- second_n = n.replace(second=second)
210
- if second_n < n:
211
- n += timedelta(minutes=1)
212
- n = n.replace(second=second)
285
+ second_n = next_run.replace(second=second)
286
+ if second_n < next_run:
287
+ next_run += timedelta(minutes=1)
288
+ if self.task.last_run is not None:
289
+ if second_n < self.task.last_run:
290
+ next_run += timedelta(minutes=1)
291
+ next_run = next_run.replace(second=second)
292
+
213
293
  minute = 0
214
294
  if self.minute is not None:
215
295
  minute = self.minute
216
- minute_n = n.replace(minute=minute)
217
- if minute_n < n:
218
- n += timedelta(hours=1)
219
- n = n.replace(minute=minute, second=second)
296
+ minute_n = next_run.replace(minute=minute)
297
+ if minute_n < next_run:
298
+ next_run += timedelta(hours=1)
299
+ if self.task.last_run is not None:
300
+ if minute_n < self.task.last_run:
301
+ next_run += timedelta(hours=1)
302
+ next_run = next_run.replace(minute=minute, second=second)
303
+
220
304
  hour = 0
221
305
  if self.hour is not None:
222
306
  hour = self.hour
223
- hour_n = n.replace(hour=hour)
224
- if hour_n < n:
225
- n += timedelta(days=1)
226
- n = n.replace(hour=hour, minute=minute, second=second)
307
+ hour_n = next_run.replace(hour=hour)
308
+ if hour_n < next_run:
309
+ next_run += timedelta(days=1)
310
+ if self.task.last_run is not None:
311
+ if hour_n < self.task.last_run:
312
+ next_run += timedelta(days=1)
313
+ next_run = next_run.replace(hour=hour, minute=minute, second=second)
314
+
227
315
  day = 1
228
316
  if self.day is not None:
229
317
  day = self.day
230
- day_n = n.replace(day=day)
231
- if day_n < n:
318
+ day_n = next_run.replace(day=day)
319
+ if day_n < next_run:
232
320
  while True:
233
- n += timedelta(days=1)
234
- if n.day == day:
321
+ next_run += timedelta(days=1)
322
+ if next_run.day == day:
235
323
  break
236
- n = n.replace(day=day, hour=hour, minute=minute, second=second)
324
+ if self.task.last_run is not None:
325
+ if day_n < self.task.last_run:
326
+ while True:
327
+ next_run += timedelta(days=1)
328
+ if next_run.day == day:
329
+ break
330
+ next_run = next_run.replace(day=day, hour=hour, minute=minute, second=second)
331
+
237
332
  month = 1
238
333
  if self.month is not None:
239
334
  month = self.month
240
- month_n = n.replace(month=month)
241
- if month_n < n:
335
+ month_n = next_run.replace(month=month)
336
+ if month_n < next_run:
242
337
  while True:
243
- n += timedelta(days=1)
244
- if n.month == month:
338
+ next_run += timedelta(days=1)
339
+ if next_run.month == month:
245
340
  break
246
- n = n.replace(month=month, day=day, hour=hour, minute=minute, second=second)
341
+ if self.task.last_run is not None:
342
+ if month_n < self.task.last_run:
343
+ while True:
344
+ next_run += timedelta(days=1)
345
+ if next_run.month == month:
346
+ break
347
+ next_run = next_run.replace(month=month, day=day, hour=hour, minute=minute, second=second)
348
+
247
349
  if self.year is not None:
248
- n = n.replace(year=self.year, month=month, day=day, hour=hour, minute=minute, second=second)
350
+ next_run = next_run.replace(year=self.year, month=month, day=day, hour=hour, minute=minute, second=second)
249
351
 
250
- return n
352
+ if next_run <= _datetime.now():
353
+ return True
354
+ return False
251
355
 
252
356
 
253
- class AtNow(At):
254
- def init(self, manager):
255
- self.manager = manager
357
+ class AtDatetime(Trigger):
358
+ def __init__(self,
359
+ datetime: Optional[_datetime] = None,
360
+ delay_for_seconds: int = 0,
361
+ delay_for_minutes: int = 0,
362
+ delay_for_hours: int = 0,
363
+ delay_for_days: int = 0):
364
+ super().__init__()
365
+
366
+ self._datetime = datetime
367
+ self._delay_for_seconds = delay_for_seconds
368
+ self._delay_for_minutes = delay_for_minutes
369
+ self._delay_for_hours = delay_for_hours
370
+ self._delay_for_days = delay_for_days
371
+
372
+ @property
373
+ def datetime(self) -> Optional[_datetime]:
374
+ with self.lock:
375
+ return self._datetime
376
+
377
+ @datetime.setter
378
+ def datetime(self, value: Optional[_datetime]):
379
+ with self.lock:
380
+ self._datetime = value
381
+
382
+ @property
383
+ def delay_for_seconds(self) -> int:
384
+ with self.lock:
385
+ return self._delay_for_seconds
386
+
387
+ @delay_for_seconds.setter
388
+ def delay_for_seconds(self, value: int):
389
+ with self.lock:
390
+ self._delay_for_seconds = value
391
+
392
+ @property
393
+ def delay_for_minutes(self) -> int:
394
+ with self.lock:
395
+ return self._delay_for_minutes
396
+
397
+ @delay_for_minutes.setter
398
+ def delay_for_minutes(self, value: int):
399
+ with self.lock:
400
+ self._delay_for_minutes = value
401
+
402
+ @property
403
+ def delay_for_hours(self) -> int:
404
+ with self.lock:
405
+ return self._delay_for_hours
406
+
407
+ @delay_for_hours.setter
408
+ def delay_for_hours(self, value: int):
409
+ with self.lock:
410
+ self._delay_for_hours = value
411
+
412
+ @property
413
+ def delay_for_days(self) -> int:
414
+ with self.lock:
415
+ return self._delay_for_days
416
+
417
+ @delay_for_days.setter
418
+ def delay_for_days(self, value: int):
419
+ with self.lock:
420
+ self._delay_for_days = value
421
+
422
+ def manager_added(self) -> None:
423
+ super().manager_added()
424
+
425
+ if self.datetime is None:
426
+ raise ValueError("Datetime must be set.")
427
+ if not isinstance(self.datetime, _datetime):
428
+ raise ValueError("Datetime must be an instance of datetime.")
429
+
430
+ self.datetime = self.datetime + timedelta(seconds=self.delay_for_seconds,
431
+ minutes=self.delay_for_minutes,
432
+ hours=self.delay_for_hours,
433
+ days=self.delay_for_days)
434
+
435
+ def check(self) -> bool:
436
+ if self.task.last_run is None:
437
+ return True
438
+ return False
439
+
440
+
441
+ class _AtPredefinedDatetime(AtDatetime):
442
+ def __init__(self,
443
+ delay_for_seconds: int = 0,
444
+ delay_for_minutes: int = 0,
445
+ delay_for_hours: int = 0,
446
+ delay_for_days: int = 0):
447
+ super().__init__(delay_for_seconds=delay_for_seconds,
448
+ delay_for_minutes=delay_for_minutes,
449
+ delay_for_hours=delay_for_hours,
450
+ delay_for_days=delay_for_days)
256
451
 
257
- now = datetime.now() + timedelta(seconds=self.delay_for_seconds,
258
- minutes=self.delay_for_minutes,
259
- hours=self.delay_for_hours,
260
- days=self.delay_for_days)
261
- self.second = now.second
262
- self.minute = now.minute
263
- self.hour = now.hour
264
- self.day = now.day
265
- self.month = now.month
266
- self.year = now.year
267
452
 
268
- super().init(manager)
453
+ class AtNow(_AtPredefinedDatetime):
454
+ def manager_added(self) -> None:
455
+ self.datetime = _datetime.now()
456
+ super().manager_added()
269
457
 
270
458
 
271
- class AtCreation(At):
272
- def init(self, manager):
273
- self.manager = manager
459
+ class AtManagerCreation(AtDatetime):
460
+ def manager_added(self) -> None:
461
+ self.datetime = self.task.manager.creation_time
462
+ super().manager_added()
274
463
 
275
- creation_time = self.manager.creation_time + timedelta(seconds=self.delay_for_seconds,
276
- minutes=self.delay_for_minutes,
277
- hours=self.delay_for_hours,
278
- days=self.delay_for_days)
279
- self.second = creation_time.second
280
- self.minute = creation_time.minute
281
- self.hour = creation_time.hour
282
- self.day = creation_time.day
283
- self.month = creation_time.month
284
- self.year = creation_time.year
285
464
 
286
- super().init(manager)
465
+ class AtManagerStart(AtDatetime):
466
+ def manager_added(self) -> None:
467
+ self.datetime = self.task.manager.start_time
468
+ super().manager_added()
@@ -0,0 +1,61 @@
1
+ import asyncio
2
+ import threading
3
+ import time
4
+ from typing import Union
5
+
6
+ _TIMERS: dict[str, float] = {}
7
+ _lock = threading.Lock()
8
+
9
+
10
+ def timer(name: str, seconds: Union[float, int]) -> bool:
11
+ global _TIMERS
12
+
13
+ seconds = float(seconds)
14
+
15
+ if name not in _TIMERS:
16
+ with _lock:
17
+ _TIMERS[name] = time.perf_counter() + seconds
18
+ wait = True
19
+ else:
20
+ with _lock:
21
+ delta = time.perf_counter() - _TIMERS[name]
22
+ wait = delta < 0
23
+ if wait:
24
+ ...
25
+ else:
26
+ clear_timer(name=name)
27
+ return wait
28
+
29
+
30
+ def timer_loop(name: str, seconds: Union[float, int], loop_delay: float = 0.001) -> None:
31
+ while timer(name, seconds):
32
+ time.sleep(loop_delay)
33
+
34
+
35
+ async def timer_loop_async(name: str, seconds: Union[float, int], loop_delay: float = 0.001) -> None:
36
+ while timer(name, seconds):
37
+ await asyncio.sleep(loop_delay)
38
+
39
+
40
+ def clear_timer(name: str) -> None:
41
+ global _TIMERS
42
+ if name in _TIMERS:
43
+ with _lock:
44
+ del _TIMERS[name]
45
+
46
+
47
+ def clear_all_timers() -> None:
48
+ global _TIMERS
49
+ with _lock:
50
+ _TIMERS = {}
51
+
52
+
53
+ if __name__ == '__main__':
54
+ try:
55
+ while True:
56
+ print(time.time())
57
+ timer_loop("test-loop", 1)
58
+ except KeyboardInterrupt:
59
+ ...
60
+
61
+ print("end")
@@ -136,11 +136,15 @@ class Typer(_Typer):
136
136
  # register the main callback
137
137
  self.callback()(self.main_callback)
138
138
 
139
+ @property
140
+ def title_header(self) -> str:
141
+ return text2art(self.title)
142
+
139
143
  def main_callback(self, *args, **kwargs):
140
144
  ...
141
145
 
142
146
  def info_command(self) -> Optional[int]:
143
- card_body = [text2art(self.title)]
147
+ card_body = [self.title_header]
144
148
  second_section = ""
145
149
  if self.description is not None:
146
150
  second_section += f"{self.description}"