nextmv 0.28.3.dev0__tar.gz → 0.28.3.dev2__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.dev2}/PKG-INFO +1 -1
- nextmv-0.28.3.dev2/nextmv/__about__.py +1 -0
- nextmv-0.28.3.dev0/nextmv/serialization.py → nextmv-0.28.3.dev2/nextmv/_serialization.py +35 -6
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/application.py +50 -17
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/client.py +33 -12
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/run.py +1 -1
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/input.py +1 -1
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/output.py +1 -1
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_application.py +30 -4
- nextmv-0.28.3.dev2/tests/test_serialization.py +96 -0
- nextmv-0.28.3.dev0/nextmv/__about__.py +0 -1
- nextmv-0.28.3.dev0/tests/test_serialization.py +0 -53
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/.gitignore +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/LICENSE +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/README.md +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/base_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/account.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/batch_experiment.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/manifest.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/package.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/safe.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/status.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/cloud/version.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/deprecated.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/logger.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/nextmv/options.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/pyproject.toml +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/requirements.txt +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/app.yaml +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_client.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_manifest.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_package.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_run.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options1.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options2.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options3.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options4.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options5.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options6.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options7.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_base_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_input.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_logger.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_model.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_options.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_output.py +0 -0
- {nextmv-0.28.3.dev0 → nextmv-0.28.3.dev2}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.28.3.dev2"
|
|
@@ -3,12 +3,9 @@ import json
|
|
|
3
3
|
from typing import Any, Union
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
obj: Union[dict, list],
|
|
8
|
-
json_configurations: dict[str, Any] = None,
|
|
9
|
-
) -> str:
|
|
6
|
+
def deflated_serialize_json(obj: Union[dict, list], json_configurations: dict[str, Any] = None) -> str:
|
|
10
7
|
"""
|
|
11
|
-
Serialize a Python object (dict or list) to a JSON string.
|
|
8
|
+
Serialize a Python object (dict or list) to a JSON string with default configuration for a deflated format.
|
|
12
9
|
|
|
13
10
|
Parameters
|
|
14
11
|
----------
|
|
@@ -25,7 +22,7 @@ def serialize_json(
|
|
|
25
22
|
A JSON string representation of the object.
|
|
26
23
|
"""
|
|
27
24
|
|
|
28
|
-
# Apply
|
|
25
|
+
# Apply a default configuration if not provided targeting a deflated format
|
|
29
26
|
json_configurations = json_configurations or {}
|
|
30
27
|
if "default" not in json_configurations:
|
|
31
28
|
json_configurations["default"] = _custom_serial
|
|
@@ -38,6 +35,38 @@ def serialize_json(
|
|
|
38
35
|
)
|
|
39
36
|
|
|
40
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
|
+
|
|
41
70
|
def _custom_serial(obj: Any) -> str:
|
|
42
71
|
"""
|
|
43
72
|
JSON serializer for objects not serializable by default json serializer.
|
|
@@ -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
|
|
@@ -66,7 +67,6 @@ from nextmv.logger import log
|
|
|
66
67
|
from nextmv.model import Model, ModelConfiguration
|
|
67
68
|
from nextmv.options import Options
|
|
68
69
|
from nextmv.output import Output
|
|
69
|
-
from nextmv.serialization import serialize_json
|
|
70
70
|
|
|
71
71
|
# Maximum size of the run input/output in bytes. This constant defines the
|
|
72
72
|
# maximum allowed size for run inputs and outputs. When the size exceeds this
|
|
@@ -185,10 +185,12 @@ class PollingOptions:
|
|
|
185
185
|
"""
|
|
186
186
|
max_delay: float = 20
|
|
187
187
|
"""Maximum delay to use between polls, in seconds."""
|
|
188
|
-
max_duration: float =
|
|
189
|
-
"""
|
|
190
|
-
|
|
191
|
-
"""
|
|
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."""
|
|
192
194
|
jitter: float = 1
|
|
193
195
|
"""
|
|
194
196
|
Jitter to use for the polling strategy. A uniform distribution is sampled
|
|
@@ -1491,6 +1493,7 @@ class Application:
|
|
|
1491
1493
|
configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
|
|
1492
1494
|
batch_experiment_id: Optional[str] = None,
|
|
1493
1495
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1496
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
1494
1497
|
) -> str:
|
|
1495
1498
|
"""
|
|
1496
1499
|
Submit an input to start a new run of the application. Returns the
|
|
@@ -1539,6 +1542,9 @@ class Application:
|
|
|
1539
1542
|
configuration. This is used when the run is an external run. We
|
|
1540
1543
|
suggest that instead of specifying this parameter, you use the
|
|
1541
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.
|
|
1542
1548
|
|
|
1543
1549
|
Returns
|
|
1544
1550
|
----------
|
|
@@ -1589,7 +1595,7 @@ class Application:
|
|
|
1589
1595
|
if isinstance(v, str):
|
|
1590
1596
|
options_dict[k] = v
|
|
1591
1597
|
else:
|
|
1592
|
-
options_dict[k] =
|
|
1598
|
+
options_dict[k] = deflated_serialize_json(v, json_configurations=json_configurations)
|
|
1593
1599
|
|
|
1594
1600
|
payload = {}
|
|
1595
1601
|
if upload_id_used:
|
|
@@ -1627,6 +1633,7 @@ class Application:
|
|
|
1627
1633
|
endpoint=f"{self.endpoint}/runs",
|
|
1628
1634
|
payload=payload,
|
|
1629
1635
|
query_params=query_params,
|
|
1636
|
+
json_configurations=json_configurations,
|
|
1630
1637
|
)
|
|
1631
1638
|
|
|
1632
1639
|
return response.json()["run_id"]
|
|
@@ -2792,6 +2799,7 @@ class Application:
|
|
|
2792
2799
|
self,
|
|
2793
2800
|
input: Union[dict[str, Any], str],
|
|
2794
2801
|
upload_url: UploadURL,
|
|
2802
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
2795
2803
|
) -> None:
|
|
2796
2804
|
"""
|
|
2797
2805
|
Upload large input data to the provided upload URL.
|
|
@@ -2807,6 +2815,9 @@ class Application:
|
|
|
2807
2815
|
converted to JSON, or a pre-formatted JSON string.
|
|
2808
2816
|
upload_url : UploadURL
|
|
2809
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`.
|
|
2810
2821
|
|
|
2811
2822
|
Returns
|
|
2812
2823
|
-------
|
|
@@ -2831,7 +2842,7 @@ class Application:
|
|
|
2831
2842
|
"""
|
|
2832
2843
|
|
|
2833
2844
|
if isinstance(input, dict):
|
|
2834
|
-
input =
|
|
2845
|
+
input = deflated_serialize_json(input, json_configurations=json_configurations)
|
|
2835
2846
|
|
|
2836
2847
|
self.client.upload_to_presigned_url(
|
|
2837
2848
|
url=upload_url.upload_url,
|
|
@@ -3175,7 +3186,11 @@ class Application:
|
|
|
3175
3186
|
raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
|
|
3176
3187
|
|
|
3177
3188
|
|
|
3178
|
-
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:
|
|
3179
3194
|
"""
|
|
3180
3195
|
Poll a function until it succeeds or the polling strategy is exhausted.
|
|
3181
3196
|
|
|
@@ -3248,13 +3263,20 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any,
|
|
|
3248
3263
|
if polling_options.verbose:
|
|
3249
3264
|
log(f"polling | sleeping for initial delay: {polling_options.initial_delay}")
|
|
3250
3265
|
|
|
3251
|
-
|
|
3266
|
+
__sleep_func(polling_options.initial_delay)
|
|
3252
3267
|
|
|
3253
3268
|
start_time = time.time()
|
|
3254
3269
|
stopped = False
|
|
3255
3270
|
|
|
3256
3271
|
# Begin the polling process.
|
|
3257
|
-
|
|
3272
|
+
max_reached = False
|
|
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,22 +3296,33 @@ 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
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3305
|
+
if max_reached:
|
|
3306
|
+
# If we already reached the maximum, we don't want to further calculate the
|
|
3307
|
+
# delay to avoid overflows.
|
|
3308
|
+
delay = polling_options.max_delay
|
|
3309
|
+
delay += random.uniform(0, polling_options.jitter) # Add jitter.
|
|
3310
|
+
else:
|
|
3311
|
+
delay = polling_options.delay # Base
|
|
3312
|
+
delay += polling_options.backoff * (2**ix) # Add exponential backoff.
|
|
3313
|
+
delay += random.uniform(0, polling_options.jitter) # Add jitter.
|
|
3314
|
+
|
|
3315
|
+
# We cannot exceed the max delay.
|
|
3316
|
+
if delay >= polling_options.max_delay:
|
|
3317
|
+
max_reached = True
|
|
3318
|
+
delay = polling_options.max_delay
|
|
3286
3319
|
|
|
3287
|
-
# Sleep for the calculated delay.
|
|
3288
|
-
sleep_duration =
|
|
3320
|
+
# Sleep for the calculated delay.
|
|
3321
|
+
sleep_duration = delay
|
|
3289
3322
|
if polling_options.verbose:
|
|
3290
3323
|
log(f"polling | sleeping for duration: {sleep_duration}")
|
|
3291
3324
|
|
|
3292
|
-
|
|
3325
|
+
__sleep_func(sleep_duration)
|
|
3293
3326
|
|
|
3294
3327
|
if stopped:
|
|
3295
3328
|
log("polling | stop condition met, stopping polling")
|
|
@@ -23,7 +23,7 @@ import requests
|
|
|
23
23
|
import yaml
|
|
24
24
|
from requests.adapters import HTTPAdapter, Retry
|
|
25
25
|
|
|
26
|
-
from nextmv.
|
|
26
|
+
from nextmv._serialization import deflated_serialize_json
|
|
27
27
|
|
|
28
28
|
_MAX_LAMBDA_PAYLOAD_SIZE: int = 500 * 1024 * 1024
|
|
29
29
|
"""int: Maximum size of the payload handled by the Nextmv Cloud API.
|
|
@@ -200,6 +200,7 @@ class Client:
|
|
|
200
200
|
headers: Optional[dict[str, str]] = None,
|
|
201
201
|
payload: Optional[dict[str, Any]] = None,
|
|
202
202
|
query_params: Optional[dict[str, Any]] = None,
|
|
203
|
+
json_configurations: Optional[dict[str, Any]] = None,
|
|
203
204
|
) -> requests.Response:
|
|
204
205
|
"""
|
|
205
206
|
Makes a request to the Nextmv Cloud API.
|
|
@@ -222,6 +223,11 @@ class Client:
|
|
|
222
223
|
provided.
|
|
223
224
|
query_params : dict[str, Any], optional
|
|
224
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.
|
|
225
231
|
|
|
226
232
|
Returns
|
|
227
233
|
-------
|
|
@@ -262,16 +268,19 @@ class Client:
|
|
|
262
268
|
if payload is not None and data is not None:
|
|
263
269
|
raise ValueError("cannot use both data and payload")
|
|
264
270
|
|
|
265
|
-
if
|
|
271
|
+
if (
|
|
272
|
+
payload is not None
|
|
273
|
+
and get_size(payload, json_configurations=json_configurations) > _MAX_LAMBDA_PAYLOAD_SIZE
|
|
274
|
+
):
|
|
266
275
|
raise ValueError(
|
|
267
|
-
f"payload size of {get_size(payload)} bytes exceeds
|
|
268
|
-
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"
|
|
269
278
|
)
|
|
270
279
|
|
|
271
|
-
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:
|
|
272
281
|
raise ValueError(
|
|
273
|
-
f"data size of {get_size(data)} bytes exceeds
|
|
274
|
-
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"
|
|
275
284
|
)
|
|
276
285
|
|
|
277
286
|
session = requests.Session()
|
|
@@ -295,7 +304,7 @@ class Client:
|
|
|
295
304
|
kwargs["data"] = data
|
|
296
305
|
if payload is not None:
|
|
297
306
|
if isinstance(payload, (dict, list)):
|
|
298
|
-
data =
|
|
307
|
+
data = deflated_serialize_json(payload, json_configurations=json_configurations)
|
|
299
308
|
kwargs["data"] = data
|
|
300
309
|
else:
|
|
301
310
|
raise ValueError("payload must be a dictionary or a list")
|
|
@@ -313,7 +322,9 @@ class Client:
|
|
|
313
322
|
|
|
314
323
|
return response
|
|
315
324
|
|
|
316
|
-
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:
|
|
317
328
|
"""
|
|
318
329
|
Uploads data to a presigned URL.
|
|
319
330
|
|
|
@@ -328,6 +339,11 @@ class Client:
|
|
|
328
339
|
as is.
|
|
329
340
|
url : str
|
|
330
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.
|
|
331
347
|
|
|
332
348
|
Raises
|
|
333
349
|
------
|
|
@@ -346,7 +362,7 @@ class Client:
|
|
|
346
362
|
|
|
347
363
|
upload_data: Optional[str] = None
|
|
348
364
|
if isinstance(data, dict):
|
|
349
|
-
upload_data =
|
|
365
|
+
upload_data = deflated_serialize_json(data, json_configurations=json_configurations)
|
|
350
366
|
elif isinstance(data, str):
|
|
351
367
|
upload_data = data
|
|
352
368
|
else:
|
|
@@ -398,7 +414,7 @@ class Client:
|
|
|
398
414
|
}
|
|
399
415
|
|
|
400
416
|
|
|
401
|
-
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:
|
|
402
418
|
"""
|
|
403
419
|
Finds the size of an object in bytes.
|
|
404
420
|
|
|
@@ -412,6 +428,11 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
|
412
428
|
- If a dict, it's converted to a JSON string.
|
|
413
429
|
- If a file-like object (e.g., opened file), its size is read.
|
|
414
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.
|
|
415
436
|
|
|
416
437
|
Returns
|
|
417
438
|
-------
|
|
@@ -441,7 +462,7 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
|
|
|
441
462
|
"""
|
|
442
463
|
|
|
443
464
|
if isinstance(obj, dict):
|
|
444
|
-
obj_str =
|
|
465
|
+
obj_str = deflated_serialize_json(obj, json_configurations=json_configurations)
|
|
445
466
|
return len(obj_str.encode("utf-8"))
|
|
446
467
|
|
|
447
468
|
elif hasattr(obj, "read"):
|
|
@@ -46,11 +46,11 @@ from typing import Any, Optional, Union
|
|
|
46
46
|
|
|
47
47
|
from pydantic import AliasChoices, Field
|
|
48
48
|
|
|
49
|
+
from nextmv._serialization import serialize_json
|
|
49
50
|
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:
|
|
@@ -31,9 +31,9 @@ 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
|
-
from nextmv.serialization import serialize_json
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class InputFormat(str, Enum):
|
|
@@ -50,11 +50,11 @@ from typing import Any, Optional, Union
|
|
|
50
50
|
|
|
51
51
|
from pydantic import AliasChoices, Field
|
|
52
52
|
|
|
53
|
+
from nextmv._serialization import serialize_json
|
|
53
54
|
from nextmv.base_model import BaseModel
|
|
54
55
|
from nextmv.deprecated import deprecated
|
|
55
56
|
from nextmv.logger import reset_stdout
|
|
56
57
|
from nextmv.options import Options
|
|
57
|
-
from nextmv.serialization import serialize_json
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class RunStatistics(BaseModel):
|
|
@@ -4,6 +4,11 @@ from typing import Any
|
|
|
4
4
|
from nextmv.cloud.application import PollingOptions, poll
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
# This is a dummy function to avoid actually sleeping during tests.
|
|
8
|
+
def no_sleep(value: float) -> None:
|
|
9
|
+
return
|
|
10
|
+
|
|
11
|
+
|
|
7
12
|
class TestApplication(unittest.TestCase):
|
|
8
13
|
def test_poll(self):
|
|
9
14
|
counter = 0
|
|
@@ -17,9 +22,9 @@ class TestApplication(unittest.TestCase):
|
|
|
17
22
|
|
|
18
23
|
return "result", True
|
|
19
24
|
|
|
20
|
-
polling_options = PollingOptions(
|
|
25
|
+
polling_options = PollingOptions()
|
|
21
26
|
|
|
22
|
-
result = poll(polling_options, polling_func)
|
|
27
|
+
result = poll(polling_options, polling_func, no_sleep)
|
|
23
28
|
|
|
24
29
|
self.assertEqual(result, "result")
|
|
25
30
|
|
|
@@ -42,8 +47,29 @@ class TestApplication(unittest.TestCase):
|
|
|
42
47
|
if counter == 3:
|
|
43
48
|
return True
|
|
44
49
|
|
|
45
|
-
polling_options = PollingOptions(
|
|
50
|
+
polling_options = PollingOptions(stop=stop)
|
|
46
51
|
|
|
47
|
-
result = poll(polling_options, polling_func)
|
|
52
|
+
result = poll(polling_options, polling_func, no_sleep)
|
|
48
53
|
|
|
49
54
|
self.assertIsNone(result)
|
|
55
|
+
|
|
56
|
+
def test_poll_long(self):
|
|
57
|
+
counter = 0
|
|
58
|
+
max_tries = 1000000
|
|
59
|
+
|
|
60
|
+
def polling_func() -> tuple[Any, bool]:
|
|
61
|
+
nonlocal counter
|
|
62
|
+
counter += 1
|
|
63
|
+
|
|
64
|
+
if counter < max_tries:
|
|
65
|
+
return "result", False
|
|
66
|
+
|
|
67
|
+
return "result", True
|
|
68
|
+
|
|
69
|
+
polling_options = PollingOptions(
|
|
70
|
+
max_tries=max_tries + 1,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
result = poll(polling_options, polling_func, no_sleep)
|
|
74
|
+
|
|
75
|
+
self.assertEqual(result, "result")
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
indent=2,
|
|
27
|
+
)
|
|
28
|
+
self.assertEqual(serialized, expected)
|
|
29
|
+
|
|
30
|
+
def test_default_deflated_serialization(self):
|
|
31
|
+
"""Test the default deflated serialization"""
|
|
32
|
+
|
|
33
|
+
data = {
|
|
34
|
+
"name": "Test",
|
|
35
|
+
"value": 42,
|
|
36
|
+
"timestamp": nextmv._serialization._custom_serial(datetime.datetime(2023, 10, 1)),
|
|
37
|
+
}
|
|
38
|
+
serialized = nextmv._serialization.deflated_serialize_json(data)
|
|
39
|
+
expected = json.dumps(
|
|
40
|
+
{
|
|
41
|
+
"name": "Test",
|
|
42
|
+
"value": 42,
|
|
43
|
+
"timestamp": "2023-10-01T00:00:00",
|
|
44
|
+
},
|
|
45
|
+
separators=(",", ":"),
|
|
46
|
+
)
|
|
47
|
+
self.assertEqual(serialized, expected)
|
|
48
|
+
|
|
49
|
+
def test_custom_serialization(self):
|
|
50
|
+
"""Test custom serialization with additional configurations"""
|
|
51
|
+
|
|
52
|
+
data = {
|
|
53
|
+
"name": "Test",
|
|
54
|
+
"value": 42,
|
|
55
|
+
"timestamp": nextmv._serialization._custom_serial(datetime.datetime(2023, 10, 1)),
|
|
56
|
+
}
|
|
57
|
+
json_configurations = {
|
|
58
|
+
"indent": 2,
|
|
59
|
+
"default": nextmv._serialization._custom_serial,
|
|
60
|
+
"separators": (",", ": "),
|
|
61
|
+
}
|
|
62
|
+
serialized = nextmv._serialization.serialize_json(data, json_configurations)
|
|
63
|
+
expected = json.dumps(
|
|
64
|
+
{
|
|
65
|
+
"name": "Test",
|
|
66
|
+
"value": 42,
|
|
67
|
+
"timestamp": "2023-10-01T00:00:00",
|
|
68
|
+
},
|
|
69
|
+
indent=2,
|
|
70
|
+
separators=(",", ": "),
|
|
71
|
+
)
|
|
72
|
+
self.assertEqual(serialized, expected)
|
|
73
|
+
|
|
74
|
+
def test_compressed_serialization(self):
|
|
75
|
+
"""Test a requested compressed serialization"""
|
|
76
|
+
|
|
77
|
+
data = {
|
|
78
|
+
"name": "Test",
|
|
79
|
+
"value": 42,
|
|
80
|
+
"timestamp": nextmv._serialization._custom_serial(datetime.datetime(2023, 10, 1)),
|
|
81
|
+
}
|
|
82
|
+
json_configurations = {
|
|
83
|
+
"separators": (",", ":"), # Remove spaces for a compressed format
|
|
84
|
+
"indent": None, # No indentation for compressed format
|
|
85
|
+
}
|
|
86
|
+
serialized = nextmv._serialization.serialize_json(data, json_configurations)
|
|
87
|
+
expected = json.dumps(
|
|
88
|
+
{
|
|
89
|
+
"name": "Test",
|
|
90
|
+
"value": 42,
|
|
91
|
+
"timestamp": "2023-10-01T00:00:00",
|
|
92
|
+
},
|
|
93
|
+
separators=(",", ":"),
|
|
94
|
+
indent=None,
|
|
95
|
+
)
|
|
96
|
+
self.assertEqual(serialized, expected)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v0.28.3.dev0"
|
|
@@ -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
|