nextmv 0.28.3.dev0__tar.gz → 0.28.3.dev1__tar.gz
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-0.28.3.dev0 → nextmv-0.28.3.dev1}/PKG-INFO +1 -1
- nextmv-0.28.3.dev1/nextmv/__about__.py +1 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/application.py +21 -9
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/client.py +4 -9
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/run.py +3 -3
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/input.py +1 -2
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/output.py +52 -10
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_output.py +9 -1
- nextmv-0.28.3.dev0/nextmv/__about__.py +0 -1
- nextmv-0.28.3.dev0/nextmv/serialization.py +0 -67
- nextmv-0.28.3.dev0/tests/test_serialization.py +0 -53
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/.gitignore +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/LICENSE +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/README.md +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/base_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/account.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/batch_experiment.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/manifest.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/package.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/safe.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/status.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/cloud/version.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/deprecated.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/logger.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/nextmv/options.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/pyproject.toml +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/requirements.txt +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/app.yaml +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_application.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_client.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_manifest.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_package.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_run.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options1.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options2.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options3.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options4.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options5.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options6.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options7.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_base_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_input.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_logger.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_options.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev1}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.28.3.dev1"
|
|
@@ -66,7 +66,6 @@ from nextmv.logger import log
|
|
|
66
66
|
from nextmv.model import Model, ModelConfiguration
|
|
67
67
|
from nextmv.options import Options
|
|
68
68
|
from nextmv.output import Output
|
|
69
|
-
from nextmv.serialization import serialize_json
|
|
70
69
|
|
|
71
70
|
# Maximum size of the run input/output in bytes. This constant defines the
|
|
72
71
|
# maximum allowed size for run inputs and outputs. When the size exceeds this
|
|
@@ -1589,7 +1588,7 @@ class Application:
|
|
|
1589
1588
|
if isinstance(v, str):
|
|
1590
1589
|
options_dict[k] = v
|
|
1591
1590
|
else:
|
|
1592
|
-
options_dict[k] =
|
|
1591
|
+
options_dict[k] = json.dumps(v)
|
|
1593
1592
|
|
|
1594
1593
|
payload = {}
|
|
1595
1594
|
if upload_id_used:
|
|
@@ -2831,7 +2830,7 @@ class Application:
|
|
|
2831
2830
|
"""
|
|
2832
2831
|
|
|
2833
2832
|
if isinstance(input, dict):
|
|
2834
|
-
input =
|
|
2833
|
+
input = json.dumps(input)
|
|
2835
2834
|
|
|
2836
2835
|
self.client.upload_to_presigned_url(
|
|
2837
2836
|
url=upload_url.upload_url,
|
|
@@ -3175,7 +3174,7 @@ class Application:
|
|
|
3175
3174
|
raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
|
|
3176
3175
|
|
|
3177
3176
|
|
|
3178
|
-
def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any, bool]]) -> Any:
|
|
3177
|
+
def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any, bool]]) -> Any: # noqa: C901
|
|
3179
3178
|
"""
|
|
3180
3179
|
Poll a function until it succeeds or the polling strategy is exhausted.
|
|
3181
3180
|
|
|
@@ -3254,6 +3253,7 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3254
3253
|
stopped = False
|
|
3255
3254
|
|
|
3256
3255
|
# Begin the polling process.
|
|
3256
|
+
max_reached = False
|
|
3257
3257
|
for ix in range(polling_options.max_tries):
|
|
3258
3258
|
# Check is we should stop polling according to the stop callback.
|
|
3259
3259
|
if polling_options.stop is not None and polling_options.stop():
|
|
@@ -3280,12 +3280,24 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3280
3280
|
)
|
|
3281
3281
|
|
|
3282
3282
|
# Calculate the delay.
|
|
3283
|
-
delay =
|
|
3284
|
-
|
|
3285
|
-
|
|
3283
|
+
delay = 0.0
|
|
3284
|
+
if max_reached:
|
|
3285
|
+
# If we already reached the maximum, we don't want to further calculate the
|
|
3286
|
+
# delay to avoid overflows.
|
|
3287
|
+
delay = polling_options.max_delay
|
|
3288
|
+
delay += random.uniform(0, polling_options.jitter) # Add jitter.
|
|
3289
|
+
else:
|
|
3290
|
+
delay = polling_options.delay # Base
|
|
3291
|
+
delay += polling_options.backoff * (2**ix) # Add exponential backoff.
|
|
3292
|
+
delay += random.uniform(0, polling_options.jitter) # Add jitter.
|
|
3293
|
+
|
|
3294
|
+
# We cannot exceed the max delay.
|
|
3295
|
+
if delay >= polling_options.max_delay:
|
|
3296
|
+
max_reached = True
|
|
3297
|
+
delay = polling_options.max_delay
|
|
3286
3298
|
|
|
3287
|
-
# Sleep for the calculated delay.
|
|
3288
|
-
sleep_duration =
|
|
3299
|
+
# Sleep for the calculated delay.
|
|
3300
|
+
sleep_duration = delay
|
|
3289
3301
|
if polling_options.verbose:
|
|
3290
3302
|
log(f"polling | sleeping for duration: {sleep_duration}")
|
|
3291
3303
|
|
|
@@ -14,6 +14,7 @@ get_size(obj)
|
|
|
14
14
|
Finds the size of an object in bytes.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
import json
|
|
17
18
|
import os
|
|
18
19
|
from dataclasses import dataclass, field
|
|
19
20
|
from typing import IO, Any, Optional, Union
|
|
@@ -23,8 +24,6 @@ import requests
|
|
|
23
24
|
import yaml
|
|
24
25
|
from requests.adapters import HTTPAdapter, Retry
|
|
25
26
|
|
|
26
|
-
from nextmv.serialization import serialize_json
|
|
27
|
-
|
|
28
27
|
_MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
|
|
29
28
|
"""int: Maximum size of the payload handled by the Nextmv Cloud API.
|
|
30
29
|
|
|
@@ -294,11 +293,7 @@ class Client:
|
|
|
294
293
|
if data is not None:
|
|
295
294
|
kwargs["data"] = data
|
|
296
295
|
if payload is not None:
|
|
297
|
-
|
|
298
|
-
data = serialize_json(payload)
|
|
299
|
-
kwargs["data"] = data
|
|
300
|
-
else:
|
|
301
|
-
raise ValueError("payload must be a dictionary or a list")
|
|
296
|
+
kwargs["json"] = payload
|
|
302
297
|
if query_params is not None:
|
|
303
298
|
kwargs["params"] = query_params
|
|
304
299
|
|
|
@@ -346,7 +341,7 @@ class Client:
|
|
|
346
341
|
|
|
347
342
|
upload_data: Optional[str] = None
|
|
348
343
|
if isinstance(data, dict):
|
|
349
|
-
upload_data =
|
|
344
|
+
upload_data = json.dumps(data, separators=(",", ":"))
|
|
350
345
|
elif isinstance(data, str):
|
|
351
346
|
upload_data = data
|
|
352
347
|
else:
|
|
@@ -441,7 +436,7 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
|
441
436
|
"""
|
|
442
437
|
|
|
443
438
|
if isinstance(obj, dict):
|
|
444
|
-
obj_str =
|
|
439
|
+
obj_str = json.dumps(obj, separators=(",", ":"))
|
|
445
440
|
return len(obj_str.encode("utf-8"))
|
|
446
441
|
|
|
447
442
|
elif hasattr(obj, "read"):
|
|
@@ -39,6 +39,7 @@ run_duration(start, end)
|
|
|
39
39
|
Calculate the duration of a run in milliseconds.
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
+
import json
|
|
42
43
|
from dataclasses import dataclass
|
|
43
44
|
from datetime import datetime
|
|
44
45
|
from enum import Enum
|
|
@@ -50,7 +51,6 @@ from nextmv.base_model import BaseModel
|
|
|
50
51
|
from nextmv.cloud.status import Status, StatusV2
|
|
51
52
|
from nextmv.input import Input, InputFormat
|
|
52
53
|
from nextmv.output import Output, OutputFormat
|
|
53
|
-
from nextmv.serialization import serialize_json
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def run_duration(start: Union[datetime, float], end: Union[datetime, float]) -> int:
|
|
@@ -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
|
+
_ = json.dumps(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
|
+
_ = json.dumps(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
|
|
|
@@ -33,7 +33,6 @@ from typing import Any, Optional, Union
|
|
|
33
33
|
|
|
34
34
|
from nextmv.deprecated import deprecated
|
|
35
35
|
from nextmv.options import Options
|
|
36
|
-
from nextmv.serialization import serialize_json
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
class InputFormat(str, Enum):
|
|
@@ -140,7 +139,7 @@ class Input:
|
|
|
140
139
|
|
|
141
140
|
if self.input_format == InputFormat.JSON:
|
|
142
141
|
try:
|
|
143
|
-
_ =
|
|
142
|
+
_ = json.dumps(self.data)
|
|
144
143
|
except (TypeError, OverflowError) as e:
|
|
145
144
|
raise ValueError(
|
|
146
145
|
f"Input has input_format InputFormat.JSON and "
|
|
@@ -42,6 +42,8 @@ write
|
|
|
42
42
|
|
|
43
43
|
import copy
|
|
44
44
|
import csv
|
|
45
|
+
import datetime
|
|
46
|
+
import json
|
|
45
47
|
import os
|
|
46
48
|
import sys
|
|
47
49
|
from dataclasses import dataclass
|
|
@@ -54,7 +56,6 @@ from nextmv.base_model import BaseModel
|
|
|
54
56
|
from nextmv.deprecated import deprecated
|
|
55
57
|
from nextmv.logger import reset_stdout
|
|
56
58
|
from nextmv.options import Options
|
|
57
|
-
from nextmv.serialization import serialize_json
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
class RunStatistics(BaseModel):
|
|
@@ -643,7 +644,7 @@ class Output:
|
|
|
643
644
|
|
|
644
645
|
if self.output_format == OutputFormat.JSON:
|
|
645
646
|
try:
|
|
646
|
-
_ =
|
|
647
|
+
_ = json.dumps(self.solution, default=_custom_serial)
|
|
647
648
|
except (TypeError, OverflowError) as e:
|
|
648
649
|
raise ValueError(
|
|
649
650
|
f"Output has output_format OutputFormat.JSON and "
|
|
@@ -721,6 +722,12 @@ class Output:
|
|
|
721
722
|
and self.csv_configurations != {}
|
|
722
723
|
):
|
|
723
724
|
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
|
|
724
731
|
|
|
725
732
|
return output_dict
|
|
726
733
|
|
|
@@ -815,9 +822,19 @@ class LocalOutputWriter(OutputWriter):
|
|
|
815
822
|
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
816
823
|
json_configurations = output.json_configurations
|
|
817
824
|
|
|
818
|
-
|
|
825
|
+
indent, custom_serial = 2, _custom_serial
|
|
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(
|
|
819
834
|
output_dict,
|
|
820
|
-
|
|
835
|
+
indent=indent,
|
|
836
|
+
default=custom_serial,
|
|
837
|
+
**json_configurations,
|
|
821
838
|
)
|
|
822
839
|
|
|
823
840
|
if path is None or path == "":
|
|
@@ -860,17 +877,13 @@ class LocalOutputWriter(OutputWriter):
|
|
|
860
877
|
if not os.path.exists(dir_path):
|
|
861
878
|
os.makedirs(dir_path)
|
|
862
879
|
|
|
863
|
-
|
|
864
|
-
if hasattr(output, "json_configurations") and output.json_configurations is not None:
|
|
865
|
-
json_configurations = output.json_configurations
|
|
866
|
-
|
|
867
|
-
serialized = serialize_json(
|
|
880
|
+
serialized = json.dumps(
|
|
868
881
|
{
|
|
869
882
|
"options": output_dict.get("options", {}),
|
|
870
883
|
"statistics": output_dict.get("statistics", {}),
|
|
871
884
|
"assets": output_dict.get("assets", []),
|
|
872
885
|
},
|
|
873
|
-
|
|
886
|
+
indent=2,
|
|
874
887
|
)
|
|
875
888
|
print(serialized, file=sys.stdout)
|
|
876
889
|
|
|
@@ -1105,3 +1118,32 @@ def write(
|
|
|
1105
1118
|
"""
|
|
1106
1119
|
|
|
1107
1120
|
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")
|
|
@@ -118,6 +118,13 @@ class TestOutput(unittest.TestCase):
|
|
|
118
118
|
result = output.to_dict()
|
|
119
119
|
self.assertEqual(result["assets"][0]["name"], "asset3")
|
|
120
120
|
|
|
121
|
+
# Test with JSON configurations
|
|
122
|
+
json_config = {"indent": 4, "sort_keys": True}
|
|
123
|
+
output = nextmv.Output(output_format=nextmv.OutputFormat.JSON, json_configurations=json_config)
|
|
124
|
+
result = output.to_dict()
|
|
125
|
+
self.assertEqual(result["json_configurations"]["indent"], 4)
|
|
126
|
+
self.assertEqual(result["json_configurations"]["sort_keys"], True)
|
|
127
|
+
|
|
121
128
|
# Test with CSV configurations
|
|
122
129
|
csv_config = {"delimiter": ";", "quoting": csv.QUOTE_NONNUMERIC}
|
|
123
130
|
output = nextmv.Output(output_format=nextmv.OutputFormat.CSV_ARCHIVE, csv_configurations=csv_config)
|
|
@@ -176,6 +183,7 @@ class TestOutput(unittest.TestCase):
|
|
|
176
183
|
self.assertEqual(result["assets"][0]["name"], "asset1")
|
|
177
184
|
self.assertEqual(result["assets"][0]["visual"]["schema"], "chartjs")
|
|
178
185
|
self.assertEqual(result["solution"]["value"], 42)
|
|
186
|
+
self.assertEqual(result["json_configurations"]["indent"], 4)
|
|
179
187
|
|
|
180
188
|
def test_local_writer_json_stdout_default(self):
|
|
181
189
|
output = nextmv.Output(
|
|
@@ -254,7 +262,7 @@ class TestOutput(unittest.TestCase):
|
|
|
254
262
|
|
|
255
263
|
self.assertEqual(
|
|
256
264
|
mock_stdout.getvalue(),
|
|
257
|
-
'{"assets":[],"options":{},"solution":{"empanadas":"are_life"},"statistics":{"foo":"bar"}}\n',
|
|
265
|
+
'{"assets":[],"json_configurations":{"separators":[",",":"],"sort_keys":true},"options":{},"solution":{"empanadas":"are_life"},"statistics":{"foo":"bar"}}\n',
|
|
258
266
|
)
|
|
259
267
|
|
|
260
268
|
def test_local_writer_json_stdout_with_options(self):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v0.28.3.dev0"
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import json
|
|
3
|
-
from typing import Any, Union
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def serialize_json(
|
|
7
|
-
obj: Union[dict, list],
|
|
8
|
-
json_configurations: dict[str, Any] = None,
|
|
9
|
-
) -> str:
|
|
10
|
-
"""
|
|
11
|
-
Serialize a Python object (dict or list) to a JSON string.
|
|
12
|
-
|
|
13
|
-
Parameters
|
|
14
|
-
----------
|
|
15
|
-
obj : Union[dict, list]
|
|
16
|
-
The Python object to serialize.
|
|
17
|
-
json_configurations : dict, optional
|
|
18
|
-
Additional configurations for JSON serialization. This allows customization
|
|
19
|
-
of the Python `json.dumps` function. You can specify parameters like `indent`
|
|
20
|
-
for pretty printing or `default` for custom serialization functions.
|
|
21
|
-
|
|
22
|
-
Returns
|
|
23
|
-
-------
|
|
24
|
-
str
|
|
25
|
-
A JSON string representation of the object.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
# Apply some default configuration if not provided
|
|
29
|
-
json_configurations = json_configurations or {}
|
|
30
|
-
if "default" not in json_configurations:
|
|
31
|
-
json_configurations["default"] = _custom_serial
|
|
32
|
-
if "separators" not in json_configurations:
|
|
33
|
-
json_configurations["separators"] = (",", ":")
|
|
34
|
-
|
|
35
|
-
return json.dumps(
|
|
36
|
-
obj,
|
|
37
|
-
**json_configurations,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _custom_serial(obj: Any) -> str:
|
|
42
|
-
"""
|
|
43
|
-
JSON serializer for objects not serializable by default json serializer.
|
|
44
|
-
|
|
45
|
-
This function provides custom serialization for datetime objects, converting
|
|
46
|
-
them to ISO format strings.
|
|
47
|
-
|
|
48
|
-
Parameters
|
|
49
|
-
----------
|
|
50
|
-
obj : Any
|
|
51
|
-
The object to serialize.
|
|
52
|
-
|
|
53
|
-
Returns
|
|
54
|
-
-------
|
|
55
|
-
str
|
|
56
|
-
The serialized representation of the object.
|
|
57
|
-
|
|
58
|
-
Raises
|
|
59
|
-
------
|
|
60
|
-
TypeError
|
|
61
|
-
If the object type is not supported for serialization.
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
65
|
-
return obj.isoformat()
|
|
66
|
-
|
|
67
|
-
raise TypeError(f"Type {type(obj)} not serializable")
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import json
|
|
3
|
-
import unittest
|
|
4
|
-
|
|
5
|
-
import nextmv.serialization
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestSerialization(unittest.TestCase):
|
|
9
|
-
"""Tests for the common serialization functionality."""
|
|
10
|
-
|
|
11
|
-
def test_default_serialization(self):
|
|
12
|
-
"""Test the default serialization"""
|
|
13
|
-
|
|
14
|
-
data = {
|
|
15
|
-
"name": "Test",
|
|
16
|
-
"value": 42,
|
|
17
|
-
"timestamp": nextmv.serialization._custom_serial(datetime.datetime(2023, 10, 1)),
|
|
18
|
-
}
|
|
19
|
-
serialized = nextmv.serialization.serialize_json(data)
|
|
20
|
-
expected = json.dumps(
|
|
21
|
-
{
|
|
22
|
-
"name": "Test",
|
|
23
|
-
"value": 42,
|
|
24
|
-
"timestamp": "2023-10-01T00:00:00",
|
|
25
|
-
},
|
|
26
|
-
separators=(",", ":"),
|
|
27
|
-
)
|
|
28
|
-
self.assertEqual(serialized, expected)
|
|
29
|
-
|
|
30
|
-
def test_custom_serialization(self):
|
|
31
|
-
"""Test custom serialization with additional configurations"""
|
|
32
|
-
|
|
33
|
-
data = {
|
|
34
|
-
"name": "Test",
|
|
35
|
-
"value": 42,
|
|
36
|
-
"timestamp": nextmv.serialization._custom_serial(datetime.datetime(2023, 10, 1)),
|
|
37
|
-
}
|
|
38
|
-
json_configurations = {
|
|
39
|
-
"indent": 2,
|
|
40
|
-
"default": nextmv.serialization._custom_serial,
|
|
41
|
-
"separators": (",", ": "),
|
|
42
|
-
}
|
|
43
|
-
serialized = nextmv.serialization.serialize_json(data, json_configurations)
|
|
44
|
-
expected = json.dumps(
|
|
45
|
-
{
|
|
46
|
-
"name": "Test",
|
|
47
|
-
"value": 42,
|
|
48
|
-
"timestamp": "2023-10-01T00:00:00",
|
|
49
|
-
},
|
|
50
|
-
indent=2,
|
|
51
|
-
separators=(",", ": "),
|
|
52
|
-
)
|
|
53
|
-
self.assertEqual(serialized, expected)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|