meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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.
Files changed (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.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
- subaction = get_subactions(args['action'][0])[args['action'][1]]
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
- "When syncing or viewing a pipe's data, sync to a local database for later analysis."
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,6 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define internal utilities for interacting with the CLI daemons.
6
+ """
@@ -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