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.
- nextmv/__about__.py +1 -1
- nextmv/cli/CONTRIBUTING.md +81 -29
- nextmv/cli/cloud/acceptance/create.py +20 -22
- nextmv/cli/cloud/acceptance/delete.py +7 -8
- nextmv/cli/cloud/acceptance/get.py +9 -10
- nextmv/cli/cloud/acceptance/list.py +3 -3
- nextmv/cli/cloud/acceptance/update.py +6 -6
- nextmv/cli/cloud/account/__init__.py +3 -3
- nextmv/cli/cloud/account/create.py +11 -11
- nextmv/cli/cloud/account/delete.py +6 -7
- nextmv/cli/cloud/account/get.py +3 -3
- nextmv/cli/cloud/account/update.py +5 -5
- nextmv/cli/cloud/app/create.py +25 -26
- nextmv/cli/cloud/app/delete.py +5 -6
- nextmv/cli/cloud/app/exists.py +2 -2
- nextmv/cli/cloud/app/get.py +2 -2
- nextmv/cli/cloud/app/list.py +3 -3
- nextmv/cli/cloud/app/push.py +269 -45
- nextmv/cli/cloud/app/update.py +12 -12
- nextmv/cli/cloud/batch/create.py +26 -28
- nextmv/cli/cloud/batch/delete.py +5 -6
- nextmv/cli/cloud/batch/get.py +8 -8
- nextmv/cli/cloud/batch/list.py +3 -3
- nextmv/cli/cloud/batch/metadata.py +4 -4
- nextmv/cli/cloud/batch/update.py +6 -6
- nextmv/cli/cloud/data/__init__.py +1 -1
- nextmv/cli/cloud/data/upload.py +15 -15
- nextmv/cli/cloud/ensemble/__init__.py +2 -0
- nextmv/cli/cloud/ensemble/create.py +21 -22
- nextmv/cli/cloud/ensemble/delete.py +5 -6
- nextmv/cli/cloud/ensemble/get.py +4 -4
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +9 -9
- nextmv/cli/cloud/input_set/create.py +20 -22
- nextmv/cli/cloud/input_set/get.py +3 -3
- nextmv/cli/cloud/input_set/list.py +3 -3
- nextmv/cli/cloud/input_set/update.py +24 -24
- nextmv/cli/cloud/instance/create.py +14 -15
- nextmv/cli/cloud/instance/delete.py +5 -6
- nextmv/cli/cloud/instance/exists.py +2 -2
- nextmv/cli/cloud/instance/get.py +2 -2
- nextmv/cli/cloud/instance/list.py +3 -3
- nextmv/cli/cloud/instance/update.py +14 -14
- nextmv/cli/cloud/managed_input/create.py +14 -16
- nextmv/cli/cloud/managed_input/delete.py +6 -7
- nextmv/cli/cloud/managed_input/get.py +3 -3
- nextmv/cli/cloud/managed_input/list.py +3 -3
- nextmv/cli/cloud/managed_input/update.py +9 -9
- nextmv/cli/cloud/run/cancel.py +2 -2
- nextmv/cli/cloud/run/create.py +32 -33
- nextmv/cli/cloud/run/get.py +8 -8
- nextmv/cli/cloud/run/input.py +4 -4
- nextmv/cli/cloud/run/list.py +6 -6
- nextmv/cli/cloud/run/logs.py +9 -10
- nextmv/cli/cloud/run/metadata.py +4 -4
- nextmv/cli/cloud/run/track.py +32 -33
- nextmv/cli/cloud/scenario/create.py +21 -21
- nextmv/cli/cloud/scenario/delete.py +5 -6
- nextmv/cli/cloud/scenario/get.py +8 -8
- nextmv/cli/cloud/scenario/list.py +3 -3
- nextmv/cli/cloud/scenario/metadata.py +4 -4
- nextmv/cli/cloud/scenario/update.py +6 -6
- nextmv/cli/cloud/secrets/create.py +17 -17
- nextmv/cli/cloud/secrets/delete.py +5 -6
- nextmv/cli/cloud/secrets/get.py +4 -4
- nextmv/cli/cloud/secrets/list.py +3 -3
- nextmv/cli/cloud/secrets/update.py +17 -20
- nextmv/cli/cloud/shadow/create.py +31 -31
- nextmv/cli/cloud/shadow/delete.py +5 -6
- nextmv/cli/cloud/shadow/get.py +2 -2
- nextmv/cli/cloud/shadow/list.py +3 -3
- nextmv/cli/cloud/shadow/metadata.py +4 -4
- nextmv/cli/cloud/shadow/start.py +3 -3
- nextmv/cli/cloud/shadow/stop.py +4 -6
- nextmv/cli/cloud/shadow/update.py +6 -6
- nextmv/cli/cloud/switchback/create.py +19 -15
- nextmv/cli/cloud/switchback/delete.py +5 -6
- nextmv/cli/cloud/switchback/get.py +3 -3
- nextmv/cli/cloud/switchback/list.py +3 -3
- nextmv/cli/cloud/switchback/metadata.py +6 -6
- nextmv/cli/cloud/switchback/start.py +4 -4
- nextmv/cli/cloud/switchback/stop.py +4 -6
- nextmv/cli/cloud/switchback/update.py +6 -6
- nextmv/cli/cloud/upload/create.py +2 -2
- nextmv/cli/cloud/version/create.py +9 -10
- nextmv/cli/cloud/version/delete.py +5 -6
- nextmv/cli/cloud/version/exists.py +2 -2
- nextmv/cli/cloud/version/get.py +2 -2
- nextmv/cli/cloud/version/list.py +3 -3
- nextmv/cli/cloud/version/update.py +8 -8
- nextmv/cli/community/clone.py +12 -10
- nextmv/cli/community/list.py +9 -9
- nextmv/cli/configuration/config.py +43 -10
- nextmv/cli/configuration/create.py +3 -3
- nextmv/cli/configuration/delete.py +7 -7
- nextmv/cli/configuration/list.py +3 -3
- nextmv/cli/confirm.py +32 -0
- nextmv/cli/main.py +27 -36
- nextmv/cli/message.py +2 -2
- nextmv/cli/version.py +1 -1
- nextmv/cloud/application/__init__.py +190 -54
- nextmv/cloud/application/_batch_scenario.py +2 -2
- nextmv/cloud/application/_instance.py +2 -2
- nextmv/cloud/application/_managed_input.py +1 -1
- nextmv/cloud/application/_shadow.py +1 -1
- nextmv/cloud/application/_switchback.py +11 -3
- nextmv/cloud/application/_version.py +3 -2
- nextmv/cloud/shadow.py +43 -4
- nextmv/cloud/switchback.py +46 -9
- {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/METADATA +1 -1
- nextmv-1.0.0.dev4.dist-info/RECORD +183 -0
- nextmv-1.0.0.dev3.dist-info/RECORD +0 -181
- {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev3.dist-info → nextmv-1.0.0.dev4.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
277
|
+
description : str | None = None
|
|
276
278
|
Description of the application.
|
|
277
|
-
is_workflow : bool
|
|
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
|
-
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
that is created. If
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
451
|
-
If True,
|
|
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
|
|
457
|
-
|
|
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
|
-
|
|
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
|
|
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":
|
|
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(
|
|
605
|
-
|
|
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
|
|
@@ -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":
|
|
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.
|
|
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
|
|
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
|
|
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
|
nextmv/cloud/switchback.py
CHANGED
|
@@ -91,10 +91,10 @@ class SwitchbackPlan(BaseModel):
|
|
|
91
91
|
|
|
92
92
|
Parameters
|
|
93
93
|
----------
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|