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/package.py
CHANGED
|
@@ -10,7 +10,7 @@ import tarfile
|
|
|
10
10
|
import tempfile
|
|
11
11
|
from typing import Optional
|
|
12
12
|
|
|
13
|
-
from nextmv.cloud.manifest import
|
|
13
|
+
from nextmv.cloud.manifest import MANIFEST_FILE_NAME, Manifest, ManifestBuild, ManifestType
|
|
14
14
|
from nextmv.logger import log
|
|
15
15
|
from nextmv.model import Model, ModelConfiguration, _cleanup_python_model
|
|
16
16
|
|
|
@@ -55,7 +55,7 @@ def _package(
|
|
|
55
55
|
raise Exception(f"error copying asset files {file['absolute_path']}: {e}") from e
|
|
56
56
|
|
|
57
57
|
if verbose:
|
|
58
|
-
log(f'📋 Copied files listed in "{
|
|
58
|
+
log(f'📋 Copied files listed in "{MANIFEST_FILE_NAME}" manifest.')
|
|
59
59
|
|
|
60
60
|
if manifest.type == ManifestType.PYTHON:
|
|
61
61
|
_cleanup_python_model(app_dir, model_configuration, verbose)
|
nextmv/cloud/run.py
CHANGED
|
@@ -1,4 +1,43 @@
|
|
|
1
|
-
"""This module contains definitions for an app run.
|
|
1
|
+
"""This module contains definitions for an app run.
|
|
2
|
+
|
|
3
|
+
Classes
|
|
4
|
+
-------
|
|
5
|
+
Metadata
|
|
6
|
+
Metadata of a run, whether it was successful or not.
|
|
7
|
+
RunInformation
|
|
8
|
+
Information of a run.
|
|
9
|
+
ErrorLog
|
|
10
|
+
Error log of a run, when it was not successful.
|
|
11
|
+
RunResult
|
|
12
|
+
Result of a run, whether it was successful or not.
|
|
13
|
+
RunLog
|
|
14
|
+
Log of a run.
|
|
15
|
+
FormatInput
|
|
16
|
+
Input format for a run configuration.
|
|
17
|
+
Format
|
|
18
|
+
Format for a run configuration.
|
|
19
|
+
RunType
|
|
20
|
+
The actual type of the run.
|
|
21
|
+
RunTypeConfiguration
|
|
22
|
+
Defines the configuration for the type of the run that is being executed
|
|
23
|
+
on an application.
|
|
24
|
+
RunQueuing
|
|
25
|
+
RunQueuing configuration for a run.
|
|
26
|
+
RunConfiguration
|
|
27
|
+
Configuration for an app run.
|
|
28
|
+
ExternalRunResult
|
|
29
|
+
Result of a run used to configure a new application run as an
|
|
30
|
+
external one.
|
|
31
|
+
TrackedRunStatus
|
|
32
|
+
The status of a tracked run.
|
|
33
|
+
TrackedRun
|
|
34
|
+
An external run that is tracked in the Nextmv platform.
|
|
35
|
+
|
|
36
|
+
Functions
|
|
37
|
+
---------
|
|
38
|
+
run_duration(start, end)
|
|
39
|
+
Calculate the duration of a run in milliseconds.
|
|
40
|
+
"""
|
|
2
41
|
|
|
3
42
|
import json
|
|
4
43
|
from dataclasses import dataclass
|
|
@@ -14,19 +53,22 @@ from nextmv.input import Input, InputFormat
|
|
|
14
53
|
from nextmv.output import Output, OutputFormat
|
|
15
54
|
|
|
16
55
|
|
|
17
|
-
def run_duration(
|
|
18
|
-
start: Union[datetime, float],
|
|
19
|
-
end: Union[datetime, float],
|
|
20
|
-
) -> int:
|
|
56
|
+
def run_duration(start: Union[datetime, float], end: Union[datetime, float]) -> int:
|
|
21
57
|
"""
|
|
22
58
|
Calculate the duration of a run in milliseconds.
|
|
23
59
|
|
|
60
|
+
You can import the `run_duration` function directly from `cloud`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from nextmv.cloud import run_duration
|
|
64
|
+
```
|
|
65
|
+
|
|
24
66
|
Parameters
|
|
25
67
|
----------
|
|
26
|
-
start :
|
|
68
|
+
start : datetime or float
|
|
27
69
|
The start time of the run. Can be a datetime object or a float
|
|
28
70
|
representing the start time in seconds since the epoch.
|
|
29
|
-
end :
|
|
71
|
+
end : datetime or float
|
|
30
72
|
The end time of the run. Can be a datetime object or a float
|
|
31
73
|
representing the end time in seconds since the epoch.
|
|
32
74
|
|
|
@@ -34,20 +76,73 @@ def run_duration(
|
|
|
34
76
|
-------
|
|
35
77
|
int
|
|
36
78
|
The duration of the run in milliseconds.
|
|
79
|
+
|
|
80
|
+
Raises
|
|
81
|
+
------
|
|
82
|
+
ValueError
|
|
83
|
+
If the start time is after the end time.
|
|
84
|
+
TypeError
|
|
85
|
+
If start and end are not both datetime objects or both float numbers.
|
|
86
|
+
|
|
87
|
+
Examples
|
|
88
|
+
--------
|
|
89
|
+
>>> from datetime import datetime, timedelta
|
|
90
|
+
>>> start_dt = datetime(2023, 1, 1, 12, 0, 0)
|
|
91
|
+
>>> end_dt = datetime(2023, 1, 1, 12, 0, 1)
|
|
92
|
+
>>> run_duration(start_dt, end_dt)
|
|
93
|
+
1000
|
|
94
|
+
|
|
95
|
+
>>> start_float = 1672574400.0 # Corresponds to 2023-01-01 12:00:00
|
|
96
|
+
>>> end_float = 1672574401.0 # Corresponds to 2023-01-01 12:00:01
|
|
97
|
+
>>> run_duration(start_float, end_float)
|
|
98
|
+
1000
|
|
37
99
|
"""
|
|
38
100
|
if isinstance(start, float) and isinstance(end, float):
|
|
39
101
|
if start > end:
|
|
40
102
|
raise ValueError("Start time must be before end time.")
|
|
41
103
|
return int(round((end - start) * 1000))
|
|
104
|
+
|
|
42
105
|
if isinstance(start, datetime) and isinstance(end, datetime):
|
|
43
106
|
if start > end:
|
|
44
107
|
raise ValueError("Start time must be before end time.")
|
|
45
108
|
return int(round((end - start).total_seconds() * 1000))
|
|
109
|
+
|
|
46
110
|
raise TypeError("Start and end must be either datetime or float.")
|
|
47
111
|
|
|
48
112
|
|
|
49
113
|
class Metadata(BaseModel):
|
|
50
|
-
"""
|
|
114
|
+
"""
|
|
115
|
+
Metadata of a run, whether it was successful or not.
|
|
116
|
+
|
|
117
|
+
You can import the `Metadata` class directly from `cloud`:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from nextmv.cloud import Metadata
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
application_id : str
|
|
126
|
+
ID of the application where the run was submitted to.
|
|
127
|
+
application_instance_id : str
|
|
128
|
+
ID of the instance where the run was submitted to.
|
|
129
|
+
application_version_id : str
|
|
130
|
+
ID of the version of the application where the run was submitted to.
|
|
131
|
+
created_at : datetime
|
|
132
|
+
Date and time when the run was created.
|
|
133
|
+
duration : float
|
|
134
|
+
Duration of the run in milliseconds.
|
|
135
|
+
error : str
|
|
136
|
+
Error message if the run failed.
|
|
137
|
+
input_size : float
|
|
138
|
+
Size of the input in bytes.
|
|
139
|
+
output_size : float
|
|
140
|
+
Size of the output in bytes.
|
|
141
|
+
status : Status
|
|
142
|
+
Deprecated: use status_v2.
|
|
143
|
+
status_v2 : StatusV2
|
|
144
|
+
Status of the run.
|
|
145
|
+
"""
|
|
51
146
|
|
|
52
147
|
application_id: str
|
|
53
148
|
"""ID of the application where the run was submitted to."""
|
|
@@ -72,7 +167,30 @@ class Metadata(BaseModel):
|
|
|
72
167
|
|
|
73
168
|
|
|
74
169
|
class RunInformation(BaseModel):
|
|
75
|
-
"""
|
|
170
|
+
"""
|
|
171
|
+
Information of a run.
|
|
172
|
+
|
|
173
|
+
You can import the `RunInformation` class directly from `cloud`:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from nextmv.cloud import RunInformation
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
description : str
|
|
182
|
+
Description of the run.
|
|
183
|
+
id : str
|
|
184
|
+
ID of the run.
|
|
185
|
+
metadata : Metadata
|
|
186
|
+
Metadata of the run.
|
|
187
|
+
name : str
|
|
188
|
+
Name of the run.
|
|
189
|
+
user_email : str
|
|
190
|
+
Email of the user who submitted the run.
|
|
191
|
+
console_url : str, optional
|
|
192
|
+
URL to the run in the Nextmv console. Defaults to "".
|
|
193
|
+
"""
|
|
76
194
|
|
|
77
195
|
description: str
|
|
78
196
|
"""Description of the run."""
|
|
@@ -88,7 +206,24 @@ class RunInformation(BaseModel):
|
|
|
88
206
|
|
|
89
207
|
|
|
90
208
|
class ErrorLog(BaseModel):
|
|
91
|
-
"""
|
|
209
|
+
"""
|
|
210
|
+
Error log of a run, when it was not successful.
|
|
211
|
+
|
|
212
|
+
You can import the `ErrorLog` class directly from `cloud`:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from nextmv.cloud import ErrorLog
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
error : str, optional
|
|
221
|
+
Error message. Defaults to None.
|
|
222
|
+
stdout : str, optional
|
|
223
|
+
Standard output. Defaults to None.
|
|
224
|
+
stderr : str, optional
|
|
225
|
+
Standard error. Defaults to None.
|
|
226
|
+
"""
|
|
92
227
|
|
|
93
228
|
error: Optional[str] = None
|
|
94
229
|
"""Error message."""
|
|
@@ -99,7 +234,24 @@ class ErrorLog(BaseModel):
|
|
|
99
234
|
|
|
100
235
|
|
|
101
236
|
class RunResult(RunInformation):
|
|
102
|
-
"""
|
|
237
|
+
"""
|
|
238
|
+
Result of a run, whether it was successful or not.
|
|
239
|
+
|
|
240
|
+
You can import the `RunResult` class directly from `cloud`:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from nextmv.cloud import RunResult
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
error_log : ErrorLog, optional
|
|
249
|
+
Error log of the run. Only available if the run failed. Defaults to
|
|
250
|
+
None.
|
|
251
|
+
output : dict[str, Any], optional
|
|
252
|
+
Output of the run. Only available if the run succeeded. Defaults to
|
|
253
|
+
None.
|
|
254
|
+
"""
|
|
103
255
|
|
|
104
256
|
error_log: Optional[ErrorLog] = None
|
|
105
257
|
"""Error log of the run. Only available if the run failed."""
|
|
@@ -108,14 +260,40 @@ class RunResult(RunInformation):
|
|
|
108
260
|
|
|
109
261
|
|
|
110
262
|
class RunLog(BaseModel):
|
|
111
|
-
"""
|
|
263
|
+
"""
|
|
264
|
+
Log of a run.
|
|
265
|
+
|
|
266
|
+
You can import the `RunLog` class directly from `cloud`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from nextmv.cloud import RunLog
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
log : str
|
|
275
|
+
Log of the run.
|
|
276
|
+
"""
|
|
112
277
|
|
|
113
278
|
log: str
|
|
114
279
|
"""Log of the run."""
|
|
115
280
|
|
|
116
281
|
|
|
117
282
|
class FormatInput(BaseModel):
|
|
118
|
-
"""
|
|
283
|
+
"""
|
|
284
|
+
Input format for a run configuration.
|
|
285
|
+
|
|
286
|
+
You can import the `FormatInput` class directly from `cloud`:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from nextmv.cloud import FormatInput
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
input_type : InputFormat, optional
|
|
295
|
+
Type of the input format. Defaults to `InputFormat.JSON`.
|
|
296
|
+
"""
|
|
119
297
|
|
|
120
298
|
input_type: InputFormat = Field(
|
|
121
299
|
serialization_alias="type",
|
|
@@ -126,7 +304,20 @@ class FormatInput(BaseModel):
|
|
|
126
304
|
|
|
127
305
|
|
|
128
306
|
class Format(BaseModel):
|
|
129
|
-
"""
|
|
307
|
+
"""
|
|
308
|
+
Format for a run configuration.
|
|
309
|
+
|
|
310
|
+
You can import the `Format` class directly from `cloud`:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from nextmv.cloud import Format
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
format_input : FormatInput
|
|
319
|
+
Input format for the run configuration.
|
|
320
|
+
"""
|
|
130
321
|
|
|
131
322
|
format_input: FormatInput = Field(
|
|
132
323
|
serialization_alias="input",
|
|
@@ -136,7 +327,24 @@ class Format(BaseModel):
|
|
|
136
327
|
|
|
137
328
|
|
|
138
329
|
class RunType(str, Enum):
|
|
139
|
-
"""
|
|
330
|
+
"""
|
|
331
|
+
The actual type of the run.
|
|
332
|
+
|
|
333
|
+
You can import the `RunType` class directly from `cloud`:
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from nextmv.cloud import RunType
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
STANDARD : str
|
|
342
|
+
Standard run type.
|
|
343
|
+
EXTERNAL : str
|
|
344
|
+
External run type.
|
|
345
|
+
ENSEMBLE : str
|
|
346
|
+
Ensemble run type.
|
|
347
|
+
"""
|
|
140
348
|
|
|
141
349
|
STANDARD = "standard"
|
|
142
350
|
"""Standard run type."""
|
|
@@ -147,8 +355,25 @@ class RunType(str, Enum):
|
|
|
147
355
|
|
|
148
356
|
|
|
149
357
|
class RunTypeConfiguration(BaseModel):
|
|
150
|
-
"""
|
|
151
|
-
|
|
358
|
+
"""
|
|
359
|
+
Defines the configuration for the type of the run that is being executed
|
|
360
|
+
on an application.
|
|
361
|
+
|
|
362
|
+
You can import the `RunTypeConfiguration` class directly from `cloud`:
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from nextmv.cloud import RunTypeConfiguration
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
run_type : RunType
|
|
371
|
+
Type of the run.
|
|
372
|
+
definition_id : str, optional
|
|
373
|
+
ID of the definition for the run type. Defaults to None.
|
|
374
|
+
reference_id : str, optional
|
|
375
|
+
ID of the reference for the run type. Defaults to None.
|
|
376
|
+
"""
|
|
152
377
|
|
|
153
378
|
run_type: RunType = Field(
|
|
154
379
|
serialization_alias="type",
|
|
@@ -162,7 +387,24 @@ class RunTypeConfiguration(BaseModel):
|
|
|
162
387
|
|
|
163
388
|
|
|
164
389
|
class RunQueuing(BaseModel):
|
|
165
|
-
"""
|
|
390
|
+
"""
|
|
391
|
+
RunQueuing configuration for a run.
|
|
392
|
+
|
|
393
|
+
You can import the `RunQueuing` class directly from `cloud`:
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
from nextmv.cloud import RunQueuing
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
priority : int, optional
|
|
402
|
+
Priority of the run in the queue. 1 is the highest priority, 9 is the
|
|
403
|
+
lowest priority. Defaults to None.
|
|
404
|
+
disabled : bool, optional
|
|
405
|
+
Whether the run should be queued, or not. If True, the run will not be
|
|
406
|
+
queued. If False, the run will be queued. Defaults to None.
|
|
407
|
+
"""
|
|
166
408
|
|
|
167
409
|
priority: Optional[int] = None
|
|
168
410
|
"""
|
|
@@ -176,7 +418,15 @@ class RunQueuing(BaseModel):
|
|
|
176
418
|
"""
|
|
177
419
|
|
|
178
420
|
def __post_init_post_parse__(self):
|
|
179
|
-
"""
|
|
421
|
+
"""
|
|
422
|
+
Validations done after parsing the model.
|
|
423
|
+
|
|
424
|
+
Raises
|
|
425
|
+
------
|
|
426
|
+
ValueError
|
|
427
|
+
If priority is not between 1 and 9, or if disabled is not a
|
|
428
|
+
boolean value.
|
|
429
|
+
"""
|
|
180
430
|
|
|
181
431
|
if self.priority is not None and (self.priority < 1 or self.priority > 9):
|
|
182
432
|
raise ValueError("Priority must be between 1 and 9.")
|
|
@@ -186,7 +436,28 @@ class RunQueuing(BaseModel):
|
|
|
186
436
|
|
|
187
437
|
|
|
188
438
|
class RunConfiguration(BaseModel):
|
|
189
|
-
"""
|
|
439
|
+
"""
|
|
440
|
+
Configuration for an app run.
|
|
441
|
+
|
|
442
|
+
You can import the `RunConfiguration` class directly from `cloud`:
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
from nextmv.cloud import RunConfiguration
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
execution_class : str, optional
|
|
451
|
+
Execution class for the instance. Defaults to None.
|
|
452
|
+
format : Format, optional
|
|
453
|
+
Format for the run configuration. Defaults to None.
|
|
454
|
+
run_type : RunTypeConfiguration, optional
|
|
455
|
+
Run type configuration for the run. Defaults to None.
|
|
456
|
+
secrets_collection_id : str, optional
|
|
457
|
+
ID of the secrets collection to use for the run. Defaults to None.
|
|
458
|
+
queuing : RunQueuing, optional
|
|
459
|
+
Queuing configuration for the run. Defaults to None.
|
|
460
|
+
"""
|
|
190
461
|
|
|
191
462
|
execution_class: Optional[str] = None
|
|
192
463
|
"""Execution class for the instance."""
|
|
@@ -201,8 +472,29 @@ class RunConfiguration(BaseModel):
|
|
|
201
472
|
|
|
202
473
|
|
|
203
474
|
class ExternalRunResult(BaseModel):
|
|
204
|
-
"""
|
|
205
|
-
|
|
475
|
+
"""
|
|
476
|
+
Result of a run used to configure a new application run as an
|
|
477
|
+
external one.
|
|
478
|
+
|
|
479
|
+
You can import the `ExternalRunResult` class directly from `cloud`:
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
from nextmv.cloud import ExternalRunResult
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
output_upload_id : str, optional
|
|
488
|
+
ID of the output upload. Defaults to None.
|
|
489
|
+
error_upload_id : str, optional
|
|
490
|
+
ID of the error upload. Defaults to None.
|
|
491
|
+
status : str, optional
|
|
492
|
+
Status of the run. Must be "succeeded" or "failed". Defaults to None.
|
|
493
|
+
error_message : str, optional
|
|
494
|
+
Error message of the run. Defaults to None.
|
|
495
|
+
execution_duration : int, optional
|
|
496
|
+
Duration of the run, in milliseconds. Defaults to None.
|
|
497
|
+
"""
|
|
206
498
|
|
|
207
499
|
output_upload_id: Optional[str] = None
|
|
208
500
|
"""ID of the output upload."""
|
|
@@ -216,7 +508,14 @@ class ExternalRunResult(BaseModel):
|
|
|
216
508
|
"""Duration of the run, in milliseconds."""
|
|
217
509
|
|
|
218
510
|
def __post_init_post_parse__(self):
|
|
219
|
-
"""
|
|
511
|
+
"""
|
|
512
|
+
Validations done after parsing the model.
|
|
513
|
+
|
|
514
|
+
Raises
|
|
515
|
+
------
|
|
516
|
+
ValueError
|
|
517
|
+
If the status value is not "succeeded" or "failed".
|
|
518
|
+
"""
|
|
220
519
|
|
|
221
520
|
valid_statuses = {"succeeded", "failed"}
|
|
222
521
|
if self.status is not None and self.status not in valid_statuses:
|
|
@@ -227,7 +526,13 @@ class TrackedRunStatus(str, Enum):
|
|
|
227
526
|
"""
|
|
228
527
|
The status of a tracked run.
|
|
229
528
|
|
|
230
|
-
|
|
529
|
+
You can import the `TrackedRunStatus` class directly from `cloud`:
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
from nextmv.cloud import TrackedRunStatus
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
231
536
|
----------
|
|
232
537
|
SUCCEEDED : str
|
|
233
538
|
The run succeeded.
|
|
@@ -246,28 +551,41 @@ class TrackedRun:
|
|
|
246
551
|
"""
|
|
247
552
|
An external run that is tracked in the Nextmv platform.
|
|
248
553
|
|
|
249
|
-
|
|
554
|
+
You can import the `TrackedRun` class directly from `cloud`:
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
from nextmv.cloud import TrackedRun
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Parameters
|
|
250
561
|
----------
|
|
251
|
-
input :
|
|
562
|
+
input : Input or dict[str, Any] or str
|
|
252
563
|
The input of the run being tracked. Please note that if the input
|
|
253
564
|
format is JSON, then the input data must be JSON serializable. This
|
|
254
565
|
field is required.
|
|
255
|
-
output :
|
|
566
|
+
output : Output or dict[str, Any] or str
|
|
256
567
|
The output of the run being tracked. Please note that if the output
|
|
257
568
|
format is JSON, then the output data must be JSON serializable. This
|
|
258
|
-
field is required.
|
|
569
|
+
field is required. Only JSON output_format is supported.
|
|
259
570
|
status : TrackedRunStatus
|
|
260
571
|
The status of the run being tracked. This field is required.
|
|
261
|
-
duration :
|
|
262
|
-
The duration of the run being tracked, in
|
|
263
|
-
optional.
|
|
264
|
-
error :
|
|
572
|
+
duration : int, optional
|
|
573
|
+
The duration of the run being tracked, in milliseconds. This field is
|
|
574
|
+
optional. Defaults to None.
|
|
575
|
+
error : str, optional
|
|
265
576
|
An error message if the run failed. You should only specify this if the
|
|
266
577
|
run failed (the `status` is `TrackedRunStatus.FAILED`), otherwise an
|
|
267
|
-
exception will be raised. This field is optional.
|
|
268
|
-
logs :
|
|
578
|
+
exception will be raised. This field is optional. Defaults to None.
|
|
579
|
+
logs : list[str], optional
|
|
269
580
|
The logs of the run being tracked. Each element of the list is a line in
|
|
270
|
-
the log. This field is optional.
|
|
581
|
+
the log. This field is optional. Defaults to None.
|
|
582
|
+
|
|
583
|
+
Raises
|
|
584
|
+
------
|
|
585
|
+
ValueError
|
|
586
|
+
If the status value is invalid, if an error message is provided for a
|
|
587
|
+
successful run, or if input/output formats are not JSON or
|
|
588
|
+
input/output dicts are not JSON serializable.
|
|
271
589
|
"""
|
|
272
590
|
|
|
273
591
|
input: Union[Input, dict[str, Any], str]
|
|
@@ -287,7 +605,16 @@ class TrackedRun:
|
|
|
287
605
|
the log."""
|
|
288
606
|
|
|
289
607
|
def __post_init__(self): # noqa: C901
|
|
290
|
-
"""
|
|
608
|
+
"""
|
|
609
|
+
Validations done after parsing the model.
|
|
610
|
+
|
|
611
|
+
Raises
|
|
612
|
+
------
|
|
613
|
+
ValueError
|
|
614
|
+
If the status value is invalid, if an error message is provided for
|
|
615
|
+
a successful run, or if input/output formats are not JSON or
|
|
616
|
+
input/output dicts are not JSON serializable.
|
|
617
|
+
"""
|
|
291
618
|
|
|
292
619
|
valid_statuses = {TrackedRunStatus.SUCCEEDED, TrackedRunStatus.FAILED}
|
|
293
620
|
if self.status not in valid_statuses:
|
|
@@ -318,14 +645,18 @@ class TrackedRun:
|
|
|
318
645
|
"""
|
|
319
646
|
Returns the logs as a single string.
|
|
320
647
|
|
|
321
|
-
|
|
322
|
-
----------
|
|
323
|
-
None
|
|
648
|
+
Each log entry is separated by a newline character.
|
|
324
649
|
|
|
325
650
|
Returns
|
|
326
651
|
-------
|
|
327
652
|
str
|
|
328
|
-
The logs as a single string.
|
|
653
|
+
The logs as a single string. If no logs are present, an empty
|
|
654
|
+
string is returned.
|
|
655
|
+
|
|
656
|
+
Raises
|
|
657
|
+
------
|
|
658
|
+
TypeError
|
|
659
|
+
If `self.logs` is not a string or a list of strings.
|
|
329
660
|
"""
|
|
330
661
|
|
|
331
662
|
if self.logs is None:
|
|
@@ -335,6 +666,6 @@ class TrackedRun:
|
|
|
335
666
|
return self.logs
|
|
336
667
|
|
|
337
668
|
if isinstance(self.logs, list):
|
|
338
|
-
return "
|
|
669
|
+
return "\\n".join(self.logs)
|
|
339
670
|
|
|
340
671
|
raise TypeError("Logs must be a string or a list of strings.")
|
nextmv/cloud/safe.py
CHANGED
|
@@ -11,27 +11,27 @@ INDEX_TAG_CHAR_COUNT: int = 3 # room reserved for “-001”, “-xyz”, etc.
|
|
|
11
11
|
RE_NON_ALNUM = re.compile(r"[^A-Za-z0-9]+")
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def _kebab_case(value: str) -> str:
|
|
15
15
|
"""Convert arbitrary text to `kebab-case` (lower-case, hyphen-separated)."""
|
|
16
16
|
|
|
17
17
|
cleaned = RE_NON_ALNUM.sub(" ", value).strip()
|
|
18
18
|
return "-".join(word.lower() for word in cleaned.split())
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def
|
|
21
|
+
def _start_case(value: str) -> str:
|
|
22
22
|
"""Convert `kebab-case` (or any hyphen/underscore string) to `Start Case`."""
|
|
23
23
|
|
|
24
24
|
cleaned = re.sub(r"[-_]+", " ", value)
|
|
25
25
|
return " ".join(word.capitalize() for word in cleaned.split())
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def _nanoid(size: int = 8, alphabet: str = string.ascii_lowercase + string.digits) -> str:
|
|
29
29
|
"""Simple nanoid clone using the std-lib `secrets` module."""
|
|
30
30
|
|
|
31
31
|
return "".join(secrets.choice(alphabet) for _ in range(size))
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def _name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
|
|
35
35
|
"""
|
|
36
36
|
Generate a safe ID and human-readable name from a prefix and user-supplied
|
|
37
37
|
identifier.
|
|
@@ -53,8 +53,8 @@ def name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
|
|
|
53
53
|
if not prefix or not entity_id:
|
|
54
54
|
return "", ""
|
|
55
55
|
|
|
56
|
-
safe_user_defined_id =
|
|
57
|
-
random_slug =
|
|
56
|
+
safe_user_defined_id = _kebab_case(entity_id)
|
|
57
|
+
random_slug = _nanoid(8)
|
|
58
58
|
|
|
59
59
|
# Space available for user text once prefix, random slug and separator "-"
|
|
60
60
|
# are accounted for
|
|
@@ -78,6 +78,6 @@ def name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
|
|
|
78
78
|
safe_id_parts.append(safe_slug)
|
|
79
79
|
|
|
80
80
|
safe_id = "-".join(filter(None, safe_id_parts)) + f"-{random_slug}"
|
|
81
|
-
safe_name =
|
|
81
|
+
safe_name = _start_case(safe_id)
|
|
82
82
|
|
|
83
83
|
return safe_name, safe_id
|