meerschaum 3.0.0rc3__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.
- meerschaum/_internal/arguments/_parser.py +14 -2
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +434 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +113 -19
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +3 -1
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +7 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +103 -97
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/pipes.py +136 -57
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/callbacks/tokens.py +2 -1
- meerschaum/api/dash/components.py +6 -7
- meerschaum/api/dash/keys.py +17 -1
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pages/pipes.py +14 -4
- meerschaum/api/dash/pipes.py +186 -65
- meerschaum/api/dash/tokens.py +1 -1
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +20 -10
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +98 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/sql/tables/__init__.py +20 -3
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +62 -72
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +135 -35
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/_get_pipes.py +30 -4
- meerschaum/utils/daemon/Daemon.py +195 -41
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_pprint.py +1 -0
- meerschaum/utils/formatting/_shell.py +16 -6
- meerschaum/utils/misc.py +18 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +39 -7
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +171 -144
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/RECORD +125 -119
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc3.dist-info → meerschaum-3.0.0rc7.dist-info}/zip-safe +0 -0
meerschaum/plugins/__init__.py
CHANGED
@@ -8,6 +8,7 @@ Expose plugin management APIs from the `meerschaum.plugins` module.
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
+
import pathlib
|
11
12
|
import functools
|
12
13
|
|
13
14
|
import meerschaum as mrsm
|
@@ -16,13 +17,15 @@ from meerschaum.utils.threading import Lock, RLock
|
|
16
17
|
from meerschaum.core.Plugin import Plugin
|
17
18
|
|
18
19
|
_api_plugins: Dict[str, List[Callable[['fastapi.App'], Any]]] = {}
|
19
|
-
_pre_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
|
20
|
-
_post_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
|
20
|
+
_pre_sync_hooks: Dict[Union[str, None], List[Callable[[Any], Any]]] = {}
|
21
|
+
_post_sync_hooks: Dict[Union[str, None], List[Callable[[Any], Any]]] = {}
|
22
|
+
_actions_daemon_enabled: Dict[str, bool] = {}
|
21
23
|
_locks = {
|
22
24
|
'_api_plugins': RLock(),
|
23
25
|
'_dash_plugins': RLock(),
|
24
26
|
'_pre_sync_hooks': RLock(),
|
25
27
|
'_post_sync_hooks': RLock(),
|
28
|
+
'_actions_daemon_enabled': RLock(),
|
26
29
|
'__path__': RLock(),
|
27
30
|
'sys.path': RLock(),
|
28
31
|
'internal_plugins': RLock(),
|
@@ -53,11 +56,14 @@ __pdoc__ = {
|
|
53
56
|
|
54
57
|
|
55
58
|
def make_action(
|
56
|
-
function: Callable[[Any], Any],
|
59
|
+
function: Optional[Callable[[Any], Any]] = None,
|
57
60
|
shell: bool = False,
|
58
61
|
activate: bool = True,
|
59
62
|
deactivate: bool = True,
|
60
|
-
debug: bool = False
|
63
|
+
debug: bool = False,
|
64
|
+
daemon: bool = True,
|
65
|
+
skip_if_loaded: bool = True,
|
66
|
+
_plugin_name: Optional[str] = None,
|
61
67
|
) -> Callable[[Any], Any]:
|
62
68
|
"""
|
63
69
|
Make a function a Meerschaum action. Useful for plugins that are adding multiple actions.
|
@@ -84,25 +90,36 @@ def make_action(
|
|
84
90
|
... return True, "Success"
|
85
91
|
>>>
|
86
92
|
"""
|
93
|
+
def _decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
94
|
+
from meerschaum.actions import actions, _custom_actions_plugins, _plugins_actions
|
95
|
+
if skip_if_loaded and func.__name__ in actions:
|
96
|
+
return func
|
87
97
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
plugin_name = (
|
92
|
-
package_name.split('.')[1]
|
93
|
-
if package_name.startswith('plugins.') else None
|
94
|
-
)
|
95
|
-
plugin = Plugin(plugin_name) if plugin_name else None
|
96
|
-
|
97
|
-
if debug:
|
98
|
-
from meerschaum.utils.debug import dprint
|
99
|
-
dprint(
|
100
|
-
f"Adding action '{function.__name__}' from plugin " +
|
101
|
-
f"'{plugin}'..."
|
98
|
+
from meerschaum.config.paths import PLUGINS_RESOURCES_PATH
|
99
|
+
plugin_name = (
|
100
|
+
func.__name__.split(f"{PLUGINS_RESOURCES_PATH.stem}.", maxsplit=1)[-1].split('.')[0]
|
102
101
|
)
|
102
|
+
plugin = Plugin(plugin_name) if plugin_name else None
|
103
103
|
|
104
|
-
|
105
|
-
|
104
|
+
if debug:
|
105
|
+
from meerschaum.utils.debug import dprint
|
106
|
+
dprint(
|
107
|
+
f"Adding action '{func.__name__}' from plugin "
|
108
|
+
f"'{plugin}'..."
|
109
|
+
)
|
110
|
+
|
111
|
+
actions[func.__name__] = func
|
112
|
+
_custom_actions_plugins[func.__name__] = plugin_name
|
113
|
+
if plugin_name not in _plugins_actions:
|
114
|
+
_plugins_actions[plugin_name] = []
|
115
|
+
_plugins_actions[plugin_name].append(func.__name__)
|
116
|
+
if not daemon:
|
117
|
+
_actions_daemon_enabled[func.__name__] = False
|
118
|
+
return func
|
119
|
+
|
120
|
+
if function is None:
|
121
|
+
return _decorator
|
122
|
+
return _decorator(function)
|
106
123
|
|
107
124
|
|
108
125
|
def pre_sync_hook(
|
@@ -130,10 +147,11 @@ def pre_sync_hook(
|
|
130
147
|
>>>
|
131
148
|
"""
|
132
149
|
with _locks['_pre_sync_hooks']:
|
150
|
+
plugin_name = _get_parent_plugin()
|
133
151
|
try:
|
134
|
-
if
|
135
|
-
_pre_sync_hooks[
|
136
|
-
_pre_sync_hooks[
|
152
|
+
if plugin_name not in _pre_sync_hooks:
|
153
|
+
_pre_sync_hooks[plugin_name] = []
|
154
|
+
_pre_sync_hooks[plugin_name].append(function)
|
137
155
|
except Exception as e:
|
138
156
|
from meerschaum.utils.warnings import warn
|
139
157
|
warn(e)
|
@@ -170,9 +188,10 @@ def post_sync_hook(
|
|
170
188
|
"""
|
171
189
|
with _locks['_post_sync_hooks']:
|
172
190
|
try:
|
173
|
-
|
174
|
-
|
175
|
-
|
191
|
+
plugin_name = _get_parent_plugin()
|
192
|
+
if plugin_name not in _post_sync_hooks:
|
193
|
+
_post_sync_hooks[plugin_name] = []
|
194
|
+
_post_sync_hooks[plugin_name].append(function)
|
176
195
|
except Exception as e:
|
177
196
|
from meerschaum.utils.warnings import warn
|
178
197
|
warn(e)
|
@@ -180,6 +199,7 @@ def post_sync_hook(
|
|
180
199
|
|
181
200
|
|
182
201
|
_plugin_endpoints_to_pages = {}
|
202
|
+
_plugins_web_pages = {}
|
183
203
|
def web_page(
|
184
204
|
page: Union[str, None, Callable[[Any], Any]] = None,
|
185
205
|
login_required: bool = True,
|
@@ -226,11 +246,7 @@ def web_page(
|
|
226
246
|
)
|
227
247
|
)
|
228
248
|
|
229
|
-
|
230
|
-
plugin_name = (
|
231
|
-
package_name.split('.')[1]
|
232
|
-
if package_name.startswith('plugins.') else None
|
233
|
-
)
|
249
|
+
plugin_name = _get_parent_plugin()
|
234
250
|
page_group = page_group or plugin_name
|
235
251
|
if page_group not in _plugin_endpoints_to_pages:
|
236
252
|
_plugin_endpoints_to_pages[page_group] = {}
|
@@ -240,6 +256,9 @@ def web_page(
|
|
240
256
|
'skip_navbar': skip_navbar,
|
241
257
|
'page_key': page_key,
|
242
258
|
}
|
259
|
+
if plugin_name not in _plugins_web_pages:
|
260
|
+
_plugins_web_pages[plugin_name] = []
|
261
|
+
_plugins_web_pages[plugin_name].append(_func)
|
243
262
|
return wrapper
|
244
263
|
|
245
264
|
if callable(page):
|
@@ -258,10 +277,11 @@ def dash_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
|
258
277
|
Execute the function when starting the Dash application.
|
259
278
|
"""
|
260
279
|
with _locks['_dash_plugins']:
|
280
|
+
plugin_name = _get_parent_plugin()
|
261
281
|
try:
|
262
|
-
if
|
263
|
-
_dash_plugins[
|
264
|
-
_dash_plugins[
|
282
|
+
if plugin_name not in _dash_plugins:
|
283
|
+
_dash_plugins[plugin_name] = []
|
284
|
+
_dash_plugins[plugin_name].append(function)
|
265
285
|
except Exception as e:
|
266
286
|
from meerschaum.utils.warnings import warn
|
267
287
|
warn(e)
|
@@ -299,16 +319,19 @@ def api_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
|
299
319
|
|
300
320
|
|
301
321
|
_synced_symlinks: bool = False
|
322
|
+
_injected_plugin_symlinks = set()
|
302
323
|
def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
303
324
|
"""
|
304
|
-
Update the plugins
|
325
|
+
Update the plugins' internal symlinks.
|
305
326
|
"""
|
306
327
|
global _synced_symlinks
|
307
328
|
with _locks['_synced_symlinks']:
|
308
329
|
if _synced_symlinks:
|
309
330
|
return
|
310
331
|
|
311
|
-
import
|
332
|
+
import os
|
333
|
+
import pathlib
|
334
|
+
import time
|
312
335
|
from collections import defaultdict
|
313
336
|
import importlib.util
|
314
337
|
from meerschaum.utils.misc import flatten_list, make_symlink, is_symlink
|
@@ -321,6 +344,7 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
321
344
|
PLUGINS_INIT_PATH,
|
322
345
|
PLUGINS_DIR_PATHS,
|
323
346
|
PLUGINS_INTERNAL_LOCK_PATH,
|
347
|
+
PLUGINS_INJECTED_RESOURCES_PATH,
|
324
348
|
)
|
325
349
|
|
326
350
|
### If the lock file exists, sleep for up to a second or until it's removed before continuing.
|
@@ -354,7 +378,7 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
354
378
|
try:
|
355
379
|
from importlib.metadata import entry_points
|
356
380
|
except ImportError:
|
357
|
-
importlib_metadata = attempt_import('importlib_metadata', lazy=False)
|
381
|
+
importlib_metadata = mrsm.attempt_import('importlib_metadata', lazy=False)
|
358
382
|
entry_points = importlib_metadata.entry_points
|
359
383
|
|
360
384
|
### NOTE: Allow plugins to be installed via `pip`.
|
@@ -382,23 +406,27 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
382
406
|
|
383
407
|
PLUGINS_RESOURCES_PATH.mkdir(exist_ok=True)
|
384
408
|
|
385
|
-
existing_symlinked_paths =
|
386
|
-
(
|
409
|
+
existing_symlinked_paths = {
|
410
|
+
_existing_symlink: pathlib.Path(os.path.realpath(_existing_symlink))
|
387
411
|
for item in os.listdir(PLUGINS_RESOURCES_PATH)
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
412
|
+
if is_symlink(_existing_symlink := (PLUGINS_RESOURCES_PATH / item))
|
413
|
+
}
|
414
|
+
injected_symlinked_paths = {
|
415
|
+
_injected_symlink: pathlib.Path(os.path.realpath(_injected_symlink))
|
416
|
+
for item in os.listdir(PLUGINS_INJECTED_RESOURCES_PATH)
|
417
|
+
if is_symlink(_injected_symlink := (PLUGINS_INJECTED_RESOURCES_PATH / item))
|
418
|
+
}
|
392
419
|
plugins_to_be_symlinked = list(flatten_list(
|
393
420
|
[
|
394
421
|
[
|
395
|
-
(plugins_path / item)
|
422
|
+
pathlib.Path(os.path.realpath(plugins_path / item))
|
396
423
|
for item in os.listdir(plugins_path)
|
397
424
|
if (
|
398
425
|
not item.startswith('.')
|
399
426
|
) and (item not in ('__pycache__', '__init__.py'))
|
400
427
|
]
|
401
428
|
for plugins_path in PLUGINS_DIR_PATHS
|
429
|
+
if plugins_path.exists()
|
402
430
|
]
|
403
431
|
))
|
404
432
|
plugins_to_be_symlinked.extend(packaged_plugin_paths)
|
@@ -413,16 +441,19 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
413
441
|
if warn:
|
414
442
|
_warn(f"Found duplicate plugins named '{plugin_name}'.")
|
415
443
|
|
416
|
-
for plugin_symlink_path in existing_symlinked_paths:
|
417
|
-
real_path = pathlib.Path(os.path.realpath(plugin_symlink_path))
|
444
|
+
for plugin_symlink_path, real_path in existing_symlinked_paths.items():
|
418
445
|
|
419
446
|
### Remove invalid symlinks.
|
420
447
|
if real_path not in plugins_to_be_symlinked:
|
421
|
-
if
|
448
|
+
if plugin_symlink_path in _injected_plugin_symlinks:
|
449
|
+
continue
|
450
|
+
if plugin_symlink_path in injected_symlinked_paths:
|
451
|
+
continue
|
452
|
+
if real_path in injected_symlinked_paths.values():
|
422
453
|
continue
|
423
454
|
try:
|
424
455
|
plugin_symlink_path.unlink()
|
425
|
-
except Exception
|
456
|
+
except Exception:
|
426
457
|
pass
|
427
458
|
|
428
459
|
### Remove valid plugins from the to-be-symlinked list.
|
@@ -499,13 +530,13 @@ def import_plugins(
|
|
499
530
|
|
500
531
|
"""
|
501
532
|
import sys
|
502
|
-
import os
|
503
533
|
import importlib
|
504
534
|
from meerschaum.utils.misc import flatten_list
|
505
535
|
from meerschaum.config._paths import PLUGINS_RESOURCES_PATH
|
506
536
|
from meerschaum.utils.venv import is_venv_active, activate_venv, deactivate_venv, Venv
|
507
537
|
from meerschaum.utils.warnings import warn as _warn
|
508
538
|
plugins_to_import = list(plugins_to_import)
|
539
|
+
prepended_sys_path = False
|
509
540
|
with _locks['sys.path']:
|
510
541
|
|
511
542
|
### Since plugins may depend on other plugins,
|
@@ -518,6 +549,7 @@ def import_plugins(
|
|
518
549
|
already_active_venvs = [is_venv_active(plugin_name) for plugin_name in plugins_names]
|
519
550
|
|
520
551
|
if not sys.path or sys.path[0] != str(PLUGINS_RESOURCES_PATH.parent):
|
552
|
+
prepended_sys_path = True
|
521
553
|
sys.path.insert(0, str(PLUGINS_RESOURCES_PATH.parent))
|
522
554
|
|
523
555
|
if not plugins_to_import:
|
@@ -563,9 +595,9 @@ def import_plugins(
|
|
563
595
|
imported_plugins.append(None)
|
564
596
|
|
565
597
|
if imported_plugins is None and warn:
|
566
|
-
_warn(
|
598
|
+
_warn("Failed to import plugins.", stacklevel=3)
|
567
599
|
|
568
|
-
if str(PLUGINS_RESOURCES_PATH.parent) in sys.path:
|
600
|
+
if prepended_sys_path and str(PLUGINS_RESOURCES_PATH.parent) in sys.path:
|
569
601
|
sys.path.remove(str(PLUGINS_RESOURCES_PATH.parent))
|
570
602
|
|
571
603
|
if isinstance(imported_plugins, list):
|
@@ -623,7 +655,7 @@ def from_plugin_import(plugin_import_name: str, *attrs: str) -> Any:
|
|
623
655
|
attrs_to_return = []
|
624
656
|
with mrsm.Venv(plugin):
|
625
657
|
if plugin.module is None:
|
626
|
-
|
658
|
+
raise ImportError(f"Unable to import plugin '{plugin}'.")
|
627
659
|
|
628
660
|
try:
|
629
661
|
submodule = importlib.import_module(submodule_import_name)
|
@@ -656,16 +688,23 @@ def from_plugin_import(plugin_import_name: str, *attrs: str) -> Any:
|
|
656
688
|
return tuple(attrs_to_return)
|
657
689
|
|
658
690
|
|
659
|
-
|
691
|
+
_loaded_plugins: bool = False
|
692
|
+
def load_plugins(
|
693
|
+
skip_if_loaded: bool = True,
|
694
|
+
shell: bool = False,
|
695
|
+
debug: bool = False,
|
696
|
+
) -> None:
|
660
697
|
"""
|
661
698
|
Import Meerschaum plugins and update the actions dictionary.
|
662
699
|
"""
|
700
|
+
global _loaded_plugins
|
701
|
+
if skip_if_loaded and _loaded_plugins:
|
702
|
+
return
|
703
|
+
|
663
704
|
from inspect import isfunction, getmembers
|
664
705
|
from meerschaum.actions import __all__ as _all, modules
|
665
706
|
from meerschaum.config._paths import PLUGINS_RESOURCES_PATH
|
666
707
|
from meerschaum.utils.packages import get_modules_from_package
|
667
|
-
if debug:
|
668
|
-
from meerschaum.utils.debug import dprint
|
669
708
|
|
670
709
|
_plugins_names, plugins_modules = get_modules_from_package(
|
671
710
|
import_plugins(),
|
@@ -676,7 +715,8 @@ def load_plugins(debug: bool = False, shell: bool = False) -> None:
|
|
676
715
|
### I'm appending here to keep from redefining the modules list.
|
677
716
|
new_modules = (
|
678
717
|
[
|
679
|
-
mod
|
718
|
+
mod
|
719
|
+
for mod in modules
|
680
720
|
if not mod.__name__.startswith(PLUGINS_RESOURCES_PATH.stem + '.')
|
681
721
|
]
|
682
722
|
+ plugins_modules
|
@@ -692,7 +732,120 @@ def load_plugins(debug: bool = False, shell: bool = False) -> None:
|
|
692
732
|
if not isfunction(func):
|
693
733
|
continue
|
694
734
|
if name == module.__name__.split('.')[-1]:
|
695
|
-
make_action(
|
735
|
+
make_action(
|
736
|
+
func,
|
737
|
+
**{'shell': shell, 'debug': debug},
|
738
|
+
_plugin_name=name,
|
739
|
+
skip_if_loaded=True,
|
740
|
+
)
|
741
|
+
|
742
|
+
_loaded_plugins = True
|
743
|
+
|
744
|
+
|
745
|
+
def unload_custom_actions(plugins: Optional[List[str]] = None, debug: bool = False) -> None:
|
746
|
+
"""
|
747
|
+
Unload the custom actions added by plugins.
|
748
|
+
"""
|
749
|
+
from meerschaum.actions import (
|
750
|
+
actions,
|
751
|
+
_custom_actions_plugins,
|
752
|
+
_plugins_actions,
|
753
|
+
)
|
754
|
+
from meerschaum._internal.entry import _shell
|
755
|
+
import meerschaum._internal.shell as shell_pkg
|
756
|
+
|
757
|
+
plugins = plugins or list(_plugins_actions.keys())
|
758
|
+
|
759
|
+
for plugin in plugins:
|
760
|
+
action_names = _plugins_actions.get(plugin, [])
|
761
|
+
actions_to_remove = {
|
762
|
+
action_name: actions.get(action_name, None)
|
763
|
+
for action_name in action_names
|
764
|
+
}
|
765
|
+
for action_name in action_names:
|
766
|
+
_ = actions.pop(action_name, None)
|
767
|
+
_ = _custom_actions_plugins.pop(action_name, None)
|
768
|
+
_ = _actions_daemon_enabled.pop(action_name, None)
|
769
|
+
|
770
|
+
_ = _plugins_actions.pop(plugin, None)
|
771
|
+
shell_pkg._remove_shell_actions(
|
772
|
+
_shell=_shell,
|
773
|
+
actions=actions_to_remove,
|
774
|
+
)
|
775
|
+
|
776
|
+
|
777
|
+
def unload_plugins(
|
778
|
+
plugins: Optional[List[str]] = None,
|
779
|
+
remove_symlinks: bool = True,
|
780
|
+
debug: bool = False,
|
781
|
+
) -> None:
|
782
|
+
"""
|
783
|
+
Unload the specified plugins from memory.
|
784
|
+
"""
|
785
|
+
global _loaded_plugins
|
786
|
+
import sys
|
787
|
+
from meerschaum.config.paths import PLUGINS_RESOURCES_PATH, PLUGINS_INJECTED_RESOURCES_PATH
|
788
|
+
from meerschaum.connectors import unload_plugin_connectors
|
789
|
+
if debug:
|
790
|
+
from meerschaum.utils.warnings import dprint
|
791
|
+
|
792
|
+
_loaded_plugins = False
|
793
|
+
|
794
|
+
plugins = plugins or get_plugins_names()
|
795
|
+
if debug:
|
796
|
+
dprint(f"Unloading plugins: {plugins}")
|
797
|
+
|
798
|
+
unload_custom_actions(plugins, debug=debug)
|
799
|
+
unload_plugin_connectors(plugins, debug=debug)
|
800
|
+
|
801
|
+
module_prefix = f"{PLUGINS_RESOURCES_PATH.stem}."
|
802
|
+
loaded_modules = [mod_name for mod_name in sys.modules if mod_name.startswith(module_prefix)]
|
803
|
+
|
804
|
+
for plugin_name in plugins:
|
805
|
+
for mod_name in loaded_modules:
|
806
|
+
if mod_name[len(PLUGINS_RESOURCES_PATH.stem):].startswith(plugin_name):
|
807
|
+
_ = sys.modules.pop(mod_name, None)
|
808
|
+
|
809
|
+
### Unload sync hooks.
|
810
|
+
_ = _pre_sync_hooks.pop(plugin_name, None)
|
811
|
+
_ = _post_sync_hooks.pop(plugin_name, None)
|
812
|
+
|
813
|
+
### Unload API endpoints and pages.
|
814
|
+
_ = _dash_plugins.pop(plugin_name, None)
|
815
|
+
web_page_funcs = _plugins_web_pages.pop(plugin_name, None) or []
|
816
|
+
page_groups_to_pop = []
|
817
|
+
for page_group, page_functions in _plugin_endpoints_to_pages.items():
|
818
|
+
page_functions_to_pop = [
|
819
|
+
page_str
|
820
|
+
for page_str, page_payload in page_functions.items()
|
821
|
+
if page_payload.get('function', None) in web_page_funcs
|
822
|
+
]
|
823
|
+
for page_str in page_functions_to_pop:
|
824
|
+
page_functions.pop(page_str, None)
|
825
|
+
if not page_functions:
|
826
|
+
page_groups_to_pop.append(page_group)
|
827
|
+
|
828
|
+
for page_group in page_groups_to_pop:
|
829
|
+
_plugin_endpoints_to_pages.pop(page_group, None)
|
830
|
+
|
831
|
+
### Remove all but injected symlinks.
|
832
|
+
if remove_symlinks:
|
833
|
+
dir_symlink_path = PLUGINS_RESOURCES_PATH / plugin_name
|
834
|
+
dir_symlink_injected_path = PLUGINS_INJECTED_RESOURCES_PATH / plugin_name
|
835
|
+
file_symlink_path = PLUGINS_RESOURCES_PATH / f"{plugin_name}.py"
|
836
|
+
file_symlink_injected_path = PLUGINS_INJECTED_RESOURCES_PATH / f"{plugin_name}.py"
|
837
|
+
|
838
|
+
try:
|
839
|
+
if dir_symlink_path.exists() and not dir_symlink_injected_path.exists():
|
840
|
+
dir_symlink_path.unlink()
|
841
|
+
except Exception:
|
842
|
+
pass
|
843
|
+
|
844
|
+
try:
|
845
|
+
if file_symlink_path.exists() and not file_symlink_injected_path.exists():
|
846
|
+
file_symlink_path.unlink()
|
847
|
+
except Exception:
|
848
|
+
pass
|
696
849
|
|
697
850
|
|
698
851
|
def reload_plugins(plugins: Optional[List[str]] = None, debug: bool = False) -> None:
|
@@ -705,19 +858,11 @@ def reload_plugins(plugins: Optional[List[str]] = None, debug: bool = False) ->
|
|
705
858
|
The plugins to reload. `None` will reload all plugins.
|
706
859
|
|
707
860
|
"""
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
plugins = get_plugins_names()
|
714
|
-
for plugin_name in plugins:
|
715
|
-
if debug:
|
716
|
-
dprint(f"Reloading plugin '{plugin_name}'...")
|
717
|
-
mod_name = 'plugins.' + str(plugin_name)
|
718
|
-
if mod_name in sys.modules:
|
719
|
-
del sys.modules[mod_name]
|
720
|
-
load_plugins(debug=debug)
|
861
|
+
global _synced_symlinks
|
862
|
+
unload_plugins(plugins, debug=debug)
|
863
|
+
_synced_symlinks = False
|
864
|
+
sync_plugins_symlinks(debug=debug)
|
865
|
+
load_plugins(skip_if_loaded=False, debug=debug)
|
721
866
|
|
722
867
|
|
723
868
|
def get_plugins(*to_load, try_import: bool = True) -> Union[Tuple[Plugin], Plugin]:
|
@@ -737,7 +882,8 @@ def get_plugins(*to_load, try_import: bool = True) -> Union[Tuple[Plugin], Plugi
|
|
737
882
|
import os
|
738
883
|
sync_plugins_symlinks()
|
739
884
|
_plugins = [
|
740
|
-
Plugin(name)
|
885
|
+
Plugin(name)
|
886
|
+
for name in (
|
741
887
|
to_load or [
|
742
888
|
(
|
743
889
|
name if (PLUGINS_RESOURCES_PATH / name).is_dir()
|
@@ -797,8 +943,8 @@ def add_plugin_argument(*args, **kwargs) -> None:
|
|
797
943
|
>>>
|
798
944
|
"""
|
799
945
|
from meerschaum._internal.arguments._parser import groups, _seen_plugin_args, parser
|
800
|
-
from meerschaum.utils.warnings import warn
|
801
|
-
_parent_plugin_name = _get_parent_plugin(
|
946
|
+
from meerschaum.utils.warnings import warn
|
947
|
+
_parent_plugin_name = _get_parent_plugin()
|
802
948
|
title = f"Plugin '{_parent_plugin_name}' options" if _parent_plugin_name else 'Custom options'
|
803
949
|
group_key = 'plugin_' + (_parent_plugin_name or '')
|
804
950
|
if group_key not in groups:
|
@@ -814,12 +960,62 @@ def add_plugin_argument(*args, **kwargs) -> None:
|
|
814
960
|
warn(e)
|
815
961
|
|
816
962
|
|
817
|
-
def
|
963
|
+
def inject_plugin_path(
|
964
|
+
plugin_path: pathlib.Path,
|
965
|
+
plugins_resources_path: Optional[pathlib.Path] = None) -> None:
|
966
|
+
"""
|
967
|
+
Inject a plugin as a symlink into the internal `plugins` directory.
|
968
|
+
|
969
|
+
Parameters
|
970
|
+
----------
|
971
|
+
plugin_path: pathlib.Path
|
972
|
+
The path to the plugin's source module.
|
973
|
+
"""
|
974
|
+
from meerschaum.utils.misc import make_symlink
|
975
|
+
if plugins_resources_path is None:
|
976
|
+
from meerschaum.config.paths import PLUGINS_RESOURCES_PATH, PLUGINS_INJECTED_RESOURCES_PATH
|
977
|
+
plugins_resources_path = PLUGINS_RESOURCES_PATH
|
978
|
+
plugins_injected_resources_path = PLUGINS_INJECTED_RESOURCES_PATH
|
979
|
+
else:
|
980
|
+
plugins_injected_resources_path = plugins_resources_path / '.injected'
|
981
|
+
|
982
|
+
if plugin_path.is_dir():
|
983
|
+
plugin_name = plugin_path.name
|
984
|
+
dest_path = plugins_resources_path / plugin_name
|
985
|
+
injected_path = plugins_injected_resources_path / plugin_name
|
986
|
+
elif plugin_path.name == '__init__.py':
|
987
|
+
plugin_name = plugin_path.parent.name
|
988
|
+
dest_path = plugins_resources_path / plugin_name
|
989
|
+
injected_path = plugins_injected_resources_path / plugin_name
|
990
|
+
elif plugin_path.name.endswith('.py'):
|
991
|
+
plugin_name = plugin_path.name[:(-1 * len('.py'))]
|
992
|
+
dest_path = plugins_resources_path / plugin_path.name
|
993
|
+
injected_path = plugins_injected_resources_path / plugin_path.name
|
994
|
+
else:
|
995
|
+
raise ValueError(f"Cannot deduce plugin name from path '{plugin_path}'.")
|
996
|
+
|
997
|
+
_injected_plugin_symlinks.add(dest_path)
|
998
|
+
make_symlink(plugin_path, dest_path)
|
999
|
+
make_symlink(plugin_path, injected_path)
|
1000
|
+
|
1001
|
+
|
1002
|
+
def _get_parent_plugin(stacklevel: Union[int, Tuple[int, ...]] = (1, 2, 3, 4)) -> Union[str, None]:
|
818
1003
|
"""If this function is called from outside a Meerschaum plugin, it will return None."""
|
819
|
-
import inspect
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
1004
|
+
import inspect
|
1005
|
+
if not isinstance(stacklevel, tuple):
|
1006
|
+
stacklevel = (stacklevel,)
|
1007
|
+
|
1008
|
+
for _level in stacklevel:
|
1009
|
+
try:
|
1010
|
+
parent_globals = inspect.stack()[_level][0].f_globals
|
1011
|
+
global_name = parent_globals.get('__name__', '')
|
1012
|
+
if global_name.startswith('meerschaum.'):
|
1013
|
+
continue
|
1014
|
+
plugin_name = global_name.replace('plugins.', '').split('.')[0]
|
1015
|
+
if plugin_name.startswith('_') or plugin_name == 'importlib':
|
1016
|
+
continue
|
1017
|
+
return plugin_name
|
1018
|
+
except Exception:
|
1019
|
+
continue
|
1020
|
+
|
1021
|
+
return None
|
meerschaum/utils/_get_pipes.py
CHANGED
@@ -128,11 +128,12 @@ def get_pipes(
|
|
128
128
|
```
|
129
129
|
"""
|
130
130
|
|
131
|
+
import json
|
132
|
+
from collections import defaultdict
|
131
133
|
from meerschaum.config import get_config
|
132
134
|
from meerschaum.utils.warnings import error
|
133
135
|
from meerschaum.utils.misc import filter_keywords
|
134
136
|
from meerschaum.utils.pool import get_pool
|
135
|
-
from collections import defaultdict
|
136
137
|
|
137
138
|
if connector_keys is None:
|
138
139
|
connector_keys = []
|
@@ -194,19 +195,42 @@ def get_pipes(
|
|
194
195
|
### obtained from the chosen `method`.
|
195
196
|
from meerschaum import Pipe
|
196
197
|
pipes = {}
|
197
|
-
for
|
198
|
+
for keys_tuple in result:
|
199
|
+
ck, mk, lk = keys_tuple[0], keys_tuple[1], keys_tuple[2]
|
200
|
+
pipe_tags_or_parameters = keys_tuple[3] if len(keys_tuple) == 4 else None
|
201
|
+
pipe_parameters = (
|
202
|
+
pipe_tags_or_parameters
|
203
|
+
if isinstance(pipe_tags_or_parameters, (dict, str))
|
204
|
+
else None
|
205
|
+
)
|
206
|
+
if isinstance(pipe_parameters, str):
|
207
|
+
pipe_parameters = json.loads(pipe_parameters)
|
208
|
+
pipe_tags = (
|
209
|
+
pipe_tags_or_parameters
|
210
|
+
if isinstance(pipe_tags_or_parameters, list)
|
211
|
+
else (
|
212
|
+
pipe_tags_or_parameters.get('tags', None)
|
213
|
+
if isinstance(pipe_tags_or_parameters, dict)
|
214
|
+
else None
|
215
|
+
)
|
216
|
+
)
|
217
|
+
|
198
218
|
if ck not in pipes:
|
199
219
|
pipes[ck] = {}
|
200
220
|
|
201
221
|
if mk not in pipes[ck]:
|
202
222
|
pipes[ck][mk] = {}
|
203
223
|
|
204
|
-
|
224
|
+
pipe = Pipe(
|
205
225
|
ck, mk, lk,
|
206
226
|
mrsm_instance = connector,
|
227
|
+
parameters = pipe_parameters,
|
228
|
+
tags = pipe_tags,
|
207
229
|
debug = debug,
|
208
230
|
**filter_keywords(Pipe, **kw)
|
209
231
|
)
|
232
|
+
pipe.__dict__['_tags'] = pipe_tags
|
233
|
+
pipes[ck][mk][lk] = pipe
|
210
234
|
|
211
235
|
if not as_list and not as_tags_dict:
|
212
236
|
return pipes
|
@@ -218,7 +242,9 @@ def get_pipes(
|
|
218
242
|
|
219
243
|
pool = get_pool(workers=(workers if connector.IS_THREAD_SAFE else 1))
|
220
244
|
def gather_pipe_tags(pipe: mrsm.Pipe) -> Tuple[mrsm.Pipe, List[str]]:
|
221
|
-
|
245
|
+
_tags = pipe.__dict__.get('_tags', None)
|
246
|
+
gathered_tags = _tags if _tags is not None else pipe.tags
|
247
|
+
return pipe, (gathered_tags or [])
|
222
248
|
|
223
249
|
tags_pipes = defaultdict(lambda: [])
|
224
250
|
pipes_tags = dict(pool.map(gather_pipe_tags, pipes_list))
|