meerschaum 2.2.6__py3-none-any.whl → 2.2.7__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 (33) hide show
  1. meerschaum/__main__.py +10 -5
  2. meerschaum/_internal/entry.py +13 -13
  3. meerschaum/_internal/shell/Shell.py +26 -22
  4. meerschaum/_internal/shell/updates.py +175 -0
  5. meerschaum/actions/register.py +19 -5
  6. meerschaum/actions/sync.py +3 -3
  7. meerschaum/actions/upgrade.py +28 -36
  8. meerschaum/api/routes/_pipes.py +20 -20
  9. meerschaum/config/_formatting.py +1 -0
  10. meerschaum/config/_paths.py +4 -0
  11. meerschaum/config/_shell.py +78 -66
  12. meerschaum/config/_version.py +1 -1
  13. meerschaum/config/static/__init__.py +1 -0
  14. meerschaum/connectors/api/_misc.py +1 -1
  15. meerschaum/connectors/api/_request.py +13 -9
  16. meerschaum/core/Pipe/_sync.py +3 -0
  17. meerschaum/utils/daemon/Daemon.py +88 -129
  18. meerschaum/utils/daemon/FileDescriptorInterceptor.py +14 -5
  19. meerschaum/utils/daemon/RotatingFile.py +8 -1
  20. meerschaum/utils/daemon/__init__.py +28 -21
  21. meerschaum/utils/formatting/__init__.py +81 -36
  22. meerschaum/utils/formatting/_jobs.py +47 -9
  23. meerschaum/utils/packages/__init__.py +21 -15
  24. meerschaum/utils/prompt.py +5 -0
  25. meerschaum/utils/schedule.py +21 -15
  26. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/METADATA +1 -1
  27. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/RECORD +33 -32
  28. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/LICENSE +0 -0
  29. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/NOTICE +0 -0
  30. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/WHEEL +0 -0
  31. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/entry_points.txt +0 -0
  32. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/top_level.txt +0 -0
  33. {meerschaum-2.2.6.dist-info → meerschaum-2.2.7.dist-info}/zip-safe +0 -0
@@ -67,19 +67,17 @@ def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
67
67
  if daemon.status == 'running':
68
68
  return True, f"Daemon '{daemon}' is already running."
69
69
  return daemon.run(
70
- debug = debug,
71
- allow_dirty_run = True,
70
+ debug=debug,
71
+ allow_dirty_run=True,
72
72
  )
73
73
 
74
74
  success_tuple = run_daemon(
75
75
  entry,
76
76
  filtered_sysargs,
77
- daemon_id = _args.get('name', None) if _args else None,
78
- label = label,
79
- keep_daemon_output = ('--rm' not in sysargs)
77
+ daemon_id=_args.get('name', None) if _args else None,
78
+ label=label,
79
+ keep_daemon_output=('--rm' not in sysargs),
80
80
  )
81
- if not isinstance(success_tuple, tuple):
82
- success_tuple = False, str(success_tuple)
83
81
  return success_tuple
84
82
 
85
83
 
@@ -109,25 +107,25 @@ def daemon_action(**kw) -> SuccessTuple:
109
107
 
110
108
 
111
109
  def run_daemon(
112
- func: Callable[[Any], Any],
113
- *args,
114
- daemon_id: Optional[str] = None,
115
- keep_daemon_output: bool = False,
116
- allow_dirty_run: bool = False,
117
- label: Optional[str] = None,
118
- **kw
119
- ) -> Any:
110
+ func: Callable[[Any], Any],
111
+ *args,
112
+ daemon_id: Optional[str] = None,
113
+ keep_daemon_output: bool = True,
114
+ allow_dirty_run: bool = False,
115
+ label: Optional[str] = None,
116
+ **kw
117
+ ) -> Any:
120
118
  """Execute a function as a daemon."""
