meerschaum 2.3.0rc2__py3-none-any.whl → 2.3.0rc3__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.
@@ -512,6 +512,7 @@ def _complete_delete_jobs(
512
512
  get_executor_keys_from_context,
513
513
  )
514
514
  from meerschaum.utils.misc import remove_ansi
515
+ from meerschaum.connectors.parse import parse_executor_keys
515
516
 
516
517
  executor_keys = (
517
518
  executor_keys
@@ -520,6 +521,9 @@ def _complete_delete_jobs(
520
521
  )
521
522
  )
522
523
 
524
+ if parse_executor_keys(executor_keys, construct=False) is None:
525
+ return []
526
+
523
527
  jobs = get_jobs(executor_keys, include_hidden=False)
524
528
  if _get_job_method:
525
529
  method_keys = [_get_job_method] if isinstance(_get_job_method, str) else _get_job_method
@@ -24,13 +24,13 @@ from dash import Patch
24
24
  html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
25
25
  import dash_bootstrap_components as dbc
26
26
  from meerschaum.api.dash.components import alert_from_success_tuple, build_cards_grid
27
- from meerschaum.utils.daemon import Daemon
28
27
  from dash.exceptions import PreventUpdate
29
28
  from meerschaum.api.dash.jobs import (
30
29
  build_manage_job_buttons_div_children,
31
30
  build_status_children,
32
31
  build_process_timestamps_children,
33
32
  )
33
+ from meerschaum.jobs import Job
34
34
  from meerschaum.api.dash.users import is_session_authenticated
35
35
 
36
36
  @dash_app.callback(
@@ -53,15 +53,11 @@ def download_job_logs(n_clicks):
53
53
  raise PreventUpdate
54
54
 
55
55
  component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
56
- daemon_id = component_dict['index']
57
- daemon = Daemon(daemon_id=daemon_id)
56
+ job_name = component_dict['index']
58
57
  now = datetime.now(timezone.utc)
59
- filename = (
60
- daemon.rotating_log.file_path.name[:(-1 * len('.log'))]
61
- + '_' + str(int(now.timestamp())) + '.log'
62
- )
58
+ filename = job_name + '_' + str(int(now.timestamp())) + '.log'
63
59
  return {
64
- 'content': daemon.log_text,
60
+ 'content': job.get_logs(),
65
61
  'filename': filename,
66
62
  }
67
63
 
@@ -73,12 +69,14 @@ def download_job_logs(n_clicks):
73
69
  Output({'type': 'process-timestamps-div', 'index': MATCH}, 'children'),
74
70
  Input({'type': 'manage-job-button', 'action': ALL, 'index': MATCH}, 'n_clicks'),
75
71
  State('session-store', 'data'),
72
+ State({'type': 'job-label-p', 'index': MATCH}, 'children'),
76
73
  prevent_initial_call = True,
77
74
  )
78
75
  def manage_job_button_click(
79
- n_clicks: Optional[int] = None,
80
- session_data: Optional[Dict[str, Any]] = None,
81
- ):
76
+ n_clicks: Optional[int] = None,
77
+ session_data: Optional[Dict[str, Any]] = None,
78
+ job_label: Optional[str] = None,
79
+ ):
82
80
  """
83
81
  Start, stop, pause, or delete the given job.
84
82
  """
