nextmv 1.0.0.dev1__py3-none-any.whl → 1.0.0.dev3__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/cloud/__init__.py +4 -0
- nextmv/cli/cloud/batch/get.py +1 -1
- nextmv/cli/cloud/input_set/create.py +5 -3
- nextmv/cli/cloud/input_set/update.py +1 -1
- nextmv/cli/cloud/scenario/get.py +1 -1
- nextmv/cli/cloud/secrets/update.py +1 -1
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +96 -0
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +147 -0
- nextmv/cli/cloud/switchback/delete.py +68 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +43 -0
- nextmv/cli/cloud/switchback/update.py +96 -0
- nextmv/cli/options.py +28 -0
- nextmv/cloud/__init__.py +10 -0
- nextmv/cloud/application/__init__.py +4 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_switchback.py +315 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/switchback.py +189 -0
- {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/METADATA +1 -1
- {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/RECORD +37 -15
- {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
$ [green]nextmv cloud switchback update --app-id hare-app --switchback-test-id carrot-feast \\
|
|
61
|
+
--name "Spring Carrot Harvest"[/green]
|
|
62
|
+
|
|
63
|
+
- Update the description of a switchback test.
|
|
64
|
+
$ [green]nextmv cloud switchback update --app-id hare-app --switchback-test-id bunny-hop-routes \\
|
|
65
|
+
--description "Optimizing hop paths through the meadow"[/green]
|
|
66
|
+
|
|
67
|
+
- Update both name and description and save the result.
|
|
68
|
+
$ [green]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[/green]
|
|
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/options.py
CHANGED
|
@@ -190,3 +190,31 @@ SecretsCollectionIDOption = Annotated[
|
|
|
190
190
|
metavar="SECRETS_COLLECTION_ID",
|
|
191
191
|
),
|
|
192
192
|
]
|
|
193
|
+
|
|
194
|
+
# shadow_test_id option - can be used in any command that requires a shadow test ID.
|
|
195
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
196
|
+
# shadow_test_id: ShadowTestIDOption
|
|
197
|
+
ShadowTestIDOption = Annotated[
|
|
198
|
+
str,
|
|
199
|
+
typer.Option(
|
|
200
|
+
"--shadow-test-id",
|
|
201
|
+
"-s",
|
|
202
|
+
help="The Nextmv Cloud shadow test ID to use for this action.",
|
|
203
|
+
envvar="NEXTMV_SHADOW_TEST_ID",
|
|
204
|
+
metavar="SHADOW_TEST_ID",
|
|
205
|
+
),
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
# switchback_test_id option - can be used in any command that requires a switchback test ID.
|
|
209
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
210
|
+
# switchback_test_id: SwitchbackTestIDOption
|
|
211
|
+
SwitchbackTestIDOption = Annotated[
|
|
212
|
+
str,
|
|
213
|
+
typer.Option(
|
|
214
|
+
"--switchback-test-id",
|
|
215
|
+
"-s",
|
|
216
|
+
help="The Nextmv Cloud switchback test ID to use for this action.",
|
|
217
|
+
envvar="NEXTMV_SWITCHBACK_TEST_ID",
|
|
218
|
+
metavar="SWITCHBACK_TEST_ID",
|
|
219
|
+
),
|
|
220
|
+
]
|
nextmv/cloud/__init__.py
CHANGED
|
@@ -90,6 +90,16 @@ from .secrets import Secret as Secret
|
|
|
90
90
|
from .secrets import SecretsCollection as SecretsCollection
|
|
91
91
|
from .secrets import SecretsCollectionSummary as SecretsCollectionSummary
|
|
92
92
|
from .secrets import SecretType as SecretType
|
|
93
|
+
from .shadow import ShadowTest as ShadowTest
|
|
94
|
+
from .shadow import ShadowTestMetadata as ShadowTestMetadata
|
|
95
|
+
from .shadow import StartEvents as StartEvents
|
|
96
|
+
from .shadow import TerminationEvents as TerminationEvents
|
|
97
|
+
from .shadow import TestComparison as TestComparison
|
|
98
|
+
from .switchback import SwitchbackPlan as SwitchbackPlan
|
|
99
|
+
from .switchback import SwitchbackPlanUnit as SwitchbackPlanUnit
|
|
100
|
+
from .switchback import SwitchbackTest as SwitchbackTest
|
|
101
|
+
from .switchback import SwitchbackTestMetadata as SwitchbackTestMetadata
|
|
102
|
+
from .switchback import TestComparisonSingle as TestComparisonSingle
|
|
93
103
|
from .url import DownloadURL as DownloadURL
|
|
94
104
|
from .url import UploadURL as UploadURL
|
|
95
105
|
from .version import Version as Version
|
|
@@ -41,6 +41,8 @@ from nextmv.cloud.application._instance import ApplicationInstanceMixin
|
|
|
41
41
|
from nextmv.cloud.application._managed_input import ApplicationManagedInputMixin
|
|
42
42
|
from nextmv.cloud.application._run import ApplicationRunMixin
|
|
43
43
|
from nextmv.cloud.application._secrets import ApplicationSecretsMixin
|
|
44
|
+
from nextmv.cloud.application._shadow import ApplicationShadowMixin
|
|
45
|
+
from nextmv.cloud.application._switchback import ApplicationSwitchbackMixin
|
|
44
46
|
from nextmv.cloud.application._utils import _is_not_exist_error
|
|
45
47
|
from nextmv.cloud.application._version import ApplicationVersionMixin
|
|
46
48
|
from nextmv.cloud.client import Client
|
|
@@ -100,6 +102,8 @@ class Application(
|
|
|
100
102
|
ApplicationVersionMixin,
|
|
101
103
|
ApplicationInputSetMixin,
|
|
102
104
|
ApplicationManagedInputMixin,
|
|
105
|
+
ApplicationShadowMixin,
|
|
106
|
+
ApplicationSwitchbackMixin,
|
|
103
107
|
):
|
|
104
108
|
"""
|
|
105
109
|
A published decision model that can be executed.
|
|
@@ -0,0 +1,314 @@
|
|
|
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, 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:
|
|
208
|
+
name = shadow_test_id
|
|
209
|
+
|
|
210
|
+
payload = {
|
|
211
|
+
"id": 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) -> 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
|
+
|
|
261
|
+
Raises
|
|
262
|
+
------
|
|
263
|
+
requests.HTTPError
|
|
264
|
+
If the response status code is not 2xx.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
_ = self.client.request(
|
|
268
|
+
method="PUT",
|
|
269
|
+
endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}/stop",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def update_shadow_test(
|
|
273
|
+
self: "Application",
|
|
274
|
+
shadow_test_id: str,
|
|
275
|
+
name: str | None = None,
|
|
276
|
+
description: str | None = None,
|
|
277
|
+
) -> ShadowTest:
|
|
278
|
+
"""
|
|
279
|
+
Update a shadow test.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
shadow_test_id : str
|
|
284
|
+
ID of the shadow test to update.
|
|
285
|
+
name : Optional[str], default=None
|
|
286
|
+
Optional name of the shadow test.
|
|
287
|
+
description : Optional[str], default=None
|
|
288
|
+
Optional description of the shadow test.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
ShadowTest
|
|
293
|
+
The information with the updated shadow test.
|
|
294
|
+
|
|
295
|
+
Raises
|
|
296
|
+
------
|
|
297
|
+
requests.HTTPError
|
|
298
|
+
If the response status code is not 2xx.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
payload = {}
|
|
302
|
+
|
|
303
|
+
if name is not None:
|
|
304
|
+
payload["name"] = name
|
|
305
|
+
if description is not None:
|
|
306
|
+
payload["description"] = description
|
|
307
|
+
|
|
308
|
+
response = self.client.request(
|
|
309
|
+
method="PATCH",
|
|
310
|
+
endpoint=f"{self.experiments_endpoint}/shadow/{shadow_test_id}",
|
|
311
|
+
payload=payload,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return ShadowTest.from_dict(response.json())
|