nextmv 0.40.0__py3-none-any.whl → 1.0.0__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 (163) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +20 -204
  110. nextmv/cli/community/list.py +61 -126
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +103 -6
  113. nextmv/cli/configuration/create.py +17 -18
  114. nextmv/cli/configuration/delete.py +25 -13
  115. nextmv/cli/configuration/list.py +4 -4
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +68 -36
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +196 -0
  120. nextmv/cli/version.py +20 -1
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cli/community/community.py +0 -24
  158. nextmv/cli/configuration/configuration.py +0 -23
  159. nextmv/cli/error.py +0 -22
  160. nextmv/cloud/application.py +0 -4204
  161. nextmv-0.40.0.dist-info/RECORD +0 -66
  162. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  163. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
nextmv/cloud/account.py CHANGED
@@ -15,16 +15,18 @@ Account
15
15
  The Nextmv Platform account with API access methods.
16
16
  """
17
17
 
18
- from dataclasses import dataclass
19
18
  from datetime import datetime
20
19
 
20
+ from pydantic import AliasChoices, Field
21
+
21
22
  from nextmv.base_model import BaseModel
22
23
  from nextmv.cloud.client import Client
23
- from nextmv.status import Status, StatusV2
24
+ from nextmv.status import StatusV2
24
25
 
25
26
 
26
27
  class QueuedRun(BaseModel):
27
- """A run that is pending to be executed in the account.
28
+ """
29
+ A run that is pending to be executed in the account.
28
30
 
29
31
  You can import the `QueuedRun` class directly from `cloud`:
30
32
 
@@ -55,8 +57,6 @@ class QueuedRun(BaseModel):
55
57
  ID of the application version used for the run.
56
58
  execution_class : str
57
59
  Execution class used for the run.
58
- status : Status
59
- Deprecated: use status_v2.
60
60
  status_v2 : StatusV2
61
61
  Status of the run.
62
62
 
@@ -72,7 +72,6 @@ class QueuedRun(BaseModel):
72
72
  ... "application_instance_id": "appins-123456",
73
73
  ... "application_version_id": "appver-123456",
74
74
  ... "execution_class": "standard",
75
- ... "status": "RUNNING",
76
75
  ... "status_v2": "RUNNING"
77
76
  ... })
78
77
  >>> print(queued_run.name)
@@ -97,14 +96,13 @@ class QueuedRun(BaseModel):
97
96
  """ID of the application version used for the run."""
98
97
  execution_class: str
99
98
  """Execution class used for the run."""
100
- status: Status
101
- """Deprecated: use status_v2."""
102
99
  status_v2: StatusV2
103
100
  """Status of the run."""
104
101
 
105
102
 
106
103
  class Queue(BaseModel):
107
- """A queue is a list of runs that are pending to be executed, or currently
104
+ """
105
+ A queue is a list of runs that are pending to be executed, or currently
108
106
  being executed, in the account.
109
107
 
110
108
  You can import the `Queue` class directly from `cloud`:
@@ -123,7 +121,9 @@ class Queue(BaseModel):
123
121
 
124
122
  Examples
125
123
  --------
126
- >>> account = Account(client=Client(api_key="your-api-key"))
124
+ >>> from nextmv.cloud import Client, Account
125
+ >>> client = Client(api_key="your-api-key")
126
+ >>> account = Account.get(client=client, account_id="your-account-id")
127
127
  >>> queue = account.queue()
128
128
  >>> print(f"Number of runs in queue: {len(queue.runs)}")
129
129
  Number of runs in queue: 5
@@ -137,9 +137,54 @@ class Queue(BaseModel):
137
137
  """List of runs in the queue."""
138
138
 
139
139
 
140
- @dataclass
141
- class Account:
142
- """The Nextmv Platform account.
140
+ class AccountMember(BaseModel):
141
+ """
142
+ A member of a Nextmv Cloud account (organization).
143
+
144
+ You can import the `AccountMember` class directly from `cloud`:
145
+
146
+ ```python
147
+ from nextmv.cloud import AccountMember
148
+ ```
149
+
150
+ Represents an individual member of an organization in Nextmv Cloud,
151
+ including their role and invitation status.
152
+
153
+ Attributes
154
+ ----------
155
+ email : str | None
156
+ Email of the account member.
157
+ role : str | None
158
+ Role of the account member.
159
+ pending_invite : bool | None
160
+ Whether the member has a pending invite.
161
+
162
+ Examples
163
+ --------
164
+ >>> member = AccountMember.from_dict({
165
+ ... "email": "peter.rabbit@carrotexpress.com",
166
+ ... "role": "admin",
167
+ ... "pending_invite": False
168
+ ... })
169
+ >>> print(f"{member.email} - {member.role}")
170
+ peter.rabbit@carrotexpress.com - admin
171
+ """
172
+
173
+ email: str | None = None
174
+ """Email of the account member."""
175
+ role: str | None = None
176
+ """Role of the account member."""
177
+ pending_invite: bool | None = None
178
+ """Whether the member has a pending invite."""
179
+
180
+
181
+ class Account(BaseModel):
182
+ """
183
+ The Nextmv Cloud account (organization).
184
+
185
+ To handle managed accounts, SSO must be configured for your organization.
186
+ Please contact [Nextmv support](https://www.nextmv.io/contact) for
187
+ assistance.
143
188
 
144
189
  You can import the `Account` class directly from `cloud`:
145
190
 
@@ -147,41 +192,191 @@ class Account:
147
192
  from nextmv.cloud import Account
148
193
  ```
149
194
 
150
- This class provides access to account-level operations in the Nextmv Platform,
195
+ This class provides access to account-level operations in the Nextmv Cloud,
151
196
  such as retrieving the queue of runs.
152
197
 
153
- Parameters
154
- ----------
155
- client : Client
156
- Client to use for interacting with the Nextmv Cloud API.
157
- endpoint : str, optional
158
- Base endpoint for the account, by default "v1/account"
198
+ Note: It is recommended to use `Account.get()` or `Account.new()`
199
+ instead of direct initialization to ensure proper setup.
159
200
 
160
- Attributes
201
+ Parameters
161
202
  ----------
162
203
  client : Client
163
204
  Client to use for interacting with the Nextmv Cloud API.
164
- endpoint : str
165
- Base endpoint for the account.
205
+ account_id : str, optional
206
+ ID of the account (organization).
207
+ name : str, optional
208
+ Name of the account (organization).
209
+ members : list[AccountMember], optional
210
+ List of members in the account (organization).
211
+ account_endpoint : str, default="v1/account"
212
+ Base endpoint for the account (SDK-specific).
213
+ organization_endpoint : str, default="v1/organization/{organization_id}"
214
+ Base endpoint for organization operations (SDK-specific).
166
215
 
167
216
  Examples
168
217
  --------
169
218
  >>> from nextmv.cloud import Client, Account
170
219
  >>> client = Client(api_key="your-api-key")
171
- >>> account = Account(client=client)
220
+ >>> # Retrieve an existing account
221
+ >>> account = Account.get(client=client, account_id="your-account-id")
222
+ >>> print(f"Account name: {account.name}")
223
+ Account name: Bunny Logistics
224
+ >>> # Create a new account
225
+ >>> new_account = Account.new(client=client, name="Hare Delivery Co", admins=["admin@example.com"])
226
+ >>> # Get the queue of runs
172
227
  >>> queue = account.queue()
173
228
  >>> print(f"Number of runs in queue: {len(queue.runs)}")
174
229
  Number of runs in queue: 3
175
230
  """
