nextmv 0.40.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.
Files changed (129) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__init__.py +2 -0
  3. nextmv/cli/CONTRIBUTING.md +511 -0
  4. nextmv/cli/cloud/__init__.py +45 -0
  5. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  6. nextmv/cli/cloud/acceptance/create.py +393 -0
  7. nextmv/cli/cloud/acceptance/delete.py +68 -0
  8. nextmv/cli/cloud/acceptance/get.py +104 -0
  9. nextmv/cli/cloud/acceptance/list.py +62 -0
  10. nextmv/cli/cloud/acceptance/update.py +95 -0
  11. nextmv/cli/cloud/account/__init__.py +28 -0
  12. nextmv/cli/cloud/account/create.py +83 -0
  13. nextmv/cli/cloud/account/delete.py +60 -0
  14. nextmv/cli/cloud/account/get.py +66 -0
  15. nextmv/cli/cloud/account/update.py +70 -0
  16. nextmv/cli/cloud/app/__init__.py +35 -0
  17. nextmv/cli/cloud/app/create.py +141 -0
  18. nextmv/cli/cloud/app/delete.py +58 -0
  19. nextmv/cli/cloud/app/exists.py +44 -0
  20. nextmv/cli/cloud/app/get.py +66 -0
  21. nextmv/cli/cloud/app/list.py +61 -0
  22. nextmv/cli/cloud/app/push.py +137 -0
  23. nextmv/cli/cloud/app/update.py +124 -0
  24. nextmv/cli/cloud/batch/__init__.py +29 -0
  25. nextmv/cli/cloud/batch/create.py +454 -0
  26. nextmv/cli/cloud/batch/delete.py +68 -0
  27. nextmv/cli/cloud/batch/get.py +104 -0
  28. nextmv/cli/cloud/batch/list.py +63 -0
  29. nextmv/cli/cloud/batch/metadata.py +66 -0
  30. nextmv/cli/cloud/batch/update.py +95 -0
  31. nextmv/cli/cloud/data/__init__.py +26 -0
  32. nextmv/cli/cloud/data/upload.py +162 -0
  33. nextmv/cli/cloud/ensemble/__init__.py +31 -0
  34. nextmv/cli/cloud/ensemble/create.py +414 -0
  35. nextmv/cli/cloud/ensemble/delete.py +67 -0
  36. nextmv/cli/cloud/ensemble/get.py +65 -0
  37. nextmv/cli/cloud/ensemble/update.py +103 -0
  38. nextmv/cli/cloud/input_set/__init__.py +30 -0
  39. nextmv/cli/cloud/input_set/create.py +168 -0
  40. nextmv/cli/cloud/input_set/get.py +63 -0
  41. nextmv/cli/cloud/input_set/list.py +63 -0
  42. nextmv/cli/cloud/input_set/update.py +123 -0
  43. nextmv/cli/cloud/instance/__init__.py +35 -0
  44. nextmv/cli/cloud/instance/create.py +290 -0
  45. nextmv/cli/cloud/instance/delete.py +62 -0
  46. nextmv/cli/cloud/instance/exists.py +39 -0
  47. nextmv/cli/cloud/instance/get.py +62 -0
  48. nextmv/cli/cloud/instance/list.py +60 -0
  49. nextmv/cli/cloud/instance/update.py +216 -0
  50. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  51. nextmv/cli/cloud/managed_input/create.py +146 -0
  52. nextmv/cli/cloud/managed_input/delete.py +65 -0
  53. nextmv/cli/cloud/managed_input/get.py +63 -0
  54. nextmv/cli/cloud/managed_input/list.py +60 -0
  55. nextmv/cli/cloud/managed_input/update.py +97 -0
  56. nextmv/cli/cloud/run/__init__.py +37 -0
  57. nextmv/cli/cloud/run/cancel.py +37 -0
  58. nextmv/cli/cloud/run/create.py +530 -0
  59. nextmv/cli/cloud/run/get.py +199 -0
  60. nextmv/cli/cloud/run/input.py +86 -0
  61. nextmv/cli/cloud/run/list.py +80 -0
  62. nextmv/cli/cloud/run/logs.py +167 -0
  63. nextmv/cli/cloud/run/metadata.py +67 -0
  64. nextmv/cli/cloud/run/track.py +501 -0
  65. nextmv/cli/cloud/scenario/__init__.py +29 -0
  66. nextmv/cli/cloud/scenario/create.py +451 -0
  67. nextmv/cli/cloud/scenario/delete.py +65 -0
  68. nextmv/cli/cloud/scenario/get.py +102 -0
  69. nextmv/cli/cloud/scenario/list.py +63 -0
  70. nextmv/cli/cloud/scenario/metadata.py +67 -0
  71. nextmv/cli/cloud/scenario/update.py +93 -0
  72. nextmv/cli/cloud/secrets/__init__.py +33 -0
  73. nextmv/cli/cloud/secrets/create.py +206 -0
  74. nextmv/cli/cloud/secrets/delete.py +67 -0
  75. nextmv/cli/cloud/secrets/get.py +66 -0
  76. nextmv/cli/cloud/secrets/list.py +60 -0
  77. nextmv/cli/cloud/secrets/update.py +147 -0
  78. nextmv/cli/cloud/upload/__init__.py +22 -0
  79. nextmv/cli/cloud/upload/create.py +39 -0
  80. nextmv/cli/cloud/version/__init__.py +33 -0
  81. nextmv/cli/cloud/version/create.py +97 -0
  82. nextmv/cli/cloud/version/delete.py +62 -0
  83. nextmv/cli/cloud/version/exists.py +39 -0
  84. nextmv/cli/cloud/version/get.py +62 -0
  85. nextmv/cli/cloud/version/list.py +60 -0
  86. nextmv/cli/cloud/version/update.py +92 -0
  87. nextmv/cli/community/__init__.py +24 -0
  88. nextmv/cli/community/clone.py +3 -3
  89. nextmv/cli/community/list.py +1 -1
  90. nextmv/cli/configuration/__init__.py +23 -0
  91. nextmv/cli/configuration/config.py +68 -4
  92. nextmv/cli/configuration/create.py +14 -15
  93. nextmv/cli/configuration/delete.py +24 -12
  94. nextmv/cli/configuration/list.py +1 -1
  95. nextmv/cli/main.py +58 -16
  96. nextmv/cli/message.py +153 -0
  97. nextmv/cli/options.py +168 -0
  98. nextmv/cli/version.py +20 -1
  99. nextmv/cloud/__init__.py +4 -1
  100. nextmv/cloud/acceptance_test.py +19 -18
  101. nextmv/cloud/account.py +268 -24
  102. nextmv/cloud/application/__init__.py +955 -0
  103. nextmv/cloud/application/_acceptance.py +419 -0
  104. nextmv/cloud/application/_batch_scenario.py +860 -0
  105. nextmv/cloud/application/_ensemble.py +251 -0
  106. nextmv/cloud/application/_input_set.py +227 -0
  107. nextmv/cloud/application/_instance.py +289 -0
  108. nextmv/cloud/application/_managed_input.py +227 -0
  109. nextmv/cloud/application/_run.py +1393 -0
  110. nextmv/cloud/application/_secrets.py +294 -0
  111. nextmv/cloud/application/_utils.py +54 -0
  112. nextmv/cloud/application/_version.py +303 -0
  113. nextmv/cloud/batch_experiment.py +3 -1
  114. nextmv/cloud/instance.py +11 -1
  115. nextmv/cloud/integration.py +1 -1
  116. nextmv/cloud/package.py +50 -9
  117. nextmv/input.py +20 -36
  118. nextmv/local/application.py +3 -15
  119. nextmv/polling.py +54 -16
  120. nextmv/run.py +83 -27
  121. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/METADATA +33 -8
  122. nextmv-1.0.0.dev0.dist-info/RECORD +158 -0
  123. nextmv/cli/community/community.py +0 -24
  124. nextmv/cli/configuration/configuration.py +0 -23
  125. nextmv/cli/error.py +0 -22
  126. nextmv/cloud/application.py +0 -4204
  127. nextmv-0.40.0.dist-info/RECORD +0 -66
  128. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/WHEEL +0 -0
  129. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/cli/options.py CHANGED
