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.
Files changed (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {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 < 2:
78
- raise ValueError("At least 2 files must be kept.")
79
- if max_file_size < 1:
80
- raise ValueError("Subfiles must contain at least one byte.")
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.subfile_regex_pattern = re.compile(r'(.*)\.log(?:\.\d+)?$')
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 as e:
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 as e:
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
- old_subfile_index = latest_subfile_index
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
- ### Sanity check in case writing somehow fails.
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
- self.delete(unused_only=True)
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
- return self._current_file_obj
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 as e:
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 as e:
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 as e:
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 as e:
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 as e:
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 as e:
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 as e:
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.unlink()
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
- with open(self.file_path, 'wb') as f:
66
- f.write(data)
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.touch()
91
- time.sleep(0.1)
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
- import os, random
11
- from meerschaum.utils.typing import Dict, List, Tuple
12
- from meerschaum.config._paths import DAEMON_RESOURCES_PATH
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) + '\n'
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
- from meerschaum.utils.packages import import_rich, attempt_import
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 dtype_is_special(type_: str) -> bool:
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.print_stack()
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=1)
100
+ format_success_tuple(success_tuple, left_padding=0)
101
101
  if success_tuple is not None
102
102
  else None
103
103
  )