176
231
 
177
- client: Client
232
+ # Actual API attributes of an account.
233
+ account_id: str | None = Field(
234
+ serialization_alias="id",
235
+ validation_alias=AliasChoices("id", "account_id"),
236
+ default=None,
237
+ )
238
+ """ID of the account (organization)."""
239
+ name: str | None = None
240
+ """Name of the account (organization)."""
241
+ members: list[AccountMember] | None = None
242
+ """List of members in the account (organization)."""
243
+
244
+ # SDK-specific attributes for convenience when using methods.
245
+ client: Client = Field(exclude=True)
178
246
  """Client to use for interacting with the Nextmv Cloud API."""
179
-
180
- endpoint: str = "v1/account"
247
+ account_endpoint: str = Field(exclude=True, default="v1/account")
181
248
  """Base endpoint for the account."""
249
+ organization_endpoint: str = Field(exclude=True, default="v1/organization/{organization_id}")
250
+
251
+ def model_post_init(self, __context) -> None:
252
+ """
253
+ Initialize the organization_endpoint attribute.
254
+
255
+ This method is automatically called after class initialization to
256
+ format the organization_endpoint URL with the account ID.
257
+ """
258
+
259
+ self.organization_endpoint = self.organization_endpoint.format(organization_id=self.account_id)
260
+
261
+ @classmethod
262
+ def get(cls, client: Client, account_id: str) -> "Account":
263
+ """
264
+ Retrieve an account directly from Nextmv Cloud.
265
+
266
+ This function is useful if you want to populate an `Account` class
267
+ by fetching the attributes directly from Nextmv Cloud.
268
+
269
+ Parameters
270
+ ----------
271
+ client : Client
272
+ Client to use for interacting with the Nextmv Cloud API.
273
+ account_id : str
274
+ ID of the account to retrieve.
275
+
276
+ Returns
277
+ -------
278
+ Account
279
+ The requested account.
280
+
281
+ Raises
282
+ ------
283
+ requests.HTTPError
284
+ If the response status code is not 2xx.
285
+
286
+ Examples
287
+ --------
288
+ >>> from nextmv.cloud import Client, Account
289
+ >>> client = Client(api_key="your-api-key")
290
+ >>> account = Account.get(client=client, account_id="bunny-logistics")
291
+ >>> print(f"Account: {account.name}")
292
+ Account: Bunny Logistics
293
+ >>> print(f"Members: {len(account.members)}")
294
+ Members: 3
295
+ """
296
+
297
+ response = client.request(
298
+ method="GET",
299
+ endpoint=f"v1/organization/{account_id}",
300
+ )
301
+
302
+ return cls.from_dict({"client": client} | response.json())
303
+
304
+ @classmethod
305
+ def new(
306
+ cls,
307
+ client: Client,
308
+ name: str,
309
+ admins: list[str],
310
+ ) -> "Account":
311
+ """
312
+ Create a new account (organization) directly in Nextmv Cloud.
313
+
314
+ To create managed accounts, SSO must be configured for your
315
+ organization. Please contact [Nextmv
316
+ support](https://www.nextmv.io/contact) for assistance.
317
+
318
+ Parameters
319
+ ----------
320
+ client : Client
321
+ Client to use for interacting with the Nextmv Cloud API.
322
+ name : str
323
+ Name of the new account.
324
+ admins : list[str]
325
+ List of admin user emails for the new account.
326
+
327
+ Returns
328
+ -------
329
+ Account
330
+ The newly created account.
331
+
332
+ Examples
333
+ --------
334
+ >>> from nextmv.cloud import Client
335
+ >>> client = Client(api_key="your-api-key")
336
+ >>> account = Account.new(client=client, name="My New Account", admins=["admin@example.com"])
337
+ """
338
+
339
+ if len(admins) == 0:
340
+ raise ValueError("at least one admin email must be provided to create an account")
341
+
342
+ payload = {
343
+ "name": name,
344
+ "admins": admins,
345
+ }
346
+
347
+ response = client.request(
348
+ method="POST",
349
+ endpoint="v1/organization",
350
+ payload=payload,
351
+ )
352
+
353
+ return cls.from_dict({"client": client} | response.json())
354
+
355
+ def delete(self) -> None:
356
+ """
357
+ Delete the account.
358
+
359
+ Permanently removes the account (organization) from Nextmv Cloud. You
360
+ must have the administrator role on that account in order to delete it.
361
+
362
+ Raises
363
+ ------
364
+ requests.HTTPError
365
+ If the response status code is not 2xx.
366
+
367
+ Examples
368
+ --------
369
+ >>> account.delete() # Permanently deletes the account
370
+ """
371
+
372
+ _ = self.client.request(
373
+ method="DELETE",
374
+ endpoint=self.organization_endpoint,
375
+ )
182
376
 
