meerschaum 2.2.7__py3-none-any.whl → 2.3.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.
- meerschaum/__init__.py +6 -1
- meerschaum/__main__.py +0 -5
- meerschaum/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +72 -6
- meerschaum/_internal/arguments/_parser.py +45 -15
- meerschaum/_internal/docs/index.py +265 -8
- meerschaum/_internal/entry.py +154 -24
- meerschaum/_internal/shell/Shell.py +264 -77
- meerschaum/actions/__init__.py +29 -17
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +113 -0
- meerschaum/actions/copy.py +68 -41
- meerschaum/actions/delete.py +112 -50
- meerschaum/actions/edit.py +3 -3
- meerschaum/actions/install.py +40 -32
- meerschaum/actions/pause.py +44 -27
- meerschaum/actions/restart.py +107 -0
- meerschaum/actions/show.py +130 -159
- meerschaum/actions/start.py +161 -100
- meerschaum/actions/stop.py +78 -42
- meerschaum/api/_events.py +25 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/callbacks/jobs.py +36 -44
- meerschaum/api/dash/jobs.py +89 -78
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +148 -17
- meerschaum/api/routes/_jobs.py +407 -0
- meerschaum/api/routes/_pipes.py +5 -5
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_jobs.py +1 -1
- meerschaum/config/_paths.py +7 -0
- meerschaum/config/_shell.py +8 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +17 -0
- meerschaum/connectors/Connector.py +13 -7
- meerschaum/connectors/__init__.py +28 -15
- meerschaum/connectors/api/APIConnector.py +27 -1
- meerschaum/connectors/api/_actions.py +71 -6
- meerschaum/connectors/api/_jobs.py +368 -0
- meerschaum/connectors/api/_pipes.py +85 -84
- meerschaum/connectors/parse.py +27 -15
- meerschaum/core/Pipe/_bootstrap.py +16 -8
- meerschaum/jobs/_Executor.py +69 -0
- meerschaum/jobs/_Job.py +899 -0
- meerschaum/jobs/__init__.py +396 -0
- meerschaum/jobs/systemd.py +694 -0
- meerschaum/plugins/__init__.py +97 -12
- meerschaum/utils/daemon/Daemon.py +276 -30
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +5 -5
- meerschaum/utils/daemon/RotatingFile.py +14 -7
- meerschaum/utils/daemon/StdinFile.py +121 -0
- meerschaum/utils/daemon/__init__.py +15 -7
- meerschaum/utils/daemon/_names.py +15 -13
- meerschaum/utils/formatting/__init__.py +2 -1
- meerschaum/utils/formatting/_jobs.py +115 -62
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/misc.py +41 -22
- meerschaum/utils/packages/_packages.py +9 -6
- meerschaum/utils/process.py +9 -9
- meerschaum/utils/prompt.py +16 -8
- meerschaum/utils/venv/__init__.py +2 -2
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/RECORD +70 -61
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
meerschaum/actions/start.py
CHANGED
@@ -7,12 +7,12 @@ Start subsystems (API server, logging daemon, etc.).
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
|
10
|
+
from meerschaum.utils.typing import SuccessTuple, Optional, List, Any, Union
|
11
11
|
|
12
12
|
def start(
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
action: Optional[List[str]] = None,
|
14
|
+
**kw: Any,
|
15
|
+
) -> SuccessTuple:
|
16
16
|
"""
|
17
17
|
Start subsystems (API server, background job, etc.).
|
18
18
|
"""
|
@@ -23,21 +23,29 @@ def start(
|
|
23
23
|
'gui': _start_gui,
|
24
24
|
'webterm': _start_webterm,
|
25
25
|
'connectors': _start_connectors,
|
26
|
+
'pipeline': _start_pipeline,
|
26
27
|
}
|
27
28
|
return choose_subaction(action, options, **kw)
|
28
29
|
|
29
30
|
|
30
31
|
def _complete_start(
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
action: Optional[List[str]] = None,
|
33
|
+
**kw: Any
|
34
|
+
) -> List[str]:
|
34
35
|
"""
|
35
36
|
Override the default Meerschaum `complete_` function.
|
36
37
|
"""
|
38
|
+
from meerschaum.actions.delete import _complete_delete_jobs
|
39
|
+
from functools import partial
|
37
40
|
|
38
41
|
if action is None:
|
39
42
|
action = []
|
40
43
|
|
44
|
+
_complete_start_jobs = partial(
|
45
|
+
_complete_delete_jobs,
|
46
|
+
_get_job_method=['stopped', 'paused'],
|
47
|
+
)
|
48
|
+
|
41
49
|
options = {
|
42
50
|
'job': _complete_start_jobs,
|
43
51
|
'jobs': _complete_start_jobs,
|
@@ -75,11 +83,15 @@ def _start_api(action: Optional[List[str]] = None, **kw):
|
|
75
83
|
from meerschaum.actions import actions
|
76
84
|
return actions['api'](action=['start'], **kw)
|
77
85
|
|
86
|
+
|
78
87
|
def _start_jobs(
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
88
|
+
action: Optional[List[str]] = None,
|
89
|
+
name: Optional[str] = None,
|
90
|
+
sysargs: Optional[List[str]] = None,
|
91
|
+
executor_keys: Optional[str] = None,
|
92
|
+
debug: bool = False,
|
93
|
+
**kw
|
94
|
+
) -> SuccessTuple:
|
83
95
|
"""
|
84
96
|
Run a Meerschaum action as a background job.
|
85
97
|
|
@@ -109,23 +121,24 @@ def _start_jobs(
|
|
109
121
|
Start the job 'happy_seal' but via the `--name` flag.
|
110
122
|
This only applies when no text follows the words 'start job'.
|
111
123
|
"""
|
112
|
-
import textwrap
|
113
124
|
from meerschaum.utils.warnings import warn, info
|
114
|
-
from meerschaum.utils.daemon import (
|
115
|
-
daemon_action, Daemon, get_daemon_ids, get_daemons, get_filtered_daemons,
|
116
|
-
get_stopped_daemons, get_running_daemons, get_paused_daemons,
|
117
|
-
)
|
118
125
|
from meerschaum.utils.daemon._names import get_new_daemon_name
|
119
|
-
from meerschaum.
|
126
|
+
from meerschaum.jobs import (
|
127
|
+
Job,
|
128
|
+
get_filtered_jobs,
|
129
|
+
get_stopped_jobs,
|
130
|
+
get_running_jobs,
|
131
|
+
get_paused_jobs,
|
132
|
+
_install_healthcheck_job,
|
133
|
+
)
|
120
134
|
from meerschaum.actions import actions
|
121
135
|
from meerschaum.utils.prompt import yes_no
|
122
136
|
from meerschaum.utils.formatting import print_tuple
|
123
|
-
from meerschaum.utils.formatting._jobs import
|
124
|
-
from meerschaum.utils.formatting._shell import clear_screen
|
137
|
+
from meerschaum.utils.formatting._jobs import pprint_jobs
|
125
138
|
from meerschaum.utils.misc import items_str
|
126
139
|
|
127
140
|
names = []
|
128
|
-
|
141
|
+
jobs = get_filtered_jobs(executor_keys, action, debug=debug)
|
129
142
|
|
130
143
|
new_job = len(list(action)) > 0
|
131
144
|
_potential_jobs = {'known': [], 'unknown': []}
|
@@ -134,7 +147,7 @@ def _start_jobs(
|
|
134
147
|
for a in action:
|
135
148
|
_potential_jobs[(
|
136
149
|
'known'
|
137
|
-
if a in
|
150
|
+
if a in jobs
|
138
151
|
else 'unknown'
|
139
152
|
)].append(a)
|
140
153
|
|
@@ -158,7 +171,7 @@ def _start_jobs(
|
|
158
171
|
+ items_str(_potential_jobs['unknown'])
|
159
172
|
+ " will be ignored."
|
160
173
|
),
|
161
|
-
stack
|
174
|
+
stack=False
|
162
175
|
)
|
163
176
|
|
164
177
|
### Determine the `names` list.
|
@@ -182,88 +195,83 @@ def _start_jobs(
|
|
182
195
|
|
183
196
|
### No action or --name was provided. Ask to start all stopped jobs.
|
184
197
|
else:
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
198
|
+
running_jobs = get_running_jobs(executor_keys, jobs, debug=debug)
|
199
|
+
paused_jobs = get_paused_jobs(executor_keys, jobs, debug=debug)
|
200
|
+
stopped_jobs = get_stopped_jobs(executor_keys, jobs, debug=debug)
|
201
|
+
|
202
|
+
if not stopped_jobs and not paused_jobs:
|
203
|
+
if not running_jobs:
|
204
|
+
return False, "No jobs to start"
|
191
205
|
return True, "All jobs are running."
|
192
206
|
|
193
|
-
names = [
|
207
|
+
names = [
|
208
|
+
name
|
209
|
+
for name in list(stopped_jobs) + list(paused_jobs)
|
210
|
+
]
|
194
211
|
|
195
212
|
def _run_new_job(name: Optional[str] = None):
|
196
|
-
kw['action'] = action
|
197
213
|
name = name or get_new_daemon_name()
|
198
|
-
|
199
|
-
|
200
|
-
return _action_success_tuple, name
|
201
|
-
|
202
|
-
def _run_existing_job(name: Optional[str] = None):
|
203
|
-
daemon = Daemon(daemon_id=name)
|
204
|
-
if daemon.process is not None:
|
205
|
-
if daemon.status == 'paused':
|
206
|
-
return daemon.resume(), daemon.daemon_id
|
207
|
-
return (True, f"Job '{name}' is already running."), daemon.daemon_id
|
208
|
-
|
209
|
-
if not daemon.path.exists():
|
210
|
-
if not kw.get('nopretty', False):
|
211
|
-
warn(f"There isn't a job with the name '{name}'.", stack=False)
|
212
|
-
print(
|
213
|
-
f"You can start a new job named '{name}' with `start job "
|
214
|
-
+ "{options}" + f" --name {name}`"
|
215
|
-
)
|
216
|
-
return (False, f"Job '{name}' does not exist."), daemon.daemon_id
|
214
|
+
job = Job(name, sysargs, executor_keys=executor_keys)
|
215
|
+
return job.start(debug=debug), name
|
217
216
|
|
218
|
-
|
217
|
+
def _run_existing_job(name: str):
|
218
|
+
job = Job(name, executor_keys=executor_keys)
|
219
|
+
return job.start(debug=debug), name
|
219
220
|
|
220
221
|
if not names:
|
221
222
|
return False, "No jobs to start."
|
222
223
|
|
223
224
|
### Get user permission to clear logs.
|
224
|
-
|
225
|
-
if not kw.get('force', False) and
|
226
|
-
|
227
|
-
|
228
|
-
if
|
229
|
-
pprint_jobs(
|
225
|
+
_filtered_jobs = get_filtered_jobs(executor_keys, names, debug=debug)
|
226
|
+
if not kw.get('force', False) and _filtered_jobs:
|
227
|
+
_filtered_running_jobs = get_running_jobs(executor_keys, _filtered_jobs, debug=debug)
|
228
|
+
_skipped_jobs = []
|
229
|
+
if _filtered_running_jobs:
|
230
|
+
pprint_jobs(_filtered_running_jobs)
|
230
231
|
if yes_no(
|
231
232
|
"Do you want to first stop these jobs?",
|
232
|
-
default
|
233
|
-
yes
|
234
|
-
noask
|
233
|
+
default='n',
|
234
|
+
yes=kw.get('yes', False),
|
235
|
+
noask=kw.get('noask', False)
|
235
236
|
):
|
236
237
|
stop_success_tuple = actions['stop'](
|
237
|
-
action
|
238
|
-
force
|
238
|
+
action=['jobs'] + [_name for _name in _filtered_running_jobs],
|
239
|
+
force=True,
|
240
|
+
executor_keys=executor_keys,
|
241
|
+
debug=debug,
|
239
242
|
)
|
240
243
|
if not stop_success_tuple[0]:
|
241
244
|
warn(
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
245
|
+
(
|
246
|
+
"Failed to stop job"
|
247
|
+
+ ("s" if len(_filtered_running_jobs) != 1 else '')
|
248
|
+
+ items_str([_name for _name in _filtered_running_jobs])
|
249
|
+
+ "."
|
250
|
+
),
|
251
|
+
stack=False
|
246
252
|
)
|
247
|
-
for
|
248
|
-
names.remove(
|
249
|
-
|
253
|
+
for _name in _filtered_running_jobs:
|
254
|
+
names.remove(_name)
|
255
|
+
_filtered_jobs.pop(_name)
|
250
256
|
else:
|
251
257
|
info(
|
252
258
|
"Skipping already running job"
|
253
|
-
+ ("s" if len(
|
254
|
-
+
|
259
|
+
+ ("s" if len(_filtered_running_jobs) != 1 else '')
|
260
|
+
+ ' '
|
261
|
+
+ items_str([_name for _name in _filtered_running_jobs])
|
262
|
+
+ '.'
|
255
263
|
)
|
256
|
-
for
|
257
|
-
names.remove(
|
258
|
-
|
259
|
-
|
264
|
+
for _name in _filtered_running_jobs:
|
265
|
+
names.remove(_name)
|
266
|
+
_filtered_jobs.pop(_name)
|
267
|
+
_skipped_jobs.append(_name)
|
260
268
|
|
261
|
-
if not
|
262
|
-
return len(
|
269
|
+
if not _filtered_jobs:
|
270
|
+
return len(_skipped_jobs) > 0, "No jobs to start."
|
263
271
|
|
264
|
-
pprint_jobs(
|
272
|
+
pprint_jobs(_filtered_jobs, nopretty=kw.get('nopretty', False))
|
265
273
|
info(
|
266
|
-
|
274
|
+
"Starting the job"
|
267
275
|
+ ("s" if len(names) != 1 else "")
|
268
276
|
+ " " + items_str(names)
|
269
277
|
+ "..."
|
@@ -278,7 +286,11 @@ def _start_jobs(
|
|
278
286
|
)
|
279
287
|
if not kw.get('nopretty', False):
|
280
288
|
print_tuple(success_tuple)
|
281
|
-
|
289
|
+
|
290
|
+
if success_tuple[0]:
|
291
|
+
_successes.append(_name)
|
292
|
+
else:
|
293
|
+
_failures.append(_name)
|
282
294
|
|
283
295
|
msg = (
|
284
296
|
(("Successfully started job" + ("s" if len(_successes) != 1 else '')
|
@@ -287,29 +299,9 @@ def _start_jobs(
|
|
287
299
|
+ ("Failed to start job" + ("s" if len(_failures) != 1 else '')
|
288
300
|
+ f" {items_str(_failures)}." if _failures else '')
|
289
301
|
)
|
302
|
+
_install_healthcheck_job()
|
290
303
|
return len(_failures) == 0, msg
|
291
304
|
|
292
|
-
def _complete_start_jobs(
|
293
|
-
action: Optional[List[str]] = None,
|
294
|
-
line: str = '',
|
295
|
-
**kw
|
296
|
-
) -> List[str]:
|
297
|
-
from meerschaum.utils.daemon import get_daemon_ids
|
298
|
-
daemon_ids = get_daemon_ids()
|
299
|
-
if not action:
|
300
|
-
return daemon_ids
|
301
|
-
possibilities = []
|
302
|
-
_line_end = line.split(' ')[-1]
|
303
|
-
for daemon_id in daemon_ids:
|
304
|
-
if daemon_id in action:
|
305
|
-
continue
|
306
|
-
if _line_end == '':
|
307
|
-
possibilities.append(daemon_id)
|
308
|
-
continue
|
309
|
-
if daemon_id.startswith(action[-1]):
|
310
|
-
possibilities.append(daemon_id)
|
311
|
-
return possibilities
|
312
|
-
|
313
305
|
|
314
306
|
def _start_gui(
|
315
307
|
action: Optional[List[str]] = None,
|
@@ -546,6 +538,75 @@ def _complete_start_connectors(**kw) -> List[str]:
|
|
546
538
|
return _complete_show_connectors(**kw)
|
547
539
|
|
548
540
|
|
541
|
+
def _start_pipeline(
|
542
|
+
action: Optional[List[str]] = None,
|
543
|
+
sub_args: Optional[List[str]] = None,
|
544
|
+
loop: bool = False,
|
545
|
+
min_seconds: Union[float, int, None] = 1.0,
|
546
|
+
params: Optional[Dict[str, Any]] = None,
|
547
|
+
**kwargs
|
548
|
+
) -> SuccessTuple:
|
549
|
+
"""
|
550
|
+
Run a series of Meerschaum commands as a single action.
|
551
|
+
|
552
|
+
Add `:` to the end of chained arguments to apply additional flags to the pipeline.
|
553
|
+
|
554
|
+
Examples
|
555
|
+
--------
|
556
|
+
|
557
|
+
`sync pipes -i sql:local + sync pipes -i sql:main :: -s 'daily'`
|
558
|
+
|
559
|
+
`show version + show arguments :: --loop`
|
560
|
+
|
561
|
+
"""
|
562
|
+
import time
|
563
|
+
from meerschaum._internal.entry import entry
|
564
|
+
from meerschaum.utils.warnings import info, warn
|
565
|
+
from meerschaum.utils.misc import is_int
|
566
|
+
|
567
|
+
do_n_times = (
|
568
|
+
int(action[0].lstrip('x'))
|
569
|
+
if action and is_int(action[0].lstrip('x'))
|
570
|
+
else 1
|
571
|
+
)
|
572
|
+
|
573
|
+
if not sub_args:
|
574
|
+
return False, "Nothing to do."
|
575
|
+
|
576
|
+
if min_seconds is None:
|
577
|
+
min_seconds = 1.0
|
578
|
+
|
579
|
+
ran_n_times = 0
|
580
|
+
success, msg = False, "Did not run pipeline."
|
581
|
+
def run_loop():
|
582
|
+
nonlocal ran_n_times, success, msg
|
583
|
+
while True:
|
584
|
+
success, msg = entry(sub_args, _patch_args=params)
|
585
|
+
ran_n_times += 1
|
586
|
+
|
587
|
+
if not loop and do_n_times == 1:
|
588
|
+
break
|
589
|
+
|
590
|
+
if min_seconds != 0 and ran_n_times != do_n_times:
|
591
|
+
info(f"Sleeping for {min_seconds} seconds...")
|
592
|
+
time.sleep(min_seconds)
|
593
|
+
|
594
|
+
if loop:
|
595
|
+
continue
|
596
|
+
|
597
|
+
if ran_n_times >= do_n_times:
|
598
|
+
break
|
599
|
+
|
600
|
+
try:
|
601
|
+
run_loop()
|
602
|
+
except KeyboardInterrupt:
|
603
|
+
warn("Cancelled pipeline.", stack=False)
|
604
|
+
|
605
|
+
if do_n_times != 1:
|
606
|
+
info(f"Ran pipeline {ran_n_times} time" + ('s' if ran_n_times != 1 else '') + '.')
|
607
|
+
return success, msg
|
608
|
+
|
609
|
+
|
549
610
|
### NOTE: This must be the final statement of the module.
|
550
611
|
### Any subactions added below these lines will not
|
551
612
|
### be added to the `help` docstring.
|
meerschaum/actions/stop.py
CHANGED
@@ -27,13 +27,20 @@ def _complete_stop(
|
|
27
27
|
"""
|
28
28
|
Override the default Meerschaum `complete_` function.
|
29
29
|
"""
|
30
|
-
from meerschaum.actions.
|
30
|
+
from meerschaum.actions.delete import _complete_delete_jobs
|
31
|
+
from functools import partial
|
32
|
+
|
31
33
|
if action is None:
|
32
34
|
action = []
|
33
35
|
|
36
|
+
_complete_stop_jobs = partial(
|
37
|
+
_complete_delete_jobs,
|
38
|
+
_get_job_method=('running', 'paused', 'restart'),
|
39
|
+
)
|
40
|
+
|
34
41
|
options = {
|
35
|
-
'job' :
|
36
|
-
'jobs' :
|
42
|
+
'job' : _complete_stop_jobs,
|
43
|
+
'jobs' : _complete_stop_jobs,
|
37
44
|
}
|
38
45
|
|
39
46
|
if (
|
@@ -50,11 +57,13 @@ def _complete_stop(
|
|
50
57
|
|
51
58
|
def _stop_jobs(
|
52
59
|
action: Optional[List[str]] = None,
|
60
|
+
executor_keys: Optional[str] = None,
|
53
61
|
timeout_seconds: Optional[int] = None,
|
54
62
|
noask: bool = False,
|
55
63
|
force: bool = False,
|
56
64
|
yes: bool = False,
|
57
65
|
nopretty: bool = False,
|
66
|
+
debug: bool = False,
|
58
67
|
**kw
|
59
68
|
) -> SuccessTuple:
|
60
69
|
"""
|
@@ -62,66 +71,93 @@ def _stop_jobs(
|
|
62
71
|
|
63
72
|
To see running processes, run `show jobs`.
|
64
73
|
"""
|
74
|
+
from meerschaum.jobs import (
|
75
|
+
get_filtered_jobs,
|
76
|
+
get_running_jobs,
|
77
|
+
get_paused_jobs,
|
78
|
+
get_stopped_jobs,
|
79
|
+
get_restart_jobs,
|
80
|
+
)
|
65
81
|
from meerschaum.utils.formatting._jobs import pprint_jobs
|
66
82
|
from meerschaum.utils.daemon import (
|
67
83
|
get_filtered_daemons, get_running_daemons, get_stopped_daemons, get_paused_daemons,
|
68
84
|
)
|
69
85
|
from meerschaum.utils.warnings import warn
|
70
86
|
from meerschaum.utils.prompt import yes_no
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
87
|
+
|
88
|
+
jobs = get_filtered_jobs(executor_keys, action, warn=(not nopretty))
|
89
|
+
running_jobs = get_running_jobs(executor_keys, jobs)
|
90
|
+
paused_jobs = get_paused_jobs(executor_keys, jobs)
|
91
|
+
restart_jobs = get_restart_jobs(executor_keys, jobs)
|
92
|
+
stopped_jobs = {
|
93
|
+
name: job
|
94
|
+
for name, job in get_stopped_jobs(executor_keys, jobs).items()
|
95
|
+
if name not in restart_jobs
|
96
|
+
}
|
97
|
+
|
98
|
+
jobs_to_stop = {
|
99
|
+
**running_jobs,
|
100
|
+
**paused_jobs,
|
101
|
+
**restart_jobs,
|
102
|
+
}
|
103
|
+
|
104
|
+
if action and stopped_jobs and not nopretty:
|
76
105
|
warn(
|
77
|
-
|
78
|
-
|
79
|
-
|
106
|
+
"Skipping stopped job"
|
107
|
+
+ ("s" if len(stopped_jobs) != 1 else '')
|
108
|
+
+ " '"
|
109
|
+
+ ("', '".join(name for name in stopped_jobs)) + "'.",
|
110
|
+
stack=False,
|
80
111
|
)
|
81
112
|
|
82
|
-
|
83
|
-
|
84
|
-
return False, "No running or paused jobs to stop."
|
113
|
+
if not jobs_to_stop:
|
114
|
+
return False, "No running, paused or restarting jobs to stop."
|
85
115
|
|
86
116
|
if not action:
|
87
117
|
if not force:
|
88
|
-
pprint_jobs(
|
118
|
+
pprint_jobs(jobs_to_stop)
|
89
119
|
if not yes_no(
|
90
120
|
"Stop the above jobs?",
|
91
121
|
noask=noask, yes=yes, default='n'
|
92
122
|
):
|
93
123
|
return False, "No jobs were stopped."
|
94
124
|
|
95
|
-
|
96
|
-
for
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
warn(
|
103
|
-
f"Failed to gracefully quit job '{d.daemon_id}', attempting to terminate:\n "
|
104
|
-
+ f"{quit_msg}",
|
105
|
-
stack = False,
|
106
|
-
)
|
107
|
-
|
108
|
-
kill_success, kill_msg = d.kill(timeout=timeout_seconds)
|
109
|
-
if kill_success:
|
110
|
-
_kill_daemons.append(d)
|
111
|
-
continue
|
112
|
-
if not nopretty:
|
113
|
-
warn(f"Failed to kill job '{d.daemon_id}' (PID {d.pid}):\n{kill_msg}", stack=False)
|
125
|
+
job_success_tuples = {}
|
126
|
+
for name, job in jobs_to_stop.items():
|
127
|
+
stop_success, stop_msg = job.stop(
|
128
|
+
timeout_seconds=timeout_seconds,
|
129
|
+
debug=debug,
|
130
|
+
)
|
131
|
+
job_success_tuples[name] = (stop_success, stop_msg)
|
114
132
|
|
133
|
+
num_success = sum(
|
134
|
+
(
|
135
|
+
1
|
136
|
+
for name, (stop_success, stop_msg) in job_success_tuples.items()
|
137
|
+
if stop_success
|
138
|
+
)
|
139
|
+
)
|
140
|
+
num_fail = sum(
|
141
|
+
(
|
142
|
+
1
|
143
|
+
for name, (stop_success, stop_msg) in job_success_tuples.items()
|
144
|
+
if not stop_success
|
145
|
+
)
|
146
|
+
)
|
147
|
+
success = num_success > 0
|
115
148
|
msg = (
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
+ (("\n" if _quit_daemons else "")
|
120
|
-
+ ("Killed job" + ("s" if len(_kill_daemons) != 1 else '') +
|
121
|
-
" '" + "', '".join([d.daemon_id for d in _kill_daemons]) + "'.")
|
122
|
-
if _kill_daemons else '')
|
149
|
+
f"Stopped {num_success} job"
|
150
|
+
+ ('s' if num_success != 1 else '')
|
151
|
+
+ '.'
|
123
152
|
)
|
124
|
-
|
153
|
+
if num_fail > 0:
|
154
|
+
msg += (
|
155
|
+
f"\nFailed to stop {num_fail} job"
|
156
|
+
+ ('s' if num_fail != 1 else '')
|
157
|
+
+ '.'
|
158
|
+
)
|
159
|
+
|
160
|
+
return success, msg
|
125
161
|
|
126
162
|
|
127
163
|
### NOTE: This must be the final statement of the module.
|
meerschaum/api/_events.py
CHANGED
@@ -19,14 +19,24 @@ from meerschaum.utils.debug import dprint
|
|
19
19
|
from meerschaum.connectors.poll import retry_connect
|
20
20
|
from meerschaum.utils.warnings import warn
|
21
21
|
from meerschaum._internal.term.tools import is_webterm_running
|
22
|
+
from meerschaum.jobs import (
|
23
|
+
start_check_jobs_thread,
|
24
|
+
stop_check_jobs_thread,
|
25
|
+
get_executor_keys_from_context,
|
26
|
+
)
|
27
|
+
|
28
|
+
_check_jobs_thread = None
|
22
29
|
|
23
30
|
@app.on_event("startup")
|
24
31
|
async def startup():
|
25
|
-
|
32
|
+
"""
|
33
|
+
Connect to the instance database and begin monitoring jobs.
|
34
|
+
"""
|
26
35
|
try:
|
27
36
|
if not no_dash:
|
28
37
|
from meerschaum.api.dash.webterm import start_webterm
|
29
38
|
start_webterm()
|
39
|
+
|
30
40
|
connected = retry_connect(
|
31
41
|
get_api_connector(),
|
32
42
|
workers = get_uvicorn_config().get('workers', None),
|
@@ -36,18 +46,32 @@ async def startup():
|
|
36
46
|
import traceback
|
37
47
|
traceback.print_exc()
|
38
48
|
connected = False
|
49
|
+
|
39
50
|
if not connected:
|
40
51
|
await shutdown()
|
41
52
|
os._exit(1)
|
42
53
|
|
54
|
+
if get_executor_keys_from_context() == 'local':
|
55
|
+
start_check_jobs_thread()
|
56
|
+
|
43
57
|
|
44
58
|
@app.on_event("shutdown")
|
45
59
|
async def shutdown():
|
60
|
+
"""
|
61
|
+
Close the database connection and stop monitoring jobs.
|
62
|
+
"""
|
46
63
|
if debug:
|
47
64
|
dprint("Closing connection...")
|
48
65
|
if get_api_connector().type == 'sql':
|
49
66
|
get_api_connector().engine.dispose()
|
50
67
|
|
68
|
+
if get_executor_keys_from_context() == 'local':
|
69
|
+
stop_check_jobs_thread()
|
70
|
+
|
71
|
+
from meerschaum.api.routes._actions import _temp_jobs
|
72
|
+
for name, job in _temp_jobs.items():
|
73
|
+
job.delete()
|
74
|
+
|
51
75
|
### Terminate any running jobs left over.
|
52
76
|
if 'meerschaum.api.dash' in sys.modules:
|
53
77
|
from meerschaum.api.dash.actions import running_jobs, stop_action
|
meerschaum/api/_oauth2.py
CHANGED
@@ -30,6 +30,7 @@ class CustomOAuth2PasswordRequestForm:
|
|
30
30
|
self.client_id = client_id
|
31
31
|
self.client_secret = client_secret
|
32
32
|
|
33
|
+
|
33
34
|
LoginManager = fastapi_login.LoginManager
|
34
35
|
def generate_secret_key() -> str:
|
35
36
|
"""
|
@@ -46,5 +47,6 @@ def generate_secret_key() -> str:
|
|
46
47
|
|
47
48
|
return secret_key.encode('utf-8')
|
48
49
|
|
50
|
+
|
49
51
|
SECRET = generate_secret_key()
|
50
52
|
manager = LoginManager(SECRET, token_url=endpoints['login'])
|
meerschaum/api/_websockets.py
CHANGED
@@ -23,8 +23,8 @@ sessions = {}
|
|
23
23
|
@app.websocket('/dash/ws')
|
24
24
|
@app.websocket(_websocket_endpoint)
|
25
25
|
async def websocket_endpoint(
|
26
|
-
|
27
|
-
|
26
|
+
websocket: fastapi.WebSocket,
|
27
|
+
):
|
28
28
|
"""
|
29
29
|
Communicate with the Web Interface over a websocket.
|
30
30
|
"""
|