UncountablePythonSDK 0.0.8__py3-none-any.whl → 0.0.92__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.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

Files changed (312) hide show
  1. UncountablePythonSDK-0.0.92.dist-info/METADATA +61 -0
  2. UncountablePythonSDK-0.0.92.dist-info/RECORD +301 -0
  3. {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
  4. {UncountablePythonSDK-0.0.8.dist-info → UncountablePythonSDK-0.0.92.dist-info}/top_level.txt +1 -1
  5. docs/.gitignore +1 -0
  6. docs/conf.py +57 -0
  7. docs/index.md +13 -0
  8. docs/justfile +12 -0
  9. docs/quickstart.md +19 -0
  10. docs/requirements.txt +7 -0
  11. docs/static/favicons/android-chrome-192x192.png +0 -0
  12. docs/static/favicons/android-chrome-512x512.png +0 -0
  13. docs/static/favicons/apple-touch-icon.png +0 -0
  14. docs/static/favicons/browserconfig.xml +9 -0
  15. docs/static/favicons/favicon-16x16.png +0 -0
  16. docs/static/favicons/favicon-32x32.png +0 -0
  17. docs/static/favicons/manifest.json +18 -0
  18. docs/static/favicons/mstile-150x150.png +0 -0
  19. docs/static/favicons/safari-pinned-tab.svg +32 -0
  20. docs/static/logo_blue.png +0 -0
  21. examples/async_batch.py +35 -0
  22. examples/create_entity.py +22 -17
  23. examples/download_files.py +26 -0
  24. examples/edit_recipe_inputs.py +50 -0
  25. examples/integration-server/jobs/materials_auto/example_cron.py +18 -0
  26. examples/integration-server/jobs/materials_auto/example_wh.py +15 -0
  27. examples/integration-server/jobs/materials_auto/profile.yaml +43 -0
  28. examples/integration-server/pyproject.toml +224 -0
  29. examples/invoke_uploader.py +26 -0
  30. examples/set_recipe_metadata_file.py +40 -0
  31. examples/set_recipe_output_file_sdk.py +26 -0
  32. examples/upload_files.py +18 -0
  33. pkgs/argument_parser/__init__.py +5 -0
  34. pkgs/argument_parser/_is_enum.py +1 -6
  35. pkgs/argument_parser/argument_parser.py +232 -76
  36. pkgs/argument_parser/case_convert.py +4 -3
  37. pkgs/filesystem_utils/__init__.py +20 -0
  38. pkgs/filesystem_utils/_blob_session.py +137 -0
  39. pkgs/filesystem_utils/_gdrive_session.py +309 -0
  40. pkgs/filesystem_utils/_local_session.py +69 -0
  41. pkgs/filesystem_utils/_s3_session.py +117 -0
  42. pkgs/filesystem_utils/_sftp_session.py +147 -0
  43. pkgs/filesystem_utils/file_type_utils.py +91 -0
  44. pkgs/filesystem_utils/filesystem_session.py +39 -0
  45. pkgs/py.typed +0 -0
  46. pkgs/serialization/__init__.py +8 -1
  47. pkgs/serialization/annotation.py +64 -0
  48. pkgs/serialization/missing_sentry.py +1 -1
  49. pkgs/serialization/opaque_key.py +1 -1
  50. pkgs/serialization/serial_alias.py +47 -0
  51. pkgs/serialization/serial_class.py +65 -50
  52. pkgs/serialization/serial_generic.py +16 -0
  53. pkgs/serialization/serial_union.py +84 -0
  54. pkgs/serialization/yaml.py +57 -0
  55. pkgs/serialization_util/__init__.py +7 -7
  56. pkgs/serialization_util/_get_type_for_serialization.py +1 -3
  57. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  58. pkgs/serialization_util/dataclasses.py +14 -0
  59. pkgs/serialization_util/serialization_helpers.py +116 -74
  60. pkgs/strenum_compat/strenum_compat.py +1 -9
  61. pkgs/type_spec/actions_registry/__init__.py +0 -0
  62. pkgs/type_spec/actions_registry/__main__.py +126 -0
  63. pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
  64. pkgs/type_spec/builder.py +475 -89
  65. pkgs/type_spec/config.py +24 -19
  66. pkgs/type_spec/emit_io_ts.py +5 -2
  67. pkgs/type_spec/emit_open_api.py +266 -32
  68. pkgs/type_spec/emit_open_api_util.py +32 -13
  69. pkgs/type_spec/emit_python.py +599 -151
  70. pkgs/type_spec/emit_typescript.py +74 -273
  71. pkgs/type_spec/emit_typescript_util.py +239 -5
  72. pkgs/type_spec/load_types.py +55 -10
  73. pkgs/type_spec/open_api_util.py +30 -41
  74. pkgs/type_spec/parts/base.py.prepart +4 -3
  75. pkgs/type_spec/type_info/emit_type_info.py +178 -16
  76. pkgs/type_spec/util.py +11 -11
  77. pkgs/type_spec/value_spec/__main__.py +3 -3
  78. pkgs/type_spec/value_spec/convert_type.py +8 -1
  79. pkgs/type_spec/value_spec/emit_python.py +13 -4
  80. uncountable/__init__.py +1 -2
  81. uncountable/core/__init__.py +12 -2
  82. uncountable/core/async_batch.py +37 -0
  83. uncountable/core/client.py +293 -43
  84. uncountable/core/environment.py +41 -0
  85. uncountable/core/file_upload.py +135 -0
  86. uncountable/core/types.py +17 -0
  87. uncountable/integration/__init__.py +0 -0
  88. uncountable/integration/cli.py +49 -0
  89. uncountable/integration/construct_client.py +51 -0
  90. uncountable/integration/cron.py +29 -0
  91. uncountable/integration/db/__init__.py +0 -0
  92. uncountable/integration/db/connect.py +18 -0
  93. uncountable/integration/db/session.py +25 -0
  94. uncountable/integration/entrypoint.py +13 -0
  95. uncountable/integration/executors/__init__.py +0 -0
  96. uncountable/integration/executors/executors.py +148 -0
  97. uncountable/integration/executors/generic_upload_executor.py +284 -0
  98. uncountable/integration/executors/script_executor.py +25 -0
  99. uncountable/integration/job.py +87 -0
  100. uncountable/integration/queue_runner/__init__.py +0 -0
  101. uncountable/integration/queue_runner/command_server/__init__.py +24 -0
  102. uncountable/integration/queue_runner/command_server/command_client.py +68 -0
  103. uncountable/integration/queue_runner/command_server/command_server.py +64 -0
  104. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  105. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
  108. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
  109. uncountable/integration/queue_runner/command_server/types.py +52 -0
  110. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  111. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
  112. uncountable/integration/queue_runner/datastore/interface.py +19 -0
  113. uncountable/integration/queue_runner/datastore/model.py +17 -0
  114. uncountable/integration/queue_runner/job_scheduler.py +163 -0
  115. uncountable/integration/queue_runner/queue_runner.py +26 -0
  116. uncountable/integration/queue_runner/types.py +7 -0
  117. uncountable/integration/queue_runner/worker.py +119 -0
  118. uncountable/integration/scan_profiles.py +67 -0
  119. uncountable/integration/scheduler.py +150 -0
  120. uncountable/integration/secret_retrieval/__init__.py +3 -0
  121. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  122. uncountable/integration/server.py +117 -0
  123. uncountable/integration/telemetry.py +209 -0
  124. uncountable/integration/webhook_server/entrypoint.py +170 -0
  125. uncountable/types/__init__.py +136 -20
  126. uncountable/types/api/batch/execute_batch.py +15 -7
  127. uncountable/types/api/batch/execute_batch_load_async.py +42 -0
  128. uncountable/types/api/chemical/__init__.py +1 -0
  129. uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
  130. uncountable/types/api/entity/create_entities.py +23 -11
  131. uncountable/types/api/entity/create_entity.py +21 -12
  132. uncountable/types/api/entity/get_entities_data.py +18 -8
  133. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  134. uncountable/types/api/entity/list_entities.py +27 -12
  135. uncountable/types/api/entity/lock_entity.py +45 -0
  136. uncountable/types/api/entity/resolve_entity_ids.py +17 -7
  137. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  138. uncountable/types/api/entity/set_values.py +14 -7
  139. uncountable/types/api/entity/transition_entity_phase.py +80 -0
  140. uncountable/types/api/entity/unlock_entity.py +44 -0
  141. uncountable/types/api/equipment/__init__.py +1 -0
  142. uncountable/types/api/equipment/associate_equipment_input.py +44 -0
  143. uncountable/types/api/field_options/__init__.py +1 -0
  144. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  145. uncountable/types/api/files/__init__.py +1 -0
  146. uncountable/types/api/files/download_file.py +77 -0
  147. uncountable/types/api/id_source/__init__.py +1 -0
  148. uncountable/types/api/id_source/list_id_source.py +56 -0
  149. uncountable/types/api/id_source/match_id_source.py +54 -0
  150. uncountable/types/api/input_groups/get_input_group_names.py +16 -6
  151. uncountable/types/api/inputs/create_inputs.py +24 -11
  152. uncountable/types/api/inputs/get_input_data.py +32 -13
  153. uncountable/types/api/inputs/get_input_names.py +18 -8
  154. uncountable/types/api/inputs/get_inputs_data.py +29 -10
  155. uncountable/types/api/inputs/set_input_attribute_values.py +16 -9
  156. uncountable/types/api/inputs/set_input_category.py +44 -0
  157. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  158. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  159. uncountable/types/api/material_families/__init__.py +1 -0
  160. uncountable/types/api/material_families/update_entity_material_families.py +48 -0
  161. uncountable/types/api/outputs/get_output_data.py +32 -16
  162. uncountable/types/api/outputs/get_output_names.py +18 -8
  163. uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
  164. uncountable/types/api/permissions/__init__.py +1 -0
  165. uncountable/types/api/permissions/set_core_permissions.py +105 -0
  166. uncountable/types/api/project/get_projects.py +17 -7
  167. uncountable/types/api/project/get_projects_data.py +21 -11
  168. uncountable/types/api/recipe_links/__init__.py +1 -0
  169. uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
  170. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  171. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +18 -8
  172. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  173. uncountable/types/api/recipes/archive_recipes.py +42 -0
  174. uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
  175. uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
  176. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  177. uncountable/types/api/recipes/create_recipe.py +51 -0
  178. uncountable/types/api/recipes/create_recipes.py +25 -12
  179. uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
  180. uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
  181. uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
  182. uncountable/types/api/recipes/get_curve.py +15 -7
  183. uncountable/types/api/recipes/get_recipe_calculations.py +17 -10
  184. uncountable/types/api/recipes/get_recipe_links.py +13 -6
  185. uncountable/types/api/recipes/get_recipe_names.py +16 -6
  186. uncountable/types/api/recipes/get_recipe_output_metadata.py +14 -7
  187. uncountable/types/api/recipes/get_recipes_data.py +63 -38
  188. uncountable/types/api/recipes/lock_recipes.py +63 -0
  189. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  190. uncountable/types/api/recipes/set_recipe_inputs.py +19 -10
  191. uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
  192. uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
  193. uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
  194. uncountable/types/api/recipes/set_recipe_outputs.py +26 -12
  195. uncountable/types/api/recipes/set_recipe_tags.py +109 -0
  196. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  197. uncountable/types/api/recipes/unlock_recipes.py +50 -0
  198. uncountable/types/api/triggers/__init__.py +1 -0
  199. uncountable/types/api/triggers/run_trigger.py +43 -0
  200. uncountable/types/api/uploader/__init__.py +1 -0
  201. uncountable/types/api/uploader/invoke_uploader.py +47 -0
  202. uncountable/types/async_batch.py +13 -0
  203. uncountable/types/async_batch_processor.py +384 -0
  204. uncountable/types/async_batch_t.py +97 -0
  205. uncountable/types/async_jobs.py +9 -0
  206. uncountable/types/async_jobs_t.py +53 -0
  207. uncountable/types/auth_retrieval.py +12 -0
  208. uncountable/types/auth_retrieval_t.py +75 -0
  209. uncountable/types/base.py +5 -78
  210. uncountable/types/base_t.py +85 -0
  211. uncountable/types/calculations.py +3 -18
  212. uncountable/types/calculations_t.py +27 -0
  213. uncountable/types/chemical_structure.py +8 -0
  214. uncountable/types/chemical_structure_t.py +28 -0
  215. uncountable/types/client_base.py +1093 -54
  216. uncountable/types/client_config.py +8 -0
  217. uncountable/types/client_config_t.py +26 -0
  218. uncountable/types/curves.py +5 -42
  219. uncountable/types/curves_t.py +51 -0
  220. uncountable/types/entity.py +8 -269
  221. uncountable/types/entity_t.py +393 -0
  222. uncountable/types/experiment_groups.py +3 -18
  223. uncountable/types/experiment_groups_t.py +27 -0
  224. uncountable/types/field_values.py +17 -60
  225. uncountable/types/field_values_t.py +204 -0
  226. uncountable/types/fields.py +3 -19
  227. uncountable/types/fields_t.py +28 -0
  228. uncountable/types/generic_upload.py +15 -0
  229. uncountable/types/generic_upload_t.py +119 -0
  230. uncountable/types/id_source.py +12 -0
  231. uncountable/types/id_source_t.py +68 -0
  232. uncountable/types/identifier.py +11 -0
  233. uncountable/types/identifier_t.py +63 -0
  234. uncountable/types/input_attributes.py +3 -24
  235. uncountable/types/input_attributes_t.py +30 -0
  236. uncountable/types/inputs.py +6 -56
  237. uncountable/types/inputs_t.py +83 -0
  238. uncountable/types/integration_server.py +9 -0
  239. uncountable/types/integration_server_t.py +42 -0
  240. uncountable/types/job_definition.py +27 -0
  241. uncountable/types/job_definition_t.py +260 -0
  242. uncountable/types/outputs.py +3 -21
  243. uncountable/types/outputs_t.py +30 -0
  244. uncountable/types/overrides.py +10 -0
  245. uncountable/types/overrides_t.py +49 -0
  246. uncountable/types/permissions.py +8 -0
  247. uncountable/types/permissions_t.py +46 -0
  248. uncountable/types/phases.py +3 -18
  249. uncountable/types/phases_t.py +27 -0
  250. uncountable/types/post_base.py +8 -0
  251. uncountable/types/post_base_t.py +30 -0
  252. uncountable/types/queued_job.py +16 -0
  253. uncountable/types/queued_job_t.py +123 -0
  254. uncountable/types/recipe_identifiers.py +12 -0
  255. uncountable/types/recipe_identifiers_t.py +76 -0
  256. uncountable/types/recipe_inputs.py +9 -0
  257. uncountable/types/recipe_inputs_t.py +30 -0
  258. uncountable/types/recipe_links.py +4 -45
  259. uncountable/types/recipe_links_t.py +54 -0
  260. uncountable/types/recipe_metadata.py +5 -45
  261. uncountable/types/recipe_metadata_t.py +58 -0
  262. uncountable/types/recipe_output_metadata.py +3 -19
  263. uncountable/types/recipe_output_metadata_t.py +28 -0
  264. uncountable/types/recipe_tags.py +3 -18
  265. uncountable/types/recipe_tags_t.py +27 -0
  266. uncountable/types/recipe_workflow_steps.py +14 -0
  267. uncountable/types/recipe_workflow_steps_t.py +95 -0
  268. uncountable/types/recipes.py +8 -0
  269. uncountable/types/recipes_t.py +25 -0
  270. uncountable/types/response.py +3 -20
  271. uncountable/types/response_t.py +26 -0
  272. uncountable/types/secret_retrieval.py +12 -0
  273. uncountable/types/secret_retrieval_t.py +75 -0
  274. uncountable/types/units.py +3 -18
  275. uncountable/types/units_t.py +27 -0
  276. uncountable/types/users.py +3 -19
  277. uncountable/types/users_t.py +28 -0
  278. uncountable/types/webhook_job.py +9 -0
  279. uncountable/types/webhook_job_t.py +37 -0
  280. uncountable/types/workflows.py +4 -27
  281. uncountable/types/workflows_t.py +39 -0
  282. UncountablePythonSDK-0.0.8.dist-info/METADATA +0 -27
  283. UncountablePythonSDK-0.0.8.dist-info/RECORD +0 -134
  284. examples/recipe-import/importer.py +0 -39
  285. type_spec/external/api/batch/execute_batch.yaml +0 -56
  286. type_spec/external/api/entity/create_entities.yaml +0 -33
  287. type_spec/external/api/entity/create_entity.yaml +0 -39
  288. type_spec/external/api/entity/get_entities_data.yaml +0 -29
  289. type_spec/external/api/entity/list_entities.yaml +0 -52
  290. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  291. type_spec/external/api/entity/set_values.yaml +0 -18
  292. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  293. type_spec/external/api/inputs/create_inputs.yaml +0 -48
  294. type_spec/external/api/inputs/get_input_data.yaml +0 -95
  295. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  296. type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
  297. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
  298. type_spec/external/api/outputs/get_output_data.yaml +0 -92
  299. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  300. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  301. type_spec/external/api/project/get_projects.yaml +0 -42
  302. type_spec/external/api/project/get_projects_data.yaml +0 -50
  303. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  304. type_spec/external/api/recipes/create_recipes.yaml +0 -47
  305. type_spec/external/api/recipes/get_curve.yaml +0 -18
  306. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
  307. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  308. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  309. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
  310. type_spec/external/api/recipes/get_recipes_data.yaml +0 -238
  311. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
  312. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
@@ -0,0 +1,14 @@
1
+ from dataclasses import fields
2
+ from typing import TYPE_CHECKING, Any, Iterator
3
+
4
+ if TYPE_CHECKING:
5
+ from _typeshed import DataclassInstance
6
+
7
+
8
+ def iterate_fields(d: "DataclassInstance") -> Iterator[tuple[str, Any]]:
9
+ for field in fields(d):
10
+ yield field.name, getattr(d, field.name)
11
+
12
+
13
+ def dict_fields(d: "DataclassInstance") -> dict[str, Any]:
14
+ return dict(iterate_fields(d))
@@ -1,27 +1,31 @@
1
+ # warnings -- types here assume that keys to dictionaries are strings
2
+ # this is true most of the time, but there are cases where we have integer indexes
3
+
4
+ import dataclasses
1
5
  import datetime
2
6
  import enum
7
+ import functools
8
+ from collections.abc import Callable, Mapping, Sequence
3
9
  from decimal import Decimal
4
10
  from typing import (
5
11
  TYPE_CHECKING,
6
12
  Any,
7
- Callable,
8
- Mapping,
9
- Optional,
10
- Sequence,
13
+ ClassVar,
14
+ Protocol,
11
15
  TypeVar,
12
16
  Union,
17
+ overload,
13
18
  )
14
19
 
15
- from pkgs.argument_parser import camel_to_snake_case, snake_to_camel_case
20
+ from pkgs.argument_parser import snake_to_camel_case
16
21
  from pkgs.serialization import (
17
22
  MISSING_SENTRY,
18
- MissingSentryType,
19
- MissingType,
20
23
  OpaqueKey,
21
24
  get_serial_class_data,
22
25
  )
23
26
 
24
27
  from ._get_type_for_serialization import SerializationType, get_serialization_type
28
+ from .dataclasses import iterate_fields
25
29
 
26
30
  # Inlined types which otherwise would import from types/base.py
27
31
  JsonScalar = Union[str, float, bool, Decimal, None, datetime.datetime, datetime.date]
@@ -30,53 +34,101 @@ if TYPE_CHECKING:
30
34
  else:
31
35
  JsonValue = Union[JsonScalar, dict[str, Any], list[Any]]
32
36
 
37
+ T = TypeVar("T")
38
+
39
+
40
+ class Dataclass(Protocol):
41
+ __dataclass_fields__: ClassVar[dict] # type: ignore[type-arg,unused-ignore]
42
+
43
+
44
+ def identity(x: T) -> T:
45
+ return x
33
46
 
34
- def key_convert_to_camelcase(o: Any) -> Any:
47
+
48
+ @overload
49
+ def key_convert_to_camelcase(o: str) -> str: ...
50
+
51
+
52
+ @overload
53
+ def key_convert_to_camelcase(o: int) -> int: ...
54
+
55
+
56
+ def key_convert_to_camelcase(o: Any) -> str | int:
35
57
  if isinstance(o, OpaqueKey):
36
58
  return o
37
- if isinstance(o, enum.Enum):
59
+ if isinstance(o, enum.StrEnum):
38
60
  return o.value
39
61
  if isinstance(o, str):
40
62
  return snake_to_camel_case(o)
41
- return o
63
+ if isinstance(o, int):
64
+ # we allow dictionaries to use integer keys
65
+ return o
66
+ raise ValueError("Unexpected key type", o)
42
67
 
43
68
 
44
- def _convert_dict(d: Any) -> Any:
69
+ def _convert_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
45
70
  return {
46
- key_convert_to_camelcase(k): convert_to_camelcase(v)
71
+ key_convert_to_camelcase(k): serialize_for_api(v)
47
72
  for k, v in d.items()
48
73
  if v != MISSING_SENTRY
49
74
  }
50
75
 
51
76
 
52
- def _serialize_dict(d: Any) -> dict[str, Any]:
53
- return {k: serialize(v) for k, v in d.items() if v != MISSING_SENTRY}
77
+ def _serialize_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
78
+ return {k: serialize_for_storage(v) for k, v in d.items() if v != MISSING_SENTRY}
54
79
 
55
80
 
56
- def _convert_dataclass(d: Any) -> Any:
57
- dct = type(d)
58
- scd = get_serial_class_data(dct)
81
+ def _serialize_dataclass(d: Any) -> dict[str, JsonValue]:
82
+ return {
83
+ k: serialize_for_storage(v) for k, v in iterate_fields(d) if v != MISSING_SENTRY
84
+ }
85
+
86
+
87
+ def _to_string_value(value: Any) -> str:
88
+ assert isinstance(value, (Decimal, int))
89
+ return str(value)
90
+
91
+
92
+ @dataclasses.dataclass(kw_only=True)
93
+ class DataclassConversions:
94
+ key_conversions: dict[str, str]
95
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]]
59
96
 
