UncountablePythonSDK 0.0.52__py3-none-any.whl → 0.0.131__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 (316) hide show
  1. docs/conf.py +54 -7
  2. docs/index.md +107 -4
  3. docs/integration_examples/create_ingredient.md +43 -0
  4. docs/integration_examples/create_output.md +56 -0
  5. docs/integration_examples/index.md +6 -0
  6. docs/justfile +2 -2
  7. docs/requirements.txt +6 -4
  8. examples/async_batch.py +3 -3
  9. examples/basic_auth.py +7 -0
  10. examples/create_entity.py +3 -1
  11. examples/create_ingredient_sdk.py +34 -0
  12. examples/download_files.py +26 -0
  13. examples/edit_recipe_inputs.py +4 -2
  14. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  15. examples/integration-server/jobs/materials_auto/example_cron.py +21 -0
  16. examples/integration-server/jobs/materials_auto/example_http.py +47 -0
  17. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  18. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  19. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  20. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
  21. examples/integration-server/jobs/materials_auto/example_wh.py +23 -0
  22. examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
  23. examples/integration-server/pyproject.toml +224 -0
  24. examples/invoke_uploader.py +4 -1
  25. examples/oauth.py +7 -0
  26. examples/set_recipe_metadata_file.py +40 -0
  27. examples/set_recipe_output_file_sdk.py +26 -0
  28. examples/upload_files.py +1 -2
  29. pkgs/argument_parser/__init__.py +9 -0
  30. pkgs/argument_parser/_is_namedtuple.py +3 -0
  31. pkgs/argument_parser/argument_parser.py +217 -70
  32. pkgs/filesystem_utils/__init__.py +1 -0
  33. pkgs/filesystem_utils/_blob_session.py +144 -0
  34. pkgs/filesystem_utils/_gdrive_session.py +10 -7
  35. pkgs/filesystem_utils/_s3_session.py +15 -13
  36. pkgs/filesystem_utils/_sftp_session.py +11 -7
  37. pkgs/filesystem_utils/file_type_utils.py +30 -10
  38. pkgs/py.typed +0 -0
  39. pkgs/serialization/__init__.py +7 -2
  40. pkgs/serialization/annotation.py +64 -0
  41. pkgs/serialization/missing_sentry.py +1 -1
  42. pkgs/serialization/opaque_key.py +1 -1
  43. pkgs/serialization/serial_alias.py +47 -0
  44. pkgs/serialization/serial_class.py +47 -26
  45. pkgs/serialization/serial_generic.py +16 -0
  46. pkgs/serialization/serial_union.py +17 -14
  47. pkgs/serialization/yaml.py +4 -1
  48. pkgs/serialization_util/__init__.py +6 -0
  49. pkgs/serialization_util/dataclasses.py +14 -0
  50. pkgs/serialization_util/serialization_helpers.py +15 -5
  51. pkgs/type_spec/actions_registry/__main__.py +0 -4
  52. pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
  53. pkgs/type_spec/builder.py +354 -119
  54. pkgs/type_spec/builder_types.py +9 -0
  55. pkgs/type_spec/config.py +51 -11
  56. pkgs/type_spec/cross_output_links.py +99 -0
  57. pkgs/type_spec/emit_io_ts.py +1 -1
  58. pkgs/type_spec/emit_open_api.py +127 -36
  59. pkgs/type_spec/emit_open_api_util.py +5 -6
  60. pkgs/type_spec/emit_python.py +329 -121
  61. pkgs/type_spec/emit_typescript.py +117 -256
  62. pkgs/type_spec/emit_typescript_util.py +291 -2
  63. pkgs/type_spec/load_types.py +18 -4
  64. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  65. pkgs/type_spec/open_api_util.py +29 -4
  66. pkgs/type_spec/parts/base.py.prepart +13 -10
  67. pkgs/type_spec/parts/base.ts.prepart +4 -0
  68. pkgs/type_spec/type_info/__main__.py +3 -1
  69. pkgs/type_spec/type_info/emit_type_info.py +124 -29
  70. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  71. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  72. pkgs/type_spec/util.py +4 -4
  73. pkgs/type_spec/value_spec/__main__.py +26 -9
  74. pkgs/type_spec/value_spec/convert_type.py +21 -1
  75. pkgs/type_spec/value_spec/emit_python.py +25 -7
  76. pkgs/type_spec/value_spec/types.py +1 -1
  77. uncountable/core/async_batch.py +1 -1
  78. uncountable/core/client.py +142 -39
  79. uncountable/core/environment.py +41 -0
  80. uncountable/core/file_upload.py +52 -18
  81. uncountable/integration/cli.py +142 -0
  82. uncountable/integration/construct_client.py +8 -8
  83. uncountable/integration/cron.py +11 -37
  84. uncountable/integration/db/connect.py +12 -2
  85. uncountable/integration/db/session.py +25 -0
  86. uncountable/integration/entrypoint.py +8 -37
  87. uncountable/integration/executors/executors.py +125 -2
  88. uncountable/integration/executors/generic_upload_executor.py +87 -29
  89. uncountable/integration/executors/script_executor.py +3 -3
  90. uncountable/integration/http_server/__init__.py +5 -0
  91. uncountable/integration/http_server/types.py +69 -0
  92. uncountable/integration/job.py +242 -12
  93. uncountable/integration/queue_runner/__init__.py +0 -0
  94. uncountable/integration/queue_runner/command_server/__init__.py +28 -0
  95. uncountable/integration/queue_runner/command_server/command_client.py +133 -0
  96. uncountable/integration/queue_runner/command_server/command_server.py +142 -0
  97. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  98. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  99. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
  100. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
  101. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
  102. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
  103. uncountable/integration/queue_runner/command_server/types.py +75 -0
  104. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  105. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
  106. uncountable/integration/queue_runner/datastore/interface.py +29 -0
  107. uncountable/integration/queue_runner/datastore/model.py +24 -0
  108. uncountable/integration/queue_runner/job_scheduler.py +200 -0
  109. uncountable/integration/queue_runner/queue_runner.py +34 -0
  110. uncountable/integration/queue_runner/types.py +7 -0
  111. uncountable/integration/queue_runner/worker.py +116 -0
  112. uncountable/integration/scan_profiles.py +67 -0
  113. uncountable/integration/scheduler.py +199 -0
  114. uncountable/integration/secret_retrieval/retrieve_secret.py +26 -4
  115. uncountable/integration/server.py +94 -69
  116. uncountable/integration/telemetry.py +150 -34
  117. uncountable/integration/webhook_server/entrypoint.py +97 -0
  118. uncountable/types/__init__.py +78 -1
  119. uncountable/types/api/batch/execute_batch.py +13 -6
  120. uncountable/types/api/batch/execute_batch_load_async.py +9 -3
  121. uncountable/types/api/chemical/convert_chemical_formats.py +17 -5
  122. uncountable/types/api/condition_parameters/__init__.py +1 -0
  123. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  124. uncountable/types/api/entity/create_entities.py +19 -7
  125. uncountable/types/api/entity/create_entity.py +17 -8
  126. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  127. uncountable/types/api/entity/export_entities.py +59 -0
  128. uncountable/types/api/entity/get_entities_data.py +13 -4
  129. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  130. uncountable/types/api/entity/list_aggregate.py +79 -0
  131. uncountable/types/api/entity/list_entities.py +42 -10
  132. uncountable/types/api/entity/lock_entity.py +11 -4
  133. uncountable/types/api/entity/lookup_entity.py +116 -0
  134. uncountable/types/api/entity/resolve_entity_ids.py +15 -6
  135. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  136. uncountable/types/api/entity/set_values.py +10 -3
  137. uncountable/types/api/entity/transition_entity_phase.py +22 -7
  138. uncountable/types/api/entity/unlock_entity.py +10 -3
  139. uncountable/types/api/equipment/associate_equipment_input.py +9 -3
  140. uncountable/types/api/field_options/upsert_field_options.py +17 -7
  141. uncountable/types/api/files/__init__.py +1 -0
  142. uncountable/types/api/files/download_file.py +77 -0
  143. uncountable/types/api/id_source/list_id_source.py +16 -7
  144. uncountable/types/api/id_source/match_id_source.py +14 -5
  145. uncountable/types/api/input_groups/get_input_group_names.py +13 -4
  146. uncountable/types/api/inputs/create_inputs.py +23 -9
  147. uncountable/types/api/inputs/get_input_data.py +30 -12
  148. uncountable/types/api/inputs/get_input_names.py +16 -7
  149. uncountable/types/api/inputs/get_inputs_data.py +25 -7
  150. uncountable/types/api/inputs/set_input_attribute_values.py +12 -6
  151. uncountable/types/api/inputs/set_input_category.py +12 -5
  152. uncountable/types/api/inputs/set_input_subcategories.py +10 -3
  153. uncountable/types/api/inputs/set_intermediate_type.py +11 -4
  154. uncountable/types/api/integrations/__init__.py +1 -0
  155. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  156. uncountable/types/api/integrations/push_notification.py +49 -0
  157. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  158. uncountable/types/api/listing/__init__.py +1 -0
  159. uncountable/types/api/listing/fetch_listing.py +58 -0
  160. uncountable/types/api/material_families/update_entity_material_families.py +10 -4
  161. uncountable/types/api/notebooks/__init__.py +1 -0
  162. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  163. uncountable/types/api/outputs/get_output_data.py +28 -13
  164. uncountable/types/api/outputs/get_output_names.py +15 -6
  165. uncountable/types/api/outputs/get_output_organization.py +173 -0
  166. uncountable/types/api/outputs/resolve_output_conditions.py +20 -8
  167. uncountable/types/api/permissions/set_core_permissions.py +26 -10
  168. uncountable/types/api/project/get_projects.py +16 -7
  169. uncountable/types/api/project/get_projects_data.py +17 -8
  170. uncountable/types/api/recipe_links/create_recipe_link.py +12 -5
  171. uncountable/types/api/recipe_links/remove_recipe_link.py +11 -4
  172. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +16 -7
  173. uncountable/types/api/recipes/add_recipe_to_project.py +10 -3
  174. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  175. uncountable/types/api/recipes/archive_recipes.py +11 -4
  176. uncountable/types/api/recipes/associate_recipe_as_input.py +12 -5
  177. uncountable/types/api/recipes/associate_recipe_as_lot.py +10 -3
  178. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  179. uncountable/types/api/recipes/create_mix_order.py +44 -0
  180. uncountable/types/api/recipes/create_recipe.py +15 -9
  181. uncountable/types/api/recipes/create_recipes.py +21 -9
  182. uncountable/types/api/recipes/disassociate_recipe_as_input.py +10 -3
  183. uncountable/types/api/recipes/edit_recipe_inputs.py +134 -22
  184. uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
  185. uncountable/types/api/recipes/get_curve.py +11 -5
  186. uncountable/types/api/recipes/get_recipe_calculations.py +13 -7
  187. uncountable/types/api/recipes/get_recipe_links.py +10 -4
  188. uncountable/types/api/recipes/get_recipe_names.py +13 -4
  189. uncountable/types/api/recipes/get_recipe_output_metadata.py +12 -6
  190. uncountable/types/api/recipes/get_recipes_data.py +87 -33
  191. uncountable/types/api/recipes/lock_recipes.py +19 -8
  192. uncountable/types/api/recipes/remove_recipe_from_project.py +10 -3
  193. uncountable/types/api/recipes/set_recipe_inputs.py +16 -10
  194. uncountable/types/api/recipes/set_recipe_metadata.py +10 -3
  195. uncountable/types/api/recipes/set_recipe_output_annotations.py +24 -12
  196. uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
  197. uncountable/types/api/recipes/set_recipe_outputs.py +35 -12
  198. uncountable/types/api/recipes/set_recipe_tags.py +26 -9
  199. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  200. uncountable/types/api/recipes/unarchive_recipes.py +10 -3
  201. uncountable/types/api/recipes/unlock_recipes.py +14 -6
  202. uncountable/types/api/runsheet/__init__.py +1 -0
  203. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  204. uncountable/types/api/triggers/run_trigger.py +11 -4
  205. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  206. uncountable/types/api/uploader/invoke_uploader.py +13 -6
  207. uncountable/types/api/user/__init__.py +1 -0
  208. uncountable/types/api/user/get_current_user_info.py +40 -0
  209. uncountable/types/async_batch.py +2 -1
  210. uncountable/types/async_batch_processor.py +618 -18
  211. uncountable/types/async_batch_t.py +54 -7
  212. uncountable/types/async_jobs.py +8 -0
  213. uncountable/types/async_jobs_t.py +52 -0
  214. uncountable/types/auth_retrieval.py +11 -0
  215. uncountable/types/auth_retrieval_t.py +75 -0
  216. uncountable/types/base.py +0 -1
  217. uncountable/types/base_t.py +13 -11
  218. uncountable/types/calculations.py +0 -1
  219. uncountable/types/calculations_t.py +5 -2
  220. uncountable/types/chemical_structure.py +0 -1
  221. uncountable/types/chemical_structure_t.py +6 -5
  222. uncountable/types/client_base.py +751 -70
  223. uncountable/types/client_config.py +1 -1
  224. uncountable/types/client_config_t.py +17 -3
  225. uncountable/types/curves.py +0 -1
  226. uncountable/types/curves_t.py +10 -7
  227. uncountable/types/data.py +12 -0
  228. uncountable/types/data_t.py +103 -0
  229. uncountable/types/entity.py +4 -1
  230. uncountable/types/entity_t.py +125 -7
  231. uncountable/types/experiment_groups.py +0 -1
  232. uncountable/types/experiment_groups_t.py +5 -2
  233. uncountable/types/exports.py +8 -0
  234. uncountable/types/exports_t.py +34 -0
  235. uncountable/types/field_values.py +19 -1
  236. uncountable/types/field_values_t.py +246 -9
  237. uncountable/types/fields.py +0 -1
  238. uncountable/types/fields_t.py +5 -2
  239. uncountable/types/generic_upload.py +6 -1
  240. uncountable/types/generic_upload_t.py +88 -9
  241. uncountable/types/id_source.py +0 -1
  242. uncountable/types/id_source_t.py +26 -7
  243. uncountable/types/identifier.py +0 -1
  244. uncountable/types/identifier_t.py +13 -5
  245. uncountable/types/input_attributes.py +0 -1
  246. uncountable/types/input_attributes_t.py +4 -4
  247. uncountable/types/inputs.py +1 -1
  248. uncountable/types/inputs_t.py +24 -4
  249. uncountable/types/integration_server.py +8 -0
  250. uncountable/types/integration_server_t.py +46 -0
  251. uncountable/types/integration_session.py +10 -0
  252. uncountable/types/integration_session_t.py +60 -0
  253. uncountable/types/integrations.py +10 -0
  254. uncountable/types/integrations_t.py +62 -0
  255. uncountable/types/job_definition.py +4 -6
  256. uncountable/types/job_definition_t.py +96 -65
  257. uncountable/types/listing.py +9 -0
  258. uncountable/types/listing_t.py +51 -0
  259. uncountable/types/notices.py +8 -0
  260. uncountable/types/notices_t.py +37 -0
  261. uncountable/types/notifications.py +11 -0
  262. uncountable/types/notifications_t.py +74 -0
  263. uncountable/types/outputs.py +0 -1
  264. uncountable/types/outputs_t.py +6 -3
  265. uncountable/types/overrides.py +9 -0
  266. uncountable/types/overrides_t.py +49 -0
  267. uncountable/types/permissions.py +0 -1
  268. uncountable/types/permissions_t.py +1 -2
  269. uncountable/types/phases.py +0 -1
  270. uncountable/types/phases_t.py +5 -2
  271. uncountable/types/post_base.py +0 -1
  272. uncountable/types/post_base_t.py +1 -2
  273. uncountable/types/queued_job.py +17 -0
  274. uncountable/types/queued_job_t.py +140 -0
  275. uncountable/types/recipe_identifiers.py +0 -1
  276. uncountable/types/recipe_identifiers_t.py +21 -8
  277. uncountable/types/recipe_inputs.py +0 -1
  278. uncountable/types/recipe_inputs_t.py +1 -2
  279. uncountable/types/recipe_links.py +0 -1
  280. uncountable/types/recipe_links_t.py +7 -4
  281. uncountable/types/recipe_metadata.py +0 -1
  282. uncountable/types/recipe_metadata_t.py +14 -9
  283. uncountable/types/recipe_output_metadata.py +0 -1
  284. uncountable/types/recipe_output_metadata_t.py +5 -2
  285. uncountable/types/recipe_tags.py +0 -1
  286. uncountable/types/recipe_tags_t.py +5 -2
  287. uncountable/types/recipe_workflow_steps.py +0 -1
  288. uncountable/types/recipe_workflow_steps_t.py +14 -7
  289. uncountable/types/recipes.py +0 -1
  290. uncountable/types/recipes_t.py +6 -2
  291. uncountable/types/response.py +0 -1
  292. uncountable/types/response_t.py +3 -2
  293. uncountable/types/secret_retrieval.py +0 -1
  294. uncountable/types/secret_retrieval_t.py +13 -7
  295. uncountable/types/sockets.py +20 -0
  296. uncountable/types/sockets_t.py +169 -0
  297. uncountable/types/structured_filters.py +25 -0
  298. uncountable/types/structured_filters_t.py +248 -0
  299. uncountable/types/units.py +0 -1
  300. uncountable/types/units_t.py +5 -2
  301. uncountable/types/uploader.py +24 -0
  302. uncountable/types/uploader_t.py +222 -0
  303. uncountable/types/users.py +0 -1
  304. uncountable/types/users_t.py +5 -2
  305. uncountable/types/webhook_job.py +9 -0
  306. uncountable/types/webhook_job_t.py +48 -0
  307. uncountable/types/workflows.py +0 -1
  308. uncountable/types/workflows_t.py +10 -4
  309. uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
  310. uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
  311. {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
  312. UncountablePythonSDK-0.0.52.dist-info/METADATA +0 -56
  313. UncountablePythonSDK-0.0.52.dist-info/RECORD +0 -246
  314. docs/quickstart.md +0 -19
  315. uncountable/core/version.py +0 -11
  316. {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -0
@@ -3,21 +3,21 @@ import dataclasses
3
3
  import decimal
4
4
  import io
5
5
  import json
6
- from typing import Any, Optional, TypeAlias, Union, cast
6
+ from enum import Enum
7
+ from typing import Any, cast
7
8
 
8
- from main.base.types import data_t
9
+ import yaml
10
+
11
+ from main.base.types import data_t, type_info_t
9
12
  from main.base.types.base_t import PureJsonValue
10
13
  from pkgs.argument_parser import CachedParser
11
- from pkgs.serialization_util import (
12
- serialize_for_api,
13
- serialize_for_storage,
14
- )
14
+ from pkgs.serialization_util import serialize_for_api, serialize_for_storage
15
15
 
16
16
  from .. import builder, util
17
17
  from ..emit_typescript_util import MODIFY_NOTICE, ts_name
18
18
  from ..value_spec import convert_to_value_spec_type
19
19
 
20
- ext_info_parser = CachedParser(data_t.ExtInfo)
20
+ ext_info_parser = CachedParser(type_info_t.ExtInfo, strict_property_parsing=True)
21
21
 
22
22
 
23
23
  def type_path_of(stype: builder.SpecType) -> object: # NamePath
@@ -41,10 +41,23 @@ def type_path_of(stype: builder.SpecType) -> object: # NamePath
41
41
  parts: list[object] = ["$literal"]
42
42
  for parameter in stype.parameters:
43
43
  assert isinstance(parameter, builder.SpecTypeLiteralWrapper)
44
+ emit_value = parameter.value
45
+ if isinstance(parameter.value_type, builder.SpecTypeDefnObject):
46
+ emit_value = parameter.value
47
+ assert isinstance(emit_value, (str, bool)), (
48
+ f"invalid-literal-value:{emit_value}"
49
+ )
50
+ elif isinstance(parameter.value_type, builder.SpecTypeDefnStringEnum):
51
+ key = parameter.value
52
+ assert isinstance(key, str)
53
+ emit_value = parameter.value_type.values[key].value
54
+ else:
55
+ raise Exception("unhandled-literal-type")
56
+
44
57
  # This allows expansion to enum literal values later
45
58
  parts.append([
46
59
  "$value",
47
- parameter.value,
60
+ emit_value,
48
61
  type_path_of(parameter.value_type),
49
62
  ])
50
63
  return parts
@@ -95,6 +108,35 @@ def emit_type_info(build: builder.SpecBuilder, output: str) -> None:
95
108
  util.rewrite_file(f"{output}/type_map.ts", type_map_out.getvalue())
96
109
 
97
110
 
111
+ def _convert_value_for_yaml_dump(value: Any) -> Any:
112
+ if isinstance(value, Enum):
113
+ return value.value
114
+ elif isinstance(value, list):
115
+ return [_convert_value_for_yaml_dump(item) for item in value]
116
+ elif isinstance(value, dict):
117
+ return {k: _convert_value_for_yaml_dump(v) for k, v in value.items()}
118
+ elif isinstance(value, decimal.Decimal):
119
+ return str(value)
120
+ else:
121
+ return value
122
+
123
+
124
+ def asdict_for_yaml_dump(dataclass_instance: Any) -> Any:
125
+ return {
126
+ k: _convert_value_for_yaml_dump(v)
127
+ for k, v in dataclasses.asdict(dataclass_instance).items()
128
+ }
129
+
130
+
131
+ def emit_type_info_python(build: builder.SpecBuilder, output: str) -> None:
132
+ type_map = _build_map_all(build, python=True)
133
+
134
+ stripped = _dict_null_strip(asdict_for_yaml_dump(type_map))
135
+
136
+ yaml_content = yaml.dump(stripped, default_flow_style=False, sort_keys=True)
137
+ util.rewrite_file(f"{output}/type_map.yaml", yaml_content)
138
+
139
+
98
140
  @dataclasses.dataclass
99
141
  class MapProperty:
100
142
  api_name: str
@@ -106,7 +148,7 @@ class MapProperty:
106
148
  desc: str | None
107
149
  # We don't have typing on defaults yet, relying on emitters to check it. Limit
108
150
  # use of this field, as it'll necessarily change when adding type info
109
- default: object
151
+ default: PureJsonValue
110
152
 
111
153
 
112
154
  @dataclasses.dataclass
@@ -129,12 +171,19 @@ class MapTypeAlias(MapTypeBase):
129
171
  discriminator: str | None
130
172
 
131
173
 
174
+ @dataclasses.dataclass
175
+ class StringEnumValue:
176
+ value: str
177
+ label: str
178
+ deprecated: bool = False
179
+
180
+
132
181
  @dataclasses.dataclass
133
182
  class MapStringEnum(MapTypeBase):
134
- values: dict[str, str]
183
+ values: dict[str, StringEnumValue]
135
184
 
136
185
 
137
- MapType: TypeAlias = Union[MapTypeObject, MapTypeAlias, MapStringEnum]
186
+ MapType = MapTypeObject | MapTypeAlias | MapStringEnum
138
187
 
139
188
 
140
189
  @dataclasses.dataclass
@@ -147,11 +196,14 @@ class MapAll:
147
196
  namespaces: dict[str, MapNamespace]
148
197
 
149
198
 
150
- def _build_map_all(build: builder.SpecBuilder) -> MapAll:
199
+ def _build_map_all(build: builder.SpecBuilder, *, python: bool = False) -> MapAll:
151
200
  map_all = MapAll(namespaces={})
152
201
 
153
202
  for namespace in build.namespaces.values():
154
- if not namespace.emit_type_info:
203
+ if not python and not namespace.emit_type_info:
204
+ continue
205
+
206
+ if python and not namespace.emit_type_info_python:
155
207
  continue
156
208
 
157
209
  map_namespace = MapNamespace(types={})
@@ -172,9 +224,9 @@ class InheritablePropertyParts:
172
224
  at that level, but that needs to be done in builder. When that is done, the
173
225
  "label" and "desc" could probably be removed from this list."""
174
226
 
175
- label: Optional[str] = None
176
- desc: Optional[str] = None
177
- ext_info: Optional[data_t.ExtInfo] = None
227
+ label: str | None = None
228
+ desc: str | None = None
229
+ ext_info: type_info_t.ExtInfo | None = None
178
230
 
179
231
 
180
232
  def _extract_inheritable_property_parts(
@@ -201,8 +253,13 @@ def _extract_inheritable_property_parts(
201
253
  elif base_parts.ext_info is None:
202
254
  ext_info = local_ext_info
203
255
  else:
204
- ext_info = data_t.ExtInfo(
205
- **(local_ext_info.__dict__ | base_parts.ext_info.__dict__)
256
+ ext_info = dataclasses.replace(
257
+ local_ext_info,
258
+ **{
259
+ field.name: getattr(base_parts.ext_info, field.name)
260
+ for field in dataclasses.fields(type_info_t.ExtInfo)
261
+ if getattr(base_parts.ext_info, field.name) is not None
262
+ },
206
263
  )
207
264
 
208
265
  return InheritablePropertyParts(label=label, desc=desc, ext_info=ext_info)
@@ -214,7 +271,7 @@ ALL_FIELDS_GROUP = "*all_fields"
214
271
 
215
272
  def _extract_and_validate_layout(
216
273
  stype: builder.SpecTypeDefnObject,
217
- ext_info: data_t.ExtInfo,
274
+ ext_info: type_info_t.ExtInfo,
218
275
  base_layout: ExtInfoLayout | None,
219
276
  ) -> ExtInfoLayout:
220
277
  """
@@ -230,13 +287,15 @@ def _extract_and_validate_layout(
230
287
  for group in ext_info.layout.groups:
231
288
  fields = set(group.fields or [])
232
289
  for field in fields:
233
- assert field in stype.properties, f"layout-refers-to-missing-field:{field}"
290
+ assert field in stype.properties or field == DISCRIMINATOR_COMMON_NAME, (
291
+ f"layout-refers-to-missing-field:{field}"
292
+ )
234
293
 
235
294
  local_ref_name = None
236
295
  if group.ref_name is not None:
237
- assert (
238
- base_layout is None or base_layout.get(group.ref_name) is None
239
- ), f"group-name-duplicate-in-base:{group.ref_name}"
296
+ assert base_layout is None or base_layout.get(group.ref_name) is None, (
297
+ f"group-name-duplicate-in-base:{group.ref_name}"
298
+ )
240
299
  local_ref_name = group.ref_name
241
300
 
242
301
  if group.extends:
@@ -255,14 +314,34 @@ def _extract_and_validate_layout(
255
314
  assert group_ref_name in layout, f"missing-base-group:{group_ref_name}"
256
315
 
257
316
  for prop_ref_name in stype.properties:
258
- assert prop_ref_name in all_fields_group, f"layout-missing-field:{prop_ref_name}"
317
+ assert prop_ref_name in all_fields_group, (
318
+ f"layout-missing-field:{prop_ref_name}"
319
+ )
259
320
 
260
321
  return layout
261
322
 
262
323
 
324
+ def _pull_property_from_type_recursively(
325
+ stype: builder.SpecTypeDefnObject,
326
+ property_name: str,
327
+ ) -> builder.SpecProperty | None:
328
+ assert stype.properties is not None
329
+ prop = stype.properties.get(property_name)
330
+ if prop is not None:
331
+ return prop
332
+
333
+ if stype.base is None:
334
+ return None
335
+
336
+ return _pull_property_from_type_recursively(stype.base, property_name)
337
+
338
+
339
+ DISCRIMINATOR_COMMON_NAME = "type"
340
+
341
+
263
342
  def _validate_type_ext_info(
264
343
  stype: builder.SpecTypeDefnObject,
265
- ) -> tuple[ExtInfoLayout | None, Optional[data_t.ExtInfo]]:
344
+ ) -> tuple[ExtInfoLayout | None, type_info_t.ExtInfo | None]:
266
345
  ext_info = _parse_ext_info(stype.ext_info)
267
346
  if ext_info is None:
268
347
  return None, None
@@ -270,9 +349,19 @@ def _validate_type_ext_info(
270
349
  if ext_info.label_fields is not None:
271
350
  assert stype.properties is not None
272
351
  for name in ext_info.label_fields:
273
- prop = stype.properties.get(name)
352
+ if name == DISCRIMINATOR_COMMON_NAME:
353
+ continue
354
+ prop = _pull_property_from_type_recursively(stype, name)
274
355
  assert prop is not None, f"missing-label-field:{name}"
275
356
 
357
+ if ext_info.actions is not None:
358
+ assert stype.properties is not None
359
+ for action in ext_info.actions:
360
+ if action.property == DISCRIMINATOR_COMMON_NAME:
361
+ continue
362
+ prop = _pull_property_from_type_recursively(stype, action.property)
363
+ assert prop is not None, f"missing-action-field:{action.property}"
364
+
276
365
  if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefnObject):
277
366
  base_layout, _ = _validate_type_ext_info(stype.base)
278
367
  else:
@@ -354,7 +443,11 @@ def _build_map_type(
354
443
  # IMPROVE: We probably want the label here, but this requires a change
355
444
  # to the front-end type-info and form code to handle
356
445
  values={
357
- entry.value: (entry.label or entry.name)
446
+ entry.value: StringEnumValue(
447
+ value=entry.value,
448
+ label=entry.label or entry.name,
449
+ deprecated=entry.deprecated,
450
+ )
358
451
  for entry in stype.values.values()
359
452
  },
360
453
  )
@@ -362,7 +455,7 @@ def _build_map_type(
362
455
  return None
363
456
 
364
457
 
365
- def _parse_ext_info(in_ext: Any) -> Optional[data_t.ExtInfo]:
458
+ def _parse_ext_info(in_ext: Any) -> type_info_t.ExtInfo | None:
366
459
  if in_ext is None:
367
460
  return None
368
461
  assert isinstance(in_ext, dict)
@@ -380,10 +473,12 @@ def _parse_ext_info(in_ext: Any) -> Optional[data_t.ExtInfo]:
380
473
  df["result_type"] = serialize_for_storage(converted)
381
474
  mod_ext["data_format"] = df
382
475
 
476
+ if "open_api" in mod_ext:
477
+ del mod_ext["open_api"]
383
478
  return ext_info_parser.parse_storage(mod_ext)
384
479
 
385
480
 
386
- def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
481
+ def _convert_ext_info(in_ext: Any) -> PureJsonValue | None:
387
482
  # we need to convert this to API storage since it'll be used as-is in the UI
388
483
  parsed = _parse_ext_info(in_ext)
389
484
  return cast(PureJsonValue, serialize_for_api(parsed))
@@ -0,0 +1,4 @@
1
+ # CLOSED MODULE
2
+ from .generate_ui_entry_actions import (
3
+ generate_entry_actions_typescript as generate_entry_actions_typescript,
4
+ )
@@ -0,0 +1,308 @@
1
+ import json
2
+ import re
3
+ from dataclasses import dataclass
4
+ from io import StringIO
5
+ from pathlib import Path
6
+ from typing import assert_never
7
+
8
+ from main.base.types import (
9
+ base_t,
10
+ ui_entry_actions_t,
11
+ )
12
+ from pkgs.serialization_util import serialize_for_api
13
+ from pkgs.type_spec import emit_typescript_util
14
+ from pkgs.type_spec.builder import (
15
+ BaseTypeName,
16
+ NameCase,
17
+ RawDict,
18
+ SpecBuilder,
19
+ SpecNamespace,
20
+ SpecTypeDefnObject,
21
+ )
22
+ from pkgs.type_spec.config import Config
23
+ from pkgs.type_spec.load_types import load_types
24
+ from pkgs.type_spec.util import rewrite_file
25
+ from pkgs.type_spec.value_spec.convert_type import convert_from_value_spec_type
26
+
27
+ _INIT_ACTION_INDEX_TYPE_DATA = {
28
+ "EntryActionInfo<InputT, OutputT>": {
29
+ "type": BaseTypeName.s_object,
30
+ "properties": {"inputs": {"type": "InputT"}, "outputs": {"type": "OutputT"}},
31
+ }
32
+ }
33
+ _TYPES_ROOT = "unc_types"
34
+
35
+
36
+ @dataclass(kw_only=True)
37
+ class EntryActionTypeInfo:
38
+ inputs_type: SpecTypeDefnObject
39
+ outputs_type: SpecTypeDefnObject
40
+ name: str
41
+
42
+
43
+ def ui_entry_variable_to_type_spec_type(
44
+ variable: ui_entry_actions_t.UiEntryActionVariable,
45
+ ) -> str:
46
+ match variable:
47
+ case ui_entry_actions_t.UiEntryActionVariableString():
48
+ return BaseTypeName.s_string
49
+ case ui_entry_actions_t.UiEntryActionVariableSingleEntity():
50
+ return "ObjectId"
51
+ case _:
52
+ assert_never(variable)
53
+
54
+
55
+ def construct_inputs_type_data(
56
+ vars: dict[str, ui_entry_actions_t.UiEntryActionVariable],
57
+ ) -> RawDict:
58
+ if len(vars) == 0:
59
+ return {"type": BaseTypeName.s_object}
60
+ properties: dict[str, dict[str, str]] = {}
61
+ for input_name, input_defn in (vars).items():
62
+ properties[f"{input_name}"] = {
63
+ "type": ui_entry_variable_to_type_spec_type(input_defn)
64
+ }
65
+ return {"type": BaseTypeName.s_object, "properties": properties}
66
+
67
+
68
+ def construct_outputs_type_data(
69
+ vars: dict[str, ui_entry_actions_t.UiEntryActionOutput],
70
+ ) -> RawDict:
71
+ if len(vars) == 0:
72
+ return {"type": BaseTypeName.s_object}
73
+ properties: dict[str, dict[str, str]] = {}
74
+ for output_name, output_defn in (vars).items():
75
+ # All outputs are optional
76
+ properties[f"{output_name}"] = {
77
+ "type": f"Optional<{convert_from_value_spec_type(output_defn.vs_type)}>"
78
+ }
79
+ return {"type": BaseTypeName.s_object, "properties": properties}
80
+
81
+
82
+ def construct_outputs_type(
83
+ *,
84
+ action_scope: ui_entry_actions_t.ActionScope,
85
+ vars: dict[str, ui_entry_actions_t.UiEntryActionOutput],
86
+ builder: SpecBuilder,
87
+ namespace: SpecNamespace,
88
+ ) -> SpecTypeDefnObject:
89
+ stype = SpecTypeDefnObject(
90
+ namespace=namespace,
91
+ name=emit_typescript_util.ts_type_name(f"{action_scope}_outputs"),
92
+ )
93
+ namespace.types[stype.name] = stype
94
+ stype.process(
95
+ builder=builder,
96
+ data=construct_outputs_type_data(vars=vars),
97
+ )
98
+ return stype
99
+
100
+
101
+ def construct_inputs_type(
102
+ *,
103
+ action_scope: ui_entry_actions_t.ActionScope,
104
+ vars: dict[str, ui_entry_actions_t.UiEntryActionVariable],
105
+ builder: SpecBuilder,
106
+ namespace: SpecNamespace,
107
+ ) -> SpecTypeDefnObject:
108
+ stype = SpecTypeDefnObject(
109
+ namespace=namespace,
110
+ name=emit_typescript_util.ts_type_name(f"{action_scope}_inputs"),
111
+ )
112
+ stype.process(builder=builder, data=construct_inputs_type_data(vars))
113
+ namespace.types[stype.name] = stype
114
+ return stype
115
+
116
+
117
+ def _get_types_root(destination_root: Path) -> Path:
118
+ return destination_root / "types"
119
+
120
+
121
+ def emit_imports_ts(
122
+ namespaces: set[SpecNamespace],
123
+ out: StringIO,
124
+ ) -> None:
125
+ for ns in sorted(
126
+ namespaces,
127
+ key=lambda ns: ns.name,
128
+ ):
129
+ import_as = emit_typescript_util.resolve_namespace_ref(ns)
130
+ import_from = f"{_TYPES_ROOT}/{ns.name}"
131
+ out.write(f'import * as {import_as} from "{import_from}"\n')
132
+
133
+
134
+ def emit_entry_action_definition(
135
+ *,
136
+ ctx: emit_typescript_util.EmitTypescriptContext,
137
+ defn: ui_entry_actions_t.UiEntryActionDefinition,
138
+ builder: SpecBuilder,
139
+ action_scope: ui_entry_actions_t.ActionScope,
140
+ ) -> EntryActionTypeInfo:
141
+ inputs_type = construct_inputs_type(
142
+ action_scope=action_scope,
143
+ vars=defn.inputs,
144
+ builder=builder,
145
+ namespace=ctx.namespace,
146
+ )
147
+ outputs_type = construct_outputs_type(
148
+ action_scope=action_scope,
149
+ vars=defn.outputs,
150
+ builder=builder,
151
+ namespace=ctx.namespace,
152
+ )
153
+
154
+ return EntryActionTypeInfo(
155
+ inputs_type=inputs_type,
156
+ outputs_type=outputs_type,
157
+ name=action_scope,
158
+ )
159
+
160
+
161
+ def _validate_input(input: ui_entry_actions_t.UiEntryActionVariable) -> None:
162
+ if "_" in input.vs_var_name:
163
+ raise ValueError(f"Expected camelCase for variable {input.vs_var_name}")
164
+ if not re.fullmatch(base_t.REF_NAME_STRICT_REGEX, input.vs_var_name):
165
+ raise ValueError(
166
+ f"Variable {input.vs_var_name} has invalid syntax. See REF_NAME_STRICT_REGEX"
167
+ )
168
+
169
+
170
+ def emit_query_index(
171
+ ctx: emit_typescript_util.EmitTypescriptContext,
172
+ defn_infos: list[EntryActionTypeInfo],
173
+ index_path: Path,
174
+ builder: SpecBuilder,
175
+ definitions: dict[
176
+ ui_entry_actions_t.ActionScope, ui_entry_actions_t.UiEntryActionDefinition
177
+ ],
178
+ ) -> bool:
179
+ query_index_type_data = {
180
+ **_INIT_ACTION_INDEX_TYPE_DATA,
181
+ "EntityActionTypeLookup": {
182
+ "type": BaseTypeName.s_object,
183
+ "properties": {
184
+ defn_info.name: {
185
+ "type": f"EntryActionInfo<{defn_info.inputs_type.name},{defn_info.outputs_type.name}>",
186
+ "name_case": NameCase.preserve,
187
+ }
188
+ for defn_info in defn_infos
189
+ },
190
+ },
191
+ "InputInfo": {
192
+ "type": BaseTypeName.s_object,
193
+ "properties": {
194
+ "value_spec_var": {"type": "String"},
195
+ "type": {"type": "ui_entry_actions.UiEntryActionDataType"},
196
+ "variable": {"type": "ui_entry_actions.UiEntryActionVariable"},
197
+ },
198
+ },
199
+ "OutputInfo": {
200
+ "type": BaseTypeName.s_object,
201
+ "properties": {
202
+ "name": {"type": "String"},
203
+ "desc": {"type": "String"},
204
+ "type": {"type": "value_spec.BaseType"},
205
+ },
206
+ },
207
+ "DefinitionInfo": {
208
+ "type": BaseTypeName.s_object,
209
+ "properties": {
210
+ "inputs": {
211
+ "type": "ReadonlyArray<InputInfo>",
212
+ },
213
+ "outputs": {
214
+ "type": "ReadonlyArray<OutputInfo>",
215
+ },
216
+ },
217
+ },
218
+ }
219
+ ctx.namespace.prescan(query_index_type_data)
220
+ ctx.namespace.process(
221
+ builder=builder,
222
+ data=query_index_type_data,
223
+ )
224
+
225
+ defn_lookup_info = {}
226
+ for scope, defn in definitions.items():
227
+ inputs = []
228
+ outputs = []
229
+ for input in defn.inputs.values():
230
+ _validate_input(input)
231
+ inputs.append(
232
+ serialize_for_api({
233
+ "value_spec_var": input.vs_var_name,
234
+ "type": input.type,
235
+ "variable": input,
236
+ })
237
+ )
238
+ for name, output in defn.outputs.items():
239
+ outputs.append(
240
+ serialize_for_api({
241
+ "name": name,
242
+ "desc": output.description,
243
+ "type": output.vs_type,
244
+ })
245
+ )
246
+ defn_lookup_info[scope] = {"inputs": inputs, "outputs": outputs}
247
+
248
+ defn_lookup_out = f"export const DEFINITION_LOOKUP = {json.dumps(defn_lookup_info, sort_keys=True, indent=2)} as const\n\nexport const DEFINITION_LOOKUP_TYPED = DEFINITION_LOOKUP as Record<UiEntryActionsT.ActionScope, DefinitionInfo>\n"
249
+
250
+ for stype in ctx.namespace.types.values():
251
+ emit_typescript_util.emit_type_ts(
252
+ ctx=ctx,
253
+ stype=stype,
254
+ )
255
+
256
+ import_buffer = StringIO()
257
+ emit_typescript_util.emit_namespace_imports_from_root_ts(
258
+ namespaces=ctx.namespaces,
259
+ out=import_buffer,
260
+ root=_TYPES_ROOT,
261
+ )
262
+
263
+ return rewrite_file(
264
+ content=import_buffer.getvalue() + ctx.out.getvalue() + defn_lookup_out,
265
+ filename=str(index_path),
266
+ )
267
+
268
+
269
+ def generate_entry_actions_typescript(
270
+ *,
271
+ definitions: dict[
272
+ ui_entry_actions_t.ActionScope, ui_entry_actions_t.UiEntryActionDefinition
273
+ ],
274
+ destination_root: Path,
275
+ materials_type_spec_config: Config,
276
+ ) -> None:
277
+ builder = load_types(materials_type_spec_config)
278
+ assert builder is not None
279
+
280
+ definition_buffer = StringIO()
281
+ index_namespace = SpecNamespace(name="index")
282
+ ctx = emit_typescript_util.EmitTypescriptContext(
283
+ out=definition_buffer,
284
+ namespace=index_namespace,
285
+ api_endpoints={},
286
+ )
287
+ builder.namespaces[index_namespace.name] = index_namespace
288
+
289
+ defn_infos: list[EntryActionTypeInfo] = []
290
+
291
+ for action_scope, defn in definitions.items():
292
+ defn_infos.append(
293
+ emit_entry_action_definition(
294
+ action_scope=action_scope,
295
+ ctx=ctx,
296
+ defn=defn,
297
+ builder=builder,
298
+ )
299
+ )
300
+
301
+ index_path = _get_types_root(destination_root) / "index.ts"
302
+ emit_query_index(
303
+ ctx=ctx,
304
+ builder=builder,
305
+ defn_infos=defn_infos,
306
+ definitions=definitions,
307
+ index_path=index_path,
308
+ )
pkgs/type_spec/util.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  from dataclasses import dataclass
4
- from typing import Optional, TypeVar, Union
4
+ from typing import TypeVar, Union
5
5
 
6
6
  import regex as re
7
7
 
@@ -29,8 +29,8 @@ LiteralTypeValue = Union[str, bool]
29
29
  class ParsedTypePart:
30
30
  name: str
31
31
  # An empty list is distinct from None
32
- parameters: Optional[list["ParsedTypePath"]] = None
33
- literal_value: Optional[LiteralTypeValue] = None
32
+ parameters: list["ParsedTypePath"] | None = None
33
+ literal_value: LiteralTypeValue | None = None
34
34
 
35
35
 
36
36
  ParsedTypePath = list[ParsedTypePart]
@@ -159,7 +159,7 @@ def is_valid_property_name(name: str) -> bool:
159
159
  def check_fields(data: dict[str, T], allowed: list[str]) -> None:
160
160
  for key in data:
161
161
  if key not in allowed:
162
- raise Exception(f"unexpected-field: {key}")
162
+ raise Exception(f"unexpected-field: {key}. Allowed: {allowed}")
163
163
 
164
164
 
165
165
  def split_any_name(name: str) -> list[str]: