nextmv 1.0.0.dev5__py3-none-any.whl → 1.0.0.dev7__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 (37) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/_serialization.py +1 -1
  3. nextmv/cli/cloud/acceptance/create.py +12 -12
  4. nextmv/cli/cloud/app/push.py +15 -15
  5. nextmv/cli/cloud/input_set/__init__.py +2 -0
  6. nextmv/cli/cloud/input_set/delete.py +67 -0
  7. nextmv/cli/cloud/run/create.py +4 -9
  8. nextmv/cli/cloud/shadow/stop.py +14 -2
  9. nextmv/cli/cloud/switchback/stop.py +14 -2
  10. nextmv/cli/community/clone.py +11 -197
  11. nextmv/cli/community/list.py +46 -116
  12. nextmv/cloud/__init__.py +4 -0
  13. nextmv/cloud/application/__init__.py +1 -200
  14. nextmv/cloud/application/_acceptance.py +13 -8
  15. nextmv/cloud/application/_input_set.py +42 -6
  16. nextmv/cloud/application/_run.py +1 -8
  17. nextmv/cloud/application/_shadow.py +9 -3
  18. nextmv/cloud/application/_switchback.py +11 -2
  19. nextmv/cloud/batch_experiment.py +3 -1
  20. nextmv/cloud/client.py +1 -1
  21. nextmv/cloud/community.py +441 -0
  22. nextmv/cloud/integration.py +7 -4
  23. nextmv/cloud/shadow.py +25 -0
  24. nextmv/cloud/switchback.py +2 -0
  25. nextmv/default_app/main.py +6 -4
  26. nextmv/local/executor.py +3 -83
  27. nextmv/local/geojson_handler.py +1 -1
  28. nextmv/manifest.py +7 -11
  29. nextmv/model.py +2 -2
  30. nextmv/options.py +1 -1
  31. nextmv/output.py +21 -57
  32. nextmv/run.py +3 -12
  33. {nextmv-1.0.0.dev5.dist-info → nextmv-1.0.0.dev7.dist-info}/METADATA +3 -1
  34. {nextmv-1.0.0.dev5.dist-info → nextmv-1.0.0.dev7.dist-info}/RECORD +37 -35
  35. {nextmv-1.0.0.dev5.dist-info → nextmv-1.0.0.dev7.dist-info}/WHEEL +0 -0
  36. {nextmv-1.0.0.dev5.dist-info → nextmv-1.0.0.dev7.dist-info}/entry_points.txt +0 -0
  37. {nextmv-1.0.0.dev5.dist-info → nextmv-1.0.0.dev7.dist-info}/licenses/LICENSE +0 -0
@@ -2,18 +2,18 @@
2
2
  This module defines the community list command for the Nextmv CLI.
3
3
  """
4
4
 
5
- from typing import Annotated, Any
5
+ from typing import Annotated
6
6
 
7
- import requests
8
7
  import rich
9
8
  import typer
10
- import yaml
11
9
  from rich.console import Console
12
10
  from rich.table import Table
13
11
 
14
12
  from nextmv.cli.configuration.config import build_client
15
13
  from nextmv.cli.message import error
16
14
  from nextmv.cli.options import ProfileOption
15
+ from nextmv.cloud.client import Client
16
+ from nextmv.cloud.community import CommunityApp, list_community_apps
17
17
 
18
18
  # Set up subcommand application.
19
19
  app = typer.Typer()
@@ -62,96 +62,73 @@ def list(
62
62
  if app is not None and app == "":
63
63
  error("The --app flag cannot be an empty string.")
64
64
 
65
- manifest = download_manifest(profile=profile)
65
+ client = build_client(profile)
66
66
  if flat and app is None:
67
- apps_list(manifest)
67
+ _apps_list(client)
68
68
  raise typer.Exit()
69
69
  elif not flat and app is None:
70
- apps_table(manifest)
70
+ _apps_table(client)
71
71
  raise typer.Exit()
72
72
  elif flat and app is not None and app != "":
73
- versions_list(manifest, app)
73
+ _versions_list(client, app)
74
74
  raise typer.Exit()
75
75
  elif not flat and app is not None and app != "":
76
- versions_table(manifest, app)
76
+ _versions_table(client, app)
77
77
  raise typer.Exit()
78
78
 
79
79
 
80
- def download_manifest(profile: str | None = None) -> dict:
80
+ def _apps_table(client: Client) -> None:
81
81
  """
