meerschaum 2.3.5.dev0__py3-none-any.whl → 2.3.6__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.
@@ -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
@@ -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):
@@ -568,6 +569,7 @@ def _start_pipeline(
568
569
  else 1
569
570
  )
570
571
 
572
+ params = params or {}
571
573
  sub_args_line = params.get('sub_args_line', None)
572
574
  patch_args = params.get('patch_args', None)
573
575
 
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.
@@ -67,104 +67,6 @@ def get_actions(
67
67
  return list(actions)
68
68
 
69
69
 
70
- async def notify_client(client, content: str):
71
- """
72
- Send a line of text to a client.
73
- """
74
- try:
75
- await client.send_text(content)
76
- except (RuntimeError, WebSocketDisconnect, Exception):
77
- raise StopMonitoringLogs
78
-
79
- _temp_jobs = {}
80
- @app.websocket(actions_endpoint + '/ws')
81
- async def do_action_websocket(websocket: WebSocket):
82
- """
83
- Execute an action and stream the output to the client.
84
- """
85
- await websocket.accept()
86
-
87
- stop_event = asyncio.Event()
88
- job_name = '.' + generate_password(12)
89
- job = None
90
-
91
- async def monitor_logs(job):
92
- success, msg = job.start()
93
- await job.monitor_logs_async(
94
- partial(notify_client, websocket),
95
- stop_event=stop_event,
96
- stop_on_exit=True,
97
- )
98
-
99
- try:
100
- token = await websocket.receive_text()
101
- user = await manager.get_current_user(token) if not no_auth else None
102
- if user is None and not no_auth:
103
- stop_event.set()
104
- if job is not None:
105
- job.delete()
106
- _ = _temp_jobs.pop(job_name, None)
107
- raise fastapi.HTTPException(
108
- status_code=401,
109
- detail="Invalid credentials.",
110
- )
111
-
112
- auth_success, auth_msg = (
113
- is_user_allowed_to_execute(user)
114
- if not no_auth
115
- else (True, "Success")
116
- )
117
- auth_payload = {
118
- 'is_authenticated': auth_success,
119
- 'timestamp': datetime.now(timezone.utc).isoformat(),
120
- }
121
- await websocket.send_json(auth_payload)
122
- if not auth_success:
123
- stop_event.set()
124
- job.stop()
125
- await websocket.close()
126
-
127
- sysargs = clean_sysargs(await websocket.receive_json())
128
- job = Job(
129
- job_name,
130
- sysargs,
131
- executor_keys='local',
132
- _properties={
133
- 'logs': {
134
- 'write_timestamps': False,
135
- },
136
- },
137
- )
138
- _temp_jobs[job_name] = job
139
- monitor_task = asyncio.create_task(monitor_logs(job))
140
-
141
- ### NOTE: Await incoming text to trigger `WebSocketDisconnect`.
142
- while True:
143
- await websocket.receive_text()
144
-
145
- except fastapi.HTTPException:
146
- await websocket.send_text("Invalid credentials.")
147
- await websocket.close()
148
- except (WebSocketDisconnect, asyncio.CancelledError):
149
- stop_event.set()
150
- job.stop()
151
- except Exception:
152
- stop_event.set()
153
- job.stop()
154
- warn(f"Error in logs websocket:\n{traceback.format_exc()}")
155
- finally:
156
- stop_event.set()
157
- job.stop()
158
- monitor_task.cancel()
159
- if job is not None:
160
- job.delete()
161
- _ = _temp_jobs.pop(job_name, None)
162
- try:
163
- await websocket.close()
164
- except RuntimeError:
165
- pass
166
-
167
-
168
70
  @app.post(actions_endpoint + "/{action}", tags=['Actions'])
169
71
  def do_action_legacy(
170
72
  action: str,
@@ -40,7 +40,15 @@ from meerschaum.config.static import STATIC_CONFIG
40
40
 
41
41
  JOBS_STDIN_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stdin_message']
42
42
  JOBS_STOP_MESSAGE: str = STATIC_CONFIG['api']['jobs']['stop_message']
43
- EXECUTOR_KEYS: str = get_executor_keys_from_context()
43
+ EXECUTOR_KEYS: str = 'local'
44
+
45
+
46
+ def _get_job(name: str):
47
+ systemd_job = Job(name, executor_keys='systemd')
48
+ if systemd_job.exists():
49
+ return systemd_job
50
+
51
+ return Job(name, executor_keys=EXECUTOR_KEYS)
44
52
 
45
53
 
46
54
  @app.get(endpoints['jobs'], tags=['Jobs'])
@@ -52,7 +60,7 @@ def get_jobs(
52
60
  """
53
61
  Return metadata about the current jobs.
54
62
  """
55
- jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=False)
63
+ jobs = _get_jobs(executor_keys=EXECUTOR_KEYS, combine_local_and_systemd=True)
56
64
  return {
57
65
  name: {
58
66
  'sysargs': job.sysargs,
@@ -83,7 +91,7 @@ def get_job(
83
91
  """
84
92
  Return metadata for a single job.
85
93
  """
86
- job = Job(name, executor_keys=EXECUTOR_KEYS)
94
+ job = _get_job(name)
87
95
  if not job.exists():
88
96
  raise fastapi.HTTPException(
89
97
  status_code=404,
@@ -163,7 +171,7 @@ def delete_job(
163
171
  """
164
172
  Delete a job.
165
173
  """
166
- job = Job(name, executor_keys=EXECUTOR_KEYS)
174
+ job = _get_job(name)
167
175
  return job.delete()
168
176
 
169
177
 
@@ -177,7 +185,7 @@ def get_job_exists(
177
185
  """
178
186
  Return whether a job exists.
179
187
  """
180
- job = Job(name, executor_keys=EXECUTOR_KEYS)
188
+ job = _get_job(name)
181
189
  return job.exists()
182
190
 
183
191
 
@@ -192,7 +200,7 @@ def get_logs(
192
200
  Return a job's log text.
193
201
  To stream log text, connect to the WebSocket endpoint `/logs/{name}/ws`.
194
202
  """
195
- job = Job(name, executor_keys=EXECUTOR_KEYS)
203
+ job = _get_job(name)
196
204
  if not job.exists():
197
205
  raise fastapi.HTTPException(
198
206
  status_code=404,
@@ -212,7 +220,7 @@ def start_job(
212
220
  """
213
221
  Start a job if stopped.
214
222
  """
215
- job = Job(name, executor_keys=EXECUTOR_KEYS)
223
+ job = _get_job(name)
216
224
  if not job.exists():
217
225
  raise fastapi.HTTPException(
218
226
  status_code=404,
@@ -231,7 +239,7 @@ def stop_job(
231
239
  """
232
240
  Stop a job if running.
233
241
  """
234
- job = Job(name, executor_keys=EXECUTOR_KEYS)
242
+ job = _get_job(name)
235
243
  if not job.exists():
236
244
  raise fastapi.HTTPException(
237
245
  status_code=404,
@@ -250,7 +258,7 @@ def pause_job(
250
258
  """
251
259
  Pause a job if running.
252
260
  """
253
- job = Job(name, executor_keys=EXECUTOR_KEYS)
261
+ job = _get_job(name)
254
262
  if not job.exists():
255
263
  raise fastapi.HTTPException(
256
264
  status_code=404,
@@ -269,7 +277,7 @@ def get_stop_time(
269
277
  """
270
278
  Get the timestamp when the job was manually stopped.
271
279
  """
272
- job = Job(name, executor_keys=EXECUTOR_KEYS)
280
+ job = _get_job(name)
273
281
  return job.stop_time
274
282
 
275
283
 
@@ -283,12 +291,13 @@ def get_is_blocking_on_stdin(
283
291
  """
284
292
  Return whether a job is blocking on stdin.
285
293
  """
286
- job = Job(name, executor_keys=EXECUTOR_KEYS)
294
+ job = _get_job(name)
287
295
  return job.is_blocking_on_stdin()
288
296
 
289
297
 
290
298
  _job_clients = defaultdict(lambda: [])
291
299
  _job_stop_events = defaultdict(lambda: asyncio.Event())
300
+ _job_queues = defaultdict(lambda: asyncio.Queue())
292
301
  async def notify_clients(name: str, websocket: WebSocket, content: str):
293
302
  """
294
303
  Write the given content to all connected clients.
@@ -315,12 +324,16 @@ async def get_input_from_clients(name: str, websocket: WebSocket) -> str:
315
324
  async def _read_client(client):
316
325
  try:
317
326
  await client.send_text(JOBS_STDIN_MESSAGE)
318
- data = await client.receive_text()
327
+ data = await _job_queues[name].get()
319
328
  except WebSocketDisconnect:
320
329
  if client in _job_clients[name]:
321
330
  _job_clients[name].remove(client)
331
+ if not _job_clients[name]:
332
+ _job_stop_events[name].set()
322
333
  except Exception:
323
334
  pass
335
+ finally:
336
+ _job_queues[name].task_done()
324
337
  return data
325
338
 
326
339
  read_tasks = [
@@ -357,10 +370,12 @@ async def logs_websocket(name: str, websocket: WebSocket):
357
370
  Stream logs from a job over a websocket.
358
371
  """
359
372
  await websocket.accept()
360
- job = Job(name, executor_keys=EXECUTOR_KEYS)
373
+ job = _get_job(name)
361
374
  _job_clients[name].append(websocket)
362
375
 
376
+ _task = None
363
377
  async def monitor_logs():
378
+ nonlocal _task
364
379
  try:
365
380
  callback_function = partial(
366
381
  notify_clients,
@@ -377,16 +392,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
377
392
  name,
378
393
  websocket,
379
394
  )
380
- await job.monitor_logs_async(
395
+ _task = asyncio.create_task(job.monitor_logs_async(
381
396
  callback_function=callback_function,
382
397
  input_callback_function=input_callback_function,
383
398
  stop_callback_function=stop_callback_function,
384
399
  stop_event=_job_stop_events[name],
385
400
  stop_on_exit=True,
386
401
  accept_input=True,
387
- )
402
+ ))
388
403
  except Exception:
389
404
  warn(traceback.format_exc())
405
+ _task.cancel()
390
406
 
391
407
  try:
392
408
  token = await websocket.receive_text()
@@ -397,13 +413,17 @@ async def logs_websocket(name: str, websocket: WebSocket):
397
413
  detail="Invalid credentials.",
398
414
  )
399
415
  monitor_task = asyncio.create_task(monitor_logs())
400
- await monitor_task
416
+ while True:
417
+ text = await websocket.receive_text()
418
+ await _job_queues[name].put(text)
419
+
401
420
  except fastapi.HTTPException:
402
421
  await websocket.send_text("Invalid credentials.")
403
422
  await websocket.close()
404
423
  except WebSocketDisconnect:
405
- _job_stop_events[name].set()
406
- monitor_task.cancel()
424
+ if not _job_clients[name]:
425
+ _job_stop_events[name].set()
426
+ monitor_task.cancel()
407
427
  except asyncio.CancelledError:
408
428
  pass
409
429
  except Exception:
@@ -16,7 +16,6 @@ default_meerschaum_config = {
16
16
  'api_instance': 'MRSM{meerschaum:instance}',
17
17
  'web_instance': 'MRSM{meerschaum:instance}',
18
18
  'default_repository': 'api:mrsm',
19
- # 'default_executor': 'local',
20
19
  'connectors': {
21
20
  'sql': {
22
21
  'default': {},
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.3.5.dev0"
5
+ __version__ = "2.3.6"
@@ -56,7 +56,6 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
56
56
 
57
57
  volumes = {
58
58
  'api_root': '/meerschaum',
59
- 'api_user_local': '/home/meerschaum/.local',
60
59
  'meerschaum_db_data': '/var/lib/postgresql/data',
61
60
  'grafana_storage': '/var/lib/grafana',
62
61
  }
@@ -160,7 +159,6 @@ default_docker_compose_config = {
160
159
  },
161
160
  'volumes' : [
162
161
  'api_root:' + volumes['api_root'],
163
- 'api_user_local:' + volumes['api_user_local'],
164
162
  ],
165
163
  },
166
164
  'grafana': {
@@ -46,6 +46,7 @@ STATIC_CONFIG: Dict[str, Any] = {
46
46
  'stdin_message': 'MRSM_STDIN',
47
47
  'stop_message': 'MRSM_STOP',
48
48
  'metadata_cache_seconds': 5,
49
+ 'temp_prefix': '.api-temp-',
49
50
  },
50
51
  },
51
52
  'sql': {
@@ -73,6 +74,7 @@ STATIC_CONFIG: Dict[str, Any] = {
73
74
  'systemd_log_path': 'MRSM_SYSTEMD_LOG_PATH',
74
75
  'systemd_stdin_path': 'MRSM_SYSTEMD_STDIN_PATH',
75
76
  'systemd_result_path': 'MRSM_SYSTEMD_RESULT_PATH',
77
+ 'systemd_delete_job': 'MRSM_SYSTEMD_DELETE_JOB',
76
78
  'uri_regex': r'MRSM_([a-zA-Z0-9]*)_(\d*[a-zA-Z][a-zA-Z0-9-_+]*$)',
77
79
  'prefix': 'MRSM_',
78
80
  },
@@ -17,6 +17,7 @@ from meerschaum.utils.typing import SuccessTuple, List, Callable, Optional
17
17
  from meerschaum.config.static import STATIC_CONFIG
18
18
 
19
19
  ACTIONS_ENDPOINT: str = STATIC_CONFIG['api']['endpoints']['actions']
20
+ TEMP_PREFIX: str = STATIC_CONFIG['api']['jobs']['temp_prefix']
20
21
 
21
22
 
22
23
  def get_actions(self):
@@ -37,43 +38,28 @@ async def do_action_async(
37
38
  callback_function: Callable[[str], None] = partial(print, end=''),
38
39
  ) -> SuccessTuple:
39
40
  """
40
- Monitor a job's log files and await a callback with the changes.
41
+ Execute an action as a temporary remote job.
41
42
  """
42
- websockets, websockets_exceptions = mrsm.attempt_import('websockets', 'websockets.exceptions')
43
- protocol = 'ws' if self.URI.startswith('http://') else 'wss'
44
- port = self.port if 'port' in self.__dict__ else ''
45
- uri = f"{protocol}://{self.host}:{port}{ACTIONS_ENDPOINT}/ws"
46
- if sysargs and sysargs[0] == 'api' and len(sysargs) > 2:
47
- sysargs = sysargs[2:]
48
-
49
- sysargs_str = json.dumps(sysargs)
50
-
51
- async with websockets.connect(uri) as websocket:
52
- try:
53
- await websocket.send(self.token or 'no-login')
54
- response = await websocket.recv()
55
- init_data = json.loads(response)
56
- if not init_data.get('is_authenticated'):
57
- return False, "Cannot authenticate with actions endpoint."
58
-
59
- await websocket.send(sysargs_str)
60
- except websockets_exceptions.ConnectionClosedOK:
61
- return False, "Connection was closed."
62
-
63
- while True:
64
- try:
65
- line = await websocket.recv()
66
- if asyncio.iscoroutinefunction(callback_function):
67
- await callback_function(line)
68
- else:
69
- callback_function(line)
70
- except KeyboardInterrupt:
71
- await websocket.close()
72
- break
73
- except websockets_exceptions.ConnectionClosedOK:
74
- break
75
-
76
- return True, "Success"
43
+ from meerschaum._internal.arguments import remove_api_executor_keys
44
+ from meerschaum.utils.misc import generate_password
45
+ sysargs = remove_api_executor_keys(sysargs)
46
+
47
+ job_name = TEMP_PREFIX + generate_password(12)
48
+ job = mrsm.Job(job_name, sysargs, executor_keys=str(self))
49
+
50
+ start_success, start_msg = job.start()
51
+ if not start_success:
52
+ return start_success, start_msg
53
+
54
+ await job.monitor_logs_async(
55
+ callback_function=callback_function,
56
+ stop_on_exit=True,
57
+ strip_timestamps=True,
58
+ )
59
+
60
+ success, msg = job.result
61
+ job.delete()
62
+ return success, msg
77
63
 
78
64
 
79
65
  def do_action_legacy(
@@ -277,6 +277,7 @@ async def monitor_logs_async(
277
277
  """
278
278
  Monitor a job's log files and await a callback with the changes.
279
279
  """
280
+ import traceback
280
281
  from meerschaum.jobs import StopMonitoringLogs
281
282
  from meerschaum.utils.formatting._jobs import strip_timestamp_from_line
282
283
 
meerschaum/jobs/_Job.py CHANGED
@@ -60,9 +60,10 @@ class Job:
60
60
  sysargs: Union[List[str], str, None] = None,
61
61
  env: Optional[Dict[str, str]] = None,
62
62
  executor_keys: Optional[str] = None,
63
+ delete_after_completion: bool = False,
63
64
  _properties: Optional[Dict[str, Any]] = None,
64
- _rotating_log = None,
65
- _stdin_file = None,
65
+ _rotating_log=None,
66
+ _stdin_file=None,
66
67
  _status_hook: Optional[Callable[[], str]] = None,
67
68
  _result_hook: Optional[Callable[[], SuccessTuple]] = None,
68
69
  _externally_managed: bool = False,
@@ -85,6 +86,9 @@ class Job:
85
86
  executor_keys: Optional[str], default None
86
87
  If provided, execute the job remotely on an API instance, e.g. 'api:main'.
87
88
 
89
+ delete_after_completion: bool, default False
90
+ If `True`, delete this job when it has finished executing.
91
+
88
92
  _properties: Optional[Dict[str, Any]], default None
89
93
  If provided, use this to patch the daemon's properties.
90
94
  """
@@ -146,6 +150,9 @@ class Job:
146
150
  if env:
147
151
  self._properties_patch.update({'env': env})
148
152
 
153
+ if delete_after_completion:
154
+ self._properties_patch.update({'delete_after_completion': delete_after_completion})
155
+
149
156
  daemon_sysargs = (
150
157
  self._daemon.properties.get('target', {}).get('args', [None])[0]
151
158
  if self._daemon is not None
@@ -245,7 +252,7 @@ class Job:
245
252
  return True, f"{self} is already running."
246
253
 
247
254
  success, msg = self.daemon.run(
248
- keep_daemon_output=True,
255
+ keep_daemon_output=(not self.delete_after_completion),
249
256
  allow_dirty_run=True,
250
257
  )
251
258
  if not success:
@@ -407,7 +414,6 @@ class Job:
407
414
  )
408
415
  return asyncio.run(monitor_logs_coroutine)
409
416
 
410
-
411
417
  async def monitor_logs_async(
412
418
  self,
413
419
  callback_function: Callable[[str], None] = partial(print, end='', flush=True),
@@ -418,8 +424,8 @@ class Job:
418
424
  strip_timestamps: bool = False,
419
425
  accept_input: bool = True,
420
426
  _logs_path: Optional[pathlib.Path] = None,
421
- _log = None,
422
- _stdin_file = None,
427
+ _log=None,
428
+ _stdin_file=None,
423
429
  debug: bool = False,
424
430
  ):
425
431
  """
@@ -466,6 +472,7 @@ class Job:
466
472
  input_callback_function=input_callback_function,
467
473
  stop_callback_function=stop_callback_function,
468
474
  stop_on_exit=stop_on_exit,
475
+ strip_timestamps=strip_timestamps,
469
476
  accept_input=accept_input,
470
477
  debug=debug,
471
478
  )
@@ -557,7 +564,6 @@ class Job:
557
564
  for task in pending:
558
565
  task.cancel()
559
566
  except asyncio.exceptions.CancelledError:
560
- print('cancelled?')
561
567
  pass
562
568
  finally:
563
569
  combined_event.set()
@@ -870,7 +876,9 @@ class Job:
870
876
  """
871
877
  Return the job's Daemon label (joined sysargs).
872
878
  """
873
- return shlex.join(self.sysargs).replace(' + ', '\n+ ')
879
+ from meerschaum._internal.arguments import compress_pipeline_sysargs
880
+ sysargs = compress_pipeline_sysargs(self.sysargs)
881
+ return shlex.join(sysargs).replace(' + ', '\n+ ')
874
882
 
875
883
  @property
876
884
  def _externally_managed_file(self) -> pathlib.Path:
@@ -916,6 +924,16 @@ class Job:
916
924
  self._env = {**default_env, **_env}
917
925
  return self._env
918
926
 
927
+ @property
928
+ def delete_after_completion(self) -> bool:
929
+ """
930
+ Return whether this job is configured to delete itself after completion.
931
+ """
932
+ if '_delete_after_completion' in self.__dict__:
933
+ return self.__dict__.get('_delete_after_completion', False)
934
+
935
+ self._delete_after_completion = self.daemon.properties.get('delete_after_completion', False)
936
+ return self._delete_after_completion
919
937
 
920
938
  def __str__(self) -> str:
921
939
  sysargs = self.sysargs
@@ -42,7 +42,12 @@ class SystemdExecutor(Executor):
42
42
  return [
43
43
  service_name[len('mrsm-'):(-1 * len('.service'))]
44
44
  for service_name in os.listdir(SYSTEMD_USER_RESOURCES_PATH)
45
- if service_name.startswith('mrsm-') and service_name.endswith('.service')
45
+ if (
46
+ service_name.startswith('mrsm-')
47
+ and service_name.endswith('.service')
48
+ ### Check for broken symlinks.
49
+ and (SYSTEMD_USER_RESOURCES_PATH / service_name).exists()
50
+ )
46
51
  ]
47
52
 
48
53
  def get_job_exists(self, name: str, debug: bool = False) -> bool:
@@ -146,6 +151,11 @@ class SystemdExecutor(Executor):
146
151
  STATIC_CONFIG['environment']['systemd_log_path']: service_logs_path.as_posix(),
147
152
  STATIC_CONFIG['environment']['systemd_result_path']: result_path.as_posix(),
148
153
  STATIC_CONFIG['environment']['systemd_stdin_path']: socket_path.as_posix(),
154
+ STATIC_CONFIG['environment']['systemd_delete_job']: (
155
+ '1'
156
+ if job.delete_after_completion
157
+ else '0',
158
+ ),
149
159
  })
150
160
 
151
161
  ### Allow for user-defined environment variables.
@@ -603,7 +613,8 @@ class SystemdExecutor(Executor):
603
613
 
604
614
  check_timeout_interval = get_config('jobs', 'check_timeout_interval_seconds')
605
615
  loop_start = time.perf_counter()
606
- while (time.perf_counter() - loop_start) < get_config('jobs', 'timeout_seconds'):
616
+ timeout_seconds = get_config('jobs', 'timeout_seconds')
617
+ while (time.perf_counter() - loop_start) < timeout_seconds:
607
618
  if self.get_job_status(name, debug=debug) == 'stopped':
608
619
  return True, 'Success'
609
620
 
@@ -630,12 +641,14 @@ class SystemdExecutor(Executor):
630
641
  Delete a job's service.
631
642
  """
632
643
  from meerschaum.config.paths import SYSTEMD_LOGS_RESOURCES_PATH
644
+ job = self.get_hidden_job(name, debug=debug)
633
645
 
634
- _ = self.stop_job(name, debug=debug)
635
- _ = self.run_command(
636
- ['disable', self.get_service_name(name, debug=debug)],
637
- debug=debug,
638
- )
646
+ if not job.delete_after_completion:
647
+ _ = self.stop_job(name, debug=debug)
648
+ _ = self.run_command(
649
+ ['disable', self.get_service_name(name, debug=debug)],
650
+ debug=debug,
651
+ )
639
652
 
640
653
  service_job_path = self.get_service_job_path(name, debug=debug)
641
654
  try:
@@ -666,7 +679,6 @@ class SystemdExecutor(Executor):
666
679
  warn(e)
667
680
  return False, str(e)
668
681
 
669
- job = self.get_hidden_job(name, debug=debug)
670
682
  _ = job.delete()
671
683
 
672
684
  return self.run_command(['daemon-reload'], debug=debug)
@@ -113,7 +113,7 @@ def prompt(
113
113
  ) if not noask else ''
114
114
  )
115
115
  else:
116
- print(question, end='', flush=True)
116
+ print(question, end='\n', flush=True)
117
117
  try:
118
118
  answer = input()
119
119
  except EOFError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.3.5.dev0
3
+ Version: 2.3.6
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares
@@ -1,9 +1,9 @@
1
1
  meerschaum/__init__.py,sha256=6bn5zz7VInDP4fE_FGBMzJYrM6rQhBMJNQqsf1pU7eI,1701
2
2
  meerschaum/__main__.py,sha256=r5UjYxH1WA6dGG9YGBPul5xOdgF3Iwl0X4dWDtXU-30,2646
3
3
  meerschaum/_internal/__init__.py,sha256=ilC7utfKtin7GAvuN34fKyUQYfPyqH0Mm3MJF5iyEf4,169
4
- meerschaum/_internal/entry.py,sha256=N14gqUfKZGgB3HZAm4ceFlYd3Phb4z_qo8LVAhsBOVM,11430
5
- meerschaum/_internal/arguments/__init__.py,sha256=UehxpvVPD6VGzl24xZBzjQzRZWFiKdAeJgXTCIronGQ,462
6
- meerschaum/_internal/arguments/_parse_arguments.py,sha256=lKGQzZEMEZyY3n11lYSRUcPNoDCjp6ndYxTfFbbp8XM,14058
4
+ meerschaum/_internal/entry.py,sha256=XtGl-PPg9dLFlfbh_Wd5DUAdpjxqwQCxQxDrVf18QEE,12026
5
+ meerschaum/_internal/arguments/__init__.py,sha256=_nSKKVLXNsJeSv-buxEZsx8_c0BAbkhRyE4nT6Bv6q0,541
6
+ meerschaum/_internal/arguments/_parse_arguments.py,sha256=dtGjoyuKpQJtABdkQPzK1OM3Hd9JqlL-aQl_046tNa0,15976
7
7
  meerschaum/_internal/arguments/_parser.py,sha256=LfiVDTr1akj5D31qpJAAXKxMcnLQiD3jRpZtvvHWrAo,14917
8
8
  meerschaum/_internal/docs/__init__.py,sha256=ZQYHWo6n0kfLLkyG36YXqTYvv2Pc7it5HZHMylT6cBA,126
9
9
  meerschaum/_internal/docs/index.py,sha256=LOxk1p_ChRkijfRGOB6UnoUEzt-SvtnMO56fTcSIMsU,24390
@@ -22,7 +22,7 @@ meerschaum/_internal/term/TermPageHandler.py,sha256=Rt5S47Pr_3HLJc8xIXpZUczYE_Dw
22
22
  meerschaum/_internal/term/__init__.py,sha256=eXjfRzpnASWomB4xpY2AfzC_CLenkInNnVWSLZEexlg,1638
23
23
  meerschaum/_internal/term/tools.py,sha256=dXVAimKD-Yv2fg2WOTr0YGBY7XDKjQqw-RizcS65YVI,727
24
24
  meerschaum/actions/__init__.py,sha256=MHPs8aRBhbZQXnqd_6tVtisTrNCgPAPgnNcXYbn0zP8,11640
25
- meerschaum/actions/api.py,sha256=8wjREHNRkl8WBgtz1KfWmAVjeFR_J2j7ghXjSyYUW3g,12603
25
+ meerschaum/actions/api.py,sha256=nRHGeMTy0njI9Qec_606GwSS0AnoxiGJodOS8uWpjfU,12539
26
26
  meerschaum/actions/attach.py,sha256=UV19d9W_2WYcrf7BRz7k3mriDoX1V4rA4AKvbLdor0o,3106
27
27
  meerschaum/actions/bootstrap.py,sha256=9D3cBHzgZbZyWy-Y7iQgk9bpTbKEhumFKbIIThZgPXI,14058
28
28
  meerschaum/actions/clear.py,sha256=OoFZE0bK5m8s3GLNZcixuVT0DMj1izXVxGCATcmUGbI,4851
@@ -44,7 +44,7 @@ meerschaum/actions/sh.py,sha256=fLfTJaacKu4sjLTRqEzzYlT2WbbdZBEczsKb6F-qAek,2026
44
44
  meerschaum/actions/show.py,sha256=Ij6v5so9GHUrYVi7AhPfhHGjABBofXTPAljLFa2xuWA,28141
45
45
  meerschaum/actions/sql.py,sha256=wYofwk1vGO96U2ncigGEfMtYMZeprz2FR1PRRZhkAPI,4311
46
46
  meerschaum/actions/stack.py,sha256=7ODAxzmCx8i9AHxvkbr5ZtzUNPpY-iqlSVo4rZHMOw4,5900
47
- meerschaum/actions/start.py,sha256=Tma2VhH3mDsufQvp_1ZWHHspHw1SPwKWYC7_BYv4ME4,19343
47
+ meerschaum/actions/start.py,sha256=eYk4rUo8LijxK-GLbuH4j0dapv4BqzE5xw7-KaVcV2M,19419
48
48
  meerschaum/actions/stop.py,sha256=5fdUw70YN-yuUWrC-NhA88cxr9FZ5NbssbQ8xXO8nFU,4632
49
49
  meerschaum/actions/sync.py,sha256=AkH-1O5bkUC-UElQGr0lRhrX-z18ZY2nBPSy9EsW1Kc,17506
50
50
  meerschaum/actions/tag.py,sha256=SJf5qFW0ccLXjqlTdkK_0MCcrCMdg6xhYrhKdco0hdA,3053
@@ -53,7 +53,7 @@ meerschaum/actions/upgrade.py,sha256=uhFhAPmguGDgxLa1UkVZnPQ-JRcbmOdExE38v-HrLvo
53
53
  meerschaum/actions/verify.py,sha256=tY5slGpHiWiE0v9TDnjbmxSKn86zBnu9WBpixUgKNQU,4885
54
54
  meerschaum/api/__init__.py,sha256=tjxyG8SQR_ymYuj3YUaadlguXBj-edL4kY_CxWYrYHk,7449
55
55
  meerschaum/api/_chain.py,sha256=h8-WXUGXX6AqzdALfsBC5uv0FkAcLdHJXCGzqzuq89k,875
56
- meerschaum/api/_events.py,sha256=oiF2iT79UfdEJh4YnGdKtjXbdnQVergUlXAXm2nePlY,2176
56
+ meerschaum/api/_events.py,sha256=6Bgch1x-R82tmnixLMYunqv8NumlkOn9ergBRU5-vdI,2247
57
57
  meerschaum/api/_oauth2.py,sha256=SDdFbssy-9QoFBkuZZmep7mCX7jsn_zjqCbMXiksEVg,1647
58
58
  meerschaum/api/_websockets.py,sha256=Rso85r4Yj8yUB-Oh8T8bGBvO5vCzZboryOjY-VnxdZQ,1601
59
59
  meerschaum/api/dash/__init__.py,sha256=NkqnUP77c02J6sjWp5PGw48Hz9Dhov7gBO5f2mK2b40,2198
@@ -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=xB_g1AeVGdFo0qBZgAmXPA1GuVkWGNFWihqON_N7bZ4,6574
118
+ meerschaum/api/routes/_actions.py,sha256=9vRu4dsBfIBrfhkVWurvk8n-ClerlHxggiPgQhwP1Oo,3701
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=odL_roGipSwvZ-heGZd7Ub59a7-e0PeDmuqi3YfwQ9g,11227
121
+ meerschaum/api/routes/_jobs.py,sha256=znS8Pt5kvrRpv_ZOwRftbvQ1jpNfQ1-ngMy7kPgI5iM,11567
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
@@ -129,7 +129,7 @@ meerschaum/api/routes/_webterm.py,sha256=7eilgDXckpEc2LyeNmJS5YO6HxlyMkwPnAMWd7e
129
129
  meerschaum/api/tables/__init__.py,sha256=e2aNC0CdlWICTUMx1i9RauF8Pm426J0RZJbsJWv4SWo,482
130
130
  meerschaum/config/__init__.py,sha256=jZgQSjvbOWyvGouqRgIeYBg9Pp343_9CDsCWWpwYDGY,11577
131
131
  meerschaum/config/_dash.py,sha256=BJHl4xMrQB-YHUEU7ldEW8q_nOPoIRSOqLrfGElc6Dw,187
132
- meerschaum/config/_default.py,sha256=SsS80gbxoiYnrmgzpgqmOJIvy0ubT-GI7PpH04UEMv4,5349
132
+ meerschaum/config/_default.py,sha256=LuPp-se3DsUglYRRr88nqU7PCdsMllbRuAJvtaogQu8,5313
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
@@ -140,24 +140,24 @@ meerschaum/config/_preprocess.py,sha256=-AEA8m_--KivZwTQ1sWN6LTn5sio_fUr2XZ51BO6
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=glPnSrQWzzidiljlSe9lTKxgO5s_mx2sORdE64VNYgU,76
143
+ meerschaum/config/_version.py,sha256=5vwwTic_40GgjRL5Yt4nGmmLyCYvMKKKyfPqYmEzffQ,71
144
144
  meerschaum/config/paths.py,sha256=JjibeGN3YAdSNceRwsd42aNmeUrIgM6ndzC8qZAmNI0,621
145
145
  meerschaum/config/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
- meerschaum/config/stack/__init__.py,sha256=Yt7GNzC_hz7iUDZ4gVho_lugJO2DnXgnMtsMG_ReoRg,9114
146
+ meerschaum/config/stack/__init__.py,sha256=WNcVSaMbIcSYi75hn2kRQeJ0j1ZGjjBvsZVTQwD50sA,9002
147
147
  meerschaum/config/stack/grafana/__init__.py,sha256=LNXQw2FvHKrD68RDhqDmi2wJjAHaKw9IWx8rNuyWEPo,2010
148
148
  meerschaum/config/stack/mosquitto/__init__.py,sha256=-OwOjq8KiBoSH_pmgCAAF3Dp3CRD4KgAEdimZSadROs,186
149
149
  meerschaum/config/stack/mosquitto/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
150
  meerschaum/config/stack/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
- meerschaum/config/static/__init__.py,sha256=bSugcNkYKnApqUxT0LcpqE_3-2tyJfTu2fDmzbB4fVM,5127
151
+ meerschaum/config/static/__init__.py,sha256=SLAcxX69MFiZBbonn3YiBxOHtdkPoz5Sbtz0Tt9CjKY,5225
152
152
  meerschaum/connectors/Connector.py,sha256=utNV3Fy5DhUVbQE-vtm7enH5rH2gxQERmgmP7PUzH30,6682
153
153
  meerschaum/connectors/__init__.py,sha256=fLoatCZV2hsDffYEg2aBzqPLzM_2o1_Z3X2iZvLwoqA,12503
154
154
  meerschaum/connectors/parse.py,sha256=sDeI2OIS9Inwhbn9jkFAXxOPnmmAHqsuHiiHfWjVnSA,4307
155
155
  meerschaum/connectors/poll.py,sha256=gIY9TvFBqMvMNQvR0O2No7koLLz2PjfExBr_Dsosgpg,7363
156
156
  meerschaum/connectors/api/APIConnector.py,sha256=MPYeImCBAeSpueYis5CxkfT7kkV82MnGKDLoNE8uOVw,5017
157
157
  meerschaum/connectors/api/__init__.py,sha256=JwKrGtuE5aOd2VnsRwudFBYyBf5IxczOwPVdNvCUgSQ,205
158
- meerschaum/connectors/api/_actions.py,sha256=Vg_ugl02MUjYtfyx0o9jt5ZoluNC0J1R-UEIRlcIpMI,4701
158
+ meerschaum/connectors/api/_actions.py,sha256=gd3F8i5BvN8XRvMcPvPVR8sc1DeLzgoBHdDhc8PtACE,3966
159
159
  meerschaum/connectors/api/_fetch.py,sha256=Khq9AFr1nk8Dsmcedb77aWhAuHw0JGgVeahDG95Q5MQ,2072
160
- meerschaum/connectors/api/_jobs.py,sha256=bPQpnacqiJwXxrMEDgsv63Dn6KRprTvDqdeEmpZhkFw,11249
160
+ meerschaum/connectors/api/_jobs.py,sha256=N5lpHFGG10jlVgaJeWAOTuLBQw3AdgjXsEPpp1YwZQE,11270
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=Nnc-IShiTkCia548dePymKosQCcl2InFCyUX3Q-Xx6Q,20604
@@ -201,9 +201,9 @@ 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=qM62BhFTM4tyJ7p90KOM0y3qyeRY9k3ZV_aTDJMHnO8,1682
204
- meerschaum/jobs/_Job.py,sha256=f8eeH6OpBF4YEKTRB2Jx-SUWGiAxe0cUtdwDGhNEZLo,31240
204
+ meerschaum/jobs/_Job.py,sha256=XnvS-qN12907t23OG1suBJ5k_TDuXkS1diNE_1xNAF0,32152
205
205
  meerschaum/jobs/__init__.py,sha256=ieruFbPxozkgyYMEO5wWQ4OXqfD5Pw9VtFWGub6DQxs,11970
206
- meerschaum/jobs/systemd.py,sha256=tQl5VFqGa3fiaX412fsjAFQAAW07o54ADmzAy3W-QWA,24165
206
+ meerschaum/jobs/systemd.py,sha256=Rq-tsDPslG17ZhpKMrVJ5r8Z0IPr6DEc9APObfIoXCg,24614
207
207
  meerschaum/plugins/_Plugin.py,sha256=p6j39tm-xrZENBq-eGtixBuXxLLddtEKWRCRFNqpRu0,34086
208
208
  meerschaum/plugins/__init__.py,sha256=trMQ53qgP7ikJhhV_uXzqJw6X1NDz2rPOGXFk40bb1Y,26190
209
209
  meerschaum/plugins/bootstrap.py,sha256=qg9MQ1YAU8ShwGqWDl38WjiXLIxDPl95pSIGDLN9rOw,11423
@@ -216,7 +216,7 @@ meerschaum/utils/misc.py,sha256=2f0wLQ0ymdOh5-5iP-jlyUxSUStvQAn5ot5_0GZTg-8,4638
216
216
  meerschaum/utils/networking.py,sha256=Sr_eYUGW8_UV9-k9LqRFf7xLtbUcsDucODyLCRsFRUc,1006
217
217
  meerschaum/utils/pool.py,sha256=vkE42af4fjrTEJTxf6Ek3xGucm1MtEkpsSEiaVzNKHs,2655
218
218
  meerschaum/utils/process.py,sha256=o7UtTQX87YGkg2dItPhlvN7gNQPkElXTYSzKf5Ro8Uc,7474
219
- meerschaum/utils/prompt.py,sha256=JCM36APVEJ6hN-ncQOD2qXtbyVlIey5OhhyWSIdRs9k,19008
219
+ meerschaum/utils/prompt.py,sha256=0asF_ndumQIN7p5kEOzK-ldsdE4m8FFapcT3-4wgPi8,19010
220
220
  meerschaum/utils/schedule.py,sha256=WSJ0eDGvCVxkQwbUc_7vNm0uuKf9WJcY2NCZZdbnZrk,10837
221
221
  meerschaum/utils/sql.py,sha256=4sCNEpgUd6uFz6ySs4nnUMVaOT0YAvPM1ZlQYJTSF-0,46656
222
222
  meerschaum/utils/threading.py,sha256=3N8JXPAnwqJiSjuQcbbJg3Rv9-CCUMJpeQRfKFR7MaA,2489
@@ -241,11 +241,11 @@ meerschaum/utils/packages/_packages.py,sha256=GzbJ0kxW_EQogXmY4vguRkUyad42cshFs7
241
241
  meerschaum/utils/packages/lazy_loader.py,sha256=VHnph3VozH29R4JnSSBfwtA5WKZYZQFT_GeQSShCnuc,2540
242
242
  meerschaum/utils/venv/_Venv.py,sha256=sBnlmxHdAh2bx8btfVoD79-H9-cYsv5lP02IIXkyECs,3553
243
243
  meerschaum/utils/venv/__init__.py,sha256=bLAWnllKDuE_z6bLk7gLh4mI3Sp1j5hsboTqPKOQq84,24361
244
- meerschaum-2.3.5.dev0.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
245
- meerschaum-2.3.5.dev0.dist-info/METADATA,sha256=aW9MN6MOEmWcmHoVp9T2UexyQrQ2-3lf2Lcnyo978M8,24011
246
- meerschaum-2.3.5.dev0.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
247
- meerschaum-2.3.5.dev0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
248
- meerschaum-2.3.5.dev0.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
249
- meerschaum-2.3.5.dev0.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
250
- meerschaum-2.3.5.dev0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
251
- meerschaum-2.3.5.dev0.dist-info/RECORD,,
244
+ meerschaum-2.3.6.dist-info/LICENSE,sha256=jG2zQEdRNt88EgHUWPpXVWmOrOduUQRx7MnYV9YIPaw,11359
245
+ meerschaum-2.3.6.dist-info/METADATA,sha256=tyAwvxygzzfTbfg_HW3zlWQ_08IDqb0HDSswLfVzbiM,24006
246
+ meerschaum-2.3.6.dist-info/NOTICE,sha256=OTA9Fcthjf5BRvWDDIcBC_xfLpeDV-RPZh3M-HQBRtQ,114
247
+ meerschaum-2.3.6.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
248
+ meerschaum-2.3.6.dist-info/entry_points.txt,sha256=5YBVzibw-0rNA_1VjB16z5GABsOGf-CDhW4yqH8C7Gc,88
249
+ meerschaum-2.3.6.dist-info/top_level.txt,sha256=bNoSiDj0El6buocix-FRoAtJOeq1qOF5rRm2u9i7Q6A,11
250
+ meerschaum-2.3.6.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
251
+ meerschaum-2.3.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5