nextmv 0.39.0.dev1__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +86 -0
  110. nextmv/cli/community/list.py +200 -0
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +228 -0
  113. nextmv/cli/configuration/create.py +94 -0
  114. nextmv/cli/configuration/delete.py +67 -0
  115. nextmv/cli/configuration/list.py +77 -0
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +161 -3
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +220 -0
  120. nextmv/cli/version.py +22 -2
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cloud/application.py +0 -4204
  158. nextmv-0.39.0.dev1.dist-info/RECORD +0 -55
  159. nextmv-0.39.0.dev1.dist-info/entry_points.txt +0 -2
  160. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  161. {nextmv-0.39.0.dev1.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,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: by Nextmv.[/italic]",
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
+ ]