fractal-server 2.7.1__py3-none-any.whl → 2.8.1__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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/models/user_settings.py +1 -0
- fractal_server/app/models/v2/task.py +15 -0
- fractal_server/app/routes/api/v2/dataset.py +39 -6
- fractal_server/app/routes/api/v2/task.py +2 -5
- fractal_server/app/routes/api/v2/task_collection.py +14 -42
- fractal_server/app/routes/api/v2/task_collection_custom.py +3 -3
- fractal_server/app/schemas/_validators.py +1 -1
- fractal_server/app/schemas/user_settings.py +18 -0
- fractal_server/app/schemas/v2/dataset.py +6 -4
- fractal_server/app/schemas/v2/task_collection.py +31 -12
- fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +39 -0
- fractal_server/string_tools.py +10 -3
- fractal_server/tasks/utils.py +0 -31
- fractal_server/tasks/v1/background_operations.py +11 -11
- fractal_server/tasks/v1/endpoint_operations.py +5 -5
- fractal_server/tasks/v1/utils.py +2 -2
- fractal_server/tasks/v2/collection_local.py +357 -0
- fractal_server/tasks/v2/{background_operations_ssh.py → collection_ssh.py} +108 -102
- fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -8
- fractal_server/tasks/v2/templates/_2_preliminary_pip_operations.sh +2 -2
- fractal_server/tasks/v2/templates/_3_pip_install.sh +22 -1
- fractal_server/tasks/v2/templates/_5_pip_show.sh +5 -5
- fractal_server/tasks/v2/utils_background.py +209 -0
- fractal_server/tasks/v2/utils_package_names.py +77 -0
- fractal_server/tasks/v2/{utils.py → utils_python_interpreter.py} +0 -26
- fractal_server/tasks/v2/utils_templates.py +59 -0
- fractal_server/utils.py +48 -3
- {fractal_server-2.7.1.dist-info → fractal_server-2.8.1.dist-info}/METADATA +11 -8
- {fractal_server-2.7.1.dist-info → fractal_server-2.8.1.dist-info}/RECORD +34 -31
- fractal_server/tasks/v2/_venv_pip.py +0 -198
- fractal_server/tasks/v2/background_operations.py +0 -456
- /fractal_server/{tasks/v2/endpoint_operations.py → app/routes/api/v2/_aux_functions_task_collection.py} +0 -0
- {fractal_server-2.7.1.dist-info → fractal_server-2.8.1.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.1.dist-info → fractal_server-2.8.1.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.1.dist-info → fractal_server-2.8.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from fractal_server.logger import get_logger
|
4
|
+
|
5
|
+
|
6
|
+
def _parse_wheel_filename(wheel_filename: str) -> dict[str, str]:
|
7
|
+
"""
|
8
|
+
Extract distribution and version from a wheel filename.
|
9
|
+
|
10
|
+
The structure of a wheel filename is fixed, and it must start with
|
11
|
+
`{distribution}-{version}` (see
|
12
|
+
https://packaging.python.org/en/latest/specifications/binary-distribution-format
|
13
|
+
).
|
14
|
+
|
15
|
+
Note that we transform exceptions in `ValueError`s, since this function is
|
16
|
+
also used within Pydantic validators.
|
17
|
+
"""
|
18
|
+
if "/" in wheel_filename:
|
19
|
+
raise ValueError(
|
20
|
+
"[_parse_wheel_filename] Input must be a filename, not a full "
|
21
|
+
f"path (given: {wheel_filename})."
|
22
|
+
)
|
23
|
+
try:
|
24
|
+
parts = wheel_filename.split("-")
|
25
|
+
return dict(distribution=parts[0], version=parts[1])
|
26
|
+
except Exception as e:
|
27
|
+
raise ValueError(
|
28
|
+
f"Invalid {wheel_filename=}. Original error: {str(e)}."
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
def normalize_package_name(name: str) -> str:
|
33
|
+
"""
|
34
|
+
Implement PyPa specifications for package-name normalization
|
35
|
+
|
36
|
+
The name should be lowercased with all runs of the characters `.`, `-`,
|
37
|
+
or `_` replaced with a single `-` character. This can be implemented in
|
38
|
+
Python with the re module.
|
39
|
+
(https://packaging.python.org/en/latest/specifications/name-normalization)
|
40
|
+
|
41
|
+
Args:
|
42
|
+
name: The non-normalized package name.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
The normalized package name.
|
46
|
+
"""
|
47
|
+
return re.sub(r"[-_.]+", "-", name).lower()
|
48
|
+
|
49
|
+
|
50
|
+
def compare_package_names(
|
51
|
+
*,
|
52
|
+
pkg_name_pip_show: str,
|
53
|
+
pkg_name_task_group: str,
|
54
|
+
logger_name: str,
|
55
|
+
) -> None:
|
56
|
+
"""
|
57
|
+
Compare the package names from `pip show` and from the db.
|
58
|
+
"""
|
59
|
+
logger = get_logger(logger_name)
|
60
|
+
|
61
|
+
if pkg_name_pip_show == pkg_name_task_group:
|
62
|
+
return
|
63
|
+
|
64
|
+
logger.warning(
|
65
|
+
f"Package name mismatch: "
|
66
|
+
f"{pkg_name_task_group=}, {pkg_name_pip_show=}."
|
67
|
+
)
|
68
|
+
normalized_pkg_name_pip = normalize_package_name(pkg_name_pip_show)
|
69
|
+
normalized_pkg_name_taskgroup = normalize_package_name(pkg_name_task_group)
|
70
|
+
if normalized_pkg_name_pip != normalized_pkg_name_taskgroup:
|
71
|
+
error_msg = (
|
72
|
+
f"Package name mismatch persists, after normalization: "
|
73
|
+
f"{pkg_name_task_group=}, "
|
74
|
+
f"{pkg_name_pip_show=}."
|
75
|
+
)
|
76
|
+
logger.error(error_msg)
|
77
|
+
raise ValueError(error_msg)
|
@@ -31,29 +31,3 @@ def get_python_interpreter_v2(
|
|
31
31
|
if value is None:
|
32
32
|
raise ValueError(f"Requested {python_version=}, but {key}={value}.")
|
33
33
|
return value
|
34
|
-
|
35
|
-
|
36
|
-
def _parse_wheel_filename(wheel_filename: str) -> dict[str, str]:
|
37
|
-
"""
|
38
|
-
Extract distribution and version from a wheel filename.
|
39
|
-
|
40
|
-
The structure of a wheel filename is fixed, and it must start with
|
41
|
-
`{distribution}-{version}` (see
|
42
|
-
https://packaging.python.org/en/latest/specifications/binary-distribution-format
|
43
|
-
).
|
44
|
-
|
45
|
-
Note that we transform exceptions in `ValueError`s, since this function is
|
46
|
-
also used within Pydantic validators.
|
47
|
-
"""
|
48
|
-
if "/" in wheel_filename:
|
49
|
-
raise ValueError(
|
50
|
-
"[_parse_wheel_filename] Input must be a filename, not a full "
|
51
|
-
f"path (given: {wheel_filename})."
|
52
|
-
)
|
53
|
-
try:
|
54
|
-
parts = wheel_filename.split("-")
|
55
|
-
return dict(distribution=parts[0], version=parts[1])
|
56
|
-
except Exception as e:
|
57
|
-
raise ValueError(
|
58
|
-
f"Invalid {wheel_filename=}. Original error: {str(e)}."
|
59
|
-
)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
4
|
+
|
5
|
+
|
6
|
+
def customize_template(
|
7
|
+
*,
|
8
|
+
template_name: str,
|
9
|
+
replacements: list[tuple[str, str]],
|
10
|
+
script_path: str,
|
11
|
+
) -> str:
|
12
|
+
"""
|
13
|
+
Customize a bash-script template and write it to disk.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
template_filename:
|
17
|
+
templates_folder:
|
18
|
+
replacements:
|
19
|
+
"""
|
20
|
+
# Read template
|
21
|
+
template_path = TEMPLATES_DIR / template_name
|
22
|
+
with template_path.open("r") as f:
|
23
|
+
template_data = f.read()
|
24
|
+
# Customize template
|
25
|
+
script_data = template_data
|
26
|
+
for old_new in replacements:
|
27
|
+
script_data = script_data.replace(old_new[0], old_new[1])
|
28
|
+
# Write script locally
|
29
|
+
with open(script_path, "w") as f:
|
30
|
+
f.write(script_data)
|
31
|
+
|
32
|
+
|
33
|
+
def parse_script_5_stdout(stdout: str) -> dict[str, str]:
|
34
|
+
"""
|
35
|
+
Parse standard output of template 5.
|
36
|
+
"""
|
37
|
+
searches = [
|
38
|
+
("Python interpreter:", "python_bin"),
|
39
|
+
("Package name:", "package_name"),
|
40
|
+
("Package version:", "package_version"),
|
41
|
+
("Package parent folder:", "package_root_parent"),
|
42
|
+
("Manifest absolute path:", "manifest_path"),
|
43
|
+
]
|
44
|
+
stdout_lines = stdout.splitlines()
|
45
|
+
attributes = dict()
|
46
|
+
for search, attribute_name in searches:
|
47
|
+
matching_lines = [_line for _line in stdout_lines if search in _line]
|
48
|
+
if len(matching_lines) == 0:
|
49
|
+
raise ValueError(f"String '{search}' not found in stdout.")
|
50
|
+
elif len(matching_lines) > 1:
|
51
|
+
raise ValueError(
|
52
|
+
f"String '{search}' found too many times "
|
53
|
+
f"({len(matching_lines)})."
|
54
|
+
)
|
55
|
+
else:
|
56
|
+
actual_line = matching_lines[0]
|
57
|
+
attribute_value = actual_line.split(search)[-1].strip(" ")
|
58
|
+
attributes[attribute_name] = attribute_value
|
59
|
+
return attributes
|
fractal_server/utils.py
CHANGED
@@ -14,13 +14,15 @@ This module provides general purpose utilities that are not specific to any
|
|
14
14
|
subsystem.
|
15
15
|
"""
|
16
16
|
import asyncio
|
17
|
+
import shlex
|
18
|
+
import subprocess # nosec
|
17
19
|
from datetime import datetime
|
18
20
|
from datetime import timezone
|
19
21
|
from pathlib import Path
|
20
|
-
from shlex import split as shlex_split
|
21
22
|
from typing import Optional
|
22
23
|
|
23
24
|
from .logger import get_logger
|
25
|
+
from .string_tools import validate_cmd
|
24
26
|
|
25
27
|
|
26
28
|
def get_timestamp() -> datetime:
|
@@ -30,7 +32,7 @@ def get_timestamp() -> datetime:
|
|
30
32
|
return datetime.now(tz=timezone.utc)
|
31
33
|
|
32
34
|
|
33
|
-
async def
|
35
|
+
async def execute_command_async(
|
34
36
|
*,
|
35
37
|
command: str,
|
36
38
|
cwd: Optional[Path] = None,
|
@@ -56,7 +58,7 @@ async def execute_command(
|
|
56
58
|
RuntimeError: if the process exited with non-zero status. The error
|
57
59
|
string is set to the `stderr` of the process.
|
58
60
|
"""
|
59
|
-
command_split =
|
61
|
+
command_split = shlex.split(command)
|
60
62
|
cmd, *args = command_split
|
61
63
|
|
62
64
|
logger = get_logger(logger_name)
|
@@ -75,3 +77,46 @@ async def execute_command(
|
|
75
77
|
if proc.returncode != 0:
|
76
78
|
raise RuntimeError(stderr.decode("utf-8"))
|
77
79
|
return stdout.decode("utf-8")
|
80
|
+
|
81
|
+
|
82
|
+
def execute_command_sync(
|
83
|
+
*,
|
84
|
+
command: str,
|
85
|
+
logger_name: Optional[str] = None,
|
86
|
+
allow_char: Optional[str] = None,
|
87
|
+
) -> str:
|
88
|
+
"""
|
89
|
+
Execute arbitrary command
|
90
|
+
|
91
|
+
If the command returns a return code different from zero, a `RuntimeError`
|
92
|
+
is raised.
|
93
|
+
|
94
|
+
Arguments:
|
95
|
+
command: Command to be executed.
|
96
|
+
logger_name: Name of the logger.
|
97
|
+
allow_char: Argument propagated to `validate_cmd`.
|
98
|
+
"""
|
99
|
+
logger = get_logger(logger_name)
|
100
|
+
logger.debug(f"START subprocess call to '{command}'")
|
101
|
+
validate_cmd(command=command, allow_char=allow_char)
|
102
|
+
res = subprocess.run( # nosec
|
103
|
+
shlex.split(command),
|
104
|
+
capture_output=True,
|
105
|
+
encoding="utf-8",
|
106
|
+
)
|
107
|
+
returncode = res.returncode
|
108
|
+
stdout = res.stdout
|
109
|
+
stderr = res.stderr
|
110
|
+
logger.debug(f"{returncode=}")
|
111
|
+
logger.debug(f"{stdout=}")
|
112
|
+
logger.debug(f"{stderr=}")
|
113
|
+
if res.returncode != 0:
|
114
|
+
logger.debug(f"ERROR in subprocess call to '{command}'")
|
115
|
+
raise RuntimeError(
|
116
|
+
f"Command {command} failed.\n"
|
117
|
+
f"returncode={res.returncode}\n"
|
118
|
+
f"{stdout=}\n"
|
119
|
+
f"{stderr=}\n"
|
120
|
+
)
|
121
|
+
logger.debug(f"END subprocess call to '{command}'")
|
122
|
+
return stdout
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fractal-server
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.8.1
|
4
4
|
Summary: Server component of the Fractal analytics platform
|
5
5
|
Home-page: https://github.com/fractal-analytics-platform/fractal-server
|
6
6
|
License: BSD-3-Clause
|
@@ -37,15 +37,20 @@ Description-Content-Type: text/markdown
|
|
37
37
|
|
38
38
|
# Fractal Server
|
39
39
|
|
40
|
+
<p align="center">
|
41
|
+
<img src="https://github.com/user-attachments/assets/16e9cf11-d47d-4db8-a9b1-f5349e4175b7" alt="Fractal server" width="400">
|
42
|
+
</p>
|
43
|
+
|
40
44
|
[](https://pypi.org/project/fractal-server/)
|
45
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
41
46
|
[](https://github.com/fractal-analytics-platform/fractal-server/actions/workflows/ci.yml?query=branch%3Amain)
|
42
47
|
[](https://htmlpreview.github.io/?https://github.com/fractal-analytics-platform/fractal-server/blob/python-coverage-comment-action-data/htmlcov/index.html)
|
43
|
-
[](https://fractal-analytics-platform.github.io/fractal-server)
|
44
49
|
[](https://htmlpreview.github.io/?https://github.com/fractal-analytics-platform/fractal-server/blob/benchmark-api/benchmarks/bench.html)
|
45
50
|
|
46
51
|
[Fractal](https://fractal-analytics-platform.github.io/) is a framework developed at the [BioVisionCenter](https://www.biovisioncenter.uzh.ch/en.html) to process bioimaging data at scale in the OME-Zarr format and prepare the images for interactive visualization.
|
47
52
|
|
48
|
-

|
49
54
|
|
50
55
|
This is the server component of the fractal analytics platform.
|
51
56
|
Find more information about Fractal in general and the other repositories at
|
@@ -58,14 +63,12 @@ See https://fractal-analytics-platform.github.io/fractal-server.
|
|
58
63
|
|
59
64
|
# Contributors and license
|
60
65
|
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
Fractal was conceived in the Liberali Lab at the Friedrich Miescher Institute for Biomedical Research and in the Pelkmans Lab at the University of Zurich by [@jluethi](https://github.com/jluethi) and [@gusqgm](https://github.com/gusqgm). The Fractal project is now developed at the [BioVisionCenter](https://www.biovisioncenter.uzh.ch/en.html) at the University of Zurich and the project lead is with [@jluethi](https://github.com/jluethi). The core development is done under contract by [eXact lab S.r.l.](https://www.exact-lab.it).
|
67
|
+
|
68
|
+
Unless otherwise specified, Fractal components are released under the BSD 3-Clause License, and copyright is with the BioVisionCenter at the University of Zurich.
|
64
69
|
|
65
70
|
The SLURM compatibility layer is based on
|
66
71
|
[`clusterfutures`](https://github.com/sampsyo/clusterfutures), by
|
67
72
|
[@sampsyo](https://github.com/sampsyo) and collaborators, and it is released
|
68
73
|
under the terms of the MIT license.
|
69
74
|
|
70
|
-
Fractal was conceived in the Liberali Lab at the Friedrich Miescher Institute for Biomedical Research and in the Pelkmans Lab at the University of Zurich by [@jluethi](https://github.com/jluethi) and [@gusqgm](https://github.com/gusqgm). The Fractal project is now developed at the [BioVisionCenter](https://www.biovisioncenter.uzh.ch/en.html) at the University of Zurich and the project lead is with [@jluethi](https://github.com/jluethi). The core development is done under contract by [eXact lab S.r.l.](https://www.exact-lab.it/).
|
71
|
-
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=_UadKzwkiZjaRG8tsoM0cHEnXlBYpVpNzh9Ld4g5XDg,22
|
2
2
|
fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -7,7 +7,7 @@ fractal_server/app/models/__init__.py,sha256=aG7mf1zZbsgzDSp7GHEcZhdjHfW3TGPOLCI
|
|
7
7
|
fractal_server/app/models/linkusergroup.py,sha256=LWTUfhH2uAnn_4moK7QdRUIHWtpw-hPZuW-5jClv_OE,610
|
8
8
|
fractal_server/app/models/linkuserproject.py,sha256=eQaourbGRshvlMVlKzLYJKHEjfsW1CbWws9yW4eHXhA,567
|
9
9
|
fractal_server/app/models/security.py,sha256=2npjgRKBZ7OAnhAXNbYxjtuOsSm1P4kak__qfk2SpeM,3770
|
10
|
-
fractal_server/app/models/user_settings.py,sha256=
|
10
|
+
fractal_server/app/models/user_settings.py,sha256=3LodhERbRz3ajjmCnZiU1TOitduKu_9Lyv_Rgdnyusw,1350
|
11
11
|
fractal_server/app/models/v1/__init__.py,sha256=hUI7dEbPaiZGN0IbHW4RSmSicyvtn_xeuevoX7zvUwI,466
|
12
12
|
fractal_server/app/models/v1/dataset.py,sha256=99GDgt7njx8yYQApkImqp_7bHA5HH3ElvbR6Oyj9kVI,2017
|
13
13
|
fractal_server/app/models/v1/job.py,sha256=QLGXcWdVRHaUHQNDapYYlLpEfw4K7QyD8TmcwhrWw2o,3304
|
@@ -20,7 +20,7 @@ fractal_server/app/models/v2/collection_state.py,sha256=Yx18ZbywjraOdlHFyRVlb3VP
|
|
20
20
|
fractal_server/app/models/v2/dataset.py,sha256=-7sxHEw4IIAvF_uSan7tA3o8hvoakBkQ0SRvqS2iOQU,1455
|
21
21
|
fractal_server/app/models/v2/job.py,sha256=ypJmN-qspkKBGhBG7Mt-HypSQqcQ2EmB4Bzzb2-y550,1535
|
22
22
|
fractal_server/app/models/v2/project.py,sha256=rAHoh5KfYwIaW7rTX0_O0jvWmxEvfo1BafvmcXuSSRk,786
|
23
|
-
fractal_server/app/models/v2/task.py,sha256=
|
23
|
+
fractal_server/app/models/v2/task.py,sha256=NmDGtSX5kGahAnsvFG__QEpyXCKxO9-E2Y_1lK6kKM8,3712
|
24
24
|
fractal_server/app/models/v2/workflow.py,sha256=YBgFGCziUgU0aJ5EM3Svu9W2c46AewZO9VBlFCHiSps,1069
|
25
25
|
fractal_server/app/models/v2/workflowtask.py,sha256=iDuJYk8kp4PNqGmbKRtGI7y-QsbjkNd_gDsbMzL4i-g,1274
|
26
26
|
fractal_server/app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -43,16 +43,17 @@ fractal_server/app/routes/api/v1/workflow.py,sha256=2T93DuEnSshaDCue-JPmjuvGCtbk
|
|
43
43
|
fractal_server/app/routes/api/v1/workflowtask.py,sha256=OYYConwJbmNULDw5I3T-UbSJKrbbBiAHbbBeVcpoFKQ,5785
|
44
44
|
fractal_server/app/routes/api/v2/__init__.py,sha256=jybEV-vrknPoQvbgKJl0QQvHDPHOJXbDUG5vatHeis4,1963
|
45
45
|
fractal_server/app/routes/api/v2/_aux_functions.py,sha256=mb4R_qqFxeW0LAis2QJIIfVx8Sydv1jTYaRIMsMxnIk,11720
|
46
|
+
fractal_server/app/routes/api/v2/_aux_functions_task_collection.py,sha256=MtUoI0XWHuPSousDeH2IC2WU--AUKQVup6Q6AbHiNUA,4102
|
46
47
|
fractal_server/app/routes/api/v2/_aux_functions_tasks.py,sha256=IVzSb7J4ls5qp1HI8WcjRLIq6nx3ffcMI3vUi_aM_gc,10565
|
47
|
-
fractal_server/app/routes/api/v2/dataset.py,sha256=
|
48
|
+
fractal_server/app/routes/api/v2/dataset.py,sha256=Y6uZz--YSEGgnPYu05rZ9sr1Ug08bNl2v1h3VeApBe8,9441
|
48
49
|
fractal_server/app/routes/api/v2/images.py,sha256=JR1rR6qEs81nacjriOXAOBQjAbCXF4Ew7M7mkWdxBU0,7920
|
49
50
|
fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhKRjEH2_ayEE,5157
|
50
51
|
fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhzYBsVGYFhHZZXAE,6653
|
51
52
|
fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
|
52
53
|
fractal_server/app/routes/api/v2/submit.py,sha256=tq-NGnUlpIcm_MRN47rJRHkRcIJ5HiL4Wj1wItJy3o8,8185
|
53
|
-
fractal_server/app/routes/api/v2/task.py,sha256=
|
54
|
-
fractal_server/app/routes/api/v2/task_collection.py,sha256=
|
55
|
-
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=
|
54
|
+
fractal_server/app/routes/api/v2/task.py,sha256=K0ik33t7vL8BAK5S7fqyJDNdRK4stGqb_73bSa8tvPE,7159
|
55
|
+
fractal_server/app/routes/api/v2/task_collection.py,sha256=aCOg9zhRtGfJvRpukS_mSah19jhGEqXaE8hUDOMIZUs,10479
|
56
|
+
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=CTxRy0ghYZahlS6WNr6qRFa0xfAQbbU6xZh6F9VykAY,6212
|
56
57
|
fractal_server/app/routes/api/v2/task_group.py,sha256=P32EUYbtGThexSWe5zI9WUFrgoOMof035fJBILTNnfQ,5580
|
57
58
|
fractal_server/app/routes/api/v2/workflow.py,sha256=PyvkrUHHzFGUGZE5X0VW5u3DPQA7wtXXNcEpG7-N66I,8687
|
58
59
|
fractal_server/app/routes/api/v2/workflow_import.py,sha256=rD26vZ-ztjehvglrERixTeHtXuzepAtgAuPiKRNz84Q,10981
|
@@ -131,10 +132,10 @@ fractal_server/app/runner/v2/runner_functions_low_level.py,sha256=1fWvQ6YZUUnDhO
|
|
131
132
|
fractal_server/app/runner/v2/task_interface.py,sha256=hT3p-bRGsLNAR_dNv_PYFoqzIF_EQtSsGwl38j1haYA,1824
|
132
133
|
fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
|
133
134
|
fractal_server/app/schemas/__init__.py,sha256=stURAU_t3AOBaH0HSUbV-GKhlPKngnnIMoqWc3orFyI,135
|
134
|
-
fractal_server/app/schemas/_validators.py,sha256=
|
135
|
+
fractal_server/app/schemas/_validators.py,sha256=3YBteFMxrEE9DOYlIHvIMsUOWTPSHEZlDL7dUQzBQjs,3616
|
135
136
|
fractal_server/app/schemas/user.py,sha256=aUD8YAcfYTEO06TEUoTx4heVrXFiX7E2Mb8D2--4FsA,2130
|
136
137
|
fractal_server/app/schemas/user_group.py,sha256=YwJvYgj-PI66LWy38CEd_FIZPsBV1_2N5zJPGFcFvBw,2143
|
137
|
-
fractal_server/app/schemas/user_settings.py,sha256=
|
138
|
+
fractal_server/app/schemas/user_settings.py,sha256=TalISeEfCrtN8LgqbLx1Q8ZPoeiZnbksg5NYAVzkIqY,3527
|
138
139
|
fractal_server/app/schemas/v1/__init__.py,sha256=CrBGgBhoemCvmZ70ZUchM-jfVAICnoa7AjZBAtL2UB0,1852
|
139
140
|
fractal_server/app/schemas/v1/applyworkflow.py,sha256=uuIh7fHlHEL4yLqL-dePI6-nfCsqgBYATmht7w_KITw,4302
|
140
141
|
fractal_server/app/schemas/v1/dataset.py,sha256=n71lNUO3JLy2K3IM9BZM2Fk1EnKQOTU7pm2s2rJ1FGY,3444
|
@@ -146,14 +147,14 @@ fractal_server/app/schemas/v1/task.py,sha256=7BxOZ_qoRQ8n3YbQpDvB7VMcxB5fSYQmR5R
|
|
146
147
|
fractal_server/app/schemas/v1/task_collection.py,sha256=uvq9bcMaGD_qHsh7YtcpoSAkVAbw12eY4DocIO3MKOg,3057
|
147
148
|
fractal_server/app/schemas/v1/workflow.py,sha256=tuOs5E5Q_ozA8if7YPZ07cQjzqB_QMkBS4u92qo4Ro0,4618
|
148
149
|
fractal_server/app/schemas/v2/__init__.py,sha256=97y6QY0I4322CPXQCt3WO3QBWhVkmFgwLn8y2ZwWNR0,2382
|
149
|
-
fractal_server/app/schemas/v2/dataset.py,sha256=
|
150
|
+
fractal_server/app/schemas/v2/dataset.py,sha256=Jipcj9LiOOipAeM2Ew113wuNQ6CrbC1nf3KwnNApBco,2638
|
150
151
|
fractal_server/app/schemas/v2/dumps.py,sha256=s6dg-pHZFui6t2Ktm0SMxjKDN-v-ZqBHz9iTsBQF3eU,1712
|
151
152
|
fractal_server/app/schemas/v2/job.py,sha256=oYSLYkQ0HL83QyjEGIaggtZ117FndzFlONMKWd9sTXM,3270
|
152
153
|
fractal_server/app/schemas/v2/manifest.py,sha256=Uqtd7DbyOkf9bxBOKkU7Sv7nToBIFGUcfjY7rd5iO7c,6981
|
153
154
|
fractal_server/app/schemas/v2/project.py,sha256=UXEA0UUUe0bFFOVLLmVtvDFLBO5vmD1JVI7EeTIcwDo,756
|
154
155
|
fractal_server/app/schemas/v2/status.py,sha256=SQaUpQkjFq5c5k5J4rOjNhuQaDOEg8lksPhkKmPU5VU,332
|
155
156
|
fractal_server/app/schemas/v2/task.py,sha256=FFAbYwDlqowB8gVMdjFVPVHvAM0T89PYLixUth49xfQ,6870
|
156
|
-
fractal_server/app/schemas/v2/task_collection.py,sha256=
|
157
|
+
fractal_server/app/schemas/v2/task_collection.py,sha256=56VWGwOZcgmfZ1bKz-_MSSO8HYwtMAKNBL5tfeQc7uQ,7000
|
157
158
|
fractal_server/app/schemas/v2/task_group.py,sha256=oWy7NNsw8Co85qpLyK8FPNTgpAMvx0ZuXjIOVZuLEpM,2466
|
158
159
|
fractal_server/app/schemas/v2/workflow.py,sha256=HSNQSrBRdoBzh8Igr76FUWCAWvVzykrqmUv1vGv-8og,2026
|
159
160
|
fractal_server/app/schemas/v2/workflowtask.py,sha256=vDdMktYbHeYBgB5OuWSv6wRPRXWqvetkeqQ7IC5YtfA,5751
|
@@ -174,6 +175,7 @@ fractal_server/migrations/naming_convention.py,sha256=htbKrVdetx3pklowb_9Cdo5Rqe
|
|
174
175
|
fractal_server/migrations/script.py.mako,sha256=oMXw9LC3zRbinWWPPDgeZ4z9FJrV2zhRWiYdS5YgNbI,526
|
175
176
|
fractal_server/migrations/versions/034a469ec2eb_task_groups.py,sha256=vrPhC8hfFu1c4HmLHNZyCuqEfecFD8-bWc49bXMNes0,6199
|
176
177
|
fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py,sha256=-BSS9AFTPcu3gYC-sYbawSy4MWQQx8TfMb5BW5EBKmQ,1450
|
178
|
+
fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py,sha256=Q1Gj1cJ0UrdLBJ5AXfFK9QpxTtmcv-4Z3NEGDnxOme4,961
|
177
179
|
fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py,sha256=-wHe-fOffmYeAm0JXVl_lxZ7hhDkaEVqxgxpHkb_uL8,954
|
178
180
|
fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py,sha256=Mob8McGYAcmgvrseyyYOa54E6Gsgr-4SiGdC-r9O4_A,1157
|
179
181
|
fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py,sha256=5ROUgcoZOdjf8kMt6cxuvPhzHmV6xaCxvZEbhUEyZM4,3271
|
@@ -198,33 +200,34 @@ fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.p
|
|
198
200
|
fractal_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
199
201
|
fractal_server/ssh/__init__.py,sha256=sVUmzxf7_DuXG1xoLQ1_00fo5NPhi2LJipSmU5EAkPs,124
|
200
202
|
fractal_server/ssh/_fabric.py,sha256=Pha-gRVUImj1cMsxulrJzaQa6Z60CmMYRAS4o22FcP0,19506
|
201
|
-
fractal_server/string_tools.py,sha256=
|
203
|
+
fractal_server/string_tools.py,sha256=XtMNsr5R7GmgzmFi68zkKMedHs8vjGoVMMCXqWhIk9k,2568
|
202
204
|
fractal_server/syringe.py,sha256=3qSMW3YaMKKnLdgnooAINOPxnCOxP7y2jeAQYB21Gdo,2786
|
203
205
|
fractal_server/tasks/__init__.py,sha256=kadmVUoIghl8s190_Tt-8f-WBqMi8u8oU4Pvw39NHE8,23
|
204
|
-
fractal_server/tasks/utils.py,sha256=
|
206
|
+
fractal_server/tasks/utils.py,sha256=lJeaGX0xw4uE_dQGMnFAvmz03gUVh_nv8m47yuwFcKc,1340
|
205
207
|
fractal_server/tasks/v1/_TaskCollectPip.py,sha256=ARq5AoHYXH0hziEsb-nFAqbsLA-VIddXOdXL38O6_zA,3746
|
206
208
|
fractal_server/tasks/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
207
|
-
fractal_server/tasks/v1/background_operations.py,sha256=
|
208
|
-
fractal_server/tasks/v1/endpoint_operations.py,sha256=
|
209
|
+
fractal_server/tasks/v1/background_operations.py,sha256=0Zm8TT_RV0BxJCXbruYJCu1eXOkEcHpqnWn_BEe7huw,11829
|
210
|
+
fractal_server/tasks/v1/endpoint_operations.py,sha256=NQYvgh-_qEI9YhsLiulfOFPDacCd-rgl3cCbPbkJUA0,5103
|
209
211
|
fractal_server/tasks/v1/get_collection_data.py,sha256=5C22jp356rCH5IIC0J57wOu-DCC_kp3B6p68JooN7IM,508
|
210
|
-
fractal_server/tasks/v1/utils.py,sha256=
|
212
|
+
fractal_server/tasks/v1/utils.py,sha256=HYFyNAyZofmf--mVgdwGC5TJpGShIWIDaS01yRr4HxM,1771
|
211
213
|
fractal_server/tasks/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
212
|
-
fractal_server/tasks/v2/
|
213
|
-
fractal_server/tasks/v2/
|
214
|
-
fractal_server/tasks/v2/background_operations_ssh.py,sha256=_G0rOohkkHLGcvCQyfoMObnII-sxVwrLDW2PKz8IfCQ,13631
|
214
|
+
fractal_server/tasks/v2/collection_local.py,sha256=pRGF2NYIc5hwwNZOruyb7RSChWytRMxC2e8WSeMuYMI,13615
|
215
|
+
fractal_server/tasks/v2/collection_ssh.py,sha256=Yv8TgUK0yqiRjVo1mHXLbYV2NpTWE_nPzBFGdCPJX0E,13437
|
215
216
|
fractal_server/tasks/v2/database_operations.py,sha256=6r56yyFPnEBrXl6ncmO6D76znzISQCFZqCYcD-Ummd4,1213
|
216
|
-
fractal_server/tasks/v2/
|
217
|
-
fractal_server/tasks/v2/templates/
|
218
|
-
fractal_server/tasks/v2/templates/
|
219
|
-
fractal_server/tasks/v2/templates/_3_pip_install.sh,sha256=T9sabeB9iQzVZpLfuLkKGz9EpfHkUrJHKWO4HNij6yM,595
|
217
|
+
fractal_server/tasks/v2/templates/_1_create_venv.sh,sha256=bfpX3GJtrns1ogidyQHmYaAZjVdQLG-YYazc2GR4z5w,967
|
218
|
+
fractal_server/tasks/v2/templates/_2_preliminary_pip_operations.sh,sha256=nRDvTbGljCmJdzfRJz_w-RptyyJoeAtIMiP7NCwVNnU,625
|
219
|
+
fractal_server/tasks/v2/templates/_3_pip_install.sh,sha256=nTw8mGmZEVGc-WGDaIK3WVSsJRDuIS8X2l1T3gl34OM,1371
|
220
220
|
fractal_server/tasks/v2/templates/_4_pip_freeze.sh,sha256=qHdDKu1svXi1VQKGePciEJK4_uEKuwAvwaDCcGxSvNk,274
|
221
|
-
fractal_server/tasks/v2/templates/_5_pip_show.sh,sha256=
|
222
|
-
fractal_server/tasks/v2/
|
221
|
+
fractal_server/tasks/v2/templates/_5_pip_show.sh,sha256=OdPKVUu3gU0N9ygLoRWvW5rBe8_HO4YCCNX8ulsy9-M,1754
|
222
|
+
fractal_server/tasks/v2/utils_background.py,sha256=Q88PtbeyfYXfxupTU9d71EcNRurxKwCjuunXN567SC0,6569
|
223
|
+
fractal_server/tasks/v2/utils_package_names.py,sha256=RDg__xrvQs4ieeVzmVdMcEh95vGQYrv9Hfal-5EDBM8,2393
|
224
|
+
fractal_server/tasks/v2/utils_python_interpreter.py,sha256=-EWh3Y3VqHLDOWUO_wG_wknqmGqKAD0O2KTLhNjrZaI,948
|
225
|
+
fractal_server/tasks/v2/utils_templates.py,sha256=61uz9WSb4BDe6JUyJGiasw8BjVrPAmtc8pnNSJ51yS4,1840
|
223
226
|
fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
|
224
|
-
fractal_server/utils.py,sha256=
|
227
|
+
fractal_server/utils.py,sha256=BTvNYA8xG-2K42x_hhsKmxrludbSvUbPpbWPq-Dousg,3481
|
225
228
|
fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
|
226
|
-
fractal_server-2.
|
227
|
-
fractal_server-2.
|
228
|
-
fractal_server-2.
|
229
|
-
fractal_server-2.
|
230
|
-
fractal_server-2.
|
229
|
+
fractal_server-2.8.1.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
230
|
+
fractal_server-2.8.1.dist-info/METADATA,sha256=Kg5Q0Ww6lgtblwriHrTueIpkYnal0gCmO9F3JMvf7jc,4588
|
231
|
+
fractal_server-2.8.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
232
|
+
fractal_server-2.8.1.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
233
|
+
fractal_server-2.8.1.dist-info/RECORD,,
|
@@ -1,198 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
from ..utils import COLLECTION_FREEZE_FILENAME
|
5
|
-
from fractal_server.app.models.v2 import TaskGroupV2
|
6
|
-
from fractal_server.config import get_settings
|
7
|
-
from fractal_server.logger import get_logger
|
8
|
-
from fractal_server.syringe import Inject
|
9
|
-
from fractal_server.tasks.v2.utils import get_python_interpreter_v2
|
10
|
-
from fractal_server.utils import execute_command
|
11
|
-
|
12
|
-
|
13
|
-
async def _init_venv_v2(
|
14
|
-
*,
|
15
|
-
venv_path: Path,
|
16
|
-
python_version: Optional[str] = None,
|
17
|
-
logger_name: str,
|
18
|
-
) -> Path:
|
19
|
-
"""
|
20
|
-
Set a virtual environment at `path/venv`
|
21
|
-
|
22
|
-
Args:
|
23
|
-
path : Path
|
24
|
-
path to the venv actual directory (not its parent).
|
25
|
-
python_version : default=None
|
26
|
-
Python version the virtual environment will be based upon
|
27
|
-
|
28
|
-
Returns:
|
29
|
-
python_bin : Path
|
30
|
-
path to python interpreter
|
31
|
-
"""
|
32
|
-
logger = get_logger(logger_name)
|
33
|
-
logger.debug(f"[_init_venv_v2] {venv_path=}")
|
34
|
-
interpreter = get_python_interpreter_v2(python_version=python_version)
|
35
|
-
logger.debug(f"[_init_venv_v2] {interpreter=}")
|
36
|
-
await execute_command(
|
37
|
-
command=f"{interpreter} -m venv {venv_path}",
|
38
|
-
logger_name=logger_name,
|
39
|
-
)
|
40
|
-
python_bin = venv_path / "bin/python"
|
41
|
-
logger.debug(f"[_init_venv_v2] {python_bin=}")
|
42
|
-
return python_bin
|
43
|
-
|
44
|
-
|
45
|
-
async def _pip_install(
|
46
|
-
task_group: TaskGroupV2,
|
47
|
-
logger_name: str,
|
48
|
-
) -> Path:
|
49
|
-
"""
|
50
|
-
Install package in venv
|
51
|
-
|
52
|
-
Args:
|
53
|
-
venv_path:
|
54
|
-
task_pkg:
|
55
|
-
logger_name:
|
56
|
-
|
57
|
-
Returns:
|
58
|
-
The location of the package.
|
59
|
-
"""
|
60
|
-
settings = Inject(get_settings)
|
61
|
-
|
62
|
-
logger = get_logger(logger_name)
|
63
|
-
|
64
|
-
python_bin = Path(task_group.venv_path) / "bin/python"
|
65
|
-
pip_install_str = task_group.pip_install_string
|
66
|
-
logger.info(f"{pip_install_str=}")
|
67
|
-
|
68
|
-
await execute_command(
|
69
|
-
cwd=Path(task_group.venv_path),
|
70
|
-
command=(
|
71
|
-
f"{python_bin} -m pip install --upgrade "
|
72
|
-
f"'pip<={settings.FRACTAL_MAX_PIP_VERSION}'"
|
73
|
-
),
|
74
|
-
logger_name=logger_name,
|
75
|
-
)
|
76
|
-
await execute_command(
|
77
|
-
cwd=Path(task_group.venv_path),
|
78
|
-
command=f"{python_bin} -m pip install setuptools",
|
79
|
-
logger_name=logger_name,
|
80
|
-
)
|
81
|
-
await execute_command(
|
82
|
-
cwd=Path(task_group.venv_path),
|
83
|
-
command=f"{python_bin} -m pip install {pip_install_str}",
|
84
|
-
logger_name=logger_name,
|
85
|
-
)
|
86
|
-
|
87
|
-
if task_group.pinned_package_versions:
|
88
|
-
for (
|
89
|
-
pinned_pkg_name,
|
90
|
-
pinned_pkg_version,
|
91
|
-
) in task_group.pinned_package_versions.items():
|
92
|
-
logger.debug(
|
93
|
-
"Specific version required: "
|
94
|
-
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
95
|
-
)
|
96
|
-
logger.debug(
|
97
|
-
"Preliminary check: verify that "
|
98
|
-
f"{pinned_pkg_name} is already installed"
|
99
|
-
)
|
100
|
-
stdout_show = await execute_command(
|
101
|
-
cwd=Path(task_group.venv_path),
|
102
|
-
command=f"{python_bin} -m pip show {pinned_pkg_name}",
|
103
|
-
logger_name=logger_name,
|
104
|
-
)
|
105
|
-
current_version = next(
|
106
|
-
line.split()[-1]
|
107
|
-
for line in stdout_show.split("\n")
|
108
|
-
if line.startswith("Version:")
|
109
|
-
)
|
110
|
-
if current_version != pinned_pkg_version:
|
111
|
-
logger.debug(
|
112
|
-
f"Currently installed version of {pinned_pkg_name} "
|
113
|
-
f"({current_version}) differs from pinned version "
|
114
|
-
f"({pinned_pkg_version}); "
|
115
|
-
f"install version {pinned_pkg_version}."
|
116
|
-
)
|
117
|
-
await execute_command(
|
118
|
-
cwd=Path(task_group.venv_path),
|
119
|
-
command=(
|
120
|
-
f"{python_bin} -m pip install "
|
121
|
-
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
122
|
-
),
|
123
|
-
logger_name=logger_name,
|
124
|
-
)
|
125
|
-
else:
|
126
|
-
logger.debug(
|
127
|
-
f"Currently installed version of {pinned_pkg_name} "
|
128
|
-
f"({current_version}) already matches the pinned version."
|
129
|
-
)
|
130
|
-
|
131
|
-
# Extract package installation path from `pip show`
|
132
|
-
stdout_show = await execute_command(
|
133
|
-
cwd=Path(task_group.venv_path),
|
134
|
-
command=f"{python_bin} -m pip show {task_group.pkg_name}",
|
135
|
-
logger_name=logger_name,
|
136
|
-
)
|
137
|
-
|
138
|
-
location = Path(
|
139
|
-
next(
|
140
|
-
line.split()[-1]
|
141
|
-
for line in stdout_show.split("\n")
|
142
|
-
if line.startswith("Location:")
|
143
|
-
)
|
144
|
-
)
|
145
|
-
|
146
|
-
# NOTE
|
147
|
-
# https://packaging.python.org/en/latest/specifications/recording-installed-packages/
|
148
|
-
# This directory is named as {name}-{version}.dist-info, with name and
|
149
|
-
# version fields corresponding to Core metadata specifications. Both
|
150
|
-
# fields must be normalized (see the name normalization specification and
|
151
|
-
# the version normalization specification), and replace dash (-)
|
152
|
-
# characters with underscore (_) characters, so the .dist-info directory
|
153
|
-
# always has exactly one dash (-) character in its stem, separating the
|
154
|
-
# name and version fields.
|
155
|
-
package_root = location / (task_group.pkg_name.replace("-", "_"))
|
156
|
-
logger.debug(f"[_pip install] {location=}")
|
157
|
-
logger.debug(f"[_pip install] {task_group.pkg_name=}")
|
158
|
-
logger.debug(f"[_pip install] {package_root=}")
|
159
|
-
|
160
|
-
# Run `pip freeze --all` and store its output
|
161
|
-
stdout_freeze = await execute_command(
|
162
|
-
cwd=Path(task_group.venv_path),
|
163
|
-
command=f"{python_bin} -m pip freeze --all",
|
164
|
-
logger_name=logger_name,
|
165
|
-
)
|
166
|
-
with (Path(task_group.path) / COLLECTION_FREEZE_FILENAME).open("w") as f:
|
167
|
-
f.write(stdout_freeze)
|
168
|
-
|
169
|
-
return package_root
|
170
|
-
|
171
|
-
|
172
|
-
async def _create_venv_install_package_pip(
|
173
|
-
*,
|
174
|
-
task_group: TaskGroupV2,
|
175
|
-
logger_name: str,
|
176
|
-
) -> tuple[Path, Path]:
|
177
|
-
"""
|
178
|
-
Create venv and install package
|
179
|
-
|
180
|
-
Args:
|
181
|
-
path: the directory in which to create the environment
|
182
|
-
task_pkg: object containing the different metadata required to install
|
183
|
-
the package
|
184
|
-
|
185
|
-
Returns:
|
186
|
-
python_bin: path to venv's python interpreter
|
187
|
-
package_root: the location of the package manifest
|
188
|
-
"""
|
189
|
-
python_bin = await _init_venv_v2(
|
190
|
-
venv_path=Path(task_group.venv_path),
|
191
|
-
python_version=task_group.python_version,
|
192
|
-
logger_name=logger_name,
|
193
|
-
)
|
194
|
-
package_root = await _pip_install(
|
195
|
-
task_group=task_group,
|
196
|
-
logger_name=logger_name,
|
197
|
-
)
|
198
|
-
return python_bin, package_root
|