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,9 @@ Register new Pipes. Requires the API to be running.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ from datetime import datetime, timedelta, timezone
12
+
10
13
  import meerschaum as mrsm
11
14
  from meerschaum.utils.typing import SuccessTuple, Any, List, Optional, Dict
12
15
 
@@ -26,6 +29,7 @@ def register(
26
29
  'plugins' : _register_plugins,
27
30
  'users' : _register_users,
28
31
  'connectors': _register_connectors,
32
+ 'tokens' : _register_tokens,
29
33
  }
30
34
  return choose_subaction(action, options, **kw)
31
35
 
@@ -175,11 +179,11 @@ def _register_plugins(
175
179
  reload_plugins(debug=debug)
176
180
 
177
181
  repo_connector = parse_repo_keys(repository)
178
- if repo_connector.__dict__.get('type', None) != 'api':
182
+ if repo_connector.type != 'api':
179
183
  return False, (
180
184
  "Can only upload plugins to the Meerschaum API." +
181
- f"Connector '{repo_connector}' is of type " +
182
- f"'{repo_connector.get('type', type(repo_connector))}'."
185
+ f"Connector '{repo_connector}' is " +
186
+ f"'{type(repo_connector)}'."
183
187
  )
184
188
 
185
189
  if len(action) == 0 or action == ['']:
@@ -272,6 +276,7 @@ def _complete_register_plugins(*args, **kw):
272
276
  def _register_users(
273
277
  action: Optional[List[str]] = None,
274
278
  mrsm_instance: Optional[str] = None,
279
+ scopes: Optional[List[str]] = None,
275
280
  shell: bool = False,
276
281
  debug: bool = False,
277
282
  **kw: Any
@@ -280,7 +285,7 @@ def _register_users(
280
285
  Register a new user to a Meerschaum instance.
281
286
  """
282
287
  from meerschaum.config import get_config
283
- from meerschaum.config.static import STATIC_CONFIG
288
+ from meerschaum._internal.static import STATIC_CONFIG
284
289
  from meerschaum.connectors.parse import parse_instance_keys
285
290
  from meerschaum.utils.warnings import warn, info
286
291
  from meerschaum.core import User
@@ -333,9 +338,17 @@ def _register_users(
333
338
  )
334
339
  if len(email) == 0:
335
340
  email = None
336
- user = User(username, password, email=email)
341
+ user = User(
342
+ username,
343
+ password,
344
+ email=email,
345
+ attributes={
346
+ 'scopes': scopes or list(STATIC_CONFIG['tokens']['scopes']),
347
+ },
348
+ instance=instance_connector,
349
+ )
337
350
  info(f"Registering user '{user}' to Meerschaum instance '{instance_connector}'...")
338
- result_tuple = instance_connector.register_user(user, debug=debug)
351
+ result_tuple = user.register(debug=debug)
339
352
  print_tuple(result_tuple)
340
353
  success[username] = result_tuple[0]
341
354
  if success[username]:
@@ -443,6 +456,130 @@ def _complete_register_connectors(
443
456
  return _complete_show_connectors(action)
444
457
 
445
458
 
459
+ def _register_tokens(
460
+ mrsm_instance: Optional[str] = None,
461
+ name: Optional[str] = None,
462
+ ttl_days: Optional[int] = None,
463
+ scopes: Optional[List[str]] = None,
464
+ end: Optional[datetime] = None,
465
+ force: bool = False,
466
+ yes: bool = False,
467
+ noask: bool = False,
468
+ nopretty: bool = False,
469
+ debug: bool = False,
470
+ **kwargs: Any
471
+ ) -> mrsm.SuccessTuple:
472
+ """
473
+ Register a new long-lived access token for API access.
474
+ Note that omitting and end time or TTL will generate token which does not expire.
475
+
476
+ Examples:
477
+
478
+ mrsm register token --end 2032-01-01
479
+
480
+ mrsm register token --ttl-days 1000
481
+
482
+ mrsm register token --name weather-sensor
483
+
484
+ """
485
+ import json
486
+ from meerschaum.utils.schedule import parse_start_time
487
+ from meerschaum.core import User
488
+ from meerschaum.core.Token._Token import Token, _PLACEHOLDER_EXPIRATION
489
+ from meerschaum.connectors.parse import parse_instance_keys
490
+ from meerschaum.utils.prompt import yes_no, choose
491
+ from meerschaum.utils.formatting import make_header, print_tuple
492
+ from meerschaum.utils.dtypes import value_is_null
493
+
494
+ expiration = (
495
+ end
496
+ if end is not None
497
+ else (
498
+ parse_start_time(f"starting in {ttl_days} days")
499
+ if ttl_days
500
+ else _PLACEHOLDER_EXPIRATION
501
+ )
502
+ )
503
+
504
+ instance_connector = parse_instance_keys(mrsm_instance)
505
+ user = None
506
+ if instance_connector.type != 'api':
507
+ usernames = instance_connector.get_users(debug=debug)
508
+ if not usernames:
509
+ return False, f"No users are registered to '{instance_connector}'."
510
+
511
+ username = choose(
512
+ "To which user should this token be registered? Enter the number.",
513
+ usernames,
514
+ )
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}'."
527
+
528
+ user = User(username, user_id=user_id, instance=mrsm_instance)
529
+
530
+ token = Token(
531
+ label=name,
532
+ expiration=expiration,
533
+ scopes=scopes,
534
+ user=user,
535
+ instance=mrsm_instance,
536
+ )
537
+
538
+ register_success, register_msg = token.register(debug=debug)
539
+ token_id = token.id
540
+ token_secret = token.secret
541
+ if not register_success:
542
+ return False, f"Failed to register token '{token.label}':\n{register_msg}"
543
+
544
+ token_model = token.to_model(refresh=True)
545
+ token_kwargs = dict(token_model)
546
+ token_kwargs['id'] = token_id
547
+ token_kwargs['secret'] = token_secret
548
+ token = Token(**token_kwargs)
549
+
550
+ if not nopretty:
551
+ print_tuple(
552
+ (
553
+ True,
554
+ (
555
+ f"Registered token '{token}'.\n "
556
+ "Write down the client secret, because it won't be shown again."
557
+ )
558
+ ),
559
+ calm=True
560
+ )
561
+
562
+ msg_to_print = (
563
+ (
564
+ make_header(f"Attributes for token '{token}':") + "\n"
565
+ + f" - Client ID: {token_model.id}\n"
566
+ + f" - Client Secret: {token_secret}\n"
567
+ + " - Expiration: "
568
+ + (
569
+ token.expiration.isoformat()
570
+ if not value_is_null(token.expiration)
571
+ else 'Does not expire.'
572
+ )
573
+ + "\n"
574
+ + f" - API Key: {token.get_api_key()}\n"
575
+ )
576
+ if not nopretty
577
+ else json.dumps({'secret': token_secret, **dict(token_model)})
578
+ )
579
+ print(msg_to_print)
580
+ return True, f"Successfully registered token '{token.label}'."
581
+
582
+
446
583
  ### NOTE: This must be the final statement of the module.
447
584
  ### Any subactions added below these lines will not
448
585
  ### be added to the `help` docstring.
@@ -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)
@@ -45,6 +45,8 @@ def show(
45
45
  'tags' : _show_tags,
46
46
  'schedules' : _show_schedules,
47
47
  'venvs' : _show_venvs,
48
+ 'tokens' : _show_tokens,
49
+ 'daemons' : _show_daemons,
48
50
  }
49
51
  return choose_subaction(action, show_options, **kw)
50
52
 
@@ -113,11 +115,11 @@ def _show_help(**kw: Any) -> SuccessTuple:
113
115
 
114
116
 
115
117
  def _show_config(
116
- action: Optional[List[str]] = None,
117
- debug: bool = False,
118
- nopretty: bool = False,
119
- **kw: Any
120
- ) -> SuccessTuple:
118
+ action: Optional[List[str]] = None,
119
+ debug: bool = False,
120
+ nopretty: bool = False,
121
+ **kw: Any
122
+ ) -> SuccessTuple:
121
123
  """
122
124
  Show the configuration dictionary.
123
125
  Sub-actions defined in the action list are recursive indices in the config dictionary.
@@ -204,7 +206,7 @@ def _show_version(nopretty: bool = False, **kw : Any) -> SuccessTuple:
204
206
  msg = "Meerschaum v" + version
205
207
  _print = info
206
208
  _print(msg)
207
- return (True, "Success")
209
+ return True, "Success"
208
210
 
209
211
 
210
212
  def _show_connectors(
@@ -562,6 +564,7 @@ def _complete_show_packages(
562
564
 
563
565
  return possibilities
564
566
 
567
+
565
568
  def _show_jobs(
566
569
  action: Optional[List[str]] = None,
567
570
  executor_keys: Optional[str] = None,
@@ -762,7 +765,7 @@ def _show_environment(
762
765
  """
763
766
  import os
764
767
  from meerschaum.utils.formatting import pprint
765
- from meerschaum.config._environment import get_env_vars
768
+ from meerschaum.config.environment import get_env_vars
766
769
  pprint(
767
770
  {
768
771
  env_var: os.environ[env_var]
@@ -789,34 +792,22 @@ def _show_tags(
789
792
  from meerschaum.utils.formatting import pipe_repr, UNICODE, ANSI
790
793
  from meerschaum.utils.pool import get_pool
791
794
  from meerschaum.config import get_config
795
+ from meerschaum.connectors.parse import parse_instance_keys
792
796
  rich_tree, rich_panel, rich_text, rich_console, rich_columns = (
793
797
  mrsm.attempt_import('rich.tree', 'rich.panel', 'rich.text', 'rich.console', 'rich.columns')
794
798
  )
795
- panel = rich_panel.Panel.fit('Tags')
796
- tree = rich_tree.Tree(panel)
797
799
  action = action or []
798
800
  tags = action + (tags or [])
799
- pipes = mrsm.get_pipes(as_list=True, tags=tags, **kwargs)
800
- if not pipes:
801
- return False, f"No pipes were found with the given tags."
802
801
 
803
- pool = get_pool(workers=workers)
804
802
  tag_prefix = get_config('formatting', 'pipes', 'unicode', 'icons', 'tag') if UNICODE else ''
805
803
  tag_style = get_config('formatting', 'pipes', 'ansi', 'styles', 'tags') if ANSI else None
806
804
 
807
- tags_pipes = defaultdict(lambda: [])
808
- gather_pipe_tags = lambda pipe: (pipe, (pipe.tags or []))
809
-
810
- pipes_tags = dict(pool.map(gather_pipe_tags, pipes))
811
-
812
- for pipe, tags in pipes_tags.items():
813
- for tag in tags:
814
- if action and tag not in action:
815
- continue
816
- tags_pipes[tag].append(pipe)
805
+ tags_pipes = mrsm.get_pipes(as_tags_dict=True, tags=tags, **kwargs)
806
+ if action:
807
+ tags_pipes = {tag: pipes for tag, pipes in tags_pipes.items() if tag in action}
817
808
 
818
809
  columns = []
819
- sorted_tags = sorted([tag for tag in tags_pipes])
810
+ sorted_tags = sorted(list(tags_pipes))
820
811
  for tag in sorted_tags:
821
812
  _pipes = tags_pipes[tag]
822
813
  tag_text = (
@@ -904,9 +895,7 @@ def _show_schedules(
904
895
  return True, "Success"
905
896
 
906
897
 
907
- def _show_venvs(
908
- **kwargs: Any
909
- ):
898
+ def _show_venvs(**kwargs: Any) -> SuccessTuple:
910
899
  """
911
900
  Print the available virtual environments in the current MRSM_ROOT_DIR.
912
901
  """
@@ -921,12 +910,176 @@ def _show_venvs(
921
910
  for _venv in os.listdir(VIRTENV_RESOURCES_PATH)
922
911
  if venv_exists(_venv)
923
912
  ]
924
- print_options(
925
- venvs,
926
- name = 'Venvs:',
927
- **kwargs
913
+ print_options(venvs, name='Virtual Environments:', **kwargs)
914
+ return True, "Success"
915
+
916
+
917
+ def _show_tokens(
918
+ action: Optional[List[str]] = None,
919
+ mrsm_instance: Optional[str] = None,
920
+ nopretty: bool = False,
921
+ debug: bool = False,
922
+ **kwargs: Any
923
+ ) -> SuccessTuple:
924
+ """
925
+ Print a table of the registered tokens on the instance.
926
+ """
927
+ import json
928
+ import uuid
929
+ from meerschaum.connectors.parse import parse_instance_keys
930
+ from meerschaum.utils.dtypes import value_is_null, json_serialize_value
931
+ from meerschaum.utils.misc import is_uuid
932
+ from meerschaum.utils.packages import import_rich
933
+ from meerschaum.utils.formatting import UNICODE, get_console
934
+ rich = import_rich()
935
+ rich_table, rich_json, rich_box = mrsm.attempt_import('rich.table', 'rich.json', 'rich.box')
936
+
937
+ conn = parse_instance_keys(mrsm_instance)
938
+
939
+ labels = [
940
+ label
941
+ for label in (action or [])
942
+ if not is_uuid(label)
943
+ ]
944
+ potential_token_ids = [
945
+ uuid.UUID(potential_id)
946
+ for potential_id in (action or [])
947
+ if is_uuid(potential_id)
948
+ ]
949
+
950
+ tokens = conn.get_tokens(
951
+ labels=(labels or None),
952
+ ids=(potential_token_ids or None),
953
+ debug=debug,
954
+ )
955
+
956
+ if nopretty:
957
+ for token in tokens:
958
+ print(json.dumps({
959
+ 'id': str(token.id),
960
+ 'label': token.label,
961
+ 'scopes': token.scopes,
962
+ 'expiration': (
963
+ token.expiration.isoformat()
964
+ if not value_is_null(token.expiration)
965
+ else None
966
+ ),
967
+ 'creation': (token.creation.isoformat() if not value_is_null(token.creation) else None),
968
+ 'user': (token.user.username if token.user is not None else None),
969
+ 'is_valid': token.is_valid,
970
+ }))
971
+ return True, "Success"
972
+
973
+ if len(tokens) == 1:
974
+ token = tokens[0]
975
+ tokens_json = json.dumps({
976
+ 'id': str(token.id),
977
+ 'label': token.label,
978
+ 'scopes': token.scopes,
979
+ 'creation': (token.creation.isoformat() if not value_is_null(token.creation) else None),
980
+ 'expiration': (token.expiration.isoformat() if not value_is_null(token.expiration) else None),
981
+ 'user': (token.user.username if token.user is not None else None),
982
+ 'is_valid': token.is_valid,
983
+ }, default=json_serialize_value, indent=4)
984
+ get_console().print(rich_json.JSON(tokens_json))
985
+
986
+ return True, "Success"
987
+
988
+ is_valid_true = (
989
+ "🟢"
990
+ if UNICODE
991
+ else "[+]"
992
+ )
993
+ is_valid_false = (
994
+ "🔴"
995
+ if UNICODE
996
+ else "[-]"
997
+ )
998
+
999
+ table = rich_table.Table(
1000
+ title=f"Registered Tokens on instance '{conn}'",
1001
+ box=rich_box.ROUNDED,
1002
+ title_style='bold',
928
1003
  )
1004
+ table.add_column("ID")
1005
+ table.add_column("Label")
1006
+ table.add_column("User")
1007
+ table.add_column("Expiration")
1008
+ table.add_column("Valid")
1009
+
1010
+ for token in tokens:
1011
+ table.add_row(
1012
+ str(token.id),
1013
+ token.label,
1014
+ (token.user.username if token.user is not None else ""),
1015
+ (
1016
+ token.expiration.isoformat()
1017
+ if not value_is_null(token.expiration)
1018
+ else 'Does not expire'
1019
+ ),
1020
+ (is_valid_true if token.is_valid else is_valid_false),
1021
+ )
1022
+
1023
+ mrsm.pprint(table)
1024
+ return True, "Success"
1025
+
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
+ )
929
1081
 
1082
+ mrsm.pprint(table)
930
1083
  return True, "Success"
931
1084
 
932
1085