nextmv 0.27.0__py3-none-any.whl → 0.28.1.dev0__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/output.py CHANGED
@@ -1,4 +1,44 @@
1
- """Module for handling output destinations and data."""
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 a series.
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
- Statistics of a solution.
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
- """Format of an `Input`."""
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
- """Schema of a visual asset."""
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 of an asset that defines how it is plotted in the Nextmv
189
- Console.
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
- An asset represents downloadable information that is part of the `Output`.
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,80 +502,68 @@ class Asset(BaseModel):
239
502
  @dataclass
240
503
  class Output:
241
504
  """
242
- Output of a decision problem. This class is used to be later be written to
243
- some location.
244
-
245
- The output can be in different formats, such as JSON (default) or
246
- CSV_ARCHIVE.
247
-
248
- If you used options, you can also include them in the output, to be
249
- serialized to the write location.
505
+ Output of a decision problem.
250
506
 
251
- The most important part of the output is the solution, which represents the
252
- result of the decision problem. The solution's type must match the
253
- `output_format`:
507
+ You can import the `Output` class directly from `nextmv`:
254
508
 
255
- - `OutputFormat.JSON`: the data must be `dict[str, Any]`, or `Any`.
256
- - `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
257
- Any]]]`. The keys represent the file names where the data should be
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.
509
+ ```python
510
+ from nextmv import Output
511
+ ```
265
512
 
266
- The assets are used to keep track of different downloadable information that
267
- is part of the output. The assets can be of type `Asset` or a simple
268
- dictionary, but we recommend using the `Asset` class to ensure that the data is
269
- 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.
270
516
 
271
- Attributes
517
+ Parameters
272
518
  ----------
273
- options : Optional[Union[Options, dict[str, Any]]]
519
+ options : Optional[Union[Options, dict[str, Any]]], optional
274
520
  Options that the `Output` was created with. These options can be of type
275
- `Options` or a simple dictionary. If the options are of type `Options`,
276
- they will be serialized to a dictionary using the `to_dict` method. If
277
- they are a dictionary, they will be used as is. If the options are not
278
- provided, an empty dictionary will be used. If the options are of type
279
- `dict`, then the dictionary should have the following structure:
280
- ```
281
- {
282
- "duration": "30",
283
- "threads": 4,
284
- }
285
- ```
286
- output_format : Optional[OutputFormat]
521
+ `Options` or a simple dictionary. Default is None.
522
+ output_format : Optional[OutputFormat], optional
287
523
  Format of the output data. Default is `OutputFormat.JSON`.
288
- solution : Optional[Union[dict[str, Any], dict[str, list[dict[str, Any]]]]
524
+ solution : Optional[Union[dict[str, Any], Any, dict[str, list[dict[str, Any]]]]], optional
289
525
  The solution to the decision problem. The type must match the
290
- `output_format`:
291
- - `OutputFormat.JSON`: the data must be `dict[str, Any]`.
292
- - `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str,
293
- list[dict[str, Any]]]`. The keys represent the file names where the
294
- data should be written. The values are lists of dictionaries, where
295
- each dictionary represents a row in the CSV file.
296
- statistics : Optional[Union[Statistics, dict[str, Any]]]
297
- Statistics of the solution. These statistics can be of type
298
- `Statistics` or a simple dictionary. If the statistics are of type
299
- `Statistics`, they will be serialized to a dictionary using the
300
- `to_dict` method. If they are a dictionary, they will be used as is. If
301
- the statistics are not provided, an empty dictionary will be used.
302
- csv_configurations : Optional[dict[str, Any]]
303
- Optional configuration for writing CSV files, to be used when the
304
- `output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
305
- passed as kwargs to the `DictWriter` class from the `csv` module.
306
- json_configurations : Optional[dict[str, Any]]
307
- Optional configuration for writing JSON files, to be used when the
308
- `output_format` is `OutputFormat.JSON`. These configurations are passed
309
- as kwargs to the `json.dumps` function.
310
- assets : Optional[list[Union[Asset, dict[str, Any]]]]
311
- Optional list of assets to be included in the output. These assets can
312
- be of type `Asset` or a simple dictionary. If the assets are of type
313
- `Asset`, they will be serialized to a dictionary using the `to_dict`
314
- method. If they are a dictionary, they will be used as is. If the
315
- assets are not provided, an empty list will be used.
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
316
567
  """
317
568
 
318
569
  options: Optional[Union[Options, dict[str, Any]]] = None
