nextmv 0.39.0.dev1__py3-none-any.whl → 1.0.0__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 +1 -2
- nextmv/__init__.py +2 -4
- nextmv/cli/CONTRIBUTING.md +583 -0
- nextmv/cli/cloud/__init__.py +49 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +391 -0
- nextmv/cli/cloud/acceptance/delete.py +64 -0
- nextmv/cli/cloud/acceptance/get.py +103 -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 +59 -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 +140 -0
- nextmv/cli/cloud/app/delete.py +57 -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 +432 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +452 -0
- nextmv/cli/cloud/batch/delete.py +64 -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 +33 -0
- nextmv/cli/cloud/ensemble/create.py +413 -0
- nextmv/cli/cloud/ensemble/delete.py +63 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +32 -0
- nextmv/cli/cloud/input_set/create.py +168 -0
- nextmv/cli/cloud/input_set/delete.py +64 -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 +289 -0
- nextmv/cli/cloud/instance/delete.py +61 -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 +144 -0
- nextmv/cli/cloud/managed_input/delete.py +64 -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 +524 -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 +166 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +500 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +61 -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 +63 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +144 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +64 -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 +53 -0
- nextmv/cli/cloud/shadow/update.py +96 -0
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +151 -0
- nextmv/cli/cloud/switchback/delete.py +64 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +53 -0
- nextmv/cli/cloud/switchback/update.py +96 -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 +96 -0
- nextmv/cli/cloud/version/delete.py +61 -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 +86 -0
- nextmv/cli/community/list.py +200 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +228 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/confirm.py +34 -0
- nextmv/cli/main.py +161 -3
- nextmv/cli/message.py +170 -0
- nextmv/cli/options.py +220 -0
- nextmv/cli/version.py +22 -2
- nextmv/cloud/__init__.py +17 -38
- nextmv/cloud/acceptance_test.py +20 -83
- nextmv/cloud/account.py +269 -30
- nextmv/cloud/application/__init__.py +898 -0
- nextmv/cloud/application/_acceptance.py +424 -0
- nextmv/cloud/application/_batch_scenario.py +845 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +263 -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 +320 -0
- nextmv/cloud/application/_switchback.py +332 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +304 -0
- nextmv/cloud/batch_experiment.py +6 -2
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/instance.py +11 -1
- nextmv/cloud/integration.py +8 -5
- nextmv/cloud/package.py +50 -9
- nextmv/cloud/shadow.py +254 -0
- nextmv/cloud/switchback.py +228 -0
- nextmv/deprecated.py +5 -3
- nextmv/input.py +20 -88
- nextmv/local/application.py +3 -15
- nextmv/local/runner.py +1 -1
- nextmv/model.py +50 -11
- nextmv/options.py +11 -256
- nextmv/output.py +0 -62
- nextmv/polling.py +54 -16
- nextmv/run.py +84 -37
- nextmv/status.py +1 -51
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
- nextmv-1.0.0.dist-info/RECORD +185 -0
- nextmv-1.0.0.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -4204
- nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
- nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud run track command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from nextmv.cli.cloud.run.create import build_run_config
|
|
13
|
+
from nextmv.cli.configuration.config import build_app
|
|
14
|
+
from nextmv.cli.message import enum_values, error, in_progress, print_json
|
|
15
|
+
from nextmv.cli.options import AppIDOption, ProfileOption
|
|
16
|
+
from nextmv.input import InputFormat
|
|
17
|
+
from nextmv.run import RunType, TrackedRun, TrackedRunStatus
|
|
18
|
+
|
|
19
|
+
# Set up subcommand application.
|
|
20
|
+
app = typer.Typer()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def track(
|
|
25
|
+
app_id: AppIDOption,
|
|
26
|
+
# Options for controlling the tracked run.
|
|
27
|
+
output: Annotated[
|
|
28
|
+
str,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--output",
|
|
31
|
+
"-o",
|
|
32
|
+
help="The output of the run being tracked. A file or directory depending on content format.",
|
|
33
|
+
metavar="OUTPUT_PATH",
|
|
34
|
+
rich_help_panel="Tracked run configuration",
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
status: Annotated[
|
|
38
|
+
TrackedRunStatus,
|
|
39
|
+
typer.Option(
|
|
40
|
+
"--status",
|
|
41
|
+
"-s",
|
|
42
|
+
help=f"Status of the tracked run. Allowed values are: {enum_values(TrackedRunStatus)}.",
|
|
43
|
+
metavar="STATUS",
|
|
44
|
+
rich_help_panel="Tracked run configuration",
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
assets: Annotated[
|
|
48
|
+
str | None,
|
|
49
|
+
typer.Option(
|
|
50
|
+
help="The assets of the run being tracked. A [magenta]json[/magenta] file to read the assets from.",
|
|
51
|
+
metavar="ASSETS_PATH",
|
|
52
|
+
rich_help_panel="Tracked run configuration",
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
content_format: Annotated[
|
|
56
|
+
InputFormat | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--content-format",
|
|
59
|
+
"-c",
|
|
60
|
+
help=f"The content format of the run to track. Allowed values are: {enum_values(InputFormat)}.",
|
|
61
|
+
metavar="CONTENT_FORMAT",
|
|
62
|
+
rich_help_panel="Tracked run configuration",
|
|
63
|
+
),
|
|
64
|
+
] = InputFormat.JSON,
|
|
65
|
+
description: Annotated[
|
|
66
|
+
str | None,
|
|
67
|
+
typer.Option(
|
|
68
|
+
help="An optional description for the tracked run.",
|
|
69
|
+
metavar="DESCRIPTION",
|
|
70
|
+
rich_help_panel="Tracked run configuration",
|
|
71
|
+
),
|
|
72
|
+
] = None,
|
|
73
|
+
duration: Annotated[
|
|
74
|
+
int,
|
|
75
|
+
typer.Option(
|
|
76
|
+
"--duration",
|
|
77
|
+
"-d",
|
|
78
|
+
help="The duration of the run being tracked, in milliseconds.",
|
|
79
|
+
metavar="DURATION_MS",
|
|
80
|
+
rich_help_panel="Tracked run configuration",
|
|
81
|
+
),
|
|
82
|
+
] = 0,
|
|
83
|
+
error_msg: Annotated[
|
|
84
|
+
str | None,
|
|
85
|
+
typer.Option(
|
|
86
|
+
"--error-msg",
|
|
87
|
+
"-e",
|
|
88
|
+
help="An error message if the run being tracked failed.",
|
|
89
|
+
metavar="ERROR_MESSAGE",
|
|
90
|
+
rich_help_panel="Tracked run configuration",
|
|
91
|
+
),
|
|
92
|
+
] = None,
|
|
93
|
+
input: Annotated[
|
|
94
|
+
str | None,
|
|
95
|
+
typer.Option(
|
|
96
|
+
"--input",
|
|
97
|
+
"-i",
|
|
98
|
+
help="The input of the run being tracked. File or directory depending on content format. "
|
|
99
|
+
"Uses [magenta]stdin[/magenta] if not defined.",
|
|
100
|
+
metavar="INPUT_PATH",
|
|
101
|
+
rich_help_panel="Tracked run configuration",
|
|
102
|
+
),
|
|
103
|
+
] = None,
|
|
104
|
+
logs: Annotated[
|
|
105
|
+
str | None,
|
|
106
|
+
typer.Option(
|
|
107
|
+
"--logs",
|
|
108
|
+
"-l",
|
|
109
|
+
help="The logs of the run being tracked. A utf-8 encoded text file to read the logs from.",
|
|
110
|
+
metavar="LOGS_PATH",
|
|
111
|
+
rich_help_panel="Tracked run configuration",
|
|
112
|
+
),
|
|
113
|
+
] = None,
|
|
114
|
+
name: Annotated[
|
|
115
|
+
str | None,
|
|
116
|
+
typer.Option(
|
|
117
|
+
"--name",
|
|
118
|
+
"-n",
|
|
119
|
+
help="An optional name for the tracked run.",
|
|
120
|
+
metavar="NAME",
|
|
121
|
+
rich_help_panel="Tracked run configuration",
|
|
122
|
+
),
|
|
123
|
+
] = None,
|
|
124
|
+
statistics: Annotated[
|
|
125
|
+
str | None,
|
|
126
|
+
typer.Option(
|
|
127
|
+
help="The statistics of the run being tracked. A [magenta]json[/magenta] file to read the statistics from.",
|
|
128
|
+
metavar="STATISTICS_PATH",
|
|
129
|
+
rich_help_panel="Tracked run configuration",
|
|
130
|
+
),
|
|
131
|
+
] = None,
|
|
132
|
+
# Options for run configuration.
|
|
133
|
+
instance_id: Annotated[
|
|
134
|
+
str | None,
|
|
135
|
+
typer.Option(
|
|
136
|
+
help="The instance ID to use for the run.",
|
|
137
|
+
metavar="INSTANCE_ID",
|
|
138
|
+
rich_help_panel="Run configuration",
|
|
139
|
+
),
|
|
140
|
+
] = "latest",
|
|
141
|
+
profile: ProfileOption = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Track an external run as a Nextmv Cloud application run.
|
|
145
|
+
|
|
146
|
+
Please see the help of the --content-type option for details on valid
|
|
147
|
+
content types.
|
|
148
|
+
|
|
149
|
+
If the content type is [magenta]json[/magenta] or [magenta]text[/magenta],
|
|
150
|
+
then input for the run can be given through [magenta]stdin[/magenta]. The
|
|
151
|
+
--input option allows you to specify a file or directory path for the
|
|
152
|
+
input, instead of using [magenta]stdin[/magenta]. In the case of
|
|
153
|
+
[magenta]multi-file[/magenta] content type, the input must be given through
|
|
154
|
+
a directory specified via the --input option.
|
|
155
|
+
|
|
156
|
+
The --output option allows you to specify a file or directory path for the
|
|
157
|
+
output of the run. The behavior depends on the content type. If the content
|
|
158
|
+
type is [magenta]json[/magenta] or [magenta]text[/magenta], then a file
|
|
159
|
+
path must be provided. If the content type is
|
|
160
|
+
[magenta]multi-file[/magenta], then a directory path must be provided.
|
|
161
|
+
|
|
162
|
+
Run logs, assets, and statistics can be provided via files using the
|
|
163
|
+
--logs, --assets, and --statistics options, respectively. Assets and
|
|
164
|
+
statistics must be provided as [magenta]json[/magenta] files, while logs
|
|
165
|
+
must be provided as a utf-8 encoded text file.
|
|
166
|
+
|
|
167
|
+
[bold][underline]Examples[/underline][/bold]
|
|
168
|
+
|
|
169
|
+
- Track a [magenta]successful[/magenta] [magenta]json[/magenta] run via [magenta]stdin[/magenta]
|
|
170
|
+
input, for an app with ID [magenta]hare-app[/magenta].
|
|
171
|
+
$ [dim]cat input.json | nextmv cloud run track --app-id hare-app --status succeeded[/dim]
|
|
172
|
+
|
|
173
|
+
- Track a [magenta]successful[/magenta] [magenta]json[/magenta] run with input from an
|
|
174
|
+
[magenta]input.json[/magenta] file and output from an
|
|
175
|
+
[magenta]output.json[/magenta] file, for an app with ID
|
|
176
|
+
[magenta]hare-app[/magenta].
|
|
177
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.json \\
|
|
178
|
+
--output output.json[/dim]
|
|
179
|
+
|
|
180
|
+
- Track a [magenta]successful[/magenta] [magenta]json[/magenta] run including logs from a
|
|
181
|
+
[magenta]logs.log[/magenta] file, for an app with ID
|
|
182
|
+
[magenta]hare-app[/magenta].
|
|
183
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.json \\
|
|
184
|
+
--output output.json --logs logs.log[/dim]
|
|
185
|
+
|
|
186
|
+
- Track a [magenta]successful[/magenta] [magenta]json[/magenta] run with assets and statistics
|
|
187
|
+
from [magenta]json[/magenta] files, for an app with ID
|
|
188
|
+
[magenta]hare-app[/magenta].
|
|
189
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.json \\
|
|
190
|
+
--output output.json --assets assets.json --statistics statistics.json[/dim]
|
|
191
|
+
|
|
192
|
+
- Track a [magenta]failed[/magenta] run with an error message, for an app with ID
|
|
193
|
+
[magenta]hare-app[/magenta].
|
|
194
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status failed --input input.json \\
|
|
195
|
+
--error-msg "Solver timed out"[/dim]
|
|
196
|
+
|
|
197
|
+
- Track a [magenta]successful[/magenta] [magenta]text[/magenta] run with text content type,
|
|
198
|
+
for an app with ID [magenta]hare-app[/magenta].
|
|
199
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.txt \\
|
|
200
|
+
--output output.txt --content-type text[/dim]
|
|
201
|
+
|
|
202
|
+
- Track a [magenta]successful[/magenta] [magenta]multi-file[/magenta] run from an
|
|
203
|
+
[magenta]inputs[/magenta] directory with output to an
|
|
204
|
+
[magenta]outputs[/magenta] directory, for an app with ID
|
|
205
|
+
[magenta]hare-app[/magenta], using the [magenta]default[/magenta]
|
|
206
|
+
instance.
|
|
207
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input inputs \\
|
|
208
|
+
--output outputs --content-type multi-file --instance-id default[/dim]
|
|
209
|
+
|
|
210
|
+
- Track a [magenta]successful[/magenta] run with a name, description, and duration, for an app
|
|
211
|
+
with ID [magenta]hare-app[/magenta].
|
|
212
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.json \\
|
|
213
|
+
--output output.json --name "Production run" --description "Weekly optimization" --duration 5000[/dim]
|
|
214
|
+
|
|
215
|
+
- Track a [magenta]successful[/magenta] [magenta]json[/magenta] run with all available options,
|
|
216
|
+
for an app with ID [magenta]hare-app[/magenta].
|
|
217
|
+
$ [dim]nextmv cloud run track --app-id hare-app --status succeeded --input input.json \\
|
|
218
|
+
--output output.json --logs logs.log --assets assets.json --statistics statistics.json \\
|
|
219
|
+
--name "Full run" --description "Complete example" --duration 10000 --instance-id burrow[/dim]
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
# Validate that input is provided.
|
|
223
|
+
stdin = sys.stdin.read().strip() if sys.stdin.isatty() is False else None
|
|
224
|
+
if stdin is None and (input is None or input == ""):
|
|
225
|
+
error("Input data must be provided via the --input flag or [magenta]stdin[/magenta].")
|
|
226
|
+
|
|
227
|
+
# Instantiate the basic requirements to start a new run.
|
|
228
|
+
cloud_app = build_app(app_id=app_id, profile=profile)
|
|
229
|
+
config = build_run_config(
|
|
230
|
+
run_type=RunType.EXTERNAL,
|
|
231
|
+
priority=6,
|
|
232
|
+
no_queuing=False,
|
|
233
|
+
content_format=content_format,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Handles the default instance.
|
|
237
|
+
if instance_id == "default":
|
|
238
|
+
instance_id = ""
|
|
239
|
+
|
|
240
|
+
# Build the tracked run input.
|
|
241
|
+
tracked_run = build_tracked_run_input(
|
|
242
|
+
status=status,
|
|
243
|
+
duration=duration,
|
|
244
|
+
error_msg=error_msg,
|
|
245
|
+
name=name,
|
|
246
|
+
description=description,
|
|
247
|
+
stdin=stdin,
|
|
248
|
+
input=input,
|
|
249
|
+
content_format=content_format,
|
|
250
|
+
output=output,
|
|
251
|
+
assets=assets,
|
|
252
|
+
logs=logs,
|
|
253
|
+
statistics=statistics,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Actually track the run.
|
|
257
|
+
in_progress(msg="Tracking run...")
|
|
258
|
+
run_id = cloud_app.track_run(
|
|
259
|
+
tracked_run=tracked_run,
|
|
260
|
+
instance_id=instance_id,
|
|
261
|
+
configuration=config,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
print_json({"run_id": run_id})
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def build_tracked_run_input(
|
|
268
|
+
status: TrackedRunStatus,
|
|
269
|
+
duration: int,
|
|
270
|
+
error_msg: str | None,
|
|
271
|
+
name: str | None,
|
|
272
|
+
description: str | None,
|
|
273
|
+
stdin: str | None,
|
|
274
|
+
input: str | None,
|
|
275
|
+
content_format: InputFormat,
|
|
276
|
+
output: str,
|
|
277
|
+
assets: str | None = None,
|
|
278
|
+
logs: str | None = None,
|
|
279
|
+
statistics: str | None = None,
|
|
280
|
+
) -> TrackedRun:
|
|
281
|
+
"""
|
|
282
|
+
Builds the tracked run input for tracking a run. Starts by creating a
|
|
283
|
+
TrackedRun object with the provided status, duration, error message, name,
|
|
284
|
+
and description. Then resolves the input and output using helper functions.
|
|
285
|
+
Finally, it reads the assets, logs, and statistics from the provided file
|
|
286
|
+
paths, if any, and assigns them to the TrackedRun object.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
status : TrackedRunStatus
|
|
291
|
+
The status of the tracked run.
|
|
292
|
+
duration : int
|
|
293
|
+
The duration of the tracked run in milliseconds.
|
|
294
|
+
error_msg : str | None
|
|
295
|
+
An error message if the run failed.
|
|
296
|
+
name : str | None
|
|
297
|
+
An optional name for the tracked run.
|
|
298
|
+
description : str | None
|
|
299
|
+
An optional description for the tracked run.
|
|
300
|
+
stdin : str | None
|
|
301
|
+
The input provided via stdin, if any.
|
|
302
|
+
input : str | None
|
|
303
|
+
The input file or directory path, if any.
|
|
304
|
+
content_format : InputFormat
|
|
305
|
+
The content format of the input (json or text).
|
|
306
|
+
output : str
|
|
307
|
+
The output file or directory path.
|
|
308
|
+
assets : str | None
|
|
309
|
+
The assets file path, if any.
|
|
310
|
+
logs : str | None
|
|
311
|
+
The logs file path, if any.
|
|
312
|
+
statistics : str | None
|
|
313
|
+
The statistics file path, if any.
|
|
314
|
+
"""
|
|
315
|
+
tracked_run = TrackedRun(
|
|
316
|
+
status=TrackedRunStatus(status),
|
|
317
|
+
duration=duration,
|
|
318
|
+
error=error_msg,
|
|
319
|
+
name=name,
|
|
320
|
+
description=description,
|
|
321
|
+
)
|
|
322
|
+
tracked_run = resolve_input(
|
|
323
|
+
tracked_run=tracked_run,
|
|
324
|
+
stdin=stdin,
|
|
325
|
+
input=input,
|
|
326
|
+
content_format=content_format,
|
|
327
|
+
)
|
|
328
|
+
tracked_run = resolve_output(
|
|
329
|
+
tracked_run=tracked_run,
|
|
330
|
+
output=output,
|
|
331
|
+
content_format=content_format,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Handle the assets, which should be a JSON file.
|
|
335
|
+
if assets is not None and assets != "":
|
|
336
|
+
try:
|
|
337
|
+
with open(assets) as f:
|
|
338
|
+
tracked_run.assets = json.load(f)
|
|
339
|
+
except json.JSONDecodeError as e:
|
|
340
|
+
error(f"Failed to parse assets file [magenta]{assets}[/magenta] as [magenta]json[/magenta]: {e}.")
|
|
341
|
+
|
|
342
|
+
# Handle the logs, which should be a text file.
|
|
343
|
+
if logs is not None and logs != "":
|
|
344
|
+
try:
|
|
345
|
+
log_content = Path(logs).read_text()
|
|
346
|
+
tracked_run.logs = log_content
|
|
347
|
+
except Exception as e:
|
|
348
|
+
error(f"Failed to read logs file [magenta]{logs}[/magenta]: {e}.")
|
|
349
|
+
|
|
350
|
+
# Handle the statistics, which should be a JSON file.
|
|
351
|
+
if statistics is not None and statistics != "":
|
|
352
|
+
try:
|
|
353
|
+
with open(statistics) as f:
|
|
354
|
+
tracked_run.statistics = json.load(f)
|
|
355
|
+
except json.JSONDecodeError as e:
|
|
356
|
+
error(f"Failed to parse statistics file [magenta]{statistics}[/magenta] as [magenta]json[/magenta]: {e}.")
|
|
357
|
+
|
|
358
|
+
return tracked_run
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def resolve_input(
|
|
362
|
+
tracked_run: TrackedRun,
|
|
363
|
+
stdin: str | None,
|
|
364
|
+
input: str | None,
|
|
365
|
+
content_format: InputFormat,
|
|
366
|
+
) -> TrackedRun:
|
|
367
|
+
"""
|
|
368
|
+
Resolves the input for the tracked run, either from stdin or from a
|
|
369
|
+
file/directory.
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
tracked_run : TrackedRun
|
|
374
|
+
The tracked run to set the input for.
|
|
375
|
+
stdin : str | None
|
|
376
|
+
The input provided via stdin, if any.
|
|
377
|
+
input : str | None
|
|
378
|
+
The input file or directory path, if any.
|
|
379
|
+
content_format : InputFormat
|
|
380
|
+
The content format of the input (json or text).
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
TrackedRun
|
|
385
|
+
The tracked run with the resolved input.
|
|
386
|
+
"""
|
|
387
|
+
if stdin is not None:
|
|
388
|
+
# Handle the case where stdin is provided as JSON for a JSON app.
|
|
389
|
+
try:
|
|
390
|
+
input_data = json.loads(stdin)
|
|
391
|
+
if content_format != InputFormat.JSON:
|
|
392
|
+
error(
|
|
393
|
+
"Input provided via [magenta]stdin[/magenta] is [magenta]json[/magenta], "
|
|
394
|
+
f"but the specified content format is {content_format.value}. "
|
|
395
|
+
"--content-format should be set to [magenta]json[/magenta]."
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
except json.JSONDecodeError:
|
|
399
|
+
input_data = stdin
|
|
400
|
+
if content_format != InputFormat.TEXT:
|
|
401
|
+
error(
|
|
402
|
+
"Input provided via [magenta]stdin[/magenta] is [magenta]text[/magenta], "
|
|
403
|
+
f"but the specified content format is {content_format.value}. "
|
|
404
|
+
"--content-format should be set to [magenta]text[/magenta]."
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
tracked_run.input = input_data
|
|
408
|
+
|
|
409
|
+
return tracked_run
|
|
410
|
+
|
|
411
|
+
# We know that input was defined because otherwise we would have failed
|
|
412
|
+
# early if both stdin and input were undefined.
|
|
413
|
+
input_path = Path(input)
|
|
414
|
+
|
|
415
|
+
if input_path.is_file():
|
|
416
|
+
if content_format == InputFormat.JSON:
|
|
417
|
+
try:
|
|
418
|
+
with input_path.open("r") as f:
|
|
419
|
+
input_data = json.load(f)
|
|
420
|
+
|
|
421
|
+
tracked_run.input = input_data
|
|
422
|
+
|
|
423
|
+
return tracked_run
|
|
424
|
+
|
|
425
|
+
except json.JSONDecodeError as e:
|
|
426
|
+
error(f"Failed to parse input file [magenta]{input}[/magenta] as [magenta]json[/magenta]: {e}.")
|
|
427
|
+
|
|
428
|
+
elif content_format == InputFormat.TEXT:
|
|
429
|
+
input_data = input_path.read_text()
|
|
430
|
+
tracked_run.input = input_data
|
|
431
|
+
|
|
432
|
+
return tracked_run
|
|
433
|
+
|
|
434
|
+
else:
|
|
435
|
+
error(f"Unsupported content format [magenta]{content_format.value}[/magenta] for file input.")
|
|
436
|
+
|
|
437
|
+
# If the input is a directory, we give the path directly to the run method.
|
|
438
|
+
# Internally, the files will be tarred and uploaded.
|
|
439
|
+
if input_path.is_dir():
|
|
440
|
+
tracked_run.input_dir_path = input
|
|
441
|
+
|
|
442
|
+
return tracked_run
|
|
443
|
+
|
|
444
|
+
error(f"Input path [magenta]{input}[/magenta] does not exist.")
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def resolve_output(
|
|
448
|
+
tracked_run: TrackedRun,
|
|
449
|
+
output: str,
|
|
450
|
+
content_format: InputFormat,
|
|
451
|
+
) -> TrackedRun:
|
|
452
|
+
"""
|
|
453
|
+
Resolves the output for the tracked run.
|
|
454
|
+
|
|
455
|
+
Parameters
|
|
456
|
+
----------
|
|
457
|
+
tracked_run : TrackedRun
|
|
458
|
+
The tracked run to set the output for.
|
|
459
|
+
output : str
|
|
460
|
+
The output file or directory path.
|
|
461
|
+
content_format : InputFormat
|
|
462
|
+
The content format of the output (json or text).
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
TrackedRun
|
|
467
|
+
The tracked run with the resolved output.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
output_path = Path(output)
|
|
471
|
+
if output_path.is_file():
|
|
472
|
+
if content_format == InputFormat.JSON:
|
|
473
|
+
try:
|
|
474
|
+
with output_path.open("r") as f:
|
|
475
|
+
output_data = json.load(f)
|
|
476
|
+
|
|
477
|
+
tracked_run.output = output_data
|
|
478
|
+
|
|
479
|
+
return tracked_run
|
|
480
|
+
|
|
481
|
+
except json.JSONDecodeError as e:
|
|
482
|
+
error(f"Failed to parse output file [magenta]{output}[/magenta] as [magenta]json[/magenta]: {e}.")
|
|
483
|
+
|
|
484
|
+
elif content_format == InputFormat.TEXT:
|
|
485
|
+
output_data = output_path.read_text()
|
|
486
|
+
tracked_run.output = output_data
|
|
487
|
+
|
|
488
|
+
return tracked_run
|
|
489
|
+
|
|
490
|
+
else:
|
|
491
|
+
error(f"Unsupported content type [magenta]{content_format.value}[/magenta] for file output.")
|
|
492
|
+
|
|
493
|
+
# If the output is a directory, we give the path directly to the run method.
|
|
494
|
+
# Internally, the files will be downloaded and extracted.
|
|
495
|
+
if output_path.is_dir():
|
|
496
|
+
tracked_run.output_dir_path = output
|
|
497
|
+
|
|
498
|
+
return tracked_run
|
|
499
|
+
|
|
500
|
+
error(f"Output path [magenta]{output}[/magenta] does not exist.")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud scenario command tree for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from nextmv.cli.cloud.scenario.create import app as create_app
|
|
8
|
+
from nextmv.cli.cloud.scenario.delete import app as delete_app
|
|
9
|
+
from nextmv.cli.cloud.scenario.get import app as get_app
|
|
10
|
+
from nextmv.cli.cloud.scenario.list import app as list_app
|
|
11
|
+
from nextmv.cli.cloud.scenario.metadata import app as metadata_app
|
|
12
|
+
from nextmv.cli.cloud.scenario.update import app as update_app
|
|
13
|
+
|
|
14
|
+
# Set up subcommand application.
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
app.add_typer(create_app)
|
|
17
|
+
app.add_typer(delete_app)
|
|
18
|
+
app.add_typer(get_app)
|
|
19
|
+
app.add_typer(list_app)
|
|
20
|
+
app.add_typer(metadata_app)
|
|
21
|
+
app.add_typer(update_app)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback()
|
|
25
|
+
def callback() -> None:
|
|
26
|
+
"""
|
|
27
|
+
Create and manage Nextmv Cloud scenario tests.
|
|
28
|
+
"""
|
|
29
|
+
pass
|