nextmv 1.0.0.dev8__py3-none-any.whl → 1.0.0.dev10__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 (54) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/_serialization.py +1 -1
  3. nextmv/cli/CONTRIBUTING.md +31 -11
  4. nextmv/cli/cloud/acceptance/create.py +12 -12
  5. nextmv/cli/cloud/acceptance/delete.py +1 -4
  6. nextmv/cli/cloud/account/delete.py +1 -1
  7. nextmv/cli/cloud/app/delete.py +1 -1
  8. nextmv/cli/cloud/app/push.py +23 -42
  9. nextmv/cli/cloud/batch/delete.py +1 -4
  10. nextmv/cli/cloud/ensemble/delete.py +1 -4
  11. nextmv/cli/cloud/input_set/__init__.py +2 -0
  12. nextmv/cli/cloud/input_set/delete.py +64 -0
  13. nextmv/cli/cloud/instance/delete.py +1 -1
  14. nextmv/cli/cloud/managed_input/delete.py +1 -1
  15. nextmv/cli/cloud/run/create.py +4 -9
  16. nextmv/cli/cloud/scenario/delete.py +1 -4
  17. nextmv/cli/cloud/secrets/delete.py +1 -4
  18. nextmv/cli/cloud/shadow/delete.py +1 -4
  19. nextmv/cli/cloud/shadow/stop.py +14 -2
  20. nextmv/cli/cloud/switchback/delete.py +1 -4
  21. nextmv/cli/cloud/switchback/stop.py +14 -2
  22. nextmv/cli/cloud/version/delete.py +1 -1
  23. nextmv/cli/community/clone.py +11 -197
  24. nextmv/cli/community/list.py +51 -116
  25. nextmv/cli/configuration/create.py +4 -4
  26. nextmv/cli/configuration/delete.py +1 -1
  27. nextmv/cli/main.py +2 -3
  28. nextmv/cli/message.py +71 -54
  29. nextmv/cloud/__init__.py +4 -0
  30. nextmv/cloud/application/__init__.py +1 -200
  31. nextmv/cloud/application/_acceptance.py +13 -8
  32. nextmv/cloud/application/_input_set.py +42 -6
  33. nextmv/cloud/application/_run.py +1 -8
  34. nextmv/cloud/application/_shadow.py +9 -3
  35. nextmv/cloud/application/_switchback.py +11 -2
  36. nextmv/cloud/batch_experiment.py +3 -1
  37. nextmv/cloud/client.py +1 -1
  38. nextmv/cloud/community.py +446 -0
  39. nextmv/cloud/integration.py +7 -4
  40. nextmv/cloud/shadow.py +25 -0
  41. nextmv/cloud/switchback.py +2 -0
  42. nextmv/default_app/main.py +6 -4
  43. nextmv/local/executor.py +3 -83
  44. nextmv/local/geojson_handler.py +1 -1
  45. nextmv/manifest.py +7 -11
  46. nextmv/model.py +52 -13
  47. nextmv/options.py +1 -1
  48. nextmv/output.py +21 -57
  49. nextmv/run.py +3 -12
  50. {nextmv-1.0.0.dev8.dist-info → nextmv-1.0.0.dev10.dist-info}/METADATA +5 -4
  51. {nextmv-1.0.0.dev8.dist-info → nextmv-1.0.0.dev10.dist-info}/RECORD +54 -52
  52. {nextmv-1.0.0.dev8.dist-info → nextmv-1.0.0.dev10.dist-info}/WHEEL +0 -0
  53. {nextmv-1.0.0.dev8.dist-info → nextmv-1.0.0.dev10.dist-info}/entry_points.txt +0 -0
  54. {nextmv-1.0.0.dev8.dist-info → nextmv-1.0.0.dev10.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "v1.0.0.dev8"
1
+ __version__ = "v1.0.0.dev10"
nextmv/_serialization.py CHANGED
@@ -90,7 +90,7 @@ def _custom_serial(obj: Any) -> str:
90
90
  If the object type is not supported for serialization.
91
91
  """
92
92
 
93
- if isinstance(obj, datetime.datetime | datetime.date):
93
+ if isinstance(obj, (datetime.datetime, datetime.date)):
94
94
  return obj.isoformat()
95
95
 
96
96
  raise TypeError(f"Type {type(obj)} not serializable")
@@ -152,14 +152,16 @@ guidelines:
152
152
  - We embrace the use of emojis. They make the CLI friendlier and more
153
153
  approachable.
154
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.
155
+ - `message`: prints a message. You can give it an emoji for the message. The
156
+ other commands have fixed emojis.
157
+ - `info`: prints an informational message. Use for neutral messages.
158
+ - `in_progress`: prints an in-progress message. Use before executing an action.
159
+ - `success`: prints a success message. Use after successfully completing an action.
160
+ - `warning`: prints a warning message. Use for non-critical issues.
161
+ - `error`: prints an error and raises an exception. Use for critical issues
162
+ and to return early from commands.
161
163
  - For printing `JSON` information, use the `print_json` function in the
162
- `messages.py` file to print JSON output. This ensures consistent formatting
164
+ `message.py` file to print JSON output. This ensures consistent formatting
163
165
  across the CLI.
164
166
  - Emojis should be formatted according to [Rich's emoji guide][rich-emoji].
165
167
  They are strings enclosed in colons, e.g. `:rocket:`, `:boom:`,
@@ -171,6 +173,24 @@ guidelines:
171
173
  success(f"Application [magenta]{app_id}[/magenta] deleted successfully.")
172
174
  ```
173
175
 
176
+ - When showing the values of an `Enum`, use the `enum_values` function in the
177
+ `message.py` file which will give a nicely colored, comma-separated list of
178
+ the enum values. Consider the following example, where we get the allowed
179
+ values for the `InputFormat` class.
180
+
181
+ ```python
182
+ content_format: Annotated[
183
+ InputFormat | None,
184
+ typer.Option(
185
+ "--content-format",
186
+ "-c",
187
+ help=f"The content format for the instance. Allowed values are: {enum_values(InputFormat)}.",
188
+ metavar="CONTENT_FORMAT",
189
+ rich_help_panel="Instance configuration",
190
+ ),
191
+ ] = None,
192
+ ```
193
+
174
194
  ## Confirmation prompts
175
195
 
176
196
  For destructive actions (like deletions), use the `get_confirmation()` method
@@ -185,7 +205,7 @@ When using confirmation prompts, follow these guidelines:
185
205
  affected.
186
206
  - Provide a `--yes` / `-y` flag to skip the confirmation prompt where possible,
187
207
  useful for non-interactive sessions.
188
- - If the user declines, call `info()` with the `:bulb:` emoji and return early.
208
+ - If the user declines, call `info()` and return early.
189
209
 
190
210
  Consider the `nextmv cloud app delete` command:
191
211
 
@@ -196,7 +216,7 @@ if not yes:
196
216
  )
197
217
 
198
218
  if not confirm:
199
- info(msg=f"Application [magenta]{app_id}[/magenta] will not be deleted.", emoji=":bulb:")
219
+ info(f"Application [magenta]{app_id}[/magenta] will not be deleted.")
200
220
  return
201
221
  ```
202
222
 
@@ -377,7 +397,7 @@ to use it.
377
397
  """
378
398
 
379
399
  client = build_client(profile)
380
- info(msg="Getting application...", emoji=":hourglass_flowing_sand:")
400
+ in_progress("Getting application...")
381
401
 
382
402
  cloud_app = Application.get(
383
403
  client=client,
@@ -389,7 +409,7 @@ to use it.
389
409
  with open(output, "w") as f:
390
410
  json.dump(cloud_app_dict, f, indent=2)
391
411
 
392
- success(msg=f"Application information saved to [magenta]{output}[/magenta].")
412
+ success(f"Application information saved to [magenta]{output}[/magenta].")
393
413
 
394
414
  return
395
415
 
@@ -45,7 +45,7 @@ app = typer.Typer()
45
45
  - Multiple metrics as a [magenta]json[/magenta] array in a single --metrics flag.
46
46
 
47
47
  Each metric must have the following fields:
48
- - [magenta]field[/magenta]: Field of the metric to measure (e.g., "result.custom.unassigned").
48
+ - [magenta]field[/magenta]: Field of the metric to measure (e.g., "solution.objective").
49
49
  - [magenta]metric_type[/magenta]: Type of metric comparison. Allowed values: {enum_values(MetricType)}.
50
50
  - [magenta]params[/magenta]: Parameters of the metric comparison.
51
51
  - [magenta]operator[/magenta]: Comparison operator. Allowed values: {enum_values(Comparison)}.
@@ -71,8 +71,8 @@ app = typer.Typer()
71
71
  [bold][underline]Examples[/underline][/bold]
72
72
 
73
73
  - Create an acceptance test with a single metric.
74
- $ [green]METRIC='{{
75
- "field": "result.custom.unassigned",
74
+ $ [dim]METRIC='{{
75
+ "field": "solution.objective",
76
76
  "metric_type": "direct-comparison",
77
77
  "params": {{
78
78
  "operator": "lt",
@@ -85,8 +85,8 @@ app = typer.Typer()
85
85
  --metrics "$METRIC" --input-set-id input-set-123[/dim]
86
86
 
87
87
  - Create with multiple metrics by repeating the flag.
88
- $ [green]METRIC1='{{
89
- "field": "result.custom.unassigned",
88
+ $ [dim]METRIC1='{{
89
+ "field": "solution.objective",
90
90
  "metric_type": "direct-comparison",
91
91
  "params": {{
92
92
  "operator": "lt",
@@ -95,7 +95,7 @@ app = typer.Typer()
95
95
  "statistic": "mean"
96
96
  }}'
97
97
  METRIC2='{{
98
- "field": "run.duration",
98
+ "field": "statistics.run.duration",
99
99
  "metric_type": "direct-comparison",
100
100
  "params": {{
101
101
  "operator": "le",
@@ -110,7 +110,7 @@ app = typer.Typer()
110
110
  - Create with multiple metrics in a single [magenta]json[/magenta] array.
111
111
  $ [dim]METRICS='[
112
112
  {{
113
- "field": "result.custom.unassigned",
113
+ "field": "solution.objective",
114
114
  "metric_type": "direct-comparison",
115
115
  "params": {{
116
116
  "operator": "lt",
@@ -119,7 +119,7 @@ app = typer.Typer()
119
119
  "statistic": "mean"
120
120
  }},
121
121
  {{
122
- "field": "run.duration",
122
+ "field": "statistics.run.duration",
123
123
  "metric_type": "direct-comparison",
124
124
  "params": {{
125
125
  "operator": "le",
@@ -133,8 +133,8 @@ app = typer.Typer()
133
133
  --metrics "$METRICS" --input-set-id input-set-123[/dim]
134
134
 
135
135
  - Create an acceptance test and wait for it to complete.
136
- $ [green]METRIC='{{
137
- "field": "result.custom.unassigned",
136
+ $ [dim]METRIC='{{
137
+ "field": "solution.objective",
138
138
  "metric_type": "direct-comparison",
139
139
  "params": {{
140
140
  "operator": "lt",
@@ -147,8 +147,8 @@ app = typer.Typer()
147
147
  --metrics "$METRIC" --input-set-id input-set-123 --wait[/dim]
148
148
 
149
149
  - Create an acceptance test and save the results to a file, waiting for completion.
150
- $ [green]METRIC='{{
151
- "field": "result.custom.unassigned",
150
+ $ [dim]METRIC='{{
151
+ "field": "solution.objective",
152
152
  "metric_type": "direct-comparison",
153
153
  "params": {{
154
154
  "operator": "lt",
@@ -53,10 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(
57
- msg=f"Acceptance test [magenta]{acceptance_test_id}[/magenta] will not be deleted.",
58
- emoji=":bulb:",
59
- )
56
+ info(f"Acceptance test [magenta]{acceptance_test_id}[/magenta] will not be deleted.")
60
57
  return
61
58
 
62
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -51,7 +51,7 @@ def delete(
51
51
  )
52
52
 
53
53
  if not confirm:
54
- info(msg=f"Account [magenta]{account_id}[/magenta] will not be deleted.", emoji=":bulb:")
54
+ info(f"Account [magenta]{account_id}[/magenta] will not be deleted.")
55
55
  return
56
56
 
57
57
  cloud_account = build_account(account_id=account_id, profile=profile)
@@ -49,7 +49,7 @@ def delete(
49
49
  )
50
50
 
51
51
  if not confirm:
52
- info(msg=f"Application [magenta]{app_id}[/magenta] will not be deleted.", emoji=":bulb:")
52
+ info(f"Application [magenta]{app_id}[/magenta] will not be deleted.")
53
53
  return
54
54
 
55
55
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -129,26 +129,34 @@ def push(
129
129
  $ [dim]nextmv cloud app push --app-id hare-app --version-yes --update-instance-id inst-1[/dim]
130
130
  """
131
131
 
132
+ cloud_app = build_app(app_id=app_id, profile=profile)
133
+
134
+ # If a version already exists, we cannot create it.
135
+ if version_id is not None and version_id != "":
136
+ exists = cloud_app.version_exists(version_id=version_id)
137
+ if exists:
138
+ error(
139
+ f"Version [magenta]{version_id}[/magenta] already exists for application [magenta]{app_id}[/magenta]."
140
+ )
141
+
132
142
  # We cannot create and update an instance at the same time.
133
143
  update_defined = update_instance_id is not None and update_instance_id != ""
134
144
  create_defined = create_instance_id is not None and create_instance_id != ""
135
145
  if update_defined and create_defined:
136
146
  error("Cannot use --update-instance-id and --create-instance-id at the same time.")
137
147
 
138
- cloud_app = build_app(app_id=app_id, profile=profile)
139
-
140
148
  # We cannot update an instance that does not exist.
141
149
  if update_defined and not cloud_app.instance_exists(instance_id=update_instance_id):
142
150
  error(
143
- f"Used option --update-instance-id but the instance {update_instance_id} does not exist. "
144
- "Use --create-instance-id instead."
151
+ f"Used option --update-instance-id but the instance [magenta]{update_instance_id}[/magenta] "
152
+ "does not exist. Use --create-instance-id instead."
145
153
  )
146
154
 
147
155
  # We cannot create an instance that already exists.
148
156
  if create_defined and cloud_app.instance_exists(instance_id=create_instance_id):
149
157
  error(
150
- f"Used option --create-instance-id but the instance {create_instance_id} already exists. "
151
- "Use --update-instance-id instead."
158
+ f"Used option --create-instance-id but the instance [magenta]{create_instance_id}[/magenta] "
159
+ "already exists. Use --update-instance-id instead."
152
160
  )
153
161
 
154
162
  # Do the normal push first.
@@ -174,7 +182,7 @@ def push(
174
182
  # If the override for updating an instance was used, we update the instance
175
183
  # and we are done.
176
184
  if update_defined:
177
- info("Used option --update-instance-id to link version to existing instance.", emoji=":bulb:")
185
+ info("Used option --update-instance-id to link version to existing instance.")
178
186
  _update_instance(
179
187
  cloud_app=cloud_app,
180
188
  app_id=app_id,
@@ -187,7 +195,7 @@ def push(
187
195
  # If the override for creating a new instance was used, we create the
188
196
  # instance and we are done.
189
197
  if create_defined:
190
- info("Used option --create-instance-id to link version to new instance.", emoji=":bulb:")
198
+ info("Used option --create-instance-id to link version to new instance.")
191
199
  _create_instance(
192
200
  cloud_app=cloud_app,
193
201
  app_id=app_id,
@@ -243,34 +251,19 @@ def _handle_version_creation(
243
251
  # If the user provides a version, and it exists, we use it directly and we
244
252
  # are done.
245
253
  if version_id is not None and version_id != "":
246
- exists = cloud_app.version_exists(version_id=version_id)
247
- if exists:
248
- error(
249
- f"Version [magenta]{version_id}[/magenta] already exists for application [magenta]{app_id}[/magenta]."
250
- )
251
-
252
- return "", False
253
-
254
- info(
255
- msg=f"Version [magenta]{version_id}[/magenta] does not exist. A new version will be created.",
256
- emoji=":bulb:",
257
- )
258
-
254
+ info(f"Version [magenta]{version_id}[/magenta] does not exist. A new version will be created.")
259
255
  version_yes = True # Activate auto-confirm since user provided a version ID.
260
256
 
261
257
  # If we are not auto-confirming version creation, ask the user.
262
258
  if not version_yes:
263
259
  should_create = get_confirmation(
264
- msg=f"Do you want to create a new version [magenta]{app_id}[/magenta] now?",
260
+ msg=f"Do you want to create a new version for application [magenta]{app_id}[/magenta] now?",
265
261
  default=True,
266
262
  )
267
263
 
268
264
  # If the user does not want to create a new version, we are done.
269
265
  if not should_create:
270
- info(
271
- msg="Will not create a new version.",
272
- emoji=":bulb:",
273
- )
266
+ info("Will not create a new version.")
274
267
  return "", False
275
268
 
276
269
  # Create a new version if either the user confirms by prompt or by using
@@ -314,10 +307,7 @@ def _handle_instance_prompting(
314
307
  # If this is not an interactive terminal, do not ask for instance linking,
315
308
  # to avoid hanging indefinitely waiting for a user response.
316
309
  if not sys.stdin.isatty():
317
- info(
318
- msg="Non-interactive terminal detected. Skipping instance linking.",
319
- emoji=":bulb:",
320
- )
310
+ info("Non-interactive terminal detected. Skipping instance linking.")
321
311
 
322
312
  return
323
313
 
@@ -328,10 +318,7 @@ def _handle_instance_prompting(
328
318
  case_sensitive=False,
329
319
  )
330
320
  if instance_id == "":
331
- info(
332
- msg="No instance ID provided. Skipping instance linking.",
333
- emoji=":bulb:",
334
- )
321
+ info("No instance ID provided. Skipping instance linking.")
335
322
  return
336
323
 
337
324
  # Based on whether the instance exists or not, ask the user if they want to
@@ -347,10 +334,7 @@ def _handle_instance_prompting(
347
334
  )
348
335
 
349
336
  if not should_update:
350
- info(
351
- msg=f"Will not update instance [magenta]{instance_id}[/magenta].",
352
- emoji=":bulb:",
353
- )
337
+ info(f"Will not update instance [magenta]{instance_id}[/magenta].")
354
338
  return
355
339
 
356
340
  _update_instance(
@@ -370,10 +354,7 @@ def _handle_instance_prompting(
370
354
  )
371
355
 
372
356
  if not should_create:
373
- info(
374
- msg=f"Will not create instance [magenta]{instance_id}[/magenta].",
375
- emoji=":bulb:",
376
- )
357
+ info(f"Will not create instance [magenta]{instance_id}[/magenta].")
377
358
  return
378
359
 
379
360
  _create_instance(
@@ -53,10 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(
57
- msg=f"Batch experiment [magenta]{batch_experiment_id}[/magenta] will not be deleted.",
58
- emoji=":bulb:",
59
- )
56
+ info(f"Batch experiment [magenta]{batch_experiment_id}[/magenta] will not be deleted.")
60
57
  return
61
58
 
62
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -52,10 +52,7 @@ def delete(
52
52
  )
53
53
 
54
54
  if not confirm:
55
- info(
56
- msg=f"Ensemble definition [magenta]{ensemble_definition_id}[/magenta] will not be deleted.",
57
- emoji=":bulb:",
58
- )
55
+ info(f"Ensemble definition [magenta]{ensemble_definition_id}[/magenta] will not be deleted.")
59
56
  return
60
57
 
61
58
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -5,6 +5,7 @@ This module defines the cloud input-set command tree for the Nextmv CLI.
5
5
  import typer
6
6
 
7
7
  from nextmv.cli.cloud.input_set.create import app as create_app
8
+ from nextmv.cli.cloud.input_set.delete import app as delete_app
8
9
  from nextmv.cli.cloud.input_set.get import app as get_app
9
10
  from nextmv.cli.cloud.input_set.list import app as list_app
10
11
  from nextmv.cli.cloud.input_set.update import app as update_app
@@ -12,6 +13,7 @@ from nextmv.cli.cloud.input_set.update import app as update_app
12
13
  # Set up subcommand application.
13
14
  app = typer.Typer()
14
15
  app.add_typer(create_app)
16
+ app.add_typer(delete_app)
15
17
  app.add_typer(get_app)
16
18
  app.add_typer(list_app)
17
19
  app.add_typer(update_app)
@@ -0,0 +1,64 @@
1
+ """
2
+ This module defines the cloud input-set delete command for the Nextmv CLI.
3
+ """
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from nextmv.cli.configuration.config import build_app
10
+ from nextmv.cli.confirm import get_confirmation
11
+ from nextmv.cli.message import info, success
12
+ from nextmv.cli.options import AppIDOption, InputSetIDOption, ProfileOption
13
+
14
+ # Set up subcommand application.
15
+ app = typer.Typer()
16
+
17
+
18
+ @app.command()
19
+ def delete(
20
+ app_id: AppIDOption,
21
+ input_set_id: InputSetIDOption,
22
+ yes: Annotated[
23
+ bool,
24
+ typer.Option(
25
+ "--yes",
26
+ "-y",
27
+ help="Agree to deletion confirmation prompt. Useful for non-interactive sessions.",
28
+ ),
29
+ ] = False,
30
+ profile: ProfileOption = None,
31
+ ) -> None:
32
+ """
33
+ Deletes a Nextmv Cloud input set.
34
+
35
+ This action is permanent and cannot be undone. The input set and all
36
+ associated data will be deleted. Use the --yes flag to skip the
37
+ confirmation prompt.
38
+
39
+ [bold][underline]Examples[/underline][/bold]
40
+
41
+ - Delete the input set with the ID [magenta]hop-analysis[/magenta] from application
42
+ [magenta]hare-app[/magenta].
43
+ $ [dim]nextmv cloud input-set delete --app-id hare-app --input-set-id hop-analysis[/dim]
44
+
45
+ - Delete the input set without confirmation prompt.
46
+ $ [dim]nextmv cloud input-set delete --app-id hare-app --input-set-id carrot-routes --yes[/dim]
47
+ """
48
+
49
+ if not yes:
50
+ confirm = get_confirmation(
51
+ f"Are you sure you want to delete input set [magenta]{input_set_id}[/magenta] "
52
+ f"from application [magenta]{app_id}[/magenta]? This action cannot be undone.",
53
+ )
54
+
55
+ if not confirm:
56
+ info(f"Input set [magenta]{input_set_id}[/magenta] will not be deleted.")
57
+ return
58
+
59
+ cloud_app = build_app(app_id=app_id, profile=profile)
60
+ cloud_app.delete_input_set(input_set_id=input_set_id)
61
+ success(
62
+ f"Input set [magenta]{input_set_id}[/magenta] deleted successfully "
63
+ f"from application [magenta]{app_id}[/magenta]."
64
+ )
@@ -51,7 +51,7 @@ def delete(
51
51
  )
52
52
 
53
53
  if not confirm:
54
- info(msg=f"Instance [magenta]{instance_id}[/magenta] will not be deleted.", emoji=":bulb:")
54
+ info(f"Instance [magenta]{instance_id}[/magenta] will not be deleted.")
55
55
  return
56
56
 
57
57
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -53,7 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(msg=f"Managed input [magenta]{managed_input_id}[/magenta] will not be deleted.", emoji=":bulb:")
56
+ info(f"Managed input [magenta]{managed_input_id}[/magenta] will not be deleted.")
57
57
  return
58
58
 
59
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -129,7 +129,7 @@ def create(
129
129
  metavar="INSTANCE_ID",
130
130
  rich_help_panel="Run configuration",
131
131
  ),
132
- ] = "latest",
132
+ ] = None,
133
133
  integration_id: Annotated[
134
134
  str | None,
135
135
  typer.Option(
@@ -240,12 +240,11 @@ def create(
240
240
  specify the instance with the --instance-id flag. These are the possible
241
241
  values for this flag:
242
242
 
243
+ - [yellow]unspecified[/yellow]: Run against the default instance of the
244
+ application. When an application is created, the default instance is [magenta]latest[/magenta].
243
245
  - [yellow]latest[/yellow]: uses the special [magenta]latest[/magenta]
244
246
  instance of the application. This corresponds to the latest pushed
245
- executable. This is the default behavior.
246
- - [yellow]default[/yellow]: if the application has a [italic]default[/italic]
247
- instance configured, then it uses that instance. Setting the flag's value
248
- to [magenta]''[/magenta] (empty string) has the same effect.
247
+ executable.
249
248
  - [yellow]<INSTANCE_ID>[/yellow]: uses the instance with the given ID.
250
249
 
251
250
  [bold][underline]Examples[/underline][/bold]
@@ -319,10 +318,6 @@ def create(
319
318
  )
320
319
  run_options = build_run_options(options)
321
320
 
322
- # Handles the default instance.
323
- if instance_id == "default":
324
- instance_id = ""
325
-
326
321
  # Start the run before deciding if we should poll or not.
327
322
  input_kwarg = resolve_input_kwarg(
328
323
  stdin=stdin,
@@ -53,10 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(
57
- msg=f"Scenario test [magenta]{scenario_test_id}[/magenta] will not be deleted.",
58
- emoji=":bulb:",
59
- )
56
+ info(f"Scenario test [magenta]{scenario_test_id}[/magenta] will not be deleted.")
60
57
  return
61
58
 
62
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -52,10 +52,7 @@ def delete(
52
52
  )
53
53
 
54
54
  if not confirm:
55
- info(
56
- msg=f"Secrets collection [magenta]{secrets_collection_id}[/magenta] will not be deleted.",
57
- emoji=":bulb:",
58
- )
55
+ info(f"Secrets collection [magenta]{secrets_collection_id}[/magenta] will not be deleted.")
59
56
  return
60
57
 
61
58
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -53,10 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(
57
- msg=f"Shadow test [magenta]{shadow_test_id}[/magenta] will not be deleted.",
58
- emoji=":bulb:",
59
- )
56
+ info(f"Shadow test [magenta]{shadow_test_id}[/magenta] will not be deleted.")
60
57
  return
61
58
 
62
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -2,11 +2,14 @@
2
2
  This module defines the cloud shadow stop command for the Nextmv CLI.
3
3
  """
4
4
 
5
+ from typing import Annotated
6
+
5
7
  import typer
6
8
 
7
9
  from nextmv.cli.configuration.config import build_app
8
- from nextmv.cli.message import in_progress, success
10
+ from nextmv.cli.message import enum_values, in_progress, success
9
11
  from nextmv.cli.options import AppIDOption, ProfileOption, ShadowTestIDOption
12
+ from nextmv.cloud.shadow import StopIntent
10
13
 
11
14
  # Set up subcommand application.
12
15
  app = typer.Typer()
@@ -15,6 +18,15 @@ app = typer.Typer()
15
18
  @app.command()
16
19
  def stop(
17
20
  app_id: AppIDOption,
21
+ intent: Annotated[
22
+ StopIntent,
23
+ typer.Option(
24
+ "--intent",
25
+ "-i",
26
+ help=f"Intent for stopping the shadow test. Allowed values are: {enum_values(StopIntent)}.",
27
+ metavar="INTENT",
28
+ ),
29
+ ],
18
30
  shadow_test_id: ShadowTestIDOption,
19
31
  profile: ProfileOption = None,
20
32
  ) -> None:
@@ -34,7 +46,7 @@ def stop(
34
46
 
35
47
  in_progress(msg="Stopping shadow test...")
36
48
  cloud_app = build_app(app_id=app_id, profile=profile)
37
- cloud_app.stop_shadow_test(shadow_test_id=shadow_test_id)
49
+ cloud_app.stop_shadow_test(shadow_test_id=shadow_test_id, intent=StopIntent(intent))
38
50
  success(
39
51
  f"Shadow test [magenta]{shadow_test_id}[/magenta] stopped successfully "
40
52
  f"in application [magenta]{app_id}[/magenta]."
@@ -53,10 +53,7 @@ def delete(
53
53
  )
54
54
 
55
55
  if not confirm:
56
- info(
57
- msg=f"Switchback test [magenta]{switchback_test_id}[/magenta] will not be deleted.",
58
- emoji=":bulb:",
59
- )
56
+ info(f"Switchback test [magenta]{switchback_test_id}[/magenta] will not be deleted.")
60
57
  return
61
58
 
62
59
  cloud_app = build_app(app_id=app_id, profile=profile)
@@ -2,11 +2,14 @@
2
2
  This module defines the cloud switchback stop command for the Nextmv CLI.
3
3
  """
4
4
 
5
+ from typing import Annotated
6
+
5
7
  import typer
6
8
 
7
9
  from nextmv.cli.configuration.config import build_app
8
- from nextmv.cli.message import in_progress, success
10
+ from nextmv.cli.message import enum_values, in_progress, success
9
11
  from nextmv.cli.options import AppIDOption, ProfileOption, SwitchbackTestIDOption
12
+ from nextmv.cloud.shadow import StopIntent
10
13
 
11
14
  # Set up subcommand application.
12
15
  app = typer.Typer()
@@ -15,6 +18,15 @@ app = typer.Typer()
15
18
  @app.command()
16
19
  def stop(
17
20
  app_id: AppIDOption,
21
+ intent: Annotated[
22
+ StopIntent,
23
+ typer.Option(
24
+ "--intent",
25
+ "-i",
26
+ help=f"Intent for stopping the switchback test. Allowed values are: {enum_values(StopIntent)}.",
27
+ metavar="INTENT",
28
+ ),
29
+ ],
18
30
  switchback_test_id: SwitchbackTestIDOption,
19
31
  profile: ProfileOption = None,
20
32
  ) -> None:
@@ -34,7 +46,7 @@ def stop(
34
46
 
35
47
  in_progress(msg="Stopping switchback test...")
36
48
  cloud_app = build_app(app_id=app_id, profile=profile)
37
- cloud_app.stop_switchback_test(switchback_test_id=switchback_test_id)
49
+ cloud_app.stop_switchback_test(switchback_test_id=switchback_test_id, intent=StopIntent(intent))
38
50
  success(
39
51
  f"Switchback test [magenta]{switchback_test_id}[/magenta] stopped successfully "
40
52
  f"in application [magenta]{app_id}[/magenta]."
@@ -51,7 +51,7 @@ def delete(
51
51
  )
52
52
 
53
53
  if not confirm:
54
- info(msg=f"Version [magenta]{version_id}[/magenta] will not be deleted.", emoji=":bulb:")
54
+ info(f"Version [magenta]{version_id}[/magenta] will not be deleted.")
55
55
  return
56
56
 
57
57
  cloud_app = build_app(app_id=app_id, profile=profile)