UncountablePythonSDK 0.0.7__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 (311) 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.7.dist-info → UncountablePythonSDK-0.0.92.dist-info}/WHEEL +1 -1
  4. {UncountablePythonSDK-0.0.7.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/opaque_key.py +1 -1
  49. pkgs/serialization/serial_alias.py +47 -0
  50. pkgs/serialization/serial_class.py +65 -50
  51. pkgs/serialization/serial_generic.py +16 -0
  52. pkgs/serialization/serial_union.py +84 -0
  53. pkgs/serialization/yaml.py +57 -0
  54. pkgs/serialization_util/__init__.py +7 -7
  55. pkgs/serialization_util/_get_type_for_serialization.py +1 -3
  56. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  57. pkgs/serialization_util/dataclasses.py +14 -0
  58. pkgs/serialization_util/serialization_helpers.py +118 -73
  59. pkgs/strenum_compat/strenum_compat.py +1 -9
  60. pkgs/type_spec/actions_registry/__init__.py +0 -0
  61. pkgs/type_spec/actions_registry/__main__.py +126 -0
  62. pkgs/type_spec/actions_registry/emit_typescript.py +182 -0
  63. pkgs/type_spec/builder.py +475 -89
  64. pkgs/type_spec/config.py +24 -19
  65. pkgs/type_spec/emit_io_ts.py +5 -2
  66. pkgs/type_spec/emit_open_api.py +266 -32
  67. pkgs/type_spec/emit_open_api_util.py +32 -13
  68. pkgs/type_spec/emit_python.py +601 -150
  69. pkgs/type_spec/emit_typescript.py +74 -273
  70. pkgs/type_spec/emit_typescript_util.py +239 -5
  71. pkgs/type_spec/load_types.py +55 -10
  72. pkgs/type_spec/open_api_util.py +30 -41
  73. pkgs/type_spec/parts/base.py.prepart +4 -3
  74. pkgs/type_spec/type_info/emit_type_info.py +178 -16
  75. pkgs/type_spec/util.py +11 -11
  76. pkgs/type_spec/value_spec/__main__.py +3 -3
  77. pkgs/type_spec/value_spec/convert_type.py +8 -1
  78. pkgs/type_spec/value_spec/emit_python.py +13 -4
  79. uncountable/__init__.py +1 -2
  80. uncountable/core/__init__.py +12 -2
  81. uncountable/core/async_batch.py +37 -0
  82. uncountable/core/client.py +293 -43
  83. uncountable/core/environment.py +41 -0
  84. uncountable/core/file_upload.py +135 -0
  85. uncountable/core/types.py +17 -0
  86. uncountable/integration/__init__.py +0 -0
  87. uncountable/integration/cli.py +49 -0
  88. uncountable/integration/construct_client.py +51 -0
  89. uncountable/integration/cron.py +29 -0
  90. uncountable/integration/db/__init__.py +0 -0
  91. uncountable/integration/db/connect.py +18 -0
  92. uncountable/integration/db/session.py +25 -0
  93. uncountable/integration/entrypoint.py +13 -0
  94. uncountable/integration/executors/__init__.py +0 -0
  95. uncountable/integration/executors/executors.py +148 -0
  96. uncountable/integration/executors/generic_upload_executor.py +284 -0
  97. uncountable/integration/executors/script_executor.py +25 -0
  98. uncountable/integration/job.py +87 -0
  99. uncountable/integration/queue_runner/__init__.py +0 -0
  100. uncountable/integration/queue_runner/command_server/__init__.py +24 -0
  101. uncountable/integration/queue_runner/command_server/command_client.py +68 -0
  102. uncountable/integration/queue_runner/command_server/command_server.py +64 -0
  103. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  104. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
  105. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
  108. uncountable/integration/queue_runner/command_server/types.py +52 -0
  109. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  110. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
  111. uncountable/integration/queue_runner/datastore/interface.py +19 -0
  112. uncountable/integration/queue_runner/datastore/model.py +17 -0
  113. uncountable/integration/queue_runner/job_scheduler.py +163 -0
  114. uncountable/integration/queue_runner/queue_runner.py +26 -0
  115. uncountable/integration/queue_runner/types.py +7 -0
  116. uncountable/integration/queue_runner/worker.py +119 -0
  117. uncountable/integration/scan_profiles.py +67 -0
  118. uncountable/integration/scheduler.py +150 -0
  119. uncountable/integration/secret_retrieval/__init__.py +3 -0
  120. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  121. uncountable/integration/server.py +117 -0
  122. uncountable/integration/telemetry.py +209 -0
  123. uncountable/integration/webhook_server/entrypoint.py +170 -0
  124. uncountable/types/__init__.py +151 -5
  125. uncountable/types/api/batch/execute_batch.py +15 -7
  126. uncountable/types/api/batch/execute_batch_load_async.py +42 -0
  127. uncountable/types/api/chemical/__init__.py +1 -0
  128. uncountable/types/api/chemical/convert_chemical_formats.py +63 -0
  129. uncountable/types/api/entity/create_entities.py +23 -10
  130. uncountable/types/api/entity/create_entity.py +21 -12
  131. uncountable/types/api/entity/get_entities_data.py +19 -29
  132. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  133. uncountable/types/api/entity/list_entities.py +28 -20
  134. uncountable/types/api/entity/lock_entity.py +45 -0
  135. uncountable/types/api/entity/resolve_entity_ids.py +19 -7
  136. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  137. uncountable/types/api/entity/set_values.py +13 -28
  138. uncountable/types/api/entity/transition_entity_phase.py +80 -0
  139. uncountable/types/api/entity/unlock_entity.py +44 -0
  140. uncountable/types/api/equipment/__init__.py +1 -0
  141. uncountable/types/api/equipment/associate_equipment_input.py +44 -0
  142. uncountable/types/api/field_options/__init__.py +1 -0
  143. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  144. uncountable/types/api/files/__init__.py +1 -0
  145. uncountable/types/api/files/download_file.py +77 -0
  146. uncountable/types/api/id_source/__init__.py +1 -0
  147. uncountable/types/api/id_source/list_id_source.py +56 -0
  148. uncountable/types/api/id_source/match_id_source.py +54 -0
  149. uncountable/types/api/input_groups/get_input_group_names.py +18 -7
  150. uncountable/types/api/inputs/create_inputs.py +25 -24
  151. uncountable/types/api/inputs/get_input_data.py +37 -31
  152. uncountable/types/api/inputs/get_input_names.py +20 -9
  153. uncountable/types/api/inputs/get_inputs_data.py +33 -27
  154. uncountable/types/api/inputs/set_input_attribute_values.py +18 -13
  155. uncountable/types/api/inputs/set_input_category.py +44 -0
  156. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  157. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  158. uncountable/types/api/material_families/__init__.py +1 -0
  159. uncountable/types/api/material_families/update_entity_material_families.py +48 -0
  160. uncountable/types/api/outputs/get_output_data.py +38 -29
  161. uncountable/types/api/outputs/get_output_names.py +20 -9
  162. uncountable/types/api/outputs/resolve_output_conditions.py +23 -10
  163. uncountable/types/api/permissions/__init__.py +1 -0
  164. uncountable/types/api/permissions/set_core_permissions.py +105 -0
  165. uncountable/types/api/project/get_projects.py +23 -19
  166. uncountable/types/api/project/get_projects_data.py +26 -43
  167. uncountable/types/api/recipe_links/__init__.py +1 -0
  168. uncountable/types/api/recipe_links/create_recipe_link.py +46 -0
  169. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  170. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +21 -10
  171. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  172. uncountable/types/api/recipes/archive_recipes.py +42 -0
  173. uncountable/types/api/recipes/associate_recipe_as_input.py +44 -0
  174. uncountable/types/api/recipes/associate_recipe_as_lot.py +43 -0
  175. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  176. uncountable/types/api/recipes/create_recipe.py +51 -0
  177. uncountable/types/api/recipes/create_recipes.py +25 -24
  178. uncountable/types/api/recipes/disassociate_recipe_as_input.py +42 -0
  179. uncountable/types/api/recipes/edit_recipe_inputs.py +283 -0
  180. uncountable/types/api/recipes/get_column_calculation_values.py +58 -0
  181. uncountable/types/api/recipes/get_curve.py +13 -27
  182. uncountable/types/api/recipes/get_recipe_calculations.py +21 -21
  183. uncountable/types/api/recipes/get_recipe_links.py +14 -6
  184. uncountable/types/api/recipes/get_recipe_names.py +18 -7
  185. uncountable/types/api/recipes/get_recipe_output_metadata.py +18 -19
  186. uncountable/types/api/recipes/get_recipes_data.py +83 -144
  187. uncountable/types/api/recipes/lock_recipes.py +63 -0
  188. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  189. uncountable/types/api/recipes/set_recipe_inputs.py +21 -11
  190. uncountable/types/api/recipes/set_recipe_metadata.py +43 -0
  191. uncountable/types/api/recipes/set_recipe_output_annotations.py +115 -0
  192. uncountable/types/api/recipes/set_recipe_output_file.py +56 -0
  193. uncountable/types/api/recipes/set_recipe_outputs.py +28 -15
  194. uncountable/types/api/recipes/set_recipe_tags.py +109 -0
  195. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  196. uncountable/types/api/recipes/unlock_recipes.py +50 -0
  197. uncountable/types/api/triggers/__init__.py +1 -0
  198. uncountable/types/api/triggers/run_trigger.py +43 -0
  199. uncountable/types/api/uploader/__init__.py +1 -0
  200. uncountable/types/api/uploader/invoke_uploader.py +47 -0
  201. uncountable/types/async_batch.py +13 -0
  202. uncountable/types/async_batch_processor.py +384 -0
  203. uncountable/types/async_batch_t.py +97 -0
  204. uncountable/types/async_jobs.py +9 -0
  205. uncountable/types/async_jobs_t.py +53 -0
  206. uncountable/types/auth_retrieval.py +12 -0
  207. uncountable/types/auth_retrieval_t.py +75 -0
  208. uncountable/types/base.py +5 -78
  209. uncountable/types/base_t.py +85 -0
  210. uncountable/types/calculations.py +8 -0
  211. uncountable/types/calculations_t.py +27 -0
  212. uncountable/types/chemical_structure.py +8 -0
  213. uncountable/types/chemical_structure_t.py +28 -0
  214. uncountable/types/client_base.py +1115 -76
  215. uncountable/types/client_config.py +8 -0
  216. uncountable/types/client_config_t.py +26 -0
  217. uncountable/types/curves.py +10 -0
  218. uncountable/types/curves_t.py +51 -0
  219. uncountable/types/entity.py +8 -266
  220. uncountable/types/entity_t.py +393 -0
  221. uncountable/types/experiment_groups.py +8 -0
  222. uncountable/types/experiment_groups_t.py +27 -0
  223. uncountable/types/field_values.py +17 -23
  224. uncountable/types/field_values_t.py +204 -0
  225. uncountable/types/fields.py +8 -0
  226. uncountable/types/fields_t.py +28 -0
  227. uncountable/types/generic_upload.py +15 -0
  228. uncountable/types/generic_upload_t.py +119 -0
  229. uncountable/types/id_source.py +12 -0
  230. uncountable/types/id_source_t.py +68 -0
  231. uncountable/types/identifier.py +11 -0
  232. uncountable/types/identifier_t.py +63 -0
  233. uncountable/types/input_attributes.py +8 -0
  234. uncountable/types/input_attributes_t.py +30 -0
  235. uncountable/types/inputs.py +11 -0
  236. uncountable/types/inputs_t.py +83 -0
  237. uncountable/types/integration_server.py +9 -0
  238. uncountable/types/integration_server_t.py +42 -0
  239. uncountable/types/job_definition.py +27 -0
  240. uncountable/types/job_definition_t.py +260 -0
  241. uncountable/types/outputs.py +8 -0
  242. uncountable/types/outputs_t.py +30 -0
  243. uncountable/types/overrides.py +10 -0
  244. uncountable/types/overrides_t.py +49 -0
  245. uncountable/types/permissions.py +8 -0
  246. uncountable/types/permissions_t.py +46 -0
  247. uncountable/types/phases.py +8 -0
  248. uncountable/types/phases_t.py +27 -0
  249. uncountable/types/post_base.py +8 -0
  250. uncountable/types/post_base_t.py +30 -0
  251. uncountable/types/queued_job.py +16 -0
  252. uncountable/types/queued_job_t.py +123 -0
  253. uncountable/types/recipe_identifiers.py +12 -0
  254. uncountable/types/recipe_identifiers_t.py +76 -0
  255. uncountable/types/recipe_inputs.py +9 -0
  256. uncountable/types/recipe_inputs_t.py +30 -0
  257. uncountable/types/recipe_links.py +4 -44
  258. uncountable/types/recipe_links_t.py +54 -0
  259. uncountable/types/recipe_metadata.py +10 -0
  260. uncountable/types/recipe_metadata_t.py +58 -0
  261. uncountable/types/recipe_output_metadata.py +8 -0
  262. uncountable/types/recipe_output_metadata_t.py +28 -0
  263. uncountable/types/recipe_tags.py +8 -0
  264. uncountable/types/recipe_tags_t.py +27 -0
  265. uncountable/types/recipe_workflow_steps.py +14 -0
  266. uncountable/types/recipe_workflow_steps_t.py +95 -0
  267. uncountable/types/recipes.py +8 -0
  268. uncountable/types/recipes_t.py +25 -0
  269. uncountable/types/response.py +8 -0
  270. uncountable/types/response_t.py +26 -0
  271. uncountable/types/secret_retrieval.py +12 -0
  272. uncountable/types/secret_retrieval_t.py +75 -0
  273. uncountable/types/units.py +8 -0
  274. uncountable/types/units_t.py +27 -0
  275. uncountable/types/users.py +8 -0
  276. uncountable/types/users_t.py +28 -0
  277. uncountable/types/webhook_job.py +9 -0
  278. uncountable/types/webhook_job_t.py +37 -0
  279. uncountable/types/workflows.py +9 -0
  280. uncountable/types/workflows_t.py +39 -0
  281. UncountablePythonSDK-0.0.7.dist-info/METADATA +0 -27
  282. UncountablePythonSDK-0.0.7.dist-info/RECORD +0 -119
  283. examples/recipe-import/importer.py +0 -39
  284. type_spec/external/api/batch/execute_batch.yaml +0 -56
  285. type_spec/external/api/entity/create_entities.yaml +0 -33
  286. type_spec/external/api/entity/create_entity.yaml +0 -39
  287. type_spec/external/api/entity/get_entities_data.yaml +0 -55
  288. type_spec/external/api/entity/list_entities.yaml +0 -62
  289. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  290. type_spec/external/api/entity/set_values.yaml +0 -45
  291. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  292. type_spec/external/api/inputs/create_inputs.yaml +0 -61
  293. type_spec/external/api/inputs/get_input_data.yaml +0 -108
  294. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  295. type_spec/external/api/inputs/get_inputs_data.yaml +0 -95
  296. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -37
  297. type_spec/external/api/outputs/get_output_data.yaml +0 -103
  298. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  299. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  300. type_spec/external/api/project/get_projects.yaml +0 -52
  301. type_spec/external/api/project/get_projects_data.yaml +0 -86
  302. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  303. type_spec/external/api/recipes/create_recipes.yaml +0 -60
  304. type_spec/external/api/recipes/get_curve.yaml +0 -50
  305. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -49
  306. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  307. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  308. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -49
  309. type_spec/external/api/recipes/get_recipes_data.yaml +0 -372
  310. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -36
  311. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -56
@@ -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,26 +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
6
+ import enum
7
+ import functools
8
+ from collections.abc import Callable, Mapping, Sequence
2
9
  from decimal import Decimal
3
10
  from typing import (
4
11
  TYPE_CHECKING,
5
12
  Any,
6
- Callable,
7
- Mapping,
8
- Optional,
9
- Sequence,
13
+ ClassVar,
14
+ Protocol,
10
15
  TypeVar,
11
16
  Union,
17
+ overload,
12
18
  )
13
19
 
14
- from pkgs.argument_parser import camel_to_snake_case, snake_to_camel_case
20
+ from pkgs.argument_parser import snake_to_camel_case
15
21
  from pkgs.serialization import (
16
22
  MISSING_SENTRY,
17
- MissingSentryType,
18
- MissingType,
19
23
  OpaqueKey,
20
24
  get_serial_class_data,
21
25
  )
22
26
 
23
27
  from ._get_type_for_serialization import SerializationType, get_serialization_type
28
+ from .dataclasses import iterate_fields
24
29
 
25
30
  # Inlined types which otherwise would import from types/base.py
26
31
  JsonScalar = Union[str, float, bool, Decimal, None, datetime.datetime, datetime.date]
@@ -29,51 +34,101 @@ if TYPE_CHECKING:
29
34
  else:
30
35
  JsonValue = Union[JsonScalar, dict[str, Any], list[Any]]
31
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
32
46
 
33
- 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:
34
57
  if isinstance(o, OpaqueKey):
35
58
  return o
59
+ if isinstance(o, enum.StrEnum):
60
+ return o.value
36
61
  if isinstance(o, str):
37
62
  return snake_to_camel_case(o)
38
- 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)
39
67
 
