nextmv 1.0.0__py3-none-any.whl → 1.0.0.dev0__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 (140) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +2 -1
  3. nextmv/__init__.py +4 -0
  4. nextmv/cli/CONTRIBUTING.md +40 -112
  5. nextmv/cli/cloud/__init__.py +0 -4
  6. nextmv/cli/cloud/acceptance/create.py +22 -20
  7. nextmv/cli/cloud/acceptance/delete.py +12 -8
  8. nextmv/cli/cloud/acceptance/get.py +10 -9
  9. nextmv/cli/cloud/acceptance/list.py +3 -3
  10. nextmv/cli/cloud/acceptance/update.py +6 -6
  11. nextmv/cli/cloud/account/__init__.py +3 -3
  12. nextmv/cli/cloud/account/create.py +11 -11
  13. nextmv/cli/cloud/account/delete.py +8 -7
  14. nextmv/cli/cloud/account/get.py +3 -3
  15. nextmv/cli/cloud/account/update.py +5 -5
  16. nextmv/cli/cloud/app/create.py +26 -25
  17. nextmv/cli/cloud/app/delete.py +7 -6
  18. nextmv/cli/cloud/app/exists.py +2 -2
  19. nextmv/cli/cloud/app/get.py +2 -2
  20. nextmv/cli/cloud/app/list.py +3 -3
  21. nextmv/cli/cloud/app/push.py +54 -349
  22. nextmv/cli/cloud/app/update.py +12 -12
  23. nextmv/cli/cloud/batch/create.py +28 -26
  24. nextmv/cli/cloud/batch/delete.py +10 -6
  25. nextmv/cli/cloud/batch/get.py +9 -9
  26. nextmv/cli/cloud/batch/list.py +3 -3
  27. nextmv/cli/cloud/batch/metadata.py +4 -4
  28. nextmv/cli/cloud/batch/update.py +6 -6
  29. nextmv/cli/cloud/data/__init__.py +1 -1
  30. nextmv/cli/cloud/data/upload.py +15 -15
  31. nextmv/cli/cloud/ensemble/__init__.py +0 -2
  32. nextmv/cli/cloud/ensemble/create.py +22 -21
  33. nextmv/cli/cloud/ensemble/delete.py +10 -6
  34. nextmv/cli/cloud/ensemble/get.py +4 -4
  35. nextmv/cli/cloud/ensemble/update.py +9 -9
  36. nextmv/cli/cloud/input_set/__init__.py +0 -2
  37. nextmv/cli/cloud/input_set/create.py +22 -22
  38. nextmv/cli/cloud/input_set/get.py +3 -3
  39. nextmv/cli/cloud/input_set/list.py +3 -3
  40. nextmv/cli/cloud/input_set/update.py +24 -24
  41. nextmv/cli/cloud/instance/create.py +15 -14
  42. nextmv/cli/cloud/instance/delete.py +7 -6
  43. nextmv/cli/cloud/instance/exists.py +2 -2
  44. nextmv/cli/cloud/instance/get.py +2 -2
  45. nextmv/cli/cloud/instance/list.py +3 -3
  46. nextmv/cli/cloud/instance/update.py +14 -14
  47. nextmv/cli/cloud/managed_input/create.py +16 -14
  48. nextmv/cli/cloud/managed_input/delete.py +8 -7
  49. nextmv/cli/cloud/managed_input/get.py +3 -3
  50. nextmv/cli/cloud/managed_input/list.py +3 -3
  51. nextmv/cli/cloud/managed_input/update.py +9 -9
  52. nextmv/cli/cloud/run/cancel.py +2 -2
  53. nextmv/cli/cloud/run/create.py +40 -34
  54. nextmv/cli/cloud/run/get.py +8 -8
  55. nextmv/cli/cloud/run/input.py +4 -4
  56. nextmv/cli/cloud/run/list.py +6 -6
  57. nextmv/cli/cloud/run/logs.py +10 -9
  58. nextmv/cli/cloud/run/metadata.py +4 -4
  59. nextmv/cli/cloud/run/track.py +33 -32
  60. nextmv/cli/cloud/scenario/create.py +21 -21
  61. nextmv/cli/cloud/scenario/delete.py +10 -6
  62. nextmv/cli/cloud/scenario/get.py +9 -9
  63. nextmv/cli/cloud/scenario/list.py +3 -3
  64. nextmv/cli/cloud/scenario/metadata.py +4 -4
  65. nextmv/cli/cloud/scenario/update.py +6 -6
  66. nextmv/cli/cloud/secrets/create.py +17 -17
  67. nextmv/cli/cloud/secrets/delete.py +10 -6
  68. nextmv/cli/cloud/secrets/get.py +4 -4
  69. nextmv/cli/cloud/secrets/list.py +3 -3
  70. nextmv/cli/cloud/secrets/update.py +20 -17
  71. nextmv/cli/cloud/upload/create.py +2 -2
  72. nextmv/cli/cloud/version/create.py +10 -9
  73. nextmv/cli/cloud/version/delete.py +7 -6
  74. nextmv/cli/cloud/version/exists.py +2 -2
  75. nextmv/cli/cloud/version/get.py +2 -2
  76. nextmv/cli/cloud/version/list.py +3 -3
  77. nextmv/cli/cloud/version/update.py +8 -8
  78. nextmv/cli/community/__init__.py +1 -1
  79. nextmv/cli/community/clone.py +204 -20
  80. nextmv/cli/community/list.py +125 -60
  81. nextmv/cli/configuration/config.py +10 -43
  82. nextmv/cli/configuration/create.py +7 -7
  83. nextmv/cli/configuration/delete.py +8 -8
  84. nextmv/cli/configuration/list.py +3 -3
  85. nextmv/cli/main.py +36 -26
  86. nextmv/cli/message.py +54 -71
  87. nextmv/cli/options.py +0 -28
  88. nextmv/cli/version.py +1 -1
  89. nextmv/cloud/__init__.py +38 -14
  90. nextmv/cloud/acceptance_test.py +65 -1
  91. nextmv/cloud/account.py +6 -1
  92. nextmv/cloud/application/__init__.py +75 -18
  93. nextmv/cloud/application/_acceptance.py +8 -13
  94. nextmv/cloud/application/_batch_scenario.py +19 -4
  95. nextmv/cloud/application/_input_set.py +6 -42
  96. nextmv/cloud/application/_instance.py +3 -3
  97. nextmv/cloud/application/_managed_input.py +2 -2
  98. nextmv/cloud/application/_version.py +3 -4
  99. nextmv/cloud/batch_experiment.py +1 -3
  100. nextmv/cloud/integration.py +4 -7
  101. nextmv/deprecated.py +3 -5
  102. nextmv/input.py +52 -0
  103. nextmv/local/runner.py +1 -1
  104. nextmv/model.py +11 -50
  105. nextmv/options.py +256 -11
  106. nextmv/output.py +62 -0
  107. nextmv/run.py +10 -1
  108. nextmv/status.py +51 -1
  109. {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/METADATA +4 -5
  110. nextmv-1.0.0.dev0.dist-info/RECORD +158 -0
  111. nextmv/cli/cloud/ensemble/list.py +0 -63
  112. nextmv/cli/cloud/input_set/delete.py +0 -64
  113. nextmv/cli/cloud/shadow/__init__.py +0 -33
  114. nextmv/cli/cloud/shadow/create.py +0 -184
  115. nextmv/cli/cloud/shadow/delete.py +0 -64
  116. nextmv/cli/cloud/shadow/get.py +0 -61
  117. nextmv/cli/cloud/shadow/list.py +0 -63
  118. nextmv/cli/cloud/shadow/metadata.py +0 -66
  119. nextmv/cli/cloud/shadow/start.py +0 -43
  120. nextmv/cli/cloud/shadow/stop.py +0 -53
  121. nextmv/cli/cloud/shadow/update.py +0 -96
  122. nextmv/cli/cloud/switchback/__init__.py +0 -33
  123. nextmv/cli/cloud/switchback/create.py +0 -151
  124. nextmv/cli/cloud/switchback/delete.py +0 -64
  125. nextmv/cli/cloud/switchback/get.py +0 -62
  126. nextmv/cli/cloud/switchback/list.py +0 -63
  127. nextmv/cli/cloud/switchback/metadata.py +0 -68
  128. nextmv/cli/cloud/switchback/start.py +0 -43
  129. nextmv/cli/cloud/switchback/stop.py +0 -53
  130. nextmv/cli/cloud/switchback/update.py +0 -96
  131. nextmv/cli/confirm.py +0 -34
  132. nextmv/cloud/application/_shadow.py +0 -320
  133. nextmv/cloud/application/_switchback.py +0 -332
  134. nextmv/cloud/community.py +0 -446
  135. nextmv/cloud/shadow.py +0 -254
  136. nextmv/cloud/switchback.py +0 -228
  137. nextmv-1.0.0.dist-info/RECORD +0 -185
  138. nextmv-1.0.0.dist-info/entry_points.txt +0 -2
  139. {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/WHEEL +0 -0
  140. {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
@@ -1,63 +0,0 @@
1
- """
2
- This module defines the cloud switchback list command for the Nextmv CLI.
3
- """
4
-
5
- import json
6
- from typing import Annotated
7
-
8
- import typer
9
-
10
- from nextmv.cli.configuration.config import build_app
11
- from nextmv.cli.message import in_progress, print_json, success
12
- from nextmv.cli.options import AppIDOption, ProfileOption
13
-
14
- # Set up subcommand application.
15
- app = typer.Typer()
16
-
17
-
18
- @app.command()
19
- def list(
20
- app_id: AppIDOption,
21
- output: Annotated[
22
- str | None,
23
- typer.Option(
24
- "--output",
25
- "-o",
26
- help="Saves the list of switchback tests to this location.",
27
- metavar="OUTPUT_PATH",
28
- ),
29
- ] = None,
30
- profile: ProfileOption = None,
31
- ) -> None:
32
- """
33
- List all Nextmv Cloud switchback tests for an application.
34
-
35
- This command retrieves all switchback tests associated with the specified
36
- application.
37
-
38
- [bold][underline]Examples[/underline][/bold]
39
-
40
- - List all switchback tests for application [magenta]hare-app[/magenta].
41
- $ [dim]nextmv cloud switchback list --app-id hare-app[/dim]
42
-
43
- - List all switchback tests and save to a file.
44
- $ [dim]nextmv cloud switchback list --app-id hare-app --output tests.json[/dim]
45
-
46
- - List all switchback tests using a specific profile.
47
- $ [dim]nextmv cloud switchback list --app-id hare-app --profile prod[/dim]
48
- """
49
-
50
- cloud_app = build_app(app_id=app_id, profile=profile)
51
- in_progress(msg="Listing switchback tests...")
52
- switchback_tests = cloud_app.list_switchback_tests()
53
- switchback_tests_dict = [test.to_dict() for test in switchback_tests]
54
-
55
- if output is not None and output != "":
56
- with open(output, "w") as f:
57
- json.dump(switchback_tests_dict, f, indent=2)
58
-
59
- success(msg=f"Switchback tests list saved to [magenta]{output}[/magenta].")
60
-
61
- return
62
-
63
- print_json(switchback_tests_dict)
@@ -1,68 +0,0 @@
1
- """
2
- This module defines the cloud switchback metadata command for the Nextmv CLI.
3
- """
4
-
5
- import json
6
- from typing import Annotated
7
-
8
- import typer
9
-
10
- from nextmv.cli.configuration.config import build_app
11
- from nextmv.cli.message import in_progress, print_json, success
12
- from nextmv.cli.options import AppIDOption, ProfileOption, SwitchbackTestIDOption
13
-
14
- # Set up subcommand application.
15
- app = typer.Typer()
16
-
17
-
18
- @app.command()
19
- def metadata(
20
- app_id: AppIDOption,
21
- switchback_test_id: SwitchbackTestIDOption,
22
- output: Annotated[
23
- str | None,
24
- typer.Option(
25
- "--output",
26
- "-o",
27
- help="Saves the switchback test metadata to this location.",
28
- metavar="OUTPUT_PATH",
29
- ),
30
- ] = None,
31
- profile: ProfileOption = None,
32
- ) -> None:
33
- """
34
- Get metadata for a Nextmv Cloud switchback test.
35
-
36
- This command retrieves metadata for a specific switchback test, including
37
- status, creation date, and other high-level information without the full
38
- run details.
39
-
40
- [bold][underline]Examples[/underline][/bold]
41
-
42
- - Get metadata for switchback test [magenta]bunny-warren-optimization[/magenta] from application
43
- [magenta]hare-app[/magenta].
44
- $ [dim]nextmv cloud switchback metadata --app-id hare-app \\
45
- --switchback-test-id bunny-warren-optimization[/dim]
46
-
47
- - Get metadata and save to a file.
48
- $ [dim]nextmv cloud switchback metadata --app-id hare-app --switchback-test-id lettuce-delivery \\
49
- --output metadata.json[/dim]
50
-
51
- - Get metadata using a specific profile.
52
- $ [dim]nextmv cloud switchback metadata --app-id hare-app --switchback-test-id hop-schedule \\
53
- --profile prod[/dim]
54
- """
55
-
56
- cloud_app = build_app(app_id=app_id, profile=profile)
57
- in_progress(msg="Getting switchback test metadata...")
58
- switchback_metadata = cloud_app.switchback_test_metadata(switchback_test_id=switchback_test_id)
59
- switchback_metadata_dict = switchback_metadata.to_dict()
60
-
61
- if output is not None and output != "":
62
- with open(output, "w") as f:
63
- json.dump(switchback_metadata_dict, f, indent=2)
64
-
65
- success(msg=f"Switchback test metadata saved to [magenta]{output}[/magenta].")
66
- return
67
-
68
- print_json(switchback_metadata_dict)
@@ -1,43 +0,0 @@
1
- """
2
- This module defines the cloud switchback start command for the Nextmv CLI.
3
- """
4
-
5
- import typer
6
-
7
- from nextmv.cli.configuration.config import build_app
8
- from nextmv.cli.message import in_progress, success
9
- from nextmv.cli.options import AppIDOption, ProfileOption, SwitchbackTestIDOption
10
-
11
- # Set up subcommand application.
12
- app = typer.Typer()
13
-
14
-
15
- @app.command()
16
- def start(
17
- app_id: AppIDOption,
18
- switchback_test_id: SwitchbackTestIDOption,
19
- profile: ProfileOption = None,
20
- ) -> None:
21
- """
22
- Starts a Nextmv Cloud switchback test.
23
-
24
- Before starting a switchback test, it must be created in draft state. You
25
- may use the [code]nextmv cloud switchback create[/code] command to create a
26
- new switchback test. Alternatively, define a --start when using the
27
- [code]nextmv cloud switchback create[/code] command to have the switchback
28
- test start automatically at a specific time.
29
-
30
- [bold][underline]Examples[/underline][/bold]
31
-
32
- - Start the switchback test with the ID [magenta]hop-analysis[/magenta] from application
33
- [magenta]hare-app[/magenta].
34
- $ [dim]nextmv cloud switchback start --app-id hare-app --switchback-test-id hop-analysis[/dim]
35
- """
36
-
37
- in_progress(msg="Starting switchback test...")
38
- cloud_app = build_app(app_id=app_id, profile=profile)
39
- cloud_app.start_switchback_test(switchback_test_id=switchback_test_id)
40
- success(
41
- f"Switchback test [magenta]{switchback_test_id}[/magenta] started successfully "
42
- f"in application [magenta]{app_id}[/magenta]."
43
- )
@@ -1,53 +0,0 @@
1
- """
2
- This module defines the cloud switchback stop command for the Nextmv CLI.
3
- """
4
-
5
- from typing import Annotated
6
-
7
- import typer
8
-
9
- from nextmv.cli.configuration.config import build_app
10
- from nextmv.cli.message import enum_values, in_progress, success
11
- from nextmv.cli.options import AppIDOption, ProfileOption, SwitchbackTestIDOption
12
- from nextmv.cloud.shadow import StopIntent
13
-
14
- # Set up subcommand application.
15
- app = typer.Typer()
16
-
17
-
18
- @app.command()
19
- def stop(
20
- app_id: AppIDOption,
21
- intent: Annotated[
22
- StopIntent,
23
- typer.Option(
24
- "--intent",
25
- "-i",
26
- help=f"Intent for stopping the switchback test. Allowed values are: {enum_values(StopIntent)}.",
27
- metavar="INTENT",
28
- ),
29
- ],
30
- switchback_test_id: SwitchbackTestIDOption,
31
- profile: ProfileOption = None,
32
- ) -> None:
33
- """
34
- Stops a Nextmv Cloud switchback test.
35
-
36
- Before stopping a switchback test, it must be in a started state. Experiments
37
- in a [magenta]draft[/magenta] state, that haven't started, can be deleted
38
- with the [code]nextmv cloud switchback delete[/code] command.
39
-
40
- [bold][underline]Examples[/underline][/bold]
41
-
42
- - Stop the switchback test with the ID [magenta]hop-analysis[/magenta] from application
43
- [magenta]hare-app[/magenta].
44
- $ [dim]nextmv cloud switchback stop --app-id hare-app --switchback-test-id hop-analysis[/dim]
45
- """
46
-
47
- in_progress(msg="Stopping switchback test...")
48
- cloud_app = build_app(app_id=app_id, profile=profile)
49
- cloud_app.stop_switchback_test(switchback_test_id=switchback_test_id, intent=StopIntent(intent))
50
- success(
51
- f"Switchback test [magenta]{switchback_test_id}[/magenta] stopped successfully "
52
- f"in application [magenta]{app_id}[/magenta]."
53
- )
@@ -1,96 +0,0 @@
1
- """
2
- This module defines the cloud switchback update command for the Nextmv CLI.
3
- """
4
-
5
- import json
6
- from typing import Annotated
7
-
8
- import typer
9
-
10
- from nextmv.cli.configuration.config import build_app
11
- from nextmv.cli.message import in_progress, print_json, success
12
- from nextmv.cli.options import AppIDOption, ProfileOption, SwitchbackTestIDOption
13
-
14
- # Set up subcommand application.
15
- app = typer.Typer()
16
-
17
-
18
- @app.command()
19
- def update(
20
- app_id: AppIDOption,
21
- switchback_test_id: SwitchbackTestIDOption,
22
- description: Annotated[
23
- str | None,
24
- typer.Option(
25
- "--description",
26
- "-d",
27
- help="Updated description of the switchback test.",
28
- metavar="DESCRIPTION",
29
- ),
30
- ] = None,
31
- name: Annotated[
32
- str | None,
33
- typer.Option(
34
- "--name",
35
- "-n",
36
- help="Updated name of the switchback test.",
37
- metavar="NAME",
38
- ),
39
- ] = None,
40
- output: Annotated[
41
- str | None,
42
- typer.Option(
43
- "--output",
44
- "-o",
45
- help="Saves the updated switchback test information to this location.",
46
- metavar="OUTPUT_PATH",
47
- ),
48
- ] = None,
49
- profile: ProfileOption = None,
50
- ) -> None:
51
- """
52
- Update a Nextmv Cloud switchback test.
53
-
54
- Update the name and/or description of a switchback test. Any fields not
55
- specified will remain unchanged.
56
-
57
- [bold][underline]Examples[/underline][/bold]
58
-
59
- - Update the name of a switchback test.
60
- $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id carrot-feast \\
61
- --name "Spring Carrot Harvest"[/dim]
62
-
63
- - Update the description of a switchback test.
64
- $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id bunny-hop-routes \\
65
- --description "Optimizing hop paths through the meadow"[/dim]
66
-
67
- - Update both name and description and save the result.
68
- $ [dim]nextmv cloud switchback update --app-id hare-app --switchback-test-id lettuce-delivery \\
69
- --name "Warren Lettuce Express" --description "Fast lettuce delivery to all burrows" \\
70
- --output updated-switchback-test.json[/dim]
71
- """
72
-
73
- cloud_app = build_app(app_id=app_id, profile=profile)
74
-
75
- in_progress(msg="Updating switchback test...")
76
- switchback_test = cloud_app.update_switchback_test(
77
- switchback_test_id=switchback_test_id,
78
- name=name,
79
- description=description,
80
- )
81
-
82
- switchback_test_dict = switchback_test.to_dict()
83
- success(
84
- f"Switchback test [magenta]{switchback_test_id}[/magenta] updated successfully "
85
- f"in application [magenta]{app_id}[/magenta]."
86
- )
87
-
88
- if output is not None and output != "":
89
- with open(output, "w") as f:
90
- json.dump(switchback_test_dict, f, indent=2)
91
-
92
- success(msg=f"Updated switchback test information saved to [magenta]{output}[/magenta].")
93
-
94
- return
95
-
96
- print_json(switchback_test_dict)
nextmv/cli/confirm.py DELETED
@@ -1,34 +0,0 @@
1
- import sys
2
-
3
- from rich.prompt import Confirm
4
-
5
-
6
- def get_confirmation(msg: str, default: bool = False) -> bool:
7
- """
8
- Method to get a yes/no confirmation from the user.
9
-
10
- Parameters
11
- ----------
12
- msg : str
13
- The message to display to the user.
14
- default : bool, optional
15
- The default value if the user just presses Enter. Default is False.
16
-
17
- Returns
18
- -------
19
- bool
20
- True if the user confirmed, False otherwise.
21
- """
22
-
23
- # If this is not an interactive terminal, do not ask for confirmation, to
24
- # avoid hanging indefinitely waiting for a user response.
25
- if not sys.stdin.isatty():
26
- return default
27
-
28
- return Confirm.ask(
29
- msg,
30
- default=default,
31
- case_sensitive=False,
32
- show_default=True,
33
- show_choices=True,
34
- )
@@ -1,320 +0,0 @@
1
- """
2
- Application mixin for managing shadow tests.
3
- """
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- from nextmv.cloud.shadow import ShadowTest, ShadowTestMetadata, StartEvents, StopIntent, TerminationEvents
8
- from nextmv.run import Run
9
- from nextmv.safe import safe_id
10
-
11
- if TYPE_CHECKING:
12
- from . import Application
13
-
14
-
15
- class ApplicationShadowMixin:
16
- """
17
- Mixin class for managing shadow tests within an application.
18
- """
19
-
20
- def shadow_test(self: "Application", shadow_test_id: str) -> ShadowTest:
21
- """
22
- Get a shadow test. This method also returns the runs of the shadow
23
- test under the `.runs` attribute.
24
-
25
- Parameters
26
- ----------
27
- shadow_test_id : str
28
- ID of the shadow test.
29
-
30
- Returns
31
- -------
32
- ShadowTest
33
- The requested shadow test details.
34
-
35
- Raises
36
- ------
37
- requests.HTTPError
38
- If the response status code is not 2xx.
39
-
40
- Examples
41
- --------
42
- >>> shadow_test = app.shadow_test("shadow-123")
43
- >>> print(shadow_test.name)
44
- 'My Shadow Test'
45
- """
46
-
47
- response = self.client.request(
48
- method="GET",
49
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}",
50
- )
51
-
52
- exp = ShadowTest.from_dict(response.json())
53
-
54
- runs_response = self.client.request(
55
- method="GET",
56
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/runs",
57
- )
58
-
59
- runs = [Run.from_dict(run) for run in runs_response.json().get("runs", [])]
60
- exp.runs = runs
61
-
62
- return exp
63
-
64
- def shadow_test_metadata(self: "Application", shadow_test_id: str) -> ShadowTestMetadata:
65
- """
66
- Get metadata for a shadow test.
67
-
68
- Parameters
69
- ----------
70
- shadow_test_id : str
71
- ID of the shadow test.
72
-
73
- Returns
74
- -------
75
- ShadowTestMetadata
76
- The requested shadow test metadata.
77
-
78
- Raises
79
- ------
80
- requests.HTTPError
81
- If the response status code is not 2xx.
82
-
83
- Examples
84
- --------
85
- >>> metadata = app.shadow_test_metadata("shadow-123")
86
- >>> print(metadata.name)
87
- 'My Shadow Test'
88
- """
89
-
90
- response = self.client.request(
91
- method="GET",
92
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/metadata",
93
- )
94
-
95
- return ShadowTestMetadata.from_dict(response.json())
96
-
97
- def delete_shadow_test(self: "Application", shadow_test_id: str) -> None:
98
- """
99
- Delete a shadow test.
100
-
101
- Deletes a shadow test along with all the associated information,
102
- such as its runs.
103
-
104
- Parameters
105
- ----------
106
- shadow_test_id : str
107
- ID of the shadow test to delete.
108
-
109
- Raises
110
- ------
111
- requests.HTTPError
112
- If the response status code is not 2xx.
113
-
114
- Examples
115
- --------
116
- >>> app.delete_shadow_test("shadow-123")
117
- """
118
-
119
- _ = self.client.request(
120
- method="DELETE",
121
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}",
122
- )
123
-
124
- def list_shadow_tests(self: "Application") -> list[ShadowTest]:
125
- """
126
- List all shadow tests.
127
-
128
- Returns
129
- -------
130
- list[ShadowTest]
131
- List of shadow tests.
132
-
133
- Raises
134
- ------
135
- requests.HTTPError
136
- If the response status code is not 2xx.
137
- """
138
-
139
- response = self.client.request(
140
- method="GET",
141
- endpoint=f"{self.experiments_endpoint}/shadow",
142
- )
143
-
144
- return [ShadowTest.from_dict(shadow_test) for shadow_test in response.json()]
145
-
146
- def new_shadow_test(
147
- self: "Application",
148
- comparisons: dict[str, list[str]],
149
- termination_events: TerminationEvents,
150
- shadow_test_id: str | None = None,
151
- name: str | None = None,
152
- description: str | None = None,
153
- start_events: StartEvents | None = None,
154
- ) -> ShadowTest:
155
- """
156
- Create a new shadow test in draft mode. Shadow tests are experiments
157
- that run instances in parallel to compare their results.
158
-
159
- Use the `comparisons` parameter to define how to set up instance
160
- comparisons. The keys of the `comparisons` dictionary are the baseline
161
- instance IDs, and the values are the candidate lists of instance IDs to
162
- compare against the respective baseline.
163
-
164
- You may specify `start_events` to make the shadow test start at a
165
- specific time. Alternatively, you may use the `start_shadow_test`
166
- method to start the test.
167
-
168
- The `termination_events` parameter is required and provides control
169
- over when the shadow test should terminate. Alternatively, you may use
170
- the `stop_shadow_test` method to stop the test.
171
-
172
- Parameters
173
- ----------
174
- comparisons : dict[str, list[str]]
175
- Dictionary defining the baseline and candidate instance IDs for
176
- comparison. The keys are baseline instance IDs, and the values are
177
- lists of candidate instance IDs to compare against the respective
178
- baseline.
179
- termination_events : TerminationEvents
180
- Termination events for the shadow test.
181
- shadow_test_id : Optional[str]
182
- ID of the shadow test. Will be generated if not provided.
183
- name : Optional[str]
184
- Name of the shadow test. If not provided, the ID will be used as
185
- the name.
186
- description : Optional[str]
187
- Optional description of the shadow test.
188
- start_events : Optional[StartEvents]
189
- Start events for the shadow test.
190
-
191
- Returns
192
- -------
193
- ShadowTest
194
- The created shadow test.
195
-
196
- Raises
197
- ------
198
- requests.HTTPError
199
- If the response status code is not 2xx.
200
- """
201
-
202
- # Generate ID if not provided
203
- if shadow_test_id is None:
204
- shadow_test_id = safe_id("shadow")
205
-
206
- # Use ID as name if name not provided
207
- if name is None or name == "":
208
- name = shadow_test_id
209
-
210
- payload = {
211
- "id": shadow_test_id,
212
- "name": name,
213
- "comparisons": comparisons,
214
- "termination_events": termination_events.to_dict(),
215
- }
216
-
217
- if description is not None:
218
- payload["description"] = description
219
- if start_events is not None:
220
- payload["start_events"] = start_events.to_dict()
221
-
222
- response = self.client.request(
223
- method="POST",
224
- endpoint=f"{self.experiments_endpoint}/shadow",
225
- payload=payload,
226
- )
227
-
228
- return ShadowTest.from_dict(response.json())
229
-
230
- def start_shadow_test(self: "Application", shadow_test_id: str) -> None:
231
- """
232
- Start a shadow test. Create a shadow test in draft mode using the
233
- `new_shadow_test` method, then use this method to start the test.
234
-
235
- Parameters
236
- ----------
237
- shadow_test_id : str
238
- ID of the shadow test to start.
239
-
240
- Raises
241
- ------
242
- requests.HTTPError
243
- If the response status code is not 2xx.
244
- """
245
-
246
- _ = self.client.request(
247
- method="PUT",
248
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/start",
249
- )
250
-
251
- def stop_shadow_test(self: "Application", shadow_test_id: str, intent: StopIntent) -> None:
252
- """
253
- Stop a shadow test. The test should already have started before using
254
- this method.
255
-
256
- Parameters
257
- ----------
258
- shadow_test_id : str
259
- ID of the shadow test to stop.
260
- intent : StopIntent
261
- Intent for stopping the shadow test.
262
- Raises
263
- ------
264
- requests.HTTPError
265
- If the response status code is not 2xx.
266
- """
267
-
268
- payload = {
269
- "intent": intent.value,
270
- }
271
-
272
- _ = self.client.request(
273
- method="PUT",
274
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/stop",
275
- payload=payload,
276
- )
277
-
278
- def update_shadow_test(
279
- self: "Application",
280
- shadow_test_id: str,
281
- name: str | None = None,
282
- description: str | None = None,
283
- ) -> ShadowTest:
284
- """
285
- Update a shadow test.
286
-
287
- Parameters
288
- ----------
289
- shadow_test_id : str
290
- ID of the shadow test to update.
291
- name : Optional[str], default=None
292
- Optional name of the shadow test.
293
- description : Optional[str], default=None
294
- Optional description of the shadow test.
295
-
296
- Returns
297
- -------
298
- ShadowTest
299
- The information with the updated shadow test.
300
-
301
- Raises
302
- ------
303
- requests.HTTPError
304
- If the response status code is not 2xx.
305
- """
306
-
307
- payload = {}
308
-
309
- if name is not None:
310
- payload["name"] = name
311
- if description is not None:
312
- payload["description"] = description
313
-
314
- response = self.client.request(
315
- method="PATCH",
316
- endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}",
317
- payload=payload,
318
- )
319
-
320
- return ShadowTest.from_dict(response.json())