nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__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/__entrypoint__.py +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
nextmv/cloud/application.py
DELETED
|
@@ -1,1405 +0,0 @@
|
|
|
1
|
-
"""This module contains the application class."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import shutil
|
|
5
|
-
import time
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from datetime import datetime
|
|
8
|
-
from typing import Any, Optional, Union
|
|
9
|
-
|
|
10
|
-
import requests
|
|
11
|
-
|
|
12
|
-
from nextmv.base_model import BaseModel
|
|
13
|
-
from nextmv.cloud import package
|
|
14
|
-
from nextmv.cloud.acceptance_test import AcceptanceTest, ExperimentStatus, Metric
|
|
15
|
-
from nextmv.cloud.batch_experiment import BatchExperiment, BatchExperimentMetadata, BatchExperimentRun
|
|
16
|
-
from nextmv.cloud.client import Client, get_size
|
|
17
|
-
from nextmv.cloud.input_set import InputSet
|
|
18
|
-
from nextmv.cloud.instance import Configuration, Instance
|
|
19
|
-
from nextmv.cloud.manifest import Manifest
|
|
20
|
-
from nextmv.cloud.status import Status, StatusV2
|
|
21
|
-
from nextmv.cloud.version import Version
|
|
22
|
-
from nextmv.logger import log
|
|
23
|
-
from nextmv.model import Model, ModelConfiguration
|
|
24
|
-
|
|
25
|
-
_MAX_RUN_SIZE: int = 5 * 1024 * 1024
|
|
26
|
-
"""Maximum size of the run input/output. This value is used to determine
|
|
27
|
-
whether to use the large input upload and/or result download endpoints."""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class DownloadURL(BaseModel):
|
|
31
|
-
"""Result of getting a download URL."""
|
|
32
|
-
|
|
33
|
-
url: str
|
|
34
|
-
"""URL to use for downloading the file."""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class ErrorLog(BaseModel):
|
|
38
|
-
"""Error log of a run, when it was not successful."""
|
|
39
|
-
|
|
40
|
-
error: Optional[str] = None
|
|
41
|
-
"""Error message."""
|
|
42
|
-
stdout: Optional[str] = None
|
|
43
|
-
"""Standard output."""
|
|
44
|
-
stderr: Optional[str] = None
|
|
45
|
-
"""Standard error."""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class Metadata(BaseModel):
|
|
49
|
-
"""Metadata of a run, whether it was successful or not."""
|
|
50
|
-
|
|
51
|
-
application_id: str
|
|
52
|
-
"""ID of the application where the run was submitted to."""
|
|
53
|
-
application_instance_id: str
|
|
54
|
-
"""ID of the instance where the run was submitted to."""
|
|
55
|
-
application_version_id: str
|
|
56
|
-
"""ID of the version of the application where the run was submitted to."""
|
|
57
|
-
created_at: datetime
|
|
58
|
-
"""Date and time when the run was created."""
|
|
59
|
-
duration: float
|
|
60
|
-
"""Duration of the run in milliseconds."""
|
|
61
|
-
error: str
|
|
62
|
-
"""Error message if the run failed."""
|
|
63
|
-
input_size: float
|
|
64
|
-
"""Size of the input in bytes."""
|
|
65
|
-
output_size: float
|
|
66
|
-
"""Size of the output in bytes."""
|
|
67
|
-
status: Status
|
|
68
|
-
"""Deprecated: use status_v2."""
|
|
69
|
-
status_v2: StatusV2
|
|
70
|
-
"""Status of the run."""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class PollingOptions(BaseModel):
|
|
74
|
-
"""Options to use when polling for a run result."""
|
|
75
|
-
|
|
76
|
-
backoff: float = 1
|
|
77
|
-
"""Backoff factor to use between polls. Leave this at 1 to poll at a
|
|
78
|
-
constant rate."""
|
|
79
|
-
delay: float = 1
|
|
80
|
-
"""Delay to use between polls, in seconds."""
|
|
81
|
-
initial_delay: float = 1
|
|
82
|
-
"""Initial delay to use before starting the polling strategy, in
|
|
83
|
-
seconds."""
|
|
84
|
-
max_delay: float = 20
|
|
85
|
-
"""Maximum delay to use between polls, in seconds. This parameter is
|
|
86
|
-
activated when the backoff parameter is greater than 1, such that the delay
|
|
87
|
-
is increasing after each poll."""
|
|
88
|
-
max_duration: float = 300
|
|
89
|
-
"""Maximum duration of the polling strategy, in seconds."""
|
|
90
|
-
max_tries: int = 20
|
|
91
|
-
"""Maximum number of tries to use."""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
_DEFAULT_POLLING_OPTIONS: PollingOptions = PollingOptions()
|
|
95
|
-
"""Default polling options to use when polling for a run result."""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
class RunInformation(BaseModel):
|
|
99
|
-
"""Information of a run."""
|
|
100
|
-
|
|
101
|
-
description: str
|
|
102
|
-
"""Description of the run."""
|
|
103
|
-
id: str
|
|
104
|
-
"""ID of the run."""
|
|
105
|
-
metadata: Metadata
|
|
106
|
-
"""Metadata of the run."""
|
|
107
|
-
name: str
|
|
108
|
-
"""Name of the run."""
|
|
109
|
-
user_email: str
|
|
110
|
-
"""Email of the user who submitted the run."""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class RunResult(RunInformation):
|
|
114
|
-
"""Result of a run, whether it was successful or not."""
|
|
115
|
-
|
|
116
|
-
error_log: Optional[ErrorLog] = None
|
|
117
|
-
"""Error log of the run. Only available if the run failed."""
|
|
118
|
-
output: Optional[dict[str, Any]] = None
|
|
119
|
-
"""Output of the run. Only available if the run succeeded."""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class RunLog(BaseModel):
|
|
123
|
-
"""Log of a run."""
|
|
124
|
-
|
|
125
|
-
log: str
|
|
126
|
-
"""Log of the run."""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class UploadURL(BaseModel):
|
|
130
|
-
"""Result of getting an upload URL."""
|
|
131
|
-
|
|
132
|
-
upload_id: str
|
|
133
|
-
"""ID of the upload."""
|
|
134
|
-
upload_url: str
|
|
135
|
-
"""URL to use for uploading the file."""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@dataclass
|
|
139
|
-
class Application:
|
|
140
|
-
"""An application is a published decision model that can be executed."""
|
|
141
|
-
|
|
142
|
-
client: Client
|
|
143
|
-
"""Client to use for interacting with the Nextmv Cloud API."""
|
|
144
|
-
id: str
|
|
145
|
-
"""ID of the application."""
|
|
146
|
-
|
|
147
|
-
default_instance_id: str = "devint"
|
|
148
|
-
"""Default instance ID to use for submitting runs."""
|
|
149
|
-
endpoint: str = "v1/applications/{id}"
|
|
150
|
-
"""Base endpoint for the application."""
|
|
151
|
-
experiments_endpoint: str = "{base}/experiments"
|
|
152
|
-
"""Base endpoint for the experiments in the application."""
|
|
153
|
-
|
|
154
|
-
def __post_init__(self):
|
|
155
|
-
"""Logic to run after the class is initialized."""
|
|
156
|
-
|
|
157
|
-
self.endpoint = self.endpoint.format(id=self.id)
|
|
158
|
-
self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
|
|
159
|
-
|
|
160
|
-
def acceptance_test(self, acceptance_test_id: str) -> AcceptanceTest:
|
|
161
|
-
"""
|
|
162
|
-
Get an acceptance test.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
acceptance_test_id: ID of the acceptance test.
|
|
166
|
-
|
|
167
|
-
Returns:
|
|
168
|
-
Acceptance test.
|
|
169
|
-
|
|
170
|
-
Raises:
|
|
171
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
172
|
-
"""
|
|
173
|
-
|
|
174
|
-
response = self.client.request(
|
|
175
|
-
method="GET",
|
|
176
|
-
endpoint=f"{self.experiments_endpoint}/acceptance/{acceptance_test_id}",
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
return AcceptanceTest.from_dict(response.json())
|
|
180
|
-
|
|
181
|
-
def batch_experiment(self, batch_id: str) -> BatchExperiment:
|
|
182
|
-
"""
|
|
183
|
-
Get a batch experiment.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
batch_id: ID of the batch experiment.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Batch experiment.
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
193
|
-
"""
|
|
194
|
-
|
|
195
|
-
response = self.client.request(
|
|
196
|
-
method="GET",
|
|
197
|
-
endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
return BatchExperiment.from_dict(response.json())
|
|
201
|
-
|
|
202
|
-
def cancel_run(self, run_id: str) -> None:
|
|
203
|
-
"""
|
|
204
|
-
Cancel a run.
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
run_id: ID of the run.
|
|
208
|
-
|
|
209
|
-
Raises:
|
|
210
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
_ = self.client.request(
|
|
214
|
-
method="PATCH",
|
|
215
|
-
endpoint=f"{self.endpoint}/runs/{run_id}/cancel",
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
def delete_batch_experiment(self, batch_id: str) -> None:
|
|
219
|
-
"""
|
|
220
|
-
Deletes a batch experiment, along with all the associated information,
|
|
221
|
-
such as its runs.
|
|
222
|
-
|
|
223
|
-
Args:
|
|
224
|
-
batch_id: ID of the batch experiment.
|
|
225
|
-
|
|
226
|
-
Raises:
|
|
227
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
228
|
-
"""
|
|
229
|
-
|
|
230
|
-
_ = self.client.request(
|
|
231
|
-
method="DELETE",
|
|
232
|
-
endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
def delete_acceptance_test(self, acceptance_test_id: str) -> None:
|
|
236
|
-
"""
|
|
237
|
-
Deletes an acceptance test, along with all the associated information
|
|
238
|
-
such as the underlying batch experiment.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
acceptance_test_id: ID of the acceptance test.
|
|
242
|
-
|
|
243
|
-
Raises:
|
|
244
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
245
|
-
"""
|
|
246
|
-
|
|
247
|
-
_ = self.client.request(
|
|
248
|
-
method="DELETE",
|
|
249
|
-
endpoint=f"{self.experiments_endpoint}/acceptance/{acceptance_test_id}",
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
def input_set(self, input_set_id: str) -> InputSet:
|
|
253
|
-
"""
|
|
254
|
-
Get an input set.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
input_set_id: ID of the input set.
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
Input set.
|
|
261
|
-
|
|
262
|
-
Raises:
|
|
263
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
response = self.client.request(
|
|
267
|
-
method="GET",
|
|
268
|
-
endpoint=f"{self.experiments_endpoint}/inputsets/{input_set_id}",
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
return InputSet.from_dict(response.json())
|
|
272
|
-
|
|
273
|
-
def instance(self, instance_id: str) -> Instance:
|
|
274
|
-
"""
|
|
275
|
-
Get an instance.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
instance_id: ID of the instance.
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
Instance.
|
|
282
|
-
|
|
283
|
-
Raises:
|
|
284
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
285
|
-
"""
|
|
286
|
-
|
|
287
|
-
response = self.client.request(
|
|
288
|
-
method="GET",
|
|
289
|
-
endpoint=f"{self.endpoint}/instances/{instance_id}",
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
return Instance.from_dict(response.json())
|
|
293
|
-
|
|
294
|
-
def list_acceptance_tests(self) -> list[AcceptanceTest]:
|
|
295
|
-
"""
|
|
296
|
-
List all acceptance tests.
|
|
297
|
-
|
|
298
|
-
Returns:
|
|
299
|
-
List of acceptance tests.
|
|
300
|
-
|
|
301
|
-
Raises:
|
|
302
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
303
|
-
"""
|
|
304
|
-
|
|
305
|
-
response = self.client.request(
|
|
306
|
-
method="GET",
|
|
307
|
-
endpoint=f"{self.experiments_endpoint}/acceptance",
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return [AcceptanceTest.from_dict(acceptance_test) for acceptance_test in response.json()]
|
|
311
|
-
|
|
312
|
-
def list_batch_experiments(self) -> list[BatchExperimentMetadata]:
|
|
313
|
-
"""
|
|
314
|
-
List all batch experiments.
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
List of batch experiments.
|
|
318
|
-
|
|
319
|
-
Raises:
|
|
320
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
321
|
-
"""
|
|
322
|
-
|
|
323
|
-
response = self.client.request(
|
|
324
|
-
method="GET",
|
|
325
|
-
endpoint=f"{self.experiments_endpoint}/batch",
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
|
|
329
|
-
|
|
330
|
-
def list_input_sets(self) -> list[InputSet]:
|
|
331
|
-
"""
|
|
332
|
-
List all input sets.
|
|
333
|
-
|
|
334
|
-
Returns:
|
|
335
|
-
List of input sets.
|
|
336
|
-
|
|
337
|
-
Raises:
|
|
338
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
339
|
-
"""
|
|
340
|
-
|
|
341
|
-
response = self.client.request(
|
|
342
|
-
method="GET",
|
|
343
|
-
endpoint=f"{self.experiments_endpoint}/inputsets",
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
return [InputSet.from_dict(input_set) for input_set in response.json()]
|
|
347
|
-
|
|
348
|
-
def list_instances(self) -> list[Instance]:
|
|
349
|
-
"""
|
|
350
|
-
List all instances.
|
|
351
|
-
|
|
352
|
-
Returns:
|
|
353
|
-
List of instances.
|
|
354
|
-
|
|
355
|
-
Raises:
|
|
356
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
357
|
-
"""
|
|
358
|
-
|
|
359
|
-
response = self.client.request(
|
|
360
|
-
method="GET",
|
|
361
|
-
endpoint=f"{self.endpoint}/instances",
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
return [Instance.from_dict(instance) for instance in response.json()]
|
|
365
|
-
|
|
366
|
-
def list_versions(self) -> list[Version]:
|
|
367
|
-
"""
|
|
368
|
-
List all versions.
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
List of versions.
|
|
372
|
-
|
|
373
|
-
Raises:
|
|
374
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
375
|
-
"""
|
|
376
|
-
|
|
377
|
-
response = self.client.request(
|
|
378
|
-
method="GET",
|
|
379
|
-
endpoint=f"{self.endpoint}/versions",
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
return [Version.from_dict(version) for version in response.json()]
|
|
383
|
-
|
|
384
|
-
def new_acceptance_test(
|
|
385
|
-
self,
|
|
386
|
-
candidate_instance_id: str,
|
|
387
|
-
baseline_instance_id: str,
|
|
388
|
-
id: str,
|
|
389
|
-
metrics: list[Union[Metric, dict[str, Any]]],
|
|
390
|
-
name: str,
|
|
391
|
-
input_set_id: Optional[str] = None,
|
|
392
|
-
description: Optional[str] = None,
|
|
393
|
-
) -> AcceptanceTest:
|
|
394
|
-
"""
|
|
395
|
-
Create a new acceptance test. The acceptance test is based on a batch
|
|
396
|
-
experiment. If you already started a batch experiment, you don't need
|
|
397
|
-
to provide the input_set_id parameter. In that case, the ID of the
|
|
398
|
-
acceptance test and the batch experiment must be the same. If the batch
|
|
399
|
-
experiment does not exist, you can provide the input_set_id parameter
|
|
400
|
-
and a new batch experiment will be created for you.
|
|
401
|
-
|
|
402
|
-
Args:
|
|
403
|
-
candidate_instance_id: ID of the candidate instance.
|
|
404
|
-
baseline_instance_id: ID of the baseline instance.
|
|
405
|
-
id: ID of the acceptance test.
|
|
406
|
-
metrics: List of metrics to use for the acceptance test.
|
|
407
|
-
name: Name of the acceptance test.
|
|
408
|
-
input_set_id: ID of the input set to use for the underlying batch
|
|
409
|
-
experiment, in case it hasn't been started.
|
|
410
|
-
description: Description of the acceptance test.
|
|
411
|
-
|
|
412
|
-
Returns:
|
|
413
|
-
Acceptance test.
|
|
414
|
-
|
|
415
|
-
Raises:
|
|
416
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
417
|
-
ValueError: If the batch experiment ID does not match the
|
|
418
|
-
acceptance test ID.
|
|
419
|
-
"""
|
|
420
|
-
|
|
421
|
-
if input_set_id is None:
|
|
422
|
-
try:
|
|
423
|
-
batch_experiment = self.batch_experiment(batch_id=id)
|
|
424
|
-
batch_experiment_id = batch_experiment.id
|
|
425
|
-
except requests.HTTPError as e:
|
|
426
|
-
if e.response.status_code != 404:
|
|
427
|
-
raise e
|
|
428
|
-
|
|
429
|
-
raise ValueError(
|
|
430
|
-
f"batch experiment {id} does not exist, input_set_id must be defined to create a new one"
|
|
431
|
-
) from e
|
|
432
|
-
else:
|
|
433
|
-
batch_experiment_id = self.new_batch_experiment(
|
|
434
|
-
name=name,
|
|
435
|
-
input_set_id=input_set_id,
|
|
436
|
-
instance_ids=[candidate_instance_id, baseline_instance_id],
|
|
437
|
-
description=description,
|
|
438
|
-
id=id,
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
if batch_experiment_id != id:
|
|
442
|
-
raise ValueError(f"batch experiment_id ({batch_experiment_id}) does not match acceptance test id ({id})")
|
|
443
|
-
|
|
444
|
-
payload_metrics = [{}] * len(metrics)
|
|
445
|
-
for i, metric in enumerate(metrics):
|
|
446
|
-
payload_metrics[i] = metric.to_dict() if isinstance(metric, Metric) else metric
|
|
447
|
-
|
|
448
|
-
payload = {
|
|
449
|
-
"candidate": {"instance_id": candidate_instance_id},
|
|
450
|
-
"control": {"instance_id": baseline_instance_id},
|
|
451
|
-
"metrics": payload_metrics,
|
|
452
|
-
"experiment_id": batch_experiment_id,
|
|
453
|
-
"name": name,
|
|
454
|
-
}
|
|
455
|
-
if description is not None:
|
|
456
|
-
payload["description"] = description
|
|
457
|
-
if id is not None:
|
|
458
|
-
payload["id"] = id
|
|
459
|
-
|
|
460
|
-
response = self.client.request(
|
|
461
|
-
method="POST",
|
|
462
|
-
endpoint=f"{self.experiments_endpoint}/acceptance",
|
|
463
|
-
payload=payload,
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
return AcceptanceTest.from_dict(response.json())
|
|
467
|
-
|
|
468
|
-
def new_acceptance_test_with_result(
|
|
469
|
-
self,
|
|
470
|
-
candidate_instance_id: str,
|
|
471
|
-
baseline_instance_id: str,
|
|
472
|
-
id: str,
|
|
473
|
-
metrics: list[Union[Metric, dict[str, Any]]],
|
|
474
|
-
name: str,
|
|
475
|
-
input_set_id: Optional[str] = None,
|
|
476
|
-
description: Optional[str] = None,
|
|
477
|
-
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
478
|
-
) -> AcceptanceTest:
|
|
479
|
-
"""
|
|
480
|
-
Create a new acceptance test and poll for the result. This is a
|
|
481
|
-
convenience method that combines the new_acceptance_test with polling
|
|
482
|
-
logic to check when the acceptance test is done.
|
|
483
|
-
|
|
484
|
-
Args:
|
|
485
|
-
candidate_instance_id: ID of the candidate instance.
|
|
486
|
-
baseline_instance_id: ID of the baseline instance.
|
|
487
|
-
id: ID of the acceptance test.
|
|
488
|
-
metrics: List of metrics to use for the acceptance test.
|
|
489
|
-
name: Name of the acceptance test.
|
|
490
|
-
input_set_id: ID of the input set to use for the underlying batch
|
|
491
|
-
experiment, in case it hasn't been started.
|
|
492
|
-
description: Description of the acceptance test.
|
|
493
|
-
polling_options: Options to use when polling for the run result.
|
|
494
|
-
|
|
495
|
-
Returns:
|
|
496
|
-
Result of the acceptance test.
|
|
497
|
-
|
|
498
|
-
Raises:
|
|
499
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
500
|
-
TimeoutError: If the acceptance test does not succeed after the
|
|
501
|
-
polling strategy is exhausted based on time duration.
|
|
502
|
-
RuntimeError: If the acceptance test does not succeed after the
|
|
503
|
-
polling strategy is exhausted based on number of tries.
|
|
504
|
-
"""
|
|
505
|
-
_ = self.new_acceptance_test(
|
|
506
|
-
candidate_instance_id=candidate_instance_id,
|
|
507
|
-
baseline_instance_id=baseline_instance_id,
|
|
508
|
-
id=id,
|
|
509
|
-
metrics=metrics,
|
|
510
|
-
name=name,
|
|
511
|
-
input_set_id=input_set_id,
|
|
512
|
-
description=description,
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
time.sleep(polling_options.initial_delay)
|
|
516
|
-
delay = polling_options.delay
|
|
517
|
-
polling_ok = False
|
|
518
|
-
for _ in range(polling_options.max_tries):
|
|
519
|
-
test_information = self.acceptance_test(acceptance_test_id=id)
|
|
520
|
-
if test_information.status in [
|
|
521
|
-
ExperimentStatus.completed,
|
|
522
|
-
ExperimentStatus.failed,
|
|
523
|
-
ExperimentStatus.canceled,
|
|
524
|
-
]:
|
|
525
|
-
polling_ok = True
|
|
526
|
-
break
|
|
527
|
-
|
|
528
|
-
if delay > polling_options.max_duration:
|
|
529
|
-
raise TimeoutError(
|
|
530
|
-
f"acceptance_test {id} did not succeed after {delay} seconds",
|
|
531
|
-
)
|
|
532
|
-
|
|
533
|
-
sleep_duration = min(delay, polling_options.max_delay)
|
|
534
|
-
time.sleep(sleep_duration)
|
|
535
|
-
delay *= polling_options.backoff
|
|
536
|
-
|
|
537
|
-
if not polling_ok:
|
|
538
|
-
raise RuntimeError(
|
|
539
|
-
f"acceptance_test {id} did not succeed after {polling_options.max_tries} tries",
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
return test_information
|
|
543
|
-
|
|
544
|
-
def new_batch_experiment(
|
|
545
|
-
self,
|
|
546
|
-
name: str,
|
|
547
|
-
input_set_id: str,
|
|
548
|
-
instance_ids: list[str] = None,
|
|
549
|
-
description: Optional[str] = None,
|
|
550
|
-
id: Optional[str] = None,
|
|
551
|
-
option_sets: Optional[dict[str, dict[str, str]]] = None,
|
|
552
|
-
runs: Optional[list[Union[BatchExperimentRun, dict[str, Any]]]] = None,
|
|
553
|
-
) -> str:
|
|
554
|
-
"""
|
|
555
|
-
Create a new batch experiment.
|
|
556
|
-
|
|
557
|
-
Args:
|
|
558
|
-
name: Name of the batch experiment.
|
|
559
|
-
input_set_id: ID of the input set to use for the experiment.
|
|
560
|
-
instance_ids: List of instance IDs to use for the experiment.
|
|
561
|
-
description: Description of the batch experiment.
|
|
562
|
-
id: ID of the batch experiment.
|
|
563
|
-
option_sets: Option sets to use for the experiment.
|
|
564
|
-
runs: Runs to use for the experiment.
|
|
565
|
-
|
|
566
|
-
Returns:
|
|
567
|
-
ID of the batch experiment.
|
|
568
|
-
|
|
569
|
-
Raises:
|
|
570
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
571
|
-
"""
|
|
572
|
-
|
|
573
|
-
payload = {
|
|
574
|
-
"name": name,
|
|
575
|
-
"input_set_id": input_set_id,
|
|
576
|
-
"instance_ids": instance_ids,
|
|
577
|
-
}
|
|
578
|
-
if description is not None:
|
|
579
|
-
payload["description"] = description
|
|
580
|
-
if id is not None:
|
|
581
|
-
payload["id"] = id
|
|
582
|
-
if option_sets is not None:
|
|
583
|
-
payload["option_sets"] = option_sets
|
|
584
|
-
if runs is not None:
|
|
585
|
-
payload_runs = [{}] * len(runs)
|
|
586
|
-
for i, run in enumerate(runs):
|
|
587
|
-
payload_runs[i] = run.to_dict() if isinstance(run, BatchExperimentRun) else run
|
|
588
|
-
payload["runs"] = payload_runs
|
|
589
|
-
|
|
590
|
-
response = self.client.request(
|
|
591
|
-
method="POST",
|
|
592
|
-
endpoint=f"{self.experiments_endpoint}/batch",
|
|
593
|
-
payload=payload,
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
return response.json()["id"]
|
|
597
|
-
|
|
598
|
-
def new_input_set(
|
|
599
|
-
self,
|
|
600
|
-
id: str,
|
|
601
|
-
name: str,
|
|
602
|
-
description: Optional[str] = None,
|
|
603
|
-
end_time: Optional[datetime] = None,
|
|
604
|
-
instance_id: Optional[str] = None,
|
|
605
|
-
maximum_runs: Optional[int] = None,
|
|
606
|
-
run_ids: Optional[list[str]] = None,
|
|
607
|
-
start_time: Optional[datetime] = None,
|
|
608
|
-
) -> InputSet:
|
|
609
|
-
"""
|
|
610
|
-
Create a new input set.
|
|
611
|
-
|
|
612
|
-
Args:
|
|
613
|
-
id: ID of the input set.
|
|
614
|
-
name: Name of the input set.
|
|
615
|
-
description: Description of the input set.
|
|
616
|
-
end_time: End time of the runs to construct the input set.
|
|
617
|
-
instance_id: ID of the instance to use for the input set. If not
|
|
618
|
-
provided, the default_instance_id will be used.
|
|
619
|
-
maximum_runs: Maximum number of runs to use for the input set.
|
|
620
|
-
run_ids: IDs of the runs to use for the input set.
|
|
621
|
-
start_time: Start time of the runs to construct the input set.
|
|
622
|
-
|
|
623
|
-
Returns:
|
|
624
|
-
Input set.
|
|
625
|
-
|
|
626
|
-
Raises:
|
|
627
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
628
|
-
"""
|
|
629
|
-
|
|
630
|
-
payload = {
|
|
631
|
-
"id": id,
|
|
632
|
-
"name": name,
|
|
633
|
-
}
|
|
634
|
-
if description is not None:
|
|
635
|
-
payload["description"] = description
|
|
636
|
-
if end_time is not None:
|
|
637
|
-
payload["end_time"] = end_time.isoformat()
|
|
638
|
-
if instance_id is not None:
|
|
639
|
-
payload["instance_id"] = instance_id
|
|
640
|
-
if maximum_runs is not None:
|
|
641
|
-
payload["maximum_runs"] = maximum_runs
|
|
642
|
-
if run_ids is not None:
|
|
643
|
-
payload["run_ids"] = run_ids
|
|
644
|
-
if start_time is not None:
|
|
645
|
-
payload["start_time"] = start_time.isoformat()
|
|
646
|
-
|
|
647
|
-
response = self.client.request(
|
|
648
|
-
method="POST",
|
|
649
|
-
endpoint=f"{self.experiments_endpoint}/inputsets",
|
|
650
|
-
payload=payload,
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
return InputSet.from_dict(response.json())
|
|
654
|
-
|
|
655
|
-
def new_run( # noqa: C901 # Lot of if statements, but clear logic.
|
|
656
|
-
self,
|
|
657
|
-
input: Union[dict[str, Any], BaseModel, str] = None,
|
|
658
|
-
instance_id: Optional[str] = None,
|
|
659
|
-
name: Optional[str] = None,
|
|
660
|
-
description: Optional[str] = None,
|
|
661
|
-
upload_id: Optional[str] = None,
|
|
662
|
-
options: Optional[dict[str, str]] = None,
|
|
663
|
-
configuration: Optional[Configuration] = None,
|
|
664
|
-
) -> str:
|
|
665
|
-
"""
|
|
666
|
-
Submit an input to start a new run of the application. Returns the
|
|
667
|
-
run_id of the submitted run.
|
|
668
|
-
|
|
669
|
-
Args:
|
|
670
|
-
input: Input to use for the run. This can be JSON (given as dict
|
|
671
|
-
or BaseModel) or text (given as str).
|
|
672
|
-
instance_id: ID of the instance to use for the run. If not
|
|
673
|
-
provided, the default_instance_id will be used.
|
|
674
|
-
name: Name of the run.
|
|
675
|
-
description: Description of the run.
|
|
676
|
-
upload_id: ID to use when running a large input.
|
|
677
|
-
options: Options to use for the run.
|
|
678
|
-
configuration: Configuration to use for the run.
|
|
679
|
-
|
|
680
|
-
Returns:
|
|
681
|
-
ID of the submitted run.
|
|
682
|
-
|
|
683
|
-
Raises:
|
|
684
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
685
|
-
"""
|
|
686
|
-
|
|
687
|
-
input_size = 0
|
|
688
|
-
if isinstance(input, BaseModel):
|
|
689
|
-
input = input.to_dict()
|
|
690
|
-
if input is not None:
|
|
691
|
-
input_size = get_size(input)
|
|
692
|
-
elif isinstance(input, dict):
|
|
693
|
-
input_size = get_size(input)
|
|
694
|
-
|
|
695
|
-
upload_url_required = isinstance(input, str) or input_size > _MAX_RUN_SIZE
|
|
696
|
-
|
|
697
|
-
upload_id_used = upload_id is not None
|
|
698
|
-
if not upload_id_used and upload_url_required:
|
|
699
|
-
upload_url = self.upload_url()
|
|
700
|
-
self.upload_large_input(input=input, upload_url=upload_url)
|
|
701
|
-
upload_id = upload_url.upload_id
|
|
702
|
-
upload_id_used = True
|
|
703
|
-
|
|
704
|
-
if options is not None:
|
|
705
|
-
for key, value in options.items():
|
|
706
|
-
if not isinstance(value, str):
|
|
707
|
-
options[key] = json.dumps(value)
|
|
708
|
-
|
|
709
|
-
payload = {}
|
|
710
|
-
if upload_id_used:
|
|
711
|
-
payload["upload_id"] = upload_id
|
|
712
|
-
else:
|
|
713
|
-
payload["input"] = input
|
|
714
|
-
|
|
715
|
-
if name is not None:
|
|
716
|
-
payload["name"] = name
|
|
717
|
-
if description is not None:
|
|
718
|
-
payload["description"] = description
|
|
719
|
-
if options is not None:
|
|
720
|
-
payload["options"] = options
|
|
721
|
-
if configuration is not None:
|
|
722
|
-
payload["configuration"] = configuration.to_dict()
|
|
723
|
-
|
|
724
|
-
query_params = {
|
|
725
|
-
"instance_id": instance_id if instance_id is not None else self.default_instance_id,
|
|
726
|
-
}
|
|
727
|
-
response = self.client.request(
|
|
728
|
-
method="POST",
|
|
729
|
-
endpoint=f"{self.endpoint}/runs",
|
|
730
|
-
payload=payload,
|
|
731
|
-
query_params=query_params,
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
return response.json()["run_id"]
|
|
735
|
-
|
|
736
|
-
def new_run_with_result(
|
|
737
|
-
self,
|
|
738
|
-
input: Union[dict[str, Any], BaseModel] = None,
|
|
739
|
-
instance_id: Optional[str] = None,
|
|
740
|
-
name: Optional[str] = None,
|
|
741
|
-
description: Optional[str] = None,
|
|
742
|
-
upload_id: Optional[str] = None,
|
|
743
|
-
run_options: Optional[dict[str, str]] = None,
|
|
744
|
-
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
745
|
-
configuration: Optional[Configuration] = None,
|
|
746
|
-
) -> RunResult:
|
|
747
|
-
"""
|
|
748
|
-
Submit an input to start a new run of the application and poll for the
|
|
749
|
-
result. This is a convenience method that combines the new_run and
|
|
750
|
-
run_result_with_polling methods, applying polling logic to check when
|
|
751
|
-
the run succeeded.
|
|
752
|
-
|
|
753
|
-
Args:
|
|
754
|
-
input: Input to use for the run.
|
|
755
|
-
instance_id: ID of the instance to use for the run. If not
|
|
756
|
-
provided, the default_instance_id will be used.
|
|
757
|
-
name: Name of the run.
|
|
758
|
-
description: Description of the run.
|
|
759
|
-
upload_id: ID to use when running a large input.
|
|
760
|
-
run_options: Options to use for the run.
|
|
761
|
-
polling_options: Options to use when polling for the run result.
|
|
762
|
-
configuration: Configuration to use for the run.
|
|
763
|
-
|
|
764
|
-
Returns:
|
|
765
|
-
Result of the run.
|
|
766
|
-
|
|
767
|
-
Raises:
|
|
768
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
769
|
-
TimeoutError: If the run does not succeed after the polling
|
|
770
|
-
strategy is exhausted based on time duration.
|
|
771
|
-
RuntimeError: If the run does not succeed after the polling
|
|
772
|
-
strategy is exhausted based on number of tries.
|
|
773
|
-
"""
|
|
774
|
-
|
|
775
|
-
run_id = self.new_run(
|
|
776
|
-
input=input,
|
|
777
|
-
instance_id=instance_id,
|
|
778
|
-
name=name,
|
|
779
|
-
description=description,
|
|
780
|
-
upload_id=upload_id,
|
|
781
|
-
options=run_options,
|
|
782
|
-
configuration=configuration,
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
return self.run_result_with_polling(
|
|
786
|
-
run_id=run_id,
|
|
787
|
-
polling_options=polling_options,
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
def new_version(
|
|
791
|
-
self,
|
|
792
|
-
id: Optional[str] = None,
|
|
793
|
-
name: Optional[str] = None,
|
|
794
|
-
description: Optional[str] = None,
|
|
795
|
-
) -> Version:
|
|
796
|
-
"""
|
|
797
|
-
Create a new version using the current dev binary.
|
|
798
|
-
|
|
799
|
-
Args:
|
|
800
|
-
id: ID of the version. Will be generated if not provided.
|
|
801
|
-
name: Name of the version. Will be generated if not provided.
|
|
802
|
-
description: Description of the version. Will be generated if not provided.
|
|
803
|
-
|
|
804
|
-
Returns:
|
|
805
|
-
Version.
|
|
806
|
-
|
|
807
|
-
Raises:
|
|
808
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
809
|
-
"""
|
|
810
|
-
|
|
811
|
-
payload = {}
|
|
812
|
-
|
|
813
|
-
if id is not None:
|
|
814
|
-
payload["id"] = id
|
|
815
|
-
if name is not None:
|
|
816
|
-
payload["name"] = name
|
|
817
|
-
if description is not None:
|
|
818
|
-
payload["description"] = description
|
|
819
|
-
|
|
820
|
-
response = self.client.request(
|
|
821
|
-
method="POST",
|
|
822
|
-
endpoint=f"{self.endpoint}/versions",
|
|
823
|
-
payload=payload,
|
|
824
|
-
)
|
|
825
|
-
|
|
826
|
-
return Version.from_dict(response.json())
|
|
827
|
-
|
|
828
|
-
def new_instance(
|
|
829
|
-
self,
|
|
830
|
-
version_id: str,
|
|
831
|
-
id: Optional[str] = None,
|
|
832
|
-
name: Optional[str] = None,
|
|
833
|
-
description: Optional[str] = None,
|
|
834
|
-
configuration: Optional[Configuration] = None,
|
|
835
|
-
) -> Instance:
|
|
836
|
-
"""
|
|
837
|
-
Create a new instance and associate it with a version.
|
|
838
|
-
|
|
839
|
-
Args:
|
|
840
|
-
version_id: ID of the version to associate the instance with.
|
|
841
|
-
id: ID of the instance. Will be generated if not provided.
|
|
842
|
-
name: Name of the instance. Will be generated if not provided.
|
|
843
|
-
description: Description of the instance. Will be generated if not provided.
|
|
844
|
-
configuration: Configuration to use for the instance.
|
|
845
|
-
|
|
846
|
-
Returns:
|
|
847
|
-
Instance.
|
|
848
|
-
|
|
849
|
-
Raises:
|
|
850
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
851
|
-
"""
|
|
852
|
-
|
|
853
|
-
payload = {
|
|
854
|
-
"version_id": version_id,
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
if id is not None:
|
|
858
|
-
payload["id"] = id
|
|
859
|
-
if name is not None:
|
|
860
|
-
payload["name"] = name
|
|
861
|
-
if description is not None:
|
|
862
|
-
payload["description"] = description
|
|
863
|
-
if configuration is not None:
|
|
864
|
-
payload["configuration"] = configuration.to_dict()
|
|
865
|
-
|
|
866
|
-
response = self.client.request(
|
|
867
|
-
method="POST",
|
|
868
|
-
endpoint=f"{self.endpoint}/instances",
|
|
869
|
-
payload=payload,
|
|
870
|
-
)
|
|
871
|
-
|
|
872
|
-
return Instance.from_dict(response.json())
|
|
873
|
-
|
|
874
|
-
@staticmethod
|
|
875
|
-
def new(
|
|
876
|
-
client: Client,
|
|
877
|
-
name: str,
|
|
878
|
-
id: Optional[str] = None,
|
|
879
|
-
description: Optional[str] = None,
|
|
880
|
-
) -> "Application":
|
|
881
|
-
"""
|
|
882
|
-
Create a new application.
|
|
883
|
-
|
|
884
|
-
Args:
|
|
885
|
-
client: Client to use for interacting with the Nextmv Cloud API.
|
|
886
|
-
name: Name of the application.
|
|
887
|
-
id: ID of the application. Will be generated if not provided.
|
|
888
|
-
description: Description of the application.
|
|
889
|
-
|
|
890
|
-
Returns:
|
|
891
|
-
The new application.
|
|
892
|
-
"""
|
|
893
|
-
|
|
894
|
-
payload = {
|
|
895
|
-
"name": name,
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if description is not None:
|
|
899
|
-
payload["description"] = description
|
|
900
|
-
if id is not None:
|
|
901
|
-
payload["id"] = id
|
|
902
|
-
|
|
903
|
-
response = client.request(
|
|
904
|
-
method="POST",
|
|
905
|
-
endpoint="v1/applications",
|
|
906
|
-
payload=payload,
|
|
907
|
-
)
|
|
908
|
-
|
|
909
|
-
return Application(client=client, id=response.json()["id"])
|
|
910
|
-
|
|
911
|
-
def delete(self) -> None:
|
|
912
|
-
"""
|
|
913
|
-
Delete the application.
|
|
914
|
-
|
|
915
|
-
Raises:
|
|
916
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
917
|
-
"""
|
|
918
|
-
|
|
919
|
-
_ = self.client.request(
|
|
920
|
-
method="DELETE",
|
|
921
|
-
endpoint=self.endpoint,
|
|
922
|
-
)
|
|
923
|
-
|
|
924
|
-
def push(
|
|
925
|
-
self,
|
|
926
|
-
manifest: Optional[Manifest] = None,
|
|
927
|
-
app_dir: Optional[str] = None,
|
|
928
|
-
verbose: bool = False,
|
|
929
|
-
model: Optional[Model] = None,
|
|
930
|
-
model_configuration: Optional[ModelConfiguration] = None,
|
|
931
|
-
) -> None:
|
|
932
|
-
"""
|
|
933
|
-
Push an app to Nextmv Cloud.
|
|
934
|
-
|
|
935
|
-
If the manifest is not provided, an `app.yaml` file will be searched
|
|
936
|
-
for in the provided path. If there is no manifest file found, an
|
|
937
|
-
exception will be raised.
|
|
938
|
-
|
|
939
|
-
There are two ways to push an app to Nextmv Cloud:
|
|
940
|
-
1. Specifying `app_dir`, which is the path to an app’s root directory.
|
|
941
|
-
This acts as an external strategy, where the app is composed of files
|
|
942
|
-
in a directory and those apps are packaged and pushed to Nextmv Cloud.
|
|
943
|
-
2. Specifying a `model` and `model_configuration`. This acts as an
|
|
944
|
-
internal (or Python-native) strategy, where the app is actually a
|
|
945
|
-
`nextmv.Model`. The model is encoded, some dependencies and
|
|
946
|
-
accompanying files are packaged, and the app is pushed to Nextmv Cloud.
|
|
947
|
-
|
|
948
|
-
Examples
|
|
949
|
-
-------
|
|
950
|
-
|
|
951
|
-
1. Push an app using an external strategy, i.e., specifying the app’s
|
|
952
|
-
directory:
|
|
953
|
-
```python
|
|
954
|
-
import os
|
|
955
|
-
|
|
956
|
-
from nextmv import cloud
|
|
957
|
-
|
|
958
|
-
client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
|
|
959
|
-
app = cloud.Application(client=client, id="<YOUR-APP-ID>")
|
|
960
|
-
app.push() # Use verbose=True for step-by-step output.
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
2. Push an app using an internal strategy, i.e., specifying the model
|
|
964
|
-
and model configuration:
|
|
965
|
-
```python
|
|
966
|
-
import os
|
|
967
|
-
|
|
968
|
-
import nextroute
|
|
969
|
-
|
|
970
|
-
import nextmv
|
|
971
|
-
import nextmv.cloud
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
# Define the model that makes decisions. This model uses the Nextroute
|
|
975
|
-
# library to solve a vehicle routing problem.
|
|
976
|
-
class DecisionModel(nextmv.Model):
|
|
977
|
-
def solve(self, input: nextmv.Input) -> nextmv.Output:
|
|
978
|
-
nextroute_input = nextroute.schema.Input.from_dict(input.data)
|
|
979
|
-
nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())
|
|
980
|
-
nextroute_output = nextroute.solve(nextroute_input, nextroute_options)
|
|
981
|
-
|
|
982
|
-
return nextmv.Output(
|
|
983
|
-
options=input.options,
|
|
984
|
-
solution=nextroute_output.solutions[0].to_dict(),
|
|
985
|
-
statistics=nextroute_output.statistics.to_dict(),
|
|
986
|
-
)
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
# Define the options that the model needs.
|
|
990
|
-
parameters = []
|
|
991
|
-
default_options = nextroute.Options()
|
|
992
|
-
for name, default_value in default_options.to_dict().items():
|
|
993
|
-
parameters.append(nextmv.Parameter(name.lower(), type(default_value), default_value, name, False))
|
|
994
|
-
|
|
995
|
-
options = nextmv.Options(*parameters)
|
|
996
|
-
|
|
997
|
-
# Instantiate the model and model configuration.
|
|
998
|
-
model = DecisionModel()
|
|
999
|
-
model_configuration = nextmv.ModelConfiguration(
|
|
1000
|
-
name="python_nextroute_model",
|
|
1001
|
-
requirements=[
|
|
1002
|
-
"nextroute==1.8.1",
|
|
1003
|
-
"nextmv==0.14.0.dev1",
|
|
1004
|
-
],
|
|
1005
|
-
options=options,
|
|
1006
|
-
)
|
|
1007
|
-
|
|
1008
|
-
# Define the Nextmv application and push the model to the cloud.
|
|
1009
|
-
client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
|
|
1010
|
-
app = cloud.Application(client=client, id="<YOUR-APP-ID>")
|
|
1011
|
-
manifest = nextmv.cloud.default_python_manifest()
|
|
1012
|
-
app.push(
|
|
1013
|
-
manifest=manifest,
|
|
1014
|
-
verbose=True,
|
|
1015
|
-
model=model,
|
|
1016
|
-
model_configuration=model_configuration,
|
|
1017
|
-
)
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
Parameters
|
|
1021
|
-
----------
|
|
1022
|
-
manifest : Optional[Manifest], optional
|
|
1023
|
-
The manifest for the app, by default None.
|
|
1024
|
-
app_dir : Optional[str], optional
|
|
1025
|
-
The path to the app’s directory, by default None.
|
|
1026
|
-
verbose : bool, optional
|
|
1027
|
-
Whether to print verbose output, by default False.
|
|
1028
|
-
"""
|
|
1029
|
-
|
|
1030
|
-
if verbose:
|
|
1031
|
-
log("💽 Starting build for Nextmv application.")
|
|
1032
|
-
|
|
1033
|
-
if app_dir is None or app_dir == "":
|
|
1034
|
-
app_dir = "."
|
|
1035
|
-
|
|
1036
|
-
if manifest is None:
|
|
1037
|
-
manifest = Manifest.from_yaml(app_dir)
|
|
1038
|
-
|
|
1039
|
-
if model is not None and not isinstance(model, Model):
|
|
1040
|
-
raise TypeError("model must be an instance of nextmv.Model")
|
|
1041
|
-
|
|
1042
|
-
if model_configuration is not None and not isinstance(model_configuration, ModelConfiguration):
|
|
1043
|
-
raise TypeError("model_configuration must be an instance of nextmv.ModelConfiguration")
|
|
1044
|
-
|
|
1045
|
-
if (model is None and model_configuration is not None) or (model is not None and model_configuration is None):
|
|
1046
|
-
raise ValueError("model and model_configuration must be provided together")
|
|
1047
|
-
|
|
1048
|
-
package._run_build_command(app_dir, manifest.build, verbose)
|
|
1049
|
-
package._run_pre_push_command(app_dir, manifest.pre_push, verbose)
|
|
1050
|
-
tar_file, output_dir = package._package(app_dir, manifest, model, model_configuration, verbose)
|
|
1051
|
-
self.__update_app_binary(tar_file, manifest, verbose)
|
|
1052
|
-
|
|
1053
|
-
try:
|
|
1054
|
-
shutil.rmtree(output_dir)
|
|
1055
|
-
except OSError as e:
|
|
1056
|
-
raise Exception(f"error deleting output directory: {e}") from e
|
|
1057
|
-
|
|
1058
|
-
def run_input(self, run_id: str) -> dict[str, Any]:
|
|
1059
|
-
"""
|
|
1060
|
-
Get the input of a run.
|
|
1061
|
-
|
|
1062
|
-
Args:
|
|
1063
|
-
run_id: ID of the run.
|
|
1064
|
-
|
|
1065
|
-
Returns:
|
|
1066
|
-
Input of the run.
|
|
1067
|
-
|
|
1068
|
-
Raises:
|
|
1069
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1070
|
-
"""
|
|
1071
|
-
run_information = self.run_metadata(run_id=run_id)
|
|
1072
|
-
|
|
1073
|
-
query_params = None
|
|
1074
|
-
large = False
|
|
1075
|
-
if run_information.metadata.input_size > _MAX_RUN_SIZE:
|
|
1076
|
-
query_params = {"format": "url"}
|
|
1077
|
-
large = True
|
|
1078
|
-
|
|
1079
|
-
response = self.client.request(
|
|
1080
|
-
method="GET",
|
|
1081
|
-
endpoint=f"{self.endpoint}/runs/{run_id}/input",
|
|
1082
|
-
query_params=query_params,
|
|
1083
|
-
)
|
|
1084
|
-
if not large:
|
|
1085
|
-
return response.json()
|
|
1086
|
-
|
|
1087
|
-
download_url = DownloadURL.from_dict(response.json())
|
|
1088
|
-
download_response = self.client.request(
|
|
1089
|
-
method="GET",
|
|
1090
|
-
endpoint=download_url.url,
|
|
1091
|
-
headers={"Content-Type": "application/json"},
|
|
1092
|
-
)
|
|
1093
|
-
|
|
1094
|
-
return download_response.json()
|
|
1095
|
-
|
|
1096
|
-
def run_logs(self, run_id: str) -> RunLog:
|
|
1097
|
-
"""
|
|
1098
|
-
Get the logs of a run.
|
|
1099
|
-
|
|
1100
|
-
Args:
|
|
1101
|
-
run_id: ID of the run.
|
|
1102
|
-
|
|
1103
|
-
Returns:
|
|
1104
|
-
Logs of the run.
|
|
1105
|
-
|
|
1106
|
-
Raises:
|
|
1107
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1108
|
-
"""
|
|
1109
|
-
response = self.client.request(
|
|
1110
|
-
method="GET",
|
|
1111
|
-
endpoint=f"{self.endpoint}/runs/{run_id}/logs",
|
|
1112
|
-
)
|
|
1113
|
-
return RunLog.from_dict(response.json())
|
|
1114
|
-
|
|
1115
|
-
def run_metadata(self, run_id: str) -> RunInformation:
|
|
1116
|
-
"""
|
|
1117
|
-
Get the metadata of a run. The result does not include the run output.
|
|
1118
|
-
|
|
1119
|
-
Args:
|
|
1120
|
-
run_id: ID of the run.
|
|
1121
|
-
|
|
1122
|
-
Returns:
|
|
1123
|
-
Metadata of the run (Run result with no output).
|
|
1124
|
-
|
|
1125
|
-
Raises:
|
|
1126
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1127
|
-
"""
|
|
1128
|
-
|
|
1129
|
-
response = self.client.request(
|
|
1130
|
-
method="GET",
|
|
1131
|
-
endpoint=f"{self.endpoint}/runs/{run_id}/metadata",
|
|
1132
|
-
)
|
|
1133
|
-
|
|
1134
|
-
return RunInformation.from_dict(response.json())
|
|
1135
|
-
|
|
1136
|
-
def run_result(self, run_id: str) -> RunResult:
|
|
1137
|
-
"""
|
|
1138
|
-
Get the result of a run. The result includes the run output.
|
|
1139
|
-
|
|
1140
|
-
Args:
|
|
1141
|
-
run_id: ID of the run.
|
|
1142
|
-
|
|
1143
|
-
Returns:
|
|
1144
|
-
Result of the run.
|
|
1145
|
-
|
|
1146
|
-
Raises:
|
|
1147
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1148
|
-
"""
|
|
1149
|
-
|
|
1150
|
-
run_information = self.run_metadata(run_id=run_id)
|
|
1151
|
-
|
|
1152
|
-
return self.__run_result(run_id=run_id, run_information=run_information)
|
|
1153
|
-
|
|
1154
|
-
def run_result_with_polling(
|
|
1155
|
-
self,
|
|
1156
|
-
run_id: str,
|
|
1157
|
-
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
1158
|
-
) -> RunResult:
|
|
1159
|
-
"""
|
|
1160
|
-
Get the result of a run. The result includes the run output. This
|
|
1161
|
-
method polls for the result until the run finishes executing or the
|
|
1162
|
-
polling strategy is exhausted.
|
|
1163
|
-
|
|
1164
|
-
Args:
|
|
1165
|
-
run_id: ID of the run.
|
|
1166
|
-
polling_options: Options to use when polling for the run result.
|
|
1167
|
-
|
|
1168
|
-
Returns:
|
|
1169
|
-
Result of the run.
|
|
1170
|
-
|
|
1171
|
-
Raises:
|
|
1172
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1173
|
-
"""
|
|
1174
|
-
|
|
1175
|
-
time.sleep(polling_options.initial_delay)
|
|
1176
|
-
delay = polling_options.delay
|
|
1177
|
-
polling_ok = False
|
|
1178
|
-
for _ in range(polling_options.max_tries):
|
|
1179
|
-
run_information = self.run_metadata(run_id=run_id)
|
|
1180
|
-
if run_information.metadata.status_v2 in [
|
|
1181
|
-
StatusV2.succeeded,
|
|
1182
|
-
StatusV2.failed,
|
|
1183
|
-
StatusV2.canceled,
|
|
1184
|
-
]:
|
|
1185
|
-
polling_ok = True
|
|
1186
|
-
break
|
|
1187
|
-
|
|
1188
|
-
if delay > polling_options.max_duration:
|
|
1189
|
-
raise TimeoutError(
|
|
1190
|
-
f"run {run_id} did not succeed after {delay} seconds",
|
|
1191
|
-
)
|
|
1192
|
-
|
|
1193
|
-
sleep_duration = min(delay, polling_options.max_delay)
|
|
1194
|
-
time.sleep(sleep_duration)
|
|
1195
|
-
delay *= polling_options.backoff
|
|
1196
|
-
|
|
1197
|
-
if not polling_ok:
|
|
1198
|
-
raise RuntimeError(
|
|
1199
|
-
f"run {run_id} did not succeed after {polling_options.max_tries} tries",
|
|
1200
|
-
)
|
|
1201
|
-
|
|
1202
|
-
return self.__run_result(run_id=run_id, run_information=run_information)
|
|
1203
|
-
|
|
1204
|
-
def update_instance(
|
|
1205
|
-
self,
|
|
1206
|
-
id: str,
|
|
1207
|
-
name: str,
|
|
1208
|
-
version_id: Optional[str] = None,
|
|
1209
|
-
description: Optional[str] = None,
|
|
1210
|
-
configuration: Optional[Configuration] = None,
|
|
1211
|
-
) -> Instance:
|
|
1212
|
-
"""
|
|
1213
|
-
Update an instance.
|
|
1214
|
-
|
|
1215
|
-
Args:
|
|
1216
|
-
id: ID of the instance to update.
|
|
1217
|
-
version_id: ID of the version to associate the instance with.
|
|
1218
|
-
name: Name of the instance.
|
|
1219
|
-
description: Description of the instance.
|
|
1220
|
-
configuration: Configuration to use for the instance.
|
|
1221
|
-
|
|
1222
|
-
Returns:
|
|
1223
|
-
Instance.
|
|
1224
|
-
|
|
1225
|
-
Raises:
|
|
1226
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1227
|
-
"""
|
|
1228
|
-
|
|
1229
|
-
payload = {}
|
|
1230
|
-
|
|
1231
|
-
if version_id is not None:
|
|
1232
|
-
payload["version_id"] = version_id
|
|
1233
|
-
if name is not None:
|
|
1234
|
-
payload["name"] = name
|
|
1235
|
-
if description is not None:
|
|
1236
|
-
payload["description"] = description
|
|
1237
|
-
if configuration is not None:
|
|
1238
|
-
payload["configuration"] = configuration.to_dict()
|
|
1239
|
-
|
|
1240
|
-
response = self.client.request(
|
|
1241
|
-
method="PUT",
|
|
1242
|
-
endpoint=f"{self.endpoint}/instances/{id}",
|
|
1243
|
-
payload=payload,
|
|
1244
|
-
)
|
|
1245
|
-
|
|
1246
|
-
return Instance.from_dict(response.json())
|
|
1247
|
-
|
|
1248
|
-
def upload_large_input(
|
|
1249
|
-
self,
|
|
1250
|
-
input: Union[dict[str, Any], str],
|
|
1251
|
-
upload_url: UploadURL,
|
|
1252
|
-
) -> None:
|
|
1253
|
-
"""
|
|
1254
|
-
Upload the file located at the given path to the provided upload URL.
|
|
1255
|
-
|
|
1256
|
-
Args:
|
|
1257
|
-
upload_url: Upload URL to use for uploading the file.
|
|
1258
|
-
input: Input to use for the run.
|
|
1259
|
-
|
|
1260
|
-
Raises:
|
|
1261
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1262
|
-
"""
|
|
1263
|
-
|
|
1264
|
-
if isinstance(input, dict):
|
|
1265
|
-
input = json.dumps(input)
|
|
1266
|
-
|
|
1267
|
-
_ = self.client.upload_to_presigned_url(
|
|
1268
|
-
url=upload_url.upload_url,
|
|
1269
|
-
data=input,
|
|
1270
|
-
)
|
|
1271
|
-
|
|
1272
|
-
def upload_url(self) -> UploadURL:
|
|
1273
|
-
"""
|
|
1274
|
-
Get an upload URL to use for uploading a file.
|
|
1275
|
-
|
|
1276
|
-
Returns:
|
|
1277
|
-
Result of getting an upload URL.
|
|
1278
|
-
|
|
1279
|
-
Raises:
|
|
1280
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1281
|
-
"""
|
|
1282
|
-
|
|
1283
|
-
response = self.client.request(
|
|
1284
|
-
method="POST",
|
|
1285
|
-
endpoint=f"{self.endpoint}/runs/uploadurl",
|
|
1286
|
-
)
|
|
1287
|
-
|
|
1288
|
-
return UploadURL.from_dict(response.json())
|
|
1289
|
-
|
|
1290
|
-
def version(self, version_id: str) -> Version:
|
|
1291
|
-
"""
|
|
1292
|
-
Get a version.
|
|
1293
|
-
|
|
1294
|
-
Args:
|
|
1295
|
-
version_id: ID of the version.
|
|
1296
|
-
|
|
1297
|
-
Returns:
|
|
1298
|
-
Version.
|
|
1299
|
-
|
|
1300
|
-
Raises:
|
|
1301
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1302
|
-
"""
|
|
1303
|
-
|
|
1304
|
-
response = self.client.request(
|
|
1305
|
-
method="GET",
|
|
1306
|
-
endpoint=f"{self.endpoint}/versions/{version_id}",
|
|
1307
|
-
)
|
|
1308
|
-
|
|
1309
|
-
return Version.from_dict(response.json())
|
|
1310
|
-
|
|
1311
|
-
def __run_result(
|
|
1312
|
-
self,
|
|
1313
|
-
run_id: str,
|
|
1314
|
-
run_information: RunInformation,
|
|
1315
|
-
) -> RunResult:
|
|
1316
|
-
"""
|
|
1317
|
-
Get the result of a run. The result includes the run output. This is a
|
|
1318
|
-
private method that is the base for retrieving a run result, regardless
|
|
1319
|
-
of polling.
|
|
1320
|
-
|
|
1321
|
-
Args:
|
|
1322
|
-
run_id: ID of the run.
|
|
1323
|
-
run_information: Information of the run.
|
|
1324
|
-
|
|
1325
|
-
Returns:
|
|
1326
|
-
Result of the run.
|
|
1327
|
-
|
|
1328
|
-
Raises:
|
|
1329
|
-
requests.HTTPError: If the response status code is not 2xx.
|
|
1330
|
-
"""
|
|
1331
|
-
query_params = None
|
|
1332
|
-
large_output = False
|
|
1333
|
-
if run_information.metadata.output_size > _MAX_RUN_SIZE:
|
|
1334
|
-
query_params = {"format": "url"}
|
|
1335
|
-
large_output = True
|
|
1336
|
-
|
|
1337
|
-
response = self.client.request(
|
|
1338
|
-
method="GET",
|
|
1339
|
-
endpoint=f"{self.endpoint}/runs/{run_id}",
|
|
1340
|
-
query_params=query_params,
|
|
1341
|
-
)
|
|
1342
|
-
result = RunResult.from_dict(response.json())
|
|
1343
|
-
if not large_output:
|
|
1344
|
-
return result
|
|
1345
|
-
|
|
1346
|
-
download_url = DownloadURL.from_dict(response.json()["output"])
|
|
1347
|
-
download_response = self.client.request(
|
|
1348
|
-
method="GET",
|
|
1349
|
-
endpoint=download_url.url,
|
|
1350
|
-
headers={"Content-Type": "application/json"},
|
|
1351
|
-
)
|
|
1352
|
-
result.output = download_response.json()
|
|
1353
|
-
|
|
1354
|
-
return result
|
|
1355
|
-
|
|
1356
|
-
def __update_app_binary(
|
|
1357
|
-
self,
|
|
1358
|
-
tar_file: str,
|
|
1359
|
-
manifest: Manifest,
|
|
1360
|
-
verbose: bool = False,
|
|
1361
|
-
) -> None:
|
|
1362
|
-
"""Updates the application binary in Cloud."""
|
|
1363
|
-
|
|
1364
|
-
if verbose:
|
|
1365
|
-
log(f'🌟 Pushing to application: "{self.id}".')
|
|
1366
|
-
|
|
1367
|
-
endpoint = f"{self.endpoint}/binary"
|
|
1368
|
-
response = self.client.request(
|
|
1369
|
-
method="GET",
|
|
1370
|
-
endpoint=endpoint,
|
|
1371
|
-
)
|
|
1372
|
-
upload_url = response.json()["upload_url"]
|
|
1373
|
-
|
|
1374
|
-
with open(tar_file, "rb") as f:
|
|
1375
|
-
response = self.client.request(
|
|
1376
|
-
method="PUT",
|
|
1377
|
-
endpoint=upload_url,
|
|
1378
|
-
data=f,
|
|
1379
|
-
headers={"Content-Type": "application/octet-stream"},
|
|
1380
|
-
)
|
|
1381
|
-
|
|
1382
|
-
activation_request = {
|
|
1383
|
-
"requirements": {
|
|
1384
|
-
"executable_type": manifest.type,
|
|
1385
|
-
"runtime": manifest.runtime,
|
|
1386
|
-
},
|
|
1387
|
-
}
|
|
1388
|
-
response = self.client.request(
|
|
1389
|
-
method="PUT",
|
|
1390
|
-
endpoint=endpoint,
|
|
1391
|
-
payload=activation_request,
|
|
1392
|
-
)
|
|
1393
|
-
|
|
1394
|
-
if verbose:
|
|
1395
|
-
log(f'💥️ Successfully pushed to application: "{self.id}".')
|
|
1396
|
-
log(
|
|
1397
|
-
json.dumps(
|
|
1398
|
-
{
|
|
1399
|
-
"app_id": self.id,
|
|
1400
|
-
"endpoint": self.client.url,
|
|
1401
|
-
"instance_url": f"{self.endpoint}/runs?instance_id=devint",
|
|
1402
|
-
},
|
|
1403
|
-
indent=2,
|
|
1404
|
-
)
|
|
1405
|
-
)
|