183
377
  def queue(self) -> Queue:
184
- """Get the queue of runs in the account.
378
+ """
379
+ Get the queue of runs in the account.
185
380
 
186
381
  Retrieves the current list of runs that are pending or being executed
187
382
  in the Nextmv account.
@@ -198,7 +393,9 @@ class Account:
198
393
 
199
394
  Examples
200
395
  --------
201
- >>> account = Account(client=Client(api_key="your-api-key"))
396
+ >>> from nextmv.cloud import Client, Account
397
+ >>> client = Client(api_key="your-api-key")
398
+ >>> account = Account.get(client=client, account_id="your-account-id")
202
399
  >>> queue = account.queue()
203
400
  >>> for run in queue.runs:
204
401
  ... print(f"Run {run.id}: {run.name} - Status: {run.status_v2}")
@@ -207,7 +404,49 @@ class Account:
207
404
  """
208
405
  response = self.client.request(
209
406
  method="GET",
210
- endpoint=self.endpoint + "/queue",
407
+ endpoint=self.account_endpoint + "/queue",
211
408
  )
212
409
 
213
410
  return Queue.from_dict(response.json())
411
+
412
+ def update(self, name: str) -> "Account":
413
+ """
414
+ Update the account.
415
+
416
+ Parameters
417
+ ----------
418
+ name : str
419
+ Name of the account.
420
+
421
+ Returns
422
+ -------
423
+ Account
424
+ The updated account.
425
+
426
+ Raises
427
+ ------
428
+ requests.HTTPError
429
+ If the response status code is not 2xx.
430
+
431
+ Examples
432
+ --------
433
+ >>> from nextmv.cloud import Client, Account
434
+ >>> client = Client(api_key="your-api-key")
435
+ >>> account = Account.get(client=client, account_id="bunny-logistics")
436
+ >>> updated_account = account.update(name="Bunny Express Logistics")
437
+ >>> print(updated_account.name)
438
+ Bunny Express Logistics
439
+ """
440
+
441
+ account = self.get(client=self.client, account_id=self.account_id)
442
+ account_dict = account.to_dict()
443
+ payload = account_dict.copy()
444
+ payload["name"] = name
445
+
446
+ response = self.client.request(
447
+ method="PUT",
448
+ endpoint=self.organization_endpoint,
449
+ payload=payload,
450
+ )
451
+
452
+ return Account.from_dict({"client": self.client} | response.json())