nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__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.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
nextmv/cloud/package.py
CHANGED
|
@@ -6,33 +6,37 @@ import platform
|
|
|
6
6
|
import re
|
|
7
7
|
import shutil
|
|
8
8
|
import subprocess
|
|
9
|
+
import sys
|
|
9
10
|
import tarfile
|
|
10
11
|
import tempfile
|
|
11
|
-
from typing import Optional
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
import rich
|
|
14
|
+
|
|
14
15
|
from nextmv.logger import log
|
|
16
|
+
from nextmv.manifest import MANIFEST_FILE_NAME, Manifest, ManifestBuild, ManifestType
|
|
15
17
|
from nextmv.model import Model, ModelConfiguration, _cleanup_python_model
|
|
16
18
|
|
|
17
19
|
_MANDATORY_FILES_PER_TYPE = {
|
|
18
20
|
ManifestType.PYTHON: ["main.py"],
|
|
19
21
|
ManifestType.GO: ["main"],
|
|
22
|
+
ManifestType.BINARY: ["main"],
|
|
20
23
|
ManifestType.JAVA: ["main.jar"],
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
|
|
24
|
-
def _package(
|
|
27
|
+
def _package( # noqa: C901 # complexity attributed to printing.
|
|
25
28
|
app_dir: str,
|
|
26
29
|
manifest: Manifest,
|
|
27
|
-
model:
|
|
28
|
-
model_configuration:
|
|
30
|
+
model: Model | None = None,
|
|
31
|
+
model_configuration: ModelConfiguration | None = None,
|
|
29
32
|
verbose: bool = False,
|
|
33
|
+
rich_print: bool = False,
|
|
30
34
|
) -> tuple[str, str]:
|
|
31
|
-
"""Package the app into a tarball
|
|
35
|
+
"""Package the app into a tarball."""
|
|
32
36
|
|
|
33
37
|
with tempfile.TemporaryDirectory(prefix="nextmv-temp-") as temp_dir:
|
|
34
38
|
if manifest.type == ManifestType.PYTHON:
|
|
35
|
-
__handle_python(app_dir, temp_dir, manifest, model, model_configuration, verbose)
|
|
39
|
+
__handle_python(app_dir, temp_dir, manifest, model, model_configuration, verbose, rich_print)
|
|
36
40
|
|
|
37
41
|
found, missing, files = __find_files(app_dir, manifest.files)
|
|
38
42
|
__confirm_mandatory_files(manifest, found)
|
|
@@ -55,7 +59,13 @@ def _package(
|
|
|
55
59
|
raise Exception(f"error copying asset files {file['absolute_path']}: {e}") from e
|
|
56
60
|
|
|
57
61
|
if verbose:
|
|
58
|
-
|
|
62
|
+
if rich_print:
|
|
63
|
+
rich.print(
|
|
64
|
+
f":clipboard: Copied files listed in [magenta]{MANIFEST_FILE_NAME}[/magenta] manifest.",
|
|
65
|
+
file=sys.stderr,
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
log(f'📋 Copied files listed in "{MANIFEST_FILE_NAME}" manifest.')
|
|
59
69
|
|
|
60
70
|
if manifest.type == ManifestType.PYTHON:
|
|
61
71
|
_cleanup_python_model(app_dir, model_configuration, verbose)
|
|
@@ -66,17 +76,31 @@ def _package(
|
|
|
66
76
|
if verbose:
|
|
67
77
|
try:
|
|
68
78
|
size = __human_friendly_file_size(tar_file)
|
|
69
|
-
|
|
79
|
+
if rich_print:
|
|
80
|
+
rich.print(
|
|
81
|
+
":package: Packaged application "
|
|
82
|
+
f"([magenta]{file_count_msg}[/magenta], [magenta]{size}[/magenta]).",
|
|
83
|
+
file=sys.stderr,
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
log(f"📦 Packaged application ({file_count_msg}, {size}).")
|
|
70
87
|
except Exception:
|
|
71
|
-
|
|
88
|
+
if rich_print:
|
|
89
|
+
rich.print(
|
|
90
|
+
f":package: Packaged application ([magenta]{file_count_msg}[/magenta]).",
|
|
91
|
+
file=sys.stderr,
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
log(f"📦 Packaged application ({file_count_msg}).")
|
|
72
95
|
|
|
73
96
|
return tar_file, output_dir
|
|
74
97
|
|
|
75
98
|
|
|
76
99
|
def _run_build_command(
|
|
77
100
|
app_dir: str,
|
|
78
|
-
manifest_build:
|
|
101
|
+
manifest_build: ManifestBuild | None = None,
|
|
79
102
|
verbose: bool = False,
|
|
103
|
+
rich_print: bool = False,
|
|
80
104
|
) -> None:
|
|
81
105
|
"""Run the build command specified in the manifest."""
|
|
82
106
|
|
|
@@ -85,7 +109,12 @@ def _run_build_command(
|
|
|
85
109
|
|
|
86
110
|
elements = manifest_build.command.split(" ")
|
|
87
111
|
command_str = " ".join(elements)
|
|
88
|
-
|
|
112
|
+
|
|
113
|
+
if verbose:
|
|
114
|
+
if rich_print:
|
|
115
|
+
rich.print(f":construction: Running build command: [magenta]{command_str}[/magenta]", file=sys.stderr)
|
|
116
|
+
else:
|
|
117
|
+
log(f'🚧 Running build command: "{command_str}"')
|
|
89
118
|
try:
|
|
90
119
|
result = subprocess.run(
|
|
91
120
|
elements,
|
|
@@ -103,22 +132,38 @@ def _run_build_command(
|
|
|
103
132
|
log(result.stdout)
|
|
104
133
|
|
|
105
134
|
|
|
135
|
+
def _get_shell_command_elements(pre_push_command):
|
|
136
|
+
"""Get the shell command elements based on the operating system."""
|
|
137
|
+
# Check if we're in a Unix-like shell (including MINGW on Windows)
|
|
138
|
+
if "SHELL" in os.environ and shutil.which("bash"):
|
|
139
|
+
return ["bash", "-c", pre_push_command]
|
|
140
|
+
# Default to cmd on Windows
|
|
141
|
+
elif platform.system() == "Windows":
|
|
142
|
+
return ["cmd", "/c", pre_push_command]
|
|
143
|
+
# Default to sh on Unix-like systems (Linux, macOS)
|
|
144
|
+
else:
|
|
145
|
+
return ["sh", "-c", pre_push_command]
|
|
146
|
+
|
|
147
|
+
|
|
106
148
|
def _run_pre_push_command(
|
|
107
149
|
app_dir: str,
|
|
108
|
-
pre_push_command:
|
|
150
|
+
pre_push_command: str | None = None,
|
|
109
151
|
verbose: bool = False,
|
|
152
|
+
rich_print: bool = False,
|
|
110
153
|
) -> None:
|
|
111
154
|
"""Run the pre-push command specified in the manifest."""
|
|
112
155
|
|
|
113
156
|
if pre_push_command is None or pre_push_command == "":
|
|
114
157
|
return
|
|
115
158
|
|
|
116
|
-
elements =
|
|
117
|
-
if platform.system() == "Windows":
|
|
118
|
-
elements = ["cmd", "/c", pre_push_command]
|
|
159
|
+
elements = _get_shell_command_elements(pre_push_command)
|
|
119
160
|
|
|
120
161
|
command_str = " ".join(elements)
|
|
121
|
-
|
|
162
|
+
if verbose:
|
|
163
|
+
if rich_print:
|
|
164
|
+
rich.print(f":hammer: Running pre-push command: [magenta]{command_str}[/magenta]", file=sys.stderr)
|
|
165
|
+
else:
|
|
166
|
+
log(f'🔨 Running pre-push command: "{command_str}"')
|
|
122
167
|
try:
|
|
123
168
|
result = subprocess.run(
|
|
124
169
|
elements,
|
|
@@ -194,8 +239,15 @@ def __find_files(
|
|
|
194
239
|
def __confirm_mandatory_files(manifest: Manifest, present_files: list[str]) -> None:
|
|
195
240
|
"""Confirm that all mandatory files are present in the given list of files."""
|
|
196
241
|
|
|
197
|
-
mandatory_files = _MANDATORY_FILES_PER_TYPE[manifest.type]
|
|
198
242
|
found_files = {os.path.normpath(file): True for file in present_files}
|
|
243
|
+
|
|
244
|
+
# Check for mandatory files (if a custom execution config is provided we check the
|
|
245
|
+
# custom entrypoint instead)
|
|
246
|
+
mandatory_files = []
|
|
247
|
+
if manifest.execution is None or manifest.execution.entrypoint is None:
|
|
248
|
+
mandatory_files = _MANDATORY_FILES_PER_TYPE[manifest.type]
|
|
249
|
+
else:
|
|
250
|
+
mandatory_files.append(os.path.normpath(manifest.execution.entrypoint))
|
|
199
251
|
missing_files = [file for file in mandatory_files if file not in found_files]
|
|
200
252
|
|
|
201
253
|
if missing_files:
|
|
@@ -206,23 +258,30 @@ def __handle_python(
|
|
|
206
258
|
app_dir: str,
|
|
207
259
|
temp_dir: str,
|
|
208
260
|
manifest: Manifest,
|
|
209
|
-
model:
|
|
210
|
-
model_configuration:
|
|
261
|
+
model: Model | None = None,
|
|
262
|
+
model_configuration: ModelConfiguration | None = None,
|
|
211
263
|
verbose: bool = False,
|
|
264
|
+
rich_print: bool = False,
|
|
212
265
|
) -> None:
|
|
213
266
|
"""Handles the Python-specific packaging logic."""
|
|
214
267
|
|
|
215
268
|
if model is not None and model_configuration is not None:
|
|
216
269
|
if verbose:
|
|
217
|
-
|
|
270
|
+
if rich_print:
|
|
271
|
+
rich.print(":crystal_ball: Encoding Python model.", file=sys.stderr)
|
|
272
|
+
else:
|
|
273
|
+
log("🔮 Encoding Python model.")
|
|
218
274
|
model.save(app_dir, model_configuration)
|
|
219
275
|
|
|
220
276
|
if verbose:
|
|
221
|
-
|
|
277
|
+
if rich_print:
|
|
278
|
+
rich.print(":snake: Bundling Python dependencies.", file=sys.stderr)
|
|
279
|
+
else:
|
|
280
|
+
log("🐍 Bundling Python dependencies.")
|
|
222
281
|
__install_dependencies(manifest, app_dir, temp_dir)
|
|
223
282
|
|
|
224
283
|
|
|
225
|
-
def __install_dependencies(
|
|
284
|
+
def __install_dependencies( # noqa: C901 # complexity
|
|
226
285
|
manifest: Manifest,
|
|
227
286
|
app_dir: str,
|
|
228
287
|
temp_dir: str,
|
|
@@ -233,46 +292,91 @@ def __install_dependencies(
|
|
|
233
292
|
return
|
|
234
293
|
|
|
235
294
|
pip_requirements = manifest.python.pip_requirements
|
|
295
|
+
|
|
236
296
|
if pip_requirements is None or pip_requirements == "":
|
|
297
|
+
# If no pip requirements are specified, we do not install any dependencies.
|
|
237
298
|
return
|
|
238
299
|
|
|
239
|
-
if
|
|
240
|
-
|
|
300
|
+
if isinstance(pip_requirements, list):
|
|
301
|
+
# If pip_requirements is a list, we write it to a temporary file so that we can
|
|
302
|
+
# pass it to pip.
|
|
303
|
+
pip_requirements_file = os.path.join(temp_dir, "requirements.txt")
|
|
304
|
+
with open(pip_requirements_file, "w") as f:
|
|
305
|
+
for requirement in pip_requirements:
|
|
306
|
+
f.write(requirement + "\n")
|
|
307
|
+
pip_requirements = pip_requirements_file
|
|
308
|
+
elif isinstance(pip_requirements, str):
|
|
309
|
+
# If pip_requirements is a string, we expect it to be a file path to a
|
|
310
|
+
# requirements file.
|
|
311
|
+
pip_requirements = pip_requirements.strip()
|
|
312
|
+
if not os.path.isfile(os.path.join(app_dir, pip_requirements)):
|
|
313
|
+
raise FileNotFoundError(f"pip requirements file '{pip_requirements}' not found in '{app_dir}'")
|
|
314
|
+
|
|
315
|
+
platform_filter = []
|
|
316
|
+
if not manifest.python.arch or manifest.python.arch == "arm64":
|
|
317
|
+
platform_filter.extend(
|
|
318
|
+
[
|
|
319
|
+
"--platform=manylinux2014_aarch64",
|
|
320
|
+
"--platform=manylinux_2_17_aarch64",
|
|
321
|
+
"--platform=manylinux_2_24_aarch64",
|
|
322
|
+
"--platform=manylinux_2_26_aarch64",
|
|
323
|
+
"--platform=manylinux_2_28_aarch64",
|
|
324
|
+
"--platform=manylinux_2_34_aarch64",
|
|
325
|
+
"--platform=linux_aarch64",
|
|
326
|
+
]
|
|
327
|
+
)
|
|
328
|
+
elif manifest.python.arch == "amd64":
|
|
329
|
+
platform_filter.extend(
|
|
330
|
+
[
|
|
331
|
+
"--platform=manylinux2014_x86_64",
|
|
332
|
+
"--platform=manylinux_2_17_x86_64",
|
|
333
|
+
"--platform=manylinux_2_24_x86_64",
|
|
334
|
+
"--platform=manylinux_2_26_x86_64",
|
|
335
|
+
"--platform=manylinux_2_28_x86_64",
|
|
336
|
+
"--platform=manylinux_2_34_x86_64",
|
|
337
|
+
"--platform=linux_x86_64",
|
|
338
|
+
]
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
raise Exception(f"unknown architecture '{manifest.python.arch}' specified in manifest")
|
|
342
|
+
|
|
343
|
+
version_filter = ["--python-version=3.11"]
|
|
344
|
+
if manifest.python.version:
|
|
345
|
+
__confirm_python_bundling_version(manifest.python.version)
|
|
346
|
+
version_filter = [f"--python-version={manifest.python.version}"]
|
|
241
347
|
|
|
242
348
|
py_cmd = __get_python_command()
|
|
243
349
|
dep_dir = os.path.join(".nextmv", "python", "deps")
|
|
244
|
-
command =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
"--quiet",
|
|
266
|
-
]
|
|
350
|
+
command = (
|
|
351
|
+
[
|
|
352
|
+
py_cmd,
|
|
353
|
+
"-m",
|
|
354
|
+
"pip",
|
|
355
|
+
"install",
|
|
356
|
+
"-r",
|
|
357
|
+
pip_requirements,
|
|
358
|
+
"--only-binary=:all:",
|
|
359
|
+
"--implementation=cp",
|
|
360
|
+
"--upgrade",
|
|
361
|
+
"--no-warn-conflicts",
|
|
362
|
+
"--target",
|
|
363
|
+
os.path.join(temp_dir, dep_dir),
|
|
364
|
+
"--no-user", # We explicitly avoid user mode (mainly to fix issues with Windows store Python installations)
|
|
365
|
+
"--no-input",
|
|
366
|
+
"--quiet",
|
|
367
|
+
]
|
|
368
|
+
+ platform_filter
|
|
369
|
+
+ version_filter
|
|
370
|
+
)
|
|
267
371
|
result = subprocess.run(
|
|
268
372
|
command,
|
|
269
373
|
cwd=app_dir,
|
|
374
|
+
stdout=subprocess.PIPE,
|
|
375
|
+
stderr=subprocess.STDOUT, # Merge stderr into stdout
|
|
270
376
|
text=True,
|
|
271
|
-
capture_output=True,
|
|
272
|
-
check=True,
|
|
273
377
|
)
|
|
274
378
|
if result.returncode != 0:
|
|
275
|
-
raise Exception(f"error installing dependencies: {result.
|
|
379
|
+
raise Exception(f"error installing dependencies: {os.linesep}{result.stdout}")
|
|
276
380
|
|
|
277
381
|
|
|
278
382
|
def __run_command(binary: str, dir: str, redirect_out_err: bool, *arguments: str) -> str:
|
|
@@ -361,10 +465,21 @@ def __confirm_python_version(output: str) -> None:
|
|
|
361
465
|
except ValueError:
|
|
362
466
|
major, minor = map(int, version.split("."))
|
|
363
467
|
|
|
364
|
-
if major == 3 and minor >=
|
|
468
|
+
if major == 3 and minor >= 10:
|
|
365
469
|
return
|
|
366
470
|
|
|
367
|
-
raise Exception("python version 3.
|
|
471
|
+
raise Exception("python version 3.10 or higher is required")
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def __confirm_python_bundling_version(version: str) -> None:
|
|
475
|
+
# Only accept versions in the form "major.minor" where both are integers
|
|
476
|
+
re_version = re.compile(r"^(\d+)\.(\d+)$")
|
|
477
|
+
match = re_version.fullmatch(version)
|
|
478
|
+
if match:
|
|
479
|
+
major, minor = int(match.group(1)), int(match.group(2))
|
|
480
|
+
if major == 3 and minor >= 10:
|
|
481
|
+
return
|
|
482
|
+
raise Exception(f"python version 3.10 or higher is required for bundling, got {version}")
|
|
368
483
|
|
|
369
484
|
|
|
370
485
|
def __compress_tar(source: str, target: str) -> tuple[str, int]:
|