82
- Downloads and returns the community apps manifest.
82
+ This function prints a table of community apps.
83
83
 
84
84
  Parameters
85
85
  ----------
86
- profile : str | None
87
- The profile name to use. If None, the default profile is used.
88
-
89
- Returns
90
- -------
91
- dict
92
- The community apps manifest as a dictionary.
93
-
94
- Raises
95
- requests.HTTPError
96
- If the response status code is not 2xx.
97
- """
98
-
99
- response = download_file(directory="community-apps", file="manifest.yml", profile=profile)
100
- manifest = yaml.safe_load(response.text)
101
-
102
- return manifest
103
-
104
-
105
- def apps_table(manifest: dict[str, Any]) -> None:
106
- """
107
- This function prints a table of community apps from the manifest.
108
-
109
- Parameters
110
- ----------
111
- manifest : dict[str, Any]
112
- The community apps manifest.
86
+ client : Client
87
+ The Nextmv Cloud client to use for the request.
113
88
  """
114
89
 
90
+ apps = list_community_apps(client)
115
91
  table = Table("Name", "Type", "Latest", "Description", border_style="cyan", header_style="cyan")
116
- for app in manifest.get("apps", []):
92
+ for app in apps:
117
93
  table.add_row(
118
- app.get("name", ""),
119
- app.get("type", ""),
120
- app.get("latest_app_version", ""),
121
- app.get("description", ""),
94
+ app.name,
95
+ app.app_type,
96
+ app.latest_app_version if app.latest_app_version is not None else "",
97
+ app.description,
122
98
  )
123
99
 
124
100
  console.print(table)
125
101
 
126
102
 
127
- def apps_list(manifest: dict[str, Any]) -> None:
103
+ def _apps_list(client: Client) -> None:
128
104
  """
129
- This function prints a flat list of community app names from the manifest.
105
+ This function prints a flat list of community app names.
130
106
 
131
107
  Parameters
132
108
  ----------
133
- manifest : dict[str, Any]
134
- The community apps manifest.
109
+ client : Client
110
+ The Nextmv Cloud client to use for the request.
135
111
  """
136
112
 
137
- names = [app.get("name", "") for app in manifest.get("apps", [])]
113
+ apps = list_community_apps(client)
114
+ names = [app.name for app in apps]
138
115
  print("\n".join(names))
139
116
 
140
117
 
141
- def versions_table(manifest: dict[str, Any], app: str) -> None:
118
+ def _versions_table(client: Client, app: str) -> None:
142
119
  """
143
120
  This function prints a table of versions for a specific community app.
144
121
 
145
122
  Parameters
146
123
  ----------
147
- manifest : dict[str, Any]
148
- The community apps manifest.
124
+ client : Client
125
+ The Nextmv Cloud client to use for the request.
149
126
  app : str
150
127
  The name of the community app.
151
128
  """
152
129
 
153
- app_obj = find_app(manifest, app)
154
- latest_version = app_obj.get("latest_app_version", "")
130
+ comm_app = _find_app(client, app)
131
+ latest_version = comm_app.latest_app_version if comm_app.latest_app_version is not None else ""
155
132
 
156
133
  # Add the latest version with indicator
157
134
  table = Table("Version", "Latest?", border_style="cyan", header_style="cyan")
@@ -159,7 +136,7 @@ def versions_table(manifest: dict[str, Any], app: str) -> None:
159
136
  table.add_row("", "") # Empty row to separate latest from others.
160
137
 
161
138
  # Add all other versions (excluding the latest)
162
- versions = app_obj.get("app_versions", [])
139
+ versions = comm_app.app_versions if comm_app.app_versions is not None else []
163
140
  for version in versions:
164
141
  if version != latest_version:
165
142
  table.add_row(version, "")
@@ -167,99 +144,52 @@ def versions_table(manifest: dict[str, Any], app: str) -> None:
167
144
  console.print(table)
168
145
 
169
146
 
170
- def versions_list(manifest: dict[str, Any], app: str) -> None:
147
+ def _versions_list(client: Client, app: str) -> None:
171
148
  """
172
149
  This function prints a flat list of versions for a specific community app.
173
150
 
174
151
  Parameters
175
152
  ----------
