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/cli/community/clone.py
CHANGED
|
@@ -13,7 +13,7 @@ import rich
|
|
|
13
13
|
import typer
|
|
14
14
|
|
|
15
15
|
from nextmv.cli.community.list import download_file, download_manifest, find_app, versions_table
|
|
16
|
-
from nextmv.cli.
|
|
16
|
+
from nextmv.cli.message import error, success
|
|
17
17
|
from nextmv.cli.options import ProfileOption
|
|
18
18
|
|
|
19
19
|
# Set up subcommand application.
|
|
@@ -134,8 +134,8 @@ def clone(
|
|
|
134
134
|
# Remove the tarball after extraction
|
|
135
135
|
os.remove(downloaded_object)
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
f"
|
|
137
|
+
success(
|
|
138
|
+
f"Successfully cloned the [magenta]{app}[/magenta] community app, "
|
|
139
139
|
f"using version [magenta]{original_version}[/magenta] in path: [magenta]{full_destination}[/magenta]."
|
|
140
140
|
)
|
|
141
141
|
|
nextmv/cli/community/list.py
CHANGED
|
@@ -12,7 +12,7 @@ from rich.console import Console
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from nextmv.cli.configuration.config import build_client
|
|
15
|
-
from nextmv.cli.
|
|
15
|
+
from nextmv.cli.message import error
|
|
16
16
|
from nextmv.cli.options import ProfileOption
|
|
17
17
|
|
|
18
18
|
# Set up subcommand application.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the configuration command tree for the Nextmv CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from nextmv.cli.configuration.create import app as create_app
|
|
8
|
+
from nextmv.cli.configuration.delete import app as delete_app
|
|
9
|
+
from nextmv.cli.configuration.list import app as list_app
|
|
10
|
+
|
|
11
|
+
# Set up subcommand application.
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
app.add_typer(create_app)
|
|
14
|
+
app.add_typer(delete_app)
|
|
15
|
+
app.add_typer(list_app)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.callback()
|
|
19
|
+
def callback() -> None:
|
|
20
|
+
"""
|
|
21
|
+
Configure the CLI and manage profiles.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
@@ -7,7 +7,9 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
|
-
from nextmv.cli.
|
|
10
|
+
from nextmv.cli.message import error
|
|
11
|
+
from nextmv.cloud.account import Account
|
|
12
|
+
from nextmv.cloud.application import Application
|
|
11
13
|
from nextmv.cloud.client import Client
|
|
12
14
|
|
|
13
15
|
# Some useful constants.
|
|
@@ -89,15 +91,15 @@ def build_client(profile: str | None = None) -> Client:
|
|
|
89
91
|
|
|
90
92
|
if profile is not None:
|
|
91
93
|
if profile not in config:
|
|
92
|
-
error(f"Profile [
|
|
94
|
+
error(f"Profile [magenta]{profile}[/magenta] does not exist.")
|
|
93
95
|
|
|
94
96
|
api_key = config[profile].get(API_KEY_KEY)
|
|
95
97
|
if api_key is None or api_key == "":
|
|
96
|
-
error(f"API key for profile [
|
|
98
|
+
error(f"API key for profile [magenta]{profile}[/magenta] is not set or is empty.")
|
|
97
99
|
|
|
98
100
|
endpoint = config[profile].get(ENDPOINT_KEY)
|
|
99
101
|
if endpoint is None or endpoint == "":
|
|
100
|
-
error(f"Endpoint for profile [
|
|
102
|
+
error(f"Endpoint for profile [magenta]{profile}[/magenta] is not set or is empty.")
|
|
101
103
|
else:
|
|
102
104
|
api_key = config.get(API_KEY_KEY)
|
|
103
105
|
if api_key is None or api_key == "":
|
|
@@ -110,6 +112,68 @@ def build_client(profile: str | None = None) -> Client:
|
|
|
110
112
|
return Client(api_key=api_key, url=f"https://{endpoint}")
|
|
111
113
|
|
|
112
114
|
|
|
115
|
+
def build_app(app_id: str, profile: str | None = None) -> Application:
|
|
116
|
+
"""
|
|
117
|
+
Builds a `cloud.Application` using the given application ID and the API
|
|
118
|
+
key and endpoint for the given profile. If no profile is given, the default
|
|
119
|
+
profile is used. If the application does not exist, an exception is raised.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
app_id : str
|
|
124
|
+
The application ID.
|
|
125
|
+
profile : str | None
|
|
126
|
+
The profile name to use. If None, the default profile is used.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
Application
|
|
131
|
+
An application object for the given application ID.
|
|
132
|
+
|
|
133
|
+
Raises
|
|
134
|
+
------
|
|
135
|
+
typer.Exit
|
|
136
|
+
If the application does not exist.
|
|
137
|
+
"""
|
|
138
|
+
client = build_client(profile)
|
|
139
|
+
exists = Application.exists(client=client, id=app_id)
|
|
140
|
+
if not exists:
|
|
141
|
+
error(
|
|
142
|
+
f"Application with ID [magenta]{app_id}[/magenta] does not exist. "
|
|
143
|
+
"Use [code]nextmv cloud app create[/code] to create a new application."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return Application(client=client, id=app_id)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def build_account(account_id: str | None = None, profile: str | None = None) -> Account:
|
|
150
|
+
"""
|
|
151
|
+
Builds a `cloud.Account` using the API key and endpoint for the given
|
|
152
|
+
profile. If no profile is given, the default profile is used.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
account_id : str | None
|
|
157
|
+
The account ID. If None, no account ID is set.
|
|
158
|
+
profile : str | None
|
|
159
|
+
The profile name to use. If None, the default profile is used.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Account
|
|
164
|
+
An account object for the configured profile.
|
|
165
|
+
|
|
166
|
+
Raises
|
|
167
|
+
------
|
|
168
|
+
typer.Exit
|
|
169
|
+
If the configuration is invalid or missing.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
client = build_client(profile)
|
|
173
|
+
|
|
174
|
+
return Account(account_id=account_id, client=client)
|
|
175
|
+
|
|
176
|
+
|
|
113
177
|
def obscure_api_key(api_key: str) -> str:
|
|
114
178
|
"""
|
|
115
179
|
Obscure an API key for display purposes.
|
|
@@ -4,7 +4,6 @@ This module defines the configuration create command for the Nextmv CLI.
|
|
|
4
4
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
import rich
|
|
8
7
|
import typer
|
|
9
8
|
|
|
10
9
|
from nextmv.cli.configuration.config import (
|
|
@@ -15,7 +14,7 @@ from nextmv.cli.configuration.config import (
|
|
|
15
14
|
obscure_api_key,
|
|
16
15
|
save_config,
|
|
17
16
|
)
|
|
18
|
-
from nextmv.cli.
|
|
17
|
+
from nextmv.cli.message import error, info, success
|
|
19
18
|
|
|
20
19
|
# Set up subcommand application.
|
|
21
20
|
app = typer.Typer()
|
|
@@ -34,6 +33,14 @@ def create(
|
|
|
34
33
|
metavar="NEXTMV_API_KEY",
|
|
35
34
|
),
|
|
36
35
|
],
|
|
36
|
+
endpoint: Annotated[ # Hidden because it is meant for internal use.
|
|
37
|
+
str | None,
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--endpoint",
|
|
40
|
+
"-e",
|
|
41
|
+
hidden=True,
|
|
42
|
+
),
|
|
43
|
+
] = DEFAULT_ENDPOINT,
|
|
37
44
|
profile: Annotated[ # Similar to nextmv.cli.options.ProfileOption but with different help text.
|
|
38
45
|
str | None,
|
|
39
46
|
typer.Option(
|
|
@@ -44,14 +51,6 @@ def create(
|
|
|
44
51
|
metavar="PROFILE_NAME",
|
|
45
52
|
),
|
|
46
53
|
] = None,
|
|
47
|
-
endpoint: Annotated[ # Hidden because it is meant for internal use.
|
|
48
|
-
str | None,
|
|
49
|
-
typer.Option(
|
|
50
|
-
"--endpoint",
|
|
51
|
-
"-e",
|
|
52
|
-
hidden=True,
|
|
53
|
-
),
|
|
54
|
-
] = DEFAULT_ENDPOINT,
|
|
55
54
|
) -> None:
|
|
56
55
|
"""
|
|
57
56
|
Create a new configuration or update an existing one.
|
|
@@ -61,7 +60,7 @@ def create(
|
|
|
61
60
|
- Default configuration.
|
|
62
61
|
$ [green]nextmv configuration create --api-key NEXTMV_API_KEY[/green]
|
|
63
62
|
|
|
64
|
-
- Configure a profile named [
|
|
63
|
+
- Configure a profile named [magenta]hare[/magenta].
|
|
65
64
|
$ [green]nextmv configuration create --api-key NEXTMV_API_KEY --profile hare[/green]
|
|
66
65
|
"""
|
|
67
66
|
|
|
@@ -88,8 +87,8 @@ def create(
|
|
|
88
87
|
|
|
89
88
|
save_config(config)
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
success("Configuration saved successfully.")
|
|
91
|
+
info(f"\t[bold]Profile[/bold]: {profile or 'Default'}")
|
|
92
|
+
info(f"\t[bold]API Key[/bold]: {obscure_api_key(api_key)}")
|
|
94
93
|
if endpoint != DEFAULT_ENDPOINT:
|
|
95
|
-
|
|
94
|
+
info(f"\t[bold]Endpoint[/bold]: {endpoint}")
|
|
@@ -4,12 +4,11 @@ This module defines the configuration delete command for the Nextmv CLI.
|
|
|
4
4
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
import rich
|
|
8
7
|
import typer
|
|
9
8
|
from rich.prompt import Confirm
|
|
10
9
|
|
|
11
10
|
from nextmv.cli.configuration.config import load_config, save_config
|
|
12
|
-
from nextmv.cli.
|
|
11
|
+
from nextmv.cli.message import error, info, success
|
|
13
12
|
|
|
14
13
|
# Set up subcommand application.
|
|
15
14
|
app = typer.Typer()
|
|
@@ -27,29 +26,42 @@ def delete(
|
|
|
27
26
|
metavar="PROFILE_NAME",
|
|
28
27
|
),
|
|
29
28
|
],
|
|
29
|
+
yes: Annotated[
|
|
30
|
+
bool,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--yes",
|
|
33
|
+
"-y",
|
|
34
|
+
help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
|
|
35
|
+
),
|
|
36
|
+
] = False,
|
|
30
37
|
) -> None:
|
|
31
38
|
"""
|
|
32
|
-
Delete a profile from the configuration.
|
|
39
|
+
Delete a profile from the configuration. Use the [code]--yes[/code]
|
|
40
|
+
flag to skip the confirmation prompt.
|
|
33
41
|
|
|
34
42
|
[bold][underline]Examples[/underline][/bold]
|
|
35
43
|
|
|
36
44
|
- Delete a profile named [magenta]hare[/magenta].
|
|
37
45
|
$ [green]nextmv configuration delete --profile hare[/green]
|
|
46
|
+
|
|
47
|
+
- Delete a profile named [magenta]hare[/magenta] without confirmation prompt.
|
|
48
|
+
$ [green]nextmv configuration delete --profile hare --yes[/green]
|
|
38
49
|
"""
|
|
39
50
|
config = load_config()
|
|
40
51
|
if profile not in config:
|
|
41
|
-
error(f"Profile [
|
|
52
|
+
error(f"Profile [magenta]{profile}[/magenta] does not exist.")
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
if not yes:
|
|
55
|
+
confirm = Confirm.ask(
|
|
56
|
+
f"Are you sure you want to delete profile [magenta]{profile}[/magenta]? This action cannot be undone.",
|
|
57
|
+
default=False,
|
|
58
|
+
)
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
if not confirm:
|
|
61
|
+
info(msg=f"Profile [magenta]{profile}[/magenta] will not be deleted.", emoji=":bulb:")
|
|
62
|
+
return
|
|
51
63
|
|
|
52
64
|
del config[profile]
|
|
53
65
|
save_config(config)
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
success(f"Profile [magenta]{profile}[/magenta] deleted successfully.")
|
nextmv/cli/configuration/list.py
CHANGED
|
@@ -7,7 +7,7 @@ from rich.console import Console
|
|
|
7
7
|
from rich.table import Table
|
|
8
8
|
|
|
9
9
|
from nextmv.cli.configuration.config import API_KEY_KEY, ENDPOINT_KEY, load_config, obscure_api_key
|
|
10
|
-
from nextmv.cli.
|
|
10
|
+
from nextmv.cli.message import error
|
|
11
11
|
|
|
12
12
|
# Set up subcommand application.
|
|
13
13
|
app = typer.Typer()
|
nextmv/cli/main.py
CHANGED
|
@@ -14,16 +14,20 @@ epilog of the Typer application defined below.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import os
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Annotated
|
|
17
19
|
|
|
18
20
|
import rich
|
|
19
21
|
import typer
|
|
20
22
|
from rich.prompt import Confirm
|
|
21
23
|
|
|
22
|
-
from nextmv.cli.
|
|
24
|
+
from nextmv.cli.cloud import app as cloud_app
|
|
25
|
+
from nextmv.cli.community import app as community_app
|
|
26
|
+
from nextmv.cli.configuration import app as configuration_app
|
|
23
27
|
from nextmv.cli.configuration.config import CONFIG_DIR, GO_CLI_PATH, load_config
|
|
24
|
-
from nextmv.cli.
|
|
25
|
-
from nextmv.cli.error import error
|
|
28
|
+
from nextmv.cli.message import error, info, success, warning
|
|
26
29
|
from nextmv.cli.version import app as version_app
|
|
30
|
+
from nextmv.cli.version import version_callback
|
|
27
31
|
|
|
28
32
|
# Main CLI application.
|
|
29
33
|
app = typer.Typer(
|
|
@@ -32,17 +36,31 @@ app = typer.Typer(
|
|
|
32
36
|
rich_markup_mode="rich",
|
|
33
37
|
context_settings={"help_option_names": ["--help", "-h"]},
|
|
34
38
|
no_args_is_help=True,
|
|
39
|
+
invoke_without_command=True,
|
|
40
|
+
pretty_exceptions_show_locals=False,
|
|
35
41
|
)
|
|
36
42
|
|
|
37
43
|
# Register subcommands. The `name` parameter is required when the subcommand
|
|
38
44
|
# module has a callback function defined.
|
|
45
|
+
app.add_typer(cloud_app, name="cloud")
|
|
39
46
|
app.add_typer(community_app, name="community")
|
|
40
47
|
app.add_typer(configuration_app, name="configuration")
|
|
41
48
|
app.add_typer(version_app)
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
@app.callback()
|
|
45
|
-
def callback(
|
|
52
|
+
def callback(
|
|
53
|
+
ctx: typer.Context,
|
|
54
|
+
version: Annotated[
|
|
55
|
+
bool | None,
|
|
56
|
+
typer.Option(
|
|
57
|
+
"--version",
|
|
58
|
+
"-v",
|
|
59
|
+
help="Show the current version of the Nextmv CLI.",
|
|
60
|
+
callback=version_callback,
|
|
61
|
+
),
|
|
62
|
+
] = None,
|
|
63
|
+
) -> None:
|
|
46
64
|
"""
|
|
47
65
|
Callback function that runs before any command. Useful for checks on the
|
|
48
66
|
environment.
|
|
@@ -64,15 +82,16 @@ def handle_go_cli() -> None:
|
|
|
64
82
|
if exists:
|
|
65
83
|
delete = Confirm.ask(
|
|
66
84
|
"Do you want to delete the [italic red]deprecated[/italic red] Nextmv CLI "
|
|
67
|
-
f"at [
|
|
68
|
-
default=
|
|
85
|
+
f"at [magenta]{GO_CLI_PATH}[/magenta] now?",
|
|
86
|
+
default=False,
|
|
69
87
|
)
|
|
70
88
|
if delete:
|
|
71
89
|
remove_go_cli()
|
|
72
90
|
else:
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
f"
|
|
91
|
+
info(
|
|
92
|
+
msg="You can delete the [italic red]deprecated[/italic red] Nextmv CLI later by removing "
|
|
93
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. Make sure you also clean up your [code]PATH[/code].",
|
|
94
|
+
emoji=":bulb:",
|
|
76
95
|
)
|
|
77
96
|
|
|
78
97
|
|
|
@@ -109,9 +128,9 @@ def go_cli_exists() -> bool:
|
|
|
109
128
|
# Check if the Go CLI executable exists
|
|
110
129
|
exists = GO_CLI_PATH.exists()
|
|
111
130
|
if exists:
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
f"[
|
|
131
|
+
warning(
|
|
132
|
+
"A [italic red]deprecated[/italic red] Nextmv CLI is installed at "
|
|
133
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. You must delete it to avoid conflicts."
|
|
115
134
|
)
|
|
116
135
|
|
|
117
136
|
check_config_in_path()
|
|
@@ -126,7 +145,7 @@ def remove_go_cli() -> None:
|
|
|
126
145
|
|
|
127
146
|
if GO_CLI_PATH.exists():
|
|
128
147
|
GO_CLI_PATH.unlink()
|
|
129
|
-
|
|
148
|
+
success(f"Deleted deprecated [magenta]{GO_CLI_PATH}[/magenta].")
|
|
130
149
|
|
|
131
150
|
check_config_in_path()
|
|
132
151
|
|
|
@@ -140,7 +159,30 @@ def check_config_in_path() -> None:
|
|
|
140
159
|
config_dir_str = str(CONFIG_DIR)
|
|
141
160
|
|
|
142
161
|
if config_dir_str in path_dirs:
|
|
143
|
-
|
|
144
|
-
f"
|
|
145
|
-
f"You should remove any entries related to [
|
|
162
|
+
warning(
|
|
163
|
+
f"[magenta]{CONFIG_DIR}[/magenta] was found in your [code]PATH[/code]. "
|
|
164
|
+
f"You should remove any entries related to [magenta]{CONFIG_DIR}[/magenta] from your [code]PATH[/code]."
|
|
146
165
|
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def main() -> None:
|
|
169
|
+
"""
|
|
170
|
+
Entry point for the CLI with global exception handling.
|
|
171
|
+
|
|
172
|
+
Catches all exceptions except Typer/Click exceptions (which handle their
|
|
173
|
+
own exit codes) and displays a clean error message instead of a traceback.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
app()
|
|
178
|
+
except (typer.Exit, typer.Abort, SystemExit):
|
|
179
|
+
raise
|
|
180
|
+
except Exception as e:
|
|
181
|
+
# We do not use the messages.error function here because doing so would
|
|
182
|
+
# raise a Typer exception, which would print a traceback.
|
|
183
|
+
msg = str(e).rstrip("\n")
|
|
184
|
+
if not msg.endswith("."):
|
|
185
|
+
msg += "."
|
|
186
|
+
|
|
187
|
+
rich.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
|
|
188
|
+
sys.exit(1)
|
nextmv/cli/message.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
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 error(msg: str) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Pretty-print an error message and exit with code 1. Your message should end
|
|
17
|
+
with a period.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
msg : str
|
|
22
|
+
The error message to display.
|
|
23
|
+
|
|
24
|
+
Raises
|
|
25
|
+
------
|
|
26
|
+
typer.Exit
|
|
27
|
+
Exits the program with code 1.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
msg = msg.rstrip("\n")
|
|
31
|
+
if not msg.endswith("."):
|
|
32
|
+
msg += "."
|
|
33
|
+
|
|
34
|
+
rich.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
|
|
35
|
+
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def success(msg: str) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Pretty-print a success message. Your message should end with a period.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
msg : str
|
|
46
|
+
The success message to display.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
msg = msg.rstrip("\n")
|
|
50
|
+
if not msg.endswith("."):
|
|
51
|
+
msg += "."
|
|
52
|
+
|
|
53
|
+
rich.print(f":white_check_mark: {msg}", file=sys.stderr)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def warning(msg: str) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Pretty-print a warning message. Your message should end with a period.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
msg : str
|
|
63
|
+
The warning message to display.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
msg = msg.rstrip("\n")
|
|
67
|
+
if not msg.endswith("."):
|
|
68
|
+
msg += "."
|
|
69
|
+
|
|
70
|
+
rich.print(f":construction: {msg}", file=sys.stderr)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def info(msg: str, emoji: str | None = None) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Pretty-print an informational message. Your message should end with a
|
|
76
|
+
period. The use of emojis is encouraged to give context to the message. An
|
|
77
|
+
emoji should be a string as specified in:
|
|
78
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
msg : str
|
|
83
|
+
The informational message to display.
|
|
84
|
+
emoji : str | None
|
|
85
|
+
An optional emoji to prefix the message. If None, no emoji is used. The
|
|
86
|
+
emoji should be a string as specified in:
|
|
87
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji. For example:
|
|
88
|
+
`:hourglass_flowing_sand:`.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
msg = msg.rstrip("\n")
|
|
92
|
+
if not msg.endswith("."):
|
|
93
|
+
msg += "."
|
|
94
|
+
|
|
95
|
+
if emoji:
|
|
96
|
+
rich.print(f"{emoji} {msg}", file=sys.stderr)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
rich.print(msg, file=sys.stderr)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def in_progress(msg: str) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Pretty-print an in-progress message with an hourglass emoji. Your message
|
|
105
|
+
should end with a period.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
msg : str
|
|
110
|
+
The in-progress message to display.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
info(msg, emoji=":hourglass_flowing_sand:")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def print_json(data: dict[str, Any] | list[dict[str, Any]]) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Pretty-print json-serializable data as JSON to stdout.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
data : dict[str, Any] | list[dict[str, Any]]
|
|
123
|
+
The data to print as JSON.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
rich.print_json(data=data)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def enum_values(enum_class: Enum) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Get a nicely formatted string of the values of an Enum class, using commas
|
|
132
|
+
and an oxford comma.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
enum_class : Enum
|
|
137
|
+
The Enum class to get the values from.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
str
|
|
142
|
+
A nicely formatted string of the values of the Enum class.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
values = [f"[magenta]{member.value}[/magenta]" for member in enum_class]
|
|
146
|
+
if len(values) == 0:
|
|
147
|
+
return ""
|
|
148
|
+
if len(values) == 1:
|
|
149
|
+
return values[0]
|
|
150
|
+
if len(values) == 2:
|
|
151
|
+
return " and ".join(values)
|
|
152
|
+
|
|
153
|
+
return ", ".join(values[:-1]) + ", and " + values[-1]
|