nextmv 0.28.3.dev1__py3-none-any.whl → 0.28.4__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/_serialization.py +96 -0
- nextmv/cloud/application.py +33 -12
- nextmv/cloud/client.py +38 -12
- nextmv/cloud/run.py +3 -3
- nextmv/input.py +2 -1
- nextmv/output.py +10 -52
- {nextmv-0.28.3.dev1.dist-info → nextmv-0.28.4.dist-info}/METADATA +2 -2
- {nextmv-0.28.3.dev1.dist-info → nextmv-0.28.4.dist-info}/RECORD +11 -10
- {nextmv-0.28.3.dev1.dist-info → nextmv-0.28.4.dist-info}/WHEEL +0 -0
- {nextmv-0.28.3.dev1.dist-info → nextmv-0.28.4.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "v0.28.
|
|
1
|
+
__version__ = "v0.28.4"
|
nextmv/_serialization.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def deflated_serialize_json(obj: Union[dict, list], json_configurations: dict[str, Any] = None) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Serialize a Python object (dict or list) to a JSON string with default configuration for a deflated format.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
obj : Union[dict, list]
|
|
13
|
+
The Python object to serialize.
|
|
14
|
+
json_configurations : dict, optional
|
|
15
|
+
Additional configurations for JSON serialization. This allows customization
|
|
16
|
+
of the Python `json.dumps` function. You can specify parameters like `indent`
|
|
17
|
+
for pretty printing or `default` for custom serialization functions.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
str
|
|
22
|
+
A JSON string representation of the object.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Apply a default configuration if not provided targeting a deflated format
|
|
26
|
+
json_configurations = json_configurations or {}
|
|
27
|
+
if "default" not in json_configurations:
|
|
28
|
+
json_configurations["default"] = _custom_serial
|
|
29
|
+
if "separators" not in json_configurations:
|
|
30
|
+
json_configurations["separators"] = (",", ":")
|
|
31
|
+
|
|
32
|
+
return json.dumps(
|
|
33
|
+
obj,
|
|
34
|
+
**json_configurations,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def serialize_json(obj: Union[dict, list], json_configurations: dict[str, Any] = None) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Serialize a Python object (dict or list) to a JSON string.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
obj : Union[dict, list]
|
|
45
|
+
The Python object to serialize.
|
|
46
|
+
json_configurations : dict, optional
|
|
47
|
+
Additional configurations for JSON serialization. This allows customization
|
|
48
|
+
of the Python `json.dumps` function. You can specify parameters like `indent`
|
|
49
|
+
for pretty printing or `default` for custom serialization functions.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
str
|
|
54
|
+
A JSON string representation of the object.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Apply some default configuration if not provided
|
|
58
|
+
json_configurations = json_configurations or {}
|
|
59
|
+
if "default" not in json_configurations:
|
|
60
|
+
json_configurations["default"] = _custom_serial
|
|
61
|
+
if "indent" not in json_configurations:
|
|
62
|
+
json_configurations["indent"] = 2
|
|
63
|
+
|
|
64
|
+
return json.dumps(
|
|
65
|
+
obj,
|
|
66
|
+
**json_configurations,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _custom_serial(obj: Any) -> str:
|
|
71
|
+
"""
|
|
72
|
+
JSON serializer for objects not serializable by default json serializer.
|
|
73
|
+
|
|
74
|
+
This function provides custom serialization for datetime objects, converting
|
|
75
|
+
them to ISO format strings.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
obj : Any
|
|
80
|
+
The object to serialize.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
str
|
|
85
|
+
The serialized representation of the object.
|
|
86
|
+
|
|
87
|
+
Raises
|
|
88
|
+
------
|
|
89
|
+
TypeError
|
|
90
|
+
If the object type is not supported for serialization.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
94
|
+
return obj.isoformat()
|
|
95
|
+
|
|
96
|
+
raise TypeError(f"Type {type(obj)} not serializable")
|
nextmv/cloud/application.py
CHANGED
|
@@ -33,6 +33,7 @@ from typing import Any, Optional, Union
|
|
|
33
33
|
|
|
34
34
|
import requests
|
|
35
35
|
|
|
36
|
+
from nextmv._serialization import deflated_serialize_json
|
|
36
37
|
from nextmv.base_model import BaseModel
|
|
37
38
|
from nextmv.cloud import package
|
|
38
39
|
from nextmv.cloud.acceptance_test import AcceptanceTest, ExperimentStatus, Metric
|
|
@@ -184,10 +185,12 @@ class PollingOptions:
|
|
|
184
185
|
"""
|
|
185
186
|
max_delay: float = 20
|
|
186
187
|
"""Maximum delay to use between polls, in seconds."""
|
|
187
|
-
max_duration: float =
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
"""
|
|
188
|
+
max_duration: float = -1
|
|
189
|
+
"""
|
|
190
|
+
Maximum duration of the polling strategy, in seconds. A negative value means no limit.
|
|
191
|
+
"""
|
|
192
|
+
max_tries: int = -1
|
|
193
|
+
"""Maximum number of tries to use. A negative value means no limit."""
|
|
191
194
|
jitter: float = 1
|
|
192
195
|
"""
|
|
193
196
|
Jitter to use for the polling strategy. A uniform distribution is sampled
|
|
@@ -1490,6 +1493,7 @@ class Application:
|
|
|
1490
1493
|
configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
|
|
1491
1494
|
batch_experiment_id: Optional[str] = None,
|
|
1492
1495
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1496
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
1493
1497
|
) -> str:
|
|
1494
1498
|
"""
|
|
1495
1499
|
Submit an input to start a new run of the application. Returns the
|
|
@@ -1538,6 +1542,9 @@ class Application:
|
|
|
1538
1542
|
configuration. This is used when the run is an external run. We
|
|
1539
1543
|
suggest that instead of specifying this parameter, you use the
|
|
1540
1544
|
`track_run` method of the class.
|
|
1545
|
+
json_configurations: Optional[dict[str, Any]]
|
|
1546
|
+
Optional configurations for JSON serialization. This is used to
|
|
1547
|
+
customize the serialization before data is sent.
|
|
1541
1548
|
|
|
1542
1549
|
Returns
|
|
1543
1550
|
----------
|
|
@@ -1588,7 +1595,7 @@ class Application:
|
|
|
1588
1595
|
if isinstance(v, str):
|
|
1589
1596
|
options_dict[k] = v
|
|
1590
1597
|
else:
|
|
1591
|
-
options_dict[k] =
|
|
1598
|
+
options_dict[k] = deflated_serialize_json(v, json_configurations=json_configurations)
|
|
1592
1599
|
|
|
1593
1600
|
payload = {}
|
|
1594
1601
|
if upload_id_used:
|
|
@@ -1626,6 +1633,7 @@ class Application:
|
|
|
1626
1633
|
endpoint=f"{self.endpoint}/runs",
|
|
1627
1634
|
payload=payload,
|
|
1628
1635
|
query_params=query_params,
|
|
1636
|
+
json_configurations=json_configurations,
|
|
1629
1637
|
)
|
|
1630
1638
|
|
|
1631
1639
|
return response.json()["run_id"]
|
|
@@ -2791,6 +2799,7 @@ class Application:
|
|
|
2791
2799
|
self,
|
|
2792
2800
|
input: Union[dict[str, Any], str],
|
|
2793
2801
|
upload_url: UploadURL,
|
|
2802
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
2794
2803
|
) -> None:
|
|
2795
2804
|
"""
|
|
2796
2805
|
Upload large input data to the provided upload URL.
|
|
@@ -2806,6 +2815,9 @@ class Application:
|
|
|
2806
2815
|
converted to JSON, or a pre-formatted JSON string.
|
|
2807
2816
|
upload_url : UploadURL
|
|
2808
2817
|
Upload URL object containing the pre-signed URL to use for uploading.
|
|
2818
|
+
json_configurations : Optional[dict[str, Any]], default=None
|
|
2819
|
+
Optional configurations for JSON serialization. If provided, these
|
|
2820
|
+
configurations will be used when serializing the data via `json.dumps`.
|
|
2809
2821
|
|
|
2810
2822
|
Returns
|
|
2811
2823
|
-------
|
|
@@ -2830,7 +2842,7 @@ class Application:
|
|
|
2830
2842
|
"""
|
|
2831
2843
|
|
|
2832
2844
|
if isinstance(input, dict):
|
|
2833
|
-
input =
|
|
2845
|
+
input = deflated_serialize_json(input, json_configurations=json_configurations)
|
|
2834
2846
|
|
|
2835
2847
|
self.client.upload_to_presigned_url(
|
|
2836
2848
|
url=upload_url.upload_url,
|
|
@@ -3174,7 +3186,11 @@ class Application:
|
|
|
3174
3186
|
raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
|
|
3175
3187
|
|
|
3176
3188
|
|
|
3177
|
-
def poll(
|
|
3189
|
+
def poll( # noqa: C901
|
|
3190
|
+
polling_options: PollingOptions,
|
|
3191
|
+
polling_func: Callable[[], tuple[Any, bool]],
|
|
3192
|
+
__sleep_func: Callable[[float], None] = time.sleep,
|
|
3193
|
+
) -> Any:
|
|
3178
3194
|
"""
|
|
3179
3195
|
Poll a function until it succeeds or the polling strategy is exhausted.
|
|
3180
3196
|
|
|
@@ -3247,14 +3263,20 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3247
3263
|
if polling_options.verbose:
|
|
3248
3264
|
log(f"polling | sleeping for initial delay: {polling_options.initial_delay}")
|
|
3249
3265
|
|
|
3250
|
-
|
|
3266
|
+
__sleep_func(polling_options.initial_delay)
|
|
3251
3267
|
|
|
3252
3268
|
start_time = time.time()
|
|
3253
3269
|
stopped = False
|
|
3254
3270
|
|
|
3255
3271
|
# Begin the polling process.
|
|
3256
3272
|
max_reached = False
|
|
3257
|
-
|
|
3273
|
+
ix = 0
|
|
3274
|
+
while True:
|
|
3275
|
+
# Check if we reached the maximum number of tries. Break if so.
|
|
3276
|
+
if ix >= polling_options.max_tries and polling_options.max_tries >= 0:
|
|
3277
|
+
break
|
|
3278
|
+
ix += 1
|
|
3279
|
+
|
|
3258
3280
|
# Check is we should stop polling according to the stop callback.
|
|
3259
3281
|
if polling_options.stop is not None and polling_options.stop():
|
|
3260
3282
|
stopped = True
|
|
@@ -3274,13 +3296,12 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3274
3296
|
if polling_options.verbose:
|
|
3275
3297
|
log(f"polling | elapsed time: {passed}")
|
|
3276
3298
|
|
|
3277
|
-
if passed >= polling_options.max_duration:
|
|
3299
|
+
if passed >= polling_options.max_duration and polling_options.max_duration >= 0:
|
|
3278
3300
|
raise TimeoutError(
|
|
3279
3301
|
f"polling did not succeed after {passed} seconds, exceeds max duration: {polling_options.max_duration}",
|
|
3280
3302
|
)
|
|
3281
3303
|
|
|
3282
3304
|
# Calculate the delay.
|
|
3283
|
-
delay = 0.0
|
|
3284
3305
|
if max_reached:
|
|
3285
3306
|
# If we already reached the maximum, we don't want to further calculate the
|
|
3286
3307
|
# delay to avoid overflows.
|
|
@@ -3301,7 +3322,7 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3301
3322
|
if polling_options.verbose:
|
|
3302
3323
|
log(f"polling | sleeping for duration: {sleep_duration}")
|
|
3303
3324
|
|
|
3304
|
-
|
|
3325
|
+
__sleep_func(sleep_duration)
|
|
3305
3326
|
|
|
3306
3327
|
if stopped:
|
|
3307
3328
|
log("polling | stop condition met, stopping polling")
|
nextmv/cloud/client.py
CHANGED
|
@@ -14,7 +14,6 @@ get_size(obj)
|
|
|
14
14
|
Finds the size of an object in bytes.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
import json
|
|
18
17
|
import os
|
|
19
18
|
from dataclasses import dataclass, field
|
|
20
19
|
from typing import IO, Any, Optional, Union
|
|
@@ -24,6 +23,8 @@ import requests
|
|
|
24
23
|
import yaml
|
|
25
24
|
from requests.adapters import HTTPAdapter, Retry
|
|
26
25
|
|
|
26
|
+
from nextmv._serialization import deflated_serialize_json
|
|
27
|
+
|
|
27
28
|
_MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
|
|
28
29
|
"""int: Maximum size of the payload handled by the Nextmv Cloud API.
|
|
29
30
|
|
|
@@ -199,6 +200,7 @@ class Client:
|
|
|
199
200
|
headers: Optional[dict[str, str]] = None,
|
|
200
201
|
payload: Optional[dict[str, Any]] = None,
|
|
201
202
|
query_params: Optional[dict[str, Any]] = None,
|
|
203
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
202
204
|
) -> requests.Response:
|
|
203
205
|
"""
|
|
204
206
|
Makes a request to the Nextmv Cloud API.
|
|
@@ -221,6 +223,11 @@ class Client:
|
|
|
221
223
|
provided.
|
|
222
224
|
query_params : dict[str, Any], optional
|
|
223
225
|
Query parameters to append to the request URL.
|
|
226
|
+
json_configurations : dict[str, Any], optional
|
|
227
|
+
Additional configurations for JSON serialization. This allows
|
|
228
|
+
customization of the Python `json.dumps` function, such as
|
|
229
|
+
specifying `indent` for pretty printing or `default` for custom
|
|
230
|
+
serialization functions.
|
|
224
231
|
|
|
225
232
|
Returns
|
|
226
233
|
-------
|
|
@@ -261,16 +268,19 @@ class Client:
|
|
|
261
268
|
if payload is not None and data is not None:
|
|
262
269
|
raise ValueError("cannot use both data and payload")
|
|
263
270
|
|
|
264
|
-
if
|
|
271
|
+
if (
|
|
272
|
+
payload is not None
|
|
273
|
+
and get_size(payload, json_configurations=json_configurations) > _MAX_LAMBDA_PAYLOAD_SIZE
|
|
274
|
+
):
|
|
265
275
|
raise ValueError(
|
|
266
|
-
f"payload size of {get_size(payload)} bytes exceeds
|
|
267
|
-
f"allowed size of {_MAX_LAMBDA_PAYLOAD_SIZE} bytes"
|
|
276
|
+
f"payload size of {get_size(payload, json_configurations=json_configurations)} bytes exceeds "
|
|
277
|
+
+ f"the maximum allowed size of {_MAX_LAMBDA_PAYLOAD_SIZE} bytes"
|
|
268
278
|
)
|
|
269
279
|
|
|
270
|
-
if data is not None and get_size(data) > _MAX_LAMBDA_PAYLOAD_SIZE:
|
|
280
|
+
if data is not None and get_size(data, json_configurations=json_configurations) > _MAX_LAMBDA_PAYLOAD_SIZE:
|
|
271
281
|
raise ValueError(
|
|
272
|
-
f"data size of {get_size(data)} bytes exceeds
|
|
273
|
-
f"allowed size of {_MAX_LAMBDA_PAYLOAD_SIZE} bytes"
|
|
282
|
+
f"data size of {get_size(data, json_configurations=json_configurations)} bytes exceeds "
|
|
283
|
+
+ f"the maximum allowed size of {_MAX_LAMBDA_PAYLOAD_SIZE} bytes"
|
|
274
284
|
)
|
|
275
285
|
|
|
276
286
|
session = requests.Session()
|
|
@@ -293,7 +303,11 @@ class Client:
|
|
|
293
303
|
if data is not None:
|
|
294
304
|
kwargs["data"] = data
|
|
295
305
|
if payload is not None:
|
|
296
|
-
|
|
306
|
+
if isinstance(payload, (dict, list)):
|
|
307
|
+
data = deflated_serialize_json(payload, json_configurations=json_configurations)
|
|
308
|
+
kwargs["data"] = data
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError("payload must be a dictionary or a list")
|
|
297
311
|
if query_params is not None:
|
|
298
312
|
kwargs["params"] = query_params
|
|
299
313
|
|
|
@@ -308,7 +322,9 @@ class Client:
|
|
|
308
322
|
|
|
309
323
|
return response
|
|
310
324
|
|
|
311
|
-
def upload_to_presigned_url(
|
|
325
|
+
def upload_to_presigned_url(
|
|
326
|
+
self, data: Union[dict[str, Any], str], url: str, json_configurations: Optional[dict[str, Any]] = None
|
|
327
|
+
) -> None:
|
|
312
328
|
"""
|
|
313
329
|
Uploads data to a presigned URL.
|
|
314
330
|
|
|
@@ -323,6 +339,11 @@ class Client:
|
|
|
323
339
|
as is.
|
|
324
340
|
url : str
|
|
325
341
|
The presigned URL to which the data will be uploaded.
|
|
342
|
+
json_configurations : dict[str, Any], optional
|
|
343
|
+
Additional configurations for JSON serialization. This allows
|
|
344
|
+
customization of the Python `json.dumps` function, such as
|
|
345
|
+
specifying `indent` for pretty printing or `default` for custom
|
|
346
|
+
serialization functions.
|
|
326
347
|
|
|
327
348
|
Raises
|
|
328
349
|
------
|
|
@@ -341,7 +362,7 @@ class Client:
|
|
|
341
362
|
|
|
342
363
|
upload_data: Optional[str] = None
|
|
343
364
|
if isinstance(data, dict):
|
|
344
|
-
upload_data =
|
|
365
|
+
upload_data = deflated_serialize_json(data, json_configurations=json_configurations)
|
|
345
366
|
elif isinstance(data, str):
|
|
346
367
|
upload_data = data
|
|
347
368
|
else:
|
|
@@ -393,7 +414,7 @@ class Client:
|
|
|
393
414
|
}
|
|
394
415
|
|
|
395
416
|
|
|
396
|
-
def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
417
|
+
def get_size(obj: Union[dict[str, Any], IO[bytes], str], json_configurations: Optional[dict[str, Any]] = None) -> int:
|
|
397
418
|
"""
|
|
398
419
|
Finds the size of an object in bytes.
|
|
399
420
|
|
|
@@ -407,6 +428,11 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
|
407
428
|
- If a dict, it's converted to a JSON string.
|
|
408
429
|
- If a file-like object (e.g., opened file), its size is read.
|
|
409
430
|
- If a string, its UTF-8 encoded byte length is calculated.
|
|
431
|
+
json_configurations : dict[str, Any], optional
|
|
432
|
+
Additional configurations for JSON serialization. This allows
|
|
433
|
+
customization of the Python `json.dumps` function, such as specifying
|
|
434
|
+
`indent` for pretty printing or `default` for custom serialization
|
|
435
|
+
functions.
|
|
410
436
|
|
|
411
437
|
Returns
|
|
412
438
|
-------
|
|
@@ -436,7 +462,7 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
|
436
462
|
"""
|
|
437
463
|
|
|
438
464
|
if isinstance(obj, dict):
|
|
439
|
-
obj_str =
|
|
465
|
+
obj_str = deflated_serialize_json(obj, json_configurations=json_configurations)
|
|
440
466
|
return len(obj_str.encode("utf-8"))
|
|
441
467
|
|
|
442
468
|
elif hasattr(obj, "read"):
|
nextmv/cloud/run.py
CHANGED
|
@@ -39,7 +39,6 @@ run_duration(start, end)
|
|
|
39
39
|
Calculate the duration of a run in milliseconds.
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
-
import json
|
|
43
42
|
from dataclasses import dataclass
|
|
44
43
|
from datetime import datetime
|
|
45
44
|
from enum import Enum
|
|
@@ -47,6 +46,7 @@ from typing import Any, Optional, Union
|
|
|
47
46
|
|
|
48
47
|
from pydantic import AliasChoices, Field
|
|
49
48
|
|
|
49
|
+
from nextmv._serialization import serialize_json
|
|
50
50
|
from nextmv.base_model import BaseModel
|
|
51
51
|
from nextmv.cloud.status import Status, StatusV2
|
|
52
52
|
from nextmv.input import Input, InputFormat
|
|
@@ -628,7 +628,7 @@ class TrackedRun:
|
|
|
628
628
|
raise ValueError("Input.input_format must be JSON.")
|
|
629
629
|
elif isinstance(self.input, dict):
|
|
630
630
|
try:
|
|
631
|
-
_ =
|
|
631
|
+
_ = serialize_json(self.input)
|
|
632
632
|
except (TypeError, OverflowError) as e:
|
|
633
633
|
raise ValueError("Input is dict[str, Any] but it is not JSON serializable") from e
|
|
634
634
|
|
|
@@ -637,7 +637,7 @@ class TrackedRun:
|
|
|
637
637
|
raise ValueError("Output.output_format must be JSON.")
|
|
638
638
|
elif isinstance(self.output, dict):
|
|
639
639
|
try:
|
|
640
|
-
_ =
|
|
640
|
+
_ = serialize_json(self.output)
|
|
641
641
|
except (TypeError, OverflowError) as e:
|
|
642
642
|
raise ValueError("Output is dict[str, Any] but it is not JSON serializable") from e
|
|
643
643
|
|
nextmv/input.py
CHANGED
|
@@ -31,6 +31,7 @@ from dataclasses import dataclass
|
|
|
31
31
|
from enum import Enum
|
|
32
32
|
from typing import Any, Optional, Union
|
|
33
33
|
|
|
34
|
+
from nextmv._serialization import serialize_json
|
|
34
35
|
from nextmv.deprecated import deprecated
|
|
35
36
|
from nextmv.options import Options
|
|
36
37
|
|
|
@@ -139,7 +140,7 @@ class Input:
|
|
|
139
140
|
|
|
140
141
|
if self.input_format == InputFormat.JSON:
|
|
141
142
|
try:
|
|
142
|
-
_ =
|
|
143
|
+
_ = serialize_json(self.data)
|
|
143
144
|
except (TypeError, OverflowError) as e:
|
|
144
145
|
raise ValueError(
|
|
145
146
|
f"Input has input_format InputFormat.JSON and "
|
nextmv/output.py
CHANGED
|
@@ -42,8 +42,6 @@ write
|
|
|
42
42
|
|
|
43
43
|
import copy
|
|
44
44
|
import csv
|
|
45
|
-
import datetime
|
|
46
|
-
import json
|
|
47
45
|
import os
|
|
48
46
|
import sys
|
|
49
47
|
from dataclasses import dataclass
|
|
@@ -52,6 +50,7 @@ from typing import Any, Optional, Union
|
|
|
52
50
|
|
|
53
51
|
from pydantic import AliasChoices, Field
|
|
54
52
|
|
|
53
|
+
from nextmv._serialization import serialize_json
|
|
55
54
|
from nextmv.base_model import BaseModel
|
|
56
55
|
from nextmv.deprecated import deprecated
|
|
57
56
|
from nextmv.logger import reset_stdout
|
|
@@ -644,7 +643,7 @@ class Output:
|
|
|
644
643
|
|
|
645
644
|
if self.output_format == OutputFormat.JSON:
|
|
646
645
|
try:
|
|
647
|
-
_ =
|
|
646
|
+
_ = serialize_json(self.solution)
|
|
648
647
|
except (TypeError, OverflowError) as e:
|
|
649
648
|
raise ValueError(
|
|
650
649
|
f"Output has output_format OutputFormat.JSON and "
|
|
@@ -722,12 +721,6 @@ class Output:
|
|
|
722
721
|
and self.csv_configurations != {}
|
|
723
722
|
):
|
|
724
723
|
output_dict["csv_configurations"] = self.csv_configurations
|
|
725
|
-
elif (
|
|
726
|
-
self.output_format == OutputFormat.JSON
|
|
727
|
-
and self.json_configurations is not None
|
|
728
|
-
and self.json_configurations != {}
|
|
729
|
-
):
|
|
730
|
-
output_dict["json_configurations"] = self.json_configurations
|
|
731
724
|
|
|
732
725
|
return output_dict
|
|
733
726
|
|
|
@@ -822,19 +815,9 @@ class LocalOutputWriter(OutputWriter):
|
|
|
822
815
|
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
823
816
|
json_configurations = output.json_configurations
|
|
824
817
|
|
|
825
|
-
|
|
826
|
-
if "indent" in json_configurations:
|
|
827
|
-
indent = json_configurations["indent"]
|
|
828
|
-
del json_configurations["indent"]
|
|
829
|
-
if "default" in json_configurations:
|
|
830
|
-
custom_serial = json_configurations["default"]
|
|
831
|
-
del json_configurations["default"]
|
|
832
|
-
|
|
833
|
-
serialized = json.dumps(
|
|
818
|
+
serialized = serialize_json(
|
|
834
819
|
output_dict,
|
|
835
|
-
|
|
836
|
-
default=custom_serial,
|
|
837
|
-
**json_configurations,
|
|
820
|
+
json_configurations=json_configurations,
|
|
838
821
|
)
|
|
839
822
|
|
|
840
823
|
if path is None or path == "":
|
|
@@ -877,13 +860,17 @@ class LocalOutputWriter(OutputWriter):
|
|
|
877
860
|
if not os.path.exists(dir_path):
|
|
878
861
|
os.makedirs(dir_path)
|
|
879
862
|
|
|
880
|
-
|
|
863
|
+
json_configurations = {}
|
|
864
|
+
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
865
|
+
json_configurations = output.json_configurations
|
|
866
|
+
|
|
867
|
+
serialized = serialize_json(
|
|
881
868
|
{
|
|
882
869
|
"options": output_dict.get("options", {}),
|
|
883
870
|
"statistics": output_dict.get("statistics", {}),
|
|
884
871
|
"assets": output_dict.get("assets", []),
|
|
885
872
|
},
|
|
886
|
-
|
|
873
|
+
json_configurations=json_configurations,
|
|
887
874
|
)
|
|
888
875
|
print(serialized, file=sys.stdout)
|
|
889
876
|
|
|
@@ -1118,32 +1105,3 @@ def write(
|
|
|
1118
1105
|
"""
|
|
1119
1106
|
|
|
1120
1107
|
writer.write(output, path, skip_stdout_reset)
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
def _custom_serial(obj: Any) -> str:
|
|
1124
|
-
"""
|
|
1125
|
-
JSON serializer for objects not serializable by default json serializer.
|
|
1126
|
-
|
|
1127
|
-
This function provides custom serialization for datetime objects, converting
|
|
1128
|
-
them to ISO format strings.
|
|
1129
|
-
|
|
1130
|
-
Parameters
|
|
1131
|
-
----------
|
|
1132
|
-
obj : Any
|
|
1133
|
-
The object to serialize.
|
|
1134
|
-
|
|
1135
|
-
Returns
|
|
1136
|
-
-------
|
|
1137
|
-
str
|
|
1138
|
-
The serialized representation of the object.
|
|
1139
|
-
|
|
1140
|
-
Raises
|
|
1141
|
-
------
|
|
1142
|
-
TypeError
|
|
1143
|
-
If the object type is not supported for serialization.
|
|
1144
|
-
"""
|
|
1145
|
-
|
|
1146
|
-
if isinstance(obj, (datetime.datetime | datetime.date)):
|
|
1147
|
-
return obj.isoformat()
|
|
1148
|
-
|
|
1149
|
-
raise TypeError(f"Type {type(obj)} not serializable")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nextmv
|
|
3
|
-
Version: 0.28.
|
|
3
|
+
Version: 0.28.4
|
|
4
4
|
Summary: The all-purpose Python SDK for Nextmv
|
|
5
5
|
Project-URL: Homepage, https://www.nextmv.io
|
|
6
6
|
Project-URL: Documentation, https://www.nextmv.io/docs/python-sdks/nextmv/installation
|
|
@@ -266,4 +266,4 @@ Install all optional dependencies (recommended):
|
|
|
266
266
|
pip install "nextmv[all]"
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
[docs]: https://nextmv-py.
|
|
269
|
+
[docs]: https://nextmv-py.docs.nextmv.io/en/latest/nextmv/
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
nextmv/__about__.py,sha256=
|
|
1
|
+
nextmv/__about__.py,sha256=i7DSTP6V3pMn78gIv3yrwcKV294-A5BWS2FFPE__RHo,24
|
|
2
2
|
nextmv/__entrypoint__.py,sha256=dA0iwwHtrq6Z9w9FxmxKLoBGLyhe7jWtUAU-Y3PEgHg,1094
|
|
3
3
|
nextmv/__init__.py,sha256=3E3c3dk4hCR1IXDHZxFDDDK2NPEEO8utwdd9wZ0q6ys,1506
|
|
4
|
+
nextmv/_serialization.py,sha256=JlSl6BL0M2Esf7F89GsGIZ__Pp8RnFRNM0UxYhuuYU4,2853
|
|
4
5
|
nextmv/base_model.py,sha256=qmJ4AsYr9Yv01HQX_BERrn3229gyoZrYyP9tcyqNfeU,2311
|
|
5
6
|
nextmv/deprecated.py,sha256=kEVfyQ-nT0v2ePXTNldjQG9uH5IlfQVy3L4tztIxwmU,1638
|
|
6
|
-
nextmv/input.py,sha256=
|
|
7
|
+
nextmv/input.py,sha256=AsOhaw4kjq692BsBBVE4v8JwOHrNbl5dbdnHyHjChd0,21200
|
|
7
8
|
nextmv/logger.py,sha256=kNIbu46MisrzYe4T0hNMpWfRTKKacDVvbtQcNys_c_E,2513
|
|
8
9
|
nextmv/model.py,sha256=ebL_Ta25ptaLkP1_PmPlLM2yh6dzA2e7KRjSJ8RZcOU,14614
|
|
9
10
|
nextmv/options.py,sha256=8hIlTnMMqL12z7GGrR-igVOQVtx4FLtlEqDktoPOxTg,33226
|
|
10
|
-
nextmv/output.py,sha256=
|
|
11
|
+
nextmv/output.py,sha256=bC_N_BSVOiIO3Tilmu-pWhsfoVPHz612oXCWLpVUS_8,36169
|
|
11
12
|
nextmv/cloud/__init__.py,sha256=7BCh3z-XkbIcMvFHmbj2wA8OquIovjrAZL7O9kA9VZc,3868
|
|
12
13
|
nextmv/cloud/acceptance_test.py,sha256=Bcfdmh2fkPeBx8FDCngeUo2fjV_LhsUdygnzDQCDbYY,26898
|
|
13
14
|
nextmv/cloud/account.py,sha256=eukiYQha4U2fkIjg4SgdoawKE1kU5G7GPyDJVrn8hHA,6064
|
|
14
|
-
nextmv/cloud/application.py,sha256=
|
|
15
|
+
nextmv/cloud/application.py,sha256=2DeJE21GXQhxp-aQsJktIOIZR5YzzQco9zBb_ZF8jm4,111813
|
|
15
16
|
nextmv/cloud/batch_experiment.py,sha256=rD3m-ioE1G8ADYN7afzr7zlq-3H22TNlj9RAh-_ZqIo,7270
|
|
16
|
-
nextmv/cloud/client.py,sha256=
|
|
17
|
+
nextmv/cloud/client.py,sha256=YKrJMNx4d-NEtUZ2nUKS46QHldUdVkexJ7CmzbFLFS4,17360
|
|
17
18
|
nextmv/cloud/input_set.py,sha256=2dqmf5z-rZjTKwtBRvnUdfPfKv28It5uTCX0C70uP4Y,4242
|
|
18
19
|
nextmv/cloud/instance.py,sha256=SS4tbp0LQMWDaeYpwcNxJei82oi_Hozv1t5i3QGjASY,4024
|
|
19
20
|
nextmv/cloud/manifest.py,sha256=KLdaHIfyCsjVjE0PXqAoVy4mRH-0KWf1b7TcEICdaJU,30541
|
|
20
21
|
nextmv/cloud/package.py,sha256=RcS8X3rMDyQVv0IWmTEDbmaoDs5nPWoIFBfUgSTuAtA,13028
|
|
21
|
-
nextmv/cloud/run.py,sha256=
|
|
22
|
+
nextmv/cloud/run.py,sha256=zRPYeceNiuBMEcu-XZ2TUESn982HAUWGrwC2t13zHMg,19483
|
|
22
23
|
nextmv/cloud/safe.py,sha256=idifvV8P_79Zo2hIC_qxqZt9LUmD5TLQ9ikKwRUvd34,2522
|
|
23
24
|
nextmv/cloud/scenario.py,sha256=JRFTDiFBcrgud6wE2qDHUu5oO-Ur3zbPYhhB6ONCxTo,14263
|
|
24
25
|
nextmv/cloud/secrets.py,sha256=fA5cX0jfTsPVZWV7433wzETGlXpWRLHGswuObx9e6FQ,6820
|
|
25
26
|
nextmv/cloud/status.py,sha256=blvykRCTCTBkaqH88j4dzdQLhU2v1Ig62-_va98zw20,2789
|
|
26
27
|
nextmv/cloud/version.py,sha256=5_S7_pWUVBFbvAArku20eK7S645GJcHtgE2OpXLdSzQ,5300
|
|
27
|
-
nextmv-0.28.
|
|
28
|
-
nextmv-0.28.
|
|
29
|
-
nextmv-0.28.
|
|
30
|
-
nextmv-0.28.
|
|
28
|
+
nextmv-0.28.4.dist-info/METADATA,sha256=-HewmTWfItAqhUesijP2V5PxF2HeHErBRTdEVQzxY1w,15310
|
|
29
|
+
nextmv-0.28.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
30
|
+
nextmv-0.28.4.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
|
|
31
|
+
nextmv-0.28.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|