meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc1__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 (153) 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 +17 -1
  5. meerschaum/_internal/entry.py +6 -6
  6. meerschaum/_internal/shell/Shell.py +1 -1
  7. meerschaum/_internal/static.py +372 -0
  8. meerschaum/actions/api.py +12 -2
  9. meerschaum/actions/bootstrap.py +7 -7
  10. meerschaum/actions/edit.py +142 -18
  11. meerschaum/actions/register.py +137 -6
  12. meerschaum/actions/show.py +117 -29
  13. meerschaum/actions/stop.py +4 -1
  14. meerschaum/actions/sync.py +1 -1
  15. meerschaum/actions/tag.py +9 -8
  16. meerschaum/api/__init__.py +9 -2
  17. meerschaum/api/_events.py +39 -2
  18. meerschaum/api/_oauth2.py +118 -8
  19. meerschaum/api/_tokens.py +102 -0
  20. meerschaum/api/dash/__init__.py +0 -1
  21. meerschaum/api/dash/callbacks/custom.py +2 -2
  22. meerschaum/api/dash/callbacks/dashboard.py +102 -18
  23. meerschaum/api/dash/callbacks/plugins.py +0 -1
  24. meerschaum/api/dash/callbacks/register.py +1 -1
  25. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  26. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  27. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  28. meerschaum/api/dash/components.py +30 -8
  29. meerschaum/api/dash/keys.py +19 -93
  30. meerschaum/api/dash/pages/dashboard.py +1 -20
  31. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  32. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  33. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  34. meerschaum/api/dash/pipes.py +94 -59
  35. meerschaum/api/dash/sessions.py +12 -0
  36. meerschaum/api/dash/tokens.py +606 -0
  37. meerschaum/api/dash/websockets.py +1 -1
  38. meerschaum/api/dash/webterm.py +4 -0
  39. meerschaum/api/models/__init__.py +23 -3
  40. meerschaum/api/models/_actions.py +22 -0
  41. meerschaum/api/models/_pipes.py +85 -7
  42. meerschaum/api/models/_tokens.py +81 -0
  43. meerschaum/api/resources/templates/termpage.html +12 -0
  44. meerschaum/api/routes/__init__.py +1 -0
  45. meerschaum/api/routes/_actions.py +3 -4
  46. meerschaum/api/routes/_connectors.py +3 -7
  47. meerschaum/api/routes/_jobs.py +14 -35
  48. meerschaum/api/routes/_login.py +49 -12
  49. meerschaum/api/routes/_misc.py +5 -10
  50. meerschaum/api/routes/_pipes.py +134 -111
  51. meerschaum/api/routes/_plugins.py +38 -28
  52. meerschaum/api/routes/_tokens.py +236 -0
  53. meerschaum/api/routes/_users.py +47 -35
  54. meerschaum/api/routes/_version.py +3 -3
  55. meerschaum/config/__init__.py +43 -20
  56. meerschaum/config/_default.py +32 -5
  57. meerschaum/config/_edit.py +28 -24
  58. meerschaum/config/_environment.py +1 -1
  59. meerschaum/config/_patch.py +6 -6
  60. meerschaum/config/_paths.py +5 -1
  61. meerschaum/config/_read_config.py +65 -34
  62. meerschaum/config/_sync.py +6 -3
  63. meerschaum/config/_version.py +1 -1
  64. meerschaum/config/stack/__init__.py +24 -5
  65. meerschaum/config/static.py +18 -0
  66. meerschaum/connectors/_Connector.py +10 -4
  67. meerschaum/connectors/__init__.py +4 -20
  68. meerschaum/connectors/api/_APIConnector.py +34 -6
  69. meerschaum/connectors/api/_actions.py +2 -2
  70. meerschaum/connectors/api/_jobs.py +1 -1
  71. meerschaum/connectors/api/_login.py +33 -7
  72. meerschaum/connectors/api/_misc.py +2 -2
  73. meerschaum/connectors/api/_pipes.py +15 -14
  74. meerschaum/connectors/api/_plugins.py +2 -2
  75. meerschaum/connectors/api/_request.py +1 -1
  76. meerschaum/connectors/api/_tokens.py +146 -0
  77. meerschaum/connectors/api/_users.py +70 -58
  78. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  79. meerschaum/connectors/instance/__init__.py +10 -0
  80. meerschaum/connectors/instance/_pipes.py +442 -0
  81. meerschaum/connectors/instance/_plugins.py +151 -0
  82. meerschaum/connectors/instance/_tokens.py +296 -0
  83. meerschaum/connectors/instance/_users.py +181 -0
  84. meerschaum/connectors/parse.py +4 -1
  85. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  86. meerschaum/connectors/sql/_cli.py +12 -11
  87. meerschaum/connectors/sql/_create_engine.py +6 -154
  88. meerschaum/connectors/sql/_fetch.py +2 -18
  89. meerschaum/connectors/sql/_pipes.py +42 -31
  90. meerschaum/connectors/sql/_plugins.py +29 -0
  91. meerschaum/connectors/sql/_sql.py +8 -1
  92. meerschaum/connectors/sql/_users.py +29 -2
  93. meerschaum/connectors/sql/tables/__init__.py +1 -1
  94. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  95. meerschaum/connectors/valkey/_pipes.py +9 -10
  96. meerschaum/connectors/valkey/_plugins.py +2 -26
  97. meerschaum/core/Pipe/__init__.py +31 -14
  98. meerschaum/core/Pipe/_attributes.py +156 -58
  99. meerschaum/core/Pipe/_bootstrap.py +54 -24
  100. meerschaum/core/Pipe/_data.py +41 -1
  101. meerschaum/core/Pipe/_dtypes.py +29 -14
  102. meerschaum/core/Pipe/_edit.py +12 -4
  103. meerschaum/core/Pipe/_show.py +5 -5
  104. meerschaum/core/Pipe/_sync.py +48 -53
  105. meerschaum/core/Pipe/_verify.py +1 -1
  106. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  107. meerschaum/core/Plugin/__init__.py +1 -1
  108. meerschaum/core/Token/_Token.py +221 -0
  109. meerschaum/core/Token/__init__.py +12 -0
  110. meerschaum/core/User/_User.py +34 -8
  111. meerschaum/core/User/__init__.py +9 -1
  112. meerschaum/core/__init__.py +1 -0
  113. meerschaum/jobs/_Job.py +3 -2
  114. meerschaum/jobs/__init__.py +3 -2
  115. meerschaum/jobs/systemd.py +1 -1
  116. meerschaum/models/__init__.py +35 -0
  117. meerschaum/models/pipes.py +247 -0
  118. meerschaum/models/tokens.py +38 -0
  119. meerschaum/models/users.py +26 -0
  120. meerschaum/plugins/__init__.py +22 -7
  121. meerschaum/plugins/bootstrap.py +2 -1
  122. meerschaum/utils/_get_pipes.py +68 -27
  123. meerschaum/utils/daemon/Daemon.py +2 -1
  124. meerschaum/utils/daemon/__init__.py +30 -2
  125. meerschaum/utils/dataframe.py +95 -14
  126. meerschaum/utils/dtypes/__init__.py +91 -18
  127. meerschaum/utils/dtypes/sql.py +44 -0
  128. meerschaum/utils/formatting/__init__.py +1 -1
  129. meerschaum/utils/formatting/_pipes.py +5 -4
  130. meerschaum/utils/formatting/_shell.py +11 -9
  131. meerschaum/utils/misc.py +237 -80
  132. meerschaum/utils/packages/__init__.py +3 -6
  133. meerschaum/utils/packages/_packages.py +34 -32
  134. meerschaum/utils/pipes.py +181 -0
  135. meerschaum/utils/process.py +1 -1
  136. meerschaum/utils/prompt.py +3 -1
  137. meerschaum/utils/schedule.py +1 -0
  138. meerschaum/utils/sql.py +114 -37
  139. meerschaum/utils/typing.py +1 -4
  140. meerschaum/utils/venv/_Venv.py +2 -2
  141. meerschaum/utils/venv/__init__.py +5 -7
  142. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
  143. meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
  144. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
  145. meerschaum/api/models/_interfaces.py +0 -15
  146. meerschaum/api/models/_locations.py +0 -15
  147. meerschaum/api/models/_metrics.py +0 -15
  148. meerschaum/config/static/__init__.py +0 -186
  149. meerschaum-2.9.5.dist-info/RECORD +0 -263
  150. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  151. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  152. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
  153. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