60
- def key_convert(key: Any) -> Any:
97
+
98
+ @functools.lru_cache(maxsize=10000)
99
+ def _get_dataclass_conversion_lookups(dataclass_type: Any) -> DataclassConversions:
100
+ scd = get_serial_class_data(dataclass_type)
101
+
102
+ key_conversions: dict[str, str] = {}
103
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]] = {}
104
+
105
+ for field in dataclasses.fields(dataclass_type):
106
+ key = field.name
61
107
  if scd.has_unconverted_key(key):
62
- return key
63
- return key_convert_to_camelcase(key)
108
+ key_conversions[key] = key
109
+ else:
110
+ key_conversions[key] = key_convert_to_camelcase(key)
64
111
 
65
- def value_convert(key: Any, value: Any) -> Any:
66
- if value is None:
67
- return None
68
112
  if scd.has_to_string_value(key):
69
- # Limit to types we know we need to support to avoid surprises
70
- # Generics, like List/Dict would need to be per-value stringified
71
- assert isinstance(value, (Decimal, int))
72
- return str(value)
73
- if scd.has_unconverted_value(key):
74
- return value
75
- return convert_to_camelcase(value)
113
+ value_conversion_functions[key] = _to_string_value
114
+ elif scd.has_unconverted_value(key):
115
+ value_conversion_functions[key] = identity
116
+ else:
117
+ value_conversion_functions[key] = serialize_for_api
76
118
 
