nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
nextmv/output.py
CHANGED
|
@@ -1,26 +1,103 @@
|
|
|
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
|
+
SolutionFile
|
|
25
|
+
Represents a solution to be written as a file.
|
|
26
|
+
VisualSchema
|
|
27
|
+
Enumeration of supported visualization schemas.
|
|
28
|
+
Visual
|
|
29
|
+
Visual schema definition for an asset.
|
|
30
|
+
Asset
|
|
31
|
+
Represents downloadable information that is part of the `Output`.
|
|
32
|
+
Output
|
|
33
|
+
A class for representing the output of a decision problem.
|
|
34
|
+
OutputWriter
|
|
35
|
+
Base class for writing outputs to different destinations.
|
|
36
|
+
LocalOutputWriter
|
|
37
|
+
Class for writing outputs to local files or stdout.
|
|
38
|
+
|
|
39
|
+
Functions
|
|
40
|
+
---------
|
|
41
|
+
write
|
|
42
|
+
Write the output to the specified destination.
|
|
43
|
+
|
|
44
|
+
Attributes
|
|
45
|
+
----------
|
|
46
|
+
ASSETS_KEY : str
|
|
47
|
+
Assets key constant used for identifying assets in the run output.
|
|
48
|
+
STATISTICS_KEY : str
|
|
49
|
+
Statistics key constant used for identifying statistics in the run output.
|
|
50
|
+
SOLUTIONS_KEY : str
|
|
51
|
+
Solutions key constant used for identifying solutions in the run output.
|
|
52
|
+
OUTPUTS_KEY : str
|
|
53
|
+
Outputs key constant used for identifying outputs in the run output.
|
|
54
|
+
"""
|
|
2
55
|
|
|
3
56
|
import copy
|
|
4
57
|
import csv
|
|
5
|
-
import datetime
|
|
6
|
-
import json
|
|
7
58
|
import os
|
|
8
59
|
import sys
|
|
60
|
+
from collections.abc import Callable
|
|
9
61
|
from dataclasses import dataclass
|
|
10
62
|
from enum import Enum
|
|
11
|
-
from typing import Any
|
|
63
|
+
from typing import Any
|
|
12
64
|
|
|
13
65
|
from pydantic import AliasChoices, Field
|
|
14
66
|
|
|
67
|
+
from nextmv._serialization import serialize_json
|
|
15
68
|
from nextmv.base_model import BaseModel
|
|
69
|
+
from nextmv.deprecated import deprecated
|
|
16
70
|
from nextmv.logger import reset_stdout
|
|
17
71
|
from nextmv.options import Options
|
|
18
72
|
|
|
73
|
+
ASSETS_KEY = "assets"
|
|
74
|
+
"""
|
|
75
|
+
Assets key constant used for identifying assets in the run output.
|
|
76
|
+
"""
|
|
77
|
+
STATISTICS_KEY = "statistics"
|
|
78
|
+
"""
|
|
79
|
+
Statistics key constant used for identifying statistics in the run output.
|
|
80
|
+
"""
|
|
81
|
+
SOLUTIONS_KEY = "solutions"
|
|
82
|
+
"""
|
|
83
|
+
Solutions key constant used for identifying solutions in the run output.
|
|
84
|
+
"""
|
|
85
|
+
OUTPUTS_KEY = "outputs"
|
|
86
|
+
"""
|
|
87
|
+
Outputs key constant used for identifying outputs in the run output.
|
|
88
|
+
"""
|
|
89
|
+
|
|
19
90
|
|
|
20
91
|
class RunStatistics(BaseModel):
|
|
21
92
|
"""
|
|
22
93
|
Statistics about a general run.
|
|
23
94
|
|
|
95
|
+
You can import the `RunStatistics` class directly from `nextmv`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from nextmv import RunStatistics
|
|
99
|
+
```
|
|
100
|
+
|
|
24
101
|
Parameters
|
|
25
102
|
----------
|
|
26
103
|
duration : float, optional
|
|
@@ -30,18 +107,23 @@ class RunStatistics(BaseModel):
|
|
|
30
107
|
custom : Union[Any, dict[str, Any]], optional
|
|
31
108
|
Custom statistics created by the user. Can normally expect a `dict[str,
|
|
32
109
|
Any]`.
|
|
110
|
+
|
|
111
|
+
Examples
|
|
112
|
+
--------
|
|
113
|
+
>>> from nextmv.output import RunStatistics
|
|
114
|
+
>>> stats = RunStatistics(duration=10.5, iterations=100)
|
|
115
|
+
>>> stats.duration
|
|
116
|
+
10.5
|
|
117
|
+
>>> stats.custom = {"convergence": 0.001}
|
|
118
|
+
>>> stats.to_dict()
|
|
119
|
+
{'duration': 10.5, 'iterations': 100, 'custom': {'convergence': 0.001}}
|
|
33
120
|
"""
|
|
34
121
|
|
|
35
|
-
duration:
|
|
122
|
+
duration: float | None = None
|
|
36
123
|
"""Duration of the run in seconds."""
|
|
37
|
-
iterations:
|
|
124
|
+
iterations: int | None = None
|
|
38
125
|
"""Number of iterations."""
|
|
39
|
-
custom:
|
|
40
|
-
Union[
|
|
41
|
-
Any,
|
|
42
|
-
dict[str, Any],
|
|
43
|
-
]
|
|
44
|
-
] = None
|
|
126
|
+
custom: Any | dict[str, Any] | None = None
|
|
45
127
|
"""Custom statistics created by the user. Can normally expect a `dict[str,
|
|
46
128
|
Any]`."""
|
|
47
129
|
|
|
@@ -50,6 +132,12 @@ class ResultStatistics(BaseModel):
|
|
|
50
132
|
"""
|
|
51
133
|
Statistics about a specific result.
|
|
52
134
|
|
|
135
|
+
You can import the `ResultStatistics` class directly from `nextmv`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from nextmv import ResultStatistics
|
|
139
|
+
```
|
|
140
|
+
|
|
53
141
|
Parameters
|
|
54
142
|
----------
|
|
55
143
|
duration : float, optional
|
|
@@ -59,25 +147,36 @@ class ResultStatistics(BaseModel):
|
|
|
59
147
|
custom : Union[Any, dict[str, Any]], optional
|
|
60
148
|
Custom statistics created by the user. Can normally expect a `dict[str,
|
|
61
149
|
Any]`.
|
|
150
|
+
|
|
151
|
+
Examples
|
|
152
|
+
--------
|
|
153
|
+
>>> from nextmv.output import ResultStatistics
|
|
154
|
+
>>> result_stats = ResultStatistics(duration=5.2, value=42.0)
|
|
155
|
+
>>> result_stats.value
|
|
156
|
+
42.0
|
|
157
|
+
>>> result_stats.custom = {"gap": 0.05}
|
|
158
|
+
>>> result_stats.to_dict()
|
|
159
|
+
{'duration': 5.2, 'value': 42.0, 'custom': {'gap': 0.05}}
|
|
62
160
|
"""
|
|
63
161
|
|
|
64
|
-
duration:
|
|
162
|
+
duration: float | None = None
|
|
65
163
|
"""Duration of the run in seconds."""
|
|
66
|
-
value:
|
|
164
|
+
value: float | None = None
|
|
67
165
|
"""Value of the result."""
|
|
68
|
-
custom:
|
|
69
|
-
Union[
|
|
70
|
-
Any,
|
|
71
|
-
dict[str, Any],
|
|
72
|
-
]
|
|
73
|
-
] = None
|
|
166
|
+
custom: Any | dict[str, Any] | None = None
|
|
74
167
|
"""Custom statistics created by the user. Can normally expect a `dict[str,
|
|
75
168
|
Any]`."""
|
|
76
169
|
|
|
77
170
|
|
|
78
171
|
class DataPoint(BaseModel):
|
|
79
172
|
"""
|
|
80
|
-
A data point.
|
|
173
|
+
A data point representing a 2D coordinate.
|
|
174
|
+
|
|
175
|
+
You can import the `DataPoint` class directly from `nextmv`:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from nextmv import DataPoint
|
|
179
|
+
```
|
|
81
180
|
|
|
82
181
|
Parameters
|
|
83
182
|
----------
|
|
@@ -85,6 +184,15 @@ class DataPoint(BaseModel):
|
|
|
85
184
|
X coordinate of the data point.
|
|
86
185
|
y : float
|
|
87
186
|
Y coordinate of the data point.
|
|
187
|
+
|
|
188
|
+
Examples
|
|
189
|
+
--------
|
|
190
|
+
>>> from nextmv.output import DataPoint
|
|
191
|
+
>>> point = DataPoint(x=3.5, y=4.2)
|
|
192
|
+
>>> point.x
|
|
193
|
+
3.5
|
|
194
|
+
>>> point.to_dict()
|
|
195
|
+
{'x': 3.5, 'y': 4.2}
|
|
88
196
|
"""
|
|
89
197
|
|
|
90
198
|
x: float
|
|
@@ -95,25 +203,47 @@ class DataPoint(BaseModel):
|
|
|
95
203
|
|
|
96
204
|
class Series(BaseModel):
|
|
97
205
|
"""
|
|
98
|
-
A series of data points.
|
|
206
|
+
A series of data points for visualization or analysis.
|
|
207
|
+
|
|
208
|
+
You can import the `Series` class directly from `nextmv`:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from nextmv import Series
|
|
212
|
+
```
|
|
99
213
|
|
|
100
214
|
Parameters
|
|
101
215
|
----------
|
|
102
216
|
name : str, optional
|
|
103
217
|
Name of the series.
|
|
104
218
|
data_points : list[DataPoint], optional
|
|
105
|
-
Data of the series.
|
|
219
|
+
Data points of the series.
|
|
220
|
+
|
|
221
|
+
Examples
|
|
222
|
+
--------
|
|
223
|
+
>>> from nextmv.output import Series, DataPoint
|
|
224
|
+
>>> points = [DataPoint(x=1.0, y=2.0), DataPoint(x=2.0, y=3.0)]
|
|
225
|
+
>>> series = Series(name="Example Series", data_points=points)
|
|
226
|
+
>>> series.name
|
|
227
|
+
'Example Series'
|
|
228
|
+
>>> len(series.data_points)
|
|
229
|
+
2
|
|
106
230
|
"""
|
|
107
231
|
|
|
108
|
-
name:
|
|
232
|
+
name: str | None = None
|
|
109
233
|
"""Name of the series."""
|
|
110
|
-
data_points:
|
|
234
|
+
data_points: list[DataPoint] | None = None
|
|
111
235
|
"""Data of the series."""
|
|
112
236
|
|
|
113
237
|
|
|
114
238
|
class SeriesData(BaseModel):
|
|
115
239
|
"""
|
|
116
|
-
Data of
|
|
240
|
+
Data container for multiple series of data points.
|
|
241
|
+
|
|
242
|
+
You can import the `SeriesData` class directly from `nextmv`:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from nextmv import SeriesData
|
|
246
|
+
```
|
|
117
247
|
|
|
118
248
|
Parameters
|
|
119
249
|
----------
|
|
@@ -121,17 +251,35 @@ class SeriesData(BaseModel):
|
|
|
121
251
|
A series for the value of the solution.
|
|
122
252
|
custom : list[Series], optional
|
|
123
253
|
A list of series for custom statistics.
|
|
254
|
+
|
|
255
|
+
Examples
|
|
256
|
+
--------
|
|
257
|
+
>>> from nextmv.output import SeriesData, Series, DataPoint
|
|
258
|
+
>>> value_series = Series(name="Solution Value", data_points=[DataPoint(x=0, y=10), DataPoint(x=1, y=5)])
|
|
259
|
+
>>> custom_series = [Series(name="Gap", data_points=[DataPoint(x=0, y=0.5), DataPoint(x=1, y=0.1)])]
|
|
260
|
+
>>> series_data = SeriesData(value=value_series, custom=custom_series)
|
|
261
|
+
>>> series_data.value.name
|
|
262
|
+
'Solution Value'
|
|
263
|
+
>>> len(series_data.custom)
|
|
264
|
+
1
|
|
124
265
|
"""
|
|
125
266
|
|
|
126
|
-
value:
|
|
267
|
+
value: Series | None = None
|
|
127
268
|
"""A series for the value of the solution."""
|
|
128
|
-
custom:
|
|
269
|
+
custom: list[Series] | None = None
|
|
129
270
|
"""A list of series for custom statistics."""
|
|
130
271
|
|
|
131
272
|
|
|
132
273
|
class Statistics(BaseModel):
|
|
133
274
|
"""
|
|
134
|
-
|
|
275
|
+
Complete statistics container for a solution, including run metrics and
|
|
276
|
+
result data.
|
|
277
|
+
|
|
278
|
+
You can import the `Statistics` class directly from `nextmv`:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from nextmv import Statistics
|
|
282
|
+
```
|
|
135
283
|
|
|
136
284
|
Parameters
|
|
137
285
|
----------
|
|
@@ -143,15 +291,26 @@ class Statistics(BaseModel):
|
|
|
143
291
|
Series data about some metric.
|
|
144
292
|
statistics_schema : str, optional
|
|
145
293
|
Schema (version). This class only supports `v1`.
|
|
294
|
+
|
|
295
|
+
Examples
|
|
296
|
+
--------
|
|
297
|
+
>>> from nextmv.output import Statistics, RunStatistics, ResultStatistics
|
|
298
|
+
>>> run_stats = RunStatistics(duration=10.0, iterations=50)
|
|
299
|
+
>>> result_stats = ResultStatistics(value=100.0)
|
|
300
|
+
>>> stats = Statistics(run=run_stats, result=result_stats, statistics_schema="v1")
|
|
301
|
+
>>> stats.run.duration
|
|
302
|
+
10.0
|
|
303
|
+
>>> stats.result.value
|
|
304
|
+
100.0
|
|
146
305
|
"""
|
|
147
306
|
|
|
148
|
-
run:
|
|
307
|
+
run: RunStatistics | None = None
|
|
149
308
|
"""Statistics about the run."""
|
|
150
|
-
result:
|
|
309
|
+
result: ResultStatistics | None = None
|
|
151
310
|
"""Statistics about the last result."""
|
|
152
|
-
series_data:
|
|
311
|
+
series_data: SeriesData | None = None
|
|
153
312
|
"""Data of the series."""
|
|
154
|
-
statistics_schema:
|
|
313
|
+
statistics_schema: str | None = Field(
|
|
155
314
|
serialization_alias="schema",
|
|
156
315
|
validation_alias=AliasChoices("schema", "statistics_schema"),
|
|
157
316
|
default="v1",
|
|
@@ -159,17 +318,28 @@ class Statistics(BaseModel):
|
|
|
159
318
|
"""Schema (version). This class only supports `v1`."""
|
|
160
319
|
|
|
161
320
|
|
|
162
|
-
class
|
|
163
|
-
"""
|
|
321
|
+
class VisualSchema(str, Enum):
|
|
322
|
+
"""
|
|
323
|
+
Enumeration of supported visualization schemas.
|
|
164
324
|
|
|
165
|
-
|
|
166
|
-
"""JSON format, utf-8 encoded."""
|
|
167
|
-
CSV_ARCHIVE = "CSV_ARCHIVE"
|
|
168
|
-
"""CSV archive format: multiple CSV files."""
|
|
325
|
+
You can import the `VisualSchema` class directly from `nextmv`:
|
|
169
326
|
|
|
327
|
+
```python
|
|
328
|
+
from nextmv import VisualSchema
|
|
329
|
+
```
|
|
170
330
|
|
|
171
|
-
|
|
172
|
-
|
|
331
|
+
This enum defines the different visualization libraries or rendering methods
|
|
332
|
+
that can be used to display custom asset data in the Nextmv Console.
|
|
333
|
+
|
|
334
|
+
Attributes
|
|
335
|
+
----------
|
|
336
|
+
CHARTJS : str
|
|
337
|
+
Tells Nextmv Console to render the custom asset data with the Chart.js library.
|
|
338
|
+
GEOJSON : str
|
|
339
|
+
Tells Nextmv Console to render the custom asset data as GeoJSON on a map.
|
|
340
|
+
PLOTLY : str
|
|
341
|
+
Tells Nextmv Console to render the custom asset data with the Plotly library.
|
|
342
|
+
"""
|
|
173
343
|
|
|
174
344
|
CHARTJS = "chartjs"
|
|
175
345
|
"""Tells Nextmv Console to render the custom asset data with the Chart.js
|
|
@@ -184,8 +354,39 @@ class VisualSchema(str, Enum):
|
|
|
184
354
|
|
|
185
355
|
class Visual(BaseModel):
|
|
186
356
|
"""
|
|
187
|
-
Visual schema
|
|
188
|
-
|
|
357
|
+
Visual schema definition for an asset.
|
|
358
|
+
|
|
359
|
+
You can import the `Visual` class directly from `nextmv`:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from nextmv import Visual
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
This class defines how an asset is plotted in the Nextmv Console,
|
|
366
|
+
including the schema type, label, and display type.
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
visual_schema : VisualSchema
|
|
371
|
+
Schema of the visual asset.
|
|
372
|
+
label : str
|
|
373
|
+
Label for the custom tab of the visual asset in the Nextmv Console.
|
|
374
|
+
visual_type : str, optional
|
|
375
|
+
Defines the type of custom visual. Default is "custom-tab".
|
|
376
|
+
|
|
377
|
+
Raises
|
|
378
|
+
------
|
|
379
|
+
ValueError
|
|
380
|
+
If an unsupported schema or visual_type is provided.
|
|
381
|
+
|
|
382
|
+
Examples
|
|
383
|
+
--------
|
|
384
|
+
>>> from nextmv.output import Visual, VisualSchema
|
|
385
|
+
>>> visual = Visual(visual_schema=VisualSchema.CHARTJS, label="Performance Chart")
|
|
386
|
+
>>> visual.visual_schema
|
|
387
|
+
<VisualSchema.CHARTJS: 'chartjs'>
|
|
388
|
+
>>> visual.label
|
|
389
|
+
'Performance Chart'
|
|
189
390
|
"""
|
|
190
391
|
|
|
191
392
|
visual_schema: VisualSchema = Field(
|
|
@@ -196,7 +397,7 @@ class Visual(BaseModel):
|
|
|
196
397
|
label: str
|
|
197
398
|
"""Label for the custom tab of the visual asset in the Nextmv Console."""
|
|
198
399
|
|
|
199
|
-
visual_type:
|
|
400
|
+
visual_type: str | None = Field(
|
|
200
401
|
serialization_alias="type",
|
|
201
402
|
validation_alias=AliasChoices("type", "visual_type"),
|
|
202
403
|
default="custom-tab",
|
|
@@ -206,6 +407,14 @@ class Visual(BaseModel):
|
|
|
206
407
|
details."""
|
|
207
408
|
|
|
208
409
|
def __post_init__(self):
|
|
410
|
+
"""
|
|
411
|
+
Validate the visual schema and type.
|
|
412
|
+
|
|
413
|
+
Raises
|
|
414
|
+
------
|
|
415
|
+
ValueError
|
|
416
|
+
If the visual_schema is not in VisualSchema or if visual_type is not 'custom-tab'.
|
|
417
|
+
"""
|
|
209
418
|
if self.visual_schema not in VisualSchema:
|
|
210
419
|
raise ValueError(f"unsupported schema: {self.visual_schema}, supported schemas are {VisualSchema}")
|
|
211
420
|
|
|
@@ -215,153 +424,810 @@ class Visual(BaseModel):
|
|
|
215
424
|
|
|
216
425
|
class Asset(BaseModel):
|
|
217
426
|
"""
|
|
218
|
-
|
|
427
|
+
Represents downloadable information that is part of the `Output`.
|
|
428
|
+
|
|
429
|
+
You can import the `Asset` class directly from `nextmv`:
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
from nextmv import Asset
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
An asset contains content that can be serialized to JSON and optionally
|
|
436
|
+
includes visual information for rendering in the Nextmv Console.
|
|
437
|
+
|
|
438
|
+
Parameters
|
|
439
|
+
----------
|
|
440
|
+
name : str
|
|
441
|
+
Name of the asset.
|
|
442
|
+
content : Any
|
|
443
|
+
Content of the asset. The type must be serializable to JSON.
|
|
444
|
+
content_type : str, optional
|
|
445
|
+
Content type of the asset. Only "json" is currently supported. Default is "json".
|
|
446
|
+
description : str, optional
|
|
447
|
+
Description of the asset. Default is None.
|
|
448
|
+
visual : Visual, optional
|
|
449
|
+
Visual schema of the asset. Default is None.
|
|
450
|
+
|
|
451
|
+
Raises
|
|
452
|
+
------
|
|
453
|
+
ValueError
|
|
454
|
+
If the content_type is not "json".
|
|
455
|
+
|
|
456
|
+
Examples
|
|
457
|
+
--------
|
|
458
|
+
>>> from nextmv.output import Asset, Visual, VisualSchema
|
|
459
|
+
>>> visual = Visual(visual_schema=VisualSchema.CHARTJS, label="Solution Progress")
|
|
460
|
+
>>> asset = Asset(
|
|
461
|
+
... name="optimization_progress",
|
|
462
|
+
... content={"iterations": [1, 2, 3], "values": [10, 8, 7]},
|
|
463
|
+
... description="Optimization progress over iterations",
|
|
464
|
+
... visual=visual
|
|
465
|
+
... )
|
|
466
|
+
>>> asset.name
|
|
467
|
+
'optimization_progress'
|
|
219
468
|
"""
|
|
220
469
|
|
|
221
470
|
name: str
|
|
222
471
|
"""Name of the asset."""
|
|
223
|
-
content: Any
|
|
224
|
-
"""Content of the asset. The type must be serializable to JSON."""
|
|
225
472
|
|
|
226
|
-
|
|
473
|
+
id: str | None = None
|
|
474
|
+
"""
|
|
475
|
+
The ID of the asset. This ID will be populated by the Nextmv platform and can be used
|
|
476
|
+
to download the asset later.
|
|
477
|
+
"""
|
|
478
|
+
content: Any | None = None
|
|
479
|
+
"""
|
|
480
|
+
Content of the asset. The type must be serializable to JSON. Can be empty when
|
|
481
|
+
fetching the asset metadata only (e.g.: via the asset list endpoint).
|
|
482
|
+
"""
|
|
483
|
+
content_type: str | None = "json"
|
|
227
484
|
"""Content type of the asset. Only `json` is allowed"""
|
|
228
|
-
description:
|
|
485
|
+
description: str | None = None
|
|
229
486
|
"""Description of the asset."""
|
|
230
|
-
visual:
|
|
487
|
+
visual: Visual | None = None
|
|
231
488
|
"""Visual schema of the asset."""
|
|
232
489
|
|
|
233
490
|
def __post_init__(self):
|
|
491
|
+
"""
|
|
492
|
+
Validate the content type.
|
|
493
|
+
|
|
494
|
+
Raises
|
|
495
|
+
------
|
|
496
|
+
ValueError
|
|
497
|
+
If the content_type is not "json".
|
|
498
|
+
"""
|
|
234
499
|
if self.content_type != "json":
|
|
235
500
|
raise ValueError(f"unsupported content_type: {self.content_type}, supported types are `json`")
|
|
236
501
|
|
|
237
502
|
|
|
503
|
+
class OutputFormat(str, Enum):
|
|
504
|
+
"""
|
|
505
|
+
Enumeration of supported output formats.
|
|
506
|
+
|
|
507
|
+
You can import the `OutputFormat` class directly from `nextmv`:
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
from nextmv import OutputFormat
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
This enum defines the different formats that can be used for outputting data.
|
|
514
|
+
Each format has specific requirements and behaviors when writing.
|
|
515
|
+
|
|
516
|
+
Attributes
|
|
517
|
+
----------
|
|
518
|
+
JSON : str
|
|
519
|
+
JSON format, utf-8 encoded.
|
|
520
|
+
CSV_ARCHIVE : str
|
|
521
|
+
CSV archive format: multiple CSV files.
|
|
522
|
+
MULTI_FILE : str
|
|
523
|
+
Multi-file format: multiple files in a directory.
|
|
524
|
+
TEXT : str
|
|
525
|
+
Text format, utf-8 encoded.
|
|
526
|
+
"""
|
|
527
|
+
|
|
528
|
+
JSON = "json"
|
|
529
|
+
"""JSON format, utf-8 encoded."""
|
|
530
|
+
CSV_ARCHIVE = "csv-archive"
|
|
531
|
+
"""CSV archive format: multiple CSV files."""
|
|
532
|
+
MULTI_FILE = "multi-file"
|
|
533
|
+
"""Multi-file format: multiple files in a directory."""
|
|
534
|
+
TEXT = "text"
|
|
535
|
+
"""Text format, utf-8 encoded."""
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@dataclass
|
|
539
|
+
class SolutionFile:
|
|
540
|
+
"""
|
|
541
|
+
Represents a solution to be written as a file.
|
|
542
|
+
|
|
543
|
+
You can import the `SolutionFile` class directly from `nextmv`:
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
from nextmv import SolutionFile
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
This class is used to define a solution that will be written to a file in
|
|
550
|
+
the filesystem. It includes the name of the file, the data to be written,
|
|
551
|
+
and the writer function that will handle the serialization of the data.
|
|
552
|
+
This `SolutionFile` class is typically used in the `Output`, when the
|
|
553
|
+
`Output.output_format` is set to `OutputFormat.MULTI_FILE`. Given that it
|
|
554
|
+
is difficult to handle every edge case of how a solution is serialized, and
|
|
555
|
+
written to a file, this class exists so that the user can implement the
|
|
556
|
+
`writer` callable of their choice and provide it with any `writer_args`
|
|
557
|
+
and `writer_kwargs` they might need.
|
|
558
|
+
|
|
559
|
+
Parameters
|
|
560
|
+
----------
|
|
561
|
+
name : str
|
|
562
|
+
Name of the output file. The file extension should be included in the
|
|
563
|
+
name.
|
|
564
|
+
data : Any
|
|
565
|
+
The actual data that will be written to the file. This can be any type
|
|
566
|
+
that can be given to the `writer` function. For example, if the `writer`
|
|
567
|
+
is a `csv.DictWriter`, then the data should be a list of dictionaries,
|
|
568
|
+
where each dictionary represents a row in the CSV file.
|
|
569
|
+
writer : Callable
|
|
570
|
+
Callable that writes the solution data to the file. This should be a
|
|
571
|
+
function implemented by the user. There are convenience functions that you
|
|
572
|
+
can use as a writer as well. The `writer` must receive, at the very
|
|
573
|
+
minimum, the following arguments:
|
|
574
|
+
|
|
575
|
+
- `file_path`: a `str` argument which is the location where this solution
|
|
576
|
+
will be written to. This includes the dir and the name of the file. As
|
|
577
|
+
such, the `name` parameter of this class is going to be passed to this
|
|
578
|
+
function joined with the directory where the file will be written.
|
|
579
|
+
- `data`: the actual data that will be written to the file. This can be any
|
|
580
|
+
type that can be given to the `writer` function. The `data` parameter of
|
|
581
|
+
this class is going to be passed to the `writer` function.
|
|
582
|
+
|
|
583
|
+
The `writer` can also receive additional arguments, and keyword arguments.
|
|
584
|
+
The `writer_args` and `writer_kwargs` parameters of this class can be used
|
|
585
|
+
to provide those additional arguments.
|
|
586
|
+
writer_args : Optional[list[Any]], optional
|
|
587
|
+
Positional arguments to pass to the writer function.
|
|
588
|
+
writer_kwargs : Optional[dict[str, Any]], optional
|
|
589
|
+
Keyword arguments to pass to the writer function.
|
|
590
|
+
|
|
591
|
+
Examples
|
|
592
|
+
--------
|
|
593
|
+
>>> from nextmv import SolutionFile
|
|
594
|
+
>>> solution_file = SolutionFile(
|
|
595
|
+
... name="solution.csv",
|
|
596
|
+
... data=[{"id": 1, "value": 100}, {"id": 2, "value": 200}],
|
|
597
|
+
... writer=csv.DictWriter,
|
|
598
|
+
... writer_kwargs={"fieldnames": ["id", "value"]},
|
|
599
|
+
... writer_args=[open("solution.csv", "w", newline="")],
|
|
600
|
+
... )
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
name: str
|
|
604
|
+
"""
|
|
605
|
+
Name of the solution (output) file. The file extension should be included in the
|
|
606
|
+
name.
|
|
607
|
+
"""
|
|
608
|
+
data: Any
|
|
609
|
+
"""
|
|
610
|
+
The actual data that will be written to the file. This can be any type that
|
|
611
|
+
can be given to the `writer` function. For example, if the `writer` is a
|
|
612
|
+
`csv.DictWriter`, then the data should be a list of dictionaries, where
|
|
613
|
+
each dictionary represents a row in the CSV file.
|
|
614
|
+
"""
|
|
615
|
+
writer: Callable[[str, Any], None]
|
|
616
|
+
"""
|
|
617
|
+
Callable that writes the solution data to the file. This should be a
|
|
618
|
+
function implemented by the user. There are convenience functions that you
|
|
619
|
+
can use as a writer as well. The `writer` must receive, at the very
|
|
620
|
+
minimum, the following arguments:
|
|
621
|
+
|
|
622
|
+
- `file_path`: a `str` argument which is the location where this solution
|
|
623
|
+
will be written to. This includes the dir and the name of the file. As
|
|
624
|
+
such, the `name` parameter of this class is going to be passed to this
|
|
625
|
+
function joined with the directory where the file will be written.
|
|
626
|
+
- `data`: the actual data that will be written to the file. This can be any
|
|
627
|
+
type that can be given to the `writer` function. The `data` parameter of
|
|
628
|
+
this class is going to be passed to the `writer` function.
|
|
629
|
+
|
|
630
|
+
The `writer` can also receive additional arguments, and keyword arguments.
|
|
631
|
+
The `writer_args` and `writer_kwargs` parameters of this class can be used
|
|
632
|
+
to provide those additional arguments.
|
|
633
|
+
"""
|
|
634
|
+
writer_args: list[Any] | None = None
|
|
635
|
+
"""
|
|
636
|
+
Optional positional arguments to pass to the writer function. This can be
|
|
637
|
+
used to customize the behavior of the writer.
|
|
638
|
+
"""
|
|
639
|
+
writer_kwargs: dict[str, Any] | None = None
|
|
640
|
+
"""
|
|
641
|
+
Optional keyword arguments to pass to the writer function. This can be used
|
|
642
|
+
to customize the behavior of the writer.
|
|
643
|
+
"""
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def json_solution_file(
|
|
647
|
+
name: str,
|
|
648
|
+
data: dict[str, Any],
|
|
649
|
+
json_configurations: dict[str, Any] | None = None,
|
|
650
|
+
) -> SolutionFile:
|
|
651
|
+
"""
|
|
652
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
653
|
+
given `data` to a `.json` file with the provided `name`.
|
|
654
|
+
|
|
655
|
+
You can import this function directly from `nextmv`:
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
from nextmv import json_solution_file
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
Parameters
|
|
662
|
+
----------
|
|
663
|
+
name : str
|
|
664
|
+
Name of the output file. You don't need to include the `.json`
|
|
665
|
+
extension.
|
|
666
|
+
data : dict[str, Any]
|
|
667
|
+
The actual data that will be written to the file. This should be a
|
|
668
|
+
dictionary that can be serialized to JSON.
|
|
669
|
+
json_configurations : Optional[dict[str, Any]], optional
|
|
670
|
+
Optional configuration options for the JSON serialization process. You
|
|
671
|
+
can use these options to configure parameters such as indentation.
|
|
672
|
+
|
|
673
|
+
Returns
|
|
674
|
+
-------
|
|
675
|
+
SolutionFile
|
|
676
|
+
The constructed `SolutionFile` object.
|
|
677
|
+
|
|
678
|
+
Examples
|
|
679
|
+
--------
|
|
680
|
+
>>> from nextmv import json_solution_file
|
|
681
|
+
>>> solution_file = json_solution_file(
|
|
682
|
+
... name="solution",
|
|
683
|
+
... data={"id": 1, "value": 100}
|
|
684
|
+
... )
|
|
685
|
+
>>> solution_file.name
|
|
686
|
+
'solution.json'
|
|
687
|
+
>>> solution_file.data
|
|
688
|
+
{'id': 1, 'value': 100}
|
|
689
|
+
"""
|
|
690
|
+
|
|
691
|
+
if not name.endswith(".json"):
|
|
692
|
+
name += ".json"
|
|
693
|
+
|
|
694
|
+
json_configurations = json_configurations or {}
|
|
695
|
+
|
|
696
|
+
def writer(file_path: str, write_data: dict[str, Any]) -> None:
|
|
697
|
+
serialized = serialize_json(write_data, json_configurations=json_configurations)
|
|
698
|
+
|
|
699
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
700
|
+
file.write(serialized + "\n")
|
|
701
|
+
|
|
702
|
+
return SolutionFile(
|
|
703
|
+
name=name,
|
|
704
|
+
data=data,
|
|
705
|
+
writer=writer,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def csv_solution_file(
|
|
710
|
+
name: str,
|
|
711
|
+
data: list[dict[str, Any]],
|
|
712
|
+
csv_configurations: dict[str, Any] | None = None,
|
|
713
|
+
) -> SolutionFile:
|
|
714
|
+
"""
|
|
715
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
716
|
+
given `data` to a `.csv` file with the provided `name`.
|
|
717
|
+
|
|
718
|
+
You can import this function directly from `nextmv`:
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
from nextmv import csv_solution_file
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
name : str
|
|
727
|
+
Name of the output file. You don't need to include the `.csv`
|
|
728
|
+
extension.
|
|
729
|
+
data : list[dict[str, Any]]
|
|
730
|
+
The actual data that will be written to the file. This should be a list
|
|
731
|
+
of dictionaries, where each dictionary represents a row in the CSV file.
|
|
732
|
+
The keys of the dictionaries will be used as the column headers in the
|
|
733
|
+
CSV file.
|
|
734
|
+
csv_configurations : Optional[dict[str, Any]], optional
|
|
735
|
+
Optional configuration options for the CSV serialization process.
|
|
736
|
+
|
|
737
|
+
Returns
|
|
738
|
+
-------
|
|
739
|
+
SolutionFile
|
|
740
|
+
The constructed `SolutionFile` object.
|
|
741
|
+
|
|
742
|
+
Examples
|
|
743
|
+
--------
|
|
744
|
+
>>> from nextmv import csv_solution_file
|
|
745
|
+
>>> solution_file = csv_solution_file(
|
|
746
|
+
... name="solution",
|
|
747
|
+
... data=[{"id": 1, "value": 100}, {"id": 2, "value": 200}]
|
|
748
|
+
... )
|
|
749
|
+
>>> solution_file.name
|
|
750
|
+
'solution.csv'
|
|
751
|
+
>>> solution_file.data
|
|
752
|
+
[{'id': 1, 'value': 100}, {'id': 2, 'value': 200}]
|
|
753
|
+
"""
|
|
754
|
+
|
|
755
|
+
if not name.endswith(".csv"):
|
|
756
|
+
name += ".csv"
|
|
757
|
+
|
|
758
|
+
csv_configurations = csv_configurations or {}
|
|
759
|
+
|
|
760
|
+
def writer(file_path: str, write_data: list[dict[str, Any]]) -> None:
|
|
761
|
+
with open(file_path, "w", encoding="utf-8", newline="") as file:
|
|
762
|
+
writer = csv.DictWriter(
|
|
763
|
+
file,
|
|
764
|
+
fieldnames=write_data[0].keys(),
|
|
765
|
+
**csv_configurations,
|
|
766
|
+
)
|
|
767
|
+
writer.writeheader()
|
|
768
|
+
writer.writerows(write_data)
|
|
769
|
+
|
|
770
|
+
return SolutionFile(
|
|
771
|
+
name=name,
|
|
772
|
+
data=data,
|
|
773
|
+
writer=writer,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def text_solution_file(name: str, data: str) -> SolutionFile:
|
|
778
|
+
"""
|
|
779
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
780
|
+
given `data` to a utf-8 encoded file with the provided `name`.
|
|
781
|
+
|
|
782
|
+
You can import this function directly from `nextmv`:
|
|
783
|
+
|
|
784
|
+
```python
|
|
785
|
+
from nextmv import text_solution_file
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
You must provide the extension as part of the `name` parameter.
|
|
789
|
+
|
|
790
|
+
Parameters
|
|
791
|
+
----------
|
|
792
|
+
name : str
|
|
793
|
+
Name of the output file. The file extension must be provided in the
|
|
794
|
+
name.
|
|
795
|
+
data : str
|
|
796
|
+
The actual data that will be written to the file.
|
|
797
|
+
|
|
798
|
+
Returns
|
|
799
|
+
-------
|
|
800
|
+
SolutionFile
|
|
801
|
+
The constructed `SolutionFile` object.
|
|
802
|
+
|
|
803
|
+
Examples
|
|
804
|
+
--------
|
|
805
|
+
>>> from nextmv import text_solution_file
|
|
806
|
+
>>> solution_file = text_solution_file(
|
|
807
|
+
... name="solution.txt",
|
|
808
|
+
... data="This is a sample text solution."
|
|
809
|
+
... )
|
|
810
|
+
>>> solution_file.name
|
|
811
|
+
'solution.txt'
|
|
812
|
+
>>> solution_file.data
|
|
813
|
+
'This is a sample text solution.'
|
|
814
|
+
"""
|
|
815
|
+
|
|
816
|
+
def writer(file_path: str, write_data: str) -> None:
|
|
817
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
818
|
+
file.write(write_data + "\n")
|
|
819
|
+
|
|
820
|
+
return SolutionFile(
|
|
821
|
+
name=name,
|
|
822
|
+
data=data,
|
|
823
|
+
writer=writer,
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
|
|
238
827
|
@dataclass
|
|
239
828
|
class Output:
|
|
240
829
|
"""
|
|
241
|
-
Output of a decision problem.
|
|
242
|
-
some location.
|
|
830
|
+
Output of a decision problem.
|
|
243
831
|
|
|
244
|
-
|
|
245
|
-
CSV_ARCHIVE.
|
|
832
|
+
You can import the `Output` class directly from `nextmv`:
|
|
246
833
|
|
|
247
|
-
|
|
248
|
-
|
|
834
|
+
```python
|
|
835
|
+
from nextmv import Output
|
|
836
|
+
```
|
|
249
837
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
838
|
+
This class is used to structure the output of a decision problem that
|
|
839
|
+
can later be written to various destinations. It supports different output
|
|
840
|
+
formats and allows for customization of the serialization process.
|
|
253
841
|
|
|
254
|
-
|
|
255
|
-
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
256
|
-
Any]]]`. The keys represent the file names where the data should be
|
|
257
|
-
written. The values are lists of dictionaries, where each dictionary
|
|
258
|
-
represents a row in the CSV file.
|
|
842
|
+
The `solution`'s type must match the `output_format`:
|
|
259
843
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
844
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
845
|
+
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
846
|
+
Any]]]`. The keys represent the file names where the data should be
|
|
847
|
+
written. The values are lists of dictionaries, where each dictionary
|
|
848
|
+
represents a row in the CSV file.
|
|
849
|
+
|
|
850
|
+
If you are working with `OutputFormat.MULTI_FILE`, you should use
|
|
851
|
+
`solution_files` instead of `solution`. When `solution_files` is not
|
|
852
|
+
`None`, then the `output_format` _must_ be `OutputFormat.MULTI_FILE`.
|
|
853
|
+
`solution_files` is a list of `SolutionFile` objects, which allows you to
|
|
854
|
+
define the name of the file, the data to be written, and the writer
|
|
855
|
+
function that will handle the serialization of the data. This is useful when
|
|
856
|
+
you need to write the solution to multiple files with different formats or
|
|
857
|
+
configurations.
|
|
858
|
+
|
|
859
|
+
There are convenience functions to create `SolutionFile` objects for
|
|
860
|
+
common use cases, such as:
|
|
861
|
+
|
|
862
|
+
- `json_solution_file`: for writing JSON data to a file.
|
|
863
|
+
- `csv_solution_file`: for writing CSV data to a file.
|
|
864
|
+
- `text_solution_file`: for writing utf-8 encoded data to a file.
|
|
865
|
+
|
|
866
|
+
For other data types, such as Excel, you can create your own `SolutionFile`
|
|
867
|
+
objects by providing a `name`, `data`, and a `writer` function that will
|
|
868
|
+
handle the serialization of the data.
|
|
264
869
|
|
|
265
870
|
Parameters
|
|
266
871
|
----------
|
|
267
|
-
options : Options, optional
|
|
268
|
-
Options that the `
|
|
269
|
-
|
|
872
|
+
options : Optional[Union[Options, dict[str, Any]]], optional
|
|
873
|
+
Options that the `Output` was created with. These options can be of type
|
|
874
|
+
`Options` or a simple dictionary. Default is None.
|
|
875
|
+
output_format : Optional[OutputFormat], optional
|
|
270
876
|
Format of the output data. Default is `OutputFormat.JSON`.
|
|
271
|
-
solution : Union[dict[str, Any], dict[str, list[dict[str, Any]]], optional
|
|
272
|
-
The solution to the decision problem.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
877
|
+
solution : Optional[Union[dict[str, Any], Any, dict[str, list[dict[str, Any]]]]], optional
|
|
878
|
+
The solution to the decision problem. The type must match the
|
|
879
|
+
`output_format`. Default is None.
|
|
880
|
+
statistics : Optional[Union[Statistics, dict[str, Any]]], optional
|
|
881
|
+
Statistics of the solution. Default is None.
|
|
882
|
+
csv_configurations : Optional[dict[str, Any]], optional
|
|
883
|
+
Configuration for writing CSV files. Default is None.
|
|
884
|
+
json_configurations : Optional[dict[str, Any]], optional
|
|
885
|
+
Configuration for writing JSON files. Default is None.
|
|
886
|
+
assets : Optional[list[Union[Asset, dict[str, Any]]]], optional
|
|
887
|
+
List of assets to be included in the output. Default is None.
|
|
888
|
+
solution_files: Optional[list[SolutionFile]], default = None
|
|
889
|
+
Optional list of solution files to be included in the output. These
|
|
890
|
+
files are of type `SolutionFile`, which allows for custom serialization
|
|
891
|
+
and writing of the solution data to files. When this field is
|
|
892
|
+
specified, then the `output_format` must be set to
|
|
893
|
+
`OutputFormat.MULTI_FILE`, otherwise an exception will be raised. The
|
|
894
|
+
`SolutionFile` class allows you to define the name of the file, the
|
|
895
|
+
data to be written, and the writer function that will handle the
|
|
896
|
+
serialization of the data. This is useful when you need to write the
|
|
897
|
+
solution to multiple files with different formats or configurations.
|
|
898
|
+
|
|
899
|
+
There are convenience functions to create `SolutionFile` objects for
|
|
900
|
+
common use cases, such as:
|
|
901
|
+
|
|
902
|
+
- `json_solution_file`: for writing JSON data to a file.
|
|
903
|
+
- `csv_solution_file`: for writing CSV data to a file.
|
|
904
|
+
- `text_solution_file`: for writing utf-8 encoded data to a file.
|
|
905
|
+
|
|
906
|
+
For other data types, such as Excel, you can create your own
|
|
907
|
+
`SolutionFile` objects by providing a `name`, `data`, and a `writer`
|
|
908
|
+
function that will handle the serialization of the data.
|
|
909
|
+
|
|
910
|
+
Raises
|
|
911
|
+
------
|
|
912
|
+
ValueError
|
|
913
|
+
If the solution is not compatible with the specified output_format.
|
|
914
|
+
TypeError
|
|
915
|
+
If options, statistics, or assets have unsupported types.
|
|
916
|
+
|
|
917
|
+
Examples
|
|
918
|
+
--------
|
|
919
|
+
>>> from nextmv.output import Output, OutputFormat, Statistics, RunStatistics
|
|
920
|
+
>>> run_stats = RunStatistics(duration=30.0, iterations=100)
|
|
921
|
+
>>> stats = Statistics(run=run_stats)
|
|
922
|
+
>>> solution = {"routes": [{"vehicle": 1, "stops": [1, 2, 3]}, {"vehicle": 2, "stops": [4, 5]}]}
|
|
923
|
+
>>> output = Output(
|
|
924
|
+
... output_format=OutputFormat.JSON,
|
|
925
|
+
... solution=solution,
|
|
926
|
+
... statistics=stats,
|
|
927
|
+
... json_configurations={"indent": 4}
|
|
928
|
+
... )
|
|
929
|
+
>>> output_dict = output.to_dict()
|
|
930
|
+
>>> "solution" in output_dict and "statistics" in output_dict
|
|
931
|
+
True
|
|
932
|
+
"""
|
|
933
|
+
|
|
934
|
+
options: Options | dict[str, Any] | None = None
|
|
935
|
+
"""
|
|
936
|
+
Options that the `Output` was created with. These options can be of type
|
|
937
|
+
`Options` or a simple dictionary. If the options are of type `Options`,
|
|
938
|
+
they will be serialized to a dictionary using the `to_dict` method. If
|
|
939
|
+
they are a dictionary, they will be used as is. If the options are not
|
|
940
|
+
provided, an empty dictionary will be used. If the options are of type
|
|
941
|
+
`dict`, then the dictionary should have the following structure:
|
|
942
|
+
|
|
943
|
+
```python
|
|
944
|
+
{
|
|
945
|
+
"duration": "30",
|
|
946
|
+
"threads": 4,
|
|
947
|
+
}
|
|
948
|
+
```
|
|
949
|
+
"""
|
|
950
|
+
output_format: OutputFormat | None = OutputFormat.JSON
|
|
951
|
+
"""
|
|
952
|
+
Format of the output data. Default is `OutputFormat.JSON`. When set to
|
|
953
|
+
`OutputFormat.MULTI_FILE`, the `solution_files` field must be specified and
|
|
954
|
+
cannot be `None`.
|
|
955
|
+
"""
|
|
956
|
+
solution: dict[str, Any] | Any | dict[str, list[dict[str, Any]]] | None = None
|
|
957
|
+
"""
|
|
958
|
+
The solution to the decision problem. Use this filed when working with
|
|
959
|
+
`output_format` of types:
|
|
960
|
+
|
|
961
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
962
|
+
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
963
|
+
Any]]]`. The keys represent the file names where the data will be written
|
|
964
|
+
to. The values are lists of dictionaries, where each dictionary represents
|
|
965
|
+
a row in the CSV file.
|
|
966
|
+
|
|
967
|
+
Note that when the `output_format` is set to `OutputFormat.MULTI_FILE`,
|
|
968
|
+
this `solution` field is ignored, as you should use the `solution_files`
|
|
969
|
+
field instead.
|
|
970
|
+
"""
|
|
971
|
+
statistics: Statistics | dict[str, Any] | None = None
|
|
972
|
+
"""
|
|
973
|
+
Statistics of the solution. These statistics can be of type `Statistics` or a
|
|
974
|
+
simple dictionary. If the statistics are of type `Statistics`, they will be
|
|
975
|
+
serialized to a dictionary using the `to_dict` method. If they are a
|
|
976
|
+
dictionary, they will be used as is. If the statistics are not provided, an
|
|
977
|
+
empty dictionary will be used.
|
|
978
|
+
"""
|
|
979
|
+
csv_configurations: dict[str, Any] | None = None
|
|
980
|
+
"""
|
|
981
|
+
Optional configuration for writing CSV files, to be used when the
|
|
982
|
+
`output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
|
|
983
|
+
passed as kwargs to the `DictWriter` class from the `csv` module.
|
|
984
|
+
"""
|
|
985
|
+
json_configurations: dict[str, Any] | None = None
|
|
986
|
+
"""
|
|
987
|
+
Optional configuration for writing JSON files, to be used when the
|
|
988
|
+
`output_format` is `OutputFormat.JSON`. These configurations are passed as
|
|
989
|
+
kwargs to the `json.dumps` function.
|
|
990
|
+
"""
|
|
991
|
+
assets: list[Asset | dict[str, Any]] | None = None
|
|
992
|
+
"""
|
|
993
|
+
Optional list of assets to be included in the output. These assets can be of
|
|
994
|
+
type `Asset` or a simple dictionary. If the assets are of type `Asset`, they
|
|
995
|
+
will be serialized to a dictionary using the `to_dict` method. If they are a
|
|
996
|
+
dictionary, they will be used as is. If the assets are not provided, an
|
|
997
|
+
empty list will be used.
|
|
998
|
+
"""
|
|
999
|
+
solution_files: list[SolutionFile] | None = None
|
|
1000
|
+
"""
|
|
1001
|
+
Optional list of solution files to be included in the output. These files
|
|
1002
|
+
are of type `SolutionFile`, which allows for custom serialization and
|
|
1003
|
+
writing of the solution data to files. When this field is specified, then
|
|
1004
|
+
the `output_format` must be set to `OutputFormat.MULTI_FILE`, otherwise an
|
|
1005
|
+
exception will be raised. The `SolutionFile` class allows you to define the
|
|
1006
|
+
name of the file, the data to be written, and the writer function that will
|
|
1007
|
+
handle the serialization of the data. This is useful when you need to write
|
|
1008
|
+
the solution to multiple files with different formats or configurations.
|
|
1009
|
+
|
|
1010
|
+
There are convenience functions to create `SolutionFile` objects for
|
|
1011
|
+
common use cases, such as:
|
|
1012
|
+
|
|
1013
|
+
- `json_solution_file`: for writing JSON data to a file.
|
|
1014
|
+
- `csv_solution_file`: for writing CSV data to a file.
|
|
1015
|
+
- `text_solution_file`: for writing utf-8 encoded data to a file.
|
|
1016
|
+
|
|
1017
|
+
For other data types, such as Excel, you can create your own `SolutionFile`
|
|
1018
|
+
objects by providing a `name`, `data`, and a `writer` function that will
|
|
1019
|
+
handle the serialization of the data.
|
|
1020
|
+
"""
|
|
296
1021
|
|
|
297
1022
|
def __post_init__(self):
|
|
298
|
-
"""
|
|
299
|
-
|
|
1023
|
+
"""
|
|
1024
|
+
Initialize and validate the Output instance.
|
|
1025
|
+
|
|
1026
|
+
This method performs two main tasks:
|
|
1027
|
+
1. Creates a deep copy of the options to preserve the original values
|
|
1028
|
+
2. Validates that the solution matches the specified output_format
|
|
300
1029
|
|
|
1030
|
+
Raises
|
|
1031
|
+
------
|
|
1032
|
+
ValueError
|
|
1033
|
+
If the solution is not compatible with the specified output_format.
|
|
1034
|
+
"""
|
|
301
1035
|
# Capture a snapshot of the options that were used to create the class
|
|
302
1036
|
# so even if they are changed later, we have a record of the original.
|
|
303
1037
|
init_options = self.options
|
|
304
1038
|
new_options = copy.deepcopy(init_options)
|
|
305
1039
|
self.options = new_options
|
|
306
1040
|
|
|
307
|
-
if self.solution is None:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
1041
|
+
if self.solution is not None:
|
|
1042
|
+
if self.output_format == OutputFormat.JSON:
|
|
1043
|
+
try:
|
|
1044
|
+
_ = serialize_json(self.solution)
|
|
1045
|
+
except (TypeError, OverflowError) as e:
|
|
1046
|
+
raise ValueError(
|
|
1047
|
+
f"Output has output_format OutputFormat.JSON and "
|
|
1048
|
+
f"Output.solution is of type {type(self.solution)}, which is not JSON serializable"
|
|
1049
|
+
) from e
|
|
1050
|
+
|
|
1051
|
+
elif self.output_format == OutputFormat.CSV_ARCHIVE and not isinstance(self.solution, dict):
|
|
314
1052
|
raise ValueError(
|
|
315
|
-
f"Output
|
|
316
|
-
|
|
317
|
-
)
|
|
1053
|
+
f"unsupported Output.solution type: {type(self.solution)} with "
|
|
1054
|
+
"output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
1055
|
+
)
|
|
318
1056
|
|
|
319
|
-
|
|
1057
|
+
if self.solution_files is not None and self.output_format != OutputFormat.MULTI_FILE:
|
|
320
1058
|
raise ValueError(
|
|
321
|
-
f"
|
|
322
|
-
"output_format
|
|
1059
|
+
f"`solution_files` are not `None`, but `output_format` is different from `OutputFormat.MULTI_FILE`: "
|
|
1060
|
+
f"{self.output_format}. If you want to use `solution_files`, set `output_format` "
|
|
1061
|
+
"to `OutputFormat.MULTI_FILE`."
|
|
1062
|
+
)
|
|
1063
|
+
elif self.solution_files is not None and not isinstance(self.solution_files, list):
|
|
1064
|
+
raise TypeError(
|
|
1065
|
+
f"unsupported Output.solution_files type: {type(self.solution_files)}, supported type is `list`"
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
def to_dict(self) -> dict[str, Any]: # noqa: C901
|
|
1069
|
+
"""
|
|
1070
|
+
Convert the `Output` object to a dictionary.
|
|
1071
|
+
|
|
1072
|
+
Returns
|
|
1073
|
+
-------
|
|
1074
|
+
dict[str, Any]
|
|
1075
|
+
The dictionary representation of the `Output` object.
|
|
1076
|
+
"""
|
|
1077
|
+
|
|
1078
|
+
# Options need to end up as a dict, so we achieve that based on the
|
|
1079
|
+
# type of options that were used to create the class.
|
|
1080
|
+
if self.options is None:
|
|
1081
|
+
options = {}
|
|
1082
|
+
elif isinstance(self.options, Options):
|
|
1083
|
+
options = self.options.to_dict()
|
|
1084
|
+
elif isinstance(self.options, dict):
|
|
1085
|
+
options = self.options
|
|
1086
|
+
else:
|
|
1087
|
+
raise TypeError(f"unsupported options type: {type(self.options)}, supported types are `Options` or `dict`")
|
|
1088
|
+
|
|
1089
|
+
# Statistics need to end up as a dict, so we achieve that based on the
|
|
1090
|
+
# type of statistics that were used to create the class.
|
|
1091
|
+
if self.statistics is None:
|
|
1092
|
+
statistics = {}
|
|
1093
|
+
elif isinstance(self.statistics, Statistics):
|
|
1094
|
+
statistics = self.statistics.to_dict()
|
|
1095
|
+
elif isinstance(self.statistics, dict):
|
|
1096
|
+
statistics = self.statistics
|
|
1097
|
+
else:
|
|
1098
|
+
raise TypeError(
|
|
1099
|
+
f"unsupported statistics type: {type(self.statistics)}, supported types are `Statistics` or `dict`"
|
|
323
1100
|
)
|
|
324
1101
|
|
|
1102
|
+
# Assets need to end up as a list of dicts, so we achieve that based on
|
|
1103
|
+
# the type of each asset in the list.
|
|
1104
|
+
assets = []
|
|
1105
|
+
if isinstance(self.assets, list):
|
|
1106
|
+
for ix, asset in enumerate(self.assets):
|
|
1107
|
+
if isinstance(asset, Asset):
|
|
1108
|
+
assets.append(asset.to_dict())
|
|
1109
|
+
elif isinstance(asset, dict):
|
|
1110
|
+
assets.append(asset)
|
|
1111
|
+
else:
|
|
1112
|
+
raise TypeError(
|
|
1113
|
+
f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`"
|
|
1114
|
+
)
|
|
1115
|
+
elif self.assets is not None:
|
|
1116
|
+
raise TypeError(f"unsupported assets type: {type(self.assets)}, supported types are `list`")
|
|
1117
|
+
|
|
1118
|
+
output_dict = {
|
|
1119
|
+
"options": options,
|
|
1120
|
+
"solution": self.solution if self.solution is not None else {},
|
|
1121
|
+
STATISTICS_KEY: statistics,
|
|
1122
|
+
ASSETS_KEY: assets,
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
# Add the auxiliary configurations to the output dictionary if they are
|
|
1126
|
+
# defined and not empty.
|
|
1127
|
+
if (
|
|
1128
|
+
self.output_format == OutputFormat.CSV_ARCHIVE
|
|
1129
|
+
and self.csv_configurations is not None
|
|
1130
|
+
and self.csv_configurations != {}
|
|
1131
|
+
):
|
|
1132
|
+
output_dict["csv_configurations"] = self.csv_configurations
|
|
1133
|
+
|
|
1134
|
+
return output_dict
|
|
1135
|
+
|
|
325
1136
|
|
|
326
1137
|
class OutputWriter:
|
|
327
|
-
"""
|
|
1138
|
+
"""
|
|
1139
|
+
Base class for writing outputs.
|
|
328
1140
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
1141
|
+
You can import the `OutputWriter` class directly from `nextmv`:
|
|
1142
|
+
|
|
1143
|
+
```python
|
|
1144
|
+
from nextmv import OutputWriter
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
This is an abstract base class that defines the interface for writing outputs
|
|
1148
|
+
to different destinations. Subclasses should implement the `write` method.
|
|
1149
|
+
|
|
1150
|
+
Examples
|
|
1151
|
+
--------
|
|
1152
|
+
>>> class CustomOutputWriter(OutputWriter):
|
|
1153
|
+
... def write(self, output, path=None, **kwargs):
|
|
1154
|
+
... # Custom implementation for writing output
|
|
1155
|
+
... print(f"Writing output to {path}")
|
|
1156
|
+
"""
|
|
1157
|
+
|
|
1158
|
+
def write(self, output: Output | dict[str, Any] | BaseModel, *args, **kwargs) -> None:
|
|
332
1159
|
"""
|
|
1160
|
+
Write the output data.
|
|
1161
|
+
|
|
1162
|
+
This is an abstract method that should be implemented by subclasses.
|
|
333
1163
|
|
|
1164
|
+
Parameters
|
|
1165
|
+
----------
|
|
1166
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1167
|
+
The output data to write.
|
|
1168
|
+
*args
|
|
1169
|
+
Variable length argument list.
|
|
1170
|
+
**kwargs
|
|
1171
|
+
Arbitrary keyword arguments.
|
|
1172
|
+
|
|
1173
|
+
Raises
|
|
1174
|
+
------
|
|
1175
|
+
NotImplementedError
|
|
1176
|
+
This method must be implemented by subclasses.
|
|
1177
|
+
"""
|
|
334
1178
|
raise NotImplementedError
|
|
335
1179
|
|
|
336
1180
|
|
|
337
1181
|
class LocalOutputWriter(OutputWriter):
|
|
338
1182
|
"""
|
|
339
|
-
Class for
|
|
340
|
-
|
|
1183
|
+
Class for writing outputs to local files or stdout.
|
|
1184
|
+
|
|
1185
|
+
You can import the `LocalOutputWriter` class directly from `nextmv`:
|
|
1186
|
+
|
|
1187
|
+
```python
|
|
1188
|
+
from nextmv import LocalOutputWriter
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
This class implements the OutputWriter interface to write output data to
|
|
1192
|
+
local files or stdout. The destination and format depend on the output
|
|
1193
|
+
format and the provided path.
|
|
1194
|
+
|
|
1195
|
+
Examples
|
|
1196
|
+
--------
|
|
1197
|
+
>>> from nextmv.output import LocalOutputWriter, Output, Statistics
|
|
1198
|
+
>>> writer = LocalOutputWriter()
|
|
1199
|
+
>>> output = Output(solution={"result": 42}, statistics=Statistics())
|
|
1200
|
+
>>> # Write to stdout
|
|
1201
|
+
>>> writer.write(output, path=None)
|
|
1202
|
+
>>> # Write to a file
|
|
1203
|
+
>>> writer.write(output, path="results.json")
|
|
341
1204
|
"""
|
|
342
1205
|
|
|
343
1206
|
def _write_json(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
path: Optional[str] = None,
|
|
1207
|
+
self,
|
|
1208
|
+
output: Output | dict[str, Any] | BaseModel,
|
|
1209
|
+
output_dict: dict[str, Any],
|
|
1210
|
+
path: str | None = None,
|
|
349
1211
|
) -> None:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1212
|
+
"""
|
|
1213
|
+
Write output in JSON format.
|
|
1214
|
+
|
|
1215
|
+
Parameters
|
|
1216
|
+
----------
|
|
1217
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1218
|
+
The output object containing configuration.
|
|
1219
|
+
output_dict : dict[str, Any]
|
|
1220
|
+
Dictionary representation of the output to write.
|
|
1221
|
+
path : str, optional
|
|
1222
|
+
Path to write the output. If None or empty, writes to stdout.
|
|
1223
|
+
"""
|
|
1224
|
+
json_configurations = {}
|
|
1225
|
+
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
1226
|
+
json_configurations = output.json_configurations
|
|
1227
|
+
|
|
1228
|
+
serialized = serialize_json(
|
|
1229
|
+
output_dict,
|
|
1230
|
+
json_configurations=json_configurations,
|
|
365
1231
|
)
|
|
366
1232
|
|
|
367
1233
|
if path is None or path == "":
|
|
@@ -372,12 +1238,29 @@ class LocalOutputWriter(OutputWriter):
|
|
|
372
1238
|
file.write(serialized + "\n")
|
|
373
1239
|
|
|
374
1240
|
def _write_archive(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
path: Optional[str] = None,
|
|
1241
|
+
self,
|
|
1242
|
+
output: Output | dict[str, Any] | BaseModel,
|
|
1243
|
+
output_dict: dict[str, Any],
|
|
1244
|
+
path: str | None = None,
|
|
380
1245
|
) -> None:
|
|
1246
|
+
"""
|
|
1247
|
+
Write output in CSV archive format.
|
|
1248
|
+
|
|
1249
|
+
Parameters
|
|
1250
|
+
----------
|
|
1251
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1252
|
+
The output object containing configuration and solution data.
|
|
1253
|
+
output_dict : dict[str, Any]
|
|
1254
|
+
Dictionary representation of the output to write.
|
|
1255
|
+
path : str, optional
|
|
1256
|
+
Directory path to write the CSV files. If None or empty,
|
|
1257
|
+
writes to a directory named "output" in the current working directory.
|
|
1258
|
+
|
|
1259
|
+
Raises
|
|
1260
|
+
------
|
|
1261
|
+
ValueError
|
|
1262
|
+
If the path is an existing file instead of a directory.
|
|
1263
|
+
"""
|
|
381
1264
|
dir_path = "output"
|
|
382
1265
|
if path is not None and path != "":
|
|
383
1266
|
if os.path.isfile(path):
|
|
@@ -388,13 +1271,17 @@ class LocalOutputWriter(OutputWriter):
|
|
|
388
1271
|
if not os.path.exists(dir_path):
|
|
389
1272
|
os.makedirs(dir_path)
|
|
390
1273
|
|
|
391
|
-
|
|
1274
|
+
json_configurations = {}
|
|
1275
|
+
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
1276
|
+
json_configurations = output.json_configurations
|
|
1277
|
+
|
|
1278
|
+
serialized = serialize_json(
|
|
392
1279
|
{
|
|
393
|
-
"options": options,
|
|
394
|
-
|
|
395
|
-
|
|
1280
|
+
"options": output_dict.get("options", {}),
|
|
1281
|
+
STATISTICS_KEY: output_dict.get(STATISTICS_KEY, {}),
|
|
1282
|
+
ASSETS_KEY: output_dict.get(ASSETS_KEY, []),
|
|
396
1283
|
},
|
|
397
|
-
|
|
1284
|
+
json_configurations=json_configurations,
|
|
398
1285
|
)
|
|
399
1286
|
print(serialized, file=sys.stdout)
|
|
400
1287
|
|
|
@@ -416,53 +1303,183 @@ class LocalOutputWriter(OutputWriter):
|
|
|
416
1303
|
writer.writeheader()
|
|
417
1304
|
writer.writerows(data)
|
|
418
1305
|
|
|
1306
|
+
def _write_multi_file(
|
|
1307
|
+
self,
|
|
1308
|
+
output: Output | dict[str, Any] | BaseModel,
|
|
1309
|
+
output_dict: dict[str, Any],
|
|
1310
|
+
path: str | None = None,
|
|
1311
|
+
) -> None:
|
|
1312
|
+
"""
|
|
1313
|
+
Write output to multiple files.
|
|
1314
|
+
|
|
1315
|
+
Parameters
|
|
1316
|
+
----------
|
|
1317
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1318
|
+
The output object containing configuration and solution data.
|
|
1319
|
+
output_dict : dict[str, Any]
|
|
1320
|
+
Dictionary representation of the output to write.
|
|
1321
|
+
path : str, optional
|
|
1322
|
+
Directory path to write the CSV files. If None or empty,
|
|
1323
|
+
writes to a directory named "output" in the current working directory.
|
|
1324
|
+
|
|
1325
|
+
Raises
|
|
1326
|
+
------
|
|
1327
|
+
ValueError
|
|
1328
|
+
If the path is an existing file instead of a directory.
|
|
1329
|
+
"""
|
|
1330
|
+
dir_path = OUTPUTS_KEY
|
|
1331
|
+
if path is not None and path != "":
|
|
1332
|
+
if os.path.isfile(path):
|
|
1333
|
+
raise ValueError(f"The path refers to an existing file: {path}")
|
|
1334
|
+
|
|
1335
|
+
dir_path = path
|
|
1336
|
+
|
|
1337
|
+
if not os.path.exists(dir_path):
|
|
1338
|
+
os.makedirs(dir_path)
|
|
1339
|
+
|
|
1340
|
+
json_configurations = {}
|
|
1341
|
+
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
1342
|
+
json_configurations = output.json_configurations
|
|
1343
|
+
|
|
1344
|
+
self._write_multi_file_element(
|
|
1345
|
+
parent_dir=dir_path,
|
|
1346
|
+
json_configurations=json_configurations,
|
|
1347
|
+
output_dict=output_dict,
|
|
1348
|
+
element_key=STATISTICS_KEY,
|
|
1349
|
+
)
|
|
1350
|
+
self._write_multi_file_element(
|
|
1351
|
+
parent_dir=dir_path,
|
|
1352
|
+
json_configurations=json_configurations,
|
|
1353
|
+
output_dict=output_dict,
|
|
1354
|
+
element_key=ASSETS_KEY,
|
|
1355
|
+
)
|
|
1356
|
+
self._write_multi_file_solution(dir_path=dir_path, output=output)
|
|
1357
|
+
|
|
1358
|
+
def _write_multi_file_element(
|
|
1359
|
+
self,
|
|
1360
|
+
parent_dir: str,
|
|
1361
|
+
output_dict: dict[str, Any],
|
|
1362
|
+
element_key: str,
|
|
1363
|
+
json_configurations: dict[str, Any] | None = None,
|
|
1364
|
+
):
|
|
1365
|
+
"""
|
|
1366
|
+
Auxiliary function to write a specific element of the output
|
|
1367
|
+
dictionary to a file in the specified parent directory.
|
|
1368
|
+
"""
|
|
1369
|
+
|
|
1370
|
+
element = output_dict.get(element_key)
|
|
1371
|
+
if element is None or not element:
|
|
1372
|
+
return
|
|
1373
|
+
|
|
1374
|
+
final_dir = os.path.join(parent_dir, element_key)
|
|
1375
|
+
|
|
1376
|
+
if not os.path.exists(final_dir):
|
|
1377
|
+
os.makedirs(final_dir)
|
|
1378
|
+
|
|
1379
|
+
keyed_element = {element_key: element} # The element is expected behind its key.
|
|
1380
|
+
|
|
1381
|
+
serialized = serialize_json(keyed_element, json_configurations=json_configurations)
|
|
1382
|
+
|
|
1383
|
+
with open(os.path.join(final_dir, f"{element_key}.json"), "w", encoding="utf-8") as file:
|
|
1384
|
+
file.write(serialized + "\n")
|
|
1385
|
+
|
|
1386
|
+
def _write_multi_file_solution(
|
|
1387
|
+
self,
|
|
1388
|
+
dir_path: str,
|
|
1389
|
+
output: Output,
|
|
1390
|
+
):
|
|
1391
|
+
"""
|
|
1392
|
+
Auxiliary function to write the solution files to the specified
|
|
1393
|
+
directory.
|
|
1394
|
+
"""
|
|
1395
|
+
|
|
1396
|
+
if output.solution_files is None:
|
|
1397
|
+
return
|
|
1398
|
+
|
|
1399
|
+
solutions_dir = os.path.join(dir_path, SOLUTIONS_KEY)
|
|
1400
|
+
|
|
1401
|
+
if not os.path.exists(solutions_dir):
|
|
1402
|
+
os.makedirs(solutions_dir)
|
|
1403
|
+
|
|
1404
|
+
for solution_file in output.solution_files:
|
|
1405
|
+
if not isinstance(solution_file, SolutionFile):
|
|
1406
|
+
raise TypeError(
|
|
1407
|
+
f"unsupported solution_file type: {type(solution_file)}, supported type is `SolutionFile`"
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
file_path = os.path.join(solutions_dir, solution_file.name)
|
|
1411
|
+
if solution_file.writer_args is None:
|
|
1412
|
+
solution_file.writer_args = []
|
|
1413
|
+
if solution_file.writer_kwargs is None:
|
|
1414
|
+
solution_file.writer_kwargs = {}
|
|
1415
|
+
|
|
1416
|
+
# Call the writer function with the final path, and user provided
|
|
1417
|
+
# arguments and keyword arguments.
|
|
1418
|
+
solution_file.writer(
|
|
1419
|
+
file_path,
|
|
1420
|
+
solution_file.data,
|
|
1421
|
+
*solution_file.writer_args,
|
|
1422
|
+
**solution_file.writer_kwargs,
|
|
1423
|
+
)
|
|
1424
|
+
|
|
419
1425
|
# Callback functions for writing the output data.
|
|
420
1426
|
FILE_WRITERS = {
|
|
421
1427
|
OutputFormat.JSON: _write_json,
|
|
422
1428
|
OutputFormat.CSV_ARCHIVE: _write_archive,
|
|
1429
|
+
OutputFormat.MULTI_FILE: _write_multi_file,
|
|
1430
|
+
OutputFormat.TEXT: _write_json,
|
|
423
1431
|
}
|
|
1432
|
+
"""Dictionary mapping output formats to writer functions."""
|
|
424
1433
|
|
|
425
1434
|
def write(
|
|
426
1435
|
self,
|
|
427
|
-
output:
|
|
428
|
-
path:
|
|
1436
|
+
output: Output | dict[str, Any] | BaseModel,
|
|
1437
|
+
path: str | None = None,
|
|
429
1438
|
skip_stdout_reset: bool = False,
|
|
430
1439
|
) -> None:
|
|
431
1440
|
"""
|
|
432
|
-
Write the
|
|
433
|
-
the `path` parameter, depending on the `Output.output_format`:
|
|
434
|
-
|
|
435
|
-
- `OutputFormat.JSON`: the `path` is the file where the JSON data will
|
|
436
|
-
be written. If empty or `None`, the data will be written to stdout.
|
|
437
|
-
- `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
|
|
438
|
-
files will be written. If empty or `None`, the data will be written
|
|
439
|
-
to a directory named `output` under the current working directory.
|
|
440
|
-
The `Output.options` and `Output.statistics` will be written to
|
|
441
|
-
stdout.
|
|
1441
|
+
Write the output to the local filesystem or stdout.
|
|
442
1442
|
|
|
443
|
-
This
|
|
444
|
-
|
|
445
|
-
`skip_stdout_reset` parameter to `True`.
|
|
446
|
-
|
|
447
|
-
If the `output` is a `dict`, it will be simply written to the specified
|
|
448
|
-
`path`, as a passthrough. On the other hand, if the `output` is of type
|
|
449
|
-
`Output`, a more structured object will be written, which adheres to
|
|
450
|
-
the schema specified by the corresponding `Output` class.
|
|
1443
|
+
This method writes the provided output to the specified path or to stdout,
|
|
1444
|
+
depending on the output format and the path parameter.
|
|
451
1445
|
|
|
452
1446
|
Parameters
|
|
453
1447
|
----------
|
|
454
|
-
output: Output, dict[str, Any]
|
|
455
|
-
Output data to write.
|
|
456
|
-
path : str
|
|
457
|
-
Path to write the output data to.
|
|
1448
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1449
|
+
Output data to write. Can be an Output object, a dictionary, or a BaseModel.
|
|
1450
|
+
path : str, optional
|
|
1451
|
+
Path to write the output data to. The interpretation depends on the output format:
|
|
1452
|
+
- For OutputFormat.JSON: File path for the JSON output. If None or empty, writes to stdout.
|
|
1453
|
+
- For OutputFormat.CSV_ARCHIVE: Directory path for CSV files. If None or empty,
|
|
1454
|
+
writes to a directory named "output" in the current working directory.
|
|
458
1455
|
skip_stdout_reset : bool, optional
|
|
459
|
-
Skip resetting stdout before writing the output data. Default is
|
|
460
|
-
`False`.
|
|
1456
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
461
1457
|
|
|
462
1458
|
Raises
|
|
463
1459
|
------
|
|
464
1460
|
ValueError
|
|
465
|
-
If the
|
|
1461
|
+
If the Output.output_format is not supported.
|
|
1462
|
+
TypeError
|
|
1463
|
+
If the output is of an unsupported type.
|
|
1464
|
+
|
|
1465
|
+
Notes
|
|
1466
|
+
-----
|
|
1467
|
+
This function detects if stdout was redirected and resets it to avoid
|
|
1468
|
+
unexpected behavior. If you want to skip this behavior, set the
|
|
1469
|
+
skip_stdout_reset parameter to True.
|
|
1470
|
+
|
|
1471
|
+
If the output is a dict or a BaseModel, it will be written as JSON. If
|
|
1472
|
+
the output is an Output object, it will be written according to its
|
|
1473
|
+
output_format.
|
|
1474
|
+
|
|
1475
|
+
Examples
|
|
1476
|
+
--------
|
|
1477
|
+
>>> from nextmv.output import LocalOutputWriter, Output
|
|
1478
|
+
>>> writer = LocalOutputWriter()
|
|
1479
|
+
>>> # Write JSON to a file
|
|
1480
|
+
>>> writer.write(Output(solution={"result": 42}), path="result.json")
|
|
1481
|
+
>>> # Write JSON to stdout
|
|
1482
|
+
>>> writer.write({"simple": "data"})
|
|
466
1483
|
"""
|
|
467
1484
|
|
|
468
1485
|
# If the user forgot to reset stdout after redirecting it, we need to
|
|
@@ -474,140 +1491,150 @@ class LocalOutputWriter(OutputWriter):
|
|
|
474
1491
|
output_format = output.output_format
|
|
475
1492
|
elif isinstance(output, dict):
|
|
476
1493
|
output_format = OutputFormat.JSON
|
|
1494
|
+
elif isinstance(output, BaseModel):
|
|
1495
|
+
output_format = OutputFormat.JSON
|
|
477
1496
|
else:
|
|
478
|
-
raise TypeError(
|
|
1497
|
+
raise TypeError(
|
|
1498
|
+
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
1499
|
+
)
|
|
479
1500
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1501
|
+
output_dict = {}
|
|
1502
|
+
if isinstance(output, Output):
|
|
1503
|
+
output_dict = output.to_dict()
|
|
1504
|
+
elif isinstance(output, BaseModel):
|
|
1505
|
+
output_dict = output.to_dict()
|
|
1506
|
+
elif isinstance(output, dict):
|
|
1507
|
+
output_dict = output
|
|
1508
|
+
else:
|
|
1509
|
+
raise TypeError(
|
|
1510
|
+
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
1511
|
+
)
|
|
483
1512
|
|
|
484
1513
|
self.FILE_WRITERS[output_format](
|
|
1514
|
+
self,
|
|
485
1515
|
output=output,
|
|
486
|
-
|
|
487
|
-
statistics=statistics,
|
|
488
|
-
assets=assets,
|
|
1516
|
+
output_dict=output_dict,
|
|
489
1517
|
path=path,
|
|
490
1518
|
)
|
|
491
1519
|
|
|
492
|
-
@staticmethod
|
|
493
|
-
def _extract_statistics(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
494
|
-
"""Extract JSON-serializable statistics."""
|
|
495
1520
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return statistics
|
|
505
|
-
|
|
506
|
-
if isinstance(stats, Statistics):
|
|
507
|
-
statistics = stats.to_dict()
|
|
508
|
-
elif isinstance(stats, dict):
|
|
509
|
-
statistics = stats
|
|
510
|
-
else:
|
|
511
|
-
raise TypeError(f"unsupported statistics type: {type(stats)}, supported types are `Statistics` or `dict`")
|
|
512
|
-
|
|
513
|
-
return statistics
|
|
514
|
-
|
|
515
|
-
@staticmethod
|
|
516
|
-
def _extract_options(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
517
|
-
"""Extract JSON-serializable options."""
|
|
518
|
-
|
|
519
|
-
options = {}
|
|
520
|
-
|
|
521
|
-
if not isinstance(output, Output):
|
|
522
|
-
return options
|
|
523
|
-
|
|
524
|
-
opt = output.options
|
|
525
|
-
|
|
526
|
-
if opt is None:
|
|
527
|
-
return options
|
|
1521
|
+
def write_local(
|
|
1522
|
+
output: Output | dict[str, Any],
|
|
1523
|
+
path: str | None = None,
|
|
1524
|
+
skip_stdout_reset: bool = False,
|
|
1525
|
+
) -> None:
|
|
1526
|
+
"""
|
|
1527
|
+
!!! warning
|
|
1528
|
+
`write_local` is deprecated, use `write` instead.
|
|
528
1529
|
|
|
529
|
-
|
|
530
|
-
options = opt.to_dict()
|
|
531
|
-
elif isinstance(opt, dict):
|
|
532
|
-
options = opt
|
|
533
|
-
else:
|
|
534
|
-
raise TypeError(f"unsupported options type: {type(opt)}, supported types are `Options` or `dict`")
|
|
1530
|
+
Write the output to the local filesystem or stdout.
|
|
535
1531
|
|
|
536
|
-
|
|
1532
|
+
This is a convenience function for instantiating a `LocalOutputWriter` and
|
|
1533
|
+
calling its `write` method.
|
|
537
1534
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1535
|
+
Parameters
|
|
1536
|
+
----------
|
|
1537
|
+
output : Union[Output, dict[str, Any]]
|
|
1538
|
+
Output data to write. Can be an Output object or a dictionary.
|
|
1539
|
+
path : str, optional
|
|
1540
|
+
Path to write the output data to. The interpretation depends on the
|
|
1541
|
+
output format:
|
|
1542
|
+
|
|
1543
|
+
- For `OutputFormat.JSON`: File path for the JSON output. If None or
|
|
1544
|
+
empty, writes to stdout.
|
|
1545
|
+
- For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
|
|
1546
|
+
or empty, writes to a directory named "output" in the current working
|
|
1547
|
+
directory.
|
|
1548
|
+
skip_stdout_reset : bool, optional
|
|
1549
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
541
1550
|
|
|
542
|
-
|
|
1551
|
+
Raises
|
|
1552
|
+
------
|
|
1553
|
+
ValueError
|
|
1554
|
+
If the Output.output_format is not supported.
|
|
1555
|
+
TypeError
|
|
1556
|
+
If the output is of an unsupported type.
|
|
543
1557
|
|
|
544
|
-
|
|
545
|
-
|
|
1558
|
+
Notes
|
|
1559
|
+
-----
|
|
1560
|
+
This function detects if stdout was redirected and resets it to avoid
|
|
1561
|
+
unexpected behavior. If you want to skip this behavior, set the
|
|
1562
|
+
skip_stdout_reset parameter to True.
|
|
1563
|
+
|
|
1564
|
+
Examples
|
|
1565
|
+
--------
|
|
1566
|
+
>>> from nextmv.output import write_local, Output
|
|
1567
|
+
>>> # Write JSON to a file
|
|
1568
|
+
>>> write_local(Output(solution={"result": 42}), path="result.json")
|
|
1569
|
+
>>> # Write JSON to stdout
|
|
1570
|
+
>>> write_local({"simple": "data"})
|
|
1571
|
+
"""
|
|
546
1572
|
|
|
547
|
-
|
|
1573
|
+
deprecated(
|
|
1574
|
+
name="write_local",
|
|
1575
|
+
reason="`write_local` is deprecated, use `write` instead",
|
|
1576
|
+
)
|
|
548
1577
|
|
|
549
|
-
|
|
550
|
-
|
|
1578
|
+
writer = LocalOutputWriter()
|
|
1579
|
+
writer.write(output, path, skip_stdout_reset)
|
|
551
1580
|
|
|
552
|
-
for ix, asset in enumerate(assts):
|
|
553
|
-
if isinstance(asset, Asset):
|
|
554
|
-
assets.append(asset.to_dict())
|
|
555
|
-
elif isinstance(asset, dict):
|
|
556
|
-
assets.append(asset)
|
|
557
|
-
else:
|
|
558
|
-
raise TypeError(f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`")
|
|
559
1581
|
|
|
560
|
-
|
|
1582
|
+
_LOCAL_OUTPUT_WRITER = LocalOutputWriter()
|
|
1583
|
+
"""Default LocalOutputWriter instance used by the write function."""
|
|
561
1584
|
|
|
562
1585
|
|
|
563
|
-
def
|
|
564
|
-
output:
|
|
565
|
-
path:
|
|
1586
|
+
def write(
|
|
1587
|
+
output: Output | dict[str, Any] | BaseModel,
|
|
1588
|
+
path: str | None = None,
|
|
566
1589
|
skip_stdout_reset: bool = False,
|
|
1590
|
+
writer: OutputWriter | None = _LOCAL_OUTPUT_WRITER,
|
|
567
1591
|
) -> None:
|
|
568
1592
|
"""
|
|
569
|
-
|
|
570
|
-
calling its `write` method.
|
|
1593
|
+
Write the output to the specified destination.
|
|
571
1594
|
|
|
572
|
-
|
|
573
|
-
`path` parameter, depending on the `Output.output_format`:
|
|
1595
|
+
You can import the `write` function directly from `nextmv`:
|
|
574
1596
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
files will be written. If empty or `None`, the data will be written
|
|
579
|
-
to a directory named `output` under the current working directory.
|
|
580
|
-
The `Output.options` and `Output.statistics` will be written to
|
|
581
|
-
stdout.
|
|
1597
|
+
```python
|
|
1598
|
+
from nextmv import write
|
|
1599
|
+
```
|
|
582
1600
|
|
|
583
|
-
This
|
|
584
|
-
|
|
585
|
-
`skip_stdout_reset` parameter to `True`.
|
|
1601
|
+
This is a convenience function for writing output data using a provided writer.
|
|
1602
|
+
By default, it uses the `LocalOutputWriter` to write to files or stdout.
|
|
586
1603
|
|
|
587
1604
|
Parameters
|
|
588
1605
|
----------
|
|
589
|
-
output : Output, dict[str, Any]
|
|
590
|
-
Output data to write.
|
|
591
|
-
path : str
|
|
592
|
-
Path to write the output data to.
|
|
1606
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1607
|
+
Output data to write. Can be an Output object, a dictionary, or a BaseModel.
|
|
1608
|
+
path : str, optional
|
|
1609
|
+
Path to write the output data to. The interpretation depends on the
|
|
1610
|
+
output format:
|
|
1611
|
+
|
|
1612
|
+
- For `OutputFormat.JSON`: File path for the JSON output. If None or
|
|
1613
|
+
empty, writes to stdout.
|
|
1614
|
+
- For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
|
|
1615
|
+
or empty, writes to a directory named "output" in the current working
|
|
1616
|
+
directory.
|
|
593
1617
|
skip_stdout_reset : bool, optional
|
|
594
|
-
Skip resetting stdout before writing the output data. Default is
|
|
595
|
-
|
|
1618
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
1619
|
+
writer : OutputWriter, optional
|
|
1620
|
+
The writer to use for writing the output. Default is a
|
|
1621
|
+
`LocalOutputWriter` instance.
|
|
596
1622
|
|
|
597
1623
|
Raises
|
|
598
1624
|
------
|
|
599
1625
|
ValueError
|
|
600
|
-
If the
|
|
1626
|
+
If the Output.output_format is not supported.
|
|
1627
|
+
TypeError
|
|
1628
|
+
If the output is of an unsupported type.
|
|
1629
|
+
|
|
1630
|
+
Examples
|
|
1631
|
+
--------
|
|
1632
|
+
>>> from nextmv.output import write, Output, OutputFormat
|
|
1633
|
+
>>> # Write JSON to a file
|
|
1634
|
+
>>> write(Output(solution={"result": 42}), path="result.json")
|
|
1635
|
+
>>> # Write CSV archive
|
|
1636
|
+
>>> data = {"vehicles": [{"id": 1, "capacity": 100}, {"id": 2, "capacity": 150}]}
|
|
1637
|
+
>>> write(Output(output_format=OutputFormat.CSV_ARCHIVE, solution=data), path="output_dir")
|
|
601
1638
|
"""
|
|
602
1639
|
|
|
603
|
-
writer = LocalOutputWriter()
|
|
604
1640
|
writer.write(output, path, skip_stdout_reset)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
def _custom_serial(obj: Any):
|
|
608
|
-
"""JSON serializer for objects not serializable by default one."""
|
|
609
|
-
|
|
610
|
-
if isinstance(obj, (datetime.datetime | datetime.date)):
|
|
611
|
-
return obj.isoformat()
|
|
612
|
-
|
|
613
|
-
raise TypeError(f"Type {type(obj)} not serializable")
|