meerschaum 2.9.5__py3-none-any.whl → 3.0.0__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 (200) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +33 -4
  5. meerschaum/_internal/cli/__init__.py +6 -0
  6. meerschaum/_internal/cli/daemons.py +103 -0
  7. meerschaum/_internal/cli/entry.py +220 -0
  8. meerschaum/_internal/cli/workers.py +435 -0
  9. meerschaum/_internal/docs/index.py +48 -2
  10. meerschaum/_internal/entry.py +50 -14
  11. meerschaum/_internal/shell/Shell.py +121 -29
  12. meerschaum/_internal/shell/__init__.py +4 -1
  13. meerschaum/_internal/static.py +359 -0
  14. meerschaum/_internal/term/TermPageHandler.py +1 -2
  15. meerschaum/_internal/term/__init__.py +40 -6
  16. meerschaum/_internal/term/tools.py +33 -8
  17. meerschaum/actions/__init__.py +6 -4
  18. meerschaum/actions/api.py +53 -13
  19. meerschaum/actions/attach.py +1 -0
  20. meerschaum/actions/bootstrap.py +8 -8
  21. meerschaum/actions/delete.py +4 -2
  22. meerschaum/actions/edit.py +171 -25
  23. meerschaum/actions/login.py +8 -8
  24. meerschaum/actions/register.py +143 -6
  25. meerschaum/actions/reload.py +22 -5
  26. meerschaum/actions/restart.py +14 -0
  27. meerschaum/actions/show.py +184 -31
  28. meerschaum/actions/start.py +166 -17
  29. meerschaum/actions/stop.py +38 -2
  30. meerschaum/actions/sync.py +7 -2
  31. meerschaum/actions/tag.py +9 -8
  32. meerschaum/actions/verify.py +5 -8
  33. meerschaum/api/__init__.py +45 -15
  34. meerschaum/api/_events.py +46 -4
  35. meerschaum/api/_oauth2.py +162 -9
  36. meerschaum/api/_tokens.py +102 -0
  37. meerschaum/api/dash/__init__.py +0 -3
  38. meerschaum/api/dash/callbacks/__init__.py +1 -0
  39. meerschaum/api/dash/callbacks/custom.py +4 -3
  40. meerschaum/api/dash/callbacks/dashboard.py +198 -118
  41. meerschaum/api/dash/callbacks/jobs.py +14 -7
  42. meerschaum/api/dash/callbacks/login.py +10 -1
  43. meerschaum/api/dash/callbacks/pipes.py +194 -14
  44. meerschaum/api/dash/callbacks/plugins.py +0 -1
  45. meerschaum/api/dash/callbacks/register.py +10 -3
  46. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  47. meerschaum/api/dash/callbacks/tokens.py +389 -0
  48. meerschaum/api/dash/components.py +36 -15
  49. meerschaum/api/dash/jobs.py +1 -1
  50. meerschaum/api/dash/keys.py +35 -93
  51. meerschaum/api/dash/pages/__init__.py +2 -1
  52. meerschaum/api/dash/pages/dashboard.py +1 -20
  53. meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
  54. meerschaum/api/dash/pages/login.py +2 -2
  55. meerschaum/api/dash/pages/pipes.py +16 -5
  56. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  57. meerschaum/api/dash/pages/tokens.py +53 -0
  58. meerschaum/api/dash/pipes.py +382 -95
  59. meerschaum/api/dash/sessions.py +12 -0
  60. meerschaum/api/dash/tokens.py +603 -0
  61. meerschaum/api/dash/websockets.py +1 -1
  62. meerschaum/api/dash/webterm.py +18 -6
  63. meerschaum/api/models/__init__.py +23 -3
  64. meerschaum/api/models/_actions.py +22 -0
  65. meerschaum/api/models/_pipes.py +91 -7
  66. meerschaum/api/models/_tokens.py +81 -0
  67. meerschaum/api/resources/static/js/terminado.js +3 -0
  68. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  69. meerschaum/api/resources/templates/termpage.html +13 -0
  70. meerschaum/api/routes/__init__.py +1 -0
  71. meerschaum/api/routes/_actions.py +3 -4
  72. meerschaum/api/routes/_connectors.py +3 -7
  73. meerschaum/api/routes/_jobs.py +26 -35
  74. meerschaum/api/routes/_login.py +120 -15
  75. meerschaum/api/routes/_misc.py +5 -10
  76. meerschaum/api/routes/_pipes.py +178 -143
  77. meerschaum/api/routes/_plugins.py +38 -28
  78. meerschaum/api/routes/_tokens.py +236 -0
  79. meerschaum/api/routes/_users.py +47 -35
  80. meerschaum/api/routes/_version.py +3 -3
  81. meerschaum/api/routes/_webterm.py +3 -3
  82. meerschaum/config/__init__.py +100 -30
  83. meerschaum/config/_default.py +132 -64
  84. meerschaum/config/_edit.py +38 -32
  85. meerschaum/config/_formatting.py +2 -0
  86. meerschaum/config/_patch.py +10 -8
  87. meerschaum/config/_paths.py +133 -13
  88. meerschaum/config/_read_config.py +87 -36
  89. meerschaum/config/_sync.py +6 -3
  90. meerschaum/config/_version.py +1 -1
  91. meerschaum/config/environment.py +262 -0
  92. meerschaum/config/stack/__init__.py +37 -15
  93. meerschaum/config/static.py +18 -0
  94. meerschaum/connectors/_Connector.py +11 -6
  95. meerschaum/connectors/__init__.py +41 -22
  96. meerschaum/connectors/api/_APIConnector.py +34 -6
  97. meerschaum/connectors/api/_actions.py +2 -2
  98. meerschaum/connectors/api/_jobs.py +12 -1
  99. meerschaum/connectors/api/_login.py +33 -7
  100. meerschaum/connectors/api/_misc.py +2 -2
  101. meerschaum/connectors/api/_pipes.py +23 -32
  102. meerschaum/connectors/api/_plugins.py +2 -2
  103. meerschaum/connectors/api/_request.py +1 -1
  104. meerschaum/connectors/api/_tokens.py +146 -0
  105. meerschaum/connectors/api/_users.py +70 -58
  106. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  107. meerschaum/connectors/instance/__init__.py +10 -0
  108. meerschaum/connectors/instance/_pipes.py +442 -0
  109. meerschaum/connectors/instance/_plugins.py +159 -0
  110. meerschaum/connectors/instance/_tokens.py +317 -0
  111. meerschaum/connectors/instance/_users.py +188 -0
  112. meerschaum/connectors/parse.py +5 -2
  113. meerschaum/connectors/sql/_SQLConnector.py +22 -5
  114. meerschaum/connectors/sql/_cli.py +12 -11
  115. meerschaum/connectors/sql/_create_engine.py +12 -168
  116. meerschaum/connectors/sql/_fetch.py +2 -18
  117. meerschaum/connectors/sql/_pipes.py +295 -278
  118. meerschaum/connectors/sql/_plugins.py +29 -0
  119. meerschaum/connectors/sql/_sql.py +46 -21
  120. meerschaum/connectors/sql/_users.py +36 -2
  121. meerschaum/connectors/sql/tables/__init__.py +254 -122
  122. meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
  123. meerschaum/connectors/valkey/_pipes.py +60 -31
  124. meerschaum/connectors/valkey/_plugins.py +2 -26
  125. meerschaum/core/Pipe/__init__.py +115 -85
  126. meerschaum/core/Pipe/_attributes.py +425 -124
  127. meerschaum/core/Pipe/_bootstrap.py +54 -24
  128. meerschaum/core/Pipe/_cache.py +555 -0
  129. meerschaum/core/Pipe/_clear.py +0 -11
  130. meerschaum/core/Pipe/_data.py +96 -68
  131. meerschaum/core/Pipe/_deduplicate.py +0 -13
  132. meerschaum/core/Pipe/_delete.py +12 -21
  133. meerschaum/core/Pipe/_drop.py +11 -23
  134. meerschaum/core/Pipe/_dtypes.py +49 -19
  135. meerschaum/core/Pipe/_edit.py +14 -4
  136. meerschaum/core/Pipe/_fetch.py +1 -1
  137. meerschaum/core/Pipe/_index.py +8 -14
  138. meerschaum/core/Pipe/_show.py +5 -5
  139. meerschaum/core/Pipe/_sync.py +123 -204
  140. meerschaum/core/Pipe/_verify.py +4 -4
  141. meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
  142. meerschaum/core/Plugin/__init__.py +1 -1
  143. meerschaum/core/Token/_Token.py +220 -0
  144. meerschaum/core/Token/__init__.py +12 -0
  145. meerschaum/core/User/_User.py +35 -10
  146. meerschaum/core/User/__init__.py +9 -1
  147. meerschaum/core/__init__.py +1 -0
  148. meerschaum/jobs/_Executor.py +88 -4
  149. meerschaum/jobs/_Job.py +149 -38
  150. meerschaum/jobs/__init__.py +3 -2
  151. meerschaum/jobs/systemd.py +8 -3
  152. meerschaum/models/__init__.py +35 -0
  153. meerschaum/models/pipes.py +247 -0
  154. meerschaum/models/tokens.py +38 -0
  155. meerschaum/models/users.py +26 -0
  156. meerschaum/plugins/__init__.py +301 -88
  157. meerschaum/plugins/bootstrap.py +510 -4
  158. meerschaum/utils/_get_pipes.py +97 -30
  159. meerschaum/utils/daemon/Daemon.py +199 -43
  160. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  161. meerschaum/utils/daemon/RotatingFile.py +63 -36
  162. meerschaum/utils/daemon/StdinFile.py +53 -13
  163. meerschaum/utils/daemon/__init__.py +47 -6
  164. meerschaum/utils/daemon/_names.py +6 -3
  165. meerschaum/utils/dataframe.py +479 -81
  166. meerschaum/utils/debug.py +49 -19
  167. meerschaum/utils/dtypes/__init__.py +476 -34
  168. meerschaum/utils/dtypes/sql.py +369 -29
  169. meerschaum/utils/formatting/__init__.py +5 -2
  170. meerschaum/utils/formatting/_jobs.py +1 -1
  171. meerschaum/utils/formatting/_pipes.py +52 -50
  172. meerschaum/utils/formatting/_pprint.py +1 -0
  173. meerschaum/utils/formatting/_shell.py +44 -18
  174. meerschaum/utils/misc.py +268 -186
  175. meerschaum/utils/packages/__init__.py +25 -40
  176. meerschaum/utils/packages/_packages.py +42 -34
  177. meerschaum/utils/pipes.py +213 -0
  178. meerschaum/utils/process.py +2 -2
  179. meerschaum/utils/prompt.py +175 -144
  180. meerschaum/utils/schedule.py +2 -1
  181. meerschaum/utils/sql.py +134 -47
  182. meerschaum/utils/threading.py +42 -0
  183. meerschaum/utils/typing.py +1 -4
  184. meerschaum/utils/venv/_Venv.py +2 -2
  185. meerschaum/utils/venv/__init__.py +7 -7
  186. meerschaum/utils/warnings.py +19 -13
  187. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
  188. meerschaum-3.0.0.dist-info/RECORD +289 -0
  189. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
  190. meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
  191. meerschaum/api/models/_interfaces.py +0 -15
  192. meerschaum/api/models/_locations.py +0 -15
  193. meerschaum/api/models/_metrics.py +0 -15
  194. meerschaum/config/_environment.py +0 -145
  195. meerschaum/config/static/__init__.py +0 -186
  196. meerschaum-2.9.5.dist-info/RECORD +0 -263
  197. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
  198. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
  199. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
  200. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -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
 
