meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc7__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/_internal/arguments/_parser.py +14 -2
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +434 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +113 -19
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +3 -1
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +2 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +29 -0
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pipes.py +72 -36
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +20 -10
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +93 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +45 -71
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +135 -35
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/daemon/Daemon.py +195 -41
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_shell.py +16 -6
- meerschaum/utils/misc.py +18 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +33 -5
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +171 -144
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +116 -110
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -140,7 +140,11 @@ def parse_help(sysargs: Union[List[str], Dict[str, Any]]) -> None:
|
|
140
140
|
### Check for subactions.
|
141
141
|
if len(args['action']) > 1:
|
142
142
|
try:
|
143
|
-
|
143
|
+
subactions = get_subactions(args['action'][0])
|
144
|
+
subaction_name = args['action'][1]
|
145
|
+
if subaction_name not in subactions:
|
146
|
+
subaction_name = subaction_name + 's'
|
147
|
+
subaction = subactions[subaction_name]
|
144
148
|
except Exception:
|
145
149
|
subaction = None
|
146
150
|
if subaction is not None:
|
@@ -377,7 +381,7 @@ groups['sync'].add_argument(
|
|
377
381
|
groups['sync'].add_argument(
|
378
382
|
'--cache', action='store_true',
|
379
383
|
help=(
|
380
|
-
"
|
384
|
+
"Sync pipes' metadata to disk."
|
381
385
|
)
|
382
386
|
)
|
383
387
|
|
@@ -389,6 +393,10 @@ groups['api'].add_argument(
|
|
389
393
|
'--host', type=str,
|
390
394
|
help="The host address to bind to for the API server. Defaults to '0.0.0.0'."
|
391
395
|
)
|
396
|
+
groups['api'].add_argument(
|
397
|
+
'--webterm-port', type=int,
|
398
|
+
help="The port on which to run the webterm server.",
|
399
|
+
)
|
392
400
|
groups['api'].add_argument(
|
393
401
|
'-w', '--workers', type=int,
|
394
402
|
help = "How many workers to run a concurrent action (e.g. running the API or syncing pipes)"
|
@@ -508,3 +516,7 @@ groups['misc'].add_argument(
|
|
508
516
|
+ "Default behavior is to only allow recognized actions."
|
509
517
|
)
|
510
518
|
)
|
519
|
+
groups['misc'].add_argument(
|
520
|
+
'--no-daemon', action='store_true',
|
521
|
+
help="Do not use the Meerschaum CLI daemon to execute the action.",
|
522
|
+
)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define the CLI daemons utilities.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import threading
|
10
|
+
import pathlib
|
11
|
+
from typing import Optional, List
|
12
|
+
|
13
|
+
import meerschaum as mrsm
|
14
|
+
|
15
|
+
|
16
|
+
def get_cli_daemon(ix: Optional[int] = None):
|
17
|
+
"""
|
18
|
+
Get the CLI daemon.
|
19
|
+
"""
|
20
|
+
from meerschaum._internal.cli.workers import ActionWorker
|
21
|
+
ix = ix if ix is not None else get_available_cli_daemon_ix()
|
22
|
+
return ActionWorker(ix)
|
23
|
+
|
24
|
+
|
25
|
+
def get_cli_lock_path(ix: int) -> pathlib.Path:
|
26
|
+
"""
|
27
|
+
Return the path to a CLI daemon's lock file.
|
28
|
+
"""
|
29
|
+
from meerschaum.config.paths import CLI_RESOURCES_PATH
|
30
|
+
return CLI_RESOURCES_PATH / f"ix-{ix}.lock"
|
31
|
+
|
32
|
+
|
33
|
+
def get_cli_session_id() -> str:
|
34
|
+
"""
|
35
|
+
Return the session ID to use for the current process and thread.
|
36
|
+
"""
|
37
|
+
return f"{os.getpid()}.{threading.current_thread().ident}"
|
38
|
+
|
39
|
+
|
40
|
+
def get_available_cli_daemon_ix() -> int:
|
41
|
+
"""
|
42
|
+
Return the index for an available CLI daemon.
|
43
|
+
"""
|
44
|
+
max_ix = mrsm.get_config('system', 'cli', 'max_daemons') - 1
|
45
|
+
ix = 0
|
46
|
+
while True:
|
47
|
+
lock_path = get_cli_lock_path(ix)
|
48
|
+
if not lock_path.exists():
|
49
|
+
return ix
|
50
|
+
|
51
|
+
ix += 1
|
52
|
+
if ix > max_ix:
|
53
|
+
raise EnvironmentError("Too many CLI daemons are running.")
|
54
|
+
|
55
|
+
|
56
|
+
def get_existing_cli_daemon_indices() -> List[int]:
|
57
|
+
"""
|
58
|
+
Return a list of the existing CLI daemons.
|
59
|
+
"""
|
60
|
+
from meerschaum.utils.daemon import get_daemon_ids
|
61
|
+
daemon_ids = [daemon_id for daemon_id in get_daemon_ids() if daemon_id.startswith('.cli.')]
|
62
|
+
indices = []
|
63
|
+
|
64
|
+
for daemon_id in daemon_ids:
|
65
|
+
try:
|
66
|
+
ix = int(daemon_id[len('.cli.'):])
|
67
|
+
indices.append(ix)
|
68
|
+
except Exception:
|
69
|
+
pass
|
70
|
+
|
71
|
+
return indices
|
72
|
+
|
73
|
+
|
74
|
+
def get_existing_cli_daemons() -> 'List[ActionWorker]':
|
75
|
+
"""
|
76
|
+
Return a list of the existing CLI daemons.
|
77
|
+
"""
|
78
|
+
from meerschaum._internal.cli.workers import ActionWorker
|
79
|
+
indices = get_existing_cli_daemon_indices()
|
80
|
+
return [
|
81
|
+
ActionWorker(ix)
|
82
|
+
for ix in indices
|
83
|
+
]
|
84
|
+
|
85
|
+
|
86
|
+
def _get_cli_session_dir_path(session_id: str) -> pathlib.Path:
|
87
|
+
"""
|
88
|
+
Return the path to the file handles for the CLI session.
|
89
|
+
"""
|
90
|
+
from meerschaum.config.paths import CLI_RESOURCES_PATH
|
91
|
+
return CLI_RESOURCES_PATH / session_id
|
92
|
+
|
93
|
+
|
94
|
+
def _get_cli_stream_path(
|
95
|
+
session_id: str,
|
96
|
+
action_id: str,
|
97
|
+
stream: str = 'stdout',
|
98
|
+
) -> pathlib.Path:
|
99
|
+
"""
|
100
|
+
Return the file path for the stdout / stderr file stream.
|
101
|
+
"""
|
102
|
+
session_dir_path = _get_cli_session_dir_path(session_id)
|
103
|
+
return session_dir_path / f'{action_id}.{stream}'
|
@@ -0,0 +1,220 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# vim:fenc=utf-8
|
3
|
+
|
4
|
+
"""
|
5
|
+
Define the CLI daemon entrypoint.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import time
|
10
|
+
import uuid
|
11
|
+
import shlex
|
12
|
+
import shutil
|
13
|
+
import traceback
|
14
|
+
import signal
|
15
|
+
from typing import Optional, Dict, List, Any
|
16
|
+
|
17
|
+
import meerschaum as mrsm
|
18
|
+
|
19
|
+
|
20
|
+
def entry_with_daemon(
|
21
|
+
sysargs: Optional[List[str]] = None,
|
22
|
+
_patch_args: Optional[Dict[str, Any]] = None,
|
23
|
+
_use_cli_daemon: bool = True,
|
24
|
+
_session_id: Optional[str] = None,
|
25
|
+
) -> mrsm.SuccessTuple:
|
26
|
+
"""
|
27
|
+
Parse arguments and launch a Meerschaum action.
|
28
|
+
|
29
|
+
Returns
|
30
|
+
-------
|
31
|
+
A `SuccessTuple` indicating success.
|
32
|
+
"""
|
33
|
+
from meerschaum.actions import get_action
|
34
|
+
from meerschaum.plugins import load_plugins, _actions_daemon_enabled
|
35
|
+
from meerschaum._internal.entry import entry_without_daemon
|
36
|
+
from meerschaum._internal.cli.workers import ActionWorker
|
37
|
+
from meerschaum._internal.cli.daemons import (
|
38
|
+
get_available_cli_daemon_ix,
|
39
|
+
get_cli_session_id,
|
40
|
+
)
|
41
|
+
from meerschaum.config import get_possible_keys
|
42
|
+
from meerschaum._internal.arguments import split_pipeline_sysargs, split_chained_sysargs
|
43
|
+
daemon_is_ready = True
|
44
|
+
|
45
|
+
load_plugins(skip_if_loaded=True)
|
46
|
+
|
47
|
+
found_acceptable_prefix = False
|
48
|
+
found_unacceptable_prefix = False
|
49
|
+
found_disabled_action = False
|
50
|
+
allowed_prefixes = (
|
51
|
+
mrsm.get_config('system', 'cli', 'allowed_prefixes')
|
52
|
+
)
|
53
|
+
disallowed_prefixes = (
|
54
|
+
mrsm.get_config('system', 'cli', 'disallowed_prefixes')
|
55
|
+
)
|
56
|
+
refresh_seconds = mrsm.get_config('system', 'cli', 'refresh_seconds')
|
57
|
+
sysargs_str = sysargs if isinstance(sysargs, str) else shlex.join(sysargs or [])
|
58
|
+
debug = ' --debug' in sysargs_str
|
59
|
+
_sysargs = shlex.split(sysargs_str)
|
60
|
+
_sysargs, _pipeline_args = split_pipeline_sysargs(_sysargs)
|
61
|
+
_chained_sysargs = split_chained_sysargs(_sysargs)
|
62
|
+
_action_functions = {
|
63
|
+
_action_func.__name__: _action_func
|
64
|
+
for _step_sysargs in _chained_sysargs
|
65
|
+
if (_action_func := get_action(_step_sysargs))
|
66
|
+
}
|
67
|
+
for action_name, enabled in _actions_daemon_enabled.items():
|
68
|
+
if action_name not in _action_functions:
|
69
|
+
continue
|
70
|
+
if enabled:
|
71
|
+
continue
|
72
|
+
found_disabled_action = True
|
73
|
+
break
|
74
|
+
|
75
|
+
for prefix in allowed_prefixes:
|
76
|
+
if sysargs_str.startswith(prefix) or prefix == '*':
|
77
|
+
found_acceptable_prefix = True
|
78
|
+
break
|
79
|
+
|
80
|
+
for prefix in disallowed_prefixes:
|
81
|
+
if sysargs_str.startswith(prefix) or prefix == '*':
|
82
|
+
found_unacceptable_prefix = True
|
83
|
+
break
|
84
|
+
|
85
|
+
if not found_acceptable_prefix or found_unacceptable_prefix or found_disabled_action:
|
86
|
+
daemon_is_ready = False
|
87
|
+
|
88
|
+
try:
|
89
|
+
daemon_ix = get_available_cli_daemon_ix() if daemon_is_ready else -1
|
90
|
+
except EnvironmentError as e:
|
91
|
+
from meerschaum.utils.warnings import warn
|
92
|
+
warn(e, stack=False)
|
93
|
+
daemon_ix = -1
|
94
|
+
daemon_is_ready = False
|
95
|
+
|
96
|
+
worker = ActionWorker(daemon_ix, refresh_seconds=refresh_seconds) if daemon_ix != -1 else None
|
97
|
+
if worker and worker.lock_path.exists():
|
98
|
+
daemon_is_ready = False
|
99
|
+
|
100
|
+
start_success, start_msg = (
|
101
|
+
worker.job.start()
|
102
|
+
if worker and daemon_is_ready
|
103
|
+
else (False, "Lock exists.")
|
104
|
+
)
|
105
|
+
|
106
|
+
if not start_success:
|
107
|
+
daemon_is_ready = False
|
108
|
+
|
109
|
+
if start_success and worker:
|
110
|
+
start = time.perf_counter()
|
111
|
+
while (time.perf_counter() - start) < 3:
|
112
|
+
if worker.is_ready():
|
113
|
+
break
|
114
|
+
time.sleep(refresh_seconds)
|
115
|
+
|
116
|
+
if not daemon_is_ready or worker is None:
|
117
|
+
if debug:
|
118
|
+
print("Revert to entry without daemon.")
|
119
|
+
return entry_without_daemon(sysargs, _patch_args=_patch_args)
|
120
|
+
|
121
|
+
session_id = _session_id or get_cli_session_id()
|
122
|
+
action_id = uuid.uuid4().hex
|
123
|
+
|
124
|
+
terminal_size = shutil.get_terminal_size()
|
125
|
+
env = {
|
126
|
+
**{
|
127
|
+
'LINES': str(terminal_size.lines),
|
128
|
+
'COLUMNS': str(terminal_size.columns),
|
129
|
+
},
|
130
|
+
**dict(os.environ),
|
131
|
+
}
|
132
|
+
for key in get_possible_keys():
|
133
|
+
_ = mrsm.get_config(key)
|
134
|
+
config = mrsm.get_config()
|
135
|
+
|
136
|
+
worker.write_input_data({
|
137
|
+
'session_id': session_id,
|
138
|
+
'action_id': action_id,
|
139
|
+
'sysargs': sysargs,
|
140
|
+
'patch_args': _patch_args,
|
141
|
+
'env': env,
|
142
|
+
'config': config,
|
143
|
+
})
|
144
|
+
|
145
|
+
accepted = False
|
146
|
+
exit_data = None
|
147
|
+
worker_data = None
|
148
|
+
|
149
|
+
while not accepted:
|
150
|
+
state = worker.read_output_data().get('state', None)
|
151
|
+
if state == 'accepted':
|
152
|
+
accepted = True
|
153
|
+
break
|
154
|
+
|
155
|
+
time.sleep(refresh_seconds)
|
156
|
+
|
157
|
+
worker.start_cli_logs_refresh_thread()
|
158
|
+
|
159
|
+
try:
|
160
|
+
log = worker.job.daemon.rotating_log
|
161
|
+
worker.job.monitor_logs(
|
162
|
+
stop_on_exit=True,
|
163
|
+
callback_function=worker.monitor_callback,
|
164
|
+
_log=log,
|
165
|
+
_wait_if_stopped=False,
|
166
|
+
)
|
167
|
+
worker_data = worker.read_output_data()
|
168
|
+
except KeyboardInterrupt:
|
169
|
+
exit_data = {
|
170
|
+
'session_id': session_id,
|
171
|
+
'action_id': action_id,
|
172
|
+
'signal': signal.SIGINT,
|
173
|
+
'traceback': traceback.format_exc(),
|
174
|
+
'success': True,
|
175
|
+
'message': 'Exiting due to keyboard interrupt.',
|
176
|
+
}
|
177
|
+
except Exception as e:
|
178
|
+
exit_data = {
|
179
|
+
'session_id': session_id,
|
180
|
+
'action_id': action_id,
|
181
|
+
'signal': signal.SIGINT,
|
182
|
+
'traceback': traceback.format_exc(),
|
183
|
+
'success': False,
|
184
|
+
'message': f'Encountered exception: {e}',
|
185
|
+
}
|
186
|
+
except SystemExit:
|
187
|
+
exit_data = {
|
188
|
+
'session_id': session_id,
|
189
|
+
'action_id': action_id,
|
190
|
+
'signal': signal.SIGTERM,
|
191
|
+
'traceback': traceback.format_exc(),
|
192
|
+
'success': True,
|
193
|
+
'message': 'Exiting on SIGTERM.',
|
194
|
+
}
|
195
|
+
except BrokenPipeError:
|
196
|
+
return False, "Connection to daemon is broken."
|
197
|
+
|
198
|
+
if exit_data:
|
199
|
+
exit_success = bool(exit_data['success'])
|
200
|
+
exit_signal = exit_data['signal']
|
201
|
+
worker.send_signal(exit_signal)
|
202
|
+
try:
|
203
|
+
worker.job.monitor_logs(
|
204
|
+
stop_on_exit=True,
|
205
|
+
callback_function=worker.monitor_callback,
|
206
|
+
_wait_if_stopped=False,
|
207
|
+
_log=log,
|
208
|
+
)
|
209
|
+
except (KeyboardInterrupt, SystemExit):
|
210
|
+
worker.send_signal(signal.SIGTERM)
|
211
|
+
worker_data = worker.read_output_data()
|
212
|
+
|
213
|
+
if not exit_success:
|
214
|
+
print(exit_data['traceback'])
|
215
|
+
|
216
|
+
worker.stop_cli_logs_refresh_thread()
|
217
|
+
worker.write_input_data({'increment': True})
|
218
|
+
success = (worker_data or {}).get('success', False)
|
219
|
+
message = (worker_data or {}).get('message', "Failed to retrieve message from CLI worker.")
|
220
|
+
return success, message
|