nextmv 0.40.0__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 (163) 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 +20 -204
  110. nextmv/cli/community/list.py +61 -126
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +103 -6
  113. nextmv/cli/configuration/create.py +17 -18
  114. nextmv/cli/configuration/delete.py +25 -13
  115. nextmv/cli/configuration/list.py +4 -4
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +68 -36
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +196 -0
  120. nextmv/cli/version.py +20 -1
  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.40.0.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/cli/community/community.py +0 -24
  158. nextmv/cli/configuration/configuration.py +0 -23
  159. nextmv/cli/error.py +0 -22
  160. nextmv/cloud/application.py +0 -4204
  161. nextmv-0.40.0.dist-info/RECORD +0 -66
  162. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  163. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "v0.40.0"
1
+ __version__ = "v1.0.0"
nextmv/__entrypoint__.py CHANGED
@@ -10,13 +10,12 @@ human to use it during local development. It is the standard way in which a
10
10
  from mlflow.pyfunc import load_model
11
11
 
12
12
  import nextmv
13
- from nextmv import cloud
14
13
 
15
14
 
16
15
  def main() -> None:
17
16
  """Entry point for the program."""
18
17
 
19
- manifest = cloud.Manifest.from_yaml(".")
18
+ manifest = nextmv.Manifest.from_yaml(".")
20
19
 
21
20
  # Load the options from the manifest.
22
21
  options = manifest.extract_options()
nextmv/__init__.py CHANGED
@@ -11,7 +11,6 @@ from .input import LocalInputLoader as LocalInputLoader
11
11
  from .input import csv_data_file as csv_data_file
12
12
  from .input import json_data_file as json_data_file
13
13
  from .input import load as load
14
- from .input import load_local as load_local
15
14
  from .input import text_data_file as text_data_file
16
15
  from .logger import log as log
17
16
  from .logger import redirect_stdout as redirect_stdout
@@ -31,7 +30,6 @@ from .model import Model as Model
31
30
  from .model import ModelConfiguration as ModelConfiguration
32
31
  from .options import Option as Option
33
32
  from .options import Options as Options
34
- from .options import Parameter as Parameter
35
33
  from .output import Asset as Asset
36
34
  from .output import DataPoint as DataPoint
37
35
  from .output import LocalOutputWriter as LocalOutputWriter
@@ -50,9 +48,9 @@ from .output import csv_solution_file as csv_solution_file
50
48
  from .output import json_solution_file as json_solution_file
51
49
  from .output import text_solution_file as text_solution_file
52
50
  from .output import write as write
53
- from .output import write_local as write_local
54
51
  from .polling import DEFAULT_POLLING_OPTIONS as DEFAULT_POLLING_OPTIONS
55
52
  from .polling import PollingOptions as PollingOptions
53
+ from .polling import default_polling_options as default_polling_options
56
54
  from .polling import poll as poll
57
55
  from .run import ErrorLog as ErrorLog
58
56
  from .run import ExternalRunResult as ExternalRunResult
@@ -72,12 +70,12 @@ from .run import RunType as RunType
72
70
  from .run import RunTypeConfiguration as RunTypeConfiguration
73
71
  from .run import StatisticsIndicator as StatisticsIndicator
74
72
  from .run import SyncedRun as SyncedRun
73
+ from .run import TimestampedRunLog as TimestampedRunLog
75
74
  from .run import TrackedRun as TrackedRun
76
75
  from .run import TrackedRunStatus as TrackedRunStatus
77
76
  from .run import run_duration as run_duration
78
77
  from .safe import safe_id as safe_id
79
78
  from .safe import safe_name_and_id as safe_name_and_id
80
- from .status import Status as Status
81
79
  from .status import StatusV2 as StatusV2
82
80
 
83
81
  VERSION = __version__
@@ -0,0 +1,583 @@
1
+ # Contributing to Nextmv CLI
2
+
3
+ Hello dear contributor. Thank you for helping out with the CLI 😎. Here are a
4
+ few style guidelines to help all of us maintain a high-quality tool, that feels
5
+ unified and consistent. You are required to read and understand these
6
+ guidelines before submitting a pull request.
7
+
8
+ The Nextmv CLI is built using [Typer][typer]. If you don't know Typer, we
9
+ _strongly encourage_ you to read through the [Typer Learn][typer-learn]
10
+ section to understand what is possible with the library. Even if you decide not
11
+ to run the example commands, you should spend 1 - 2 hours reading through the
12
+ material to get a good understanding of how Typer works.
13
+
14
+ We are following Typer's recommendations and using [Rich][rich] for formatting.
15
+ We also _strongly encourage_ you to read though Rich's documentation, spending
16
+ 30 minutes to an hour familiarizing yourself with the library. Quoting directly
17
+ from the Typer docs:
18
+
19
+ > If you are wondering what tool should be used for what, Typer is useful for
20
+ > structuring the command line application, with options, arguments,
21
+ > subcommands, data validation, etc.
22
+ >
23
+ > In general, Typer tends to be the entry point to your program, taking the
24
+ > first input from the user.
25
+ >
26
+ > Rich is useful for the parts that need to display information. Showing
27
+ > beautiful content on the screen.
28
+ >
29
+ > The best results for your command line application would be achieved
30
+ > combining both Typer and Rich.
31
+
32
+ ## Command structure
33
+
34
+ The logic for command tree organization is based on:
35
+
36
+ > Domain > Entity > Action
37
+
38
+ Where:
39
+
40
+ - Domain separates `cloud` from `local`. If there is no domain, the command
41
+ belongs to the root command tree.
42
+ - Entity refers to resources like `run`, `batch-experiment`,
43
+ `secrets-collection`, etc.
44
+ - Action details what can be done on the entity: `create`, `get`, `delete`,
45
+ `list`, etc.
46
+ - Sometimes, it is not practical to follow this logic. Consider the `nextmv
47
+ cloud run` command tree. The available subcommands are:
48
+
49
+ - cancel: Cancel a queued/running Nextmv Cloud application run.
50
+ - create: Create a new Nextmv Cloud application run.
51
+ - get: Get the result (output) of a Nextmv Cloud application run.
52
+ - input: Get the input of a Nextmv Cloud application run.
53
+ - list: Get the list of runs for a Nextmv Cloud application.
54
+ - logs: Get the logs of a Nextmv Cloud application run.
55
+ - metadata: Get the metadata of a Nextmv Cloud application run.
56
+ - track: Track an external run as a Nextmv Cloud application run.
57
+
58
+ Strictly speaking, `input`, `logs`, and `metadata` are not actions. To avoid
59
+ defining a `nextmv cloud run input get` command, which starts getting
60
+ convoluted, we decided to keep these commands as-is. On the other hand, if
61
+ you have the need to define commands like `nextmv cloud run input-update`, or
62
+ `nextmv cloud run input-delete`, then it is better to define a `nextmv cloud run
63
+ input` command tree, with `get`, `update`, and `delete` subcommands.
64
+
65
+ - If possible, use single words for command trees and commands.
66
+ - Use the best judgement when deciding how to structure commands. If you
67
+ believe a different structure makes more sense, feel free to propose it in
68
+ your pull request, explaining the reasoning behind it.
69
+
70
+ ## File organization
71
+
72
+ Follow these guidelines when organizing files and directories for commands:
73
+
74
+ - Directories and files should be named following the command names. For
75
+ example, the `cloud` directory has the `cloud` command tree, and the
76
+ `cloud/app/create.py` file contains the `nextmv cloud app create` command.
77
+ - Place commands in their own Python files. Take the `community` dir, for
78
+ example. The `clone.py` file hosts the `nextmv community clone` command and
79
+ the `list.py` file has the `nextmv community list` command.
80
+ - Place command trees in their own directories. The main command of the command
81
+ tree should live in the `__init__.py` file in the directory. Consider the
82
+ `cloud/app` directory. The `__init__.py` file has the following code, which
83
+ sets up the `nextmv cloud app` command tree and adds seven subcommands to
84
+ it.
85
+
86
+ ```python
87
+ import typer
88
+
89
+ from nextmv.cli.cloud.app.create import app as create_app
90
+ from nextmv.cli.cloud.app.delete import app as delete_app
91
+ from nextmv.cli.cloud.app.exists import app as exists_app
92
+ from nextmv.cli.cloud.app.get import app as get_app
93
+ from nextmv.cli.cloud.app.list import app as list_app
94
+ from nextmv.cli.cloud.app.push import app as push_app
95
+ from nextmv.cli.cloud.app.update import app as update_app
96
+
97
+ # Set up subcommand application.
98
+ app = typer.Typer()
99
+ app.add_typer(create_app)
100
+ app.add_typer(delete_app)
101
+ app.add_typer(exists_app)
102
+ app.add_typer(get_app)
103
+ app.add_typer(list_app)
104
+ app.add_typer(push_app)
105
+ app.add_typer(update_app)
106
+
107
+
108
+ @app.callback()
109
+ def callback() -> None:
110
+ """
111
+ Create, manage, and push Nextmv Cloud applications.
112
+ """
113
+ pass
114
+ ```
115
+
116
+ Each of the commands should be in their own file in the same directory, e.g.
117
+ `create.py`, `delete.py`, etc.
118
+
119
+ - Use the same principle of placing command tree definitions in an
120
+ `__init__.py` file for subcommand trees as well. Consider the `cloud`
121
+ directory. It has an `__init__.py` file and subdirectories. The `__init__.py`
122
+ file has the following code, which sets up the `nextmv cloud` command tree
123
+ and adds several subcommand trees to it.
124
+
125
+ ```python
126
+ import typer
127
+
128
+ from nextmv.cli.cloud.app import app as app_app
129
+ from nextmv.cli.cloud.run import app as run_app
130
+
131
+ # Set up subcommand application.
132
+ app = typer.Typer()
133
+ app.add_typer(app_app, name="app")
134
+ app.add_typer(run_app, name="run")
135
+
136
+
137
+ @app.callback()
138
+ def callback() -> None:
139
+ """
140
+ Interact with Nextmv Cloud, a platform for deploying and managing models.
141
+ """
142
+ pass
143
+ ```
144
+
145
+ ## Printing
146
+
147
+ When information to the user, i.e., printing to the console, follow these
148
+ guidelines:
149
+
150
+ - Unless otherwise necessary, always print and log to `stderr`. This ensures
151
+ that the CLI's output can be piped and redirected without issues.
152
+ - We embrace the use of emojis. They make the CLI friendlier and more
153
+ approachable.
154
+ - The `message.py` file contains helper functions for printing messages, like:
155
+ - `message`: prints a message. You can give it an emoji for the message. The
156
+ other commands have fixed emojis.
157
+ - `info`: prints an informational message. Use for neutral messages.
158
+ - `in_progress`: prints an in-progress message. Use before executing an action.
159
+ - `success`: prints a success message. Use after successfully completing an action.
160
+ - `warning`: prints a warning message. Use for non-critical issues.
161
+ - `error`: prints an error and raises an exception. Use for critical issues
162
+ and to return early from commands.
163
+ - For printing `JSON` information, use the `print_json` function in the
164
+ `message.py` file to print JSON output. This ensures consistent formatting
165
+ across the CLI.
166
+ - Emojis should be formatted according to [Rich's emoji guide][rich-emoji].
167
+ They are strings enclosed in colons, e.g. `:rocket:`, `:boom:`,
168
+ `:hourglass_flowing_sand:`, etc.
169
+ - When using the `success` function, include the variable or entity that was
170
+ affected, formatted with `[magenta]`:
171
+
172
+ ```python
173
+ success(f"Application [magenta]{app_id}[/magenta] deleted successfully.")
174
+ ```
175
+
176
+ - When showing the values of an `Enum`, use the `enum_values` function in the
177
+ `message.py` file which will give a nicely colored, comma-separated list of
178
+ the enum values. Consider the following example, where we get the allowed
179
+ values for the `InputFormat` class.
180
+
181
+ ```python
182
+ content_format: Annotated[
183
+ InputFormat | None,
184
+ typer.Option(
185
+ "--content-format",
186
+ "-c",
187
+ help=f"The content format for the instance. Allowed values are: {enum_values(InputFormat)}.",
188
+ metavar="CONTENT_FORMAT",
189
+ rich_help_panel="Instance configuration",
190
+ ),
191
+ ] = None,
192
+ ```
193
+
194
+ ## Confirmation prompts
195
+
196
+ For destructive actions (like deletions), use the `get_confirmation()` method
197
+ to ask for user confirmation before proceeding. The method is available from
198
+ the `cli/confirm.py` file. This method already handles sensible values used for
199
+ getting a confirmation from a user. Additionally, it handles non-interactive
200
+ sessions by defaulting to `False` if no input can be provided.
201
+
202
+ When using confirmation prompts, follow these guidelines:
203
+
204
+ - The confirmation message should use `[magenta]` for the variable/s being
205
+ affected.
206
+ - Provide a `--yes` / `-y` flag to skip the confirmation prompt where possible,
207
+ useful for non-interactive sessions.
208
+ - If the user declines, call `info()` and return early.
209
+
210
+ Consider the `nextmv cloud app delete` command:
211
+
212
+ ```python
213
+ if not yes:
214
+ confirm = get_confirmation(
215
+ f"Are you sure you want to delete application [magenta]{app_id}[/magenta]? This action cannot be undone.",
216
+ )
217
+
218
+ if not confirm:
219
+ info(f"Application [magenta]{app_id}[/magenta] will not be deleted.")
220
+ return
221
+ ```
222
+
223
+ ## Formatting, colors, and styles
224
+
225
+ Use these Rich markup colors/styles when formatting help text and messages.
226
+ These are the main colors/styles that can be used for highlighting/contrast (we
227
+ limit colors to keep coloring consistent):
228
+
229
+ - `[code]`: commands. CLI related variables. - technical things that are CLI
230
+ commands.
231
+ - `[magenta]`: variable names, values, literals, etc. - mainly short technical things.
232
+ - `[dim]`: examples. - longer technical things.
233
+ - `[yellow]`: emphasis, highlight of special items, type contrast to
234
+ `[magenta]`. Use sparingly only.
235
+
236
+ In any case, the best advice is to follow existing examples in the codebase to
237
+ maintain consistency.
238
+
239
+ Here are some guidelines for when to use each formatting style.
240
+
241
+ - When talking about a command use the `[code]` `[/code]` tags. Consider the
242
+ help message of the `cloud/shadow/stop.py` file. We tell the user they can
243
+ delete an experiment with the `nextmv cloud shadow delete` command. The
244
+ formatting of that command is done using the `[code]` `[/code]` tags:
245
+
246
+ ```python
247
+ @app.command()
248
+ def stop(
249
+ app_id: AppIDOption,
250
+ shadow_test_id: ShadowTestIDOption,
251
+ profile: ProfileOption = None,
252
+ ) -> None:
253
+ """
254
+ Stops a Nextmv Cloud shadow test.
255
+
256
+ Before stopping a shadow test, it must be in a started state. Experiments
257
+ in a [magenta]draft[/magenta] state, that haven't started, can be deleted
258
+ with the [code]nextmv cloud shadow delete[/code] command.
259
+
260
+ [bold][underline]Examples[/underline][/bold]
261
+
262
+ - Stop the shadow test with the ID [magenta]hop-analysis[/magenta] from application
263
+ [magenta]hare-app[/magenta].
264
+ $ [dim]nextmv cloud shadow stop --app-id hare-app --shadow-test-id hop-analysis[/dim]
265
+ """
266
+
267
+ in_progress(msg="Stopping shadow test...")
268
+ cloud_app = build_app(app_id=app_id, profile=profile)
269
+ cloud_app.stop_shadow_test(shadow_test_id=shadow_test_id)
270
+ success(
271
+ f"Shadow test [magenta]{shadow_test_id}[/magenta] stopped successfully "
272
+ f"in application [magenta]{app_id}[/magenta]."
273
+ )
274
+ ```
275
+
276
+ - When talking about a command option, there is no formatting needed. Typer
277
+ automatically adds coloring to options in the help menu. Take this example
278
+ from the help menu of the `cloud/app/delete.py` file. In the command help,
279
+ when referring to the `--yes` option:
280
+
281
+ ```python
282
+ @app.command()
283
+ def delete(
284
+ app_id: AppIDOption,
285
+ yes: Annotated[
286
+ bool,
287
+ typer.Option(
288
+ "--yes",
289
+ "-y",
290
+ help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
291
+ ),
292
+ ] = False,
293
+ profile: ProfileOption = None,
294
+ ) -> None:
295
+ """
296
+ Deletes a Nextmv Cloud application.
297
+
298
+ This action is permanent and cannot be undone. Use the --yes
299
+ flag to skip the confirmation prompt.
300
+
301
+ [bold][underline]Examples[/underline][/bold]
302
+
303
+ - Delete the application with the ID [magenta]hare-app[/magenta].
304
+ $ [dim]nextmv cloud app delete --app-id hare-app[/dim]
305
+
306
+ - Delete the application with the ID [magenta]hare-app[/magenta] without confirmation prompt.
307
+ $ [dim]nextmv cloud app delete --app-id hare-app --yes[/dim]
308
+ """
309
+ ```
310
+
311
+ - When talking about a variable (like a filepath, value of an option, a string,
312
+ etc.), use the `[magenta]` `[/magenta]` tags. Using the same example as
313
+ above, when referring to the application ID `hare-app`, we use
314
+ `[magenta]hare-app[/magenta]` to format it as a variable. Another example is
315
+ when providing error messages that include values:
316
+
317
+ ```python
318
+ error(f"Input path [magenta]{input}[/magenta] does not exist.")
319
+ ```
320
+
321
+ - When talking about longer technical things, like examples for a command
322
+ usage, or examples of a JSON object, use the `[dim]` `[/dim]` tags. Consider
323
+ the examples section of the `cloud/app/delete.py` file above. The example
324
+ commands are formatted using the `[dim]` `[/dim]` tags. The `[dim]` tag is
325
+ discussed in more detail in the command documentation section below.
326
+
327
+ - Links to URLs should be formatted using the `[link=URL_LINK][bold]
328
+ [/bold][/link]` tags. Consider the main help message of the `nextmv
329
+ community` command, in the `community/__init__.py` file:
330
+
331
+ ```python
332
+ @app.callback()
333
+ def callback() -> None:
334
+ """
335
+ Interact with community apps, which are pre-built decision models.
336
+
337
+ Community apps are maintained in the following GitHub repository:
338
+ [link=https://github.com/nextmv-io/community-apps][bold]nextmv-io/community-apps[/bold][/link].
339
+ """
340
+ pass
341
+ ```
342
+
343
+ The link provided is <https://github.com/nextmv-io/community-apps>, and it will
344
+ be applied to the text `nextmv-io/community-apps`.
345
+
346
+ ## Command documentation
347
+
348
+ Every command should have good-enough documentation that guides the user on how
349
+ to use it.
350
+
351
+ - Document every command using Python docstrings.
352
+ - Document every option and argument using the `help` parameter of the
353
+ `typer.Option` functions.
354
+ - Option documentation should be short and to the point. Avoid long
355
+ explanations. If necessary, you can add more detailed information in the
356
+ command's help.
357
+ - The help of the command should be structured as follows:
358
+ - A short, one-line description of what the command does.
359
+ - A blank line.
360
+ - A more detailed description of what the command does. This can be multiple
361
+ paragraphs. Only add this section if necessary.
362
+ - A blank line.
363
+ - An Examples section, with one or more examples of how to use the command.
364
+ Each example should have a short description of what it does, followed by
365
+ the command itself. More on example formatting below.
366
+ - Consider the `nextmv cloud app get` command, under the `cloud/app/get.py` file:
367
+
368
+ ```python
369
+ @app.command()
370
+ def get(
371
+ app_id: AppIDOption,
372
+ output: Annotated[
373
+ str | None,
374
+ typer.Option(
375
+ "--output",
376
+ "-o",
377
+ help="Saves the app information to this location.",
378
+ metavar="OUTPUT_PATH",
379
+ ),
380
+ ] = None,
381
+ profile: ProfileOption = None,
382
+ ) -> None:
383
+ """
384
+ Get a Nextmv Cloud application.
385
+
386
+ This command is useful to get the attributes of an existing Nextmv Cloud
387
+ application by its ID.
388
+
389
+ [bold][underline]Examples[/underline][/bold]
390
+
391
+ - Get the application with the ID [magenta]hare-app[/magenta].
392
+ $ [dim]nextmv cloud app get --app-id hare-app[/dim]
393
+
394
+ - Get the application with the ID [magenta]hare-app[/magenta] and save the information to an
395
+ [magenta]app.json[/magenta] file.
396
+ $ [dim]nextmv cloud app get --app-id hare-app --output app.json[/dim]
397
+ """
398
+
399
+ client = build_client(profile)
400
+ in_progress("Getting application...")
401
+
402
+ cloud_app = Application.get(
403
+ client=client,
404
+ id=app_id,
405
+ )
406
+ cloud_app_dict = cloud_app.to_dict()
407
+
408
+ if output is not None and output != "":
409
+ with open(output, "w") as f:
410
+ json.dump(cloud_app_dict, f, indent=2)
411
+
412
+ success(f"Application information saved to [magenta]{output}[/magenta].")
413
+
414
+ return
415
+
416
+ print_json(cloud_app_dict)
417
+ ```
418
+
419
+ - The short description is: `Get a Nextmv Cloud application.`
420
+ - The detailed description is:
421
+
422
+ ```text
423
+ This command is useful to get the attributes of an existing Nextmv Cloud
424
+ application by its ID.
425
+ ```
426
+
427
+ - The examples section is fenced with the `[bold][underline]
428
+ [/underline][/bold]` tags.
429
+ - Each example is listed as a bullet, using a hyphen (`-`).
430
+ - Each example has a short description, followed by the command itself in a
431
+ new line, with 4 spaces of indentation in comparison to where the hyphen is.
432
+ - The command itself should be formatted using the `[dim]` `[/dim]` tags.
433
+ - The command should start with a dollar sign (`$`), followed by a space, and
434
+ then the actual command.
435
+ - When an example command is too long, use a double backslash (`\\`) for line
436
+ continuation. It gets rendered as a single backslash. The next line should
437
+ have 4 additional spaces of indentation (8 spaces total from the hyphen):
438
+
439
+ ```text
440
+ - Create an application with an ID and description.
441
+ $ [dim]nextmv cloud app create --name "Hare App" --app-id hare-app \\
442
+ --description "An application for routing hares"[/dim]
443
+ ```
444
+
445
+ ## Command options
446
+
447
+ Consider the following guideline when declaring command options:
448
+
449
+ - We _only_ use command options, we _do not_ use command arguments.
450
+ - Use the `Annotated` type hint from the `typing_extensions` module to declare
451
+ options. Consider the `name` option from the `nextmv cloud app create`
452
+ command hosted in the `cloud/app/create.py` file:
453
+
454
+ ```python
455
+ name: Annotated[
456
+ str,
457
+ typer.Option(
458
+ "--name",
459
+ "-n",
460
+ help="A name for the application.",
461
+ metavar="NAME",
462
+ ),
463
+ ],
464
+ ```
465
+
466
+ The type of the option is `str`, and we use `typer.Option` to declare the
467
+ option's properties.
468
+
469
+ - If possible, provide both a long and short version of the option. In the example
470
+ above, the long version is `--name` and the short version is `-n`.
471
+ - Always provide a `help` parameter that describes what the option does. Avoid
472
+ long-winded explanations here, keep it short and to the point. If you need to
473
+ provide more context about using the option, add that information to the
474
+ command's help docstring.
475
+ - For `str` options, always provide a `metavar` parameter for describing the
476
+ expected value. In the example above, the `metavar` is `NAME`, indicating
477
+ that the option expects a name string.
478
+ - _Optional_ options are declared using the `| None` type hint and normally
479
+ have a default value of `None`. Consider the `default_instance_id` option of
480
+ the same command:
481
+
482
+ ```python
483
+ default_instance_id: Annotated[
484
+ str | None,
485
+ typer.Option(
486
+ "--default-instance-id",
487
+ "-i",
488
+ help="An optional default instance ID for the application.",
489
+ metavar="DEFAULT_INSTANCE_ID",
490
+ ),
491
+ ] = None,
492
+ ```
493
+
494
+ The type hint is `str | None`, and the default value is `None`.
495
+
496
+ - For `bool` options, always provide at least the long name, to avoid the
497
+ auto-populated `--no-...` version of the option, given by Typer.
498
+ - `bool` options should have a default value of either `True` or `False`.
499
+ - Use the `rich_help_panel` to organize commands that have a large number of
500
+ options. Consider the `input` option of the `nextmv cloud run create`
501
+ command, hosted in the `cloud/run/create.py` file:
502
+
503
+ ```python
504
+ input: Annotated[
505
+ str | None,
506
+ typer.Option(
507
+ "--input",
508
+ "-i",
509
+ help="The input path to use. File or directory depending on content format. "
510
+ "Uses [magenta]stdin[/magenta] if not defined.",
511
+ metavar="INPUT_PATH",
512
+ rich_help_panel="Input control",
513
+ ),
514
+ ] = None,
515
+ ```
516
+
517
+ The `rich_help_panel` parameter is set to `Input control`, which groups
518
+ this option under the `Input control` panel in the command's help message.
519
+
520
+ - When an option can be set via an environment variable, use the `envvar`
521
+ parameter. Environment variable names should follow the `NEXTMV_<OPTION_NAME>`
522
+ convention, e.g. `NEXTMV_PROFILE`, `NEXTMV_API_KEY`, `NEXTMV_APP_ID`,
523
+ `NEXTMV_RUN_ID`.
524
+ - Place widely-used options in the `options.py` file, and import them into commands
525
+ that need them. Consider the `profile` option, which is used
526
+ in many `nextmv cloud` commands. It is defined in the `options.py` file:
527
+
528
+ ```python
529
+ # profile option - can be used in any command to specify which profile to use.
530
+ # Define it as follows in commands or callbacks, as necessary:
531
+ # profile: ProfileOption = None
532
+ ProfileOption = Annotated[
533
+ str | None,
534
+ typer.Option(
535
+ "--profile",
536
+ "-p",
537
+ help="Profile to use for this action. Use [code]nextmv configuration[/code] to manage profiles.",
538
+ envvar="NEXTMV_PROFILE",
539
+ metavar="PROFILE_NAME",
540
+ ),
541
+ ]
542
+ ```
543
+
544
+ Then, in commands that need these options, simply import them and use them
545
+ as needed. Consider the `nextmv cloud app list` command, in the `cloud/app/list.py`
546
+ file:
547
+
548
+ ```python
549
+ @app.command()
550
+ def list(
551
+ output: Annotated[
552
+ str | None,
553
+ typer.Option(
554
+ "--output",
555
+ "-o",
556
+ help="Saves the app list information to this location.",
557
+ metavar="OUTPUT_PATH",
558
+ ),
559
+ ] = None,
560
+ profile: ProfileOption = None,
561
+ ) -> None:
562
+ ```
563
+
564
+ The `profile` option's type is `ProfileOption`, which is imported from the
565
+ `options.py` file.
566
+
567
+ - If a command outputs `JSON` content, try to always provide an `output` option
568
+ to allow the user to save the output to a file. Consider the `nextmv cloud
569
+ app list` command again. It has an `output` option that allows the user to
570
+ save the list of applications to a file.
571
+ - Always order command options alphabetically. Required options (without
572
+ default values) should be listed first, in alphabetical order. Optional
573
+ options (with default values) should follow, also in alphabetical order. When
574
+ using `rich_help_panel` to group options, maintain alphabetical order within
575
+ each panel. This ensures consistency across the CLI and makes it easier to
576
+ locate options in the code. An exception for this is the `profile` option, which
577
+ should always be the last option in the command's signature, for consistency
578
+ across the CLI.
579
+
580
+ [typer]: https://typer.tiangolo.com
581
+ [typer-learn]: https://typer.tiangolo.com/tutorial/
582
+ [rich]: https://rich.readthedocs.io/en/stable/
583
+ [rich-emoji]: https://rich.readthedocs.io/en/latest/markup.html#emoji
@@ -0,0 +1,49 @@
1
+ """
2
+ This module defines the cloud command tree for the Nextmv CLI.
3
+ """
4
+
5
+ import typer
6
+
7
+ from nextmv.cli.cloud.acceptance import app as acceptance_app
8
+ from nextmv.cli.cloud.account import app as account_app
9
+ from nextmv.cli.cloud.app import app as app_app
10
+ from nextmv.cli.cloud.batch import app as batch_app
11
+ from nextmv.cli.cloud.data import app as data_app
12
+ from nextmv.cli.cloud.ensemble import app as ensemble_app
13
+ from nextmv.cli.cloud.input_set import app as input_set_app
14
+ from nextmv.cli.cloud.instance import app as instance_app
15
+ from nextmv.cli.cloud.managed_input import app as managed_input_app
16
+ from nextmv.cli.cloud.run import app as run_app
17
+ from nextmv.cli.cloud.scenario import app as scenario_app
18
+ from nextmv.cli.cloud.secrets import app as secrets_app
19
+ from nextmv.cli.cloud.shadow import app as shadow_app
20
+ from nextmv.cli.cloud.switchback import app as switchback_app
21
+ from nextmv.cli.cloud.upload import app as upload_app
22
+ from nextmv.cli.cloud.version import app as version_app
23
+
24
+ # Set up subcommand application.
25
+ app = typer.Typer()
26
+ app.add_typer(acceptance_app, name="acceptance")
27
+ app.add_typer(account_app, name="account")
28
+ app.add_typer(app_app, name="app")
29
+ app.add_typer(batch_app, name="batch")
30
+ app.add_typer(data_app, name="data")
31
+ app.add_typer(ensemble_app, name="ensemble")
32
+ app.add_typer(input_set_app, name="input-set")
33
+ app.add_typer(instance_app, name="instance")
34
+ app.add_typer(managed_input_app, name="managed-input")
35
+ app.add_typer(run_app, name="run")
36
+ app.add_typer(scenario_app, name="scenario")
37
+ app.add_typer(secrets_app, name="secrets")
38
+ app.add_typer(shadow_app, name="shadow")
39
+ app.add_typer(switchback_app, name="switchback")
40
+ app.add_typer(upload_app, name="upload")
41
+ app.add_typer(version_app, name="version")
42
+
43
+
44
+ @app.callback()
45
+ def callback() -> None:
46
+ """
47
+ Interact with Nextmv Cloud, a platform for deploying and managing decision models.
48
+ """
49
+ pass