nextmv 0.26.3__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/__entrypoint__.py +3 -5
- 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 +1231 -396
- 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 +625 -87
- nextmv/cloud/package.py +2 -2
- nextmv/cloud/run.py +376 -45
- 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 +300 -82
- nextmv/logger.py +71 -7
- nextmv/model.py +225 -58
- nextmv/options.py +301 -69
- nextmv/output.py +667 -236
- {nextmv-0.26.3.dist-info → nextmv-0.28.0.dist-info}/METADATA +1 -1
- nextmv-0.28.0.dist-info/RECORD +30 -0
- nextmv-0.26.3.dist-info/RECORD +0 -30
- {nextmv-0.26.3.dist-info → nextmv-0.28.0.dist-info}/WHEEL +0 -0
- {nextmv-0.26.3.dist-info → nextmv-0.28.0.dist-info}/licenses/LICENSE +0 -0
nextmv/output.py
CHANGED
|
@@ -1,4 +1,44 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Module for handling output destinations and data.
|
|
3
|
+
|
|
4
|
+
This module provides classes and functions for handling the output of decision
|
|
5
|
+
problems, including formatting, serialization, and writing to various
|
|
6
|
+
destinations.
|
|
7
|
+
|
|
8
|
+
Classes
|
|
9
|
+
-------
|
|
10
|
+
RunStatistics
|
|
11
|
+
Statistics about a general run.
|
|
12
|
+
ResultStatistics
|
|
13
|
+
Statistics about a specific result.
|
|
14
|
+
DataPoint
|
|
15
|
+
A data point representing a 2D coordinate.
|
|
16
|
+
Series
|
|
17
|
+
A series of data points for visualization or analysis.
|
|
18
|
+
SeriesData
|
|
19
|
+
Data container for multiple series of data points.
|
|
20
|
+
Statistics
|
|
21
|
+
Complete statistics container for a solution, including run metrics and result data.
|
|
22
|
+
OutputFormat
|
|
23
|
+
Enumeration of supported output formats.
|
|
24
|
+
VisualSchema
|
|
25
|
+
Enumeration of supported visualization schemas.
|
|
26
|
+
Visual
|
|
27
|
+
Visual schema definition for an asset.
|
|
28
|
+
Asset
|
|
29
|
+
Represents downloadable information that is part of the `Output`.
|
|
30
|
+
Output
|
|
31
|
+
A class for representing the output of a decision problem.
|
|
32
|
+
OutputWriter
|
|
33
|
+
Base class for writing outputs to different destinations.
|
|
34
|
+
LocalOutputWriter
|
|
35
|
+
Class for writing outputs to local files or stdout.
|
|
36
|
+
|
|
37
|
+
Functions
|
|
38
|
+
---------
|
|
39
|
+
write
|
|
40
|
+
Write the output to the specified destination.
|
|
41
|
+
"""
|
|
2
42
|
|
|
3
43
|
import copy
|
|
4
44
|
import csv
|
|
@@ -22,6 +62,12 @@ class RunStatistics(BaseModel):
|
|
|
22
62
|
"""
|
|
23
63
|
Statistics about a general run.
|
|
24
64
|
|
|
65
|
+
You can import the `RunStatistics` class directly from `nextmv`:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from nextmv import RunStatistics
|
|
69
|
+
```
|
|
70
|
+
|
|
25
71
|
Parameters
|
|
26
72
|
----------
|
|
27
73
|
duration : float, optional
|
|
@@ -31,6 +77,16 @@ class RunStatistics(BaseModel):
|
|
|
31
77
|
custom : Union[Any, dict[str, Any]], optional
|
|
32
78
|
Custom statistics created by the user. Can normally expect a `dict[str,
|
|
33
79
|
Any]`.
|
|
80
|
+
|
|
81
|
+
Examples
|
|
82
|
+
--------
|
|
83
|
+
>>> from nextmv.output import RunStatistics
|
|
84
|
+
>>> stats = RunStatistics(duration=10.5, iterations=100)
|
|
85
|
+
>>> stats.duration
|
|
86
|
+
10.5
|
|
87
|
+
>>> stats.custom = {"convergence": 0.001}
|
|
88
|
+
>>> stats.to_dict()
|
|
89
|
+
{'duration': 10.5, 'iterations': 100, 'custom': {'convergence': 0.001}}
|
|
34
90
|
"""
|
|
35
91
|
|
|
36
92
|
duration: Optional[float] = None
|
|
@@ -51,6 +107,12 @@ class ResultStatistics(BaseModel):
|
|
|
51
107
|
"""
|
|
52
108
|
Statistics about a specific result.
|
|
53
109
|
|
|
110
|
+
You can import the `ResultStatistics` class directly from `nextmv`:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from nextmv import ResultStatistics
|
|
114
|
+
```
|
|
115
|
+
|
|
54
116
|
Parameters
|
|
55
117
|
----------
|
|
56
118
|
duration : float, optional
|
|
@@ -60,6 +122,16 @@ class ResultStatistics(BaseModel):
|
|
|
60
122
|
custom : Union[Any, dict[str, Any]], optional
|
|
61
123
|
Custom statistics created by the user. Can normally expect a `dict[str,
|
|
62
124
|
Any]`.
|
|
125
|
+
|
|
126
|
+
Examples
|
|
127
|
+
--------
|
|
128
|
+
>>> from nextmv.output import ResultStatistics
|
|
129
|
+
>>> result_stats = ResultStatistics(duration=5.2, value=42.0)
|
|
130
|
+
>>> result_stats.value
|
|
131
|
+
42.0
|
|
132
|
+
>>> result_stats.custom = {"gap": 0.05}
|
|
133
|
+
>>> result_stats.to_dict()
|
|
134
|
+
{'duration': 5.2, 'value': 42.0, 'custom': {'gap': 0.05}}
|
|
63
135
|
"""
|
|
64
136
|
|
|
65
137
|
duration: Optional[float] = None
|
|
@@ -78,7 +150,13 @@ class ResultStatistics(BaseModel):
|
|
|
78
150
|
|
|
79
151
|
class DataPoint(BaseModel):
|
|
80
152
|
"""
|
|
81
|
-
A data point.
|
|
153
|
+
A data point representing a 2D coordinate.
|
|
154
|
+
|
|
155
|
+
You can import the `DataPoint` class directly from `nextmv`:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from nextmv import DataPoint
|
|
159
|
+
```
|
|
82
160
|
|
|
83
161
|
Parameters
|
|
84
162
|
----------
|
|
@@ -86,6 +164,15 @@ class DataPoint(BaseModel):
|
|
|
86
164
|
X coordinate of the data point.
|
|
87
165
|
y : float
|
|
88
166
|
Y coordinate of the data point.
|
|
167
|
+
|
|
168
|
+
Examples
|
|
169
|
+
--------
|
|
170
|
+
>>> from nextmv.output import DataPoint
|
|
171
|
+
>>> point = DataPoint(x=3.5, y=4.2)
|
|
172
|
+
>>> point.x
|
|
173
|
+
3.5
|
|
174
|
+
>>> point.to_dict()
|
|
175
|
+
{'x': 3.5, 'y': 4.2}
|
|
89
176
|
"""
|
|
90
177
|
|
|
91
178
|
x: float
|
|
@@ -96,14 +183,30 @@ class DataPoint(BaseModel):
|
|
|
96
183
|
|
|
97
184
|
class Series(BaseModel):
|
|
98
185
|
"""
|
|
99
|
-
A series of data points.
|
|
186
|
+
A series of data points for visualization or analysis.
|
|
187
|
+
|
|
188
|
+
You can import the `Series` class directly from `nextmv`:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from nextmv import Series
|
|
192
|
+
```
|
|
100
193
|
|
|
101
194
|
Parameters
|
|
102
195
|
----------
|
|
103
196
|
name : str, optional
|
|
104
197
|
Name of the series.
|
|
105
198
|
data_points : list[DataPoint], optional
|
|
106
|
-
Data of the series.
|
|
199
|
+
Data points of the series.
|
|
200
|
+
|
|
201
|
+
Examples
|
|
202
|
+
--------
|
|
203
|
+
>>> from nextmv.output import Series, DataPoint
|
|
204
|
+
>>> points = [DataPoint(x=1.0, y=2.0), DataPoint(x=2.0, y=3.0)]
|
|
205
|
+
>>> series = Series(name="Example Series", data_points=points)
|
|
206
|
+
>>> series.name
|
|
207
|
+
'Example Series'
|
|
208
|
+
>>> len(series.data_points)
|
|
209
|
+
2
|
|
107
210
|
"""
|
|
108
211
|
|
|
109
212
|
name: Optional[str] = None
|
|
@@ -114,7 +217,13 @@ class Series(BaseModel):
|
|
|
114
217
|
|
|
115
218
|
class SeriesData(BaseModel):
|
|
116
219
|
"""
|
|
117
|
-
Data of
|
|
220
|
+
Data container for multiple series of data points.
|
|
221
|
+
|
|
222
|
+
You can import the `SeriesData` class directly from `nextmv`:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from nextmv import SeriesData
|
|
226
|
+
```
|
|
118
227
|
|
|
119
228
|
Parameters
|
|
120
229
|
----------
|
|
@@ -122,6 +231,17 @@ class SeriesData(BaseModel):
|
|
|
122
231
|
A series for the value of the solution.
|
|
123
232
|
custom : list[Series], optional
|
|
124
233
|
A list of series for custom statistics.
|
|
234
|
+
|
|
235
|
+
Examples
|
|
236
|
+
--------
|
|
237
|
+
>>> from nextmv.output import SeriesData, Series, DataPoint
|
|
238
|
+
>>> value_series = Series(name="Solution Value", data_points=[DataPoint(x=0, y=10), DataPoint(x=1, y=5)])
|
|
239
|
+
>>> custom_series = [Series(name="Gap", data_points=[DataPoint(x=0, y=0.5), DataPoint(x=1, y=0.1)])]
|
|
240
|
+
>>> series_data = SeriesData(value=value_series, custom=custom_series)
|
|
241
|
+
>>> series_data.value.name
|
|
242
|
+
'Solution Value'
|
|
243
|
+
>>> len(series_data.custom)
|
|
244
|
+
1
|
|
125
245
|
"""
|
|
126
246
|
|
|
127
247
|
value: Optional[Series] = None
|
|
@@ -132,7 +252,14 @@ class SeriesData(BaseModel):
|
|
|
132
252
|
|
|
133
253
|
class Statistics(BaseModel):
|
|
134
254
|
"""
|
|
135
|
-
|
|
255
|
+
Complete statistics container for a solution, including run metrics and
|
|
256
|
+
result data.
|
|
257
|
+
|
|
258
|
+
You can import the `Statistics` class directly from `nextmv`:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from nextmv import Statistics
|
|
262
|
+
```
|
|
136
263
|
|
|
137
264
|
Parameters
|
|
138
265
|
----------
|
|
@@ -144,6 +271,17 @@ class Statistics(BaseModel):
|
|
|
144
271
|
Series data about some metric.
|
|
145
272
|
statistics_schema : str, optional
|
|
146
273
|
Schema (version). This class only supports `v1`.
|
|
274
|
+
|
|
275
|
+
Examples
|
|
276
|
+
--------
|
|
277
|
+
>>> from nextmv.output import Statistics, RunStatistics, ResultStatistics
|
|
278
|
+
>>> run_stats = RunStatistics(duration=10.0, iterations=50)
|
|
279
|
+
>>> result_stats = ResultStatistics(value=100.0)
|
|
280
|
+
>>> stats = Statistics(run=run_stats, result=result_stats, statistics_schema="v1")
|
|
281
|
+
>>> stats.run.duration
|
|
282
|
+
10.0
|
|
283
|
+
>>> stats.result.value
|
|
284
|
+
100.0
|
|
147
285
|
"""
|
|
148
286
|
|
|
149
287
|
run: Optional[RunStatistics] = None
|
|
@@ -161,7 +299,25 @@ class Statistics(BaseModel):
|
|
|
161
299
|
|
|
162
300
|
|
|
163
301
|
class OutputFormat(str, Enum):
|
|
164
|
-
"""
|
|
302
|
+
"""
|
|
303
|
+
Enumeration of supported output formats.
|
|
304
|
+
|
|
305
|
+
You can import the `OutputFormat` class directly from `nextmv`:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from nextmv import OutputFormat
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
This enum defines the different formats that can be used for outputting data.
|
|
312
|
+
Each format has specific requirements and behaviors when writing.
|
|
313
|
+
|
|
314
|
+
Attributes
|
|
315
|
+
----------
|
|
316
|
+
JSON : str
|
|
317
|
+
JSON format, utf-8 encoded.
|
|
318
|
+
CSV_ARCHIVE : str
|
|
319
|
+
CSV archive format: multiple CSV files.
|
|
320
|
+
"""
|
|
165
321
|
|
|
166
322
|
JSON = "json"
|
|
167
323
|
"""JSON format, utf-8 encoded."""
|
|
@@ -170,7 +326,27 @@ class OutputFormat(str, Enum):
|
|
|
170
326
|
|
|
171
327
|
|
|
172
328
|
class VisualSchema(str, Enum):
|
|
173
|
-
"""
|
|
329
|
+
"""
|
|
330
|
+
Enumeration of supported visualization schemas.
|
|
331
|
+
|
|
332
|
+
You can import the `VisualSchema` class directly from `nextmv`:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
from nextmv import VisualSchema
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
This enum defines the different visualization libraries or rendering methods
|
|
339
|
+
that can be used to display custom asset data in the Nextmv Console.
|
|
340
|
+
|
|
341
|
+
Attributes
|
|
342
|
+
----------
|
|
343
|
+
CHARTJS : str
|
|
344
|
+
Tells Nextmv Console to render the custom asset data with the Chart.js library.
|
|
345
|
+
GEOJSON : str
|
|
346
|
+
Tells Nextmv Console to render the custom asset data as GeoJSON on a map.
|
|
347
|
+
PLOTLY : str
|
|
348
|
+
Tells Nextmv Console to render the custom asset data with the Plotly library.
|
|
349
|
+
"""
|
|
174
350
|
|
|
175
351
|
CHARTJS = "chartjs"
|
|
176
352
|
"""Tells Nextmv Console to render the custom asset data with the Chart.js
|
|
@@ -185,8 +361,39 @@ class VisualSchema(str, Enum):
|
|
|
185
361
|
|
|
186
362
|
class Visual(BaseModel):
|
|
187
363
|
"""
|
|
188
|
-
Visual schema
|
|
189
|
-
|
|
364
|
+
Visual schema definition for an asset.
|
|
365
|
+
|
|
366
|
+
You can import the `Visual` class directly from `nextmv`:
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
from nextmv import Visual
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
This class defines how an asset is plotted in the Nextmv Console,
|
|
373
|
+
including the schema type, label, and display type.
|
|
374
|
+
|
|
375
|
+
Parameters
|
|
376
|
+
----------
|
|
377
|
+
visual_schema : VisualSchema
|
|
378
|
+
Schema of the visual asset.
|
|
379
|
+
label : str
|
|
380
|
+
Label for the custom tab of the visual asset in the Nextmv Console.
|
|
381
|
+
visual_type : str, optional
|
|
382
|
+
Defines the type of custom visual. Default is "custom-tab".
|
|
383
|
+
|
|
384
|
+
Raises
|
|
385
|
+
------
|
|
386
|
+
ValueError
|
|
387
|
+
If an unsupported schema or visual_type is provided.
|
|
388
|
+
|
|
389
|
+
Examples
|
|
390
|
+
--------
|
|
391
|
+
>>> from nextmv.output import Visual, VisualSchema
|
|
392
|
+
>>> visual = Visual(visual_schema=VisualSchema.CHARTJS, label="Performance Chart")
|
|
393
|
+
>>> visual.visual_schema
|
|
394
|
+
<VisualSchema.CHARTJS: 'chartjs'>
|
|
395
|
+
>>> visual.label
|
|
396
|
+
'Performance Chart'
|
|
190
397
|
"""
|
|
191
398
|
|
|
192
399
|
visual_schema: VisualSchema = Field(
|
|
@@ -207,6 +414,14 @@ class Visual(BaseModel):
|
|
|
207
414
|
details."""
|
|
208
415
|
|
|
209
416
|
def __post_init__(self):
|
|
417
|
+
"""
|
|
418
|
+
Validate the visual schema and type.
|
|
419
|
+
|
|
420
|
+
Raises
|
|
421
|
+
------
|
|
422
|
+
ValueError
|
|
423
|
+
If the visual_schema is not in VisualSchema or if visual_type is not 'custom-tab'.
|
|
424
|
+
"""
|
|
210
425
|
if self.visual_schema not in VisualSchema:
|
|
211
426
|
raise ValueError(f"unsupported schema: {self.visual_schema}, supported schemas are {VisualSchema}")
|
|
212
427
|
|
|
@@ -216,7 +431,47 @@ class Visual(BaseModel):
|
|
|
216
431
|
|
|
217
432
|
class Asset(BaseModel):
|
|
218
433
|
"""
|
|
219
|
-
|
|
434
|
+
Represents downloadable information that is part of the `Output`.
|
|
435
|
+
|
|
436
|
+
You can import the `Asset` class directly from `nextmv`:
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
from nextmv import Asset
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
An asset contains content that can be serialized to JSON and optionally
|
|
443
|
+
includes visual information for rendering in the Nextmv Console.
|
|
444
|
+
|
|
445
|
+
Parameters
|
|
446
|
+
----------
|
|
447
|
+
name : str
|
|
448
|
+
Name of the asset.
|
|
449
|
+
content : Any
|
|
450
|
+
Content of the asset. The type must be serializable to JSON.
|
|
451
|
+
content_type : str, optional
|
|
452
|
+
Content type of the asset. Only "json" is currently supported. Default is "json".
|
|
453
|
+
description : str, optional
|
|
454
|
+
Description of the asset. Default is None.
|
|
455
|
+
visual : Visual, optional
|
|
456
|
+
Visual schema of the asset. Default is None.
|
|
457
|
+
|
|
458
|
+
Raises
|
|
459
|
+
------
|
|
460
|
+
ValueError
|
|
461
|
+
If the content_type is not "json".
|
|
462
|
+
|
|
463
|
+
Examples
|
|
464
|
+
--------
|
|
465
|
+
>>> from nextmv.output import Asset, Visual, VisualSchema
|
|
466
|
+
>>> visual = Visual(visual_schema=VisualSchema.CHARTJS, label="Solution Progress")
|
|
467
|
+
>>> asset = Asset(
|
|
468
|
+
... name="optimization_progress",
|
|
469
|
+
... content={"iterations": [1, 2, 3], "values": [10, 8, 7]},
|
|
470
|
+
... description="Optimization progress over iterations",
|
|
471
|
+
... visual=visual
|
|
472
|
+
... )
|
|
473
|
+
>>> asset.name
|
|
474
|
+
'optimization_progress'
|
|
220
475
|
"""
|
|
221
476
|
|
|
222
477
|
name: str
|
|
@@ -232,6 +487,14 @@ class Asset(BaseModel):
|
|
|
232
487
|
"""Visual schema of the asset."""
|
|
233
488
|
|
|
234
489
|
def __post_init__(self):
|
|
490
|
+
"""
|
|
491
|
+
Validate the content type.
|
|
492
|
+
|
|
493
|
+
Raises
|
|
494
|
+
------
|
|
495
|
+
ValueError
|
|
496
|
+
If the content_type is not "json".
|
|
497
|
+
"""
|
|
235
498
|
if self.content_type != "json":
|
|
236
499
|
raise ValueError(f"unsupported content_type: {self.content_type}, supported types are `json`")
|
|
237
500
|
|
|
@@ -239,44 +502,86 @@ class Asset(BaseModel):
|
|
|
239
502
|
@dataclass
|
|
240
503
|
class Output:
|
|
241
504
|
"""
|
|
242
|
-
Output of a decision problem.
|
|
243
|
-
some location.
|
|
244
|
-
|
|
245
|
-
The output can be in different formats, such as JSON (default) or
|
|
246
|
-
CSV_ARCHIVE.
|
|
505
|
+
Output of a decision problem.
|
|
247
506
|
|
|
248
|
-
|
|
249
|
-
serialized to the write location.
|
|
507
|
+
You can import the `Output` class directly from `nextmv`:
|
|
250
508
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
509
|
+
```python
|
|
510
|
+
from nextmv import Output
|
|
511
|
+
```
|
|
254
512
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
written. The values are lists of dictionaries, where each dictionary
|
|
259
|
-
represents a row in the CSV file.
|
|
260
|
-
|
|
261
|
-
The statistics are used to keep track of different metrics that were
|
|
262
|
-
obtained after the run was completed. Although it can be a simple
|
|
263
|
-
dictionary, we recommend using the `Statistics` class to ensure that the
|
|
264
|
-
data is correctly formatted.
|
|
513
|
+
This class is used to structure the output of a decision problem that
|
|
514
|
+
can later be written to various destinations. It supports different output
|
|
515
|
+
formats and allows for customization of the serialization process.
|
|
265
516
|
|
|
266
517
|
Parameters
|
|
267
518
|
----------
|
|
268
|
-
options : Options, optional
|
|
269
|
-
Options that the `
|
|
270
|
-
|
|
519
|
+
options : Optional[Union[Options, dict[str, Any]]], optional
|
|
520
|
+
Options that the `Output` was created with. These options can be of type
|
|
521
|
+
`Options` or a simple dictionary. Default is None.
|
|
522
|
+
output_format : Optional[OutputFormat], optional
|
|
271
523
|
Format of the output data. Default is `OutputFormat.JSON`.
|
|
272
|
-
solution : Union[dict[str, Any], dict[str, list[dict[str, Any]]], optional
|
|
273
|
-
The solution to the decision problem.
|
|
274
|
-
|
|
275
|
-
|
|
524
|
+
solution : Optional[Union[dict[str, Any], Any, dict[str, list[dict[str, Any]]]]], optional
|
|
525
|
+
The solution to the decision problem. The type must match the
|
|
526
|
+
`output_format`. Default is None.
|
|
527
|
+
statistics : Optional[Union[Statistics, dict[str, Any]]], optional
|
|
528
|
+
Statistics of the solution. Default is None.
|
|
529
|
+
csv_configurations : Optional[dict[str, Any]], optional
|
|
530
|
+
Configuration for writing CSV files. Default is None.
|
|
531
|
+
json_configurations : Optional[dict[str, Any]], optional
|
|
532
|
+
Configuration for writing JSON files. Default is None.
|
|
533
|
+
assets : Optional[list[Union[Asset, dict[str, Any]]]], optional
|
|
534
|
+
List of assets to be included in the output. Default is None.
|
|
535
|
+
|
|
536
|
+
Raises
|
|
537
|
+
------
|
|
538
|
+
ValueError
|
|
539
|
+
If the solution is not compatible with the specified output_format.
|
|
540
|
+
TypeError
|
|
541
|
+
If options, statistics, or assets have unsupported types.
|
|
542
|
+
|
|
543
|
+
Notes
|
|
544
|
+
-----
|
|
545
|
+
The solution's type must match the `output_format`:
|
|
546
|
+
|
|
547
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
548
|
+
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str, Any]]]`.
|
|
549
|
+
The keys represent the file names where the data should be written. The values
|
|
550
|
+
are lists of dictionaries, where each dictionary represents a row in the CSV file.
|
|
551
|
+
|
|
552
|
+
Examples
|
|
553
|
+
--------
|
|
554
|
+
>>> from nextmv.output import Output, OutputFormat, Statistics, RunStatistics
|
|
555
|
+
>>> run_stats = RunStatistics(duration=30.0, iterations=100)
|
|
556
|
+
>>> stats = Statistics(run=run_stats)
|
|
557
|
+
>>> solution = {"routes": [{"vehicle": 1, "stops": [1, 2, 3]}, {"vehicle": 2, "stops": [4, 5]}]}
|
|
558
|
+
>>> output = Output(
|
|
559
|
+
... output_format=OutputFormat.JSON,
|
|
560
|
+
... solution=solution,
|
|
561
|
+
... statistics=stats,
|
|
562
|
+
... json_configurations={"indent": 4}
|
|
563
|
+
... )
|
|
564
|
+
>>> output_dict = output.to_dict()
|
|
565
|
+
>>> "solution" in output_dict and "statistics" in output_dict
|
|
566
|
+
True
|
|
276
567
|
"""
|
|
277
568
|
|
|
278
|
-
options: Optional[Options] = None
|
|
279
|
-
"""
|
|
569
|
+
options: Optional[Union[Options, dict[str, Any]]] = None
|
|
570
|
+
"""
|
|
571
|
+
Options that the `Output` was created with. These options can be of type
|
|
572
|
+
`Options` or a simple dictionary. If the options are of type `Options`,
|
|
573
|
+
they will be serialized to a dictionary using the `to_dict` method. If
|
|
574
|
+
they are a dictionary, they will be used as is. If the options are not
|
|
575
|
+
provided, an empty dictionary will be used. If the options are of type
|
|
576
|
+
`dict`, then the dictionary should have the following structure:
|
|
577
|
+
|
|
578
|
+
```python
|
|
579
|
+
{
|
|
580
|
+
"duration": "30",
|
|
581
|
+
"threads": 4,
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
"""
|
|
280
585
|
output_format: Optional[OutputFormat] = OutputFormat.JSON
|
|
281
586
|
"""Format of the output data. Default is `OutputFormat.JSON`."""
|
|
282
587
|
solution: Optional[
|
|
@@ -287,22 +592,47 @@ class Output:
|
|
|
287
592
|
] = None
|
|
288
593
|
"""The solution to the decision problem."""
|
|
289
594
|
statistics: Optional[Union[Statistics, dict[str, Any]]] = None
|
|
290
|
-
"""
|
|
595
|
+
"""
|
|
596
|
+
Statistics of the solution. These statistics can be of type `Statistics` or a
|
|
597
|
+
simple dictionary. If the statistics are of type `Statistics`, they will be
|
|
598
|
+
serialized to a dictionary using the `to_dict` method. If they are a
|
|
599
|
+
dictionary, they will be used as is. If the statistics are not provided, an
|
|
600
|
+
empty dictionary will be used.
|
|
601
|
+
"""
|
|
291
602
|
csv_configurations: Optional[dict[str, Any]] = None
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
|
|
603
|
+
"""
|
|
604
|
+
Optional configuration for writing CSV files, to be used when the
|
|
605
|
+
`output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
|
|
606
|
+
passed as kwargs to the `DictWriter` class from the `csv` module.
|
|
607
|
+
"""
|
|
295
608
|
json_configurations: Optional[dict[str, Any]] = None
|
|
296
|
-
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"""
|
|
609
|
+
"""
|
|
610
|
+
Optional configuration for writing JSON files, to be used when the
|
|
611
|
+
`output_format` is `OutputFormat.JSON`. These configurations are passed as
|
|
612
|
+
kwargs to the `json.dumps` function.
|
|
613
|
+
"""
|
|
614
|
+
assets: Optional[list[Union[Asset, dict[str, Any]]]] = None
|
|
615
|
+
"""
|
|
616
|
+
Optional list of assets to be included in the output. These assets can be of
|
|
617
|
+
type `Asset` or a simple dictionary. If the assets are of type `Asset`, they
|
|
618
|
+
will be serialized to a dictionary using the `to_dict` method. If they are a
|
|
619
|
+
dictionary, they will be used as is. If the assets are not provided, an
|
|
620
|
+
empty list will be used.
|
|
621
|
+
"""
|
|
301
622
|
|
|
302
623
|
def __post_init__(self):
|
|
303
|
-
"""
|
|
304
|
-
|
|
624
|
+
"""
|
|
625
|
+
Initialize and validate the Output instance.
|
|
626
|
+
|
|
627
|
+
This method performs two main tasks:
|
|
628
|
+
1. Creates a deep copy of the options to preserve the original values
|
|
629
|
+
2. Validates that the solution matches the specified output_format
|
|
305
630
|
|
|
631
|
+
Raises
|
|
632
|
+
------
|
|
633
|
+
ValueError
|
|
634
|
+
If the solution is not compatible with the specified output_format.
|
|
635
|
+
"""
|
|
306
636
|
# Capture a snapshot of the options that were used to create the class
|
|
307
637
|
# so even if they are changed later, we have a record of the original.
|
|
308
638
|
init_options = self.options
|
|
@@ -327,68 +657,167 @@ class Output:
|
|
|
327
657
|
"output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
328
658
|
)
|
|
329
659
|
|
|
330
|
-
def to_dict(self) -> dict[str,
|
|
660
|
+
def to_dict(self) -> dict[str, Any]: # noqa: C901
|
|
331
661
|
"""
|
|
332
662
|
Convert the `Output` object to a dictionary.
|
|
333
663
|
|
|
334
664
|
Returns
|
|
335
665
|
-------
|
|
336
|
-
dict[str,
|
|
666
|
+
dict[str, Any]
|
|
337
667
|
The dictionary representation of the `Output` object.
|
|
338
668
|
"""
|
|
339
669
|
|
|
670
|
+
# Options need to end up as a dict, so we achieve that based on the
|
|
671
|
+
# type of options that were used to create the class.
|
|
672
|
+
if self.options is None:
|
|
673
|
+
options = {}
|
|
674
|
+
elif isinstance(self.options, Options):
|
|
675
|
+
options = self.options.to_dict()
|
|
676
|
+
elif isinstance(self.options, dict):
|
|
677
|
+
options = self.options
|
|
678
|
+
else:
|
|
679
|
+
raise TypeError(f"unsupported options type: {type(self.options)}, supported types are `Options` or `dict`")
|
|
680
|
+
|
|
681
|
+
# Statistics need to end up as a dict, so we achieve that based on the
|
|
682
|
+
# type of statistics that were used to create the class.
|
|
683
|
+
if self.statistics is None:
|
|
684
|
+
statistics = {}
|
|
685
|
+
elif isinstance(self.statistics, Statistics):
|
|
686
|
+
statistics = self.statistics.to_dict()
|
|
687
|
+
elif isinstance(self.statistics, dict):
|
|
688
|
+
statistics = self.statistics
|
|
689
|
+
else:
|
|
690
|
+
raise TypeError(
|
|
691
|
+
f"unsupported statistics type: {type(self.statistics)}, supported types are `Statistics` or `dict`"
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
# Assets need to end up as a list of dicts, so we achieve that based on
|
|
695
|
+
# the type of each asset in the list.
|
|
696
|
+
assets = []
|
|
697
|
+
if isinstance(self.assets, list):
|
|
698
|
+
for ix, asset in enumerate(self.assets):
|
|
699
|
+
if isinstance(asset, Asset):
|
|
700
|
+
assets.append(asset.to_dict())
|
|
701
|
+
elif isinstance(asset, dict):
|
|
702
|
+
assets.append(asset)
|
|
703
|
+
else:
|
|
704
|
+
raise TypeError(
|
|
705
|
+
f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`"
|
|
706
|
+
)
|
|
707
|
+
elif self.assets is not None:
|
|
708
|
+
raise TypeError(f"unsupported assets type: {type(self.assets)}, supported types are `list`")
|
|
709
|
+
|
|
340
710
|
output_dict = {
|
|
341
|
-
"options":
|
|
711
|
+
"options": options,
|
|
342
712
|
"solution": self.solution if self.solution is not None else {},
|
|
343
|
-
"statistics":
|
|
344
|
-
"assets":
|
|
713
|
+
"statistics": statistics,
|
|
714
|
+
"assets": assets,
|
|
345
715
|
}
|
|
346
716
|
|
|
347
|
-
if
|
|
717
|
+
# Add the auxiliary configurations to the output dictionary if they are
|
|
718
|
+
# defined and not empty.
|
|
719
|
+
if (
|
|
720
|
+
self.output_format == OutputFormat.CSV_ARCHIVE
|
|
721
|
+
and self.csv_configurations is not None
|
|
722
|
+
and self.csv_configurations != {}
|
|
723
|
+
):
|
|
348
724
|
output_dict["csv_configurations"] = self.csv_configurations
|
|
349
|
-
elif
|
|
725
|
+
elif (
|
|
726
|
+
self.output_format == OutputFormat.JSON
|
|
727
|
+
and self.json_configurations is not None
|
|
728
|
+
and self.json_configurations != {}
|
|
729
|
+
):
|
|
350
730
|
output_dict["json_configurations"] = self.json_configurations
|
|
351
731
|
|
|
352
732
|
return output_dict
|
|
353
733
|
|
|
354
734
|
|
|
355
735
|
class OutputWriter:
|
|
356
|
-
"""
|
|
736
|
+
"""
|
|
737
|
+
Base class for writing outputs.
|
|
738
|
+
|
|
739
|
+
You can import the `OutputWriter` class directly from `nextmv`:
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
from nextmv import OutputWriter
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
This is an abstract base class that defines the interface for writing outputs
|
|
746
|
+
to different destinations. Subclasses should implement the `write` method.
|
|
747
|
+
|
|
748
|
+
Examples
|
|
749
|
+
--------
|
|
750
|
+
>>> class CustomOutputWriter(OutputWriter):
|
|
751
|
+
... def write(self, output, path=None, **kwargs):
|
|
752
|
+
... # Custom implementation for writing output
|
|
753
|
+
... print(f"Writing output to {path}")
|
|
754
|
+
"""
|
|
357
755
|
|
|
358
756
|
def write(self, output: Union[Output, dict[str, Any], BaseModel], *args, **kwargs) -> None:
|
|
359
757
|
"""
|
|
360
|
-
Write the output data.
|
|
361
|
-
|
|
758
|
+
Write the output data.
|
|
759
|
+
|
|
760
|
+
This is an abstract method that should be implemented by subclasses.
|
|
761
|
+
|
|
762
|
+
Parameters
|
|
763
|
+
----------
|
|
764
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
765
|
+
The output data to write.
|
|
766
|
+
*args
|
|
767
|
+
Variable length argument list.
|
|
768
|
+
**kwargs
|
|
769
|
+
Arbitrary keyword arguments.
|
|
362
770
|
|
|
771
|
+
Raises
|
|
772
|
+
------
|
|
773
|
+
NotImplementedError
|
|
774
|
+
This method must be implemented by subclasses.
|
|
775
|
+
"""
|
|
363
776
|
raise NotImplementedError
|
|
364
777
|
|
|
365
778
|
|
|
366
779
|
class LocalOutputWriter(OutputWriter):
|
|
367
780
|
"""
|
|
368
|
-
Class for
|
|
369
|
-
|
|
781
|
+
Class for writing outputs to local files or stdout.
|
|
782
|
+
|
|
783
|
+
You can import the `LocalOutputWriter` class directly from `nextmv`:
|
|
784
|
+
|
|
785
|
+
```python
|
|
786
|
+
from nextmv import LocalOutputWriter
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
This class implements the OutputWriter interface to write output data to
|
|
790
|
+
local files or stdout. The destination and format depend on the output
|
|
791
|
+
format and the provided path.
|
|
792
|
+
|
|
793
|
+
Examples
|
|
794
|
+
--------
|
|
795
|
+
>>> from nextmv.output import LocalOutputWriter, Output, Statistics
|
|
796
|
+
>>> writer = LocalOutputWriter()
|
|
797
|
+
>>> output = Output(solution={"result": 42}, statistics=Statistics())
|
|
798
|
+
>>> # Write to stdout
|
|
799
|
+
>>> writer.write(output, path=None)
|
|
800
|
+
>>> # Write to a file
|
|
801
|
+
>>> writer.write(output, path="results.json")
|
|
370
802
|
"""
|
|
371
803
|
|
|
372
804
|
def _write_json(
|
|
373
805
|
output: Union[Output, dict[str, Any], BaseModel],
|
|
374
|
-
|
|
375
|
-
statistics: dict[str, Any],
|
|
376
|
-
assets: list[dict[str, Any]],
|
|
806
|
+
output_dict: dict[str, Any],
|
|
377
807
|
path: Optional[str] = None,
|
|
378
808
|
) -> None:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
elif isinstance(output, BaseModel):
|
|
382
|
-
final_output = output.to_dict()
|
|
383
|
-
else:
|
|
384
|
-
solution = output.solution if output.solution is not None else {}
|
|
385
|
-
final_output = {
|
|
386
|
-
"options": options,
|
|
387
|
-
"solution": solution,
|
|
388
|
-
"statistics": statistics,
|
|
389
|
-
"assets": assets,
|
|
390
|
-
}
|
|
809
|
+
"""
|
|
810
|
+
Write output in JSON format.
|
|
391
811
|
|
|
812
|
+
Parameters
|
|
813
|
+
----------
|
|
814
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
815
|
+
The output object containing configuration.
|
|
816
|
+
output_dict : dict[str, Any]
|
|
817
|
+
Dictionary representation of the output to write.
|
|
818
|
+
path : str, optional
|
|
819
|
+
Path to write the output. If None or empty, writes to stdout.
|
|
820
|
+
"""
|
|
392
821
|
json_configurations = {}
|
|
393
822
|
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
394
823
|
json_configurations = output.json_configurations
|
|
@@ -402,7 +831,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
402
831
|
del json_configurations["default"]
|
|
403
832
|
|
|
404
833
|
serialized = json.dumps(
|
|
405
|
-
|
|
834
|
+
output_dict,
|
|
406
835
|
indent=indent,
|
|
407
836
|
default=custom_serial,
|
|
408
837
|
**json_configurations,
|
|
@@ -416,12 +845,28 @@ class LocalOutputWriter(OutputWriter):
|
|
|
416
845
|
file.write(serialized + "\n")
|
|
417
846
|
|
|
418
847
|
def _write_archive(
|
|
419
|
-
output: Output,
|
|
420
|
-
|
|
421
|
-
statistics: dict[str, Any],
|
|
422
|
-
assets: list[dict[str, Any]],
|
|
848
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
849
|
+
output_dict: dict[str, Any],
|
|
423
850
|
path: Optional[str] = None,
|
|
424
851
|
) -> None:
|
|
852
|
+
"""
|
|
853
|
+
Write output in CSV archive format.
|
|
854
|
+
|
|
855
|
+
Parameters
|
|
856
|
+
----------
|
|
857
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
858
|
+
The output object containing configuration and solution data.
|
|
859
|
+
output_dict : dict[str, Any]
|
|
860
|
+
Dictionary representation of the output to write.
|
|
861
|
+
path : str, optional
|
|
862
|
+
Directory path to write the CSV files. If None or empty,
|
|
863
|
+
writes to a directory named "output" in the current working directory.
|
|
864
|
+
|
|
865
|
+
Raises
|
|
866
|
+
------
|
|
867
|
+
ValueError
|
|
868
|
+
If the path is an existing file instead of a directory.
|
|
869
|
+
"""
|
|
425
870
|
dir_path = "output"
|
|
426
871
|
if path is not None and path != "":
|
|
427
872
|
if os.path.isfile(path):
|
|
@@ -434,9 +879,9 @@ class LocalOutputWriter(OutputWriter):
|
|
|
434
879
|
|
|
435
880
|
serialized = json.dumps(
|
|
436
881
|
{
|
|
437
|
-
"options": options,
|
|
438
|
-
"statistics": statistics,
|
|
439
|
-
"assets": assets,
|
|
882
|
+
"options": output_dict.get("options", {}),
|
|
883
|
+
"statistics": output_dict.get("statistics", {}),
|
|
884
|
+
"assets": output_dict.get("assets", []),
|
|
440
885
|
},
|
|
441
886
|
indent=2,
|
|
442
887
|
)
|
|
@@ -465,6 +910,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
465
910
|
OutputFormat.JSON: _write_json,
|
|
466
911
|
OutputFormat.CSV_ARCHIVE: _write_archive,
|
|
467
912
|
}
|
|
913
|
+
"""Dictionary mapping output formats to writer functions."""
|
|
468
914
|
|
|
469
915
|
def write(
|
|
470
916
|
self,
|
|
@@ -473,40 +919,48 @@ class LocalOutputWriter(OutputWriter):
|
|
|
473
919
|
skip_stdout_reset: bool = False,
|
|
474
920
|
) -> None:
|
|
475
921
|
"""
|
|
476
|
-
Write the
|
|
477
|
-
the `path` parameter, depending on the `Output.output_format`:
|
|
922
|
+
Write the output to the local filesystem or stdout.
|
|
478
923
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
- `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
|
|
482
|
-
files will be written. If empty or `None`, the data will be written
|
|
483
|
-
to a directory named `output` under the current working directory.
|
|
484
|
-
The `Output.options` and `Output.statistics` will be written to
|
|
485
|
-
stdout.
|
|
486
|
-
|
|
487
|
-
This function detects if stdout was redirected and resets it to avoid
|
|
488
|
-
unexpected behavior. If you want to skip this behavior, set the
|
|
489
|
-
`skip_stdout_reset` parameter to `True`.
|
|
490
|
-
|
|
491
|
-
If the `output` is a `dict`, it will be simply written to the specified
|
|
492
|
-
`path`, as a passthrough. On the other hand, if the `output` is of type
|
|
493
|
-
`Output`, a more structured object will be written, which adheres to
|
|
494
|
-
the schema specified by the corresponding `Output` class.
|
|
924
|
+
This method writes the provided output to the specified path or to stdout,
|
|
925
|
+
depending on the output format and the path parameter.
|
|
495
926
|
|
|
496
927
|
Parameters
|
|
497
928
|
----------
|
|
498
|
-
output: Output, dict[str, Any]
|
|
499
|
-
Output data to write.
|
|
500
|
-
path : str
|
|
501
|
-
Path to write the output data to.
|
|
929
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
930
|
+
Output data to write. Can be an Output object, a dictionary, or a BaseModel.
|
|
931
|
+
path : str, optional
|
|
932
|
+
Path to write the output data to. The interpretation depends on the output format:
|
|
933
|
+
- For OutputFormat.JSON: File path for the JSON output. If None or empty, writes to stdout.
|
|
934
|
+
- For OutputFormat.CSV_ARCHIVE: Directory path for CSV files. If None or empty,
|
|
935
|
+
writes to a directory named "output" in the current working directory.
|
|
502
936
|
skip_stdout_reset : bool, optional
|
|
503
|
-
Skip resetting stdout before writing the output data. Default is
|
|
504
|
-
`False`.
|
|
937
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
505
938
|
|
|
506
939
|
Raises
|
|
507
940
|
------
|
|
508
941
|
ValueError
|
|
509
|
-
If the
|
|
942
|
+
If the Output.output_format is not supported.
|
|
943
|
+
TypeError
|
|
944
|
+
If the output is of an unsupported type.
|
|
945
|
+
|
|
946
|
+
Notes
|
|
947
|
+
-----
|
|
948
|
+
This function detects if stdout was redirected and resets it to avoid
|
|
949
|
+
unexpected behavior. If you want to skip this behavior, set the
|
|
950
|
+
skip_stdout_reset parameter to True.
|
|
951
|
+
|
|
952
|
+
If the output is a dict or a BaseModel, it will be written as JSON. If
|
|
953
|
+
the output is an Output object, it will be written according to its
|
|
954
|
+
output_format.
|
|
955
|
+
|
|
956
|
+
Examples
|
|
957
|
+
--------
|
|
958
|
+
>>> from nextmv.output import LocalOutputWriter, Output
|
|
959
|
+
>>> writer = LocalOutputWriter()
|
|
960
|
+
>>> # Write JSON to a file
|
|
961
|
+
>>> writer.write(Output(solution={"result": 42}), path="result.json")
|
|
962
|
+
>>> # Write JSON to stdout
|
|
963
|
+
>>> writer.write({"simple": "data"})
|
|
510
964
|
"""
|
|
511
965
|
|
|
512
966
|
# If the user forgot to reset stdout after redirecting it, we need to
|
|
@@ -525,88 +979,24 @@ class LocalOutputWriter(OutputWriter):
|
|
|
525
979
|
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
526
980
|
)
|
|
527
981
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
982
|
+
output_dict = {}
|
|
983
|
+
if isinstance(output, Output):
|
|
984
|
+
output_dict = output.to_dict()
|
|
985
|
+
elif isinstance(output, BaseModel):
|
|
986
|
+
output_dict = output.to_dict()
|
|
987
|
+
elif isinstance(output, dict):
|
|
988
|
+
output_dict = output
|
|
989
|
+
else:
|
|
990
|
+
raise TypeError(
|
|
991
|
+
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
992
|
+
)
|
|
531
993
|
|
|
532
994
|
self.FILE_WRITERS[output_format](
|
|
533
995
|
output=output,
|
|
534
|
-
|
|
535
|
-
statistics=statistics,
|
|
536
|
-
assets=assets,
|
|
996
|
+
output_dict=output_dict,
|
|
537
997
|
path=path,
|
|
538
998
|
)
|
|
539
999
|
|
|
540
|
-
@staticmethod
|
|
541
|
-
def _extract_statistics(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
542
|
-
"""Extract JSON-serializable statistics."""
|
|
543
|
-
|
|
544
|
-
statistics = {}
|
|
545
|
-
|
|
546
|
-
if not isinstance(output, Output):
|
|
547
|
-
return statistics
|
|
548
|
-
|
|
549
|
-
stats = output.statistics
|
|
550
|
-
|
|
551
|
-
if stats is None:
|
|
552
|
-
return statistics
|
|
553
|
-
|
|
554
|
-
if isinstance(stats, Statistics):
|
|
555
|
-
statistics = stats.to_dict()
|
|
556
|
-
elif isinstance(stats, dict):
|
|
557
|
-
statistics = stats
|
|
558
|
-
else:
|
|
559
|
-
raise TypeError(f"unsupported statistics type: {type(stats)}, supported types are `Statistics` or `dict`")
|
|
560
|
-
|
|
561
|
-
return statistics
|
|
562
|
-
|
|
563
|
-
@staticmethod
|
|
564
|
-
def _extract_options(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
565
|
-
"""Extract JSON-serializable options."""
|
|
566
|
-
|
|
567
|
-
options = {}
|
|
568
|
-
|
|
569
|
-
if not isinstance(output, Output):
|
|
570
|
-
return options
|
|
571
|
-
|
|
572
|
-
opt = output.options
|
|
573
|
-
|
|
574
|
-
if opt is None:
|
|
575
|
-
return options
|
|
576
|
-
|
|
577
|
-
if isinstance(opt, Options):
|
|
578
|
-
options = opt.to_dict()
|
|
579
|
-
elif isinstance(opt, dict):
|
|
580
|
-
options = opt
|
|
581
|
-
else:
|
|
582
|
-
raise TypeError(f"unsupported options type: {type(opt)}, supported types are `Options` or `dict`")
|
|
583
|
-
|
|
584
|
-
return options
|
|
585
|
-
|
|
586
|
-
@staticmethod
|
|
587
|
-
def _extract_assets(output: Union[Output, dict[str, Any]]) -> list[dict[str, Any]]:
|
|
588
|
-
"""Extract JSON-serializable assets."""
|
|
589
|
-
|
|
590
|
-
assets = []
|
|
591
|
-
|
|
592
|
-
if not isinstance(output, Output):
|
|
593
|
-
return assets
|
|
594
|
-
|
|
595
|
-
assts = output.assets
|
|
596
|
-
|
|
597
|
-
if assts is None:
|
|
598
|
-
return assets
|
|
599
|
-
|
|
600
|
-
for ix, asset in enumerate(assts):
|
|
601
|
-
if isinstance(asset, Asset):
|
|
602
|
-
assets.append(asset.to_dict())
|
|
603
|
-
elif isinstance(asset, dict):
|
|
604
|
-
assets.append(asset)
|
|
605
|
-
else:
|
|
606
|
-
raise TypeError(f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`")
|
|
607
|
-
|
|
608
|
-
return assets
|
|
609
|
-
|
|
610
1000
|
|
|
611
1001
|
def write_local(
|
|
612
1002
|
output: Union[Output, dict[str, Any]],
|
|
@@ -614,42 +1004,50 @@ def write_local(
|
|
|
614
1004
|
skip_stdout_reset: bool = False,
|
|
615
1005
|
) -> None:
|
|
616
1006
|
"""
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1007
|
+
!!! warning
|
|
1008
|
+
`write_local` is deprecated, use `write` instead.
|
|
1009
|
+
|
|
1010
|
+
Write the output to the local filesystem or stdout.
|
|
620
1011
|
|
|
621
1012
|
This is a convenience function for instantiating a `LocalOutputWriter` and
|
|
622
1013
|
calling its `write` method.
|
|
623
1014
|
|
|
624
|
-
Write the `output` to the local filesystem. Consider the following for the
|
|
625
|
-
`path` parameter, depending on the `Output.output_format`:
|
|
626
|
-
|
|
627
|
-
- `OutputFormat.JSON`: the `path` is the file where the JSON data will
|
|
628
|
-
be written. If empty or `None`, the data will be written to stdout.
|
|
629
|
-
- `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
|
|
630
|
-
files will be written. If empty or `None`, the data will be written
|
|
631
|
-
to a directory named `output` under the current working directory.
|
|
632
|
-
The `Output.options` and `Output.statistics` will be written to
|
|
633
|
-
stdout.
|
|
634
|
-
|
|
635
|
-
This function detects if stdout was redirected and resets it to avoid
|
|
636
|
-
unexpected behavior. If you want to skip this behavior, set the
|
|
637
|
-
`skip_stdout_reset` parameter to `True`.
|
|
638
|
-
|
|
639
1015
|
Parameters
|
|
640
1016
|
----------
|
|
641
|
-
output : Output, dict[str, Any]
|
|
642
|
-
Output data to write.
|
|
643
|
-
path : str
|
|
644
|
-
Path to write the output data to.
|
|
1017
|
+
output : Union[Output, dict[str, Any]]
|
|
1018
|
+
Output data to write. Can be an Output object or a dictionary.
|
|
1019
|
+
path : str, optional
|
|
1020
|
+
Path to write the output data to. The interpretation depends on the
|
|
1021
|
+
output format:
|
|
1022
|
+
|
|
1023
|
+
- For `OutputFormat.JSON`: File path for the JSON output. If None or
|
|
1024
|
+
empty, writes to stdout.
|
|
1025
|
+
- For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
|
|
1026
|
+
or empty, writes to a directory named "output" in the current working
|
|
1027
|
+
directory.
|
|
645
1028
|
skip_stdout_reset : bool, optional
|
|
646
|
-
Skip resetting stdout before writing the output data. Default is
|
|
647
|
-
`False`.
|
|
1029
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
648
1030
|
|
|
649
1031
|
Raises
|
|
650
1032
|
------
|
|
651
1033
|
ValueError
|
|
652
|
-
If the
|
|
1034
|
+
If the Output.output_format is not supported.
|
|
1035
|
+
TypeError
|
|
1036
|
+
If the output is of an unsupported type.
|
|
1037
|
+
|
|
1038
|
+
Notes
|
|
1039
|
+
-----
|
|
1040
|
+
This function detects if stdout was redirected and resets it to avoid
|
|
1041
|
+
unexpected behavior. If you want to skip this behavior, set the
|
|
1042
|
+
skip_stdout_reset parameter to True.
|
|
1043
|
+
|
|
1044
|
+
Examples
|
|
1045
|
+
--------
|
|
1046
|
+
>>> from nextmv.output import write_local, Output
|
|
1047
|
+
>>> # Write JSON to a file
|
|
1048
|
+
>>> write_local(Output(solution={"result": 42}), path="result.json")
|
|
1049
|
+
>>> # Write JSON to stdout
|
|
1050
|
+
>>> write_local({"simple": "data"})
|
|
653
1051
|
"""
|
|
654
1052
|
|
|
655
1053
|
deprecated(
|
|
@@ -662,6 +1060,7 @@ def write_local(
|
|
|
662
1060
|
|
|
663
1061
|
|
|
664
1062
|
_LOCAL_OUTPUT_WRITER = LocalOutputWriter()
|
|
1063
|
+
"""Default LocalOutputWriter instance used by the write function."""
|
|
665
1064
|
|
|
666
1065
|
|
|
667
1066
|
def write(
|
|
@@ -671,46 +1070,78 @@ def write(
|
|
|
671
1070
|
writer: Optional[OutputWriter] = _LOCAL_OUTPUT_WRITER,
|
|
672
1071
|
) -> None:
|
|
673
1072
|
"""
|
|
674
|
-
|
|
675
|
-
output to the specified destination. The `writer` is used to call the
|
|
676
|
-
`.write` method. Note that the default writes is the `LocalOutputWriter`.
|
|
1073
|
+
Write the output to the specified destination.
|
|
677
1074
|
|
|
678
|
-
|
|
679
|
-
`Output.output_format`:
|
|
1075
|
+
You can import the `write` function directly from `nextmv`:
|
|
680
1076
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
files will be written. If empty or `None`, the data will be written
|
|
685
|
-
to a directory named `output` under the current working directory.
|
|
686
|
-
The `Output.options` and `Output.statistics` will be written to
|
|
687
|
-
stdout.
|
|
1077
|
+
```python
|
|
1078
|
+
from nextmv import write
|
|
1079
|
+
```
|
|
688
1080
|
|
|
689
|
-
This
|
|
690
|
-
|
|
691
|
-
`skip_stdout_reset` parameter to `True`.
|
|
1081
|
+
This is a convenience function for writing output data using a provided writer.
|
|
1082
|
+
By default, it uses the `LocalOutputWriter` to write to files or stdout.
|
|
692
1083
|
|
|
693
1084
|
Parameters
|
|
694
1085
|
----------
|
|
695
|
-
output : Output, dict[str, Any]
|
|
696
|
-
Output data to write.
|
|
697
|
-
path : str
|
|
698
|
-
Path to write the output data to.
|
|
1086
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1087
|
+
Output data to write. Can be an Output object, a dictionary, or a BaseModel.
|
|
1088
|
+
path : str, optional
|
|
1089
|
+
Path to write the output data to. The interpretation depends on the
|
|
1090
|
+
output format:
|
|
1091
|
+
|
|
1092
|
+
- For `OutputFormat.JSON`: File path for the JSON output. If None or
|
|
1093
|
+
empty, writes to stdout.
|
|
1094
|
+
- For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
|
|
1095
|
+
or empty, writes to a directory named "output" in the current working
|
|
1096
|
+
directory.
|
|
699
1097
|
skip_stdout_reset : bool, optional
|
|
700
|
-
Skip resetting stdout before writing the output data. Default is
|
|
701
|
-
|
|
1098
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
1099
|
+
writer : OutputWriter, optional
|
|
1100
|
+
The writer to use for writing the output. Default is a
|
|
1101
|
+
`LocalOutputWriter` instance.
|
|
702
1102
|
|
|
703
1103
|
Raises
|
|
704
1104
|
------
|
|
705
1105
|
ValueError
|
|
706
|
-
If the
|
|
1106
|
+
If the Output.output_format is not supported.
|
|
1107
|
+
TypeError
|
|
1108
|
+
If the output is of an unsupported type.
|
|
1109
|
+
|
|
1110
|
+
Examples
|
|
1111
|
+
--------
|
|
1112
|
+
>>> from nextmv.output import write, Output, OutputFormat
|
|
1113
|
+
>>> # Write JSON to a file
|
|
1114
|
+
>>> write(Output(solution={"result": 42}), path="result.json")
|
|
1115
|
+
>>> # Write CSV archive
|
|
1116
|
+
>>> data = {"vehicles": [{"id": 1, "capacity": 100}, {"id": 2, "capacity": 150}]}
|
|
1117
|
+
>>> write(Output(output_format=OutputFormat.CSV_ARCHIVE, solution=data), path="output_dir")
|
|
707
1118
|
"""
|
|
708
1119
|
|
|
709
1120
|
writer.write(output, path, skip_stdout_reset)
|
|
710
1121
|
|
|
711
1122
|
|
|
712
|
-
def _custom_serial(obj: Any):
|
|
713
|
-
"""
|
|
1123
|
+
def _custom_serial(obj: Any) -> str:
|
|
1124
|
+
"""
|
|
1125
|
+
JSON serializer for objects not serializable by default json serializer.
|
|
1126
|
+
|
|
1127
|
+
This function provides custom serialization for datetime objects, converting
|
|
1128
|
+
them to ISO format strings.
|
|
1129
|
+
|
|
1130
|
+
Parameters
|
|
1131
|
+
----------
|
|
1132
|
+
obj : Any
|
|
1133
|
+
The object to serialize.
|
|
1134
|
+
|
|
1135
|
+
Returns
|
|
1136
|
+
-------
|
|
1137
|
+
str
|
|
1138
|
+
The serialized representation of the object.
|
|
1139
|
+
|
|
1140
|
+
Raises
|
|
1141
|
+
------
|
|
1142
|
+
TypeError
|
|
1143
|
+
If the object type is not supported for serialization.
|
|
1144
|
+
"""
|
|
714
1145
|
|
|
715
1146
|
if isinstance(obj, (datetime.datetime | datetime.date)):
|
|
716
1147
|
return obj.isoformat()
|