@@ -102,20 +100,20 @@ def manage_job_button_click(
102
100
  raise PreventUpdate
103
101
 
104
102
  component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
105
- daemon_id = component_dict['index']
103
+ job_name = component_dict['index']
106
104
  manage_job_action = component_dict['action']
107
105
  try:
108
- daemon = Daemon(daemon_id=daemon_id)
106
+ job = Job(job_name, job_label.replace('\n', ' ') if job_label else None)
109
107
  except Exception as e:
110
- daemon = None
111
- if daemon is None:
108
+ job = None
109
+ if job is None:
112
110
  raise PreventUpdate
113
111
 
114
112
  manage_functions = {
115
- 'start': functools.partial(daemon.run, allow_dirty_run=True),
116
- 'stop': daemon.quit,
117
- 'pause': daemon.pause,
118
- 'delete': daemon.cleanup,
113
+ 'start': job.start,
114
+ 'stop': job.stop,
115
+ 'pause': job.pause,
116
+ 'delete': job.delete,
119
117
  }
120
118
  if manage_job_action not in manage_functions:
121
119
  return (
@@ -125,7 +123,7 @@ def manage_job_button_click(
125
123
  dash.no_update,
126
124
  )
127
125
 
128
- old_status = daemon.status
126
+ old_status = job.status
129
127
  try:
130
128
  success, msg = manage_functions[manage_job_action]()
131
129
  except Exception as e:
@@ -136,15 +134,15 @@ def manage_job_button_click(
136
134
  check_interval_seconds = 0.01
137
135
  begin = time.perf_counter()
138
136
  while (time.perf_counter() - begin) < timeout_seconds:
139
- if daemon.status != old_status:
137
+ if job.status != old_status:
140
138
  break
141
139
  time.sleep(check_interval_seconds)
142
140
 
143
141
  return (
144
142
  alert_from_success_tuple((success, msg)),
145
- build_manage_job_buttons_div_children(daemon),
146
- build_status_children(daemon),
147
- build_process_timestamps_children(daemon),
143
+ build_manage_job_buttons_div_children(job),
144
+ build_status_children(job),
145
+ build_process_timestamps_children(job),
148
146
  )
149
147
 
150
148
  dash_app.clientside_callback(
@@ -165,7 +163,7 @@ dash_app.clientside_callback(
165
163
  }
166
164
 
167
165
  const triggered_id = dash_clientside.callback_context.triggered_id;
168
- const job_daemon_id = triggered_id["index"];
166
+ const job_name = triggered_id["index"];
169
167
 
170
168
  iframe = document.getElementById('webterm-iframe');
171
169
  if (!iframe){ return dash_clientside.no_update; }
@@ -174,7 +172,7 @@ dash_app.clientside_callback(
174
172
  {
175
173
  action: "show",
176
174
  subaction: "logs",
177
- subaction_text: job_daemon_id,
175
+ subaction_text: job_name,
178
176
  },
179
177
  url
180
178
  );
@@ -197,44 +195,38 @@ dash_app.clientside_callback(
197
195
  prevent_initial_call = True,
198
196
  )
199
197
  def refresh_jobs_on_interval(
200
- n_intervals: Optional[int] = None,
201
- session_data: Optional[Dict[str, Any]] = None,
202
- ):
198
+ n_intervals: Optional[int] = None,
199
+ session_data: Optional[Dict[str, Any]] = None,
200
+ ):
203
201
  """
204
202
  When the jobs refresh interval fires, rebuild the jobs' onscreen components.
205
203
  """
206
204
  session_id = session_data.get('session-id', None)
207
205
  is_authenticated = is_session_authenticated(session_id)
208
206
 
209
- daemon_ids = [
207
+ job_names = [
210
208
  component_dict['id']['index']
211
209
  for component_dict in dash.callback_context.outputs_grouping[0]
212
210
  ]
213
211
 
214
- ### NOTE: The daemon may have been deleted, but the card may still exist.
215
- daemons = []
216
- for daemon_id in daemon_ids:
217
- try:
218
- daemon = Daemon(daemon_id=daemon_id)
219
- except Exception as e:
220
- daemon = None
221
- daemons.append(daemon)
212
+ ### NOTE: The job may have been deleted, but the card may still exist.
213
+ jobs = [Job(name) for name in job_names]
222
214
 
223
215
  return (
224
216
  [
225
217
  (
226
- build_manage_job_buttons_div_children(daemon)
218
+ build_manage_job_buttons_div_children(job)
227
219
  if is_authenticated
228
220
  else []
229
221
  )
230
- for daemon in daemons
222
+ for job in jobs
231
223
  ],
232
224
  [
233
- build_status_children(daemon)
234
- for daemon in daemons
225
+ build_status_children(job)
226
+ for job in jobs
235
227
  ],
236
228
  [
237
- build_process_timestamps_children(daemon)
238
- for daemon in daemons
229
+ build_process_timestamps_children(job)
230
+ for job in jobs
239
231
  ],
240
232
  )
@@ -15,12 +15,12 @@ from meerschaum.api.dash.users import is_session_authenticated
15
15
  from meerschaum.api import CHECK_UPDATE
16
16
  dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
17
17
  html, dcc = import_html(), import_dcc()
18
- dateutil_parser = attempt_import('dateutil.parser', check_update=CHECK_UPDATE)
19
18
  from meerschaum.jobs import (
20
19
  get_jobs,
21
20
  get_running_jobs,
22
21
  get_paused_jobs,
23
22
  get_stopped_jobs,
23
+ get_executor_keys_from_context,
24
24
  Job,
25
25
  )
26
26
  from meerschaum.config import get_config
@@ -33,20 +33,21 @@ STATUS_EMOJI: Dict[str, str] = {
33
33
  'dne': get_config('formatting', 'emoji', 'failure')
34
34
  }
35
35
 
36
+ EXECUTOR_KEYS: str = get_executor_keys_from_context()
37
+
36
38
  def get_jobs_cards(state: WebState):
37
39
  """
38
40
  Build cards and alerts lists for jobs.
39
41
  """
40
- jobs = get_jobs(include_hidden=False)
42
+ jobs = get_jobs(executor_keys=EXECUTOR_KEYS, include_hidden=False)
41
43
  session_id = state['session-store.data'].get('session-id', None)
42
44
  is_authenticated = is_session_authenticated(session_id)
43
45
 
44
46
  cards = []
45
47
 
46
48
  for name, job in jobs.items():
47
- d = job.daemon
48
49
  footer_children = html.Div(
49
- build_process_timestamps_children(d),
50
+ build_process_timestamps_children(job),
50
51
  id = {'type': 'process-timestamps-div', 'index': name},
51
52
  )
52
53
  follow_logs_button = dbc.DropdownMenuItem(
@@ -62,7 +63,7 @@ def get_jobs_cards(state: WebState):
62
63
  )
63
64
  header_children = [
64
65
  html.Div(
65
- build_status_children(d),
66
+ build_status_children(job),
66
67
  id={'type': 'manage-job-status-div', 'index': name},
67
68
  style={'float': 'left'},
68
69
  ),
@@ -83,15 +84,16 @@ def get_jobs_cards(state: WebState):
83
84
  html.H4(html.B(name), className="card-title"),
84
85
  html.Div(
85
86
  html.P(
86
- d.label,
87
+ job.label,
87
88
  className="card-text job-card-text",
88
89
  style={"word-wrap": "break-word"},
90
+ id={'type': 'job-label-p', 'index': name},
89
91
  ),
90
92
  style={"white-space": "pre-wrap"},
91
93
  ),
92
94
  html.Div(
93
95
  (
94
- build_manage_job_buttons_div_children(d)
96
+ build_manage_job_buttons_div_children(job)
95
97
  if is_authenticated
96
98
  else []
97
99
  ),
@@ -179,13 +181,14 @@ def build_manage_job_buttons(job: Job):
179
181
  },
180
182
  )
181
183
  buttons = []
182
- if job.status in ('stopped', 'paused'):
184
+ status = job.status
185
+ if status in ('stopped', 'paused'):
183
186
  buttons.append(start_button)
184
- if job.status == 'stopped':
187
+ if status == 'stopped':
185
188
  buttons.append(delete_button)
186
- if job.status in ('running',):
189
+ if status in ('running',):
187
190
  buttons.append(pause_button)
188
- if job.status in ('running', 'paused'):
191
+ if status in ('running', 'paused'):
189
192
  buttons.append(stop_button)
190
193
 
191
194
  return buttons
@@ -193,7 +196,7 @@ def build_manage_job_buttons(job: Job):
193
196
 
194
197
  def build_status_children(job: Job) -> List[html.P]:
195
198
  """
196
- Return the status HTML component for this daemon.
199
+ Return the status HTML component for this Job.
197
200
  """
198
201
  if job is None:
199
202
  return STATUS_EMOJI['dne']
@@ -217,10 +220,16 @@ def build_process_timestamps_children(job: Job) -> List[dbc.Row]:
217
220
  return []
218
221
 
219
222
  children = []
220
- for timestamp_key, timestamp_val in sorted_dict(
221
- job.daemon.properties.get('process', {})
223
+ for timestamp_key, timestamp in sorted_dict(
224
+ {
225
+ 'began': job.began,
226
+ 'paused': job.paused,
227
+ 'ended': job.ended,
228
+ }
222
229
  ).items():
223
- timestamp = dateutil_parser.parse(timestamp_val)
230
+ if timestamp is None:
231
+ continue
232
+
224
233
  timestamp_str = timestamp.strftime('%Y-%m-%d %H:%M UTC')
225
234
  children.append(
226
235
  dbc.Row(
@@ -26,6 +26,7 @@ from meerschaum.actions import actions
26
26
  import meerschaum.core
27
27
  from meerschaum.config import get_config
28
28
  from meerschaum._internal.arguments._parse_arguments import parse_dict_to_sysargs, parse_arguments
29
+ from meerschaum.api.routes._jobs import clean_sysargs
29
30
 
30
31
  actions_endpoint = endpoints['actions']
31
32
 
@@ -115,11 +116,11 @@ async def do_action_websocket(websocket: WebSocket):
115
116
  if not auth_success:
116
117
  await websocket.close()
117
118
 
118
- sysargs = await websocket.receive_json()
119
- kwargs = parse_arguments(sysargs)
120
- _ = kwargs.pop('executor_keys', None)
121
- _ = kwargs.pop('shell', None)
122
- sysargs = parse_dict_to_sysargs(kwargs)
119
+ sysargs = clean_sysargs(await websocket.receive_json())
120
+ # kwargs = parse_arguments(sysargs)
121
+ # _ = kwargs.pop('executor_keys', None)
122
+ # _ = kwargs.pop('shell', None)
123
+ # sysargs = parse_dict_to_sysargs(kwargs)
123
124
 
124
125
  job = Job(
125
126
  job_name,
@@ -107,6 +107,24 @@ def get_job(
107
107
  }
108
108
 
109
109
 
110
+ def clean_sysargs(sysargs: List[str]) -> List[str]:
111
+ """
112
+ Remove the executor flag or leading `api {label}` action.
113
+ """
114
+ clean_sysargs = []
115
+ executor_flag = False
116
+ for arg in sysargs:
117
+ if arg in ('-e', '--executor', 'api'):
118
+ executor_flag = True
119
+ continue
120
+ if executor_flag:
121
+ executor_flag = False
122
+ continue
123
+
124
+ clean_sysargs.append(arg)
125
+ return clean_sysargs
126
+
127
+
110
128
  @app.post(endpoints['jobs'] + '/{name}', tags=['Jobs'])
111
129
  def create_job(
112
130
  name: str,
@@ -118,7 +136,7 @@ def create_job(
118
136
  """
119
137
  Create and start a new job.
120
138
  """
121
- job = Job(name, sysargs, executor_keys=EXECUTOR_KEYS)
139
+ job = Job(name, clean_sysargs(sysargs), executor_keys=EXECUTOR_KEYS)
122
140
  if job.exists():
123
141
  raise fastapi.HTTPException(
124
142
  status_code=409,
@@ -7,7 +7,7 @@ Default configuration for jobs.
7
7
  """
8
8
 
9
9
  default_jobs_config = {
10
- 'timeout_seconds': 8,
10
+ 'timeout_seconds': 4,
11
11
  'check_timeout_interval_seconds': 0.1,
12
12
  'terminal': {
13
13
  'lines': 40,
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.3.0rc2"
5
+ __version__ = "2.3.0rc3"
@@ -90,6 +90,7 @@ class APIConnector(Connector):
90
90
  get_job_is_blocking_on_stdin,
91
91
  get_job_began,
92
92
  get_job_ended,
93
+ get_job_paused,
93
94
  get_job_status,
94
95
  )
95
96
 
@@ -112,7 +112,7 @@ def get_job_status(self, name: str, debug: bool = False) -> str:
112
112
  metadata = self.get_job_metadata(name, debug=debug)
113
113
  return metadata.get('status', 'stopped')
114
114
 
115
- def get_job_began(self, name: str, debug: bool = False) -> str:
115
+ def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
116
116
  """
117
117
  Return a job's `began` timestamp, if it exists.
118
118
  """
@@ -123,7 +123,7 @@ def get_job_began(self, name: str, debug: bool = False) -> str:
123
123
 
124
124
  return began_str
125
125
 
126
- def get_job_ended(self, name: str, debug: bool = False) -> str:
126
+ def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
127
127
  """
128
128
  Return a job's `ended` timestamp, if it exists.
129
129
  """
@@ -134,6 +134,17 @@ def get_job_ended(self, name: str, debug: bool = False) -> str:
134
134
 
135
135
  return ended_str
136
136
 
137
+ def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
138
+ """
139
+ Return a job's `paused` timestamp, if it exists.
140
+ """
141
+ properties = self.get_job_properties(name, debug=debug)
142
+ paused_str = properties.get('daemon', {}).get('paused', None)
143
+ if paused_str is None:
144
+ return None
145
+
146
+ return paused_str
147
+
137
148
  def get_job_exists(self, name: str, debug: bool = False) -> bool:
138
149
  """
139
150
  Return whether a job exists.
meerschaum/jobs/_Job.py CHANGED
@@ -740,7 +740,7 @@ class Job:
740
740
  The datetime when the job began running.
741
741
  """
742
742
  if self.executor is not None:
743
- began_str = self.executor.get_job_began(name)
743
+ began_str = self.executor.get_job_began(self.name)
744
744
  if began_str is None:
745
745
  return None
746
746
  return (
@@ -762,6 +762,8 @@ class Job:
762
762
  """
763
763
  if self.executor is not None:
764
764
  ended_str = self.executor.get_job_ended(self.name)
765
+ if ended_str is None:
766
+ return None
765
767
  return (
766
768
  datetime.fromisoformat(ended_str)
767
769
  .astimezone(timezone.utc)
@@ -779,6 +781,16 @@ class Job:
779
781
  """
780
782
  The datetime when the job was suspended while running.
781
783
  """
784
+ if self.executor is not None:
785
+ paused_str = self.executor.get_job_paused(self.name)
786
+ if paused_str is None:
787
+ return None
788
+ return (
789
+ datetime.fromisoformat(paused_str)
790
+ .astimezone(timezone.utc)
791
+ .replace(tzinfo=None)
792
+ )
793
+
782
794
  paused_str = self.daemon.properties.get('process', {}).get('paused', None)
783
795
  if paused_str is None:
784
796
  return None
@@ -796,11 +808,8 @@ class Job:
796
808
  if not self.daemon.stop_path.exists():
797
809
  return None
798
810
 
799
- try:
800
- with open(self.daemon.stop_path, 'r', encoding='utf-8') as f:
801
- stop_data = json.load(f)
802
- except Exception as e:
803
- warn(f"Failed to read stop file for {self}:\n{e}")
811
+ stop_data = self.daemon._read_stop_file()
812
+ if not stop_data:
804
813
  return None
805
814
 
806
815
  stop_time_str = stop_data.get('stop_time', None)
@@ -19,6 +19,7 @@ from functools import partial
19
19
  import meerschaum as mrsm
20
20
  from meerschaum.jobs import Job, Executor, make_executor
21
21
  from meerschaum.utils.typing import Dict, Any, List, SuccessTuple, Union, Optional, Callable
22
+ from meerschaum.config import get_config
22
23
  from meerschaum.config.static import STATIC_CONFIG
23
24
  from meerschaum.utils.warnings import warn, dprint
24
25
  from meerschaum._internal.arguments._parse_arguments import parse_arguments
@@ -141,7 +142,8 @@ class SystemdExecutor(Executor):
141
142
  STATIC_CONFIG['environment']['systemd_log_path']: service_logs_path.as_posix(),
142
143
  STATIC_CONFIG['environment']['systemd_result_path']: result_path.as_posix(),
143
144
  STATIC_CONFIG['environment']['systemd_stdin_path']: socket_path.as_posix(),
144
-
145
+ 'LINES': get_config('jobs', 'terminal', 'lines'),
146
+ 'COLUMNS': get_config('jobs', 'terminal', 'columns'),
145
147
  })
146
148
  environment_lines = [
147
149
  f"Environment={key}={val}"
@@ -259,7 +261,10 @@ class SystemdExecutor(Executor):
259
261
  return None
260
262
 
261
263
  psutil = mrsm.attempt_import('psutil')
262
- return psutil.Process(pid)
264
+ try:
265
+ return psutil.Process(pid)
266
+ except Exception:
267
+ return None
263
268
 
264
269
  def get_job_status(self, name: str, debug: bool = False) -> str:
265
270
  """
@@ -271,13 +276,19 @@ class SystemdExecutor(Executor):
271
276
  debug=debug,
272
277
  )
273
278
 
279
+ if output == 'activating':
280
+ return 'running'
281
+
274
282
  if output == 'active':
275
283
  process = self.get_job_process(name, debug=debug)
276
284
  if process is None:
277
285
  return 'stopped'
278
286
 
279
- if process.status() == 'stopped':
280
- return 'paused'
287
+ try:
288
+ if process.status() == 'stopped':
289
+ return 'paused'
290
+ except Exception:
291
+ return 'stopped'
281
292
 
282
293
  return 'running'
283
294
 
@@ -310,7 +321,7 @@ class SystemdExecutor(Executor):
310
321
 
311
322
  return None
312
323
 
313
- def get_job_began(self, name: str, debug: bool = False) -> Union[datetime, None]:
324
+ def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
314
325
  """
315
326
  Return when a job began running.
316
327
  """
@@ -326,10 +337,62 @@ class SystemdExecutor(Executor):
326
337
  if not output.startswith('ActiveEnterTimestamp'):
327
338
  return None
328
339
 
340
+ dt_str = output.split('=')[-1]
341
+ if not dt_str:
342
+ return None
343
+
329
344
  dateutil_parser = mrsm.attempt_import('dateutil.parser')
330
- dt = dateutil_parser.parse(output.split('=')[-1])
345
+ try:
346
+ dt = dateutil_parser.parse(dt_str)
347
+ except Exception as e:
348
+ warn(f"Cannot parse '{output}' as a datetime:\n{e}")
349
+ return None
350
+
331
351
  return dt.astimezone(timezone.utc).isoformat()
332
352
 
353
+ def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
354
+ """
355
+ Return when a job began running.
356
+ """
357
+ output = self.run_command(
358
+ [
359
+ 'show',
360
+ self.get_service_name(name, debug=debug),
361
+ '--property=InactiveEnterTimestamp'
362
+ ],
363
+ as_output=True,
364
+ debug=debug,
365
+ )
366
+ if not output.startswith('InactiveEnterTimestamp'):
367
+ return None
368
+
369
+ dt_str = output.split('=')[-1]
370
+ if not dt_str:
371
+ return None
372
+
373
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
374
+
375
+ try:
376
+ dt = dateutil_parser.parse(dt_str)
377
+ except Exception as e:
378
+ warn(f"Cannot parse '{output}' as a datetime:\n{e}")
379
+ return None
380
+ return dt.astimezone(timezone.utc).isoformat()
381
+
382
+ def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
383
+ """
384
+ Return when a job was paused.
385
+ """
386
+ job = self.get_hidden_job(name, debug=debug)
387
+ if self.get_job_status(name, debug=debug) != 'paused':
388
+ return None
389
+
390
+ stop_time = job.stop_time
391
+ if stop_time is None:
392
+ return None
393
+
394
+ return stop_time.isoformat()
395
+
333
396
  def get_job_result(self, name: str, debug: bool = False) -> SuccessTuple:
334
397
  """
335
398
  Return the job's result SuccessTuple.
@@ -495,12 +558,13 @@ class SystemdExecutor(Executor):
495
558
  debug=debug,
496
559
  )
497
560
 
561
+ check_timeout_interval = get_config('jobs', 'check_timeout_interval_seconds')
498
562
  loop_start = time.perf_counter()
499
- while (time.perf_counter() - loop_start) < 5:
563
+ while (time.perf_counter() - loop_start) < get_config('jobs', 'timeout_seconds'):
500
564
  if self.get_job_status(name, debug=debug) == 'stopped':
501
565
  return True, 'Success'
502
566
 
503
- time.sleep(0.1)
567
+ time.sleep(check_timeout_interval)
504
568
 
505
569
  return self.run_command(
506
570
  ['stop', self.get_service_name(name, debug=debug)],
@@ -102,6 +102,12 @@ def get_jobs(
102
102
  )
103
103
  return {**local_jobs, **systemd_jobs}
104
104
 
105
+ if executor_keys == 'local':
106
+ return _get_local_jobs()
107
+
108
+ if executor_keys == 'systemd':
109
+ return _get_systemd_jobs()
110
+
105
111
  try:
106
112
  _ = parse_executor_keys(executor_keys, construct=False)
107
113
  conn = parse_executor_keys(executor_keys)
@@ -623,6 +623,20 @@ class Daemon:
623
623
 
624
624
  return True, "Success"
625
625
 
626
+ def _read_stop_file(self) -> Dict[str, Any]:
627
+ """
628
+ Read the stop file if it exists.
629
+ """
630
+ if not self.stop_path.exists():
631
+ return {}
632
+
633
+ try:
634
+ with open(self.stop_path, 'r', encoding='utf-8') as f:
635
+ data = json.load(f)
636
+ return data
637
+ except Exception:
638
+ return {}
639
+
626
640
  def _handle_sigterm(self, signal_number: int, stack_frame: 'frame') -> None:
627
641
  """
628
642
  Handle `SIGTERM` within the `Daemon` context.
@@ -84,7 +84,7 @@ _nouns: List[str] = []
84
84
  for category, items in _bank['nouns'].items():
85
85
  _nouns += items
86
86
 
87
- def generate_random_name(separator: str = '_'):
87
+ def generate_random_name(separator: str = '-'):
88
88
  """
89
89
  Return a random adjective and noun combination.
90
90
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.3.0rc2
3
+ Version: 2.3.0rc3
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -28,7 +28,7 @@ meerschaum/actions/bootstrap.py,sha256=9D3cBHzgZbZyWy-Y7iQgk9bpTbKEhumFKbIIThZgP
28
28
  meerschaum/actions/clear.py,sha256=OoFZE0bK5m8s3GLNZcixuVT0DMj1izXVxGCATcmUGbI,4851
29
29
  meerschaum/actions/copy.py,sha256=hLRc81oVBjnV22lpeSDb-HsDX2on9K2u6C1H8qJITNQ,6844
30
30
  meerschaum/actions/deduplicate.py,sha256=puYyxeFYEUy1Sd2IOcZB2e6MrNxAZl2bTLmNzFDkCiw,1167
31
- meerschaum/actions/delete.py,sha256=GRokg2NRzlow2UMYXNs9xwBu-9AuJdK6kAxsxgwp-E0,18823
31
+ meerschaum/actions/delete.py,sha256=OidLnYT4E3OEHGHOeHOHbL3FFJonE7iR_3sPCj0azK0,18974
32
32
  meerschaum/actions/drop.py,sha256=Hd5h4rrWd7qL2rTqglsTonUsEoH7qQlsfqNFSHGeqr0,2453
33
33
  meerschaum/actions/edit.py,sha256=6d1Y8Ejd4zQWVARvx04cvbGyRA2r4ihJXWqMFiFX0aM,11740
34
34
  meerschaum/actions/install.py,sha256=jdhOrR_KlvinTKr0YJNkUHsnh5EY6OzA7cRq0Vnp1oU,7494
@@ -61,7 +61,7 @@ meerschaum/api/dash/actions.py,sha256=eUClPPdNVNSCtyq8Ecr1saasxj5VBgd1gcXejvQxXE
61
61
  meerschaum/api/dash/components.py,sha256=oGX7HFzNCi37iMww747iL3bbB52ZTt9tFa2iLohffg4,6399
62
62
  meerschaum/api/dash/connectors.py,sha256=nJxBOFldtCMJLYjUSVYZwX5BO-LNjTNHgoEaXe-0XMo,843
63
63
  meerschaum/api/dash/graphs.py,sha256=wJUDWzcLN8-C3xko6rj0F2v7Rt8YDkSXoVkkXJjYGIk,2046
64
- meerschaum/api/dash/jobs.py,sha256=Y3XM7aG_hWQsQLoQfgUVlH5V2vNGA6qkfN0OX6sX04g,7302
64
+ meerschaum/api/dash/jobs.py,sha256=PbQgo1i3iPyysMJkkMRzZZOuV9vHzFShjJltiWsWUKo,7456
65
65
  meerschaum/api/dash/keys.py,sha256=hzEVeN60SAfVTVSO5lajGaykxRIKGhj9Ph00HRJnNoE,12598
66
66
  meerschaum/api/dash/pipes.py,sha256=RVVYIJJy4G01cXou9OT8Gr5ZsygQ_GUqJDDhU1akhi8,20615
67
67
  meerschaum/api/dash/plugins.py,sha256=JiQjeeCT21MoPDo81pkTXw9viqIH6xpivQgZEO4qtzQ,3676
@@ -78,7 +78,7 @@ meerschaum/api/dash/assets/logo_500x500.png,sha256=9EUtf6wQcEZTXHKfQ2kjNXod6Rn_4
78
78
  meerschaum/api/dash/callbacks/__init__.py,sha256=CKOkt7PIHbYxX9aDab2xPDuufIAQjSR5MVX-hU8Zi2w,450
79
79
  meerschaum/api/dash/callbacks/custom.py,sha256=N9pVolAF8sIuJD3V6xBSgS7k8THJo_f8d1qAoh1Kg60,1161
80
80
  meerschaum/api/dash/callbacks/dashboard.py,sha256=8Rjxuw8uGww6oRfwRUC_mjm6i7aD0dVW0CeY2a7r7Yo,33101
81
- meerschaum/api/dash/callbacks/jobs.py,sha256=OEYxJRlmnxqbsvHb5jBJJCsRvVCsMNusm9AClCT9Pm0,7689
81
+ meerschaum/api/dash/callbacks/jobs.py,sha256=Xvw17za3tNvgu-yHzNku_0pZVK17vfEWKEy_DLtgo70,7407
82
82
  meerschaum/api/dash/callbacks/login.py,sha256=oUGAo7gokAQDFgF4dBHaGf-An04JjkrGFHxSGB-ZMqU,2825
83
83
  meerschaum/api/dash/callbacks/plugins.py,sha256=7CrwwbBI2N3DR4Yb1bKmrtJ3cAQ3dVv1l6E_AtZ6Wng,2777
84
84
  meerschaum/api/dash/callbacks/register.py,sha256=9AgTcl--TPt6oETKzirQCkH9Azlm3XrU9U6XJM2b8Lk,3600
@@ -115,10 +115,10 @@ meerschaum/api/resources/templates/old_index.html,sha256=BDeOlcXhSsBH3-NaRtuX4Z1
115
115
  meerschaum/api/resources/templates/secret.html,sha256=0QWkm4ZoN81Aw1pd2-62rGCvx3nXPHfFUoegj3Iy8Ls,141
116
116
  meerschaum/api/resources/templates/termpage.html,sha256=qspXRuOkzqOn2mXw9mmUldzsvOHq_LyaywQ29CUevp0,4527
117
117
  meerschaum/api/routes/__init__.py,sha256=jbkeFNl51Tg8aT5gWe560ZLZLojFJsLMe5IENRjRkb0,606
118
- meerschaum/api/routes/_actions.py,sha256=glmLj2CzZvrwKKyJwuBzYLvzs1hwzh8vo-9Hcy82yeE,4511
118
+ meerschaum/api/routes/_actions.py,sha256=-UYsRkDvdv9HG9AkaaLjAGWXkdBYYGLFWz85vkvHtKc,4592
119
119
  meerschaum/api/routes/_connectors.py,sha256=NNbcn5xWhKqw2PqueSEaqRaZ95hFGDKazG5lE7gsssc,1849
120
120
  meerschaum/api/routes/_index.py,sha256=QI6CBo6pI2Zi0a6fJHDjZfiLa9f4okb0BGe3A_JD0kM,578
121
- meerschaum/api/routes/_jobs.py,sha256=0G7IhQpRenFJ9OSKo25lqCuJonNzK_q-XMXSD_pjdv0,10515
121
+ meerschaum/api/routes/_jobs.py,sha256=593jLAdF2N28USjLEfyIABpD3c4CuajLDbVZu41WuOU,10977
122
122
  meerschaum/api/routes/_login.py,sha256=psPKmFkXgYVX83NepqwIhaLsQ5uWgOc4F2QZtPGxY1A,2482
123
123
  meerschaum/api/routes/_misc.py,sha256=05--9ZVFeaCgZrHER2kA3SYdK4TyfkEXOCjLvPbum-w,2469
124
124
  meerschaum/api/routes/_pipes.py,sha256=1gBuE4E-QvIK_kmbmiw7uLcXjnIobFI1t4tb2skpp6E,21592
@@ -133,14 +133,14 @@ meerschaum/config/_default.py,sha256=8ryAQV9yYkpIZaVYe9W7dm5c-WAu_lX1jSKGLLo4FtQ
133
133
  meerschaum/config/_edit.py,sha256=_kabgFbJdI5kcLs-JIsoaTo0JdyxnPnBdrlTyTAgPm8,8236
134
134
  meerschaum/config/_environment.py,sha256=Vv4DLDfc2vKLbCLsMvkQDj77K4kEvHKEBmUBo-wCrgo,4419
135
135
  meerschaum/config/_formatting.py,sha256=OMuqS1EWOsj_34wSs2tOqGIWci3bTMIZ5l-uelZgsIM,6672
136
- meerschaum/config/_jobs.py,sha256=2bEikO48qVSguFS3lrbWz6uiKt_0b3oSRWhwyz8RRDM,1279
136
+ meerschaum/config/_jobs.py,sha256=gS_4mMGdmVP7WB4V5Sz8kYP0HmhWcMY2jSWGR7jX6cw,1279
137
137
  meerschaum/config/_patch.py,sha256=21N30q1ANmWMDQ-2RUjpMx7KafWfPQ3lKx9rrMqg1s4,1526
138
138
  meerschaum/config/_paths.py,sha256=d6CKJfMmc9JrAW0E9Kpyy6bKQiB6yeHqZx91-cxKJIQ,9677
139
139
  meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6wLs,1220
140
140
  meerschaum/config/_read_config.py,sha256=WFZKIXZMDe_ca0ES7ivgM_mnwShvFxLdoeisT_X5-h0,14720
141
141
  meerschaum/config/_shell.py,sha256=46_m49Txc5q1rGfCgO49ca48BODx45DQJi8D0zz1R18,4245
142
142
  meerschaum/config/_sync.py,sha256=oK2ZujO2T1he08BXCFyiniBUevNGWSQKXLcS_jRv_7Y,4155
143
- meerschaum/config/_version.py,sha256=bfQh2_v-HpBVdlfw2zoIhMos9XaCBgtxmAFNwHxb3HM,74
143
+ meerschaum/config/_version.py,sha256=Z6PjBfAKMgVlUvxdR1JT93ZJZE4LQbhVOjo8PSU0nm4,74
144
144
  meerschaum/config/paths.py,sha256=JjibeGN3YAdSNceRwsd42aNmeUrIgM6ndzC8qZAmNI0,621
145
145
  meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
146
  meerschaum/config/stack/__init__.py,sha256=Yt7GNzC_hz7iUDZ4gVho_lugJO2DnXgnMtsMG_ReoRg,9114
@@ -153,11 +153,11 @@ meerschaum/connectors/Connector.py,sha256=utNV3Fy5DhUVbQE-vtm7enH5rH2gxQERmgmP7P
153
153
  meerschaum/connectors/__init__.py,sha256=b1RdpMUNWtA5Q4KbymeUt9HtEiMWUHf0BBXzcl4itkc,12557
154
154
  meerschaum/connectors/parse.py,sha256=sDeI2OIS9Inwhbn9jkFAXxOPnmmAHqsuHiiHfWjVnSA,4307
155
155
  meerschaum/connectors/poll.py,sha256=gIY9TvFBqMvMNQvR0O2No7koLLz2PjfExBr_Dsosgpg,7363
156
- meerschaum/connectors/api/APIConnector.py,sha256=xe1Bsbnh5PBNR2r5yr6iqJ1UozEKXWJBZnUU0qdXC7c,4934
156
+ meerschaum/connectors/api/APIConnector.py,sha256=S_QL44WODA2uisIcC8wk_mix_3GhBzUXZjfon29P5lk,4958
157
157
  meerschaum/connectors/api/__init__.py,sha256=JwKrGtuE5aOd2VnsRwudFBYyBf5IxczOwPVdNvCUgSQ,205
158
158
  meerschaum/connectors/api/_actions.py,sha256=KS2Ib5_yHuN0Fw86wyg0bXORV2xY4m3DQrBtcqIIjnY,2343
159
159
  meerschaum/connectors/api/_fetch.py,sha256=Khq9AFr1nk8Dsmcedb77aWhAuHw0JGgVeahDG95Q5MQ,2072
160
- meerschaum/connectors/api/_jobs.py,sha256=sieFAgk38PdyAnCbgzA-gOWdGK1gyzLhxbapxr4Hg04,10696
160
+ meerschaum/connectors/api/_jobs.py,sha256=XlZXuTbkqDzrp6WgoD8OskGUfrKf2VbayhuB_WbRkzw,11066
161
161
  meerschaum/connectors/api/_login.py,sha256=5GsD-B214vr5EYfM3XrTUs1sTFApxZA-9dNxq8oNSyg,2050
162
162
  meerschaum/connectors/api/_misc.py,sha256=OZRZBYOokKIEjmQaR8jUYgu6ZRn9VzXBChzR8CfDv_w,1092
163
163
  meerschaum/connectors/api/_pipes.py,sha256=wf-_jqFBiTNw2s2msp0Ye6IyTl5JWiyC9aLBI0PQuPs,20962
@@ -201,10 +201,10 @@ meerschaum/core/Plugin/__init__.py,sha256=UXg64EvJPgI1PCxkY_KM02-ZmBm4FZpLPIQR_u
201
201
  meerschaum/core/User/_User.py,sha256=CApB7Y0QJL6S9QOCCfrG4SbPuPXJ9AsAYQ5pASMP_Aw,6527
202
202
  meerschaum/core/User/__init__.py,sha256=lJ7beIZTG9sO4dAi3367fFBl17dXYEWHKi7HoaPlDyk,193
203
203
  meerschaum/jobs/_Executor.py,sha256=ZUsQl0hgU0HW_F9bQCfm8KwrdyTBLCrfxsSnbJOwtWI,1575
204
- meerschaum/jobs/_Job.py,sha256=a0bND2kun57FZk8lthO7J6SQseVpkGhn7hmCo0_am-E,28759
204
+ meerschaum/jobs/_Job.py,sha256=x1KXKOewv1hutf89i31yvRx_zlHnxn03ZbISHLXNbLM,29013
205
205
  meerschaum/jobs/_LocalExecutor.py,sha256=v_zvU1vgJDy-iiztXS5gnMayJ0UZnW48qJj3OSB6o-k,2195
206
- meerschaum/jobs/_SystemdExecutor.py,sha256=Us3MH3KmAJxjLJPJPmegBlZy8HExgRIylNPb7Wro-qk,20933
207
- meerschaum/jobs/__init__.py,sha256=Lhox8AboqyKmRyq13elSHq85rSDEuz0AQkoFW3PVzr8,10662
206
+ meerschaum/jobs/_SystemdExecutor.py,sha256=ek2pO7u8qt0PTbfPPJLXM_j5rlP3ScUUzPBwXK5HhII,22960
207
+ meerschaum/jobs/__init__.py,sha256=hGlyB80olsP1BSkYhmHHyg6hi0UuIOU7pWMX7Ry8EFU,10800
208
208
  meerschaum/plugins/_Plugin.py,sha256=q1B4gVAO2BGA-PIR0HI3-O9q1n8BaT7tSANIyW7gUPg,34084
209
209
  meerschaum/plugins/__init__.py,sha256=3Hg9yyfkN0TxPPTnlt6pi53znjRIAR92WpG3zEKYm10,26152
210
210
  meerschaum/plugins/bootstrap.py,sha256=qg9MQ1YAU8ShwGqWDl38WjiXLIxDPl95pSIGDLN9rOw,11423
@@ -224,12 +224,12 @@ meerschaum/utils/threading.py,sha256=3N8JXPAnwqJiSjuQcbbJg3Rv9-CCUMJpeQRfKFR7MaA
224
224
  meerschaum/utils/typing.py,sha256=U3MC347sh1umpa3Xr1k71eADyDmk4LB6TnVCpq8dVzI,2830
225
225
  meerschaum/utils/warnings.py,sha256=IDiwYspsfjIi1gtk3V9cSo9vNLckB9bCsHhRClpPJTc,6639
226
226
  meerschaum/utils/yaml.py,sha256=Da9ZtNdT8f68sqz6g4eLQM3jz8QQ2J9_FglX-fw5VXY,3901
227
- meerschaum/utils/daemon/Daemon.py,sha256=mQG-bJNvATM0TDxyZVvXkQ1X3H9NZb3hlg3wEUB75bs,41274
227
+ meerschaum/utils/daemon/Daemon.py,sha256=8MPsHwZT_X9zZ9nxNlwa80PpwkB_NJYp3aBsfxTEW54,41640
228
228
  meerschaum/utils/daemon/FileDescriptorInterceptor.py,sha256=MJKMO0Syf3d8yWUs6xXcQzg8Ptsuvh2aCRRoglOjusA,5257
229
229
  meerschaum/utils/daemon/RotatingFile.py,sha256=ePm_svjwyFDWh6V1k-bp1RHXCSWlyxDtlFu4SU4XvPU,24369
230
230
  meerschaum/utils/daemon/StdinFile.py,sha256=CECjb40If-TKTXwj_3nFkUfY2xTrrIx73M32mcn5hS8,3172
231
231
  meerschaum/utils/daemon/__init__.py,sha256=o9jWb4lRTIyny4EPt7fPXFgV_vIf1mUofsTwoE1ZecA,8751
232
- meerschaum/utils/daemon/_names.py,sha256=Prf7xA2GWDbKR_9Xq9_5RTTIf9GNWY3Yt0s4tEU3JgM,4330
232
+ meerschaum/utils/daemon/_names.py,sha256=QInmWQHBA8P8d3lEjVb-ukYvXXK8T87G7xljjOJtL1Q,4330
233
233
  meerschaum/utils/dtypes/__init__.py,sha256=JR9PViJTzhukZhq0QoPIs73HOnXZZr8OmfhAAD4OAUA,6261
234
234
  meerschaum/utils/dtypes/sql.py,sha256=IkEOyB63je-rCLHM6WwFzGbCerYk1zobL1cXkWqmTa4,14638
235
235
  meerschaum/utils/formatting/__init__.py,sha256=NcNPxXRSnKZAIBxAD0ictCjVBzYzFtOwpf2CDMfr4_Y,15354
@@ -242,11 +242,11 @@ meerschaum/utils/packages/_packages.py,sha256=GzbJ0kxW_EQogXmY4vguRkUyad42cshFs7
242
242
  meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
243
243
  meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
244
244
  meerschaum/utils/venv/__init__.py,sha256=bLAWnllKDuE_z6bLk7gLh4mI3Sp1j5hsboTqPKOQq84,24361
245
- meerschaum-2.3.0rc2.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
246
- meerschaum-2.3.0rc2.dist-info/METADATA,sha256=e_ahh83m8FwVMO1VezxSC_WnK-XJoDUNzmIkFgXbFs8,24009
247
- meerschaum-2.3.0rc2.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
248
- meerschaum-2.3.0rc2.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
249
- meerschaum-2.3.0rc2.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
250
- meerschaum-2.3.0rc2.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
251
- meerschaum-2.3.0rc2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
252
- meerschaum-2.3.0rc2.dist-info/RECORD,,
245
+ meerschaum-2.3.0rc3.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
246
+ meerschaum-2.3.0rc3.dist-info/METADATA,sha256=sOTBNkKdN7uRPVbNXQUXxGb2gDU1q30FWsl3KqZtxyE,24009
247
+ meerschaum-2.3.0rc3.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
248
+ meerschaum-2.3.0rc3.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
249
+ meerschaum-2.3.0rc3.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
250
+ meerschaum-2.3.0rc3.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
251
+ meerschaum-2.3.0rc3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
252
+ meerschaum-2.3.0rc3.dist-info/RECORD,,