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.
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 +434 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +113 -19
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +3 -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 +20 -10
  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 +135 -35
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +195 -41
  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 +16 -6
  99. meerschaum/utils/misc.py +18 -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 +171 -144
  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.0rc7.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -18,7 +18,7 @@ def delete(
18
18
  Delete an element.
19
19
 
20
20
  Command:
21
- `delete {config, pipes, plugins, users, connectors, jobs}`
21
+ `delete {config, pipes, plugins, users, connectors, jobs, venvs}`
22
22
 
23
23
  """
24
24
  from meerschaum.actions import choose_subaction
@@ -159,7 +159,9 @@ def _delete_config(
159
159
  answer = yes_no(
160
160
  "Are you sure you want to delete the following configuration files?" +
161
161
  f"{sep + sep.join([str(p) for p in paths])}\n",
162
- default='n', noask=noask, yes=yes
162
+ default='n',
163
+ noask=noask,
164
+ yes=yes,
163
165
  )
164
166
 
165
167
  if answer or force:
@@ -67,7 +67,12 @@ def _complete_edit(
67
67
  return default_action_completer(action=(['edit'] + action), **kw)
68
68
 
69
69
 
70
- def _edit_config(action: Optional[List[str]] = None, **kw: Any) -> SuccessTuple:
70
+ def _edit_config(
71
+ action: Optional[List[str]] = None,
72
+ noask: bool = False,
73
+ debug: bool = False,
74
+ **kwargs: Any
75
+ ) -> SuccessTuple:
71
76
  """
72
77
  Edit Meerschaum configuration files.
73
78
 
@@ -87,11 +92,26 @@ def _edit_config(action: Optional[List[str]] = None, **kw: Any) -> SuccessTuple:
87
92
  ```
88
93
  """
89
94
  from meerschaum.config._edit import edit_config
90
- if action is None:
91
- action = []
92
- if len(action) == 0:
93
- action.append('meerschaum')
94
- return edit_config(keys=action, **kw)
95
+ from meerschaum.config._read_config import get_possible_keys
96
+ from meerschaum.actions import actions
97
+ from meerschaum.utils.prompt import choose
98
+
99
+ if not action:
100
+ action = [
101
+ choose(
102
+ "Choose a configuration file to edit:",
103
+ get_possible_keys(),
104
+ default='meerschaum',
105
+ noask=noask,
106
+ )
107
+ ]
108
+
109
+ edit_success, edit_msg = edit_config(keys=action, debug=debug, **kwargs)
110
+ if not edit_success:
111
+ return edit_success, edit_msg
112
+
113
+ return actions['reload'](debug=debug)
114
+
95
115
 
96
116
  def _complete_edit_config(action: Optional[List[str]] = None, **kw: Any) -> List[str]:
97
117
  from meerschaum.config._read_config import get_possible_keys
@@ -343,7 +363,6 @@ def _edit_plugins(
343
363
  from meerschaum.utils.warnings import warn
344
364
  from meerschaum.utils.prompt import prompt, yes_no
345
365
  from meerschaum.utils.misc import edit_file
346
- from meerschaum.utils.packages import reload_meerschaum
347
366
  from meerschaum.actions import actions
348
367
 
349
368
  if not action:
@@ -376,7 +395,7 @@ def _edit_plugins(
376
395
  continue
377
396
 
378
397
  edit_file(plugin_file_path)
379
- reload_meerschaum(debug=debug)
398
+ actions['reload'](debug=debug)
380
399
 
381
400
  return True, "Success"
382
401
 
@@ -10,13 +10,13 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
11
 
12
12
  def login(
13
- action: Optional[List[str]] = None,
14
- connector_keys: Optional[List[str]] = None,
15
- yes: bool = False,
16
- noask: bool = False,
17
- debug: bool = False,
18
- **kw: Any
19
- ) -> SuccessTuple:
13
+ action: Optional[List[str]] = None,
14
+ connector_keys: Optional[List[str]] = None,
15
+ yes: bool = False,
16
+ noask: bool = False,
17
+ debug: bool = False,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """
21
21
  Log into a Meerschaum API instance.
22
22
  """
@@ -56,7 +56,7 @@ def login(
56
56
  for k in _keys:
57
57
  try:
58
58
  _connectors.append(parse_instance_keys(k))
59
- except Exception as e:
59
+ except Exception:
60
60
  warn(f"Unable to build connector '{k}'. Is it registered?", stack=False)
61
61
 
62
62
  meerschaum_config = get_config('meerschaum')
@@ -512,14 +512,20 @@ def _register_tokens(
512
512
  "To which user should this token be registered? Enter the number.",
513
513
  usernames,
514
514
  )
515
- user_id = instance_connector.get_user_id(
516
- User(username, instance=mrsm_instance),
517
- debug=debug,
518
- )
519
- if user_id is None:
520
- return False, f"Cannot load ID for user '{username}'."
515
+ else:
516
+ username = getattr(instance_connector, 'username')
517
+
518
+ if not username:
519
+ return False, "Cannot register a token without a user."
520
+
521
+ user_id = instance_connector.get_user_id(
522
+ User(username, instance=mrsm_instance),
523
+ debug=debug,
524
+ )
525
+ if user_id is None:
526
+ return False, f"Cannot load ID for user '{username}'."
521
527
 
522
- user = User(username, user_id=user_id, instance=mrsm_instance)
528
+ user = User(username, user_id=user_id, instance=mrsm_instance)
523
529
 
524
530
  token = Token(
525
531
  label=name,
@@ -9,13 +9,30 @@ Reload the running Meerschaum instance.
9
9
  from __future__ import annotations
10
10
  from meerschaum.utils.typing import Any, SuccessTuple, List, Optional
11
11
 
12
+
12
13
  def reload(
13
- action: Optional[List[str]] = None,
14
- debug: bool = False,
15
- **kw: Any
16
- ) -> SuccessTuple:
14
+ action: Optional[List[str]] = None,
15
+ debug: bool = False,
16
+ _stop_daemons: bool = True,
17
+ **kw: Any
18
+ ) -> SuccessTuple:
17
19
  """
18
20
  Reload the running Meerschaum instance.
19
21
  """
20
22
  from meerschaum.utils.packages import reload_meerschaum
21
- return reload_meerschaum(debug=debug)
23
+ from meerschaum.actions import actions
24
+
25
+ if _stop_daemons:
26
+ from meerschaum._internal.cli.workers import get_existing_cli_worker_indices
27
+ indices = get_existing_cli_worker_indices()
28
+ cli_action = 'restart' if indices else 'stop'
29
+
30
+ stop_daemon_success, stop_daemon_msg = actions[cli_action](['daemons'], debug=debug, **kw)
31
+ if not stop_daemon_success:
32
+ return stop_daemon_success, stop_daemon_msg
33
+
34
+ reload_success, reload_msg = reload_meerschaum(debug=debug)
35
+ if not reload_success:
36
+ return reload_success, reload_msg
37
+
38
+ return True, "Success"
@@ -7,6 +7,7 @@ Restart stopped jobs which have not been manually stopped.
7
7
 
8
8
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
9
9
 
10
+
10
11
  def restart(
11
12
  action: Optional[List[str]] = None,
12
13
  executor_keys: Optional[str] = None,
@@ -18,6 +19,7 @@ def restart(
18
19
  from meerschaum.actions import choose_subaction
19
20
  attach_options = {
20
21
  'jobs': _restart_jobs,
22
+ 'daemons': _restart_daemons,
21
23
  }
22
24
  return choose_subaction(action, attach_options, executor_keys=executor_keys, **kwargs)
23
25
 
@@ -111,3 +113,15 @@ def _restart_jobs(
111
113
  time.sleep(min_seconds)
112
114
 
113
115
  return check_success, check_msg
116
+
117
+
118
+ def _restart_daemons(
119
+ action: Optional[List[str]] = None,
120
+ **kwargs
121
+ ) -> SuccessTuple:
122
+ """
123
+ Restart the CLI daemons.
124
+ """
125
+ from meerschaum.actions import actions
126
+ kwargs['force'] = True
127
+ return actions['start'](['daemons'], **kwargs)
@@ -46,6 +46,7 @@ def show(
46
46
  'schedules' : _show_schedules,
47
47
  'venvs' : _show_venvs,
48
48
  'tokens' : _show_tokens,
49
+ 'daemons' : _show_daemons,
49
50
  }
50
51
  return choose_subaction(action, show_options, **kw)
51
52
 
@@ -205,7 +206,7 @@ def _show_version(nopretty: bool = False, **kw : Any) -> SuccessTuple:
205
206
  msg = "Meerschaum v" + version
206
207
  _print = info
207
208
  _print(msg)
208
- return (True, "Success")
209
+ return True, "Success"
209
210
 
210
211
 
211
212
  def _show_connectors(
@@ -563,6 +564,7 @@ def _complete_show_packages(
563
564
 
564
565
  return possibilities
565
566
 
567
+
566
568
  def _show_jobs(
567
569
  action: Optional[List[str]] = None,
568
570
  executor_keys: Optional[str] = None,
@@ -763,7 +765,7 @@ def _show_environment(
763
765
  """
764
766
  import os
765
767
  from meerschaum.utils.formatting import pprint
766
- from meerschaum.config._environment import get_env_vars
768
+ from meerschaum.config.environment import get_env_vars
767
769
  pprint(
768
770
  {
769
771
  env_var: os.environ[env_var]
@@ -930,7 +932,7 @@ def _show_tokens(
930
932
  from meerschaum.utils.packages import import_rich
931
933
  from meerschaum.utils.formatting import UNICODE, get_console
932
934
  rich = import_rich()
933
- rich_table, rich_json = mrsm.attempt_import('rich.table', 'rich.json')
935
+ rich_table, rich_json, rich_box = mrsm.attempt_import('rich.table', 'rich.json', 'rich.box')
934
936
 
935
937
  conn = parse_instance_keys(mrsm_instance)
936
938
 
@@ -994,7 +996,11 @@ def _show_tokens(
994
996
  else "[-]"
995
997
  )
996
998
 
997
- table = rich_table.Table(title=f"Registered Tokens on instance '{conn}'")
999
+ table = rich_table.Table(
1000
+ title=f"Registered Tokens on instance '{conn}'",
1001
+ box=rich_box.ROUNDED,
1002
+ title_style='bold',
1003
+ )
998
1004
  table.add_column("ID")
999
1005
  table.add_column("Label")
1000
1006
  table.add_column("User")
@@ -1018,6 +1024,65 @@ def _show_tokens(
1018
1024
  return True, "Success"
1019
1025
 
1020
1026
 
1027
+ def _show_daemons() -> SuccessTuple:
1028
+ """
1029
+ Print information about the running CLI daemons.
1030
+ """
1031
+ from meerschaum._internal.cli.workers import get_existing_cli_workers
1032
+ workers = get_existing_cli_workers()
1033
+ if not workers:
1034
+ return True, "No CLI daemons are running."
1035
+
1036
+ rich_table, rich_box, rich_text = mrsm.attempt_import('rich.table', 'rich.box', 'rich.text')
1037
+ table = rich_table.Table(
1038
+ rich_table.Column("Index", justify='center'),
1039
+ rich_table.Column("Status"),
1040
+ rich_table.Column("Locked", justify='center'),
1041
+ rich_table.Column("Ready", justify='center'),
1042
+ rich_table.Column("Actions"),
1043
+ title='CLI Daemons',
1044
+ box=rich_box.ROUNDED,
1045
+ title_style='bold',
1046
+ )
1047
+
1048
+ running_icon = mrsm.get_config('formatting', 'emoji', 'running')
1049
+ paused_icon = mrsm.get_config('formatting', 'emoji', 'paused')
1050
+ stopped_icon = mrsm.get_config('formatting', 'emoji', 'stopped')
1051
+ locked_icon = mrsm.get_config('formatting', 'emoji', 'locked')
1052
+ unlocked_icon = mrsm.get_config('formatting', 'emoji', 'unlocked')
1053
+ status_styles = {
1054
+ 'running': 'green',
1055
+ 'stopped': 'red',
1056
+ 'paused': 'yellow',
1057
+ }
1058
+
1059
+ for worker in workers:
1060
+ status = worker.job.status
1061
+ status_text = rich_text.Text(
1062
+ status,
1063
+ style=status_styles[status],
1064
+ )
1065
+ if not worker.is_ready():
1066
+ ready_icon = stopped_icon
1067
+ else:
1068
+ ready_icon = (
1069
+ running_icon
1070
+ if status == 'running'
1071
+ else paused_icon
1072
+ )
1073
+
1074
+ table.add_row(
1075
+ str(worker.ix),
1076
+ status_text,
1077
+ (locked_icon if worker.lock_path.exists() else unlocked_icon),
1078
+ ready_icon,
1079
+ str(worker.job.daemon.rotating_log.get_latest_subfile_index()),
1080
+ )
1081
+
1082
+ mrsm.pprint(table)
1083
+ return True, "Success"
1084
+
1085
+
1021
1086
  ### NOTE: This must be the final statement of the module.
1022
1087
  ### Any subactions added below these lines will not
1023
1088
  ### be added to the `help` docstring.
@@ -7,6 +7,8 @@ Start subsystems (API server, logging daemon, etc.).
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ import meerschaum as mrsm
10
12
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any, Union, Dict
11
13
 
12
14
 
@@ -25,6 +27,7 @@ def start(
25
27
  'webterm': _start_webterm,
26
28
  'connectors': _start_connectors,
27
29
  'pipeline': _start_pipeline,
30
+ 'daemons': _start_daemons,
28
31
  }
29
32
  return choose_subaction(action, options, **kw)
30
33
 
@@ -308,6 +311,7 @@ def _start_gui(
308
311
  action: Optional[List[str]] = None,
309
312
  mrsm_instance: Optional[str] = None,
310
313
  port: Optional[int] = None,
314
+ webterm_port: Optional[int] = None,
311
315
  debug: bool = False,
312
316
  **kw
313
317
  ) -> SuccessTuple:
@@ -321,7 +325,6 @@ def _start_gui(
321
325
 
322
326
  from meerschaum.utils.venv import venv_exec
323
327
  from meerschaum.utils.packages import attempt_import
324
- from meerschaum.utils.warnings import warn
325
328
  from meerschaum.utils.debug import dprint
326
329
  from meerschaum.utils.networking import find_open_ports
327
330
  from meerschaum.connectors.parse import parse_instance_keys
@@ -329,9 +332,10 @@ def _start_gui(
329
332
  webview, requests = attempt_import('webview', 'requests')
330
333
 
331
334
  success, msg = True, "Success"
332
- host = '127.0.0.1'
333
- if port is None:
334
- port = 8765
335
+ host = mrsm.get_config('webterm', 'host')
336
+ port = port or webterm_port
337
+ if not port:
338
+ port = mrsm.get_config('webterm', 'port')
335
339
 
336
340
  if not is_webterm_running(host, port, session_id='mrsm'):
337
341
  port = next(find_open_ports(port, 9000))
@@ -369,7 +373,7 @@ def _start_gui(
369
373
  break
370
374
  except Exception as e:
371
375
  if debug:
372
- dprint(e)
376
+ dprint(str(e))
373
377
  continue
374
378
  if starting_up is False:
375
379
  return False, f"The webterm failed to start within {timeout} seconds."
@@ -417,7 +421,6 @@ def _start_webterm(
417
421
  - `-i`, '--instance'
418
422
  The default instance to use for the Webterm shell.
419
423
  """
420
- import uuid
421
424
  from meerschaum._internal.term import get_webterm_app_and_manager, tornado_ioloop
422
425
  from meerschaum._internal.term.tools import (
423
426
  is_webterm_running,
@@ -426,15 +429,17 @@ def _start_webterm(
426
429
  )
427
430
  from meerschaum.utils.networking import find_open_ports
428
431
  from meerschaum.utils.warnings import info
432
+ from meerschaum.config.paths import WEBTERM_INTERNAL_RESOURCES_PATH
429
433
 
430
434
  if host is None:
431
- host = '127.0.0.1'
435
+ host = mrsm.get_config('api', 'webterm', 'host')
432
436
  if port is None:
433
- port = 8765
437
+ port = mrsm.get_config('api', 'webterm', 'port')
434
438
  if sysargs is None:
435
439
  sysargs = ['start', 'webterm']
436
440
  session_id = 'mrsm'
437
- tornado_app, term_manager = get_webterm_app_and_manager(instance_keys=mrsm_instance)
441
+
442
+ env_path = WEBTERM_INTERNAL_RESOURCES_PATH / (str(port) + '.json')
438
443
 
439
444
  if is_webterm_running(host, port, session_id=session_id):
440
445
  if force:
@@ -445,11 +450,18 @@ def _start_webterm(
445
450
  + " Include `-f` to start another server on a new port\n"
446
451
  + " or specify a different port with `-p`."
447
452
  )
453
+
454
+ tornado_app, term_manager = get_webterm_app_and_manager(
455
+ instance_keys=mrsm_instance,
456
+ port=port,
457
+ env_path=env_path,
458
+ )
448
459
  if not nopretty:
449
460
  info(
450
461
  f"Starting the webterm at http://{host}:{port}/webterm/{session_id} ..."
451
462
  "\n Press CTRL+C to quit."
452
463
  )
464
+
453
465
  tornado_app.listen(port, host)
454
466
  loop = tornado_ioloop.IOLoop.instance()
455
467
  try:
@@ -462,10 +474,15 @@ def _start_webterm(
462
474
  term_manager.shutdown()
463
475
  loop.close()
464
476
 
465
- sessions = get_mrsm_tmux_sessions()
477
+ sessions = get_mrsm_tmux_sessions(port=port)
466
478
  for session in sessions:
467
479
  kill_tmux_session(session)
468
480
 
481
+ try:
482
+ env_path.unlink()
483
+ except Exception:
484
+ pass
485
+
469
486
  return True, "Success"
470
487
 
471
488
 
@@ -483,7 +500,6 @@ def _start_connectors(
483
500
  from meerschaum.connectors.parse import parse_instance_keys
484
501
  from meerschaum.utils.pool import get_pool
485
502
  from meerschaum.utils.warnings import warn
486
- from meerschaum.utils.formatting import pprint
487
503
  from meerschaum.utils.misc import items_str
488
504
  if action is None:
489
505
  action = []
@@ -575,7 +591,6 @@ def _start_pipeline(
575
591
  """
576
592
  import json
577
593
  import time
578
- import sys
579
594
  from meerschaum._internal.entry import entry
580
595
  from meerschaum.utils.warnings import info, warn
581
596
  from meerschaum.utils.misc import is_int
@@ -630,7 +645,7 @@ def _start_pipeline(
630
645
  def do_entry() -> None:
631
646
  nonlocal success, msg, proc
632
647
  if timeout_seconds is None:
633
- success, msg = entry(sub_args_line, _patch_args=patch_args)
648
+ success, msg = entry(sub_args_line, _patch_args=patch_args, _use_cli_daemon=False)
634
649
  return
635
650
 
636
651
  sub_args_line_escaped = sub_args_line.replace("'", "<QUOTE>")
@@ -640,7 +655,7 @@ def _start_pipeline(
640
655
  "from meerschaum._internal.entry import entry\n\n"
641
656
  f"sub_args_line = '{sub_args_line_escaped}'.replace(\"<QUOTE>\", \"'\")\n"
642
657
  f"patch_args = json.loads('{patch_args_escaped_str}'.replace('<QUOTE>', \"'\"))\n"
643
- "success, msg = entry(sub_args_line, _patch_args=patch_args)\n"
658
+ "success, msg = entry(sub_args_line, _patch_args=patch_args, _use_cli_daemon=False)\n"
644
659
  f"print('{fence_begin}' + json.dumps((success, msg)) + '{fence_end}')"
645
660
  )
646
661
  proc = venv_exec(src, venv=None, as_proc=True)
@@ -688,6 +703,112 @@ def _start_pipeline(
688
703
  return success, msg
689
704
 
690
705
 
706
+ def _start_daemons(
707
+ timeout_seconds: Union[int, float, None] = None,
708
+ yes: bool = False,
709
+ force: bool = False,
710
+ noask: bool = False,
711
+ debug: bool = False,
712
+ **kwargs
713
+ ) -> SuccessTuple:
714
+ """
715
+ Start the Meerschaum CLI daemon processes.
716
+ """
717
+ from meerschaum.utils.warnings import warn, dprint
718
+ from meerschaum._internal.cli.daemons import (
719
+ get_cli_daemon,
720
+ get_cli_lock_path,
721
+ )
722
+ from meerschaum._internal.cli.workers import (
723
+ get_existing_cli_workers,
724
+ get_existing_cli_worker_indices,
725
+ )
726
+ from meerschaum.utils.prompt import yes_no
727
+ from meerschaum.actions import actions
728
+
729
+ workers = get_existing_cli_workers()
730
+ if not workers:
731
+ if debug:
732
+ dprint("No daemons are running, spawning a new process...")
733
+ workers = [get_cli_daemon()]
734
+
735
+ accepted_restart = False
736
+ any_daemons_are_running = any((worker.job.status == 'running') for worker in workers)
737
+ lock_paths = [get_cli_lock_path(ix) for ix in get_existing_cli_worker_indices()]
738
+ any_locks_exist = any(lock_path.exists() for lock_path in lock_paths)
739
+
740
+ if any_locks_exist:
741
+ warn(
742
+ "Locks are currently held by the CLI daemons.\n"
743
+ "Run again with `--force` to remove the locks.",
744
+ stack=False,
745
+ )
746
+
747
+ if not force:
748
+ return False, "Actions are currently running."
749
+
750
+ for lock_path in lock_paths:
751
+ try:
752
+ if lock_path.exists():
753
+ lock_path.unlink()
754
+ except Exception as e:
755
+ warn(f"Failed to release lock:\n{e}")
756
+
757
+ if any_daemons_are_running:
758
+ accepted_restart = force or yes_no(
759
+ "Restart running CLI daemons?",
760
+ yes=yes,
761
+ noask=noask,
762
+ default='n',
763
+ )
764
+
765
+ if not accepted_restart:
766
+ return True, "Daemons are already running; nothing to do."
767
+
768
+ stop_success, stop_msg = actions['stop'](
769
+ ['daemons'],
770
+ timeout_seconds=timeout_seconds,
771
+ debug=debug,
772
+ )
773
+ if not stop_success:
774
+ return stop_success, stop_msg
775
+
776
+ worker = get_cli_daemon()
777
+
778
+ if debug:
779
+ dprint("Cleaning up CLI daemon...")
780
+ cleanup_success, cleanup_msg = worker.cleanup()
781
+ if not cleanup_success:
782
+ return cleanup_success, cleanup_msg
783
+
784
+ if debug:
785
+ dprint("Starting CLI daemon...")
786
+
787
+ start_success, start_msg = worker.job.start()
788
+ if not start_success:
789
+ return start_success, start_msg
790
+
791
+ return True, "Success"
792
+
793
+
794
+ def _start_worker(action: Optional[List[str]] = None, **kwargs: Any) -> SuccessTuple:
795
+ """
796
+ Start a CLI worker process. This is intended for internal use only.
797
+ """
798
+ from meerschaum._internal.cli.workers import ActionWorker
799
+ from meerschaum.utils.misc import is_int
800
+
801
+ if not action:
802
+ return False, "No worker index is provided."
803
+
804
+ if not is_int(action[0]):
805
+ return False, "Invalid worker index."
806
+
807
+ ix = int(action[0])
808
+ worker = ActionWorker(ix)
809
+ return worker.run()
810
+
811
+
691
812
  ### NOTE: This must be the final statement of the module.
692
813
  ### Any subactions added below these lines will not
693
814
  ### be added to the `help` docstring.
@@ -7,7 +7,7 @@ Stop running jobs that were started with `-d` or `start job`.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import Optional, List, SuccessTuple, Any
10
+ from meerschaum.utils.typing import Optional, List, SuccessTuple, Any, Union
11
11
 
12
12
 
13
13
  def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
@@ -17,6 +17,7 @@ def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
17
17
  from meerschaum.actions import choose_subaction
18
18
  options = {
19
19
  'jobs': _stop_jobs,
20
+ 'daemons': _stop_daemons,
20
21
  }
21
22
  return choose_subaction(action, options, **kw)
22
23
 
@@ -53,7 +54,7 @@ def _complete_stop(
53
54
  return options[sub](action=action, **kw)
54
55
 
55
56
  from meerschaum._internal.shell import default_action_completer
56
- return default_action_completer(action=(['start'] + action), **kw)
57
+ return default_action_completer(action=(['stop'] + action), **kw)
57
58
 
58
59
 
59
60
  def _stop_jobs(
@@ -113,7 +114,7 @@ def _stop_jobs(
113
114
 
114
115
  if not jobs_to_stop:
115
116
  if jobs:
116
- return True, "The selected jobs are currently running."
117
+ return True, "The selected jobs are currently stopped."
117
118
  return False, "No running, paused or restarting jobs to stop."
118
119
 
119
120
  if not action:
@@ -163,6 +164,38 @@ def _stop_jobs(
163
164
  return success, msg
164
165
 
165
166
 
167
+ def _stop_daemons(
168
+ timeout_seconds: Union[int, float, None] = None,
169
+ debug: bool = False,
170
+ **kwargs
171
+ ) -> SuccessTuple:
172
+ """
173
+ Stop the Meerschaum CLI daemon.
174
+ """
175
+ import shutil
176
+ from meerschaum._internal.cli.workers import get_existing_cli_workers
177
+ from meerschaum.config.paths import CLI_RESOURCES_PATH
178
+ workers = get_existing_cli_workers()
179
+
180
+ for worker in workers:
181
+ stop_success, stop_msg = worker.job.stop(timeout_seconds=timeout_seconds, debug=debug)
182
+ if not stop_success:
183
+ return stop_success, stop_msg
184
+
185
+ cleanup_success, cleanup_msg = worker.cleanup(debug=debug)
186
+ if not cleanup_success:
187
+ return cleanup_success, cleanup_msg
188
+
189
+ try:
190
+ if CLI_RESOURCES_PATH.exists():
191
+ shutil.rmtree(CLI_RESOURCES_PATH)
192
+ CLI_RESOURCES_PATH.mkdir(parents=True, exist_ok=True)
193
+ except Exception as e:
194
+ return False, f"Failed to clean up CLI resources directory.\n{e}"
195
+
196
+ return True, "Success"
197
+
198
+
166
199
  ### NOTE: This must be the final statement of the module.
167
200
  ### Any subactions added below these lines will not
168
201
  ### be added to the `help` docstring.
@@ -289,6 +289,7 @@ def _sync_pipes(
289
289
  from meerschaum.utils.formatting import print_pipes_results
290
290
  from meerschaum._internal.static import STATIC_CONFIG
291
291
  from meerschaum.utils.misc import interval_str
292
+ from meerschaum.utils.daemon import running_in_daemon
292
293
 
293
294
  noninteractive_val = os.environ.get(STATIC_CONFIG['environment']['noninteractive'], None)
294
295
  noninteractive = str(noninteractive_val).lower() in ('1', 'true', 'yes')
@@ -301,7 +302,11 @@ def _sync_pipes(
301
302
  cooldown = 2 * (min_seconds + 1)
302
303
  success_pipes, failure_pipes = [], []
303
304
  while run:
304
- _progress = progress() if shell and not noninteractive else None
305
+ _progress = (
306
+ progress()
307
+ if (shell and not noninteractive and not running_in_daemon())
308
+ else None
309
+ )
305
310
  cm = _progress if _progress is not None else contextlib.nullcontext()
306
311
 
307
312
  lap_begin = time.perf_counter()