119
+ return DataclassConversions(
120
+ key_conversions=key_conversions,
121
+ value_conversion_functions=value_conversion_functions,
122
+ )
123
+
124
+
125
+ def _convert_dataclass(d: Any) -> dict[str, JsonValue]:
126
+ conversions = _get_dataclass_conversion_lookups(type(d)) # type: ignore[arg-type]
77
127
  return {
78
- key_convert(k): value_convert(k, v)
79
- for k, v in d.__dict__.items()
128
+ conversions.key_conversions[k]: (
129
+ conversions.value_conversion_functions[k](v) if v is not None else None
130
+ )
131
+ for k, v in iterate_fields(d)
80
132
  if v != MISSING_SENTRY
81
133
  }
82
134
 
@@ -85,38 +137,54 @@ _SERIALIZATION_FUNCS_STANDARD = {
85
137
  SerializationType.ENUM: lambda x: str(x.value),
86
138
  SerializationType.DATE: lambda x: x.isoformat(),
87
139
  SerializationType.TIMEDELTA: lambda x: x.total_seconds(),
88
- SerializationType.UNKNOWN: lambda x: x,
140
+ SerializationType.UNKNOWN: identity,
89
141
  }
90
142
 
91
- _CONVERSION_SERIALIZATION_FUNCS = {
143
+ _CONVERSION_SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
92
144
  **_SERIALIZATION_FUNCS_STANDARD,
93
145
  SerializationType.NAMED_TUPLE: lambda x: _convert_dict(x._asdict()),
94
- SerializationType.ITERABLE: lambda x: [convert_to_camelcase(v) for v in x],
146
+ SerializationType.ITERABLE: lambda x: [serialize_for_api(v) for v in x],
95
147
  SerializationType.DICT: _convert_dict,
96
148
  SerializationType.DATACLASS: _convert_dataclass,
97
149
  }
