meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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/_internal/arguments/_parser.py +14 -2
- 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 +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +115 -24
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +4 -1
- 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 +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +2 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +29 -0
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pipes.py +72 -36
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- 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 +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +32 -12
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +93 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +45 -71
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- 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 +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +146 -36
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/daemon/Daemon.py +197 -42
- 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 +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_shell.py +33 -9
- meerschaum/utils/misc.py +22 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +33 -5
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +172 -143
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.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}')"
|
@@ -16,8 +16,7 @@ import datetime
|
|
16
16
|
import threading
|
17
17
|
import shlex
|
18
18
|
|
19
|
-
from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any, Dict
|
20
|
-
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
19
|
+
from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any, Dict, Union
|
21
20
|
from meerschaum.utils.daemon.StdinFile import StdinFile
|
22
21
|
from meerschaum.utils.daemon.Daemon import Daemon
|
23
22
|
from meerschaum.utils.daemon.RotatingFile import RotatingFile
|
@@ -41,9 +40,10 @@ __all__ = (
|
|
41
40
|
'StdinFile',
|
42
41
|
'RotatingFile',
|
43
42
|
'FileDescriptorInterceptor',
|
44
|
-
'DAEMON_RESOURCES_PATH',
|
45
43
|
)
|
46
44
|
|
45
|
+
_daemons = {}
|
46
|
+
|
47
47
|
|
48
48
|
def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
49
49
|
"""Parse sysargs and execute a Meerschaum action as a daemon.
|
@@ -211,6 +211,7 @@ def get_daemon_ids() -> List[str]:
|
|
211
211
|
"""
|
212
212
|
Return the IDs of all daemons on disk.
|
213
213
|
"""
|
214
|
+
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
214
215
|
return [
|
215
216
|
daemon_dir
|
216
217
|
for daemon_dir in sorted(os.listdir(DAEMON_RESOURCES_PATH))
|
@@ -298,8 +299,6 @@ def get_filtered_daemons(
|
|
298
299
|
if warn:
|
299
300
|
_warn(f"Daemon '{d_id}' does not exist.", stack=False)
|
300
301
|
continue
|
301
|
-
if d.hidden:
|
302
|
-
pass
|
303
302
|
daemons.append(d)
|
304
303
|
return daemons
|
305
304
|
|
@@ -311,3 +310,17 @@ def running_in_daemon() -> bool:
|
|
311
310
|
from meerschaum._internal.static import STATIC_CONFIG
|
312
311
|
daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
|
313
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()
|
meerschaum/utils/debug.py
CHANGED
@@ -7,11 +7,31 @@ Functions to handle debug statements
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
import meerschaum as mrsm
|
10
13
|
from meerschaum.utils.typing import Union, Optional, List
|
11
14
|
|
15
|
+
|
16
|
+
_rich_text = None
|
17
|
+
def _import_rich_text_for_dprint():
|
18
|
+
"""
|
19
|
+
Avoid calling `attempt_import()` on every dprint.
|
20
|
+
"""
|
21
|
+
global _rich_text
|
22
|
+
if _rich_text is not None:
|
23
|
+
return _rich_text
|
24
|
+
|
25
|
+
from meerschaum.utils.packages import import_rich, attempt_import
|
26
|
+
_ = import_rich()
|
27
|
+
_rich_text = attempt_import('rich.text', lazy=False)
|
28
|
+
return _rich_text
|
29
|
+
|
30
|
+
|
12
31
|
def dprint(
|
13
32
|
msg: str,
|
14
33
|
leader: bool = True,
|
34
|
+
timestamp: bool = True,
|
15
35
|
package: bool = True,
|
16
36
|
color: Optional[Union[str, List[str]]] = None,
|
17
37
|
attrs: Optional[List[str]] = None,
|
@@ -35,6 +55,12 @@ def dprint(
|
|
35
55
|
else:
|
36
56
|
CHARSET, ANSI, colored, _color, cf = 'ascii', False, None, None, None
|
37
57
|
|
58
|
+
if timestamp:
|
59
|
+
from meerschaum.utils.dtypes import get_current_timestamp
|
60
|
+
now = get_current_timestamp('ms').replace(tzinfo=None)
|
61
|
+
else:
|
62
|
+
now = None
|
63
|
+
|
38
64
|
import logging, sys, inspect
|
39
65
|
logging.basicConfig(format='%(message)s')
|
40
66
|
log = logging.getLogger(__name__)
|
@@ -46,8 +72,13 @@ def dprint(
|
|
46
72
|
parent_package = parent_globals['__name__']
|
47
73
|
msg = str(msg)
|
48
74
|
premsg = ""
|
75
|
+
|
76
|
+
if now:
|
77
|
+
premsg = now.isoformat().split('T', maxsplit=1)[-1][:-3] + (' | ' if package else ':')
|
49
78
|
if package:
|
50
|
-
premsg = parent_package + ':' + str(parent_lineno)
|
79
|
+
premsg = premsg + parent_package + ':' + str(parent_lineno)
|
80
|
+
if premsg:
|
81
|
+
premsg += "\n"
|
51
82
|
if leader and cf is not None:
|
52
83
|
try:
|
53
84
|
debug_leader = cf['formatting']['debug'][CHARSET]['icon'] if cf is not None else ''
|
@@ -75,10 +106,9 @@ def dprint(
|
|
75
106
|
_color = {}
|
76
107
|
if colored is not None:
|
77
108
|
premsg = colored(premsg, **_color)
|
109
|
+
|
78
110
|
if _progress is not None:
|
79
|
-
|
80
|
-
rich = import_rich()
|
81
|
-
rich_text = attempt_import('rich.text')
|
111
|
+
rich_text = _import_rich_text_for_dprint()
|
82
112
|
text = rich_text.Text.from_ansi(premsg + msg)
|
83
113
|
_progress.console.log(text)
|
84
114
|
else:
|
@@ -1054,7 +1054,7 @@ def get_current_timestamp(
|
|
1054
1054
|
return ts_val.as_unit(MRSM_PRECISION_UNITS_ABBREVIATIONS[true_precision_unit])
|
1055
1055
|
|
1056
1056
|
|
1057
|
-
def
|
1057
|
+
def is_dtype_special(type_: str) -> bool:
|
1058
1058
|
"""
|
1059
1059
|
Return whether a dtype should be treated as a special Meerschaum dtype.
|
1060
1060
|
This is not the same as a Meerschaum alias.
|
@@ -1069,6 +1069,7 @@ def dtype_is_special(type_: str) -> bool:
|
|
1069
1069
|
'geometry',
|
1070
1070
|
'geography',
|
1071
1071
|
'date',
|
1072
|
+
'bool',
|
1072
1073
|
):
|
1073
1074
|
return True
|
1074
1075
|
|
@@ -1081,6 +1082,9 @@ def dtype_is_special(type_: str) -> bool:
|
|
1081
1082
|
if true_type.startswith('numeric'):
|
1082
1083
|
return True
|
1083
1084
|
|
1085
|
+
if true_type.startswith('bool'):
|
1086
|
+
return True
|
1087
|
+
|
1084
1088
|
if true_type.startswith('geometry'):
|
1085
1089
|
return True
|
1086
1090
|
|
@@ -209,6 +209,8 @@ def get_console():
|
|
209
209
|
try:
|
210
210
|
console = rich_console.Console(force_terminal=True, color_system='truecolor')
|
211
211
|
except Exception:
|
212
|
+
import traceback
|
213
|
+
traceback.print_exc()
|
212
214
|
console = None
|
213
215
|
return console
|
214
216
|
|
@@ -488,8 +490,9 @@ def fill_ansi(string: str, style: str = '') -> str:
|
|
488
490
|
msg = Text.from_ansi(string)
|
489
491
|
except AttributeError:
|
490
492
|
import traceback
|
491
|
-
traceback.
|
493
|
+
traceback.print_exc()
|
492
494
|
msg = ''
|
495
|
+
|
493
496
|
plain_indices = []
|
494
497
|
for left_span, right_span in iterate_chunks(msg.spans, 2, fillvalue=len(msg)):
|
495
498
|
left = left_span.end
|
@@ -97,7 +97,7 @@ def pprint_jobs(
|
|
97
97
|
msg = '\n'.join(line.lstrip().rstrip() for line in lines)
|
98
98
|
success_tuple = success, msg
|
99
99
|
success_tuple_str = (
|
100
|
-
format_success_tuple(success_tuple, left_padding=
|
100
|
+
format_success_tuple(success_tuple, left_padding=0)
|
101
101
|
if success_tuple is not None
|
102
102
|
else None
|
103
103
|
)
|