nextmv 0.28.2__tar.gz → 0.28.3.dev0__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.
Files changed (63) hide show
  1. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/PKG-INFO +1 -1
  2. nextmv-0.28.3.dev0/nextmv/__about__.py +1 -0
  3. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/application.py +3 -2
  4. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/client.py +9 -4
  5. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/run.py +3 -3
  6. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/input.py +2 -1
  7. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/output.py +10 -52
  8. nextmv-0.28.3.dev0/nextmv/serialization.py +67 -0
  9. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_output.py +1 -9
  10. nextmv-0.28.3.dev0/tests/test_serialization.py +53 -0
  11. nextmv-0.28.2/nextmv/__about__.py +0 -1
  12. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/.gitignore +0 -0
  13. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/LICENSE +0 -0
  14. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/README.md +0 -0
  15. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/__entrypoint__.py +0 -0
  16. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/__init__.py +0 -0
  17. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/base_model.py +0 -0
  18. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/__init__.py +0 -0
  19. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/acceptance_test.py +0 -0
  20. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/account.py +0 -0
  21. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/batch_experiment.py +0 -0
  22. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/input_set.py +0 -0
  23. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/instance.py +0 -0
  24. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/manifest.py +0 -0
  25. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/package.py +0 -0
  26. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/safe.py +0 -0
  27. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/scenario.py +0 -0
  28. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/secrets.py +0 -0
  29. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/status.py +0 -0
  30. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/cloud/version.py +0 -0
  31. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/deprecated.py +0 -0
  32. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/logger.py +0 -0
  33. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/model.py +0 -0
  34. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/nextmv/options.py +0 -0
  35. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/pyproject.toml +0 -0
  36. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/requirements.txt +0 -0
  37. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/__init__.py +0 -0
  38. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/__init__.py +0 -0
  39. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/app.yaml +0 -0
  40. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_application.py +0 -0
  41. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_client.py +0 -0
  42. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_manifest.py +0 -0
  43. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_package.py +0 -0
  44. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_run.py +0 -0
  45. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_safe_name_id.py +0 -0
  46. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/cloud/test_scenario.py +0 -0
  47. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/__init__.py +0 -0
  48. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options1.py +0 -0
  49. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options2.py +0 -0
  50. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options3.py +0 -0
  51. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options4.py +0 -0
  52. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options5.py +0 -0
  53. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options6.py +0 -0
  54. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options7.py +0 -0
  55. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/scripts/options_deprecated.py +0 -0
  56. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_base_model.py +0 -0
  57. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_entrypoint/__init__.py +0 -0
  58. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_entrypoint/test_entrypoint.py +0 -0
  59. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_input.py +0 -0
  60. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_logger.py +0 -0
  61. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_model.py +0 -0
  62. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_options.py +0 -0
  63. {nextmv-0.28.2 → nextmv-0.28.3.dev0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.28.2
3
+ Version: 0.28.3.dev0
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
@@ -0,0 +1 @@
1
+ __version__ = "v0.28.3.dev0"
@@ -66,6 +66,7 @@ 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
69
70
 
70
71
  # Maximum size of the run input/output in bytes. This constant defines the
71
72
  # maximum allowed size for run inputs and outputs. When the size exceeds this
@@ -1588,7 +1589,7 @@ class Application:
1588
1589
  if isinstance(v, str):
1589
1590
  options_dict[k] = v
1590
1591
  else:
1591
- options_dict[k] = json.dumps(v)
1592
+ options_dict[k] = serialize_json(v)
1592
1593
 
1593
1594
  payload = {}
1594
1595
  if upload_id_used:
@@ -2830,7 +2831,7 @@ class Application:
2830
2831
  """
2831
2832
 
2832
2833
  if isinstance(input, dict):
2833
- input = json.dumps(input)
2834
+ input = serialize_json(input)
2834
2835
 
2835
2836
  self.client.upload_to_presigned_url(
2836
2837
  url=upload_url.upload_url,
@@ -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 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
 
@@ -293,7 +294,11 @@ class Client:
293
294
  if data is not None:
294
295
  kwargs["data"] = data
295
296
  if payload is not None:
296
- kwargs["json"] = payload
297
+ if isinstance(payload, (dict, list)):
298
+ data = serialize_json(payload)
299
+ kwargs["data"] = data
300
+ else:
301
+ raise ValueError("payload must be a dictionary or a list")
297
302
  if query_params is not None:
298
303
  kwargs["params"] = query_params
299
304
 
@@ -341,7 +346,7 @@ class Client:
341
346
 
342
347
  upload_data: Optional[str] = None
343
348
  if isinstance(data, dict):
344
- upload_data = json.dumps(data, separators=(",", ":"))
349
+ upload_data = serialize_json(data)
345
350
  elif isinstance(data, str):
346
351
  upload_data = data
347
352
  else:
@@ -436,7 +441,7 @@ def get_size(obj: Union[dict[str, Any], IO[bytes], str]) -> int:
436
441
  """
437
442
 
438
443
  if isinstance(obj, dict):
439
- obj_str = json.dumps(obj, separators=(",", ":"))
444
+ obj_str = serialize_json(obj)
440
445
  return len(obj_str.encode("utf-8"))
441
446
 
442
447
  elif hasattr(obj, "read"):
@@ -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
@@ -51,6 +50,7 @@ from nextmv.base_model import BaseModel
51
50
  from nextmv.cloud.status import Status, StatusV2
52
51
  from nextmv.input import Input, InputFormat
53
52
  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
- _ = json.dumps(self.input)
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
- _ = json.dumps(self.output)
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
 
@@ -33,6 +33,7 @@ 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
36
37
 
37
38
 
38
39
  class InputFormat(str, Enum):
@@ -139,7 +140,7 @@ class Input:
139
140
 
140
141
  if self.input_format == InputFormat.JSON:
141
142
  try:
142
- _ = json.dumps(self.data)
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 "
@@ -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
@@ -56,6 +54,7 @@ from nextmv.base_model import BaseModel
56
54
  from nextmv.deprecated import deprecated
57
55
  from nextmv.logger import reset_stdout
58
56
  from nextmv.options import Options
57
+ from nextmv.serialization import serialize_json
59
58
 
60
59
 
61
60
  class RunStatistics(BaseModel):
@@ -644,7 +643,7 @@ class Output:
644
643
 
645
644
  if self.output_format == OutputFormat.JSON:
646
645
  try:
647
- _ = json.dumps(self.solution, default=_custom_serial)
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
- 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(
818
+ serialized = serialize_json(
834
819
  output_dict,
835
- indent=indent,
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
- serialized = json.dumps(
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
- indent=2,
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")
@@ -0,0 +1,67 @@
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")
@@ -118,13 +118,6 @@ 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
-
128
121
  # Test with CSV configurations
129
122
  csv_config = {"delimiter": ";", "quoting": csv.QUOTE_NONNUMERIC}
130
123
  output = nextmv.Output(output_format=nextmv.OutputFormat.CSV_ARCHIVE, csv_configurations=csv_config)
@@ -183,7 +176,6 @@ class TestOutput(unittest.TestCase):
183
176
  self.assertEqual(result["assets"][0]["name"], "asset1")
184
177
  self.assertEqual(result["assets"][0]["visual"]["schema"], "chartjs")
185
178
  self.assertEqual(result["solution"]["value"], 42)
186
- self.assertEqual(result["json_configurations"]["indent"], 4)
187
179
 
188
180
  def test_local_writer_json_stdout_default(self):
189
181
  output = nextmv.Output(
@@ -262,7 +254,7 @@ class TestOutput(unittest.TestCase):
262
254
 
263
255
  self.assertEqual(
264
256
  mock_stdout.getvalue(),
265
- '{"assets":[],"json_configurations":{"separators":[",",":"],"sort_keys":true},"options":{},"solution":{"empanadas":"are_life"},"statistics":{"foo":"bar"}}\n',
257
+ '{"assets":[],"options":{},"solution":{"empanadas":"are_life"},"statistics":{"foo":"bar"}}\n',
266
258
  )
267
259
 
268
260
  def test_local_writer_json_stdout_with_options(self):
@@ -0,0 +1,53 @@
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)
@@ -1 +0,0 @@
1
- __version__ = "v0.28.2"
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