meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc7__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 +434 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +113 -19
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +3 -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 +20 -10
  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 +135 -35
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +195 -41
  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 +16 -6
  99. meerschaum/utils/misc.py +18 -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 +171 -144
  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.0rc7.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
@@ -0,0 +1,434 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define utilities for managing the workers jobs.
6
+ """
7
+
8
+ import os
9
+ import pathlib
10
+ import json
11
+ import asyncio
12
+ import time
13
+ from typing import List, Dict, Any, Union, TextIO
14
+
15
+ import meerschaum as mrsm
16
+ from meerschaum.utils.warnings import warn
17
+ from meerschaum.jobs import Job
18
+ from meerschaum.utils.threading import Thread
19
+ from meerschaum._internal.static import STATIC_CONFIG
20
+
21
+ STOP_TOKEN: str = STATIC_CONFIG['jobs']['stop_token']
22
+
23
+
24
+ def get_worker_input_file_path(ix: int) -> pathlib.Path:
25
+ """
26
+ Return the file path to the worker's input named pipe file.
27
+ """
28
+ from meerschaum.config.paths import CLI_RESOURCES_PATH
29
+ return CLI_RESOURCES_PATH / f"worker-{ix}.input"
30
+
31
+
32
+ def get_worker_output_file_path(ix: int) -> pathlib.Path:
33
+ """
34
+ Return the file path to the worker's output `StdinFile`.
35
+ """
36
+ from meerschaum.config.paths import CLI_RESOURCES_PATH
37
+ return CLI_RESOURCES_PATH / f"worker-{ix}.output"
38
+
39
+
40
+ def get_worker_stop_path(ix: int) -> pathlib.Path:
41
+ """
42
+ Return the file path to the worker's stop file.
43
+ """
44
+ from meerschaum.config.paths import CLI_RESOURCES_PATH
45
+ return CLI_RESOURCES_PATH / f"worker-{ix}.stop"
46
+
47
+
48
+ class ActionWorker:
49
+ """
50
+ The process loop which will accept commands to be run.
51
+ """
52
+
53
+ def __init__(self, ix: int, refresh_seconds: Union[int, float, None] = None):
54
+ self.ix = ix
55
+ self.refresh_seconds = (
56
+ refresh_seconds
57
+ if refresh_seconds is not None
58
+ else mrsm.get_config('system', 'cli', 'refresh_seconds')
59
+ )
60
+ self.refresh_logs_stop_event = asyncio.Event()
61
+
62
+ @property
63
+ def input_file_path(self) -> pathlib.Path:
64
+ """
65
+ Return the path to the input file.
66
+ """
67
+ return get_worker_input_file_path(self.ix)
68
+
69
+ @property
70
+ def output_file_path(self) -> pathlib.Path:
71
+ """
72
+ Return the path to the output file.
73
+ """
74
+ return get_worker_output_file_path(self.ix)
75
+
76
+ @property
77
+ def lock_path(self) -> pathlib.Path:
78
+ """
79
+ Return the lock path for this CLI worker.
80
+ """
81
+ if '_lock_path' in self.__dict__:
82
+ return self._lock_path
83
+
84
+ from meerschaum._internal.cli.daemons import get_cli_lock_path
85
+ self._lock_path = get_cli_lock_path(self.ix)
86
+ return self._lock_path
87
+
88
+ @property
89
+ def job(self) -> Job:
90
+ """
91
+ Return the job associated with this worker.
92
+ """
93
+ from meerschaum.config.paths import CLI_LOGS_RESOURCES_PATH
94
+ log_path = CLI_LOGS_RESOURCES_PATH / f'cli.{self.ix}.worker.log'
95
+
96
+ return Job(
97
+ f'.cli.{self.ix}.worker',
98
+ sysargs=['start', 'worker', str(self.ix)],
99
+ executor_keys='local',
100
+ delete_after_completion=False,
101
+ _properties={
102
+ 'logs': {
103
+ 'path': log_path.as_posix(),
104
+ 'write_timestamps': False,
105
+ 'refresh_files_seconds': 31557600,
106
+ 'max_file_size': 10_000_000,
107
+ 'num_files_to_keep': 1,
108
+ 'redirect_streams': True,
109
+ 'lines_to_show': 0,
110
+ },
111
+ },
112
+ )
113
+
114
+ @property
115
+ def stop_event(self) -> asyncio.Event:
116
+ """
117
+ Return the stop event to set to exit the logs monitoring.
118
+ """
119
+ if '_stop_event' in self.__dict__:
120
+ return self._stop_event
121
+
122
+ self._stop_event = asyncio.Event()
123
+ return self._stop_event
124
+
125
+ @property
126
+ def stop_path(self) -> pathlib.Path:
127
+ """
128
+ Return the path to the worker's stop file.
129
+ """
130
+ if '_stop_path' in self.__dict__:
131
+ return self._stop_path
132
+
133
+ self._stop_path = get_worker_stop_path(self.ix)
134
+ return self._stop_path
135
+
136
+ def check_stop_status(self) -> None:
137
+ """
138
+ Check for the stop file, and if it exists, set the stop event.
139
+ """
140
+ if self.stop_path.exists():
141
+ self.stop_event.set()
142
+
143
+ def set_stop_status(self) -> None:
144
+ """
145
+ Create the stop file and set the stop event.
146
+ """
147
+ self.stop_path.touch()
148
+ self.stop_event.set()
149
+
150
+ def clear_stop_status(self) -> None:
151
+ """
152
+ Remove the stop file and clear the stop event.
153
+ """
154
+ try:
155
+ self.stop_event.clear()
156
+ if self.stop_path.exists():
157
+ self.stop_path.unlink()
158
+ except Exception as e:
159
+ warn(f"Failed to clear stop status:\n{e}")
160
+
161
+ def set_lock(self) -> None:
162
+ """
163
+ Create the lock file.
164
+ """
165
+ self.lock_path.touch()
166
+
167
+ def release_lock(self) -> None:
168
+ """
169
+ Delete the lock file.
170
+ """
171
+ try:
172
+ if self.lock_path.exists():
173
+ self.lock_path.unlink()
174
+ except Exception as e:
175
+ warn(f"Failed to release lock for {self}:\n{e}")
176
+
177
+ def send_signal(self, signalnum):
178
+ """
179
+ Send a signal to the running job.
180
+ """
181
+ pid = self.job.pid
182
+ if pid is None:
183
+ raise EnvironmentError("Job is not running.")
184
+
185
+ os.kill(pid, signalnum)
186
+
187
+ def _read_data(
188
+ self,
189
+ file_to_read: TextIO,
190
+ ) -> Dict[str, Any]:
191
+ """
192
+ Common logic for reading data from a pipe.
193
+ """
194
+ try:
195
+ data_str = file_to_read.readline()
196
+ except Exception as e:
197
+ warn(f"Could not read data:\n{e}")
198
+ return {}
199
+
200
+ if not data_str:
201
+ return {}
202
+
203
+ try:
204
+ data = json.loads(data_str)
205
+ except Exception as e:
206
+ return {'error': str(e)}
207
+
208
+ return data
209
+
210
+ @staticmethod
211
+ def _write_data(
212
+ file_to_write: TextIO,
213
+ data: Dict[str, Any],
214
+ ) -> None:
215
+ """
216
+ Write a data dictionary to a pipe file.
217
+ """
218
+ try:
219
+ file_to_write.write(json.dumps(data, separators=(',', ':')) + '\n')
220
+ except Exception as e:
221
+ warn(f"Failed to write data:\n{e}")
222
+
223
+ def read_input_data(self) -> Dict[str, Any]:
224
+ """
225
+ Read input data from the input pipe file.
226
+ This method is called from within the worker's daemon context.
227
+ """
228
+ with open(self.input_file_path, 'r') as f:
229
+ return self._read_data(f)
230
+
231
+ def write_output_data(self, output_data: Dict[str, Any]) -> None:
232
+ """
233
+ Write the output data dictionary to the output pipe file.
234
+ This method is called from within the worker's daemon context.
235
+ """
236
+ with open(self.output_file_path, 'w') as f:
237
+ self._write_data(f, output_data)
238
+
239
+ def write_input_data(self, input_data: Dict[str, Any]) -> None:
240
+ """
241
+ Write the input data dictionary to the input pipe file.
242
+ This method is called from the client entry context.
243
+ """
244
+ with open(self.input_file_path, 'w') as f:
245
+ self._write_data(f, input_data)
246
+
247
+ def read_output_data(self) -> Dict[str, Any]:
248
+ """
249
+ Read output data from the output pipe file.
250
+ This method is called from the client entry context.
251
+ """
252
+ with open(self.output_file_path, 'r') as f:
253
+ return self._read_data(f)
254
+
255
+ def increment_log(self) -> None:
256
+ """
257
+ Increment the rotating log to clear output for the next action.
258
+ """
259
+ try:
260
+ self.job.daemon.rotating_log.increment_subfiles()
261
+ if self.job.daemon.log_offset_path.exists():
262
+ self.job.daemon.log_offset_path.unlink()
263
+ except Exception as e:
264
+ warn(f"Failed to refresh log files:\n{e}")
265
+
266
+ def is_ready(self) -> bool:
267
+ """
268
+ Return whether the CLI worker is ready to accept input.
269
+ """
270
+ return (
271
+ not self.lock_path.exists()
272
+ and self.input_file_path.exists()
273
+ and not self.stop_path.exists()
274
+ )
275
+
276
+ def create_fifos(self) -> mrsm.SuccessTuple:
277
+ """
278
+ Create the named pipes (FIFO files) for input and output.
279
+ """
280
+ paths = [self.input_file_path, self.output_file_path]
281
+ for path in paths:
282
+ try:
283
+ os.mkfifo(path)
284
+ except FileExistsError:
285
+ pass
286
+ except Exception as e:
287
+ return False, f"Failed to create FIFO file for {self}:\n{e}"
288
+
289
+ return True, "Success"
290
+
291
+ def run(self) -> mrsm.SuccessTuple:
292
+ """
293
+ Run the worker's process loop.
294
+ """
295
+ from meerschaum._internal.entry import entry
296
+ from meerschaum.config import replace_config
297
+ from meerschaum.config.environment import replace_env
298
+
299
+ self.create_fifos()
300
+
301
+ while True:
302
+ self.release_lock()
303
+ self.clear_stop_status()
304
+
305
+ input_data = self.read_input_data()
306
+ self.set_lock()
307
+
308
+ if 'error' in input_data:
309
+ warn(input_data['error'])
310
+ continue
311
+
312
+ if input_data.get('increment', False):
313
+ self.increment_log()
314
+ continue
315
+
316
+ sysargs = input_data.get('sysargs', None)
317
+ session_id = input_data.get('session_id', None)
318
+ action_id = input_data.get('action_id', None)
319
+ patch_args = input_data.get('patch_args', None)
320
+ env = input_data.get('env', {})
321
+ config = input_data.get('config', {})
322
+ self.write_output_data({
323
+ 'state': 'accepted',
324
+ 'session_id': session_id,
325
+ 'action_id': action_id,
326
+ })
327
+
328
+ with replace_config(config):
329
+ with replace_env(env):
330
+ action_success, action_msg = entry(
331
+ sysargs,
332
+ _use_cli_daemon=False,
333
+ _patch_args=patch_args,
334
+ )
335
+ print(STOP_TOKEN, flush=True, end='\n')
336
+
337
+ self.write_output_data({
338
+ 'state': 'completed',
339
+ 'session_id': session_id,
340
+ 'action_id': action_id,
341
+ 'success': action_success,
342
+ 'message': action_msg,
343
+ })
344
+ self.set_stop_status()
345
+
346
+ return True, "Success"
347
+
348
+ def monitor_callback(self, data: str):
349
+ print(data, flush=True, end='')
350
+ self.check_stop_status()
351
+
352
+ def cleanup(self, debug: bool = False) -> mrsm.SuccessTuple:
353
+ """
354
+ Delete the worker's job and any existing files.
355
+ """
356
+ delete_success, delete_msg = self.job.delete(debug=debug)
357
+ if not delete_success:
358
+ return delete_success, delete_msg
359
+
360
+ paths = [
361
+ get_worker_stop_path(self.ix),
362
+ get_worker_input_file_path(self.ix),
363
+ get_worker_output_file_path(self.ix),
364
+ ]
365
+
366
+ try:
367
+ for path in paths:
368
+ if path.exists():
369
+ path.unlink()
370
+ except Exception as e:
371
+ return False, f"Failed to clean up worker files:\n{e}"
372
+
373
+ return True, "Success"
374
+
375
+ def touch_cli_logs_loop(self):
376
+ """
377
+ Touch the CLI daemon's logs to refresh the logs monitoring.
378
+ """
379
+ while not self.refresh_logs_stop_event.is_set():
380
+ self.job.daemon.rotating_log.touch()
381
+ time.sleep(self.refresh_seconds)
382
+
383
+ def start_cli_logs_refresh_thread(self):
384
+ """
385
+ Spin up a daemon thread to refresh the CLI's logs.
386
+ """
387
+ self._logs_refresh_thread = Thread(
388
+ target=self.touch_cli_logs_loop,
389
+ daemon=True,
390
+ )
391
+ self._logs_refresh_thread.start()
392
+
393
+ def stop_cli_logs_refresh_thread(self):
394
+ """
395
+ Stop the logs refresh thread.
396
+ """
397
+ self.refresh_logs_stop_event.set()
398
+ thread = self.__dict__.pop('_logs_refresh_thread', None)
399
+ if thread is None:
400
+ return
401
+
402
+ thread.join()
403
+
404
+ def __repr__(self) -> str:
405
+ return self.__str__()
406
+
407
+ def __str__(self) -> str:
408
+ return f'ActionWorker({self.ix})'
409
+
410
+
411
+ def get_existing_cli_worker_indices() -> List[int]:
412
+ """
413
+ Get a list of the existing CLI workers' indices.
414
+ """
415
+ from meerschaum.config.paths import CLI_RESOURCES_PATH
416
+ from meerschaum.utils.misc import is_int
417
+
418
+ return sorted(list({
419
+ int(worker_ix)
420
+ for filename in os.listdir(CLI_RESOURCES_PATH)
421
+ if (
422
+ filename.startswith('worker-')
423
+ and is_int(
424
+ (worker_ix := filename.split('.', maxsplit=1)[0].replace('worker-', ''))
425
+ )
426
+ )
427
+ }))
428
+
429
+
430
+ def get_existing_cli_workers() -> List[ActionWorker]:
431
+ """
432
+ Get a list of the existing CLI workers.
433
+ """
434
+ return [ActionWorker(ix) for ix in get_existing_cli_worker_indices()]
@@ -578,12 +578,12 @@ def init_dash(dash_app):
578
578
  <li><code>meerschaum.utils.dtypes.deserialize_base64()</code></li>
579
579
  <li><code>meerschaum.utils.dtypes.deserialize_bytes_string()</code></li>
580
580
  <li><code>meerschaum.utils.dtypes.deserialize_geometry()</code></li>
581
- <li><code>meerschaum.utils.dtypes.dtype_is_special()</code></li>
582
581
  <li><code>meerschaum.utils.dtypes.encode_bytes_for_bytea()</code></li>
583
582
  <li><code>meerschaum.utils.dtypes.geometry_is_wkt()</code></li>
584
583
  <li><code>meerschaum.utils.dtypes.get_current_timestamp()</code></li>
585
584
  <li><code>meerschaum.utils.dtypes.get_geometry_type_srid()</code></li>
586
585
  <li><code>meerschaum.utils.dtypes.is_dtype_numeric()</code></li>
586
+ <li><code>meerschaum.utils.dtypes.is_dtype_special()</code></li>
587
587
  <li><code>meerschaum.utils.dtypes.json_serialize_value()</code></li>
588
588
  <li><code>meerschaum.utils.dtypes.none_if_null()</code></li>
589
589
  <li><code>meerschaum.utils.dtypes.project_geometry()</code></li>
@@ -597,7 +597,6 @@ def init_dash(dash_app):
597
597
  <li><code>meerschaum.utils.dtypes.to_pandas_dtype()</code></li>
598
598
  <li><code>meerschaum.utils.dtypes.value_is_null()</code></li>
599
599
  <li><code>meerschaum.utils.dtypes.get_current_timestamp()</code></li>
600
- <li><code>meerschaum.utils.dtypes.dtype_is_special()</code></li>
601
600
  <li><code>meerschaum.utils.dtypes.get_next_precision_unit()</code></li>
602
601
  <li><code>meerschaum.utils.dtypes.round_time()</code></li>
603
602
  </ul>
@@ -12,9 +12,12 @@ from __future__ import annotations
12
12
  import os
13
13
  import sys
14
14
  import pathlib
15
+ import shlex
16
+ import json
17
+ import time
15
18
 
16
19
  import meerschaum as mrsm
17
- from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any
20
+ from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any, Union
18
21
  from meerschaum._internal.static import STATIC_CONFIG as _STATIC_CONFIG
19
22
 
20
23
  _systemd_result_path = None
@@ -46,7 +49,44 @@ if (_STATIC_CONFIG['environment']['systemd_log_path']) in os.environ:
46
49
 
47
50
 
48
51
  def entry(
49
- sysargs: Optional[List[str]] = None,
52
+ sysargs: Union[List[str], str, None] = None,
53
+ _patch_args: Optional[Dict[str, Any]] = None,
54
+ _use_cli_daemon: bool = True,
55
+ _session_id: Optional[str] = None,
56
+ ) -> SuccessTuple:
57
+ """
58
+ Parse arguments and launch a Meerschaum action.
59
+
60
+ Returns
61
+ -------
62
+ A `SuccessTuple` indicating success.
63
+ """
64
+ start = time.perf_counter()
65
+ sysargs_list = shlex.split(sysargs) if isinstance(sysargs, str) else sysargs
66
+ if (
67
+ not _use_cli_daemon
68
+ or (not sysargs or (sysargs[0] and sysargs[0].startswith('-')))
69
+ or '--no-daemon' in sysargs_list
70
+ or '--daemon' in sysargs_list
71
+ or '-d' in sysargs_list
72
+ or not mrsm.get_config('system', 'experimental', 'cli_daemon')
73
+ ):
74
+ success, msg = entry_without_daemon(sysargs, _patch_args=_patch_args)
75
+ end = time.perf_counter()
76
+ if '--debug' in sysargs_list:
77
+ print(f"Duration without daemon: {round(end - start, 3)}")
78
+ return success, msg
79
+
80
+ from meerschaum._internal.cli.entry import entry_with_daemon
81
+ success, msg = entry_with_daemon(sysargs, _patch_args=_patch_args)
82
+ end = time.perf_counter()
83
+ if '--debug' in sysargs_list:
84
+ print(f"Duration with daemon: {round(end - start, 3)}")
85
+ return success, msg
86
+
87
+
88
+ def entry_without_daemon(
89
+ sysargs: Union[List[str], str, None] = None,
50
90
  _patch_args: Optional[Dict[str, Any]] = None,
51
91
  ) -> SuccessTuple:
52
92
  """
