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.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +2 -1
- nextmv/__init__.py +4 -0
- nextmv/cli/CONTRIBUTING.md +40 -112
- nextmv/cli/cloud/__init__.py +0 -4
- nextmv/cli/cloud/acceptance/create.py +22 -20
- nextmv/cli/cloud/acceptance/delete.py +12 -8
- nextmv/cli/cloud/acceptance/get.py +10 -9
- 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 +8 -7
- nextmv/cli/cloud/account/get.py +3 -3
- nextmv/cli/cloud/account/update.py +5 -5
- nextmv/cli/cloud/app/create.py +26 -25
- nextmv/cli/cloud/app/delete.py +7 -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 +54 -349
- nextmv/cli/cloud/app/update.py +12 -12
- nextmv/cli/cloud/batch/create.py +28 -26
- nextmv/cli/cloud/batch/delete.py +10 -6
- nextmv/cli/cloud/batch/get.py +9 -9
- 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 +0 -2
- nextmv/cli/cloud/ensemble/create.py +22 -21
- nextmv/cli/cloud/ensemble/delete.py +10 -6
- nextmv/cli/cloud/ensemble/get.py +4 -4
- nextmv/cli/cloud/ensemble/update.py +9 -9
- nextmv/cli/cloud/input_set/__init__.py +0 -2
- nextmv/cli/cloud/input_set/create.py +22 -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 +15 -14
- nextmv/cli/cloud/instance/delete.py +7 -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 +16 -14
- nextmv/cli/cloud/managed_input/delete.py +8 -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 +40 -34
- 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 +10 -9
- nextmv/cli/cloud/run/metadata.py +4 -4
- nextmv/cli/cloud/run/track.py +33 -32
- nextmv/cli/cloud/scenario/create.py +21 -21
- nextmv/cli/cloud/scenario/delete.py +10 -6
- nextmv/cli/cloud/scenario/get.py +9 -9
- 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 +10 -6
- nextmv/cli/cloud/secrets/get.py +4 -4
- nextmv/cli/cloud/secrets/list.py +3 -3
- nextmv/cli/cloud/secrets/update.py +20 -17
- nextmv/cli/cloud/upload/create.py +2 -2
- nextmv/cli/cloud/version/create.py +10 -9
- nextmv/cli/cloud/version/delete.py +7 -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/__init__.py +1 -1
- nextmv/cli/community/clone.py +204 -20
- nextmv/cli/community/list.py +125 -60
- nextmv/cli/configuration/config.py +10 -43
- nextmv/cli/configuration/create.py +7 -7
- nextmv/cli/configuration/delete.py +8 -8
- nextmv/cli/configuration/list.py +3 -3
- nextmv/cli/main.py +36 -26
- nextmv/cli/message.py +54 -71
- nextmv/cli/options.py +0 -28
- nextmv/cli/version.py +1 -1
- nextmv/cloud/__init__.py +38 -14
- nextmv/cloud/acceptance_test.py +65 -1
- nextmv/cloud/account.py +6 -1
- nextmv/cloud/application/__init__.py +75 -18
- nextmv/cloud/application/_acceptance.py +8 -13
- nextmv/cloud/application/_batch_scenario.py +19 -4
- nextmv/cloud/application/_input_set.py +6 -42
- nextmv/cloud/application/_instance.py +3 -3
- nextmv/cloud/application/_managed_input.py +2 -2
- nextmv/cloud/application/_version.py +3 -4
- nextmv/cloud/batch_experiment.py +1 -3
- nextmv/cloud/integration.py +4 -7
- nextmv/deprecated.py +3 -5
- nextmv/input.py +52 -0
- nextmv/local/runner.py +1 -1
- nextmv/model.py +11 -50
- nextmv/options.py +256 -11
- nextmv/output.py +62 -0
- nextmv/run.py +10 -1
- nextmv/status.py +51 -1
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/METADATA +4 -5
- nextmv-1.0.0.dev0.dist-info/RECORD +158 -0
- nextmv/cli/cloud/ensemble/list.py +0 -63
- nextmv/cli/cloud/input_set/delete.py +0 -64
- nextmv/cli/cloud/shadow/__init__.py +0 -33
- nextmv/cli/cloud/shadow/create.py +0 -184
- nextmv/cli/cloud/shadow/delete.py +0 -64
- nextmv/cli/cloud/shadow/get.py +0 -61
- nextmv/cli/cloud/shadow/list.py +0 -63
- nextmv/cli/cloud/shadow/metadata.py +0 -66
- nextmv/cli/cloud/shadow/start.py +0 -43
- nextmv/cli/cloud/shadow/stop.py +0 -53
- nextmv/cli/cloud/shadow/update.py +0 -96
- nextmv/cli/cloud/switchback/__init__.py +0 -33
- nextmv/cli/cloud/switchback/create.py +0 -151
- nextmv/cli/cloud/switchback/delete.py +0 -64
- nextmv/cli/cloud/switchback/get.py +0 -62
- nextmv/cli/cloud/switchback/list.py +0 -63
- nextmv/cli/cloud/switchback/metadata.py +0 -68
- nextmv/cli/cloud/switchback/start.py +0 -43
- nextmv/cli/cloud/switchback/stop.py +0 -53
- nextmv/cli/cloud/switchback/update.py +0 -96
- nextmv/cli/confirm.py +0 -34
- nextmv/cloud/application/_shadow.py +0 -320
- nextmv/cloud/application/_switchback.py +0 -332
- nextmv/cloud/community.py +0 -446
- nextmv/cloud/shadow.py +0 -254
- nextmv/cloud/switchback.py +0 -228
- nextmv-1.0.0.dist-info/RECORD +0 -185
- nextmv-1.0.0.dist-info/entry_points.txt +0 -2
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Application mixin for managing switchback tests.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
from nextmv.cloud.shadow import StopIntent
|
|
9
|
-
from nextmv.cloud.switchback import SwitchbackTest, SwitchbackTestMetadata, TestComparisonSingle
|
|
10
|
-
from nextmv.run import Run
|
|
11
|
-
from nextmv.safe import safe_id
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from . import Application
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ApplicationSwitchbackMixin:
|
|
18
|
-
"""
|
|
19
|
-
Mixin class for managing switchback tests within an application.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def switchback_test(self: "Application", switchback_test_id: str) -> SwitchbackTest:
|
|
23
|
-
"""
|
|
24
|
-
Get a switchback test. This method also returns the runs of the switchback
|
|
25
|
-
test under the `.runs` attribute.
|
|
26
|
-
|
|
27
|
-
Parameters
|
|
28
|
-
----------
|
|
29
|
-
switchback_test_id : str
|
|
30
|
-
ID of the switchback test.
|
|
31
|
-
|
|
32
|
-
Returns
|
|
33
|
-
-------
|
|
34
|
-
SwitchbackTest
|
|
35
|
-
The requested switchback test details.
|
|
36
|
-
|
|
37
|
-
Raises
|
|
38
|
-
------
|
|
39
|
-
requests.HTTPError
|
|
40
|
-
If the response status code is not 2xx.
|
|
41
|
-
|
|
42
|
-
Examples
|
|
43
|
-
--------
|
|
44
|
-
>>> switchback_test = app.switchback_test("switchback-123")
|
|
45
|
-
>>> print(switchback_test.name)
|
|
46
|
-
'My Switchback Test'
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
response = self.client.request(
|
|
50
|
-
method="GET",
|
|
51
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
exp = SwitchbackTest.from_dict(response.json())
|
|
55
|
-
|
|
56
|
-
runs_response = self.client.request(
|
|
57
|
-
method="GET",
|
|
58
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/runs",
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
runs = [Run.from_dict(run) for run in runs_response.json().get("runs", [])]
|
|
62
|
-
exp.runs = runs
|
|
63
|
-
|
|
64
|
-
return exp
|
|
65
|
-
|
|
66
|
-
def switchback_test_metadata(self: "Application", switchback_test_id: str) -> SwitchbackTestMetadata:
|
|
67
|
-
"""
|
|
68
|
-
Get metadata for a switchback test.
|
|
69
|
-
|
|
70
|
-
Parameters
|
|
71
|
-
----------
|
|
72
|
-
switchback_test_id : str
|
|
73
|
-
ID of the switchback test.
|
|
74
|
-
|
|
75
|
-
Returns
|
|
76
|
-
-------
|
|
77
|
-
SwitchbackTestMetadata
|
|
78
|
-
The requested switchback test metadata.
|
|
79
|
-
|
|
80
|
-
Raises
|
|
81
|
-
------
|
|
82
|
-
requests.HTTPError
|
|
83
|
-
If the response status code is not 2xx.
|
|
84
|
-
|
|
85
|
-
Examples
|
|
86
|
-
--------
|
|
87
|
-
>>> metadata = app.switchback_test_metadata("switchback-123")
|
|
88
|
-
>>> print(metadata.name)
|
|
89
|
-
'My Switchback Test'
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
response = self.client.request(
|
|
93
|
-
method="GET",
|
|
94
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/metadata",
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
return SwitchbackTestMetadata.from_dict(response.json())
|
|
98
|
-
|
|
99
|
-
def delete_switchback_test(self: "Application", switchback_test_id: str) -> None:
|
|
100
|
-
"""
|
|
101
|
-
Delete a switchback test.
|
|
102
|
-
|
|
103
|
-
Deletes a switchback test along with all the associated information,
|
|
104
|
-
such as its runs.
|
|
105
|
-
|
|
106
|
-
Parameters
|
|
107
|
-
----------
|
|
108
|
-
switchback_test_id : str
|
|
109
|
-
ID of the switchback test to delete.
|
|
110
|
-
|
|
111
|
-
Raises
|
|
112
|
-
------
|
|
113
|
-
requests.HTTPError
|
|
114
|
-
If the response status code is not 2xx.
|
|
115
|
-
|
|
116
|
-
Examples
|
|
117
|
-
--------
|
|
118
|
-
>>> app.delete_switchback_test("switchback-123")
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
_ = self.client.request(
|
|
122
|
-
method="DELETE",
|
|
123
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def list_switchback_tests(self: "Application") -> list[SwitchbackTest]:
|
|
127
|
-
"""
|
|
128
|
-
List all switchback tests.
|
|
129
|
-
|
|
130
|
-
Returns
|
|
131
|
-
-------
|
|
132
|
-
list[SwitchbackTest]
|
|
133
|
-
List of switchback tests.
|
|
134
|
-
|
|
135
|
-
Raises
|
|
136
|
-
------
|
|
137
|
-
requests.HTTPError
|
|
138
|
-
If the response status code is not 2xx.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
response = self.client.request(
|
|
142
|
-
method="GET",
|
|
143
|
-
endpoint=f"{self.experiments_endpoint}/switchback",
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
return [SwitchbackTest.from_dict(switchback_test) for switchback_test in response.json().get("items", [])]
|
|
147
|
-
|
|
148
|
-
def new_switchback_test(
|
|
149
|
-
self: "Application",
|
|
150
|
-
comparison: TestComparisonSingle,
|
|
151
|
-
unit_duration_minutes: float,
|
|
152
|
-
units: int,
|
|
153
|
-
switchback_test_id: str | None = None,
|
|
154
|
-
name: str | None = None,
|
|
155
|
-
description: str | None = None,
|
|
156
|
-
start: datetime | None = None,
|
|
157
|
-
) -> SwitchbackTest:
|
|
158
|
-
"""
|
|
159
|
-
Create a new switchback test in draft mode. Switchback tests are
|
|
160
|
-
experiments that alternate between different instances over specified
|
|
161
|
-
time intervals.
|
|
162
|
-
|
|
163
|
-
Use the `comparison` parameter to define how to set up the instance
|
|
164
|
-
comparison. The test will alternate between the baseline and candidate
|
|
165
|
-
instances defined in the comparison.
|
|
166
|
-
|
|
167
|
-
You may specify `start` to make the switchback test start at a
|
|
168
|
-
specific time. Alternatively, you may use the `start_switchback_test`
|
|
169
|
-
method to start the test.
|
|
170
|
-
|
|
171
|
-
Parameters
|
|
172
|
-
----------
|
|
173
|
-
comparison : TestComparisonSingle
|
|
174
|
-
Comparison defining the baseline and candidate instances.
|
|
175
|
-
unit_duration_minutes : float
|
|
176
|
-
Duration of each interval in minutes. The value must be between 1
|
|
177
|
-
and 10080.
|
|
178
|
-
units : int
|
|
179
|
-
Total number of intervals in the switchback test. The value must be
|
|
180
|
-
between 1 and 1000.
|
|
181
|
-
switchback_test_id : Optional[str], default=None
|
|
182
|
-
Optional ID for the switchback test. Will be generated if not
|
|
183
|
-
provided.
|
|
184
|
-
name : Optional[str], default=None
|
|
185
|
-
Optional name of the switchback test. If not provided, the ID will
|
|
186
|
-
be used as the name.
|
|
187
|
-
description : Optional[str], default=None
|
|
188
|
-
Optional description of the switchback test.
|
|
189
|
-
start : Optional[datetime], default=None
|
|
190
|
-
Optional scheduled start time for the switchback test.
|
|
191
|
-
|
|
192
|
-
Returns
|
|
193
|
-
-------
|
|
194
|
-
SwitchbackTest
|
|
195
|
-
The created switchback test.
|
|
196
|
-
|
|
197
|
-
Raises
|
|
198
|
-
------
|
|
199
|
-
requests.HTTPError
|
|
200
|
-
If the response status code is not 2xx.
|
|
201
|
-
"""
|
|
202
|
-
|
|
203
|
-
if unit_duration_minutes < 1 or unit_duration_minutes > 10080:
|
|
204
|
-
raise ValueError("unit_duration_minutes must be between 1 and 10080")
|
|
205
|
-
|
|
206
|
-
if units < 1 or units > 1000:
|
|
207
|
-
raise ValueError("units must be between 1 and 1000")
|
|
208
|
-
|
|
209
|
-
# Generate ID if not provided
|
|
210
|
-
if switchback_test_id is None:
|
|
211
|
-
switchback_test_id = safe_id("switchback")
|
|
212
|
-
|
|
213
|
-
# Use ID as name if name not provided
|
|
214
|
-
if name is None or name == "":
|
|
215
|
-
name = switchback_test_id
|
|
216
|
-
|
|
217
|
-
payload = {
|
|
218
|
-
"id": switchback_test_id,
|
|
219
|
-
"name": name,
|
|
220
|
-
"comparison": comparison.to_dict(),
|
|
221
|
-
"generate_random_plan": {
|
|
222
|
-
"unit_duration_minutes": unit_duration_minutes,
|
|
223
|
-
"units": units,
|
|
224
|
-
},
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if description is not None:
|
|
228
|
-
payload["description"] = description
|
|
229
|
-
if start is not None:
|
|
230
|
-
payload["generate_random_plan"]["start"] = start.isoformat()
|
|
231
|
-
|
|
232
|
-
response = self.client.request(
|
|
233
|
-
method="POST",
|
|
234
|
-
endpoint=f"{self.experiments_endpoint}/switchback",
|
|
235
|
-
payload=payload,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
return SwitchbackTest.from_dict(response.json())
|
|
239
|
-
|
|
240
|
-
def start_switchback_test(self: "Application", switchback_test_id: str) -> None:
|
|
241
|
-
"""
|
|
242
|
-
Start a switchback test. Create a switchback test in draft mode using the
|
|
243
|
-
`new_switchback_test` method, then use this method to start the test.
|
|
244
|
-
|
|
245
|
-
Parameters
|
|
246
|
-
----------
|
|
247
|
-
switchback_test_id : str
|
|
248
|
-
ID of the switchback test to start.
|
|
249
|
-
|
|
250
|
-
Raises
|
|
251
|
-
------
|
|
252
|
-
requests.HTTPError
|
|
253
|
-
If the response status code is not 2xx.
|
|
254
|
-
"""
|
|
255
|
-
|
|
256
|
-
_ = self.client.request(
|
|
257
|
-
method="PUT",
|
|
258
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/start",
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
def stop_switchback_test(self: "Application", switchback_test_id: str, intent: StopIntent) -> None:
|
|
262
|
-
"""
|
|
263
|
-
Stop a switchback test. The test should already have started before using
|
|
264
|
-
this method.
|
|
265
|
-
|
|
266
|
-
Parameters
|
|
267
|
-
----------
|
|
268
|
-
switchback_test_id : str
|
|
269
|
-
ID of the switchback test to stop.
|
|
270
|
-
|
|
271
|
-
intent : StopIntent
|
|
272
|
-
Intent for stopping the switchback test.
|
|
273
|
-
|
|
274
|
-
Raises
|
|
275
|
-
------
|
|
276
|
-
requests.HTTPError
|
|
277
|
-
If the response status code is not 2xx.
|
|
278
|
-
"""
|
|
279
|
-
|
|
280
|
-
payload = {
|
|
281
|
-
"intent": intent.value,
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
_ = self.client.request(
|
|
285
|
-
method="PUT",
|
|
286
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/stop",
|
|
287
|
-
payload=payload,
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
def update_switchback_test(
|
|
291
|
-
self: "Application",
|
|
292
|
-
switchback_test_id: str,
|
|
293
|
-
name: str | None = None,
|
|
294
|
-
description: str | None = None,
|
|
295
|
-
) -> SwitchbackTest:
|
|
296
|
-
"""
|
|
297
|
-
Update a switchback test.
|
|
298
|
-
|
|
299
|
-
Parameters
|
|
300
|
-
----------
|
|
301
|
-
switchback_test_id : str
|
|
302
|
-
ID of the switchback test to update.
|
|
303
|
-
name : Optional[str], default=None
|
|
304
|
-
Optional name of the switchback test.
|
|
305
|
-
description : Optional[str], default=None
|
|
306
|
-
Optional description of the switchback test.
|
|
307
|
-
|
|
308
|
-
Returns
|
|
309
|
-
-------
|
|
310
|
-
SwitchbackTest
|
|
311
|
-
The information with the updated switchback test.
|
|
312
|
-
|
|
313
|
-
Raises
|
|
314
|
-
------
|
|
315
|
-
requests.HTTPError
|
|
316
|
-
If the response status code is not 2xx.
|
|
317
|
-
"""
|
|
318
|
-
|
|
319
|
-
payload = {}
|
|
320
|
-
|
|
321
|
-
if name is not None:
|
|
322
|
-
payload["name"] = name
|
|
323
|
-
if description is not None:
|
|
324
|
-
payload["description"] = description
|
|
325
|
-
|
|
326
|
-
response = self.client.request(
|
|
327
|
-
method="PATCH",
|
|
328
|
-
endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
|
|
329
|
-
payload=payload,
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
return SwitchbackTest.from_dict(response.json())
|