40
68
 
41
- def _convert_dict(d: Any) -> Any:
69
+ def _convert_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
42
70
  return {
43
- key_convert_to_camelcase(k): convert_to_camelcase(v)
71
+ key_convert_to_camelcase(k): serialize_for_api(v)
44
72
  for k, v in d.items()
45
73
  if v != MISSING_SENTRY
46
74
  }
47
75
 
48
76
 
49
- def _serialize_dict(d: Any) -> dict[str, Any]:
50
- 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}
51
79
 
52
80
 
53
- def _convert_dataclass(d: Any) -> Any:
54
- dct = type(d)
55
- 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]]
56
96
 
57
- 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
58
107
  if scd.has_unconverted_key(key):
59
- return key
60
- return key_convert_to_camelcase(key)
108
+ key_conversions[key] = key
109
+ else:
110
+ key_conversions[key] = key_convert_to_camelcase(key)
61
111
 
62
- def value_convert(key: Any, value: Any) -> Any:
63
- if value is None:
64
- return None
65
112
  if scd.has_to_string_value(key):
66
- # Limit to types we know we need to support to avoid surprises
67
- # Generics, like List/Dict would need to be per-value stringified
68
- assert isinstance(value, (Decimal, int))
69
- return str(value)
70
- if scd.has_unconverted_value(key):
71
- return value
72
- 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
73
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]
74
127
  return {
75
- key_convert(k): value_convert(k, v)
76
- 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)
77
132
  if v != MISSING_SENTRY