@@ -22,3 +22,171 @@ ProfileOption = Annotated[
22
22
  metavar="PROFILE_NAME",
23
23
  ),
24
24
  ]
25
+
26
+ # app_id option - can be used in any command that requires an application ID.
27
+ # Define it as follows in commands or callbacks, as necessary:
28
+ # app_id: AppIDOption
29
+ AppIDOption = Annotated[
30
+ str,
31
+ typer.Option(
32
+ "--app-id",
33
+ "-a",
34
+ help="The Nextmv Cloud application ID to use for this action.",
35
+ envvar="NEXTMV_APP_ID",
36
+ metavar="APP_ID",
37
+ ),
38
+ ]
39
+
40
+ # run_id option - can be used in any command that requires a run ID.
41
+ # Define it as follows in commands or callbacks, as necessary:
42
+ # run_id: RunIDOption
43
+ RunIDOption = Annotated[
44
+ str,
45
+ typer.Option(
46
+ "--run-id",
47
+ "-r",
48
+ help="The Nextmv Cloud run ID to use for this action.",
49
+ envvar="NEXTMV_RUN_ID",
50
+ metavar="RUN_ID",
51
+ ),
52
+ ]
53
+
54
+ # version_id option - can be used in any command that requires a version ID.
55
+ # Define it as follows in commands or callbacks, as necessary:
56
+ # version_id: VersionIDOption
57
+ VersionIDOption = Annotated[
58
+ str,
59
+ typer.Option(
60
+ "--version-id",
61
+ "-v",
62
+ help="The Nextmv Cloud version ID to use for this action.",
63
+ envvar="NEXTMV_VERSION_ID",
64
+ metavar="VERSION_ID",
65
+ ),
66
+ ]
67
+
68
+ # input_set_id option - can be used in any command that requires an input set ID.
69
+ # Define it as follows in commands or callbacks, as necessary:
70
+ # input_set_id: InputSetIDOption
71
+ InputSetIDOption = Annotated[
72
+ str,
73
+ typer.Option(
74
+ "--input-set-id",
75
+ "-s",
76
+ help="The Nextmv Cloud input set ID to use for this action.",
77
+ envvar="NEXTMV_INPUT_SET_ID",
78
+ metavar="INPUT_SET_ID",
79
+ ),
80
+ ]
81
+
82
+ # instance_id option - can be used in any command that requires an instance ID.
83
+ # Define it as follows in commands or callbacks, as necessary:
84
+ # instance_id: InstanceIDOption
85
+ InstanceIDOption = Annotated[
86
+ str,
87
+ typer.Option(
88
+ "--instance-id",
89
+ "-i",
90
+ help="The Nextmv Cloud instance ID to use for this action.",
91
+ envvar="NEXTMV_INSTANCE_ID",
92
+ metavar="INSTANCE_ID",
93
+ ),
94
+ ]
95
+
96
+ # managed_input_id option - can be used in any command that requires a managed input ID.
97
+ # Define it as follows in commands or callbacks, as necessary:
98
+ # managed_input_id: ManagedInputIDOption
99
+ ManagedInputIDOption = Annotated[
100
+ str,
101
+ typer.Option(
102
+ "--managed-input-id",
103
+ "-m",
104
+ help="The Nextmv Cloud managed input ID to use for this action.",
105
+ envvar="NEXTMV_MANAGED_INPUT_ID",
106
+ metavar="MANAGED_INPUT_ID",
107
+ ),
108
+ ]
109
+
110
+ # ensemble_definition_id option - can be used in any command that requires an ensemble definition ID.
111
+ # Define it as follows in commands or callbacks, as necessary:
112
+ # ensemble_definition_id: EnsembleDefinitionIDOption
113
+ EnsembleDefinitionIDOption = Annotated[
114
+ str,
115
+ typer.Option(
116
+ "--ensemble-definition-id",
117
+ "-e",
118
+ help="The Nextmv Cloud ensemble definition ID to use for this action.",
119
+ envvar="NEXTMV_ENSEMBLE_DEFINITION_ID",
120
+ metavar="ENSEMBLE_DEFINITION_ID",
121
+ ),
122
+ ]
123
+
124
+ # account_id option - can be used in any command that requires an account ID.
125
+ # Define it as follows in commands or callbacks, as necessary:
126
+ # account_id: AccountIDOption
127
+ AccountIDOption = Annotated[
128
+ str,
129
+ typer.Option(
130
+ "--account-id",
131
+ "-a",
132
+ help="The Nextmv Cloud account ID to use for this action.",
133
+ envvar="NEXTMV_ACCOUNT_ID",
134
+ metavar="ACCOUNT_ID",
135
+ ),
136
+ ]
137
+
138
+ # acceptance_test_id option - can be used in any command that requires an acceptance test ID.
139
+ # Define it as follows in commands or callbacks, as necessary:
140
+ # acceptance_test_id: AcceptanceTestIDOption
141
+ AcceptanceTestIDOption = Annotated[
142
+ str,
143
+ typer.Option(
144
+ "--acceptance-test-id",
145
+ "-t",
146
+ help="The Nextmv Cloud acceptance test ID to use for this action.",
147
+ envvar="NEXTMV_ACCEPTANCE_TEST_ID",
148
+ metavar="ACCEPTANCE_TEST_ID",
149
+ ),
150
+ ]
151
+
152
+ # batch_experiment_id option - can be used in any command that requires a batch experiment ID.
153
+ # Define it as follows in commands or callbacks, as necessary:
154
+ # batch_experiment_id: BatchExperimentIDOption
155
+ BatchExperimentIDOption = Annotated[
156
+ str,
157
+ typer.Option(
158
+ "--batch-experiment-id",
159
+ "-b",
160
+ help="The Nextmv Cloud batch experiment ID to use for this action.",
161
+ envvar="NEXTMV_BATCH_EXPERIMENT_ID",
162
+ metavar="BATCH_EXPERIMENT_ID",
163
+ ),
164
+ ]
165
+
166
+ # scenario_test_id option - can be used in any command that requires a scenario test ID.
167
+ # Define it as follows in commands or callbacks, as necessary:
168
+ # scenario_test_id: ScenarioTestIDOption
169
+ ScenarioTestIDOption = Annotated[
170
+ str,
171
+ typer.Option(
172
+ "--scenario-test-id",
173
+ "-i",
174
+ help="The Nextmv Cloud scenario test ID to use for this action.",
175
+ envvar="NEXTMV_SCENARIO_TEST_ID",
176
+ metavar="SCENARIO_TEST_ID",
177
+ ),
178
+ ]
179
+
180
+ # secrets_collection_id option - can be used in any command that requires a secrets collection ID.
181
+ # Define it as follows in commands or callbacks, as necessary:
182
+ # secrets_collection_id: SecretsCollectionIDOption
183
+ SecretsCollectionIDOption = Annotated[
184
+ str,
185
+ typer.Option(
186
+ "--secrets-collection-id",
187
+ "-s",
188
+ help="The Nextmv Cloud secrets collection ID to use for this action.",
189
+ envvar="NEXTMV_SECRETS_COLLECTION_ID",
190
+ metavar="SECRETS_COLLECTION_ID",
191
+ ),
192
+ ]
nextmv/cli/version.py CHANGED
@@ -14,6 +14,25 @@ app = typer.Typer()
14
14
  def version() -> None:
