meerschaum 2.2.0rc4__py3-none-any.whl → 2.2.2__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 (42) hide show
  1. meerschaum/_internal/entry.py +36 -11
  2. meerschaum/_internal/shell/Shell.py +40 -16
  3. meerschaum/_internal/term/__init__.py +3 -2
  4. meerschaum/_internal/term/tools.py +1 -1
  5. meerschaum/actions/api.py +65 -31
  6. meerschaum/actions/python.py +56 -24
  7. meerschaum/actions/start.py +2 -4
  8. meerschaum/actions/uninstall.py +5 -9
  9. meerschaum/actions/upgrade.py +11 -3
  10. meerschaum/api/__init__.py +1 -0
  11. meerschaum/api/dash/callbacks/__init__.py +4 -0
  12. meerschaum/api/dash/callbacks/custom.py +39 -0
  13. meerschaum/api/dash/callbacks/dashboard.py +39 -6
  14. meerschaum/api/dash/callbacks/login.py +3 -1
  15. meerschaum/api/dash/components.py +5 -2
  16. meerschaum/api/dash/pipes.py +145 -97
  17. meerschaum/config/_default.py +1 -0
  18. meerschaum/config/_paths.py +12 -12
  19. meerschaum/config/_version.py +1 -1
  20. meerschaum/config/paths.py +10 -0
  21. meerschaum/config/static/__init__.py +1 -1
  22. meerschaum/connectors/__init__.py +9 -2
  23. meerschaum/connectors/sql/_cli.py +7 -1
  24. meerschaum/connectors/sql/_pipes.py +6 -0
  25. meerschaum/core/Pipe/__init__.py +5 -0
  26. meerschaum/core/Pipe/_sync.py +2 -3
  27. meerschaum/plugins/__init__.py +67 -9
  28. meerschaum/utils/daemon/Daemon.py +7 -2
  29. meerschaum/utils/misc.py +6 -0
  30. meerschaum/utils/packages/__init__.py +212 -53
  31. meerschaum/utils/packages/_packages.py +3 -2
  32. meerschaum/utils/process.py +12 -2
  33. meerschaum/utils/schedule.py +10 -3
  34. meerschaum/utils/venv/__init__.py +46 -11
  35. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/METADATA +13 -9
  36. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/RECORD +42 -40
  37. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/WHEEL +1 -1
  38. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/LICENSE +0 -0
  39. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/NOTICE +0 -0
  40. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/entry_points.txt +0 -0
  41. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/top_level.txt +0 -0
  42. {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/zip-safe +0 -0
@@ -47,9 +47,9 @@ def entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
47
47
  )
48
48
  )
49
49
 
50
- if args.get('schedule', None):
51
- from meerschaum.utils.schedule import schedule_function
52
- return schedule_function(entry_with_args, **args)
50
+ # if args.get('schedule', None):
51
+ # from meerschaum.utils.schedule import schedule_function
52
+ # return schedule_function(entry_with_args, **args)
53
53
  return entry_with_args(**args)
54
54
 
55
55
 
