nextmv 1.0.0.dev3__py3-none-any.whl → 1.0.0.dev4__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 (115) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/cli/CONTRIBUTING.md +81 -29
  3. nextmv/cli/cloud/acceptance/create.py +20 -22
  4. nextmv/cli/cloud/acceptance/delete.py +7 -8
  5. nextmv/cli/cloud/acceptance/get.py +9 -10
  6. nextmv/cli/cloud/acceptance/list.py +3 -3
  7. nextmv/cli/cloud/acceptance/update.py +6 -6
  8. nextmv/cli/cloud/account/__init__.py +3 -3
  9. nextmv/cli/cloud/account/create.py +11 -11
  10. nextmv/cli/cloud/account/delete.py +6 -7
  11. nextmv/cli/cloud/account/get.py +3 -3
  12. nextmv/cli/cloud/account/update.py +5 -5
  13. nextmv/cli/cloud/app/create.py +25 -26
  14. nextmv/cli/cloud/app/delete.py +5 -6
  15. nextmv/cli/cloud/app/exists.py +2 -2
  16. nextmv/cli/cloud/app/get.py +2 -2
  17. nextmv/cli/cloud/app/list.py +3 -3
  18. nextmv/cli/cloud/app/push.py +269 -45
  19. nextmv/cli/cloud/app/update.py +12 -12
  20. nextmv/cli/cloud/batch/create.py +26 -28
  21. nextmv/cli/cloud/batch/delete.py +5 -6
  22. nextmv/cli/cloud/batch/get.py +8 -8
  23. nextmv/cli/cloud/batch/list.py +3 -3
  24. nextmv/cli/cloud/batch/metadata.py +4 -4
  25. nextmv/cli/cloud/batch/update.py +6 -6
  26. nextmv/cli/cloud/data/__init__.py +1 -1
  27. nextmv/cli/cloud/data/upload.py +15 -15
  28. nextmv/cli/cloud/ensemble/__init__.py +2 -0
  29. nextmv/cli/cloud/ensemble/create.py +21 -22
  30. nextmv/cli/cloud/ensemble/delete.py +5 -6
  31. nextmv/cli/cloud/ensemble/get.py +4 -4
  32. nextmv/cli/cloud/ensemble/list.py +63 -0
  33. nextmv/cli/cloud/ensemble/update.py +9 -9
  34. nextmv/cli/cloud/input_set/create.py +20 -22
  35. nextmv/cli/cloud/input_set/get.py +3 -3
  36. nextmv/cli/cloud/input_set/list.py +3 -3
  37. nextmv/cli/cloud/input_set/update.py +24 -24
  38. nextmv/cli/cloud/instance/create.py +14 -15
  39. nextmv/cli/cloud/instance/delete.py +5 -6
  40. nextmv/cli/cloud/instance/exists.py +2 -2
  41. nextmv/cli/cloud/instance/get.py +2 -2
  42. nextmv/cli/cloud/instance/list.py +3 -3
  43. nextmv/cli/cloud/instance/update.py +14 -14
  44. nextmv/cli/cloud/managed_input/create.py +14 -16
  45. nextmv/cli/cloud/managed_input/delete.py +6 -7
  46. nextmv/cli/cloud/managed_input/get.py +3 -3
  47. nextmv/cli/cloud/managed_input/list.py +3 -3
  48. nextmv/cli/cloud/managed_input/update.py +9 -9
  49. nextmv/cli/cloud/run/cancel.py +2 -2
  50. nextmv/cli/cloud/run/create.py +32 -33
  51. nextmv/cli/cloud/run/get.py +8 -8
  52. nextmv/cli/cloud/run/input.py +4 -4
  53. nextmv/cli/cloud/run/list.py +6 -6
  54. nextmv/cli/cloud/run/logs.py +9 -10
  55. nextmv/cli/cloud/run/metadata.py +4 -4
  56. nextmv/cli/cloud/run/track.py +32 -33
  57. nextmv/cli/cloud/scenario/create.py +21 -21
  58. nextmv/cli/cloud/scenario/delete.py +5 -6
  59. nextmv/cli/cloud/scenario/get.py +8 -8
  60. nextmv/cli/cloud/scenario/list.py +3 -3
  61. nextmv/cli/cloud/scenario/metadata.py +4 -4
  62. nextmv/cli/cloud/scenario/update.py +6 -6
  63. nextmv/cli/cloud/secrets/create.py +17 -17
  64. nextmv/cli/cloud/secrets/delete.py +5 -6
  65. nextmv/cli/cloud/secrets/get.py +4 -4
  66. nextmv/cli/cloud/secrets/list.py +3 -3
  67. nextmv/cli/cloud/secrets/update.py +17 -20
  68. nextmv/cli/cloud/shadow/create.py +31 -31
  69. nextmv/cli/cloud/shadow/delete.py +5 -6
  70. nextmv/cli/cloud/shadow/get.py +2 -2
  71. nextmv/cli/cloud/shadow/list.py +3 -3
  72. nextmv/cli/cloud/shadow/metadata.py +4 -4
  73. nextmv/cli/cloud/shadow/start.py +3 -3
  74. nextmv/cli/cloud/shadow/stop.py +4 -6
  75. nextmv/cli/cloud/shadow/update.py +6 -6
  76. nextmv/cli/cloud/switchback/create.py +19 -15
  77. nextmv/cli/cloud/switchback/delete.py +5 -6
  78. nextmv/cli/cloud/switchback/get.py +3 -3
  79. nextmv/cli/cloud/switchback/list.py +3 -3
  80. nextmv/cli/cloud/switchback/metadata.py +6 -6
  81. nextmv/cli/cloud/switchback/start.py +4 -4
  82. nextmv/cli/cloud/switchback/stop.py +4 -6
  83. nextmv/cli/cloud/switchback/update.py +6 -6
  84. nextmv/cli/cloud/upload/create.py +2 -2
  85. nextmv/cli/cloud/version/create.py +9 -10
  86. nextmv/cli/cloud/version/delete.py +5 -6
  87. nextmv/cli/cloud/version/exists.py +2 -2
  88. nextmv/cli/cloud/version/get.py +2 -2
  89. nextmv/cli/cloud/version/list.py +3 -3
  90. nextmv/cli/cloud/version/update.py +8 -8
  91. nextmv/cli/community/clone.py +12 -10
  92. nextmv/cli/community/list.py +9 -9
  93. nextmv/cli/configuration/config.py +43 -10
  94. nextmv/cli/configuration/create.py +3 -3
  95. nextmv/cli/configuration/delete.py +7 -7
  96. nextmv/cli/configuration/list.py +3 -3
  97. nextmv/cli/confirm.py +32 -0
  98. nextmv/cli/main.py +27 -36
  99. nextmv/cli/message.py +2 -2
  100. nextmv/cli/version.py +1 -1
  101. nextmv/cloud/application/__init__.py +190 -54
  102. nextmv/cloud/application/_batch_scenario.py +2 -2
  103. nextmv/cloud/application/_instance.py +2 -2
  104. nextmv/cloud/application/_managed_input.py +1 -1
  105. nextmv/cloud/application/_shadow.py +1 -1
  106. nextmv/cloud/application/_switchback.py +11 -3
  107. nextmv/cloud/application/_version.py +3 -2
  108. nextmv/cloud/shadow.py +43 -4
  109. nextmv/cloud/switchback.py +46 -9
  110. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/METADATA +1 -1
  111. nextmv-1.0.0.dev4.dist-info/RECORD +183 -0
  112. nextmv-1.0.0.dev3.dist-info/RECORD +0 -181
  113. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/WHEEL +0 -0
  114. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/entry_points.txt +0 -0
  115. {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/licenses/LICENSE +0 -0
@@ -46,7 +46,9 @@ 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
49
50
  from nextmv.cloud.url import UploadURL
51
+ from nextmv.cloud.version import Version
50
52
  from nextmv.logger import log
51
53
  from nextmv.manifest import Manifest
52
54
  from nextmv.model import Model, ModelConfiguration
@@ -250,7 +252,7 @@ class Application(
250
252
  def new(
251
253
  cls,
252
254
  client: Client,
253
- name: str,
255
+ name: str | None = None,
254
256
  id: str | None = None,
255
257
  description: str | None = None,
256
258
  is_workflow: bool | None = None,
@@ -268,13 +270,13 @@ class Application(
268
270
  ----------
269
271
  client : Client
270
272
  Client to use for interacting with the Nextmv Cloud API.
271
- name : str
272
- Name of the application.
273
- id : str, optional
273
+ name : str | None = None
274
+ Name of the application. Uses the ID as the name if not provided.
275
+ id : str | None = None
274
276
  ID of the application. Will be generated if not provided.
275
- description : str, optional
277
+ description : str | None = None
276
278
  Description of the application.
277
- is_workflow : bool, optional
279
+ is_workflow : bool | None = None
278
280
  Whether the application is a Decision Workflow.
279
281
  exist_ok : bool, default=False
280
282
  If True and an application with the same ID already exists,
@@ -296,7 +298,10 @@ class Application(
296
298
  >>> app = Application.new(client=client, name="My New App", id="my-app")
297
299
  """
298
300
 
299
- if id is None:
301
+ if exist_ok and (id is None or id == ""):
302
+ raise ValueError("If exist_ok is True, id must be provided")
303
+
304
+ if id is None or id == "":
300
305
  id = safe_id("app")
301
306
 
302
307
  if exist_ok and cls.exists(client=client, id=id):
@@ -307,6 +312,9 @@ class Application(
307
312
 
308
313
  return cls.from_dict({"client": client} | response.json())
309
314
 
315
+ if name is None or name == "":
316
+ name = id
317
+
310
318
  payload = {
311
319
  "name": name,
312
320
  "id": id,
@@ -399,10 +407,14 @@ class Application(
399
407
  model: Model | None = None,
400
408
  model_configuration: ModelConfiguration | None = None,
401
409
  rich_print: bool = False,
402
- no_version: bool = False,
410
+ auto_create: bool = False,
403
411
  version_id: str | None = None,
404
412
  version_name: str | None = None,
405
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,
406
418
  ) -> None:
407
419
  """
408
420
  Push an app to Nextmv Cloud.
@@ -420,14 +432,16 @@ class Application(
420
432
  `nextmv.Model`. The model is encoded, some dependencies and
421
433
  accompanying files are packaged, and the app is pushed to Nextmv Cloud.
422
434
 
423
- The default behavior of this function is to create a new application
424
- version _after_ the app has been pushed. You can set the `no_version`
425
- argument to `True` to skip this step. The `version_id`, `version_name`,
426
- and `version_description` arguments can be used to customize the version
427
- that is created. If the `version_id` is not specified, a randomly
428
- generated ID will be used. If the `version_name` is not specified, a
429
- generic name with a timestamp will be used. Lastly, if no description is
430
- specified, then a generic description will also be used.
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.
431
445
 
432
446
  Parameters
433
447
  ----------
@@ -447,21 +461,30 @@ class Application(
447
461
  with `model`.
448
462
  rich_print : bool, default=False
449
463
  Whether to use rich printing when verbose output is enabled.
450
- no_version : bool, default=False
451
- If True, do not create a new version after pushing the app.
464
+ auto_create : bool, default=False
465
+ If True, automatically create a new version and instance after
466
+ pushing the app.
452
467
  version_id : Optional[str], default=None
453
468
  ID of the version to create after pushing the app. If None, a unique
454
469
  ID will be generated.
455
470
  version_name : Optional[str], default=None
456
- Name of the version to create after pushing the app. If None, a name
457
- with a timestamp will be generated.
471
+ Name of the version to create after pushing the app. If None, a
472
+ name will be generated.
458
473
  version_description : Optional[str], default=None
459
474
  Description of the version to create after pushing the app. If None, a
460
475
  generic description with a timestamp will be generated.
461
-
462
- Returns
463
- -------
464
- None
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.
465
488
 
466
489
  Raises
467
490
  ------
@@ -565,44 +588,43 @@ class Application(
565
588
  except OSError as e:
566
589
  raise Exception(f"error deleting output directory: {e}") from e
567
590
 
568
- if no_version:
569
- if verbose:
570
- if rich_print:
571
- rich.print(
572
- f":white_check_mark: Push completed for Nextmv application [magenta]{self.id}[/magenta] "
573
- "without creating a new version.",
574
- file=sys.stderr,
575
- )
576
- else:
577
- log("✅ Push completed without creating a new version for Nextmv application.")
578
-
591
+ if not auto_create:
579
592
  return
580
593
 
581
- now = datetime.now(timezone.utc)
582
- if version_id is None:
583
- version_id = safe_id(prefix="version") + f"-{now.strftime('%Y%m%d-%H%M%S')}"
584
- if version_name is None:
585
- version_name = f"Version {version_id}"
586
- if version_description is None:
587
- version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
588
-
589
- version = self.new_version(
590
- id=version_id,
591
- name=version_name,
592
- description=version_description,
593
- )
594
- version_dict = version.to_dict()
595
-
596
594
  if verbose:
597
595
  if rich_print:
598
596
  rich.print(
599
- f":white_check_mark: Automatically created new version [magenta]{version.id}[/magenta].",
597
+ f":hourglass_flowing_sand: Push completed for Nextmv application [magenta]{self.id}[/magenta], "
598
+ "creating a new version and instance...",
600
599
  file=sys.stderr,
601
600
  )
602
- rich.print_json(data=version_dict)
603
601
  else:
604
- log(f'✅ Automatically created new version "{version.id}".')
605
- log(json.dumps(version_dict, indent=2))
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
+ )
606
628
 
607
629
  def update(
608
630
  self,
@@ -919,6 +941,120 @@ class Application(
919
941
  log(f'💥️ Successfully pushed to application: "{self.id}".')
920
942
  log(json.dumps(data, indent=2))
921
943
 
944
+ def __version_on_push(
945
+ self,
946
+ now: datetime,
947
+ version_id: str | None = None,
948
+ version_name: str | None = None,
949
+ version_description: str | None = None,
950
+ verbose: bool = False,
951
+ rich_print: bool = False,
952
+ ) -> Version:
953
+ if version_description is None or version_description == "":
954
+ version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
955
+
956
+ version = self.new_version(
957
+ id=version_id,
958
+ name=version_name,
959
+ description=version_description,
960
+ )
961
+ version_dict = version.to_dict()
962
+
963
+ if not verbose:
964
+ return version
965
+
966
+ if rich_print:
967
+ rich.print(
968
+ f":white_check_mark: Automatically created new version [magenta]{version.id}[/magenta].",
969
+ file=sys.stderr,
970
+ )
971
+ rich.print_json(data=version_dict)
972
+
973
+ return version
974
+
975
+ log(f'✅ Automatically created new version "{version.id}".')
976
+ log(json.dumps(version_dict, indent=2))
977
+
978
+ return version
979
+
980
+ def __instance_on_push(
981
+ self,
982
+ now: datetime,
983
+ version_id: str,
984
+ instance_id: str | None = None,
985
+ instance_name: str | None = None,
986
+ instance_description: str | None = None,
987
+ verbose: bool = False,
988
+ rich_print: bool = False,
989
+ ) -> Instance:
990
+ if instance_description is None or instance_description == "":
991
+ instance_description = f"Instance created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
992
+
993
+ instance = self.new_instance(
994
+ version_id=version_id,
995
+ id=instance_id,
996
+ name=instance_name,
997
+ description=instance_description,
998
+ )
999
+ instance_dict = instance.to_dict()
1000
+
1001
+ if not verbose:
1002
+ return instance
1003
+
1004
+ if rich_print:
1005
+ rich.print(
1006
+ f":white_check_mark: Automatically created new instance [magenta]{instance.id}[/magenta] "
1007
+ f"using version [magenta]{version_id}[/magenta].",
1008
+ file=sys.stderr,
1009
+ )
1010
+ rich.print_json(data=instance_dict)
1011
+
1012
+ return instance
1013
+
1014
+ log(f'✅ Automatically created new instance "{instance.id}" using version "{version_id}".')
1015
+ log(json.dumps(instance_dict, indent=2))
1016
+
1017
+ return instance
1018
+
1019
+ def __update_on_push(
1020
+ self,
1021
+ instance: Instance,
1022
+ update_default_instance: bool = False,
1023
+ verbose: bool = False,
1024
+ rich_print: bool = False,
1025
+ ) -> None:
1026
+ if not update_default_instance:
1027
+ return
1028
+
1029
+ if verbose:
1030
+ if rich_print:
1031
+ rich.print(
1032
+ f":hourglass_flowing_sand: Updating default instance for app [magenta]{self.id}[/magenta]...",
1033
+ file=sys.stderr,
1034
+ )
1035
+ else:
1036
+ log("⌛️ Updating default instance for the Nextmv application...")
1037
+
1038
+ updated_app = self.update(default_instance_id=instance.id)
1039
+ if not verbose:
1040
+ return
1041
+
1042
+ if rich_print:
1043
+ rich.print(
1044
+ f":white_check_mark: Updated default instance to "
1045
+ f"[magenta]{updated_app.default_instance_id}[/magenta] for application "
1046
+ f"[magenta]{self.id}[/magenta].",
1047
+ file=sys.stderr,
1048
+ )
1049
+ rich.print_json(data=updated_app.to_dict())
1050
+
1051
+ return
1052
+
1053
+ log(
1054
+ f'✅ Updated default instance to "{updated_app.default_instance_id}" for application "{self.id}".',
1055
+ )
1056
+ log(json.dumps(updated_app.to_dict(), indent=2))
1057
+
922
1058
 
923
1059
  def list_applications(client: Client) -> list[Application]:
924
1060
  """
@@ -304,7 +304,7 @@ class ApplicationBatchMixin:
304
304
  """
305
305
 
306
306
  # Generate ID if not provided
307
- if id is None:
307
+ if id is None or id == "":
308
308
  id = safe_id("batch")
309
309
 
310
310
  # Use ID as name if name not provided
@@ -481,7 +481,7 @@ class ApplicationBatchMixin:
481
481
  raise ValueError("At least one scenario must be provided")
482
482
 
483
483
  # Generate ID if not provided
484
- if id is None:
484
+ if id is None or id == "":
485
485
  id = safe_id("scenario")
486
486
 
487
487
  # Use ID as name if name not provided
@@ -194,13 +194,13 @@ class ApplicationInstanceMixin:
194
194
  'Production Instance'
195
195
  """
196
196
 
197
- if exist_ok and id is None:
197
+ if exist_ok and (id is None or id == ""):
198
198
  raise ValueError("If exist_ok is True, id must be provided")
199
199
 
200
200
  if exist_ok and self.instance_exists(instance_id=id):
201
201
  return self.instance(instance_id=id)
202
202
 
203
- if id is None:
203
+ if id is None or id == "":
204
204
  id = safe_id(prefix="instance")
205
205
  if name is None:
206
206
  name = id
@@ -147,7 +147,7 @@ class ApplicationManagedInputMixin:
147
147
  if upload_id is None and run_id is None:
148
148
  raise ValueError("Either upload_id or run_id must be specified")
149
149
 
150
- if id is None:
150
+ if id is None or id == "":
151
151
  id = safe_id(prefix="managed-input")
152
152
  if name is None:
153
153
  name = id
@@ -208,7 +208,7 @@ class ApplicationShadowMixin:
208
208
  name = shadow_test_id
209
209
 
210
210
  payload = {
211
- "id": id,
211
+ "id": shadow_test_id,
212
212
  "name": name,
213
213
  "comparisons": comparisons,
214
214
  "termination_events": termination_events.to_dict(),
@@ -172,9 +172,11 @@ class ApplicationSwitchbackMixin:
172
172
  comparison : TestComparisonSingle
173
173
  Comparison defining the baseline and candidate instances.
174
174
  unit_duration_minutes : float
175
- Duration of each interval in minutes.
175
+ Duration of each interval in minutes. The value must be between 1
176
+ and 10080.
176
177
  units : int
177
- Total number of intervals in the switchback test.
178
+ Total number of intervals in the switchback test. The value must be
179
+ between 1 and 1000.
178
180
  switchback_test_id : Optional[str], default=None
179
181
  Optional ID for the switchback test. Will be generated if not
180
182
  provided.
@@ -197,6 +199,12 @@ class ApplicationSwitchbackMixin:
197
199
  If the response status code is not 2xx.
198
200
  """
199
201
 
202
+ if unit_duration_minutes < 1 or unit_duration_minutes > 10080:
203
+ raise ValueError("unit_duration_minutes must be between 1 and 10080")
204
+
205
+ if units < 1 or units > 1000:
206
+ raise ValueError("units must be between 1 and 1000")
207
+
200
208
  # Generate ID if not provided
201
209
  if switchback_test_id is None:
202
210
  switchback_test_id = safe_id("switchback")
@@ -206,7 +214,7 @@ class ApplicationSwitchbackMixin:
206
214
  name = switchback_test_id
207
215
 
208
216
  payload = {
209
- "id": id,
217
+ "id": switchback_test_id,
210
218
  "name": name,
211
219
  "comparison": comparison,
212
220
  "generate_random_plan": {
@@ -132,14 +132,15 @@ class ApplicationVersionMixin:
132
132
  ... )
133
133
  """
134
134
 
135
- if exist_ok and id is None:
135
+ if exist_ok and (id is None or id == ""):
136
136
  raise ValueError("If exist_ok is True, id must be provided")
137
137
 
138
138
  if exist_ok and self.version_exists(version_id=id):
139
139
  return self.version(version_id=id)
140
140
 
141
- if id is None:
141
+ if id is None or id == "":
142
142
  id = safe_id(prefix="version")
143
+
143
144
  if name is None:
144
145
  name = id
145
146
 
nextmv/cloud/shadow.py CHANGED
@@ -90,7 +90,7 @@ class TerminationEvents(BaseModel):
90
90
 
91
91
  maximum_runs: int
92
92
  """
93
- Maximum number of runs for the test. Min should be 1, max should be 300.
93
+ Maximum number of runs for the test. Value must be between 1 and 300.
94
94
  """
95
95
  time: datetime | None = None
96
96
  """
@@ -99,8 +99,8 @@ class TerminationEvents(BaseModel):
99
99
  """
100
100
 
101
101
  def model_post_init(self, __context):
102
- if self.maximum_runs < 1:
103
- raise ValueError("maximum_runs must be at least 1")
102
+ if self.maximum_runs < 1 or self.maximum_runs > 300:
103
+ raise ValueError("maximum_runs must be between 1 and 300")
104
104
 
105
105
 
106
106
  class ShadowTestMetadata(BaseModel):
@@ -151,7 +151,10 @@ class ShadowTestMetadata(BaseModel):
151
151
  """The current status of the shadow test."""
152
152
 
153
153
 
154
- class ShadowTest(ShadowTestMetadata):
154
+ # This class uses some fields defined in ShadowTestMetadata. We are not
155
+ # using inheritance to help the user understand the full structure when using
156
+ # tools like intellisense.
157
+ class ShadowTest(BaseModel):
155
158
  """
156
159
  A Nextmv Cloud shadow test definition.
157
160
 
@@ -166,6 +169,20 @@ class ShadowTest(ShadowTestMetadata):
166
169
 
167
170
  Parameters
168
171
  ----------
172
+ shadow_test_id : str, optional
173
+ The unique identifier of the shadow test.
174
+ name : str, optional
175
+ Name of the shadow test.
176
+ description : str, optional
177
+ Description of the shadow test.
178
+ app_id : str, optional
179
+ ID of the application to which the shadow test belongs.
180
+ created_at : datetime, optional
181
+ Creation date of the shadow test.
182
+ updated_at : datetime, optional
183
+ Last update date of the shadow test.
184
+ status : ExperimentStatus, optional
185
+ The current status of the shadow test.
169
186
  completed_at : datetime, optional
170
187
  Completion date of the shadow test, if applicable.
171
188
  comparisons : list[TestComparison], optional
@@ -174,8 +191,30 @@ class ShadowTest(ShadowTestMetadata):
174
191
  Start events for the shadow test.
175
192
  termination_events : TerminationEvents, optional
176
193
  Termination events for the shadow test.
194
+ grouped_distributional_summaries : list[dict[str, Any]], optional
195
+ Grouped distributional summaries of the shadow test.
196
+ runs : list[Run], optional
197
+ List of runs in the shadow test.
177
198
  """
178
199
 
200
+ shadow_test_id: str | None = Field(
201
+ serialization_alias="id",
202
+ validation_alias=AliasChoices("id", "shadow_test_id"),
203
+ default=None,
204
+ )
205
+ """The unique identifier of the shadow test."""
206
+ name: str | None = None
207
+ """Name of the shadow test."""
208
+ description: str | None = None
209
+ """Description of the shadow test."""
210
+ app_id: str | None = None
211
+ """ID of the application to which the shadow test belongs."""
212
+ created_at: datetime | None = None
213
+ """Creation date of the shadow test."""
214
+ updated_at: datetime | None = None
215
+ """Last update date of the shadow test."""
216
+ status: ExperimentStatus | None = None
217
+ """The current status of the shadow test."""
179
218
  completed_at: datetime | None = None
180
219
  """Completion date of the shadow test, if applicable."""
181
220
  comparisons: list[TestComparison] | None = None
@@ -91,10 +91,10 @@ class SwitchbackPlan(BaseModel):
91
91
 
92
92
  Parameters
93
93
  ----------
94
- interval_duration_seconds : int
95
- Duration of each interval in seconds.
96
- total_intervals : int
97
- Total number of intervals in the switchback test.
94
+ start : datetime, optional
95
+ Start time of the switchback test.
96
+ units : list[SwitchbackPlanUnit], optional
97
+ List of switchback plan units.
98
98
  """
99
99
 
100
100
  start: datetime | None = None
@@ -151,7 +151,10 @@ class SwitchbackTestMetadata(BaseModel):
151
151
  """The current status of the switchback test."""
152
152
 
153
153
 
154
- class SwitchbackTest(SwitchbackTestMetadata):
154
+ # This class uses some fields defined in SwitchbackTestMetadata. We are not
155
+ # using inheritance to help the user understand the full structure when using
156
+ # tools like intellisense.
157
+ class SwitchbackTest(BaseModel):
155
158
  """
156
159
  A Nextmv Cloud switchback test definition.
157
160
 
@@ -167,16 +170,50 @@ class SwitchbackTest(SwitchbackTestMetadata):
167
170
 
168
171
  Parameters
169
172
  ----------
173
+ switchback_test_id : str, optional
174
+ The unique identifier of the switchback test.
175
+ name : str, optional
176
+ Name of the switchback test.
177
+ description : str, optional
178
+ Description of the switchback test.
179
+ app_id : str, optional
180
+ ID of the application to which the switchback test belongs.
181
+ created_at : datetime, optional
182
+ Creation date of the switchback test.
183
+ updated_at : datetime, optional
184
+ Last update date of the switchback test.
185
+ status : ExperimentStatus, optional
186
+ The current status of the switchback test.
187
+ started_at : datetime, optional
188
+ Start date of the switchback test, if applicable.
170
189
  completed_at : datetime, optional
171
190
  Completion date of the switchback test, if applicable.
172
191
  comparison : TestComparisonSingle, optional
173
192
  Test comparison defined in the switchback test.
174
- start_events : StartEvents, optional
175
- Start events for the switchback test.
176
- termination_events : TerminationEvents, optional
177
- Termination events for the switchback test.
193
+ plan : SwitchbackPlan, optional
194
+ Switchback plan defining the intervals and instance switching.
195
+ runs : list[Run], optional
196
+ List of runs in the switchback test.
178
197
  """
179
198
 
199
+ switchback_test_id: str | None = Field(
200
+ serialization_alias="id",
201
+ validation_alias=AliasChoices("id", "switchback_test_id"),
202
+ default=None,
203
+ )
204
+ """The unique identifier of the switchback test."""
205
+ name: str | None = None
206
+ """Name of the switchback test."""
207
+ description: str | None = None
208
+ """Description of the switchback test."""
209
+ app_id: str | None = None
210
+ """ID of the application to which the switchback test belongs."""
211
+ created_at: datetime | None = None
212
+ """Creation date of the switchback test."""
213
+ updated_at: datetime | None = None
214
+ """Last update date of the switchback test."""
215
+ status: ExperimentStatus | None = None
216
+ """The current status of the switchback test."""
180
217
  started_at: datetime | None = None
181
218
  """Start date of the switchback test, if applicable."""
182
219
  completed_at: datetime | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 1.0.0.dev3
3
+ Version: 1.0.0.dev4
4
4
  Summary: The all-purpose Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/