nextmv 1.0.0.dev2__py3-none-any.whl → 1.0.0.dev4__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 (120) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/cli/CONTRIBUTING.md +81 -29
  3. nextmv/cli/cloud/__init__.py +2 -0
  4. nextmv/cli/cloud/acceptance/create.py +20 -22
  5. nextmv/cli/cloud/acceptance/delete.py +7 -8
  6. nextmv/cli/cloud/acceptance/get.py +9 -10
  7. nextmv/cli/cloud/acceptance/list.py +3 -3
  8. nextmv/cli/cloud/acceptance/update.py +6 -6
  9. nextmv/cli/cloud/account/__init__.py +3 -3
  10. nextmv/cli/cloud/account/create.py +11 -11
  11. nextmv/cli/cloud/account/delete.py +6 -7
  12. nextmv/cli/cloud/account/get.py +3 -3
  13. nextmv/cli/cloud/account/update.py +5 -5
  14. nextmv/cli/cloud/app/create.py +25 -26
  15. nextmv/cli/cloud/app/delete.py +5 -6
  16. nextmv/cli/cloud/app/exists.py +2 -2
  17. nextmv/cli/cloud/app/get.py +2 -2
  18. nextmv/cli/cloud/app/list.py +3 -3
  19. nextmv/cli/cloud/app/push.py +269 -45
  20. nextmv/cli/cloud/app/update.py +12 -12
  21. nextmv/cli/cloud/batch/create.py +26 -28
  22. nextmv/cli/cloud/batch/delete.py +5 -6
  23. nextmv/cli/cloud/batch/get.py +8 -8
  24. nextmv/cli/cloud/batch/list.py +3 -3
  25. nextmv/cli/cloud/batch/metadata.py +4 -4
  26. nextmv/cli/cloud/batch/update.py +6 -6
  27. nextmv/cli/cloud/data/__init__.py +1 -1
  28. nextmv/cli/cloud/data/upload.py +15 -15
  29. nextmv/cli/cloud/ensemble/__init__.py +2 -0
  30. nextmv/cli/cloud/ensemble/create.py +21 -22
  31. nextmv/cli/cloud/ensemble/delete.py +5 -6
  32. nextmv/cli/cloud/ensemble/get.py +4 -4
  33. nextmv/cli/cloud/ensemble/list.py +63 -0
  34. nextmv/cli/cloud/ensemble/update.py +9 -9
  35. nextmv/cli/cloud/input_set/create.py +20 -22
  36. nextmv/cli/cloud/input_set/get.py +3 -3
  37. nextmv/cli/cloud/input_set/list.py +3 -3
  38. nextmv/cli/cloud/input_set/update.py +24 -24
  39. nextmv/cli/cloud/instance/create.py +14 -15
  40. nextmv/cli/cloud/instance/delete.py +5 -6
  41. nextmv/cli/cloud/instance/exists.py +2 -2
  42. nextmv/cli/cloud/instance/get.py +2 -2
  43. nextmv/cli/cloud/instance/list.py +3 -3
  44. nextmv/cli/cloud/instance/update.py +14 -14
  45. nextmv/cli/cloud/managed_input/create.py +14 -16
  46. nextmv/cli/cloud/managed_input/delete.py +6 -7
  47. nextmv/cli/cloud/managed_input/get.py +3 -3
  48. nextmv/cli/cloud/managed_input/list.py +3 -3
  49. nextmv/cli/cloud/managed_input/update.py +9 -9
  50. nextmv/cli/cloud/run/cancel.py +2 -2
  51. nextmv/cli/cloud/run/create.py +32 -33
  52. nextmv/cli/cloud/run/get.py +8 -8
  53. nextmv/cli/cloud/run/input.py +4 -4
  54. nextmv/cli/cloud/run/list.py +6 -6
  55. nextmv/cli/cloud/run/logs.py +9 -10
  56. nextmv/cli/cloud/run/metadata.py +4 -4
  57. nextmv/cli/cloud/run/track.py +32 -33
  58. nextmv/cli/cloud/scenario/create.py +21 -21
  59. nextmv/cli/cloud/scenario/delete.py +5 -6
  60. nextmv/cli/cloud/scenario/get.py +8 -8
  61. nextmv/cli/cloud/scenario/list.py +3 -3
  62. nextmv/cli/cloud/scenario/metadata.py +4 -4
  63. nextmv/cli/cloud/scenario/update.py +6 -6
  64. nextmv/cli/cloud/secrets/create.py +17 -17
  65. nextmv/cli/cloud/secrets/delete.py +5 -6
  66. nextmv/cli/cloud/secrets/get.py +4 -4
  67. nextmv/cli/cloud/secrets/list.py +3 -3
  68. nextmv/cli/cloud/secrets/update.py +17 -20
  69. nextmv/cli/cloud/shadow/__init__.py +1 -1
  70. nextmv/cli/cloud/shadow/create.py +32 -32
  71. nextmv/cli/cloud/shadow/delete.py +5 -6
  72. nextmv/cli/cloud/shadow/get.py +2 -2
  73. nextmv/cli/cloud/shadow/list.py +3 -3
  74. nextmv/cli/cloud/shadow/metadata.py +4 -4
  75. nextmv/cli/cloud/shadow/start.py +3 -3
  76. nextmv/cli/cloud/shadow/stop.py +8 -10
  77. nextmv/cli/cloud/shadow/update.py +7 -6
  78. nextmv/cli/cloud/switchback/__init__.py +33 -0
  79. nextmv/cli/cloud/switchback/create.py +151 -0
  80. nextmv/cli/cloud/switchback/delete.py +67 -0
  81. nextmv/cli/cloud/switchback/get.py +62 -0
  82. nextmv/cli/cloud/switchback/list.py +63 -0
  83. nextmv/cli/cloud/switchback/metadata.py +68 -0
  84. nextmv/cli/cloud/switchback/start.py +43 -0
  85. nextmv/cli/cloud/switchback/stop.py +41 -0
  86. nextmv/cli/cloud/switchback/update.py +96 -0
  87. nextmv/cli/cloud/upload/create.py +2 -2
  88. nextmv/cli/cloud/version/create.py +9 -10
  89. nextmv/cli/cloud/version/delete.py +5 -6
  90. nextmv/cli/cloud/version/exists.py +2 -2
  91. nextmv/cli/cloud/version/get.py +2 -2
  92. nextmv/cli/cloud/version/list.py +3 -3
  93. nextmv/cli/cloud/version/update.py +8 -8
  94. nextmv/cli/community/clone.py +12 -10
  95. nextmv/cli/community/list.py +9 -9
  96. nextmv/cli/configuration/config.py +43 -10
  97. nextmv/cli/configuration/create.py +3 -3
  98. nextmv/cli/configuration/delete.py +7 -7
  99. nextmv/cli/configuration/list.py +3 -3
  100. nextmv/cli/confirm.py +32 -0
  101. nextmv/cli/main.py +27 -36
  102. nextmv/cli/message.py +2 -2
  103. nextmv/cli/options.py +14 -0
  104. nextmv/cli/version.py +1 -1
  105. nextmv/cloud/__init__.py +5 -0
  106. nextmv/cloud/application/__init__.py +192 -54
  107. nextmv/cloud/application/_batch_scenario.py +2 -2
  108. nextmv/cloud/application/_instance.py +2 -2
  109. nextmv/cloud/application/_managed_input.py +1 -1
  110. nextmv/cloud/application/_shadow.py +1 -1
  111. nextmv/cloud/application/_switchback.py +323 -0
  112. nextmv/cloud/application/_version.py +3 -2
  113. nextmv/cloud/shadow.py +43 -4
  114. nextmv/cloud/switchback.py +226 -0
  115. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/METADATA +1 -1
  116. nextmv-1.0.0.dev4.dist-info/RECORD +183 -0
  117. nextmv-1.0.0.dev2.dist-info/RECORD +0 -170
  118. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/WHEEL +0 -0
  119. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/entry_points.txt +0 -0
  120. {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,323 @@
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. The value must be between 1
176
+ and 10080.
177
+ units : int
178
+ Total number of intervals in the switchback test. The value must be
179
+ between 1 and 1000.
180
+ switchback_test_id : Optional[str], default=None
181
+ Optional ID for the switchback test. Will be generated if not
182
+ provided.
183
+ name : Optional[str], default=None
184
+ Optional name of the switchback test. If not provided, the ID will
185
+ be used as the name.
186
+ description : Optional[str], default=None
187
+ Optional description of the switchback test.
188
+ start : Optional[datetime], default=None
189
+ Optional scheduled start time for the switchback test.
190
+
191
+ Returns
192
+ -------
193
+ SwitchbackTest
194
+ The created switchback test.
195
+
196
+ Raises
197
+ ------
198
+ requests.HTTPError
199
+ If the response status code is not 2xx.
200
+ """
201
+
202
+ if unit_duration_minutes < 1 or unit_duration_minutes > 10080:
203
+ raise ValueError("unit_duration_minutes must be between 1 and 10080")
204
+
205
+ if units < 1 or units > 1000:
206
+ raise ValueError("units must be between 1 and 1000")
207
+
208
+ # Generate ID if not provided
209
+ if switchback_test_id is None:
210
+ switchback_test_id = safe_id("switchback")
211
+
212
+ # Use ID as name if name not provided
213
+ if name is None:
214
+ name = switchback_test_id
215
+
216
+ payload = {
217
+ "id": switchback_test_id,
218
+ "name": name,
219
+ "comparison": comparison,
220
+ "generate_random_plan": {
221
+ "unit_duration_minutes": unit_duration_minutes,
222
+ "units": units,
223
+ },
224
+ }
225
+
226
+ if description is not None:
227
+ payload["description"] = description
228
+ if start is not None:
229
+ payload["generate_random_plan"]["start"] = start.isoformat()
230
+
231
+ response = self.client.request(
232
+ method="POST",
233
+ endpoint=f"{self.experiments_endpoint}/switchback",
234
+ payload=payload,
235
+ )
236
+
237
+ return SwitchbackTest.from_dict(response.json())
238
+
239
+ def start_switchback_test(self: "Application", switchback_test_id: str) -> None:
240
+ """
241
+ Start a switchback test. Create a switchback test in draft mode using the
242
+ `new_switchback_test` method, then use this method to start the test.
243
+
244
+ Parameters
245
+ ----------
246
+ switchback_test_id : str
247
+ ID of the switchback test to start.
248
+
249
+ Raises
250
+ ------
251
+ requests.HTTPError
252
+ If the response status code is not 2xx.
253
+ """
254
+
255
+ _ = self.client.request(
256
+ method="PUT",
257
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/start",
258
+ )
259
+
260
+ def stop_switchback_test(self: "Application", switchback_test_id: str) -> None:
261
+ """
262
+ Stop a switchback test. The test should already have started before using
263
+ this method.
264
+
265
+ Parameters
266
+ ----------
267
+ switchback_test_id : str
268
+ ID of the switchback test to stop.
269
+
270
+ Raises
271
+ ------
272
+ requests.HTTPError
273
+ If the response status code is not 2xx.
274
+ """
275
+
276
+ _ = self.client.request(
277
+ method="PUT",
278
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}/stop",
279
+ )
280
+
281
+ def update_switchback_test(
282
+ self: "Application",
283
+ switchback_test_id: str,
284
+ name: str | None = None,
285
+ description: str | None = None,
286
+ ) -> SwitchbackTest:
287
+ """
288
+ Update a switchback test.
289
+
290
+ Parameters
291
+ ----------
292
+ switchback_test_id : str
293
+ ID of the switchback test to update.
294
+ name : Optional[str], default=None
295
+ Optional name of the switchback test.
296
+ description : Optional[str], default=None
297
+ Optional description of the switchback test.
298
+
299
+ Returns
300
+ -------
301
+ SwitchbackTest
302
+ The information with the updated switchback test.
303
+
304
+ Raises
305
+ ------
306
+ requests.HTTPError
307
+ If the response status code is not 2xx.
308
+ """
309
+
310
+ payload = {}
311
+
312
+ if name is not None:
313
+ payload["name"] = name
314
+ if description is not None:
315
+ payload["description"] = description
316
+
317
+ response = self.client.request(
318
+ method="PATCH",
319
+ endpoint=f"{self.experiments_endpoint}/switchback/{switchback_test_id}",
320
+ payload=payload,
321
+ )
322
+
323
+ return SwitchbackTest.from_dict(response.json())
@@ -132,14 +132,15 @@ class ApplicationVersionMixin:
132
132
  ... )
133
133
  """
134
134
 
135
- if exist_ok and id is None:
135
+ if exist_ok and (id is None or id == ""):
136
136
  raise ValueError("If exist_ok is True, id must be provided")
137
137
 
138
138
  if exist_ok and self.version_exists(version_id=id):
139
139
  return self.version(version_id=id)
140
140
 
141
- if id is None:
141
+ if id is None or id == "":
142
142
  id = safe_id(prefix="version")
143
+
143
144
  if name is None:
144
145
  name = id
145
146
 
nextmv/cloud/shadow.py CHANGED
@@ -90,7 +90,7 @@ class TerminationEvents(BaseModel):
90
90
 
91
91
  maximum_runs: int
92
92
  """
93
- Maximum number of runs for the test. Min should be 1, max should be 300.
93
+ Maximum number of runs for the test. Value must be between 1 and 300.
94
94
  """
95
95
  time: datetime | None = None
96
96
  """
@@ -99,8 +99,8 @@ class TerminationEvents(BaseModel):
99
99
  """
100
100
 
101
101
  def model_post_init(self, __context):
102
- if self.maximum_runs < 1:
103
- raise ValueError("maximum_runs must be at least 1")
102
+ if self.maximum_runs < 1 or self.maximum_runs > 300:
103
+ raise ValueError("maximum_runs must be between 1 and 300")
104
104
 
105
105
 
106
106
  class ShadowTestMetadata(BaseModel):
@@ -151,7 +151,10 @@ class ShadowTestMetadata(BaseModel):
151
151
  """The current status of the shadow test."""
152
152
 
153
153
 
154
- class ShadowTest(ShadowTestMetadata):
154
+ # This class uses some fields defined in ShadowTestMetadata. We are not
155
+ # using inheritance to help the user understand the full structure when using
156
+ # tools like intellisense.
157
+ class ShadowTest(BaseModel):
155
158
  """
156
159
  A Nextmv Cloud shadow test definition.
157
160
 
@@ -166,6 +169,20 @@ class ShadowTest(ShadowTestMetadata):
166
169
 
167
170
  Parameters
168
171
  ----------
172
+ shadow_test_id : str, optional
173
+ The unique identifier of the shadow test.
174
+ name : str, optional
175
+ Name of the shadow test.
176
+ description : str, optional
177
+ Description of the shadow test.
178
+ app_id : str, optional
179
+ ID of the application to which the shadow test belongs.
180
+ created_at : datetime, optional
181
+ Creation date of the shadow test.
182
+ updated_at : datetime, optional
183
+ Last update date of the shadow test.
184
+ status : ExperimentStatus, optional
185
+ The current status of the shadow test.
169
186
  completed_at : datetime, optional
170
187
  Completion date of the shadow test, if applicable.
171
188
  comparisons : list[TestComparison], optional
@@ -174,8 +191,30 @@ class ShadowTest(ShadowTestMetadata):
174
191
  Start events for the shadow test.
175
192
  termination_events : TerminationEvents, optional
176
193
  Termination events for the shadow test.
194
+ grouped_distributional_summaries : list[dict[str, Any]], optional
195
+ Grouped distributional summaries of the shadow test.
196
+ runs : list[Run], optional
197
+ List of runs in the shadow test.
177
198
  """
178
199
 
200
+ shadow_test_id: str | None = Field(
201
+ serialization_alias="id",
202
+ validation_alias=AliasChoices("id", "shadow_test_id"),
203
+ default=None,
204
+ )
205
+ """The unique identifier of the shadow test."""
206
+ name: str | None = None
207
+ """Name of the shadow test."""
208
+ description: str | None = None
209
+ """Description of the shadow test."""
210
+ app_id: str | None = None
211
+ """ID of the application to which the shadow test belongs."""
212
+ created_at: datetime | None = None
213
+ """Creation date of the shadow test."""
214
+ updated_at: datetime | None = None
215
+ """Last update date of the shadow test."""
216
+ status: ExperimentStatus | None = None
217
+ """The current status of the shadow test."""
179
218
  completed_at: datetime | None = None
180
219
  """Completion date of the shadow test, if applicable."""
181
220
  comparisons: list[TestComparison] | None = None
@@ -0,0 +1,226 @@
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
+ start : datetime, optional
95
+ Start time of the switchback test.
96
+ units : list[SwitchbackPlanUnit], optional
97
+ List of switchback plan units.
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
+ # This class uses some fields defined in SwitchbackTestMetadata. We are not
155
+ # using inheritance to help the user understand the full structure when using
156
+ # tools like intellisense.
157
+ class SwitchbackTest(BaseModel):
158
+ """
159
+ A Nextmv Cloud switchback test definition.
160
+
161
+ A switchback test is a type of experiment where runs are executed in
162
+ sequential intervals, alternating between different instances to compare
163
+ their performance.
164
+
165
+ You can import the `SwitchbackTest` class directly from `cloud`:
166
+
167
+ ```python
168
+ from nextmv.cloud import SwitchbackTest
169
+ ```
170
+
171
+ Parameters
172
+ ----------
173
+ switchback_test_id : str, optional
174
+ The unique identifier of the switchback test.
175
+ name : str, optional
176
+ Name of the switchback test.
177
+ description : str, optional
178
+ Description of the switchback test.
179
+ app_id : str, optional
180
+ ID of the application to which the switchback test belongs.
181
+ created_at : datetime, optional
182
+ Creation date of the switchback test.
183
+ updated_at : datetime, optional
184
+ Last update date of the switchback test.
185
+ status : ExperimentStatus, optional
186
+ The current status of the switchback test.
187
+ started_at : datetime, optional
188
+ Start date of the switchback test, if applicable.
189
+ completed_at : datetime, optional
190
+ Completion date of the switchback test, if applicable.
191
+ comparison : TestComparisonSingle, optional
192
+ Test comparison defined in the switchback test.
193
+ plan : SwitchbackPlan, optional
194
+ Switchback plan defining the intervals and instance switching.
195
+ runs : list[Run], optional
196
+ List of runs in the switchback test.
197
+ """
198
+
199
+ switchback_test_id: str | None = Field(
200
+ serialization_alias="id",
201
+ validation_alias=AliasChoices("id", "switchback_test_id"),
202
+ default=None,
203
+ )
204
+ """The unique identifier of the switchback test."""
205
+ name: str | None = None
206
+ """Name of the switchback test."""
207
+ description: str | None = None
208
+ """Description of the switchback test."""
209
+ app_id: str | None = None
210
+ """ID of the application to which the switchback test belongs."""
211
+ created_at: datetime | None = None
212
+ """Creation date of the switchback test."""
213
+ updated_at: datetime | None = None
214
+ """Last update date of the switchback test."""
215
+ status: ExperimentStatus | None = None
216
+ """The current status of the switchback test."""
217
+ started_at: datetime | None = None
218
+ """Start date of the switchback test, if applicable."""
219
+ completed_at: datetime | None = None
220
+ """Completion date of the switchback test, if applicable."""
221
+ comparison: TestComparisonSingle | None = None
222
+ """Test comparison defined in the switchback test."""
223
+ plan: SwitchbackPlan | None = None
224
+ """Switchback plan defining the intervals and instance switching."""
225
+ runs: list[Run] | None = None
226
+ """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.dev2
3
+ Version: 1.0.0.dev4
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/