@@ -61,7 +61,7 @@ def entry_with_args(
61
61
  Use `_entry()` for parsing sysargs before executing.
62
62
  """
63
63
  import sys
64
- from meerschaum.plugins import Plugin
64
+ import functools
65
65
  from meerschaum.actions import get_action, get_main_action_name
66
66
  from meerschaum._internal.arguments import remove_leading_action
67
67
  from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
@@ -88,10 +88,41 @@ def entry_with_args(
88
88
  action_function.__module__.startswith('plugins.')
89
89
  ) else None
90
90
  )
91
- plugin = Plugin(plugin_name) if plugin_name else None
91
+
92
+ skip_schedule = False
93
+ if (
94
+ kw['action']
95
+ and kw['action'][0] == 'start'
96
+ and kw['action'][1] in ('job', 'jobs')
97
+ ):
98
+ skip_schedule = True
92
99
 
93
100
  kw['action'] = remove_leading_action(kw['action'], _actions=_actions)
94
101
 
102
+ do_action = functools.partial(_do_action_wrapper, action_function, plugin_name, **kw)
103
+ if kw.get('schedule', None) and not skip_schedule:
104
+ from meerschaum.utils.schedule import schedule_function
105
+ from meerschaum.utils.misc import interval_str
106
+ import time
107
+ from datetime import timedelta
108
+ start_time = time.perf_counter()
109
+ schedule_function(do_action, **kw)
110
+ delta = timedelta(seconds=(time.perf_counter() - start_time))
111
+ result = True, f"Exited scheduler after {interval_str(delta)}."
112
+ else:
113
+ result = do_action()
114
+
115
+ ### Clean up stray virtual environments.
116
+ for venv in [venv for venv in active_venvs]:
117
+ deactivate_venv(venv, debug=kw.get('debug', False), force=True)
118
+
119
+ return result
120
+
121
+
122
+ def _do_action_wrapper(action_function, plugin_name, **kw):
123
+ from meerschaum.plugins import Plugin
124
+ from meerschaum.utils.venv import Venv, active_venvs, deactivate_venv
125
+ plugin = Plugin(plugin_name) if plugin_name else None
95
126
  with Venv(plugin, debug=kw.get('debug', False)):
96
127
  action_name = ' '.join(action_function.__name__.split('_') + kw.get('action', []))
97
128
  try:
@@ -111,14 +142,8 @@ def entry_with_args(
111
142
  )
112
143
  except KeyboardInterrupt:
113
144
  result = False, f"Cancelled action `{action_name}`."
114
-
115
- ### Clean up stray virtual environments.
116
- for venv in [venv for venv in active_venvs]:
117
- deactivate_venv(venv, debug=kw.get('debug', False), force=True)
118
-
119
145
  return result
120
146
 
121
-
122
147
  _shells = []
123
148
  _shell = None
124
149
  def get_shell(
@@ -203,6 +203,29 @@ def _check_complete_keys(line: str) -> Optional[List[str]]:
203
203
  return None
204
204
 
205
205
 
206
+ def get_shell_intro(with_color: bool = True) -> str:
207
+ """
208
+ Return the introduction message string.
209
+ """
210
+ from meerschaum.utils.formatting import CHARSET, ANSI, colored
211
+ intro = get_config('shell', CHARSET, 'intro', patch=patch)
212
+ intro += '\n' + ''.join(
213
+ [' '
214
+ for i in range(
215
+ string_width(intro) - len('v' + version)
216
+ )
217
+ ]
218
+ ) + 'v' + version
219
+
220
+ if not with_color or not ANSI:
221
+ return intro
222
+
223
+ return colored(
224
+ intro,
225
+ **get_config('shell', 'ansi', 'intro', 'rich')
226
+ )
227
+
228
+
206
229
  class Shell(cmd.Cmd):
207
230
  def __init__(
208
231
  self,
@@ -277,25 +300,20 @@ class Shell(cmd.Cmd):
277
300
  except Exception as e:
278
301
  pass
279
302
 
280
-
281
303
  def load_config(self, instance: Optional[str] = None):
282
304
  """
283
305
  Set attributes from the shell configuration.
284
306
  """
285
307
  from meerschaum.utils.misc import remove_ansi
286
- from meerschaum.utils.formatting import CHARSET, ANSI, UNICODE, colored
308
+ from meerschaum.utils.formatting import CHARSET, ANSI, colored
287
309
 
288
310
  if shell_attrs.get('intro', None) != '':
289
- self.intro = get_config('shell', CHARSET, 'intro', patch=patch)
290
- self.intro += '\n' + ''.join(
291
- [' '
292
- for i in range(
293
- string_width(self.intro) - len('v' + version)
294
- )
295
- ]
296
- ) + 'v' + version
297
- else:
298
- self.intro = ""
311
+ self.intro = (
312
+ get_shell_intro(with_color=False)
313
+ if shell_attrs.get('intro', None) != ''
314
+ else ""
315
+ )
316
+
299
317
  shell_attrs['intro'] = self.intro
300
318
  shell_attrs['_prompt'] = get_config('shell', CHARSET, 'prompt', patch=patch)
301
319
  self.prompt = shell_attrs['_prompt']
@@ -822,7 +840,7 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
822
840
  """
823
841
  Replace built-in `input()` with prompt_toolkit.prompt.
824
842
  """
825
- from meerschaum.utils.formatting import CHARSET, ANSI, UNICODE, colored
843
+ from meerschaum.utils.formatting import CHARSET, ANSI, colored
826
844
  from meerschaum.connectors import is_connected, connectors
827
845
  from meerschaum.utils.misc import remove_ansi
828
846
  from meerschaum.config import get_config
@@ -849,11 +867,17 @@ def input_with_sigint(_input, session, shell: Optional[Shell] = None):
849
867
  shell_attrs['instance_keys'], 'on ' + get_config(
850
868
  'shell', 'ansi', 'instance', 'rich', 'style'
851
869
  )
852
- ) if ANSI else colored(shell_attrs['instance_keys'], 'on white')
870
+ )
871
+ if ANSI
872
+ else colored(shell_attrs['instance_keys'], 'on white')
853
873
  )
854
874
  repo_colored = (
855
- colored(shell_attrs['repo_keys'], 'on ' + get_config('shell', 'ansi', 'repo', 'rich', 'style'))
856
- if ANSI else colored(shell_attrs['repo_keys'], 'on white')
875
+ colored(
876
+ shell_attrs['repo_keys'],
877
+ 'on ' + get_config('shell', 'ansi', 'repo', 'rich', 'style')
878
+ )
879
+ if ANSI
880
+ else colored(shell_attrs['repo_keys'], 'on white')
857
881
  )
858
882
  try:
859
883
  typ, label = shell_attrs['instance_keys'].split(':')
@@ -14,9 +14,10 @@ from typing import List, Tuple
14
14
  from meerschaum.utils.packages import attempt_import
15
15
  from meerschaum._internal.term.TermPageHandler import TermPageHandler
16
16
  from meerschaum.config._paths import API_TEMPLATES_PATH, API_STATIC_PATH
17
+ from meerschaum.utils.venv import venv_executable
17
18
 
18
19
  tornado, tornado_ioloop, terminado = attempt_import(
19
- 'tornado', 'tornado.ioloop', 'terminado', lazy=False, venv=None,
20
+ 'tornado', 'tornado.ioloop', 'terminado', lazy=False,
20
21
  )
21
22
 
22
23
  def get_webterm_app_and_manager() -> Tuple[
@@ -31,7 +32,7 @@ def get_webterm_app_and_manager() -> Tuple[
31
32
  A tuple of the Tornado web application and term manager.
32
33
  """
33
34
  commands = [
34
- sys.executable,
35
+ venv_executable('mrsm'),
35
36
  '-c',
36
37
  "import os; _ = os.environ.pop('COLUMNS', None); _ = os.environ.pop('LINES', None); "
37
38
  "from meerschaum._internal.entry import get_shell; "
@@ -17,7 +17,7 @@ def is_webterm_running(host: str, port: int, protocol: str = 'http') -> int:
17
17
  requests = attempt_import('requests')
18
18
  url = f'{protocol}://{host}:{port}'
19
19
  try:
20
- r = requests.get(url)
20
+ r = requests.get(url, timeout=3)
21
21
  except Exception as e:
22
22
  return False
23
23
  if not r:
meerschaum/actions/api.py CHANGED
@@ -156,6 +156,7 @@ def _api_start(
156
156
  from meerschaum.config.static import STATIC_CONFIG, SERVER_ID
157
157
  from meerschaum.connectors.parse import parse_instance_keys
158
158
  from meerschaum.utils.pool import get_pool
159
+ from meerschaum.utils.venv import get_module_venv
159
160
  import shutil
160
161
  from copy import deepcopy
161
162
 
@@ -169,7 +170,10 @@ def _api_start(
169
170
  ### `check_update` must be False, because otherwise Uvicorn's hidden imports will break things.
170
171
  dotenv = attempt_import('dotenv', lazy=False)
171
172
  uvicorn, gunicorn = attempt_import(
172
- 'uvicorn', 'gunicorn', venv=None, lazy=False, check_update=False,
173
+ 'uvicorn', 'gunicorn',
174
+ lazy = False,
175
+ check_update = False,
176
+ venv = 'mrsm',
173
177
  )
174
178
 
175
179
  uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
@@ -305,19 +309,51 @@ def _api_start(
305
309
  ### remove custom keys before calling uvicorn
306
310
 
307
311
  def _run_uvicorn():
308
- try:
309
- uvicorn.run(
310
- **filter_keywords(
311
- uvicorn.run,
312
- **{
313
- k: v
314
- for k, v in uvicorn_config.items()
315
- if k not in custom_keys
316
- }
317
- )
318
- )
319
- except KeyboardInterrupt:
320
- pass
312
+ uvicorn_flags = [
313
+ '--host', host,
314
+ '--port', str(port),
315
+ (
316
+ '--proxy-headers'
317
+ if uvicorn_config.get('proxy_headers')
318
+ else '--no-proxy-headers'
319
+ ),
320
+ (
321
+ '--use-colors'
322
+ if uvicorn_config.get('use_colors')
323
+ else '--no-use-colors'
324
+ ),
325
+ '--env-file', uvicorn_config['env_file'],
326
+ ]
327
+ if uvicorn_reload := uvicorn_config.get('reload'):
328
+ uvicorn_flags.append('--reload')
329
+ if (
330
+ uvicorn_reload
331
+ and (reload_dirs := uvicorn_config.get('reload_dirs'))
332
+ ):
333
+ if not isinstance(reload_dirs, list):
334
+ reload_dirs = [reload_dirs]
335
+ for reload_dir in reload_dirs:
336
+ uvicorn_flags += ['--reload-dir', reload_dir]
337
+ if (
338
+ uvicorn_reload
339
+ and (reload_excludes := uvicorn_config.get('reload_excludes'))
340
+ ):
341
+ if not isinstance(reload_excludes, list):
342
+ reload_excludes = [reload_excludes]
343
+ for reload_exclude in reload_excludes:
344
+ uvicorn_flags += ['--reload-exclude', reload_exclude]
345
+ if (uvicorn_workers := uvicorn_config.get('workers')) is not None:
346
+ uvicorn_flags += ['--workers', str(uvicorn_workers)]
347
+
348
+ uvicorn_args = uvicorn_flags + ['meerschaum.api:app']
349
+ run_python_package(
350
+ 'uvicorn',
351
+ uvicorn_args,
352
+ venv = get_module_venv(uvicorn),
353
+ as_proc = False,
354
+ foreground = True,
355
+ debug = debug,
356
+ )
321
357
 
322
358
  def _run_gunicorn():
323
359
  gunicorn_args = [
@@ -338,23 +374,21 @@ def _api_start(
338
374
  ]
339
375
  if debug:
340
376
  gunicorn_args += ['--log-level=debug', '--enable-stdio-inheritance', '--reload']
341
- try:
342
- run_python_package(
343
- 'gunicorn',
344
- gunicorn_args,
345
- env = {
346
- k: (
347
- json.dumps(v)
348
- if isinstance(v, (dict, list))
349
- else v
350
- )
351
- for k, v in env_dict.items()
352
- },
353
- venv = None,
354
- debug = debug,
355
- )
356
- except KeyboardInterrupt:
357
- pass
377
+
378
+ run_python_package(
379
+ 'gunicorn',
380
+ gunicorn_args,
381
+ env = {
382
+ k: (
383
+ json.dumps(v)
384
+ if isinstance(v, (dict, list))
385
+ else v
386
+ )
387
+ for k, v in env_dict.items()
388
+ },
389
+ venv = get_module_venv(gunicorn),
390
+ debug = debug,
391
+ )
358
392
 
359
393
 
360
394
  _run_uvicorn() if not production else _run_gunicorn()
@@ -10,37 +10,39 @@ from meerschaum.utils.typing import SuccessTuple, Any, List, Optional
10
10
 
11
11
  def python(
12
12
  action: Optional[List[str]] = None,
13
+ venv: Optional[str] = 'mrsm',
14
+ sub_args: Optional[List[str]] = None,
13
15
  debug: bool = False,
14
16
  **kw: Any
15
17
  ) -> SuccessTuple:
16
18
  """
17
- Launch a Python interpreter with Meerschaum imported. Commands are optional.
18
- Note that quotes must be escaped and commands must be separated by semicolons
19
+ Launch a virtual environment's Python interpreter with Meerschaum imported.
20
+ You may pass flags to the Python binary by surrounding each flag with `[]`.
19
21
 
20
22
  Usage:
21
23
  `python {commands}`
22
24
 
23
- Example:
24
- `python print(\\'Hello, World!\\'); pipes = mrsm.get_pipes()`
25
-
26
- ```
27
- Hello, World!
28
-
29
- >>> import meerschaum as mrsm
30
- >>> print('Hello, World!')
31
- >>> pipes = mrsm.get_pipes()
32
- ```
25
+ Examples:
26
+ mrsm python
27
+ mrsm python --venv noaa
28
+ mrsm python [-i] [-c 'print("hi")']
33
29
  """
34
- import sys, subprocess
30
+ import sys, subprocess, os
35
31
  from meerschaum.utils.debug import dprint
36
32
  from meerschaum.utils.warnings import warn, error
37
- from meerschaum.utils.process import run_process
33
+ from meerschaum.utils.venv import venv_executable
34
+ from meerschaum.utils.misc import generate_password
38
35
  from meerschaum.config import __version__ as _version
36
+ from meerschaum.config.paths import VIRTENV_RESOURCES_PATH, PYTHON_RESOURCES_PATH
37
+ from meerschaum.utils.packages import run_python_package, attempt_import
39
38
 
40
39
  if action is None:
41
40
  action = []
42
41
 
43
- joined_actions = ['import meerschaum as mrsm']
42
+ if venv == 'None':
43
+ venv = None
44
+
45
+ joined_actions = ["import meerschaum as mrsm"]
44
46
  line = ""
45
47
  for i, a in enumerate(action):
46
48
  if a == '':
@@ -51,34 +53,64 @@ def python(
51
53
  line = ""
52
54
 
53
55
  ### ensure meerschaum is imported
54
- # joined_actions = ['import meerschaum as mrsm;'] + joined_actions
55
56
  if debug:
56
- dprint(joined_actions)
57
+ dprint(str(joined_actions))
57
58
 
58
- print_command = 'import sys; print("""'
59
- ps1 = ">>> "
59
+ ### TODO: format the pre-executed code using the pygments lexer.
60
+ print_command = (
61
+ 'from meerschaum.utils.packages import attempt_import; '
62
+ + 'ptft = attempt_import("prompt_toolkit.formatted_text", lazy=False); '
63
+ + 'pts = attempt_import("prompt_toolkit.shortcuts"); '
64
+ + 'ansi = ptft.ANSI("""'
65
+ )
66
+ ps1 = "\\033[1m>>> \\033[0m"
60
67
  for i, a in enumerate(joined_actions):
61
68
  line = ps1 + f"{a}".replace(';', '\n')
62
69
  if '\n' not in line and i != len(joined_actions) - 1:
63
70
  line += "\n"
64
71
  print_command += line
65
- print_command += '""")'
72
+ print_command += (
73
+ '"""); '
74
+ + 'pts.print_formatted_text(ansi); '
75
+ )
66
76
 
67
- command = ""
77
+ command = print_command
68
78
  for a in joined_actions:
69
79
  command += a
70
80
  if not a.endswith(';'):
71
81
  command += ';'
72
82
  command += ' '
73
83
 
74
- command += print_command
75
-
76
84
  if debug:
77
85
  dprint(f"command:\n{command}")
86
+
87
+ init_script_path = PYTHON_RESOURCES_PATH / (generate_password(8) + '.py')
88
+ with open(init_script_path, 'w', encoding='utf-8') as f:
89
+ f.write(command)
90
+
91
+ env_dict = os.environ.copy()
92
+ venv_path = (VIRTENV_RESOURCES_PATH / venv) if venv is not None else None
93
+ if venv_path is not None:
94
+ env_dict.update({'VIRTUAL_ENV': venv_path.as_posix()})
95
+
78
96
  try:
79
- return_code = run_process([sys.executable, '-i', '-c', command], foreground=True)
97
+ ptpython = attempt_import('ptpython', venv=venv, allow_outside_venv=False)
98
+ return_code = run_python_package(
99
+ 'ptpython',
100
+ sub_args or ['--dark-bg', '-i', init_script_path.as_posix()],
101
+ venv = venv,
102
+ foreground = True,
103
+ env = env_dict,
104
+ )
80
105
  except KeyboardInterrupt:
81
106
  return_code = 1
107
+
108
+ try:
109
+ if init_script_path.exists():
110
+ init_script_path.unlink()
111
+ except Exception as e:
112
+ warn(f"Failed to clean up tempory file '{init_script_path}'.")
113
+
82
114
  return return_code == 0, (
83
115
  "Success" if return_code == 0
84
116
  else f"Python interpreter returned {return_code}."
@@ -333,8 +333,7 @@ def _start_gui(
333
333
  from meerschaum.connectors.parse import parse_instance_keys
334
334
  from meerschaum._internal.term.tools import is_webterm_running
335
335
  import platform
336
- webview = attempt_import('webview')
337
- requests = attempt_import('requests')
336
+ webview, requests = attempt_import('webview', 'requests')
338
337
  import json
339
338
  import time
340
339
 
@@ -365,7 +364,7 @@ def _start_gui(
365
364
  base_url = 'http://' + api_kw['host'] + ':' + str(api_kw['port'])
366
365
 
367
366
  process = venv_exec(
368
- start_tornado_code, as_proc=True, venv=None, debug=debug, capture_output=(not debug)
367
+ start_tornado_code, as_proc=True, debug=debug, capture_output=(not debug)
369
368
  )
370
369
  timeout = 10
371
370
  start = time.perf_counter()
@@ -446,7 +445,6 @@ def _start_webterm(
446
445
  + " Include `-f` to start another server on a new port\n"
447
446
  + " or specify a different port with `-p`."
448
447
  )
449
-
450
448
  if not nopretty:
451
449
  info(f"Starting the webterm at http://{host}:{port} ...\n Press CTRL+C to quit.")
452
450
  tornado_app.listen(port, host)
@@ -7,7 +7,7 @@ Uninstall plugins and pip packages.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import List, Any, SuccessTuple, Optional
10
+ from meerschaum.utils.typing import List, Any, SuccessTuple, Optional, Union
11
11
 
12
12
  def uninstall(
13
13
  action: Optional[List[str]] = None,
@@ -145,13 +145,10 @@ def _complete_uninstall_plugins(action: Optional[List[str]] = None, **kw) -> Lis
145
145
  possibilities.append(name)
146
146
  return possibilities
147
147
 
148
- class NoVenv:
149
- pass
150
-
151
148
  def _uninstall_packages(
152
149
  action: Optional[List[str]] = None,
153
150
  sub_args: Optional[List[str]] = None,
154
- venv: Union[str, None, NoVenv] = NoVenv,
151
+ venv: Optional[str] = 'mrsm',
155
152
  yes: bool = False,
156
153
  force: bool = False,
157
154
  noask: bool = False,
@@ -169,9 +166,7 @@ def _uninstall_packages(
169
166
 
170
167
  from meerschaum.utils.warnings import info
171
168
  from meerschaum.utils.packages import pip_uninstall
172
-
173
- if venv is NoVenv:
174
- venv = 'mrsm'
169
+ from meerschaum.utils.misc import items_str
175
170
 
176
171
  if not (yes or force) and noask:
177
172
  return False, "Skipping uninstallation. Add `-y` or `-f` to agree to the uninstall prompt."
@@ -183,7 +178,8 @@ def _uninstall_packages(
183
178
  debug = debug,
184
179
  ):
185
180
  return True, (
186
- f"Successfully removed packages from virtual environment 'mrsm':\n" + ', '.join(action)
181
+ f"Successfully removed packages from virtual environment '{venv}':\n"
182
+ + items_str(action)
187
183
  )
188
184
 
189
185
  return False, f"Failed to uninstall packages:\n" + ', '.join(action)
@@ -130,7 +130,7 @@ def _upgrade_packages(
130
130
  upgrade packages docs
131
131
  ```
132
132
  """
133
- from meerschaum.utils.packages import packages, pip_install
133
+ from meerschaum.utils.packages import packages, pip_install, get_prerelease_dependencies
134
134
  from meerschaum.utils.warnings import info, warn
135
135
  from meerschaum.utils.prompt import yes_no
136
136
  from meerschaum.utils.formatting import make_header, pprint
@@ -140,7 +140,7 @@ def _upgrade_packages(
140
140
  if venv is NoVenv:
141
141
  venv = 'mrsm'
142
142
  if len(action) == 0:
143
- group = 'full'
143
+ group = 'api'
144
144
  else:
145
145
  group = action[0]
146
146
 
@@ -148,7 +148,7 @@ def _upgrade_packages(
148
148
  invalid_msg = f"Invalid dependency group '{group}'."
149
149
  avail_msg = make_header("Available groups:")
150
150
  for k in packages:
151
- avail_msg += "\n - {k}"
151
+ avail_msg += f"\n - {k}"
152
152
 
153
153
  warn(invalid_msg + "\n\n" + avail_msg, stack=False)
154
154
  return False, invalid_msg
@@ -160,10 +160,18 @@ def _upgrade_packages(
160
160
  f"(dependency group '{group}')?"
161
161
  )
162
162
  to_install = [install_name for import_name, install_name in packages[group].items()]
163
+ prereleases_to_install = get_prerelease_dependencies(to_install)
164
+ to_install = [
165
+ install_name
166
+ for install_name in to_install
167
+ if install_name not in prereleases_to_install
168
+ ]
163
169
 
164
170
  success, msg = False, f"Nothing installed."
165
171
  if force or yes_no(question, noask=noask, yes=yes):
166
172
  success = pip_install(*to_install, debug=debug)
173
+ if success and prereleases_to_install:
174
+ success = pip_install(*prereleases_to_install, debug=debug)
167
175
  msg = (
168
176
  f"Successfully installed {len(packages[group])} packages." if success
169
177
  else f"Failed to install packages in dependency group '{group}'."
@@ -31,6 +31,7 @@ CHECK_UPDATE = os.environ.get(STATIC_CONFIG['environment']['runtime'], None) !=
31
31
 
32
32
  endpoints = STATIC_CONFIG['api']['endpoints']
33
33
 
34
+ uv = attempt_import('uv', lazy=False, check_update=CHECK_UPDATE)
34
35
  (
35
36
  fastapi,
36
37
  aiofiles,
@@ -11,3 +11,7 @@ import meerschaum.api.dash.callbacks.login
11
11
  import meerschaum.api.dash.callbacks.plugins
12
12
  import meerschaum.api.dash.callbacks.jobs
13
13
  import meerschaum.api.dash.callbacks.register
14
+ from meerschaum.api.dash.callbacks.custom import init_dash_plugins, add_plugin_pages
15
+
16
+ init_dash_plugins()
17
+ add_plugin_pages()
@@ -0,0 +1,39 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ Import custom callbacks created by plugins.
7
+ """
8
+
9
+ import traceback
10
+ from meerschaum.api.dash import dash_app
11
+ from meerschaum.plugins import _dash_plugins, _plugin_endpoints_to_pages
12
+ from meerschaum.utils.warnings import warn
13
+ from meerschaum.api.dash.callbacks.dashboard import _paths, _required_login
14
+
15
+
16
+ def init_dash_plugins():
17
+ """
18
+ Fire the initial callbacks for Dash plugins.
19
+ """
20
+ for _module_name, _functions in _dash_plugins.items():
21
+ for _function in _functions:
22
+ try:
23
+ _function(dash_app)
24
+ except Exception as e:
25
+ warn(
26
+ f"Failed to load function '{_function.__name__}' "
27
+ + f"from plugin '{_module_name}':\n"
28
+ + traceback.format_exc()
29
+ )
30
+
31
+
32
+ def add_plugin_pages():
33
+ """
34
+ Allow users to add pages via the `@web_page` decorator.
35
+ """
36
+ for _endpoint, _page_dict in _plugin_endpoints_to_pages.items():
37
+ _paths[_endpoint] = _page_dict['function']()
38
+ if _page_dict['login_required']:
39
+ _required_login.add(_endpoint)