meerschaum 2.9.4__py3-none-any.whl → 3.0.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.
- meerschaum/__init__.py +5 -2
- meerschaum/_internal/__init__.py +1 -0
- meerschaum/_internal/arguments/_parse_arguments.py +4 -4
- meerschaum/_internal/arguments/_parser.py +33 -4
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +435 -0
- meerschaum/_internal/docs/index.py +48 -2
- meerschaum/_internal/entry.py +50 -14
- meerschaum/_internal/shell/Shell.py +121 -29
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +359 -0
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +53 -13
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/bootstrap.py +8 -8
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +171 -25
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +143 -6
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +184 -31
- meerschaum/actions/start.py +166 -17
- meerschaum/actions/stop.py +38 -2
- meerschaum/actions/sync.py +7 -2
- meerschaum/actions/tag.py +9 -8
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +45 -15
- meerschaum/api/_events.py +46 -4
- meerschaum/api/_oauth2.py +162 -9
- meerschaum/api/_tokens.py +102 -0
- meerschaum/api/dash/__init__.py +0 -3
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/custom.py +4 -3
- meerschaum/api/dash/callbacks/dashboard.py +228 -117
- meerschaum/api/dash/callbacks/jobs.py +14 -7
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +194 -14
- meerschaum/api/dash/callbacks/plugins.py +0 -1
- meerschaum/api/dash/callbacks/register.py +10 -3
- meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
- meerschaum/api/dash/callbacks/tokens.py +389 -0
- meerschaum/api/dash/components.py +36 -15
- meerschaum/api/dash/jobs.py +1 -1
- meerschaum/api/dash/keys.py +35 -93
- meerschaum/api/dash/pages/__init__.py +2 -1
- meerschaum/api/dash/pages/dashboard.py +1 -20
- meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +16 -5
- meerschaum/api/dash/pages/settings/password_reset.py +1 -1
- meerschaum/api/dash/pages/tokens.py +53 -0
- meerschaum/api/dash/pipes.py +438 -88
- meerschaum/api/dash/sessions.py +12 -0
- meerschaum/api/dash/tokens.py +603 -0
- meerschaum/api/dash/websockets.py +1 -1
- meerschaum/api/dash/webterm.py +18 -6
- meerschaum/api/models/__init__.py +23 -3
- meerschaum/api/models/_actions.py +22 -0
- meerschaum/api/models/_pipes.py +91 -7
- meerschaum/api/models/_tokens.py +81 -0
- meerschaum/api/resources/static/css/dash.css +16 -0
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +13 -0
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +3 -4
- meerschaum/api/routes/_connectors.py +3 -7
- meerschaum/api/routes/_jobs.py +26 -35
- meerschaum/api/routes/_login.py +120 -15
- meerschaum/api/routes/_misc.py +5 -10
- meerschaum/api/routes/_pipes.py +178 -143
- meerschaum/api/routes/_plugins.py +38 -28
- meerschaum/api/routes/_tokens.py +236 -0
- meerschaum/api/routes/_users.py +47 -35
- meerschaum/api/routes/_version.py +3 -3
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +100 -30
- meerschaum/config/_default.py +132 -64
- meerschaum/config/_edit.py +38 -32
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +10 -8
- meerschaum/config/_paths.py +133 -13
- meerschaum/config/_read_config.py +87 -36
- meerschaum/config/_sync.py +6 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +37 -15
- meerschaum/config/static.py +18 -0
- meerschaum/connectors/_Connector.py +11 -6
- meerschaum/connectors/__init__.py +41 -22
- meerschaum/connectors/api/_APIConnector.py +34 -6
- meerschaum/connectors/api/_actions.py +2 -2
- meerschaum/connectors/api/_jobs.py +12 -1
- meerschaum/connectors/api/_login.py +33 -7
- meerschaum/connectors/api/_misc.py +2 -2
- meerschaum/connectors/api/_pipes.py +23 -32
- meerschaum/connectors/api/_plugins.py +2 -2
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/api/_tokens.py +146 -0
- meerschaum/connectors/api/_users.py +70 -58
- meerschaum/connectors/instance/_InstanceConnector.py +83 -0
- meerschaum/connectors/instance/__init__.py +10 -0
- meerschaum/connectors/instance/_pipes.py +442 -0
- meerschaum/connectors/instance/_plugins.py +159 -0
- meerschaum/connectors/instance/_tokens.py +317 -0
- meerschaum/connectors/instance/_users.py +188 -0
- meerschaum/connectors/parse.py +5 -2
- meerschaum/connectors/sql/_SQLConnector.py +22 -5
- meerschaum/connectors/sql/_cli.py +12 -11
- meerschaum/connectors/sql/_create_engine.py +12 -168
- meerschaum/connectors/sql/_fetch.py +2 -18
- meerschaum/connectors/sql/_pipes.py +295 -278
- meerschaum/connectors/sql/_plugins.py +29 -0
- meerschaum/connectors/sql/_sql.py +47 -22
- meerschaum/connectors/sql/_users.py +36 -2
- meerschaum/connectors/sql/tables/__init__.py +254 -122
- meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
- meerschaum/connectors/valkey/_pipes.py +60 -31
- meerschaum/connectors/valkey/_plugins.py +2 -26
- meerschaum/core/Pipe/__init__.py +115 -85
- meerschaum/core/Pipe/_attributes.py +425 -124
- meerschaum/core/Pipe/_bootstrap.py +54 -24
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +96 -68
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +49 -19
- meerschaum/core/Pipe/_edit.py +14 -4
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_show.py +5 -5
- meerschaum/core/Pipe/_sync.py +123 -204
- meerschaum/core/Pipe/_verify.py +4 -4
- meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
- meerschaum/core/Plugin/__init__.py +1 -1
- meerschaum/core/Token/_Token.py +220 -0
- meerschaum/core/Token/__init__.py +12 -0
- meerschaum/core/User/_User.py +35 -10
- meerschaum/core/User/__init__.py +9 -1
- meerschaum/core/__init__.py +1 -0
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +149 -38
- meerschaum/jobs/__init__.py +3 -2
- meerschaum/jobs/systemd.py +8 -3
- meerschaum/models/__init__.py +35 -0
- meerschaum/models/pipes.py +247 -0
- meerschaum/models/tokens.py +38 -0
- meerschaum/models/users.py +26 -0
- meerschaum/plugins/__init__.py +301 -88
- meerschaum/plugins/bootstrap.py +510 -4
- meerschaum/utils/_get_pipes.py +97 -30
- meerschaum/utils/daemon/Daemon.py +199 -43
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +47 -6
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/dataframe.py +480 -82
- meerschaum/utils/debug.py +49 -19
- meerschaum/utils/dtypes/__init__.py +478 -37
- meerschaum/utils/dtypes/sql.py +369 -29
- meerschaum/utils/formatting/__init__.py +5 -2
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +52 -50
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +44 -18
- meerschaum/utils/misc.py +268 -186
- meerschaum/utils/packages/__init__.py +25 -40
- meerschaum/utils/packages/_packages.py +42 -34
- meerschaum/utils/pipes.py +213 -0
- meerschaum/utils/process.py +2 -2
- meerschaum/utils/prompt.py +175 -144
- meerschaum/utils/schedule.py +2 -1
- meerschaum/utils/sql.py +135 -49
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/typing.py +1 -4
- meerschaum/utils/venv/_Venv.py +2 -2
- meerschaum/utils/venv/__init__.py +7 -7
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
- meerschaum-3.0.0.dist-info/RECORD +289 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
- meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
- meerschaum/api/models/_interfaces.py +0 -15
- meerschaum/api/models/_locations.py +0 -15
- meerschaum/api/models/_metrics.py +0 -15
- meerschaum/config/_environment.py +0 -145
- meerschaum/config/static/__init__.py +0 -186
- meerschaum-2.9.4.dist-info/RECORD +0 -263
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.9.4.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -14,12 +14,13 @@ import traceback
|
|
14
14
|
import sys
|
15
15
|
import atexit
|
16
16
|
from datetime import datetime, timezone
|
17
|
-
from typing import List, Optional, Tuple
|
17
|
+
from typing import List, Optional, Tuple, Callable
|
18
18
|
from meerschaum.config import get_config
|
19
19
|
from meerschaum.utils.warnings import warn
|
20
20
|
from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInterceptor
|
21
21
|
from meerschaum.utils.threading import Thread
|
22
22
|
import meerschaum as mrsm
|
23
|
+
import threading
|
23
24
|
daemon = mrsm.attempt_import('daemon')
|
24
25
|
|
25
26
|
class RotatingFile(io.IOBase):
|
@@ -38,6 +39,7 @@ class RotatingFile(io.IOBase):
|
|
38
39
|
redirect_streams: bool = False,
|
39
40
|
write_timestamps: bool = False,
|
40
41
|
timestamp_format: Optional[str] = None,
|
42
|
+
write_callback: Optional[Callable[[str], None]] = None,
|
41
43
|
):
|
42
44
|
"""
|
43
45
|
Create a file-like object which manages other files.
|
@@ -66,6 +68,9 @@ class RotatingFile(io.IOBase):
|
|
66
68
|
timestamp_format: str, default None
|
67
69
|
If `write_timestamps` is `True`, use this format for the timestamps.
|
68
70
|
Defaults to `'%Y-%m-%d %H:%M'`.
|
71
|
+
|
72
|
+
write_callback: Optional[Callable[[str], None]], default None
|
73
|
+
If provided, execute this callback with the data to be written.
|
69
74
|
"""
|
70
75
|
self.file_path = pathlib.Path(file_path)
|
71
76
|
if num_files_to_keep is None:
|
@@ -74,17 +79,18 @@ class RotatingFile(io.IOBase):
|
|
74
79
|
max_file_size = get_config('jobs', 'logs', 'max_file_size')
|
75
80
|
if timestamp_format is None:
|
76
81
|
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
77
|
-
if num_files_to_keep <
|
78
|
-
raise ValueError("At least
|
79
|
-
if max_file_size <
|
80
|
-
raise ValueError("Subfiles must contain at least
|
82
|
+
if num_files_to_keep < 1:
|
83
|
+
raise ValueError("At least 1 file must be kept.")
|
84
|
+
if max_file_size < 100:
|
85
|
+
raise ValueError("Subfiles must contain at least 100 bytes.")
|
81
86
|
|
82
87
|
self.num_files_to_keep = num_files_to_keep
|
83
88
|
self.max_file_size = max_file_size
|
84
89
|
self.redirect_streams = redirect_streams
|
85
90
|
self.write_timestamps = write_timestamps
|
86
91
|
self.timestamp_format = timestamp_format
|
87
|
-
self.
|
92
|
+
self.write_callback = write_callback
|
93
|
+
self.subfile_regex_pattern = re.compile(r'(.*)\.log(?:\.\d+)?')
|
88
94
|
|
89
95
|
### When subfiles are opened, map from their index to the file objects.
|
90
96
|
self.subfile_objects = {}
|
@@ -186,7 +192,7 @@ class RotatingFile(io.IOBase):
|
|
186
192
|
"""
|
187
193
|
try:
|
188
194
|
return int(subfile_name.replace(self.file_path.name + '.', ''))
|
189
|
-
except Exception
|
195
|
+
except Exception:
|
190
196
|
return -1
|
191
197
|
|
192
198
|
|
@@ -272,7 +278,7 @@ class RotatingFile(io.IOBase):
|
|
272
278
|
try:
|
273
279
|
daemon.daemon.redirect_stream(sys.stdout, self._current_file_obj)
|
274
280
|
daemon.daemon.redirect_stream(sys.stderr, self._current_file_obj)
|
275
|
-
except OSError
|
281
|
+
except OSError:
|
276
282
|
warn(
|
277
283
|
f"Encountered an issue when redirecting streams:\n{traceback.format_exc()}"
|
278
284
|
)
|
@@ -287,30 +293,36 @@ class RotatingFile(io.IOBase):
|
|
287
293
|
self.is_subfile_too_large(latest_subfile_index, potential_new_len)
|
288
294
|
)
|
289
295
|
if create_new_file:
|
290
|
-
|
291
|
-
new_subfile_index = old_subfile_index + 1
|
292
|
-
new_file_path = self.get_subfile_path_from_index(new_subfile_index)
|
293
|
-
self._previous_file_obj = self._current_file_obj
|
294
|
-
self._current_file_obj = open(new_file_path, 'a+', encoding='utf-8')
|
295
|
-
self.subfile_objects[new_subfile_index] = self._current_file_obj
|
296
|
-
self.flush()
|
297
|
-
|
298
|
-
if self._previous_file_obj is not None:
|
299
|
-
if self.redirect_streams:
|
300
|
-
self._redirected_subfile_objects[old_subfile_index] = self._previous_file_obj
|
301
|
-
daemon.daemon.redirect_stream(self._previous_file_obj, self._current_file_obj)
|
302
|
-
daemon.daemon.redirect_stream(sys.stdout, self._current_file_obj)
|
303
|
-
daemon.daemon.redirect_stream(sys.stderr, self._current_file_obj)
|
304
|
-
self.close(unused_only=True)
|
296
|
+
self.increment_subfiles()
|
305
297
|
|
306
|
-
|
307
|
-
if self._previous_file_obj is self._current_file_obj:
|
308
|
-
self._previous_file_obj = None
|
298
|
+
return self._current_file_obj
|
309
299
|
|
310
|
-
|
300
|
+
def increment_subfiles(self, increment_by: int = 1):
|
301
|
+
"""
|
302
|
+
Create a new subfile and switch the file pointer over.
|
303
|
+
"""
|
304
|
+
latest_subfile_index = self.get_latest_subfile_index()
|
305
|
+
old_subfile_index = latest_subfile_index
|
306
|
+
new_subfile_index = old_subfile_index + increment_by
|
307
|
+
new_file_path = self.get_subfile_path_from_index(new_subfile_index)
|
308
|
+
self._previous_file_obj = self._current_file_obj
|
309
|
+
self._current_file_obj = open(new_file_path, 'a+', encoding='utf-8')
|
310
|
+
self.subfile_objects[new_subfile_index] = self._current_file_obj
|
311
|
+
self.flush()
|
311
312
|
|
312
|
-
|
313
|
+
if self.redirect_streams:
|
314
|
+
if self._previous_file_obj is not None:
|
315
|
+
self._redirected_subfile_objects[old_subfile_index] = self._previous_file_obj
|
316
|
+
daemon.daemon.redirect_stream(self._previous_file_obj, self._current_file_obj)
|
317
|
+
daemon.daemon.redirect_stream(sys.stdout, self._current_file_obj)
|
318
|
+
daemon.daemon.redirect_stream(sys.stderr, self._current_file_obj)
|
319
|
+
self.close(unused_only=True)
|
320
|
+
|
321
|
+
### Sanity check in case writing somehow fails.
|
322
|
+
if self._previous_file_obj is self._current_file_obj:
|
323
|
+
self._previous_file_obj = None
|
313
324
|
|
325
|
+
self.delete(unused_only=True)
|
314
326
|
|
315
327
|
def close(self, unused_only: bool = False) -> None:
|
316
328
|
"""
|
@@ -330,7 +342,7 @@ class RotatingFile(io.IOBase):
|
|
330
342
|
try:
|
331
343
|
if not subfile_object.closed:
|
332
344
|
subfile_object.close()
|
333
|
-
except Exception
|
345
|
+
except Exception:
|
334
346
|
warn(f"Failed to close an open subfile:\n{traceback.format_exc()}")
|
335
347
|
|
336
348
|
_ = self.subfile_objects.pop(subfile_index, None)
|
@@ -359,6 +371,12 @@ class RotatingFile(io.IOBase):
|
|
359
371
|
As such, if data is larger than max_file_size, then the corresponding subfile
|
360
372
|
may exceed this limit.
|
361
373
|
"""
|
374
|
+
try:
|
375
|
+
if callable(self.write_callback):
|
376
|
+
self.write_callback(data)
|
377
|
+
except Exception:
|
378
|
+
warn(f"Failed to execute write callback:\n{traceback.format_exc()}")
|
379
|
+
|
362
380
|
try:
|
363
381
|
self.file_path.parent.mkdir(exist_ok=True, parents=True)
|
364
382
|
if isinstance(data, bytes):
|
@@ -379,7 +397,7 @@ class RotatingFile(io.IOBase):
|
|
379
397
|
except BrokenPipeError:
|
380
398
|
warn("BrokenPipeError encountered. The daemon may have been terminated.")
|
381
399
|
return
|
382
|
-
except Exception
|
400
|
+
except Exception:
|
383
401
|
warn(f"Failed to write to subfile:\n{traceback.format_exc()}")
|
384
402
|
self.flush()
|
385
403
|
self.delete(unused_only=True)
|
@@ -410,11 +428,10 @@ class RotatingFile(io.IOBase):
|
|
410
428
|
)
|
411
429
|
for subfile_path_to_delete in existing_subfile_paths[0:end_ix]:
|
412
430
|
subfile_index = self.get_index_from_subfile_name(subfile_path_to_delete.name)
|
413
|
-
subfile_object = self.subfile_objects.get(subfile_index, None)
|
414
431
|
|
415
432
|
try:
|
416
433
|
subfile_path_to_delete.unlink()
|
417
|
-
except Exception
|
434
|
+
except Exception:
|
418
435
|
warn(
|
419
436
|
f"Unable to delete subfile '{subfile_path_to_delete}':\n"
|
420
437
|
+ f"{traceback.format_exc()}"
|
@@ -586,20 +603,21 @@ class RotatingFile(io.IOBase):
|
|
586
603
|
if not subfile_object.closed:
|
587
604
|
try:
|
588
605
|
subfile_object.flush()
|
589
|
-
except Exception
|
606
|
+
except Exception:
|
590
607
|
warn(f"Failed to flush subfile {subfile_index}:\n{traceback.format_exc()}")
|
608
|
+
|
591
609
|
if self.redirect_streams:
|
592
610
|
try:
|
593
611
|
sys.stdout.flush()
|
594
612
|
except BrokenPipeError:
|
595
613
|
pass
|
596
|
-
except Exception
|
614
|
+
except Exception:
|
597
615
|
warn(f"Failed to flush STDOUT:\n{traceback.format_exc()}")
|
598
616
|
try:
|
599
617
|
sys.stderr.flush()
|
600
618
|
except BrokenPipeError:
|
601
619
|
pass
|
602
|
-
except Exception
|
620
|
+
except Exception:
|
603
621
|
warn(f"Failed to flush STDERR:\n{traceback.format_exc()}")
|
604
622
|
|
605
623
|
|
@@ -665,10 +683,19 @@ class RotatingFile(io.IOBase):
|
|
665
683
|
for thread in interceptor_threads[:end_ix]:
|
666
684
|
try:
|
667
685
|
thread.join()
|
668
|
-
except Exception
|
686
|
+
except Exception:
|
669
687
|
warn(f"Failed to join interceptor threads:\n{traceback.format_exc()}")
|
670
688
|
del interceptor_threads[:end_ix]
|
671
689
|
|
690
|
+
def touch(self):
|
691
|
+
"""
|
692
|
+
Touch the latest subfile.
|
693
|
+
"""
|
694
|
+
subfile_path = self.get_latest_subfile_path()
|
695
|
+
subfile_path.touch()
|
696
|
+
|
697
|
+
def isatty(self) -> bool:
|
698
|
+
return True
|
672
699
|
|
673
700
|
def __repr__(self) -> str:
|
674
701
|
"""
|
@@ -13,6 +13,7 @@ import os
|
|
13
13
|
import selectors
|
14
14
|
import traceback
|
15
15
|
|
16
|
+
import meerschaum as mrsm
|
16
17
|
from meerschaum.utils.typing import Optional, Union
|
17
18
|
from meerschaum.utils.warnings import warn
|
18
19
|
|
@@ -25,6 +26,8 @@ class StdinFile(io.TextIOBase):
|
|
25
26
|
self,
|
26
27
|
file_path: Union[pathlib.Path, str],
|
27
28
|
lock_file_path: Optional[pathlib.Path] = None,
|
29
|
+
decode: bool = True,
|
30
|
+
refresh_seconds: Union[int, float, None] = None,
|
28
31
|
):
|
29
32
|
if isinstance(file_path, str):
|
30
33
|
file_path = pathlib.Path(file_path)
|
@@ -38,6 +41,13 @@ class StdinFile(io.TextIOBase):
|
|
38
41
|
self._file_handler = None
|
39
42
|
self._fd = None
|
40
43
|
self.sel = selectors.DefaultSelector()
|
44
|
+
self.decode = decode
|
45
|
+
self._write_fp = None
|
46
|
+
self._refresh_seconds = refresh_seconds
|
47
|
+
|
48
|
+
@property
|
49
|
+
def encoding(self):
|
50
|
+
return 'utf-8'
|
41
51
|
|
42
52
|
@property
|
43
53
|
def file_handler(self):
|
@@ -47,11 +57,9 @@ class StdinFile(io.TextIOBase):
|
|
47
57
|
if self._file_handler is not None:
|
48
58
|
return self._file_handler
|
49
59
|
|
50
|
-
if self.file_path.exists():
|
51
|
-
self.file_path.
|
52
|
-
|
53
|
-
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
54
|
-
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
60
|
+
if not self.file_path.exists():
|
61
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
62
|
+
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
55
63
|
|
56
64
|
self._fd = os.open(self.file_path, os.O_RDONLY | os.O_NONBLOCK)
|
57
65
|
self._file_handler = os.fdopen(self._fd, 'rb', buffering=0)
|
@@ -59,11 +67,19 @@ class StdinFile(io.TextIOBase):
|
|
59
67
|
return self._file_handler
|
60
68
|
|
61
69
|
def write(self, data):
|
70
|
+
if self._write_fp is None:
|
71
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
72
|
+
if not self.file_path.exists():
|
73
|
+
os.mkfifo(self.file_path.as_posix(), mode=0o600)
|
74
|
+
self._write_fp = open(self.file_path, 'wb')
|
75
|
+
|
62
76
|
if isinstance(data, str):
|
63
77
|
data = data.encode('utf-8')
|
64
|
-
|
65
|
-
|
66
|
-
|
78
|
+
try:
|
79
|
+
self._write_fp.write(data)
|
80
|
+
self._write_fp.flush()
|
81
|
+
except BrokenPipeError:
|
82
|
+
pass
|
67
83
|
|
68
84
|
def fileno(self):
|
69
85
|
fileno = self.file_handler.fileno()
|
@@ -83,18 +99,19 @@ class StdinFile(io.TextIOBase):
|
|
83
99
|
self.blocking_file_path.unlink()
|
84
100
|
except Exception:
|
85
101
|
warn(traceback.format_exc())
|
86
|
-
return data.decode('utf-8')
|
102
|
+
return data.decode('utf-8') if self.decode else data
|
87
103
|
except (OSError, EOFError):
|
88
104
|
pass
|
89
105
|
|
90
|
-
self.blocking_file_path.
|
91
|
-
|
106
|
+
if not self.blocking_file_path.exists():
|
107
|
+
self.blocking_file_path.touch()
|
108
|
+
time.sleep(self.refresh_seconds)
|
92
109
|
|
93
110
|
def readline(self, size=-1):
|
94
|
-
line = ''
|
111
|
+
line = '' if self.decode else b''
|
95
112
|
while True:
|
96
113
|
data = self.read(1)
|
97
|
-
if not data or data == '\n':
|
114
|
+
if not data or ((data == '\n') if self.decode else (data == b'\n')):
|
98
115
|
break
|
99
116
|
line += data
|
100
117
|
|
@@ -111,11 +128,34 @@ class StdinFile(io.TextIOBase):
|
|
111
128
|
self._file_handler = None
|
112
129
|
self._fd = None
|
113
130
|
|
131
|
+
if self._write_fp is not None:
|
132
|
+
try:
|
133
|
+
self._write_fp.close()
|
134
|
+
except BrokenPipeError:
|
135
|
+
pass
|
136
|
+
self._write_fp = None
|
137
|
+
|
138
|
+
try:
|
139
|
+
if self.blocking_file_path.exists():
|
140
|
+
self.blocking_file_path.unlink()
|
141
|
+
except Exception:
|
142
|
+
pass
|
114
143
|
super().close()
|
115
144
|
|
116
145
|
def is_open(self):
|
117
146
|
return self._file_handler is not None
|
118
147
|
|
148
|
+
def isatty(self) -> bool:
|
149
|
+
return False
|
150
|
+
|
151
|
+
@property
|
152
|
+
def refresh_seconds(self) -> Union[int, float]:
|
153
|
+
"""
|
154
|
+
How many seconds between checking for blocking functions.
|
155
|
+
"""
|
156
|
+
if not self._refresh_seconds:
|
157
|
+
self._refresh_seconds = mrsm.get_config('system', 'cli', 'refresh_seconds')
|
158
|
+
return self._refresh_seconds
|
119
159
|
|
120
160
|
def __str__(self) -> str:
|
121
161
|
return f"StdinFile('{self.file_path}')"
|
@@ -7,9 +7,16 @@ Manage Daemons via the `Daemon` class.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
import os
|
12
|
+
import pathlib
|
13
|
+
import shutil
|
14
|
+
import json
|
15
|
+
import datetime
|
16
|
+
import threading
|
17
|
+
import shlex
|
18
|
+
|
19
|
+
from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any, Dict, Union
|
13
20
|
from meerschaum.utils.daemon.StdinFile import StdinFile
|
14
21
|
from meerschaum.utils.daemon.Daemon import Daemon
|
15
22
|
from meerschaum.utils.daemon.RotatingFile import RotatingFile
|
@@ -17,6 +24,27 @@ from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInte
|
|
17
24
|
from meerschaum.utils.daemon._names import get_new_daemon_name
|
18
25
|
|
19
26
|
|
27
|
+
__all__ = (
|
28
|
+
'daemon_action',
|
29
|
+
'daemon_entry',
|
30
|
+
'get_daemons',
|
31
|
+
'get_daemon_ids',
|
32
|
+
'get_running_daemons',
|
33
|
+
'get_stopped_daemons',
|
34
|
+
'get_paused_daemons',
|
35
|
+
'get_filtered_daemons',
|
36
|
+
'get_new_daemon_name',
|
37
|
+
'run_daemon',
|
38
|
+
'running_in_daemon',
|
39
|
+
'Daemon',
|
40
|
+
'StdinFile',
|
41
|
+
'RotatingFile',
|
42
|
+
'FileDescriptorInterceptor',
|
43
|
+
)
|
44
|
+
|
45
|
+
_daemons = {}
|
46
|
+
|
47
|
+
|
20
48
|
def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
21
49
|
"""Parse sysargs and execute a Meerschaum action as a daemon.
|
22
50
|
|
@@ -183,6 +211,7 @@ def get_daemon_ids() -> List[str]:
|
|
183
211
|
"""
|
184
212
|
Return the IDs of all daemons on disk.
|
185
213
|
"""
|
214
|
+
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
186
215
|
return [
|
187
216
|
daemon_dir
|
188
217
|
for daemon_dir in sorted(os.listdir(DAEMON_RESOURCES_PATH))
|
@@ -270,8 +299,6 @@ def get_filtered_daemons(
|
|
270
299
|
if warn:
|
271
300
|
_warn(f"Daemon '{d_id}' does not exist.", stack=False)
|
272
301
|
continue
|
273
|
-
if d.hidden:
|
274
|
-
pass
|
275
302
|
daemons.append(d)
|
276
303
|
return daemons
|
277
304
|
|
@@ -280,6 +307,20 @@ def running_in_daemon() -> bool:
|
|
280
307
|
"""
|
281
308
|
Return whether the current thread is running in a Daemon context.
|
282
309
|
"""
|
283
|
-
from meerschaum.
|
310
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
284
311
|
daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
|
285
312
|
return daemon_env_var in os.environ
|
313
|
+
|
314
|
+
|
315
|
+
def get_current_daemon() -> Union[Daemon, None]:
|
316
|
+
"""
|
317
|
+
If running withing a daemon context, return the corresponding `Daemon`.
|
318
|
+
Otherwise return `None`.
|
319
|
+
"""
|
320
|
+
from meerschaum._internal.static import STATIC_CONFIG
|
321
|
+
daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
|
322
|
+
daemon_id = os.environ.get(daemon_env_var, None)
|
323
|
+
if daemon_id is None:
|
324
|
+
return None
|
325
|
+
|
326
|
+
return _daemons.get(daemon_id, Daemon(daemon_id=daemon_id))
|
@@ -7,9 +7,11 @@ Generate random names for jobs.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
import os
|
12
|
+
import random
|
13
|
+
|
14
|
+
from meerschaum.utils.typing import Dict, List
|
13
15
|
|
14
16
|
_bank: Dict[str, Dict[str, List[str]]] = {
|
15
17
|
'adjectives': {
|
@@ -116,6 +118,7 @@ def get_new_daemon_name() -> str:
|
|
116
118
|
Generate a new random name until a unique one is found
|
117
119
|
(up to ~6000 maximum possibilities).
|
118
120
|
"""
|
121
|
+
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
119
122
|
existing_names = (
|
120
123
|
os.listdir(DAEMON_RESOURCES_PATH)
|
121
124
|
if DAEMON_RESOURCES_PATH.exists()
|