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
meerschaum/actions/api.py CHANGED
@@ -6,7 +6,10 @@ Start the Meerschaum WebAPI with the `api` action.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
+
9
10
  import os
11
+
12
+ import meerschaum as mrsm
10
13
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
14
 
12
15
 
@@ -93,9 +96,11 @@ def _api_start(
93
96
  action: Optional[List[str]] = None,
94
97
  host: Optional[str] = None,
95
98
  port: Optional[int] = None,
99
+ webterm_port: Optional[int] = None,
96
100
  workers: Optional[int] = None,
97
101
  mrsm_instance: Optional[str] = None,
98
102
  no_dash: bool = False,
103
+ no_webterm: bool = False,
99
104
  no_auth: bool = False,
100
105
  private: bool = False,
101
106
  secure: bool = False,
@@ -118,6 +123,10 @@ def _api_start(
118
123
  The address to bind to.
119
124
  If `None`, use '0.0.0.0'.
120
125
 
126
+ webterm_port: Optional[int], default None
127
+ Port to bind the webterm server to.
128
+ If `None`, use 8765.
129
+
121
130
  workers: Optional[int], default None
122
131
  How many worker threads to run.
123
132
  If `None`, defaults to the number of CPU cores or 1 on Android.
@@ -148,8 +157,6 @@ def _api_start(
148
157
 
149
158
  from meerschaum.utils.packages import (
150
159
  attempt_import,
151
- venv_contains_package,
152
- pip_install,
153
160
  run_python_package,
154
161
  )
155
162
  from meerschaum.utils.misc import is_int, filter_keywords
@@ -163,10 +170,11 @@ def _api_start(
163
170
  API_UVICORN_CONFIG_PATH,
164
171
  CACHE_RESOURCES_PATH,
165
172
  PACKAGE_ROOT_PATH,
173
+ ROOT_DIR_PATH,
166
174
  )
167
175
  from meerschaum.config._patch import apply_patch_to_config
168
- from meerschaum.config._environment import get_env_vars
169
- from meerschaum.config.static import STATIC_CONFIG, SERVER_ID
176
+ from meerschaum.config.environment import get_env_vars
177
+ from meerschaum._internal.static import STATIC_CONFIG, SERVER_ID
170
178
  from meerschaum.connectors.parse import parse_instance_keys
171
179
  from meerschaum.utils.pool import get_pool
172
180
 
@@ -186,9 +194,9 @@ def _api_start(
186
194
  uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
187
195
  uvicorn_env_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'uvicorn.env'
188
196
 
189
- api_config = deepcopy(get_config('system', 'api'))
197
+ api_config = deepcopy(get_config('api'))
190
198
  cf = _config()
191
- forwarded_allow_ips = get_config('system', 'api', 'uvicorn', 'forwarded_allow_ips')
199
+ forwarded_allow_ips = get_config('api', 'uvicorn', 'forwarded_allow_ips')
192
200
  uvicorn_config = api_config['uvicorn']
193
201
  if port is None:
194
202
  ### default
@@ -222,7 +230,7 @@ def _api_start(
222
230
  instance_connector = parse_instance_keys(mrsm_instance, debug=debug)
223
231
  if instance_connector.type == 'api' and instance_connector.protocol != 'https':
224
232
  allow_http_parent = get_config(
225
- 'system', 'api', 'permissions', 'chaining', 'insecure_parent_instance'
233
+ 'api', 'permissions', 'chaining', 'insecure_parent_instance'
226
234
  )
227
235
  if not allow_http_parent:
228
236
  return False, (
@@ -232,7 +240,7 @@ def _api_start(
232
240
  f" - Ensure that '{instance_connector}' is available over HTTPS, " +
233
241
  "and with `edit config`,\n" +
234
242
  f" change the `protocol` for '{instance_connector}' to 'https'.\n\n" +
235
- " - Run `edit config system` and search for `permissions`.\n" +
243
+ " - Run `edit config api` and search for `permissions`.\n" +
236
244
  " Under `api:permissions:chaining`, change the value of " +
237
245
  "`insecure_parent_instance` to `true`,\n" +
238
246
  " then restart the API process."
@@ -243,7 +251,9 @@ def _api_start(
243
251
  'host': host,
244
252
  'env_file': str(uvicorn_env_path.as_posix()),
245
253
  'mrsm_instance': mrsm_instance,
254
+ 'webterm_port': webterm_port,
246
255
  'no_dash': no_dash,
256
+ 'no_webterm': no_webterm or no_auth,
247
257
  'no_auth': no_auth,
248
258
  'private': private,
249
259
  'production': production,
@@ -259,11 +269,33 @@ def _api_start(
259
269
  uvicorn_config['use_colors'] = (not nopretty) if nopretty else ANSI
260
270
 
261
271
  api_config['uvicorn'] = uvicorn_config
262
- cf['system']['api']['uvicorn'] = uvicorn_config
272
+ cf['api']['uvicorn'] = uvicorn_config
263
273
  if secure:
264
- cf['system']['api']['permissions']['actions']['non_admin'] = False
274
+ cf['api']['permissions']['actions']['non_admin'] = False
275
+
276
+ if not uvicorn_config['no_webterm']:
277
+ from meerschaum._internal.term.tools import is_webterm_running
278
+ if webterm_port is None:
279
+ webterm_port = int(mrsm.get_config('api', 'webterm', 'port'))
280
+ if is_webterm_running(port=webterm_port):
281
+ return (
282
+ False,
283
+ (
284
+ f"Webterm is running on port {webterm_port}. "
285
+ "Start the API again with `--webterm-port`."
286
+ )
287
+ )
265
288
 
266
- custom_keys = ['mrsm_instance', 'no_dash', 'no_auth', 'private', 'debug', 'production']
289
+ custom_keys = [
290
+ 'mrsm_instance',
291
+ 'no_dash',
292
+ 'no_webterm',
293
+ 'no_auth',
294
+ 'private',
295
+ 'debug',
296
+ 'production',
297
+ 'webterm_port',
298
+ ]
267
299
 
268
300
  ### write config to a temporary file to communicate with uvicorn threads
269
301
  try:
@@ -289,6 +321,7 @@ def _api_start(
289
321
  MRSM_SERVER_ID: SERVER_ID,
290
322
  MRSM_RUNTIME: 'api',
291
323
  MRSM_CONFIG: json.loads(os.environ.get(MRSM_CONFIG, '{}')),
324
+ MRSM_ROOT_DIR: ROOT_DIR_PATH.as_posix(),
292
325
  'FORWARDED_ALLOW_IPS': forwarded_allow_ips,
293
326
  'TERM': os.environ.get('TERM', 'screen-256color'),
294
327
  'SHELL': os.environ.get('SHELL', '/bin/bash'),
@@ -302,6 +335,8 @@ def _api_start(
302
335
  )
303
336
  ),
304
337
  'HOSTNAME': os.environ.get('HOSTNAME', 'api'),
338
+ 'XDG_RUNTIME_DIR': os.environ.get('XDG_RUNTIME_DIR', ''),
339
+ 'DBUS_SESSION_BUS_ADDRESS': os.environ.get('DBUS_SESSION_BUS_ADDRESS', ''),
305
340
  })
306
341
  for env_var in get_env_vars():
307
342
  if env_var in env_dict:
@@ -316,10 +351,10 @@ def _api_start(
316
351
  env_text = ''
317
352
  for key, val in env_dict.items():
318
353
  value = str(
319
- json.dumps(val, default=json_serialize_value)
354
+ json.dumps(val, default=json_serialize_value, separators=(',', ':'))
320
355
  if isinstance(val, (dict))
321
356
  else val
322
- ).replace('\\', '\\\\').replace("'", "'\\''")
357
+ ).replace('\\', '\\\\').replace("'", "\\'")
323
358
  env_text += f"{key}='{value}'\n"
324
359
  with open(uvicorn_env_path, 'w+', encoding='utf-8') as f:
325
360
  if debug:
@@ -384,11 +419,16 @@ def _api_start(
384
419
  except KeyboardInterrupt:
385
420
  pass
386
421
 
422
+ old_stdin = sys.stdin
423
+ sys.stdin = None
424
+
387
425
  if production:
388
426
  _run_gunicorn()
389
427
  else:
390
428
  _run_uvicorn()
391
429
 
430
+ sys.stdin = old_stdin
431
+
392
432
  ### Cleanup
393
433
  if uvicorn_config_path.parent.exists():
394
434
  shutil.rmtree(uvicorn_config_path.parent)
@@ -9,6 +9,7 @@ Attach to running jobs.
9
9
  import meerschaum as mrsm
10
10
  from meerschaum.utils.typing import Optional, List, Any, SuccessTuple
11
11
 
12
+
12
13
  def attach(
13
14
  action: Optional[List[str]] = None,
14
15
  executor_keys: Optional[str] = None,
@@ -10,7 +10,7 @@ Functions for bootstrapping elements
10
10
  from __future__ import annotations
11
11
 
12
12
  import meerschaum as mrsm
13
- from meerschaum.utils.typing import Union, Any, Sequence, SuccessTuple, Optional, Tuple, List
13
+ from meerschaum.utils.typing import Union, Any, SuccessTuple, Optional, Tuple, List
14
14
 
15
15
 
16
16
  def bootstrap(
@@ -249,7 +249,7 @@ def _bootstrap_connectors(
249
249
  from meerschaum.config._edit import write_config
250
250
  from meerschaum.utils.formatting import pprint
251
251
  from meerschaum.utils.formatting._shell import clear_screen
252
- from meerschaum.connectors import attributes as connector_attributes
252
+ from meerschaum.config._default import CONNECTOR_ATTRIBUTES
253
253
  from meerschaum.utils.warnings import warn, info
254
254
  from meerschaum.utils.misc import is_int
255
255
 
@@ -314,7 +314,7 @@ def _bootstrap_connectors(
314
314
  cls_required_attrs = getattr(cls, 'REQUIRED_ATTRIBUTES', [])
315
315
  cls_optional_attrs = getattr(cls, 'OPTIONAL_ATTRIBUTES', [])
316
316
  cls_default_attrs = getattr(cls, 'DEFAULT_ATTRIBUTES', {})
317
- type_attributes = connector_attributes.get(
317
+ type_attributes = CONNECTOR_ATTRIBUTES.get(
318
318
  _type,
319
319
  {
320
320
  'required': cls_required_attrs,
@@ -330,16 +330,16 @@ def _bootstrap_connectors(
330
330
  f"Flavor for connector '{_type}:{_label}':",
331
331
  sorted(list(type_attributes['flavors'])),
332
332
  default = (
333
- 'timescaledb'
334
- if 'timescaledb' in type_attributes['flavors']
333
+ 'postgresql'
334
+ if 'postgresql' in type_attributes['flavors']
335
335
  else None
336
336
  )
337
337
  )
338
338
  except KeyboardInterrupt:
339
339
  return abort_tuple
340
340
  new_attributes['flavor'] = flavor
341
- required = sorted(list(connector_attributes[_type]['flavors'][flavor]['requirements']))
342
- optional = sorted(list(connector_attributes[_type]['flavors'][flavor].get('optional', {})))
341
+ required = sorted(list(CONNECTOR_ATTRIBUTES[_type]['flavors'][flavor]['requirements']))
342
+ optional = sorted(list(CONNECTOR_ATTRIBUTES[_type]['flavors'][flavor].get('optional', {})))
343
343
  default = type_attributes['flavors'][flavor].get('defaults', {})
344
344
  else:
345
345
  required = sorted(list(type_attributes.get('required', {})))
@@ -429,7 +429,7 @@ def _bootstrap_plugins(
429
429
  action = [prompt("Enter the name of your new plugin:")]
430
430
 
431
431
  for plugin_name in action:
432
- bootstrap_success, bootstrap_msg = bootstrap_plugin(plugin_name)
432
+ bootstrap_success, bootstrap_msg = bootstrap_plugin(plugin_name, debug=debug)
433
433
  if not bootstrap_success:
434
434
  return bootstrap_success, bootstrap_msg
435
435
 
@@ -18,7 +18,7 @@ def delete(
18
18
  Delete an element.
19
19
 
20
20
  Command:
21
- `delete {config, pipes, plugins, users, connectors, jobs}`
21
+ `delete {config, pipes, plugins, users, connectors, jobs, venvs}`
22
22
 
23
23
  """
24
24
  from meerschaum.actions import choose_subaction
@@ -159,7 +159,9 @@ def _delete_config(
159
159
  answer = yes_no(
160
160
  "Are you sure you want to delete the following configuration files?" +
161
161
  f"{sep + sep.join([str(p) for p in paths])}\n",
162
- default='n', noask=noask, yes=yes
162
+ default='n',
163
+ noask=noask,
164
+ yes=yes,
163
165
  )
164
166
 
165
167
  if answer or force:
@@ -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,12 @@ 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(
71
+ action: Optional[List[str]] = None,
72
+ noask: bool = False,
73
+ debug: bool = False,
74
+ **kwargs: Any
75
+ ) -> SuccessTuple:
70
76
  """
71
77
  Edit Meerschaum configuration files.
72
78
 
@@ -86,11 +92,26 @@ def _edit_config(action: Optional[List[str]] = None, **kw : Any) -> SuccessTuple
86
92
  ```
87
93
  """
88
94
  from meerschaum.config._edit import edit_config
89
- if action is None:
90
- action = []
91
- if len(action) == 0:
92
- action.append('meerschaum')
93
- return edit_config(keys=action, **kw)
95
+ from meerschaum.config._read_config import get_possible_keys
96
+ from meerschaum.actions import actions
97
+ from meerschaum.utils.prompt import choose
98
+
99
+ if not action:
100
+ action = [
101
+ choose(
102
+ "Choose a configuration file to edit:",
103
+ get_possible_keys(),
104
+ default='meerschaum',
105
+ noask=noask,
106
+ )
107
+ ]
108
+
109
+ edit_success, edit_msg = edit_config(keys=action, debug=debug, **kwargs)
110
+ if not edit_success:
111
+ return edit_success, edit_msg
112
+
113
+ return actions['reload'](debug=debug)
114
+
94
115
 
95
116
  def _complete_edit_config(action: Optional[List[str]] = None, **kw: Any) -> List[str]:
96
117
  from meerschaum.config._read_config import get_possible_keys
@@ -104,14 +125,14 @@ def _complete_edit_config(action: Optional[List[str]] = None, **kw: Any) -> List
104
125
  return possibilities
105
126
 
106
127
  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:
128
+ action: Optional[List[str]] = None,
129
+ params: Optional[Dict[str, Any]] = None,
130
+ yes: bool = False,
131
+ force: bool = False,
132
+ noask: bool = False,
133
+ debug: bool = False,
134
+ **kw: Any
135
+ ) -> SuccessTuple:
115
136
  """
116
137
  Open and edit pipes' configuration files.
117
138
 
@@ -218,7 +239,7 @@ def _edit_users(
218
239
  from meerschaum.utils.prompt import prompt, yes_no, get_password, get_email
219
240
  from meerschaum.utils.misc import edit_file
220
241
  from meerschaum.config._paths import USERS_CACHE_RESOURCES_PATH
221
- from meerschaum.config.static import STATIC_CONFIG
242
+ from meerschaum._internal.static import STATIC_CONFIG
222
243
  from meerschaum.utils.yaml import yaml
223
244
  import os, pathlib
224
245
  instance_connector = parse_instance_keys(mrsm_instance)
@@ -235,7 +256,7 @@ def _edit_users(
235
256
  minimum_length = STATIC_CONFIG['users']['min_password_length'],
236
257
  )
237
258
 
238
- ## Make an admin
259
+ ### Make an admin
239
260
  _type = ''
240
261
  if yes_no(f"Change the user type for user '{username}'?", default='n', yes=yes, noask=noask):
241
262
  is_admin = yes_no(
@@ -248,15 +269,34 @@ def _edit_users(
248
269
  if yes_no(f"Change the email for user '{username}'?", default='n', yes=yes, noask=noask):
249
270
  email = get_email(username)
250
271
 
272
+ attributes = {}
273
+ ### Change the scopes.
274
+ attributes['scopes'] = prompt(
275
+ f"Scopes for user '{username}' (`*` to grant all scopes):",
276
+ default_editable=' '.join(User(
277
+ username,
278
+ instance=instance_connector,
279
+ ).get_scopes(refresh=True, debug=debug)),
280
+ ).split()
281
+ if attributes['scopes'] == ['*']:
282
+ attributes['scopes'] = list(STATIC_CONFIG['tokens']['scopes'])
283
+
251
284
  ### Change the attributes
252
- attributes = None
253
285
  if yes_no(
254
- f"Edit the attributes YAML file for user '{username}'?",
255
- default='n', yes=yes, noask=noask
286
+ f"Edit the attributes YAML file for user '{username}'?",
287
+ default='n',
288
+ yes=yes,
289
+ noask=noask,
256
290
  ):
257
291
  attr_path = pathlib.Path(os.path.join(USERS_CACHE_RESOURCES_PATH, f'{username}.yaml'))
258
292
  try:
259
- existing_attrs = instance_connector.get_user_attributes(User(username), debug=debug)
293
+ existing_attrs = instance_connector.get_user_attributes(
294
+ User(
295
+ username,
296
+ instance=instance_connector,
297
+ ),
298
+ debug=debug,
299
+ )
260
300
  with open(attr_path, 'w+') as f:
261
301
  yaml.dump(existing_attrs, stream=f, sort_keys=False)
262
302
  edit_file(attr_path)
@@ -271,7 +311,14 @@ def _edit_users(
271
311
  attributes = None
272
312
 
273
313
  ### Submit changes
274
- return User(username, password, email=email, type=_type, attributes=attributes)
314
+ return User(
315
+ username,
316
+ password,
317
+ email=email,
318
+ type=_type,
319
+ attributes=attributes,
320
+ instance=instance_connector,
321
+ )
275
322
 
276
323
  if not action:
277
324
  return False, "No users to edit."
@@ -301,14 +348,14 @@ def _edit_users(
301
348
  f" ({succeeded} succeeded, {failed} failed)."
302
349
  )
303
350
  info(msg)
304
- return True, msg
351
+ return succeeded > 0, msg
305
352
 
306
353
 
307
354
  def _edit_plugins(
308
355
  action: Optional[List[str]] = None,
309
356
  debug: bool = False,
310
357
  **kwargs: Any
311
- ):
358
+ ) -> mrsm.SuccessTuple:
312
359
  """
313
360
  Edit a plugin's source code.
314
361
  """
@@ -316,7 +363,6 @@ def _edit_plugins(
316
363
  from meerschaum.utils.warnings import warn
317
364
  from meerschaum.utils.prompt import prompt, yes_no
318
365
  from meerschaum.utils.misc import edit_file
319
- from meerschaum.utils.packages import reload_meerschaum
320
366
  from meerschaum.actions import actions
321
367
 
322
368
  if not action:
@@ -349,7 +395,7 @@ def _edit_plugins(
349
395
  continue
350
396
 
351
397
  edit_file(plugin_file_path)
352
- reload_meerschaum(debug=debug)
398
+ actions['reload'](debug=debug)
353
399
 
354
400
  return True, "Success"
355
401
 
@@ -507,6 +553,106 @@ def _edit_jobs(
507
553
  return True, msg
508
554
 
509
555
 
556
+ def _edit_tokens(
557
+ action: Optional[List[str]] = None,
558
+ mrsm_instance: Optional[str] = None,
559
+ debug: bool = False,
560
+ ) -> mrsm.SuccessTuple:
561
+ """
562
+ Edit tokens registered to an instance.
563
+ """
564
+ import uuid
565
+ from meerschaum.utils.warnings import warn
566
+ from meerschaum.utils.misc import is_uuid
567
+ from meerschaum.core import Token
568
+ from meerschaum.connectors.parse import parse_instance_keys
569
+ from meerschaum.utils.prompt import prompt, yes_no
570
+ from meerschaum._internal.static import STATIC_CONFIG
571
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
572
+
573
+ if not action:
574
+ return (
575
+ False, (
576
+ "Provide token labels or IDs for the tokens to edit\n"
577
+ " (run `show tokens` to see registered tokens)."
578
+ )
579
+ )
580
+
581
+ conn = parse_instance_keys(mrsm_instance)
582
+
583
+ labels = [
584
+ label
585
+ for label in (action or [])
586
+ if not is_uuid(label)
587
+ ]
588
+ potential_token_ids = [
589
+ uuid.UUID(potential_id)
590
+ for potential_id in (action or [])
591
+ if is_uuid(potential_id)
592
+ ]
593
+
594
+ tokens = conn.get_tokens(
595
+ labels=(labels or None),
596
+ ids=(potential_token_ids or None),
597
+ debug=debug,
598
+ )
599
+
600
+ num_edited = 0
601
+ for token in tokens:
602
+ token_model = token.to_model(refresh=True)
603
+ if token_model is None:
604
+ warn(f"Token '{token.id}' does not exist.", stack=False)
605
+ continue
606
+
607
+ new_attrs = {}
608
+
609
+ new_attrs['label'] = prompt("Label:", default_editable=token_model.label)
610
+ new_expiration_str = prompt(
611
+ "Expiration (empty for no expiration):",
612
+ default_editable=('' if token.expiration is None else str(token_model.expiration)),
613
+ )
614
+ new_attrs['expiration'] = (
615
+ dateutil_parser.parse(new_expiration_str)
616
+ if new_expiration_str
617
+ else None
618
+ )
619
+ new_scopes_str = prompt(
620
+ "Scope (`*` to grant all permissions):",
621
+ default_editable=' '.join(token_model.scopes),
622
+ )
623
+ new_attrs['scopes'] = (
624
+ new_scopes_str.split(' ')
625
+ if new_scopes_str != '*'
626
+ else list(STATIC_CONFIG['tokens']['scopes'])
627
+ )
628
+ invalidate = (
629
+ yes_no("Do you want to invalidate this token?", default='n')
630
+ if token_model.is_valid
631
+ else True
632
+ )
633
+ new_attrs['is_valid'] = token_model.is_valid and not invalidate
634
+
635
+ new_token = Token(**{**dict(token_model), **new_attrs})
636
+ edit_success, edit_msg = new_token.edit(debug=debug)
637
+ if not edit_success:
638
+ return False, edit_msg
639
+
640
+ if invalidate:
641
+ invalidate_success, invalidate_msg = new_token.invalidate(debug=debug)
642
+ if not invalidate_success:
643
+ return False, invalidate_msg
644
+
645
+ num_edited += 1
646
+
647
+ msg = (
648
+ f"Successfully edited {num_edited} token"
649
+ + ('s' if num_edited != 1 else '')
650
+ + '.'
651
+ )
652
+
653
+ return True, msg
654
+
655
+
510
656
  ### NOTE: This must be the final statement of the module.
511
657
  ### Any subactions added below these lines will not
512
658
  ### be added to the `help` docstring.
@@ -10,13 +10,13 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
11
 
12
12
  def login(
13
- action: Optional[List[str]] = None,
14
- connector_keys: Optional[List[str]] = None,
15
- yes: bool = False,
16
- noask: bool = False,
17
- debug: bool = False,
18
- **kw: Any
19
- ) -> SuccessTuple:
13
+ action: Optional[List[str]] = None,
14
+ connector_keys: Optional[List[str]] = None,
15
+ yes: bool = False,
16
+ noask: bool = False,
17
+ debug: bool = False,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """
21
21
  Log into a Meerschaum API instance.
22
22
  """
@@ -56,7 +56,7 @@ def login(
56
56
  for k in _keys:
57
57
  try:
58
58
  _connectors.append(parse_instance_keys(k))
59
- except Exception as e:
59
+ except Exception:
60
60
  warn(f"Unable to build connector '{k}'. Is it registered?", stack=False)
61
61
 
62
62
  meerschaum_config = get_config('meerschaum')