nextmv 0.27.0__py3-none-any.whl → 0.28.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.
- nextmv/__about__.py +1 -1
- nextmv/__init__.py +1 -0
- nextmv/base_model.py +52 -7
- nextmv/cloud/__init__.py +3 -0
- nextmv/cloud/acceptance_test.py +711 -20
- nextmv/cloud/account.py +152 -7
- nextmv/cloud/application.py +1213 -382
- nextmv/cloud/batch_experiment.py +133 -21
- nextmv/cloud/client.py +240 -46
- nextmv/cloud/input_set.py +96 -3
- nextmv/cloud/instance.py +89 -3
- nextmv/cloud/manifest.py +507 -131
- nextmv/cloud/package.py +2 -2
- nextmv/cloud/run.py +372 -41
- nextmv/cloud/safe.py +7 -7
- nextmv/cloud/scenario.py +205 -20
- nextmv/cloud/secrets.py +179 -6
- nextmv/cloud/status.py +95 -2
- nextmv/cloud/version.py +132 -4
- nextmv/deprecated.py +36 -2
- nextmv/input.py +298 -80
- nextmv/logger.py +71 -7
- nextmv/model.py +223 -56
- nextmv/options.py +281 -66
- nextmv/output.py +552 -159
- {nextmv-0.27.0.dist-info → nextmv-0.28.0.dist-info}/METADATA +1 -1
- nextmv-0.28.0.dist-info/RECORD +30 -0
- nextmv-0.27.0.dist-info/RECORD +0 -30
- {nextmv-0.27.0.dist-info → nextmv-0.28.0.dist-info}/WHEEL +0 -0
- {nextmv-0.27.0.dist-info → nextmv-0.28.0.dist-info}/licenses/LICENSE +0 -0
nextmv/cloud/batch_experiment.py
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
"""
|
|
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.
|
|
11
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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 :
|
|
63
|
-
ID of the instance used for the experiment.
|
|
64
|
-
version_id :
|
|
65
|
-
ID of the version used for the experiment.
|
|
66
|
-
input_set_id :
|
|
67
|
-
ID of the input set used for the experiment.
|
|
68
|
-
scenario_id :
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
"""
|
|
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.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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."""
|