@@ -323,7 +574,8 @@ class Output:
323
574
  they are a dictionary, they will be used as is. If the options are not
324
575
  provided, an empty dictionary will be used. If the options are of type
325
576
  `dict`, then the dictionary should have the following structure:
326
- ```
577
+
578
+ ```python
327
579
  {
328
580
  "duration": "30",
329
581
  "threads": 4,
@@ -369,9 +621,18 @@ class Output:
369
621
  """
370
622
 
371
623
  def __post_init__(self):
372
- """Check that the solution matches the format given to initialize the
373
- class."""
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
374
630
 
631
+ Raises
632
+ ------
633
+ ValueError
634
+ If the solution is not compatible with the specified output_format.
635
+ """
375
636
  # Capture a snapshot of the options that were used to create the class
376
637
  # so even if they are changed later, we have a record of the original.
377
638
  init_options = self.options
@@ -472,20 +733,72 @@ class Output:
472
733
 
473
734
 
474
735
  class OutputWriter:
475
- """Base class for writing outputs."""
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
+ """
476
755
 
477
756
  def write(self, output: Union[Output, dict[str, Any], BaseModel], *args, **kwargs) -> None:
478
757
  """
479
- Write the output data. This method should be implemented by subclasses.
480
- """
758
+ Write the output data.
481
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.
770
+
771
+ Raises
772
+ ------
773
+ NotImplementedError
774
+ This method must be implemented by subclasses.
775
+ """
482
776
  raise NotImplementedError
483
777
 
484
778
 
485
779
  class LocalOutputWriter(OutputWriter):
486
780
  """
487
- Class for write outputs to local files or stdout. Call the `write` method
488
- to write the output data.
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")
489
802
  """
490
803
 
491
804
  def _write_json(
@@ -493,6 +806,18 @@ class LocalOutputWriter(OutputWriter):
493
806
  output_dict: dict[str, Any],
494
807
  path: Optional[str] = None,
495
808
  ) -> None:
809
+ """
810
+ Write output in JSON format.
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
+ """
496
821
  json_configurations = {}
497
822
  if hasattr(output, "json_configurations") and output.json_configurations is not None:
498
823
  json_configurations = output.json_configurations
@@ -524,6 +849,24 @@ class LocalOutputWriter(OutputWriter):
524
849
  output_dict: dict[str, Any],
525
850
  path: Optional[str] = None,
526
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
+ """
527
870
  dir_path = "output"
528
871
  if path is not None and path != "":
529
872
  if os.path.isfile(path):
@@ -567,6 +910,7 @@ class LocalOutputWriter(OutputWriter):
567
910
  OutputFormat.JSON: _write_json,
568
911
  OutputFormat.CSV_ARCHIVE: _write_archive,
569
912
  }
913
+ """Dictionary mapping output formats to writer functions."""
570
914
 
571
915
  def write(
572
916
  self,
@@ -575,40 +919,48 @@ class LocalOutputWriter(OutputWriter):
575
919
  skip_stdout_reset: bool = False,
576
920
  ) -> None:
577
921
  """
578
- Write the `output` to the local filesystem. Consider the following for
579
- the `path` parameter, depending on the `Output.output_format`:
922
+ Write the output to the local filesystem or stdout.
580
923
 
581
- - `OutputFormat.JSON`: the `path` is the file where the JSON data will
582
- be written. If empty or `None`, the data will be written to stdout.
583
- - `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
584
- files will be written. If empty or `None`, the data will be written
585
- to a directory named `output` under the current working directory.
586
- The `Output.options` and `Output.statistics` will be written to
587
- stdout.
588
-
589
- This function detects if stdout was redirected and resets it to avoid
590
- unexpected behavior. If you want to skip this behavior, set the
591
- `skip_stdout_reset` parameter to `True`.
592
-
593
- If the `output` is a `dict`, it will be simply written to the specified
594
- `path`, as a passthrough. On the other hand, if the `output` is of type
595
- `Output`, a more structured object will be written, which adheres to
596
- 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.
597
926
 
598
927
  Parameters
599
928
  ----------
600
- output: Output, dict[str, Any]
601
- Output data to write.
602
- path : str
603
- 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.
604
936
  skip_stdout_reset : bool, optional
605
- Skip resetting stdout before writing the output data. Default is
606
- `False`.
937
+ Skip resetting stdout before writing the output data. Default is False.
607
938
 
608
939
  Raises
609
940
  ------
610
941
  ValueError
611
- If the `Output.output_format` is not supported.
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"})
612
964
  """
613
965
 
614
966
  # If the user forgot to reset stdout after redirecting it, we need to
@@ -652,42 +1004,50 @@ def write_local(
652
1004
  skip_stdout_reset: bool = False,
653
1005
  ) -> None:
654
1006
  """
655
- DEPRECATION WARNING
656
- ----------
657
- `write_local` is deprecated, use `write` instead.
1007
+ !!! warning
1008
+ `write_local` is deprecated, use `write` instead.
1009
+
1010
+ Write the output to the local filesystem or stdout.
658
1011
 
659
1012
  This is a convenience function for instantiating a `LocalOutputWriter` and
660
1013
  calling its `write` method.
661
1014
 
662
- Write the `output` to the local filesystem. Consider the following for the
663
- `path` parameter, depending on the `Output.output_format`:
664
-
665
- - `OutputFormat.JSON`: the `path` is the file where the JSON data will
666
- be written. If empty or `None`, the data will be written to stdout.
667
- - `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
668
- files will be written. If empty or `None`, the data will be written
669
- to a directory named `output` under the current working directory.
670
- The `Output.options` and `Output.statistics` will be written to
671
- stdout.
672
-
673
- This function detects if stdout was redirected and resets it to avoid
674
- unexpected behavior. If you want to skip this behavior, set the
675
- `skip_stdout_reset` parameter to `True`.
676
-
677
1015
  Parameters
678
1016
  ----------
679
- output : Output, dict[str, Any]
680
- Output data to write.
681
- path : str
682
- 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.
683
1028
  skip_stdout_reset : bool, optional
684
- Skip resetting stdout before writing the output data. Default is
685
- `False`.
1029
+ Skip resetting stdout before writing the output data. Default is False.
686
1030
 
687
1031
  Raises
688
1032
  ------
689
1033
  ValueError
690
- If the `Output.output_format` is not supported.
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"})
691
1051
  """
692
1052
 
693
1053
  deprecated(
@@ -700,6 +1060,7 @@ def write_local(
700
1060
 
701
1061
 
702
1062
  _LOCAL_OUTPUT_WRITER = LocalOutputWriter()
1063
+ """Default LocalOutputWriter instance used by the write function."""
703
1064
 
704
1065
 
705
1066
  def write(
@@ -709,46 +1070,78 @@ def write(
709
1070
  writer: Optional[OutputWriter] = _LOCAL_OUTPUT_WRITER,
710
1071
  ) -> None:
711
1072
  """
712
- This is a convenience function for writing an `Output`, i.e.: write the
713
- output to the specified destination. The `writer` is used to call the
714
- `.write` method. Note that the default writes is the `LocalOutputWriter`.
1073
+ Write the output to the specified destination.
715
1074
 
716
- Consider the following for the `path` parameter, depending on the
717
- `Output.output_format`:
1075
+ You can import the `write` function directly from `nextmv`:
718
1076
 
719
- - `OutputFormat.JSON`: the `path` is the file where the JSON data will
720
- be written. If empty or `None`, the data will be written to stdout.
721
- - `OutputFormat.CSV_ARCHIVE`: the `path` is the directory where the CSV
722
- files will be written. If empty or `None`, the data will be written
723
- to a directory named `output` under the current working directory.
724
- The `Output.options` and `Output.statistics` will be written to
725
- stdout.
1077
+ ```python
1078
+ from nextmv import write
1079
+ ```
726
1080
 
727
- This function detects if stdout was redirected and resets it to avoid
728
- unexpected behavior. If you want to skip this behavior, set the
729
- `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.
730
1083
 
731
1084
  Parameters
732
1085
  ----------
733
- output : Output, dict[str, Any]
734
- Output data to write.
735
- path : str
736
- 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.
737
1097
  skip_stdout_reset : bool, optional
738
- Skip resetting stdout before writing the output data. Default is
739
- `False`.
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.
740
1102
 
741
1103
  Raises
742
1104
  ------
743
1105
  ValueError
744
- If the `Output.output_format` is not supported.
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")
745
1118
  """
746
1119
 
747
1120
  writer.write(output, path, skip_stdout_reset)
748
1121
 
749
1122
 
750
- def _custom_serial(obj: Any):
751
- """JSON serializer for objects not serializable by default one."""
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
+ """
752
1145
 
753
1146
  if isinstance(obj, (datetime.datetime | datetime.date)):
754
1147
  return obj.isoformat()