FlowerPower 0.11.6.19__py3-none-any.whl → 0.20.0__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.
- flowerpower/cfg/__init__.py +3 -3
- flowerpower/cfg/pipeline/__init__.py +5 -3
- flowerpower/cfg/project/__init__.py +3 -3
- flowerpower/cfg/project/job_queue.py +1 -128
- flowerpower/cli/__init__.py +5 -5
- flowerpower/cli/cfg.py +0 -3
- flowerpower/cli/job_queue.py +401 -133
- flowerpower/cli/pipeline.py +14 -413
- flowerpower/cli/utils.py +0 -1
- flowerpower/flowerpower.py +537 -28
- flowerpower/job_queue/__init__.py +5 -94
- flowerpower/job_queue/base.py +201 -3
- flowerpower/job_queue/rq/concurrent_workers/thread_worker.py +0 -3
- flowerpower/job_queue/rq/manager.py +388 -77
- flowerpower/pipeline/__init__.py +2 -0
- flowerpower/pipeline/base.py +2 -2
- flowerpower/pipeline/io.py +14 -16
- flowerpower/pipeline/manager.py +21 -642
- flowerpower/pipeline/pipeline.py +571 -0
- flowerpower/pipeline/registry.py +242 -10
- flowerpower/pipeline/visualizer.py +1 -2
- flowerpower/plugins/_io/__init__.py +8 -0
- flowerpower/plugins/mqtt/manager.py +6 -6
- flowerpower/settings/backend.py +0 -2
- flowerpower/settings/job_queue.py +1 -57
- flowerpower/utils/misc.py +0 -256
- flowerpower/utils/monkey.py +1 -83
- {flowerpower-0.11.6.19.dist-info → flowerpower-0.20.0.dist-info}/METADATA +308 -152
- flowerpower-0.20.0.dist-info/RECORD +58 -0
- flowerpower/fs/__init__.py +0 -29
- flowerpower/fs/base.py +0 -662
- flowerpower/fs/ext.py +0 -2143
- flowerpower/fs/storage_options.py +0 -1420
- flowerpower/job_queue/apscheduler/__init__.py +0 -11
- flowerpower/job_queue/apscheduler/_setup/datastore.py +0 -110
- flowerpower/job_queue/apscheduler/_setup/eventbroker.py +0 -93
- flowerpower/job_queue/apscheduler/manager.py +0 -1051
- flowerpower/job_queue/apscheduler/setup.py +0 -554
- flowerpower/job_queue/apscheduler/trigger.py +0 -169
- flowerpower/job_queue/apscheduler/utils.py +0 -311
- flowerpower/pipeline/job_queue.py +0 -583
- flowerpower/pipeline/runner.py +0 -603
- flowerpower/plugins/io/base.py +0 -2520
- flowerpower/plugins/io/helpers/datetime.py +0 -298
- flowerpower/plugins/io/helpers/polars.py +0 -875
- flowerpower/plugins/io/helpers/pyarrow.py +0 -570
- flowerpower/plugins/io/helpers/sql.py +0 -202
- flowerpower/plugins/io/loader/__init__.py +0 -28
- flowerpower/plugins/io/loader/csv.py +0 -37
- flowerpower/plugins/io/loader/deltatable.py +0 -190
- flowerpower/plugins/io/loader/duckdb.py +0 -19
- flowerpower/plugins/io/loader/json.py +0 -37
- flowerpower/plugins/io/loader/mqtt.py +0 -159
- flowerpower/plugins/io/loader/mssql.py +0 -26
- flowerpower/plugins/io/loader/mysql.py +0 -26
- flowerpower/plugins/io/loader/oracle.py +0 -26
- flowerpower/plugins/io/loader/parquet.py +0 -35
- flowerpower/plugins/io/loader/postgres.py +0 -26
- flowerpower/plugins/io/loader/pydala.py +0 -19
- flowerpower/plugins/io/loader/sqlite.py +0 -23
- flowerpower/plugins/io/metadata.py +0 -244
- flowerpower/plugins/io/saver/__init__.py +0 -28
- flowerpower/plugins/io/saver/csv.py +0 -36
- flowerpower/plugins/io/saver/deltatable.py +0 -186
- flowerpower/plugins/io/saver/duckdb.py +0 -19
- flowerpower/plugins/io/saver/json.py +0 -36
- flowerpower/plugins/io/saver/mqtt.py +0 -28
- flowerpower/plugins/io/saver/mssql.py +0 -26
- flowerpower/plugins/io/saver/mysql.py +0 -26
- flowerpower/plugins/io/saver/oracle.py +0 -26
- flowerpower/plugins/io/saver/parquet.py +0 -36
- flowerpower/plugins/io/saver/postgres.py +0 -26
- flowerpower/plugins/io/saver/pydala.py +0 -20
- flowerpower/plugins/io/saver/sqlite.py +0 -24
- flowerpower/utils/scheduler.py +0 -311
- flowerpower-0.11.6.19.dist-info/RECORD +0 -102
- {flowerpower-0.11.6.19.dist-info → flowerpower-0.20.0.dist-info}/WHEEL +0 -0
- {flowerpower-0.11.6.19.dist-info → flowerpower-0.20.0.dist-info}/entry_points.txt +0 -0
- {flowerpower-0.11.6.19.dist-info → flowerpower-0.20.0.dist-info}/licenses/LICENSE +0 -0
- {flowerpower-0.11.6.19.dist-info → flowerpower-0.20.0.dist-info}/top_level.txt +0 -0
@@ -1,169 +0,0 @@
|
|
1
|
-
import datetime as dt
|
2
|
-
from enum import Enum
|
3
|
-
from typing import Any, Dict, Type
|
4
|
-
|
5
|
-
from apscheduler.triggers.calendarinterval import CalendarIntervalTrigger
|
6
|
-
from apscheduler.triggers.cron import CronTrigger
|
7
|
-
from apscheduler.triggers.date import DateTrigger
|
8
|
-
from apscheduler.triggers.interval import IntervalTrigger
|
9
|
-
|
10
|
-
from ..base import BaseTrigger
|
11
|
-
|
12
|
-
|
13
|
-
class TriggerType(Enum):
|
14
|
-
CRON = "cron"
|
15
|
-
INTERVAL = "interval"
|
16
|
-
CALENDARINTERVAL = "calendarinterval"
|
17
|
-
DATE = "date"
|
18
|
-
|
19
|
-
|
20
|
-
# Mapping of trigger type to its class and allowed kwargs
|
21
|
-
TRIGGER_CONFIG: Dict[TriggerType, Dict[str, Any]] = {
|
22
|
-
TriggerType.CRON: {
|
23
|
-
"class": CronTrigger,
|
24
|
-
"kwargs": [
|
25
|
-
"crontab",
|
26
|
-
"year",
|
27
|
-
"month",
|
28
|
-
"week",
|
29
|
-
"day",
|
30
|
-
"day_of_week",
|
31
|
-
"hour",
|
32
|
-
"minute",
|
33
|
-
"second",
|
34
|
-
"start_time",
|
35
|
-
"end_time",
|
36
|
-
"timezone",
|
37
|
-
],
|
38
|
-
},
|
39
|
-
TriggerType.INTERVAL: {
|
40
|
-
"class": IntervalTrigger,
|
41
|
-
"kwargs": [
|
42
|
-
"weeks",
|
43
|
-
"days",
|
44
|
-
"hours",
|
45
|
-
"minutes",
|
46
|
-
"seconds",
|
47
|
-
"microseconds",
|
48
|
-
"start_time",
|
49
|
-
"end_time",
|
50
|
-
],
|
51
|
-
},
|
52
|
-
TriggerType.CALENDARINTERVAL: {
|
53
|
-
"class": CalendarIntervalTrigger,
|
54
|
-
"kwargs": [
|
55
|
-
"years",
|
56
|
-
"months",
|
57
|
-
"weeks",
|
58
|
-
"days",
|
59
|
-
"hour",
|
60
|
-
"minute",
|
61
|
-
"second",
|
62
|
-
"start_date",
|
63
|
-
"end_date",
|
64
|
-
"timezone",
|
65
|
-
],
|
66
|
-
},
|
67
|
-
TriggerType.DATE: {
|
68
|
-
"class": DateTrigger,
|
69
|
-
"kwargs": [
|
70
|
-
"run_time",
|
71
|
-
],
|
72
|
-
},
|
73
|
-
}
|
74
|
-
|
75
|
-
|
76
|
-
class APSTrigger(BaseTrigger):
|
77
|
-
"""
|
78
|
-
Implementation of BaseTrigger for APScheduler.
|
79
|
-
|
80
|
-
Provides a factory for creating APScheduler trigger instances
|
81
|
-
with validation and filtering of keyword arguments.
|
82
|
-
"""
|
83
|
-
|
84
|
-
trigger_type: TriggerType
|
85
|
-
|
86
|
-
def __init__(self, trigger_type: str):
|
87
|
-
"""
|
88
|
-
Initialize APSchedulerTrigger with a trigger type.
|
89
|
-
|
90
|
-
Args:
|
91
|
-
trigger_type (str): The type of trigger (cron, interval, calendarinterval, date).
|
92
|
-
|
93
|
-
Raises:
|
94
|
-
ValueError: If the trigger_type is invalid.
|
95
|
-
"""
|
96
|
-
try:
|
97
|
-
self.trigger_type = TriggerType(trigger_type.lower())
|
98
|
-
except ValueError:
|
99
|
-
valid_types = [t.value for t in TriggerType]
|
100
|
-
raise ValueError(
|
101
|
-
f"Invalid trigger type '{trigger_type}'. Valid types are: {valid_types}"
|
102
|
-
)
|
103
|
-
|
104
|
-
def _get_allowed_kwargs(self) -> set:
|
105
|
-
"""Return the set of allowed kwargs for the current trigger type."""
|
106
|
-
return set(TRIGGER_CONFIG[self.trigger_type]["kwargs"])
|
107
|
-
|
108
|
-
def _check_kwargs(self, **kwargs) -> None:
|
109
|
-
"""
|
110
|
-
Validate that all provided kwargs are allowed for the trigger type.
|
111
|
-
|
112
|
-
Raises:
|
113
|
-
ValueError: If any kwarg is not allowed.
|
114
|
-
"""
|
115
|
-
allowed = self._get_allowed_kwargs()
|
116
|
-
invalid = [k for k in kwargs if k not in allowed]
|
117
|
-
if invalid:
|
118
|
-
raise ValueError(
|
119
|
-
f"Invalid argument(s) for trigger type '{self.trigger_type.value}': {invalid}. "
|
120
|
-
f"Allowed arguments are: {sorted(allowed)}"
|
121
|
-
)
|
122
|
-
|
123
|
-
def _filter_kwargs(self, **kwargs) -> Dict[str, Any]:
|
124
|
-
"""
|
125
|
-
Filter kwargs to only those allowed for the trigger type and not None.
|
126
|
-
|
127
|
-
Returns:
|
128
|
-
Dict[str, Any]: Filtered kwargs.
|
129
|
-
"""
|
130
|
-
allowed = self._get_allowed_kwargs()
|
131
|
-
return {k: v for k, v in kwargs.items() if k in allowed and v is not None}
|
132
|
-
|
133
|
-
def get_trigger_instance(self, **kwargs) -> Any:
|
134
|
-
"""
|
135
|
-
Create and return an APScheduler trigger instance based on the trigger type.
|
136
|
-
|
137
|
-
Args:
|
138
|
-
**kwargs: Keyword arguments for the trigger.
|
139
|
-
|
140
|
-
Returns:
|
141
|
-
Any: An APScheduler trigger instance.
|
142
|
-
|
143
|
-
Raises:
|
144
|
-
ValueError: If invalid arguments are provided or trigger type is unknown.
|
145
|
-
"""
|
146
|
-
self._check_kwargs(**kwargs)
|
147
|
-
filtered_kwargs = self._filter_kwargs(**kwargs)
|
148
|
-
trigger_cls: Type = TRIGGER_CONFIG[self.trigger_type]["class"]
|
149
|
-
|
150
|
-
if self.trigger_type == TriggerType.CRON:
|
151
|
-
crontab = filtered_kwargs.pop("crontab", None)
|
152
|
-
if crontab:
|
153
|
-
return trigger_cls.from_crontab(crontab)
|
154
|
-
return trigger_cls(**filtered_kwargs)
|
155
|
-
elif self.trigger_type == TriggerType.INTERVAL:
|
156
|
-
return trigger_cls(**filtered_kwargs)
|
157
|
-
elif self.trigger_type == TriggerType.CALENDARINTERVAL:
|
158
|
-
return trigger_cls(**filtered_kwargs)
|
159
|
-
elif self.trigger_type == TriggerType.DATE:
|
160
|
-
# Default to now if not specified
|
161
|
-
if "run_time" not in filtered_kwargs:
|
162
|
-
filtered_kwargs["run_time"] = dt.datetime.now()
|
163
|
-
return trigger_cls(**filtered_kwargs)
|
164
|
-
else:
|
165
|
-
# This should never be reached due to Enum validation in __init__
|
166
|
-
raise ValueError(f"Unknown trigger type: {self.trigger_type.value}")
|
167
|
-
|
168
|
-
|
169
|
-
# End of file
|
@@ -1,311 +0,0 @@
|
|
1
|
-
from operator import attrgetter
|
2
|
-
from typing import List
|
3
|
-
|
4
|
-
from rich.console import Console
|
5
|
-
from rich.table import Table
|
6
|
-
|
7
|
-
|
8
|
-
def humanize_crontab(minute, hour, day, month, day_of_week):
|
9
|
-
days = {
|
10
|
-
"0": "Sunday",
|
11
|
-
"sun": "Sunday",
|
12
|
-
"7": "Sunday",
|
13
|
-
"1": "Monday",
|
14
|
-
"mon": "Monday",
|
15
|
-
"2": "Tuesday",
|
16
|
-
"tue": "Tuesday",
|
17
|
-
"3": "Wednesday",
|
18
|
-
"wed": "Wednesday",
|
19
|
-
"4": "Thursday",
|
20
|
-
"thu": "Thursday",
|
21
|
-
"5": "Friday",
|
22
|
-
"fri": "Friday",
|
23
|
-
"6": "Saturday",
|
24
|
-
"sat": "Saturday",
|
25
|
-
"*": "*",
|
26
|
-
}
|
27
|
-
months = {
|
28
|
-
"1": "January",
|
29
|
-
"2": "February",
|
30
|
-
"3": "March",
|
31
|
-
"4": "April",
|
32
|
-
"5": "May",
|
33
|
-
"6": "June",
|
34
|
-
"7": "July",
|
35
|
-
"8": "August",
|
36
|
-
"9": "September",
|
37
|
-
"10": "October",
|
38
|
-
"11": "November",
|
39
|
-
"12": "December",
|
40
|
-
"*": "*",
|
41
|
-
}
|
42
|
-
|
43
|
-
def get_day_name(day_input):
|
44
|
-
day_input = str(day_input).lower().strip()
|
45
|
-
if "-" in day_input:
|
46
|
-
start, end = day_input.split("-")
|
47
|
-
return f"{days.get(start.strip(), start)}-{days.get(end.strip(), end)}"
|
48
|
-
if "," in day_input:
|
49
|
-
return ", ".join(
|
50
|
-
days.get(d.strip(), d.strip()) for d in day_input.split(",")
|
51
|
-
)
|
52
|
-
return days.get(day_input, day_input)
|
53
|
-
|
54
|
-
try:
|
55
|
-
minute, hour, day, month, day_of_week = map(
|
56
|
-
str.strip, map(str, [minute, hour, day, month, day_of_week])
|
57
|
-
)
|
58
|
-
|
59
|
-
if "/" in minute:
|
60
|
-
return f"every {minute.split('/')[1]} minutes"
|
61
|
-
if "/" in hour:
|
62
|
-
return f"every {hour.split('/')[1]} hours"
|
63
|
-
|
64
|
-
if all(x == "*" for x in [minute, hour, day, month, day_of_week]):
|
65
|
-
return "every minute"
|
66
|
-
if [minute, hour, day, month, day_of_week] == ["0", "*", "*", "*", "*"]:
|
67
|
-
return "every hour"
|
68
|
-
|
69
|
-
if (
|
70
|
-
minute == "0"
|
71
|
-
and hour != "*"
|
72
|
-
and day == "*"
|
73
|
-
and month == "*"
|
74
|
-
and day_of_week == "*"
|
75
|
-
):
|
76
|
-
return (
|
77
|
-
"every day at midnight"
|
78
|
-
if hour == "0"
|
79
|
-
else "every day at noon"
|
80
|
-
if hour == "12"
|
81
|
-
else f"every day at {hour}:00"
|
82
|
-
)
|
83
|
-
|
84
|
-
if (
|
85
|
-
minute == "0"
|
86
|
-
and hour == "0"
|
87
|
-
and day == "*"
|
88
|
-
and month == "*"
|
89
|
-
and day_of_week != "*"
|
90
|
-
):
|
91
|
-
return f"every {get_day_name(day_of_week)} at midnight"
|
92
|
-
|
93
|
-
if (
|
94
|
-
minute == "0"
|
95
|
-
and hour != "*"
|
96
|
-
and day == "*"
|
97
|
-
and month == "*"
|
98
|
-
and day_of_week != "*"
|
99
|
-
):
|
100
|
-
return (
|
101
|
-
"every weekday at {hour}:00"
|
102
|
-
if "-" in day_of_week
|
103
|
-
and "mon" in day_of_week.lower()
|
104
|
-
and "fri" in day_of_week.lower()
|
105
|
-
else f"every {get_day_name(day_of_week)} at {hour}:00"
|
106
|
-
)
|
107
|
-
|
108
|
-
if (
|
109
|
-
minute != "*"
|
110
|
-
and hour != "*"
|
111
|
-
and day == "*"
|
112
|
-
and month == "*"
|
113
|
-
and day_of_week == "*"
|
114
|
-
):
|
115
|
-
return f"every day at {hour}:{minute.zfill(2)}"
|
116
|
-
|
117
|
-
if day != "*" and month != "*" and minute == "0" and hour == "0":
|
118
|
-
return f"on day {day} of {months.get(month, month)} at midnight"
|
119
|
-
|
120
|
-
if (
|
121
|
-
minute != "*"
|
122
|
-
and hour == "*"
|
123
|
-
and day == "*"
|
124
|
-
and month == "*"
|
125
|
-
and day_of_week == "*"
|
126
|
-
):
|
127
|
-
return f"every hour at minute {minute}"
|
128
|
-
|
129
|
-
parts = []
|
130
|
-
if minute != "*":
|
131
|
-
parts.append(f"at minute {minute}")
|
132
|
-
if hour != "*":
|
133
|
-
parts.append(f"hour {hour}")
|
134
|
-
if day != "*":
|
135
|
-
parts.append(f"day {day}")
|
136
|
-
if month != "*":
|
137
|
-
parts.append(f"month {months.get(month, month)}")
|
138
|
-
if day_of_week != "*":
|
139
|
-
parts.append(f"on {get_day_name(day_of_week)}")
|
140
|
-
|
141
|
-
return f"runs {' '.join(parts)}" if parts else "every minute"
|
142
|
-
except Exception:
|
143
|
-
return f"{minute} {hour} {day} {month} {day_of_week}"
|
144
|
-
|
145
|
-
|
146
|
-
def format_trigger(trigger):
|
147
|
-
trigger_type = trigger.__class__.__name__
|
148
|
-
|
149
|
-
if trigger_type == "IntervalTrigger":
|
150
|
-
for unit in ["seconds", "minutes", "hours", "days"]:
|
151
|
-
if value := getattr(trigger, unit, None):
|
152
|
-
return f"Interval: Every {value}{unit[0]}"
|
153
|
-
return "Interval"
|
154
|
-
|
155
|
-
if trigger_type == "CronTrigger":
|
156
|
-
try:
|
157
|
-
cron_parts = dict(
|
158
|
-
part.split("=")
|
159
|
-
for part in str(trigger).strip("CronTrigger(").rstrip(")").split(", ")
|
160
|
-
)
|
161
|
-
cron_parts = {k: v.strip("'") for k, v in cron_parts.items()}
|
162
|
-
crontab = f"{cron_parts['minute']} {cron_parts['hour']} {cron_parts['day']} {cron_parts['month']} {cron_parts['day_of_week']}"
|
163
|
-
human_readable = humanize_crontab(
|
164
|
-
**{
|
165
|
-
k: cron_parts[k]
|
166
|
-
for k in ["minute", "hour", "day", "month", "day_of_week"]
|
167
|
-
}
|
168
|
-
)
|
169
|
-
return f"Cron: {human_readable} ({crontab})"
|
170
|
-
except Exception:
|
171
|
-
return f"Cron: {str(trigger)}"
|
172
|
-
|
173
|
-
if trigger_type == "DateTrigger":
|
174
|
-
return f"Date: Once at {trigger.run_date.strftime('%Y-%m-%d %H:%M:%S')}"
|
175
|
-
|
176
|
-
return f"{trigger_type}: {str(trigger)}"
|
177
|
-
|
178
|
-
|
179
|
-
def display_schedules(schedules: List):
|
180
|
-
console = Console()
|
181
|
-
total_width = console.width - 10
|
182
|
-
|
183
|
-
width_ratios = {
|
184
|
-
"id": 0.20,
|
185
|
-
"task": 0.10,
|
186
|
-
"trigger": 0.25,
|
187
|
-
"name": 0.15,
|
188
|
-
"run_args": 0.15,
|
189
|
-
"next_fire": 0.08,
|
190
|
-
"last_fire": 0.08,
|
191
|
-
"paused": 0.01,
|
192
|
-
}
|
193
|
-
|
194
|
-
widths = {k: max(10, int(total_width * ratio)) for k, ratio in width_ratios.items()}
|
195
|
-
|
196
|
-
table = Table(
|
197
|
-
show_header=True,
|
198
|
-
header_style="bold magenta",
|
199
|
-
width=total_width,
|
200
|
-
row_styles=["", "dim"],
|
201
|
-
border_style="blue",
|
202
|
-
show_lines=True,
|
203
|
-
)
|
204
|
-
|
205
|
-
for col, style, width in [
|
206
|
-
("ID", "dim", widths["id"]),
|
207
|
-
("Task", "cyan", widths["task"]),
|
208
|
-
("Trigger", "blue", widths["trigger"]),
|
209
|
-
("Name", "yellow", widths["name"]),
|
210
|
-
("Run Args", "yellow", widths["run_args"]),
|
211
|
-
("Next Fire Time", "green", widths["next_fire"]),
|
212
|
-
("Last Fire Time", "red", widths["last_fire"]),
|
213
|
-
("Paused", "bold", widths["paused"]),
|
214
|
-
]:
|
215
|
-
table.add_column(col, style=style, width=width)
|
216
|
-
|
217
|
-
for schedule in sorted(schedules, key=attrgetter("next_fire_time")):
|
218
|
-
table.add_row(
|
219
|
-
schedule.id,
|
220
|
-
schedule.task_id.split(":")[-1],
|
221
|
-
format_trigger(schedule.trigger),
|
222
|
-
(
|
223
|
-
str(schedule.args[1])
|
224
|
-
if schedule.args and len(schedule.args) > 1
|
225
|
-
else "None"
|
226
|
-
),
|
227
|
-
"\n".join(f"{k}: {v}" for k, v in (schedule.kwargs or {}).items())
|
228
|
-
or "None",
|
229
|
-
(
|
230
|
-
schedule.next_fire_time.strftime("%Y-%m-%d %H:%M:%S")
|
231
|
-
if schedule.next_fire_time
|
232
|
-
else "Never"
|
233
|
-
),
|
234
|
-
(
|
235
|
-
schedule.last_fire_time.strftime("%Y-%m-%d %H:%M:%S")
|
236
|
-
if schedule.last_fire_time
|
237
|
-
else "Never"
|
238
|
-
),
|
239
|
-
"✓" if schedule.paused else "✗",
|
240
|
-
)
|
241
|
-
|
242
|
-
console.print(table)
|
243
|
-
|
244
|
-
|
245
|
-
def display_tasks(tasks):
|
246
|
-
console = Console()
|
247
|
-
table = Table(title="Tasks")
|
248
|
-
|
249
|
-
widths = {"id": 50, "executor": 15, "max_jobs": 15, "misfire": 20}
|
250
|
-
|
251
|
-
for col, style, width in [
|
252
|
-
("ID", "cyan", widths["id"]),
|
253
|
-
("Job Executor", "blue", widths["executor"]),
|
254
|
-
("Max Running Jobs", "yellow", widths["max_jobs"]),
|
255
|
-
("Misfire Grace Time", "green", widths["misfire"]),
|
256
|
-
]:
|
257
|
-
table.add_column(col, style=style, width=width)
|
258
|
-
|
259
|
-
for task in sorted(tasks, key=attrgetter("id")):
|
260
|
-
table.add_row(
|
261
|
-
task.id,
|
262
|
-
str(task.job_executor),
|
263
|
-
str(task.max_running_jobs or "None"),
|
264
|
-
str(task.misfire_grace_time or "None"),
|
265
|
-
)
|
266
|
-
|
267
|
-
console.print(table)
|
268
|
-
|
269
|
-
|
270
|
-
def display_jobs(jobs):
|
271
|
-
console = Console()
|
272
|
-
table = Table(title="Jobs")
|
273
|
-
|
274
|
-
widths = {
|
275
|
-
"id": 10,
|
276
|
-
"task_id": 40,
|
277
|
-
"args": 20,
|
278
|
-
"kwargs": 20,
|
279
|
-
"schedule": 15,
|
280
|
-
"created": 25,
|
281
|
-
"status": 15,
|
282
|
-
}
|
283
|
-
|
284
|
-
for col, style, width in [
|
285
|
-
("ID", "cyan", widths["id"]),
|
286
|
-
("Task ID", "blue", widths["task_id"]),
|
287
|
-
("Args", "yellow", widths["args"]),
|
288
|
-
("Kwargs", "yellow", widths["kwargs"]),
|
289
|
-
("Schedule ID", "green", widths["schedule"]),
|
290
|
-
("Created At", "magenta", widths["created"]),
|
291
|
-
("Status", "red", widths["status"]),
|
292
|
-
]:
|
293
|
-
table.add_column(col, style=style, width=width)
|
294
|
-
|
295
|
-
for job in sorted(jobs, key=attrgetter("id")):
|
296
|
-
status = "Running" if job.acquired_by else "Pending"
|
297
|
-
table.add_row(
|
298
|
-
str(job.id),
|
299
|
-
job.task_id,
|
300
|
-
str(job.args if job.args else "None"),
|
301
|
-
(
|
302
|
-
"\n".join(f"{k}: {v}" for k, v in job.kwargs.items())
|
303
|
-
if job.kwargs
|
304
|
-
else "None"
|
305
|
-
),
|
306
|
-
str(job.schedule_id or "None"),
|
307
|
-
job.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
308
|
-
status,
|
309
|
-
)
|
310
|
-
|
311
|
-
console.print(table)
|