@@ -56,8 +96,6 @@ def entry(
56
96
  -------
57
97
  A `SuccessTuple` indicating success.
58
98
  """
59
- import shlex
60
- import json
61
99
  from meerschaum.utils.formatting import make_header
62
100
  from meerschaum._internal.arguments import (
63
101
  parse_arguments,
@@ -174,7 +212,6 @@ def entry(
174
212
  ).rstrip() if len(chained_sysargs) > 1 else results[0][1]
175
213
 
176
214
  if _systemd_result_path:
177
- import json
178
215
  from meerschaum.utils.warnings import warn
179
216
  import meerschaum as mrsm
180
217
 
@@ -217,7 +254,7 @@ def entry_with_args(
217
254
  if and_key in (sysargs := kw.get('sysargs', [])):
218
255
  if '-d' in sysargs or '--daemon' in sysargs:
219
256
  sysargs = [(arg if arg != and_key else escaped_and_key) for arg in sysargs]
220
- return entry(sysargs, _patch_args=_patch_args)
257
+ return entry(sysargs, _patch_args=_patch_args, _use_cli_daemon=False)
221
258
 
222
259
  if kw.get('trace', None):
223
260
  from meerschaum.utils.misc import debug_trace
@@ -287,7 +324,6 @@ def entry_with_args(
287
324
  if kw.get('schedule', None) and not _skip_schedule:
288
325
  from meerschaum.utils.schedule import schedule_function
289
326
  from meerschaum.utils.misc import interval_str
290
- import time
291
327
  from datetime import timedelta
292
328
  start_time = time.perf_counter()
293
329
  schedule_function(do_action, **kw)
@@ -321,7 +357,7 @@ def _do_action_wrapper(
321
357
  try:
322
358
  result = action_function(**filter_keywords(action_function, **kw))
323
359
  except Exception as e:
324
- if kw.get('debug', False):
360
+ if kw.get('debug', True):
325
361
  import traceback
326
362
  traceback.print_exception(type(e), e, e.__traceback__)
327
363
  result = False, (