78
133
  }
79
134
 
@@ -82,38 +137,54 @@ _SERIALIZATION_FUNCS_STANDARD = {
82
137
  SerializationType.ENUM: lambda x: str(x.value),
83
138
  SerializationType.DATE: lambda x: x.isoformat(),
84
139
  SerializationType.TIMEDELTA: lambda x: x.total_seconds(),
85
- SerializationType.UNKNOWN: lambda x: x,
140
+ SerializationType.UNKNOWN: identity,
86
141
  }
87
142
 
88
- _CONVERSION_SERIALIZATION_FUNCS = {
143
+ _CONVERSION_SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
89
144
  **_SERIALIZATION_FUNCS_STANDARD,
90
145
  SerializationType.NAMED_TUPLE: lambda x: _convert_dict(x._asdict()),
91
- 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],
92
147
  SerializationType.DICT: _convert_dict,
93
148
  SerializationType.DATACLASS: _convert_dataclass,
94
149
  }
95
150
 
96
151
 
97
- def convert_to_camelcase(obj: Any) -> Any:
98
- """@DEPRECATED prefer serialize_for_api"""
99
- return serialize_for_api(obj)
152
+ @overload
153
+ def serialize_for_api(obj: None) -> None: ...
154
+
100
155
 
156
+ @overload
157
+ def serialize_for_api(obj: dict[str, Any]) -> dict[str, JsonValue]: ...
101
158
 
