nextmv 1.0.0.dev4__py3-none-any.whl → 1.0.0.dev6__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/__entrypoint__.py +1 -2
- nextmv/__init__.py +0 -4
- nextmv/cli/cloud/app/push.py +294 -204
- nextmv/cli/cloud/input_set/__init__.py +2 -0
- nextmv/cli/cloud/input_set/delete.py +67 -0
- nextmv/cli/cloud/run/create.py +4 -9
- nextmv/cli/cloud/shadow/stop.py +14 -2
- nextmv/cli/cloud/switchback/stop.py +14 -2
- nextmv/cli/community/clone.py +11 -197
- nextmv/cli/community/list.py +46 -116
- nextmv/cli/confirm.py +5 -3
- nextmv/cloud/__init__.py +4 -38
- nextmv/cloud/acceptance_test.py +1 -65
- nextmv/cloud/account.py +1 -6
- nextmv/cloud/application/__init__.py +1 -198
- nextmv/cloud/application/_batch_scenario.py +2 -17
- nextmv/cloud/application/_input_set.py +42 -6
- nextmv/cloud/application/_instance.py +1 -1
- nextmv/cloud/application/_managed_input.py +1 -1
- nextmv/cloud/application/_shadow.py +10 -4
- nextmv/cloud/application/_switchback.py +11 -2
- nextmv/cloud/application/_version.py +1 -1
- nextmv/cloud/batch_experiment.py +3 -1
- nextmv/cloud/community.py +441 -0
- nextmv/cloud/shadow.py +25 -0
- nextmv/deprecated.py +5 -3
- nextmv/input.py +0 -52
- nextmv/local/runner.py +1 -1
- nextmv/options.py +11 -256
- nextmv/output.py +0 -62
- nextmv/run.py +1 -10
- nextmv/status.py +1 -51
- {nextmv-1.0.0.dev4.dist-info → nextmv-1.0.0.dev6.dist-info}/METADATA +3 -1
- {nextmv-1.0.0.dev4.dist-info → nextmv-1.0.0.dev6.dist-info}/RECORD +38 -36
- {nextmv-1.0.0.dev4.dist-info → nextmv-1.0.0.dev6.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev4.dist-info → nextmv-1.0.0.dev6.dist-info}/entry_points.txt +0 -0
- {nextmv-1.0.0.dev4.dist-info → nextmv-1.0.0.dev6.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,7 +21,7 @@ list_application
|
|
|
21
21
|
import json
|
|
22
22
|
import shutil
|
|
23
23
|
import sys
|
|
24
|
-
from datetime import datetime
|
|
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,
|
|
@@ -941,120 +858,6 @@ class Application(
|
|
|
941
858
|
log(f'💥️ Successfully pushed to application: "{self.id}".')
|
|
942
859
|
log(json.dumps(data, indent=2))
|
|
943
860
|
|
|
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
|
-
|
|
1058
861
|
|
|
1059
862
|
def list_applications(client: Client) -> list[Application]:
|
|
1060
863
|
"""
|
|
@@ -10,7 +10,6 @@ from nextmv.cloud.batch_experiment import (
|
|
|
10
10
|
BatchExperimentMetadata,
|
|
11
11
|
BatchExperimentRun,
|
|
12
12
|
ExperimentStatus,
|
|
13
|
-
to_runs,
|
|
14
13
|
)
|
|
15
14
|
from nextmv.cloud.input_set import InputSet, ManagedInput
|
|
16
15
|
from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
|
|
@@ -258,7 +257,6 @@ class ApplicationBatchMixin:
|
|
|
258
257
|
self: "Application",
|
|
259
258
|
name: str | None = None,
|
|
260
259
|
input_set_id: str | None = None,
|
|
261
|
-
instance_ids: list[str] | None = None,
|
|
262
260
|
description: str | None = None,
|
|
263
261
|
id: str | None = None,
|
|
264
262
|
option_sets: dict[str, dict[str, str]] | None = None,
|
|
@@ -274,9 +272,6 @@ class ApplicationBatchMixin:
|
|
|
274
272
|
Name of the batch experiment. If not provided, the ID will be used as the name.
|
|
275
273
|
input_set_id: str | None
|
|
276
274
|
ID of the input set to use for the batch experiment.
|
|
277
|
-
instance_ids: list[str]
|
|
278
|
-
This argument is deprecated, use `runs` instead.
|
|
279
|
-
List of instance IDs to use for the batch experiment.
|
|
280
275
|
description: Optional[str]
|
|
281
276
|
Optional description of the batch experiment.
|
|
282
277
|
id: Optional[str]
|
|
@@ -308,7 +303,7 @@ class ApplicationBatchMixin:
|
|
|
308
303
|
id = safe_id("batch")
|
|
309
304
|
|
|
310
305
|
# Use ID as name if name not provided
|
|
311
|
-
if name is None:
|
|
306
|
+
if name is None or name == "":
|
|
312
307
|
name = id
|
|
313
308
|
|
|
314
309
|
payload = {
|
|
@@ -317,11 +312,6 @@ class ApplicationBatchMixin:
|
|
|
317
312
|
}
|
|
318
313
|
if input_set_id is not None:
|
|
319
314
|
payload["input_set_id"] = input_set_id
|
|
320
|
-
if instance_ids is not None:
|
|
321
|
-
input_set = self.input_set(input_set_id)
|
|
322
|
-
runs = to_runs(instance_ids, input_set)
|
|
323
|
-
payload_runs = [run.to_dict() for run in runs]
|
|
324
|
-
payload["runs"] = payload_runs
|
|
325
315
|
if description is not None:
|
|
326
316
|
payload["description"] = description
|
|
327
317
|
if option_sets is not None:
|
|
@@ -346,7 +336,6 @@ class ApplicationBatchMixin:
|
|
|
346
336
|
self: "Application",
|
|
347
337
|
name: str | None = None,
|
|
348
338
|
input_set_id: str | None = None,
|
|
349
|
-
instance_ids: list[str] | None = None,
|
|
350
339
|
description: str | None = None,
|
|
351
340
|
id: str | None = None,
|
|
352
341
|
option_sets: dict[str, dict[str, str]] | None = None,
|
|
@@ -368,9 +357,6 @@ class ApplicationBatchMixin:
|
|
|
368
357
|
Name of the batch experiment. If not provided, the ID will be used as the name.
|
|
369
358
|
input_set_id: str
|
|
370
359
|
ID of the input set to use for the batch experiment.
|
|
371
|
-
instance_ids: list[str]
|
|
372
|
-
List of instance IDs to use for the batch experiment. This argument
|
|
373
|
-
is deprecated, use `runs` instead.
|
|
374
360
|
description: Optional[str]
|
|
375
361
|
Optional description of the batch experiment.
|
|
376
362
|
id: Optional[str]
|
|
@@ -402,7 +388,6 @@ class ApplicationBatchMixin:
|
|
|
402
388
|
batch_id = self.new_batch_experiment(
|
|
403
389
|
name=name,
|
|
404
390
|
input_set_id=input_set_id,
|
|
405
|
-
instance_ids=instance_ids,
|
|
406
391
|
description=description,
|
|
407
392
|
id=id,
|
|
408
393
|
option_sets=option_sets,
|
|
@@ -485,7 +470,7 @@ class ApplicationBatchMixin:
|
|
|
485
470
|
id = safe_id("scenario")
|
|
486
471
|
|
|
487
472
|
# Use ID as name if name not provided
|
|
488
|
-
if name is None:
|
|
473
|
+
if name is None or name == "":
|
|
489
474
|
name = id
|
|
490
475
|
|
|
491
476
|
scenarios_by_id = _scenarios_by_id(scenarios)
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from nextmv.cloud.input_set import InputSet, ManagedInput
|
|
9
|
+
from nextmv.safe import safe_id
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from . import Application
|
|
@@ -16,6 +17,32 @@ class ApplicationInputSetMixin:
|
|
|
16
17
|
Mixin class for managing app input sets within an application.
|
|
17
18
|
"""
|
|
18
19
|
|
|
20
|
+
def delete_input_set(self: "Application", input_set_id: str) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Delete an input set.
|
|
23
|
+
|
|
24
|
+
Deletes an input set along with all the associated information.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
input_set_id : str
|
|
29
|
+
ID of the input set to delete.
|
|
30
|
+
|
|
31
|
+
Raises
|
|
32
|
+
------
|
|
33
|
+
requests.HTTPError
|
|
34
|
+
If the response status code is not 2xx.
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> app.delete_input_set("input-set-123")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_ = self.client.request(
|
|
42
|
+
method="DELETE",
|
|
43
|
+
endpoint=f"{self.experiments_endpoint}/inputsets/{input_set_id}",
|
|
44
|
+
)
|
|
45
|
+
|
|
19
46
|
def input_set(self: "Application", input_set_id: str) -> InputSet:
|
|
20
47
|
"""
|
|
21
48
|
Get an input set.
|
|
@@ -81,8 +108,8 @@ class ApplicationInputSetMixin:
|
|
|
81
108
|
|
|
82
109
|
def new_input_set(
|
|
83
110
|
self: "Application",
|
|
84
|
-
id: str,
|
|
85
|
-
name: str,
|
|
111
|
+
id: str | None = None,
|
|
112
|
+
name: str | None = None,
|
|
86
113
|
description: str | None = None,
|
|
87
114
|
end_time: datetime | None = None,
|
|
88
115
|
instance_id: str | None = None,
|
|
@@ -107,10 +134,11 @@ class ApplicationInputSetMixin:
|
|
|
107
134
|
|
|
108
135
|
Parameters
|
|
109
136
|
----------
|
|
110
|
-
id: str
|
|
111
|
-
ID of the input set
|
|
112
|
-
name: str
|
|
113
|
-
Name of the input set.
|
|
137
|
+
id: str | None = None
|
|
138
|
+
ID of the input set, will be generated if not provided.
|
|
139
|
+
name: str | None = None
|
|
140
|
+
Name of the input set. If not provided, the ID will be used as
|
|
141
|
+
the name.
|
|
114
142
|
description: Optional[str]
|
|
115
143
|
Optional description of the input set.
|
|
116
144
|
end_time: Optional[datetime]
|
|
@@ -145,6 +173,14 @@ class ApplicationInputSetMixin:
|
|
|
145
173
|
If the response status code is not 2xx.
|
|
146
174
|
"""
|
|
147
175
|
|
|
176
|
+
# Generate ID if not provided
|
|
177
|
+
if id is None or id == "":
|
|
178
|
+
id = safe_id("input-set")
|
|
179
|
+
|
|
180
|
+
# Use ID as name if name not provided
|
|
181
|
+
if name is None or name == "":
|
|
182
|
+
name = id
|
|
183
|
+
|
|
148
184
|
payload = {
|
|
149
185
|
"id": id,
|
|
150
186
|
"name": name,
|
|
@@ -4,7 +4,7 @@ Application mixin for managing shadow tests.
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from nextmv.cloud.shadow import ShadowTest, ShadowTestMetadata, StartEvents, TerminationEvents
|
|
7
|
+
from nextmv.cloud.shadow import ShadowTest, ShadowTestMetadata, StartEvents, StopIntent, TerminationEvents
|
|
8
8
|
from nextmv.run import Run
|
|
9
9
|
from nextmv.safe import safe_id
|
|
10
10
|
|
|
@@ -204,7 +204,7 @@ class ApplicationShadowMixin:
|
|
|
204
204
|
shadow_test_id = safe_id("shadow")
|
|
205
205
|
|
|
206
206
|
# Use ID as name if name not provided
|
|
207
|
-
if name is None:
|
|
207
|
+
if name is None or name == "":
|
|
208
208
|
name = shadow_test_id
|
|
209
209
|
|
|
210
210
|
payload = {
|
|
@@ -248,7 +248,7 @@ class ApplicationShadowMixin:
|
|
|
248
248
|
endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/start",
|
|
249
249
|
)
|
|
250
250
|
|
|
251
|
-
def stop_shadow_test(self: "Application", shadow_test_id: str) -> None:
|
|
251
|
+
def stop_shadow_test(self: "Application", shadow_test_id: str, intent: StopIntent) -> None:
|
|
252
252
|
"""
|
|
253
253
|
Stop a shadow test. The test should already have started before using
|
|
254
254
|
this method.
|
|
@@ -257,16 +257,22 @@ class ApplicationShadowMixin:
|
|
|
257
257
|
----------
|
|
258
258
|
shadow_test_id : str
|
|
259
259
|
ID of the shadow test to stop.
|
|
260
|
-
|
|
260
|
+
intent : StopIntent
|
|
261
|
+
Intent for stopping the shadow test.
|
|
261
262
|
Raises
|
|
262
263
|
------
|
|
263
264
|
requests.HTTPError
|
|
264
265
|
If the response status code is not 2xx.
|
|
265
266
|
"""
|
|
266
267
|
|
|
268
|
+
payload = {
|
|
269
|
+
"intent": intent.value,
|
|
270
|
+
}
|
|
271
|
+
|
|
267
272
|
_ = self.client.request(
|
|
268
273
|
method="PUT",
|
|
269
274
|
endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/stop",
|
|
275
|
+
payload=payload,
|
|
270
276
|
)
|
|
271
277
|
|
|
272
278
|
def update_shadow_test(
|
|
@@ -5,6 +5,7 @@ Application mixin for managing switchback tests.
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from nextmv.cloud.shadow import StopIntent
|
|
8
9
|
from nextmv.cloud.switchback import SwitchbackTest, SwitchbackTestMetadata, TestComparisonSingle
|
|
9
10
|
from nextmv.run import Run
|
|
10
11
|
from nextmv.safe import safe_id
|
|
@@ -210,7 +211,7 @@ class ApplicationSwitchbackMixin:
|
|
|
210
211
|
switchback_test_id = safe_id("switchback")
|
|
211
212
|
|
|
212
213
|
# Use ID as name if name not provided
|
|
213
|
-
if name is None:
|
|
214
|
+
if name is None or name == "":
|
|
214
215
|
name = switchback_test_id
|
|
215
216
|
|
|
216
217
|
payload = {
|
|
@@ -257,7 +258,7 @@ class ApplicationSwitchbackMixin:
|
|
|
257
258
|
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/start",
|
|
258
259
|
)
|
|
259
260
|
|
|
260
|
-
def stop_switchback_test(self: "Application", switchback_test_id: str) -> None:
|
|
261
|
+
def stop_switchback_test(self: "Application", switchback_test_id: str, intent: StopIntent) -> None:
|
|
261
262
|
"""
|
|
262
263
|
Stop a switchback test. The test should already have started before using
|
|
263
264
|
this method.
|
|
@@ -267,15 +268,23 @@ class ApplicationSwitchbackMixin:
|
|
|
267
268
|
switchback_test_id : str
|
|
268
269
|
ID of the switchback test to stop.
|
|
269
270
|
|
|
271
|
+
intent : StopIntent
|
|
272
|
+
Intent for stopping the switchback test.
|
|
273
|
+
|
|
270
274
|
Raises
|
|
271
275
|
------
|
|
272
276
|
requests.HTTPError
|
|
273
277
|
If the response status code is not 2xx.
|
|
274
278
|
"""
|
|
275
279
|
|
|
280
|
+
payload = {
|
|
281
|
+
"intent": intent.value,
|
|
282
|
+
}
|
|
283
|
+
|
|
276
284
|
_ = self.client.request(
|
|
277
285
|
method="PUT",
|
|
278
286
|
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/stop",
|
|
287
|
+
payload=payload,
|
|
279
288
|
)
|
|
280
289
|
|
|
281
290
|
def update_switchback_test(
|
nextmv/cloud/batch_experiment.py
CHANGED
|
@@ -30,7 +30,9 @@ class ExperimentStatus(str, Enum):
|
|
|
30
30
|
|
|
31
31
|
You can import the `ExperimentStatus` class directly from `cloud`:
|
|
32
32
|
|
|
33
|
-
```python
|
|
33
|
+
```python
|
|
34
|
+
from nextmv.cloud import ExperimentStatus
|
|
35
|
+
```
|
|
34
36
|
|
|
35
37
|
This enum represents the comprehensive set of possible states for an
|
|
36
38
|
experiment in Nextmv Cloud.
|