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.
Files changed (37) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/cli/cloud/__init__.py +4 -0
  3. nextmv/cli/cloud/batch/get.py +1 -1
  4. nextmv/cli/cloud/input_set/create.py +5 -3
  5. nextmv/cli/cloud/input_set/update.py +1 -1
  6. nextmv/cli/cloud/scenario/get.py +1 -1
  7. nextmv/cli/cloud/secrets/update.py +1 -1
  8. nextmv/cli/cloud/shadow/__init__.py +33 -0
  9. nextmv/cli/cloud/shadow/create.py +184 -0
  10. nextmv/cli/cloud/shadow/delete.py +68 -0
  11. nextmv/cli/cloud/shadow/get.py +61 -0
  12. nextmv/cli/cloud/shadow/list.py +63 -0
  13. nextmv/cli/cloud/shadow/metadata.py +66 -0
  14. nextmv/cli/cloud/shadow/start.py +43 -0
  15. nextmv/cli/cloud/shadow/stop.py +43 -0
  16. nextmv/cli/cloud/shadow/update.py +96 -0
  17. nextmv/cli/cloud/switchback/__init__.py +33 -0
  18. nextmv/cli/cloud/switchback/create.py +147 -0
  19. nextmv/cli/cloud/switchback/delete.py +68 -0
  20. nextmv/cli/cloud/switchback/get.py +62 -0
  21. nextmv/cli/cloud/switchback/list.py +63 -0
  22. nextmv/cli/cloud/switchback/metadata.py +68 -0
  23. nextmv/cli/cloud/switchback/start.py +43 -0
  24. nextmv/cli/cloud/switchback/stop.py +43 -0
  25. nextmv/cli/cloud/switchback/update.py +96 -0
  26. nextmv/cli/options.py +28 -0
  27. nextmv/cloud/__init__.py +10 -0
  28. nextmv/cloud/application/__init__.py +4 -0
  29. nextmv/cloud/application/_shadow.py +314 -0
  30. nextmv/cloud/application/_switchback.py +315 -0
  31. nextmv/cloud/shadow.py +190 -0
  32. nextmv/cloud/switchback.py +189 -0
  33. {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/METADATA +1 -1
  34. {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/RECORD +37 -15
  35. {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/WHEEL +0 -0
  36. {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/entry_points.txt +0 -0
  37. {nextmv-1.0.0.dev1.dist-info → nextmv-1.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,315 @@
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.switchback import SwitchbackTest, SwitchbackTestMetadata, TestComparisonSingle
9
+ from nextmv.run import Run
10
+ from nextmv.safe import safe_id
11
+
12
+ if TYPE_CHECKING:
13
+ from . import Application
14
+
15
+
16
+ class ApplicationSwitchbackMixin:
17
+ """
18
+ Mixin class for managing switchback tests within an application.
19
+ """
20
+
21
+ def switchback_test(self: "Application", switchback_test_id: str) -> SwitchbackTest:
22
+ """
23
+ Get a switchback test. This method also returns the runs of the switchback
24
+ test under the `.runs` attribute.
25
+
26
+ Parameters
27
+ ----------
28
+ switchback_test_id : str
29
+ ID of the switchback test.
30
+
31
+ Returns
32
+ -------
33
+ SwitchbackTest
34
+ The requested switchback test details.
35
+
36
+ Raises
37
+ ------
38
+ requests.HTTPError
39
+ If the response status code is not 2xx.
40
+
41
+ Examples
42
+ --------
43
+ >>> switchback_test = app.switchback_test("switchback-123")
44
+ >>> print(switchback_test.name)
45
+ 'My Switchback Test'
46
+ """
47
+
48
+ response = self.client.request(
49
+ method="GET",
50
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
51
+ )
52
+
53
+ exp = SwitchbackTest.from_dict(response.json())
54
+
55
+ runs_response = self.client.request(
56
+ method="GET",
57
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/runs",
58
+ )
59
+
60
+ runs = [Run.from_dict(run) for run in runs_response.json().get("runs", [])]
61
+ exp.runs = runs
62
+
63
+ return exp
64
+
65
+ def switchback_test_metadata(self: "Application", switchback_test_id: str) -> SwitchbackTestMetadata:
66
+ """
67
+ Get metadata for a switchback test.
68
+
69
+ Parameters
70
+ ----------
71
+ switchback_test_id : str
72
+ ID of the switchback test.
73
+
74
+ Returns
75
+ -------
76
+ SwitchbackTestMetadata
77
+ The requested switchback test metadata.
78
+
79
+ Raises
80
+ ------
81
+ requests.HTTPError
82
+ If the response status code is not 2xx.
83
+
84
+ Examples
85
+ --------
86
+ >>> metadata = app.switchback_test_metadata("switchback-123")
87
+ >>> print(metadata.name)
88
+ 'My Switchback Test'
89
+ """
90
+
91
+ response = self.client.request(
92
+ method="GET",
93
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/metadata",
94
+ )
95
+
96
+ return SwitchbackTestMetadata.from_dict(response.json())
97
+
98
+ def delete_switchback_test(self: "Application", switchback_test_id: str) -> None:
99
+ """
100
+ Delete a switchback test.
101
+
102
+ Deletes a switchback test along with all the associated information,
103
+ such as its runs.
104
+
105
+ Parameters
106
+ ----------
107
+ switchback_test_id : str
108
+ ID of the switchback test to delete.
109
+
110
+ Raises
111
+ ------
112
+ requests.HTTPError
113
+ If the response status code is not 2xx.
114
+
115
+ Examples
116
+ --------
117
+ >>> app.delete_switchback_test("switchback-123")
118
+ """
119
+
120
+ _ = self.client.request(
121
+ method="DELETE",
122
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
123
+ )
124
+
125
+ def list_switchback_tests(self: "Application") -> list[SwitchbackTest]:
126
+ """
127
+ List all switchback tests.
128
+
129
+ Returns
130
+ -------
131
+ list[SwitchbackTest]
132
+ List of switchback tests.
133
+
134
+ Raises
135
+ ------
136
+ requests.HTTPError
137
+ If the response status code is not 2xx.
138
+ """
139
+
140
+ response = self.client.request(
141
+ method="GET",
142
+ endpoint=f"{self.experiments_endpoint}/switchback",
143
+ )
144
+
145
+ return [SwitchbackTest.from_dict(switchback_test) for switchback_test in response.json().get("items", [])]
146
+
147
+ def new_switchback_test(
148
+ self: "Application",
149
+ comparison: TestComparisonSingle,
150
+ unit_duration_minutes: float,
151
+ units: int,
152
+ switchback_test_id: str | None = None,
153
+ name: str | None = None,
154
+ description: str | None = None,
155
+ start: datetime | None = None,
156
+ ) -> SwitchbackTest:
157
+ """
158
+ Create a new switchback test in draft mode. Switchback tests are
159
+ experiments that alternate between different instances over specified
160
+ time intervals.
161
+
162
+ Use the `comparison` parameter to define how to set up the instance
163
+ comparison. The test will alternate between the baseline and candidate
164
+ instances defined in the comparison.
165
+
166
+ You may specify `start` to make the switchback test start at a
167
+ specific time. Alternatively, you may use the `start_switchback_test`
168
+ method to start the test.
169
+
170
+ Parameters
171
+ ----------
172
+ comparison : TestComparisonSingle
173
+ Comparison defining the baseline and candidate instances.
174
+ unit_duration_minutes : float
175
+ Duration of each interval in minutes.
176
+ units : int
177
+ Total number of intervals in the switchback test.
178
+ switchback_test_id : Optional[str], default=None
179
+ Optional ID for the switchback test. Will be generated if not
180
+ provided.
181
+ name : Optional[str], default=None
182
+ Optional name of the switchback test. If not provided, the ID will
183
+ be used as the name.
184
+ description : Optional[str], default=None
185
+ Optional description of the switchback test.
186
+ start : Optional[datetime], default=None
187
+ Optional scheduled start time for the switchback test.
188
+
189
+ Returns
190
+ -------
191
+ SwitchbackTest
192
+ The created switchback test.
193
+
194
+ Raises
195
+ ------
196
+ requests.HTTPError
197
+ If the response status code is not 2xx.
198
+ """
199
+
200
+ # Generate ID if not provided
201
+ if switchback_test_id is None:
202
+ switchback_test_id = safe_id("switchback")
203
+
204
+ # Use ID as name if name not provided
205
+ if name is None:
206
+ name = switchback_test_id
207
+
208
+ payload = {
209
+ "id": id,
210
+ "name": name,
211
+ "comparison": comparison,
212
+ "generate_random_plan": {
213
+ "unit_duration_minutes": unit_duration_minutes,
214
+ "units": units,
215
+ },
216
+ }
217
+
218
+ if description is not None:
219
+ payload["description"] = description
220
+ if start is not None:
221
+ payload["generate_random_plan"]["start"] = start.isoformat()
222
+
223
+ response = self.client.request(
224
+ method="POST",
225
+ endpoint=f"{self.experiments_endpoint}/switchback",
226
+ payload=payload,
227
+ )
228
+
229
+ return SwitchbackTest.from_dict(response.json())
230
+
231
+ def start_switchback_test(self: "Application", switchback_test_id: str) -> None:
232
+ """
233
+ Start a switchback test. Create a switchback test in draft mode using the
234
+ `new_switchback_test` method, then use this method to start the test.
235
+
236
+ Parameters
237
+ ----------
238
+ switchback_test_id : str
239
+ ID of the switchback test to start.
240
+
241
+ Raises
242
+ ------
243
+ requests.HTTPError
244
+ If the response status code is not 2xx.
245
+ """
246
+
247
+ _ = self.client.request(
248
+ method="PUT",
249
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/start",
250
+ )
251
+
252
+ def stop_switchback_test(self: "Application", switchback_test_id: str) -> None:
253
+ """
254
+ Stop a switchback test. The test should already have started before using
255
+ this method.
256
+
257
+ Parameters
258
+ ----------
259
+ switchback_test_id : str
260
+ ID of the switchback test to stop.
261
+
262
+ Raises
263
+ ------
264
+ requests.HTTPError
265
+ If the response status code is not 2xx.
266
+ """
267
+
268
+ _ = self.client.request(
269
+ method="PUT",
270
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/stop",
271
+ )
272
+
273
+ def update_switchback_test(
274
+ self: "Application",
275
+ switchback_test_id: str,
276
+ name: str | None = None,
277
+ description: str | None = None,
278
+ ) -> SwitchbackTest:
279
+ """
280
+ Update a switchback test.
281
+
282
+ Parameters
283
+ ----------
284
+ switchback_test_id : str
285
+ ID of the switchback test to update.
286
+ name : Optional[str], default=None
287
+ Optional name of the switchback test.
288
+ description : Optional[str], default=None
289
+ Optional description of the switchback test.
290
+
291
+ Returns
292
+ -------
293
+ SwitchbackTest
294
+ The information with the updated switchback test.
295
+
296
+ Raises
297
+ ------
298
+ requests.HTTPError
299
+ If the response status code is not 2xx.
300
+ """
301
+
302
+ payload = {}
303
+
304
+ if name is not None:
305
+ payload["name"] = name
306
+ if description is not None:
307
+ payload["description"] = description
308
+
309
+ response = self.client.request(
310
+ method="PATCH",
311
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
312
+ payload=payload,
313
+ )
314
+
315
+ return SwitchbackTest.from_dict(response.json())
nextmv/cloud/shadow.py ADDED
@@ -0,0 +1,190 @@
1
+ """
2
+ Classes for working with Nextmv Cloud shadow tests.
3
+
4
+ This module provides classes for interacting with shadow tests in Nextmv Cloud.
5
+ It details the core data structures for these types of experiments.
6
+
7
+ Classes
8
+ -------
9
+ TestComparison
10
+ A structure to define comparison parameters for tests.
11
+ StartEvents
12
+ A structure to define start events for tests.
13
+ TerminationEvents
14
+ A structure to define termination events for tests.
15
+ ShadowTestMetadata
16
+ Metadata for a Nextmv Cloud shadow test.
17
+ ShadowTest
18
+ A Nextmv Cloud shadow test definition.
19
+ """
20
+
21
+ from datetime import datetime
22
+ from typing import Any
23
+
24
+ from pydantic import AliasChoices, Field
25
+
26
+ from nextmv.base_model import BaseModel
27
+ from nextmv.cloud.batch_experiment import ExperimentStatus
28
+ from nextmv.run import Run
29
+
30
+
31
+ class TestComparison(BaseModel):
32
+ """
33
+ A structure to define comparison parameters for tests.
34
+
35
+ You can import the `TestComparison` class directly from `cloud`:
36
+
37
+ ```python
38
+ from nextmv.cloud import TestComparison
39
+ ```
40
+
41
+ Parameters
42
+ ----------
43
+ baseline_instance_id : str
44
+ ID of the baseline instance for comparison.
45
+ candidate_instance_ids : list[str]
46
+ List of candidate instance IDs to compare against the baseline.
47
+ """
48
+
49
+ baseline_instance_id: str
50
+ """ID of the baseline instance for comparison."""
51
+ candidate_instance_ids: list[str]
52
+ """List of candidate instance IDs to compare against the baseline."""
53
+
54
+
55
+ class StartEvents(BaseModel):
56
+ """
57
+ A structure to define start events for tests.
58
+
59
+ You can import the `StartEvents` class directly from `cloud`:
60
+
61
+ ```python
62
+ from nextmv.cloud import StartEvents
63
+ ```
64
+
65
+ Parameters
66
+ ----------
67
+ time : datetime, optional
68
+ Scheduled time for the test to start.
69
+ """
70
+
71
+ time: datetime | None = None
72
+ """Scheduled time for the test to start."""
73
+
74
+
75
+ class TerminationEvents(BaseModel):
76
+ """
77
+ A structure to define termination events for tests.
78
+
79
+ You can import the `TerminationEvents` class directly from `cloud`:
80
+
81
+ ```python
82
+ from nextmv.cloud import TerminationEvents
83
+ ```
84
+
85
+ Parameters
86
+ ----------
87
+ time : datetime, optional
88
+ Scheduled time for the test to terminate.
89
+ """
90
+
91
+ maximum_runs: int
92
+ """
93
+ Maximum number of runs for the test. Min should be 1, max should be 300.
94
+ """
95
+ time: datetime | None = None
96
+ """
97
+ Scheduled time for the test to terminate. A zero value means no
98
+ limit.
99
+ """
100
+
101
+ def model_post_init(self, __context):
102
+ if self.maximum_runs < 1:
103
+ raise ValueError("maximum_runs must be at least 1")
104
+
105
+
106
+ class ShadowTestMetadata(BaseModel):
107
+ """
108
+ Metadata for a Nextmv Cloud shadow test.
109
+
110
+ You can import the `ShadowTestMetadata` class directly from `cloud`:
111
+
112
+ ```python
113
+ from nextmv.cloud import ShadowTestMetadata
114
+ ```
115
+
116
+ Parameters
117
+ ----------
118
+ shadow_test_id : str, optional
119
+ The unique identifier of the shadow test.
120
+ name : str, optional
121
+ Name of the shadow test.
122
+ description : str, optional
123
+ Description of the shadow test.
124
+ app_id : str, optional
125
+ ID of the application to which the shadow test belongs.
126
+ created_at : datetime, optional
127
+ Creation date of the shadow test.
128
+ updated_at : datetime, optional
129
+ Last update date of the shadow test.
130
+ status : ExperimentStatus, optional
131
+ The current status of the shadow test.
132
+ """
133
+
134
+ shadow_test_id: str | None = Field(
135
+ serialization_alias="id",
136
+ validation_alias=AliasChoices("id", "shadow_test_id"),
137
+ default=None,
138
+ )
139
+ """The unique identifier of the shadow test."""
140
+ name: str | None = None
141
+ """Name of the shadow test."""
142
+ description: str | None = None
143
+ """Description of the shadow test."""
144
+ app_id: str | None = None
145
+ """ID of the application to which the shadow test belongs."""
146
+ created_at: datetime | None = None
147
+ """Creation date of the shadow test."""
148
+ updated_at: datetime | None = None
149
+ """Last update date of the shadow test."""
150
+ status: ExperimentStatus | None = None
151
+ """The current status of the shadow test."""
152
+
153
+
154
+ class ShadowTest(ShadowTestMetadata):
155
+ """
156
+ A Nextmv Cloud shadow test definition.
157
+
158
+ A shadow test is a type of experiment where runs are executed in parallel
159
+ to compare different instances.
160
+
161
+ You can import the `ShadowTest` class directly from `cloud`:
162
+
163
+ ```python
164
+ from nextmv.cloud import ShadowTest
165
+ ```
166
+
167
+ Parameters
168
+ ----------
169
+ completed_at : datetime, optional
170
+ Completion date of the shadow test, if applicable.
171
+ comparisons : list[TestComparison], optional
172
+ List of test comparisons defined in the shadow test.
173
+ start_events : StartEvents, optional
174
+ Start events for the shadow test.
175
+ termination_events : TerminationEvents, optional
176
+ Termination events for the shadow test.
177
+ """
178
+
179
+ completed_at: datetime | None = None
180
+ """Completion date of the shadow test, if applicable."""
181
+ comparisons: list[TestComparison] | None = None
182
+ """List of test comparisons defined in the shadow test."""
183
+ start_events: StartEvents | None = None
184
+ """Start events for the shadow test."""
185
+ termination_events: TerminationEvents | None = None
186
+ """Termination events for the shadow test."""
187
+ grouped_distributional_summaries: list[dict[str, Any]] | None = None
188
+ """Grouped distributional summaries of the shadow test."""
189
+ runs: list[Run] | None = None
190
+ """List of runs in the shadow test."""
@@ -0,0 +1,189 @@
1
+ """
2
+ Classes for working with Nextmv Cloud switchback tests.
3
+
4
+ This module provides classes for interacting with switchback tests in Nextmv Cloud.
5
+ It details the core data structures for these types of experiments.
6
+
7
+ Classes
8
+ -------
9
+ TestComparisonSingle
10
+ A structure to define a single comparison for tests.
11
+ SwitchbackPlanUnit
12
+ A structure to define a single unit in the switchback plan.
13
+ SwitchbackPlan
14
+ A structure to define the switchback plan for tests.
15
+ SwitchbackTestMetadata
16
+ Metadata for a Nextmv Cloud switchback test.
17
+ SwitchbackTest
18
+ A Nextmv Cloud switchback test definition.
19
+ """
20
+
21
+ from datetime import datetime
22
+
23
+ from pydantic import AliasChoices, Field
24
+
25
+ from nextmv.base_model import BaseModel
26
+ from nextmv.cloud.batch_experiment import ExperimentStatus
27
+ from nextmv.run import Run
28
+
29
+
30
+ class TestComparisonSingle(BaseModel):
31
+ """
32
+ A structure to define a single comparison for tests.
33
+
34
+ You can import the `TestComparisonSingle` class directly from `cloud`:
35
+
36
+ ```python
37
+ from nextmv.cloud import TestComparisonSingle
38
+ ```
39
+
40
+ Parameters
41
+ ----------
42
+ baseline_instance_id : str
43
+ ID of the baseline instance for comparison.
44
+ candidate_instance_id : str
45
+ ID of the candidate instance for comparison.
46
+ """
47
+
48
+ baseline_instance_id: str
49
+ """ID of the baseline instance for comparison."""
50
+ candidate_instance_id: str
51
+ """ID of the candidate instance for comparison."""
52
+
53
+
54
+ class SwitchbackPlanUnit(BaseModel):
55
+ """
56
+ A structure to define a single unit in the switchback plan.
57
+
58
+ You can import the `SwitchbackPlanUnit` class directly from `cloud`:
59
+
60
+ ```python
61
+ from nextmv.cloud import SwitchbackPlanUnit
62
+ ```
63
+
64
+ Parameters
65
+ ----------
66
+ duration_minutes : float
67
+ Duration of this interval in minutes.
68
+ instance_id : str
69
+ ID of the instance to run during this unit.
70
+ index : int
71
+ Index of this unit in the switchback plan.
72
+ """
73
+
74
+ duration_minutes: float
75
+ """Duration of this interval in minutes."""
76
+ instance_id: str
77
+ """ID of the instance to run during this unit."""
78
+ index: int
79
+ """Index of this unit in the switchback plan."""
80
+
81
+
82
+ class SwitchbackPlan(BaseModel):
83
+ """
84
+ A structure to define the switchback plan for tests.
85
+
86
+ You can import the `SwitchbackPlan` class directly from `cloud`:
87
+
88
+ ```python
89
+ from nextmv.cloud import SwitchbackPlan
90
+ ```
91
+
92
+ Parameters
93
+ ----------
94
+ interval_duration_seconds : int
95
+ Duration of each interval in seconds.
96
+ total_intervals : int
97
+ Total number of intervals in the switchback test.
98
+ """
99
+
100
+ start: datetime | None = None
101
+ """Start time of the switchback test."""
102
+ units: list[SwitchbackPlanUnit] | None = None
103
+ """List of switchback plan units."""
104
+
105
+
106
+ class SwitchbackTestMetadata(BaseModel):
107
+ """
108
+ Metadata for a Nextmv Cloud switchback test.
109
+
110
+ You can import the `SwitchbackTestMetadata` class directly from `cloud`:
111
+
112
+ ```python
113
+ from nextmv.cloud import SwitchbackTestMetadata
114
+ ```
115
+
116
+ Parameters
117
+ ----------
118
+ switchback_test_id : str, optional
119
+ The unique identifier of the switchback test.
120
+ name : str, optional
121
+ Name of the switchback test.
122
+ description : str, optional
123
+ Description of the switchback test.
124
+ app_id : str, optional
125
+ ID of the application to which the switchback test belongs.
126
+ created_at : datetime, optional
127
+ Creation date of the switchback test.
128
+ updated_at : datetime, optional
129
+ Last update date of the switchback test.
130
+ status : ExperimentStatus, optional
131
+ The current status of the switchback test.
132
+ """
133
+
134
+ switchback_test_id: str | None = Field(
135
+ serialization_alias="id",
136
+ validation_alias=AliasChoices("id", "switchback_test_id"),
137
+ default=None,
138
+ )
139
+ """The unique identifier of the switchback test."""
140
+ name: str | None = None
141
+ """Name of the switchback test."""
142
+ description: str | None = None
143
+ """Description of the switchback test."""
144
+ app_id: str | None = None
145
+ """ID of the application to which the switchback test belongs."""
146
+ created_at: datetime | None = None
147
+ """Creation date of the switchback test."""
148
+ updated_at: datetime | None = None
149
+ """Last update date of the switchback test."""
150
+ status: ExperimentStatus | None = None
151
+ """The current status of the switchback test."""
152
+
153
+
154
+ class SwitchbackTest(SwitchbackTestMetadata):
155
+ """
156
+ A Nextmv Cloud switchback test definition.
157
+
158
+ A switchback test is a type of experiment where runs are executed in
159
+ sequential intervals, alternating between different instances to compare
160
+ their performance.
161
+
162
+ You can import the `SwitchbackTest` class directly from `cloud`:
163
+
164
+ ```python
165
+ from nextmv.cloud import SwitchbackTest
166
+ ```
167
+
168
+ Parameters
169
+ ----------
170
+ completed_at : datetime, optional
171
+ Completion date of the switchback test, if applicable.
172
+ comparison : TestComparisonSingle, optional
173
+ Test comparison defined in the switchback test.
174
+ start_events : StartEvents, optional
175
+ Start events for the switchback test.
176
+ termination_events : TerminationEvents, optional
177
+ Termination events for the switchback test.
178
+ """
179
+
180
+ started_at: datetime | None = None
181
+ """Start date of the switchback test, if applicable."""
182
+ completed_at: datetime | None = None
183
+ """Completion date of the switchback test, if applicable."""
184
+ comparison: TestComparisonSingle | None = None
185
+ """Test comparison defined in the switchback test."""
186
+ plan: SwitchbackPlan | None = None
187
+ """Switchback plan defining the intervals and instance switching."""
188
+ runs: list[Run] | None = None
189
+ """List of runs in the switchback test."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 1.0.0.dev1
3
+ Version: 1.0.0.dev3
4
4
  Summary: The all-purpose Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/