102
- 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:
103
169
  """
104
170
  Serialize to a parsed-JSON format suitably encoded for API output.
105
171
 
106
172
  Use the CachedParser.parse_api to parse this data.
107
173
  """
108
174
  serialization_type = get_serialization_type(type(obj)) # type: ignore
109
- 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
110
181
 
111
182
 
112
183
  _SERIALIZATION_FUNCS_DICT: dict[
113
184
  SerializationType, Callable[[Any], dict[str, JsonValue]]
114
185
  ] = {
115
186
  SerializationType.DICT: _serialize_dict,
116
- SerializationType.DATACLASS: lambda x: _serialize_dict(x.__dict__),
187
+ SerializationType.DATACLASS: _serialize_dataclass,
117
188
  }
118
189
 
119
190
 
@@ -121,15 +192,10 @@ _SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
121
192
  **_SERIALIZATION_FUNCS_STANDARD,
122
193
  **_SERIALIZATION_FUNCS_DICT,
123
194
  SerializationType.NAMED_TUPLE: lambda x: _serialize_dict(x._asdict()),
124
- SerializationType.ITERABLE: lambda x: [serialize(v) for v in x],
195
+ SerializationType.ITERABLE: lambda x: [serialize_for_storage(v) for v in x],
125
196
  }
126
197
 
