hpcflow 0.1.9__py3-none-any.whl → 0.2.0a271__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.
Files changed (275) hide show
  1. hpcflow/__init__.py +2 -11
  2. hpcflow/__pyinstaller/__init__.py +5 -0
  3. hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
  4. hpcflow/_version.py +1 -1
  5. hpcflow/app.py +43 -0
  6. hpcflow/cli.py +2 -462
  7. hpcflow/data/demo_data_manifest/__init__.py +3 -0
  8. hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
  9. hpcflow/data/jinja_templates/test/test_template.txt +8 -0
  10. hpcflow/data/programs/hello_world/README.md +1 -0
  11. hpcflow/data/programs/hello_world/hello_world.c +87 -0
  12. hpcflow/data/programs/hello_world/linux/hello_world +0 -0
  13. hpcflow/data/programs/hello_world/macos/hello_world +0 -0
  14. hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
  15. hpcflow/data/scripts/__init__.py +1 -0
  16. hpcflow/data/scripts/bad_script.py +2 -0
  17. hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
  18. hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
  19. hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
  20. hpcflow/data/scripts/do_nothing.py +2 -0
  21. hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
  22. hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
  23. hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
  24. hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
  25. hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
  26. hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
  27. hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
  28. hpcflow/data/scripts/generate_t1_file_01.py +7 -0
  29. hpcflow/data/scripts/import_future_script.py +7 -0
  30. hpcflow/data/scripts/input_file_generator_basic.py +3 -0
  31. hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
  32. hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
  33. hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
  34. hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
  35. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
  36. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
  37. hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
  38. hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
  39. hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
  40. hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
  41. hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
  42. hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
  43. hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
  44. hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
  45. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
  46. hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
  47. hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
  48. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
  49. hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
  50. hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
  51. hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
  52. hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
  53. hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
  54. hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
  55. hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
  56. hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
  57. hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
  58. hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
  59. hpcflow/data/scripts/output_file_parser_basic.py +3 -0
  60. hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
  61. hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
  62. hpcflow/data/scripts/parse_t1_file_01.py +4 -0
  63. hpcflow/data/scripts/script_exit_test.py +5 -0
  64. hpcflow/data/template_components/__init__.py +1 -0
  65. hpcflow/data/template_components/command_files.yaml +26 -0
  66. hpcflow/data/template_components/environments.yaml +13 -0
  67. hpcflow/data/template_components/parameters.yaml +14 -0
  68. hpcflow/data/template_components/task_schemas.yaml +139 -0
  69. hpcflow/data/workflows/workflow_1.yaml +5 -0
  70. hpcflow/examples.ipynb +1037 -0
  71. hpcflow/sdk/__init__.py +149 -0
  72. hpcflow/sdk/app.py +4266 -0
  73. hpcflow/sdk/cli.py +1479 -0
  74. hpcflow/sdk/cli_common.py +385 -0
  75. hpcflow/sdk/config/__init__.py +5 -0
  76. hpcflow/sdk/config/callbacks.py +246 -0
  77. hpcflow/sdk/config/cli.py +388 -0
  78. hpcflow/sdk/config/config.py +1410 -0
  79. hpcflow/sdk/config/config_file.py +501 -0
  80. hpcflow/sdk/config/errors.py +272 -0
  81. hpcflow/sdk/config/types.py +150 -0
  82. hpcflow/sdk/core/__init__.py +38 -0
  83. hpcflow/sdk/core/actions.py +3857 -0
  84. hpcflow/sdk/core/app_aware.py +25 -0
  85. hpcflow/sdk/core/cache.py +224 -0
  86. hpcflow/sdk/core/command_files.py +814 -0
  87. hpcflow/sdk/core/commands.py +424 -0
  88. hpcflow/sdk/core/element.py +2071 -0
  89. hpcflow/sdk/core/enums.py +221 -0
  90. hpcflow/sdk/core/environment.py +256 -0
  91. hpcflow/sdk/core/errors.py +1043 -0
  92. hpcflow/sdk/core/execute.py +207 -0
  93. hpcflow/sdk/core/json_like.py +809 -0
  94. hpcflow/sdk/core/loop.py +1320 -0
  95. hpcflow/sdk/core/loop_cache.py +282 -0
  96. hpcflow/sdk/core/object_list.py +933 -0
  97. hpcflow/sdk/core/parameters.py +3371 -0
  98. hpcflow/sdk/core/rule.py +196 -0
  99. hpcflow/sdk/core/run_dir_files.py +57 -0
  100. hpcflow/sdk/core/skip_reason.py +7 -0
  101. hpcflow/sdk/core/task.py +3792 -0
  102. hpcflow/sdk/core/task_schema.py +993 -0
  103. hpcflow/sdk/core/test_utils.py +538 -0
  104. hpcflow/sdk/core/types.py +447 -0
  105. hpcflow/sdk/core/utils.py +1207 -0
  106. hpcflow/sdk/core/validation.py +87 -0
  107. hpcflow/sdk/core/values.py +477 -0
  108. hpcflow/sdk/core/workflow.py +4820 -0
  109. hpcflow/sdk/core/zarr_io.py +206 -0
  110. hpcflow/sdk/data/__init__.py +13 -0
  111. hpcflow/sdk/data/config_file_schema.yaml +34 -0
  112. hpcflow/sdk/data/config_schema.yaml +260 -0
  113. hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
  114. hpcflow/sdk/data/files_spec_schema.yaml +5 -0
  115. hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
  116. hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
  117. hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
  118. hpcflow/sdk/demo/__init__.py +3 -0
  119. hpcflow/sdk/demo/cli.py +242 -0
  120. hpcflow/sdk/helper/__init__.py +3 -0
  121. hpcflow/sdk/helper/cli.py +137 -0
  122. hpcflow/sdk/helper/helper.py +300 -0
  123. hpcflow/sdk/helper/watcher.py +192 -0
  124. hpcflow/sdk/log.py +288 -0
  125. hpcflow/sdk/persistence/__init__.py +18 -0
  126. hpcflow/sdk/persistence/base.py +2817 -0
  127. hpcflow/sdk/persistence/defaults.py +6 -0
  128. hpcflow/sdk/persistence/discovery.py +39 -0
  129. hpcflow/sdk/persistence/json.py +954 -0
  130. hpcflow/sdk/persistence/pending.py +948 -0
  131. hpcflow/sdk/persistence/store_resource.py +203 -0
  132. hpcflow/sdk/persistence/types.py +309 -0
  133. hpcflow/sdk/persistence/utils.py +73 -0
  134. hpcflow/sdk/persistence/zarr.py +2388 -0
  135. hpcflow/sdk/runtime.py +320 -0
  136. hpcflow/sdk/submission/__init__.py +3 -0
  137. hpcflow/sdk/submission/enums.py +70 -0
  138. hpcflow/sdk/submission/jobscript.py +2379 -0
  139. hpcflow/sdk/submission/schedulers/__init__.py +281 -0
  140. hpcflow/sdk/submission/schedulers/direct.py +233 -0
  141. hpcflow/sdk/submission/schedulers/sge.py +376 -0
  142. hpcflow/sdk/submission/schedulers/slurm.py +598 -0
  143. hpcflow/sdk/submission/schedulers/utils.py +25 -0
  144. hpcflow/sdk/submission/shells/__init__.py +52 -0
  145. hpcflow/sdk/submission/shells/base.py +229 -0
  146. hpcflow/sdk/submission/shells/bash.py +504 -0
  147. hpcflow/sdk/submission/shells/os_version.py +115 -0
  148. hpcflow/sdk/submission/shells/powershell.py +352 -0
  149. hpcflow/sdk/submission/submission.py +1402 -0
  150. hpcflow/sdk/submission/types.py +140 -0
  151. hpcflow/sdk/typing.py +194 -0
  152. hpcflow/sdk/utils/arrays.py +69 -0
  153. hpcflow/sdk/utils/deferred_file.py +55 -0
  154. hpcflow/sdk/utils/hashing.py +16 -0
  155. hpcflow/sdk/utils/patches.py +31 -0
  156. hpcflow/sdk/utils/strings.py +69 -0
  157. hpcflow/tests/api/test_api.py +32 -0
  158. hpcflow/tests/conftest.py +123 -0
  159. hpcflow/tests/data/__init__.py +0 -0
  160. hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
  161. hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
  162. hpcflow/tests/data/multi_path_sequences.yaml +29 -0
  163. hpcflow/tests/data/workflow_1.json +10 -0
  164. hpcflow/tests/data/workflow_1.yaml +5 -0
  165. hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
  166. hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
  167. hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
  168. hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
  169. hpcflow/tests/programs/test_programs.py +180 -0
  170. hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
  171. hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
  172. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
  173. hpcflow/tests/scripts/test_input_file_generators.py +282 -0
  174. hpcflow/tests/scripts/test_main_scripts.py +1361 -0
  175. hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
  176. hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
  177. hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
  178. hpcflow/tests/unit/test_action.py +1066 -0
  179. hpcflow/tests/unit/test_action_rule.py +24 -0
  180. hpcflow/tests/unit/test_app.py +132 -0
  181. hpcflow/tests/unit/test_cache.py +46 -0
  182. hpcflow/tests/unit/test_cli.py +172 -0
  183. hpcflow/tests/unit/test_command.py +377 -0
  184. hpcflow/tests/unit/test_config.py +195 -0
  185. hpcflow/tests/unit/test_config_file.py +162 -0
  186. hpcflow/tests/unit/test_element.py +666 -0
  187. hpcflow/tests/unit/test_element_iteration.py +88 -0
  188. hpcflow/tests/unit/test_element_set.py +158 -0
  189. hpcflow/tests/unit/test_group.py +115 -0
  190. hpcflow/tests/unit/test_input_source.py +1479 -0
  191. hpcflow/tests/unit/test_input_value.py +398 -0
  192. hpcflow/tests/unit/test_jobscript_unit.py +757 -0
  193. hpcflow/tests/unit/test_json_like.py +1247 -0
  194. hpcflow/tests/unit/test_loop.py +2674 -0
  195. hpcflow/tests/unit/test_meta_task.py +325 -0
  196. hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
  197. hpcflow/tests/unit/test_object_list.py +116 -0
  198. hpcflow/tests/unit/test_parameter.py +243 -0
  199. hpcflow/tests/unit/test_persistence.py +664 -0
  200. hpcflow/tests/unit/test_resources.py +243 -0
  201. hpcflow/tests/unit/test_run.py +286 -0
  202. hpcflow/tests/unit/test_run_directories.py +29 -0
  203. hpcflow/tests/unit/test_runtime.py +9 -0
  204. hpcflow/tests/unit/test_schema_input.py +372 -0
  205. hpcflow/tests/unit/test_shell.py +129 -0
  206. hpcflow/tests/unit/test_slurm.py +39 -0
  207. hpcflow/tests/unit/test_submission.py +502 -0
  208. hpcflow/tests/unit/test_task.py +2560 -0
  209. hpcflow/tests/unit/test_task_schema.py +182 -0
  210. hpcflow/tests/unit/test_utils.py +616 -0
  211. hpcflow/tests/unit/test_value_sequence.py +549 -0
  212. hpcflow/tests/unit/test_values.py +91 -0
  213. hpcflow/tests/unit/test_workflow.py +827 -0
  214. hpcflow/tests/unit/test_workflow_template.py +186 -0
  215. hpcflow/tests/unit/utils/test_arrays.py +40 -0
  216. hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
  217. hpcflow/tests/unit/utils/test_hashing.py +65 -0
  218. hpcflow/tests/unit/utils/test_patches.py +5 -0
  219. hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
  220. hpcflow/tests/unit/utils/test_strings.py +97 -0
  221. hpcflow/tests/workflows/__init__.py +0 -0
  222. hpcflow/tests/workflows/test_directory_structure.py +31 -0
  223. hpcflow/tests/workflows/test_jobscript.py +355 -0
  224. hpcflow/tests/workflows/test_run_status.py +198 -0
  225. hpcflow/tests/workflows/test_skip_downstream.py +696 -0
  226. hpcflow/tests/workflows/test_submission.py +140 -0
  227. hpcflow/tests/workflows/test_workflows.py +564 -0
  228. hpcflow/tests/workflows/test_zip.py +18 -0
  229. hpcflow/viz_demo.ipynb +6794 -0
  230. hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
  231. hpcflow-0.2.0a271.dist-info/METADATA +65 -0
  232. hpcflow-0.2.0a271.dist-info/RECORD +237 -0
  233. {hpcflow-0.1.9.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
  234. hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
  235. hpcflow/api.py +0 -458
  236. hpcflow/archive/archive.py +0 -308
  237. hpcflow/archive/cloud/cloud.py +0 -47
  238. hpcflow/archive/cloud/errors.py +0 -9
  239. hpcflow/archive/cloud/providers/dropbox.py +0 -432
  240. hpcflow/archive/errors.py +0 -5
  241. hpcflow/base_db.py +0 -4
  242. hpcflow/config.py +0 -232
  243. hpcflow/copytree.py +0 -66
  244. hpcflow/data/examples/_config.yml +0 -14
  245. hpcflow/data/examples/damask/demo/1.run.yml +0 -4
  246. hpcflow/data/examples/damask/demo/2.process.yml +0 -29
  247. hpcflow/data/examples/damask/demo/geom.geom +0 -2052
  248. hpcflow/data/examples/damask/demo/load.load +0 -1
  249. hpcflow/data/examples/damask/demo/material.config +0 -185
  250. hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
  251. hpcflow/data/examples/damask/inputs/load.load +0 -1
  252. hpcflow/data/examples/damask/inputs/material.config +0 -185
  253. hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
  254. hpcflow/data/examples/damask/profiles/damask.yml +0 -4
  255. hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
  256. hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
  257. hpcflow/data/examples/damask/profiles/default.yml +0 -6
  258. hpcflow/data/examples/thinking.yml +0 -177
  259. hpcflow/errors.py +0 -2
  260. hpcflow/init_db.py +0 -37
  261. hpcflow/models.py +0 -2549
  262. hpcflow/nesting.py +0 -9
  263. hpcflow/profiles.py +0 -455
  264. hpcflow/project.py +0 -81
  265. hpcflow/scheduler.py +0 -323
  266. hpcflow/utils.py +0 -103
  267. hpcflow/validation.py +0 -167
  268. hpcflow/variables.py +0 -544
  269. hpcflow-0.1.9.dist-info/METADATA +0 -168
  270. hpcflow-0.1.9.dist-info/RECORD +0 -45
  271. hpcflow-0.1.9.dist-info/entry_points.txt +0 -8
  272. hpcflow-0.1.9.dist-info/top_level.txt +0 -1
  273. /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
  274. /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
  275. /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
@@ -0,0 +1,1247 @@
1
+ # mypy: disable-error-code="annotation-unchecked"
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass
4
+ import enum
5
+ from types import SimpleNamespace
6
+ from typing import Any
7
+
8
+ import pytest
9
+
10
+ from hpcflow.app import app as hf
11
+ from hpcflow.sdk.core.json_like import BaseJSONLike, ChildObjectSpec
12
+ from hpcflow.sdk.core.object_list import ObjectList
13
+
14
+ # BE AWARE THAT MYPY CANNOT CORRECTLY TYPE-CHECK THIS FILE AT ALL.
15
+ # It fails massively due to all the classes inside functions being passed to other functions.
16
+ # Omitting the types makes it ignore them all, which us for the best.
17
+
18
+
19
+ @pytest.fixture
20
+ def null_config(tmp_path):
21
+ if not hf.is_config_loaded:
22
+ hf.load_config(config_dir=tmp_path)
23
+
24
+
25
+ def test_json_like_name_is_name(null_config):
26
+ spec = ChildObjectSpec(name="a")
27
+ assert spec.json_like_name == "a"
28
+
29
+
30
+ @pytest.fixture
31
+ def obj_and_json_like_1(null_config):
32
+ @dataclass
33
+ class ObjA(BaseJSONLike):
34
+ a: int
35
+ b: float
36
+
37
+ js_1 = {
38
+ "a": 1,
39
+ "b": 2.1,
40
+ }
41
+ return ObjA, js_1
42
+
43
+
44
+ def test_from_json_like_expected_obj_simple(obj_and_json_like_1):
45
+ ObjA, js_1 = obj_and_json_like_1
46
+ assert ObjA.from_json_like(js_1) == ObjA(**js_1)
47
+
48
+
49
+ def test_to_json_like_expected_json_like_simple(obj_and_json_like_1):
50
+ ObjA, js_1 = obj_and_json_like_1
51
+ js_2, _ = ObjA(**js_1).to_json_like()
52
+ assert js_2 == js_1
53
+
54
+
55
+ def test_json_like_round_trip_obj_simple(obj_and_json_like_1):
56
+ ObjA, js_1 = obj_and_json_like_1
57
+ obj1 = ObjA(**js_1)
58
+ js_2, _ = obj1.to_json_like()
59
+ obj2 = ObjA.from_json_like(js_2)
60
+ assert obj1 == obj2
61
+
62
+
63
+ @pytest.fixture
64
+ def BaseJSONLikeSubClass(null_config):
65
+ return type("MyBaseJSONLike", (BaseJSONLike,), {})
66
+
67
+
68
+ def test_BaseJSONLike_child_object_class_namespace_via_obj(null_config):
69
+ """Child object class passed directly as a class object."""
70
+
71
+ @dataclass
72
+ class ObjB(BaseJSONLike):
73
+ c: int
74
+
75
+ @dataclass
76
+ class ObjA(BaseJSONLike):
77
+ _child_objects = (
78
+ ChildObjectSpec(
79
+ name="b",
80
+ class_obj=ObjB,
81
+ ),
82
+ )
83
+ a: int
84
+ b: float
85
+
86
+ js_1 = {
87
+ "a": 1,
88
+ "b": {
89
+ "c": 8,
90
+ },
91
+ }
92
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
93
+
94
+
95
+ def test_BaseJSONLike_child_object_class_namespace_via_name_and_dict_namespace(
96
+ BaseJSONLikeSubClass: type[BaseJSONLike],
97
+ ):
98
+ """Child object class passed as a name and namespace passed as a dict."""
99
+ T: type = BaseJSONLikeSubClass # Workaround for python/mypy#14458
100
+
101
+ @dataclass
102
+ class ObjB(T):
103
+ c: int
104
+
105
+ @dataclass
106
+ class ObjA(T):
107
+ _child_objects = (
108
+ ChildObjectSpec(
109
+ name="b",
110
+ class_name="ObjB",
111
+ ),
112
+ )
113
+ a: int
114
+ b: float
115
+
116
+ BaseJSONLikeSubClass._set_class_namespace({"ObjB": ObjB}, is_dict=True)
117
+
118
+ js_1 = {
119
+ "a": 1,
120
+ "b": {
121
+ "c": 8,
122
+ },
123
+ }
124
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
125
+
126
+
127
+ def test_BaseJSONLike_child_object_class_namespace_via_name_and_func_locals(
128
+ BaseJSONLikeSubClass: type[BaseJSONLike],
129
+ ):
130
+ """Child object class passed as a name and namespace passed as function locals."""
131
+ T: type = BaseJSONLikeSubClass # Workaround for python/mypy#14458
132
+
133
+ @dataclass
134
+ class ObjB(T):
135
+ c: int
136
+
137
+ @dataclass
138
+ class ObjA(T):
139
+ _child_objects = (
140
+ ChildObjectSpec(
141
+ name="b",
142
+ class_name="ObjB",
143
+ ),
144
+ )
145
+ a: int
146
+ b: float
147
+
148
+ BaseJSONLikeSubClass._set_class_namespace(locals(), is_dict=True)
149
+
150
+ js_1 = {
151
+ "a": 1,
152
+ "b": {
153
+ "c": 8,
154
+ },
155
+ }
156
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
157
+
158
+
159
+ def test_BaseJSONLike_child_object_class_namespace_via_name_and_SimpleNamespace(
160
+ BaseJSONLikeSubClass: type[BaseJSONLike],
161
+ ):
162
+ """Child object class passed as a name and namespace passed as a SimpleNamespace."""
163
+ T: type = BaseJSONLikeSubClass # Workaround for python/mypy#14458
164
+
165
+ @dataclass
166
+ class ObjB(T):
167
+ c: int
168
+
169
+ @dataclass
170
+ class ObjA(T):
171
+ _child_objects = (
172
+ ChildObjectSpec(
173
+ name="b",
174
+ class_name="ObjB",
175
+ ),
176
+ )
177
+ a: int
178
+ b: float
179
+
180
+ BaseJSONLikeSubClass._set_class_namespace(SimpleNamespace(ObjB=ObjB))
181
+
182
+ js_1 = {
183
+ "a": 1,
184
+ "b": {
185
+ "c": 8,
186
+ },
187
+ }
188
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
189
+
190
+
191
+ @pytest.fixture
192
+ def obj_and_child_obj_and_json_like_1(null_config):
193
+ @dataclass
194
+ class ObjB(BaseJSONLike):
195
+ c: int
196
+
197
+ @dataclass
198
+ class ObjA(BaseJSONLike):
199
+ _child_objects = (
200
+ ChildObjectSpec(
201
+ name="b",
202
+ class_obj=ObjB,
203
+ ),
204
+ )
205
+ a: int
206
+ b: float
207
+
208
+ js_1 = {
209
+ "a": 1,
210
+ "b": {
211
+ "c": 8,
212
+ },
213
+ }
214
+
215
+ return ObjA, ObjB, js_1
216
+
217
+
218
+ def test_from_json_like_expected_obj_with_child_obj(obj_and_child_obj_and_json_like_1):
219
+ ObjA, ObjB, js_1 = obj_and_child_obj_and_json_like_1
220
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
221
+
222
+
223
+ def test_to_json_like_expected_json_like_with_child_obj(
224
+ obj_and_child_obj_and_json_like_1,
225
+ ):
226
+ ObjA, ObjB, js_1 = obj_and_child_obj_and_json_like_1
227
+ obj = ObjA(a=1, b=ObjB(c=js_1["b"]["c"]))
228
+ js, _ = obj.to_json_like()
229
+ assert js == js_1
230
+
231
+
232
+ def test_json_like_round_trip_with_child_obj(obj_and_child_obj_and_json_like_1):
233
+ ObjA, ObjB, _ = obj_and_child_obj_and_json_like_1
234
+ obj1 = ObjA(a=1, b=ObjB(c=4))
235
+ js, _ = obj1.to_json_like()
236
+ obj2 = ObjA.from_json_like(js)
237
+ assert obj1 == obj2
238
+
239
+
240
+ @pytest.fixture
241
+ def obj_and_child_obj_with_json_like_name_and_json_like(null_config):
242
+ @dataclass
243
+ class ObjB(BaseJSONLike):
244
+ c: int
245
+
246
+ @dataclass
247
+ class ObjA(BaseJSONLike):
248
+ _child_objects = (
249
+ ChildObjectSpec(
250
+ name="b",
251
+ json_like_name="json_b",
252
+ class_obj=ObjB,
253
+ ),
254
+ )
255
+ a: int
256
+ b: float
257
+
258
+ js_1 = {
259
+ "a": 1,
260
+ "json_b": {
261
+ "c": 8,
262
+ },
263
+ }
264
+
265
+ return ObjA, ObjB, js_1
266
+
267
+
268
+ def test_from_json_like_expected_obj_with_child_obj_with_json_like_name(
269
+ obj_and_child_obj_with_json_like_name_and_json_like,
270
+ ):
271
+ ObjA, ObjB, js_1 = obj_and_child_obj_with_json_like_name_and_json_like
272
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=ObjB(c=8))
273
+
274
+
275
+ def test_to_json_like_expected_json_like_with_child_obj_with_json_like_name(
276
+ obj_and_child_obj_with_json_like_name_and_json_like,
277
+ ):
278
+ ObjA, ObjB, js_1 = obj_and_child_obj_with_json_like_name_and_json_like
279
+ obj = ObjA(a=1, b=ObjB(c=js_1["json_b"]["c"]))
280
+ js, _ = obj.to_json_like()
281
+ assert js == js_1
282
+
283
+
284
+ def test_json_like_round_trip_with_child_obj_with_json_like_name(
285
+ obj_and_child_obj_with_json_like_name_and_json_like,
286
+ ):
287
+ ObjA, ObjB, _ = obj_and_child_obj_with_json_like_name_and_json_like
288
+ obj1 = ObjA(a=1, b=ObjB(c=4))
289
+ obj2 = ObjA.from_json_like(obj1.to_json_like()[0])
290
+ assert obj1 == obj2
291
+
292
+
293
+ @pytest.fixture
294
+ def obj_and_child_obj_with_list_and_json_like(null_config):
295
+ @dataclass
296
+ class ObjB(BaseJSONLike):
297
+ c: int
298
+
299
+ @dataclass
300
+ class ObjA(BaseJSONLike):
301
+ _child_objects = (
302
+ ChildObjectSpec(
303
+ name="b",
304
+ class_obj=ObjB,
305
+ is_multiple=True,
306
+ ),
307
+ )
308
+ a: int
309
+ b: float
310
+
311
+ js_1 = {
312
+ "a": 1,
313
+ "b": [{"c": 8}, {"c": 9}],
314
+ }
315
+
316
+ return ObjA, ObjB, js_1
317
+
318
+
319
+ def test_from_json_like_expected_obj_with_child_obj_list(
320
+ obj_and_child_obj_with_list_and_json_like,
321
+ ):
322
+ ObjA, ObjB, js_1 = obj_and_child_obj_with_list_and_json_like
323
+ assert ObjA.from_json_like(js_1) == ObjA(a=1, b=[ObjB(c=8), ObjB(c=9)])
324
+
325
+
326
+ def test_to_json_like_expected_json_like_with_child_obj_list(
327
+ obj_and_child_obj_with_list_and_json_like,
328
+ ):
329
+ ObjA, ObjB, js_1 = obj_and_child_obj_with_list_and_json_like
330
+ obj = ObjA(a=1, b=[ObjB(c=js_1["b"][0]["c"]), ObjB(c=js_1["b"][1]["c"])])
331
+ js, _ = obj.to_json_like()
332
+ assert js == js_1
333
+
334
+
335
+ def test_json_like_round_trip_with_child_obj_list(
336
+ obj_and_child_obj_with_list_and_json_like,
337
+ ):
338
+ ObjA, ObjB, _ = obj_and_child_obj_with_list_and_json_like
339
+ obj1 = ObjA(a=1, b=[ObjB(c=4), ObjB(c=5)])
340
+ obj2 = ObjA.from_json_like(obj1.to_json_like()[0])
341
+ assert obj1 == obj2
342
+
343
+
344
+ @pytest.fixture
345
+ def obj_and_child_obj_with_dict_key_only_and_json_like_and_json_like_normed(null_config):
346
+ @dataclass
347
+ class ObjB(BaseJSONLike):
348
+ name: str
349
+ c: int
350
+
351
+ @dataclass
352
+ class ObjA(BaseJSONLike):
353
+ _child_objects = (
354
+ ChildObjectSpec(
355
+ name="b", class_obj=ObjB, is_multiple=True, dict_key_attr="name"
356
+ ),
357
+ )
358
+ a: int
359
+ b: float
360
+
361
+ js_1 = {
362
+ "a": 1,
363
+ "b": {
364
+ "c1": {"c": 8},
365
+ "c2": {"c": 9},
366
+ },
367
+ }
368
+ js_1_normed = {
369
+ "a": 1,
370
+ "b": [
371
+ {"name": "c1", "c": 8},
372
+ {"name": "c2", "c": 9},
373
+ ],
374
+ }
375
+ return ObjA, ObjB, js_1, js_1_normed
376
+
377
+
378
+ def test_from_json_like_expected_obj_with_child_obj_dict_key_only(
379
+ obj_and_child_obj_with_dict_key_only_and_json_like_and_json_like_normed,
380
+ ):
381
+ (
382
+ ObjA,
383
+ ObjB,
384
+ js_1,
385
+ _,
386
+ ) = obj_and_child_obj_with_dict_key_only_and_json_like_and_json_like_normed
387
+ assert ObjA.from_json_like(js_1) == ObjA(
388
+ a=1,
389
+ b=[ObjB(name="c1", c=8), ObjB(name="c2", c=9)],
390
+ )
391
+
392
+
393
+ def test_to_json_like_expected_json_like_with_child_obj_dict_key_only(
394
+ obj_and_child_obj_with_dict_key_only_and_json_like_and_json_like_normed,
395
+ ):
396
+ (
397
+ ObjA,
398
+ ObjB,
399
+ js_1,
400
+ js_1_normed,
401
+ ) = obj_and_child_obj_with_dict_key_only_and_json_like_and_json_like_normed
402
+ obj = ObjA(
403
+ a=1,
404
+ b=[
405
+ ObjB(name="c1", c=js_1["b"]["c1"]["c"]),
406
+ ObjB(name="c2", c=js_1["b"]["c2"]["c"]),
407
+ ],
408
+ )
409
+ js, _ = obj.to_json_like()
410
+ assert js == js_1_normed
411
+
412
+
413
+ @pytest.fixture
414
+ def obj_and_child_obj_with_dict_key_val_and_json_like_and_json_like_normed(null_config):
415
+ @dataclass
416
+ class ObjB(BaseJSONLike):
417
+ name: str
418
+ c: int
419
+
420
+ @dataclass
421
+ class ObjA(BaseJSONLike):
422
+ _child_objects = (
423
+ ChildObjectSpec(
424
+ name="b",
425
+ class_obj=ObjB,
426
+ is_multiple=True,
427
+ dict_key_attr="name",
428
+ dict_val_attr="c",
429
+ ),
430
+ )
431
+ a: int
432
+ b: float
433
+
434
+ js_1 = {
435
+ "a": 1,
436
+ "b": {
437
+ "c1": 8,
438
+ "c2": 9,
439
+ },
440
+ }
441
+ js_1_normed = {
442
+ "a": 1,
443
+ "b": [
444
+ {"name": "c1", "c": 8},
445
+ {"name": "c2", "c": 9},
446
+ ],
447
+ }
448
+ return ObjA, ObjB, js_1, js_1_normed
449
+
450
+
451
+ def test_from_json_like_expected_obj_with_child_obj_dict_key_dict_val(
452
+ obj_and_child_obj_with_dict_key_val_and_json_like_and_json_like_normed,
453
+ ):
454
+ (
455
+ ObjA,
456
+ ObjB,
457
+ js_1,
458
+ _,
459
+ ) = obj_and_child_obj_with_dict_key_val_and_json_like_and_json_like_normed
460
+ assert ObjA.from_json_like(js_1) == ObjA(
461
+ a=1,
462
+ b=[ObjB(name="c1", c=8), ObjB(name="c2", c=9)],
463
+ )
464
+
465
+
466
+ def test_to_json_like_expected_json_like_with_child_obj_dict_key_dict_val(
467
+ obj_and_child_obj_with_dict_key_val_and_json_like_and_json_like_normed,
468
+ ):
469
+ (
470
+ ObjA,
471
+ ObjB,
472
+ js_1,
473
+ js_1_normed,
474
+ ) = obj_and_child_obj_with_dict_key_val_and_json_like_and_json_like_normed
475
+ obj = ObjA(
476
+ a=1,
477
+ b=[
478
+ ObjB(name="c1", c=js_1["b"]["c1"]),
479
+ ObjB(name="c2", c=js_1["b"]["c2"]),
480
+ ],
481
+ )
482
+ js, _ = obj.to_json_like()
483
+ assert js == js_1_normed
484
+
485
+
486
+ def test_from_json_like_raise_on_is_multiple_with_dict_but_no_dict_key_attr(null_config):
487
+ @dataclass
488
+ class ObjA(BaseJSONLike):
489
+ _child_objects = (
490
+ ChildObjectSpec(
491
+ name="b",
492
+ is_multiple=True,
493
+ ),
494
+ )
495
+ a: int
496
+ b: float
497
+
498
+ js_1 = {
499
+ "a": 1,
500
+ "b": {
501
+ "c1": 8,
502
+ "c2": 9,
503
+ },
504
+ }
505
+
506
+ with pytest.raises(ValueError):
507
+ ObjA.from_json_like(js_1)
508
+
509
+
510
+ def test_from_json_like_raise_on_is_multiple_with_dict_key_no_dict_val_but_non_dict_vals(
511
+ null_config,
512
+ ):
513
+ @dataclass
514
+ class ObjA(BaseJSONLike):
515
+ _child_objects = (
516
+ ChildObjectSpec(
517
+ name="b",
518
+ is_multiple=True,
519
+ dict_key_attr="name",
520
+ ),
521
+ )
522
+ a: int
523
+ b: float
524
+
525
+ js_1 = {
526
+ "a": 1,
527
+ "b": {
528
+ "c1": 8,
529
+ "c2": 9,
530
+ },
531
+ }
532
+ with pytest.raises(TypeError):
533
+ ObjA.from_json_like(js_1)
534
+
535
+
536
+ def test_from_json_like_raise_on_is_multiple_not_list_or_dict(null_config):
537
+ @dataclass
538
+ class ObjA(BaseJSONLike):
539
+ _child_objects = (
540
+ ChildObjectSpec(
541
+ name="b",
542
+ is_multiple=True,
543
+ ),
544
+ )
545
+ a: int
546
+ b: float
547
+
548
+ js_1 = {
549
+ "a": 1,
550
+ "b": 2,
551
+ }
552
+ with pytest.raises(TypeError):
553
+ ObjA.from_json_like(js_1)
554
+
555
+
556
+ def test_from_json_like_with_parent_ref(null_config):
557
+ @dataclass
558
+ class ObjB(BaseJSONLike):
559
+ name: str
560
+ c: int
561
+ obj_A: Any = None
562
+
563
+ def __eq__(self, other):
564
+ if not isinstance(other, self.__class__):
565
+ return False
566
+ return self.name == other.name and self.c == other.c
567
+
568
+ @dataclass
569
+ class ObjA(BaseJSONLike):
570
+ _child_objects = (
571
+ ChildObjectSpec(
572
+ name="b",
573
+ class_obj=ObjB,
574
+ parent_ref="obj_A",
575
+ ),
576
+ )
577
+ a: int
578
+ b: float
579
+
580
+ def __post_init__(self):
581
+ self._set_parent_refs()
582
+
583
+ def __eq__(self, other):
584
+ if not isinstance(other, self.__class__):
585
+ return False
586
+ return (
587
+ self.a == other.a
588
+ and self.b == other.b
589
+ and self.b.obj_A is self
590
+ and other.b.obj_A is other
591
+ )
592
+
593
+ js_1 = {
594
+ "a": 1,
595
+ "b": {
596
+ "name": "c1",
597
+ "c": 8,
598
+ },
599
+ }
600
+
601
+ objA = ObjA(a=1, b=ObjB(name=js_1["b"]["name"], c=js_1["b"]["c"]))
602
+ objA.b.obj_A = objA
603
+
604
+ assert ObjA.from_json_like(js_1) == objA
605
+
606
+
607
+ def test_json_like_round_trip_with_parent_ref(null_config):
608
+ @dataclass
609
+ class ObjB(BaseJSONLike):
610
+ name: str
611
+ c: int
612
+ obj_A: Any = None
613
+
614
+ def __eq__(self, other):
615
+ if not isinstance(other, self.__class__):
616
+ return False
617
+ return self.name == other.name and self.c == other.c
618
+
619
+ @dataclass
620
+ class ObjA(BaseJSONLike):
621
+ _child_objects = (
622
+ ChildObjectSpec(
623
+ name="b",
624
+ class_obj=ObjB,
625
+ parent_ref="obj_A",
626
+ ),
627
+ )
628
+ a: int
629
+ b: float
630
+
631
+ def __eq__(self, other):
632
+ if not isinstance(other, self.__class__):
633
+ return False
634
+ return (
635
+ self.a == other.a
636
+ and self.b == other.b
637
+ and self.b.obj_A is self
638
+ and other.b.obj_A is other
639
+ )
640
+
641
+ js_1 = {
642
+ "a": 1,
643
+ "b": {
644
+ "name": "c1",
645
+ "c": 8,
646
+ },
647
+ }
648
+
649
+ objA = ObjA.from_json_like(js_1)
650
+ assert objA.to_json_like()[0] == js_1
651
+
652
+
653
+ def test_from_json_like_optional_attr(null_config):
654
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
655
+
656
+ @dataclass
657
+ class ObjB(BaseJSONLikeSubClass):
658
+ name: str
659
+ c: int
660
+
661
+ @dataclass
662
+ class ObjA(BaseJSONLikeSubClass):
663
+ _child_objects = (
664
+ ChildObjectSpec(
665
+ name="b",
666
+ class_obj=ObjB,
667
+ ),
668
+ )
669
+ a: int
670
+ b: Any = None
671
+
672
+ js_in = {"a": 9, "b": None}
673
+ objA = ObjA.from_json_like(js_in)
674
+ assert objA == ObjA(a=9)
675
+
676
+
677
+ def test_from_json_like_optional_attr_with_is_multiple_both_none(null_config):
678
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
679
+
680
+ @dataclass
681
+ class ObjB(BaseJSONLikeSubClass):
682
+ name: str
683
+ c: int
684
+
685
+ @dataclass
686
+ class ObjA(BaseJSONLikeSubClass):
687
+ _child_objects = (
688
+ ChildObjectSpec(
689
+ name="b",
690
+ is_multiple=True,
691
+ class_obj=ObjB,
692
+ ),
693
+ )
694
+ a: int
695
+ b: Any = None
696
+
697
+ js_in = {
698
+ "a": 9,
699
+ "b": [None, None],
700
+ }
701
+ objA = ObjA.from_json_like(js_in)
702
+ assert objA == ObjA(a=9, b=[None, None])
703
+
704
+
705
+ def test_from_json_like_optional_attr_with_is_multiple_one_none(null_config):
706
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
707
+
708
+ @dataclass
709
+ class ObjB(BaseJSONLikeSubClass):
710
+ name: str
711
+ c: int
712
+
713
+ @dataclass
714
+ class ObjA(BaseJSONLikeSubClass):
715
+ _child_objects = (
716
+ ChildObjectSpec(
717
+ name="b",
718
+ is_multiple=True,
719
+ class_obj=ObjB,
720
+ ),
721
+ )
722
+ a: int
723
+ b: Any = None
724
+
725
+ js_in = {
726
+ "a": 9,
727
+ "b": [None, {"name": "c1", "c": 2}],
728
+ }
729
+ objA = ObjA.from_json_like(js_in)
730
+ assert objA == ObjA(a=9, b=[None, ObjB(name="c1", c=2)])
731
+
732
+
733
+ def test_from_json_like_optional_attr_with_is_multiple_one_none_and_shared_data_name(
734
+ null_config,
735
+ ):
736
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
737
+
738
+ @dataclass
739
+ class ObjB(BaseJSONLikeSubClass):
740
+ name: str
741
+ c: int
742
+
743
+ @dataclass
744
+ class ObjA(BaseJSONLikeSubClass):
745
+ _child_objects = (
746
+ ChildObjectSpec(
747
+ name="b",
748
+ is_multiple=True,
749
+ class_obj=ObjB,
750
+ shared_data_name="bees",
751
+ shared_data_primary_key="name",
752
+ ),
753
+ )
754
+ a: int
755
+ b: Any = None
756
+
757
+ dcts = [
758
+ {"name": "c1", "c": 2},
759
+ {"name": "c2", "c": 3},
760
+ ]
761
+
762
+ obj_lst = ObjectList([ObjB(**i) for i in dcts])
763
+
764
+ js_in = {
765
+ "a": 9,
766
+ "b": [None, "c1"],
767
+ }
768
+ objA = ObjA.from_json_like(js_in, shared_data={"bees": obj_lst})
769
+ assert objA == ObjA(a=9, b=[None, ObjB(name="c1", c=2)])
770
+
771
+
772
+ def test_from_json_like_optional_attr_with_is_multiple_all_none_and_shared_data_name(
773
+ null_config,
774
+ ):
775
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
776
+
777
+ @dataclass
778
+ class ObjB(BaseJSONLikeSubClass):
779
+ name: str
780
+ c: int
781
+
782
+ @dataclass
783
+ class ObjA(BaseJSONLikeSubClass):
784
+ _child_objects = (
785
+ ChildObjectSpec(
786
+ name="b",
787
+ is_multiple=True,
788
+ class_obj=ObjB,
789
+ shared_data_name="bees",
790
+ shared_data_primary_key="name",
791
+ ),
792
+ )
793
+ a: int
794
+ b: Any = None
795
+
796
+ dcts = [
797
+ {"name": "c1", "c": 2},
798
+ {"name": "c2", "c": 3},
799
+ ]
800
+
801
+ obj_lst = ObjectList([ObjB(**i) for i in dcts])
802
+
803
+ js_in = {
804
+ "a": 9,
805
+ "b": [None, None],
806
+ }
807
+ objA = ObjA.from_json_like(js_in, shared_data={"bees": obj_lst})
808
+ assert objA == ObjA(a=9, b=[None, None])
809
+
810
+
811
+ def test_from_json_like_optional_attr_with_shared_data_name(null_config):
812
+ # test optional attribute with shared_data_name
813
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
814
+
815
+ @dataclass
816
+ class ObjB(BaseJSONLikeSubClass):
817
+ name: str
818
+ c: int
819
+
820
+ @dataclass
821
+ class ObjA(BaseJSONLikeSubClass):
822
+ _child_objects = (
823
+ ChildObjectSpec(
824
+ name="b",
825
+ class_obj=ObjB,
826
+ shared_data_name="bees",
827
+ shared_data_primary_key="name",
828
+ ),
829
+ )
830
+ a: int
831
+ b: Any = None
832
+
833
+ dcts = [
834
+ {"name": "c1", "c": 2},
835
+ {"name": "c2", "c": 3},
836
+ ]
837
+
838
+ obj_lst = ObjectList([ObjB(**i) for i in dcts])
839
+
840
+ js_in = {
841
+ "a": 9,
842
+ "b": None,
843
+ }
844
+ objA = ObjA.from_json_like(js_in, shared_data={"bees": obj_lst})
845
+ assert objA == ObjA(a=9, b=None)
846
+
847
+
848
+ def test_from_json_like_optional_attr_with_enum(null_config):
849
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
850
+
851
+ class MyEnum(enum.Enum):
852
+ A = 0
853
+ B = 1
854
+ C = 2
855
+
856
+ @dataclass
857
+ class ObjA(BaseJSONLikeSubClass):
858
+ _child_objects = (ChildObjectSpec(name="b", class_obj=MyEnum, is_enum=True),)
859
+ a: int
860
+ b: Any = None
861
+
862
+ js_in = {
863
+ "a": 9,
864
+ "b": None,
865
+ }
866
+ objA = ObjA.from_json_like(js_in)
867
+ assert objA == ObjA(a=9, b=None)
868
+
869
+
870
+ def test_from_json_like_with_is_multiple(null_config):
871
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
872
+
873
+ @dataclass
874
+ class ObjB(BaseJSONLikeSubClass):
875
+ name: str
876
+ c: int
877
+
878
+ @dataclass
879
+ class ObjA(BaseJSONLikeSubClass):
880
+ _child_objects = (ChildObjectSpec(name="b", class_obj=ObjB, is_multiple=True),)
881
+ a: int
882
+ b: Any
883
+
884
+ # e.g. from data files:
885
+ js_in = {
886
+ "a": 9,
887
+ "b": [{"name": "c1", "c": 2}, {"name": "c2", "c": 3}], # multiple
888
+ }
889
+
890
+ objA = ObjA.from_json_like(js_in)
891
+ assert objA == ObjA(a=9, b=[ObjB(name="c1", c=2), ObjB(name="c2", c=3)])
892
+
893
+
894
+ def test_from_json_like_with_is_multiple_dict_values(null_config):
895
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
896
+
897
+ @dataclass
898
+ class ObjB(BaseJSONLikeSubClass):
899
+ name: str
900
+ c: int
901
+
902
+ @dataclass
903
+ class ObjA(BaseJSONLikeSubClass):
904
+ _child_objects = (
905
+ ChildObjectSpec(
906
+ name="b",
907
+ class_obj=ObjB,
908
+ is_multiple=True,
909
+ is_dict_values=True,
910
+ ),
911
+ )
912
+ a: int
913
+ b: Any
914
+
915
+ # e.g. from data files:
916
+ js_in = {
917
+ "a": 9,
918
+ "b": {
919
+ "key1": {"name": "c1", "c": 2},
920
+ "key2": {"name": "c2", "c": 3},
921
+ }, # multiple dict values, arbitrary keys, dict structure will be maintained
922
+ }
923
+
924
+ objA = ObjA.from_json_like(js_in)
925
+ assert objA == ObjA(
926
+ a=9,
927
+ b={"key1": ObjB(name="c1", c=2), "key2": ObjB(name="c2", c=3)},
928
+ )
929
+
930
+
931
+ def test_from_json_like_with_is_multiple_dict_values_ensure_list(null_config):
932
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
933
+
934
+ @dataclass
935
+ class ObjB(BaseJSONLikeSubClass):
936
+ name: str
937
+ c: int
938
+
939
+ @dataclass
940
+ class ObjA(BaseJSONLikeSubClass):
941
+ _child_objects = (
942
+ ChildObjectSpec(
943
+ name="b",
944
+ class_obj=ObjB,
945
+ is_multiple=True,
946
+ is_dict_values=True,
947
+ is_dict_values_ensure_list=True,
948
+ ),
949
+ )
950
+ a: int
951
+ b: Any
952
+
953
+ # e.g. from data files:
954
+ js_in = {
955
+ "a": 9,
956
+ "b": {
957
+ "key1": {"name": "c1", "c": 2},
958
+ "key2": [{"name": "c2", "c": 3}, {"name": "c3", "c": 4}],
959
+ },
960
+ # multiple dict values (and multiple items for each), arbitrary keys, dict
961
+ # structure will be maintained
962
+ }
963
+
964
+ objA = ObjA.from_json_like(js_in)
965
+ assert objA == ObjA(
966
+ a=9,
967
+ b={
968
+ "key1": [ObjB(name="c1", c=2)],
969
+ "key2": [ObjB(name="c2", c=3), ObjB(name="c3", c=4)],
970
+ },
971
+ )
972
+
973
+
974
+ def test_from_json_like_round_trip_with_is_multiple_dict_values(null_config):
975
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
976
+
977
+ @dataclass
978
+ class ObjB(BaseJSONLikeSubClass):
979
+ name: str
980
+ c: int
981
+
982
+ @dataclass
983
+ class ObjA(BaseJSONLikeSubClass):
984
+ _child_objects = (
985
+ ChildObjectSpec(
986
+ name="b",
987
+ class_obj=ObjB,
988
+ is_multiple=True,
989
+ is_dict_values=True,
990
+ ),
991
+ )
992
+ a: int
993
+ b: Any
994
+
995
+ # e.g. from data files:
996
+ js_in = {
997
+ "a": 9,
998
+ "b": {
999
+ "key1": {"name": "c1", "c": 2},
1000
+ "key2": {"name": "c2", "c": 3},
1001
+ }, # multiple dict values, arbitrary keys, dict structure will be maintained
1002
+ }
1003
+
1004
+ objA = ObjA.from_json_like(js_in)
1005
+ assert objA.to_json_like()[0] == js_in
1006
+
1007
+
1008
+ def test_from_json_like_round_trip_with_is_multiple_dict_values_ensure_list(null_config):
1009
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1010
+
1011
+ @dataclass
1012
+ class ObjB(BaseJSONLikeSubClass):
1013
+ name: str
1014
+ c: int
1015
+
1016
+ @dataclass
1017
+ class ObjA(BaseJSONLikeSubClass):
1018
+ _child_objects = (
1019
+ ChildObjectSpec(
1020
+ name="b",
1021
+ class_obj=ObjB,
1022
+ is_multiple=True,
1023
+ is_dict_values=True,
1024
+ is_dict_values_ensure_list=True,
1025
+ ),
1026
+ )
1027
+ a: int
1028
+ b: Any
1029
+
1030
+ # e.g. from data files:
1031
+ js_in = {
1032
+ "a": 9,
1033
+ "b": {
1034
+ "key1": [{"name": "c1", "c": 2}],
1035
+ "key2": [{"name": "c2", "c": 3}, {"name": "c3", "c": 4}],
1036
+ },
1037
+ # multiple dict values (and multiple items for each), arbitrary keys, dict
1038
+ # structure will be maintained
1039
+ }
1040
+
1041
+ objA = ObjA.from_json_like(js_in)
1042
+ assert objA.to_json_like()[0] == js_in
1043
+
1044
+
1045
+ def test_from_json_like_with_is_multiple_and_shared_data(null_config):
1046
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1047
+
1048
+ @dataclass
1049
+ class ObjB(BaseJSONLikeSubClass):
1050
+ name: str
1051
+ c: int
1052
+
1053
+ @dataclass
1054
+ class ObjA(BaseJSONLikeSubClass):
1055
+ _child_objects = (
1056
+ ChildObjectSpec(
1057
+ name="b",
1058
+ class_obj=ObjB,
1059
+ is_multiple=True,
1060
+ shared_data_name="bees",
1061
+ shared_data_primary_key="name",
1062
+ ),
1063
+ )
1064
+ a: int
1065
+ b: Any
1066
+
1067
+ dcts = [
1068
+ {"name": "c1", "c": 2},
1069
+ {"name": "c2", "c": 3},
1070
+ ]
1071
+
1072
+ obj_lst = ObjectList([ObjB(**i) for i in dcts])
1073
+
1074
+ # e.g. from data files:
1075
+ js_in = {
1076
+ "a": 9,
1077
+ "b": ["c1", "c2"], # multiple from shared
1078
+ }
1079
+
1080
+ objA = ObjA.from_json_like(js_in, shared_data={"bees": obj_lst})
1081
+ assert objA == ObjA(a=9, b=[ObjB(name="c1", c=2), ObjB(name="c2", c=3)])
1082
+
1083
+
1084
+ def test_from_json_like_with_is_multiple_and_shared_data_dict_lookup(null_config):
1085
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1086
+
1087
+ @dataclass
1088
+ class ObjB(BaseJSONLikeSubClass):
1089
+ name: str
1090
+ c: int
1091
+ _hash_value: str | None = None
1092
+
1093
+ @dataclass
1094
+ class ObjA(BaseJSONLikeSubClass):
1095
+ _child_objects = (
1096
+ ChildObjectSpec(
1097
+ name="b",
1098
+ class_obj=ObjB,
1099
+ is_multiple=True,
1100
+ dict_key_attr="name",
1101
+ shared_data_name="bees",
1102
+ shared_data_primary_key="name",
1103
+ ),
1104
+ )
1105
+ a: int
1106
+ b: Any
1107
+
1108
+ dcts = [
1109
+ {"name": "c1", "c": 2},
1110
+ {"name": "c2", "c": 3},
1111
+ ]
1112
+
1113
+ obj_lst = ObjectList([ObjB(**i) for i in dcts])
1114
+
1115
+ # e.g. from data files:
1116
+ js_in = {
1117
+ "a": 9,
1118
+ "b": {
1119
+ "c1": {"c": 2},
1120
+ "c2": {"c": 3},
1121
+ }, # multiple from shared as dict with lookup kwargs
1122
+ }
1123
+
1124
+ objA = ObjA.from_json_like(js_in, shared_data={"bees": obj_lst})
1125
+ assert objA == ObjA(a=9, b=[ObjB(name="c1", c=2), ObjB(name="c2", c=3)])
1126
+
1127
+
1128
+ def test_from_json_like_enum(null_config):
1129
+ # test enum from_json_like
1130
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1131
+
1132
+ class MyEnum(enum.Enum):
1133
+ A = 0
1134
+ B = 1
1135
+ C = 2
1136
+
1137
+ @dataclass
1138
+ class ObjA(BaseJSONLikeSubClass):
1139
+ _child_objects = (ChildObjectSpec(name="b", class_obj=MyEnum, is_enum=True),)
1140
+ a: int
1141
+ b: Any
1142
+
1143
+ js_in = {
1144
+ "a": 9,
1145
+ "b": "A",
1146
+ }
1147
+ objA = ObjA.from_json_like(js_in)
1148
+ assert objA == ObjA(a=9, b=MyEnum.A)
1149
+
1150
+
1151
+ def test_from_to_json_round_trip_enum(null_config):
1152
+ # test enum round trip
1153
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1154
+
1155
+ class MyEnum(enum.Enum):
1156
+ A = 0
1157
+ B = 1
1158
+ C = 2
1159
+
1160
+ @dataclass
1161
+ class ObjA(BaseJSONLikeSubClass):
1162
+ _child_objects = (ChildObjectSpec(name="b", class_obj=MyEnum, is_enum=True),)
1163
+ a: int
1164
+ b: Any
1165
+
1166
+ js_in = {
1167
+ "a": 9,
1168
+ "b": "A",
1169
+ }
1170
+ objA = ObjA.from_json_like(js_in)
1171
+ assert objA.to_json_like()[0] == js_in
1172
+
1173
+
1174
+ def test_from_json_like_round_trip_enum_case_insensitivity(null_config):
1175
+ # test enum from_json_like case-insensitivity
1176
+ BaseJSONLikeSubClass = type("MyBaseJSONLike", (BaseJSONLike,), {})
1177
+
1178
+ class MyEnum(enum.Enum):
1179
+ A = 0
1180
+ B = 1
1181
+ C = 2
1182
+
1183
+ @dataclass
1184
+ class ObjA(BaseJSONLikeSubClass):
1185
+ _child_objects = (ChildObjectSpec(name="b", class_obj=MyEnum, is_enum=True),)
1186
+ a: int
1187
+ b: Any
1188
+
1189
+ js_in_1 = {
1190
+ "a": 9,
1191
+ "b": "a",
1192
+ }
1193
+ js_in_2 = {
1194
+ "a": 9,
1195
+ "b": "A",
1196
+ }
1197
+ objA_1 = ObjA.from_json_like(js_in_1)
1198
+ objA_2 = ObjA.from_json_like(js_in_2)
1199
+ assert objA_1 == objA_2
1200
+
1201
+
1202
+ @pytest.mark.skip(
1203
+ reason=(
1204
+ "We currently cull parent references in `JSONLike.to_dict`. This should ideally "
1205
+ "be in BaseJSONLike.to_dict, which would allow this test to pass. However, "
1206
+ "culling involves looping over app._core_classes, which we cannot access from "
1207
+ "this class."
1208
+ )
1209
+ )
1210
+ def test_to_json_like_with_child_ref(null_config):
1211
+ """i.e. check parent references are not included in child item to_json_like output:"""
1212
+
1213
+ @dataclass
1214
+ class ObjB(BaseJSONLike):
1215
+ name: str
1216
+ c: int
1217
+ obj_A: Any = None
1218
+
1219
+ @dataclass
1220
+ class ObjA(BaseJSONLike):
1221
+ _child_objects = (
1222
+ ChildObjectSpec(
1223
+ name="b",
1224
+ class_obj=ObjB,
1225
+ parent_ref="obj_A",
1226
+ ),
1227
+ )
1228
+ a: int
1229
+ b: float
1230
+
1231
+ def __post_init__(self):
1232
+ self._set_parent_refs()
1233
+
1234
+ objB = ObjB(name="c1", c=8)
1235
+ objA = ObjA(a=1, b=objB)
1236
+
1237
+ js_1 = {
1238
+ "a": 1,
1239
+ "b": {
1240
+ "name": "c1",
1241
+ "c": 8,
1242
+ },
1243
+ }
1244
+
1245
+ objA = ObjA.from_json_like(js_1)
1246
+
1247
+ assert objA.b.obj_A == objA and objA.b.to_json_like()[0] == js_1["b"]