98
150
 
99
151
 
100
- def convert_to_camelcase(obj: Any) -> Any:
101
- """@DEPRECATED prefer serialize_for_api"""
102
- return serialize_for_api(obj)
152
+ @overload
153
+ def serialize_for_api(obj: None) -> None: ...
154
+
103
155
 
156
+ @overload
157
+ def serialize_for_api(obj: dict[str, Any]) -> dict[str, JsonValue]: ...
104
158
 
105
- def serialize_for_api(obj: Any) -> Any:
159
+
160
+ @overload
161
+ def serialize_for_api(obj: Dataclass) -> dict[str, JsonValue]: ...
162
+
163
+
164
+ @overload
165
+ def serialize_for_api(obj: Any) -> JsonValue: ...
166
+
167
+
168
+ def serialize_for_api(obj: Any) -> JsonValue:
106
169
  """
107
170
  Serialize to a parsed-JSON format suitably encoded for API output.
108
171
 
109
172
  Use the CachedParser.parse_api to parse this data.
110
173
  """
111
174
  serialization_type = get_serialization_type(type(obj)) # type: ignore
112
- return _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
175
+ if (
176
+ serialization_type == SerializationType.UNKNOWN
177
+ ): # performance optimization to not do function lookup
178
+ return obj # type: ignore
179
+ r = _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
180
+ return r
113
181
 
