meerschaum 2.3.5.dev0__py3-none-any.whl → 2.4.0.dev0__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 (62) hide show
  1. meerschaum/_internal/arguments/__init__.py +2 -1
  2. meerschaum/_internal/arguments/_parse_arguments.py +86 -7
  3. meerschaum/_internal/entry.py +29 -13
  4. meerschaum/actions/api.py +16 -16
  5. meerschaum/actions/bootstrap.py +36 -10
  6. meerschaum/actions/start.py +16 -15
  7. meerschaum/api/_events.py +11 -7
  8. meerschaum/api/dash/__init__.py +7 -6
  9. meerschaum/api/dash/callbacks/__init__.py +1 -0
  10. meerschaum/api/dash/callbacks/dashboard.py +7 -5
  11. meerschaum/api/dash/callbacks/pipes.py +42 -0
  12. meerschaum/api/dash/pages/__init__.py +1 -0
  13. meerschaum/api/dash/pages/pipes.py +16 -0
  14. meerschaum/api/dash/pipes.py +79 -47
  15. meerschaum/api/dash/users.py +19 -6
  16. meerschaum/api/routes/_actions.py +0 -98
  17. meerschaum/api/routes/_jobs.py +38 -18
  18. meerschaum/api/routes/_login.py +4 -4
  19. meerschaum/api/routes/_pipes.py +3 -3
  20. meerschaum/config/_default.py +9 -2
  21. meerschaum/config/_version.py +1 -1
  22. meerschaum/config/stack/__init__.py +59 -18
  23. meerschaum/config/static/__init__.py +2 -0
  24. meerschaum/connectors/Connector.py +19 -13
  25. meerschaum/connectors/__init__.py +9 -5
  26. meerschaum/connectors/api/_actions.py +22 -36
  27. meerschaum/connectors/api/_jobs.py +1 -0
  28. meerschaum/connectors/poll.py +30 -24
  29. meerschaum/connectors/sql/_pipes.py +126 -154
  30. meerschaum/connectors/sql/_plugins.py +45 -43
  31. meerschaum/connectors/sql/_users.py +46 -38
  32. meerschaum/connectors/valkey/ValkeyConnector.py +535 -0
  33. meerschaum/connectors/valkey/__init__.py +8 -0
  34. meerschaum/connectors/valkey/_fetch.py +75 -0
  35. meerschaum/connectors/valkey/_pipes.py +839 -0
  36. meerschaum/connectors/valkey/_plugins.py +265 -0
  37. meerschaum/connectors/valkey/_users.py +305 -0
  38. meerschaum/core/Pipe/__init__.py +2 -0
  39. meerschaum/core/Pipe/_attributes.py +1 -2
  40. meerschaum/core/Pipe/_drop.py +4 -4
  41. meerschaum/core/Pipe/_dtypes.py +14 -14
  42. meerschaum/core/Pipe/_edit.py +15 -14
  43. meerschaum/core/Pipe/_sync.py +134 -51
  44. meerschaum/core/User/_User.py +14 -12
  45. meerschaum/jobs/_Job.py +26 -8
  46. meerschaum/jobs/systemd.py +20 -8
  47. meerschaum/plugins/_Plugin.py +17 -13
  48. meerschaum/utils/_get_pipes.py +14 -20
  49. meerschaum/utils/dataframe.py +288 -101
  50. meerschaum/utils/dtypes/__init__.py +31 -6
  51. meerschaum/utils/dtypes/sql.py +4 -4
  52. meerschaum/utils/misc.py +3 -3
  53. meerschaum/utils/packages/_packages.py +1 -0
  54. meerschaum/utils/prompt.py +1 -1
  55. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/METADATA +3 -1
  56. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/RECORD +62 -54
  57. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/WHEEL +1 -1
  58. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/LICENSE +0 -0
  59. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/NOTICE +0 -0
  60. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/entry_points.txt +0 -0
  61. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/top_level.txt +0 -0
  62. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dev0.dist-info}/zip-safe +0 -0
