reykit 1.1.66__py3-none-any.whl → 1.1.68__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.
reykit/rschedule.py
CHANGED
@@ -9,14 +9,16 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from typing import Any, Literal
|
12
|
+
from typing import Any, Literal, TypedDict, NotRequired
|
13
|
+
from functools import wraps as functools_wraps
|
13
14
|
from collections.abc import Callable
|
14
15
|
from apscheduler.executors.pool import ThreadPoolExecutor
|
15
16
|
from apscheduler.schedulers.background import BackgroundScheduler
|
16
17
|
from apscheduler.schedulers.blocking import BlockingScheduler
|
17
18
|
from apscheduler.job import Job
|
19
|
+
from reydb.rdb import Database
|
18
20
|
|
19
|
-
from .rbase import Base
|
21
|
+
from .rbase import Base, throw
|
20
22
|
|
21
23
|
|
22
24
|
__all__ = (
|
@@ -27,6 +29,7 @@ __all__ = (
|
|
27
29
|
class Schedule(Base):
|
28
30
|
"""
|
29
31
|
Schedule type.
|
32
|
+
Can create database used `self.build_db` method.
|
30
33
|
"""
|
31
34
|
|
32
35
|
|
@@ -35,7 +38,8 @@ class Schedule(Base):
|
|
35
38
|
max_workers: int = 10,
|
36
39
|
max_instances: int = 1,
|
37
40
|
coalesce: bool = True,
|
38
|
-
block: bool = False
|
41
|
+
block: bool = False,
|
42
|
+
database: Database | None = None
|
39
43
|
) -> None:
|
40
44
|
"""
|
41
45
|
Build instance attributes.
|
@@ -46,17 +50,20 @@ class Schedule(Base):
|
|
46
50
|
max_instances : Maximum number of synchronized executions of tasks with the same ID.
|
47
51
|
coalesce : Whether to coalesce tasks with the same ID.
|
48
52
|
block : Whether to block.
|
53
|
+
database : `Database` instance.
|
54
|
+
- `None`: Not use database.
|
55
|
+
- `Database`: Automatic record to database.
|
49
56
|
"""
|
50
57
|
|
51
|
-
#
|
58
|
+
# Build.
|
59
|
+
|
60
|
+
## Scheduler.
|
52
61
|
executor = ThreadPoolExecutor(max_workers)
|
53
62
|
executors = {'default': executor}
|
54
63
|
job_defaults = {
|
55
64
|
'coalesce': coalesce,
|
56
65
|
'max_instances': max_instances
|
57
66
|
}
|
58
|
-
|
59
|
-
# Instance.
|
60
67
|
if block:
|
61
68
|
scheduler = BlockingScheduler(
|
62
69
|
executors=executors,
|
@@ -67,10 +74,20 @@ class Schedule(Base):
|
|
67
74
|
executors=executors,
|
68
75
|
job_defaults=job_defaults
|
69
76
|
)
|
70
|
-
|
71
|
-
# Set attribute.
|
72
77
|
self.scheduler = scheduler
|
73
78
|
|
79
|
+
## Database.
|
80
|
+
self.database = database
|
81
|
+
|
82
|
+
### Database path name.
|
83
|
+
self.db_names = {
|
84
|
+
'base': 'base',
|
85
|
+
'base.schedule': 'schedule',
|
86
|
+
'base.stats_schedule': 'stats_schedule'
|
87
|
+
}
|
88
|
+
|
89
|
+
self.notes: dict[str, str] = {}
|
90
|
+
|
74
91
|
|
75
92
|
def start(self) -> None:
|
76
93
|
"""
|
@@ -122,13 +139,91 @@ class Schedule(Base):
|
|
122
139
|
return jobs
|
123
140
|
|
124
141
|
|
142
|
+
def wrap_record_db(
|
143
|
+
self,
|
144
|
+
task: Callable,
|
145
|
+
note: str | None
|
146
|
+
) -> None:
|
147
|
+
"""
|
148
|
+
Decorator, record to database.
|
149
|
+
|
150
|
+
Parameters
|
151
|
+
----------
|
152
|
+
task : Task.
|
153
|
+
note : Task note.
|
154
|
+
"""
|
155
|
+
|
156
|
+
|
157
|
+
# Define.
|
158
|
+
@functools_wraps(task)
|
159
|
+
def _task(*args, **kwargs) -> None:
|
160
|
+
"""
|
161
|
+
Decorated function.
|
162
|
+
|
163
|
+
Parameters
|
164
|
+
----------
|
165
|
+
args : Position arguments of function.
|
166
|
+
kwargs : Keyword arguments of function.
|
167
|
+
"""
|
168
|
+
|
169
|
+
# Handle parameter.
|
170
|
+
nonlocal task, note
|
171
|
+
|
172
|
+
# Status executing.
|
173
|
+
data = {
|
174
|
+
'status': 0,
|
175
|
+
'task': task.__name__,
|
176
|
+
'note': note
|
177
|
+
}
|
178
|
+
conn = self.database.connect()
|
179
|
+
conn.execute_insert(
|
180
|
+
(self.db_names['base'], self.db_names['base.schedule']),
|
181
|
+
data
|
182
|
+
)
|
183
|
+
id_ = conn.variables['identity']
|
184
|
+
conn.commit()
|
185
|
+
|
186
|
+
# Try execute.
|
187
|
+
|
188
|
+
## Record error.
|
189
|
+
task = self.database.error.wrap(task, note=note)
|
190
|
+
|
191
|
+
try:
|
192
|
+
task(*args, **kwargs)
|
193
|
+
|
194
|
+
# Status occurred error.
|
195
|
+
except BaseException:
|
196
|
+
data = {
|
197
|
+
'id': id_,
|
198
|
+
'status': 2
|
199
|
+
}
|
200
|
+
self.database.execute_update(
|
201
|
+
(self.db_names['base'], self.db_names['base.schedule']),
|
202
|
+
data
|
203
|
+
)
|
204
|
+
raise
|
205
|
+
|
206
|
+
# Status completed.
|
207
|
+
else:
|
208
|
+
data = {
|
209
|
+
'id': id_,
|
210
|
+
'status': 1
|
211
|
+
}
|
212
|
+
self.database.execute_update(
|
213
|
+
(self.db_names['base'], self.db_names['base.schedule']),
|
214
|
+
data
|
215
|
+
)
|
216
|
+
|
217
|
+
return _task
|
218
|
+
|
219
|
+
|
125
220
|
def add_task(
|
126
221
|
self,
|
127
222
|
task: Callable,
|
128
|
-
|
129
|
-
args:
|
130
|
-
kwargs:
|
131
|
-
|
223
|
+
plan: dict[str, Any],
|
224
|
+
args: Any | None = None,
|
225
|
+
kwargs: Any | None = None,
|
226
|
+
note: str | None = None
|
132
227
|
) -> Job:
|
133
228
|
"""
|
134
229
|
Add task.
|
@@ -136,35 +231,53 @@ class Schedule(Base):
|
|
136
231
|
Parameters
|
137
232
|
----------
|
138
233
|
task : Task function.
|
139
|
-
|
234
|
+
plan : Plan trigger keyword arguments.
|
140
235
|
args : Task position arguments.
|
141
236
|
kwargs : Task keyword arguments.
|
142
|
-
|
237
|
+
note : Task note.
|
143
238
|
|
144
239
|
Returns
|
145
240
|
-------
|
146
241
|
Task instance.
|
147
242
|
"""
|
148
243
|
|
244
|
+
# Handle parameter.
|
245
|
+
if plan is None:
|
246
|
+
plan = {}
|
247
|
+
trigger = plan.get('trigger')
|
248
|
+
trigger_args = {
|
249
|
+
key: value
|
250
|
+
for key, value in plan.items()
|
251
|
+
if key != 'trigger'
|
252
|
+
}
|
253
|
+
|
149
254
|
# Add.
|
255
|
+
|
256
|
+
## Database.
|
257
|
+
if self.database is not None:
|
258
|
+
task = self.wrap_record_db(task, note)
|
259
|
+
|
150
260
|
job = self.scheduler.add_job(
|
151
261
|
task,
|
152
262
|
trigger,
|
153
263
|
args,
|
154
264
|
kwargs,
|
155
|
-
**
|
265
|
+
**trigger_args
|
156
266
|
)
|
157
267
|
|
268
|
+
# Note.
|
269
|
+
self.notes[job.id] = note
|
270
|
+
|
158
271
|
return job
|
159
272
|
|
160
273
|
|
161
274
|
def modify_task(
|
162
275
|
self,
|
163
276
|
task: Job | str,
|
164
|
-
|
165
|
-
args:
|
166
|
-
kwargs:
|
167
|
-
|
277
|
+
plan: dict[str, Any],
|
278
|
+
args: Any | None = None,
|
279
|
+
kwargs: Any | None = None,
|
280
|
+
note: str | None = None
|
168
281
|
) -> None:
|
169
282
|
"""
|
170
283
|
Modify task.
|
@@ -172,37 +285,46 @@ class Schedule(Base):
|
|
172
285
|
Parameters
|
173
286
|
----------
|
174
287
|
task : Task instance or ID.
|
175
|
-
|
288
|
+
plan : Plan trigger keyword arguments.
|
176
289
|
args : Task position arguments.
|
177
290
|
kwargs : Task keyword arguments.
|
178
|
-
|
291
|
+
note : Task note.
|
179
292
|
"""
|
180
293
|
|
181
|
-
#
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
294
|
+
# Handle parameter.
|
295
|
+
if type(task) == Job:
|
296
|
+
task = task.id
|
297
|
+
if plan is None:
|
298
|
+
plan = {}
|
299
|
+
trigger = plan.get('trigger')
|
300
|
+
trigger_args = {
|
301
|
+
key: value
|
302
|
+
for key, value in plan.items()
|
303
|
+
if key != 'trigger'
|
304
|
+
}
|
187
305
|
|
188
|
-
|
189
|
-
if
|
190
|
-
self.scheduler.
|
191
|
-
task
|
192
|
-
|
306
|
+
# Modify plan.
|
307
|
+
if plan != {}:
|
308
|
+
self.scheduler.reschedule_job(
|
309
|
+
task,
|
310
|
+
trigger=trigger,
|
311
|
+
**trigger_args
|
193
312
|
)
|
194
313
|
|
195
|
-
#
|
314
|
+
# Modify arguments.
|
196
315
|
if (
|
197
|
-
|
198
|
-
or
|
316
|
+
args != ()
|
317
|
+
or kwargs != {}
|
199
318
|
):
|
200
|
-
self.scheduler.
|
201
|
-
task
|
202
|
-
|
203
|
-
|
319
|
+
self.scheduler.modify_job(
|
320
|
+
task,
|
321
|
+
args=args,
|
322
|
+
kwargs=kwargs
|
204
323
|
)
|
205
324
|
|
325
|
+
# Modify note.
|
326
|
+
self.notes[task] = note
|
327
|
+
|
206
328
|
|
207
329
|
def remove_task(
|
208
330
|
self,
|
@@ -270,4 +392,162 @@ class Schedule(Base):
|
|
270
392
|
self.scheduler.resume_job(id_)
|
271
393
|
|
272
394
|
|
395
|
+
def build_db(self) -> None:
|
396
|
+
"""
|
397
|
+
Check and build all standard databases and tables, by `self.db_names`.
|
398
|
+
"""
|
399
|
+
|
400
|
+
# Check.
|
401
|
+
if self.database is None:
|
402
|
+
throw(ValueError, self.database)
|
403
|
+
|
404
|
+
# Set parameter.
|
405
|
+
|
406
|
+
## Database.
|
407
|
+
databases = [
|
408
|
+
{
|
409
|
+
'name': self.db_names['base']
|
410
|
+
}
|
411
|
+
]
|
412
|
+
|
413
|
+
## Table.
|
414
|
+
tables = [
|
415
|
+
|
416
|
+
### 'schedule'.
|
417
|
+
{
|
418
|
+
'path': (self.db_names['base'], self.db_names['base.schedule']),
|
419
|
+
'fields': [
|
420
|
+
{
|
421
|
+
'name': 'create_time',
|
422
|
+
'type': 'datetime',
|
423
|
+
'constraint': 'NOT NULL DEFAULT CURRENT_TIMESTAMP',
|
424
|
+
'comment': 'Record create time.'
|
425
|
+
},
|
426
|
+
{
|
427
|
+
'name': 'update_time',
|
428
|
+
'type': 'datetime',
|
429
|
+
'constraint': 'DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP',
|
430
|
+
'comment': 'Record update time.'
|
431
|
+
},
|
432
|
+
{
|
433
|
+
'name': 'id',
|
434
|
+
'type': 'int unsigned',
|
435
|
+
'constraint': 'NOT NULL AUTO_INCREMENT',
|
436
|
+
'comment': 'ID.'
|
437
|
+
},
|
438
|
+
{
|
439
|
+
'name': 'status',
|
440
|
+
'type': 'tinyint',
|
441
|
+
'constraint': 'NOT NULL',
|
442
|
+
'comment': 'Schedule status, 0 is executing, 1 is completed, 2 is occurred error.'
|
443
|
+
},
|
444
|
+
{
|
445
|
+
'name': 'task',
|
446
|
+
'type': 'varchar(100)',
|
447
|
+
'constraint': 'NOT NULL',
|
448
|
+
'comment': 'Schedule task function name.'
|
449
|
+
},
|
450
|
+
{
|
451
|
+
'name': 'note',
|
452
|
+
'type': 'varchar(500)',
|
453
|
+
'comment': 'Schedule note.'
|
454
|
+
}
|
455
|
+
],
|
456
|
+
'primary': 'id',
|
457
|
+
'indexes': [
|
458
|
+
{
|
459
|
+
'name': 'n_create_time',
|
460
|
+
'fields': 'create_time',
|
461
|
+
'type': 'noraml',
|
462
|
+
'comment': 'Record create time normal index.'
|
463
|
+
},
|
464
|
+
{
|
465
|
+
'name': 'n_update_time',
|
466
|
+
'fields': 'update_time',
|
467
|
+
'type': 'noraml',
|
468
|
+
'comment': 'Record update time normal index.'
|
469
|
+
},
|
470
|
+
{
|
471
|
+
'name': 'n_task',
|
472
|
+
'fields': 'task',
|
473
|
+
'type': 'noraml',
|
474
|
+
'comment': 'Schedule task function name normal index.'
|
475
|
+
}
|
476
|
+
],
|
477
|
+
'comment': 'Schedule execute record table.'
|
478
|
+
}
|
479
|
+
|
480
|
+
]
|
481
|
+
|
482
|
+
## View stats.
|
483
|
+
views_stats = [
|
484
|
+
|
485
|
+
### 'stats_schedule'.
|
486
|
+
{
|
487
|
+
'path': (self.db_names['base'], self.db_names['base.stats_schedule']),
|
488
|
+
'items': [
|
489
|
+
{
|
490
|
+
'name': 'count',
|
491
|
+
'select': (
|
492
|
+
'SELECT COUNT(1)\n'
|
493
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`'
|
494
|
+
),
|
495
|
+
'comment': 'Schedule count.'
|
496
|
+
},
|
497
|
+
{
|
498
|
+
'name': 'past_day_count',
|
499
|
+
'select': (
|
500
|
+
'SELECT COUNT(1)\n'
|
501
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`\n'
|
502
|
+
'WHERE TIMESTAMPDIFF(DAY, `create_time`, NOW()) = 0'
|
503
|
+
),
|
504
|
+
'comment': 'Schedule count in the past day.'
|
505
|
+
},
|
506
|
+
{
|
507
|
+
'name': 'past_week_count',
|
508
|
+
'select': (
|
509
|
+
'SELECT COUNT(1)\n'
|
510
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`\n'
|
511
|
+
'WHERE TIMESTAMPDIFF(DAY, `create_time`, NOW()) <= 6'
|
512
|
+
),
|
513
|
+
'comment': 'Schedule count in the past week.'
|
514
|
+
},
|
515
|
+
{
|
516
|
+
'name': 'past_month_count',
|
517
|
+
'select': (
|
518
|
+
'SELECT COUNT(1)\n'
|
519
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`\n'
|
520
|
+
'WHERE TIMESTAMPDIFF(DAY, `create_time`, NOW()) <= 29'
|
521
|
+
),
|
522
|
+
'comment': 'Schedule count in the past month.'
|
523
|
+
},
|
524
|
+
{
|
525
|
+
'name': 'task_count',
|
526
|
+
'select': (
|
527
|
+
'SELECT COUNT(DISTINCT `task`)\n'
|
528
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`'
|
529
|
+
),
|
530
|
+
'comment': 'Task count.'
|
531
|
+
},
|
532
|
+
{
|
533
|
+
'name': 'last_time',
|
534
|
+
'select': (
|
535
|
+
'SELECT IFNULL(MAX(`update_time`), MAX(`create_time`))\n'
|
536
|
+
f'FROM `{self.db_names['base']}`.`{self.db_names['base.schedule']}`'
|
537
|
+
),
|
538
|
+
'comment': 'Schedule last record time.'
|
539
|
+
}
|
540
|
+
]
|
541
|
+
|
542
|
+
}
|
543
|
+
|
544
|
+
]
|
545
|
+
|
546
|
+
# Build.
|
547
|
+
self.database.build.build(databases, tables, views_stats=views_stats)
|
548
|
+
|
549
|
+
## Error.
|
550
|
+
self.database.error.build_db()
|
551
|
+
|
552
|
+
|
273
553
|
__iter__ = tasks
|
@@ -11,7 +11,7 @@ reykit/rnum.py,sha256=PhG4V_BkVfCJUsbpMDN1umGZly1Hsus80TW8bpyBtyY,3653
|
|
11
11
|
reykit/ros.py,sha256=Yi_mfYzVHwjjUZke0BNX7PKFLZpoT5NRyj5aDOSNQRU,46911
|
12
12
|
reykit/rrand.py,sha256=4VwooITgox54_GonELcJfcIpStDi-UJchpnyWKnyeIA,8606
|
13
13
|
reykit/rre.py,sha256=1qva7xatKVE9qC2j7IujjXSM59qxHWwTYpiizFFQ8Xo,6024
|
14
|
-
reykit/rschedule.py,sha256=
|
14
|
+
reykit/rschedule.py,sha256=uUSZyWAjurkWPlvDO57OgRLAreeE7WHOV0J_MG6S-zY,14985
|
15
15
|
reykit/rstdout.py,sha256=yesWo7wIGablpyAu-2J2Gw11Qp3GdQjGICTyIcvLyt4,8200
|
16
16
|
reykit/rsys.py,sha256=AP62KyN40flCeQJBclfJq8shachSAFT0LkVjiKsXkrw,24946
|
17
17
|
reykit/rtable.py,sha256=UQ-JlwjssMR3gY1iY-VGQEKQ5_BZabpJy6TL7Fx19c4,12200
|
@@ -22,7 +22,7 @@ reykit/rwrap.py,sha256=FEmeK_fboJ-OyXeJf8bilc7U2ph8xIbZGNHb6fLCy2c,15063
|
|
22
22
|
reykit/rzip.py,sha256=BGEONswuBZxQ-zcgd_xp2fcvYesC9AmKaaXWvnT3bTI,3456
|
23
23
|
reykit/rdll/__init__.py,sha256=nLSb8onBm2ilyoxzpDzUeGfSCKwkLEesIhzK3LiJ8mk,701
|
24
24
|
reykit/rdll/rdll_core.py,sha256=o6-rKcTQgxZQe0kD3GnwyNb3KL9IogzgCQNOmYLMm7A,5086
|
25
|
-
reykit-1.1.
|
26
|
-
reykit-1.1.
|
27
|
-
reykit-1.1.
|
28
|
-
reykit-1.1.
|
25
|
+
reykit-1.1.68.dist-info/METADATA,sha256=VkV4-QQHi1iAAq-8m7WMj0KO9qyqIsRkNfes0n2l6RM,1872
|
26
|
+
reykit-1.1.68.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
27
|
+
reykit-1.1.68.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
28
|
+
reykit-1.1.68.dist-info/RECORD,,
|
File without changes
|
File without changes
|