114
182
 
115
183
  _SERIALIZATION_FUNCS_DICT: dict[
116
184
  SerializationType, Callable[[Any], dict[str, JsonValue]]
117
185
  ] = {
118
186
  SerializationType.DICT: _serialize_dict,
119
- SerializationType.DATACLASS: lambda x: _serialize_dict(x.__dict__),
187
+ SerializationType.DATACLASS: _serialize_dataclass,
120
188
  }
121
189
 
122
190
 
@@ -124,15 +192,10 @@ _SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
124
192
  **_SERIALIZATION_FUNCS_STANDARD,
125
193
  **_SERIALIZATION_FUNCS_DICT,
126
194
  SerializationType.NAMED_TUPLE: lambda x: _serialize_dict(x._asdict()),
127
- SerializationType.ITERABLE: lambda x: [serialize(v) for v in x],
195
+ SerializationType.ITERABLE: lambda x: [serialize_for_storage(v) for v in x],
128
196
  }
129
197
 
130
198
 
131
- def serialize(obj: Any) -> Any:
132
- """@DEPRECATED: prefer serialize_for_storage"""
133
- return serialize_for_storage(obj)
134
-
135
-
136
199
  def serialize_for_storage(obj: Any) -> JsonValue:
137
200
  """
138
201
  Convert a value into the pseudo-JSON form for
@@ -141,37 +204,16 @@ def serialize_for_storage(obj: Any) -> JsonValue:
141
204
  Use the CachedParser.parse_storage to parse this data.
142
205
  """
