meerschaum 2.1.7__py3-none-any.whl → 2.2.0.dev1__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.
@@ -346,6 +346,9 @@ groups['misc'].add_argument(
346
346
  groups['misc'].add_argument(
347
347
  '--nopretty', action="store_true", help="Print elements without 'pretty' formatting"
348
348
  )
349
+ groups['misc'].add_argument(
350
+ '--skip-deps', action="store_true", help="Skip dependencies when installing plugins.",
351
+ )
349
352
  groups['misc'].add_argument(
350
353
  '-P', '--params', type=string_to_dict, help=(
351
354
  "Parameters dictionary in JSON format or simple format. " +
@@ -1,6 +1,7 @@
1
1
  #! /usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
  # vim:fenc=utf-8
4
+ # type: ignore
4
5
 
5
6
  """
6
7
  The entry point for launching Meerschaum actions.
@@ -61,7 +62,7 @@ def entry_with_args(
61
62
  """
62
63
  import sys
63
64
  from meerschaum.plugins import Plugin
64
- from meerschaum.actions import get_shell, get_action, get_main_action_name
65
+ from meerschaum.actions import get_action, get_main_action_name
65
66
  from meerschaum._internal.arguments import remove_leading_action
66
67
  from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
67
68
  if kw.get('trace', None):
@@ -54,6 +54,7 @@ def _complete_install(
54
54
  def _install_plugins(
55
55
  action: Optional[List[str]] = None,
56
56
  repository: Optional[str] = None,
57
+ skip_deps: bool = False,
57
58
  force: bool = False,
58
59
  debug: bool = False,
59
60
  **kw: Any
@@ -87,11 +88,14 @@ def _install_plugins(
87
88
 
88
89
  repo_connector = parse_repo_keys(repository)
89
90
 
90
- successes = {}
91
91
  for name in action:
92
92
  info(f"Installing plugin '{name}' from Meerschaum repository '{repo_connector}'...")
93
- success, msg = repo_connector.install_plugin(name, force=force, debug=debug)
94
- successes[name] = (success, msg)
93
+ success, msg = repo_connector.install_plugin(
94
+ name,
95
+ force = force,
96
+ skip_deps = skip_deps,
97
+ debug = debug,
98
+ )
95
99
  print_tuple((success, msg))
96
100
 
97
101
  reload_plugins(debug=debug)
@@ -453,16 +453,20 @@ def _wrap_pipe(
453
453
  return False, msg
454
454
  return True, "Success"
455
455
 
456
- hook_results = []
456
+ pre_hook_results, post_hook_results = [], []
457
457
  def apply_hooks(is_pre_sync: bool):
458
458
  _sync_hooks = (_pre_sync_hooks if is_pre_sync else _post_sync_hooks)
459
+ _hook_results = (pre_hook_results if is_pre_sync else post_hook_results)
459
460
  for module_name, sync_hooks in _sync_hooks.items():
460
461
  plugin_name = module_name.split('.')[-1] if module_name.startswith('plugins.') else None
461
462
  for sync_hook in sync_hooks:
462
463
  hook_result = pool.apply_async(call_sync_hook, (plugin_name, sync_hook))
463
- hook_results.append(hook_result)
464
+ _hook_results.append(hook_result)
464
465
 
465
466
  apply_hooks(True)
467
+ for hook_result in pre_hook_results:
468
+ hook_success, hook_msg = hook_result.get()
469
+ mrsm.pprint((hook_success, hook_msg))
466
470
 
467
471
  try:
468
472
  with Venv(get_connector_plugin(pipe.connector), debug=debug):
@@ -480,7 +484,7 @@ def _wrap_pipe(
480
484
  'sync_complete_timestamp': datetime.now(timezone.utc),
481
485
  })
482
486
  apply_hooks(False)
483
- for hook_result in hook_results:
487
+ for hook_result in post_hook_results:
484
488
  hook_success, hook_msg = hook_result.get()
485
489
  mrsm.pprint((hook_success, hook_msg))
486
490
 
@@ -488,11 +488,11 @@ def update_keys_options(
488
488
  """
489
489
  ctx = dash.callback_context
490
490
  trigger = ctx.triggered[0]['prop_id'].split('.')[0]
491
+ instance_click = trigger == 'instance-select'
491
492
 
492
493
  ### Update the instance first.
493
494
  update_instance_keys = False
494
495
  if not instance_keys:
495
- # instance_keys = get_config('meerschaum', 'web_instance')
496
496
  instance_keys = str(get_api_connector())
497
497
  update_instance_keys = True
498
498
  instance_alerts = []
@@ -519,9 +519,9 @@ def update_keys_options(
519
519
  _ck_alone, _mk_alone, _lk_alone = False, False, False
520
520
  _ck_filter, _mk_filter, _lk_filter = connector_keys, metric_keys, location_keys
521
521
 
522
- _ck_alone = connector_keys and num_filter == 1
523
- _mk_alone = metric_keys and num_filter == 1
524
- _lk_alone = location_keys and num_filter == 1
522
+ _ck_alone = (connector_keys and num_filter == 1) or instance_click
523
+ _mk_alone = (metric_keys and num_filter == 1) or instance_click
524
+ _lk_alone = (location_keys and num_filter == 1) or instance_click
525
525
 
526
526
  from meerschaum.utils import fetch_pipes_keys
527
527
 
@@ -545,15 +545,36 @@ def update_keys_options(
545
545
  k = locals()[key_type]
546
546
  if k not in _seen_keys[key_type]:
547
547
  _k = 'None' if k in (None, '[None]', 'None', 'null') else k
548
- options.append({'label' : _k, 'value' : _k})
548
+ options.append({'label': _k, 'value': _k})
549
549
  _seen_keys[key_type].add(k)
550
550
 
551
551
  add_options(_connectors_options, _all_keys if _ck_alone else _keys, 'ck')
552
552
  add_options(_metrics_options, _all_keys if _mk_alone else _keys, 'mk')
553
553
  add_options(_locations_options, _all_keys if _lk_alone else _keys, 'lk')
554
- connector_keys = [ck for ck in connector_keys if ck in [_ck['value'] for _ck in _connectors_options]]
555
- metric_keys = [mk for mk in metric_keys if mk in [_mk['value'] for _mk in _metrics_options]]
556
- location_keys = [lk for lk in location_keys if lk in [_lk['value'] for _lk in _locations_options]]
554
+ connector_keys = sorted([
555
+ ck
556
+ for ck in connector_keys
557
+ if ck in [
558
+ _ck['value']
559
+ for _ck in _connectors_options
560
+ ]
561
+ ])
562
+ metric_keys = sorted([
563
+ mk
564
+ for mk in metric_keys
565
+ if mk in [
566
+ _mk['value']
567
+ for _mk in _metrics_options
568
+ ]
569
+ ])
570
+ location_keys = sorted([
571
+ lk
572
+ for lk in location_keys
573
+ if lk in [
574
+ _lk['value']
575
+ for _lk in _locations_options
576
+ ]
577
+ ])
557
578
  _connectors_datalist = [html.Option(value=o['value']) for o in _connectors_options]
558
579
  _metrics_datalist = [html.Option(value=o['value']) for o in _metrics_options]
559
580
  _locations_datalist = [html.Option(value=o['value']) for o in _locations_options]
@@ -680,6 +701,9 @@ dash_app.clientside_callback(
680
701
  Input({'type': 'pipe-download-csv-button', 'index': ALL}, 'n_clicks'),
681
702
  )
682
703
  def download_pipe_csv(n_clicks):
704
+ """
705
+ Download the most recent chunk as a CSV file.
706
+ """
683
707
  if not n_clicks:
684
708
  raise PreventUpdate
685
709
  ctx = dash.callback_context.triggered
@@ -818,6 +842,51 @@ def sync_documents_click(n_clicks, sync_editor_text):
818
842
  return alert_from_success_tuple((success, msg))
819
843
 
820
844
 
845
+ dash_app.clientside_callback(
846
+ """
847
+ function(n_clicks_arr, url){
848
+ display_block = {"display": "block"};
849
+
850
+ var clicked = false;
851
+ for (var i = 0; i < n_clicks_arr.length; i++){
852
+ if (n_clicks_arr[i]){
853
+ clicked = true;
854
+ break;
855
+ }
856
+ }
857
+ if (!clicked){ return dash_clientside.no_update; }
858
+
859
+ const triggered_id = dash_clientside.callback_context.triggered_id;
860
+ const action = triggered_id["action"];
861
+ const pipe_meta = JSON.parse(triggered_id["index"]);
862
+
863
+ iframe = document.getElementById('webterm-iframe');
864
+ if (!iframe){ return dash_clientside.no_update; }
865
+ var location = pipe_meta.location;
866
+ if (!pipe_meta.location){
867
+ location = "None";
868
+ }
869
+
870
+ iframe.contentWindow.postMessage(
871
+ {
872
+ action: action,
873
+ subaction: "pipes",
874
+ connector_keys: [pipe_meta.connector],
875
+ metric_keys: [pipe_meta.metric],
876
+ location_keys: [location],
877
+ instance: pipe_meta.instance,
878
+ },
879
+ url
880
+ );
881
+ dash_clientside.set_props("webterm-div", {"style": display_block});
882
+ return [];
883
+ }
884
+ """,
885
+ Output('content-div-right', 'children'),
886
+ Input({'type': 'manage-pipe-button', 'index': ALL, 'action': ALL}, 'n_clicks'),
887
+ State('location', 'href'),
888
+ )
889
+
821
890
  @dash_app.callback(
822
891
  Output("navbar-collapse", "is_open"),
823
892
  [Input("navbar-toggler", "n_clicks")],
@@ -11,6 +11,7 @@ import json
11
11
  import functools
12
12
  import time
13
13
  import traceback
14
+ from datetime import datetime, timezone
14
15
  import meerschaum as mrsm
15
16
  from meerschaum.utils.typing import Optional, Dict, Any
16
17
  from meerschaum.api import get_api_connector, endpoints, CHECK_UPDATE
@@ -54,9 +55,14 @@ def download_job_logs(n_clicks):
54
55
  component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
55
56
  daemon_id = component_dict['index']
56
57
  daemon = Daemon(daemon_id=daemon_id)
58
+ now = datetime.now(timezone.utc)
59
+ filename = (
60
+ daemon.rotating_log.file_path.name[:(-1 * len('.log'))]
61
+ + '_' + str(int(now.timestamp())) + '.log'
62
+ )
57
63
  return {
58
64
  'content': daemon.log_text,
59
- 'filename': daemon.rotating_log.file_path.name,
65
+ 'filename': filename,
60
66
  }
61
67
 
62
68
 
@@ -74,7 +80,7 @@ def manage_job_button_click(
74
80
  session_data: Optional[Dict[str, Any]] = None,
75
81
  ):
76
82
  """
77
- Start, stop, or pause the given job.
83
+ Start, stop, pause, or delete the given job.
78
84
  """
79
85
  if not n_clicks:
80
86
  raise PreventUpdate
@@ -98,12 +104,18 @@ def manage_job_button_click(
98
104
  component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
99
105
  daemon_id = component_dict['index']
100
106
  manage_job_action = component_dict['action']
101
- daemon = Daemon(daemon_id=daemon_id)
107
+ try:
108
+ daemon = Daemon(daemon_id=daemon_id)
109
+ except Exception as e:
110
+ daemon = None
111
+ if daemon is None:
112
+ raise PreventUpdate
102
113
 
103
114
  manage_functions = {
104
115
  'start': functools.partial(daemon.run, allow_dirty_run=True),
105
116
  'stop': daemon.quit,
106
117
  'pause': daemon.pause,
118
+ 'delete': daemon.cleanup,
107
119
  }
108
120
  if manage_job_action not in manage_functions:
109
121
  return (
@@ -135,6 +147,46 @@ def manage_job_button_click(
135
147
  build_process_timestamps_children(daemon),
136
148
  )
137
149
 
150
+ dash_app.clientside_callback(
151
+ """
152
+ function(n_clicks_arr, url){
153
+ display_block = {"display": "block"};
154
+
155
+ var clicked = false;
156
+ for (var i = 0; i < n_clicks_arr.length; i++){
157
+ if (n_clicks_arr[i]){
158
+ clicked = true;
159
+ break;
160
+ }
161
+ }
162
+
163
+ if (!clicked){
164
+ return dash_clientside.no_update;
165
+ }
166
+
167
+ const triggered_id = dash_clientside.callback_context.triggered_id;
168
+ const job_daemon_id = triggered_id["index"];
169
+
170
+ iframe = document.getElementById('webterm-iframe');
171
+ if (!iframe){ return dash_clientside.no_update; }
172
+
173
+ iframe.contentWindow.postMessage(
174
+ {
175
+ action: "show",
176
+ subaction: "logs",
177
+ subaction_text: job_daemon_id,
178
+ },
179
+ url
180
+ );
181
+ dash_clientside.set_props("webterm-div", {"style": display_block});
182
+ return [];
183
+ }
184
+ """,
185
+ Output('content-div-right', 'children'),
186
+ Input({'type': 'follow-logs-button', 'index': ALL}, 'n_clicks'),
187
+ State('location', 'href'),
188
+ )
189
+
138
190
 
139
191
  @dash_app.callback(
140
192
  Output({'type': 'manage-job-buttons-div', 'index': ALL}, 'children'),
@@ -49,6 +49,17 @@ def get_jobs_cards(state: WebState):
49
49
  build_process_timestamps_children(d),
50
50
  id = {'type': 'process-timestamps-div', 'index': d.daemon_id},
51
51
  )
52
+ follow_logs_button = dbc.DropdownMenuItem(
53
+ "Follow logs",
54
+ id = {'type': 'follow-logs-button', 'index': d.daemon_id},
55
+ )
56
+ download_logs_button = dbc.DropdownMenuItem(
57
+ "Download logs",
58
+ id = {'type': 'job-download-logs-button', 'index': d.daemon_id},
59
+ )
60
+ logs_menu_children = (
61
+ ([follow_logs_button] if is_authenticated else []) + [download_logs_button]
62
+ )
52
63
  header_children = [
53
64
  html.Div(
54
65
  build_status_children(d),
@@ -56,11 +67,13 @@ def get_jobs_cards(state: WebState):
56
67
  style = {'float': 'left'},
57
68
  ),
58
69
  html.Div(
59
- dbc.Button(
60
- 'Download logs',
61
- size = 'sm',
62
- color = 'link',
63
- id = {'type': 'job-download-logs-button', 'index': d.daemon_id},
70
+ dbc.DropdownMenu(
71
+ logs_menu_children,
72
+ label = "Logs",
73
+ size = "sm",
74
+ align_end = True,
75
+ color = "secondary",
76
+ menu_variant = 'dark',
64
77
  ),
65
78
  style = {'float': 'right'},
66
79
  ),
@@ -74,7 +87,7 @@ def get_jobs_cards(state: WebState):
74
87
  className = "card-text job-card-text",
75
88
  style = {"word-wrap": "break-word"}
76
89
  ),
77
- style={"white-space": "pre-wrap"},
90
+ style = {"white-space": "pre-wrap"},
78
91
  ),
79
92
  html.Div(
80
93
  (
@@ -82,7 +95,7 @@ def get_jobs_cards(state: WebState):
82
95
  if is_authenticated
83
96
  else []
84
97
  ),
85
- id={'type': 'manage-job-buttons-div', 'index': d.daemon_id}
98
+ id = {'type': 'manage-job-buttons-div', 'index': d.daemon_id},
86
99
  ),
87
100
  html.Div(id={'type': 'manage-job-alert-div', 'index': d.daemon_id}),
88
101
  ]
@@ -108,7 +121,7 @@ def build_manage_job_buttons_div_children(daemon: Daemon):
108
121
  return [
109
122
  html.Br(),
110
123
  dbc.Row([
111
- dbc.Col(button, width=4)
124
+ dbc.Col(button, width=6)
112
125
  for button in buttons
113
126
  ])
114
127
  ]
@@ -153,9 +166,22 @@ def build_manage_job_buttons(daemon: Daemon):
153
166
  'index': daemon.daemon_id,
154
167
  },
155
168
  )
169
+ delete_button = dbc.Button(
170
+ 'Delete',
171
+ size = 'sm',
172
+ color = 'danger',
173
+ style = {'width': '100%'},
174
+ id = {
175
+ 'type': 'manage-job-button',
176
+ 'action': 'delete',
177
+ 'index': daemon.daemon_id,
178
+ },
179
+ )
156
180
  buttons = []
157
181
  if daemon.status in ('stopped', 'paused'):
158
182
  buttons.append(start_button)
183
+ if daemon.status == 'stopped':
184
+ buttons.append(delete_button)
159
185
  if daemon.status in ('running',):
160
186
  buttons.append(pause_button)
161
187
  if daemon.status in ('running', 'paused'):
@@ -115,16 +115,72 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
115
115
  if not isinstance(_pipes, list):
116
116
  _pipes = []
117
117
  for p in _pipes:
118
- footer_children = dbc.Row([
119
- dbc.Col(
120
- dbc.Button(
121
- 'Download recent data',
122
- size = 'sm',
123
- color = 'link',
124
- id = {'type': 'pipe-download-csv-button', 'index': json.dumps(p.meta)},
125
- )
126
- ),
127
- ])
118
+ meta_str = json.dumps(p.meta)
119
+ footer_children = dbc.Row(
120
+ [
121
+ dbc.Col(
122
+ (
123
+ dbc.DropdownMenu(
124
+ label = "Manage",
125
+ children = [
126
+ dbc.DropdownMenuItem(
127
+ 'Sync',
128
+ id = {
129
+ 'type': 'manage-pipe-button',
130
+ 'index': meta_str,
131
+ 'action': 'sync',
132
+ },
133
+ ),
134
+ dbc.DropdownMenuItem(
135
+ 'Drop',
136
+ id = {
137
+ 'type': 'manage-pipe-button',
138
+ 'index': meta_str,
139
+ 'action': 'drop',
140
+ },
141
+ ),
142
+ dbc.DropdownMenuItem(
143
+ 'Delete',
144
+ id = {
145
+ 'type': 'manage-pipe-button',
146
+ 'index': meta_str,
147
+ 'action': 'delete',
148
+ },
149
+ ),
150
+ ],
151
+ direction = "end",
152
+ menu_variant = "dark",
153
+ size = 'sm',
154
+ color = 'secondary',
155
+ )
156
+ ) if authenticated else [],
157
+ width = 2,
158
+ ),
159
+ # dbc.Col(
160
+ # (
161
+ # dbc.Button(
162
+ # 'Sync',
163
+ # size = 'sm',
164
+ # style = {'width': '100%'},
165
+ # id = {'type': 'pipe-sync-button', 'index': meta_str},
166
+ # ) if authenticated else []
167
+ # ),
168
+ # width = 2,
169
+ # ),
170
+ dbc.Col(width=6),
171
+ dbc.Col(
172
+ dbc.Button(
173
+ 'Download recent data',
174
+ size = 'sm',
175
+ color = 'link',
176
+ style = {'float': 'right'},
177
+ id = {'type': 'pipe-download-csv-button', 'index': meta_str},
178
+ ),
179
+ width = 4,
180
+ ),
181
+ ],
182
+ justify = 'start',
183
+ )
128
184
  card_body_children = [
129
185
  html.H5(
130
186
  html.B(str(p)),
@@ -136,7 +192,7 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
136
192
  accordion_items_from_pipe(p, authenticated=authenticated),
137
193
  flush = True,
138
194
  start_collapsed = True,
139
- id = {'type': 'pipe-accordion', 'index': json.dumps(p.meta)},
195
+ id = {'type': 'pipe-accordion', 'index': meta_str},
140
196
  )
141
197
  )
142
198
 
@@ -196,6 +252,18 @@ def accordion_items_from_pipe(
196
252
  ]
197
253
  for col_key, col in pipe.columns.items():
198
254
  overview_rows.append(html.Tr([html.Td(f"'{col_key}' Index"), html.Td(col)]))
255
+ tags = pipe.tags
256
+ if tags:
257
+ tags_items = html.Ul([
258
+ html.Li(tag)
259
+ for tag in tags
260
+ ])
261
+ overview_rows.append(
262
+ html.Tr([
263
+ html.Td("Tags"),
264
+ html.Td(tags_items),
265
+ ])
266
+ )
199
267
 
200
268
  items_bodies['overview'] = dbc.Table(
201
269
  overview_header + [html.Tbody(overview_rows)],