127
198
 
128
- def serialize(obj: Any) -> Any:
129
- """@DEPRECATED: prefer serialize_for_storage"""
130
- return serialize_for_storage(obj)
131
-
132
-
133
199
  def serialize_for_storage(obj: Any) -> JsonValue:
134
200
  """
135
201
  Convert a value into the pseudo-JSON form for
@@ -138,37 +204,16 @@ def serialize_for_storage(obj: Any) -> JsonValue:
138
204
  Use the CachedParser.parse_storage to parse this data.
139
205
  """
140
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
141
211
  return _SERIALIZATION_FUNCS[serialization_type](obj)
142
212
 
143
213
 
144
- 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]
145
215
  """
146
216
  Same as serialize for storage but guarantees outer object is a dictionary
147
217
  """
148
- serialization_type = get_serialization_type(type(obj)) # type: ignore
218
+ serialization_type = get_serialization_type(type(obj))
149
219
  return _SERIALIZATION_FUNCS_DICT[serialization_type](obj)
150
-
151
-
152
- def key_convert_to_snake_case(o: Any) -> Any:
153
- if isinstance(o, OpaqueKey):
154
- return o
155
- if isinstance(o, str):
156
- return camel_to_snake_case(o)
157
- return o
158
-
159
-
160
- def convert_dict_to_snake_case(data: Any) -> Any:
161
- return {
162
- key_convert_to_snake_case(k): convert_dict_to_snake_case(v)
163
- if isinstance(v, dict)
164
- else v
165
- for k, v in data.items()
166
- if v != MISSING_SENTRY
167
- }
168
-
169
-
170
- T = TypeVar("T")
171
-
172
-
173
- def resolve_missing_to_none(val: MissingType[T]) -> Optional[T]:
174
- 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} ]"