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.
Files changed (161) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +86 -0
  110. nextmv/cli/community/list.py +200 -0
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +228 -0
  113. nextmv/cli/configuration/create.py +94 -0
  114. nextmv/cli/configuration/delete.py +67 -0
  115. nextmv/cli/configuration/list.py +77 -0
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +161 -3
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +220 -0
  120. nextmv/cli/version.py +22 -2
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cloud/application.py +0 -4204
  158. nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
  159. nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
  160. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  161. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,37 @@
1
+ """
2
+ This module defines the cloud run command tree for the Nextmv CLI.
3
+ """
4
+
5
+ import typer
6
+
7
+ from nextmv.cli.cloud.run.cancel import app as cancel_app
8
+ from nextmv.cli.cloud.run.create import app as create_app
9
+ from nextmv.cli.cloud.run.get import app as get_app
10
+ from nextmv.cli.cloud.run.input import app as input_app
11
+ from nextmv.cli.cloud.run.list import app as list_app
12
+ from nextmv.cli.cloud.run.logs import app as logs_app
13
+ from nextmv.cli.cloud.run.metadata import app as metadata_app
14
+ from nextmv.cli.cloud.run.track import app as track_app
15
+
16
+ # Set up subcommand application.
17
+ app = typer.Typer()
18
+ app.add_typer(cancel_app)
19
+ app.add_typer(create_app)
20
+ app.add_typer(get_app)
21
+ app.add_typer(input_app)
22
+ app.add_typer(list_app)
23
+ app.add_typer(logs_app)
24
+ app.add_typer(metadata_app)
25
+ app.add_typer(track_app)
26
+
27
+
28
+ @app.callback()
29
+ def callback() -> None:
30
+ """
31
+ Create and manage Nextmv Cloud application runs.
32
+
33
+ A run represents the execution of a decision model within a Nextmv Cloud
34
+ application. Each run takes an input, processes it using the decision model,
35
+ and produces an output.
36
+ """
37
+ pass
@@ -0,0 +1,37 @@
1
+ """
2
+ This module defines the cloud run cancel command for the Nextmv CLI.
3
+ """
4
+
5
+ import typer
6
+
7
+ from nextmv.cli.configuration.config import build_app
8
+ from nextmv.cli.message import in_progress, success
9
+ from nextmv.cli.options import AppIDOption, ProfileOption, RunIDOption
10
+
11
+ # Set up subcommand application.
12
+ app = typer.Typer()
13
+
14
+
15
+ @app.command()
16
+ def cancel(
17
+ app_id: AppIDOption,
18
+ run_id: RunIDOption,
19
+ profile: ProfileOption = None,
20
+ ) -> None:
21
+ """
22
+ Cancel a queued/running Nextmv Cloud application run.
23
+
24
+ [bold][underline]Examples[/underline][/bold]
25
+
26
+ - Cancel the run with ID [magenta]burrow-123[/magenta] belonging to an app with ID [magenta]hare-app[/magenta].
27
+ $ [dim]nextmv cloud run cancel --app-id hare-app --run-id burrow-123[/dim]
28
+
29
+ - Cancel the run with ID [magenta]burrow-123[/magenta] belonging to an app with ID [magenta]hare-app[/magenta].
30
+ Use the profile named [magenta]hare[/magenta].
31
+ $ [dim]nextmv cloud run cancel --app-id hare-app --run-id burrow-123 --profile hare[/dim]
32
+ """
33
+
34
+ cloud_app = build_app(app_id=app_id, profile=profile)
35
+ in_progress(msg=f"Canceling run [magenta]{run_id}[/magenta]...")
36
+ cloud_app.cancel_run(run_id)
37
+ success(f"Run [magenta]{run_id}[/magenta] canceled.")
@@ -0,0 +1,524 @@
1
+ """
2
+ This module defines the cloud run create command for the Nextmv CLI.
3
+ """
4
+
5
+ import json
6
+ import sys
7
+ import tarfile
8
+ from pathlib import Path
9
+ from typing import Annotated, Any
10
+
11
+ import typer
12
+
13
+ from nextmv.cli.cloud.run.get import handle_outputs
14
+ from nextmv.cli.cloud.run.logs import handle_logs
15
+ from nextmv.cli.configuration.config import build_app
16
+ from nextmv.cli.message import enum_values, error, print_json, success
17
+ from nextmv.cli.options import AppIDOption, ProfileOption
18
+ from nextmv.cloud.application import Application
19
+ from nextmv.input import InputFormat
20
+ from nextmv.polling import default_polling_options
21
+ from nextmv.run import Format, FormatInput, RunConfiguration, RunQueuing, RunType, RunTypeConfiguration
22
+
23
+ # Set up subcommand application.
24
+ app = typer.Typer()
25
+
26
+
27
+ @app.command()
28
+ def create(
29
+ app_id: AppIDOption,
30
+ # Options for controlling input.
31
+ input: Annotated[
32
+ str | None,
33
+ typer.Option(
34
+ "--input",
35
+ "-i",
36
+ help="The input path to use. File or directory depending on content format. "
37
+ "Uses [magenta]stdin[/magenta] if not defined. "
38
+ "Can be a [magenta].tar.gz[/magenta] file for multi-file content format.",
39
+ metavar="INPUT_PATH",
40
+ rich_help_panel="Input control",
41
+ ),
42
+ ] = None,
43
+ # Options for controlling output.
44
+ logs: Annotated[
45
+ str | None,
46
+ typer.Option(
47
+ "--logs",
48
+ "-l",
49
+ help="Waits for the run to complete and saves the logs to this location.",
50
+ metavar="LOGS_PATH",
51
+ rich_help_panel="Output control",
52
+ ),
53
+ ] = None,
54
+ output: Annotated[
55
+ str | None,
56
+ typer.Option(
57
+ "--output",
58
+ "-u",
59
+ help="Waits for the run to complete and save the output to this location. "
60
+ "A file or directory will be created depending on content format. ",
61
+ metavar="OUTPUT_PATH",
62
+ rich_help_panel="Output control",
63
+ ),
64
+ ] = None,
65
+ tail: Annotated[
66
+ bool,
67
+ typer.Option(
68
+ "--tail",
69
+ "-t",
70
+ help="Tail the logs until the run completes. Logs are streamed to [magenta]stderr[/magenta]. "
71
+ "Specify log output location with --logs.",
72
+ rich_help_panel="Output control",
73
+ ),
74
+ ] = False,
75
+ wait: Annotated[
76
+ bool,
77
+ typer.Option(
78
+ "--wait",
79
+ "-w",
80
+ help="Wait for the run to complete. Run result is printed to [magenta]stdout[/magenta] for "
81
+ "[magenta]json[/magenta], to a dir for [magenta]multi-file[/magenta]. "
82
+ "Specify output location with --output.",
83
+ rich_help_panel="Output control",
84
+ ),
85
+ ] = False,
86
+ # Options for run configuration.
87
+ content_format: Annotated[
88
+ InputFormat | None,
89
+ typer.Option(
90
+ "--content-format",
91
+ "-c",
92
+ help=f"The content format of the run to create. Allowed values are: {enum_values(InputFormat)}.",
93
+ metavar="CONTENT_FORMAT",
94
+ rich_help_panel="Run configuration",
95
+ ),
96
+ ] = None,
97
+ definition_id: Annotated[
98
+ str | None,
99
+ typer.Option(
100
+ "--definition-id",
101
+ "-d",
102
+ help="The definition ID to use for the run. Required for certain run types like ensemble runs.",
103
+ metavar="DEFINITION_ID",
104
+ rich_help_panel="Run configuration",
105
+ ),
106
+ ] = None,
107
+ description: Annotated[
108
+ str | None,
109
+ typer.Option(
110
+ help="An optional description for the new run.",
111
+ metavar="DESCRIPTION",
112
+ rich_help_panel="Run configuration",
113
+ ),
114
+ ] = None,
115
+ execution_class: Annotated[
116
+ str | None,
117
+ typer.Option(
118
+ "--execution-class",
119
+ "-e",
120
+ help="The execution class to use for the run, if applicable.",
121
+ metavar="EXECUTION_CLASS",
122
+ rich_help_panel="Run configuration",
123
+ ),
124
+ ] = None,
125
+ instance_id: Annotated[
126
+ str | None,
127
+ typer.Option(
128
+ help="The instance ID to use for the run.",
129
+ metavar="INSTANCE_ID",
130
+ rich_help_panel="Run configuration",
131
+ ),
132
+ ] = None,
133
+ integration_id: Annotated[
134
+ str | None,
135
+ typer.Option(
136
+ help="The integration ID to use for the run, if applicable.",
137
+ metavar="INTEGRATION_ID",
138
+ rich_help_panel="Run configuration",
139
+ ),
140
+ ] = None,
141
+ name: Annotated[
142
+ str | None,
143
+ typer.Option(
144
+ "--name",
145
+ "-n",
146
+ help="An optional name for the new run.",
147
+ metavar="NAME",
148
+ rich_help_panel="Run configuration",
149
+ ),
150
+ ] = None,
151
+ no_queuing: Annotated[
152
+ bool,
153
+ typer.Option(
154
+ "--no-queuing",
155
+ help="Do not queue run. Default is [magenta]False[/magenta], "
156
+ "meaning the run [italic]will[/italic] be queued.",
157
+ rich_help_panel="Run configuration",
158
+ ),
159
+ ] = False,
160
+ options: Annotated[
161
+ list[str] | None,
162
+ typer.Option(
163
+ "--options",
164
+ "-o",
165
+ help="Options passed to the run. Format: [magenta]key=value[/magenta]. "
166
+ "Pass multiple options by repeating the flag, or separating with commas.",
167
+ metavar="KEY=VALUE",
168
+ rich_help_panel="Run configuration",
169
+ ),
170
+ ] = None,
171
+ priority: Annotated[
172
+ int,
173
+ typer.Option(
174
+ help="The priority of the run. Priority is between 1 and 10, with 1 being the highest priority.",
175
+ metavar="PRIORITY",
176
+ rich_help_panel="Run configuration",
177
+ ),
178
+ ] = 6,
179
+ run_type: Annotated[
180
+ RunType,
181
+ typer.Option(
182
+ "--run-type",
183
+ "-r",
184
+ help=f"The type of run to create. Allowed values are: {enum_values(RunType)}.",
185
+ metavar="RUN_TYPE",
186
+ rich_help_panel="Run configuration",
187
+ ),
188
+ ] = RunType.STANDARD,
189
+ secret_collection_id: Annotated[
190
+ str | None,
191
+ typer.Option(
192
+ "--secret-collection-id",
193
+ "-s",
194
+ help="The secret collection ID to use for the run, if applicable.",
195
+ metavar="SECRET_COLLECTION_ID",
196
+ rich_help_panel="Run configuration",
197
+ ),
198
+ ] = None,
199
+ timeout: Annotated[
200
+ int,
201
+ typer.Option(
202
+ help="The maximum time in seconds to wait for results when polling. Poll indefinitely if not set.",
203
+ metavar="TIMEOUT_SECONDS",
204
+ rich_help_panel="Run configuration",
205
+ ),
206
+ ] = -1,
207
+ profile: ProfileOption = None,
208
+ ) -> None:
209
+ """
210
+ Create a new Nextmv Cloud application run.
211
+
212
+ Input for the run should be given through [magenta]stdin[/magenta] or the
213
+ --input flag. When using the --input flag, the value can be one of the
214
+ following:
215
+
216
+ - [yellow]<FILE_PATH>[/yellow]: path to a [magenta]file[/magenta] containing
217
+ the input data. Use with the [magenta]json[/magenta], and
218
+ [magenta]text[/magenta] content formats.
219
+ - [yellow]<DIR_PATH>[/yellow]: path to a [magenta]directory[/magenta]
220
+ containing the input data files. Use with the
221
+ [magenta]multi-file[/magenta] content format.
222
+ - [yellow]<.tar.gz PATH>[/yellow]: path to a [magenta].tar.gz[/magenta] file
223
+ containing tarred input data files. Use with the
224
+ [magenta]multi-file[/magenta] content format.
225
+
226
+ The CLI determines how to send the input to the application based on the
227
+ value.
228
+
229
+ Use the --wait flag to wait for the run to complete, polling for results.
230
+ Using the --output flag will also activate waiting, and allows you to
231
+ specify a destination (file or dir) for the output, depending on the
232
+ content type.
233
+
234
+ Use the --tail flag to stream logs to [magenta]stderr[/magenta] until the
235
+ run completes. Using the --logs flag will also activate waiting, and allows
236
+ you to specify a file to write the logs to.
237
+
238
+ An application run executes against a specific instance. An instance
239
+ represents the combination of executable code and configuration. You can
240
+ specify the instance with the --instance-id flag. These are the possible
241
+ values for this flag:
242
+
243
+ - [yellow]unspecified[/yellow]: Run against the default instance of the
244
+ application. When an application is created, the default instance is [magenta]latest[/magenta].
245
+ - [yellow]latest[/yellow]: uses the special [magenta]latest[/magenta]
246
+ instance of the application. This corresponds to the latest pushed
247
+ executable.
248
+ - [yellow]<INSTANCE_ID>[/yellow]: uses the instance with the given ID.
249
+
250
+ [bold][underline]Examples[/underline][/bold]
251
+
252
+ - Read a [magenta]json[/magenta] input via [magenta]stdin[/magenta], from an [magenta]input.json[/magenta] file,
253
+ and submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
254
+ $ [dim]cat input.json | nextmv cloud run create --app-id hare-app[/dim]
255
+
256
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and
257
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
258
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json[/dim]
259
+
260
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and
261
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
262
+ Wait for the run to complete and print the result to [magenta]stdout[/magenta].
263
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json --wait[/dim]
264
+
265
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and
266
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
267
+ Tail the run's logs, streaming to [magenta]stderr[/magenta].
268
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json --tail[/dim]
269
+
270
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and
271
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
272
+ Wait for the run to complete and write the result to an [magenta]output.json[/magenta] file.
273
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json --output output.json[/dim]
274
+
275
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and
276
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance.
277
+ Wait for the run to complete, and write the logs to a [magenta]logs.log[/magenta] file.
278
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json --logs logs.log[/dim]
279
+
280
+ - Read a [magenta]json[/magenta] input from an [magenta]input.json[/magenta] file, and submit a run to an app with
281
+ ID [magenta]hare-app[/magenta], using the [magenta]latest[/magenta] instance. Wait for the run to complete. Tail
282
+ the run's logs, streaming to [magenta]stderr[/magenta]. Write the logs to a [magenta]logs.log[/magenta] file.
283
+ Write the result to an [magenta]output.json[/magenta] file.
284
+ $ [dim]nextmv cloud run create --app-id hare-app --input input.json --tail --logs logs.log \\
285
+ --output output.json [/dim]
286
+
287
+ - Read a [magenta]multi-file[/magenta] input from an [magenta]inputs[/magenta] directory, and
288
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]default[/magenta] instance.
289
+ $ [dim]nextmv cloud run create --app-id hare-app --input inputs --instance-id default[/dim]
290
+
291
+ - Read a [magenta]multi-file[/magenta] input from an [magenta]inputs[/magenta] directory, and
292
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]default[/magenta] instance.
293
+ Wait for the run to complete, and save the results to the default location (a directory named after the run ID).
294
+ $ [dim]nextmv cloud run create --app-id hare-app --input inputs --instance-id default --wait[/dim]
295
+
296
+ - Read a [magenta]multi-file[/magenta] input from an [magenta]inputs[/magenta] directory, and
297
+ submit a run to an app with ID [magenta]hare-app[/magenta], using the [magenta]burrow[/magenta] instance.
298
+ Wait for the run to complete and download the result files to an [magenta]outputs[/magenta] directory.
299
+ $ [dim]nextmv cloud run create --app-id hare-app --input inputs --instance-id burrow --output outputs[/dim]
300
+ """
301
+
302
+ # Validate that input is provided.
303
+ stdin = sys.stdin.read().strip() if sys.stdin.isatty() is False else None
304
+ if stdin is None and (input is None or input == ""):
305
+ error("Input data must be provided via the --input flag or [magenta]stdin[/magenta].")
306
+
307
+ # Instantiate the basic requirements to start a new run.
308
+ cloud_app = build_app(app_id=app_id, profile=profile)
309
+ config = build_run_config(
310
+ run_type=run_type,
311
+ priority=priority,
312
+ no_queuing=no_queuing,
313
+ execution_class=execution_class,
314
+ content_format=content_format,
315
+ secret_collection_id=secret_collection_id,
316
+ integration_id=integration_id,
317
+ definition_id=definition_id,
318
+ )
319
+ run_options = build_run_options(options)
320
+
321
+ # Start the run before deciding if we should poll or not.
322
+ input_kwarg = resolve_input_kwarg(
323
+ stdin=stdin,
324
+ input=input,
325
+ cloud_app=cloud_app,
326
+ )
327
+ run_id = cloud_app.new_run(
328
+ **input_kwarg,
329
+ instance_id=instance_id,
330
+ name=name,
331
+ description=description,
332
+ options=run_options,
333
+ configuration=config,
334
+ )
335
+
336
+ # If we don't need to poll at all we are done.
337
+ if not wait and not tail and output is None and logs is None:
338
+ print_json({"run_id": run_id})
339
+
340
+ return
341
+
342
+ success(f"Run [magenta]{run_id}[/magenta] created.")
343
+
344
+ # Build the polling options.
345
+ polling_options = default_polling_options()
346
+ polling_options.max_duration = timeout
347
+
348
+ # Handle what happens after the run is created for logging and result
349
+ # retrieval.
350
+ handle_logs(
351
+ cloud_app=cloud_app,
352
+ run_id=run_id,
353
+ tail=tail,
354
+ logs=logs,
355
+ polling_options=polling_options,
356
+ file_output=True,
357
+ )
358
+ handle_outputs(
359
+ cloud_app=cloud_app,
360
+ run_id=run_id,
361
+ wait=wait,
362
+ output=output,
363
+ polling_options=polling_options,
364
+ skip_wait_check=False,
365
+ )
366
+
367
+
368
+ def build_run_config(
369
+ run_type: RunType,
370
+ priority: int,
371
+ no_queuing: bool,
372
+ execution_class: str | None = None,
373
+ content_format: InputFormat | None = None,
374
+ secret_collection_id: str | None = None,
375
+ integration_id: str | None = None,
376
+ definition_id: str | None = None,
377
+ ) -> RunConfiguration:
378
+ """
379
+ Builds the run configuration for the new run.
380
+
381
+ Parameters
382
+ ----------
383
+ run_type : RunType
384
+ The type of run to create.
385
+ priority : int
386
+ The priority of the run.
387
+ no_queuing : bool
388
+ Whether to disable queuing for the run.
389
+ execution_class : str | None
390
+ The execution class to use for the run, if applicable.
391
+ content_format : InputFormat | None
392
+ The content format of the run to create, if applicable.
393
+ secret_collection_id : str | None
394
+ The secret collection ID to use for the run, if applicable.
395
+ integration_id : str | None
396
+ The integration ID to use for the run, if applicable.
397
+ definition_id : str | None
398
+ The definition ID to use for the run, if applicable.
399
+
400
+ Returns
401
+ -------
402
+ RunConfiguration
403
+ The built run configuration.
404
+ """
405
+
406
+ config = RunConfiguration(
407
+ run_type=RunTypeConfiguration(
408
+ run_type=RunType(run_type),
409
+ ),
410
+ queuing=RunQueuing(
411
+ priority=priority,
412
+ disabled=no_queuing,
413
+ ),
414
+ )
415
+ if execution_class is not None:
416
+ config.execution_class = execution_class
417
+ if content_format is not None:
418
+ config.format = Format(
419
+ format_input=FormatInput(
420
+ input_type=InputFormat(content_format),
421
+ ),
422
+ )
423
+ if secret_collection_id is not None:
424
+ config.secrets_collection_id = secret_collection_id
425
+ if integration_id is not None:
426
+ config.integration_id = integration_id
427
+ if definition_id is not None:
428
+ config.run_type.definition_id = definition_id
429
+
430
+ return config
431
+
432
+
433
+ def build_run_options(options: list[str] | None) -> dict[str, str]:
434
+ """
435
+ Builds the run options for the new run. One can pass options by either
436
+ using the flag multiple times or by separating with commas in the same
437
+ flag. A combination of both is also possible.
438
+
439
+ Parameters
440
+ ----------
441
+ options : list[str] | None
442
+ The list of run options as strings.
443
+
444
+ Returns
445
+ -------
446
+ dict[str, str]
447
+ The built run options.
448
+ """
449
+
450
+ if options is None:
451
+ return None
452
+
453
+ run_options = {}
454
+ for opt in options:
455
+ # It is possible to pass multiple options separated by commas. The
456
+ # default way though is to use the flag multiple times to specify
457
+ # different options.
458
+ sub_opts = opt.split(",")
459
+ for sub_opt in sub_opts:
460
+ key_value = sub_opt.split("=", 1)
461
+ if len(key_value) != 2:
462
+ error(f"Invalid option format: {sub_opt}. Expected format is [magenta]key=value[/magenta].")
463
+
464
+ key, value = key_value
465
+ run_options[key] = value
466
+
467
+ return run_options
468
+
469
+
470
+ def resolve_input_kwarg(
471
+ stdin: str | None,
472
+ input: str | None,
473
+ cloud_app: Application,
474
+ ) -> dict[str, Any]:
475
+ """
476
+ Gets the keyword argument related to the input that is needed for the run
477
+ creation. It handles stdin, file, and directory inputs. It uploads the
478
+ input to the cloud application if needed.
479
+
480
+ Parameters
481
+ ----------
482
+ stdin : str | None
483
+ The stdin input data, if provided.
484
+ input : str | None
485
+ The input path, if provided.
486
+ cloud_app : Application
487
+ The cloud application instance.
488
+
489
+ Returns
490
+ -------
491
+ dict[str, Any]
492
+ The keyword argument with the resolved input.
493
+ """
494
+
495
+ if stdin is not None:
496
+ # Handle the case where stdin is provided as JSON for a JSON app.
497
+ try:
498
+ input_data = json.loads(stdin)
499
+ except json.JSONDecodeError:
500
+ input_data = stdin
501
+
502
+ return {"input": input_data}
503
+
504
+ input_path = Path(input)
505
+
506
+ # If the input is a file, we need to determine if it is a tar file or
507
+ # a regular file and upload it accordingly. If it is a regular file, we
508
+ # need to read its content.
509
+ if input_path.is_file():
510
+ upload_url = cloud_app.upload_url()
511
+ if tarfile.is_tarfile(input_path):
512
+ cloud_app.upload_data(data=None, upload_url=upload_url, tar_file=input_path)
513
+ else:
514
+ input_data = input_path.read_text()
515
+ cloud_app.upload_data(data=input_data, upload_url=upload_url)
516
+
517
+ return {"upload_id": upload_url.upload_id}
518
+
519
+ # If the input is a directory, we give the path directly to the run method.
520
+ # Internally, the files will be tarred and uploaded.
521
+ if input_path.is_dir():
522
+ return {"input_dir_path": input}
523
+
524
+ error(f"Input path [magenta]{input}[/magenta] does not exist.")