176
- manifest : dict[str, Any]
177
- The community apps manifest.
153
+ client : Client
154
+ The Nextmv Cloud client to use for the request.
178
155
  app : str
179
156
  The name of the community app.
180
157
  """
181
158
 
182
- app_obj = find_app(manifest, app)
183
- versions = app_obj.get("app_versions", [])
159
+ comm_app = _find_app(client, app)
160
+ versions = comm_app.app_versions if comm_app.app_versions is not None else []
184
161
 
185
162
  versions_output = ""
186
163
  for version in versions:
187
164
  versions_output += f"{version}\n"
188
165
 
189
- print("\n".join(app_obj.get("app_versions", [])))
190
-
191
-
192
- def download_file(
193
- directory: str,
194
- file: str,
195
- profile: str | None = None,
196
- ) -> requests.Response:
197
- """
198
- Gets a file from an internal bucket and return it.
199
-
200
- Parameters
201
- ----------
202
- directory : str
203
- The directory in the bucket where the file is located.
204
- file : str
205
- The name of the file to download.
206
- profile : str | None
207
- The profile name to use. If None, the default profile is used.
208
-
209
- Returns
210
- -------
211
- requests.Response
212
- The response object containing the file data.
213
-
214
- Raises
215
- requests.HTTPError
216
- If the response status code is not 2xx.
217
- """
218
-
219
- client = build_client(profile)
220
-
221
- # Request the download URL for the file.
222
- response = client.request(
223
- method="GET",
224
- endpoint="v0/internal/tools",
225
- headers=client.headers | {"request-source": "cli"}, # Pass `client.headers` to preserve auth.
226
- query_params={"file": f"{directory}/{file}"},
227
- )
228
-
229
- # Use the URL obtained to download the file.
230
- body = response.json()
231
- download_response = client.request(
232
- method="GET",
233
- endpoint=body.get("url"),
234
- headers={"Content-Type": "application/json"},
235
- )
236
-
237
- return download_response
166
+ print("\n".join(versions_output))
238
167
 
239
168
 
240
- def find_app(manifest: dict[str, Any], app: str) -> dict[str, Any] | None:
169
+ def _find_app(client: Client, app: str) -> CommunityApp | None:
241
170
  """
242
171
  Finds and returns a community app from the manifest by its name.
243
172
 
244
173
  Parameters
245
174
  ----------
246
- manifest : dict[str, Any]
247
- The community apps manifest.
175
+ client : Client
176
+ The Nextmv Cloud client to use for the request.
248
177
  app : str
249
178
  The name of the community app to find.
250
179
 
251
180
  Returns
252
181
  -------
253
- dict[str, Any] | None
254
- The community app dictionary if found, otherwise None.
182
+ CommunityApp | None
183
+ The community app if found, otherwise None.
255
184
  """
256
185
 
257
- for manifest_app in manifest.get("apps", []):
258
- if manifest_app.get("name", "") == app:
259
- return manifest_app
186
+ comm_apps = list_community_apps(client)
187
+ for comm_app in comm_apps:
188
+ if comm_app.name == app:
189
+ return comm_app
260
190
 
261
191
  # We don't use error() here to allow printing something before exiting.
262
192
  rich.print(f"[red]Error:[/red] Community app [magenta]{app}[/magenta] was not found. Here are the available apps:")
263
- apps_table(manifest)
193
+ _apps_table(client)
264
194
 
265
195
  raise typer.Exit(code=1)
nextmv/cloud/__init__.py CHANGED
@@ -30,6 +30,9 @@ from .batch_experiment import BatchExperimentRun as BatchExperimentRun
30
30
  from .batch_experiment import ExperimentStatus as ExperimentStatus
31
31
  from .client import Client as Client
32
32
  from .client import get_size as get_size
33
+ from .community import CommunityApp as CommunityApp
34
+ from .community import clone_community_app as clone_community_app
35
+ from .community import list_community_apps as list_community_apps
33
36
  from .ensemble import EnsembleDefinition as EnsembleDefinition
34
37
  from .ensemble import EvaluationRule as EvaluationRule
35
38
  from .ensemble import RuleObjective as RuleObjective
@@ -55,6 +58,7 @@ from .secrets import SecretType as SecretType
55
58
  from .shadow import ShadowTest as ShadowTest
56
59
  from .shadow import ShadowTestMetadata as ShadowTestMetadata
57
60
  from .shadow import StartEvents as StartEvents
61
+ from .shadow import StopIntent as StopIntent
58
62
  from .shadow import TerminationEvents as TerminationEvents
59
63
  from .shadow import TestComparison as TestComparison
60
64
  from .switchback import SwitchbackPlan as SwitchbackPlan
@@ -21,7 +21,7 @@ list_application
21
21
  import json
22
22
  import shutil
23
23
  import sys
24
- from datetime import datetime, timezone
24
+ from datetime import datetime
25
25
  from enum import Enum
26
26
  from typing import Any
27
27
 
@@ -46,9 +46,7 @@ from nextmv.cloud.application._switchback import ApplicationSwitchbackMixin
46
46
  from nextmv.cloud.application._utils import _is_not_exist_error
47
47
  from nextmv.cloud.application._version import ApplicationVersionMixin
48
48
  from nextmv.cloud.client import Client
49
- from nextmv.cloud.instance import Instance
50
49
  from nextmv.cloud.url import UploadURL
51
- from nextmv.cloud.version import Version
52
50
  from nextmv.logger import log
53
51
  from nextmv.manifest import Manifest
54
52
  from nextmv.model import Model, ModelConfiguration
@@ -407,14 +405,6 @@ class Application(
407
405
  model: Model | None = None,
408
406
  model_configuration: ModelConfiguration | None = None,
409
407
  rich_print: bool = False,
410
- auto_create: bool = False,
411
- version_id: str | None = None,
412
- version_name: str | None = None,
413
- version_description: str | None = None,
414
- instance_id: str | None = None,
415
- instance_name: str | None = None,
416
- instance_description: str | None = None,
417
- update_default_instance: bool = False,
418
408
  ) -> None:
419
409
  """