15
15
  """
16
16
  Show the current version of the Nextmv CLI.
17
+
18
+ [bold][underline]Examples[/underline][/bold]
19
+
20
+ - Show the version.
21
+ $ [green]nextmv version[/green]
17
22
  """
18
23
 
19
- print(__version__)
24
+ version_callback(True)
25
+
26
+
27
+ def version_callback(value: bool):
28
+ """
29
+ Callback function to display the version.
30
+
31
+ Parameters
32
+ ----------
33
+ value : bool
34
+ If True, print the version and exit.
35
+ """
36
+ if value:
37
+ print(__version__)
38
+ raise typer.Exit()
nextmv/cloud/__init__.py CHANGED
@@ -15,6 +15,7 @@ from nextmv.manifest import ManifestPythonModel as ManifestPythonModel
15
15
  from nextmv.manifest import ManifestRuntime as ManifestRuntime
16
16
  from nextmv.manifest import ManifestType as ManifestType
17
17
  from nextmv.polling import PollingOptions as PollingOptions
18
+ from nextmv.polling import poll as poll
18
19
  from nextmv.run import ErrorLog as ErrorLog
19
20
  from nextmv.run import ExternalRunResult as ExternalRunResult
20
21
  from nextmv.run import Format as Format
@@ -53,10 +54,12 @@ from .acceptance_test import ResultStatistics as ResultStatistics
53
54
  from .acceptance_test import StatisticType as StatisticType
54
55
  from .acceptance_test import ToleranceType as ToleranceType
55
56
  from .account import Account as Account
57
+ from .account import AccountMember as AccountMember
56
58
  from .account import Queue as Queue
57
59
  from .account import QueuedRun as QueuedRun
58
60
  from .application import Application as Application
59
- from .application import poll as poll
61
+ from .application import ApplicationType as ApplicationType
62
+ from .application import list_applications as list_applications
60
63
  from .assets import RunAsset as RunAsset
61
64
  from .batch_experiment import BatchExperiment as BatchExperiment
62
65
  from .batch_experiment import BatchExperimentInformation as BatchExperimentInformation
@@ -888,20 +888,20 @@ class AcceptanceTest(BaseModel):
888
888
  Name of the acceptance test.
889
889
  description : str
890
890
  Description of the acceptance test.
891
- app_id : str
891
+ created_at : datetime
892
+ Creation date of the acceptance test.
893
+ updated_at : datetime
894
+ Last update date of the acceptance test.
895
+ app_id : str, optional
892
896
  ID of the app that owns the acceptance test.
893
- experiment_id : str
897
+ experiment_id : str, optional
894
898
  ID of the batch experiment underlying the acceptance test.
895
- control : ComparisonInstance
899
+ control : ComparisonInstance, optional
896
900
  Control instance of the acceptance test.
897
- candidate : ComparisonInstance
901
+ candidate : ComparisonInstance, optional
898
902
  Candidate instance of the acceptance test.
899
- metrics : list[Metric]
903
+ metrics : list[Metric], optional
900
904
  Metrics to evaluate in the acceptance test.
901
- created_at : datetime
902
- Creation date of the acceptance test.
903
- updated_at : datetime
904
- Last update date of the acceptance test.
905
905
  status : ExperimentStatus, optional
906
906
  Status of the acceptance test.
907
907
  results : AcceptanceTestResults, optional
@@ -942,20 +942,21 @@ class AcceptanceTest(BaseModel):
942
942
  """Name of the acceptance test."""
943
943
  description: str
944
944
  """Description of the acceptance test."""
945
- app_id: str
945
+ created_at: datetime
946
+ """Creation date of the acceptance test."""
947
+ updated_at: datetime
948
+ """Last update date of the acceptance test."""
949
+
950
+ app_id: str | None = None
946
951
  """ID of the app that owns the acceptance test."""
947
- experiment_id: str
952
+ experiment_id: str | None = None
948
953
  """ID of the batch experiment underlying in the acceptance test."""
949
- control: ComparisonInstance
954
+ control: ComparisonInstance | None = None
950
955
  """Control instance of the acceptance test."""
951
- candidate: ComparisonInstance
956
+ candidate: ComparisonInstance | None = None
952
957
  """Candidate instance of the acceptance test."""
953
- metrics: list[Metric]
958
+ metrics: list[Metric] | None = None
954
959
  """Metrics of the acceptance test."""
955
- created_at: datetime
956
- """Creation date of the acceptance test."""
957
- updated_at: datetime
958
- """Last update date of the acceptance test."""
959
960
  status: ExperimentStatus | None = ExperimentStatus.UNKNOWN