@@ -91,6 +94,7 @@ def _start_jobs(
91
94
  sysargs: Optional[List[str]] = None,
92
95
  executor_keys: Optional[str] = None,
93
96
  rm: bool = False,
97
+ line: Optional[str] = None,
94
98
  debug: bool = False,
95
99
  **kw
96
100
  ) -> SuccessTuple:
@@ -122,6 +126,7 @@ def _start_jobs(
122
126
  - `start job --name happy_seal`
123
127
  Start the job 'happy_seal' but via the `--name` flag.
124
128
  """
129
+ import shlex
125
130
  from meerschaum.utils.warnings import warn, info
126
131
  from meerschaum.utils.daemon._names import get_new_daemon_name
127
132
  from meerschaum.jobs import (
@@ -212,7 +217,29 @@ def _start_jobs(
212
217
 
213
218
  def _run_new_job(name: Optional[str] = None):
214
219
  name = name or get_new_daemon_name()
215
- job = Job(name, sysargs, executor_keys=executor_keys, delete_after_completion=rm)
220
+ if len(sysargs or []) >= 2 and sysargs[0] == 'start' and sysargs[1].startswith('job'):
221
+ return (False, "Create a new job with `-d` / `--daemon`."), name
222
+
223
+ job_sysargs = (
224
+ shlex.join([a for a in sysargs if a not in ('-d', '--daemon')])
225
+ if not line
226
+ else line
227
+ )
228
+ job = Job(name, job_sysargs, executor_keys=executor_keys, delete_after_completion=rm)
229
+
230
+ if not job.exists():
231
+ try:
232
+ confirm = yes_no(
233
+ f"Create new job '{name}'?\n {job_sysargs}\n",
234
+ default='y',
235
+ yes=kw.get('yes', False),
236
+ noask=kw.get('noask', False),
237
+ )
238
+ except Exception:
239
+ confirm = True
240
+ if not confirm:
241
+ return (False, "Nothing changed."), name
242
+
216
243
  return job.start(debug=debug), name
217
244
 
218
245
  def _run_existing_job(name: str):
@@ -285,6 +312,7 @@ def _start_jobs(
285
312
  if new_job
286
313
  else _run_existing_job(_name)
287
314
  )
315
+
288
316
  if not kw.get('nopretty', False):
289
317
  print_tuple(success_tuple)
290
318
 
@@ -300,7 +328,10 @@ def _start_jobs(
300
328
  + ("Failed to start job" + ("s" if len(_failures) != 1 else '')
301
329
  + f" {items_str(_failures)}." if _failures else '')
302
330
  )
303
- _install_healthcheck_job()
331
+ if not msg:
332
+ msg = "Nothing changed."
333
+ else:
334
+ _install_healthcheck_job()
304
335
  return len(_failures) == 0, msg
305
336
 
306
337
 
@@ -308,6 +339,7 @@ def _start_gui(
308
339
  action: Optional[List[str]] = None,
309
340
  mrsm_instance: Optional[str] = None,
310
341
  port: Optional[int] = None,
342
+ webterm_port: Optional[int] = None,
311
343
  debug: bool = False,
312
344
  **kw
313
345
  ) -> SuccessTuple:
@@ -321,7 +353,6 @@ def _start_gui(
321
353
 
322
354
  from meerschaum.utils.venv import venv_exec
323
355
  from meerschaum.utils.packages import attempt_import
324
- from meerschaum.utils.warnings import warn
325
356
  from meerschaum.utils.debug import dprint
326
357
  from meerschaum.utils.networking import find_open_ports
327
358
  from meerschaum.connectors.parse import parse_instance_keys
@@ -329,9 +360,10 @@ def _start_gui(
329
360
  webview, requests = attempt_import('webview', 'requests')
330
361
 
331
362
  success, msg = True, "Success"
332
- host = '127.0.0.1'
333
- if port is None:
334
- port = 8765
363
+ host = mrsm.get_config('webterm', 'host')
364
+ port = port or webterm_port
365
+ if not port:
366
+ port = mrsm.get_config('webterm', 'port')
335
367
 
336
368
  if not is_webterm_running(host, port, session_id='mrsm'):
337
369
  port = next(find_open_ports(port, 9000))
@@ -369,7 +401,7 @@ def _start_gui(
369
401
  break
370
402
  except Exception as e:
371
403
  if debug:
372
- dprint(e)
404
+ dprint(str(e))
373
405
  continue
374
406
  if starting_up is False:
375
407
  return False, f"The webterm failed to start within {timeout} seconds."
@@ -417,7 +449,6 @@ def _start_webterm(
417
449
  - `-i`, '--instance'
418
450
  The default instance to use for the Webterm shell.
419
451
  """
420
- import uuid
421
452
  from meerschaum._internal.term import get_webterm_app_and_manager, tornado_ioloop
422
453
  from meerschaum._internal.term.tools import (
423
454
  is_webterm_running,
@@ -426,15 +457,17 @@ def _start_webterm(
426
457
  )
427
458
  from meerschaum.utils.networking import find_open_ports
428
459
  from meerschaum.utils.warnings import info
460
+ from meerschaum.config.paths import WEBTERM_INTERNAL_RESOURCES_PATH
429
461
 
430
462
  if host is None:
431
- host = '127.0.0.1'
463
+ host = mrsm.get_config('api', 'webterm', 'host')
432
464
  if port is None:
433
- port = 8765
465
+ port = mrsm.get_config('api', 'webterm', 'port')
434
466
  if sysargs is None:
435
467
  sysargs = ['start', 'webterm']
436
468
  session_id = 'mrsm'
437
- tornado_app, term_manager = get_webterm_app_and_manager(instance_keys=mrsm_instance)
469
+
470
+ env_path = WEBTERM_INTERNAL_RESOURCES_PATH / (str(port) + '.json')
438
471
 
439
472
  if is_webterm_running(host, port, session_id=session_id):
440
473
  if force:
@@ -445,11 +478,18 @@ def _start_webterm(
445
478
  + " Include `-f` to start another server on a new port\n"
446
479
  + " or specify a different port with `-p`."
447
480
  )
481
+
482
+ tornado_app, term_manager = get_webterm_app_and_manager(
483
+ instance_keys=mrsm_instance,
484
+ port=port,
485
+ env_path=env_path,
486
+ )
448
487
  if not nopretty:
449
488
  info(
450
489
  f"Starting the webterm at http://{host}:{port}/webterm/{session_id} ..."
451
490
  "\n Press CTRL+C to quit."
452
491
  )
492
+
453
493
  tornado_app.listen(port, host)
454
494
  loop = tornado_ioloop.IOLoop.instance()
455
495
  try:
@@ -462,10 +502,15 @@ def _start_webterm(
462
502
  term_manager.shutdown()
463
503
  loop.close()
464
504
 
465
- sessions = get_mrsm_tmux_sessions()
505
+ sessions = get_mrsm_tmux_sessions(port=port)
466
506
  for session in sessions:
467
507
  kill_tmux_session(session)
468
508
 
509
+ try:
510
+ env_path.unlink()
511
+ except Exception:
512
+ pass
513
+
469
514
  return True, "Success"
470
515
 
471
516
 
@@ -483,7 +528,6 @@ def _start_connectors(
483
528
  from meerschaum.connectors.parse import parse_instance_keys
484
529
  from meerschaum.utils.pool import get_pool
485
530
  from meerschaum.utils.warnings import warn
486
- from meerschaum.utils.formatting import pprint
487
531
  from meerschaum.utils.misc import items_str
488
532
  if action is None:
489
533
  action = []
@@ -575,7 +619,6 @@ def _start_pipeline(
575
619
  """
576
620
  import json
577
621
  import time
578
- import sys
579
622
  from meerschaum._internal.entry import entry
580
623
  from meerschaum.utils.warnings import info, warn
581
624
  from meerschaum.utils.misc import is_int
@@ -630,7 +673,7 @@ def _start_pipeline(
630
673
  def do_entry() -> None:
631
674
  nonlocal success, msg, proc
632
675
  if timeout_seconds is None:
633
- success, msg = entry(sub_args_line, _patch_args=patch_args)
676
+ success, msg = entry(sub_args_line, _patch_args=patch_args, _use_cli_daemon=False)
634
677
  return
635
678
 
636
679
  sub_args_line_escaped = sub_args_line.replace("'", "<QUOTE>")
@@ -640,7 +683,7 @@ def _start_pipeline(
640
683
  "from meerschaum._internal.entry import entry\n\n"
641
684
  f"sub_args_line = '{sub_args_line_escaped}'.replace(\"<QUOTE>\", \"'\")\n"
642
685
  f"patch_args = json.loads('{patch_args_escaped_str}'.replace('<QUOTE>', \"'\"))\n"
643
- "success, msg = entry(sub_args_line, _patch_args=patch_args)\n"
686
+ "success, msg = entry(sub_args_line, _patch_args=patch_args, _use_cli_daemon=False)\n"
644
687
  f"print('{fence_begin}' + json.dumps((success, msg)) + '{fence_end}')"
645
688
  )
646
689
  proc = venv_exec(src, venv=None, as_proc=True)
@@ -676,7 +719,7 @@ def _start_pipeline(
676
719
 
677
720
  try:
678
721
  run_loop()
679
- except KeyboardInterrupt:
722
+ except (KeyboardInterrupt, SystemExit):
680
723
  warn("Cancelled pipeline.", stack=False)
681
724
  if proc is not None:
682
725
  _stop_process(proc)
@@ -688,6 +731,112 @@ def _start_pipeline(
688
731
  return success, msg
689
732
 
690
733
 
734
+ def _start_daemons(
735
+ timeout_seconds: Union[int, float, None] = None,
736
+ yes: bool = False,
737
+ force: bool = False,
738
+ noask: bool = False,
739
+ debug: bool = False,
740
+ **kwargs
741
+ ) -> SuccessTuple:
742
+ """
743
+ Start the Meerschaum CLI daemon processes.
744
+ """
745
+ from meerschaum.utils.warnings import warn, dprint
746
+ from meerschaum._internal.cli.daemons import (
747
+ get_cli_daemon,
748
+ get_cli_lock_path,
749
+ )
750
+ from meerschaum._internal.cli.workers import (
751
+ get_existing_cli_workers,
752
+ get_existing_cli_worker_indices,
753
+ )
754
+ from meerschaum.utils.prompt import yes_no
755
+ from meerschaum.actions import actions
756
+
757
+ workers = get_existing_cli_workers()
758
+ if not workers:
759
+ if debug:
760
+ dprint("No daemons are running, spawning a new process...")
761
+ workers = [get_cli_daemon()]
762
+
763
+ accepted_restart = False
764
+ any_daemons_are_running = any((worker.job.status == 'running') for worker in workers)
765
+ lock_paths = [get_cli_lock_path(ix) for ix in get_existing_cli_worker_indices()]
766
+ any_locks_exist = any(lock_path.exists() for lock_path in lock_paths)
767
+
768
+ if any_locks_exist:
769
+ warn(
770
+ "Locks are currently held by the CLI daemons.\n"
771
+ "Run again with `--force` to remove the locks.",
772
+ stack=False,
773
+ )
774
+
775
+ if not force:
776
+ return False, "Actions are currently running."
777
+
778
+ for lock_path in lock_paths:
779
+ try:
780
+ if lock_path.exists():
781
+ lock_path.unlink()
782
+ except Exception as e:
783
+ warn(f"Failed to release lock:\n{e}")
784
+
785
+ if any_daemons_are_running:
786
+ accepted_restart = force or yes_no(
787
+ "Restart running CLI daemons?",
788
+ yes=yes,
789
+ noask=noask,
790
+ default='n',
791
+ )
792
+
793
+ if not accepted_restart:
794
+ return True, "Daemons are already running; nothing to do."
795
+
796
+ stop_success, stop_msg = actions['stop'](
797
+ ['daemons'],
798
+ timeout_seconds=timeout_seconds,
799
+ debug=debug,
800
+ )
801
+ if not stop_success:
802
+ return stop_success, stop_msg
803
+
804
+ worker = get_cli_daemon()
805
+
806
+ if debug:
807
+ dprint("Cleaning up CLI daemon...")
808
+ cleanup_success, cleanup_msg = worker.cleanup()
809
+ if not cleanup_success:
810
+ return cleanup_success, cleanup_msg
811
+
812
+ if debug:
813
+ dprint("Starting CLI daemon...")
814
+
815
+ start_success, start_msg = worker.job.start()
816
+ if not start_success:
817
+ return start_success, start_msg
818
+
819
+ return True, "Success"
820
+
821
+
822
+ def _start_worker(action: Optional[List[str]] = None, **kwargs: Any) -> SuccessTuple:
823
+ """
824
+ Start a CLI worker process. This is intended for internal use only.
825
+ """
826
+ from meerschaum._internal.cli.workers import ActionWorker
827
+ from meerschaum.utils.misc import is_int
828
+
829
+ if not action:
830
+ return False, "No worker index is provided."
831
+
832
+ if not is_int(action[0]):
833
+ return False, "Invalid worker index."
834
+
835
+ ix = int(action[0])
836
+ worker = ActionWorker(ix)
837
+ return worker.run()
838
+
839
+
691
840
  ### NOTE: This must be the final statement of the module.
692
841
  ### Any subactions added below these lines will not
693
842
  ### be added to the `help` docstring.
@@ -7,7 +7,8 @@ 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, Dict, SuccessTuple, Any
10
+ from meerschaum.utils.typing import Optional, List, SuccessTuple, Any, Union
11
+
11
12
 
12
13
  def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
13
14
  """
@@ -16,6 +17,7 @@ def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
16
17
  from meerschaum.actions import choose_subaction
17
18
  options = {
18
19
  'jobs': _stop_jobs,
20
+ 'daemons': _stop_daemons,
19
21
  }
20
22
  return choose_subaction(action, options, **kw)
21
23
 
@@ -52,7 +54,7 @@ def _complete_stop(
52
54
  return options[sub](action=action, **kw)
53
55
 
54
56
  from meerschaum._internal.shell import default_action_completer
55
- return default_action_completer(action=(['start'] + action), **kw)
57
+ return default_action_completer(action=(['stop'] + action), **kw)
56
58
 
57
59
 
58
60
  def _stop_jobs(
@@ -111,6 +113,8 @@ def _stop_jobs(
111
113
  )
112
114
 
113
115
  if not jobs_to_stop:
116
+ if jobs:
117
+ return True, "The selected jobs are currently stopped."
114
118
  return False, "No running, paused or restarting jobs to stop."
115
119
 
116
120
  if not action:
@@ -160,6 +164,38 @@ def _stop_jobs(
160
164
  return success, msg
161
165
 
162
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
+
163
199
  ### NOTE: This must be the final statement of the module.
164
200
  ### Any subactions added below these lines will not
165
201
  ### be added to the `help` docstring.
@@ -287,8 +287,9 @@ def _sync_pipes(
287
287
  from meerschaum.utils.formatting._shell import progress
288
288
  from meerschaum.utils.formatting._shell import clear_screen
289
289
  from meerschaum.utils.formatting import print_pipes_results
290
- from meerschaum.config.static import STATIC_CONFIG
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()
meerschaum/actions/tag.py CHANGED
@@ -8,12 +8,12 @@ Functions for editing elements belong here.
8
8
 
9
9
  from __future__ import annotations
10
10
  import meerschaum as mrsm
11
- from meerschaum.utils.typing import List, Any, SuccessTuple, Optional, Dict
11
+ from meerschaum.utils.typing import List, Any, SuccessTuple, Optional
12
12
 
13
13
  def tag(
14
- action: Optional[List[str]] = None,
15
- **kwargs: Any
16
- ) -> SuccessTuple:
14
+ action: Optional[List[str]] = None,
15
+ **kwargs: Any
16
+ ) -> SuccessTuple:
17
17
  """
18
18
  Edit an existing element.
19
19
  """
@@ -25,10 +25,10 @@ def tag(
25
25
 
26
26
 
27
27
  def _tag_pipes(
28
- action: Optional[List[str]] = None,
29
- debug: bool = False,
30
- **kwargs: Any
31
- ) -> SuccessTuple:
28
+ action: Optional[List[str]] = None,
29
+ debug: bool = False,
30
+ **kwargs: Any
31
+ ) -> SuccessTuple:
32
32
  """
33
33
  Add or remove tags to registered pipes.
34
34
  Prefix a tag with a leading underscore to remove it.
@@ -68,6 +68,7 @@ def _tag_pipes(
68
68
  pipe_was_edited = True
69
69
 
70
70
  if pipe_was_edited:
71
+ pipe.tags = pipe_tags
71
72
  edited_pipes.append(pipe)
72
73
 
73
74
  if not edited_pipes:
@@ -57,8 +57,11 @@ def _verify_packages(
57
57
  Verify the versions of packages.
58
58
  """
59
59
  from meerschaum.utils.packages import (
60
- attempt_import, all_packages, is_installed, venv_contains_package,
61
- _monkey_patch_get_distribution, manually_import_module,
60
+ attempt_import,
61
+ all_packages,
62
+ is_installed,
63
+ venv_contains_package,
64
+ manually_import_module,
62
65
  )
63
66
 
64
67
  venv_packages, base_packages, miss_packages = [], [], []
@@ -78,12 +81,6 @@ def _verify_packages(
78
81
  )
79
82
  _where_list.append(import_name)
80
83
 
81
- if 'flask_compress' in venv_packages or 'dash' in venv_packages:
82
- flask_compress = attempt_import('flask_compress', lazy=False, debug=debug)
83
- _monkey_patch_get_distribution('flask-compress', flask_compress.__version__)
84
- if 'flask_compress' in venv_packages:
85
- venv_packages.remove('flask_compress')
86
-
87
84
  for import_name in base_packages:
88
85
  manually_import_module(import_name, debug=debug, venv=None)
89
86
  for import_name in venv_packages:
@@ -14,14 +14,13 @@ from fnmatch import fnmatch
14
14
  import meerschaum as mrsm
15
15
  from meerschaum.utils.typing import Dict, Any, Optional, PipesDict
16
16
  from meerschaum.config import get_config
17
- from meerschaum.config.static import STATIC_CONFIG, SERVER_ID
17
+ from meerschaum._internal.static import STATIC_CONFIG, SERVER_ID
18
18
  from meerschaum.utils.packages import attempt_import
19
19
  from meerschaum.utils import get_pipes as _get_pipes
20
20
  from meerschaum.config._paths import API_UVICORN_CONFIG_PATH, API_UVICORN_RESOURCES_PATH
21
21
  from meerschaum.plugins import _api_plugins
22
22
  from meerschaum.utils.warnings import warn, dprint
23
23
  from meerschaum.utils.threading import RLock
24
- from meerschaum.utils.misc import is_pipe_registered
25
24
  from meerschaum.connectors.parse import parse_instance_keys
26
25
 
27
26
  from meerschaum import __version__ as version
@@ -67,16 +66,13 @@ from meerschaum.api._exceptions import APIPermissionError
67
66
  uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
68
67
 
69
68
  uvicorn_config = None
70
- sys_config = get_config('system', 'api')
71
- permissions_config = get_config('system', 'api', 'permissions')
69
+ sys_config = get_config('api')
70
+ permissions_config = get_config('api', 'permissions')
72
71
 
73
72
  def get_uvicorn_config() -> Dict[str, Any]:
74
73
  """Read the Uvicorn configuration JSON and return a dictionary."""
75
74
  global uvicorn_config
76
75
  import json
77
- runtime = os.environ.get(STATIC_CONFIG['environment']['runtime'], None)
78
- if runtime == 'api':
79
- return get_config('system', 'api', 'uvicorn')
80
76
  _uvicorn_config = uvicorn_config
81
77
  with _locks['uvicorn_config']:
82
78
  if uvicorn_config is None:
@@ -85,6 +81,8 @@ def get_uvicorn_config() -> Dict[str, Any]:
85
81
  uvicorn_config = json.load(f)
86
82
  _uvicorn_config = uvicorn_config
87
83
  except Exception:
84
+ import traceback
85
+ traceback.print_exc()
88
86
  _uvicorn_config = sys_config.get('uvicorn', None)
89
87
 
90
88
  if _uvicorn_config is None:
@@ -95,11 +93,17 @@ def get_uvicorn_config() -> Dict[str, Any]:
95
93
 
96
94
  debug = get_uvicorn_config().get('debug', False)
97
95
  no_dash = get_uvicorn_config().get('no_dash', False)
96
+ no_webterm = get_uvicorn_config().get('no_webterm', False)
98
97
  no_auth = get_uvicorn_config().get('no_auth', False)
99
98
  private = get_uvicorn_config().get('private', False)
100
99
  production = get_uvicorn_config().get('production', False)
101
100
  _include_dash = (not no_dash)
101
+ _include_webterm = (not no_webterm) and _include_dash
102
102
  docs_enabled = not production or sys_config.get('endpoints', {}).get('docs_in_production', True)
103
+ webterm_port = (
104
+ get_uvicorn_config().get('webterm_port', None)
105
+ or mrsm.get_config('api', 'webterm', 'port')
106
+ )
103
107
 
104
108
  default_instance_keys = None
105
109
  _instance_connectors = defaultdict(lambda: None)
@@ -127,7 +131,7 @@ def get_api_connector(instance_keys: Optional[str] = None):
127
131
  )
128
132
  found_match: bool = False
129
133
  for allowed_keys_pattern in allowed_instance_keys:
130
- if fnmatch(instance_keys, allowed_keys_pattern):
134
+ if fnmatch(str(instance_keys), allowed_keys_pattern):
131
135
  found_match = True
132
136
  break
133
137
  if not found_match:
@@ -139,7 +143,9 @@ def get_api_connector(instance_keys: Optional[str] = None):
139
143
  if _instance_connectors[instance_keys] is None:
140
144
  try:
141
145
  is_valid_connector = True
142
- _instance_connectors[instance_keys] = parse_instance_keys(instance_keys, debug=debug)
146
+ instance_connector = parse_instance_keys(instance_keys, debug=debug)
147
+ instance_connector._cache_connector = get_cache_connector()
148
+ _instance_connectors[instance_keys] = instance_connector
143
149
  except Exception:
144
150
  is_valid_connector = False
145
151
 
@@ -166,7 +172,7 @@ def get_cache_connector(connector_keys: Optional[str] = None):
166
172
  return None
167
173
 
168
174
  connector_keys = connector_keys or get_config(
169
- 'system', 'api', 'cache', 'connector',
175
+ 'api', 'cache', 'connector',
170
176
  warn=False,
171
177
  )
172
178
  if connector_keys is None:
@@ -194,7 +200,11 @@ def pipes(instance_keys: Optional[str] = None, refresh: bool = False) -> PipesDi
194
200
  with _locks['pipes-' + instance_keys]:
195
201
  pipes = _instance_pipes[instance_keys]
196
202
  if pipes is None or refresh:
197
- pipes = _get_pipes(mrsm_instance=instance_keys)
203
+ pipes = _get_pipes(
204
+ mrsm_instance=instance_keys,
205
+ cache=True,
206
+ cache_connector_keys=get_cache_connector(),
207
+ )
198
208
  _instance_pipes[instance_keys] = pipes
199
209
  return pipes
200
210
 
@@ -210,9 +220,29 @@ def get_pipe(
210
220
  if location_key in ('[None]', 'None', 'null'):
211
221
  location_key = None
212
222
  instance_keys = str(get_api_connector(instance_keys))
213
- pipe = mrsm.Pipe(connector_keys, metric_key, location_key, mrsm_instance=instance_keys)
214
- if is_pipe_registered(pipe, pipes(instance_keys)):
215
- return pipes(instance_keys, refresh=refresh)[connector_keys][metric_key][location_key]
223
+ if connector_keys == 'mrsm':
224
+ raise fastapi.HTTPException(
225
+ status_code=403,
226
+ detail="Unable to serve any pipes with connector keys `mrsm` over the API.",
227
+ )
228
+
229
+ pipes_dict = pipes(instance_keys)
230
+ if (
231
+ not refresh
232
+ and connector_keys in pipes_dict
233
+ and metric_key in pipes_dict[connector_keys]
234
+ and location_key in pipes_dict[connector_keys][metric_key]
235
+ ):
236
+ return pipes_dict[connector_keys][metric_key][location_key]
237
+
238
+ pipe = mrsm.Pipe(
239
+ connector_keys,
240
+ metric_key,
241
+ location_key,
242
+ mrsm_instance=instance_keys,
243
+ cache=True,
244
+ cache_connector_keys=get_cache_connector(),
245
+ )
216
246
  return pipe
217
247
 
218
248
 
@@ -291,7 +321,7 @@ def __getattr__(name: str):
291
321
  raise AttributeError(f"Could not import '{name}'.")
292
322
 
293
323
  ### Import everything else within the API.
294
- from meerschaum.api._oauth2 import manager
324
+ from meerschaum.api._oauth2 import manager, ScopedAuth
295
325
  import meerschaum.api.routes as routes
296
326
  import meerschaum.api._events
297
327
  import meerschaum.api._websockets