@@ -27,6 +27,7 @@ def edit(
27
27
  'users' : _edit_users,
28
28
  'plugins' : _edit_plugins,
29
29
  'jobs' : _edit_jobs,
30
+ 'tokens' : _edit_tokens,
30
31
  }
31
32
  return choose_subaction(action, options, **kw)
32
33
 
@@ -66,7 +67,7 @@ def _complete_edit(
66
67
  return default_action_completer(action=(['edit'] + action), **kw)
67
68
 
68
69
 
69
- def _edit_config(action: Optional[List[str]] = None, **kw : Any) -> SuccessTuple:
70
+ def _edit_config(action: Optional[List[str]] = None, **kw: Any) -> SuccessTuple:
70
71
  """
71
72
  Edit Meerschaum configuration files.
72
73
 
@@ -104,14 +105,14 @@ def _complete_edit_config(action: Optional[List[str]] = None, **kw: Any) -> List
104
105
  return possibilities
105
106
 
106
107
  def _edit_pipes(
107
- action: Optional[List[str]] = None,
108
- params: Optional[Dict[str, Any]] = None,
109
- yes: bool = False,
110
- force: bool = False,
111
- noask: bool = False,
112
- debug: bool = False,
113
- **kw: Any
114
- ) -> SuccessTuple:
108
+ action: Optional[List[str]] = None,
109
+ params: Optional[Dict[str, Any]] = None,
110
+ yes: bool = False,
111
+ force: bool = False,
112
+ noask: bool = False,
113
+ debug: bool = False,
114
+ **kw: Any
115
+ ) -> SuccessTuple:
115
116
  """
116
117
  Open and edit pipes' configuration files.
117
118
 
@@ -218,7 +219,7 @@ def _edit_users(
218
219
  from meerschaum.utils.prompt import prompt, yes_no, get_password, get_email
219
220
  from meerschaum.utils.misc import edit_file
220
221
  from meerschaum.config._paths import USERS_CACHE_RESOURCES_PATH
221
- from meerschaum.config.static import STATIC_CONFIG
222
+ from meerschaum._internal.static import STATIC_CONFIG
222
223
  from meerschaum.utils.yaml import yaml
223
224
  import os, pathlib
224
225
  instance_connector = parse_instance_keys(mrsm_instance)
@@ -235,7 +236,7 @@ def _edit_users(
235
236
  minimum_length = STATIC_CONFIG['users']['min_password_length'],
236
237
  )
237
238
 
238
- ## Make an admin
239
+ ### Make an admin
239
240
  _type = ''
240
241
  if yes_no(f"Change the user type for user '{username}'?", default='n', yes=yes, noask=noask):
241
242
  is_admin = yes_no(
@@ -248,15 +249,34 @@ def _edit_users(
248
249
  if yes_no(f"Change the email for user '{username}'?", default='n', yes=yes, noask=noask):
249
250
  email = get_email(username)
250
251
 
252
+ attributes = {}
253
+ ### Change the scopes.
254
+ attributes['scopes'] = prompt(
255
+ f"Scopes for user '{username}' (`*` to grant all scopes):",
256
+ default_editable=' '.join(User(
257
+ username,
258
+ instance=instance_connector,
259
+ ).get_scopes(refresh=True, debug=debug)),
260
+ ).split()
261
+ if attributes['scopes'] == ['*']:
262
+ attributes['scopes'] = list(STATIC_CONFIG['tokens']['scopes'])
263
+
251
264
  ### Change the attributes
252
- attributes = None
253
265
  if yes_no(
254
- f"Edit the attributes YAML file for user '{username}'?",
255
- default='n', yes=yes, noask=noask
266
+ f"Edit the attributes YAML file for user '{username}'?",
267
+ default='n',
268
+ yes=yes,
269
+ noask=noask,
256
270
  ):
257
271
  attr_path = pathlib.Path(os.path.join(USERS_CACHE_RESOURCES_PATH, f'{username}.yaml'))
258
272
  try:
259
- existing_attrs = instance_connector.get_user_attributes(User(username), debug=debug)
273
+ existing_attrs = instance_connector.get_user_attributes(
274
+ User(
275
+ username,
276
+ instance=instance_connector,
277
+ ),
278
+ debug=debug,
279
+ )
260
280
  with open(attr_path, 'w+') as f:
261
281
  yaml.dump(existing_attrs, stream=f, sort_keys=False)
262
282
  edit_file(attr_path)
@@ -271,7 +291,14 @@ def _edit_users(
271
291
  attributes = None
272
292
 
273
293
  ### Submit changes
274
- return User(username, password, email=email, type=_type, attributes=attributes)
294
+ return User(
295
+ username,
296
+ password,
297
+ email=email,
298
+ type=_type,
299
+ attributes=attributes,
300
+ instance=instance_connector,
301
+ )
275
302
 
276
303
  if not action:
277
304
  return False, "No users to edit."
@@ -301,14 +328,14 @@ def _edit_users(
301
328
  f" ({succeeded} succeeded, {failed} failed)."
302
329
  )
303
330
  info(msg)
304
- return True, msg
331
+ return succeeded > 0, msg
305
332
 
306
333
 
307
334
  def _edit_plugins(
308
335
  action: Optional[List[str]] = None,
309
336
  debug: bool = False,
310
337
  **kwargs: Any
311
- ):
338
+ ) -> mrsm.SuccessTuple:
312
339
  """
313
340
  Edit a plugin's source code.
314
341
  """
@@ -507,6 +534,103 @@ def _edit_jobs(
507
534
  return True, msg
508
535
 
509
536
 
537
+ def _edit_tokens(
538
+ action: Optional[List[str]] = None,
539
+ mrsm_instance: Optional[str] = None,
540
+ debug: bool = False,
541
+ ) -> mrsm.SuccessTuple:
542
+ """
543
+ Edit tokens registered to an instance.
544
+ """
545
+ import uuid
546
+ from meerschaum.utils.warnings import warn
547
+ from meerschaum.utils.misc import is_uuid
548
+ from meerschaum.core import Token
549
+ from meerschaum.connectors.parse import parse_instance_keys
550
+ from meerschaum.utils.prompt import prompt, yes_no
551
+ from meerschaum._internal.static import STATIC_CONFIG
552
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
553
+
554
+ if not action:
555
+ return False, "Provide token labels or IDs for the tokens to edit."
556
+
557
+ conn = parse_instance_keys(mrsm_instance)
558
+
559
+ labels = [
560
+ label
561
+ for label in (action or [])
562
+ if not is_uuid(label)
563
+ ]
564
+ potential_token_ids = [
565
+ uuid.UUID(potential_id)
566
+ for potential_id in (action or [])
567
+ if is_uuid(potential_id)
568
+ ]
569
+
570
+ tokens = conn.get_tokens(
571
+ labels=(labels or None),
572
+ ids=(potential_token_ids or None),
573
+ debug=debug,
574
+ )
575
+
576
+ num_edited = 0
577
+ for token in tokens:
578
+ token_model = token.to_model(refresh=True)
579
+ if token_model is None:
580
+ warn(f"Token '{token.id}' does not exist.", stack=False)
581
+ continue
582
+
583
+ new_attrs = {}
584
+
585
+ new_attrs['label'] = prompt("Label:", default_editable=token_model.label)
586
+ new_expiration_str = prompt(
587
+ "Expiration (empty for no expiration):",
588
+ default_editable=('' if token.expiration is None else str(token_model.expiration)),
589
+ )
590
+ new_attrs['expiration'] = (
591
+ dateutil_parser.parse(new_expiration_str)
592
+ if new_expiration_str
593
+ else None
594
+ )
595
+ new_scopes_str = prompt(
596
+ "Scope (`*` to grant all permissions):",
597
+ default_editable=' '.join(token_model.scopes),
598
+ )
599
+ new_attrs['scopes'] = (
600
+ new_scopes_str.split(' ')
601
+ if new_scopes_str != '*'
602
+ else list(STATIC_CONFIG['tokens']['scopes'])
603
+ )
604
+ invalidate = (
605
+ yes_no("Do you want to invalidate this token?", default='n')
606
+ if token_model.is_valid
607
+ else True
608
+ )
609
+ new_attrs['is_valid'] = token_model.is_valid and not invalidate
610
+
611
+ new_token = Token(**{**dict(token_model), **new_attrs})
612
+ edit_success, edit_msg = new_token.edit(debug=debug)
613
+ if not edit_success:
614
+ return False, edit_msg
615
+
616
+ if invalidate:
617
+ invalidate_success, invalidate_msg = new_token.invalidate(debug=debug)
618
+ if not invalidate_success:
619
+ return False, invalidate_msg
620
+
621
+ num_edited += 1
622
+
623
+ msg = (
624
+ f"Successfully edited {num_edited} token"
625
+ + ('s' if num_edited != 1 else '')
626
+ + '.'
627
+ )
628
+
629
+ return True, msg
630
+
631
+
632
+
633
+
510
634
  ### NOTE: This must be the final statement of the module.
511
635
  ### Any subactions added below these lines will not
512
636
  ### be added to the `help` docstring.
@@ -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,124 @@ 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
+ 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}'."
521
+
522
+ user = User(username, user_id=user_id, instance=mrsm_instance)
523
+
524
+ token = Token(
525
+ label=name,
526
+ expiration=expiration,
527
+ scopes=scopes,
528
+ user=user,
529
+ instance=mrsm_instance,
530
+ )
531
+
532
+ register_success, register_msg = token.register(debug=debug)
533
+ token_id = token.id
534
+ token_secret = token.secret
535
+ if not register_success:
536
+ return False, f"Failed to register token '{token.label}':\n{register_msg}"
537
+
538
+ token_model = token.to_model(refresh=True)
539
+ token_kwargs = dict(token_model)
540
+ token_kwargs['id'] = token_id
541
+ token_kwargs['secret'] = token_secret
542
+ token = Token(**token_kwargs)
543
+
544
+ if not nopretty:
545
+ print_tuple(
546
+ (
547
+ True,
548
+ (
549
+ f"Registered token '{token}'.\n "
550
+ "Write down the client secret, because it won't be shown again."
551
+ )
552
+ ),
553
+ calm=True
554
+ )
555
+
556
+ msg_to_print = (
557
+ (
558
+ make_header(f"Attributes for token '{token}':") + "\n"
559
+ + f" - Client ID: {token_model.id}\n"
560
+ + f" - Client Secret: {token_secret}\n"
561
+ + " - Expiration: "
562
+ + (
563
+ token.expiration.isoformat()
564
+ if not value_is_null(token.expiration)
565
+ else 'Does not expire.'
566
+ )
567
+ + "\n"
568
+ + f" - API Key: {token.get_api_key()}\n"
569
+ )
570
+ if not nopretty
571
+ else json.dumps({'secret': token_secret, **dict(token_model)})
572
+ )
573
+ print(msg_to_print)
574
+ return True, f"Successfully registered token '{token.label}'."
575
+
576
+
446
577
  ### NOTE: This must be the final statement of the module.
447
578
  ### Any subactions added below these lines will not
448
579
  ### be added to the `help` docstring.
@@ -45,6 +45,7 @@ def show(
45
45
  'tags' : _show_tags,
46
46
  'schedules' : _show_schedules,
47
47
  'venvs' : _show_venvs,
48
+ 'tokens' : _show_tokens,
48
49
  }
49
50
  return choose_subaction(action, show_options, **kw)
50
51
 
@@ -113,11 +114,11 @@ def _show_help(**kw: Any) -> SuccessTuple:
113
114
 
114
115
 
115
116
  def _show_config(
116
- action: Optional[List[str]] = None,
117
- debug: bool = False,
118
- nopretty: bool = False,
119
- **kw: Any
120
- ) -> SuccessTuple:
117
+ action: Optional[List[str]] = None,
118
+ debug: bool = False,
119
+ nopretty: bool = False,
120
+ **kw: Any
121
+ ) -> SuccessTuple:
121
122
  """
122
123
  Show the configuration dictionary.
123
124
  Sub-actions defined in the action list are recursive indices in the config dictionary.
@@ -789,34 +790,22 @@ def _show_tags(
789
790
  from meerschaum.utils.formatting import pipe_repr, UNICODE, ANSI
790
791
  from meerschaum.utils.pool import get_pool
791
792
  from meerschaum.config import get_config
793
+ from meerschaum.connectors.parse import parse_instance_keys
792
794
  rich_tree, rich_panel, rich_text, rich_console, rich_columns = (
793
795
  mrsm.attempt_import('rich.tree', 'rich.panel', 'rich.text', 'rich.console', 'rich.columns')
794
796
  )
795
- panel = rich_panel.Panel.fit('Tags')
796
- tree = rich_tree.Tree(panel)
797
797
  action = action or []
798
798
  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
799
 
803
- pool = get_pool(workers=workers)
804
800
  tag_prefix = get_config('formatting', 'pipes', 'unicode', 'icons', 'tag') if UNICODE else ''
805
801
  tag_style = get_config('formatting', 'pipes', 'ansi', 'styles', 'tags') if ANSI else None
806
802
 
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)
803
+ tags_pipes = mrsm.get_pipes(as_tags_dict=True, tags=tags, **kwargs)
804
+ if action:
805
+ tags_pipes = {tag: pipes for tag, pipes in tags_pipes.items() if tag in action}
817
806
 
818
807
  columns = []
819
- sorted_tags = sorted([tag for tag in tags_pipes])
808
+ sorted_tags = sorted(list(tags_pipes))
820
809
  for tag in sorted_tags:
821
810
  _pipes = tags_pipes[tag]
822
811
  tag_text = (
@@ -904,9 +893,7 @@ def _show_schedules(
904
893
  return True, "Success"
905
894
 
906
895
 
907
- def _show_venvs(
908
- **kwargs: Any
909
- ):
896
+ def _show_venvs(**kwargs: Any) -> SuccessTuple:
910
897
  """
911
898
  Print the available virtual environments in the current MRSM_ROOT_DIR.
912
899
  """
@@ -921,12 +908,113 @@ def _show_venvs(
921
908
  for _venv in os.listdir(VIRTENV_RESOURCES_PATH)
922
909
  if venv_exists(_venv)
923
910
  ]
924
- print_options(
925
- venvs,
926
- name = 'Venvs:',
927
- **kwargs
911
+ print_options(venvs, name='Virtual Environments:', **kwargs)
912
+ return True, "Success"
913
+
914
+
915
+ def _show_tokens(
916
+ action: Optional[List[str]] = None,
917
+ mrsm_instance: Optional[str] = None,
918
+ nopretty: bool = False,
919
+ debug: bool = False,
920
+ **kwargs: Any
921
+ ) -> SuccessTuple:
922
+ """
923
+ Print a table of the registered tokens on the instance.
924
+ """
925
+ import json
926
+ import uuid
927
+ from meerschaum.connectors.parse import parse_instance_keys
928
+ from meerschaum.utils.dtypes import value_is_null, json_serialize_value
929
+ from meerschaum.utils.misc import is_uuid
930
+ from meerschaum.utils.packages import import_rich
931
+ from meerschaum.utils.formatting import UNICODE, get_console
932
+ rich = import_rich()
933
+ rich_table, rich_json = mrsm.attempt_import('rich.table', 'rich.json')
934
+
935
+ conn = parse_instance_keys(mrsm_instance)
936
+
937
+ labels = [
938
+ label
939
+ for label in (action or [])
940
+ if not is_uuid(label)
941
+ ]
942
+ potential_token_ids = [
943
+ uuid.UUID(potential_id)
944
+ for potential_id in (action or [])
945
+ if is_uuid(potential_id)
946
+ ]
947
+
948
+ tokens = conn.get_tokens(
949
+ labels=(labels or None),
950
+ ids=(potential_token_ids or None),
951
+ debug=debug,
952
+ )
953
+
954
+ if nopretty:
955
+ for token in tokens:
956
+ print(json.dumps({
957
+ 'id': str(token.id),
958
+ 'label': token.label,
959
+ 'scopes': token.scopes,
960
+ 'expiration': (
961
+ token.expiration.isoformat()
962
+ if not value_is_null(token.expiration)
963
+ else None
964
+ ),
965
+ 'creation': (token.creation.isoformat() if not value_is_null(token.creation) else None),
966
+ 'user': (token.user.username if token.user is not None else None),
967
+ 'is_valid': token.is_valid,
968
+ }))
969
+ return True, "Success"
970
+
971
+ if len(tokens) == 1:
972
+ token = tokens[0]
973
+ tokens_json = json.dumps({
974
+ 'id': str(token.id),
975
+ 'label': token.label,
976
+ 'scopes': token.scopes,
977
+ 'creation': (token.creation.isoformat() if not value_is_null(token.creation) else None),
978
+ 'expiration': (token.expiration.isoformat() if not value_is_null(token.expiration) else None),
979
+ 'user': (token.user.username if token.user is not None else None),
980
+ 'is_valid': token.is_valid,
981
+ }, default=json_serialize_value, indent=4)
982
+ get_console().print(rich_json.JSON(tokens_json))
983
+
984
+ return True, "Success"
985
+
986
+ is_valid_true = (
987
+ "🟢"
988
+ if UNICODE
989
+ else "[+]"
928
990
  )
991
+ is_valid_false = (
992
+ "🔴"
993
+ if UNICODE
994
+ else "[-]"
995
+ )
996
+
997
+ table = rich_table.Table(title=f"Registered Tokens on instance '{conn}'")
998
+ table.add_column("ID")
999
+ table.add_column("Label")
1000
+ table.add_column("User")
1001
+ table.add_column("Expiration")
1002
+ table.add_column("Valid")
1003
+
1004
+ for token in tokens:
1005
+ table.add_row(
1006
+ str(token.id),
1007
+ token.label,
1008
+ (token.user.username if token.user is not None else ""),
1009
+ (
1010
+ token.expiration.isoformat()
1011
+ if not value_is_null(token.expiration)
1012
+ else 'Does not expire'
1013
+ ),
1014
+ (is_valid_true if token.is_valid else is_valid_false),
1015
+ )
929
1016
 
1017
+ mrsm.pprint(table)
930
1018
  return True, "Success"
931
1019
 
932
1020
 
@@ -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
11
+
11
12
 
12
13
  def stop(action: Optional[List[str]] = None, **kw) -> SuccessTuple:
13
14
  """
@@ -111,6 +112,8 @@ def _stop_jobs(
111
112
  )
112
113
 
113
114
  if not jobs_to_stop:
115
+ if jobs:
116
+ return True, "The selected jobs are currently running."
114
117
  return False, "No running, paused or restarting jobs to stop."
115
118
 
116
119
  if not action:
@@ -287,7 +287,7 @@ 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
292
 
293
293
  noninteractive_val = os.environ.get(STATIC_CONFIG['environment']['noninteractive'], None)
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: