nextmv 0.27.0__py3-none-any.whl → 0.28.1.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.
@@ -1,4 +1,17 @@
1
- """This module contains definitions for batch experiments."""
1
+ """
2
+ This module contains definitions for batch experiments.
3
+
4
+ Classes
5
+ -------
6
+ BatchExperimentInformation
7
+ Base class for all batch experiment models containing common information.
8
+ BatchExperiment
9
+ Class representing a batch experiment that compares two or more instances.
10
+ BatchExperimentRun
11
+ Class representing a single execution of a batch experiment.
12
+ BatchExperimentMetadata
13
+ Class containing metadata of a batch experiment.
14
+ """
2
15
 
3
16
  from datetime import datetime
4
17
  from typing import Any, Optional
@@ -7,8 +20,58 @@ from nextmv.base_model import BaseModel
7
20
 
8
21
 
9
22
  class BatchExperimentInformation(BaseModel):
10
- """Information about a batch experiment. This serves as a base for all the
11
- other batch experiment models."""
23
+ """Information about a batch experiment.
24
+
25
+ You can import the `BatchExperimentInformation` class directly from `cloud`:
26
+
27
+ ```python
28
+ from nextmv.cloud import BatchExperimentInformation
29
+ ```
30
+
31
+ This class serves as a base for all the other batch experiment models and
32
+ contains common attributes shared by different types of batch experiments.
33
+
34
+ Parameters
35
+ ----------
36
+ id : str
37
+ ID of the batch experiment.
38
+ name : str
39
+ Name of the batch experiment.
40
+ created_at : datetime
41
+ Creation date of the batch experiment.
42
+ updated_at : datetime
43
+ Last update date of the batch experiment.
44
+ status : str, optional
45
+ Status of the batch experiment. Defaults to None.
46
+ description : str, optional
47
+ Description of the batch experiment. Defaults to None.
48
+ number_of_requested_runs : int, optional
49
+ Number of runs requested for the batch experiment. Defaults to None.
50
+ number_of_runs : int, optional
51
+ Number of runs in the batch experiment. Defaults to None.
52
+ number_of_completed_runs : int, optional
53
+ Number of completed runs in the batch experiment. Defaults to None.
54
+ type : str, optional
55
+ Type of the batch experiment. Defaults to None.
56
+ option_sets : dict[str, dict[str, str]], optional
57
+ Option sets used for the experiment. Defaults to None.
58
+
59
+ Examples
60
+ --------
61
+ >>> from datetime import datetime
62
+ >>> info = BatchExperimentInformation(
63
+ ... id="bexp-123",
64
+ ... name="Test Experiment",
65
+ ... created_at=datetime.now(),
66
+ ... updated_at=datetime.now(),
67
+ ... status="running",
68
+ ... description="A sample batch experiment."
69
+ ... )
70
+ >>> print(info.id)
71
+ bexp-123
72
+ >>> print(info.name)
73
+ Test Experiment
74
+ """
12
75
 
13
76
  id: str
14
77
  """ID of the batch experiment."""
@@ -37,7 +100,26 @@ class BatchExperimentInformation(BaseModel):
37
100
 
38
101
  class BatchExperiment(BatchExperimentInformation):
39
102
  """A batch experiment compares two or more instances by executing all the
40
- inputs contained in the input set."""
103
+ inputs contained in the input set.
104
+
105
+ You can import the `BatchExperiment` class directly from `cloud`:
106
+
107
+ ```python
108
+ from nextmv.cloud import BatchExperiment
109
+ ```
110
+
111
+ This class extends `BatchExperimentInformation` with attributes specific
112
+ to a full batch experiment.
113
+
114
+ Parameters
115
+ ----------
116
+ input_set_id : str
117
+ ID of the input set used for the experiment.
118
+ instance_ids : list[str]
119
+ List of instance IDs used for the experiment.
120
+ grouped_distributional_summaries : list[dict[str, Any]], optional
121
+ Grouped distributional summaries of the batch experiment. Defaults to None.
122
+ """
41
123
 
42
124
  input_set_id: str
43
125
  """ID of the input set used for the experiment."""
@@ -48,29 +130,36 @@ class BatchExperiment(BatchExperimentInformation):
48
130
 
49
131
 
50
132
  class BatchExperimentRun(BaseModel):
51
- """
52
- A batch experiment run is a single execution of a batch experiment. It
53
- contains information about the experiment, the input used, and the
133
+ """A batch experiment run is a single execution of a batch experiment.
134
+
135
+ You can import the `BatchExperimentRun` class directly from `cloud`:
136
+
137
+ ```python
138
+ from nextmv.cloud import BatchExperimentRun
139
+ ```
140
+
141
+ It contains information about the experiment, the input used, and the
54
142
  configuration used for the run.
55
143
 
56
- Attributes
144
+ Parameters
57
145
  ----------
58
146
  option_set : str
59
147
  Option set used for the experiment.
60
148
  input_id : str
61
149
  ID of the input used for the experiment.
62
- instance_id : Optional[str]
63
- ID of the instance used for the experiment.
64
- version_id : Optional[str]
65
- ID of the version used for the experiment.
66
- input_set_id : Optional[str]
67
- ID of the input set used for the experiment.
68
- scenario_id : Optional[str]
150
+ instance_id : str, optional
151
+ ID of the instance used for the experiment. Defaults to None.
152
+ version_id : str, optional
153
+ ID of the version used for the experiment. Defaults to None.
154
+ input_set_id : str, optional
155
+ ID of the input set used for the experiment. Defaults to None.
156
+ scenario_id : str, optional
69
157
  If the batch experiment is a scenario test, this is the ID of that test.
70
- repetition : Optional[int]
71
- Repetition number of the experiment.
72
- run_number : Optional[str]
73
- Run number of the experiment.
158
+ Defaults to None.
159
+ repetition : int, optional
160
+ Repetition number of the experiment. Defaults to None.
161
+ run_number : str, optional
162
+ Run number of the experiment. Defaults to None.
74
163
  """
75
164
 
76
165
  option_set: str
@@ -92,14 +181,37 @@ class BatchExperimentRun(BaseModel):
92
181
  """Run number of the experiment."""
93
182
 
94
183
  def __post_init_post_parse__(self):
95
- """Logic to run after the class is initialized."""
184
+ """
185
+ Logic to run after the class is initialized.
186
+
187
+ Ensures that either `instance_id` or `version_id` is set.
96
188
 
189
+ Raises
190
+ ------
191
+ ValueError
192
+ If both `instance_id` and `version_id` are None.
193
+ """
97
194
  if self.instance_id is None and self.version_id is None:
98
195
  raise ValueError("either instance_id or version_id must be set")
99
196
 
100
197
 
101
198
  class BatchExperimentMetadata(BatchExperimentInformation):
102
- """Metadata of a batch experiment."""
199
+ """Metadata of a batch experiment.
200
+
201
+ You can import the `BatchExperimentMetadata` class directly from `cloud`:
202
+
203
+ ```python
204
+ from nextmv.cloud import BatchExperimentMetadata
205
+ ```
206
+
207
+ This class extends `BatchExperimentInformation` with application-specific
208
+ metadata.
209
+
210
+ Parameters
211
+ ----------
212
+ app_id : str, optional
213
+ ID of the application used for the batch experiment. Defaults to None.
214
+ """
103
215
 
104
216
  app_id: Optional[str] = None
105
217
  """ID of the application used for the batch experiment."""
nextmv/cloud/client.py CHANGED
@@ -1,4 +1,18 @@
1
- """Module with the client class."""
1
+ """Module with the client class.
2
+
3
+ This module provides the `Client` class for interacting with the Nextmv Cloud
4
+ API, and a helper function `get_size` to determine the size of objects.
5
+
6
+ Classes
7
+ -------
8
+ Client
9
+ Client that interacts directly with the Nextmv Cloud API.
10
+
11
+ Functions
12
+ ---------
13
+ get_size(obj)
14
+ Finds the size of an object in bytes.
15
+ """
2
16
 
3
17
  import json
4
18
  import os
@@ -11,16 +25,76 @@ import yaml
11
25
  from requests.adapters import HTTPAdapter, Retry
12
26
 
13
27
  _MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
14
- """Maximum size of the payload handled by the Nextmv Cloud API."""
28
+ """int: Maximum size of the payload handled by the Nextmv Cloud API.
29
+
30
+ This constant defines the upper limit for the size of data payloads that can
31
+ be sent to the Nextmv Cloud API, specifically for lambda functions. It is set
32
+ to 500 MiB.
33
+ """
15
34
 
16
35
 
17
36
  @dataclass
18
37
  class Client:
19
38
  """
20
- Client that interacts directly with the Nextmv Cloud API. The API key will
21
- be searched, in order of precedence, in: the api_key arg in the
22
- constructor, the NEXTMV_API_KEY environment variable, the
23
- ~/.nextmv/config.yaml file used by the Nextmv CLI.
39
+ Client that interacts directly with the Nextmv Cloud API.
40
+
41
+ You can import the `Client` class directly from `cloud`:
42
+
43
+ ```python
44
+ from nextmv.cloud import Client
45
+ ```
46
+
47
+ The API key will be searched, in order of precedence, in:
48
+
49
+ 1. The `api_key` argument in the constructor.
50
+ 2. The `NEXTMV_API_KEY` environment variable.
51
+ 3. The `~/.nextmv/config.yaml` file used by the Nextmv CLI.
52
+
53
+ Parameters
54
+ ----------
55
+ api_key : str, optional
56
+ API key to use for authenticating with the Nextmv Cloud API. If not
57
+ provided, the client will look for the `NEXTMV_API_KEY` environment
58
+ variable.
59
+ allowed_methods : list[str]
60
+ Allowed HTTP methods to use for retries in requests to the Nextmv
61
+ Cloud API. Defaults to ``["GET", "POST", "PUT", "DELETE"]``.
62
+ backoff_factor : float
63
+ Exponential backoff factor to use for requests to the Nextmv Cloud
64
+ API. Defaults to ``1``.
65
+ backoff_jitter : float
66
+ Jitter to use for requests to the Nextmv Cloud API when backing off.
67
+ Defaults to ``0.1``.
68
+ backoff_max : float
69
+ Maximum backoff time to use for requests to the Nextmv Cloud API, in
70
+ seconds. Defaults to ``60``.
71
+ configuration_file : str
72
+ Path to the configuration file used by the Nextmv CLI. Defaults to
73
+ ``"~/.nextmv/config.yaml"``.
74
+ headers : dict[str, str], optional
75
+ Headers to use for requests to the Nextmv Cloud API. Automatically
76
+ set up with the API key.
77
+ max_retries : int
78
+ Maximum number of retries to use for requests to the Nextmv Cloud
79
+ API. Defaults to ``10``.
80
+ status_forcelist : list[int]
81
+ Status codes to retry for requests to the Nextmv Cloud API. Defaults
82
+ to ``[429]``.
83
+ timeout : float
84
+ Timeout to use for requests to the Nextmv Cloud API, in seconds.
85
+ Defaults to ``20``.
86
+ url : str
87
+ URL of the Nextmv Cloud API. Defaults to
88
+ ``"https://api.cloud.nextmv.io"``.
89
+ console_url : str
90
+ URL of the Nextmv Cloud console. Defaults to
91
+ ``"https://cloud.nextmv.io"``.
92
+
93
+ Examples
94
+ --------
95
+ >>> client = Client(api_key="YOUR_API_KEY")
96
+ >>> response = client.request(method="GET", endpoint="/v1/applications")
97
+ >>> print(response.json())
24
98
  """
25
99
 
26
100
  api_key: Optional[str] = None
@@ -59,7 +133,23 @@ class Client:
59
133
  """URL of the Nextmv Cloud console."""
60
134
 
61
135
  def __post_init__(self):
62
- """Logic to run after the class is initialized."""
136
+ """
137
+ Initializes the client after dataclass construction.
138
+
139
+ This method handles the logic for API key retrieval and header
140
+ setup. It checks for the API key in the constructor, environment
141
+ variables, and the configuration file, in that order.
142
+
143
+ Raises
144
+ ------
145
+ ValueError
146
+ If `api_key` is an empty string.
147
+ If no API key is found in any of the lookup locations.
148
+ If a profile is specified via `NEXTMV_PROFILE` but not found in
149
+ the configuration file.
150
+ If `apikey` is not found in the configuration file for the
151
+ selected profile.
152
+ """
63
153
 
64
154
  if self.api_key is not None and self.api_key != "":
65
155
  self._set_headers_api_key(self.api_key)
@@ -77,7 +167,7 @@ class Client:
77
167
  config_path = os.path.expanduser(self.configuration_file)
78
168
  if not os.path.exists(config_path):
79
169
  raise ValueError(
80
- "no API key set in constructor or NEXTMV_API_KEY env var, and ~/.nextmv/config.yaml does not exist"
170
+ f"no API key set in constructor or NEXTMV_API_KEY env var, and {self.configuration_file} does not exist"
81
171
  )
82
172
 
83
173
  with open(config_path) as f:
@@ -88,11 +178,11 @@ class Client:
88
178
  if profile is not None:
89
179
  parent = config.get(profile)
90
180
  if parent is None:
91
- raise ValueError(f"profile {profile} set via NEXTMV_PROFILE but not found in ~/.nextmv/config.yaml")
181
+ raise ValueError(f"profile {profile} set via NEXTMV_PROFILE but not found in {self.configuration_file}")
92
182
 
93
183
  api_key = parent.get("apikey")
94
184
  if api_key is None:
95
- raise ValueError("no apiKey found in ~/.nextmv/config.yaml")
185
+ raise ValueError(f"no apiKey found in {self.configuration_file}")
96
186
  self.api_key = api_key
97
187
 
98
188
  endpoint = parent.get("endpoint")
@@ -111,25 +201,61 @@ class Client:
111
201
  query_params: Optional[dict[str, Any]] = None,
112
202
  ) -> requests.Response:
113
203
  """
114
- Method to make a request to the Nextmv Cloud API.
115
-
116
- Args:
117
- method: HTTP method to use. Valid methods include: GET, POST.
118
- endpoint: Endpoint to send the request to.
119
- data: Data to send with the request.
120
- headers: Headers to send with the request.
121
- payload: Payload to send with the request. Prefer using this over
122
- data.
123
- query_params: Query parameters to send with the request.
124
-
125
- Returns:
126
- Response from the Nextmv Cloud API.
127
-
128
- Raises:
129
- requests.HTTPError: If the response status code is not 2xx.
130
- ValueError: If both data and payload are provided.
131
- ValueError: If the payload size exceeds the maximum allowed size.
132
- ValueError: If the data size exceeds the maximum allowed size.
204
+ Makes a request to the Nextmv Cloud API.
205
+
206
+ Parameters
207
+ ----------
208
+ method : str
209
+ HTTP method to use (e.g., "GET", "POST").
210
+ endpoint : str
211
+ API endpoint to send the request to (e.g., "/v1/applications").
212
+ data : Any, optional
213
+ Data to send in the request body. Typically used for form data.
214
+ Cannot be used if `payload` is also provided.
215
+ headers : dict[str, str], optional
216
+ Additional headers to send with the request. These will override
217
+ the default client headers if keys conflict.
218
+ payload : dict[str, Any], optional
219
+ JSON payload to send with the request. Prefer using this over
220
+ `data` for JSON requests. Cannot be used if `data` is also
221
+ provided.
222
+ query_params : dict[str, Any], optional
223
+ Query parameters to append to the request URL.
224
+
225
+ Returns
226
+ -------
227
+ requests.Response
228
+ The response object from the Nextmv Cloud API.
229
+
230
+ Raises
231
+ ------
232
+ requests.HTTPError
233
+ If the response status code is not in the 2xx range.
234
+ ValueError
235
+ If both `data` and `payload` are provided.
236
+ If the `payload` size exceeds `_MAX_LAMBDA_PAYLOAD_SIZE`.
237
+ If the `data` size exceeds `_MAX_LAMBDA_PAYLOAD_SIZE`.
238
+
239
+ Examples
240
+ --------
241
+ >>> client = Client(api_key="YOUR_API_KEY")
242
+ >>> # Get a list of applications
243
+ >>> response = client.request(method="GET", endpoint="/v1/applications")
244
+ >>> print(response.status_code)
245
+ 200
246
+ >>> # Create a new run
247
+ >>> run_payload = {
248
+ ... "applicationId": "app_id",
249
+ ... "instanceId": "instance_id",
250
+ ... "input": {"value": 10}
251
+ ... }
252
+ >>> response = client.request(
253
+ ... method="POST",
254
+ ... endpoint="/v1/runs",
255
+ ... payload=run_payload
256
+ ... )
257
+ >>> print(response.json()["id"])
258
+ run_xxxxxxxxxxxx
133
259
  """
134
260
 
135
261
  if payload is not None and data is not None:
@@ -159,7 +285,7 @@ class Client:
159
285
  adapter = HTTPAdapter(max_retries=retries)
160
286
  session.mount("https://", adapter)
161
287
 
162
- kwargs = {
288
+ kwargs: dict[str, Any] = {
163
289
  "url": urljoin(self.url, endpoint),
164
290
  "timeout": self.timeout,
165
291
  }
@@ -182,19 +308,38 @@ class Client:
182
308
 
183
309
  return response
184
310
 
185
- def upload_to_presigned_url(
186
- self,
187
- data: Union[dict[str, Any], str],
188
- url: str,
189
- ) -> None:
311
+ def upload_to_presigned_url(self, data: Union[dict[str, Any], str], url: str) -> None:
190
312
  """
191
- Method to upload data to a presigned URL of the Nextmv Cloud API.
192
- Args:
193
- data: data to upload.
194
- url: URL to upload the data to.
313
+ Uploads data to a presigned URL.
314
+
315
+ This method is typically used for uploading large input or output files
316
+ directly to cloud storage, bypassing the main API for efficiency.
317
+
318
+ Parameters
319
+ ----------
320
+ data : dict[str, Any] or str
321
+ The data to upload. If a dictionary is provided, it will be
322
+ JSON-serialized. If a string is provided, it will be uploaded
323
+ as is.
324
+ url : str
325
+ The presigned URL to which the data will be uploaded.
326
+
327
+ Raises
328
+ ------
329
+ ValueError
330
+ If `data` is not a dictionary or a string.
331
+ requests.HTTPError
332
+ If the upload request fails.
333
+
334
+ Examples
335
+ --------
336
+ Assume `presigned_upload_url` is obtained from a previous API call.
337
+ >>> client = Client(api_key="YOUR_API_KEY")
338
+ >>> input_data = {"value": 42, "items": [1, 2, 3]}
339
+ >>> client.upload_to_presigned_url(data=input_data, url="PRE_SIGNED_URL") # doctest: +SKIP
195
340
  """
196
341
 
197
- upload_data = None
342
+ upload_data: Optional[str] = None
198
343
  if isinstance(data, dict):
199
344
  upload_data = json.dumps(data, separators=(",", ":"))
200
345
  elif isinstance(data, str):
@@ -213,7 +358,7 @@ class Client:
213
358
  )
214
359
  adapter = HTTPAdapter(max_retries=retries)
215
360
  session.mount("https://", adapter)
216
- kwargs = {
361
+ kwargs: dict[str, Any] = {
217
362
  "url": url,
218
363
  "timeout": self.timeout,
219
364
  "data": upload_data,
@@ -226,11 +371,21 @@ class Client:
226
371
  except requests.HTTPError as e:
227
372
  raise requests.HTTPError(
228
373
  f"upload to presigned URL {url} failed with "
229
- + f"status code {response.status_code} and message: {response.text}"
374
+ f"status code {response.status_code} and message: {response.text}"
230
375
  ) from e
231
376
 
232
377
  def _set_headers_api_key(self, api_key: str) -> None:
233
- """Sets the API key to use for requests to the Nextmv Cloud API."""
378
+ """
379
+ Sets the Authorization and Content-Type headers.
380
+
381
+ This is an internal method used to configure the necessary headers
382
+ for API authentication and content type specification.
383
+
384
+ Parameters
385
+ ----------
386
+ api_key : str
387
+ The API key to be included in the Authorization header.
388
+ """
234
389
 
235
390
  self.headers = {
236
391
  "Authorization": f"Bearer {api_key}",
@@ -238,8 +393,47 @@ class Client:
238
393
  }
239
394
 
240
395
 
241
- def get_size(obj: Union[dict[str, Any], IO[bytes]]) -> int:
242
- """Finds the size of an object in bytes."""
396
+ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
397
+ """
398
+ Finds the size of an object in bytes.
399
+
400
+ This function supports dictionaries (JSON-serialized), file-like objects
401
+ (by reading their content), and strings.
402
+
403
+ Parameters
404
+ ----------
405
+ obj : dict[str, Any] or IO[bytes] or str
406
+ The object whose size is to be determined.
407
+ - If a dict, it's converted to a JSON string.
408
+ - If a file-like object (e.g., opened file), its size is read.
409
+ - If a string, its UTF-8 encoded byte length is calculated.
410
+
411
+ Returns
412
+ -------
413
+ int
414
+ The size of the object in bytes.
415
+
416
+ Raises
417
+ ------
418
+ TypeError
419
+ If the object type is not supported (i.e., not a dict,
420
+ file-like object, or string).
421
+
422
+ Examples
423
+ --------
424
+ >>> my_dict = {"key": "value", "number": 123}
425
+ >>> get_size(my_dict)
426
+ 30
427
+ >>> import io
428
+ >>> my_string = "Hello, Nextmv!"
429
+ >>> string_io = io.StringIO(my_string)
430
+ >>> # To get size of underlying buffer for StringIO, we need to encode
431
+ >>> string_bytes_io = io.BytesIO(my_string.encode('utf-8'))
432
+ >>> get_size(string_bytes_io)
433
+ 14
434
+ >>> get_size("Hello, Nextmv!")
435
+ 14
436
+ """
243
437
 
244
438
  if isinstance(obj, dict):
245
439
  obj_str = json.dumps(obj, separators=(",", ":"))
@@ -255,4 +449,4 @@ def get_size(obj: Union[dict[str, Any], IO[bytes]]) -> int:
255
449
  return len(obj.encode("utf-8"))
256
450
 
257
451
  else:
258
- raise TypeError("Unsupported type. Only dictionaries and file objects are supported.")
452
+ raise TypeError("Unsupported type. Only dictionaries, file objects (IO[bytes]), and strings are supported.")
nextmv/cloud/input_set.py CHANGED
@@ -1,4 +1,14 @@
1
- """This module contains definitions for input sets."""
1
+ """Definitions for input sets and related cloud objects.
2
+
3
+ This module provides classes for managing inputs and input sets in the Nextmv Cloud.
4
+
5
+ Classes
6
+ -------
7
+ ManagedInput
8
+ An input created for experimenting with an application.
9
+ InputSet
10
+ A collection of inputs from associated runs.
11
+ """
2
12
 
3
13
  from datetime import datetime
4
14
  from typing import Optional
@@ -8,7 +18,43 @@ from nextmv.cloud.run import Format
8
18
 
9
19
 
10
20
  class ManagedInput(BaseModel):
11
- """An input created for experimenting with an application."""
21
+ """An input created for experimenting with an application.
22
+
23
+ You can import the `ManagedInput` class directly from `cloud`:
24
+
25
+ ```python
26
+ from nextmv.cloud import ManagedInput
27
+ ```
28
+
29
+ This class represents an input that was uploaded to the Nextmv Cloud
30
+ for experimentation purposes. It contains metadata about the input,
31
+ such as its ID, name, description, and creation time.
32
+
33
+ Parameters
34
+ ----------
35
+ id : str
36
+ Unique identifier of the input.
37
+ name : str, optional
38
+ User-defined name of the input.
39
+ description : str, optional
40
+ User-defined description of the input.
41
+ run_id : str, optional
42
+ Identifier of the run that created this input.
43
+ upload_id : str, optional
44
+ Identifier of the upload that created this input.
45
+ format : Format, optional
46
+ Format of the input (e.g., JSON, CSV).
47
+ created_at : datetime, optional
48
+ Timestamp when the input was created.
49
+ updated_at : datetime, optional
50
+ Timestamp when the input was last updated.
51
+
52
+ Examples
53
+ --------
54
+ >>> input = ManagedInput(id="inp_123456789")
55
+ >>> print(input.id)
56
+ inp_123456789
57
+ """
12
58
 
13
59
  id: str
14
60
  """ID of the input."""
@@ -30,7 +76,54 @@ class ManagedInput(BaseModel):
30
76
 
31
77
 
32
78
  class InputSet(BaseModel):
33
- """An input set is the collection of inputs from the associated runs."""
79
+ """A collection of inputs from associated runs.
80
+
81
+ You can import the `InputSet` class directly from `cloud`:
82
+
83
+ ```python
84
+ from nextmv.cloud import InputSet
85
+ ```
86
+
87
+ An input set aggregates multiple inputs used for experimentation with an application
88
+ in the Nextmv Cloud. It allows organizing and managing related inputs
89
+ for comparison and analysis.
90
+
91
+ Parameters
92
+ ----------
93
+ app_id : str
94
+ Identifier of the application that the input set belongs to.
95
+ created_at : datetime
96
+ Timestamp when the input set was created.
97
+ description : str
98
+ User-defined description of the input set.
99
+ id : str
100
+ Unique identifier of the input set.
101
+ input_ids : list[str]
102
+ List of identifiers of the inputs in the input set.
103
+ name : str
104
+ User-defined name of the input set.
105
+ updated_at : datetime
106
+ Timestamp when the input set was last updated.
107
+ inputs : list[ManagedInput]
108
+ List of ManagedInput objects contained in this input set.
109
+
110
+ Examples
111
+ --------
112
+ >>> input_set = InputSet(
113
+ ... app_id="app_123456789",
114
+ ... id="is_987654321",
115
+ ... name="My Input Set",
116
+ ... description="A collection of routing inputs",
117
+ ... input_ids=["inp_111", "inp_222"],
118
+ ... created_at=datetime.now(),
119
+ ... updated_at=datetime.now(),
120
+ ... inputs=[]
121
+ ... )
122
+ >>> print(input_set.name)
123
+ My Input Set
124
+ >>> print(len(input_set.input_ids))
125
+ 2
126
+ """
34
127
 
35
128
  app_id: str
36
129
  """ID of the application that the input set belongs to."""