143
206
  serialization_type = get_serialization_type(type(obj)) # type: ignore
207
+ if (
208
+ serialization_type == SerializationType.UNKNOWN
209
+ ): # performance optimization to not do function lookup
210
+ return obj # type: ignore
144
211
  return _SERIALIZATION_FUNCS[serialization_type](obj)
145
212
 
146
213
 
147
- def serialize_for_storage_dict(obj: Any) -> dict[str, JsonValue]:
214
+ def serialize_for_storage_dict(obj: dict | Dataclass) -> dict[str, JsonValue]: # type: ignore[type-arg]
148
215
  """
149
216
  Same as serialize for storage but guarantees outer object is a dictionary
150
217
  """
151
- serialization_type = get_serialization_type(type(obj)) # type: ignore
218
+ serialization_type = get_serialization_type(type(obj))
152
219
  return _SERIALIZATION_FUNCS_DICT[serialization_type](obj)
153
-
154
-
155
- def key_convert_to_snake_case(o: Any) -> Any:
156
- if isinstance(o, OpaqueKey):
157
- return o
158
- if isinstance(o, str):
159
- return camel_to_snake_case(o)
160
- return o
161
-
162
-
163
- def convert_dict_to_snake_case(data: Any) -> Any:
164
- return {
165
- key_convert_to_snake_case(k): convert_dict_to_snake_case(v)
166
- if isinstance(v, dict)
167
- else v
168
- for k, v in data.items()
169
- if v != MISSING_SENTRY
170
- }
171
-
172
-
173
- T = TypeVar("T")
174
-
175
-
176
- def resolve_missing_to_none(val: MissingType[T]) -> Optional[T]:
177
- return val if not isinstance(val, MissingSentryType) else None
@@ -1,9 +1 @@
1
- import sys
2
- from enum import Enum
3
-
4
- if sys.version_info >= (3, 11):
5
- from enum import StrEnum as StrEnum
6
- else:
7
-
8
- class StrEnum(str, Enum):
9
- pass
1
+ from enum import StrEnum as StrEnum
File without changes
@@ -0,0 +1,126 @@
1
+ """
2
+ This processes the directory main/unc/materials/shared/actions_registry and emits actions_registry/action_definitions.tsx
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ from collections import defaultdict
8
+ from dataclasses import dataclass
9
+ from typing import TypeVar
10
+
11
+ from main.base.types import actions_registry_t
12
+ from pkgs.type_spec import builder
13
+
14
+ from ...argument_parser import CachedParser
15
+ from ..emit_typescript_util import ts_name
16
+ from ..util import rewrite_file
17
+ from .emit_typescript import ActionDefinitionWithArgument, emit_action_definitions
18
+
19
+ key_name = "name"
20
+ key_icon = "icon"
21
+ key_short_description = "short_description"
22
+ key_description = "description"
23
+
24
+
25
+ TypeT = TypeVar("TypeT")
26
+
27
+
28
+ class InvalidSpecException(Exception):
29
+ pass
30
+
31
+
32
+ @dataclass(kw_only=True)
33
+ class ActionRegistryFileInfo:
34
+ directories: list[str]
35
+ filename: str
36
+ filepath: str
37
+
38
+
39
+ ACTIONS_REGISTRY_ROOT = "main/unc/materials/shared/actions_registry/"
40
+ action_definition_parser = CachedParser(
41
+ dict[str, actions_registry_t.ActionDefinitionYaml]
42
+ )
43
+
44
+
45
+ def get_action_registry_files_info() -> list[ActionRegistryFileInfo]:
46
+ files_info = []
47
+ for dirname, _, files in os.walk(ACTIONS_REGISTRY_ROOT):
48
+ directories = dirname.replace(ACTIONS_REGISTRY_ROOT, "").split("/")
49
+ for filename in files:
50
+ filepath = os.path.join(dirname, filename)
51
+ if os.path.isfile(filepath) and filepath.endswith(".yaml"):
52
+ files_info.append(
53
+ ActionRegistryFileInfo(
54
+ directories=directories,
55
+ filename=filename.replace(".yaml", ""),
56
+ filepath=filepath,
57
+ )
58
+ )
59
+ return files_info
60
+
61
+
62
+ def main() -> None:
63
+ files_info = get_action_registry_files_info()
64
+ action_definitions: defaultdict[str, list[ActionDefinitionWithArgument]] = (
65
+ defaultdict(list)
66
+ )
67
+ all_action_definitions: list[actions_registry_t.ActionDefinitionInternal] = []
68
+ action_definitions_with_arguments_list: list[ActionDefinitionWithArgument] = []
69
+ for file_info in files_info:
70
+ in_action_definitions = action_definition_parser.parse_yaml_file(
71
+ file_info.filepath
72
+ )
73
+ if len(in_action_definitions) == 0:
74
+ continue
75
+ for ref_name, definition in in_action_definitions.items():
76
+ modules = [*file_info.directories]
77
+ # if the actions are stored in index.yaml, parent dir should be treated as module
78
+ if file_info.filename != "index":
79
+ modules.append(file_info.filename)
80
+
81
+ module_str = "_".join(modules)
82
+ action_definition = actions_registry_t.ActionDefinitionInternal(
83
+ name=definition.name,
84
+ short_description=definition.short_description,
85
+ description=definition.description,
86
+ icon=definition.icon,
87
+ ref_name=ref_name,
88
+ module=actions_registry_t.ActionsRegistryModule(
89
+ ts_name(module_str, name_case=builder.NameCase.convert)
90
+ ),
91
+ visibility_scope=[
92
+ actions_registry_t.ActionDefinitionVisibilityScope(item)
93
+ for item in definition.visibility_scope
94
+ ]
95
+ if definition.visibility_scope is not None
96
+ else None,
97
+ )
98
+ action_definitions_with_arguments_list.append(
99
+ ActionDefinitionWithArgument(
100
+ module=module_str,
101
+ ref_name=ref_name,
102
+ arguments=definition.arguments,
103
+ definition=action_definition,
104
+ )
105
+ )
106
+ all_action_definitions.append(action_definition)
107
+ action_definitions_with_arguments_list = sorted(
108
+ action_definitions_with_arguments_list,
109
+ key=lambda item: (item.module, item.ref_name),
110
+ )
111
+
112
+ for action_definition_with_argument in action_definitions_with_arguments_list:
113
+ action_definitions[action_definition_with_argument.module].append(
114
+ action_definition_with_argument
115
+ )
116
+
117
+ ts_content = emit_action_definitions(action_definitions)
118
+ rewrite_file(
119
+ "main/site/js/materials/base/actions_registry/action_definitions.tsx",
120
+ ts_content,
121
+ )
122
+
123
+ sys.exit(0)
124
+
125
+
126
+ main()
@@ -0,0 +1,182 @@
1
+ import io
2
+ from collections import defaultdict
3
+ from dataclasses import dataclass
4
+
5
+ from main.base.types import actions_registry_t
6
+
7
+ from ...type_spec import builder
8
+ from ..emit_typescript_util import (
9
+ INDENT,
10
+ MODIFY_NOTICE,
11
+ resolve_namespace_ref,
12
+ ts_name,
13
+ ts_type_name,
14
+ )
15
+ from ..util import encode_common_string
16
+
17
+
18
+ def _action_symbol_name(
19
+ action_definition: actions_registry_t.ActionDefinitionInternal,
20
+ ) -> str:
21
+ return f"{ts_name(action_definition.ref_name, name_case=builder.NameCase.convert)}"
22
+
23
+
24
+ def _action_type_name(ref_name: str) -> str:
25
+ return f"{ts_type_name(ref_name)}"
26
+
27
+
28
+ def _action_namespace_name(name: str) -> str:
29
+ return f"{resolve_namespace_ref(builder.SpecNamespace(name=name))}"
30
+
31
+
32
+ def _action_module_name_base(module: str) -> str:
33
+ return f"{ts_name(module, name_case=builder.NameCase.convert)}"
34
+
35
+
36
+ def _action_module_name_obj(module: str) -> str:
37
+ return f"{_action_module_name_base(module)}Actions"
38
+
39
+
40
+ def _action_module_name(module: str) -> str:
41
+ return f"ActionsRegistryT.ActionsRegistryModule.{_action_module_name_base(module)}"
42
+
43
+
44
+ @dataclass(kw_only=True)
45
+ class ActionDefinitionWithArgument:
46
+ module: str
47
+ ref_name: str
48
+ arguments: str | None
49
+ definition: actions_registry_t.ActionDefinitionInternal
50
+
51
+
52
+ def _emit_imports(
53
+ action_definitions: defaultdict[str, list[ActionDefinitionWithArgument]],
54
+ ) -> str:
55
+ out = io.StringIO()
56
+ namespaces = set()
57
+ for definitions in action_definitions.values():
58
+ for definition in definitions:
59
+ if definition.arguments is not None:
60
+ argument_parts = definition.arguments.split(".")
61
+ namespace = argument_parts[0]
62
+ namespaces.add(namespace)
63
+ namespaces_to_import = ", ".join(
64
+ sorted([f"type {_action_namespace_name(item)}" for item in list(namespaces)])
65
+ )
66
+ out.write(
67
+ f'import {{ ActionsRegistryT, {namespaces_to_import} }} from "unc_types"\n\n'
68
+ )
69
+ out.write(MODIFY_NOTICE)
70
+ return out.getvalue()
71
+
72
+
73
+ def emit_action_definitions(
74
+ action_definitions: defaultdict[str, list[ActionDefinitionWithArgument]],
75
+ ) -> str:
76
+ out = io.StringIO()
77
+ out.write(MODIFY_NOTICE)
78
+ out.write("\n")
79
+ out.write(_emit_imports(action_definitions))
80
+ modules = []
81
+ for key, values in action_definitions.items():
82
+ out.write(MODIFY_NOTICE)
83
+ modules.append(key)
84
+ out.write(f"export const {_action_module_name_obj(key)} = {{\n")
85
+ for item in values:
86
+ out.write(MODIFY_NOTICE)
87
+ out.write(_emit_action_definition(item.definition, item.arguments, INDENT))
88
+ out.write("}\n")
89
+
90
+ out.write(MODIFY_NOTICE)
91
+ out.write("\n")
92
+ out.write(MODIFY_NOTICE)
93
+ out.write("\n")
94
+ out.write("export const actionDefinitions = {\n")
95
+ for module in modules:
96
+ out.write(
97
+ f"{INDENT}[{_action_module_name(module)}]: {_action_module_name_obj(module)},\n"
98
+ )
99
+
100
+ out.write("}\n")
101
+ out.write(_emit_action_definition_types(modules, indent=""))
102
+ out.write(MODIFY_NOTICE)
103
+ out.write("\n")
104
+
105
+ return out.getvalue()
106
+
107
+
108
+ def _get_argument_type(arguments: str | None) -> str | None:
109
+ if arguments is None:
110
+ return None
111
+ argument_parts = arguments.split(".")
112
+ namespace = argument_parts[0]
113
+ type_name = argument_parts[1]
114
+ return f"{_action_namespace_name(namespace)}.{type_name}"
115
+
116
+
117
+ def _emit_action_definition(
118
+ action_definition: actions_registry_t.ActionDefinitionInternal,
119
+ arguments: str | None,
120
+ indent: str,
121
+ ) -> str:
122
+ out = io.StringIO()
123
+
124
+ sub_indent = indent + INDENT
125
+ argument_type = _get_argument_type(arguments)
126
+ argument_type = f"<{argument_type}>" if argument_type is not None else ""
127
+ out.write(f"{indent}{_action_symbol_name(action_definition)}: {{\n")
128
+ out.write(f"{sub_indent}name: {encode_common_string(action_definition.name)},\n")
129
+ if action_definition.icon is not None:
130
+ out.write(
131
+ f"{sub_indent}icon: {encode_common_string(action_definition.icon)},\n"
132
+ )
133
+ out.write(
134
+ f"{sub_indent}shortDescription: {encode_common_string(action_definition.short_description)},\n"
135
+ )
136
+ out.write(
137
+ f"{sub_indent}description: {encode_common_string(action_definition.description)},\n"
138
+ )
139
+ out.write(
140
+ f"{sub_indent}refName: {encode_common_string(action_definition.ref_name)},\n"
141
+ )
142
+ out.write(f"{sub_indent}module: {_action_module_name(action_definition.module)},\n")
143
+ if (
144
+ action_definition.visibility_scope is not None
145
+ and len(action_definition.visibility_scope) > 0
146
+ ):
147
+ out.write(
148
+ f"{sub_indent}visibilityScope: {_emit_visibility_scope(action_definition.visibility_scope)},\n"
149
+ )
150
+ out.write(f"{indent}}} as ActionsRegistryT.ActionDefinition{argument_type},\n")
151
+ return out.getvalue()
152
+
153
+
154
+ def _emit_action_definition_types(modules: list[str], indent: str) -> str:
155
+ out = io.StringIO()
156
+
157
+ sub_indent = indent + INDENT
158
+ out.write(
159
+ f"{indent}type RefNameKeys<M extends ActionsRegistryT.ActionsRegistryModule> = keyof (typeof actionDefinitions)[M]\n"
160
+ )
161
+ out.write(
162
+ f"{indent}type ActionDefinitionIdentifierGetter<M extends ActionsRegistryT.ActionsRegistryModule> = {{ module: M; refName: RefNameKeys<M> }}\n"
163
+ )
164
+ out.write(f"{indent}export type ActionDefinitionIdentifier =\n")
165
+ for module in modules:
166
+ out.write(
167
+ f"{sub_indent}| ActionDefinitionIdentifierGetter<{_action_module_name(module)}>\n"
168
+ )
169
+ out.write("\n")
170
+ return out.getvalue()
171
+
172
+
173
+ def _emit_visibility_scope(
174
+ visibility_scope: list[actions_registry_t.ActionDefinitionVisibilityScope],
175
+ ) -> str:
176
+ visibility_scope_types = ",".join([
177
+ f"ActionsRegistryT.ActionDefinitionVisibilityScope.{ts_name(visibility_item, name_case=builder.NameCase.convert)}"
178
+ for visibility_item in visibility_scope
179
+ if visibility_item is not None
180
+ ])
181
+
182
+ return f"[ {visibility_scope_types} ]"