420
410
  Push an app to Nextmv Cloud.
@@ -432,17 +422,6 @@ class Application(
432
422
  `nextmv.Model`. The model is encoded, some dependencies and
433
423
  accompanying files are packaged, and the app is pushed to Nextmv Cloud.
434
424
 
435
- By default, this function only pushes the app. If you want to
436
- automatically create a new version and instance after pushing, set
437
- `auto_create=True`. The `version_id`, `version_name`, and
438
- `version_description` arguments allow you to customize the new version
439
- that is created. If not specified, defaults will be generated (random
440
- ID, timestamped name/description). Similarly, `instance_id`,
441
- `instance_name`, and `instance_description` can be used to customize
442
- the new instance. If `update_default_instance` is True, the
443
- application's default instance will be updated to the newly created
444
- instance.
445
-
446
425
  Parameters
447
426
  ----------
448
427
  manifest : Optional[Manifest], default=None
@@ -461,30 +440,6 @@ class Application(
461
440
  with `model`.
462
441
  rich_print : bool, default=False
463
442
  Whether to use rich printing when verbose output is enabled.
464
- auto_create : bool, default=False
465
- If True, automatically create a new version and instance after
466
- pushing the app.
467
- version_id : Optional[str], default=None
468
- ID of the version to create after pushing the app. If None, a unique
469
- ID will be generated.
470
- version_name : Optional[str], default=None
471
- Name of the version to create after pushing the app. If None, a
472
- name will be generated.
473
- version_description : Optional[str], default=None
474
- Description of the version to create after pushing the app. If None, a
475
- generic description with a timestamp will be generated.
476
- instance_id : Optional[str], default=None
477
- ID of the instance to create after pushing the app. If None, a unique
478
- ID will be generated.
479
- instance_name : Optional[str], default=None
480
- Name of the instance to create after pushing the app. If None, a
481
- name will be generated.
482
- instance_description : Optional[str], default=None
483
- Description of the instance to create after pushing the app. If None,
484
- a generic description with a timestamp will be generated.
485
- update_default_instance : bool, default=False
486
- If True, update the application's default instance to the newly
487
- created instance.
488
443
 
489
444
  Raises
490
445
  ------
@@ -588,44 +543,6 @@ class Application(
588
543
  except OSError as e:
589
544
  raise Exception(f"error deleting output directory: {e}") from e
590
545
 
591
- if not auto_create:
592
- return
593
-
594
- if verbose:
595
- if rich_print:
596
- rich.print(
597
- f":hourglass_flowing_sand: Push completed for Nextmv application [magenta]{self.id}[/magenta], "
598
- "creating a new version and instance...",
599
- file=sys.stderr,
600
- )
601
- else:
602
- log("⌛️ Push completed for the Nextmv application, creating a new version and instance...")
603
-
604
- now = datetime.now(timezone.utc)
605
- version = self.__version_on_push(
606
- now=now,
607
- version_id=version_id,
608
- version_name=version_name,
609
- version_description=version_description,
610
- verbose=verbose,
611
- rich_print=rich_print,
612
- )
613
- instance = self.__instance_on_push(
614
- now=now,
615
- version_id=version.id,
616
- instance_id=instance_id,
617
- instance_name=instance_name,
618
- instance_description=instance_description,
619
- verbose=verbose,
620
- rich_print=rich_print,
621
- )
622
- self.__update_on_push(
623
- instance=instance,
624
- update_default_instance=update_default_instance,
625
- verbose=verbose,
626
- rich_print=rich_print,
627
- )
628
-
629
546
  def update(
630
547
  self,
631
548
  name: str | None = None,
@@ -867,8 +784,6 @@ class Application(
867
784
  output_config = multi_config["output_configuration"] = {}
868
785
  if content.multi_file.output.statistics:
869
786
  output_config["statistics_path"] = content.multi_file.output.statistics
870
- if content.multi_file.output.metrics:
871
- output_config["metrics_path"] = content.multi_file.output.metrics
872
787
  if content.multi_file.output.assets:
873
788
  output_config["assets_path"] = content.multi_file.output.assets
874
789
  if content.multi_file.output.solutions:
@@ -943,120 +858,6 @@ class Application(
943
858
  log(f'💥️ Successfully pushed to application: "{self.id}".')
944
859
  log(json.dumps(data, indent=2))
945
860
 
946
- def __version_on_push(
947
- self,
948
- now: datetime,
949
- version_id: str | None = None,
950
- version_name: str | None = None,
951
- version_description: str | None = None,
952
- verbose: bool = False,
953
- rich_print: bool = False,
954
- ) -> Version:
955
- if version_description is None or version_description == "":
956
- version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
957
-
958
- version = self.new_version(
959
- id=version_id,
960
- name=version_name,
961
- description=version_description,
962
- )
963
- version_dict = version.to_dict()
964
-
965
- if not verbose:
966
- return version
967
-
968
- if rich_print:
969
- rich.print(
970
- f":white_check_mark: Automatically created new version [magenta]{version.id}[/magenta].",
971
- file=sys.stderr,
972
- )
973
- rich.print_json(data=version_dict)
974
-
975
- return version
976
-
977
- log(f'✅ Automatically created new version "{version.id}".')
978
- log(json.dumps(version_dict, indent=2))
979
-
980
- return version
981
-
982
- def __instance_on_push(
983
- self,
984
- now: datetime,
985
- version_id: str,
986
- instance_id: str | None = None,
987
- instance_name: str | None = None,
988
- instance_description: str | None = None,
989
- verbose: bool = False,
990
- rich_print: bool = False,
991
- ) -> Instance:
992
- if instance_description is None or instance_description == "":
993
- instance_description = f"Instance created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
994
-
995
- instance = self.new_instance(
996
- version_id=version_id,
997
- id=instance_id,
998
- name=instance_name,
999
- description=instance_description,
1000
- )
1001
- instance_dict = instance.to_dict()
1002
-
1003
- if not verbose:
1004
- return instance
1005
-
1006
- if rich_print:
1007
- rich.print(
1008
- f":white_check_mark: Automatically created new instance [magenta]{instance.id}[/magenta] "
1009
- f"using version [magenta]{version_id}[/magenta].",
1010
- file=sys.stderr,
1011
- )
1012
- rich.print_json(data=instance_dict)
1013
-
1014
- return instance
1015
-
1016
- log(f'✅ Automatically created new instance "{instance.id}" using version "{version_id}".')
1017
- log(json.dumps(instance_dict, indent=2))
1018
-
1019
- return instance
1020
-
1021
- def __update_on_push(
1022
- self,
1023
- instance: Instance,
1024
- update_default_instance: bool = False,
1025
- verbose: bool = False,
1026
- rich_print: bool = False,
1027
- ) -> None:
1028
- if not update_default_instance:
1029
- return
1030
-
1031
- if verbose:
1032
- if rich_print:
1033
- rich.print(
1034
- f":hourglass_flowing_sand: Updating default instance for app [magenta]{self.id}[/magenta]...",
1035
- file=sys.stderr,
1036
- )
1037
- else:
1038
- log("⌛️ Updating default instance for the Nextmv application...")
1039
-
1040
- updated_app = self.update(default_instance_id=instance.id)
1041
- if not verbose:
1042
- return
1043
-
1044
- if rich_print:
1045
- rich.print(
1046
- f":white_check_mark: Updated default instance to "
1047
- f"[magenta]{updated_app.default_instance_id}[/magenta] for application "
1048
- f"[magenta]{self.id}[/magenta].",
1049
- file=sys.stderr,
1050
- )
1051
- rich.print_json(data=updated_app.to_dict())
1052
-
1053
- return
1054
-
1055
- log(
1056
- f'✅ Updated default instance to "{updated_app.default_instance_id}" for application "{self.id}".',
1057
- )
1058
- log(json.dumps(updated_app.to_dict(), indent=2))
1059
-
1060
861
 
1061
862
  def list_applications(client: Client) -> list[Application]:
1062
863
  """
@@ -9,6 +9,7 @@ import requests
9
9
  from nextmv.cloud.acceptance_test import AcceptanceTest, Metric
10
10
  from nextmv.cloud.batch_experiment import BatchExperimentRun, ExperimentStatus
11
11
  from nextmv.polling import DEFAULT_POLLING_OPTIONS, PollingOptions, poll
12
+ from nextmv.safe import safe_id
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from . import Application
@@ -163,8 +164,8 @@ class ApplicationAcceptanceMixin:
163
164
  self: "Application",
164
165
  candidate_instance_id: str,
165
166
  baseline_instance_id: str,
166
- id: str,
167
167
  metrics: list[Metric | dict[str, Any]],
168
+ id: str | None = None,
168
169
  name: str | None = None,
169
170
  input_set_id: str | None = None,
170
171
  description: str | None = None,
@@ -185,8 +186,8 @@ class ApplicationAcceptanceMixin:
185
186
  ID of the candidate instance.
186
187
  baseline_instance_id : str
187
188
  ID of the baseline instance.
188
- id : str
189
- ID of the acceptance test.
189
+ id : str | None, default=None
190
+ ID of the acceptance test. Will be generated if not provided.
190
191
  metrics : list[Union[Metric, dict[str, Any]]]
191
192
  List of metrics to use for the acceptance test.
192
193
  name : Optional[str], default=None
@@ -210,6 +211,11 @@ class ApplicationAcceptanceMixin:
210
211
  If the batch experiment ID does not match the acceptance test ID.
211
212
  """
212
213
 
214
+ # Generate ID if not provided
215
+ if id is None or id == "":
216
+ id = safe_id("acceptance")
217
+
218
+ # Use ID as name if name not provided
213
219
  if name is None or name == "":
214
220
  name = id
215
221
 
@@ -265,11 +271,10 @@ class ApplicationAcceptanceMixin:
265
271
  "metrics": payload_metrics,
266
272
  "experiment_id": batch_experiment_id,
267
273
  "name": name,
274
+ "id": id,
268
275
  }
269
276
  if description is not None:
270
277
  payload["description"] = description
271
- if id is not None:
272
- payload["id"] = id
273
278
 
274
279
  response = self.client.request(
275
280
  method="POST",
@@ -283,8 +288,8 @@ class ApplicationAcceptanceMixin:
283
288
  self: "Application",
284
289
  candidate_instance_id: str,
285
290
  baseline_instance_id: str,
286
- id: str,
287
291
  metrics: list[Metric | dict[str, Any]],
292
+ id: str | None = None,
288
293
  name: str | None = None,
289
294
  input_set_id: str | None = None,
290
295
  description: str | None = None,
@@ -302,8 +307,8 @@ class ApplicationAcceptanceMixin:
302
307
  ID of the candidate instance.
303
308
  baseline_instance_id : str
304
309
  ID of the baseline instance.
305
- id : str
306
- ID of the acceptance test.
310
+ id : str | None, default=None
311
+ ID of the acceptance test. Will be generated if not provided.
307
312
  metrics : list[Union[Metric, dict[str, Any]]]
308
313
  List of metrics to use for the acceptance test.
309
314
  name : Optional[str], default=None