meerschaum 2.2.7__py3-none-any.whl → 2.3.0.dev3__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/_internal/arguments/_parse_arguments.py +10 -3
- meerschaum/_internal/arguments/_parser.py +44 -15
- meerschaum/_internal/entry.py +22 -1
- meerschaum/_internal/shell/Shell.py +129 -31
- meerschaum/actions/__init__.py +8 -6
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +108 -0
- meerschaum/actions/delete.py +35 -26
- meerschaum/actions/show.py +119 -148
- meerschaum/actions/start.py +85 -75
- meerschaum/actions/stop.py +68 -39
- meerschaum/api/_events.py +18 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/jobs.py +5 -2
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +122 -44
- meerschaum/api/routes/_jobs.py +371 -0
- meerschaum/api/routes/_pipes.py +5 -5
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_paths.py +1 -0
- meerschaum/config/_shell.py +8 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +10 -0
- meerschaum/connectors/__init__.py +9 -11
- meerschaum/connectors/api/APIConnector.py +18 -1
- meerschaum/connectors/api/_actions.py +60 -71
- meerschaum/connectors/api/_jobs.py +330 -0
- meerschaum/connectors/parse.py +23 -7
- meerschaum/plugins/__init__.py +89 -5
- meerschaum/utils/daemon/Daemon.py +255 -30
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +5 -5
- meerschaum/utils/daemon/RotatingFile.py +10 -6
- meerschaum/utils/daemon/StdinFile.py +110 -0
- meerschaum/utils/daemon/__init__.py +13 -7
- meerschaum/utils/formatting/__init__.py +2 -1
- meerschaum/utils/formatting/_jobs.py +83 -54
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/jobs/_Job.py +710 -0
- meerschaum/utils/jobs/__init__.py +245 -0
- meerschaum/utils/misc.py +18 -17
- meerschaum/utils/packages/_packages.py +2 -2
- meerschaum/utils/prompt.py +16 -8
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/METADATA +9 -9
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/RECORD +52 -46
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.7.dist-info → meerschaum-2.3.0.dev3.dist-info}/zip-safe +0 -0
@@ -63,6 +63,9 @@ class RotatingFile(io.IOBase):
|
|
63
63
|
|
64
64
|
write_timestamps: bool, default False
|
65
65
|
If `True`, prepend the current UTC timestamp to each line of the file.
|
66
|
+
|
67
|
+
timestamp_format: str, default '%Y-%m-%d %H:%M'
|
68
|
+
If `write_timestamps` is `True`, use this format for the timestamps.
|
66
69
|
"""
|
67
70
|
self.file_path = pathlib.Path(file_path)
|
68
71
|
if num_files_to_keep is None:
|
@@ -232,10 +235,10 @@ class RotatingFile(io.IOBase):
|
|
232
235
|
|
233
236
|
|
234
237
|
def refresh_files(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
238
|
+
self,
|
239
|
+
potential_new_len: int = 0,
|
240
|
+
start_interception: bool = False,
|
241
|
+
) -> '_io.TextUIWrapper':
|
239
242
|
"""
|
240
243
|
Check the state of the subfiles.
|
241
244
|
If the latest subfile is too large, create a new file and delete old ones.
|
@@ -339,7 +342,7 @@ class RotatingFile(io.IOBase):
|
|
339
342
|
|
340
343
|
def get_timestamp_prefix_str(self) -> str:
|
341
344
|
"""
|
342
|
-
Return the current minute
|
345
|
+
Return the current minute prefix string.
|
343
346
|
"""
|
344
347
|
return datetime.now(timezone.utc).strftime(self.timestamp_format) + ' | '
|
345
348
|
|
@@ -568,7 +571,8 @@ class RotatingFile(io.IOBase):
|
|
568
571
|
return
|
569
572
|
|
570
573
|
self._cursor = (max_ix, position)
|
571
|
-
self._current_file_obj
|
574
|
+
if self._current_file_obj is not None:
|
575
|
+
self._current_file_obj.seek(position)
|
572
576
|
|
573
577
|
|
574
578
|
def flush(self) -> None:
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Create a file manager to pass STDIN to the Daemon.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import io
|
10
|
+
import pathlib
|
11
|
+
import time
|
12
|
+
import os
|
13
|
+
import selectors
|
14
|
+
import traceback
|
15
|
+
|
16
|
+
from meerschaum.utils.typing import Optional
|
17
|
+
from meerschaum.utils.warnings import warn
|
18
|
+
|
19
|
+
|
20
|
+
class StdinFile(io.TextIOBase):
|
21
|
+
"""
|
22
|
+
Redirect user input into a Daemon's context.
|
23
|
+
"""
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
file_path: pathlib.Path,
|
27
|
+
lock_file_path: Optional[pathlib.Path] = None,
|
28
|
+
):
|
29
|
+
self.file_path = file_path
|
30
|
+
self.blocking_file_path = (
|
31
|
+
lock_file_path
|
32
|
+
if lock_file_path is not None
|
33
|
+
else (file_path.parent / (file_path.name + '.block'))
|
34
|
+
)
|
35
|
+
self._file_handler = None
|
36
|
+
self._fd = None
|
37
|
+
self.sel = selectors.DefaultSelector()
|
38
|
+
|
39
|
+
@property
|
40
|
+
def file_handler(self):
|
41
|
+
"""
|
42
|
+
Return the read file handler to the provided file path.
|
43
|
+
"""
|
44
|
+
if self._file_handler is not None:
|
45
|
+
return self._file_handler
|
46
|
+
|
47
|
+
if self.file_path.exists():
|
48
|
+
self.file_path.unlink()
|
49
|
+
|
50
|
+
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
51
|
+
|
52
|
+
self._fd = os.open(self.file_path, os.O_RDONLY | os.O_NONBLOCK)
|
53
|
+
self._file_handler = os.fdopen(self._fd, 'rb', buffering=0)
|
54
|
+
self.sel.register(self._file_handler, selectors.EVENT_READ)
|
55
|
+
return self._file_handler
|
56
|
+
|
57
|
+
def write(self, data):
|
58
|
+
if isinstance(data, str):
|
59
|
+
data = data.encode('utf-8')
|
60
|
+
|
61
|
+
with open(self.file_path, 'wb') as f:
|
62
|
+
f.write(data)
|
63
|
+
|
64
|
+
def fileno(self):
|
65
|
+
fileno = self.file_handler.fileno()
|
66
|
+
return fileno
|
67
|
+
|
68
|
+
def read(self, size=-1):
|
69
|
+
"""
|
70
|
+
Read from the FIFO pipe, blocking on EOFError.
|
71
|
+
"""
|
72
|
+
_ = self.file_handler
|
73
|
+
while True:
|
74
|
+
try:
|
75
|
+
data = self._file_handler.read(size)
|
76
|
+
if data:
|
77
|
+
try:
|
78
|
+
if self.blocking_file_path.exists():
|
79
|
+
self.blocking_file_path.unlink()
|
80
|
+
except Exception:
|
81
|
+
warn(traceback.format_exc())
|
82
|
+
return data.decode('utf-8')
|
83
|
+
except (OSError, EOFError):
|
84
|
+
pass
|
85
|
+
|
86
|
+
self.blocking_file_path.touch()
|
87
|
+
time.sleep(0.1)
|
88
|
+
|
89
|
+
def readline(self, size=-1):
|
90
|
+
line = ''
|
91
|
+
while True:
|
92
|
+
data = self.read(1)
|
93
|
+
if not data or data == '\n':
|
94
|
+
break
|
95
|
+
line += data
|
96
|
+
|
97
|
+
return line
|
98
|
+
|
99
|
+
def close(self):
|
100
|
+
if self._file_handler is not None:
|
101
|
+
self.sel.unregister(self._file_handler)
|
102
|
+
self._file_handler.close()
|
103
|
+
os.close(self._fd)
|
104
|
+
self._file_handler = None
|
105
|
+
self._fd = None
|
106
|
+
|
107
|
+
super().close()
|
108
|
+
|
109
|
+
def is_open(self):
|
110
|
+
return self._file_handler is not None
|
@@ -76,7 +76,7 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
|
76
76
|
filtered_sysargs,
|
77
77
|
daemon_id=_args.get('name', None) if _args else None,
|
78
78
|
label=label,
|
79
|
-
keep_daemon_output=('--rm' not in sysargs),
|
79
|
+
keep_daemon_output=('--rm' not in (sysargs or [])),
|
80
80
|
)
|
81
81
|
return success_tuple
|
82
82
|
|
@@ -181,7 +181,11 @@ def get_daemon_ids() -> List[str]:
|
|
181
181
|
"""
|
182
182
|
Return the IDs of all daemons on disk.
|
183
183
|
"""
|
184
|
-
return
|
184
|
+
return [
|
185
|
+
daemon_dir
|
186
|
+
for daemon_dir in sorted(os.listdir(DAEMON_RESOURCES_PATH))
|
187
|
+
if (DAEMON_RESOURCES_PATH / daemon_dir / 'properties.json').exists()
|
188
|
+
]
|
185
189
|
|
186
190
|
|
187
191
|
def get_running_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
@@ -225,10 +229,11 @@ def get_stopped_daemons(daemons: Optional[List[Daemon]] = None) -> List[Daemon]:
|
|
225
229
|
|
226
230
|
|
227
231
|
def get_filtered_daemons(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
"""
|
232
|
+
filter_list: Optional[List[str]] = None,
|
233
|
+
warn: bool = False,
|
234
|
+
) -> List[Daemon]:
|
235
|
+
"""
|
236
|
+
Return a list of `Daemons` filtered by a list of `daemon_ids`.
|
232
237
|
Only `Daemons` that exist are returned.
|
233
238
|
|
234
239
|
If `filter_list` is `None` or empty, return all `Daemons` (from `get_daemons()`).
|
@@ -250,13 +255,14 @@ def get_filtered_daemons(
|
|
250
255
|
if not filter_list:
|
251
256
|
daemons = get_daemons()
|
252
257
|
return [d for d in daemons if not d.hidden]
|
258
|
+
|
253
259
|
from meerschaum.utils.warnings import warn as _warn
|
254
260
|
daemons = []
|
255
261
|
for d_id in filter_list:
|
256
262
|
try:
|
257
263
|
d = Daemon(daemon_id=d_id)
|
258
264
|
_exists = d.path.exists()
|
259
|
-
except Exception
|
265
|
+
except Exception:
|
260
266
|
_exists = False
|
261
267
|
if not _exists:
|
262
268
|
if warn:
|
@@ -28,9 +28,10 @@ _attrs = {
|
|
28
28
|
'ANSI': None,
|
29
29
|
'UNICODE': None,
|
30
30
|
'CHARSET': None,
|
31
|
+
'RESET': '\033[0m',
|
31
32
|
}
|
32
33
|
__all__ = sorted([
|
33
|
-
'ANSI', 'CHARSET', 'UNICODE',
|
34
|
+
'ANSI', 'CHARSET', 'UNICODE', 'RESET',
|
34
35
|
'colored',
|
35
36
|
'translate_rich_to_termcolor',
|
36
37
|
'get_console',
|
@@ -7,46 +7,52 @@ Print jobs information.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
|
10
13
|
import meerschaum as mrsm
|
11
|
-
from meerschaum.utils.typing import List, Optional, Any, is_success_tuple
|
12
|
-
from meerschaum.utils.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
from meerschaum.utils.typing import List, Optional, Any, is_success_tuple, Dict
|
15
|
+
from meerschaum.utils.jobs import (
|
16
|
+
Job,
|
17
|
+
get_jobs,
|
18
|
+
get_running_jobs,
|
19
|
+
get_stopped_jobs,
|
20
|
+
get_paused_jobs,
|
18
21
|
)
|
22
|
+
from meerschaum.config import get_config
|
23
|
+
|
19
24
|
|
20
25
|
def pprint_jobs(
|
21
|
-
|
26
|
+
jobs: Dict[str, Job],
|
22
27
|
nopretty: bool = False,
|
23
28
|
):
|
24
29
|
"""Pretty-print a list of Daemons."""
|
25
30
|
from meerschaum.utils.formatting import make_header
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
running_jobs = get_running_jobs(jobs=jobs)
|
33
|
+
paused_jobs = get_paused_jobs(jobs=jobs)
|
34
|
+
stopped_jobs = get_stopped_jobs(jobs=jobs)
|
35
|
+
executor_keys = (list(jobs.values())[0].executor_keys if jobs else None) or 'local'
|
30
36
|
|
31
37
|
def _nopretty_print():
|
32
38
|
from meerschaum.utils.misc import print_options
|
33
|
-
if
|
39
|
+
if running_jobs:
|
34
40
|
if not nopretty:
|
35
41
|
print('\n' + make_header('Running jobs'))
|
36
|
-
for
|
37
|
-
pprint_job(
|
42
|
+
for name, job in running_jobs.items():
|
43
|
+
pprint_job(job, nopretty=nopretty)
|
38
44
|
|
39
|
-
if
|
45
|
+
if paused_jobs:
|
40
46
|
if not nopretty:
|
41
47
|
print('\n' + make_header('Paused jobs'))
|
42
|
-
for
|
43
|
-
pprint_job(
|
48
|
+
for name, job in paused_jobs.items():
|
49
|
+
pprint_job(job, nopretty=nopretty)
|
44
50
|
|
45
|
-
if
|
51
|
+
if stopped_jobs:
|
46
52
|
if not nopretty:
|
47
53
|
print('\n' + make_header('Stopped jobs'))
|
48
|
-
for
|
49
|
-
pprint_job(
|
54
|
+
for name, job in stopped_jobs.items():
|
55
|
+
pprint_job(job, nopretty=nopretty)
|
50
56
|
|
51
57
|
def _pretty_print():
|
52
58
|
from meerschaum.utils.formatting import get_console, UNICODE, ANSI, format_success_tuple
|
@@ -56,19 +62,17 @@ def pprint_jobs(
|
|
56
62
|
'rich.table', 'rich.text', 'rich.box', 'rich.json', 'rich.panel', 'rich.console',
|
57
63
|
)
|
58
64
|
table = rich_table.Table(
|
59
|
-
title
|
60
|
-
box
|
61
|
-
show_lines
|
62
|
-
show_header
|
65
|
+
title=rich_text.Text(f"\nJobs on Executor '{executor_keys}'"),
|
66
|
+
box=(rich_box.ROUNDED if UNICODE else rich_box.ASCII),
|
67
|
+
show_lines=True,
|
68
|
+
show_header=ANSI,
|
63
69
|
)
|
64
70
|
table.add_column("Name", justify='right', style=('magenta' if ANSI else ''))
|
65
71
|
table.add_column("Command")
|
66
72
|
table.add_column("Status")
|
67
73
|
|
68
|
-
def get_success_text(
|
69
|
-
success_tuple =
|
70
|
-
if isinstance(success_tuple, list):
|
71
|
-
success_tuple = tuple(success_tuple)
|
74
|
+
def get_success_text(job):
|
75
|
+
success_tuple = job.result
|
72
76
|
if not is_success_tuple(success_tuple):
|
73
77
|
return rich_text.Text('')
|
74
78
|
|
@@ -92,38 +96,43 @@ def pprint_jobs(
|
|
92
96
|
return rich_text.Text('\n') + success_tuple_text
|
93
97
|
|
94
98
|
|
95
|
-
for
|
96
|
-
if
|
99
|
+
for name, job in running_jobs.items():
|
100
|
+
if job.hidden:
|
97
101
|
continue
|
102
|
+
|
103
|
+
status_text = (
|
104
|
+
rich_text.Text(job.status, style=('green' if ANSI else ''))
|
105
|
+
if not job.is_blocking_on_stdin()
|
106
|
+
else rich_text.Text('waiting for input', style=('yellow' if ANSI else ''))
|
107
|
+
)
|
108
|
+
|
98
109
|
table.add_row(
|
99
|
-
|
100
|
-
|
101
|
-
rich_console.Group(
|
102
|
-
rich_text.Text(d.status, style=('green' if ANSI else '')),
|
103
|
-
),
|
110
|
+
job.name,
|
111
|
+
job.label,
|
112
|
+
rich_console.Group(status_text),
|
104
113
|
)
|
105
114
|
|
106
|
-
for
|
107
|
-
if
|
115
|
+
for name, job in paused_jobs.items():
|
116
|
+
if job.hidden:
|
108
117
|
continue
|
109
118
|
table.add_row(
|
110
|
-
|
111
|
-
|
119
|
+
job.name,
|
120
|
+
job.label,
|
112
121
|
rich_console.Group(
|
113
|
-
rich_text.Text(
|
122
|
+
rich_text.Text(job.status, style=('yellow' if ANSI else '')),
|
114
123
|
),
|
115
124
|
)
|
116
125
|
|
117
|
-
for
|
118
|
-
if
|
126
|
+
for name, job in stopped_jobs.items():
|
127
|
+
if job.hidden:
|
119
128
|
continue
|
120
129
|
|
121
130
|
table.add_row(
|
122
|
-
|
123
|
-
|
131
|
+
job.name,
|
132
|
+
job.label,
|
124
133
|
rich_console.Group(
|
125
|
-
rich_text.Text(
|
126
|
-
get_success_text(
|
134
|
+
rich_text.Text(job.status, style=('red' if ANSI else '')),
|
135
|
+
get_success_text(job)
|
127
136
|
),
|
128
137
|
)
|
129
138
|
get_console().print(table)
|
@@ -133,15 +142,35 @@ def pprint_jobs(
|
|
133
142
|
|
134
143
|
|
135
144
|
def pprint_job(
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
"""Pretty-print a single
|
140
|
-
if
|
145
|
+
job: Job,
|
146
|
+
nopretty: bool = False,
|
147
|
+
):
|
148
|
+
"""Pretty-print a single `Job`."""
|
149
|
+
if job.hidden:
|
141
150
|
return
|
151
|
+
|
142
152
|
from meerschaum.utils.warnings import info
|
143
153
|
if not nopretty:
|
144
|
-
info(f"Command for job '{
|
145
|
-
print('\n' +
|
154
|
+
info(f"Command for job '{job.name}':")
|
155
|
+
print('\n' + job.label + '\n')
|
146
156
|
else:
|
147
|
-
print(
|
157
|
+
print(job.name)
|
158
|
+
|
159
|
+
|
160
|
+
def strip_timestamp_from_line(line: str) -> str:
|
161
|
+
"""
|
162
|
+
Remove the leading timestamp from a job's line (if present).
|
163
|
+
"""
|
164
|
+
now = datetime.now(timezone.utc)
|
165
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
166
|
+
now_str = now.strftime(timestamp_format)
|
167
|
+
|
168
|
+
date_prefix_str = line[:len(now_str)]
|
169
|
+
try:
|
170
|
+
line_timestamp = datetime.strptime(date_prefix_str, timestamp_format)
|
171
|
+
except Exception:
|
172
|
+
line_timestamp = None
|
173
|
+
if line_timestamp:
|
174
|
+
line = line[(len(now_str) + 3):]
|
175
|
+
|
176
|
+
return line
|
@@ -47,9 +47,15 @@ def clear_screen(debug: bool = False) -> bool:
|
|
47
47
|
from meerschaum.utils.formatting import ANSI, get_console
|
48
48
|
from meerschaum.utils.debug import dprint
|
49
49
|
from meerschaum.config import get_config
|
50
|
+
from meerschaum.utils.daemon import running_in_daemon
|
50
51
|
global _tried_clear_command
|
52
|
+
|
53
|
+
if running_in_daemon():
|
54
|
+
return True
|
55
|
+
|
51
56
|
if not get_config('shell', 'clear_screen'):
|
52
57
|
return True
|
58
|
+
|
53
59
|
print("", end="", flush=True)
|
54
60
|
if debug:
|
55
61
|
dprint("Skipping screen clear.")
|