meerschaum 2.2.6__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. meerschaum/__init__.py +6 -1
  2. meerschaum/__main__.py +9 -9
  3. meerschaum/_internal/arguments/__init__.py +1 -1
  4. meerschaum/_internal/arguments/_parse_arguments.py +72 -6
  5. meerschaum/_internal/arguments/_parser.py +45 -15
  6. meerschaum/_internal/docs/index.py +265 -8
  7. meerschaum/_internal/entry.py +167 -37
  8. meerschaum/_internal/shell/Shell.py +290 -99
  9. meerschaum/_internal/shell/updates.py +175 -0
  10. meerschaum/actions/__init__.py +29 -17
  11. meerschaum/actions/api.py +12 -12
  12. meerschaum/actions/attach.py +113 -0
  13. meerschaum/actions/copy.py +68 -41
  14. meerschaum/actions/delete.py +112 -50
  15. meerschaum/actions/edit.py +3 -3
  16. meerschaum/actions/install.py +40 -32
  17. meerschaum/actions/pause.py +44 -27
  18. meerschaum/actions/register.py +19 -5
  19. meerschaum/actions/restart.py +107 -0
  20. meerschaum/actions/show.py +130 -159
  21. meerschaum/actions/start.py +161 -100
  22. meerschaum/actions/stop.py +78 -42
  23. meerschaum/actions/sync.py +3 -3
  24. meerschaum/actions/upgrade.py +28 -36
  25. meerschaum/api/_events.py +25 -1
  26. meerschaum/api/_oauth2.py +2 -0
  27. meerschaum/api/_websockets.py +2 -2
  28. meerschaum/api/dash/callbacks/jobs.py +36 -44
  29. meerschaum/api/dash/jobs.py +89 -78
  30. meerschaum/api/routes/__init__.py +1 -0
  31. meerschaum/api/routes/_actions.py +148 -17
  32. meerschaum/api/routes/_jobs.py +407 -0
  33. meerschaum/api/routes/_pipes.py +25 -25
  34. meerschaum/config/_default.py +1 -0
  35. meerschaum/config/_formatting.py +1 -0
  36. meerschaum/config/_jobs.py +1 -1
  37. meerschaum/config/_paths.py +11 -0
  38. meerschaum/config/_shell.py +84 -67
  39. meerschaum/config/_version.py +1 -1
  40. meerschaum/config/static/__init__.py +18 -0
  41. meerschaum/connectors/Connector.py +13 -7
  42. meerschaum/connectors/__init__.py +28 -15
  43. meerschaum/connectors/api/APIConnector.py +27 -1
  44. meerschaum/connectors/api/_actions.py +71 -6
  45. meerschaum/connectors/api/_jobs.py +368 -0
  46. meerschaum/connectors/api/_misc.py +1 -1
  47. meerschaum/connectors/api/_pipes.py +85 -84
  48. meerschaum/connectors/api/_request.py +13 -9
  49. meerschaum/connectors/parse.py +27 -15
  50. meerschaum/core/Pipe/_bootstrap.py +16 -8
  51. meerschaum/core/Pipe/_sync.py +3 -0
  52. meerschaum/jobs/_Executor.py +69 -0
  53. meerschaum/jobs/_Job.py +899 -0
  54. meerschaum/jobs/__init__.py +396 -0
  55. meerschaum/jobs/systemd.py +694 -0
  56. meerschaum/plugins/__init__.py +97 -12
  57. meerschaum/utils/daemon/Daemon.py +352 -147
  58. meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
  59. meerschaum/utils/daemon/RotatingFile.py +22 -8
  60. meerschaum/utils/daemon/StdinFile.py +121 -0
  61. meerschaum/utils/daemon/__init__.py +42 -27
  62. meerschaum/utils/daemon/_names.py +15 -13
  63. meerschaum/utils/formatting/__init__.py +83 -37
  64. meerschaum/utils/formatting/_jobs.py +146 -55
  65. meerschaum/utils/formatting/_shell.py +6 -0
  66. meerschaum/utils/misc.py +41 -22
  67. meerschaum/utils/packages/__init__.py +21 -15
  68. meerschaum/utils/packages/_packages.py +9 -6
  69. meerschaum/utils/process.py +9 -9
  70. meerschaum/utils/prompt.py +20 -7
  71. meerschaum/utils/schedule.py +21 -15
  72. meerschaum/utils/venv/__init__.py +2 -2
  73. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
  74. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/RECORD +80 -70
  75. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
  76. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
  77. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
  78. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
  79. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
  80. {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,396 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Higher-level utilities for managing `meerschaum.utils.daemon.Daemon`.
7
+ """
8
+
9
+ import pathlib
10
+
11
+ import meerschaum as mrsm
12
+ from meerschaum.utils.typing import Dict, Optional, List, Callable, Any, SuccessTuple
13
+
14
+ from meerschaum.jobs._Job import Job, StopMonitoringLogs
15
+ from meerschaum.jobs._Executor import Executor
16
+
17
+ __all__ = (
18
+ 'Job',
19
+ 'systemd',
20
+ 'get_jobs',
21
+ 'get_filtered_jobs',
22
+ 'get_restart_jobs',
23
+ 'get_running_jobs',
24
+ 'get_stopped_jobs',
25
+ 'get_paused_jobs',
26
+ 'get_restart_jobs',
27
+ 'make_executor',
28
+ 'Executor',
29
+ 'check_restart_jobs',
30
+ 'start_check_jobs_thread',
31
+ 'stop_check_jobs_thread',
32
+ )
33
+
34
+ executor_types: List[str] = ['api', 'local']
35
+
36
+
37
+ def get_jobs(
38
+ executor_keys: Optional[str] = None,
39
+ include_hidden: bool = False,
40
+ combine_local_and_systemd: bool = True,
41
+ debug: bool = False,
42
+ ) -> Dict[str, Job]:
43
+ """
44
+ Return a dictionary of the existing jobs.
45
+
46
+ Parameters
47
+ ----------
48
+ executor_keys: Optional[str], default None
49
+ If provided, return remote jobs on the given API instance.
50
+ Otherwise return local jobs.
51
+
52
+ include_hidden: bool, default False
53
+ If `True`, include jobs with the `hidden` attribute.
54
+
55
+ Returns
56
+ -------
57
+ A dictionary mapping job names to jobs.
58
+ """
59
+ from meerschaum.connectors.parse import parse_executor_keys
60
+ include_local_and_system = (
61
+ combine_local_and_systemd
62
+ and str(executor_keys).split(':', maxsplit=1)[0] in ('None', 'local', 'systemd')
63
+ and get_executor_keys_from_context() == 'systemd'
64
+ )
65
+
66
+ def _get_local_jobs():
67
+ from meerschaum.utils.daemon import get_daemons
68
+ daemons = get_daemons()
69
+ jobs = {
70
+ daemon.daemon_id: Job(name=daemon.daemon_id, executor_keys='local')
71
+ for daemon in daemons
72
+ }
73
+ return {
74
+ name: job
75
+ for name, job in jobs.items()
76
+ if (include_hidden or not job.hidden) and not job._is_externally_managed
77
+
78
+ }
79
+
80
+ def _get_systemd_jobs():
81
+ conn = mrsm.get_connector('systemd')
82
+ jobs = conn.get_jobs(debug=debug)
83
+ return {
84
+ name: job
85
+ for name, job in jobs.items()
86
+ if include_hidden or not job.hidden
87
+ }
88
+
89
+ if include_local_and_system:
90
+ local_jobs = _get_local_jobs()
91
+ systemd_jobs = _get_systemd_jobs()
92
+ shared_jobs = set(local_jobs) & set(systemd_jobs)
93
+ if shared_jobs:
94
+ from meerschaum.utils.misc import items_str
95
+ from meerschaum.utils.warnings import warn
96
+ warn(
97
+ "Job"
98
+ + ('s' if len(shared_jobs) != 1 else '')
99
+ + f" {items_str(list(shared_jobs))} "
100
+ + "exist"
101
+ + ('s' if len(shared_jobs) == 1 else '')
102
+ + " in both `local` and `systemd`.",
103
+ stack=False,
104
+ )
105
+ return {**local_jobs, **systemd_jobs}
106
+
107
+ if executor_keys == 'local':
108
+ return _get_local_jobs()
109
+
110
+ if executor_keys == 'systemd':
111
+ return _get_systemd_jobs()
112
+
113
+ try:
114
+ _ = parse_executor_keys(executor_keys, construct=False)
115
+ conn = parse_executor_keys(executor_keys)
116
+ jobs = conn.get_jobs(debug=debug)
117
+ return {
118
+ name: job
119
+ for name, job in jobs.items()
120
+ if include_hidden or not job.hidden
121
+ }
122
+ except Exception:
123
+ return {}
124
+
125
+
126
+ def get_filtered_jobs(
127
+ executor_keys: Optional[str] = None,
128
+ filter_list: Optional[List[str]] = None,
129
+ include_hidden: bool = False,
130
+ warn: bool = False,
131
+ debug: bool = False,
132
+ ) -> Dict[str, Job]:
133
+ """
134
+ Return a list of jobs filtered by the user.
135
+ """
136
+ from meerschaum.utils.warnings import warn as _warn
137
+ jobs = get_jobs(executor_keys, include_hidden=True, debug=debug)
138
+ if not filter_list:
139
+ return {
140
+ name: job
141
+ for name, job in jobs.items()
142
+ if include_hidden or not job.hidden
143
+ }
144
+
145
+ jobs_to_return = {}
146
+ for name in filter_list:
147
+ job = jobs.get(name, None)
148
+ if job is None:
149
+ if warn:
150
+ _warn(
151
+ f"Job '{name}' does not exist.",
152
+ stack=False,
153
+ )
154
+ continue
155
+ jobs_to_return[name] = job
156
+
157
+ return jobs_to_return
158
+
159
+
160
+ def get_restart_jobs(
161
+ executor_keys: Optional[str] = None,
162
+ jobs: Optional[Dict[str, Job]] = None,
163
+ include_hidden: bool = False,
164
+ debug: bool = False,
165
+ ) -> Dict[str, Job]:
166
+ """
167
+ Return jobs which were created with `--restart` or `--loop`.
168
+ """
169
+ if jobs is None:
170
+ jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
171
+
172
+ return {
173
+ name: job
174
+ for name, job in jobs.items()
175
+ if job.restart
176
+ }
177
+
178
+
179
+ def get_running_jobs(
180
+ executor_keys: Optional[str] = None,
181
+ jobs: Optional[Dict[str, Job]] = None,
182
+ include_hidden: bool = False,
183
+ debug: bool = False,
184
+ ) -> Dict[str, Job]:
185
+ """
186
+ Return a dictionary of running jobs.
187
+ """
188
+ if jobs is None:
189
+ jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
190
+
191
+ return {
192
+ name: job
193
+ for name, job in jobs.items()
194
+ if job.status == 'running'
195
+ }
196
+
197
+
198
+ def get_paused_jobs(
199
+ executor_keys: Optional[str] = None,
200
+ jobs: Optional[Dict[str, Job]] = None,
201
+ include_hidden: bool = False,
202
+ debug: bool = False,
203
+ ) -> Dict[str, Job]:
204
+ """
205
+ Return a dictionary of paused jobs.
206
+ """
207
+ if jobs is None:
208
+ jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
209
+
210
+ return {
211
+ name: job
212
+ for name, job in jobs.items()
213
+ if job.status == 'paused'
214
+ }
215
+
216
+
217
+ def get_stopped_jobs(
218
+ executor_keys: Optional[str] = None,
219
+ jobs: Optional[Dict[str, Job]] = None,
220
+ include_hidden: bool = False,
221
+ debug: bool = False,
222
+ ) -> Dict[str, Job]:
223
+ """
224
+ Return a dictionary of stopped jobs.
225
+ """
226
+ if jobs is None:
227
+ jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
228
+
229
+ return {
230
+ name: job
231
+ for name, job in jobs.items()
232
+ if job.status == 'stopped'
233
+ }
234
+
235
+
236
+ def make_executor(cls):
237
+ """
238
+ Register a class as an `Executor`.
239
+ """
240
+ import re
241
+ from meerschaum.connectors import make_connector
242
+ suffix_regex = r'executor$'
243
+ typ = re.sub(suffix_regex, '', cls.__name__.lower())
244
+ if typ not in executor_types:
245
+ executor_types.append(typ)
246
+ return make_connector(cls, _is_executor=True)
247
+
248
+
249
+ def check_restart_jobs(
250
+ executor_keys: Optional[str] = 'local',
251
+ jobs: Optional[Dict[str, Job]] = None,
252
+ include_hidden: bool = True,
253
+ silent: bool = False,
254
+ debug: bool = False,
255
+ ) -> SuccessTuple:
256
+ """
257
+ Restart any stopped jobs which were created with `--restart`.
258
+
259
+ Parameters
260
+ ----------
261
+ executor_keys: Optional[str], default None
262
+ If provided, check jobs on the given remote API instance.
263
+ Otherwise check local jobs.
264
+
265
+ include_hidden: bool, default True
266
+ If `True`, include hidden jobs in the check.
267
+
268
+ silent: bool, default False
269
+ If `True`, do not print the restart success message.
270
+ """
271
+ from meerschaum.utils.misc import items_str
272
+
273
+ if jobs is None:
274
+ jobs = get_jobs(
275
+ executor_keys,
276
+ include_hidden=include_hidden,
277
+ debug=debug,
278
+ )
279
+
280
+ if not jobs:
281
+ return True, "No jobs to restart."
282
+
283
+ results = {}
284
+ for name, job in jobs.items():
285
+ check_success, check_msg = job.check_restart()
286
+ results[job.name] = (check_success, check_msg)
287
+ if not silent:
288
+ mrsm.pprint((check_success, check_msg))
289
+
290
+ success_names = [name for name, (check_success, check_msg) in results.items() if check_success]
291
+ fail_names = [name for name, (check_success, check_msg) in results.items() if not check_success]
292
+ success = len(success_names) == len(jobs)
293
+ msg = (
294
+ (
295
+ "Successfully restarted job"
296
+ + ('s' if len(success_names) != 1 else '')
297
+ + ' ' + items_str(success_names) + '.'
298
+ )
299
+ if success
300
+ else (
301
+ "Failed to restart job"
302
+ + ('s' if len(success_names) != 1 else '')
303
+ + ' ' + items_str(fail_names) + '.'
304
+ )
305
+ )
306
+ return success, msg
307
+
308
+
309
+ def _check_restart_jobs_against_lock(*args, **kwargs):
310
+ from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
311
+ fasteners = mrsm.attempt_import('fasteners')
312
+ lock = fasteners.InterProcessLock(CHECK_JOBS_LOCK_PATH)
313
+ with lock:
314
+ check_restart_jobs(*args, **kwargs)
315
+
316
+
317
+ _check_loop_stop_thread = None
318
+ def start_check_jobs_thread():
319
+ """
320
+ Start a thread to regularly monitor jobs.
321
+ """
322
+ import atexit
323
+ from functools import partial
324
+ from meerschaum.utils.threading import RepeatTimer
325
+ from meerschaum.config.static import STATIC_CONFIG
326
+
327
+ global _check_loop_stop_thread
328
+ sleep_seconds = STATIC_CONFIG['jobs']['check_restart_seconds']
329
+
330
+ _check_loop_stop_thread = RepeatTimer(
331
+ sleep_seconds,
332
+ partial(
333
+ _check_restart_jobs_against_lock,
334
+ silent=True,
335
+ )
336
+ )
337
+ _check_loop_stop_thread.daemon = True
338
+ atexit.register(stop_check_jobs_thread)
339
+
340
+ _check_loop_stop_thread.start()
341
+ return _check_loop_stop_thread
342
+
343
+
344
+ def stop_check_jobs_thread():
345
+ """
346
+ Stop the job monitoring thread.
347
+ """
348
+ from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
349
+ from meerschaum.utils.warnings import warn
350
+ if _check_loop_stop_thread is None:
351
+ return
352
+
353
+ _check_loop_stop_thread.cancel()
354
+
355
+ try:
356
+ if CHECK_JOBS_LOCK_PATH.exists():
357
+ CHECK_JOBS_LOCK_PATH.unlink()
358
+ except Exception as e:
359
+ warn(f"Failed to remove check jobs lock file:\n{e}")
360
+
361
+
362
+ _context_keys = None
363
+ def get_executor_keys_from_context() -> str:
364
+ """
365
+ If we are running on the host with the default root, default to `'systemd'`.
366
+ Otherwise return `'local'`.
367
+ """
368
+ global _context_keys
369
+
370
+ if _context_keys is not None:
371
+ return _context_keys
372
+
373
+ from meerschaum.config.paths import ROOT_DIR_PATH, DEFAULT_ROOT_DIR_PATH
374
+ from meerschaum.utils.misc import is_systemd_available
375
+
376
+ _context_keys = (
377
+ 'systemd'
378
+ if is_systemd_available() and ROOT_DIR_PATH == DEFAULT_ROOT_DIR_PATH
379
+ else 'local'
380
+ )
381
+ return _context_keys
382
+
383
+
384
+ def _install_healthcheck_job() -> SuccessTuple:
385
+ """
386
+ Install the systemd job which checks local jobs.
387
+ """
388
+ if get_executor_keys_from_context() != 'systemd':
389
+ return False, "Not running systemd."
390
+
391
+ job = Job(
392
+ '.local-healthcheck',
393
+ ['restart', 'jobs', '-e', 'local', '--loop'],
394
+ executor_keys='systemd',
395
+ )
396
+ return job.start()