nextmv 0.28.5__py3-none-any.whl → 0.29.0.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/__about__.py +1 -1
- nextmv/__init__.py +8 -0
- nextmv/cloud/application.py +125 -13
- nextmv/cloud/client.py +28 -9
- nextmv/cloud/manifest.py +142 -14
- nextmv/cloud/package.py +1 -1
- nextmv/input.py +419 -6
- nextmv/model.py +12 -3
- nextmv/options.py +88 -0
- nextmv/output.py +535 -51
- {nextmv-0.28.5.dist-info → nextmv-0.29.0.dev0.dist-info}/METADATA +1 -1
- {nextmv-0.28.5.dist-info → nextmv-0.29.0.dev0.dist-info}/RECORD +14 -14
- {nextmv-0.28.5.dist-info → nextmv-0.29.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-0.28.5.dist-info → nextmv-0.29.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/output.py
CHANGED
|
@@ -21,6 +21,8 @@ Statistics
|
|
|
21
21
|
Complete statistics container for a solution, including run metrics and result data.
|
|
22
22
|
OutputFormat
|
|
23
23
|
Enumeration of supported output formats.
|
|
24
|
+
SolutionFile
|
|
25
|
+
Represents a solution to be written as a file.
|
|
24
26
|
VisualSchema
|
|
25
27
|
Enumeration of supported visualization schemas.
|
|
26
28
|
Visual
|
|
@@ -44,6 +46,7 @@ import copy
|
|
|
44
46
|
import csv
|
|
45
47
|
import os
|
|
46
48
|
import sys
|
|
49
|
+
from collections.abc import Callable
|
|
47
50
|
from dataclasses import dataclass
|
|
48
51
|
from enum import Enum
|
|
49
52
|
from typing import Any, Optional, Union
|
|
@@ -297,33 +300,6 @@ class Statistics(BaseModel):
|
|
|
297
300
|
"""Schema (version). This class only supports `v1`."""
|
|
298
301
|
|
|
299
302
|
|
|
300
|
-
class OutputFormat(str, Enum):
|
|
301
|
-
"""
|
|
302
|
-
Enumeration of supported output formats.
|
|
303
|
-
|
|
304
|
-
You can import the `OutputFormat` class directly from `nextmv`:
|
|
305
|
-
|
|
306
|
-
```python
|
|
307
|
-
from nextmv import OutputFormat
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
This enum defines the different formats that can be used for outputting data.
|
|
311
|
-
Each format has specific requirements and behaviors when writing.
|
|
312
|
-
|
|
313
|
-
Attributes
|
|
314
|
-
----------
|
|
315
|
-
JSON : str
|
|
316
|
-
JSON format, utf-8 encoded.
|
|
317
|
-
CSV_ARCHIVE : str
|
|
318
|
-
CSV archive format: multiple CSV files.
|
|
319
|
-
"""
|
|
320
|
-
|
|
321
|
-
JSON = "json"
|
|
322
|
-
"""JSON format, utf-8 encoded."""
|
|
323
|
-
CSV_ARCHIVE = "csv-archive"
|
|
324
|
-
"""CSV archive format: multiple CSV files."""
|
|
325
|
-
|
|
326
|
-
|
|
327
303
|
class VisualSchema(str, Enum):
|
|
328
304
|
"""
|
|
329
305
|
Enumeration of supported visualization schemas.
|
|
@@ -498,6 +474,326 @@ class Asset(BaseModel):
|
|
|
498
474
|
raise ValueError(f"unsupported content_type: {self.content_type}, supported types are `json`")
|
|
499
475
|
|
|
500
476
|
|
|
477
|
+
class OutputFormat(str, Enum):
|
|
478
|
+
"""
|
|
479
|
+
Enumeration of supported output formats.
|
|
480
|
+
|
|
481
|
+
You can import the `OutputFormat` class directly from `nextmv`:
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from nextmv import OutputFormat
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
This enum defines the different formats that can be used for outputting data.
|
|
488
|
+
Each format has specific requirements and behaviors when writing.
|
|
489
|
+
|
|
490
|
+
Attributes
|
|
491
|
+
----------
|
|
492
|
+
JSON : str
|
|
493
|
+
JSON format, utf-8 encoded.
|
|
494
|
+
CSV_ARCHIVE : str
|
|
495
|
+
CSV archive format: multiple CSV files.
|
|
496
|
+
MULTI_FILE : str
|
|
497
|
+
Multi-file format: multiple files in a directory.
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
JSON = "json"
|
|
501
|
+
"""JSON format, utf-8 encoded."""
|
|
502
|
+
CSV_ARCHIVE = "csv-archive"
|
|
503
|
+
"""CSV archive format: multiple CSV files."""
|
|
504
|
+
MULTI_FILE = "multi-file"
|
|
505
|
+
"""Multi-file format: multiple files in a directory."""
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@dataclass
|
|
509
|
+
class SolutionFile:
|
|
510
|
+
"""
|
|
511
|
+
Represents a solution to be written as a file.
|
|
512
|
+
|
|
513
|
+
You can import the `SolutionFile` class directly from `nextmv`:
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
from nextmv import SolutionFile
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
This class is used to define a solution that will be written to a file in
|
|
520
|
+
the filesystem. It includes the name of the file, the data to be written,
|
|
521
|
+
and the writer function that will handle the serialization of the data.
|
|
522
|
+
This `SolutionFile` class is typically used in the `Output`, when the
|
|
523
|
+
`Output.output_format` is set to `OutputFormat.MULTI_FILE`. Given that it
|
|
524
|
+
is difficult to handle every edge case of how a solution is serialized, and
|
|
525
|
+
written to a file, this class exists so that the user can implement the
|
|
526
|
+
`writer` callable of their choice and provide it with any `writer_args`
|
|
527
|
+
and `writer_kwargs` they might need.
|
|
528
|
+
|
|
529
|
+
Parameters
|
|
530
|
+
----------
|
|
531
|
+
name : str
|
|
532
|
+
Name of the output file. The file extension should be included in the
|
|
533
|
+
name.
|
|
534
|
+
data : Any
|
|
535
|
+
The actual data that will be written to the file. This can be any type
|
|
536
|
+
that can be given to the `writer` function. For example, if the `writer`
|
|
537
|
+
is a `csv.DictWriter`, then the data should be a list of dictionaries,
|
|
538
|
+
where each dictionary represents a row in the CSV file.
|
|
539
|
+
writer : Callable
|
|
540
|
+
Callable that writes the solution data to the file. This should be a
|
|
541
|
+
function implemented by the user. There are convenience functions that you
|
|
542
|
+
can use as a writer as well. The `writer` must receive, at the very
|
|
543
|
+
minimum, the following arguments:
|
|
544
|
+
|
|
545
|
+
- `file_path`: a `str` argument which is the location where this solution
|
|
546
|
+
will be written to. This includes the dir and the name of the file. As
|
|
547
|
+
such, the `name` parameter of this class is going to be passed to this
|
|
548
|
+
function joined with the directory where the file will be written.
|
|
549
|
+
- `data`: the actual data that will be written to the file. This can be any
|
|
550
|
+
type that can be given to the `writer` function. The `data` parameter of
|
|
551
|
+
this class is going to be passed to the `writer` function.
|
|
552
|
+
|
|
553
|
+
The `writer` can also receive additional arguments, and keyword arguments.
|
|
554
|
+
The `writer_args` and `writer_kwargs` parameters of this class can be used
|
|
555
|
+
to provide those additional arguments.
|
|
556
|
+
writer_args : Optional[list[Any]], optional
|
|
557
|
+
Positional arguments to pass to the writer function.
|
|
558
|
+
writer_kwargs : Optional[dict[str, Any]], optional
|
|
559
|
+
Keyword arguments to pass to the writer function.
|
|
560
|
+
|
|
561
|
+
Examples
|
|
562
|
+
--------
|
|
563
|
+
>>> from nextmv import SolutionFile
|
|
564
|
+
>>> solution_file = SolutionFile(
|
|
565
|
+
... name="solution.csv",
|
|
566
|
+
... data=[{"id": 1, "value": 100}, {"id": 2, "value": 200}],
|
|
567
|
+
... writer=csv.DictWriter,
|
|
568
|
+
... writer_kwargs={"fieldnames": ["id", "value"]},
|
|
569
|
+
... writer_args=[open("solution.csv", "w", newline="")],
|
|
570
|
+
... )
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
name: str
|
|
574
|
+
"""
|
|
575
|
+
Name of the solution (output) file. The file extension should be included in the
|
|
576
|
+
name.
|
|
577
|
+
"""
|
|
578
|
+
data: Any
|
|
579
|
+
"""
|
|
580
|
+
The actual data that will be written to the file. This can be any type that
|
|
581
|
+
can be given to the `writer` function. For example, if the `writer` is a
|
|
582
|
+
`csv.DictWriter`, then the data should be a list of dictionaries, where
|
|
583
|
+
each dictionary represents a row in the CSV file.
|
|
584
|
+
"""
|
|
585
|
+
writer: Callable[[str, str, Any], None]
|
|
586
|
+
"""
|
|
587
|
+
Callable that writes the solution data to the file. This should be a
|
|
588
|
+
function implemented by the user. There are convenience functions that you
|
|
589
|
+
can use as a writer as well. The `writer` must receive, at the very
|
|
590
|
+
minimum, the following arguments:
|
|
591
|
+
|
|
592
|
+
- `file_path`: a `str` argument which is the location where this solution
|
|
593
|
+
will be written to. This includes the dir and the name of the file. As
|
|
594
|
+
such, the `name` parameter of this class is going to be passed to this
|
|
595
|
+
function joined with the directory where the file will be written.
|
|
596
|
+
- `data`: the actual data that will be written to the file. This can be any
|
|
597
|
+
type that can be given to the `writer` function. The `data` parameter of
|
|
598
|
+
this class is going to be passed to the `writer` function.
|
|
599
|
+
|
|
600
|
+
The `writer` can also receive additional arguments, and keyword arguments.
|
|
601
|
+
The `writer_args` and `writer_kwargs` parameters of this class can be used
|
|
602
|
+
to provide those additional arguments.
|
|
603
|
+
"""
|
|
604
|
+
writer_args: Optional[list[Any]] = None
|
|
605
|
+
"""
|
|
606
|
+
Optional positional arguments to pass to the writer function. This can be
|
|
607
|
+
used to customize the behavior of the writer.
|
|
608
|
+
"""
|
|
609
|
+
writer_kwargs: Optional[dict[str, Any]] = None
|
|
610
|
+
"""
|
|
611
|
+
Optional keyword arguments to pass to the writer function. This can be used
|
|
612
|
+
to customize the behavior of the writer.
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def json_solution_file(
|
|
617
|
+
name: str,
|
|
618
|
+
data: dict[str, Any],
|
|
619
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
620
|
+
) -> SolutionFile:
|
|
621
|
+
"""
|
|
622
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
623
|
+
given `data` to a `.json` file with the provided `name`.
|
|
624
|
+
|
|
625
|
+
You can import this function directly from `nextmv`:
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
from nextmv import json_solution_file
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
name : str
|
|
634
|
+
Name of the output file. You don't need to include the `.json`
|
|
635
|
+
extension.
|
|
636
|
+
data : dict[str, Any]
|
|
637
|
+
The actual data that will be written to the file. This should be a
|
|
638
|
+
dictionary that can be serialized to JSON.
|
|
639
|
+
json_configurations : Optional[dict[str, Any]], optional
|
|
640
|
+
Optional configuration options for the JSON serialization process. You
|
|
641
|
+
can use these options to configure parameters such as indentation.
|
|
642
|
+
|
|
643
|
+
Returns
|
|
644
|
+
-------
|
|
645
|
+
SolutionFile
|
|
646
|
+
The constructed `SolutionFile` object.
|
|
647
|
+
|
|
648
|
+
Examples
|
|
649
|
+
--------
|
|
650
|
+
>>> from nextmv import json_solution_file
|
|
651
|
+
>>> solution_file = json_solution_file(
|
|
652
|
+
... name="solution",
|
|
653
|
+
... data={"id": 1, "value": 100}
|
|
654
|
+
... )
|
|
655
|
+
>>> solution_file.name
|
|
656
|
+
'solution.json'
|
|
657
|
+
>>> solution_file.data
|
|
658
|
+
{'id': 1, 'value': 100}
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
if not name.endswith(".json"):
|
|
662
|
+
name += ".json"
|
|
663
|
+
|
|
664
|
+
json_configurations = json_configurations or {}
|
|
665
|
+
|
|
666
|
+
def writer(file_path: str, write_data: dict[str, Any]) -> None:
|
|
667
|
+
serialized = serialize_json(write_data, json_configurations=json_configurations)
|
|
668
|
+
|
|
669
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
670
|
+
file.write(serialized + "\n")
|
|
671
|
+
|
|
672
|
+
return SolutionFile(
|
|
673
|
+
name=name,
|
|
674
|
+
data=data,
|
|
675
|
+
writer=writer,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def csv_solution_file(
|
|
680
|
+
name: str,
|
|
681
|
+
data: list[dict[str, Any]],
|
|
682
|
+
csv_configurations: Optional[dict[str, Any]] = None,
|
|
683
|
+
) -> SolutionFile:
|
|
684
|
+
"""
|
|
685
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
686
|
+
given `data` to a `.csv` file with the provided `name`.
|
|
687
|
+
|
|
688
|
+
You can import this function directly from `nextmv`:
|
|
689
|
+
|
|
690
|
+
```python
|
|
691
|
+
from nextmv import csv_solution_file
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Parameters
|
|
695
|
+
----------
|
|
696
|
+
name : str
|
|
697
|
+
Name of the output file. You don't need to include the `.csv`
|
|
698
|
+
extension.
|
|
699
|
+
data : list[dict[str, Any]]
|
|
700
|
+
The actual data that will be written to the file. This should be a list
|
|
701
|
+
of dictionaries, where each dictionary represents a row in the CSV file.
|
|
702
|
+
The keys of the dictionaries will be used as the column headers in the
|
|
703
|
+
CSV file.
|
|
704
|
+
csv_configurations : Optional[dict[str, Any]], optional
|
|
705
|
+
Optional configuration options for the CSV serialization process.
|
|
706
|
+
|
|
707
|
+
Returns
|
|
708
|
+
-------
|
|
709
|
+
SolutionFile
|
|
710
|
+
The constructed `SolutionFile` object.
|
|
711
|
+
|
|
712
|
+
Examples
|
|
713
|
+
--------
|
|
714
|
+
>>> from nextmv import csv_solution_file
|
|
715
|
+
>>> solution_file = csv_solution_file(
|
|
716
|
+
... name="solution",
|
|
717
|
+
... data=[{"id": 1, "value": 100}, {"id": 2, "value": 200}]
|
|
718
|
+
... )
|
|
719
|
+
>>> solution_file.name
|
|
720
|
+
'solution.csv'
|
|
721
|
+
>>> solution_file.data
|
|
722
|
+
[{'id': 1, 'value': 100}, {'id': 2, 'value': 200}]
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
if not name.endswith(".csv"):
|
|
726
|
+
name += ".csv"
|
|
727
|
+
|
|
728
|
+
csv_configurations = csv_configurations or {}
|
|
729
|
+
|
|
730
|
+
def writer(file_path: str, write_data: list[dict[str, Any]]) -> None:
|
|
731
|
+
with open(file_path, "w", encoding="utf-8", newline="") as file:
|
|
732
|
+
writer = csv.DictWriter(
|
|
733
|
+
file,
|
|
734
|
+
fieldnames=write_data[0].keys(),
|
|
735
|
+
**csv_configurations,
|
|
736
|
+
)
|
|
737
|
+
writer.writeheader()
|
|
738
|
+
writer.writerows(write_data)
|
|
739
|
+
|
|
740
|
+
return SolutionFile(
|
|
741
|
+
name=name,
|
|
742
|
+
data=data,
|
|
743
|
+
writer=writer,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def text_solution_file(name: str, data: str) -> SolutionFile:
|
|
748
|
+
"""
|
|
749
|
+
This is a convenience function to build a `SolutionFile`. It writes the
|
|
750
|
+
given `data` to a utf-8 encoded file with the provided `name`.
|
|
751
|
+
|
|
752
|
+
You can import this function directly from `nextmv`:
|
|
753
|
+
|
|
754
|
+
```python
|
|
755
|
+
from nextmv import text_solution_file
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
You must provide the extension as part of the `name` parameter.
|
|
759
|
+
|
|
760
|
+
Parameters
|
|
761
|
+
----------
|
|
762
|
+
name : str
|
|
763
|
+
Name of the output file. The file extension must be provided in the
|
|
764
|
+
name.
|
|
765
|
+
data : str
|
|
766
|
+
The actual data that will be written to the file.
|
|
767
|
+
|
|
768
|
+
Returns
|
|
769
|
+
-------
|
|
770
|
+
SolutionFile
|
|
771
|
+
The constructed `SolutionFile` object.
|
|
772
|
+
|
|
773
|
+
Examples
|
|
774
|
+
--------
|
|
775
|
+
>>> from nextmv import text_solution_file
|
|
776
|
+
>>> solution_file = text_solution_file(
|
|
777
|
+
... name="solution.txt",
|
|
778
|
+
... data="This is a sample text solution."
|
|
779
|
+
... )
|
|
780
|
+
>>> solution_file.name
|
|
781
|
+
'solution.txt'
|
|
782
|
+
>>> solution_file.data
|
|
783
|
+
'This is a sample text solution.'
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
def writer(file_path: str, write_data: str) -> None:
|
|
787
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
788
|
+
file.write(write_data + "\n")
|
|
789
|
+
|
|
790
|
+
return SolutionFile(
|
|
791
|
+
name=name,
|
|
792
|
+
data=data,
|
|
793
|
+
writer=writer,
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
|
|
501
797
|
@dataclass
|
|
502
798
|
class Output:
|
|
503
799
|
"""
|
|
@@ -513,6 +809,34 @@ class Output:
|
|
|
513
809
|
can later be written to various destinations. It supports different output
|
|
514
810
|
formats and allows for customization of the serialization process.
|
|
515
811
|
|
|
812
|
+
The `solution`'s type must match the `output_format`:
|
|
813
|
+
|
|
814
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
815
|
+
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
816
|
+
Any]]]`. The keys represent the file names where the data should be
|
|
817
|
+
written. The values are lists of dictionaries, where each dictionary
|
|
818
|
+
represents a row in the CSV file.
|
|
819
|
+
|
|
820
|
+
If you are working with `OutputFormat.MULTI_FILE`, you should use
|
|
821
|
+
`solution_files` instead of `solution`. When `solution_files` is not
|
|
822
|
+
`None`, then the `output_format` _must_ be `OutputFormat.MULTI_FILE`.
|
|
823
|
+
`solution_files` is a list of `SolutionFile` objects, which allows you to
|
|
824
|
+
define the name of the file, the data to be written, and the writer
|
|
825
|
+
function that will handle the serialization of the data. This is useful when
|
|
826
|
+
you need to write the solution to multiple files with different formats or
|
|
827
|
+
configurations.
|
|
828
|
+
|
|
829
|
+
There are convenience functions to create `SolutionFile` objects for
|
|
830
|
+
common use cases, such as:
|
|
831
|
+
|
|
832
|
+
- `json_solution_file`: for writing JSON data to a file.
|
|
833
|
+
- `csv_solution_file`: for writing CSV data to a file.
|
|
834
|
+
- `text_solution_file`: for writing utf-8 encoded data to a file.
|
|
835
|
+
|
|
836
|
+
For other data types, such as Excel, you can create your own `SolutionFile`
|
|
837
|
+
objects by providing a `name`, `data`, and a `writer` function that will
|
|
838
|
+
handle the serialization of the data.
|
|
839
|
+
|
|
516
840
|
Parameters
|
|
517
841
|
----------
|
|
518
842
|
options : Optional[Union[Options, dict[str, Any]]], optional
|
|
@@ -539,15 +863,6 @@ class Output:
|
|
|
539
863
|
TypeError
|
|
540
864
|
If options, statistics, or assets have unsupported types.
|
|
541
865
|
|
|
542
|
-
Notes
|
|
543
|
-
-----
|
|
544
|
-
The solution's type must match the `output_format`:
|
|
545
|
-
|
|
546
|
-
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
547
|
-
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str, Any]]]`.
|
|
548
|
-
The keys represent the file names where the data should be written. The values
|
|
549
|
-
are lists of dictionaries, where each dictionary represents a row in the CSV file.
|
|
550
|
-
|
|
551
866
|
Examples
|
|
552
867
|
--------
|
|
553
868
|
>>> from nextmv.output import Output, OutputFormat, Statistics, RunStatistics
|
|
@@ -582,14 +897,31 @@ class Output:
|
|
|
582
897
|
```
|
|
583
898
|
"""
|
|
584
899
|
output_format: Optional[OutputFormat] = OutputFormat.JSON
|
|
585
|
-
"""
|
|
900
|
+
"""
|
|
901
|
+
Format of the output data. Default is `OutputFormat.JSON`. When set to
|
|
902
|
+
`OutputFormat.MULTI_FILE`, the `solution_files` field must be specified and
|
|
903
|
+
cannot be `None`.
|
|
904
|
+
"""
|
|
586
905
|
solution: Optional[
|
|
587
906
|
Union[
|
|
588
907
|
Union[dict[str, Any], Any], # JSON
|
|
589
908
|
dict[str, list[dict[str, Any]]], # CSV_ARCHIVE
|
|
590
909
|
]
|
|
591
910
|
] = None
|
|
592
|
-
"""
|
|
911
|
+
"""
|
|
912
|
+
The solution to the decision problem. Use this filed when working with
|
|
913
|
+
`output_format` of types:
|
|
914
|
+
|
|
915
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]` or `Any`.
|
|
916
|
+
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
917
|
+
Any]]]`. The keys represent the file names where the data will be written
|
|
918
|
+
to. The values are lists of dictionaries, where each dictionary represents
|
|
919
|
+
a row in the CSV file.
|
|
920
|
+
|
|
921
|
+
Note that when the `output_format` is set to `OutputFormat.MULTI_FILE`,
|
|
922
|
+
this `solution` field is ignored, as you should use the `solution_files`
|
|
923
|
+
field instead.
|
|
924
|
+
"""
|
|
593
925
|
statistics: Optional[Union[Statistics, dict[str, Any]]] = None
|
|
594
926
|
"""
|
|
595
927
|
Statistics of the solution. These statistics can be of type `Statistics` or a
|
|
@@ -618,6 +950,28 @@ class Output:
|
|
|
618
950
|
dictionary, they will be used as is. If the assets are not provided, an
|
|
619
951
|
empty list will be used.
|
|
620
952
|
"""
|
|
953
|
+
solution_files: Optional[list[SolutionFile]] = None
|
|
954
|
+
"""
|
|
955
|
+
Optional list of solution files to be included in the output. These files
|
|
956
|
+
are of type `SolutionFile`, which allows for custom serialization and
|
|
957
|
+
writing of the solution data to files. When this field is specified, then
|
|
958
|
+
the `output_format` must be set to `OutputFormat.MULTI_FILE`, otherwise an
|
|
959
|
+
exception will be raised. The `SolutionFile` class allows you to define the
|
|
960
|
+
name of the file, the data to be written, and the writer function that will
|
|
961
|
+
handle the serialization of the data. This is useful when you need to write
|
|
962
|
+
the solution to multiple files with different formats or configurations.
|
|
963
|
+
|
|
964
|
+
There are convenience functions to create `SolutionFile` objects for
|
|
965
|
+
common use cases, such as:
|
|
966
|
+
|
|
967
|
+
- `json_solution_file`: for writing JSON data to a file.
|
|
968
|
+
- `csv_solution_file`: for writing CSV data to a file.
|
|
969
|
+
- `text_solution_file`: for writing utf-8 encoded data to a file.
|
|
970
|
+
|
|
971
|
+
For other data types, such as Excel, you can create your own `SolutionFile`
|
|
972
|
+
objects by providing a `name`, `data`, and a `writer` function that will
|
|
973
|
+
handle the serialization of the data.
|
|
974
|
+
"""
|
|
621
975
|
|
|
622
976
|
def __post_init__(self):
|
|
623
977
|
"""
|
|
@@ -638,22 +992,31 @@ class Output:
|
|
|
638
992
|
new_options = copy.deepcopy(init_options)
|
|
639
993
|
self.options = new_options
|
|
640
994
|
|
|
641
|
-
if self.solution is None:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
995
|
+
if self.solution is not None:
|
|
996
|
+
if self.output_format == OutputFormat.JSON:
|
|
997
|
+
try:
|
|
998
|
+
_ = serialize_json(self.solution)
|
|
999
|
+
except (TypeError, OverflowError) as e:
|
|
1000
|
+
raise ValueError(
|
|
1001
|
+
f"Output has output_format OutputFormat.JSON and "
|
|
1002
|
+
f"Output.solution is of type {type(self.solution)}, which is not JSON serializable"
|
|
1003
|
+
) from e
|
|
1004
|
+
|
|
1005
|
+
elif self.output_format == OutputFormat.CSV_ARCHIVE and not isinstance(self.solution, dict):
|
|
648
1006
|
raise ValueError(
|
|
649
|
-
f"Output
|
|
650
|
-
|
|
651
|
-
)
|
|
1007
|
+
f"unsupported Output.solution type: {type(self.solution)} with "
|
|
1008
|
+
"output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
1009
|
+
)
|
|
652
1010
|
|
|
653
|
-
|
|
1011
|
+
if self.solution_files is not None and self.output_format != OutputFormat.MULTI_FILE:
|
|
654
1012
|
raise ValueError(
|
|
655
|
-
f"
|
|
656
|
-
"output_format
|
|
1013
|
+
f"`solution_files` are not `None`, but `output_format` is different from `OutputFormat.MULTI_FILE`: "
|
|
1014
|
+
f"{self.output_format}. If you want to use `solution_files`, set `output_format` "
|
|
1015
|
+
"to `OutputFormat.MULTI_FILE`."
|
|
1016
|
+
)
|
|
1017
|
+
elif self.solution_files is not None and not isinstance(self.solution_files, list):
|
|
1018
|
+
raise TypeError(
|
|
1019
|
+
f"unsupported Output.solution_files type: {type(self.solution_files)}, supported type is `list`"
|
|
657
1020
|
)
|
|
658
1021
|
|
|
659
1022
|
def to_dict(self) -> dict[str, Any]: # noqa: C901
|
|
@@ -795,6 +1158,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
795
1158
|
"""
|
|
796
1159
|
|
|
797
1160
|
def _write_json(
|
|
1161
|
+
self,
|
|
798
1162
|
output: Union[Output, dict[str, Any], BaseModel],
|
|
799
1163
|
output_dict: dict[str, Any],
|
|
800
1164
|
path: Optional[str] = None,
|
|
@@ -828,6 +1192,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
828
1192
|
file.write(serialized + "\n")
|
|
829
1193
|
|
|
830
1194
|
def _write_archive(
|
|
1195
|
+
self,
|
|
831
1196
|
output: Union[Output, dict[str, Any], BaseModel],
|
|
832
1197
|
output_dict: dict[str, Any],
|
|
833
1198
|
path: Optional[str] = None,
|
|
@@ -892,10 +1257,128 @@ class LocalOutputWriter(OutputWriter):
|
|
|
892
1257
|
writer.writeheader()
|
|
893
1258
|
writer.writerows(data)
|
|
894
1259
|
|
|
1260
|
+
def _write_multi_file(
|
|
1261
|
+
self,
|
|
1262
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
1263
|
+
output_dict: dict[str, Any],
|
|
1264
|
+
path: Optional[str] = None,
|
|
1265
|
+
) -> None:
|
|
1266
|
+
"""
|
|
1267
|
+
Write output to multiple files.
|
|
1268
|
+
|
|
1269
|
+
Parameters
|
|
1270
|
+
----------
|
|
1271
|
+
output : Union[Output, dict[str, Any], BaseModel]
|
|
1272
|
+
The output object containing configuration and solution data.
|
|
1273
|
+
output_dict : dict[str, Any]
|
|
1274
|
+
Dictionary representation of the output to write.
|
|
1275
|
+
path : str, optional
|
|
1276
|
+
Directory path to write the CSV files. If None or empty,
|
|
1277
|
+
writes to a directory named "output" in the current working directory.
|
|
1278
|
+
|
|
1279
|
+
Raises
|
|
1280
|
+
------
|
|
1281
|
+
ValueError
|
|
1282
|
+
If the path is an existing file instead of a directory.
|
|
1283
|
+
"""
|
|
1284
|
+
dir_path = "outputs"
|
|
1285
|
+
if path is not None and path != "":
|
|
1286
|
+
if os.path.isfile(path):
|
|
1287
|
+
raise ValueError(f"The path refers to an existing file: {path}")
|
|
1288
|
+
|
|
1289
|
+
dir_path = path
|
|
1290
|
+
|
|
1291
|
+
if not os.path.exists(dir_path):
|
|
1292
|
+
os.makedirs(dir_path)
|
|
1293
|
+
|
|
1294
|
+
json_configurations = {}
|
|
1295
|
+
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
1296
|
+
json_configurations = output.json_configurations
|
|
1297
|
+
|
|
1298
|
+
self._write_multi_file_element(
|
|
1299
|
+
parent_dir=dir_path,
|
|
1300
|
+
json_configurations=json_configurations,
|
|
1301
|
+
output_dict=output_dict,
|
|
1302
|
+
element_key="statistics",
|
|
1303
|
+
)
|
|
1304
|
+
self._write_multi_file_element(
|
|
1305
|
+
parent_dir=dir_path,
|
|
1306
|
+
json_configurations=json_configurations,
|
|
1307
|
+
output_dict=output_dict,
|
|
1308
|
+
element_key="assets",
|
|
1309
|
+
)
|
|
1310
|
+
self._write_multi_file_solution(dir_path=dir_path, output=output)
|
|
1311
|
+
|
|
1312
|
+
def _write_multi_file_element(
|
|
1313
|
+
self,
|
|
1314
|
+
parent_dir: str,
|
|
1315
|
+
output_dict: dict[str, Any],
|
|
1316
|
+
element_key: str,
|
|
1317
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
1318
|
+
):
|
|
1319
|
+
"""
|
|
1320
|
+
Auxiliary function to write a specific element of the output
|
|
1321
|
+
dictionary to a file in the specified parent directory.
|
|
1322
|
+
"""
|
|
1323
|
+
|
|
1324
|
+
element = output_dict.get(element_key)
|
|
1325
|
+
if element is None or not element:
|
|
1326
|
+
return
|
|
1327
|
+
|
|
1328
|
+
final_dir = os.path.join(parent_dir, element_key)
|
|
1329
|
+
|
|
1330
|
+
if not os.path.exists(final_dir):
|
|
1331
|
+
os.makedirs(final_dir)
|
|
1332
|
+
|
|
1333
|
+
serialized = serialize_json(element, json_configurations=json_configurations)
|
|
1334
|
+
|
|
1335
|
+
with open(os.path.join(final_dir, f"{element_key}.json"), "w", encoding="utf-8") as file:
|
|
1336
|
+
file.write(serialized + "\n")
|
|
1337
|
+
|
|
1338
|
+
def _write_multi_file_solution(
|
|
1339
|
+
self,
|
|
1340
|
+
dir_path: str,
|
|
1341
|
+
output: Output,
|
|
1342
|
+
):
|
|
1343
|
+
"""
|
|
1344
|
+
Auxiliary function to write the solution files to the specified
|
|
1345
|
+
directory.
|
|
1346
|
+
"""
|
|
1347
|
+
|
|
1348
|
+
if output.solution_files is None:
|
|
1349
|
+
return
|
|
1350
|
+
|
|
1351
|
+
solutions_dir = os.path.join(dir_path, "solutions")
|
|
1352
|
+
|
|
1353
|
+
if not os.path.exists(solutions_dir):
|
|
1354
|
+
os.makedirs(solutions_dir)
|
|
1355
|
+
|
|
1356
|
+
for solution_file in output.solution_files:
|
|
1357
|
+
if not isinstance(solution_file, SolutionFile):
|
|
1358
|
+
raise TypeError(
|
|
1359
|
+
f"unsupported solution_file type: {type(solution_file)}, supported type is `SolutionFile`"
|
|
1360
|
+
)
|
|
1361
|
+
|
|
1362
|
+
file_path = os.path.join(solutions_dir, solution_file.name)
|
|
1363
|
+
if solution_file.writer_args is None:
|
|
1364
|
+
solution_file.writer_args = []
|
|
1365
|
+
if solution_file.writer_kwargs is None:
|
|
1366
|
+
solution_file.writer_kwargs = {}
|
|
1367
|
+
|
|
1368
|
+
# Call the writer function with the final path, and user provided
|
|
1369
|
+
# arguments and keyword arguments.
|
|
1370
|
+
solution_file.writer(
|
|
1371
|
+
file_path,
|
|
1372
|
+
solution_file.data,
|
|
1373
|
+
*solution_file.writer_args,
|
|
1374
|
+
**solution_file.writer_kwargs,
|
|
1375
|
+
)
|
|
1376
|
+
|
|
895
1377
|
# Callback functions for writing the output data.
|
|
896
1378
|
FILE_WRITERS = {
|
|
897
1379
|
OutputFormat.JSON: _write_json,
|
|
898
1380
|
OutputFormat.CSV_ARCHIVE: _write_archive,
|
|
1381
|
+
OutputFormat.MULTI_FILE: _write_multi_file,
|
|
899
1382
|
}
|
|
900
1383
|
"""Dictionary mapping output formats to writer functions."""
|
|
901
1384
|
|
|
@@ -979,6 +1462,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
979
1462
|
)
|
|
980
1463
|
|
|
981
1464
|
self.FILE_WRITERS[output_format](
|
|
1465
|
+
self,
|
|
982
1466
|
output=output,
|
|
983
1467
|
output_dict=output_dict,
|
|
984
1468
|
path=path,
|