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.
- meerschaum/_internal/entry.py +36 -11
- meerschaum/_internal/shell/Shell.py +40 -16
- meerschaum/_internal/term/__init__.py +3 -2
- meerschaum/_internal/term/tools.py +1 -1
- meerschaum/actions/api.py +65 -31
- meerschaum/actions/python.py +56 -24
- meerschaum/actions/start.py +2 -4
- meerschaum/actions/uninstall.py +5 -9
- meerschaum/actions/upgrade.py +11 -3
- meerschaum/api/__init__.py +1 -0
- meerschaum/api/dash/callbacks/__init__.py +4 -0
- meerschaum/api/dash/callbacks/custom.py +39 -0
- meerschaum/api/dash/callbacks/dashboard.py +39 -6
- meerschaum/api/dash/callbacks/login.py +3 -1
- meerschaum/api/dash/components.py +5 -2
- meerschaum/api/dash/pipes.py +145 -97
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_paths.py +12 -12
- meerschaum/config/_version.py +1 -1
- meerschaum/config/paths.py +10 -0
- meerschaum/config/static/__init__.py +1 -1
- meerschaum/connectors/__init__.py +9 -2
- meerschaum/connectors/sql/_cli.py +7 -1
- meerschaum/connectors/sql/_pipes.py +6 -0
- meerschaum/core/Pipe/__init__.py +5 -0
- meerschaum/core/Pipe/_sync.py +2 -3
- meerschaum/plugins/__init__.py +67 -9
- meerschaum/utils/daemon/Daemon.py +7 -2
- meerschaum/utils/misc.py +6 -0
- meerschaum/utils/packages/__init__.py +212 -53
- meerschaum/utils/packages/_packages.py +3 -2
- meerschaum/utils/process.py +12 -2
- meerschaum/utils/schedule.py +10 -3
- meerschaum/utils/venv/__init__.py +46 -11
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/METADATA +13 -9
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/RECORD +42 -40
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.0rc4.dist-info → meerschaum-2.2.2.dist-info}/zip-safe +0 -0
meerschaum/plugins/__init__.py
CHANGED
@@ -7,6 +7,7 @@ Expose plugin management APIs from the `meerschaum.plugins` module.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
import functools
|
10
11
|
from meerschaum.utils.typing import Callable, Any, Union, Optional, Dict, List, Tuple
|
11
12
|
from meerschaum.utils.threading import Lock, RLock
|
12
13
|
from meerschaum.plugins._Plugin import Plugin
|
@@ -16,6 +17,7 @@ _pre_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
|
|
16
17
|
_post_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
|
17
18
|
_locks = {
|
18
19
|
'_api_plugins': RLock(),
|
20
|
+
'_dash_plugins': RLock(),
|
19
21
|
'_pre_sync_hooks': RLock(),
|
20
22
|
'_post_sync_hooks': RLock(),
|
21
23
|
'__path__': RLock(),
|
@@ -156,6 +158,71 @@ def post_sync_hook(
|
|
156
158
|
return function
|
157
159
|
|
158
160
|
|
161
|
+
_plugin_endpoints_to_pages = {}
|
162
|
+
def web_page(
|
163
|
+
page: Union[str, None, Callable[[Any], Any]] = None,
|
164
|
+
login_required: bool = True,
|
165
|
+
**kwargs
|
166
|
+
) -> Any:
|
167
|
+
"""
|
168
|
+
Quickly add pages to the dash application.
|
169
|
+
|
170
|
+
Examples
|
171
|
+
--------
|
172
|
+
>>> import meerschaum as mrsm
|
173
|
+
>>> from meerschaum.plugins import web_page
|
174
|
+
>>> html = mrsm.attempt_import('dash.html')
|
175
|
+
>>>
|
176
|
+
>>> @web_page('foo/bar', login_required=False)
|
177
|
+
>>> def foo_bar():
|
178
|
+
... return html.Div([html.H1("Hello, World!")])
|
179
|
+
>>>
|
180
|
+
"""
|
181
|
+
page_str = None
|
182
|
+
|
183
|
+
def _decorator(_func: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
184
|
+
nonlocal page_str
|
185
|
+
|
186
|
+
@functools.wraps(_func)
|
187
|
+
def wrapper(*_args, **_kwargs):
|
188
|
+
return _func(*_args, **_kwargs)
|
189
|
+
|
190
|
+
if page_str is None:
|
191
|
+
page_str = _func.__name__
|
192
|
+
|
193
|
+
page_str = page_str.lstrip('/').rstrip('/').strip()
|
194
|
+
_plugin_endpoints_to_pages[page_str] = {
|
195
|
+
'function': _func,
|
196
|
+
'login_required': login_required,
|
197
|
+
}
|
198
|
+
return wrapper
|
199
|
+
|
200
|
+
if callable(page):
|
201
|
+
decorator_to_return = _decorator(page)
|
202
|
+
page_str = page.__name__
|
203
|
+
else:
|
204
|
+
decorator_to_return = _decorator
|
205
|
+
page_str = page
|
206
|
+
|
207
|
+
return decorator_to_return
|
208
|
+
|
209
|
+
|
210
|
+
_dash_plugins = {}
|
211
|
+
def dash_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
212
|
+
"""
|
213
|
+
Execute the function when starting the Dash application.
|
214
|
+
"""
|
215
|
+
with _locks['_dash_plugins']:
|
216
|
+
try:
|
217
|
+
if function.__module__ not in _dash_plugins:
|
218
|
+
_dash_plugins[function.__module__] = []
|
219
|
+
_dash_plugins[function.__module__].append(function)
|
220
|
+
except Exception as e:
|
221
|
+
from meerschaum.utils.warnings import warn
|
222
|
+
warn(e)
|
223
|
+
return function
|
224
|
+
|
225
|
+
|
159
226
|
def api_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
160
227
|
"""
|
161
228
|
Execute the function when initializing the Meerschaum API module.
|
@@ -164,15 +231,6 @@ def api_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
|
164
231
|
|
165
232
|
The FastAPI app will be passed as the only parameter.
|
166
233
|
|
167
|
-
Parameters
|
168
|
-
----------
|
169
|
-
function: Callable[[Any, Any]]
|
170
|
-
The function to be called before starting the Meerschaum API.
|
171
|
-
|
172
|
-
Returns
|
173
|
-
-------
|
174
|
-
Another function (decorator function).
|
175
|
-
|
176
234
|
Examples
|
177
235
|
--------
|
178
236
|
>>> from meerschaum.plugins import api_plugin
|
@@ -465,8 +465,9 @@ class Daemon:
|
|
465
465
|
Handle `SIGINT` within the Daemon context.
|
466
466
|
This method is injected into the `DaemonContext`.
|
467
467
|
"""
|
468
|
-
|
469
|
-
|
468
|
+
from meerschaum.utils.process import signal_handler
|
469
|
+
signal_handler(signal_number, stack_frame)
|
470
|
+
|
470
471
|
self.rotating_log.stop_log_fd_interception(unused_only=False)
|
471
472
|
timer = self.__dict__.get('_log_refresh_timer', None)
|
472
473
|
if timer is not None:
|
@@ -477,6 +478,7 @@ class Daemon:
|
|
477
478
|
daemon_context.close()
|
478
479
|
|
479
480
|
_close_pools()
|
481
|
+
|
480
482
|
import threading
|
481
483
|
for thread in threading.enumerate():
|
482
484
|
if thread.name == 'MainThread':
|
@@ -495,6 +497,9 @@ class Daemon:
|
|
495
497
|
Handle `SIGTERM` within the `Daemon` context.
|
496
498
|
This method is injected into the `DaemonContext`.
|
497
499
|
"""
|
500
|
+
from meerschaum.utils.process import signal_handler
|
501
|
+
signal_handler(signal_number, stack_frame)
|
502
|
+
|
498
503
|
timer = self.__dict__.get('_log_refresh_timer', None)
|
499
504
|
if timer is not None:
|
500
505
|
timer.cancel()
|
meerschaum/utils/misc.py
CHANGED
@@ -1103,6 +1103,12 @@ def is_docker_available() -> bool:
|
|
1103
1103
|
return has_docker
|
1104
1104
|
|
1105
1105
|
|
1106
|
+
def is_android() -> bool:
|
1107
|
+
"""Return `True` if the current platform is Android."""
|
1108
|
+
import sys
|
1109
|
+
return hasattr(sys, 'getandroidapilevel')
|
1110
|
+
|
1111
|
+
|
1106
1112
|
def is_bcp_available() -> bool:
|
1107
1113
|
"""Check if the MSSQL `bcp` utility is installed."""
|
1108
1114
|
import subprocess
|
@@ -46,6 +46,7 @@ def get_module_path(
|
|
46
46
|
"""
|
47
47
|
Get a module's path without importing.
|
48
48
|
"""
|
49
|
+
import site
|
49
50
|
if debug:
|
50
51
|
from meerschaum.utils.debug import dprint
|
51
52
|
if not _try_install_name_on_fail:
|
@@ -54,33 +55,58 @@ def get_module_path(
|
|
54
55
|
import_name_lower = install_name_lower
|
55
56
|
else:
|
56
57
|
import_name_lower = import_name.lower().replace('-', '_')
|
58
|
+
|
57
59
|
vtp = venv_target_path(venv, allow_nonexistent=True, debug=debug)
|
58
60
|
if not vtp.exists():
|
59
61
|
if debug:
|
60
|
-
dprint(
|
62
|
+
dprint(
|
63
|
+
(
|
64
|
+
"Venv '{venv}' does not exist, cannot import "
|
65
|
+
+ f"'{import_name}'."
|
66
|
+
),
|
67
|
+
color = False,
|
68
|
+
)
|
61
69
|
return None
|
70
|
+
|
71
|
+
venv_target_candidate_paths = [vtp]
|
72
|
+
if venv is None:
|
73
|
+
site_user_packages_dirs = [pathlib.Path(site.getusersitepackages())]
|
74
|
+
site_packages_dirs = [pathlib.Path(path) for path in site.getsitepackages()]
|
75
|
+
|
76
|
+
paths_to_add = [
|
77
|
+
path
|
78
|
+
for path in site_user_packages_dirs + site_packages_dirs
|
79
|
+
if path not in venv_target_candidate_paths
|
80
|
+
]
|
81
|
+
venv_target_candidate_paths += paths_to_add
|
82
|
+
|
62
83
|
candidates = []
|
63
|
-
for
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if file_name.endswith('dist_info'):
|
84
|
+
for venv_target_candidate in venv_target_candidate_paths:
|
85
|
+
try:
|
86
|
+
file_names = os.listdir(venv_target_candidate)
|
87
|
+
except FileNotFoundError:
|
68
88
|
continue
|
69
|
-
|
89
|
+
for file_name in file_names:
|
90
|
+
file_name_lower = file_name.lower().replace('-', '_')
|
91
|
+
if not file_name_lower.startswith(import_name_lower):
|
92
|
+
continue
|
93
|
+
if file_name.endswith('dist_info'):
|
94
|
+
continue
|
95
|
+
file_path = venv_target_candidate / file_name
|
70
96
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
97
|
+
### Most likely: Is a directory with __init__.py
|
98
|
+
if file_name_lower == import_name_lower and file_path.is_dir():
|
99
|
+
init_path = file_path / '__init__.py'
|
100
|
+
if init_path.exists():
|
101
|
+
candidates.append(init_path)
|
76
102
|
|
77
|
-
|
78
|
-
|
79
|
-
|
103
|
+
### May be a standalone .py file.
|
104
|
+
elif file_name_lower == import_name_lower + '.py':
|
105
|
+
candidates.append(file_path)
|
80
106
|
|
81
|
-
|
82
|
-
|
83
|
-
|
107
|
+
### Compiled wheels (e.g. pyodbc)
|
108
|
+
elif file_name_lower.startswith(import_name_lower + '.'):
|
109
|
+
candidates.append(file_path)
|
84
110
|
|
85
111
|
if len(candidates) == 1:
|
86
112
|
return candidates[0]
|
@@ -466,12 +492,13 @@ def _get_package_metadata(import_name: str, venv: Optional[str]) -> Dict[str, st
|
|
466
492
|
import re
|
467
493
|
from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
|
468
494
|
install_name = _import_to_install_name(import_name)
|
469
|
-
_args = ['show', install_name]
|
495
|
+
_args = ['pip', 'show', install_name]
|
470
496
|
if venv is not None:
|
471
497
|
cache_dir_path = VIRTENV_RESOURCES_PATH / venv / 'cache'
|
472
|
-
_args += ['--cache-dir',
|
498
|
+
_args += ['--cache-dir', cache_dir_path.as_posix()]
|
499
|
+
|
473
500
|
proc = run_python_package(
|
474
|
-
'
|
501
|
+
'uv', _args,
|
475
502
|
capture_output=True, as_proc=True, venv=venv, universal_newlines=True,
|
476
503
|
)
|
477
504
|
outs, errs = proc.communicate()
|
@@ -680,12 +707,22 @@ def need_update(
|
|
680
707
|
return False
|
681
708
|
|
682
709
|
|
683
|
-
def get_pip(
|
710
|
+
def get_pip(
|
711
|
+
venv: Optional[str] = 'mrsm',
|
712
|
+
color: bool = True,
|
713
|
+
debug: bool = False,
|
714
|
+
) -> bool:
|
684
715
|
"""
|
685
716
|
Download and run the get-pip.py script.
|
686
717
|
|
687
718
|
Parameters
|
688
719
|
----------
|
720
|
+
venv: Optional[str], default 'mrsm'
|
721
|
+
The virtual environment into which to install `pip`.
|
722
|
+
|
723
|
+
color: bool, default True
|
724
|
+
If `True`, force color output.
|
725
|
+
|
689
726
|
debug: bool, default False
|
690
727
|
Verbosity toggle.
|
691
728
|
|
@@ -708,7 +745,7 @@ def get_pip(venv: Optional[str] = 'mrsm', debug: bool=False) -> bool:
|
|
708
745
|
if venv is not None:
|
709
746
|
init_venv(venv=venv, debug=debug)
|
710
747
|
cmd_list = [venv_executable(venv=venv), dest.as_posix()]
|
711
|
-
return subprocess.call(cmd_list, env=_get_pip_os_env()) == 0
|
748
|
+
return subprocess.call(cmd_list, env=_get_pip_os_env(color=color)) == 0
|
712
749
|
|
713
750
|
|
714
751
|
def pip_install(
|
@@ -721,6 +758,8 @@ def pip_install(
|
|
721
758
|
check_pypi: bool = True,
|
722
759
|
check_wheel: bool = True,
|
723
760
|
_uninstall: bool = False,
|
761
|
+
_from_completely_uninstall: bool = False,
|
762
|
+
_install_uv_pip: bool = True,
|
724
763
|
color: bool = True,
|
725
764
|
silent: bool = False,
|
726
765
|
debug: bool = False,
|
@@ -776,7 +815,9 @@ def pip_install(
|
|
776
815
|
|
777
816
|
"""
|
778
817
|
from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
|
818
|
+
from meerschaum.config import get_config
|
779
819
|
from meerschaum.utils.warnings import warn
|
820
|
+
from meerschaum.utils.misc import is_android
|
780
821
|
if args is None:
|
781
822
|
args = ['--upgrade'] if not _uninstall else []
|
782
823
|
if color:
|
@@ -787,10 +828,43 @@ def pip_install(
|
|
787
828
|
have_wheel = venv_contains_package('wheel', venv=venv, debug=debug)
|
788
829
|
|
789
830
|
_args = list(args)
|
790
|
-
have_pip = venv_contains_package('pip', venv=
|
831
|
+
have_pip = venv_contains_package('pip', venv=None, debug=debug)
|
832
|
+
try:
|
833
|
+
import pip
|
834
|
+
have_pip = True
|
835
|
+
except ImportError:
|
836
|
+
have_pip = False
|
837
|
+
try:
|
838
|
+
import uv
|
839
|
+
uv_bin = uv.find_uv_bin()
|
840
|
+
have_uv_pip = True
|
841
|
+
except (ImportError, FileNotFoundError):
|
842
|
+
uv_bin = None
|
843
|
+
have_uv_pip = False
|
844
|
+
if have_pip and not have_uv_pip and _install_uv_pip and not is_android():
|
845
|
+
if not pip_install(
|
846
|
+
'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
|
+
):
|
854
|
+
warn(
|
855
|
+
f"Failed to install `uv` for virtual environment '{venv}'.",
|
856
|
+
color = False,
|
857
|
+
)
|
858
|
+
|
859
|
+
use_uv_pip = (
|
860
|
+
venv_contains_package('uv', venv=None, debug=debug)
|
861
|
+
and uv_bin is not None
|
862
|
+
and venv is not None
|
863
|
+
)
|
864
|
+
|
791
865
|
import sys
|
792
|
-
if not have_pip:
|
793
|
-
if not get_pip(venv=venv, debug=debug):
|
866
|
+
if not have_pip and not use_uv_pip:
|
867
|
+
if not get_pip(venv=venv, color=color, debug=debug):
|
794
868
|
import sys
|
795
869
|
minor = sys.version_info.minor
|
796
870
|
print(
|
@@ -806,13 +880,18 @@ def pip_install(
|
|
806
880
|
|
807
881
|
with Venv(venv, debug=debug):
|
808
882
|
if venv is not None:
|
809
|
-
if
|
883
|
+
if (
|
884
|
+
'--ignore-installed' not in args
|
885
|
+
and '-I' not in _args
|
886
|
+
and not _uninstall
|
887
|
+
and not use_uv_pip
|
888
|
+
):
|
810
889
|
_args += ['--ignore-installed']
|
811
890
|
if '--cache-dir' not in args and not _uninstall:
|
812
891
|
cache_dir_path = VIRTENV_RESOURCES_PATH / venv / 'cache'
|
813
892
|
_args += ['--cache-dir', str(cache_dir_path)]
|
814
893
|
|
815
|
-
if 'pip' not in ' '.join(_args):
|
894
|
+
if 'pip' not in ' '.join(_args) and not use_uv_pip:
|
816
895
|
if check_update and not _uninstall:
|
817
896
|
pip = attempt_import('pip', venv=venv, install=False, debug=debug, lazy=False)
|
818
897
|
if need_update(pip, check_pypi=check_pypi, debug=debug):
|
@@ -820,17 +899,20 @@ def pip_install(
|
|
820
899
|
|
821
900
|
_args = (['install'] if not _uninstall else ['uninstall']) + _args
|
822
901
|
|
823
|
-
if check_wheel and not _uninstall:
|
902
|
+
if check_wheel and not _uninstall and not use_uv_pip:
|
824
903
|
if not have_wheel:
|
825
904
|
if not pip_install(
|
826
|
-
'setuptools', 'wheel',
|
905
|
+
'setuptools', 'wheel', 'uv',
|
827
906
|
venv = venv,
|
828
|
-
check_update = False,
|
829
|
-
|
907
|
+
check_update = False,
|
908
|
+
check_pypi = False,
|
909
|
+
check_wheel = False,
|
910
|
+
debug = debug,
|
911
|
+
_install_uv_pip = False,
|
830
912
|
):
|
831
913
|
warn(
|
832
914
|
(
|
833
|
-
"Failed to install `setuptools` and `
|
915
|
+
"Failed to install `setuptools`, `wheel`, and `uv` for virtual "
|
834
916
|
+ f"environment '{venv}'."
|
835
917
|
),
|
836
918
|
color = False,
|
@@ -838,24 +920,24 @@ def pip_install(
|
|
838
920
|
|
839
921
|
if requirements_file_path is not None:
|
840
922
|
_args.append('-r')
|
841
|
-
_args.append(
|
923
|
+
_args.append(pathlib.Path(requirements_file_path).resolve().as_posix())
|
842
924
|
|
843
925
|
if not ANSI and '--no-color' not in _args:
|
844
926
|
_args.append('--no-color')
|
845
927
|
|
846
|
-
if '--no-input' not in _args:
|
928
|
+
if '--no-input' not in _args and not use_uv_pip:
|
847
929
|
_args.append('--no-input')
|
848
930
|
|
849
|
-
if _uninstall and '-y' not in _args:
|
931
|
+
if _uninstall and '-y' not in _args and not use_uv_pip:
|
850
932
|
_args.append('-y')
|
851
933
|
|
852
|
-
if '--no-warn-conflicts' not in _args and not _uninstall:
|
934
|
+
if '--no-warn-conflicts' not in _args and not _uninstall and not use_uv_pip:
|
853
935
|
_args.append('--no-warn-conflicts')
|
854
936
|
|
855
|
-
if '--disable-pip-version-check' not in _args:
|
937
|
+
if '--disable-pip-version-check' not in _args and not use_uv_pip:
|
856
938
|
_args.append('--disable-pip-version-check')
|
857
939
|
|
858
|
-
if '--target' not in _args and '-t' not in _args and not _uninstall:
|
940
|
+
if '--target' not in _args and '-t' not in _args and not (not use_uv_pip and _uninstall):
|
859
941
|
if venv is not None:
|
860
942
|
_args += ['--target', venv_target_path(venv, debug=debug)]
|
861
943
|
elif (
|
@@ -863,12 +945,14 @@ def pip_install(
|
|
863
945
|
and '-t' not in _args
|
864
946
|
and not inside_venv()
|
865
947
|
and not _uninstall
|
948
|
+
and not use_uv_pip
|
866
949
|
):
|
867
950
|
_args += ['--user']
|
868
951
|
|
869
952
|
if debug:
|
870
953
|
if '-v' not in _args or '-vv' not in _args or '-vvv' not in _args:
|
871
|
-
|
954
|
+
if use_uv_pip:
|
955
|
+
_args.append('--verbose')
|
872
956
|
else:
|
873
957
|
if '-q' not in _args or '-qq' not in _args or '-qqq' not in _args:
|
874
958
|
pass
|
@@ -883,10 +967,10 @@ def pip_install(
|
|
883
967
|
if not silent:
|
884
968
|
print(msg)
|
885
969
|
|
886
|
-
if not
|
970
|
+
if _uninstall and not _from_completely_uninstall and not use_uv_pip:
|
887
971
|
for install_name in _packages:
|
888
972
|
_install_no_version = get_install_no_version(install_name)
|
889
|
-
if _install_no_version in ('pip', 'wheel'):
|
973
|
+
if _install_no_version in ('pip', 'wheel', 'uv'):
|
890
974
|
continue
|
891
975
|
if not completely_uninstall_package(
|
892
976
|
_install_no_version,
|
@@ -896,11 +980,17 @@ def pip_install(
|
|
896
980
|
f"Failed to clean up package '{_install_no_version}'.",
|
897
981
|
)
|
898
982
|
|
983
|
+
### NOTE: Only append the `--prerelease=allow` flag if we explicitly depend on a prerelease.
|
984
|
+
if use_uv_pip:
|
985
|
+
_args.insert(0, 'pip')
|
986
|
+
if not _uninstall and get_prerelease_dependencies(_packages):
|
987
|
+
_args.append('--prerelease=allow')
|
988
|
+
|
899
989
|
rc = run_python_package(
|
900
|
-
'pip',
|
990
|
+
('pip' if not use_uv_pip else 'uv'),
|
901
991
|
_args + _packages,
|
902
|
-
venv =
|
903
|
-
env = _get_pip_os_env(),
|
992
|
+
venv = None,
|
993
|
+
env = _get_pip_os_env(color=color),
|
904
994
|
debug = debug,
|
905
995
|
)
|
906
996
|
if debug:
|
@@ -918,6 +1008,33 @@ def pip_install(
|
|
918
1008
|
return success
|
919
1009
|
|
920
1010
|
|
1011
|
+
def get_prerelease_dependencies(_packages: Optional[List[str]] = None):
|
1012
|
+
"""
|
1013
|
+
Return a list of explicitly prerelease dependencies from a list of packages.
|
1014
|
+
"""
|
1015
|
+
if _packages is None:
|
1016
|
+
_packages = list(all_packages.keys())
|
1017
|
+
prelrease_strings = ['dev', 'rc', 'a']
|
1018
|
+
prerelease_packages = []
|
1019
|
+
for install_name in _packages:
|
1020
|
+
_install_no_version = get_install_no_version(install_name)
|
1021
|
+
import_name = _install_to_import_name(install_name)
|
1022
|
+
install_with_version = _import_to_install_name(import_name)
|
1023
|
+
version_only = (
|
1024
|
+
install_with_version.lower().replace(_install_no_version.lower(), '')
|
1025
|
+
.split(']')[-1]
|
1026
|
+
)
|
1027
|
+
|
1028
|
+
is_prerelease = False
|
1029
|
+
for prelrease_string in prelrease_strings:
|
1030
|
+
if prelrease_string in version_only:
|
1031
|
+
is_prerelease = True
|
1032
|
+
|
1033
|
+
if is_prerelease:
|
1034
|
+
prerelease_packages.append(install_name)
|
1035
|
+
return prerelease_packages
|
1036
|
+
|
1037
|
+
|
921
1038
|
def completely_uninstall_package(
|
922
1039
|
install_name: str,
|
923
1040
|
venv: str = 'mrsm',
|
@@ -944,7 +1061,7 @@ def completely_uninstall_package(
|
|
944
1061
|
continue
|
945
1062
|
installed_versions.append(file_name)
|
946
1063
|
|
947
|
-
max_attempts = len(installed_versions)
|
1064
|
+
max_attempts = len(installed_versions)
|
948
1065
|
while attempts < max_attempts:
|
949
1066
|
if not venv_contains_package(
|
950
1067
|
_install_to_import_name(_install_no_version),
|
@@ -953,8 +1070,10 @@ def completely_uninstall_package(
|
|
953
1070
|
return True
|
954
1071
|
if not pip_uninstall(
|
955
1072
|
_install_no_version,
|
956
|
-
venv=venv,
|
957
|
-
silent=(not debug),
|
1073
|
+
venv = venv,
|
1074
|
+
silent = (not debug),
|
1075
|
+
_from_completely_uninstall = True,
|
1076
|
+
debug = debug,
|
958
1077
|
):
|
959
1078
|
return False
|
960
1079
|
attempts += 1
|
@@ -1031,6 +1150,10 @@ def run_python_package(
|
|
1031
1150
|
if cwd is not None:
|
1032
1151
|
os.chdir(cwd)
|
1033
1152
|
executable = venv_executable(venv=venv)
|
1153
|
+
venv_path = (VIRTENV_RESOURCES_PATH / venv) if venv is not None else None
|
1154
|
+
env_dict = kw.get('env', os.environ).copy()
|
1155
|
+
if venv_path is not None:
|
1156
|
+
env_dict.update({'VIRTUAL_ENV': venv_path.as_posix()})
|
1034
1157
|
command = [executable, '-m', str(package_name)] + [str(a) for a in args]
|
1035
1158
|
import traceback
|
1036
1159
|
if debug:
|
@@ -1055,7 +1178,7 @@ def run_python_package(
|
|
1055
1178
|
command,
|
1056
1179
|
stdout = stdout,
|
1057
1180
|
stderr = stderr,
|
1058
|
-
env =
|
1181
|
+
env = env_dict,
|
1059
1182
|
)
|
1060
1183
|
to_return = proc if as_proc else proc.wait()
|
1061
1184
|
except KeyboardInterrupt:
|
@@ -1075,9 +1198,10 @@ def attempt_import(
|
|
1075
1198
|
check_update: bool = False,
|
1076
1199
|
check_pypi: bool = False,
|
1077
1200
|
check_is_installed: bool = True,
|
1201
|
+
allow_outside_venv: bool = True,
|
1078
1202
|
color: bool = True,
|
1079
1203
|
debug: bool = False
|
1080
|
-
) ->
|
1204
|
+
) -> Any:
|
1081
1205
|
"""
|
1082
1206
|
Raise a warning if packages are not installed; otherwise import and return modules.
|
1083
1207
|
If `lazy` is `True`, return lazy-imported modules.
|
@@ -1120,6 +1244,15 @@ def attempt_import(
|
|
1120
1244
|
check_is_installed: bool, default True
|
1121
1245
|
If `True`, check if the package is contained in the virtual environment.
|
1122
1246
|
|
1247
|
+
allow_outside_venv: bool, default True
|
1248
|
+
If `True`, search outside of the specified virtual environment
|
1249
|
+
if the package cannot be found.
|
1250
|
+
Setting to `False` will reinstall the package into a virtual environment, even if it
|
1251
|
+
is installed outside.
|
1252
|
+
|
1253
|
+
color: bool, default True
|
1254
|
+
If `False`, do not print ANSI colors.
|
1255
|
+
|
1123
1256
|
Returns
|
1124
1257
|
-------
|
1125
1258
|
The specified modules. If they're not available and `install` is `True`, it will first
|
@@ -1201,6 +1334,7 @@ def attempt_import(
|
|
1201
1334
|
name,
|
1202
1335
|
venv = venv,
|
1203
1336
|
split = split,
|
1337
|
+
allow_outside_venv = allow_outside_venv,
|
1204
1338
|
debug = debug,
|
1205
1339
|
)
|
1206
1340
|
_is_installed_first_check[name] = package_is_installed
|
@@ -1346,7 +1480,9 @@ def import_rich(
|
|
1346
1480
|
'pygments', lazy=False,
|
1347
1481
|
)
|
1348
1482
|
rich = attempt_import(
|
1349
|
-
'rich', lazy=lazy,
|
1483
|
+
'rich', lazy=lazy,
|
1484
|
+
**kw
|
1485
|
+
)
|
1350
1486
|
return rich
|
1351
1487
|
|
1352
1488
|
|
@@ -1580,10 +1716,26 @@ def is_installed(
|
|
1580
1716
|
import_name: str,
|
1581
1717
|
venv: Optional[str] = 'mrsm',
|
1582
1718
|
split: bool = True,
|
1719
|
+
allow_outside_venv: bool = True,
|
1583
1720
|
debug: bool = False,
|
1584
1721
|
) -> bool:
|
1585
1722
|
"""
|
1586
1723
|
Check whether a package is installed.
|
1724
|
+
|
1725
|
+
Parameters
|
1726
|
+
----------
|
1727
|
+
import_name: str
|
1728
|
+
The import name of the module.
|
1729
|
+
|
1730
|
+
venv: Optional[str], default 'mrsm'
|
1731
|
+
The venv in which to search for the module.
|
1732
|
+
|
1733
|
+
split: bool, default True
|
1734
|
+
If `True`, split on periods to determine the root module name.
|
1735
|
+
|
1736
|
+
allow_outside_venv: bool, default True
|
1737
|
+
If `True`, search outside of the specified virtual environment
|
1738
|
+
if the package cannot be found.
|
1587
1739
|
"""
|
1588
1740
|
if debug:
|
1589
1741
|
from meerschaum.utils.debug import dprint
|
@@ -1594,7 +1746,11 @@ def is_installed(
|
|
1594
1746
|
spec_path = pathlib.Path(
|
1595
1747
|
get_module_path(root_name, venv=venv, debug=debug)
|
1596
1748
|
or
|
1597
|
-
|
1749
|
+
(
|
1750
|
+
importlib.util.find_spec(root_name).origin
|
1751
|
+
if venv is not None and allow_outside_venv
|
1752
|
+
else None
|
1753
|
+
)
|
1598
1754
|
)
|
1599
1755
|
except (ModuleNotFoundError, ValueError, AttributeError, TypeError) as e:
|
1600
1756
|
spec_path = None
|
@@ -1623,6 +1779,8 @@ def venv_contains_package(
|
|
1623
1779
|
"""
|
1624
1780
|
Search the contents of a virtual environment for a package.
|
1625
1781
|
"""
|
1782
|
+
import site
|
1783
|
+
import pathlib
|
1626
1784
|
root_name = import_name.split('.')[0] if split else import_name
|
1627
1785
|
return get_module_path(root_name, venv=venv, debug=debug) is not None
|
1628
1786
|
|
@@ -1686,7 +1844,7 @@ def _monkey_patch_get_distribution(_dist: str, _version: str) -> None:
|
|
1686
1844
|
pkg_resources.get_distribution = _get_distribution
|
1687
1845
|
|
1688
1846
|
|
1689
|
-
def _get_pip_os_env():
|
1847
|
+
def _get_pip_os_env(color: bool = True):
|
1690
1848
|
"""
|
1691
1849
|
Return the environment variables context in which `pip` should be run.
|
1692
1850
|
See PEP 668 for why we are overriding the environment.
|
@@ -1695,5 +1853,6 @@ def _get_pip_os_env():
|
|
1695
1853
|
pip_os_env = os.environ.copy()
|
1696
1854
|
pip_os_env.update({
|
1697
1855
|
'PIP_BREAK_SYSTEM_PACKAGES': 'true',
|
1856
|
+
('FORCE_COLOR' if color else 'NO_COLOR'): '1',
|
1698
1857
|
})
|
1699
1858
|
return pip_os_env
|
@@ -53,6 +53,7 @@ packages: Dict[str, Dict[str, str]] = {
|
|
53
53
|
'dill' : 'dill>=0.3.3',
|
54
54
|
'virtualenv' : 'virtualenv>=20.1.0',
|
55
55
|
'apscheduler' : 'APScheduler>=4.0.0a5',
|
56
|
+
'uv' : 'uv>=0.2.11',
|
56
57
|
},
|
57
58
|
'drivers': {
|
58
59
|
'cryptography' : 'cryptography>=38.0.1',
|
@@ -60,8 +61,8 @@ packages: Dict[str, Dict[str, str]] = {
|
|
60
61
|
'pymysql' : 'PyMySQL>=0.9.0',
|
61
62
|
'aiomysql' : 'aiomysql>=0.0.21',
|
62
63
|
'sqlalchemy_cockroachdb' : 'sqlalchemy-cockroachdb>=2.0.0',
|
63
|
-
'duckdb' : 'duckdb
|
64
|
-
'duckdb_engine' : 'duckdb-engine>=0.
|
64
|
+
'duckdb' : 'duckdb>=1.0.0',
|
65
|
+
'duckdb_engine' : 'duckdb-engine>=0.13.0',
|
65
66
|
},
|
66
67
|
'drivers-extras': {
|
67
68
|
'pyodbc' : 'pyodbc>=4.0.30',
|