@@ -9,7 +9,8 @@ This package includes argument parsing utilities.
9
9
  from meerschaum._internal.arguments._parse_arguments import (
10
10
  parse_arguments, parse_line, remove_leading_action,
11
11
  parse_dict_to_sysargs, split_chained_sysargs, split_pipeline_sysargs,
12
- sysargs_has_api_executor_keys,
12
+ sysargs_has_api_executor_keys, get_pipeline_sysargs,
13
+ compress_pipeline_sysargs, remove_api_executor_keys,
13
14
  )
14
15
  from meerschaum._internal.arguments._parser import parser
15
16
  from meerschaum.plugins import add_plugin_argument
@@ -424,12 +424,91 @@ def sysargs_has_api_executor_keys(sysargs: List[str]) -> bool:
424
424
  if '-e' not in sysargs and '--executor-keys' not in sysargs:
425
425
  return False
426
426
 
427
- executor_keys_flag = '-e' if '-e' in sysargs else '--executor-keys'
428
- executor_keys_flag_ix = sysargs.index(executor_keys_flag)
429
- executor_keys_ix = executor_keys_flag_ix + 1
427
+ for i, arg in enumerate(sysargs):
428
+ if arg not in ('-e', '--executor-keys'):
429
+ continue
430
430
 
431
- if len(sysargs) < executor_keys_ix:
432
- return False
431
+ executor_keys_ix = i + 1
432
+ if len(sysargs) <= executor_keys_ix:
433
+ return False
434
+
435
+ executor_keys = sysargs[executor_keys_ix]
436
+ if executor_keys.startswith('api:'):
437
+ return True
438
+
439
+ return False
440
+
441
+
442
+ def remove_api_executor_keys(sysargs: List[str]) -> List[str]:
443
+ """
444
+ Remove any api executor keys from `sysargs`.
445
+ """
446
+ from meerschaum.utils.misc import flatten_list
447
+
448
+ if not sysargs_has_api_executor_keys(sysargs):
449
+ return sysargs
450
+
451
+ skip_indices = set(flatten_list(
452
+ [
453
+ [i, i+1]
454
+ for i, arg in enumerate(sysargs)
455
+ if arg in ('-e', '--executor-keys')
456
+ ]
457
+ ))
458
+
459
+ return [
460
+ arg
461
+ for i, arg in enumerate(sysargs)
462
+ if i not in skip_indices
463
+ ]
433
464
 
434
- executor_keys = sysargs[executor_keys_ix]
435
- return executor_keys.startswith('api:')
465
+
466
+ def get_pipeline_sysargs(
467
+ sysargs: List[str],
468
+ pipeline_args: List[str],
469
+ _patch_args: Optional[Dict[str, Any]] = None,
470
+ ) -> List[str]:
471
+ """
472
+ Parse `sysargs` and `pipeline_args` into a single `start pipeline` sysargs.
473
+ """
474
+ import shlex
475
+ start_pipeline_params = {
476
+ 'sub_args_line': shlex.join(sysargs),
477
+ 'patch_args': _patch_args,
478
+ }
479
+ return (
480
+ ['start', 'pipeline']
481
+ + [str(arg) for arg in pipeline_args]
482
+ + ['-P', json.dumps(start_pipeline_params, separators=(',', ':'))]
483
+ )
484
+
485
+
486
+ def compress_pipeline_sysargs(pipeline_sysargs: List[str]) -> List[str]:
487
+ """
488
+ Given a `start pipeline` sysargs, return a condensed syntax rendition.
489
+ """
490
+ import shlex
491
+
492
+ if pipeline_sysargs[:2] != ['start', 'pipeline']:
493
+ return pipeline_sysargs
494
+
495
+ if '-P' not in pipeline_sysargs:
496
+ return pipeline_sysargs
497
+
498
+ params_ix = pipeline_sysargs.index('-P')
499
+ pipeline_args = pipeline_sysargs[2:params_ix]
500
+ params_str = pipeline_sysargs[-1]
501
+ try:
502
+ start_pipeline_params = json.loads(params_str)
503
+ except Exception:
504
+ return pipeline_sysargs
505
+
506
+ sub_args_line = start_pipeline_params.get('sub_args_line', None)
507
+ if not sub_args_line:
508
+ return pipeline_sysargs
509
+
510
+ return (
511
+ shlex.split(sub_args_line)
512
+ + [':']
513
+ + pipeline_args
514
+ )
@@ -11,6 +11,8 @@ from __future__ import annotations
11
11
 
