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/input.py
CHANGED
|
@@ -27,6 +27,7 @@ import csv
|
|
|
27
27
|
import json
|
|
28
28
|
import os
|
|
29
29
|
import sys
|
|
30
|
+
from collections.abc import Callable
|
|
30
31
|
from dataclasses import dataclass
|
|
31
32
|
from enum import Enum
|
|
32
33
|
from typing import Any, Optional, Union
|
|
@@ -58,6 +59,8 @@ class InputFormat(str, Enum):
|
|
|
58
59
|
CSV format, utf-8 encoded.
|
|
59
60
|
CSV_ARCHIVE : str
|
|
60
61
|
CSV archive format: multiple CSV files.
|
|
62
|
+
MULTI_FILE : str
|
|
63
|
+
Multi-file format, used for loading multiple files in a single input.
|
|
61
64
|
"""
|
|
62
65
|
|
|
63
66
|
JSON = "json"
|
|
@@ -68,6 +71,233 @@ class InputFormat(str, Enum):
|
|
|
68
71
|
"""CSV format, utf-8 encoded."""
|
|
69
72
|
CSV_ARCHIVE = "csv-archive"
|
|
70
73
|
"""CSV archive format: multiple CSV files."""
|
|
74
|
+
MULTI_FILE = "multi-file"
|
|
75
|
+
"""Multi-file format, used for loading multiple files in a single input."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class DataFile:
|
|
80
|
+
"""
|
|
81
|
+
Represents data to be read from a file.
|
|
82
|
+
|
|
83
|
+
You can import the `DataFile` class directly from `nextmv`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from nextmv import DataFile
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This class is used to define data that will be read from a file in the
|
|
90
|
+
filesystem. It includes the name of the file, and the reader function that
|
|
91
|
+
will handle the loading, and deserialization of the data from the file.
|
|
92
|
+
This `DataFile` class is typically used in the `Input`, when the
|
|
93
|
+
`Input.input_format` is set to `InputFormat.MULTI_FILE`. Given that it is
|
|
94
|
+
difficul to handle every edge case of how data is deserialized, and read
|
|
95
|
+
from a file, this class exists so that the user can implement the `reader`
|
|
96
|
+
callable of their choice and provide it with any `reader_args` and
|
|
97
|
+
`reader_kwargs` they might need.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
name : str
|
|
102
|
+
Name of the data (input) file. The file extension should be included in
|
|
103
|
+
the name.
|
|
104
|
+
reader : Callable[[str], Any]
|
|
105
|
+
Callable that reads the data from the file. This should be a function
|
|
106
|
+
implemented by the user. There are convenience functions that you can
|
|
107
|
+
use as a reader as well. The `reader` must receive, at the very minimum,
|
|
108
|
+
the following arguments:
|
|
109
|
+
|
|
110
|
+
- `file_path`: a `str` argument which is the location where this
|
|
111
|
+
solution will be read from. This includes the dir and name of the
|
|
112
|
+
file. As such, the `name` parameter of this class is going to be
|
|
113
|
+
passed to the `reader` function, joined with the directory where the
|
|
114
|
+
file will be read from.
|
|
115
|
+
|
|
116
|
+
The `reader` can also receive additional arguments, and keyword
|
|
117
|
+
arguments. The `reader_args` and `reader_kwargs` parameters of this
|
|
118
|
+
class can be used to provide those additional arguments.
|
|
119
|
+
|
|
120
|
+
The `reader` function should return the data that will be used in the
|
|
121
|
+
model.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
name: str
|
|
125
|
+
"""
|
|
126
|
+
Name of the data (input) file. The file extension should be included in the
|
|
127
|
+
name.
|
|
128
|
+
"""
|
|
129
|
+
loader: Callable[[str], Any]
|
|
130
|
+
"""
|
|
131
|
+
Callable that reads (loads) the data from the file. This should be a function
|
|
132
|
+
implemented by the user. There are convenience functions that you can use
|
|
133
|
+
as a `loader` as well. The `loader` must receive, at the very minimum, the
|
|
134
|
+
following arguments:
|
|
135
|
+
|
|
136
|
+
- `file_path`: a `str` argument which is the location where this
|
|
137
|
+
solution will be read from. This includes the dir and name of the
|
|
138
|
+
file. As such, the `name` parameter of this class is going to be
|
|
139
|
+
passed to the `loader` function, joined with the directory where the
|
|
140
|
+
file will be read from.
|
|
141
|
+
|
|
142
|
+
The `loader` can also receive additional arguments, and keyword arguments.
|
|
143
|
+
The `loader_args` and `loader_kwargs` parameters of this class can be used
|
|
144
|
+
to provide those additional arguments.
|
|
145
|
+
|
|
146
|
+
The `loader` function should return the data that will be used in the model.
|
|
147
|
+
"""
|
|
148
|
+
loader_kwargs: Optional[dict[str, Any]] = None
|
|
149
|
+
"""
|
|
150
|
+
Optional keyword arguments to pass to the loader function. This can be used
|
|
151
|
+
to customize the behavior of the loader.
|
|
152
|
+
"""
|
|
153
|
+
loader_args: Optional[list[Any]] = None
|
|
154
|
+
"""
|
|
155
|
+
Optional positional arguments to pass to the loader function. This can be
|
|
156
|
+
used to customize the behavior of the loader.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def json_data_file(name: str, json_configurations: Optional[dict[str, Any]] = None) -> DataFile:
|
|
161
|
+
"""
|
|
162
|
+
This is a convenience function to create a `DataFile` that reads JSON data.
|
|
163
|
+
|
|
164
|
+
You can import the `json_data_file` function directly from `nextmv`:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from nextmv import json_data_file
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
name : str
|
|
173
|
+
Name of the data file. You don't need to include the `.json` extension.
|
|
174
|
+
json_configurations : dict[str, Any], optional
|
|
175
|
+
JSON-specific configurations for reading the data.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
DataFile
|
|
180
|
+
A `DataFile` instance that reads JSON data from a file with the given
|
|
181
|
+
name.
|
|
182
|
+
|
|
183
|
+
Examples
|
|
184
|
+
--------
|
|
185
|
+
>>> from nextmv import json_data_file
|
|
186
|
+
>>> data_file = json_data_file("my_data")
|
|
187
|
+
>>> data = data_file.read()
|
|
188
|
+
>>> print(data)
|
|
189
|
+
{
|
|
190
|
+
"key": "value",
|
|
191
|
+
"another_key": [1, 2, 3]
|
|
192
|
+
}
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
if not name.endswith(".json"):
|
|
196
|
+
name += ".json"
|
|
197
|
+
|
|
198
|
+
json_configurations = json_configurations or {}
|
|
199
|
+
|
|
200
|
+
def loader(file_path: str) -> Union[dict[str, Any], Any]:
|
|
201
|
+
with open(file_path, encoding="utf-8") as f:
|
|
202
|
+
return json.load(f, **json_configurations)
|
|
203
|
+
|
|
204
|
+
return DataFile(
|
|
205
|
+
name=name,
|
|
206
|
+
loader=loader,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def csv_data_file(name: str, csv_configurations: Optional[dict[str, Any]] = None) -> DataFile:
|
|
211
|
+
"""
|
|
212
|
+
This is a convenience function to create a `DataFile` that reads CSV data.
|
|
213
|
+
|
|
214
|
+
You can import the `csv_data_file` function directly from `nextmv`:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from nextmv import csv_data_file
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
name : str
|
|
223
|
+
Name of the data file. You don't need to include the `.csv` extension.
|
|
224
|
+
csv_configurations : dict[str, Any], optional
|
|
225
|
+
CSV-specific configurations for reading the data.
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
DataFile
|
|
230
|
+
A `DataFile` instance that reads CSV data from a file with the given
|
|
231
|
+
name.
|
|
232
|
+
|
|
233
|
+
Examples
|
|
234
|
+
--------
|
|
235
|
+
>>> from nextmv import csv_data_file
|
|
236
|
+
>>> data_file = csv_data_file("my_data")
|
|
237
|
+
>>> data = data_file.read()
|
|
238
|
+
>>> print(data)
|
|
239
|
+
[
|
|
240
|
+
{"column1": "value1", "column2": "value2"},
|
|
241
|
+
{"column1": "value3", "column2": "value4"}
|
|
242
|
+
]
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
if not name.endswith(".csv"):
|
|
246
|
+
name += ".csv"
|
|
247
|
+
|
|
248
|
+
csv_configurations = csv_configurations or {}
|
|
249
|
+
|
|
250
|
+
def loader(file_path: str) -> list[dict[str, Any]]:
|
|
251
|
+
with open(file_path, encoding="utf-8") as f:
|
|
252
|
+
return list(csv.DictReader(f, **csv_configurations))
|
|
253
|
+
|
|
254
|
+
return DataFile(
|
|
255
|
+
name=name,
|
|
256
|
+
loader=loader,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def text_data_file(name: str) -> DataFile:
|
|
261
|
+
"""
|
|
262
|
+
This is a convenience function to create a `DataFile` that reads utf-8
|
|
263
|
+
encoded text data.
|
|
264
|
+
|
|
265
|
+
You can import the `text_data_file` function directly from `nextmv`:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from nextmv import text_data_file
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
You must provide the extension as part of the `name` parameter.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
name : str
|
|
276
|
+
Name of the data file. The file extension must be provided in the name.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
DataFile
|
|
281
|
+
A `DataFile` instance that reads text data from a file with the given
|
|
282
|
+
name.
|
|
283
|
+
|
|
284
|
+
Examples
|
|
285
|
+
--------
|
|
286
|
+
>>> from nextmv import text_data_file
|
|
287
|
+
>>> data_file = text_data_file("my_data")
|
|
288
|
+
>>> data = data_file.read()
|
|
289
|
+
>>> print(data)
|
|
290
|
+
This is some text data.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def loader(file_path: str) -> str:
|
|
294
|
+
with open(file_path, encoding="utf-8") as f:
|
|
295
|
+
return f.read().rstrip("\n")
|
|
296
|
+
|
|
297
|
+
return DataFile(
|
|
298
|
+
name=name,
|
|
299
|
+
loader=loader,
|
|
300
|
+
)
|
|
71
301
|
|
|
72
302
|
|
|
73
303
|
@dataclass
|
|
@@ -83,12 +313,40 @@ class Input:
|
|
|
83
313
|
|
|
84
314
|
Parameters
|
|
85
315
|
----------
|
|
86
|
-
data : Union[dict[str, Any],
|
|
316
|
+
data : Union[Union[dict[str, Any], Any], str, list[dict[str, Any]],
|
|
317
|
+
dict[str, list[dict[str, Any]]], dict[str, Any]]
|
|
87
318
|
The actual data.
|
|
88
319
|
input_format : InputFormat, optional
|
|
89
320
|
Format of the input data. Default is `InputFormat.JSON`.
|
|
90
321
|
options : Options, optional
|
|
91
322
|
Options that the input was created with.
|
|
323
|
+
|
|
324
|
+
Notes
|
|
325
|
+
-----
|
|
326
|
+
The `data`'s type must match the `input_format`:
|
|
327
|
+
|
|
328
|
+
- `InputFormat.JSON`: the data is `Union[dict[str, Any], Any]`. This just
|
|
329
|
+
means that the data must be JSON-deserializable, which includes dicts and
|
|
330
|
+
lists.
|
|
331
|
+
- `InputFormat.TEXT`: the data is `str`, and it must be utf-8 encoded.
|
|
332
|
+
- `InputFormat.CSV`: the data is `list[dict[str, Any]]`, where each dict
|
|
333
|
+
represents a row in the CSV.
|
|
334
|
+
- `InputFormat.CSV_ARCHIVE`: the data is `dict[str, list[dict[str, Any]]]`,
|
|
335
|
+
where each key is the name of a CSV file and the value is a list of dicts
|
|
336
|
+
representing the rows in that CSV file.
|
|
337
|
+
- `InputFormat.MULTI_FILE`: the data is `dict[str, Any]`, where for each
|
|
338
|
+
item, the key is the file name (with the extension) and the actual data
|
|
339
|
+
from the file is the value. When working with multi-file, data is loaded
|
|
340
|
+
from one or more files in a specific directory. Given that each file can
|
|
341
|
+
be of different types (JSON, CSV, Excel, etc...), the data captured from
|
|
342
|
+
each might vary. To reflect this, the data is loaded as a dict of items.
|
|
343
|
+
|
|
344
|
+
Raises
|
|
345
|
+
------
|
|
346
|
+
ValueError
|
|
347
|
+
If the data type doesn't match the expected type for the given format.
|
|
348
|
+
ValueError
|
|
349
|
+
If the `input_format` is not one of the supported formats.
|
|
92
350
|
"""
|
|
93
351
|
|
|
94
352
|
data: Union[
|
|
@@ -96,6 +354,7 @@ class Input:
|
|
|
96
354
|
str, # TEXT
|
|
97
355
|
list[dict[str, Any]], # CSV
|
|
98
356
|
dict[str, list[dict[str, Any]]], # CSV_ARCHIVE
|
|
357
|
+
dict[str, Any], # MULTI_FILE
|
|
99
358
|
]
|
|
100
359
|
"""
|
|
101
360
|
The actual data.
|
|
@@ -106,6 +365,7 @@ class Input:
|
|
|
106
365
|
- For `TEXT`: `str`
|
|
107
366
|
- For `CSV`: `list[dict[str, Any]]`
|
|
108
367
|
- For `CSV_ARCHIVE`: `dict[str, list[dict[str, Any]]]`
|
|
368
|
+
- For `MULTI_FILE`: `dict[str, Any]`
|
|
109
369
|
"""
|
|
110
370
|
|
|
111
371
|
input_format: Optional[InputFormat] = InputFormat.JSON
|
|
@@ -165,6 +425,12 @@ class Input:
|
|
|
165
425
|
"input_format InputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
166
426
|
)
|
|
167
427
|
|
|
428
|
+
elif self.input_format == InputFormat.MULTI_FILE and not isinstance(self.data, dict):
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"unsupported Input.data type: {type(self.data)} with "
|
|
431
|
+
"input_format InputFormat.MULTI_FILE, supported type is `dict`"
|
|
432
|
+
)
|
|
433
|
+
|
|
168
434
|
# Capture a snapshot of the options that were used to create the class
|
|
169
435
|
# so even if they are changed later, we have a record of the original.
|
|
170
436
|
init_options = self.options
|
|
@@ -175,8 +441,10 @@ class Input:
|
|
|
175
441
|
"""
|
|
176
442
|
Convert the input to a dictionary.
|
|
177
443
|
|
|
178
|
-
This method serializes the Input object to a dictionary format that can
|
|
179
|
-
easily converted to JSON or other serialization formats.
|
|
444
|
+
This method serializes the Input object to a dictionary format that can
|
|
445
|
+
be easily converted to JSON or other serialization formats. When the
|
|
446
|
+
`input_type` is set to `InputFormat.MULTI_FILE`, it will not include
|
|
447
|
+
the `data` field, as it is uncertain how data is deserialized from the file.
|
|
180
448
|
|
|
181
449
|
Returns
|
|
182
450
|
-------
|
|
@@ -201,12 +469,18 @@ class Input:
|
|
|
201
469
|
{'data': {'key': 'value'}, 'input_format': 'json', 'options': None}
|
|
202
470
|
"""
|
|
203
471
|
|
|
204
|
-
|
|
205
|
-
"data": self.data,
|
|
472
|
+
input_dict = {
|
|
206
473
|
"input_format": self.input_format.value,
|
|
207
474
|
"options": self.options.to_dict() if self.options is not None else None,
|
|
208
475
|
}
|
|
209
476
|
|
|
477
|
+
if self.input_format == InputFormat.MULTI_FILE:
|
|
478
|
+
return input_dict
|
|
479
|
+
|
|
480
|
+
input_dict["data"] = self.data
|
|
481
|
+
|
|
482
|
+
return input_dict
|
|
483
|
+
|
|
210
484
|
|
|
211
485
|
class InputLoader:
|
|
212
486
|
"""
|
|
@@ -375,6 +649,7 @@ class LocalInputLoader(InputLoader):
|
|
|
375
649
|
options: Optional[Options] = None,
|
|
376
650
|
path: Optional[str] = None,
|
|
377
651
|
csv_configurations: Optional[dict[str, Any]] = None,
|
|
652
|
+
data_files: Optional[list[DataFile]] = None,
|
|
378
653
|
) -> Input:
|
|
379
654
|
"""
|
|
380
655
|
Load the input data. The input data can be in various formats. For
|
|
@@ -395,6 +670,10 @@ class LocalInputLoader(InputLoader):
|
|
|
395
670
|
- `InputFormat.CSV`: the data is a `list[dict[str, Any]]`.
|
|
396
671
|
- `InputFormat.CSV_ARCHIVE`: the data is a `dict[str, list[dict[str, Any]]]`.
|
|
397
672
|
Each key is the name of the CSV file, minus the `.csv` extension.
|
|
673
|
+
- `InputFormat.MULTI_FILE`: the data is a `dict[str, Any]`, where each
|
|
674
|
+
key is the file name (with extension) and the value is the data read
|
|
675
|
+
from the file. The data can be of any type, depending on the file
|
|
676
|
+
type and the reader function provided in the `DataFile` instances.
|
|
398
677
|
|
|
399
678
|
Parameters
|
|
400
679
|
----------
|
|
@@ -408,6 +687,16 @@ class LocalInputLoader(InputLoader):
|
|
|
408
687
|
Configurations for loading CSV files. The default `DictReader` is
|
|
409
688
|
used when loading a CSV file, so you have the option to pass in a
|
|
410
689
|
dictionary with custom kwargs for the `DictReader`.
|
|
690
|
+
data_files : list[DataFile], optional
|
|
691
|
+
List of `DataFile` instances to read from. This is used when the
|
|
692
|
+
`input_format` is set to `InputFormat.MULTI_FILE`. Each `DataFile`
|
|
693
|
+
instance should have a `name` (the file name with extension) and a
|
|
694
|
+
`loader` function that reads the data from the file. The `loader`
|
|
695
|
+
function should accept the file path as its first argument and return
|
|
696
|
+
the data read from the file. The `loader` can also accept additional
|
|
697
|
+
positional and keyword arguments, which can be provided through the
|
|
698
|
+
`loader_args` and `loader_kwargs` attributes of the `DataFile`
|
|
699
|
+
instance.
|
|
411
700
|
|
|
412
701
|
Returns
|
|
413
702
|
-------
|
|
@@ -428,6 +717,14 @@ class LocalInputLoader(InputLoader):
|
|
|
428
717
|
data = self._load_utf8_encoded(path=path, input_format=input_format, csv_configurations=csv_configurations)
|
|
429
718
|
elif input_format == InputFormat.CSV_ARCHIVE:
|
|
430
719
|
data = self._load_archive(path=path, csv_configurations=csv_configurations)
|
|
720
|
+
elif input_format == InputFormat.MULTI_FILE:
|
|
721
|
+
if data_files is None:
|
|
722
|
+
raise ValueError("data_files must be provided when input_format is InputFormat.MULTI_FILE")
|
|
723
|
+
|
|
724
|
+
if not isinstance(data_files, list):
|
|
725
|
+
raise ValueError("data_files must be a list of DataFile instances")
|
|
726
|
+
|
|
727
|
+
data = self._load_multi_file(data_files=data_files, path=path)
|
|
431
728
|
|
|
432
729
|
return Input(data=data, input_format=input_format, options=options)
|
|
433
730
|
|
|
@@ -528,6 +825,73 @@ class LocalInputLoader(InputLoader):
|
|
|
528
825
|
|
|
529
826
|
return data
|
|
530
827
|
|
|
828
|
+
def _load_multi_file(
|
|
829
|
+
self,
|
|
830
|
+
data_files: list[DataFile],
|
|
831
|
+
path: Optional[str] = None,
|
|
832
|
+
) -> dict[str, Any]:
|
|
833
|
+
"""
|
|
834
|
+
Load multiple files from a directory.
|
|
835
|
+
|
|
836
|
+
This internal method loads all supported files from a specified
|
|
837
|
+
directory, organizing them into a dictionary where each key is the
|
|
838
|
+
filename and each value is the parsed file content. Supports CSV files
|
|
839
|
+
(parsed as list of dictionaries), JSON files (parsed as JSON objects),
|
|
840
|
+
and any other utf-8 encoded text files (loaded as plain text strings).
|
|
841
|
+
It also supports Excel files, loading them as DataFrames.
|
|
842
|
+
|
|
843
|
+
Parameters
|
|
844
|
+
----------
|
|
845
|
+
data_files : list[DataFile]
|
|
846
|
+
List of `DataFile` instances to read from.
|
|
847
|
+
path : str, optional
|
|
848
|
+
Path to the directory containing files. If None or empty,
|
|
849
|
+
uses "./inputs" as the default directory.
|
|
850
|
+
|
|
851
|
+
Returns
|
|
852
|
+
-------
|
|
853
|
+
dict[str, Any]
|
|
854
|
+
Dictionary mapping filenames to file contents. CSV files are loaded
|
|
855
|
+
as lists of dictionaries, JSON files as parsed JSON objects, and
|
|
856
|
+
other utf-8 text files as strings. Excel files are loaded as
|
|
857
|
+
DataFrames.
|
|
858
|
+
|
|
859
|
+
Raises
|
|
860
|
+
------
|
|
861
|
+
ValueError
|
|
862
|
+
If the path is not a directory or the default directory doesn't exist.
|
|
863
|
+
"""
|
|
864
|
+
|
|
865
|
+
dir_path = "inputs"
|
|
866
|
+
if path is not None and path != "":
|
|
867
|
+
if not os.path.isdir(path):
|
|
868
|
+
raise ValueError(f"path {path} is not a directory")
|
|
869
|
+
|
|
870
|
+
dir_path = path
|
|
871
|
+
|
|
872
|
+
if not os.path.isdir(dir_path):
|
|
873
|
+
raise ValueError(f'expected input directoy "{dir_path}" to exist as a default location')
|
|
874
|
+
|
|
875
|
+
data = {}
|
|
876
|
+
|
|
877
|
+
for data_file in data_files:
|
|
878
|
+
name = data_file.name
|
|
879
|
+
file_path = os.path.join(dir_path, name)
|
|
880
|
+
|
|
881
|
+
if data_file.loader_args is None:
|
|
882
|
+
data_file.loader_args = []
|
|
883
|
+
if data_file.loader_kwargs is None:
|
|
884
|
+
data_file.loader_kwargs = {}
|
|
885
|
+
|
|
886
|
+
d = data_file.loader(
|
|
887
|
+
file_path,
|
|
888
|
+
*data_file.loader_args,
|
|
889
|
+
**data_file.loader_kwargs,
|
|
890
|
+
)
|
|
891
|
+
data[name] = d
|
|
892
|
+
|
|
893
|
+
return data
|
|
894
|
+
|
|
531
895
|
|
|
532
896
|
def load_local(
|
|
533
897
|
input_format: Optional[InputFormat] = InputFormat.JSON,
|
|
@@ -590,6 +954,7 @@ def load(
|
|
|
590
954
|
path: Optional[str] = None,
|
|
591
955
|
csv_configurations: Optional[dict[str, Any]] = None,
|
|
592
956
|
loader: Optional[InputLoader] = _LOCAL_INPUT_LOADER,
|
|
957
|
+
data_files: Optional[list[DataFile]] = None,
|
|
593
958
|
) -> Input:
|
|
594
959
|
"""
|
|
595
960
|
Load input data using the specified loader.
|
|
@@ -611,6 +976,36 @@ def load(
|
|
|
611
976
|
- `InputFormat.CSV`: the data is a `list[dict[str, Any]]`
|
|
612
977
|
- `InputFormat.CSV_ARCHIVE`: the data is a `dict[str, list[dict[str, Any]]]`
|
|
613
978
|
Each key is the name of the CSV file, minus the `.csv` extension.
|
|
979
|
+
- `InputFormat.MULTI_FILE`: the data is a `dict[str, Any]`
|
|
980
|
+
where each key is the file name (with extension) and the value is the
|
|
981
|
+
data read from the file. This is used for loading multiple files in a
|
|
982
|
+
single input, where each file can be of different types (JSON, CSV,
|
|
983
|
+
Excel, etc.). The data is loaded as a dict of items, where each item
|
|
984
|
+
corresponds to a file and its content.
|
|
985
|
+
|
|
986
|
+
When specifying `input_format` as `InputFormat.MULTI_FILE`, the
|
|
987
|
+
`data_files` argument must be provided. This argument is a list of
|
|
988
|
+
`DataFile` instances, each representing a file to be read. Each `DataFile`
|
|
989
|
+
instance should have a `name` (the file name with extension) and a `loader`
|
|
990
|
+
function that reads the data from the file. The `loader` function should
|
|
991
|
+
accept the file path as its first argument and return the data read from
|
|
992
|
+
the file. The `loader` can also accept additional positional and keyword
|
|
993
|
+
arguments, which can be provided through the `loader_args` and
|
|
994
|
+
`loader_kwargs` attributes of the `DataFile` instance.
|
|
995
|
+
|
|
996
|
+
There are convenience functions that can be used to create `DataFile`
|
|
997
|
+
classes, such as:
|
|
998
|
+
|
|
999
|
+
- `json_data_file`: Creates a `DataFile` that reads JSON data.
|
|
1000
|
+
- `csv_data_file`: Creates a `DataFile` that reads CSV data.
|
|
1001
|
+
- `text_data_file`: Creates a `DataFile` that reads utf-8 encoded text
|
|
1002
|
+
data.
|
|
1003
|
+
|
|
1004
|
+
When workiing with data in other formats, such as Excel files, you are
|
|
1005
|
+
encouraged to create your own `DataFile` objects with your own
|
|
1006
|
+
implementation of the `loader` function. This allows you to read data
|
|
1007
|
+
from files in a way that suits your needs, while still adhering to the
|
|
1008
|
+
`DataFile` interface.
|
|
614
1009
|
|
|
615
1010
|
Parameters
|
|
616
1011
|
----------
|
|
@@ -629,6 +1024,24 @@ def load(
|
|
|
629
1024
|
loader : InputLoader, optional
|
|
630
1025
|
The loader to use for loading the input data.
|
|
631
1026
|
Default is an instance of `LocalInputLoader`.
|
|
1027
|
+
data_files : list[DataFile], optional
|
|
1028
|
+
List of `DataFile` instances to read from. This is used when the
|
|
1029
|
+
`input_format` is set to `InputFormat.MULTI_FILE`. Each `DataFile`
|
|
1030
|
+
instance should have a `name` (the file name with extension) and a
|
|
1031
|
+
`loader` function that reads the data from the file. The `loader`
|
|
1032
|
+
function should accept the file path as its first argument and return
|
|
1033
|
+
the data read from the file. The `loader` can also accept additional
|
|
1034
|
+
positional and keyword arguments, which can be provided through the
|
|
1035
|
+
`loader_args` and `loader_kwargs` attributes of the `DataFile`
|
|
1036
|
+
instance.
|
|
1037
|
+
|
|
1038
|
+
There are convenience functions that can be used to create `DataFile`
|
|
1039
|
+
classes, such as `json_data_file`, `csv_data_file`, and
|
|
1040
|
+
`text_data_file`. When working with data in other formats, such as
|
|
1041
|
+
Excel files, you are encouraged to create your own `DataFile` objects
|
|
1042
|
+
with your own implementation of the `loader` function. This allows you
|
|
1043
|
+
to read data from files in a way that suits your needs, while still
|
|
1044
|
+
adhering to the `DataFile` interface.
|
|
632
1045
|
|
|
633
1046
|
Returns
|
|
634
1047
|
-------
|
|
@@ -651,4 +1064,4 @@ def load(
|
|
|
651
1064
|
>>> input_obj = load(input_format=InputFormat.CSV_ARCHIVE, path="input_dir")
|
|
652
1065
|
"""
|
|
653
1066
|
|
|
654
|
-
return loader.load(input_format, options, path, csv_configurations)
|
|
1067
|
+
return loader.load(input_format, options, path, csv_configurations, data_files)
|
nextmv/model.py
CHANGED
|
@@ -24,7 +24,7 @@ from typing import Any, Optional
|
|
|
24
24
|
|
|
25
25
|
from nextmv.input import Input
|
|
26
26
|
from nextmv.logger import log
|
|
27
|
-
from nextmv.options import Options
|
|
27
|
+
from nextmv.options import Options, OptionsEnforcement
|
|
28
28
|
from nextmv.output import Output
|
|
29
29
|
|
|
30
30
|
# The following block of code is used to suppress warnings from mlflow. We
|
|
@@ -132,6 +132,9 @@ class ModelConfiguration:
|
|
|
132
132
|
formatted as they would appear in a requirements.txt file.
|
|
133
133
|
options : Options, optional
|
|
134
134
|
Options that the decision model requires.
|
|
135
|
+
options_enforcement:
|
|
136
|
+
Enforcement of options for the model. This controls how options
|
|
137
|
+
are handled when the model is run.
|
|
135
138
|
|
|
136
139
|
Examples
|
|
137
140
|
--------
|
|
@@ -139,17 +142,23 @@ class ModelConfiguration:
|
|
|
139
142
|
>>> config = ModelConfiguration(
|
|
140
143
|
... name="my_routing_model",
|
|
141
144
|
... requirements=["nextroute>=1.0.0"],
|
|
142
|
-
... options=Options({"max_time": 60})
|
|
145
|
+
... options=Options({"max_time": 60}),
|
|
146
|
+
... options_enforcement=OptionsEnforcement(
|
|
147
|
+
strict=True,
|
|
148
|
+
validation_enforce=True
|
|
149
|
+
)
|
|
143
150
|
... )
|
|
144
151
|
"""
|
|
145
152
|
|
|
146
153
|
name: str
|
|
147
154
|
"""The name of the decision model."""
|
|
148
|
-
|
|
149
155
|
requirements: Optional[list[str]] = None
|
|
150
156
|
"""A list of Python dependencies that the decision model requires."""
|
|
151
157
|
options: Optional[Options] = None
|
|
152
158
|
"""Options that the decision model requires."""
|
|
159
|
+
options_enforcement: Optional[OptionsEnforcement] = None
|
|
160
|
+
"""Enforcement of options for the model."""
|
|
161
|
+
|
|
153
162
|
|
|
154
163
|
|
|
155
164
|
class Model:
|