nextmv 0.26.3__py3-none-any.whl → 0.27.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +3 -5
- nextmv/cloud/application.py +21 -17
- nextmv/cloud/manifest.py +187 -25
- nextmv/cloud/run.py +6 -6
- nextmv/cloud/scenario.py +1 -1
- nextmv/input.py +2 -2
- nextmv/model.py +3 -3
- nextmv/options.py +20 -3
- nextmv/output.py +166 -128
- {nextmv-0.26.3.dist-info → nextmv-0.27.0.dist-info}/METADATA +1 -1
- {nextmv-0.26.3.dist-info → nextmv-0.27.0.dist-info}/RECORD +14 -14
- {nextmv-0.26.3.dist-info → nextmv-0.27.0.dist-info}/WHEEL +0 -0
- {nextmv-0.26.3.dist-info → nextmv-0.27.0.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "v0.
|
|
1
|
+
__version__ = "v0.27.0"
|
nextmv/__entrypoint__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
When working in a notebook environment, we don
|
|
2
|
+
When working in a notebook environment, we don't really create a `main.py` file
|
|
3
3
|
with the main entrypoint of the program. Because the logic is mostly encoded
|
|
4
4
|
inside the `Model` class, we need to create a `main.py` file that we can run in
|
|
5
5
|
Nextmv Cloud. This file is used as that entrypoint. It is not intended for a
|
|
@@ -19,9 +19,7 @@ def main() -> None:
|
|
|
19
19
|
manifest = cloud.Manifest.from_yaml(".")
|
|
20
20
|
|
|
21
21
|
# Load the options from the manifest.
|
|
22
|
-
options =
|
|
23
|
-
if manifest.options is not None:
|
|
24
|
-
options = manifest.extract_options()
|
|
22
|
+
options = manifest.extract_options()
|
|
25
23
|
|
|
26
24
|
# Load the model.
|
|
27
25
|
loaded_model = load_model(
|
|
@@ -29,7 +27,7 @@ def main() -> None:
|
|
|
29
27
|
suppress_warnings=True,
|
|
30
28
|
)
|
|
31
29
|
|
|
32
|
-
# Load the input and solve the model by using mlflow
|
|
30
|
+
# Load the input and solve the model by using mlflow's inference API.
|
|
33
31
|
input = nextmv.load(options=options)
|
|
34
32
|
output = loaded_model.predict(input)
|
|
35
33
|
|
nextmv/cloud/application.py
CHANGED
|
@@ -881,7 +881,7 @@ class Application:
|
|
|
881
881
|
instance_id: Optional[str]
|
|
882
882
|
ID of the instance to use for the input set. This is used to
|
|
883
883
|
filter the runs associated with the input set. If not provided,
|
|
884
|
-
the application
|
|
884
|
+
the application's `default_instance_id` is used.
|
|
885
885
|
maximum_runs: Optional[int]
|
|
886
886
|
Maximum number of runs to use for the input set. This is used to
|
|
887
887
|
filter the runs associated with the input set. If not provided,
|
|
@@ -997,7 +997,7 @@ class Application:
|
|
|
997
997
|
description: Optional[str] = None,
|
|
998
998
|
upload_id: Optional[str] = None,
|
|
999
999
|
run_id: Optional[str] = None,
|
|
1000
|
-
format: Optional[Union[Format, dict[str,
|
|
1000
|
+
format: Optional[Union[Format, dict[str, Any]]] = None,
|
|
1001
1001
|
) -> ManagedInput:
|
|
1002
1002
|
"""
|
|
1003
1003
|
Create a new managed input. There are two methods for creating a
|
|
@@ -1076,9 +1076,9 @@ class Application:
|
|
|
1076
1076
|
description: Optional[str] = None,
|
|
1077
1077
|
upload_id: Optional[str] = None,
|
|
1078
1078
|
options: Optional[Union[Options, dict[str, str]]] = None,
|
|
1079
|
-
configuration: Optional[Union[RunConfiguration, dict[str,
|
|
1079
|
+
configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
|
|
1080
1080
|
batch_experiment_id: Optional[str] = None,
|
|
1081
|
-
external_result: Optional[Union[ExternalRunResult, dict[str,
|
|
1081
|
+
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1082
1082
|
) -> str:
|
|
1083
1083
|
"""
|
|
1084
1084
|
Submit an input to start a new run of the application. Returns the
|
|
@@ -1112,7 +1112,7 @@ class Application:
|
|
|
1112
1112
|
used, the options are extracted from the `.to_cloud_dict()` method.
|
|
1113
1113
|
Note that specifying `options` overrides the `input.options` (if
|
|
1114
1114
|
the `input` is of type `nextmv.Input`).
|
|
1115
|
-
configuration: Optional[Union[RunConfiguration, dict[str,
|
|
1115
|
+
configuration: Optional[Union[RunConfiguration, dict[str, Any]]]
|
|
1116
1116
|
Configuration to use for the run. This can be a
|
|
1117
1117
|
`cloud.RunConfiguration` object or a dict. If the object is used,
|
|
1118
1118
|
then the `.to_dict()` method is applied to extract the
|
|
@@ -1120,7 +1120,7 @@ class Application:
|
|
|
1120
1120
|
batch_experiment_id: Optional[str]
|
|
1121
1121
|
ID of a batch experiment to associate the run with. This is used
|
|
1122
1122
|
when the run is part of a batch experiment.
|
|
1123
|
-
external_result: Optional[Union[ExternalRunResult, dict[str,
|
|
1123
|
+
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]]
|
|
1124
1124
|
External result to use for the run. This can be a
|
|
1125
1125
|
`cloud.ExternalRunResult` object or a dict. If the object is used,
|
|
1126
1126
|
then the `.to_dict()` method is applied to extract the
|
|
@@ -1228,9 +1228,9 @@ class Application:
|
|
|
1228
1228
|
upload_id: Optional[str] = None,
|
|
1229
1229
|
run_options: Optional[Union[Options, dict[str, str]]] = None,
|
|
1230
1230
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
1231
|
-
configuration: Optional[Union[RunConfiguration, dict[str,
|
|
1231
|
+
configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
|
|
1232
1232
|
batch_experiment_id: Optional[str] = None,
|
|
1233
|
-
external_result: Optional[Union[ExternalRunResult, dict[str,
|
|
1233
|
+
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1234
1234
|
) -> RunResult:
|
|
1235
1235
|
"""
|
|
1236
1236
|
Submit an input to start a new run of the application and poll for the
|
|
@@ -1271,7 +1271,7 @@ class Application:
|
|
|
1271
1271
|
convenience method that combines the `new_run` and
|
|
1272
1272
|
`run_result_with_polling` methods, applying polling logic to check
|
|
1273
1273
|
when the run succeeded.
|
|
1274
|
-
configuration: Optional[Union[RunConfiguration, dict[str,
|
|
1274
|
+
configuration: Optional[Union[RunConfiguration, dict[str, Any]]]
|
|
1275
1275
|
Configuration to use for the run. This can be a
|
|
1276
1276
|
`cloud.RunConfiguration` object or a dict. If the object is used,
|
|
1277
1277
|
then the `.to_dict()` method is applied to extract the
|
|
@@ -1279,7 +1279,7 @@ class Application:
|
|
|
1279
1279
|
batch_experiment_id: Optional[str]
|
|
1280
1280
|
ID of a batch experiment to associate the run with. This is used
|
|
1281
1281
|
when the run is part of a batch experiment.
|
|
1282
|
-
external_result: Optional[Union[ExternalRunResult, dict[str,
|
|
1282
|
+
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]]
|
|
1283
1283
|
External result to use for the run. This can be a
|
|
1284
1284
|
`cloud.ExternalRunResult` object or a dict. If the object is used,
|
|
1285
1285
|
then the `.to_dict()` method is applied to extract the
|
|
@@ -1555,7 +1555,7 @@ class Application:
|
|
|
1555
1555
|
exception will be raised.
|
|
1556
1556
|
|
|
1557
1557
|
There are two ways to push an app to Nextmv Cloud:
|
|
1558
|
-
1. Specifying `app_dir`, which is the path to an app
|
|
1558
|
+
1. Specifying `app_dir`, which is the path to an app's root directory.
|
|
1559
1559
|
This acts as an external strategy, where the app is composed of files
|
|
1560
1560
|
in a directory and those apps are packaged and pushed to Nextmv Cloud.
|
|
1561
1561
|
2. Specifying a `model` and `model_configuration`. This acts as an
|
|
@@ -1566,7 +1566,7 @@ class Application:
|
|
|
1566
1566
|
Examples
|
|
1567
1567
|
-------
|
|
1568
1568
|
|
|
1569
|
-
1. Push an app using an external strategy, i.e., specifying the app
|
|
1569
|
+
1. Push an app using an external strategy, i.e., specifying the app's
|
|
1570
1570
|
directory:
|
|
1571
1571
|
```python
|
|
1572
1572
|
import os
|
|
@@ -1640,7 +1640,7 @@ class Application:
|
|
|
1640
1640
|
manifest : Optional[Manifest], optional
|
|
1641
1641
|
The manifest for the app, by default None.
|
|
1642
1642
|
app_dir : Optional[str], optional
|
|
1643
|
-
The path to the app
|
|
1643
|
+
The path to the app's directory, by default None.
|
|
1644
1644
|
verbose : bool, optional
|
|
1645
1645
|
Whether to print verbose output, by default False.
|
|
1646
1646
|
"""
|
|
@@ -1793,7 +1793,7 @@ class Application:
|
|
|
1793
1793
|
requests.HTTPError: If the response status code is not 2xx.
|
|
1794
1794
|
"""
|
|
1795
1795
|
|
|
1796
|
-
def polling_func() -> tuple[
|
|
1796
|
+
def polling_func() -> tuple[Any, bool]:
|
|
1797
1797
|
run_information = self.run_metadata(run_id=run_id)
|
|
1798
1798
|
if run_information.metadata.status_v2 in {
|
|
1799
1799
|
StatusV2.succeeded,
|
|
@@ -2327,6 +2327,10 @@ class Application:
|
|
|
2327
2327
|
"runtime": manifest.runtime,
|
|
2328
2328
|
},
|
|
2329
2329
|
}
|
|
2330
|
+
|
|
2331
|
+
if manifest.configuration is not None and manifest.configuration.options is not None:
|
|
2332
|
+
activation_request["requirements"]["options"] = manifest.configuration.options.to_dict()
|
|
2333
|
+
|
|
2330
2334
|
response = self.client.request(
|
|
2331
2335
|
method="PUT",
|
|
2332
2336
|
endpoint=endpoint,
|
|
@@ -2402,11 +2406,11 @@ class Application:
|
|
|
2402
2406
|
raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
|
|
2403
2407
|
|
|
2404
2408
|
|
|
2405
|
-
def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[
|
|
2409
|
+
def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any, bool]]) -> Any:
|
|
2406
2410
|
"""
|
|
2407
2411
|
Auxiliary function for polling.
|
|
2408
2412
|
|
|
2409
|
-
The `polling_func` is a callable that must return a `tuple[
|
|
2413
|
+
The `polling_func` is a callable that must return a `tuple[Any, bool]`
|
|
2410
2414
|
where the first element is the result of the polling and the second
|
|
2411
2415
|
element is a boolean indicating if the polling was successful or should be
|
|
2412
2416
|
retried.
|
|
@@ -2424,7 +2428,7 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[any,
|
|
|
2424
2428
|
|
|
2425
2429
|
Returns
|
|
2426
2430
|
-------
|
|
2427
|
-
|
|
2431
|
+
Any
|
|
2428
2432
|
Result of the polling function.
|
|
2429
2433
|
"""
|
|
2430
2434
|
|
nextmv/cloud/manifest.py
CHANGED
|
@@ -16,8 +16,19 @@ FILE_NAME = "app.yaml"
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class ManifestType(str, Enum):
|
|
19
|
-
"""
|
|
20
|
-
|
|
19
|
+
"""
|
|
20
|
+
Type of application in the manifest, based on the programming
|
|
21
|
+
language.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
PYTHON: str
|
|
26
|
+
Python format
|
|
27
|
+
GO: str
|
|
28
|
+
Go format
|
|
29
|
+
JAVA: str
|
|
30
|
+
Java format
|
|
31
|
+
"""
|
|
21
32
|
|
|
22
33
|
PYTHON = "python"
|
|
23
34
|
"""Python format"""
|
|
@@ -28,7 +39,23 @@ class ManifestType(str, Enum):
|
|
|
28
39
|
|
|
29
40
|
|
|
30
41
|
class ManifestRuntime(str, Enum):
|
|
31
|
-
"""
|
|
42
|
+
"""
|
|
43
|
+
Runtime (environment) where the app will be run on Nextmv Cloud.
|
|
44
|
+
|
|
45
|
+
Attributes
|
|
46
|
+
----------
|
|
47
|
+
DEFAULT: str
|
|
48
|
+
This runtime is used to run compiled applications such as Go binaries.
|
|
49
|
+
PYTHON: str
|
|
50
|
+
This runtime is used as the basis for all other Python runtimes and
|
|
51
|
+
Python applications.
|
|
52
|
+
JAVA: str
|
|
53
|
+
This runtime is used to run Java applications.
|
|
54
|
+
PYOMO: str
|
|
55
|
+
This runtime provisions Python packages to run Pyomo applications.
|
|
56
|
+
HEXALY: str
|
|
57
|
+
This runtime provisions Python packages to run Hexaly applications.
|
|
58
|
+
"""
|
|
32
59
|
|
|
33
60
|
DEFAULT = "ghcr.io/nextmv-io/runtime/default:latest"
|
|
34
61
|
"""This runtime is used to run compiled applications such as Go binaries."""
|
|
@@ -49,7 +76,20 @@ class ManifestRuntime(str, Enum):
|
|
|
49
76
|
|
|
50
77
|
|
|
51
78
|
class ManifestBuild(BaseModel):
|
|
52
|
-
"""
|
|
79
|
+
"""
|
|
80
|
+
Build-specific attributes.
|
|
81
|
+
|
|
82
|
+
Attributes
|
|
83
|
+
----------
|
|
84
|
+
command: Optional[str]
|
|
85
|
+
The command to run to build the app. This command will be executed
|
|
86
|
+
without a shell, i.e., directly. The command must exit with a status of
|
|
87
|
+
0 to continue the push process of the app to Nextmv Cloud. This command
|
|
88
|
+
is executed prior to the pre-push command.
|
|
89
|
+
environment: Optional[dict[str, Any]]
|
|
90
|
+
Environment variables to set when running the build command given as
|
|
91
|
+
key-value pairs.
|
|
92
|
+
"""
|
|
53
93
|
|
|
54
94
|
command: Optional[str] = None
|
|
55
95
|
"""
|
|
@@ -82,7 +122,19 @@ class ManifestBuild(BaseModel):
|
|
|
82
122
|
|
|
83
123
|
|
|
84
124
|
class ManifestPythonModel(BaseModel):
|
|
85
|
-
"""
|
|
125
|
+
"""
|
|
126
|
+
Model-specific instructions for a Python app.
|
|
127
|
+
|
|
128
|
+
Attributes
|
|
129
|
+
----------
|
|
130
|
+
name: str
|
|
131
|
+
The name of the decision model.
|
|
132
|
+
options: Optional[list[dict[str, Any]]]
|
|
133
|
+
Options for the decision model. This is a data representation of the
|
|
134
|
+
`nextmv.Options` class. It consists of a list of dicts. Each dict
|
|
135
|
+
represents the `nextmv.Option` class. It is used to be able to
|
|
136
|
+
reconstruct an Options object from data when loading a decision model.
|
|
137
|
+
"""
|
|
86
138
|
|
|
87
139
|
name: str
|
|
88
140
|
"""The name of the decision model."""
|
|
@@ -96,7 +148,18 @@ class ManifestPythonModel(BaseModel):
|
|
|
96
148
|
|
|
97
149
|
|
|
98
150
|
class ManifestPython(BaseModel):
|
|
99
|
-
"""
|
|
151
|
+
"""
|
|
152
|
+
Python-specific instructions.
|
|
153
|
+
|
|
154
|
+
Attributes
|
|
155
|
+
----------
|
|
156
|
+
pip_requirements: Optional[str]
|
|
157
|
+
Path to a requirements.txt file containing (additional) Python
|
|
158
|
+
dependencies that will be bundled with the app.
|
|
159
|
+
model: Optional[ManifestPythonModel]
|
|
160
|
+
Information about an encoded decision model as handlded via mlflow. This
|
|
161
|
+
information is used to load the decision model from the app bundle.
|
|
162
|
+
"""
|
|
100
163
|
|
|
101
164
|
pip_requirements: Optional[str] = Field(
|
|
102
165
|
serialization_alias="pip-requirements",
|
|
@@ -115,7 +178,23 @@ class ManifestPython(BaseModel):
|
|
|
115
178
|
|
|
116
179
|
|
|
117
180
|
class ManifestOption(BaseModel):
|
|
118
|
-
"""
|
|
181
|
+
"""
|
|
182
|
+
An option for the decision model that is recorded in the manifest.
|
|
183
|
+
|
|
184
|
+
Attributes
|
|
185
|
+
----------
|
|
186
|
+
name: str
|
|
187
|
+
The name of the option.
|
|
188
|
+
option_type: str
|
|
189
|
+
The type of the option. This is a string representation of the
|
|
190
|
+
`nextmv.Option` class.
|
|
191
|
+
default: Optional[Any]
|
|
192
|
+
The default value of the option.
|
|
193
|
+
description: Optional[str]
|
|
194
|
+
The description of the option.
|
|
195
|
+
required: bool
|
|
196
|
+
Whether the option is required or not.
|
|
197
|
+
"""
|
|
119
198
|
|
|
120
199
|
name: str
|
|
121
200
|
"""The name of the option"""
|
|
@@ -131,8 +210,13 @@ class ManifestOption(BaseModel):
|
|
|
131
210
|
"""The description of the option"""
|
|
132
211
|
required: bool = False
|
|
133
212
|
"""Whether the option is required or not"""
|
|
134
|
-
|
|
135
|
-
"""
|
|
213
|
+
additional_attributes: Optional[dict[str, Any]] = None
|
|
214
|
+
"""
|
|
215
|
+
Optional additional attributes for the option. The Nextmv Cloud may
|
|
216
|
+
perform validation on these attributes. For example, the maximum length of
|
|
217
|
+
a string or the maximum value of an integer. These additional attributes
|
|
218
|
+
will be shown in the help message of the `Options`.
|
|
219
|
+
"""
|
|
136
220
|
|
|
137
221
|
@classmethod
|
|
138
222
|
def from_option(cls, option: Option) -> "ManifestOption":
|
|
@@ -153,9 +237,9 @@ class ManifestOption(BaseModel):
|
|
|
153
237
|
if option_type is str:
|
|
154
238
|
option_type = "string"
|
|
155
239
|
elif option_type is bool:
|
|
156
|
-
option_type = "
|
|
240
|
+
option_type = "bool"
|
|
157
241
|
elif option_type is int:
|
|
158
|
-
option_type = "
|
|
242
|
+
option_type = "int"
|
|
159
243
|
elif option_type is float:
|
|
160
244
|
option_type = "float"
|
|
161
245
|
else:
|
|
@@ -167,7 +251,7 @@ class ManifestOption(BaseModel):
|
|
|
167
251
|
default=option.default,
|
|
168
252
|
description=option.description,
|
|
169
253
|
required=option.required,
|
|
170
|
-
|
|
254
|
+
additional_attributes=option.additional_attributes,
|
|
171
255
|
)
|
|
172
256
|
|
|
173
257
|
def to_option(self) -> Option:
|
|
@@ -183,9 +267,9 @@ class ManifestOption(BaseModel):
|
|
|
183
267
|
option_type_string = self.option_type
|
|
184
268
|
if option_type_string == "string":
|
|
185
269
|
option_type = str
|
|
186
|
-
elif option_type_string == "
|
|
270
|
+
elif option_type_string == "bool":
|
|
187
271
|
option_type = bool
|
|
188
|
-
elif option_type_string == "
|
|
272
|
+
elif option_type_string == "int":
|
|
189
273
|
option_type = int
|
|
190
274
|
elif option_type_string == "float":
|
|
191
275
|
option_type = float
|
|
@@ -198,10 +282,47 @@ class ManifestOption(BaseModel):
|
|
|
198
282
|
default=self.default,
|
|
199
283
|
description=self.description,
|
|
200
284
|
required=self.required,
|
|
201
|
-
|
|
285
|
+
additional_attributes=self.additional_attributes,
|
|
202
286
|
)
|
|
203
287
|
|
|
204
288
|
|
|
289
|
+
class ManifestOptions(BaseModel):
|
|
290
|
+
"""
|
|
291
|
+
Options for the decision model.
|
|
292
|
+
|
|
293
|
+
Attributes
|
|
294
|
+
----------
|
|
295
|
+
strict: bool
|
|
296
|
+
If strict is set to `True`, only the listed options will be allowed.
|
|
297
|
+
items: list[ManifestOption]
|
|
298
|
+
Optional. The actual list of options for the decision model. An option
|
|
299
|
+
is a parameter that configures the decision model.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
strict: Optional[bool] = False
|
|
303
|
+
"""If strict is set to `True`, only the listed options will be allowed."""
|
|
304
|
+
items: Optional[list[ManifestOption]] = None
|
|
305
|
+
"""
|
|
306
|
+
Optional. The actual list of options for the decision model. An option is a
|
|
307
|
+
parameter that configures the decision model.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class ManifestConfiguration(BaseModel):
|
|
312
|
+
"""
|
|
313
|
+
Configuration for the decision model.
|
|
314
|
+
|
|
315
|
+
Attributes
|
|
316
|
+
----------
|
|
317
|
+
options: ManifestOptions
|
|
318
|
+
Optional. The actual list of options for the decision model. An option
|
|
319
|
+
is a parameter that configures the decision model.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
options: ManifestOptions
|
|
323
|
+
"""Options for the decision model."""
|
|
324
|
+
|
|
325
|
+
|
|
205
326
|
class Manifest(BaseModel):
|
|
206
327
|
"""
|
|
207
328
|
An application that runs on the Nextmv Platform must contain a file named
|
|
@@ -210,6 +331,34 @@ class Manifest(BaseModel):
|
|
|
210
331
|
|
|
211
332
|
This class represents the app manifest and allows you to load it from a
|
|
212
333
|
file or create it programmatically.
|
|
334
|
+
|
|
335
|
+
Attributes
|
|
336
|
+
----------
|
|
337
|
+
files: list[str]
|
|
338
|
+
Mandatory. The files to include (or exclude) in the app.
|
|
339
|
+
runtime: ManifestRuntime
|
|
340
|
+
Mandatory. The runtime to use for the app, it provides the environment
|
|
341
|
+
in which the app runs.
|
|
342
|
+
type: ManifestType
|
|
343
|
+
Mandatory. Type of application, based on the programming language.
|
|
344
|
+
build: Optional[ManifestBuild]
|
|
345
|
+
Optional. Build-specific attributes. The build.command to run to build
|
|
346
|
+
the app. This command will be executed without a shell, i.e., directly.
|
|
347
|
+
The command must exit with a status of 0 to continue the push process of
|
|
348
|
+
the app to Nextmv Cloud. This command is executed prior to the pre-push
|
|
349
|
+
command. The build.environment is used to set environment variables when
|
|
350
|
+
running the build command given as key-value pairs.
|
|
351
|
+
pre_push: Optional[str]
|
|
352
|
+
Optional. A command to run before the app is pushed to the Nextmv Cloud.
|
|
353
|
+
This command can be used to compile a binary, run tests or similar tasks.
|
|
354
|
+
One difference with what is specified under build, is that the command
|
|
355
|
+
will be executed via
|
|
356
|
+
python: Optional[ManifestPython]
|
|
357
|
+
Optional. Only for Python apps. Contains further Python-specific
|
|
358
|
+
attributes.
|
|
359
|
+
configuration: Optional[ManifestConfiguration]
|
|
360
|
+
Optional. A list of options for the decision model. An option is a
|
|
361
|
+
parameter that configures the decision model.
|
|
213
362
|
"""
|
|
214
363
|
|
|
215
364
|
files: list[str]
|
|
@@ -250,7 +399,7 @@ class Manifest(BaseModel):
|
|
|
250
399
|
Optional. Only for Python apps. Contains further Python-specific
|
|
251
400
|
attributes.
|
|
252
401
|
"""
|
|
253
|
-
|
|
402
|
+
configuration: Optional[ManifestConfiguration] = None
|
|
254
403
|
"""
|
|
255
404
|
Optional. A list of options for the decision model. An option is a
|
|
256
405
|
parameter that configures the decision model.
|
|
@@ -292,20 +441,23 @@ class Manifest(BaseModel):
|
|
|
292
441
|
with open(os.path.join(dirpath, FILE_NAME), "w") as file:
|
|
293
442
|
yaml.dump(self.to_dict(), file)
|
|
294
443
|
|
|
295
|
-
def extract_options(self) -> Options:
|
|
444
|
+
def extract_options(self) -> Optional[Options]:
|
|
296
445
|
"""
|
|
297
|
-
Convert the manifest options to a `nextmv.Options` object.
|
|
446
|
+
Convert the manifest options to a `nextmv.Options` object. If the
|
|
447
|
+
manifest does not have valid options defined in
|
|
448
|
+
`.configuration.options.items`, this method simply returns a `None`.
|
|
298
449
|
|
|
299
450
|
Returns
|
|
300
451
|
-------
|
|
301
|
-
Options
|
|
302
|
-
The
|
|
452
|
+
Optional[Options]
|
|
453
|
+
The options extracted from the manifest. If no options are found,
|
|
454
|
+
`None` is returned.
|
|
303
455
|
"""
|
|
304
456
|
|
|
305
|
-
if self.options is None:
|
|
306
|
-
|
|
457
|
+
if self.configuration is None or self.configuration.options is None or self.configuration.options.items is None:
|
|
458
|
+
return None
|
|
307
459
|
|
|
308
|
-
options = [option.to_option() for option in self.options]
|
|
460
|
+
options = [option.to_option() for option in self.configuration.options.items]
|
|
309
461
|
|
|
310
462
|
return Options(*options)
|
|
311
463
|
|
|
@@ -348,7 +500,12 @@ class Manifest(BaseModel):
|
|
|
348
500
|
)
|
|
349
501
|
|
|
350
502
|
if model_configuration.options is not None:
|
|
351
|
-
manifest.
|
|
503
|
+
manifest.configuration = ManifestConfiguration(
|
|
504
|
+
options=ManifestOptions(
|
|
505
|
+
strict=False,
|
|
506
|
+
items=[ManifestOption.from_option(opt) for opt in model_configuration.options.options],
|
|
507
|
+
),
|
|
508
|
+
)
|
|
352
509
|
|
|
353
510
|
return manifest
|
|
354
511
|
|
|
@@ -377,7 +534,12 @@ class Manifest(BaseModel):
|
|
|
377
534
|
runtime=ManifestRuntime.PYTHON,
|
|
378
535
|
type=ManifestType.PYTHON,
|
|
379
536
|
python=ManifestPython(pip_requirements="requirements.txt"),
|
|
380
|
-
|
|
537
|
+
configuration=ManifestConfiguration(
|
|
538
|
+
options=ManifestOptions(
|
|
539
|
+
strict=False,
|
|
540
|
+
items=[ManifestOption.from_option(opt) for opt in options.options],
|
|
541
|
+
),
|
|
542
|
+
),
|
|
381
543
|
)
|
|
382
544
|
|
|
383
545
|
return manifest
|
nextmv/cloud/run.py
CHANGED
|
@@ -248,11 +248,11 @@ class TrackedRun:
|
|
|
248
248
|
|
|
249
249
|
Attributes
|
|
250
250
|
----------
|
|
251
|
-
input : Union[Input, dict[str,
|
|
251
|
+
input : Union[Input, dict[str, Any], str]
|
|
252
252
|
The input of the run being tracked. Please note that if the input
|
|
253
253
|
format is JSON, then the input data must be JSON serializable. This
|
|
254
254
|
field is required.
|
|
255
|
-
output : Union[Output, dict[str,
|
|
255
|
+
output : Union[Output, dict[str, Any], str]
|
|
256
256
|
The output of the run being tracked. Please note that if the output
|
|
257
257
|
format is JSON, then the output data must be JSON serializable. This
|
|
258
258
|
field is required.
|
|
@@ -270,9 +270,9 @@ class TrackedRun:
|
|
|
270
270
|
the log. This field is optional.
|
|
271
271
|
"""
|
|
272
272
|
|
|
273
|
-
input: Union[Input, dict[str,
|
|
273
|
+
input: Union[Input, dict[str, Any], str]
|
|
274
274
|
"""The input of the run being tracked."""
|
|
275
|
-
output: Union[Output, dict[str,
|
|
275
|
+
output: Union[Output, dict[str, Any], str]
|
|
276
276
|
"""The output of the run being tracked. Only JSON output_format is supported."""
|
|
277
277
|
status: TrackedRunStatus
|
|
278
278
|
"""The status of the run being tracked"""
|
|
@@ -303,7 +303,7 @@ class TrackedRun:
|
|
|
303
303
|
try:
|
|
304
304
|
_ = json.dumps(self.input)
|
|
305
305
|
except (TypeError, OverflowError) as e:
|
|
306
|
-
raise ValueError("Input is dict[str,
|
|
306
|
+
raise ValueError("Input is dict[str, Any] but it is not JSON serializable") from e
|
|
307
307
|
|
|
308
308
|
if isinstance(self.output, Output):
|
|
309
309
|
if self.output.output_format != OutputFormat.JSON:
|
|
@@ -312,7 +312,7 @@ class TrackedRun:
|
|
|
312
312
|
try:
|
|
313
313
|
_ = json.dumps(self.output)
|
|
314
314
|
except (TypeError, OverflowError) as e:
|
|
315
|
-
raise ValueError("Output is dict[str,
|
|
315
|
+
raise ValueError("Output is dict[str, Any] but it is not JSON serializable") from e
|
|
316
316
|
|
|
317
317
|
def logs_text(self) -> str:
|
|
318
318
|
"""
|
nextmv/cloud/scenario.py
CHANGED
|
@@ -211,7 +211,7 @@ def _option_sets(scenarios: list[Scenario]) -> dict[str, dict[str, dict[str, str
|
|
|
211
211
|
def _scenarios_by_id(scenarios: list[Scenario]) -> dict[str, Scenario]:
|
|
212
212
|
"""
|
|
213
213
|
This function maps a scenario to its ID. A scenario ID is created if it
|
|
214
|
-
wasn
|
|
214
|
+
wasn't defined. This function also checks that there are no duplicate
|
|
215
215
|
scenario IDs.
|
|
216
216
|
"""
|
|
217
217
|
|
nextmv/input.py
CHANGED
|
@@ -92,7 +92,7 @@ class Input:
|
|
|
92
92
|
new_options = copy.deepcopy(init_options)
|
|
93
93
|
self.options = new_options
|
|
94
94
|
|
|
95
|
-
def to_dict(self) -> dict[str,
|
|
95
|
+
def to_dict(self) -> dict[str, Any]:
|
|
96
96
|
"""
|
|
97
97
|
Convert the input to a dictionary.
|
|
98
98
|
|
|
@@ -102,7 +102,7 @@ class Input:
|
|
|
102
102
|
|
|
103
103
|
Returns
|
|
104
104
|
-------
|
|
105
|
-
dict[str,
|
|
105
|
+
dict[str, Any]
|
|
106
106
|
The input as a dictionary.
|
|
107
107
|
"""
|
|
108
108
|
|
nextmv/model.py
CHANGED
|
@@ -47,7 +47,7 @@ warnings.showwarning = custom_showwarning
|
|
|
47
47
|
# the model requires and that we install and bundle with the app.
|
|
48
48
|
_REQUIREMENTS_FILE = "model_requirements.txt"
|
|
49
49
|
|
|
50
|
-
# When working in a notebook environment, we don
|
|
50
|
+
# When working in a notebook environment, we don't really create a `main.py`
|
|
51
51
|
# file with the main entrypoint of the program. Because the logic is mostly
|
|
52
52
|
# encoded inside the `Model` class, we need to create a `main.py` file that we
|
|
53
53
|
# can run in Nextmv Cloud. This file is used as that entrypoint.
|
|
@@ -148,7 +148,7 @@ class Model:
|
|
|
148
148
|
saved and loaded.
|
|
149
149
|
"""
|
|
150
150
|
|
|
151
|
-
# mlflow is a big package. We don
|
|
151
|
+
# mlflow is a big package. We don't want to make it a dependency of
|
|
152
152
|
# `nextmv` because it is not always needed. We only need it if we are
|
|
153
153
|
# working with the "app from model" logic, which involves working with
|
|
154
154
|
# this `Model` class.
|
|
@@ -180,7 +180,7 @@ class Model:
|
|
|
180
180
|
params: Optional[dict[str, Any]] = None,
|
|
181
181
|
) -> Any:
|
|
182
182
|
"""
|
|
183
|
-
The predict method allows us to work with mlflow
|
|
183
|
+
The predict method allows us to work with mlflow's [python_function]
|
|
184
184
|
model flavor. Warning: This method should not be used or overridden
|
|
185
185
|
directly. Instead, you should implement the `solve` method.
|
|
186
186
|
|
nextmv/options.py
CHANGED
|
@@ -66,7 +66,7 @@ class Parameter:
|
|
|
66
66
|
def __post_init__(self):
|
|
67
67
|
deprecated(
|
|
68
68
|
name="Parameter",
|
|
69
|
-
reason="`Parameter` is deprecated, use `Option` instead
|
|
69
|
+
reason="`Parameter` is deprecated, use `Option` instead",
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
@classmethod
|
|
@@ -164,6 +164,11 @@ class Option:
|
|
|
164
164
|
argument, an environment variable or a default value.
|
|
165
165
|
choices : list[Optional[Any]], optional
|
|
166
166
|
Limits values to a specific set of choices.
|
|
167
|
+
additional_attributes : dict[str, Any], optional
|
|
168
|
+
Optional additional attributes for the option. The Nextmv Cloud may
|
|
169
|
+
perform validation on these attributes. For example, the maximum length
|
|
170
|
+
of a string or the maximum value of an integer. These additional
|
|
171
|
+
attributes will be shown in the help message of the `Options`.
|
|
167
172
|
"""
|
|
168
173
|
|
|
169
174
|
name: str
|
|
@@ -189,6 +194,13 @@ class Option:
|
|
|
189
194
|
"""
|
|
190
195
|
choices: Optional[list[Any]] = None
|
|
191
196
|
"""Limits values to a specific set of choices."""
|
|
197
|
+
additional_attributes: Optional[dict[str, Any]] = None
|
|
198
|
+
"""
|
|
199
|
+
Optional additional attributes for the option. The Nextmv Cloud may
|
|
200
|
+
perform validation on these attributes. For example, the maximum length of
|
|
201
|
+
a string or the maximum value of an integer. These additional attributes
|
|
202
|
+
will be shown in the help message of the `Options`.
|
|
203
|
+
"""
|
|
192
204
|
|
|
193
205
|
@classmethod
|
|
194
206
|
def from_dict(cls, data: dict[str, Any]) -> "Option":
|
|
@@ -209,13 +221,14 @@ class Option:
|
|
|
209
221
|
option_type_string = data["option_type"]
|
|
210
222
|
option_type = getattr(builtins, option_type_string.split("'")[1])
|
|
211
223
|
|
|
212
|
-
return
|
|
224
|
+
return cls(
|
|
213
225
|
name=data["name"],
|
|
214
226
|
option_type=option_type,
|
|
215
227
|
default=data.get("default"),
|
|
216
228
|
description=data.get("description"),
|
|
217
229
|
required=data.get("required", False),
|
|
218
230
|
choices=data.get("choices"),
|
|
231
|
+
additional_attributes=data.get("additional_attributes"),
|
|
219
232
|
)
|
|
220
233
|
|
|
221
234
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -235,6 +248,7 @@ class Option:
|
|
|
235
248
|
"description": self.description,
|
|
236
249
|
"required": self.required,
|
|
237
250
|
"choices": self.choices,
|
|
251
|
+
"additional_attributes": self.additional_attributes,
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
|
|
@@ -657,7 +671,7 @@ class Options:
|
|
|
657
671
|
options_by_field_name[option.name.replace("-", "_")] = option
|
|
658
672
|
|
|
659
673
|
# The ipkyernel uses a `-f` argument by default that it passes to the
|
|
660
|
-
# execution. We don
|
|
674
|
+
# execution. We don't want to ignore this argument because we get an
|
|
661
675
|
# error. Fix source: https://stackoverflow.com/a/56349168
|
|
662
676
|
parser.add_argument(
|
|
663
677
|
"-f",
|
|
@@ -729,6 +743,9 @@ class Options:
|
|
|
729
743
|
|
|
730
744
|
description += f" (type: {self._option_type(option).__name__})"
|
|
731
745
|
|
|
746
|
+
if isinstance(option, Option) and option.additional_attributes is not None:
|
|
747
|
+
description += f" (additional attributes: {option.additional_attributes})"
|
|
748
|
+
|
|
732
749
|
if option.description is not None and option.description != "":
|
|
733
750
|
description += f": {option.description}"
|
|
734
751
|
|
nextmv/output.py
CHANGED
|
@@ -249,10 +249,10 @@ class Output:
|
|
|
249
249
|
serialized to the write location.
|
|
250
250
|
|
|
251
251
|
The most important part of the output is the solution, which represents the
|
|
252
|
-
result of the decision problem. The solution
|
|
252
|
+
result of the decision problem. The solution's type must match the
|
|
253
253
|
`output_format`:
|
|
254
254
|
|
|
255
|
-
- `OutputFormat.JSON`: the data must be `dict[str, Any]`.
|
|
255
|
+
- `OutputFormat.JSON`: the data must be `dict[str, Any]`, or `Any`.
|
|
256
256
|
- `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
|
|
257
257
|
Any]]]`. The keys represent the file names where the data should be
|
|
258
258
|
written. The values are lists of dictionaries, where each dictionary
|
|
@@ -263,20 +263,73 @@ class Output:
|
|
|
263
263
|
dictionary, we recommend using the `Statistics` class to ensure that the
|
|
264
264
|
data is correctly formatted.
|
|
265
265
|
|
|
266
|
-
|
|
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.
|
|
270
|
+
|
|
271
|
+
Attributes
|
|
267
272
|
----------
|
|
268
|
-
options : Options,
|
|
269
|
-
Options that the `
|
|
270
|
-
|
|
273
|
+
options : Optional[Union[Options, dict[str, Any]]]
|
|
274
|
+
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]
|
|
271
287
|
Format of the output data. Default is `OutputFormat.JSON`.
|
|
272
|
-
solution : Union[dict[str, Any], dict[str, list[dict[str, Any]]]
|
|
273
|
-
The solution to the decision problem.
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
solution : Optional[Union[dict[str, Any], dict[str, list[dict[str, Any]]]]
|
|
289
|
+
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.
|
|
276
316
|
"""
|
|
277
317
|
|
|
278
|
-
options: Optional[Options] = None
|
|
279
|
-
"""
|
|
318
|
+
options: Optional[Union[Options, dict[str, Any]]] = None
|
|
319
|
+
"""
|
|
320
|
+
Options that the `Output` was created with. These options can be of type
|
|
321
|
+
`Options` or a simple dictionary. If the options are of type `Options`,
|
|
322
|
+
they will be serialized to a dictionary using the `to_dict` method. If
|
|
323
|
+
they are a dictionary, they will be used as is. If the options are not
|
|
324
|
+
provided, an empty dictionary will be used. If the options are of type
|
|
325
|
+
`dict`, then the dictionary should have the following structure:
|
|
326
|
+
```
|
|
327
|
+
{
|
|
328
|
+
"duration": "30",
|
|
329
|
+
"threads": 4,
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
"""
|
|
280
333
|
output_format: Optional[OutputFormat] = OutputFormat.JSON
|
|
281
334
|
"""Format of the output data. Default is `OutputFormat.JSON`."""
|
|
282
335
|
solution: Optional[
|
|
@@ -287,17 +340,33 @@ class Output:
|
|
|
287
340
|
] = None
|
|
288
341
|
"""The solution to the decision problem."""
|
|
289
342
|
statistics: Optional[Union[Statistics, dict[str, Any]]] = None
|
|
290
|
-
"""
|
|
343
|
+
"""
|
|
344
|
+
Statistics of the solution. These statistics can be of type `Statistics` or a
|
|
345
|
+
simple dictionary. If the statistics are of type `Statistics`, they will be
|
|
346
|
+
serialized to a dictionary using the `to_dict` method. If they are a
|
|
347
|
+
dictionary, they will be used as is. If the statistics are not provided, an
|
|
348
|
+
empty dictionary will be used.
|
|
349
|
+
"""
|
|
291
350
|
csv_configurations: Optional[dict[str, Any]] = None
|
|
292
|
-
"""
|
|
293
|
-
|
|
294
|
-
|
|
351
|
+
"""
|
|
352
|
+
Optional configuration for writing CSV files, to be used when the
|
|
353
|
+
`output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
|
|
354
|
+
passed as kwargs to the `DictWriter` class from the `csv` module.
|
|
355
|
+
"""
|
|
295
356
|
json_configurations: Optional[dict[str, Any]] = None
|
|
296
|
-
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"""
|
|
357
|
+
"""
|
|
358
|
+
Optional configuration for writing JSON files, to be used when the
|
|
359
|
+
`output_format` is `OutputFormat.JSON`. These configurations are passed as
|
|
360
|
+
kwargs to the `json.dumps` function.
|
|
361
|
+
"""
|
|
362
|
+
assets: Optional[list[Union[Asset, dict[str, Any]]]] = None
|
|
363
|
+
"""
|
|
364
|
+
Optional list of assets to be included in the output. These assets can be of
|
|
365
|
+
type `Asset` or a simple dictionary. If the assets are of type `Asset`, they
|
|
366
|
+
will be serialized to a dictionary using the `to_dict` method. If they are a
|
|
367
|
+
dictionary, they will be used as is. If the assets are not provided, an
|
|
368
|
+
empty list will be used.
|
|
369
|
+
"""
|
|
301
370
|
|
|
302
371
|
def __post_init__(self):
|
|
303
372
|
"""Check that the solution matches the format given to initialize the
|
|
@@ -327,26 +396,76 @@ class Output:
|
|
|
327
396
|
"output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
328
397
|
)
|
|
329
398
|
|
|
330
|
-
def to_dict(self) -> dict[str,
|
|
399
|
+
def to_dict(self) -> dict[str, Any]: # noqa: C901
|
|
331
400
|
"""
|
|
332
401
|
Convert the `Output` object to a dictionary.
|
|
333
402
|
|
|
334
403
|
Returns
|
|
335
404
|
-------
|
|
336
|
-
dict[str,
|
|
405
|
+
dict[str, Any]
|
|
337
406
|
The dictionary representation of the `Output` object.
|
|
338
407
|
"""
|
|
339
408
|
|
|
409
|
+
# Options need to end up as a dict, so we achieve that based on the
|
|
410
|
+
# type of options that were used to create the class.
|
|
411
|
+
if self.options is None:
|
|
412
|
+
options = {}
|
|
413
|
+
elif isinstance(self.options, Options):
|
|
414
|
+
options = self.options.to_dict()
|
|
415
|
+
elif isinstance(self.options, dict):
|
|
416
|
+
options = self.options
|
|
417
|
+
else:
|
|
418
|
+
raise TypeError(f"unsupported options type: {type(self.options)}, supported types are `Options` or `dict`")
|
|
419
|
+
|
|
420
|
+
# Statistics need to end up as a dict, so we achieve that based on the
|
|
421
|
+
# type of statistics that were used to create the class.
|
|
422
|
+
if self.statistics is None:
|
|
423
|
+
statistics = {}
|
|
424
|
+
elif isinstance(self.statistics, Statistics):
|
|
425
|
+
statistics = self.statistics.to_dict()
|
|
426
|
+
elif isinstance(self.statistics, dict):
|
|
427
|
+
statistics = self.statistics
|
|
428
|
+
else:
|
|
429
|
+
raise TypeError(
|
|
430
|
+
f"unsupported statistics type: {type(self.statistics)}, supported types are `Statistics` or `dict`"
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Assets need to end up as a list of dicts, so we achieve that based on
|
|
434
|
+
# the type of each asset in the list.
|
|
435
|
+
assets = []
|
|
436
|
+
if isinstance(self.assets, list):
|
|
437
|
+
for ix, asset in enumerate(self.assets):
|
|
438
|
+
if isinstance(asset, Asset):
|
|
439
|
+
assets.append(asset.to_dict())
|
|
440
|
+
elif isinstance(asset, dict):
|
|
441
|
+
assets.append(asset)
|
|
442
|
+
else:
|
|
443
|
+
raise TypeError(
|
|
444
|
+
f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`"
|
|
445
|
+
)
|
|
446
|
+
elif self.assets is not None:
|
|
447
|
+
raise TypeError(f"unsupported assets type: {type(self.assets)}, supported types are `list`")
|
|
448
|
+
|
|
340
449
|
output_dict = {
|
|
341
|
-
"options":
|
|
450
|
+
"options": options,
|
|
342
451
|
"solution": self.solution if self.solution is not None else {},
|
|
343
|
-
"statistics":
|
|
344
|
-
"assets":
|
|
452
|
+
"statistics": statistics,
|
|
453
|
+
"assets": assets,
|
|
345
454
|
}
|
|
346
455
|
|
|
347
|
-
if
|
|
456
|
+
# Add the auxiliary configurations to the output dictionary if they are
|
|
457
|
+
# defined and not empty.
|
|
458
|
+
if (
|
|
459
|
+
self.output_format == OutputFormat.CSV_ARCHIVE
|
|
460
|
+
and self.csv_configurations is not None
|
|
461
|
+
and self.csv_configurations != {}
|
|
462
|
+
):
|
|
348
463
|
output_dict["csv_configurations"] = self.csv_configurations
|
|
349
|
-
elif
|
|
464
|
+
elif (
|
|
465
|
+
self.output_format == OutputFormat.JSON
|
|
466
|
+
and self.json_configurations is not None
|
|
467
|
+
and self.json_configurations != {}
|
|
468
|
+
):
|
|
350
469
|
output_dict["json_configurations"] = self.json_configurations
|
|
351
470
|
|
|
352
471
|
return output_dict
|
|
@@ -371,24 +490,9 @@ class LocalOutputWriter(OutputWriter):
|
|
|
371
490
|
|
|
372
491
|
def _write_json(
|
|
373
492
|
output: Union[Output, dict[str, Any], BaseModel],
|
|
374
|
-
|
|
375
|
-
statistics: dict[str, Any],
|
|
376
|
-
assets: list[dict[str, Any]],
|
|
493
|
+
output_dict: dict[str, Any],
|
|
377
494
|
path: Optional[str] = None,
|
|
378
495
|
) -> None:
|
|
379
|
-
if isinstance(output, dict):
|
|
380
|
-
final_output = output
|
|
381
|
-
elif isinstance(output, BaseModel):
|
|
382
|
-
final_output = output.to_dict()
|
|
383
|
-
else:
|
|
384
|
-
solution = output.solution if output.solution is not None else {}
|
|
385
|
-
final_output = {
|
|
386
|
-
"options": options,
|
|
387
|
-
"solution": solution,
|
|
388
|
-
"statistics": statistics,
|
|
389
|
-
"assets": assets,
|
|
390
|
-
}
|
|
391
|
-
|
|
392
496
|
json_configurations = {}
|
|
393
497
|
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
394
498
|
json_configurations = output.json_configurations
|
|
@@ -402,7 +506,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
402
506
|
del json_configurations["default"]
|
|
403
507
|
|
|
404
508
|
serialized = json.dumps(
|
|
405
|
-
|
|
509
|
+
output_dict,
|
|
406
510
|
indent=indent,
|
|
407
511
|
default=custom_serial,
|
|
408
512
|
**json_configurations,
|
|
@@ -416,10 +520,8 @@ class LocalOutputWriter(OutputWriter):
|
|
|
416
520
|
file.write(serialized + "\n")
|
|
417
521
|
|
|
418
522
|
def _write_archive(
|
|
419
|
-
output: Output,
|
|
420
|
-
|
|
421
|
-
statistics: dict[str, Any],
|
|
422
|
-
assets: list[dict[str, Any]],
|
|
523
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
524
|
+
output_dict: dict[str, Any],
|
|
423
525
|
path: Optional[str] = None,
|
|
424
526
|
) -> None:
|
|
425
527
|
dir_path = "output"
|
|
@@ -434,9 +536,9 @@ class LocalOutputWriter(OutputWriter):
|
|
|
434
536
|
|
|
435
537
|
serialized = json.dumps(
|
|
436
538
|
{
|
|
437
|
-
"options": options,
|
|
438
|
-
"statistics": statistics,
|
|
439
|
-
"assets": assets,
|
|
539
|
+
"options": output_dict.get("options", {}),
|
|
540
|
+
"statistics": output_dict.get("statistics", {}),
|
|
541
|
+
"assets": output_dict.get("assets", []),
|
|
440
542
|
},
|
|
441
543
|
indent=2,
|
|
442
544
|
)
|
|
@@ -525,88 +627,24 @@ class LocalOutputWriter(OutputWriter):
|
|
|
525
627
|
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
526
628
|
)
|
|
527
629
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
630
|
+
output_dict = {}
|
|
631
|
+
if isinstance(output, Output):
|
|
632
|
+
output_dict = output.to_dict()
|
|
633
|
+
elif isinstance(output, BaseModel):
|
|
634
|
+
output_dict = output.to_dict()
|
|
635
|
+
elif isinstance(output, dict):
|
|
636
|
+
output_dict = output
|
|
637
|
+
else:
|
|
638
|
+
raise TypeError(
|
|
639
|
+
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
640
|
+
)
|
|
531
641
|
|
|
532
642
|
self.FILE_WRITERS[output_format](
|
|
533
643
|
output=output,
|
|
534
|
-
|
|
535
|
-
statistics=statistics,
|
|
536
|
-
assets=assets,
|
|
644
|
+
output_dict=output_dict,
|
|
537
645
|
path=path,
|
|
538
646
|
)
|
|
539
647
|
|
|
540
|
-
@staticmethod
|
|
541
|
-
def _extract_statistics(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
542
|
-
"""Extract JSON-serializable statistics."""
|
|
543
|
-
|
|
544
|
-
statistics = {}
|
|
545
|
-
|
|
546
|
-
if not isinstance(output, Output):
|
|
547
|
-
return statistics
|
|
548
|
-
|
|
549
|
-
stats = output.statistics
|
|
550
|
-
|
|
551
|
-
if stats is None:
|
|
552
|
-
return statistics
|
|
553
|
-
|
|
554
|
-
if isinstance(stats, Statistics):
|
|
555
|
-
statistics = stats.to_dict()
|
|
556
|
-
elif isinstance(stats, dict):
|
|
557
|
-
statistics = stats
|
|
558
|
-
else:
|
|
559
|
-
raise TypeError(f"unsupported statistics type: {type(stats)}, supported types are `Statistics` or `dict`")
|
|
560
|
-
|
|
561
|
-
return statistics
|
|
562
|
-
|
|
563
|
-
@staticmethod
|
|
564
|
-
def _extract_options(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
|
|
565
|
-
"""Extract JSON-serializable options."""
|
|
566
|
-
|
|
567
|
-
options = {}
|
|
568
|
-
|
|
569
|
-
if not isinstance(output, Output):
|
|
570
|
-
return options
|
|
571
|
-
|
|
572
|
-
opt = output.options
|
|
573
|
-
|
|
574
|
-
if opt is None:
|
|
575
|
-
return options
|
|
576
|
-
|
|
577
|
-
if isinstance(opt, Options):
|
|
578
|
-
options = opt.to_dict()
|
|
579
|
-
elif isinstance(opt, dict):
|
|
580
|
-
options = opt
|
|
581
|
-
else:
|
|
582
|
-
raise TypeError(f"unsupported options type: {type(opt)}, supported types are `Options` or `dict`")
|
|
583
|
-
|
|
584
|
-
return options
|
|
585
|
-
|
|
586
|
-
@staticmethod
|
|
587
|
-
def _extract_assets(output: Union[Output, dict[str, Any]]) -> list[dict[str, Any]]:
|
|
588
|
-
"""Extract JSON-serializable assets."""
|
|
589
|
-
|
|
590
|
-
assets = []
|
|
591
|
-
|
|
592
|
-
if not isinstance(output, Output):
|
|
593
|
-
return assets
|
|
594
|
-
|
|
595
|
-
assts = output.assets
|
|
596
|
-
|
|
597
|
-
if assts is None:
|
|
598
|
-
return assets
|
|
599
|
-
|
|
600
|
-
for ix, asset in enumerate(assts):
|
|
601
|
-
if isinstance(asset, Asset):
|
|
602
|
-
assets.append(asset.to_dict())
|
|
603
|
-
elif isinstance(asset, dict):
|
|
604
|
-
assets.append(asset)
|
|
605
|
-
else:
|
|
606
|
-
raise TypeError(f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`")
|
|
607
|
-
|
|
608
|
-
return assets
|
|
609
|
-
|
|
610
648
|
|
|
611
649
|
def write_local(
|
|
612
650
|
output: Union[Output, dict[str, Any]],
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
nextmv/__about__.py,sha256=
|
|
2
|
-
nextmv/__entrypoint__.py,sha256=
|
|
1
|
+
nextmv/__about__.py,sha256=dPbfXnVUa1GzXrrlZ_hOg9cbB0hO_UUubb09KvnQt_g,24
|
|
2
|
+
nextmv/__entrypoint__.py,sha256=dA0iwwHtrq6Z9w9FxmxKLoBGLyhe7jWtUAU-Y3PEgHg,1094
|
|
3
3
|
nextmv/__init__.py,sha256=QN5e_BFkIdBkR8DiGr9T06N6mXtowT84eRUJj3_Vfrg,1459
|
|
4
4
|
nextmv/base_model.py,sha256=mdaBe-epNK1cFgP4TxbOtn3So4pCi1vMTOrIBkCBp7A,1050
|
|
5
5
|
nextmv/deprecated.py,sha256=ctE39pmJEfoicR-w0EdT7FPtd0cWmP3GKth-FsNn_Ks,406
|
|
6
|
-
nextmv/input.py,sha256=
|
|
6
|
+
nextmv/input.py,sha256=H3Al-VhsiTuSdYL6ld7O7AP-MGHnr8uXq-WENIC76jU,15480
|
|
7
7
|
nextmv/logger.py,sha256=Jo_AmYJ02WqXmsz9XTTOXZ9uenVHrPanMMqhBogxGCw,1075
|
|
8
|
-
nextmv/model.py,sha256=
|
|
9
|
-
nextmv/options.py,sha256=
|
|
10
|
-
nextmv/output.py,sha256=
|
|
8
|
+
nextmv/model.py,sha256=hE0BgeGfIsnR7H624VVX0Zys5utUtrH_xK1sERT0GTQ,9855
|
|
9
|
+
nextmv/options.py,sha256=5NjZbllCPNKzCBfyx-8XXnsz0wkph0WFfngJMAcYjtQ,26314
|
|
10
|
+
nextmv/output.py,sha256=qvFdEoNs-bhT4xjkcLS_6aHN1iU1dCILEhAf1Tafzak,26719
|
|
11
11
|
nextmv/cloud/__init__.py,sha256=EEKJdSMMjW2yc9GwTpz-cmpgtu-Qs7p09k6NXIQPV0U,3726
|
|
12
12
|
nextmv/cloud/acceptance_test.py,sha256=NtqGhj-UYibxGBbU2kfjr-lYcngojb_5VMvK2WZwibI,6620
|
|
13
13
|
nextmv/cloud/account.py,sha256=mZUGzV-uMGBA5BC_FPtsiCMFuz5jxEZ3O1BbELZIm18,1841
|
|
14
|
-
nextmv/cloud/application.py,sha256=
|
|
14
|
+
nextmv/cloud/application.py,sha256=Go9DD1eWC7zMV0s76hld0VNS5QN7GWhu0Kk2Ci_wgFU,84287
|
|
15
15
|
nextmv/cloud/batch_experiment.py,sha256=Ngm1XDvvAMaU9VYU5JFNxXFnt8xjE_34H5W54kO9V5c,3768
|
|
16
16
|
nextmv/cloud/client.py,sha256=JUE3vD767_FFICl1vov5Mxmircci03TBL3KmT1BOZY4,9096
|
|
17
17
|
nextmv/cloud/input_set.py,sha256=HTLA2acJridZbBCAVJNom8ldNo2Rfy40YQ8Coyz3bdo,1482
|
|
18
18
|
nextmv/cloud/instance.py,sha256=UfyfZXfL1ugCGAB6zwZJIAi8qxI1JCUGsluwaGdjfN4,1223
|
|
19
|
-
nextmv/cloud/manifest.py,sha256=
|
|
19
|
+
nextmv/cloud/manifest.py,sha256=dwATuTRFJehXj2WdF3ZvEGH0gE96mK1zikkol38lMgk,18005
|
|
20
20
|
nextmv/cloud/package.py,sha256=Y3RethLiXXW7Y6_QBJDJdd7mFNaYaSBMyasIeGLav8o,12287
|
|
21
|
-
nextmv/cloud/run.py,sha256=
|
|
21
|
+
nextmv/cloud/run.py,sha256=0UUTmDzwI47styBeLR0MIhn4yXmOHNFt3YfbGAZVRB8,10906
|
|
22
22
|
nextmv/cloud/safe.py,sha256=IUQUOlx4ulIIyP2-Fx3-gNBm9nyWOgZgaOAlRrzqaZE,2515
|
|
23
|
-
nextmv/cloud/scenario.py,sha256=
|
|
23
|
+
nextmv/cloud/scenario.py,sha256=Z2Kkaar45IJgkFiVCCpLqe6O3Y3AxqWVl2BGfUp3I_8,8118
|
|
24
24
|
nextmv/cloud/secrets.py,sha256=kqlN4ceww_L4kVTrAU8BZykRzXINO3zhMT_BLYea6tk,1764
|
|
25
25
|
nextmv/cloud/status.py,sha256=C-ax8cLw0jPeh7CPsJkCa0s4ImRyFI4NDJJxI0_1sr4,602
|
|
26
26
|
nextmv/cloud/version.py,sha256=sjVRNRtohHA97j6IuyM33_DSSsXYkZPusYgpb6hlcrc,1244
|
|
27
|
-
nextmv-0.
|
|
28
|
-
nextmv-0.
|
|
29
|
-
nextmv-0.
|
|
30
|
-
nextmv-0.
|
|
27
|
+
nextmv-0.27.0.dist-info/METADATA,sha256=e5ip9F8XzZ5tqjBOpGo41GIfZm2HeuZX1JxuktkpDSY,14557
|
|
28
|
+
nextmv-0.27.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
29
|
+
nextmv-0.27.0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
|
|
30
|
+
nextmv-0.27.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|