12
12
  import os
13
13
  import sys
14
+ import pathlib
15
+
14
16
  from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any
15
17
  from meerschaum.config.static import STATIC_CONFIG as _STATIC_CONFIG
16
18
 
@@ -19,8 +21,17 @@ if (_STATIC_CONFIG['environment']['systemd_log_path']) in os.environ:
19
21
  from meerschaum.utils.daemon import RotatingFile as _RotatingFile, StdinFile as _StdinFile
20
22
  from meerschaum.config import get_config as _get_config
21
23
 
22
- _systemd_result_path = os.environ[_STATIC_CONFIG['environment']['systemd_result_path']]
23
- _systemd_log_path = os.environ[_STATIC_CONFIG['environment']['systemd_log_path']]
24
+ _systemd_result_path = pathlib.Path(
25
+ os.environ[_STATIC_CONFIG['environment']['systemd_result_path']]
26
+ )
27
+ _systemd_log_path = pathlib.Path(
28
+ os.environ[_STATIC_CONFIG['environment']['systemd_log_path']]
29
+ )
30
+ _systemd_delete_job = (
31
+ (os.environ.get(_STATIC_CONFIG['environment']['systemd_delete_job'], None) or '0')
32
+ not in (None, '0', 'false')
33
+ )
34
+ _job_name = os.environ[_STATIC_CONFIG['environment']['daemon_id']]
24
35
  _systemd_log = _RotatingFile(
25
36
  _systemd_log_path,
26
37
  write_timestamps=True,
@@ -51,6 +62,7 @@ def entry(
51
62
  split_chained_sysargs,
52
63
  split_pipeline_sysargs,
53
64
  sysargs_has_api_executor_keys,
65
+ get_pipeline_sysargs,
54
66
  )
55
67
  from meerschaum.config.static import STATIC_CONFIG
56
68
  if sysargs is None:
@@ -72,15 +84,7 @@ def entry(
72
84
  else split_chained_sysargs(sysargs)
73
85
  )
74
86
  if pipeline_args:
75
- start_pipeline_params = {
76
- 'sub_args_line': shlex.join(sysargs),
77
- 'patch_args': _patch_args,
78
- }
79
- chained_sysargs = [
80
- ['start', 'pipeline']
81
- + [str(arg) for arg in pipeline_args]
82
- + ['-P', json.dumps(start_pipeline_params, separators=(',', ':'))]
83
- ]
87
+ chained_sysargs = [get_pipeline_sysargs(sysargs, pipeline_args, _patch_args=_patch_args)]
84
88
 
85
89
  results: List[SuccessTuple] = []
86
90
 
@@ -169,8 +173,20 @@ def entry(
169
173
 
170
174
  if _systemd_result_path:
171
175
  import json
172
- with open(_systemd_result_path, 'w+', encoding='utf-8') as f:
173
- json.dump((success, msg), f)
176
+ from meerschaum.utils.warnings import warn
177
+ import meerschaum as mrsm
178
+
179
+ job = mrsm.Job(_job_name, executor_keys='systemd')
180
+ if job.delete_after_completion:
181
+ delete_success, delete_msg = job.delete()
182
+ mrsm.pprint((delete_success, delete_msg))
183
+ else:
184
+ try:
185
+ if _systemd_result_path.parent.exists():
186
+ with open(_systemd_result_path, 'w+', encoding='utf-8') as f:
187
+ json.dump((success, msg), f)
188
+ except Exception as e:
189
+ warn(f"Failed to write job result:\n{e}")
174
190
 
175
191
  return success, msg
176
192
 
meerschaum/actions/api.py CHANGED
@@ -89,22 +89,22 @@ def api(
89
89
  return success, message
90
90
 
91
91
  def _api_start(
92
- action: Optional[List[str]] = None,
93
- host: Optional[str] = None,
94
- port: Optional[int] = None,
95
- workers: Optional[int] = None,
96
- mrsm_instance: Optional[str] = None,
97
- no_dash: bool = False,
98
- no_auth: bool = False,
99
- private: bool = False,
100
- secure: bool = False,
101
- debug: bool = False,
102
- nopretty: bool = False,
103
- production: bool = False,
104
- keyfile: Optional[str] = None,
105
- certfile: Optional[str] = None,
106
- **kw: Any
107
- ) -> SuccessTuple:
92
+ action: Optional[List[str]] = None,
93
+ host: Optional[str] = None,
94
+ port: Optional[int] = None,
95
+ workers: Optional[int] = None,
96
+ mrsm_instance: Optional[str] = None,
97
+ no_dash: bool = False,
98
+ no_auth: bool = False,
99
+ private: bool = False,
100
+ secure: bool = False,
101
+ debug: bool = False,
102
+ nopretty: bool = False,
103
+ production: bool = False,
104
+ keyfile: Optional[str] = None,
105
+ certfile: Optional[str] = None,
106
+ **kw: Any
107
+ ) -> SuccessTuple:
108
108
  """Start the API server.
109
109
 
110
110
  Parameters
@@ -234,7 +234,13 @@ def _bootstrap_connectors(
234
234
  Prompt the user for the details necessary to create a Connector.
235
235
  """
236
236
  from meerschaum.connectors.parse import is_valid_connector_keys
237
- from meerschaum.connectors import connectors, get_connector, types, custom_types
237
+ from meerschaum.connectors import (
238
+ connectors,
239
+ get_connector,
240
+ types,
241
+ custom_types,
242
+ _load_builtin_custom_connectors,
243
+ )
238
244
  from meerschaum.utils.prompt import prompt, yes_no, choose
239
245
  from meerschaum.config import get_config
240
246
  from meerschaum.config._edit import write_config
@@ -246,6 +252,7 @@ def _bootstrap_connectors(
246
252
 
247
253
  abort_tuple = False, "No connectors bootstrapped."
248
254
  _clear = get_config('shell', 'clear_screen', patch=True)
255
+ _load_builtin_custom_connectors()
249
256
 
250
257
  if action is None:
251
258
  action = []
@@ -262,8 +269,8 @@ def _bootstrap_connectors(
262
269
  + ' See https://meerschaum.io/reference/connectors '
263
270
  + 'for documentation on connectors.\n'
264
271
  ),
265
- sorted(list(connectors)),
266
- default = 'sql'
272
+ sorted([k for k in connectors if k != 'plugin']),
273
+ default='sql',
267
274
  )
268
275
  except KeyboardInterrupt:
269
276
  return abort_tuple
@@ -288,21 +295,30 @@ def _bootstrap_connectors(
288
295
  warn(f"Connector '{_type}:{_label}' already exists.", stack=False)
289
296
  overwrite = yes_no(
290
297
  f"Do you want to overwrite connector '{_type}:{_label}'?",
291
- default = 'n',
292
- yes = yes,
293
- noask = noask,
298
+ default='n',
299
+ yes=yes,
300
+ noask=noask,
294
301
  )
295
302
  if not overwrite and not force:
296
- return False, f"No changes made to connector configuration."
303
+ return False, "No changes made to connector configuration."
297
304
  break
298
305
  elif _label == "":
299
- warn(f"Please enter a label.", stack=False)
306
+ warn("Please enter a label.", stack=False)
300
307
  else:
301
308
  break
302
309
 
303
310
  cls = types.get(_type)
304
311
  cls_required_attrs = getattr(cls, 'REQUIRED_ATTRIBUTES', [])
305
- type_attributes = connector_attributes.get(_type, {'required': cls_required_attrs})
312
+ cls_optional_attrs = getattr(cls, 'OPTIONAL_ATTRIBUTES', [])
313
+ cls_default_attrs = getattr(cls, 'DEFAULT_ATTRIBUTES', {})
314
+ type_attributes = connector_attributes.get(
315
+ _type,
316
+ {
317
+ 'required': cls_required_attrs,
318
+ 'optional': cls_optional_attrs,
319
+ 'default': cls_default_attrs,
320
+ }
321
+ )
306
322
 
307
323
  new_attributes = {}
308
324
  if 'flavors' in type_attributes:
@@ -323,6 +339,7 @@ def _bootstrap_connectors(
323
339
  default = type_attributes['flavors'][flavor].get('defaults', {})
324
340
  else:
325
341
  required = sorted(list(type_attributes.get('required', {})))
342
+ optional = sorted(list(type_attributes.get('optional', {})))
326
343
  default = type_attributes.get('default', {})
327
344
  info(
328
345
  f"Please answer the following questions to configure the new connector '{_type}:{_label}'."
@@ -330,13 +347,22 @@ def _bootstrap_connectors(
330
347
  )
331
348
  for r in required:
332
349
  try:
333
- val = prompt(f"Value for {r}:")
350
+ default_val = str(default.get(r)) if r in default else None
351
+ val = prompt(f"Value for {r}:", default=default_val)
334
352
  except KeyboardInterrupt:
335
353
  continue
336
354
  if is_int(val):
337
355
  val = int(val)
338
356
  new_attributes[r] = val
339
357
 
358
+ for o in optional:
359
+ try:
360
+ val = prompt(f"Value for {o} (optional; empty to omit):")
361
+ except KeyboardInterrupt:
362
+ continue
363
+ if val:
364
+ new_attributes[o] = val
365
+
340
366
  for k, v in default.items():
341
367
  ### skip already configured attributes, (e.g. flavor or from required)
342
368
  if k in new_attributes:
@@ -89,6 +89,7 @@ def _start_jobs(
89
89
  name: Optional[str] = None,
90
90
  sysargs: Optional[List[str]] = None,
91
91
  executor_keys: Optional[str] = None,
92
+ rm: bool = False,
92
93
  debug: bool = False,
93
94
  **kw
94
95
  ) -> SuccessTuple:
@@ -210,7 +211,7 @@ def _start_jobs(
210
211
 
211
212
  def _run_new_job(name: Optional[str] = None):
212
213
  name = name or get_new_daemon_name()
213
- job = Job(name, sysargs, executor_keys=executor_keys)
214
+ job = Job(name, sysargs, executor_keys=executor_keys, delete_after_completion=rm)
214
215
  return job.start(debug=debug), name
215
216
 
216
217
  def _run_existing_job(name: str):
@@ -454,12 +455,12 @@ def _start_webterm(
454
455
 
455
456
 
456
457
  def _start_connectors(
457
- action: Optional[List[str]] = None,
458
- connector_keys: Optional[List[str]] = None,
459
- min_seconds: int = 3,
460
- debug: bool = False,
461
- **kw
462
- ) -> SuccessTuple:
458
+ action: Optional[List[str]] = None,
459
+ connector_keys: Optional[List[str]] = None,
460
+ min_seconds: int = 3,
461
+ debug: bool = False,
462
+ **kw
463
+ ) -> SuccessTuple:
463
464
  """
464
465
  Start polling connectors to verify a connection can be made.
465
466
  """
@@ -479,7 +480,7 @@ def _start_connectors(
479
480
  for keys in unique_keys:
480
481
  try:
481
482
  conn = parse_instance_keys(keys)
482
- except Exception as e:
483
+ except Exception:
483
484
  warn(f"Invalid connector keys: '{keys}'. Skipping...", stack=False)
484
485
  continue
485
486
  valid_conns.append(conn)
@@ -487,20 +488,19 @@ def _start_connectors(
487
488
  if not valid_conns:
488
489
  return False, "No valid connector keys were provided."
489
490
 
490
-
491
491
  connected = {}
492
492
  def connect(conn):
493
493
  success = retry_connect(
494
494
  conn,
495
- retry_wait = min_seconds,
496
- enforce_chaining = False,
497
- enforce_login = False,
498
- print_on_connect = True,
499
- debug = debug,
495
+ retry_wait=min_seconds,
496
+ enforce_chaining=False,
497
+ enforce_login=False,
498
+ print_on_connect=True,
499
+ debug=debug,
500
500
  )
501
501
  connected[conn] = success
502
502
  return success
503
-
503
+
504
504
  pool = get_pool()
505
505
  try:
506
506
  pool.map(connect, valid_conns)
@@ -568,6 +568,7 @@ def _start_pipeline(
568
568
  else 1
569
569
  )
570
570
 
571
+ params = params or {}
571
572
  sub_args_line = params.get('sub_args_line', None)
572
573
  patch_args = params.get('patch_args', None)
573
574
 
meerschaum/api/_events.py CHANGED
@@ -20,12 +20,14 @@ from meerschaum.connectors.poll import retry_connect
20
20
  from meerschaum.utils.warnings import warn
21
21
  from meerschaum._internal.term.tools import is_webterm_running
22
22
  from meerschaum.jobs import (
23
+ get_jobs,
23
24
  start_check_jobs_thread,
24
25
  stop_check_jobs_thread,
25
26
  get_executor_keys_from_context,
26
27
  )
28
+ from meerschaum.config.static import STATIC_CONFIG
27
29
 
28
- _check_jobs_thread = None
30
+ TEMP_PREFIX: str = STATIC_CONFIG['api']['jobs']['temp_prefix']
29
31
 
30
32
  @app.on_event("startup")
31
33
  async def startup():
@@ -51,8 +53,7 @@ async def startup():
51
53
  await shutdown()
52
54
  os._exit(1)
53
55
 
54
- if get_executor_keys_from_context() == 'local':
55
- start_check_jobs_thread()
56
+ start_check_jobs_thread()
56
57
 
57
58
 
58
59
  @app.on_event("shutdown")
@@ -65,11 +66,14 @@ async def shutdown():
65
66
  if get_api_connector().type == 'sql':
66
67
  get_api_connector().engine.dispose()
67
68
 
68
- if get_executor_keys_from_context() == 'local':
69
- stop_check_jobs_thread()
69
+ stop_check_jobs_thread()
70
70
 
71
- from meerschaum.api.routes._actions import _temp_jobs
72
- for name, job in _temp_jobs.items():
71
+ temp_jobs = {
72
+ name: job
73
+ for name, job in get_jobs(include_hidden=True).items()
74
+ if name.startswith(TEMP_PREFIX)
75
+ }
76
+ for job in temp_jobs.values():
73
77
  job.delete()
74
78
 
75
79
  ### Terminate any running jobs left over.
@@ -50,16 +50,17 @@ stylesheets = [
50
50
  '/static/css/bootstrap.min.css',
51
51
  '/static/css/dbc_dark.css',
52
52
  '/static/css/dash.css',
53
+ '/static/css/bootstrap-icons/font/bootstrap-icons.min.css',
53
54
  ]
54
55
  scripts = ['/static/js/node_modules/xterm/lib/xterm.js']
55
56
  dash_app = enrich.DashProxy(
56
57
  __name__,
57
- title = 'Meerschaum Web',
58
- requests_pathname_prefix = endpoints['dash'] + '/',
59
- external_stylesheets = stylesheets,
60
- update_title = None,
61
- suppress_callback_exceptions = True,
62
- transforms = [
58
+ title='Meerschaum Web',
59
+ requests_pathname_prefix=endpoints['dash'] + '/',
60
+ external_stylesheets=stylesheets,
61
+ update_title=None,
62
+ suppress_callback_exceptions=True,
63
+ transforms=[
63
64
  enrich.TriggerTransform(),
64
65
  enrich.MultiplexerTransform(),
65
66
  ],
@@ -11,6 +11,7 @@ import meerschaum.api.dash.callbacks.login
11
11
  import meerschaum.api.dash.callbacks.plugins
12
12
  import meerschaum.api.dash.callbacks.jobs
13
13
  import meerschaum.api.dash.callbacks.register
14
+ import meerschaum.api.dash.callbacks.pipes
14
15
  from meerschaum.api.dash.callbacks.custom import init_dash_plugins, add_plugin_pages
15
16
 
16
17
  init_dash_plugins()
@@ -101,6 +101,7 @@ _paths = {
101
101
  '' : pages.dashboard.layout,
102
102
  'plugins' : pages.plugins.layout,
103
103
  'register': pages.register.layout,
104
+ 'pipes' : pages.pipes.layout,
104
105
  }
105
106
  _required_login = {''}
106
107
 
@@ -113,10 +114,10 @@ _required_login = {''}
113
114
  State('location', 'href'),
114
115
  )
115
116
  def update_page_layout_div(
116
- pathname: str,
117
- session_store_data: Dict[str, Any],
118
- location_href: str,
119
- ) -> Tuple[List[Any], Dict[str, Any]]:
117
+ pathname: str,
118
+ session_store_data: Dict[str, Any],
119
+ location_href: str,
120
+ ) -> Tuple[List[Any], Dict[str, Any]]:
120
121
  """
121
122
  Route the user to the correct page.
122
123
 
@@ -135,7 +136,7 @@ def update_page_layout_div(
135
136
  ctx = dash.callback_context
136
137
  dash_endpoint = endpoints['dash']
137
138
  try:
138
- session_id = session_store_data.get('session-id', None)
139
+ session_id = session_store_data.get('session-id', None)
139
140
  except AttributeError:
140
141
  session_id = None
141
142
 
@@ -760,6 +761,7 @@ def update_pipe_accordion(item, session_store_data):
760
761
  raise PreventUpdate
761
762
 
762
763
  session_id = session_store_data.get('session-id', None)
764
+ print(f"{session_id=}")
763
765
  authenticated = is_session_authenticated(str(session_id))
764
766
  return accordion_items_from_pipe(pipe, active_items=[item], authenticated=authenticated)
765
767
 
@@ -0,0 +1,42 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define callbacks for the `/dash/pipes/` page.
6
+ """
7
+
8
+ from dash.exceptions import PreventUpdate
9
+ from dash.dependencies import Input, Output, State
10
+ from dash import no_update
11
+
12
+ import meerschaum as mrsm
13
+ from meerschaum.api.dash import dash_app, debug, active_sessions
14
+ from meerschaum.api.dash.pipes import build_pipe_card
15
+ from meerschaum.api import get_api_connector, CHECK_UPDATE
16
+ from meerschaum.utils.packages import import_html, import_dcc
17
+ html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
18
+
19
+
20
+ @dash_app.callback(
21
+ Output('pipe-output-div', 'children'),
22
+ Input('pipes-location', 'pathname'),
23
+ )
24
+ def render_page_from_url(pathname):
25
+ if not str(pathname).startswith('/dash/pipes'):
26
+ return no_update
27
+
28
+ keys = pathname.replace('/dash/pipes', '').lstrip('/').rstrip('/').split('/')
29
+ if len(keys) not in (2, 3):
30
+ return no_update
31
+
32
+ ck = keys[0]
33
+ mk = keys[1]
34
+ lk = keys[2] if len(keys) == 3 else None
35
+
36
+ pipe = mrsm.Pipe(ck, mk, lk)
37
+ ### TODO Check if logged in
38
+ return [
39
+ html.Br(),
40
+ build_pipe_card(pipe, authenticated=False),
41
+ html.Br(),
42
+ ]
@@ -10,3 +10,4 @@ import meerschaum.api.dash.pages.login
10
10
  import meerschaum.api.dash.pages.dashboard
11
11
  import meerschaum.api.dash.pages.plugins
12
12
  import meerschaum.api.dash.pages.register
13
+ import meerschaum.api.dash.pages.pipes
@@ -0,0 +1,16 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Display pipes via a shareable URL.
6
+ """
7
+
8
+ from meerschaum.api import get_api_connector, CHECK_UPDATE
9
+ from meerschaum.utils.packages import import_html, import_dcc
10
+ html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
11
+ import dash_bootstrap_components as dbc
12
+
13
+ layout = dbc.Container([
14
+ dcc.Location('pipes-location'),
15
+ html.Div(id='pipe-output-div'),
16
+ ])