nextmv 0.39.0.dev1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +1 -2
- nextmv/__init__.py +2 -4
- nextmv/cli/CONTRIBUTING.md +583 -0
- nextmv/cli/cloud/__init__.py +49 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +391 -0
- nextmv/cli/cloud/acceptance/delete.py +64 -0
- nextmv/cli/cloud/acceptance/get.py +103 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +59 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +140 -0
- nextmv/cli/cloud/app/delete.py +57 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +432 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +452 -0
- nextmv/cli/cloud/batch/delete.py +64 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +33 -0
- nextmv/cli/cloud/ensemble/create.py +413 -0
- nextmv/cli/cloud/ensemble/delete.py +63 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +32 -0
- nextmv/cli/cloud/input_set/create.py +168 -0
- nextmv/cli/cloud/input_set/delete.py +64 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +289 -0
- nextmv/cli/cloud/instance/delete.py +61 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +144 -0
- nextmv/cli/cloud/managed_input/delete.py +64 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +524 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +166 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +500 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +61 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +63 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +144 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +64 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +53 -0
- nextmv/cli/cloud/shadow/update.py +96 -0
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +151 -0
- nextmv/cli/cloud/switchback/delete.py +64 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +53 -0
- nextmv/cli/cloud/switchback/update.py +96 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +96 -0
- nextmv/cli/cloud/version/delete.py +61 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +86 -0
- nextmv/cli/community/list.py +200 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +228 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/confirm.py +34 -0
- nextmv/cli/main.py +161 -3
- nextmv/cli/message.py +170 -0
- nextmv/cli/options.py +220 -0
- nextmv/cli/version.py +22 -2
- nextmv/cloud/__init__.py +17 -38
- nextmv/cloud/acceptance_test.py +20 -83
- nextmv/cloud/account.py +269 -30
- nextmv/cloud/application/__init__.py +898 -0
- nextmv/cloud/application/_acceptance.py +424 -0
- nextmv/cloud/application/_batch_scenario.py +845 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +263 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +320 -0
- nextmv/cloud/application/_switchback.py +332 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +304 -0
- nextmv/cloud/batch_experiment.py +6 -2
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/instance.py +11 -1
- nextmv/cloud/integration.py +8 -5
- nextmv/cloud/package.py +50 -9
- nextmv/cloud/shadow.py +254 -0
- nextmv/cloud/switchback.py +228 -0
- nextmv/deprecated.py +5 -3
- nextmv/input.py +20 -88
- nextmv/local/application.py +3 -15
- nextmv/local/runner.py +1 -1
- nextmv/model.py +50 -11
- nextmv/options.py +11 -256
- nextmv/output.py +0 -62
- nextmv/polling.py +54 -16
- nextmv/run.py +84 -37
- nextmv/status.py +1 -51
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
- nextmv-1.0.0.dist-info/RECORD +185 -0
- nextmv-1.0.0.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -4204
- nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
- nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
- {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the configuration list command for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, non_profile_keys, obscure_api_key
|
|
10
|
+
from nextmv.cli.message import error
|
|
11
|
+
|
|
12
|
+
# Set up subcommand application.
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command()
|
|
18
|
+
def list() -> None:
|
|
19
|
+
"""
|
|
20
|
+
List the current configuration and all profiles.
|
|
21
|
+
|
|
22
|
+
[bold][underline]Examples[/underline][/bold]
|
|
23
|
+
|
|
24
|
+
- Show current configuration and all profiles.
|
|
25
|
+
$ [dim]nextmv configuration list[/dim]
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
config = load_config()
|
|
29
|
+
if config == {}:
|
|
30
|
+
error("No configuration found. Please run [code]nextmv configuration[/code].")
|
|
31
|
+
|
|
32
|
+
default = {
|
|
33
|
+
"api_key": config.get(API_KEY_KEY),
|
|
34
|
+
"endpoint": config.get(ENDPOINT_KEY),
|
|
35
|
+
"name": "Default",
|
|
36
|
+
}
|
|
37
|
+
profiles = [default]
|
|
38
|
+
|
|
39
|
+
for k, v in config.items():
|
|
40
|
+
# Skip default configuration.
|
|
41
|
+
if k in non_profile_keys():
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
profile = {
|
|
45
|
+
"name": k,
|
|
46
|
+
"api_key": v.get(API_KEY_KEY),
|
|
47
|
+
"endpoint": v.get(ENDPOINT_KEY),
|
|
48
|
+
}
|
|
49
|
+
profiles.append(profile)
|
|
50
|
+
|
|
51
|
+
table = Table("Profile name", "API Key", "Endpoint")
|
|
52
|
+
not_set = "[italic]Not set[/italic]"
|
|
53
|
+
for profile in profiles:
|
|
54
|
+
if profile["name"] != "Default":
|
|
55
|
+
table.add_row(
|
|
56
|
+
profile["name"],
|
|
57
|
+
obscure_api_key(profile["api_key"]) if profile.get("api_key") is not None else not_set,
|
|
58
|
+
profile["endpoint"] if profile.get("endpoint") is not None else not_set,
|
|
59
|
+
)
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
api_key = not_set
|
|
63
|
+
if profile.get("api_key") is not None:
|
|
64
|
+
api_key = obscure_api_key(profile["api_key"])
|
|
65
|
+
|
|
66
|
+
endpoint = not_set
|
|
67
|
+
if profile.get("endpoint") is not None:
|
|
68
|
+
endpoint = profile["endpoint"]
|
|
69
|
+
|
|
70
|
+
table.add_row(
|
|
71
|
+
f"[bold yellow]{profile['name']}[/bold yellow]",
|
|
72
|
+
f"[bold yellow]{api_key}[/bold yellow]",
|
|
73
|
+
f"[bold yellow]{endpoint}[/bold yellow]",
|
|
74
|
+
)
|
|
75
|
+
table.add_section()
|
|
76
|
+
|
|
77
|
+
console.print(table)
|
nextmv/cli/confirm.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from rich.prompt import Confirm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_confirmation(msg: str, default: bool = False) -> bool:
|
|
7
|
+
"""
|
|
8
|
+
Method to get a yes/no confirmation from the user.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
msg : str
|
|
13
|
+
The message to display to the user.
|
|
14
|
+
default : bool, optional
|
|
15
|
+
The default value if the user just presses Enter. Default is False.
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
bool
|
|
20
|
+
True if the user confirmed, False otherwise.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# If this is not an interactive terminal, do not ask for confirmation, to
|
|
24
|
+
# avoid hanging indefinitely waiting for a user response.
|
|
25
|
+
if not sys.stdin.isatty():
|
|
26
|
+
return default
|
|
27
|
+
|
|
28
|
+
return Confirm.ask(
|
|
29
|
+
msg,
|
|
30
|
+
default=default,
|
|
31
|
+
case_sensitive=False,
|
|
32
|
+
show_default=True,
|
|
33
|
+
show_choices=True,
|
|
34
|
+
)
|
nextmv/cli/main.py
CHANGED
|
@@ -1,20 +1,178 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The Nextmv Command Line Interface (CLI).
|
|
3
3
|
|
|
4
|
-
This module is the main entry point for the Nextmv CLI application.
|
|
4
|
+
This module is the main entry point for the Nextmv CLI application. The Nextmv
|
|
5
|
+
CLI is built with [Typer](https://typer.tiangolo.com/) and provides various
|
|
6
|
+
commands to interact with Nextmv services. You should visit the "Learn" section
|
|
7
|
+
of the Typer documentation to learn about the features that are used here.
|
|
8
|
+
|
|
9
|
+
The Nextmv CLI also uses [Rich](https://rich.readthedocs.io/en/stable/) for
|
|
10
|
+
rich text and formatting in the terminal. The command documentation is created
|
|
11
|
+
using Rich markup. You should also visit the Rich documentation to learn more
|
|
12
|
+
about the features used here. An example of Rich markup can be found in the
|
|
13
|
+
epilog of the Typer application defined below.
|
|
5
14
|
"""
|
|
6
15
|
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Annotated
|
|
18
|
+
|
|
19
|
+
import rich
|
|
7
20
|
import typer
|
|
21
|
+
from typer import rich_utils
|
|
8
22
|
|
|
23
|
+
from nextmv.cli.cloud import app as cloud_app
|
|
24
|
+
from nextmv.cli.community import app as community_app
|
|
25
|
+
from nextmv.cli.configuration import app as configuration_app
|
|
26
|
+
from nextmv.cli.configuration.config import CONFIG_DIR, GO_CLI_PATH, load_config
|
|
27
|
+
from nextmv.cli.confirm import get_confirmation
|
|
28
|
+
from nextmv.cli.message import error, info, success, warning
|
|
9
29
|
from nextmv.cli.version import app as version_app
|
|
30
|
+
from nextmv.cli.version import version_callback
|
|
31
|
+
|
|
32
|
+
# Disable dim text for the extended help of commands.
|
|
33
|
+
rich_utils.STYLE_HELPTEXT = ""
|
|
10
34
|
|
|
11
35
|
# Main CLI application.
|
|
12
36
|
app = typer.Typer(
|
|
13
37
|
help="The Nextmv Command Line Interface (CLI).",
|
|
14
|
-
epilog="[italic]:rabbit: Made with :heart:
|
|
38
|
+
epilog="[dim]\n---\n\n[italic]:rabbit: Made by Nextmv with :heart:[/italic][/dim]",
|
|
15
39
|
rich_markup_mode="rich",
|
|
16
40
|
context_settings={"help_option_names": ["--help", "-h"]},
|
|
41
|
+
no_args_is_help=True,
|
|
42
|
+
invoke_without_command=True,
|
|
43
|
+
pretty_exceptions_show_locals=False,
|
|
17
44
|
)
|
|
18
45
|
|
|
19
|
-
# Register subcommands.
|
|
46
|
+
# Register subcommands. The `name` parameter is required when the subcommand
|
|
47
|
+
# module has a callback function defined.
|
|
48
|
+
app.add_typer(cloud_app, name="cloud")
|
|
49
|
+
app.add_typer(community_app, name="community")
|
|
50
|
+
app.add_typer(configuration_app, name="configuration")
|
|
20
51
|
app.add_typer(version_app)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.callback()
|
|
55
|
+
def callback(
|
|
56
|
+
ctx: typer.Context,
|
|
57
|
+
version: Annotated[
|
|
58
|
+
bool | None,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--version",
|
|
61
|
+
"-v",
|
|
62
|
+
help="Show the current version of the Nextmv CLI.",
|
|
63
|
+
callback=version_callback,
|
|
64
|
+
),
|
|
65
|
+
] = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Callback function that runs before any command. Useful for checks on the
|
|
69
|
+
environment.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# Skip checks for help commands.
|
|
73
|
+
if "--help" in sys.argv or "-h" in sys.argv:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Skip checks for certain commands.
|
|
77
|
+
ignored_commands = {"configuration", "version"}
|
|
78
|
+
if ctx.invoked_subcommand in ignored_commands:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
handle_go_cli()
|
|
82
|
+
handle_config_existence(ctx)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def handle_go_cli() -> None:
|
|
86
|
+
"""
|
|
87
|
+
Handle the presence of the deprecated Go CLI by notifying the user.
|
|
88
|
+
|
|
89
|
+
This function checks if the Go CLI is installed and prompts the user to
|
|
90
|
+
remove it to avoid conflicts with the Python CLI.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
exists = go_cli_exists()
|
|
94
|
+
if exists:
|
|
95
|
+
delete = get_confirmation(
|
|
96
|
+
"Do you want to delete the [italic red]deprecated[/italic red] Nextmv CLI "
|
|
97
|
+
f"at [magenta]{GO_CLI_PATH}[/magenta] now?"
|
|
98
|
+
)
|
|
99
|
+
if delete:
|
|
100
|
+
remove_go_cli()
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
info(
|
|
104
|
+
"You can delete the [italic red]deprecated[/italic red] Nextmv CLI later by removing "
|
|
105
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. "
|
|
106
|
+
"Make sure you also clean up your [code]PATH[/code], "
|
|
107
|
+
f"by removing references to [magenta]{CONFIG_DIR}[/magenta] from it."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def handle_config_existence(ctx: typer.Context) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Check if configuration exists and show an error if it does not.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
ctx : typer.Context
|
|
118
|
+
The Typer context object.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
config = load_config()
|
|
122
|
+
if config == {}:
|
|
123
|
+
error("No configuration found. Please run [code]nextmv configuration create[/code].")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def go_cli_exists() -> bool:
|
|
127
|
+
"""
|
|
128
|
+
Check if the Go CLI is installed by looking for the 'nextmv' executable
|
|
129
|
+
under the config dir.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
bool
|
|
134
|
+
True if the Go CLI is installed, False otherwise.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
# Check if the Go CLI executable exists
|
|
138
|
+
exists = GO_CLI_PATH.exists()
|
|
139
|
+
if exists:
|
|
140
|
+
warning(
|
|
141
|
+
"A [italic red]deprecated[/italic red] Nextmv CLI is installed at "
|
|
142
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. You should delete it to avoid conflicts."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return exists
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def remove_go_cli() -> None:
|
|
149
|
+
"""
|
|
150
|
+
Remove the Go CLI executable if it exists and notify about PATH cleanup.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
if GO_CLI_PATH.exists():
|
|
154
|
+
GO_CLI_PATH.unlink()
|
|
155
|
+
success(f"Deleted [italic red]deprecated[/italic red] [magenta]{GO_CLI_PATH}[/magenta].")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main() -> None:
|
|
159
|
+
"""
|
|
160
|
+
Entry point for the CLI with global exception handling.
|
|
161
|
+
|
|
162
|
+
Catches all exceptions except Typer/Click exceptions (which handle their
|
|
163
|
+
own exit codes) and displays a clean error message instead of a traceback.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
app()
|
|
168
|
+
except (typer.Exit, typer.Abort, SystemExit):
|
|
169
|
+
raise
|
|
170
|
+
except Exception as e:
|
|
171
|
+
# We do not use the messages.error function here because doing so would
|
|
172
|
+
# raise a Typer exception, which would print a traceback.
|
|
173
|
+
msg = str(e).rstrip("\n")
|
|
174
|
+
if not msg.endswith("."):
|
|
175
|
+
msg += "."
|
|
176
|
+
|
|
177
|
+
rich.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
|
|
178
|
+
sys.exit(1)
|
nextmv/cli/message.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The message module is used to print messages to the user with pre-defined
|
|
3
|
+
formatting. Logging, in general, is always printed to stderr.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import rich
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def message(msg: str, emoji: str | None = None) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Pretty-print a message. Your message should end with a period. The use of
|
|
17
|
+
emojis is encouraged to give context to the message. An emoji should be a
|
|
18
|
+
string as specified in:
|
|
19
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
msg : str
|
|
24
|
+
The message to display.
|
|
25
|
+
emoji : str | None
|
|
26
|
+
An optional emoji to prefix the message. If None, no emoji is used. The
|
|
27
|
+
emoji should be a string as specified in:
|
|
28
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji. For example:
|
|
29
|
+
`:hourglass_flowing_sand:`.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
msg = _format(msg)
|
|
33
|
+
if emoji:
|
|
34
|
+
rich.print(f"{emoji} {msg}", file=sys.stderr)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
rich.print(msg, file=sys.stderr)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def info(msg: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Pretty-print an informational message. Your message should end with a
|
|
43
|
+
period.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
msg : str
|
|
48
|
+
The informational message to display.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
message(msg, emoji=":bulb:")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def in_progress(msg: str) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Pretty-print an in-progress message with an hourglass emoji. Your message
|
|
57
|
+
should end with a period.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
msg : str
|
|
62
|
+
The in-progress message to display.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
message(msg, emoji=":hourglass_flowing_sand:")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def success(msg: str) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Pretty-print a success message. Your message should end with a period.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
msg : str
|
|
75
|
+
The success message to display.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
message(msg, emoji=":white_check_mark:")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def warning(msg: str) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Pretty-print a warning message. Your message should end with a period.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
msg : str
|
|
88
|
+
The warning message to display.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
msg = _format(msg)
|
|
92
|
+
rich.print(f":construction: [yellow] Warning:[/yellow] {msg}", file=sys.stderr)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def error(msg: str) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Pretty-print an error message and exit with code 1. Your message should end
|
|
98
|
+
with a period.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
msg : str
|
|
103
|
+
The error message to display.
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
------
|
|
107
|
+
typer.Exit
|
|
108
|
+
Exits the program with code 1.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
msg = _format(msg)
|
|
112
|
+
rich.print(f":x: [red]Error:[/red] {msg}", file=sys.stderr)
|
|
113
|
+
|
|
114
|
+
raise typer.Exit(code=1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_json(data: dict[str, Any] | list[dict[str, Any]]) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Pretty-print json-serializable data as JSON to stdout.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
data : dict[str, Any] | list[dict[str, Any]]
|
|
124
|
+
The data to print as JSON.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
rich.print_json(data=data)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def enum_values(enum_class: Enum) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Get a nicely formatted string of the values of an Enum class, using commas
|
|
133
|
+
and an oxford comma.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
enum_class : Enum
|
|
138
|
+
The Enum class to get the values from.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
str
|
|
143
|
+
A nicely formatted string of the values of the Enum class.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
values = [f"[magenta]{member.value}[/magenta]" for member in enum_class]
|
|
147
|
+
if len(values) == 0:
|
|
148
|
+
return ""
|
|
149
|
+
if len(values) == 1:
|
|
150
|
+
return values[0]
|
|
151
|
+
if len(values) == 2:
|
|
152
|
+
return " and ".join(values)
|
|
153
|
+
|
|
154
|
+
return ", ".join(values[:-1]) + ", and " + values[-1]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _format(msg: str) -> str:
|
|
158
|
+
"""
|
|
159
|
+
Format a message to ensure it ends with a period.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
msg : str
|
|
164
|
+
The message to format.
|
|
165
|
+
"""
|
|
166
|
+
msg = msg.rstrip("\n")
|
|
167
|
+
if not msg.endswith("."):
|
|
168
|
+
msg += "."
|
|
169
|
+
|
|
170
|
+
return msg
|
nextmv/cli/options.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared CLI options for the Nextmv CLI.
|
|
3
|
+
|
|
4
|
+
This module defines reusable option types that can be imported
|
|
5
|
+
and used across all CLI commands.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
# profile option - can be used in any command to specify which profile to use.
|
|
13
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
14
|
+
# profile: ProfileOption = None
|
|
15
|
+
ProfileOption = Annotated[
|
|
16
|
+
str | None,
|
|
17
|
+
typer.Option(
|
|
18
|
+
"--profile",
|
|
19
|
+
"-p",
|
|
20
|
+
help="Profile to use for this action. Use [code]nextmv configuration[/code] to manage profiles.",
|
|
21
|
+
envvar="NEXTMV_PROFILE",
|
|
22
|
+
metavar="PROFILE_NAME",
|
|
23
|
+
),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# app_id option - can be used in any command that requires an application ID.
|
|
27
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
28
|
+
# app_id: AppIDOption
|
|
29
|
+
AppIDOption = Annotated[
|
|
30
|
+
str,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--app-id",
|
|
33
|
+
"-a",
|
|
34
|
+
help="The Nextmv Cloud application ID to use for this action.",
|
|
35
|
+
envvar="NEXTMV_APP_ID",
|
|
36
|
+
metavar="APP_ID",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# run_id option - can be used in any command that requires a run ID.
|
|
41
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
42
|
+
# run_id: RunIDOption
|
|
43
|
+
RunIDOption = Annotated[
|
|
44
|
+
str,
|
|
45
|
+
typer.Option(
|
|
46
|
+
"--run-id",
|
|
47
|
+
"-r",
|
|
48
|
+
help="The Nextmv Cloud run ID to use for this action.",
|
|
49
|
+
envvar="NEXTMV_RUN_ID",
|
|
50
|
+
metavar="RUN_ID",
|
|
51
|
+
),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# version_id option - can be used in any command that requires a version ID.
|
|
55
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
56
|
+
# version_id: VersionIDOption
|
|
57
|
+
VersionIDOption = Annotated[
|
|
58
|
+
str,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--version-id",
|
|
61
|
+
"-v",
|
|
62
|
+
help="The Nextmv Cloud version ID to use for this action.",
|
|
63
|
+
envvar="NEXTMV_VERSION_ID",
|
|
64
|
+
metavar="VERSION_ID",
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
# input_set_id option - can be used in any command that requires an input set ID.
|
|
69
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
70
|
+
# input_set_id: InputSetIDOption
|
|
71
|
+
InputSetIDOption = Annotated[
|
|
72
|
+
str,
|
|
73
|
+
typer.Option(
|
|
74
|
+
"--input-set-id",
|
|
75
|
+
"-s",
|
|
76
|
+
help="The Nextmv Cloud input set ID to use for this action.",
|
|
77
|
+
envvar="NEXTMV_INPUT_SET_ID",
|
|
78
|
+
metavar="INPUT_SET_ID",
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# instance_id option - can be used in any command that requires an instance ID.
|
|
83
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
84
|
+
# instance_id: InstanceIDOption
|
|
85
|
+
InstanceIDOption = Annotated[
|
|
86
|
+
str,
|
|
87
|
+
typer.Option(
|
|
88
|
+
"--instance-id",
|
|
89
|
+
"-i",
|
|
90
|
+
help="The Nextmv Cloud instance ID to use for this action.",
|
|
91
|
+
envvar="NEXTMV_INSTANCE_ID",
|
|
92
|
+
metavar="INSTANCE_ID",
|
|
93
|
+
),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
# managed_input_id option - can be used in any command that requires a managed input ID.
|
|
97
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
98
|
+
# managed_input_id: ManagedInputIDOption
|
|
99
|
+
ManagedInputIDOption = Annotated[
|
|
100
|
+
str,
|
|
101
|
+
typer.Option(
|
|
102
|
+
"--managed-input-id",
|
|
103
|
+
"-m",
|
|
104
|
+
help="The Nextmv Cloud managed input ID to use for this action.",
|
|
105
|
+
envvar="NEXTMV_MANAGED_INPUT_ID",
|
|
106
|
+
metavar="MANAGED_INPUT_ID",
|
|
107
|
+
),
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
# ensemble_definition_id option - can be used in any command that requires an ensemble definition ID.
|
|
111
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
112
|
+
# ensemble_definition_id: EnsembleDefinitionIDOption
|
|
113
|
+
EnsembleDefinitionIDOption = Annotated[
|
|
114
|
+
str,
|
|
115
|
+
typer.Option(
|
|
116
|
+
"--ensemble-definition-id",
|
|
117
|
+
"-e",
|
|
118
|
+
help="The Nextmv Cloud ensemble definition ID to use for this action.",
|
|
119
|
+
envvar="NEXTMV_ENSEMBLE_DEFINITION_ID",
|
|
120
|
+
metavar="ENSEMBLE_DEFINITION_ID",
|
|
121
|
+
),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
# account_id option - can be used in any command that requires an account ID.
|
|
125
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
126
|
+
# account_id: AccountIDOption
|
|
127
|
+
AccountIDOption = Annotated[
|
|
128
|
+
str,
|
|
129
|
+
typer.Option(
|
|
130
|
+
"--account-id",
|
|
131
|
+
"-a",
|
|
132
|
+
help="The Nextmv Cloud account ID to use for this action.",
|
|
133
|
+
envvar="NEXTMV_ACCOUNT_ID",
|
|
134
|
+
metavar="ACCOUNT_ID",
|
|
135
|
+
),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# acceptance_test_id option - can be used in any command that requires an acceptance test ID.
|
|
139
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
140
|
+
# acceptance_test_id: AcceptanceTestIDOption
|
|
141
|
+
AcceptanceTestIDOption = Annotated[
|
|
142
|
+
str,
|
|
143
|
+
typer.Option(
|
|
144
|
+
"--acceptance-test-id",
|
|
145
|
+
"-t",
|
|
146
|
+
help="The Nextmv Cloud acceptance test ID to use for this action.",
|
|
147
|
+
envvar="NEXTMV_ACCEPTANCE_TEST_ID",
|
|
148
|
+
metavar="ACCEPTANCE_TEST_ID",
|
|
149
|
+
),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# batch_experiment_id option - can be used in any command that requires a batch experiment ID.
|
|
153
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
154
|
+
# batch_experiment_id: BatchExperimentIDOption
|
|
155
|
+
BatchExperimentIDOption = Annotated[
|
|
156
|
+
str,
|
|
157
|
+
typer.Option(
|
|
158
|
+
"--batch-experiment-id",
|
|
159
|
+
"-b",
|
|
160
|
+
help="The Nextmv Cloud batch experiment ID to use for this action.",
|
|
161
|
+
envvar="NEXTMV_BATCH_EXPERIMENT_ID",
|
|
162
|
+
metavar="BATCH_EXPERIMENT_ID",
|
|
163
|
+
),
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
# scenario_test_id option - can be used in any command that requires a scenario test ID.
|
|
167
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
168
|
+
# scenario_test_id: ScenarioTestIDOption
|
|
169
|
+
ScenarioTestIDOption = Annotated[
|
|
170
|
+
str,
|
|
171
|
+
typer.Option(
|
|
172
|
+
"--scenario-test-id",
|
|
173
|
+
"-i",
|
|
174
|
+
help="The Nextmv Cloud scenario test ID to use for this action.",
|
|
175
|
+
envvar="NEXTMV_SCENARIO_TEST_ID",
|
|
176
|
+
metavar="SCENARIO_TEST_ID",
|
|
177
|
+
),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
# secrets_collection_id option - can be used in any command that requires a secrets collection ID.
|
|
181
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
182
|
+
# secrets_collection_id: SecretsCollectionIDOption
|
|
183
|
+
SecretsCollectionIDOption = Annotated[
|
|
184
|
+
str,
|
|
185
|
+
typer.Option(
|
|
186
|
+
"--secrets-collection-id",
|
|
187
|
+
"-s",
|
|
188
|
+
help="The Nextmv Cloud secrets collection ID to use for this action.",
|
|
189
|
+
envvar="NEXTMV_SECRETS_COLLECTION_ID",
|
|
190
|
+
metavar="SECRETS_COLLECTION_ID",
|
|
191
|
+
),
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
# shadow_test_id option - can be used in any command that requires a shadow test ID.
|
|
195
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
196
|
+
# shadow_test_id: ShadowTestIDOption
|
|
197
|
+
ShadowTestIDOption = Annotated[
|
|
198
|
+
str,
|
|
199
|
+
typer.Option(
|
|
200
|
+
"--shadow-test-id",
|
|
201
|
+
"-s",
|
|
202
|
+
help="The Nextmv Cloud shadow test ID to use for this action.",
|
|
203
|
+
envvar="NEXTMV_SHADOW_TEST_ID",
|
|
204
|
+
metavar="SHADOW_TEST_ID",
|
|
205
|
+
),
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
# switchback_test_id option - can be used in any command that requires a switchback test ID.
|
|
209
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
210
|
+
# switchback_test_id: SwitchbackTestIDOption
|
|
211
|
+
SwitchbackTestIDOption = Annotated[
|
|
212
|
+
str,
|
|
213
|
+
typer.Option(
|
|
214
|
+
"--switchback-test-id",
|
|
215
|
+
"-s",
|
|
216
|
+
help="The Nextmv Cloud switchback test ID to use for this action.",
|
|
217
|
+
envvar="NEXTMV_SWITCHBACK_TEST_ID",
|
|
218
|
+
metavar="SWITCHBACK_TEST_ID",
|
|
219
|
+
),
|
|
220
|
+
]
|