960
961
  """Status of the acceptance test."""
961
962
  results: AcceptanceTestResults | None = None
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
24
  from nextmv.status import Status, 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
 
@@ -104,7 +106,8 @@ class QueuedRun(BaseModel):
104
106
 
105
107
 
106
108
  class Queue(BaseModel):
107
- """A queue is a list of runs that are pending to be executed, or currently
109
+ """
110
+ A queue is a list of runs that are pending to be executed, or currently
108
111
  being executed, in the account.
109
112
 
110
113
  You can import the `Queue` class directly from `cloud`:
@@ -123,7 +126,9 @@ class Queue(BaseModel):
123
126
 
124
127
  Examples
125
128
  --------
126
- >>> account = Account(client=Client(api_key="your-api-key"))
129
+ >>> from nextmv.cloud import Client, Account
130
+ >>> client = Client(api_key="your-api-key")
131
+ >>> account = Account.get(client=client, account_id="your-account-id")
127
132
  >>> queue = account.queue()
128
133
  >>> print(f"Number of runs in queue: {len(queue.runs)}")
129
134
  Number of runs in queue: 5
@@ -137,9 +142,54 @@ class Queue(BaseModel):
137
142
  """List of runs in the queue."""
138
143
 
139
144
 
140
- @dataclass
141
- class Account:
142
- """The Nextmv Platform account.
145
+ class AccountMember(BaseModel):
146
+ """
147
+ A member of a Nextmv Cloud account (organization).
148
+
149
+ You can import the `AccountMember` class directly from `cloud`:
150
+
151
+ ```python
152
+ from nextmv.cloud import AccountMember
153
+ ```
154
+
155
+ Represents an individual member of an organization in Nextmv Cloud,
156
+ including their role and invitation status.
157
+
158
+ Attributes
159
+ ----------
160
+ email : str | None
161
+ Email of the account member.
162
+ role : str | None
163
+ Role of the account member.
164
+ pending_invite : bool | None
165
+ Whether the member has a pending invite.
166
+
167
+ Examples
168
+ --------
169
+ >>> member = AccountMember.from_dict({
170
+ ... "email": "peter.rabbit@carrotexpress.com",
171
+ ... "role": "admin",
172
+ ... "pending_invite": False
173
+ ... })
174
+ >>> print(f"{member.email} - {member.role}")
175
+ peter.rabbit@carrotexpress.com - admin
176
+ """
177
+
178
+ email: str | None = None
179
+ """Email of the account member."""
180
+ role: str | None = None
181
+ """Role of the account member."""
182
+ pending_invite: bool | None = None
183
+ """Whether the member has a pending invite."""
184
+
185
+
186
+ class Account(BaseModel):
187
+ """
188
+ The Nextmv Cloud account (organization).
189
+
190
+ To handle managed accounts, SSO must be configured for your organization.
191
+ Please contact [Nextmv support](https://www.nextmv.io/contact) for
192
+ assistance.
143
193
 
144
194
  You can import the `Account` class directly from `cloud`:
145
195
 
@@ -147,41 +197,191 @@ class Account:
147
197
  from nextmv.cloud import Account
148
198
  ```
149
199
 
150
- This class provides access to account-level operations in the Nextmv Platform,
200
+ This class provides access to account-level operations in the Nextmv Cloud,
151
201
  such as retrieving the queue of runs.
152
202
 
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"
203
+ Note: It is recommended to use `Account.get()` or `Account.new()`
204
+ instead of direct initialization to ensure proper setup.
159
205
 
160
- Attributes
206
+ Parameters
161
207
  ----------
162
208
  client : Client
163
209
  Client to use for interacting with the Nextmv Cloud API.
164
- endpoint : str
165
- Base endpoint for the account.
210
+ account_id : str, optional
211
+ ID of the account (organization).
212
+ name : str, optional
213
+ Name of the account (organization).
214
+ members : list[AccountMember], optional
215
+ List of members in the account (organization).
216
+ account_endpoint : str, default="v1/account"
217
+ Base endpoint for the account (SDK-specific).
218
+ organization_endpoint : str, default="v1/organization/{organization_id}"
219
+ Base endpoint for organization operations (SDK-specific).
166
220
 
167
221
  Examples
168
222
  --------
169
223
  >>> from nextmv.cloud import Client, Account
170
224
  >>> client = Client(api_key="your-api-key")
171
- >>> account = Account(client=client)
225
+ >>> # Retrieve an existing account
226
+ >>> account = Account.get(client=client, account_id="your-account-id")
227
+ >>> print(f"Account name: {account.name}")
228
+ Account name: Bunny Logistics
229
+ >>> # Create a new account
230
+ >>> new_account = Account.new(client=client, name="Hare Delivery Co", admins=["admin@example.com"])
231
+ >>> # Get the queue of runs
172
232
  >>> queue = account.queue()
173
233
  >>> print(f"Number of runs in queue: {len(queue.runs)}")
174
234
  Number of runs in queue: 3
175
235
  """
176
236
 
177
- client: Client
237
+ # Actual API attributes of an account.
238
+ account_id: str | None = Field(
239
+ serialization_alias="id",
240
+ validation_alias=AliasChoices("id", "account_id"),
241
+ default=None,
242
+ )
243
+ """ID of the account (organization)."""
244
+ name: str | None = None
245
+ """Name of the account (organization)."""
246
+ members: list[AccountMember] | None = None
247
+ """List of members in the account (organization)."""
248
+
249
+ # SDK-specific attributes for convenience when using methods.
250
+ client: Client = Field(exclude=True)
178
251
  """Client to use for interacting with the Nextmv Cloud API."""
179
-
180
- endpoint: str = "v1/account"
252
+ account_endpoint: str = Field(exclude=True, default="v1/account")
181
253
  """Base endpoint for the account."""
254
+ organization_endpoint: str = Field(exclude=True, default="v1/organization/{organization_id}")
255
+
256
+ def model_post_init(self, __context) -> None:
257
+ """
258
+ Initialize the organization_endpoint attribute.
259
+
260
+ This method is automatically called after class initialization to
261
+ format the organization_endpoint URL with the account ID.
262
+ """
263
+
264
+ self.organization_endpoint = self.organization_endpoint.format(organization_id=self.account_id)
265
+
266
+ @classmethod
267
+ def get(cls, client: Client, account_id: str) -> "Account":
268
+ """
269
+ Retrieve an account directly from Nextmv Cloud.
270
+
271
+ This function is useful if you want to populate an `Account` class
272
+ by fetching the attributes directly from Nextmv Cloud.
273
+
274
+ Parameters
275
+ ----------
276
+ client : Client
277
+ Client to use for interacting with the Nextmv Cloud API.
278
+ account_id : str
279
+ ID of the account to retrieve.
280
+
281
+ Returns
282
+ -------
283
+ Account
284
+ The requested account.
285
+
286
+ Raises
287
+ ------
288
+ requests.HTTPError
289
+ If the response status code is not 2xx.
290
+
291
+ Examples
292
+ --------
293
+ >>> from nextmv.cloud import Client, Account
294
+ >>> client = Client(api_key="your-api-key")
295
+ >>> account = Account.get(client=client, account_id="bunny-logistics")
296
+ >>> print(f"Account: {account.name}")
297
+ Account: Bunny Logistics
298
+ >>> print(f"Members: {len(account.members)}")
299
+ Members: 3
300
+ """
301
+
302
+ response = client.request(
303
+ method="GET",
304
+ endpoint=f"v1/organization/{account_id}",
305
+ )
306
+
307
+ return cls.from_dict({"client": client} | response.json())
308
+
309
+ @classmethod
310
+ def new(
311
+ cls,
312
+ client: Client,
313
+ name: str,
314
+ admins: list[str],
315
+ ) -> "Account":
316
+ """
317
+ Create a new account (organization) directly in Nextmv Cloud.
318
+
319
+ To create managed accounts, SSO must be configured for your
320
+ organization. Please contact [Nextmv
321
+ support](https://www.nextmv.io/contact) for assistance.
322
+
323
+ Parameters
324
+ ----------
325
+ client : Client
326
+ Client to use for interacting with the Nextmv Cloud API.
327
+ name : str
328
+ Name of the new account.
329
+ admins : list[str]
330
+ List of admin user emails for the new account.
331
+
332
+ Returns
333
+ -------
334
+ Account
335
+ The newly created account.
336
+
337
+ Examples
338
+ --------
339
+ >>> from nextmv.cloud import Client
340
+ >>> client = Client(api_key="your-api-key")
341
+ >>> account = Account.new(client=client, name="My New Account", admins=["admin@example.com"])
342
+ """
343
+
344
+ if len(admins) == 0:
345
+ raise ValueError("at least one admin email must be provided to create an account")
346
+
347
+ payload = {
348
+ "name": name,
349
+ "admins": admins,
350
+ }
351
+
352
+ response = client.request(
353
+ method="POST",
354
+ endpoint="v1/organization",
355
+ payload=payload,
356
+ )
357
+
358
+ return cls.from_dict({"client": client} | response.json())
359
+
360
+ def delete(self) -> None:
361
+ """
362
+ Delete the account.
363
+
364
+ Permanently removes the account (organization) from Nextmv Cloud. You
365
+ must have the administrator role on that account in order to delete it.
366
+
367
+ Raises
368
+ ------
369
+ requests.HTTPError
370
+ If the response status code is not 2xx.
371
+
372
+ Examples
373
+ --------
374
+ >>> account.delete() # Permanently deletes the account
375
+ """
376
+
377
+ _ = self.client.request(
378
+ method="DELETE",
379
+ endpoint=self.organization_endpoint,
380
+ )
182
381
 
183
382
  def queue(self) -> Queue:
184
- """Get the queue of runs in the account.
383
+ """
384
+ Get the queue of runs in the account.
185
385
 
186
386
  Retrieves the current list of runs that are pending or being executed
187
387
  in the Nextmv account.
@@ -198,7 +398,9 @@ class Account:
198
398
 
199
399
  Examples
200
400
  --------
201
- >>> account = Account(client=Client(api_key="your-api-key"))
401
+ >>> from nextmv.cloud import Client, Account
402
+ >>> client = Client(api_key="your-api-key")
403
+ >>> account = Account.get(client=client, account_id="your-account-id")
202
404
  >>> queue = account.queue()
203
405
  >>> for run in queue.runs:
204
406
  ... print(f"Run {run.id}: {run.name} - Status: {run.status_v2}")
@@ -207,7 +409,49 @@ class Account:
207
409
  """
208
410
  response = self.client.request(
209
411
  method="GET",
210
- endpoint=self.endpoint + "/queue",
412
+ endpoint=self.account_endpoint + "/queue",
211
413
  )
212
414
 
213
415
  return Queue.from_dict(response.json())
416
+
417
+ def update(self, name: str) -> "Account":
418
+ """
419
+ Update the account.
420
+
421
+ Parameters
422
+ ----------
423
+ name : str
424
+ Name of the account.
425
+
426
+ Returns
427
+ -------
428
+ Account
429
+ The updated account.
430
+
431
+ Raises
432
+ ------
433
+ requests.HTTPError
434
+ If the response status code is not 2xx.
435
+
436
+ Examples
437
+ --------
438
+ >>> from nextmv.cloud import Client, Account
439
+ >>> client = Client(api_key="your-api-key")
440
+ >>> account = Account.get(client=client, account_id="bunny-logistics")
441
+ >>> updated_account = account.update(name="Bunny Express Logistics")
442
+ >>> print(updated_account.name)
443
+ Bunny Express Logistics
444
+ """
445
+
446
+ account = self.get(client=self.client, account_id=self.account_id)
447
+ account_dict = account.to_dict()
448
+ payload = account_dict.copy()
449
+ payload["name"] = name
450
+
451
+ response = self.client.request(
452
+ method="PUT",
453
+ endpoint=self.organization_endpoint,
454
+ payload=payload,
455
+ )
456
+
457
+ return Account.from_dict({"client": self.client} | response.json())