nextmv 0.10.3.dev0__py3-none-any.whl → 0.35.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/__entrypoint__.py +39 -0
- nextmv/__init__.py +57 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +79 -9
- nextmv/cloud/__init__.py +71 -10
- nextmv/cloud/acceptance_test.py +888 -17
- nextmv/cloud/account.py +154 -10
- nextmv/cloud/application.py +3644 -437
- nextmv/cloud/batch_experiment.py +292 -33
- nextmv/cloud/client.py +354 -53
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +121 -4
- nextmv/cloud/instance.py +125 -0
- nextmv/cloud/package.py +474 -0
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +174 -0
- 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/main.py +37 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +883 -78
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1263 -0
- nextmv/local/executor.py +1040 -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 +1472 -0
- nextmv/model.py +431 -0
- nextmv/options.py +968 -78
- nextmv/output.py +1363 -231
- nextmv/polling.py +287 -0
- nextmv/run.py +1623 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- {nextmv-0.10.3.dev0.dist-info → nextmv-0.35.0.dist-info}/METADATA +51 -288
- nextmv-0.35.0.dist-info/RECORD +50 -0
- {nextmv-0.10.3.dev0.dist-info → nextmv-0.35.0.dist-info}/WHEEL +1 -1
- nextmv/cloud/status.py +0 -29
- nextmv/nextroute/__init__.py +0 -2
- nextmv/nextroute/check/__init__.py +0 -26
- nextmv/nextroute/check/schema.py +0 -141
- nextmv/nextroute/schema/__init__.py +0 -19
- nextmv/nextroute/schema/input.py +0 -52
- nextmv/nextroute/schema/location.py +0 -13
- nextmv/nextroute/schema/output.py +0 -136
- nextmv/nextroute/schema/stop.py +0 -61
- nextmv/nextroute/schema/vehicle.py +0 -68
- nextmv-0.10.3.dev0.dist-info/RECORD +0 -28
- {nextmv-0.10.3.dev0.dist-info → nextmv-0.35.0.dist-info}/licenses/LICENSE +0 -0
nextmv/run.py
ADDED
|
@@ -0,0 +1,1623 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains definitions for an app run.
|
|
3
|
+
|
|
4
|
+
Classes
|
|
5
|
+
-------
|
|
6
|
+
Metadata
|
|
7
|
+
Metadata of a run, whether it was successful or not.
|
|
8
|
+
RunInformation
|
|
9
|
+
Information of a run.
|
|
10
|
+
ErrorLog
|
|
11
|
+
Error log of a run, when it was not successful.
|
|
12
|
+
RunResult
|
|
13
|
+
Result of a run, whether it was successful or not.
|
|
14
|
+
RunLog
|
|
15
|
+
Log of a run.
|
|
16
|
+
FormatInput
|
|
17
|
+
Input format for a run configuration.
|
|
18
|
+
FormatOutput
|
|
19
|
+
Output format for a run configuration.
|
|
20
|
+
Format
|
|
21
|
+
Format for a run configuration.
|
|
22
|
+
RunType
|
|
23
|
+
The actual type of the run.
|
|
24
|
+
RunTypeConfiguration
|
|
25
|
+
Defines the configuration for the type of the run that is being executed
|
|
26
|
+
on an application.
|
|
27
|
+
RunQueuing
|
|
28
|
+
RunQueuing configuration for a run.
|
|
29
|
+
RunConfiguration
|
|
30
|
+
Configuration for an app run.
|
|
31
|
+
ExternalRunResult
|
|
32
|
+
Result of a run used to configure a new application run as an
|
|
33
|
+
external one.
|
|
34
|
+
TrackedRunStatus
|
|
35
|
+
The status of a tracked run.
|
|
36
|
+
TrackedRun
|
|
37
|
+
An external run that is tracked in the Nextmv platform.
|
|
38
|
+
|
|
39
|
+
Functions
|
|
40
|
+
---------
|
|
41
|
+
run_duration(start, end)
|
|
42
|
+
Calculate the duration of a run in milliseconds.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from dataclasses import dataclass
|
|
46
|
+
from datetime import datetime
|
|
47
|
+
from enum import Enum
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
from pydantic import AliasChoices, Field, field_validator
|
|
51
|
+
|
|
52
|
+
from nextmv._serialization import serialize_json
|
|
53
|
+
from nextmv.base_model import BaseModel
|
|
54
|
+
from nextmv.input import Input, InputFormat
|
|
55
|
+
from nextmv.output import Asset, Output, OutputFormat, Statistics
|
|
56
|
+
from nextmv.status import Status, StatusV2
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def run_duration(start: datetime | float, end: datetime | float) -> int:
|
|
60
|
+
"""
|
|
61
|
+
Calculate the duration of a run in milliseconds.
|
|
62
|
+
|
|
63
|
+
You can import the `run_duration` function directly from `nextmv`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from nextmv import run_duration
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
start : datetime or float
|
|
72
|
+
The start time of the run. Can be a datetime object or a float
|
|
73
|
+
representing the start time in seconds since the epoch.
|
|
74
|
+
end : datetime or float
|
|
75
|
+
The end time of the run. Can be a datetime object or a float
|
|
76
|
+
representing the end time in seconds since the epoch.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
int
|
|
81
|
+
The duration of the run in milliseconds.
|
|
82
|
+
|
|
83
|
+
Raises
|
|
84
|
+
------
|
|
85
|
+
ValueError
|
|
86
|
+
If the start time is after the end time.
|
|
87
|
+
TypeError
|
|
88
|
+
If start and end are not both datetime objects or both float numbers.
|
|
89
|
+
|
|
90
|
+
Examples
|
|
91
|
+
--------
|
|
92
|
+
>>> from datetime import datetime, timedelta
|
|
93
|
+
>>> start_dt = datetime(2023, 1, 1, 12, 0, 0)
|
|
94
|
+
>>> end_dt = datetime(2023, 1, 1, 12, 0, 1)
|
|
95
|
+
>>> run_duration(start_dt, end_dt)
|
|
96
|
+
1000
|
|
97
|
+
|
|
98
|
+
>>> start_float = 1672574400.0 # Corresponds to 2023-01-01 12:00:00
|
|
99
|
+
>>> end_float = 1672574401.0 # Corresponds to 2023-01-01 12:00:01
|
|
100
|
+
>>> run_duration(start_float, end_float)
|
|
101
|
+
1000
|
|
102
|
+
"""
|
|
103
|
+
if isinstance(start, float) and isinstance(end, float):
|
|
104
|
+
if start > end:
|
|
105
|
+
raise ValueError("Start time must be before end time.")
|
|
106
|
+
return int(round((end - start) * 1000))
|
|
107
|
+
|
|
108
|
+
if isinstance(start, datetime) and isinstance(end, datetime):
|
|
109
|
+
if start > end:
|
|
110
|
+
raise ValueError("Start time must be before end time.")
|
|
111
|
+
return int(round((end - start).total_seconds() * 1000))
|
|
112
|
+
|
|
113
|
+
raise TypeError("Start and end must be either datetime or float.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class FormatInput(BaseModel):
|
|
117
|
+
"""
|
|
118
|
+
Input format for a run configuration.
|
|
119
|
+
|
|
120
|
+
You can import the `FormatInput` class directly from `nextmv`:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from nextmv import FormatInput
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
input_type : InputFormat, optional
|
|
129
|
+
Type of the input format. Defaults to `InputFormat.JSON`.
|
|
130
|
+
|
|
131
|
+
Examples
|
|
132
|
+
--------
|
|
133
|
+
>>> from nextmv import FormatInput, InputFormat
|
|
134
|
+
>>> format_input = FormatInput()
|
|
135
|
+
>>> format_input.input_type
|
|
136
|
+
<InputFormat.JSON: 'json'>
|
|
137
|
+
|
|
138
|
+
>>> format_input = FormatInput(input_type=InputFormat.TEXT)
|
|
139
|
+
>>> format_input.input_type
|
|
140
|
+
<InputFormat.TEXT: 'text'>
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
input_type: InputFormat = Field(
|
|
144
|
+
serialization_alias="type",
|
|
145
|
+
validation_alias=AliasChoices("type", "input_type"),
|
|
146
|
+
default=InputFormat.JSON,
|
|
147
|
+
)
|
|
148
|
+
"""Type of the input format."""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FormatOutput(BaseModel):
|
|
152
|
+
"""
|
|
153
|
+
Output format for a run configuration.
|
|
154
|
+
|
|
155
|
+
You can import the `FormatOutput` class directly from `nextmv`:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from nextmv import FormatOutput
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
output_type : OutputFormat, optional
|
|
164
|
+
Type of the output format. Defaults to `OutputFormat.JSON`.
|
|
165
|
+
|
|
166
|
+
Examples
|
|
167
|
+
--------
|
|
168
|
+
>>> from nextmv import FormatOutput, OutputFormat
|
|
169
|
+
>>> format_output = FormatOutput()
|
|
170
|
+
>>> format_output.output_type
|
|
171
|
+
<OutputFormat.JSON: 'json'>
|
|
172
|
+
|
|
173
|
+
>>> format_output = FormatOutput(output_type=OutputFormat.CSV_ARCHIVE)
|
|
174
|
+
>>> format_output.output_type
|
|
175
|
+
<OutputFormat.CSV_ARCHIVE: 'csv_archive'>
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
output_type: OutputFormat = Field(
|
|
179
|
+
serialization_alias="type",
|
|
180
|
+
validation_alias=AliasChoices("type", "output_type"),
|
|
181
|
+
default=OutputFormat.JSON,
|
|
182
|
+
)
|
|
183
|
+
"""Type of the output format."""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Format(BaseModel):
|
|
187
|
+
"""
|
|
188
|
+
Format for a run configuration.
|
|
189
|
+
|
|
190
|
+
You can import the `Format` class directly from `nextmv`:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from nextmv import Format
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
format_input : FormatInput
|
|
199
|
+
Input format for the run configuration.
|
|
200
|
+
format_output : FormatOutput, optional
|
|
201
|
+
Output format for the run configuration. Defaults to None.
|
|
202
|
+
|
|
203
|
+
Examples
|
|
204
|
+
--------
|
|
205
|
+
>>> from nextmv import Format, FormatInput, FormatOutput, InputFormat, OutputFormat
|
|
206
|
+
>>> format_config = Format(
|
|
207
|
+
... format_input=FormatInput(input_type=InputFormat.JSON),
|
|
208
|
+
... format_output=FormatOutput(output_type=OutputFormat.JSON)
|
|
209
|
+
... )
|
|
210
|
+
>>> format_config.format_input.input_type
|
|
211
|
+
<InputFormat.JSON: 'json'>
|
|
212
|
+
>>> format_config.format_output.output_type
|
|
213
|
+
<OutputFormat.JSON: 'json'>
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
format_input: FormatInput = Field(
|
|
217
|
+
serialization_alias="input",
|
|
218
|
+
validation_alias=AliasChoices("input", "format_input"),
|
|
219
|
+
)
|
|
220
|
+
"""Input format for the run configuration."""
|
|
221
|
+
format_output: FormatOutput | None = Field(
|
|
222
|
+
serialization_alias="output",
|
|
223
|
+
validation_alias=AliasChoices("output", "format_output"),
|
|
224
|
+
default=None,
|
|
225
|
+
)
|
|
226
|
+
"""Output format for the run configuration."""
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class RunType(str, Enum):
|
|
230
|
+
"""
|
|
231
|
+
The actual type of the run.
|
|
232
|
+
|
|
233
|
+
You can import the `RunType` class directly from `nextmv`:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from nextmv import RunType
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
STANDARD : str
|
|
242
|
+
Standard run type.
|
|
243
|
+
EXTERNAL : str
|
|
244
|
+
External run type.
|
|
245
|
+
ENSEMBLE : str
|
|
246
|
+
Ensemble run type.
|
|
247
|
+
|
|
248
|
+
Examples
|
|
249
|
+
--------
|
|
250
|
+
>>> from nextmv import RunType
|
|
251
|
+
>>> run_type = RunType.STANDARD
|
|
252
|
+
>>> run_type
|
|
253
|
+
<RunType.STANDARD: 'standard'>
|
|
254
|
+
>>> run_type.value
|
|
255
|
+
'standard'
|
|
256
|
+
|
|
257
|
+
>>> # Creating from string
|
|
258
|
+
>>> external_type = RunType("external")
|
|
259
|
+
>>> external_type
|
|
260
|
+
<RunType.EXTERNAL: 'external'>
|
|
261
|
+
|
|
262
|
+
>>> # All available types
|
|
263
|
+
>>> list(RunType)
|
|
264
|
+
[<RunType.STANDARD: 'standard'>, <RunType.EXTERNAL: 'external'>, <RunType.ENSEMBLE: 'ensemble'>]
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
STANDARD = "standard"
|
|
268
|
+
"""Standard run type."""
|
|
269
|
+
EXTERNAL = "external"
|
|
270
|
+
"""External run type."""
|
|
271
|
+
ENSEMBLE = "ensemble"
|
|
272
|
+
"""Ensemble run type."""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class RunTypeConfiguration(BaseModel):
|
|
276
|
+
"""
|
|
277
|
+
Defines the configuration for the type of the run that is being executed
|
|
278
|
+
on an application.
|
|
279
|
+
|
|
280
|
+
You can import the `RunTypeConfiguration` class directly from `nextmv`:
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from nextmv import RunTypeConfiguration
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
run_type : RunType
|
|
289
|
+
Type of the run.
|
|
290
|
+
definition_id : str, optional
|
|
291
|
+
ID of the definition for the run type. Defaults to None.
|
|
292
|
+
reference_id : str, optional
|
|
293
|
+
ID of the reference for the run type. Defaults to None.
|
|
294
|
+
|
|
295
|
+
Examples
|
|
296
|
+
--------
|
|
297
|
+
>>> from nextmv import RunTypeConfiguration, RunType
|
|
298
|
+
>>> config = RunTypeConfiguration(run_type=RunType.STANDARD)
|
|
299
|
+
>>> config.run_type
|
|
300
|
+
<RunType.STANDARD: 'standard'>
|
|
301
|
+
>>> config.definition_id is None
|
|
302
|
+
True
|
|
303
|
+
|
|
304
|
+
>>> # External run with reference
|
|
305
|
+
>>> external_config = RunTypeConfiguration(
|
|
306
|
+
... run_type=RunType.EXTERNAL,
|
|
307
|
+
... reference_id="ref-12345"
|
|
308
|
+
... )
|
|
309
|
+
>>> external_config.run_type
|
|
310
|
+
<RunType.EXTERNAL: 'external'>
|
|
311
|
+
>>> external_config.reference_id
|
|
312
|
+
'ref-12345'
|
|
313
|
+
|
|
314
|
+
>>> # Ensemble run with definition
|
|
315
|
+
>>> ensemble_config = RunTypeConfiguration(
|
|
316
|
+
... run_type=RunType.ENSEMBLE,
|
|
317
|
+
... definition_id="def-67890"
|
|
318
|
+
... )
|
|
319
|
+
>>> ensemble_config.run_type
|
|
320
|
+
<RunType.ENSEMBLE: 'ensemble'>
|
|
321
|
+
>>> ensemble_config.definition_id
|
|
322
|
+
'def-67890'
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
run_type: RunType | None = Field(
|
|
326
|
+
serialization_alias="type",
|
|
327
|
+
validation_alias=AliasChoices("type", "run_type"),
|
|
328
|
+
default=None,
|
|
329
|
+
)
|
|
330
|
+
"""Type of the run."""
|
|
331
|
+
definition_id: str | None = None
|
|
332
|
+
"""ID of the definition for the run type."""
|
|
333
|
+
reference_id: str | None = None
|
|
334
|
+
"""ID of the reference for the run type."""
|
|
335
|
+
|
|
336
|
+
@field_validator("run_type", mode="before")
|
|
337
|
+
@classmethod
|
|
338
|
+
def validate_run_type(cls, v):
|
|
339
|
+
"""Convert empty string to None for run_type validation."""
|
|
340
|
+
if v == "":
|
|
341
|
+
return None
|
|
342
|
+
return v
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class StatisticsIndicator(BaseModel):
|
|
346
|
+
"""
|
|
347
|
+
Statistics indicator of a run.
|
|
348
|
+
|
|
349
|
+
You can import the `StatisticsIndicator` class directly from `nextmv`:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from nextmv import StatisticsIndicator
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
name : str
|
|
358
|
+
Name of the indicator.
|
|
359
|
+
value : Any
|
|
360
|
+
Value of the indicator.
|
|
361
|
+
|
|
362
|
+
Examples
|
|
363
|
+
--------
|
|
364
|
+
>>> from nextmv import StatisticsIndicator
|
|
365
|
+
>>> indicator = StatisticsIndicator(name="total_cost", value=1250.75)
|
|
366
|
+
>>> indicator.name
|
|
367
|
+
'total_cost'
|
|
368
|
+
>>> indicator.value
|
|
369
|
+
1250.75
|
|
370
|
+
|
|
371
|
+
>>> # Boolean indicator
|
|
372
|
+
>>> bool_indicator = StatisticsIndicator(name="optimal", value=True)
|
|
373
|
+
>>> bool_indicator.name
|
|
374
|
+
'optimal'
|
|
375
|
+
>>> bool_indicator.value
|
|
376
|
+
True
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
name: str
|
|
380
|
+
"""Name of the indicator."""
|
|
381
|
+
value: Any
|
|
382
|
+
"""Value of the indicator."""
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class RunInfoStatistics(BaseModel):
|
|
386
|
+
"""
|
|
387
|
+
Statistics information for a run.
|
|
388
|
+
|
|
389
|
+
You can import the `RunInfoStatistics` class directly from `nextmv`:
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from nextmv import RunInfoStatistics
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
status : str
|
|
398
|
+
Status of the statistics in the run.
|
|
399
|
+
error : str, optional
|
|
400
|
+
Error message if the statistics could not be retrieved. Defaults to None.
|
|
401
|
+
indicators : list[StatisticsIndicator], optional
|
|
402
|
+
List of statistics indicators. Defaults to None.
|
|
403
|
+
|
|
404
|
+
Examples
|
|
405
|
+
--------
|
|
406
|
+
>>> from nextmv import RunInfoStatistics, StatisticsIndicator
|
|
407
|
+
>>> indicators = [
|
|
408
|
+
... StatisticsIndicator(name="total_cost", value=1250.75),
|
|
409
|
+
... StatisticsIndicator(name="optimal", value=True)
|
|
410
|
+
... ]
|
|
411
|
+
>>> stats = RunInfoStatistics(status="success", indicators=indicators)
|
|
412
|
+
>>> stats.status
|
|
413
|
+
'success'
|
|
414
|
+
>>> len(stats.indicators)
|
|
415
|
+
2
|
|
416
|
+
|
|
417
|
+
>>> # Statistics with error
|
|
418
|
+
>>> error_stats = RunInfoStatistics(
|
|
419
|
+
... status="error",
|
|
420
|
+
... error="Failed to calculate statistics"
|
|
421
|
+
... )
|
|
422
|
+
>>> error_stats.status
|
|
423
|
+
'error'
|
|
424
|
+
>>> error_stats.error
|
|
425
|
+
'Failed to calculate statistics'
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
status: str
|
|
429
|
+
"""Status of the statistics in the run."""
|
|
430
|
+
|
|
431
|
+
error: str | None = None
|
|
432
|
+
"""Error message if the statistics could not be retrieved."""
|
|
433
|
+
indicators: list[StatisticsIndicator] | None = None
|
|
434
|
+
"""List of statistics indicators."""
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class OptionsSummaryItem(BaseModel):
|
|
438
|
+
"""
|
|
439
|
+
Summary item for options used in a run.
|
|
440
|
+
|
|
441
|
+
You can import the `OptionsSummaryItem` class directly from `nextmv`:
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from nextmv import OptionsSummaryItem
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
name : str
|
|
450
|
+
Name of the option.
|
|
451
|
+
value : Any
|
|
452
|
+
Value of the option.
|
|
453
|
+
source : str
|
|
454
|
+
Source of the option.
|
|
455
|
+
|
|
456
|
+
Examples
|
|
457
|
+
--------
|
|
458
|
+
>>> from nextmv import OptionsSummaryItem
|
|
459
|
+
>>> option = OptionsSummaryItem(
|
|
460
|
+
... name="time_limit",
|
|
461
|
+
... value=30,
|
|
462
|
+
... source="config"
|
|
463
|
+
... )
|
|
464
|
+
>>> option.name
|
|
465
|
+
'time_limit'
|
|
466
|
+
>>> option.value
|
|
467
|
+
30
|
|
468
|
+
>>> option.source
|
|
469
|
+
'config'
|
|
470
|
+
|
|
471
|
+
>>> # Option from environment variable
|
|
472
|
+
>>> env_option = OptionsSummaryItem(
|
|
473
|
+
... name="solver_type",
|
|
474
|
+
... value="gurobi",
|
|
475
|
+
... source="environment"
|
|
476
|
+
... )
|
|
477
|
+
>>> env_option.source
|
|
478
|
+
'environment'
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
name: str
|
|
482
|
+
"""Name of the option."""
|
|
483
|
+
value: Any
|
|
484
|
+
"""Value of the option."""
|
|
485
|
+
source: str
|
|
486
|
+
"""Source of the option."""
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class Run(BaseModel):
|
|
490
|
+
"""
|
|
491
|
+
Information about a run in the Nextmv platform.
|
|
492
|
+
|
|
493
|
+
You can import the `Run` class directly from `nextmv`:
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from nextmv import Run
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Parameters
|
|
500
|
+
----------
|
|
501
|
+
id : str
|
|
502
|
+
ID of the run.
|
|
503
|
+
user_email : str
|
|
504
|
+
Email of the user who initiated the run.
|
|
505
|
+
name : str
|
|
506
|
+
Name of the run.
|
|
507
|
+
description : str
|
|
508
|
+
Description of the run.
|
|
509
|
+
created_at : datetime
|
|
510
|
+
Timestamp when the run was created.
|
|
511
|
+
application_id : str
|
|
512
|
+
ID of the application associated with the run.
|
|
513
|
+
application_instance_id : str
|
|
514
|
+
ID of the application instance associated with the run.
|
|
515
|
+
application_version_id : str
|
|
516
|
+
ID of the application version associated with the run.
|
|
517
|
+
run_type : RunTypeConfiguration
|
|
518
|
+
Configuration for the type of the run.
|
|
519
|
+
execution_class : str
|
|
520
|
+
Class name for the execution of a job.
|
|
521
|
+
runtime : str
|
|
522
|
+
Runtime environment for the run.
|
|
523
|
+
status : Status
|
|
524
|
+
Deprecated, use status_v2 instead.
|
|
525
|
+
status_v2 : StatusV2
|
|
526
|
+
Status of the run.
|
|
527
|
+
queuing_priority : int, optional
|
|
528
|
+
Priority of the run in the queue. Defaults to None.
|
|
529
|
+
queuing_disabled : bool, optional
|
|
530
|
+
Whether the run is disabled from queuing. Defaults to None.
|
|
531
|
+
experiment_id : str, optional
|
|
532
|
+
ID of the experiment associated with the run. Defaults to None.
|
|
533
|
+
statistics : RunInfoStatistics, optional
|
|
534
|
+
Statistics of the run. Defaults to None.
|
|
535
|
+
input_id : str, optional
|
|
536
|
+
ID of the input associated with the run. Defaults to None.
|
|
537
|
+
option_set : str, optional
|
|
538
|
+
ID of the option set associated with the run. Defaults to None.
|
|
539
|
+
options : dict[str, str], optional
|
|
540
|
+
Options associated with the run. Defaults to None.
|
|
541
|
+
request_options : dict[str, str], optional
|
|
542
|
+
Request options associated with the run. Defaults to None.
|
|
543
|
+
options_summary : list[OptionsSummaryItem], optional
|
|
544
|
+
Summary of options used in the run. Defaults to None.
|
|
545
|
+
scenario_id : str, optional
|
|
546
|
+
ID of the scenario associated with the run. Defaults to None.
|
|
547
|
+
repetition : int, optional
|
|
548
|
+
Repetition number of the run. Defaults to None.
|
|
549
|
+
input_set_id : str, optional
|
|
550
|
+
ID of the input set associated with the run. Defaults to None.
|
|
551
|
+
|
|
552
|
+
Examples
|
|
553
|
+
--------
|
|
554
|
+
>>> from nextmv import Run, RunTypeConfiguration, RunType, StatusV2
|
|
555
|
+
>>> from datetime import datetime
|
|
556
|
+
>>> run = Run(
|
|
557
|
+
... id="run-12345",
|
|
558
|
+
... user_email="user@example.com",
|
|
559
|
+
... name="Test Run",
|
|
560
|
+
... description="A test optimization run",
|
|
561
|
+
... created_at=datetime.now(),
|
|
562
|
+
... application_id="app-123",
|
|
563
|
+
... application_instance_id="instance-456",
|
|
564
|
+
... application_version_id="version-789",
|
|
565
|
+
... run_type=RunTypeConfiguration(run_type=RunType.STANDARD),
|
|
566
|
+
... execution_class="small",
|
|
567
|
+
... runtime="python",
|
|
568
|
+
... status_v2=StatusV2.SUCCEEDED
|
|
569
|
+
... )
|
|
570
|
+
>>> run.id
|
|
571
|
+
'run-12345'
|
|
572
|
+
>>> run.name
|
|
573
|
+
'Test Run'
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
id: str
|
|
577
|
+
"""ID of the run."""
|
|
578
|
+
user_email: str
|
|
579
|
+
"""Email of the user who initiated the run."""
|
|
580
|
+
name: str
|
|
581
|
+
"""Name of the run."""
|
|
582
|
+
description: str
|
|
583
|
+
"""Description of the run."""
|
|
584
|
+
created_at: datetime
|
|
585
|
+
"""Timestamp when the run was created."""
|
|
586
|
+
application_id: str
|
|
587
|
+
"""ID of the application associated with the run."""
|
|
588
|
+
application_instance_id: str
|
|
589
|
+
"""ID of the application instance associated with the run."""
|
|
590
|
+
application_version_id: str
|
|
591
|
+
"""ID of the application version associated with the run."""
|
|
592
|
+
run_type: RunTypeConfiguration
|
|
593
|
+
"""Configuration for the type of the run."""
|
|
594
|
+
execution_class: str
|
|
595
|
+
"""Class name for the execution of a job."""
|
|
596
|
+
runtime: str
|
|
597
|
+
"""Runtime environment for the run."""
|
|
598
|
+
status_v2: StatusV2
|
|
599
|
+
"""Status of the run."""
|
|
600
|
+
|
|
601
|
+
status: Status | None = None
|
|
602
|
+
"""Deprecated, use status_v2 instead."""
|
|
603
|
+
queuing_priority: int | None = None
|
|
604
|
+
"""Priority of the run in the queue."""
|
|
605
|
+
queuing_disabled: bool | None = None
|
|
606
|
+
"""Whether the run is disabled from queuing."""
|
|
607
|
+
experiment_id: str | None = None
|
|
608
|
+
"""ID of the experiment associated with the run."""
|
|
609
|
+
statistics: RunInfoStatistics | None = None
|
|
610
|
+
"""Statistics of the run."""
|
|
611
|
+
input_id: str | None = None
|
|
612
|
+
"""ID of the input associated with the run."""
|
|
613
|
+
option_set: str | None = None
|
|
614
|
+
"""ID of the option set associated with the run."""
|
|
615
|
+
options: dict[str, str] | None = None
|
|
616
|
+
"""Options associated with the run."""
|
|
617
|
+
request_options: dict[str, str] | None = None
|
|
618
|
+
"""Request options associated with the run."""
|
|
619
|
+
options_summary: list[OptionsSummaryItem] | None = None
|
|
620
|
+
"""Summary of options used in the run."""
|
|
621
|
+
scenario_id: str | None = None
|
|
622
|
+
"""ID of the scenario associated with the run."""
|
|
623
|
+
repetition: int | None = None
|
|
624
|
+
"""Repetition number of the run."""
|
|
625
|
+
input_set_id: str | None = None
|
|
626
|
+
"""ID of the input set associated with the run."""
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
class Metadata(BaseModel):
|
|
630
|
+
"""
|
|
631
|
+
Metadata of a run, whether it was successful or not.
|
|
632
|
+
|
|
633
|
+
You can import the `Metadata` class directly from `nextmv`:
|
|
634
|
+
|
|
635
|
+
```python
|
|
636
|
+
from nextmv import Metadata
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Parameters
|
|
640
|
+
----------
|
|
641
|
+
application_id : str
|
|
642
|
+
ID of the application where the run was submitted to.
|
|
643
|
+
application_instance_id : str
|
|
644
|
+
ID of the instance where the run was submitted to.
|
|
645
|
+
application_version_id : str
|
|
646
|
+
ID of the version of the application where the run was submitted to.
|
|
647
|
+
created_at : datetime
|
|
648
|
+
Date and time when the run was created.
|
|
649
|
+
duration : float
|
|
650
|
+
Duration of the run in milliseconds.
|
|
651
|
+
error : str
|
|
652
|
+
Error message if the run failed.
|
|
653
|
+
input_size : float
|
|
654
|
+
Size of the input in bytes.
|
|
655
|
+
output_size : float
|
|
656
|
+
Size of the output in bytes.
|
|
657
|
+
format : Format
|
|
658
|
+
Format of the input and output of the run.
|
|
659
|
+
status : Status
|
|
660
|
+
Deprecated: use status_v2.
|
|
661
|
+
status_v2 : StatusV2
|
|
662
|
+
Status of the run.
|
|
663
|
+
"""
|
|
664
|
+
|
|
665
|
+
application_id: str
|
|
666
|
+
"""ID of the application where the run was submitted to."""
|
|
667
|
+
application_instance_id: str
|
|
668
|
+
"""ID of the instance where the run was submitted to."""
|
|
669
|
+
application_version_id: str
|
|
670
|
+
"""ID of the version of the application where the run was submitted to."""
|
|
671
|
+
created_at: datetime
|
|
672
|
+
"""Date and time when the run was created."""
|
|
673
|
+
duration: float
|
|
674
|
+
"""Duration of the run in milliseconds."""
|
|
675
|
+
error: str
|
|
676
|
+
"""Error message if the run failed."""
|
|
677
|
+
input_size: float
|
|
678
|
+
"""Size of the input in bytes."""
|
|
679
|
+
output_size: float
|
|
680
|
+
"""Size of the output in bytes."""
|
|
681
|
+
format: Format
|
|
682
|
+
"""Format of the input and output of the run."""
|
|
683
|
+
status_v2: StatusV2
|
|
684
|
+
"""Status of the run."""
|
|
685
|
+
status: Status | None = None
|
|
686
|
+
"""Deprecated: use status_v2."""
|
|
687
|
+
statistics: dict[str, Any] | None = None
|
|
688
|
+
"""User defined statistics of the run."""
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class SyncedRun(BaseModel):
|
|
692
|
+
"""
|
|
693
|
+
Information about a run that has been synced to a remote application.
|
|
694
|
+
|
|
695
|
+
You can import the `SyncedRun` class directly from `nextmv`:
|
|
696
|
+
|
|
697
|
+
```python
|
|
698
|
+
from nextmv import SyncedRun
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Parameters
|
|
702
|
+
----------
|
|
703
|
+
run_id : str
|
|
704
|
+
ID of the synced remote run. When the `Application.sync` method is
|
|
705
|
+
used, this field marks the association between the local run (`id`) and
|
|
706
|
+
the remote run (`synced_run.id`).
|
|
707
|
+
synced_at : datetime
|
|
708
|
+
Timestamp when the run was synced with the remote run.
|
|
709
|
+
app_id : str
|
|
710
|
+
The ID of the remote application that the local run was synced to.
|
|
711
|
+
instance_id : Optional[str], optional
|
|
712
|
+
The instance of the remote application that the local run was synced
|
|
713
|
+
to. This field is optional and may be None. If it is not specified, it
|
|
714
|
+
indicates that the run was synced against the default instance of the
|
|
715
|
+
app. Defaults to None.
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
run_id: str
|
|
719
|
+
"""
|
|
720
|
+
ID of the synced remote run. When the `Application.sync` method is used,
|
|
721
|
+
this field marks the association between the local run (`id`) and the
|
|
722
|
+
remote run (`synced_run.id`)
|
|
723
|
+
"""
|
|
724
|
+
synced_at: datetime
|
|
725
|
+
"""
|
|
726
|
+
Timestamp when the run was synced with the remote run.
|
|
727
|
+
"""
|
|
728
|
+
app_id: str
|
|
729
|
+
"""
|
|
730
|
+
The ID of the remote application that the local run was synced to.
|
|
731
|
+
"""
|
|
732
|
+
|
|
733
|
+
instance_id: str | None = None
|
|
734
|
+
"""
|
|
735
|
+
The instance of the remote application that the local run was synced to.
|
|
736
|
+
This field is optional and may be None. If it is not specified, it
|
|
737
|
+
indicates that the run was synced against the default instance of the app.
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class RunInformation(BaseModel):
|
|
742
|
+
"""
|
|
743
|
+
Information of a run.
|
|
744
|
+
|
|
745
|
+
You can import the `RunInformation` class directly from `nextmv`:
|
|
746
|
+
|
|
747
|
+
```python
|
|
748
|
+
from nextmv import RunInformation
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
Parameters
|
|
752
|
+
----------
|
|
753
|
+
description : str
|
|
754
|
+
Description of the run.
|
|
755
|
+
id : str
|
|
756
|
+
ID of the run.
|
|
757
|
+
metadata : Metadata
|
|
758
|
+
Metadata of the run.
|
|
759
|
+
name : str
|
|
760
|
+
Name of the run.
|
|
761
|
+
user_email : str
|
|
762
|
+
Email of the user who submitted the run.
|
|
763
|
+
console_url : str, optional
|
|
764
|
+
URL to the run in the Nextmv console. Defaults to "".
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
description: str
|
|
768
|
+
"""Description of the run."""
|
|
769
|
+
id: str
|
|
770
|
+
"""ID of the run."""
|
|
771
|
+
metadata: Metadata
|
|
772
|
+
"""Metadata of the run."""
|
|
773
|
+
name: str
|
|
774
|
+
"""Name of the run."""
|
|
775
|
+
user_email: str
|
|
776
|
+
"""Email of the user who submitted the run."""
|
|
777
|
+
console_url: str = Field(default="")
|
|
778
|
+
"""
|
|
779
|
+
URL to the run in the Nextmv console.
|
|
780
|
+
"""
|
|
781
|
+
synced_runs: list[SyncedRun] | None = None
|
|
782
|
+
"""
|
|
783
|
+
List of synced runs associated with this run, if applicable. When the
|
|
784
|
+
`Application.sync` method is used, this field contains the associations
|
|
785
|
+
between the local run (`id`) and the remote runs (`synced_run.id`). This
|
|
786
|
+
field is None if the run was not created using `Application.sync` or if the
|
|
787
|
+
run has not been synced yet. It is possible to sync a single local run to
|
|
788
|
+
multiple remote runs. A remote run is identified by its application ID and
|
|
789
|
+
instance (if applicable). A local run cannot be synced to a remote run if
|
|
790
|
+
it is already present, this is, if there exists a record in the list with
|
|
791
|
+
the same application ID and instance. If there is not a repeated remote
|
|
792
|
+
run, a new record is added to the list.
|
|
793
|
+
"""
|
|
794
|
+
|
|
795
|
+
def to_run(self) -> Run:
|
|
796
|
+
"""
|
|
797
|
+
Transform this `RunInformation` instance into a `Run` instance.
|
|
798
|
+
|
|
799
|
+
This method maps all available attributes from the `RunInformation`
|
|
800
|
+
and its metadata to create a `Run` instance. Attributes that are not
|
|
801
|
+
available in RunInformation are set to None or appropriate defaults.
|
|
802
|
+
|
|
803
|
+
Returns
|
|
804
|
+
-------
|
|
805
|
+
Run
|
|
806
|
+
A Run instance with attributes populated from this RunInformation.
|
|
807
|
+
|
|
808
|
+
Examples
|
|
809
|
+
--------
|
|
810
|
+
>>> from nextmv import RunInformation, Metadata, Format, FormatInput, FormatOutput
|
|
811
|
+
>>> from nextmv import StatusV2, RunTypeConfiguration, RunType
|
|
812
|
+
>>> from datetime import datetime
|
|
813
|
+
>>> metadata = Metadata(
|
|
814
|
+
... application_id="app-123",
|
|
815
|
+
... application_instance_id="instance-456",
|
|
816
|
+
... application_version_id="version-789",
|
|
817
|
+
... created_at=datetime.now(),
|
|
818
|
+
... duration=5000.0,
|
|
819
|
+
... error="",
|
|
820
|
+
... input_size=1024.0,
|
|
821
|
+
... output_size=2048.0,
|
|
822
|
+
... format=Format(
|
|
823
|
+
... format_input=FormatInput(),
|
|
824
|
+
... format_output=FormatOutput()
|
|
825
|
+
... ),
|
|
826
|
+
... status_v2=StatusV2.SUCCEEDED
|
|
827
|
+
... )
|
|
828
|
+
>>> run_info = RunInformation(
|
|
829
|
+
... id="run-123",
|
|
830
|
+
... description="Test run",
|
|
831
|
+
... name="Test",
|
|
832
|
+
... user_email="user@example.com",
|
|
833
|
+
... metadata=metadata
|
|
834
|
+
... )
|
|
835
|
+
>>> run = run_info.to_run()
|
|
836
|
+
>>> run.id
|
|
837
|
+
'run-123'
|
|
838
|
+
>>> run.application_id
|
|
839
|
+
'app-123'
|
|
840
|
+
"""
|
|
841
|
+
return Run(
|
|
842
|
+
id=self.id,
|
|
843
|
+
user_email=self.user_email,
|
|
844
|
+
name=self.name,
|
|
845
|
+
description=self.description,
|
|
846
|
+
created_at=self.metadata.created_at,
|
|
847
|
+
application_id=self.metadata.application_id,
|
|
848
|
+
application_instance_id=self.metadata.application_instance_id,
|
|
849
|
+
application_version_id=self.metadata.application_version_id,
|
|
850
|
+
run_type=RunTypeConfiguration(), # Default empty configuration
|
|
851
|
+
execution_class="", # Not available in RunInformation
|
|
852
|
+
runtime="", # Not available in RunInformation
|
|
853
|
+
status=self.metadata.status,
|
|
854
|
+
status_v2=self.metadata.status_v2,
|
|
855
|
+
# Optional fields that are not available in RunInformation
|
|
856
|
+
queuing_priority=None,
|
|
857
|
+
queuing_disabled=None,
|
|
858
|
+
experiment_id=None,
|
|
859
|
+
statistics=None,
|
|
860
|
+
input_id=None,
|
|
861
|
+
option_set=None,
|
|
862
|
+
options=None,
|
|
863
|
+
request_options=None,
|
|
864
|
+
options_summary=None,
|
|
865
|
+
scenario_id=None,
|
|
866
|
+
repetition=None,
|
|
867
|
+
input_set_id=None,
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
def add_synced_run(self, synced_run: SyncedRun) -> bool:
|
|
871
|
+
"""
|
|
872
|
+
Add a synced run to the RunInformation.
|
|
873
|
+
|
|
874
|
+
This method adds a `SyncedRun` instance to the list of synced runs
|
|
875
|
+
associated with this `RunInformation`. If the list is None, it
|
|
876
|
+
initializes it first. If the run has already been synced, then it is
|
|
877
|
+
not added to the list. A run is already synced if there exists a record
|
|
878
|
+
in the list with the same application ID. This method returns True if
|
|
879
|
+
the synced run was added, and False otherwise.
|
|
880
|
+
|
|
881
|
+
Parameters
|
|
882
|
+
----------
|
|
883
|
+
synced_run : SyncedRun
|
|
884
|
+
The SyncedRun instance to add.
|
|
885
|
+
|
|
886
|
+
Returns
|
|
887
|
+
-------
|
|
888
|
+
bool
|
|
889
|
+
True if the synced run was added, False if it was already present.
|
|
890
|
+
"""
|
|
891
|
+
|
|
892
|
+
if self.synced_runs is None:
|
|
893
|
+
self.synced_runs = [synced_run]
|
|
894
|
+
|
|
895
|
+
return True
|
|
896
|
+
|
|
897
|
+
if synced_run.instance_id is None:
|
|
898
|
+
for existing_run in self.synced_runs:
|
|
899
|
+
if existing_run.app_id == synced_run.app_id:
|
|
900
|
+
return False
|
|
901
|
+
else:
|
|
902
|
+
for existing_run in self.synced_runs:
|
|
903
|
+
if existing_run.app_id == synced_run.app_id and existing_run.instance_id == synced_run.instance_id:
|
|
904
|
+
return False
|
|
905
|
+
|
|
906
|
+
self.synced_runs.append(synced_run)
|
|
907
|
+
|
|
908
|
+
return True
|
|
909
|
+
|
|
910
|
+
def is_synced(self, app_id: str, instance_id: str | None = None) -> tuple[SyncedRun, bool]:
|
|
911
|
+
"""
|
|
912
|
+
Check if the run has been synced to a specific application and instance.
|
|
913
|
+
|
|
914
|
+
This method checks if there exists a `SyncedRun` in the list of synced
|
|
915
|
+
runs that matches the given application ID and optional instance ID.
|
|
916
|
+
|
|
917
|
+
Parameters
|
|
918
|
+
----------
|
|
919
|
+
app_id : str
|
|
920
|
+
The application ID to check.
|
|
921
|
+
instance_id : Optional[str], optional
|
|
922
|
+
The instance ID to check. If None, only the application ID is
|
|
923
|
+
considered. Defaults to None.
|
|
924
|
+
|
|
925
|
+
Returns
|
|
926
|
+
-------
|
|
927
|
+
tuple[SyncedRun, bool]
|
|
928
|
+
A tuple containing the SyncedRun instance if found, and a boolean
|
|
929
|
+
indicating whether the run has been synced to the specified
|
|
930
|
+
application and instance.
|
|
931
|
+
"""
|
|
932
|
+
|
|
933
|
+
if self.synced_runs is None:
|
|
934
|
+
return None, False
|
|
935
|
+
|
|
936
|
+
if instance_id is None:
|
|
937
|
+
for existing_run in self.synced_runs:
|
|
938
|
+
if existing_run.app_id == app_id:
|
|
939
|
+
return existing_run, True
|
|
940
|
+
else:
|
|
941
|
+
for existing_run in self.synced_runs:
|
|
942
|
+
if existing_run.app_id == app_id and existing_run.instance_id == instance_id:
|
|
943
|
+
return existing_run, True
|
|
944
|
+
|
|
945
|
+
return None, False
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
class ErrorLog(BaseModel):
|
|
949
|
+
"""
|
|
950
|
+
Error log of a run, when it was not successful.
|
|
951
|
+
|
|
952
|
+
You can import the `ErrorLog` class directly from `nextmv`:
|
|
953
|
+
|
|
954
|
+
```python
|
|
955
|
+
from nextmv import ErrorLog
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Parameters
|
|
959
|
+
----------
|
|
960
|
+
error : str, optional
|
|
961
|
+
Error message. Defaults to None.
|
|
962
|
+
stdout : str, optional
|
|
963
|
+
Standard output. Defaults to None.
|
|
964
|
+
stderr : str, optional
|
|
965
|
+
Standard error. Defaults to None.
|
|
966
|
+
"""
|
|
967
|
+
|
|
968
|
+
error: str | None = None
|
|
969
|
+
"""Error message."""
|
|
970
|
+
stdout: str | None = None
|
|
971
|
+
"""Standard output."""
|
|
972
|
+
stderr: str | None = None
|
|
973
|
+
"""Standard error."""
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
class RunResult(RunInformation):
|
|
977
|
+
"""
|
|
978
|
+
Result of a run, whether it was successful or not.
|
|
979
|
+
|
|
980
|
+
You can import the `RunResult` class directly from `nextmv`:
|
|
981
|
+
|
|
982
|
+
```python
|
|
983
|
+
from nextmv import RunResult
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
Parameters
|
|
987
|
+
----------
|
|
988
|
+
error_log : ErrorLog, optional
|
|
989
|
+
Error log of the run. Only available if the run failed. Defaults to
|
|
990
|
+
None.
|
|
991
|
+
output : dict[str, Any], optional
|
|
992
|
+
Output of the run. Only available if the run succeeded. Defaults to
|
|
993
|
+
None.
|
|
994
|
+
"""
|
|
995
|
+
|
|
996
|
+
error_log: ErrorLog | None = None
|
|
997
|
+
"""Error log of the run. Only available if the run failed."""
|
|
998
|
+
output: dict[str, Any] | None = None
|
|
999
|
+
"""Output of the run. Only available if the run succeeded."""
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
class RunLog(BaseModel):
|
|
1003
|
+
"""
|
|
1004
|
+
Log of a run.
|
|
1005
|
+
|
|
1006
|
+
You can import the `RunLog` class directly from `nextmv`:
|
|
1007
|
+
|
|
1008
|
+
```python
|
|
1009
|
+
from nextmv import RunLog
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
Parameters
|
|
1013
|
+
----------
|
|
1014
|
+
log : str
|
|
1015
|
+
Log of the run.
|
|
1016
|
+
|
|
1017
|
+
Examples
|
|
1018
|
+
--------
|
|
1019
|
+
>>> from nextmv import RunLog
|
|
1020
|
+
>>> run_log = RunLog(log="Optimization completed successfully")
|
|
1021
|
+
>>> run_log.log
|
|
1022
|
+
'Optimization completed successfully'
|
|
1023
|
+
|
|
1024
|
+
>>> # Multi-line log
|
|
1025
|
+
>>> multi_line_log = RunLog(log="Starting optimization\\nProcessing data\\nCompleted")
|
|
1026
|
+
>>> multi_line_log.log
|
|
1027
|
+
'Starting optimization\\nProcessing data\\nCompleted'
|
|
1028
|
+
"""
|
|
1029
|
+
|
|
1030
|
+
log: str
|
|
1031
|
+
"""Log of the run."""
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
class RunQueuing(BaseModel):
|
|
1035
|
+
"""
|
|
1036
|
+
RunQueuing configuration for a run.
|
|
1037
|
+
|
|
1038
|
+
You can import the `RunQueuing` class directly from `nextmv`:
|
|
1039
|
+
|
|
1040
|
+
```python
|
|
1041
|
+
from nextmv import RunQueuing
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
priority : int, optional
|
|
1047
|
+
Priority of the run in the queue. 1 is the highest priority, 9 is the
|
|
1048
|
+
lowest priority. Defaults to None.
|
|
1049
|
+
disabled : bool, optional
|
|
1050
|
+
Whether the run should be queued, or not. If True, the run will not be
|
|
1051
|
+
queued. If False, the run will be queued. Defaults to None.
|
|
1052
|
+
|
|
1053
|
+
Examples
|
|
1054
|
+
--------
|
|
1055
|
+
>>> from nextmv import RunQueuing
|
|
1056
|
+
>>> queuing = RunQueuing(priority=1, disabled=False)
|
|
1057
|
+
>>> queuing.priority
|
|
1058
|
+
1
|
|
1059
|
+
>>> queuing.disabled
|
|
1060
|
+
False
|
|
1061
|
+
|
|
1062
|
+
>>> # High priority run
|
|
1063
|
+
>>> high_priority = RunQueuing(priority=1)
|
|
1064
|
+
>>> high_priority.priority
|
|
1065
|
+
1
|
|
1066
|
+
|
|
1067
|
+
>>> # Disabled queuing
|
|
1068
|
+
>>> no_queue = RunQueuing(disabled=True)
|
|
1069
|
+
>>> no_queue.disabled
|
|
1070
|
+
True
|
|
1071
|
+
"""
|
|
1072
|
+
|
|
1073
|
+
priority: int | None = None
|
|
1074
|
+
"""
|
|
1075
|
+
Priority of the run in the queue. 1 is the highest priority, 9 is the
|
|
1076
|
+
lowest priority.
|
|
1077
|
+
"""
|
|
1078
|
+
disabled: bool | None = None
|
|
1079
|
+
"""
|
|
1080
|
+
Whether the run should be queued, or not. If True, the run will not be
|
|
1081
|
+
queued. If False, the run will be queued.
|
|
1082
|
+
"""
|
|
1083
|
+
|
|
1084
|
+
def __post_init_post_parse__(self):
|
|
1085
|
+
"""
|
|
1086
|
+
Validations done after parsing the model.
|
|
1087
|
+
|
|
1088
|
+
Raises
|
|
1089
|
+
------
|
|
1090
|
+
ValueError
|
|
1091
|
+
If priority is not between 1 and 9, or if disabled is not a
|
|
1092
|
+
boolean value.
|
|
1093
|
+
"""
|
|
1094
|
+
|
|
1095
|
+
if self.priority is not None and (self.priority < 1 or self.priority > 9):
|
|
1096
|
+
raise ValueError("Priority must be between 1 and 9.")
|
|
1097
|
+
|
|
1098
|
+
if self.disabled is not None and self.disabled not in {True, False}:
|
|
1099
|
+
raise ValueError("Disabled must be a boolean value.")
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
class RunConfiguration(BaseModel):
|
|
1103
|
+
"""
|
|
1104
|
+
Configuration for an app run.
|
|
1105
|
+
|
|
1106
|
+
You can import the `RunConfiguration` class directly from `nextmv`:
|
|
1107
|
+
|
|
1108
|
+
```python
|
|
1109
|
+
from nextmv import RunConfiguration
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
Parameters
|
|
1113
|
+
----------
|
|
1114
|
+
execution_class : str, optional
|
|
1115
|
+
Execution class for the instance. Defaults to None.
|
|
1116
|
+
format : Format, optional
|
|
1117
|
+
Format for the run configuration. Defaults to None.
|
|
1118
|
+
run_type : RunTypeConfiguration, optional
|
|
1119
|
+
Run type configuration for the run. Defaults to None.
|
|
1120
|
+
secrets_collection_id : str, optional
|
|
1121
|
+
ID of the secrets collection to use for the run. Defaults to None.
|
|
1122
|
+
queuing : RunQueuing, optional
|
|
1123
|
+
Queuing configuration for the run. Defaults to None.
|
|
1124
|
+
|
|
1125
|
+
Examples
|
|
1126
|
+
--------
|
|
1127
|
+
>>> from nextmv import RunConfiguration, RunQueuing
|
|
1128
|
+
>>> config = RunConfiguration(
|
|
1129
|
+
... execution_class="large",
|
|
1130
|
+
... queuing=RunQueuing(priority=1)
|
|
1131
|
+
... )
|
|
1132
|
+
>>> config.execution_class
|
|
1133
|
+
'large'
|
|
1134
|
+
>>> config.queuing.priority
|
|
1135
|
+
1
|
|
1136
|
+
|
|
1137
|
+
>>> # Basic configuration
|
|
1138
|
+
>>> basic_config = RunConfiguration()
|
|
1139
|
+
>>> basic_config.format is None
|
|
1140
|
+
True
|
|
1141
|
+
"""
|
|
1142
|
+
|
|
1143
|
+
execution_class: str | None = None
|
|
1144
|
+
"""Execution class for the instance."""
|
|
1145
|
+
format: Format | None = None
|
|
1146
|
+
"""Format for the run configuration."""
|
|
1147
|
+
run_type: RunTypeConfiguration | None = None
|
|
1148
|
+
"""Run type configuration for the run."""
|
|
1149
|
+
secrets_collection_id: str | None = None
|
|
1150
|
+
"""ID of the secrets collection to use for the run."""
|
|
1151
|
+
queuing: RunQueuing | None = None
|
|
1152
|
+
"""Queuing configuration for the run."""
|
|
1153
|
+
|
|
1154
|
+
def resolve(
|
|
1155
|
+
self,
|
|
1156
|
+
input: Input | dict[str, Any] | BaseModel | str,
|
|
1157
|
+
dir_path: str | None = None,
|
|
1158
|
+
) -> None:
|
|
1159
|
+
"""
|
|
1160
|
+
Resolves the run configuration by modifying or setting the `format`,
|
|
1161
|
+
based on the type of input that is provided.
|
|
1162
|
+
|
|
1163
|
+
Parameters
|
|
1164
|
+
----------
|
|
1165
|
+
input : Input or dict[str, Any] or BaseModel or str
|
|
1166
|
+
The input to use for resolving the run configuration.
|
|
1167
|
+
dir_path : str, optional
|
|
1168
|
+
The directory path where inputs can be loaded from.
|
|
1169
|
+
|
|
1170
|
+
Examples
|
|
1171
|
+
--------
|
|
1172
|
+
>>> from nextmv import RunConfiguration
|
|
1173
|
+
>>> config = RunConfiguration()
|
|
1174
|
+
>>> config.resolve({"key": "value"})
|
|
1175
|
+
>>> config.format.format_input.input_type
|
|
1176
|
+
<InputFormat.JSON: 'json'>
|
|
1177
|
+
|
|
1178
|
+
>>> config = RunConfiguration()
|
|
1179
|
+
>>> config.resolve("text input")
|
|
1180
|
+
>>> config.format.format_input.input_type
|
|
1181
|
+
<InputFormat.TEXT: 'text'>
|
|
1182
|
+
|
|
1183
|
+
>>> config = RunConfiguration()
|
|
1184
|
+
>>> config.resolve({}, dir_path="/path/to/files")
|
|
1185
|
+
>>> config.format.format_input.input_type
|
|
1186
|
+
<InputFormat.MULTI_FILE: 'multi_file'>
|
|
1187
|
+
"""
|
|
1188
|
+
|
|
1189
|
+
# If the value is set by the user, do not change it.
|
|
1190
|
+
if self.format is not None:
|
|
1191
|
+
return
|
|
1192
|
+
|
|
1193
|
+
self.format = Format(
|
|
1194
|
+
format_input=FormatInput(input_type=InputFormat.JSON),
|
|
1195
|
+
format_output=FormatOutput(output_type=OutputFormat.JSON),
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
if isinstance(input, dict):
|
|
1199
|
+
self.format.format_input.input_type = InputFormat.JSON
|
|
1200
|
+
elif isinstance(input, str):
|
|
1201
|
+
self.format.format_input.input_type = InputFormat.TEXT
|
|
1202
|
+
elif dir_path is not None and dir_path != "":
|
|
1203
|
+
# Kinda hard to detect if we should be working with CSV_ARCHIVE or
|
|
1204
|
+
# MULTI_FILE, so we default to MULTI_FILE.
|
|
1205
|
+
self.format.format_input.input_type = InputFormat.MULTI_FILE
|
|
1206
|
+
elif isinstance(input, Input):
|
|
1207
|
+
self.format.format_input.input_type = input.input_format
|
|
1208
|
+
|
|
1209
|
+
# As input and output are symmetric, we set the output according to the input
|
|
1210
|
+
# format.
|
|
1211
|
+
if self.format.format_input.input_type == InputFormat.JSON:
|
|
1212
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
1213
|
+
elif self.format.format_input.input_type == InputFormat.TEXT: # Text still maps to json
|
|
1214
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
1215
|
+
elif self.format.format_input.input_type == InputFormat.CSV_ARCHIVE:
|
|
1216
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.CSV_ARCHIVE)
|
|
1217
|
+
elif self.format.format_input.input_type == InputFormat.MULTI_FILE:
|
|
1218
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.MULTI_FILE)
|
|
1219
|
+
else:
|
|
1220
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
class ExternalRunResult(BaseModel):
|
|
1224
|
+
"""
|
|
1225
|
+
Result of a run used to configure a new application run as an
|
|
1226
|
+
external one.
|
|
1227
|
+
|
|
1228
|
+
You can import the `ExternalRunResult` class directly from `nextmv`:
|
|
1229
|
+
|
|
1230
|
+
```python
|
|
1231
|
+
from nextmv import ExternalRunResult
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
Parameters
|
|
1235
|
+
----------
|
|
1236
|
+
output_upload_id : str, optional
|
|
1237
|
+
ID of the output upload. Defaults to None.
|
|
1238
|
+
error_upload_id : str, optional
|
|
1239
|
+
ID of the error upload. Defaults to None.
|
|
1240
|
+
status : str, optional
|
|
1241
|
+
Status of the run. Must be "succeeded" or "failed". Defaults to None.
|
|
1242
|
+
error_message : str, optional
|
|
1243
|
+
Error message of the run. Defaults to None.
|
|
1244
|
+
execution_duration : int, optional
|
|
1245
|
+
Duration of the run, in milliseconds. Defaults to None.
|
|
1246
|
+
|
|
1247
|
+
Examples
|
|
1248
|
+
--------
|
|
1249
|
+
>>> from nextmv import ExternalRunResult
|
|
1250
|
+
>>> # Successful external run
|
|
1251
|
+
>>> result = ExternalRunResult(
|
|
1252
|
+
... output_upload_id="upload-12345",
|
|
1253
|
+
... status="succeeded",
|
|
1254
|
+
... execution_duration=5000
|
|
1255
|
+
... )
|
|
1256
|
+
>>> result.status
|
|
1257
|
+
'succeeded'
|
|
1258
|
+
>>> result.execution_duration
|
|
1259
|
+
5000
|
|
1260
|
+
|
|
1261
|
+
>>> # Failed external run
|
|
1262
|
+
>>> failed_result = ExternalRunResult(
|
|
1263
|
+
... error_upload_id="error-67890",
|
|
1264
|
+
... status="failed",
|
|
1265
|
+
... error_message="Optimization failed due to invalid constraints",
|
|
1266
|
+
... execution_duration=2000
|
|
1267
|
+
... )
|
|
1268
|
+
>>> failed_result.status
|
|
1269
|
+
'failed'
|
|
1270
|
+
>>> failed_result.error_message
|
|
1271
|
+
'Optimization failed due to invalid constraints'
|
|
1272
|
+
"""
|
|
1273
|
+
|
|
1274
|
+
output_upload_id: str | None = None
|
|
1275
|
+
"""ID of the output upload."""
|
|
1276
|
+
error_upload_id: str | None = None
|
|
1277
|
+
"""ID of the error upload."""
|
|
1278
|
+
status: str | None = None
|
|
1279
|
+
"""Status of the run."""
|
|
1280
|
+
error_message: str | None = None
|
|
1281
|
+
"""Error message of the run."""
|
|
1282
|
+
execution_duration: int | None = None
|
|
1283
|
+
"""Duration of the run, in milliseconds."""
|
|
1284
|
+
statistics_upload_id: str | None = None
|
|
1285
|
+
"""
|
|
1286
|
+
ID of the statistics upload. Use this field when working with `CSV_ARCHIVE`
|
|
1287
|
+
or `MULTI_FILE` output formats.
|
|
1288
|
+
"""
|
|
1289
|
+
assets_upload_id: str | None = None
|
|
1290
|
+
"""
|
|
1291
|
+
ID of the assets upload. Use this field when working with `CSV_ARCHIVE`
|
|
1292
|
+
or `MULTI_FILE` output formats.
|
|
1293
|
+
"""
|
|
1294
|
+
|
|
1295
|
+
def __post_init_post_parse__(self):
|
|
1296
|
+
"""
|
|
1297
|
+
Validations done after parsing the model.
|
|
1298
|
+
|
|
1299
|
+
Raises
|
|
1300
|
+
------
|
|
1301
|
+
ValueError
|
|
1302
|
+
If the status value is not "succeeded" or "failed".
|
|
1303
|
+
"""
|
|
1304
|
+
|
|
1305
|
+
valid_statuses = {"succeeded", "failed"}
|
|
1306
|
+
if self.status is not None and self.status not in valid_statuses:
|
|
1307
|
+
raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
class TrackedRunStatus(str, Enum):
|
|
1311
|
+
"""
|
|
1312
|
+
The status of a tracked run.
|
|
1313
|
+
|
|
1314
|
+
You can import the `TrackedRunStatus` class directly from `nextmv`:
|
|
1315
|
+
|
|
1316
|
+
```python
|
|
1317
|
+
from nextmv import TrackedRunStatus
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
Parameters
|
|
1321
|
+
----------
|
|
1322
|
+
SUCCEEDED : str
|
|
1323
|
+
The run succeeded.
|
|
1324
|
+
FAILED : str
|
|
1325
|
+
The run failed.
|
|
1326
|
+
|
|
1327
|
+
Examples
|
|
1328
|
+
--------
|
|
1329
|
+
>>> from nextmv import TrackedRunStatus
|
|
1330
|
+
>>> status = TrackedRunStatus.SUCCEEDED
|
|
1331
|
+
>>> status
|
|
1332
|
+
<TrackedRunStatus.SUCCEEDED: 'succeeded'>
|
|
1333
|
+
>>> status.value
|
|
1334
|
+
'succeeded'
|
|
1335
|
+
|
|
1336
|
+
>>> # Creating from string
|
|
1337
|
+
>>> failed_status = TrackedRunStatus("failed")
|
|
1338
|
+
>>> failed_status
|
|
1339
|
+
<TrackedRunStatus.FAILED: 'failed'>
|
|
1340
|
+
|
|
1341
|
+
>>> # All available statuses
|
|
1342
|
+
>>> list(TrackedRunStatus)
|
|
1343
|
+
[<TrackedRunStatus.SUCCEEDED: 'succeeded'>, <TrackedRunStatus.FAILED: 'failed'>]
|
|
1344
|
+
"""
|
|
1345
|
+
|
|
1346
|
+
SUCCEEDED = "succeeded"
|
|
1347
|
+
"""The run succeeded."""
|
|
1348
|
+
FAILED = "failed"
|
|
1349
|
+
"""The run failed."""
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
@dataclass
|
|
1353
|
+
class TrackedRun:
|
|
1354
|
+
"""
|
|
1355
|
+
An external run that is tracked in the Nextmv platform.
|
|
1356
|
+
|
|
1357
|
+
You can import the `TrackedRun` class directly from `nextmv`:
|
|
1358
|
+
|
|
1359
|
+
```python
|
|
1360
|
+
from nextmv import TrackedRun
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
Parameters
|
|
1364
|
+
----------
|
|
1365
|
+
status : TrackedRunStatus
|
|
1366
|
+
The status of the run being tracked. This field is required.
|
|
1367
|
+
input : Input or dict[str, Any] or str, optional
|
|
1368
|
+
The input of the run being tracked. Please note that if the input
|
|
1369
|
+
format is JSON, then the input data must be JSON serializable. If both
|
|
1370
|
+
`input` and `input_dir_path` are specified, the `input` is ignored, and
|
|
1371
|
+
the files in the directory are used instead. Defaults to None.
|
|
1372
|
+
output : Output or dict[str, Any] or str, optional
|
|
1373
|
+
The output of the run being tracked. Please note that if the output
|
|
1374
|
+
format is JSON, then the output data must be JSON serializable. If both
|
|
1375
|
+
`output` and `output_dir_path` are specified, the `output` is ignored, and
|
|
1376
|
+
the files in the directory are used instead. Defaults to None.
|
|
1377
|
+
duration : int, optional
|
|
1378
|
+
The duration of the run being tracked, in milliseconds. This field is
|
|
1379
|
+
optional. Defaults to None.
|
|
1380
|
+
error : str, optional
|
|
1381
|
+
An error message if the run failed. You should only specify this if the
|
|
1382
|
+
run failed (the `status` is `TrackedRunStatus.FAILED`), otherwise an
|
|
1383
|
+
exception will be raised. This field is optional. Defaults to None.
|
|
1384
|
+
logs : list[str], optional
|
|
1385
|
+
The logs of the run being tracked. Each element of the list is a line in
|
|
1386
|
+
the log. This field is optional. Defaults to None.
|
|
1387
|
+
name : str, optional
|
|
1388
|
+
Optional name for the run being tracked. Defaults to None.
|
|
1389
|
+
description : str, optional
|
|
1390
|
+
Optional description for the run being tracked. Defaults to None.
|
|
1391
|
+
input_dir_path : str, optional
|
|
1392
|
+
Path to a directory containing input files. If specified, the calling
|
|
1393
|
+
function will package the files in the directory into a tar file and upload
|
|
1394
|
+
it as a large input. This is useful for non-JSON input formats, such as
|
|
1395
|
+
when working with `CSV_ARCHIVE` or `MULTI_FILE`. If both `input` and
|
|
1396
|
+
`input_dir_path` are specified, the `input` is ignored, and the files in
|
|
1397
|
+
the directory are used instead. Defaults to None.
|
|
1398
|
+
output_dir_path : str, optional
|
|
1399
|
+
Path to a directory containing output files. If specified, the calling
|
|
1400
|
+
function will package the files in the directory into a tar file and upload
|
|
1401
|
+
it as a large output. This is useful for non-JSON output formats, such as
|
|
1402
|
+
when working with `CSV_ARCHIVE` or `MULTI_FILE`. If both `output` and
|
|
1403
|
+
`output_dir_path` are specified, the `output` is ignored, and the files
|
|
1404
|
+
are saved in the directory instead. Defaults to None.
|
|
1405
|
+
statistics : Statistics or dict[str, Any], optional
|
|
1406
|
+
Statistics of the run being tracked. Only use this field if you want to
|
|
1407
|
+
track statistics for `CSV_ARCHIVE` or `MULTI_FILE` output formats. If you
|
|
1408
|
+
are working with `JSON` or `TEXT` output formats, this field will be
|
|
1409
|
+
ignored, as the statistics are extracted directly from the `output`.
|
|
1410
|
+
This field is optional. Defaults to None.
|
|
1411
|
+
assets : list[Asset or dict[str, Any]], optional
|
|
1412
|
+
Assets associated with the run being tracked. Only use this field if you
|
|
1413
|
+
want to track assets for `CSV_ARCHIVE` or `MULTI_FILE` output formats.
|
|
1414
|
+
If you are working with `JSON` or `TEXT` output formats, this field will
|
|
1415
|
+
be ignored, as the assets are extracted directly from the `output`.
|
|
1416
|
+
This field is optional. Defaults to None.
|
|
1417
|
+
|
|
1418
|
+
Examples
|
|
1419
|
+
--------
|
|
1420
|
+
>>> from nextmv import TrackedRun, TrackedRunStatus
|
|
1421
|
+
>>> # Successful run
|
|
1422
|
+
>>> run = TrackedRun(
|
|
1423
|
+
... status=TrackedRunStatus.SUCCEEDED,
|
|
1424
|
+
... input={"vehicles": 5, "locations": 10},
|
|
1425
|
+
... output={"routes": [{"stops": [1, 2, 3]}]},
|
|
1426
|
+
... duration=5000,
|
|
1427
|
+
... name="test-run",
|
|
1428
|
+
... description="A test optimization run"
|
|
1429
|
+
... )
|
|
1430
|
+
>>> run.status
|
|
1431
|
+
<TrackedRunStatus.SUCCEEDED: 'succeeded'>
|
|
1432
|
+
>>> run.duration
|
|
1433
|
+
5000
|
|
1434
|
+
|
|
1435
|
+
>>> # Failed run with error
|
|
1436
|
+
>>> failed_run = TrackedRun(
|
|
1437
|
+
... status=TrackedRunStatus.FAILED,
|
|
1438
|
+
... input={"vehicles": 0},
|
|
1439
|
+
... error="No vehicles available for routing",
|
|
1440
|
+
... duration=1000,
|
|
1441
|
+
... logs=["Starting optimization", "Error: No vehicles found"]
|
|
1442
|
+
... )
|
|
1443
|
+
>>> failed_run.status
|
|
1444
|
+
<TrackedRunStatus.FAILED: 'failed'>
|
|
1445
|
+
>>> failed_run.error
|
|
1446
|
+
'No vehicles available for routing'
|
|
1447
|
+
|
|
1448
|
+
>>> # Run with directory-based input/output
|
|
1449
|
+
>>> dir_run = TrackedRun(
|
|
1450
|
+
... status=TrackedRunStatus.SUCCEEDED,
|
|
1451
|
+
... input_dir_path="/path/to/input/files",
|
|
1452
|
+
... output_dir_path="/path/to/output/files",
|
|
1453
|
+
... duration=10000
|
|
1454
|
+
... )
|
|
1455
|
+
>>> dir_run.input_dir_path
|
|
1456
|
+
'/path/to/input/files'
|
|
1457
|
+
|
|
1458
|
+
Raises
|
|
1459
|
+
------
|
|
1460
|
+
ValueError
|
|
1461
|
+
If the status value is invalid, if an error message is provided for a
|
|
1462
|
+
successful run, or if input/output formats are not JSON or
|
|
1463
|
+
input/output dicts are not JSON serializable.
|
|
1464
|
+
"""
|
|
1465
|
+
|
|
1466
|
+
status: TrackedRunStatus
|
|
1467
|
+
"""The status of the run being tracked"""
|
|
1468
|
+
|
|
1469
|
+
input: Input | dict[str, Any] | str | None = None
|
|
1470
|
+
"""
|
|
1471
|
+
The input of the run being tracked. Please note that if the input
|
|
1472
|
+
format is JSON, then the input data must be JSON serializable. If both
|
|
1473
|
+
`input` and `input_dir_path` are specified, the `input` is ignored, and
|
|
1474
|
+
the files in the directory are used instead.
|
|
1475
|
+
"""
|
|
1476
|
+
output: Output | dict[str, Any] | str | None = None
|
|
1477
|
+
"""
|
|
1478
|
+
The output of the run being tracked. Please note that if the output
|
|
1479
|
+
format is JSON, then the output data must be JSON serializable. If both
|
|
1480
|
+
`output` and `output_dir_path` are specified, the `output` is ignored, and
|
|
1481
|
+
the files in the directory are used instead.
|
|
1482
|
+
"""
|
|
1483
|
+
duration: int | None = None
|
|
1484
|
+
"""The duration of the run being tracked, in milliseconds."""
|
|
1485
|
+
error: str | None = None
|
|
1486
|
+
"""An error message if the run failed. You should only specify this if the
|
|
1487
|
+
run failed, otherwise an exception will be raised."""
|
|
1488
|
+
logs: list[str] | None = None
|
|
1489
|
+
"""The logs of the run being tracked. Each element of the list is a line in
|
|
1490
|
+
the log."""
|
|
1491
|
+
name: str | None = None
|
|
1492
|
+
"""
|
|
1493
|
+
Optional name for the run being tracked.
|
|
1494
|
+
"""
|
|
1495
|
+
description: str | None = None
|
|
1496
|
+
"""
|
|
1497
|
+
Optional description for the run being tracked.
|
|
1498
|
+
"""
|
|
1499
|
+
input_dir_path: str | None = None
|
|
1500
|
+
"""
|
|
1501
|
+
Path to a directory containing input files. If specified, the calling
|
|
1502
|
+
function will package the files in the directory into a tar file and upload
|
|
1503
|
+
it as a large input. This is useful for non-JSON input formats, such as
|
|
1504
|
+
when working with `CSV_ARCHIVE` or `MULTI_FILE`. If both `input` and
|
|
1505
|
+
`input_dir_path` are specified, the `input` is ignored, and the files in
|
|
1506
|
+
the directory are used instead.
|
|
1507
|
+
"""
|
|
1508
|
+
output_dir_path: str | None = None
|
|
1509
|
+
"""
|
|
1510
|
+
Path to a directory containing output files. If specified, the calling
|
|
1511
|
+
function will package the files in the directory into a tar file and upload
|
|
1512
|
+
it as a large output. This is useful for non-JSON output formats, such as
|
|
1513
|
+
when working with `CSV_ARCHIVE` or `MULTI_FILE`. If both `output` and
|
|
1514
|
+
`output_dir_path` are specified, the `output` is ignored, and the files
|
|
1515
|
+
are saved in the directory instead.
|
|
1516
|
+
"""
|
|
1517
|
+
statistics: Statistics | dict[str, Any] | None = None
|
|
1518
|
+
"""
|
|
1519
|
+
Statistics of the run being tracked. Only use this field if you want to
|
|
1520
|
+
track statistics for `CSV_ARCHIVE` or `MULTI_FILE` output formats. If you
|
|
1521
|
+
are working with `JSON` or `TEXT` output formats, this field will be
|
|
1522
|
+
ignored, as the statistics are extracted directly from the `output`.
|
|
1523
|
+
"""
|
|
1524
|
+
assets: list[Asset | dict[str, Any]] | None = None
|
|
1525
|
+
"""
|
|
1526
|
+
Assets associated with the run being tracked. Only use this field if you
|
|
1527
|
+
want to track assets for `CSV_ARCHIVE` or `MULTI_FILE` output formats.
|
|
1528
|
+
If you are working with `JSON` or `TEXT` output formats, this field will
|
|
1529
|
+
be ignored, as the assets are extracted directly from the `output`.
|
|
1530
|
+
"""
|
|
1531
|
+
|
|
1532
|
+
def __post_init__(self): # noqa: C901
|
|
1533
|
+
"""
|
|
1534
|
+
Validations done after parsing the model.
|
|
1535
|
+
|
|
1536
|
+
Raises
|
|
1537
|
+
------
|
|
1538
|
+
ValueError
|
|
1539
|
+
If the status value is invalid, if an error message is provided for
|
|
1540
|
+
a successful run, or if input/output formats are not JSON or
|
|
1541
|
+
input/output dicts are not JSON serializable.
|
|
1542
|
+
"""
|
|
1543
|
+
|
|
1544
|
+
valid_statuses = {TrackedRunStatus.SUCCEEDED, TrackedRunStatus.FAILED}
|
|
1545
|
+
if self.status not in valid_statuses:
|
|
1546
|
+
raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
|
|
1547
|
+
|
|
1548
|
+
if self.error is not None and self.error != "" and self.status != TrackedRunStatus.FAILED:
|
|
1549
|
+
raise ValueError("Error message must be empty if the run succeeded.")
|
|
1550
|
+
|
|
1551
|
+
if isinstance(self.input, Input):
|
|
1552
|
+
try:
|
|
1553
|
+
_ = serialize_json(self.input.data)
|
|
1554
|
+
except (TypeError, OverflowError) as e:
|
|
1555
|
+
raise ValueError("Input.data is not JSON serializable") from e
|
|
1556
|
+
elif isinstance(self.input, dict):
|
|
1557
|
+
try:
|
|
1558
|
+
_ = serialize_json(self.input)
|
|
1559
|
+
except (TypeError, OverflowError) as e:
|
|
1560
|
+
raise ValueError("Input is dict[str, Any] but it is not JSON serializable") from e
|
|
1561
|
+
|
|
1562
|
+
if isinstance(self.output, Output):
|
|
1563
|
+
try:
|
|
1564
|
+
_ = serialize_json(self.output.solution)
|
|
1565
|
+
except (TypeError, OverflowError) as e:
|
|
1566
|
+
raise ValueError("`Output.solution` is not JSON serializable") from e
|
|
1567
|
+
elif isinstance(self.output, dict):
|
|
1568
|
+
try:
|
|
1569
|
+
_ = serialize_json(self.output)
|
|
1570
|
+
except (TypeError, OverflowError) as e:
|
|
1571
|
+
raise ValueError("Output is dict[str, Any] but it is not JSON serializable") from e
|
|
1572
|
+
|
|
1573
|
+
def logs_text(self) -> str:
|
|
1574
|
+
"""
|
|
1575
|
+
Returns the logs as a single string.
|
|
1576
|
+
|
|
1577
|
+
Each log entry is separated by a newline character.
|
|
1578
|
+
|
|
1579
|
+
Returns
|
|
1580
|
+
-------
|
|
1581
|
+
str
|
|
1582
|
+
The logs as a single string. If no logs are present, an empty
|
|
1583
|
+
string is returned.
|
|
1584
|
+
|
|
1585
|
+
Examples
|
|
1586
|
+
--------
|
|
1587
|
+
>>> from nextmv import TrackedRun, TrackedRunStatus
|
|
1588
|
+
>>> run = TrackedRun(
|
|
1589
|
+
... status=TrackedRunStatus.SUCCEEDED,
|
|
1590
|
+
... logs=["Starting optimization", "Processing data", "Optimization complete"]
|
|
1591
|
+
... )
|
|
1592
|
+
>>> run.logs_text()
|
|
1593
|
+
'Starting optimization\\nProcessing data\\nOptimization complete'
|
|
1594
|
+
|
|
1595
|
+
>>> # Single string log
|
|
1596
|
+
>>> run_with_string_log = TrackedRun(
|
|
1597
|
+
... status=TrackedRunStatus.SUCCEEDED,
|
|
1598
|
+
... logs="Single log entry"
|
|
1599
|
+
... )
|
|
1600
|
+
>>> run_with_string_log.logs_text()
|
|
1601
|
+
'Single log entry'
|
|
1602
|
+
|
|
1603
|
+
>>> # No logs
|
|
1604
|
+
>>> run_no_logs = TrackedRun(status=TrackedRunStatus.SUCCEEDED)
|
|
1605
|
+
>>> run_no_logs.logs_text()
|
|
1606
|
+
''
|
|
1607
|
+
|
|
1608
|
+
Raises
|
|
1609
|
+
------
|
|
1610
|
+
TypeError
|
|
1611
|
+
If `self.logs` is not a string or a list of strings.
|
|
1612
|
+
"""
|
|
1613
|
+
|
|
1614
|
+
if self.logs is None:
|
|
1615
|
+
return ""
|
|
1616
|
+
|
|
1617
|
+
if isinstance(self.logs, str):
|
|
1618
|
+
return self.logs
|
|
1619
|
+
|
|
1620
|
+
if isinstance(self.logs, list):
|
|
1621
|
+
return "\\n".join(self.logs)
|
|
1622
|
+
|
|
1623
|
+
raise TypeError("Logs must be a string or a list of strings.")
|