meerschaum 3.0.0rc3__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 +7 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +103 -97
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +136 -57
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/callbacks/tokens.py +2 -1
- meerschaum/api/dash/components.py +6 -7
- meerschaum/api/dash/keys.py +17 -1
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +14 -4
- meerschaum/api/dash/pipes.py +186 -65
- meerschaum/api/dash/tokens.py +1 -1
- 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 +98 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/sql/tables/__init__.py +20 -3
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +62 -72
- 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/_get_pipes.py +30 -4
- 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/_pprint.py +1 -0
- 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 +39 -7
- 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.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +125 -119
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -31,6 +31,7 @@ prompt_toolkit = attempt_import('prompt_toolkit', lazy=False, warn=False, instal
|
|
31
31
|
)
|
32
32
|
from meerschaum._internal.shell.ValidAutoSuggest import ValidAutoSuggest
|
33
33
|
from meerschaum._internal.shell.ShellCompleter import ShellCompleter
|
34
|
+
from meerschaum._internal.cli.daemons import get_cli_daemon, get_cli_session_id
|
34
35
|
_clear_screen = get_config('shell', 'clear_screen', patch=True)
|
35
36
|
from meerschaum.utils.misc import string_width, remove_ansi
|
36
37
|
from meerschaum.utils.warnings import warn
|
@@ -81,6 +82,7 @@ ESCAPED_AND_KEY: str = STATIC_CONFIG['system']['arguments']['escaped_and_key']
|
|
81
82
|
PIPELINE_KEY: str = STATIC_CONFIG['system']['arguments']['pipeline_key']
|
82
83
|
ESCAPED_PIPELINE_KEY: str = STATIC_CONFIG['system']['arguments']['escaped_pipeline_key']
|
83
84
|
|
85
|
+
|
84
86
|
def _insert_shell_actions(
|
85
87
|
_shell: Optional['Shell'] = None,
|
86
88
|
actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
|
@@ -99,6 +101,10 @@ def _insert_shell_actions(
|
|
99
101
|
_shell_class = _shell if _shell is not None else shell_pkg.Shell
|
100
102
|
|
101
103
|
for a, f in actions.items():
|
104
|
+
existing_method = getattr(_shell_class, 'do_' + a, None)
|
105
|
+
if existing_method == f:
|
106
|
+
continue
|
107
|
+
|
102
108
|
add_method_to_class(
|
103
109
|
func = f,
|
104
110
|
class_def = _shell_class,
|
@@ -113,6 +119,35 @@ def _insert_shell_actions(
|
|
113
119
|
setattr(_shell_class, 'complete_' + a, completer)
|
114
120
|
|
115
121
|
|
122
|
+
def _remove_shell_actions(
|
123
|
+
_shell: Optional['Shell'] = None,
|
124
|
+
actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
|
125
|
+
) -> None:
|
126
|
+
"""
|
127
|
+
Remove the actions added to the shell.
|
128
|
+
"""
|
129
|
+
import meerschaum._internal.shell as shell_pkg
|
130
|
+
if actions is None:
|
131
|
+
from meerschaum.actions import actions as _actions
|
132
|
+
actions = _actions
|
133
|
+
|
134
|
+
_shell_class = _shell if _shell is not None else shell_pkg.Shell
|
135
|
+
|
136
|
+
for a, f in actions.items():
|
137
|
+
try:
|
138
|
+
delattr(_shell_class, 'do_' + a)
|
139
|
+
except AttributeError:
|
140
|
+
pass
|
141
|
+
|
142
|
+
if a in reserved_completers:
|
143
|
+
continue
|
144
|
+
|
145
|
+
try:
|
146
|
+
delattr(_shell_class, 'complete_' + a)
|
147
|
+
except AttributeError:
|
148
|
+
pass
|
149
|
+
|
150
|
+
|
116
151
|
def _completer_wrapper(
|
117
152
|
target: Callable[[Any], List[str]]
|
118
153
|
) -> Callable[['mrsm._internal.shell.Shell', str, str, int, int], Any]:
|
@@ -294,16 +329,19 @@ class Shell(cmd.Cmd):
|
|
294
329
|
pass
|
295
330
|
|
296
331
|
### NOTE: custom actions must be added to the self._actions dictionary
|
332
|
+
shell_attrs['_session_id'] = get_cli_session_id()
|
297
333
|
shell_attrs['_actions'] = actions
|
298
334
|
shell_attrs['_sysargs'] = sysargs
|
299
335
|
shell_attrs['_actions']['instance'] = self.do_instance
|
300
336
|
shell_attrs['_actions']['repo'] = self.do_repo
|
301
337
|
shell_attrs['_actions']['executor'] = self.do_executor
|
302
338
|
shell_attrs['_actions']['debug'] = self.do_debug
|
339
|
+
shell_attrs['_actions']['daemon'] = self.do_daemon
|
303
340
|
shell_attrs['_update_bottom_toolbar'] = True
|
304
341
|
shell_attrs['_old_bottom_toolbar'] = ''
|
305
342
|
shell_attrs['debug'] = False
|
306
343
|
shell_attrs['_reload'] = True
|
344
|
+
shell_attrs['daemon'] = get_config('system', 'experimental', 'cli_daemon')
|
307
345
|
self.load_config(instance=instance_keys)
|
308
346
|
self.hidden_commands = []
|
309
347
|
### update hidden commands list (cmd2 only)
|
@@ -353,7 +391,7 @@ class Shell(cmd.Cmd):
|
|
353
391
|
shell_attrs['instance'] = instance
|
354
392
|
shell_attrs['instance_keys'] = remove_ansi(str(instance))
|
355
393
|
if shell_attrs.get('repo_keys', None) is None:
|
356
|
-
shell_attrs['repo_keys'] = get_config('meerschaum', '
|
394
|
+
shell_attrs['repo_keys'] = get_config('meerschaum', 'repository', patch=patch)
|
357
395
|
if shell_attrs.get('executor_keys', None) is None:
|
358
396
|
shell_attrs['executor_keys'] = get_executor_keys_from_context()
|
359
397
|
|
@@ -618,7 +656,7 @@ class Shell(cmd.Cmd):
|
|
618
656
|
if key == 'mrsm_instance':
|
619
657
|
default_value = get_config('meerschaum', 'instance')
|
620
658
|
elif key == 'repository':
|
621
|
-
default_value = get_config('meerschaum', '
|
659
|
+
default_value = get_config('meerschaum', 'repository')
|
622
660
|
elif key == 'executor_keys':
|
623
661
|
default_value = get_executor_keys_from_context()
|
624
662
|
else:
|
@@ -672,7 +710,12 @@ class Shell(cmd.Cmd):
|
|
672
710
|
([':'] + pipeline_args) if pipeline_args else []
|
673
711
|
)
|
674
712
|
try:
|
675
|
-
success_tuple = entry(
|
713
|
+
success_tuple = entry(
|
714
|
+
sysargs_to_execute,
|
715
|
+
_patch_args=patch_args,
|
716
|
+
_session_id=shell_attrs.get('session_id', None),
|
717
|
+
_use_cli_daemon=shell_attrs.get('daemon', False),
|
718
|
+
)
|
676
719
|
except Exception as e:
|
677
720
|
success_tuple = False, str(e)
|
678
721
|
|
@@ -732,6 +775,35 @@ class Shell(cmd.Cmd):
|
|
732
775
|
|
733
776
|
info(f"Debug mode is {'on' if shell_attrs['debug'] else 'off'}.")
|
734
777
|
|
778
|
+
def do_daemon(self, action: Optional[List[str]] = None, executor_keys=None, **kw):
|
779
|
+
"""
|
780
|
+
Toggle whether to route commands through the CLI daemon.
|
781
|
+
|
782
|
+
Command:
|
783
|
+
`debug {on/true | off/false}`
|
784
|
+
Ommitting on / off will toggle the existing value.
|
785
|
+
"""
|
786
|
+
from meerschaum.utils.warnings import info
|
787
|
+
on_commands = {'on', 'true', 'True'}
|
788
|
+
off_commands = {'off', 'false', 'False'}
|
789
|
+
if action is None:
|
790
|
+
action = []
|
791
|
+
try:
|
792
|
+
state = action[0]
|
793
|
+
except (IndexError, AttributeError):
|
794
|
+
state = ''
|
795
|
+
if state == '':
|
796
|
+
shell_attrs['daemon'] = not shell_attrs['daemon']
|
797
|
+
elif state.lower() in on_commands:
|
798
|
+
shell_attrs['daemon'] = True
|
799
|
+
elif state.lower() in off_commands:
|
800
|
+
shell_attrs['daemon'] = False
|
801
|
+
else:
|
802
|
+
info(f"Unknown state '{state}'. Ignoring...")
|
803
|
+
|
804
|
+
info(f"CLI daemon mode is {'on' if shell_attrs['daemon'] else 'off'}.")
|
805
|
+
return True, "Success"
|
806
|
+
|
735
807
|
def do_instance(
|
736
808
|
self,
|
737
809
|
action: Optional[List[str]] = None,
|
@@ -833,7 +905,7 @@ class Shell(cmd.Cmd):
|
|
833
905
|
"""
|
834
906
|
Temporarily set a default Meerschaum repository for the duration of the shell.
|
835
907
|
The default repository (mrsm.io) is loaded from the Meerschaum configuraton file
|
836
|
-
(at keys 'meerschaum
|
908
|
+
(at keys 'meerschaum.repository').
|
837
909
|
|
838
910
|
You can change the default repository with `edit config`.
|
839
911
|
|
@@ -864,7 +936,7 @@ class Shell(cmd.Cmd):
|
|
864
936
|
except (IndexError, AttributeError):
|
865
937
|
repo_keys = ''
|
866
938
|
if repo_keys == '':
|
867
|
-
repo_keys = get_config('meerschaum', '
|
939
|
+
repo_keys = get_config('meerschaum', 'repository', patch=True)
|
868
940
|
|
869
941
|
conn = parse_repo_keys(repo_keys, debug=debug)
|
870
942
|
if conn is None or not conn:
|
@@ -947,7 +1019,7 @@ class Shell(cmd.Cmd):
|
|
947
1019
|
show pipes -h
|
948
1020
|
```
|
949
1021
|
"""
|
950
|
-
from meerschaum.actions import
|
1022
|
+
from meerschaum.actions import get_action
|
951
1023
|
from meerschaum._internal.arguments._parser import parse_help
|
952
1024
|
from meerschaum._internal.arguments._parse_arguments import parse_line
|
953
1025
|
import textwrap
|
@@ -956,12 +1028,15 @@ class Shell(cmd.Cmd):
|
|
956
1028
|
del args['action']
|
957
1029
|
shell_attrs['_actions']['show'](['actions'], **args)
|
958
1030
|
return ""
|
1031
|
+
|
959
1032
|
if args['action'][0] not in shell_attrs['_actions']:
|
1033
|
+
action_func = get_action(args['action'])
|
960
1034
|
try:
|
961
|
-
print(textwrap.dedent(
|
1035
|
+
print(textwrap.dedent(action_func.__doc__))
|
962
1036
|
except Exception:
|
963
|
-
print(f"No help on '{args['action']
|
1037
|
+
print(f"No help on '{shlex.join(args['action'])}'.")
|
964
1038
|
return ""
|
1039
|
+
|
965
1040
|
parse_help(args)
|
966
1041
|
return ""
|
967
1042
|
|
@@ -1045,9 +1120,10 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
|
|
1045
1120
|
Replace built-in `input()` with prompt_toolkit.prompt.
|
1046
1121
|
"""
|
1047
1122
|
from meerschaum.utils.formatting import CHARSET, ANSI, colored, UNICODE
|
1048
|
-
from meerschaum.connectors import
|
1123
|
+
from meerschaum.connectors import connectors
|
1049
1124
|
from meerschaum.utils.misc import remove_ansi, truncate_text_for_display
|
1050
1125
|
from meerschaum.config import get_config
|
1126
|
+
|
1051
1127
|
import platform
|
1052
1128
|
if shell is None:
|
1053
1129
|
from meerschaum.actions import get_shell
|
@@ -1062,8 +1138,16 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
|
|
1062
1138
|
return None
|
1063
1139
|
if not shell_attrs['_update_bottom_toolbar'] and platform.system() == 'Windows':
|
1064
1140
|
return shell_attrs['_old_bottom_toolbar']
|
1065
|
-
|
1066
|
-
|
1141
|
+
try:
|
1142
|
+
size = os.get_terminal_size()
|
1143
|
+
num_cols, num_lines = size.columns, size.lines
|
1144
|
+
except Exception:
|
1145
|
+
from meerschaum.utils.misc import is_int
|
1146
|
+
num_cols, num_lines = os.environ.get('COLUMNS', 80), os.environ.get('LINES', 120)
|
1147
|
+
if is_int(str(num_cols)):
|
1148
|
+
num_cols = int(num_cols)
|
1149
|
+
if is_int(str(num_lines)):
|
1150
|
+
num_lines = int(num_lines)
|
1067
1151
|
truncation_suffix = (
|
1068
1152
|
'…'
|
1069
1153
|
if UNICODE
|
@@ -1112,24 +1196,34 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
|
|
1112
1196
|
|
1113
1197
|
try:
|
1114
1198
|
typ, label = shell_attrs['instance_keys'].split(':', maxsplit=1)
|
1115
|
-
|
1199
|
+
cli_worker = (
|
1200
|
+
get_cli_daemon()
|
1201
|
+
if shell_attrs.get('daemon', False)
|
1202
|
+
else None
|
1203
|
+
)
|
1204
|
+
connected = (
|
1205
|
+
cli_worker.job.status == 'running'
|
1206
|
+
if cli_worker is not None
|
1207
|
+
else (typ in connectors and label in connectors[typ])
|
1208
|
+
)
|
1116
1209
|
except Exception:
|
1117
1210
|
connected = False
|
1118
1211
|
last_connected = connected
|
1119
1212
|
connected_str = truncate_text_for_display(
|
1120
|
-
('
|
1213
|
+
('daemon' if cli_worker is not None else remove_ansi(shell_attrs['instance_keys'])),
|
1121
1214
|
**truncation_kwargs
|
1122
1215
|
)
|
1216
|
+
connected_status_str = 'disconnected' if not connected else 'connected'
|
1123
1217
|
connected_icon = get_config(
|
1124
|
-
'formatting',
|
1218
|
+
'formatting', connected_status_str, CHARSET, 'icon', warn=False,
|
1125
1219
|
) or ''
|
1126
1220
|
connection_text = (
|
1127
|
-
|
1128
|
-
colored(connected_str
|
1129
|
-
'formatting',
|
1221
|
+
(
|
1222
|
+
colored(connected_str, 'on ' + (get_config(
|
1223
|
+
'formatting', connected_status_str, 'ansi', 'rich', 'style',
|
1130
1224
|
warn=False,
|
1131
|
-
) or '') + ' ') if ANSI else (colored(connected_str
|
1132
|
-
)
|
1225
|
+
) or '') + ' ') if ANSI else (colored(connected_str, 'on white') + ' ')
|
1226
|
+
) + ' ' + connected_icon
|
1133
1227
|
)
|
1134
1228
|
|
1135
1229
|
left = (
|
@@ -8,5 +8,8 @@ Import the Shell class definition
|
|
8
8
|
|
9
9
|
from meerschaum._internal.shell.Shell import Shell
|
10
10
|
from meerschaum._internal.shell.Shell import (
|
11
|
-
default_action_completer,
|
11
|
+
default_action_completer,
|
12
|
+
_completer_wrapper,
|
13
|
+
_insert_shell_actions,
|
14
|
+
_remove_shell_actions,
|
12
15
|
)
|
meerschaum/_internal/static.py
CHANGED
@@ -240,6 +240,7 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
240
240
|
'noask': 'MRSM_NOASK',
|
241
241
|
'noninteractive': 'MRSM_NONINTERACTIVE',
|
242
242
|
'id': 'MRSM_SERVER_ID',
|
243
|
+
'test_flavors': 'MRSM_TEST_FLAVORS',
|
243
244
|
'daemon_id': 'MRSM_DAEMON_ID',
|
244
245
|
'systemd_log_path': 'MRSM_SYSTEMD_LOG_PATH',
|
245
246
|
'systemd_stdin_path': 'MRSM_SYSTEMD_STDIN_PATH',
|
@@ -322,6 +323,8 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
322
323
|
},
|
323
324
|
'jobs': {
|
324
325
|
'check_restart_seconds': 1.0,
|
326
|
+
'stop_token': '<------- MRSM_STOP_TOKEN ------->',
|
327
|
+
'clear_token': '<------- MRSM_CLEAR_TOKEN ------->',
|
325
328
|
},
|
326
329
|
'tokens': {
|
327
330
|
'minimum_length': 24,
|
@@ -352,5 +355,4 @@ STATIC_CONFIG: Dict[str, Any] = {
|
|
352
355
|
'users:delete': "Delete the associated user account (or other users for admins).",
|
353
356
|
},
|
354
357
|
},
|
355
|
-
|
356
358
|
}
|
@@ -10,11 +10,10 @@ from __future__ import annotations
|
|
10
10
|
from typing import Any
|
11
11
|
|
12
12
|
import meerschaum as mrsm
|
13
|
-
from meerschaum.utils.warnings import warn
|
14
13
|
tornado_web = mrsm.attempt_import('tornado.web', lazy=False)
|
15
14
|
terminado = mrsm.attempt_import('terminado', lazy=False)
|
16
15
|
|
17
|
-
tmux_suffix = mrsm.get_config('
|
16
|
+
tmux_suffix = mrsm.get_config('api', 'webterm', 'tmux', 'session_suffix')
|
18
17
|
|
19
18
|
|
20
19
|
class TermPageHandler(tornado_web.RequestHandler):
|
@@ -8,6 +8,9 @@ Build the web console virtual terminal using Tornado and xterm.js.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
+
import os
|
12
|
+
import json
|
13
|
+
import pathlib
|
11
14
|
from typing import Optional, Tuple
|
12
15
|
|
13
16
|
import meerschaum as mrsm
|
@@ -16,7 +19,6 @@ from meerschaum._internal.term.TermPageHandler import TermPageHandler, CustomTer
|
|
16
19
|
from meerschaum.config._paths import API_TEMPLATES_PATH, API_STATIC_PATH
|
17
20
|
from meerschaum.utils.venv import venv_executable
|
18
21
|
from meerschaum.utils.misc import is_tmux_available
|
19
|
-
from meerschaum.utils.daemon._names import get_new_daemon_name
|
20
22
|
|
21
23
|
tornado, tornado_ioloop, terminado = attempt_import(
|
22
24
|
'tornado', 'tornado.ioloop', 'terminado', lazy=False,
|
@@ -25,6 +27,8 @@ tornado, tornado_ioloop, terminado = attempt_import(
|
|
25
27
|
|
26
28
|
def get_webterm_app_and_manager(
|
27
29
|
instance_keys: Optional[str] = None,
|
30
|
+
port: Optional[int] = None,
|
31
|
+
env_path: Optional[pathlib.Path] = None,
|
28
32
|
) -> Tuple[
|
29
33
|
tornado.web.Application,
|
30
34
|
terminado.UniqueTermManager,
|
@@ -36,19 +40,49 @@ def get_webterm_app_and_manager(
|
|
36
40
|
-------
|
37
41
|
A tuple of the Tornado web application and term manager.
|
38
42
|
"""
|
43
|
+
from meerschaum.config.environment import get_env_vars
|
44
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
45
|
+
if env_path is None:
|
46
|
+
from meerschaum.config.paths import WEBTERM_INTERNAL_RESOURCES_PATH
|
47
|
+
env_path = WEBTERM_INTERNAL_RESOURCES_PATH / (str(port) + '.json')
|
48
|
+
|
49
|
+
env_vars = get_env_vars()
|
50
|
+
env_dict = {
|
51
|
+
env_var: os.environ[env_var]
|
52
|
+
for env_var in env_vars
|
53
|
+
if env_var not in (
|
54
|
+
STATIC_CONFIG['environment']['systemd_log_path'],
|
55
|
+
STATIC_CONFIG['environment']['systemd_result_path'],
|
56
|
+
STATIC_CONFIG['environment']['systemd_delete_job'],
|
57
|
+
STATIC_CONFIG['environment']['systemd_stdin_path'],
|
58
|
+
STATIC_CONFIG['environment']['daemon_id'],
|
59
|
+
)
|
60
|
+
}
|
61
|
+
with open(env_path, 'w+', encoding='utf-8') as f:
|
62
|
+
json.dump(env_dict, f)
|
63
|
+
|
39
64
|
shell_kwargs_str = f"mrsm_instance='{instance_keys}'" if instance_keys else ""
|
40
65
|
commands = [
|
41
66
|
venv_executable(None),
|
42
67
|
'-c',
|
43
|
-
"import os
|
44
|
-
"
|
68
|
+
"import os\n"
|
69
|
+
"import pathlib\n"
|
70
|
+
"import json\n"
|
71
|
+
f"env_path = pathlib.Path('{env_path.as_posix()}')\n"
|
72
|
+
"with open(env_path, 'r', encoding='utf-8') as f:\n"
|
73
|
+
" env_dict = json.load(f)\n"
|
74
|
+
"os.environ.update(env_dict)\n"
|
75
|
+
"_ = os.environ.pop('COLUMNS', None)\n"
|
76
|
+
"_ = os.environ.pop('LINES', None)\n"
|
77
|
+
"from meerschaum._internal.entry import get_shell\n"
|
45
78
|
f"get_shell({shell_kwargs_str}).cmdloop()"
|
46
79
|
]
|
47
|
-
webterm_cf = mrsm.get_config('
|
80
|
+
webterm_cf = mrsm.get_config('api', 'webterm')
|
48
81
|
if webterm_cf.get('tmux', {}).get('enabled', False) and is_tmux_available():
|
49
|
-
commands = ['tmux', 'new-session', '-A', '-s', 'MRSM_SESSION'] + commands
|
82
|
+
commands = ['tmux', 'new-session', '-A', '-s', f'MRSM_SESSION--{port}'] + commands
|
50
83
|
|
51
|
-
term_manager = terminado.NamedTermManager(shell_command=commands)
|
84
|
+
term_manager = terminado.NamedTermManager(shell_command=commands, extra_env=env_dict)
|
85
|
+
term_manager._port = port
|
52
86
|
handlers = [
|
53
87
|
(
|
54
88
|
r"/websocket/(.+)/?",
|
@@ -6,13 +6,14 @@
|
|
6
6
|
Utility functions regarding the webterm.
|
7
7
|
"""
|
8
8
|
|
9
|
+
from typing import List, Optional
|
10
|
+
|
9
11
|
import meerschaum as mrsm
|
10
|
-
from meerschaum.utils.typing import List
|
11
12
|
|
12
13
|
|
13
14
|
def is_webterm_running(
|
14
|
-
host: str,
|
15
|
-
port: int,
|
15
|
+
host: Optional[str] = None,
|
16
|
+
port: Optional[int] = None,
|
16
17
|
protocol: str = 'http',
|
17
18
|
session_id: str = 'mrsm',
|
18
19
|
) -> int:
|
@@ -20,10 +21,12 @@ def is_webterm_running(
|
|
20
21
|
Determine whether the webterm service is running on a given host and port.
|
21
22
|
"""
|
22
23
|
requests = mrsm.attempt_import('requests', lazy=False)
|
24
|
+
host = host or mrsm.get_config('api', 'webterm', 'host')
|
25
|
+
port = port or mrsm.get_config('api', 'webterm', 'port')
|
23
26
|
url = f'{protocol}://{host}:{port}/webterm/{session_id}'
|
24
27
|
try:
|
25
28
|
r = requests.get(url, timeout=3)
|
26
|
-
except Exception
|
29
|
+
except Exception:
|
27
30
|
return False
|
28
31
|
if not r:
|
29
32
|
return False
|
@@ -39,22 +42,44 @@ def kill_tmux_session(session: str) -> bool:
|
|
39
42
|
return run_process(command, capture_output=True) == 0
|
40
43
|
|
41
44
|
|
42
|
-
def get_mrsm_tmux_sessions() -> List[str]:
|
45
|
+
def get_mrsm_tmux_sessions(port: Optional[int] = None) -> List[str]:
|
43
46
|
"""
|
44
47
|
Return a list of tmux sessions created by Meerschaum.
|
45
48
|
"""
|
46
49
|
from meerschaum.utils.process import run_process
|
47
|
-
tmux_suffix = mrsm.get_config('
|
50
|
+
tmux_suffix = mrsm.get_config('api', 'webterm', 'tmux', 'session_suffix')
|
48
51
|
command = ['tmux', 'ls']
|
49
52
|
proc = run_process(command, capture_output=True, as_proc=True)
|
50
53
|
if proc.returncode != 0:
|
51
54
|
return []
|
55
|
+
|
56
|
+
port = port or mrsm.get_config('api', 'webterm', 'port')
|
57
|
+
|
52
58
|
sessions = [
|
53
59
|
line.split(':', maxsplit=1)[0]
|
54
60
|
for line in proc.stdout.read().decode('utf-8').split('\n')
|
55
61
|
]
|
62
|
+
mrsm_sessions_ports = []
|
63
|
+
for session in sessions:
|
64
|
+
if '--' not in session:
|
65
|
+
continue
|
66
|
+
|
67
|
+
parts = session.split('--', maxsplit=1)
|
68
|
+
if len(parts) != 2:
|
69
|
+
continue
|
70
|
+
|
71
|
+
if not parts[0].endswith(tmux_suffix):
|
72
|
+
continue
|
73
|
+
|
74
|
+
try:
|
75
|
+
session_port = int(parts[1])
|
76
|
+
except Exception:
|
77
|
+
continue
|
78
|
+
|
79
|
+
mrsm_sessions_ports.append((session, session_port))
|
80
|
+
|
56
81
|
return [
|
57
82
|
session
|
58
|
-
for session in
|
59
|
-
if
|
83
|
+
for session, session_port in mrsm_sessions_ports
|
84
|
+
if session_port == port
|
60
85
|
]
|
meerschaum/actions/__init__.py
CHANGED
@@ -9,7 +9,6 @@ Default actions available to the mrsm CLI.
|
|
9
9
|
from __future__ import annotations
|
10
10
|
from meerschaum.utils.typing import Callable, Any, Optional, Union, List, Dict, SuccessTuple
|
11
11
|
from meerschaum.utils.packages import get_modules_from_package
|
12
|
-
_custom_actions = []
|
13
12
|
|
14
13
|
__all__ = (
|
15
14
|
'get_action',
|
@@ -307,6 +306,8 @@ __all__ = ['actions', 'get_subactions', 'get_action', 'get_main_action_name', 'g
|
|
307
306
|
### functions that do not begin with '_' from all submodules.
|
308
307
|
from inspect import getmembers, isfunction
|
309
308
|
actions = {}
|
309
|
+
_custom_actions_plugins: Dict[str, str] = {}
|
310
|
+
_plugins_actions: Dict[str, List[str]] = {}
|
310
311
|
|
311
312
|
for module in modules:
|
312
313
|
### A couple important things happening here:
|
@@ -329,8 +330,8 @@ for module in modules:
|
|
329
330
|
if isfunction(ob[1])
|
330
331
|
### check that the function belongs to the module
|
331
332
|
and ob[0] == module.__name__.replace('_', '').split('.')[-1]
|
332
|
-
### skip functions that start with '
|
333
|
-
and ob[0][0] != '
|
333
|
+
### skip functions that start with '__'
|
334
|
+
and ob[0][0] != '__'
|
334
335
|
]
|
335
336
|
)
|
336
337
|
)
|
@@ -362,4 +363,5 @@ __pdoc__ = {
|
|
362
363
|
}
|
363
364
|
for a in actions:
|
364
365
|
__pdoc__[a] = False
|
365
|
-
|
366
|
+
|
367
|
+
meerschaum.plugins.load_plugins(skip_if_loaded=True)
|
meerschaum/actions/api.py
CHANGED
@@ -6,7 +6,10 @@ Start the Meerschaum WebAPI with the `api` action.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from __future__ import annotations
|
9
|
+
|
9
10
|
import os
|
11
|
+
|
12
|
+
import meerschaum as mrsm
|
10
13
|
from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
|
11
14
|
|
12
15
|
|
@@ -93,6 +96,7 @@ def _api_start(
|
|
93
96
|
action: Optional[List[str]] = None,
|
94
97
|
host: Optional[str] = None,
|
95
98
|
port: Optional[int] = None,
|
99
|
+
webterm_port: Optional[int] = None,
|
96
100
|
workers: Optional[int] = None,
|
97
101
|
mrsm_instance: Optional[str] = None,
|
98
102
|
no_dash: bool = False,
|
@@ -119,6 +123,10 @@ def _api_start(
|
|
119
123
|
The address to bind to.
|
120
124
|
If `None`, use '0.0.0.0'.
|
121
125
|
|
126
|
+
webterm_port: Optional[int], default None
|
127
|
+
Port to bind the webterm server to.
|
128
|
+
If `None`, use 8765.
|
129
|
+
|
122
130
|
workers: Optional[int], default None
|
123
131
|
How many worker threads to run.
|
124
132
|
If `None`, defaults to the number of CPU cores or 1 on Android.
|
@@ -149,8 +157,6 @@ def _api_start(
|
|
149
157
|
|
150
158
|
from meerschaum.utils.packages import (
|
151
159
|
attempt_import,
|
152
|
-
venv_contains_package,
|
153
|
-
pip_install,
|
154
160
|
run_python_package,
|
155
161
|
)
|
156
162
|
from meerschaum.utils.misc import is_int, filter_keywords
|
@@ -164,9 +170,10 @@ def _api_start(
|
|
164
170
|
API_UVICORN_CONFIG_PATH,
|
165
171
|
CACHE_RESOURCES_PATH,
|
166
172
|
PACKAGE_ROOT_PATH,
|
173
|
+
ROOT_DIR_PATH,
|
167
174
|
)
|
168
175
|
from meerschaum.config._patch import apply_patch_to_config
|
169
|
-
from meerschaum.config.
|
176
|
+
from meerschaum.config.environment import get_env_vars
|
170
177
|
from meerschaum._internal.static import STATIC_CONFIG, SERVER_ID
|
171
178
|
from meerschaum.connectors.parse import parse_instance_keys
|
172
179
|
from meerschaum.utils.pool import get_pool
|
@@ -187,9 +194,9 @@ def _api_start(
|
|
187
194
|
uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
|
188
195
|
uvicorn_env_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'uvicorn.env'
|
189
196
|
|
190
|
-
api_config = deepcopy(get_config('
|
197
|
+
api_config = deepcopy(get_config('api'))
|
191
198
|
cf = _config()
|
192
|
-
forwarded_allow_ips = get_config('
|
199
|
+
forwarded_allow_ips = get_config('api', 'uvicorn', 'forwarded_allow_ips')
|
193
200
|
uvicorn_config = api_config['uvicorn']
|
194
201
|
if port is None:
|
195
202
|
### default
|
@@ -223,7 +230,7 @@ def _api_start(
|
|
223
230
|
instance_connector = parse_instance_keys(mrsm_instance, debug=debug)
|
224
231
|
if instance_connector.type == 'api' and instance_connector.protocol != 'https':
|
225
232
|
allow_http_parent = get_config(
|
226
|
-
'
|
233
|
+
'api', 'permissions', 'chaining', 'insecure_parent_instance'
|
227
234
|
)
|
228
235
|
if not allow_http_parent:
|
229
236
|
return False, (
|
@@ -233,7 +240,7 @@ def _api_start(
|
|
233
240
|
f" - Ensure that '{instance_connector}' is available over HTTPS, " +
|
234
241
|
"and with `edit config`,\n" +
|
235
242
|
f" change the `protocol` for '{instance_connector}' to 'https'.\n\n" +
|
236
|
-
" - Run `edit config
|
243
|
+
" - Run `edit config api` and search for `permissions`.\n" +
|
237
244
|
" Under `api:permissions:chaining`, change the value of " +
|
238
245
|
"`insecure_parent_instance` to `true`,\n" +
|
239
246
|
" then restart the API process."
|
@@ -244,6 +251,7 @@ def _api_start(
|
|
244
251
|
'host': host,
|
245
252
|
'env_file': str(uvicorn_env_path.as_posix()),
|
246
253
|
'mrsm_instance': mrsm_instance,
|
254
|
+
'webterm_port': webterm_port,
|
247
255
|
'no_dash': no_dash,
|
248
256
|
'no_webterm': no_webterm or no_auth,
|
249
257
|
'no_auth': no_auth,
|
@@ -261,9 +269,22 @@ def _api_start(
|
|
261
269
|
uvicorn_config['use_colors'] = (not nopretty) if nopretty else ANSI
|
262
270
|
|
263
271
|
api_config['uvicorn'] = uvicorn_config
|
264
|
-
cf['
|
272
|
+
cf['api']['uvicorn'] = uvicorn_config
|
265
273
|
if secure:
|
266
|
-
cf['
|
274
|
+
cf['api']['permissions']['actions']['non_admin'] = False
|
275
|
+
|
276
|
+
if not uvicorn_config['no_webterm']:
|
277
|
+
from meerschaum._internal.term.tools import is_webterm_running
|
278
|
+
if webterm_port is None:
|
279
|
+
webterm_port = int(mrsm.get_config('api', 'webterm', 'port'))
|
280
|
+
if is_webterm_running(port=webterm_port):
|
281
|
+
return (
|
282
|
+
False,
|
283
|
+
(
|
284
|
+
f"Webterm is running on port {webterm_port}. "
|
285
|
+
"Start the API again with `--webterm-port`."
|
286
|
+
)
|
287
|
+
)
|
267
288
|
|
268
289
|
custom_keys = [
|
269
290
|
'mrsm_instance',
|
@@ -273,6 +294,7 @@ def _api_start(
|
|
273
294
|
'private',
|
274
295
|
'debug',
|
275
296
|
'production',
|
297
|
+
'webterm_port',
|
276
298
|
]
|
277
299
|
|
278
300
|
### write config to a temporary file to communicate with uvicorn threads
|
@@ -299,6 +321,7 @@ def _api_start(
|
|
299
321
|
MRSM_SERVER_ID: SERVER_ID,
|
300
322
|
MRSM_RUNTIME: 'api',
|
301
323
|
MRSM_CONFIG: json.loads(os.environ.get(MRSM_CONFIG, '{}')),
|
324
|
+
MRSM_ROOT_DIR: ROOT_DIR_PATH.as_posix(),
|
302
325
|
'FORWARDED_ALLOW_IPS': forwarded_allow_ips,
|
303
326
|
'TERM': os.environ.get('TERM', 'screen-256color'),
|
304
327
|
'SHELL': os.environ.get('SHELL', '/bin/bash'),
|
@@ -326,10 +349,10 @@ def _api_start(
|
|
326
349
|
env_text = ''
|
327
350
|
for key, val in env_dict.items():
|
328
351
|
value = str(
|
329
|
-
json.dumps(val, default=json_serialize_value)
|
352
|
+
json.dumps(val, default=json_serialize_value, separators=(',', ':'))
|
330
353
|
if isinstance(val, (dict))
|
331
354
|
else val
|
332
|
-
).replace('\\', '\\\\').replace("'", "
|
355
|
+
).replace('\\', '\\\\').replace("'", "\\'")
|
333
356
|
env_text += f"{key}='{value}'\n"
|
334
357
|
with open(uvicorn_env_path, 'w+', encoding='utf-8') as f:
|
335
358
|
if debug:
|
@@ -394,11 +417,16 @@ def _api_start(
|
|
394
417
|
except KeyboardInterrupt:
|
395
418
|
pass
|
396
419
|
|
420
|
+
old_stdin = sys.stdin
|
421
|
+
sys.stdin = None
|
422
|
+
|
397
423
|
if production:
|
398
424
|
_run_gunicorn()
|
399
425
|
else:
|
400
426
|
_run_uvicorn()
|
401
427
|
|
428
|
+
sys.stdin = old_stdin
|
429
|
+
|
402
430
|
### Cleanup
|
403
431
|
if uvicorn_config_path.parent.exists():
|
404
432
|
shutil.rmtree(uvicorn_config_path.parent)
|