121
119
  daemon = Daemon(
122
120
  func,
123
- daemon_id = daemon_id,
124
- target_args = [arg for arg in args],
125
- target_kw = kw,
126
- label = label,
121
+ daemon_id=daemon_id,
122
+ target_args=[arg for arg in args],
123
+ target_kw=kw,
124
+ label=label,
127
125
  )
128
126
  return daemon.run(
129
- keep_daemon_output = keep_daemon_output,
130
- allow_dirty_run = allow_dirty_run,
127
+ keep_daemon_output=keep_daemon_output,
128
+ allow_dirty_run=allow_dirty_run,
131
129
  )
132
130
 
133
131
 
@@ -268,3 +266,12 @@ def get_filtered_daemons(
268
266
  pass
269
267
  daemons.append(d)
270
268
  return daemons
269
+
270
+
271
+ def running_in_daemon() -> bool:
272
+ """
273
+ Return whether the current thread is running in a Daemon context.
274
+ """
275
+ from meerschaum.config.static import STATIC_CONFIG
276
+ daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
277
+ return daemon_env_var in os.environ
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import platform
11
11
  import os
12
12
  import sys
13
+ import meerschaum as mrsm
13
14
  from meerschaum.utils.typing import Optional, Union, Any, Dict
14
15
  from meerschaum.utils.formatting._shell import make_header
15
16
  from meerschaum.utils.formatting._pprint import pprint
@@ -33,6 +34,7 @@ __all__ = sorted([
33
34
  'colored',
34
35
  'translate_rich_to_termcolor',
35
36
  'get_console',
37
+ 'format_success_tuple',
36
38
  'print_tuple',
37
39
  'print_options',
38
40
  'fill_ansi',
@@ -222,16 +224,17 @@ def get_console():
222
224
 
223
225
 
224
226
  def print_tuple(
225
- tup: tuple,
226
- skip_common: bool = True,
227
- common_only: bool = False,
228
- upper_padding: int = 0,
229
- lower_padding: int = 0,
230
- calm: bool = False,
231
- _progress: Optional['rich.progress.Progress'] = None,
232
- ) -> None:
227
+ tup: mrsm.SuccessTuple,
228
+ skip_common: bool = True,
229
+ common_only: bool = False,
230
+ upper_padding: int = 0,
231
+ lower_padding: int = 0,
232
+ left_padding: int = 1,
233
+ calm: bool = False,
234
+ _progress: Optional['rich.progress.Progress'] = None,
235
+ ) -> None:
233
236
  """
234
- Print `meerschaum.utils.typing.SuccessTuple`.
237
+ Format `meerschaum.utils.typing.SuccessTuple`.
235
238
 
236
239
  Parameters
237
240
  ----------
@@ -247,24 +250,18 @@ def print_tuple(
247
250
  lower_padding: int, default 0
248
251
  How many newlines to append to the message.
249
252
 
253
+ left_padding: int, default 1
254
+ How mant spaces to preprend to the message.
255
+
250
256
  calm: bool, default False
251
257
  If `True`, use the default emoji and color scheme.
258
+
252
259
  """
253
260
  from meerschaum.config.static import STATIC_CONFIG
254
- _init()
255
- try:
256
- status = 'success' if tup[0] else 'failure'
257
- except TypeError:
258
- status = 'failure'
259
- tup = None, None
260
-
261
- if calm:
262
- status += '_calm'
261
+ do_print = True
263
262
 
264
263
  omit_messages = STATIC_CONFIG['system']['success']['ignore']
265
264
 
266
- do_print = True
267
-
268
265
  if common_only:
269
266
  skip_common = False
270
267
  do_print = tup[1] in omit_messages
@@ -272,22 +269,70 @@ def print_tuple(
272
269
  if skip_common:
273
270
  do_print = tup[1] not in omit_messages
274
271
 
275
- if do_print:
276
- ANSI, CHARSET = __getattr__('ANSI'), __getattr__('CHARSET')
277
- from meerschaum.config import get_config
278
- status_config = get_config('formatting', status, patch=True)
279
-
280
- msg = ' ' + status_config[CHARSET]['icon'] + ' ' + str(tup[1])
281
- lines = msg.split('\n')
282
- lines = [lines[0]] + [
283
- ((' ' + line if not line.startswith(' ') else line))
284
- for line in lines[1:]
285
- ]
286
- if ANSI:
287
- lines[0] = fill_ansi(highlight_pipes(lines[0]), **status_config['ansi']['rich'])
288
- msg = '\n'.join(lines)
289
- msg = ('\n' * upper_padding) + msg + ('\n' * lower_padding)
290
- print(msg)
272
+ if not do_print:
273
+ return
274
+
275
+ print(format_success_tuple(
276
+ tup,
277
+ upper_padding=upper_padding,
278
+ lower_padding=lower_padding,
279
+ calm=calm,
280
+ _progress=_progress,
281
+ ))
282
+
283
+
284
+ def format_success_tuple(
285
+ tup: mrsm.SuccessTuple,
286
+ upper_padding: int = 0,
287
+ lower_padding: int = 0,
288
+ left_padding: int = 1,
289
+ calm: bool = False,
290
+ _progress: Optional['rich.progress.Progress'] = None,
291
+ ) -> str:
292
+ """
293
+ Format `meerschaum.utils.typing.SuccessTuple`.
294
+
295
+ Parameters
296
+ ----------
297
+ upper_padding: int, default 0
298
+ How many newlines to prepend to the message.
299
+
300
+ lower_padding: int, default 0
301
+ How many newlines to append to the message.
302
+
303
+ left_padding: int, default 1
304
+ How mant spaces to preprend to the message.
305
+
306
+ calm: bool, default False
307
+ If `True`, use the default emoji and color scheme.
308
+ """
309
+ from meerschaum.config.static import STATIC_CONFIG
310
+ _init()
311
+ try:
312
+ status = 'success' if tup[0] else 'failure'
313
+ except TypeError:
314
+ status = 'failure'
315
+ tup = None, None
316
+
317
+ if calm:
318
+ status += '_calm'
319
+
320
+ ANSI, CHARSET = __getattr__('ANSI'), __getattr__('CHARSET')
321
+ from meerschaum.config import get_config
322
+ status_config = get_config('formatting', status, patch=True)
323
+
324
+ msg = (' ' * left_padding) + status_config[CHARSET]['icon'] + ' ' + str(tup[1])
325
+ lines = msg.split('\n')
326
+ lines = [lines[0]] + [
327
+ ((' ' + line if not line.startswith(' ') else line))
328
+ for line in lines[1:]
329
+ ]
330
+ if ANSI:
331
+ lines[0] = fill_ansi(highlight_pipes(lines[0]), **status_config['ansi']['rich'])
332
+
333
+ msg = '\n'.join(lines)
334
+ msg = ('\n' * upper_padding) + msg + ('\n' * lower_padding)
335
+ return msg
291
336
 
292
337
 
293
338
  def print_options(
@@ -7,7 +7,8 @@ Print jobs information.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import List, Optional, Any
10
+ import meerschaum as mrsm
11
+ from meerschaum.utils.typing import List, Optional, Any, is_success_tuple
11
12
  from meerschaum.utils.daemon import (
12
13
  Daemon,
13
14
  get_daemons,
@@ -17,9 +18,9 @@ from meerschaum.utils.daemon import (
17
18
  )
18
19
 
19
20
  def pprint_jobs(
20
- daemons: List[Daemon],
21
- nopretty: bool = False,
22
- ):
21
+ daemons: List[Daemon],
22
+ nopretty: bool = False,
23
+ ):
23
24
  """Pretty-print a list of Daemons."""
24
25
  from meerschaum.utils.formatting import make_header
25
26
 
@@ -48,10 +49,12 @@ def pprint_jobs(
48
49
  pprint_job(d, nopretty=nopretty)
49
50
 
50
51
  def _pretty_print():
51
- from meerschaum.utils.formatting import get_console, UNICODE, ANSI
52
+ from meerschaum.utils.formatting import get_console, UNICODE, ANSI, format_success_tuple
52
53
  from meerschaum.utils.packages import import_rich, attempt_import
53
54
  rich = import_rich()
54
- rich_table, rich_text, rich_box = attempt_import('rich.table', 'rich.text', 'rich.box')
55
+ rich_table, rich_text, rich_box, rich_json, rich_panel, rich_console = attempt_import(
56
+ 'rich.table', 'rich.text', 'rich.box', 'rich.json', 'rich.panel', 'rich.console',
57
+ )
55
58
  table = rich_table.Table(
56
59
  title = rich_text.Text('Jobs'),
57
60
  box = (rich_box.ROUNDED if UNICODE else rich_box.ASCII),
@@ -62,13 +65,42 @@ def pprint_jobs(
62
65
  table.add_column("Command")
63
66
  table.add_column("Status")
64
67
 
68
+ def get_success_text(d):
69
+ success_tuple = d.properties.get('result', None)
70
+ if isinstance(success_tuple, list):
71
+ success_tuple = tuple(success_tuple)
72
+ if not is_success_tuple(success_tuple):
73
+ return rich_text.Text('')
74
+
75
+ success = success_tuple[0]
76
+ msg = success_tuple[1]
77
+ lines = msg.split('\n')
78
+ msg = '\n'.join(line.lstrip().rstrip() for line in lines)
79
+ success_tuple = success, msg
80
+ success_tuple_str = (
81
+ format_success_tuple(success_tuple, left_padding=1)
82
+ if success_tuple is not None
83
+ else None
84
+ )
85
+ success_tuple_text = (
86
+ rich_text.Text.from_ansi(success_tuple_str)
87
+ ) if success_tuple_str is not None else None
88
+
89
+ if success_tuple_text is None:
90
+ return rich_text.Text('')
91
+
92
+ return rich_text.Text('\n') + success_tuple_text
93
+
94
+
65
95
  for d in running_daemons:
66
96
  if d.hidden:
67
97
  continue
68
98
  table.add_row(
69
99
  d.daemon_id,
70
100
  d.label,
71
- rich_text.Text(d.status, style=('green' if ANSI else ''))
101
+ rich_console.Group(
102
+ rich_text.Text(d.status, style=('green' if ANSI else '')),
103
+ ),
72
104
  )
73
105
 
74
106
  for d in paused_daemons:
@@ -77,16 +109,22 @@ def pprint_jobs(
77
109
  table.add_row(
78
110
  d.daemon_id,
79
111
  d.label,
80
- rich_text.Text(d.status, style=('yellow' if ANSI else ''))
112
+ rich_console.Group(
113
+ rich_text.Text(d.status, style=('yellow' if ANSI else '')),
114
+ ),
81
115
  )
82
116
 
83
117
  for d in stopped_daemons:
84
118
  if d.hidden:
85
119
  continue
120
+
86
121
  table.add_row(
87
122
  d.daemon_id,
88
123
  d.label,
89
- rich_text.Text(d.status, style=('red' if ANSI else ''))
124
+ rich_console.Group(
125
+ rich_text.Text(d.status, style=('red' if ANSI else '')),
126
+ get_success_text(d)
127
+ ),
90
128
  )
91
129
  get_console().print(table)
92
130
 
@@ -816,6 +816,7 @@ def pip_install(
816
816
  """
817
817
  from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
818
818
  from meerschaum.config import get_config
819
+ from meerschaum.config.static import STATIC_CONFIG
819
820
  from meerschaum.utils.warnings import warn
820
821
  from meerschaum.utils.misc import is_android
821
822
  if args is None:
@@ -827,6 +828,11 @@ def pip_install(
827
828
  if check_wheel:
828
829
  have_wheel = venv_contains_package('wheel', venv=venv, debug=debug)
829
830
 
831
+ daemon_env_var = STATIC_CONFIG['environment']['daemon_id']
832
+ inside_daemon = daemon_env_var in os.environ
833
+ if inside_daemon:
834
+ silent = True
835
+
830
836
  _args = list(args)
831
837
  have_pip = venv_contains_package('pip', venv=None, debug=debug)
832
838
  try:
@@ -844,16 +850,16 @@ def pip_install(
844
850
  if have_pip and not have_uv_pip and _install_uv_pip and not is_android():
845
851
  if not pip_install(
846
852
  'uv',
847
- venv = None,
848
- debug = debug,
849
- _install_uv_pip = False,
850
- check_update = False,
851
- check_pypi = False,
852
- check_wheel = False,
853
- ):
853
+ venv=None,
854
+ debug=debug,
855
+ _install_uv_pip=False,
856
+ check_update=False,
857
+ check_pypi=False,
858
+ check_wheel=False,
859
+ ) and not silent:
854
860
  warn(
855
861
  f"Failed to install `uv` for virtual environment '{venv}'.",
856
- color = False,
862
+ color=False,
857
863
  )
858
864
 
859
865
  use_uv_pip = (
@@ -909,13 +915,13 @@ def pip_install(
909
915
  check_wheel = False,
910
916
  debug = debug,
911
917
  _install_uv_pip = False,
912
- ):
918
+ ) and not silent:
913
919
  warn(
914
920
  (
915
921
  "Failed to install `setuptools`, `wheel`, and `uv` for virtual "
916
922
  + f"environment '{venv}'."
917
923
  ),
918
- color = False,
924
+ color=False,
919
925
  )
920
926
 
921
927
  if requirements_file_path is not None:
@@ -975,7 +981,7 @@ def pip_install(
975
981
  if not completely_uninstall_package(
976
982
  _install_no_version,
977
983
  venv=venv, debug=debug,
978
- ):
984
+ ) and not silent:
979
985
  warn(
980
986
  f"Failed to clean up package '{_install_no_version}'.",
981
987
  )
@@ -989,9 +995,9 @@ def pip_install(
989
995
  rc = run_python_package(
990
996
  ('pip' if not use_uv_pip else 'uv'),
991
997
  _args + _packages,
992
- venv = None,
993
- env = _get_pip_os_env(color=color),
994
- debug = debug,
998
+ venv=None,
999
+ env=_get_pip_os_env(color=color),
1000
+ debug=debug,
995
1001
  )
996
1002
  if debug:
997
1003
  print(f"{rc=}")
@@ -1003,7 +1009,7 @@ def pip_install(
1003
1009
  )
1004
1010
  if not silent:
1005
1011
  print(msg)
1006
- if debug:
1012
+ if debug and not silent:
1007
1013
  print('pip ' + ('un' if _uninstall else '') + 'install returned:', success)
1008
1014
  return success
1009
1015
 
@@ -14,6 +14,7 @@ def prompt(
14
14
  question: str,
15
15
  icon: bool = True,
16
16
  default: Union[str, Tuple[str, str], None] = None,
17
+ default_editable: Optional[str] = None,
17
18
  detect_password: bool = True,
18
19
  is_password: bool = False,
19
20
  wrap_lines: bool = True,
@@ -37,6 +38,9 @@ def prompt(
37
38
  default: Union[str, Tuple[str, str], None], default None
38
39
  If the response is '', return the default value.
39
40
 
41
+ default_editable: Optional[str], default None
42
+ If provided, auto-type this user-editable string in the prompt.
43
+
40
44
  detect_password: bool, default True
41
45
  If `True`, set the input method to a censored password box if the word `password`
42
46
  appears in the question.
@@ -102,6 +106,7 @@ def prompt(
102
106
  prompt_toolkit.prompt(
103
107
  prompt_toolkit.formatted_text.ANSI(question),
104
108
  wrap_lines = wrap_lines,
109
+ default = default_editable or '',
105
110
  **filter_keywords(prompt_toolkit.prompt, **kw)
106
111
  ) if not noask else ''
107
112
  )
@@ -7,10 +7,12 @@ Schedule processes and threads.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- import sys
10
+ import signal
11
+ import traceback
11
12
  from datetime import datetime, timezone, timedelta
12
13
  import meerschaum as mrsm
13
14
  from meerschaum.utils.typing import Callable, Any, Optional, List, Dict
15
+ from meerschaum.utils.warnings import warn, error
14
16
 
15
17
  STARTING_KEYWORD: str = 'starting'
16
18
  INTERVAL_UNITS: List[str] = ['months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'years']
@@ -70,15 +72,15 @@ SCHEDULE_ALIASES: Dict[str, str] = {
70
72
 
71
73
  _scheduler = None
72
74
  def schedule_function(
73
- function: Callable[[Any], Any],
74
- schedule: str,
75
- *args,
76
- debug: bool = False,
77
- **kw
78
- ) -> None:
75
+ function: Callable[[Any], Any],
76
+ schedule: str,
77
+ *args,
78
+ debug: bool = False,
79
+ **kw
80
+ ) -> mrsm.SuccessTuple:
79
81
  """
80
82
  Block the process and execute the function intermittently according to the frequency.
81
- https://rocketry.readthedocs.io/en/stable/condition_syntax/index.html
83
+ https://meerschaum.io/reference/background-jobs/#-schedules
82
84
 
83
85
  Parameters
84
86
  ----------
@@ -88,10 +90,13 @@ def schedule_function(
88
90
  schedule: str
89
91
  The frequency schedule at which `function` should be executed (e.g. `'daily'`).
90
92
 
93
+ Returns
94
+ -------
95
+ A `SuccessTuple` upon exit.
91
96
  """
92
97
  import asyncio
93
- from meerschaum.utils.warnings import warn
94
98
  from meerschaum.utils.misc import filter_keywords, round_time
99
+
95
100
  global _scheduler
96
101
  kw['debug'] = debug
97
102
  kw = filter_keywords(function, **kw)
@@ -105,15 +110,16 @@ def schedule_function(
105
110
  except RuntimeError:
106
111
  loop = asyncio.new_event_loop()
107
112
 
113
+
108
114
  async def run_scheduler():
109
115
  async with _scheduler:
110
116
  job = await _scheduler.add_schedule(
111
117
  function,
112
118
  trigger,
113
- args = args,
114
- kwargs = kw,
115
- max_running_jobs = 1,
116
- conflict_policy = apscheduler.ConflictPolicy.replace,
119
+ args=args,
120
+ kwargs=kw,
121
+ max_running_jobs=1,
122
+ conflict_policy=apscheduler.ConflictPolicy.replace,
117
123
  )
118
124
  try:
119
125
  await _scheduler.run_until_stopped()
@@ -126,12 +132,13 @@ def schedule_function(
126
132
  except (KeyboardInterrupt, SystemExit) as e:
127
133
  loop.run_until_complete(_stop_scheduler())
128
134
 
135
+ return True, "Success"
136
+
129
137
 
130
138
  def parse_schedule(schedule: str, now: Optional[datetime] = None):
131
139
  """
132
140
  Parse a schedule string (e.g. 'daily') into a Trigger object.
133
141
  """
134
- from meerschaum.utils.warnings import error
135
142
  from meerschaum.utils.misc import items_str, is_int
136
143
  (
137
144
  apscheduler_triggers_cron,
@@ -279,7 +286,6 @@ def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
279
286
  datetime.datetime(2024, 5, 13, 0, 30, tzinfo=datetime.timezone.utc)
280
287
  """
281
288
  from meerschaum.utils.misc import round_time
282
- from meerschaum.utils.warnings import error, warn
283
289
  dateutil_parser = mrsm.attempt_import('dateutil.parser')
284
290
  starting_parts = schedule.split(STARTING_KEYWORD)
285
291
  starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.2.6
3
+ Version: 2.2.7
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares