nextmv 0.40.0__py3-none-any.whl → 1.0.0.dev0__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/__init__.py +2 -0
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/cloud/__init__.py +45 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +168 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +3 -3
- nextmv/cli/community/list.py +1 -1
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +68 -4
- nextmv/cli/configuration/create.py +14 -15
- nextmv/cli/configuration/delete.py +24 -12
- nextmv/cli/configuration/list.py +1 -1
- nextmv/cli/main.py +58 -16
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +168 -0
- nextmv/cli/version.py +20 -1
- nextmv/cloud/__init__.py +4 -1
- nextmv/cloud/acceptance_test.py +19 -18
- nextmv/cloud/account.py +268 -24
- nextmv/cloud/application/__init__.py +955 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/batch_experiment.py +3 -1
- nextmv/cloud/instance.py +11 -1
- nextmv/cloud/integration.py +1 -1
- nextmv/cloud/package.py +50 -9
- nextmv/input.py +20 -36
- nextmv/local/application.py +3 -15
- nextmv/polling.py +54 -16
- nextmv/run.py +83 -27
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/METADATA +33 -8
- nextmv-1.0.0.dev0.dist-info/RECORD +158 -0
- nextmv/cli/community/community.py +0 -24
- nextmv/cli/configuration/configuration.py +0 -23
- nextmv/cli/error.py +0 -22
- nextmv/cloud/application.py +0 -4204
- nextmv-0.40.0.dist-info/RECORD +0 -66
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "v1.0.0.dev0"
|
nextmv/__init__.py
CHANGED
|
@@ -53,6 +53,7 @@ from .output import write as write
|
|
|
53
53
|
from .output import write_local as write_local
|
|
54
54
|
from .polling import DEFAULT_POLLING_OPTIONS as DEFAULT_POLLING_OPTIONS
|
|
55
55
|
from .polling import PollingOptions as PollingOptions
|
|
56
|
+
from .polling import default_polling_options as default_polling_options
|
|
56
57
|
from .polling import poll as poll
|
|
57
58
|
from .run import ErrorLog as ErrorLog
|
|
58
59
|
from .run import ExternalRunResult as ExternalRunResult
|
|
@@ -72,6 +73,7 @@ from .run import RunType as RunType
|
|
|
72
73
|
from .run import RunTypeConfiguration as RunTypeConfiguration
|
|
73
74
|
from .run import StatisticsIndicator as StatisticsIndicator
|
|
74
75
|
from .run import SyncedRun as SyncedRun
|
|
76
|
+
from .run import TimestampedRunLog as TimestampedRunLog
|
|
75
77
|
from .run import TrackedRun as TrackedRun
|
|
76
78
|
from .run import TrackedRunStatus as TrackedRunStatus
|
|
77
79
|
from .run import run_duration as run_duration
|
|
@@ -0,0 +1,511 @@
|
|
|
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
|
+
- `error`: prints an error and raises an exception.
|
|
156
|
+
- `success`: prints a success message.
|
|
157
|
+
- `warning`: prints a warning message.
|
|
158
|
+
- `in_progress`: prints an in-progress message.
|
|
159
|
+
- `info`: prints an informational message. You can give it an emoji for the
|
|
160
|
+
message. The other commands have fixed emojis.
|
|
161
|
+
- For printing `JSON` information, use the `print_json` function in the
|
|
162
|
+
`messages.py` file to print JSON output. This ensures consistent formatting
|
|
163
|
+
across the CLI.
|
|
164
|
+
- Emojis should be formatted according to [Rich's emoji guide][rich-emoji].
|
|
165
|
+
They are strings enclosed in colons, e.g. `:rocket:`, `:boom:`,
|
|
166
|
+
`:hourglass_flowing_sand:`, etc.
|
|
167
|
+
- When using the `success` function, include the variable or entity that was
|
|
168
|
+
affected, formatted with `[magenta]`:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
success(f"Application [magenta]{app_id}[/magenta] deleted successfully.")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Confirmation prompts
|
|
175
|
+
|
|
176
|
+
For destructive actions (like deletions), use `rich.prompt.Confirm.ask()` to
|
|
177
|
+
ask for user confirmation before proceeding. Follow these guidelines:
|
|
178
|
+
|
|
179
|
+
- The confirmation message should use `[magenta]` for the variable being
|
|
180
|
+
affected.
|
|
181
|
+
- Set `default=False` for safety, so the user must explicitly confirm.
|
|
182
|
+
- Provide a `--yes` / `-y` flag to skip the confirmation prompt, useful for
|
|
183
|
+
non-interactive sessions.
|
|
184
|
+
- If the user declines, call `info()` with the `:bulb:` emoji and return early.
|
|
185
|
+
|
|
186
|
+
Consider the `nextmv cloud app delete` command:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
if not yes:
|
|
190
|
+
confirm = Confirm.ask(
|
|
191
|
+
f"Are you sure you want to delete application [magenta]{app_id}[/magenta]? This action cannot be undone",
|
|
192
|
+
default=False,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if not confirm:
|
|
196
|
+
info(msg=f"Application [magenta]{app_id}[/magenta] will not be deleted.", emoji=":bulb:")
|
|
197
|
+
return
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Formatting
|
|
201
|
+
|
|
202
|
+
Use these Rich markup guidelines when formatting help text and messages.
|
|
203
|
+
|
|
204
|
+
- When talking about a command, or a command option, use the `[code]` `[/code]`
|
|
205
|
+
tags. Take this example from the help menu of the `cloud/app/delete.py` file.
|
|
206
|
+
In the command help, when referring to the `--yes` option, we use
|
|
207
|
+
`[code]--yes[/code]` to format it as code.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
@app.command()
|
|
211
|
+
def delete(
|
|
212
|
+
app_id: AppIDOption,
|
|
213
|
+
yes: Annotated[
|
|
214
|
+
bool,
|
|
215
|
+
typer.Option(
|
|
216
|
+
"--yes",
|
|
217
|
+
"-y",
|
|
218
|
+
help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
|
|
219
|
+
),
|
|
220
|
+
] = False,
|
|
221
|
+
profile: ProfileOption = None,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Deletes a Nextmv Cloud application.
|
|
225
|
+
|
|
226
|
+
This action is permanent and cannot be undone. Use the [code]--yes[/code]
|
|
227
|
+
flag to skip the confirmation prompt.
|
|
228
|
+
|
|
229
|
+
[bold][underline]Examples[/underline][/bold]
|
|
230
|
+
|
|
231
|
+
- Delete the application with the ID [magenta]hare-app[/magenta].
|
|
232
|
+
$ [green]nextmv cloud app delete --app-id hare-app[/green]
|
|
233
|
+
|
|
234
|
+
- Delete the application with the ID [magenta]hare-app[/magenta] without confirmation prompt.
|
|
235
|
+
$ [green]nextmv cloud app delete --app-id hare-app --yes[/green]
|
|
236
|
+
"""
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- When talking about a variable (like a filepath, value of an option, a string,
|
|
240
|
+
etc.), use the `[magenta]` `[/magenta]` tags. Using the same example as
|
|
241
|
+
above, when referring to the application ID `hare-app`, we use
|
|
242
|
+
`[magenta]hare-app[/magenta]` to format it as a variable. Another example is
|
|
243
|
+
when providing error messages that include values:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
error(f"Input path [magenta]{input}[/magenta] does not exist.")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- Links to URLs should be formatted using the `[link=URL_LINK][bold]
|
|
250
|
+
[/bold][/link]` tags. Consider the main help message of the `nextmv
|
|
251
|
+
community` command, in the `community/__init__.py` file:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
@app.callback()
|
|
255
|
+
def callback() -> None:
|
|
256
|
+
"""
|
|
257
|
+
Interact with community apps, which are pre-built decision models.
|
|
258
|
+
|
|
259
|
+
Community apps are maintained in the following GitHub repository:
|
|
260
|
+
[link=https://github.com/nextmv-io/community-apps][bold]nextmv-io/community-apps[/bold][/link].
|
|
261
|
+
"""
|
|
262
|
+
pass
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The link provided is <https://github.com/nextmv-io/community-apps>, and it will
|
|
266
|
+
be applied to the text `nextmv-io/community-apps`.
|
|
267
|
+
|
|
268
|
+
- Colors that can be used for highlighting (we limit colors to keep coloring consistent):
|
|
269
|
+
- `[magenta]`: variable, literals, etc. - mainly short technical things (see above).
|
|
270
|
+
- `[green]`: commands, etc. - longer technical things, or, as a type-contrast to magenta.
|
|
271
|
+
- `[yellow]`: emphasis, highlight of special items, etc. (use sparingly only).
|
|
272
|
+
|
|
273
|
+
## Command documentation
|
|
274
|
+
|
|
275
|
+
Every command should have good-enough documentation that guides the user on how
|
|
276
|
+
to use it.
|
|
277
|
+
|
|
278
|
+
- Document every command using Python docstrings.
|
|
279
|
+
- Document every option and argument using the `help` parameter of the
|
|
280
|
+
`typer.Option` functions.
|
|
281
|
+
- Option documentation should be short and to the point. Avoid long
|
|
282
|
+
explanations. If necessary, you can add more detailed information in the
|
|
283
|
+
command's help.
|
|
284
|
+
- The help of the command should be structured as follows:
|
|
285
|
+
- A short, one-line description of what the command does.
|
|
286
|
+
- A blank line.
|
|
287
|
+
- A more detailed description of what the command does. This can be multiple
|
|
288
|
+
paragraphs. Only add this section if necessary.
|
|
289
|
+
- A blank line.
|
|
290
|
+
- An Examples section, with one or more examples of how to use the command.
|
|
291
|
+
Each example should have a short description of what it does, followed by
|
|
292
|
+
the command itself. More on example formatting below.
|
|
293
|
+
- Consider the `nextmv cloud app get` command, under the `cloud/app/get.py` file:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
@app.command()
|
|
297
|
+
def get(
|
|
298
|
+
app_id: AppIDOption,
|
|
299
|
+
output: Annotated[
|
|
300
|
+
str | None,
|
|
301
|
+
typer.Option(
|
|
302
|
+
"--output",
|
|
303
|
+
"-o",
|
|
304
|
+
help="Saves the app information to this location.",
|
|
305
|
+
metavar="OUTPUT_PATH",
|
|
306
|
+
),
|
|
307
|
+
] = None,
|
|
308
|
+
profile: ProfileOption = None,
|
|
309
|
+
) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Get a Nextmv Cloud application.
|
|
312
|
+
|
|
313
|
+
This command is useful to get the attributes of an existing Nextmv Cloud
|
|
314
|
+
application by its ID.
|
|
315
|
+
|
|
316
|
+
[bold][underline]Examples[/underline][/bold]
|
|
317
|
+
|
|
318
|
+
- Get the application with the ID [magenta]hare-app[/magenta].
|
|
319
|
+
$ [green]nextmv cloud app get --app-id hare-app[/green]
|
|
320
|
+
|
|
321
|
+
- Get the application with the ID [magenta]hare-app[/magenta] and save the information to an
|
|
322
|
+
[magenta]app.json[/magenta] file.
|
|
323
|
+
$ [green]nextmv cloud app get --app-id hare-app --output app.json[/green]
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
client = build_client(profile)
|
|
327
|
+
info(msg="Getting application...", emoji=":hourglass_flowing_sand:")
|
|
328
|
+
|
|
329
|
+
cloud_app = Application.get(
|
|
330
|
+
client=client,
|
|
331
|
+
id=app_id,
|
|
332
|
+
)
|
|
333
|
+
cloud_app_dict = cloud_app.to_dict()
|
|
334
|
+
|
|
335
|
+
if output is not None and output != "":
|
|
336
|
+
with open(output, "w") as f:
|
|
337
|
+
json.dump(cloud_app_dict, f, indent=2)
|
|
338
|
+
|
|
339
|
+
success(msg=f"Application information saved to [magenta]{output}[/magenta].")
|
|
340
|
+
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
print_json(cloud_app_dict)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- The short description is: `Get a Nextmv Cloud application.`
|
|
347
|
+
- The detailed description is:
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
This command is useful to get the attributes of an existing Nextmv Cloud
|
|
351
|
+
application by its ID.
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
- The examples section is fenced with the `[bold][underline]
|
|
355
|
+
[/underline][/bold]` tags.
|
|
356
|
+
|
|
357
|
+
- Each example is listed as a bullet, using a hyphen (`-`).
|
|
358
|
+
- Each example has a short description, followed by the command itself in a
|
|
359
|
+
new line, with 4 spaces of indentation in comparison to where the hyphen is.
|
|
360
|
+
- The command itself should be formatted using the `[green]` `[/green]` tags.
|
|
361
|
+
- The command should start with a dollar sign (`$`), followed by a space, and
|
|
362
|
+
then the actual command.
|
|
363
|
+
- When an example command is too long, use a double backslash (`\\`) for line
|
|
364
|
+
continuation. It gets rendered as a single backslash. The next line should
|
|
365
|
+
have 4 additional spaces of indentation (8 spaces total from the hyphen):
|
|
366
|
+
|
|
367
|
+
```text
|
|
368
|
+
- Create an application with an ID and description.
|
|
369
|
+
$ [green]nextmv cloud app create --name "Hare App" --app-id hare-app \\
|
|
370
|
+
--description "An application for routing hares"[/green]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Command options
|
|
374
|
+
|
|
375
|
+
Consider the following guideline when declaring command options:
|
|
376
|
+
|
|
377
|
+
- We _only_ use command options, we _do not_ use command arguments.
|
|
378
|
+
- Use the `Annotated` type hint from the `typing_extensions` module to declare
|
|
379
|
+
options. Consider the `name` option from the `nextmv cloud app create`
|
|
380
|
+
command hosted in the `cloud/app/create.py` file:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
name: Annotated[
|
|
384
|
+
str,
|
|
385
|
+
typer.Option(
|
|
386
|
+
"--name",
|
|
387
|
+
"-n",
|
|
388
|
+
help="A name for the application.",
|
|
389
|
+
metavar="NAME",
|
|
390
|
+
),
|
|
391
|
+
],
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The type of the option is `str`, and we use `typer.Option` to declare the
|
|
395
|
+
option's properties.
|
|
396
|
+
|
|
397
|
+
- If possible, provide both a long and short version of the option. In the example
|
|
398
|
+
above, the long version is `--name` and the short version is `-n`.
|
|
399
|
+
- Always provide a `help` parameter that describes what the option does. Avoid
|
|
400
|
+
long-winded explanations here, keep it short and to the point. If you need to
|
|
401
|
+
provide more context about using the option, add that information to the
|
|
402
|
+
command's help docstring.
|
|
403
|
+
- For `str` options, always provide a `metavar` parameter for describing the
|
|
404
|
+
expected value. In the example above, the `metavar` is `NAME`, indicating
|
|
405
|
+
that the option expects a name string.
|
|
406
|
+
- _Optional_ options are declared using the `| None` type hint and normally
|
|
407
|
+
have a default value of `None`. Consider the `default_instance_id` option of
|
|
408
|
+
the same command:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
default_instance_id: Annotated[
|
|
412
|
+
str | None,
|
|
413
|
+
typer.Option(
|
|
414
|
+
"--default-instance-id",
|
|
415
|
+
"-i",
|
|
416
|
+
help="An optional default instance ID for the application.",
|
|
417
|
+
metavar="DEFAULT_INSTANCE_ID",
|
|
418
|
+
),
|
|
419
|
+
] = None,
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
The type hint is `str | None`, and the default value is `None`.
|
|
423
|
+
|
|
424
|
+
- For `bool` options, always provide at least the long name, to avoid the
|
|
425
|
+
auto-populated `--no-...` version of the option, given by Typer.
|
|
426
|
+
- `bool` options should have a default value of either `True` or `False`.
|
|
427
|
+
- Use the `rich_help_panel` to organize commands that have a large number of
|
|
428
|
+
options. Consider the `input` option of the `nextmv cloud run create`
|
|
429
|
+
command, hosted in the `cloud/run/create.py` file:
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
input: Annotated[
|
|
433
|
+
str | None,
|
|
434
|
+
typer.Option(
|
|
435
|
+
"--input",
|
|
436
|
+
"-i",
|
|
437
|
+
help="The input path to use. File or directory depending on content format. "
|
|
438
|
+
"Uses [magenta]stdin[/magenta] if not defined.",
|
|
439
|
+
metavar="INPUT_PATH",
|
|
440
|
+
rich_help_panel="Input control",
|
|
441
|
+
),
|
|
442
|
+
] = None,
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
The `rich_help_panel` parameter is set to `Input control`, which groups
|
|
446
|
+
this option under the `Input control` panel in the command's help message.
|
|
447
|
+
|
|
448
|
+
- When an option can be set via an environment variable, use the `envvar`
|
|
449
|
+
parameter. Environment variable names should follow the `NEXTMV_<OPTION_NAME>`
|
|
450
|
+
convention, e.g. `NEXTMV_PROFILE`, `NEXTMV_API_KEY`, `NEXTMV_APP_ID`,
|
|
451
|
+
`NEXTMV_RUN_ID`.
|
|
452
|
+
- Place widely-used options in the `options.py` file, and import them into commands
|
|
453
|
+
that need them. Consider the `profile` option, which is used
|
|
454
|
+
in many `nextmv cloud` commands. It is defined in the `options.py` file:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
# profile option - can be used in any command to specify which profile to use.
|
|
458
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
459
|
+
# profile: ProfileOption = None
|
|
460
|
+
ProfileOption = Annotated[
|
|
461
|
+
str | None,
|
|
462
|
+
typer.Option(
|
|
463
|
+
"--profile",
|
|
464
|
+
"-p",
|
|
465
|
+
help="Profile to use for this action. Use [code]nextmv configuration[/code] to manage profiles.",
|
|
466
|
+
envvar="NEXTMV_PROFILE",
|
|
467
|
+
metavar="PROFILE_NAME",
|
|
468
|
+
),
|
|
469
|
+
]
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Then, in commands that need these options, simply import them and use them
|
|
473
|
+
as needed. Consider the `nextmv cloud app list` command, in the `cloud/app/list.py`
|
|
474
|
+
file:
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
@app.command()
|
|
478
|
+
def list(
|
|
479
|
+
output: Annotated[
|
|
480
|
+
str | None,
|
|
481
|
+
typer.Option(
|
|
482
|
+
"--output",
|
|
483
|
+
"-o",
|
|
484
|
+
help="Saves the app list information to this location.",
|
|
485
|
+
metavar="OUTPUT_PATH",
|
|
486
|
+
),
|
|
487
|
+
] = None,
|
|
488
|
+
profile: ProfileOption = None,
|
|
489
|
+
) -> None:
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The `profile` option's type is `ProfileOption`, which is imported from the
|
|
493
|
+
`options.py` file.
|
|
494
|
+
|
|
495
|
+
- If a command outputs `JSON` content, try to always provide an `output` option
|
|
496
|
+
to allow the user to save the output to a file. Consider the `nextmv cloud
|
|
497
|
+
app list` command again. It has an `output` option that allows the user to
|
|
498
|
+
save the list of applications to a file.
|
|
499
|
+
- Always order command options alphabetically. Required options (without
|
|
500
|
+
default values) should be listed first, in alphabetical order. Optional
|
|
501
|
+
options (with default values) should follow, also in alphabetical order. When
|
|
502
|
+
using `rich_help_panel` to group options, maintain alphabetical order within
|
|
503
|
+
each panel. This ensures consistency across the CLI and makes it easier to
|
|
504
|
+
locate options in the code. An exception for this is the `profile` option, which
|
|
505
|
+
should always be the last option in the command's signature, for consistency
|
|
506
|
+
across the CLI.
|
|
507
|
+
|
|
508
|
+
[typer]: https://typer.tiangolo.com
|
|
509
|
+
[typer-learn]: https://typer.tiangolo.com/tutorial/
|
|
510
|
+
[rich]: https://rich.readthedocs.io/en/stable/
|
|
511
|
+
[rich-emoji]: https://rich.readthedocs.io/en/latest/markup.html#emoji
|
|
@@ -0,0 +1,45 @@
|
|
|
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.upload import app as upload_app
|
|
20
|
+
from nextmv.cli.cloud.version import app as version_app
|
|
21
|
+
|
|
22
|
+
# Set up subcommand application.
|
|
23
|
+
app = typer.Typer()
|
|
24
|
+
app.add_typer(acceptance_app, name="acceptance")
|
|
25
|
+
app.add_typer(account_app, name="account")
|
|
26
|
+
app.add_typer(app_app, name="app")
|
|
27
|
+
app.add_typer(batch_app, name="batch")
|
|
28
|
+
app.add_typer(data_app, name="data")
|
|
29
|
+
app.add_typer(ensemble_app, name="ensemble")
|
|
30
|
+
app.add_typer(input_set_app, name="input-set")
|
|
31
|
+
app.add_typer(instance_app, name="instance")
|
|
32
|
+
app.add_typer(managed_input_app, name="managed-input")
|
|
33
|
+
app.add_typer(run_app, name="run")
|
|
34
|
+
app.add_typer(scenario_app, name="scenario")
|
|
35
|
+
app.add_typer(secrets_app, name="secrets")
|
|
36
|
+
app.add_typer(upload_app, name="upload")
|
|
37
|
+
app.add_typer(version_app, name="version")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.callback()
|
|
41
|
+
def callback() -> None:
|
|
42
|
+
"""
|
|
43
|
+
Interact with Nextmv Cloud, a platform for deploying and managing decision models.
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the cloud acceptance command tree for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from nextmv.cli.cloud.acceptance.create import app as create_app
|
|
8
|
+
from nextmv.cli.cloud.acceptance.delete import app as delete_app
|
|
9
|
+
from nextmv.cli.cloud.acceptance.get import app as get_app
|
|
10
|
+
from nextmv.cli.cloud.acceptance.list import app as list_app
|
|
11
|
+
from nextmv.cli.cloud.acceptance.update import app as update_app
|
|
12
|
+
|
|
13
|
+
# Set up subcommand application.
|
|
14
|
+
app = typer.Typer()
|
|
15
|
+
app.add_typer(create_app)
|
|
16
|
+
app.add_typer(delete_app)
|
|
17
|
+
app.add_typer(get_app)
|
|
18
|
+
app.add_typer(list_app)
|
|
19
|
+
app.add_typer(update_app)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.callback()
|
|
23
|
+
def callback() -> None:
|
|
24
|
+
"""
|
|
25
|
+
Create and manage Nextmv Cloud acceptance tests.
|
|
26
|
+
"""
|
|
27
|
+
pass
|