UncountablePythonSDK 0.0.83__py3-none-any.whl → 0.0.132__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 (298) 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/basic_auth.py +7 -0
  9. examples/create_ingredient_sdk.py +34 -0
  10. examples/download_files.py +26 -0
  11. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  12. examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
  13. examples/integration-server/jobs/materials_auto/example_http.py +47 -0
  14. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  15. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  16. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  17. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
  18. examples/integration-server/jobs/materials_auto/example_wh.py +17 -9
  19. examples/integration-server/jobs/materials_auto/profile.yaml +61 -0
  20. examples/integration-server/pyproject.toml +10 -10
  21. examples/oauth.py +7 -0
  22. examples/set_recipe_metadata_file.py +1 -1
  23. examples/upload_files.py +1 -2
  24. pkgs/argument_parser/__init__.py +8 -0
  25. pkgs/argument_parser/_is_namedtuple.py +3 -0
  26. pkgs/argument_parser/argument_parser.py +196 -63
  27. pkgs/filesystem_utils/__init__.py +1 -0
  28. pkgs/filesystem_utils/_blob_session.py +144 -0
  29. pkgs/filesystem_utils/_gdrive_session.py +5 -5
  30. pkgs/filesystem_utils/_s3_session.py +2 -1
  31. pkgs/filesystem_utils/_sftp_session.py +6 -3
  32. pkgs/filesystem_utils/file_type_utils.py +30 -10
  33. pkgs/serialization/__init__.py +7 -2
  34. pkgs/serialization/annotation.py +64 -0
  35. pkgs/serialization/missing_sentry.py +1 -1
  36. pkgs/serialization/opaque_key.py +1 -1
  37. pkgs/serialization/serial_alias.py +47 -0
  38. pkgs/serialization/serial_class.py +40 -48
  39. pkgs/serialization/serial_generic.py +16 -0
  40. pkgs/serialization/serial_union.py +16 -16
  41. pkgs/serialization_util/__init__.py +6 -0
  42. pkgs/serialization_util/dataclasses.py +14 -0
  43. pkgs/serialization_util/serialization_helpers.py +15 -5
  44. pkgs/type_spec/actions_registry/__main__.py +0 -4
  45. pkgs/type_spec/actions_registry/emit_typescript.py +2 -4
  46. pkgs/type_spec/builder.py +248 -70
  47. pkgs/type_spec/builder_types.py +9 -0
  48. pkgs/type_spec/config.py +40 -7
  49. pkgs/type_spec/cross_output_links.py +99 -0
  50. pkgs/type_spec/emit_open_api.py +121 -34
  51. pkgs/type_spec/emit_open_api_util.py +5 -5
  52. pkgs/type_spec/emit_python.py +277 -86
  53. pkgs/type_spec/emit_typescript.py +102 -29
  54. pkgs/type_spec/emit_typescript_util.py +66 -10
  55. pkgs/type_spec/load_types.py +16 -3
  56. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  57. pkgs/type_spec/open_api_util.py +29 -4
  58. pkgs/type_spec/parts/base.py.prepart +11 -8
  59. pkgs/type_spec/parts/base.ts.prepart +4 -0
  60. pkgs/type_spec/type_info/__main__.py +3 -1
  61. pkgs/type_spec/type_info/emit_type_info.py +115 -22
  62. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  63. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  64. pkgs/type_spec/util.py +3 -3
  65. pkgs/type_spec/value_spec/__main__.py +26 -9
  66. pkgs/type_spec/value_spec/convert_type.py +18 -0
  67. pkgs/type_spec/value_spec/emit_python.py +13 -3
  68. pkgs/type_spec/value_spec/types.py +1 -1
  69. uncountable/core/async_batch.py +1 -1
  70. uncountable/core/client.py +133 -34
  71. uncountable/core/environment.py +3 -3
  72. uncountable/core/file_upload.py +39 -15
  73. uncountable/integration/cli.py +116 -23
  74. uncountable/integration/construct_client.py +3 -3
  75. uncountable/integration/executors/executors.py +12 -2
  76. uncountable/integration/executors/generic_upload_executor.py +66 -14
  77. uncountable/integration/http_server/__init__.py +5 -0
  78. uncountable/integration/http_server/types.py +69 -0
  79. uncountable/integration/job.py +192 -7
  80. uncountable/integration/queue_runner/command_server/__init__.py +4 -0
  81. uncountable/integration/queue_runner/command_server/command_client.py +65 -0
  82. uncountable/integration/queue_runner/command_server/command_server.py +83 -5
  83. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  84. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +36 -0
  85. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +28 -11
  86. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +77 -1
  87. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +135 -0
  88. uncountable/integration/queue_runner/command_server/types.py +25 -2
  89. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +168 -11
  90. uncountable/integration/queue_runner/datastore/interface.py +10 -0
  91. uncountable/integration/queue_runner/datastore/model.py +8 -1
  92. uncountable/integration/queue_runner/job_scheduler.py +63 -23
  93. uncountable/integration/queue_runner/queue_runner.py +10 -2
  94. uncountable/integration/queue_runner/worker.py +3 -5
  95. uncountable/integration/scan_profiles.py +1 -1
  96. uncountable/integration/scheduler.py +74 -25
  97. uncountable/integration/secret_retrieval/retrieve_secret.py +1 -1
  98. uncountable/integration/server.py +42 -12
  99. uncountable/integration/telemetry.py +63 -10
  100. uncountable/integration/webhook_server/entrypoint.py +39 -112
  101. uncountable/types/__init__.py +58 -1
  102. uncountable/types/api/batch/execute_batch.py +5 -6
  103. uncountable/types/api/batch/execute_batch_load_async.py +2 -3
  104. uncountable/types/api/chemical/convert_chemical_formats.py +10 -5
  105. uncountable/types/api/condition_parameters/__init__.py +1 -0
  106. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  107. uncountable/types/api/entity/create_entities.py +7 -7
  108. uncountable/types/api/entity/create_entity.py +8 -8
  109. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  110. uncountable/types/api/entity/export_entities.py +59 -0
  111. uncountable/types/api/entity/get_entities_data.py +3 -4
  112. uncountable/types/api/entity/grant_entity_permissions.py +6 -6
  113. uncountable/types/api/entity/list_aggregate.py +79 -0
  114. uncountable/types/api/entity/list_entities.py +34 -10
  115. uncountable/types/api/entity/lock_entity.py +4 -4
  116. uncountable/types/api/entity/lookup_entity.py +116 -0
  117. uncountable/types/api/entity/resolve_entity_ids.py +5 -6
  118. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  119. uncountable/types/api/entity/set_values.py +3 -3
  120. uncountable/types/api/entity/transition_entity_phase.py +14 -7
  121. uncountable/types/api/entity/unlock_entity.py +3 -3
  122. uncountable/types/api/equipment/associate_equipment_input.py +2 -3
  123. uncountable/types/api/field_options/upsert_field_options.py +7 -7
  124. uncountable/types/api/files/__init__.py +1 -0
  125. uncountable/types/api/files/download_file.py +77 -0
  126. uncountable/types/api/id_source/list_id_source.py +6 -7
  127. uncountable/types/api/id_source/match_id_source.py +4 -5
  128. uncountable/types/api/input_groups/get_input_group_names.py +3 -4
  129. uncountable/types/api/inputs/create_inputs.py +10 -9
  130. uncountable/types/api/inputs/get_input_data.py +11 -12
  131. uncountable/types/api/inputs/get_input_names.py +6 -7
  132. uncountable/types/api/inputs/get_inputs_data.py +6 -7
  133. uncountable/types/api/inputs/set_input_attribute_values.py +5 -6
  134. uncountable/types/api/inputs/set_input_category.py +5 -5
  135. uncountable/types/api/inputs/set_input_subcategories.py +3 -3
  136. uncountable/types/api/inputs/set_intermediate_type.py +4 -4
  137. uncountable/types/api/integrations/__init__.py +1 -0
  138. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  139. uncountable/types/api/integrations/push_notification.py +49 -0
  140. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  141. uncountable/types/api/listing/__init__.py +1 -0
  142. uncountable/types/api/listing/fetch_listing.py +58 -0
  143. uncountable/types/api/material_families/update_entity_material_families.py +3 -4
  144. uncountable/types/api/notebooks/__init__.py +1 -0
  145. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  146. uncountable/types/api/outputs/get_output_data.py +12 -13
  147. uncountable/types/api/outputs/get_output_names.py +5 -6
  148. uncountable/types/api/outputs/get_output_organization.py +173 -0
  149. uncountable/types/api/outputs/resolve_output_conditions.py +7 -8
  150. uncountable/types/api/permissions/set_core_permissions.py +16 -10
  151. uncountable/types/api/project/get_projects.py +6 -7
  152. uncountable/types/api/project/get_projects_data.py +7 -8
  153. uncountable/types/api/recipe_links/create_recipe_link.py +5 -5
  154. uncountable/types/api/recipe_links/remove_recipe_link.py +4 -4
  155. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +6 -7
  156. uncountable/types/api/recipes/add_recipe_to_project.py +3 -3
  157. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  158. uncountable/types/api/recipes/archive_recipes.py +4 -4
  159. uncountable/types/api/recipes/associate_recipe_as_input.py +5 -5
  160. uncountable/types/api/recipes/associate_recipe_as_lot.py +3 -3
  161. uncountable/types/api/recipes/clear_recipe_outputs.py +3 -3
  162. uncountable/types/api/recipes/create_mix_order.py +44 -0
  163. uncountable/types/api/recipes/create_recipe.py +8 -9
  164. uncountable/types/api/recipes/create_recipes.py +8 -9
  165. uncountable/types/api/recipes/disassociate_recipe_as_input.py +3 -3
  166. uncountable/types/api/recipes/edit_recipe_inputs.py +101 -24
  167. uncountable/types/api/recipes/get_column_calculation_values.py +4 -5
  168. uncountable/types/api/recipes/get_curve.py +4 -5
  169. uncountable/types/api/recipes/get_recipe_calculations.py +6 -7
  170. uncountable/types/api/recipes/get_recipe_links.py +3 -4
  171. uncountable/types/api/recipes/get_recipe_names.py +3 -4
  172. uncountable/types/api/recipes/get_recipe_output_metadata.py +5 -6
  173. uncountable/types/api/recipes/get_recipes_data.py +62 -34
  174. uncountable/types/api/recipes/lock_recipes.py +9 -8
  175. uncountable/types/api/recipes/remove_recipe_from_project.py +3 -3
  176. uncountable/types/api/recipes/set_recipe_inputs.py +9 -10
  177. uncountable/types/api/recipes/set_recipe_metadata.py +3 -3
  178. uncountable/types/api/recipes/set_recipe_output_annotations.py +11 -12
  179. uncountable/types/api/recipes/set_recipe_output_file.py +5 -6
  180. uncountable/types/api/recipes/set_recipe_outputs.py +24 -13
  181. uncountable/types/api/recipes/set_recipe_tags.py +14 -9
  182. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  183. uncountable/types/api/recipes/unarchive_recipes.py +3 -3
  184. uncountable/types/api/recipes/unlock_recipes.py +7 -6
  185. uncountable/types/api/runsheet/__init__.py +1 -0
  186. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  187. uncountable/types/api/triggers/run_trigger.py +4 -4
  188. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  189. uncountable/types/api/uploader/invoke_uploader.py +4 -5
  190. uncountable/types/api/user/__init__.py +1 -0
  191. uncountable/types/api/user/get_current_user_info.py +40 -0
  192. uncountable/types/async_batch.py +1 -1
  193. uncountable/types/async_batch_processor.py +506 -23
  194. uncountable/types/async_batch_t.py +35 -8
  195. uncountable/types/async_jobs.py +0 -1
  196. uncountable/types/async_jobs_t.py +1 -2
  197. uncountable/types/auth_retrieval.py +0 -1
  198. uncountable/types/auth_retrieval_t.py +6 -6
  199. uncountable/types/base.py +0 -1
  200. uncountable/types/base_t.py +11 -9
  201. uncountable/types/calculations.py +0 -1
  202. uncountable/types/calculations_t.py +1 -2
  203. uncountable/types/chemical_structure.py +0 -1
  204. uncountable/types/chemical_structure_t.py +5 -5
  205. uncountable/types/client_base.py +614 -69
  206. uncountable/types/client_config.py +1 -1
  207. uncountable/types/client_config_t.py +13 -3
  208. uncountable/types/curves.py +0 -1
  209. uncountable/types/curves_t.py +6 -7
  210. uncountable/types/data.py +12 -0
  211. uncountable/types/data_t.py +103 -0
  212. uncountable/types/entity.py +1 -1
  213. uncountable/types/entity_t.py +90 -10
  214. uncountable/types/experiment_groups.py +0 -1
  215. uncountable/types/experiment_groups_t.py +1 -2
  216. uncountable/types/exports.py +8 -0
  217. uncountable/types/exports_t.py +34 -0
  218. uncountable/types/field_values.py +19 -1
  219. uncountable/types/field_values_t.py +242 -9
  220. uncountable/types/fields.py +0 -1
  221. uncountable/types/fields_t.py +1 -2
  222. uncountable/types/generic_upload.py +0 -1
  223. uncountable/types/generic_upload_t.py +14 -14
  224. uncountable/types/id_source.py +0 -1
  225. uncountable/types/id_source_t.py +13 -7
  226. uncountable/types/identifier.py +0 -1
  227. uncountable/types/identifier_t.py +10 -5
  228. uncountable/types/input_attributes.py +0 -1
  229. uncountable/types/input_attributes_t.py +3 -4
  230. uncountable/types/inputs.py +0 -1
  231. uncountable/types/inputs_t.py +3 -4
  232. uncountable/types/integration_server.py +0 -1
  233. uncountable/types/integration_server_t.py +13 -4
  234. uncountable/types/integration_session.py +10 -0
  235. uncountable/types/integration_session_t.py +60 -0
  236. uncountable/types/integrations.py +10 -0
  237. uncountable/types/integrations_t.py +62 -0
  238. uncountable/types/job_definition.py +2 -1
  239. uncountable/types/job_definition_t.py +57 -32
  240. uncountable/types/listing.py +9 -0
  241. uncountable/types/listing_t.py +51 -0
  242. uncountable/types/notices.py +8 -0
  243. uncountable/types/notices_t.py +37 -0
  244. uncountable/types/notifications.py +11 -0
  245. uncountable/types/notifications_t.py +74 -0
  246. uncountable/types/outputs.py +0 -1
  247. uncountable/types/outputs_t.py +2 -3
  248. uncountable/types/overrides.py +0 -1
  249. uncountable/types/overrides_t.py +10 -4
  250. uncountable/types/permissions.py +0 -1
  251. uncountable/types/permissions_t.py +1 -2
  252. uncountable/types/phases.py +0 -1
  253. uncountable/types/phases_t.py +1 -2
  254. uncountable/types/post_base.py +0 -1
  255. uncountable/types/post_base_t.py +1 -2
  256. uncountable/types/queued_job.py +2 -1
  257. uncountable/types/queued_job_t.py +29 -12
  258. uncountable/types/recipe_identifiers.py +0 -1
  259. uncountable/types/recipe_identifiers_t.py +18 -8
  260. uncountable/types/recipe_inputs.py +0 -1
  261. uncountable/types/recipe_inputs_t.py +1 -2
  262. uncountable/types/recipe_links.py +0 -1
  263. uncountable/types/recipe_links_t.py +3 -4
  264. uncountable/types/recipe_metadata.py +0 -1
  265. uncountable/types/recipe_metadata_t.py +9 -10
  266. uncountable/types/recipe_output_metadata.py +0 -1
  267. uncountable/types/recipe_output_metadata_t.py +1 -2
  268. uncountable/types/recipe_tags.py +0 -1
  269. uncountable/types/recipe_tags_t.py +1 -2
  270. uncountable/types/recipe_workflow_steps.py +0 -1
  271. uncountable/types/recipe_workflow_steps_t.py +7 -7
  272. uncountable/types/recipes.py +0 -1
  273. uncountable/types/recipes_t.py +2 -2
  274. uncountable/types/response.py +0 -1
  275. uncountable/types/response_t.py +2 -2
  276. uncountable/types/secret_retrieval.py +0 -1
  277. uncountable/types/secret_retrieval_t.py +7 -7
  278. uncountable/types/sockets.py +20 -0
  279. uncountable/types/sockets_t.py +169 -0
  280. uncountable/types/structured_filters.py +25 -0
  281. uncountable/types/structured_filters_t.py +248 -0
  282. uncountable/types/units.py +0 -1
  283. uncountable/types/units_t.py +1 -2
  284. uncountable/types/uploader.py +24 -0
  285. uncountable/types/uploader_t.py +222 -0
  286. uncountable/types/users.py +0 -1
  287. uncountable/types/users_t.py +1 -2
  288. uncountable/types/webhook_job.py +1 -1
  289. uncountable/types/webhook_job_t.py +14 -3
  290. uncountable/types/workflows.py +0 -1
  291. uncountable/types/workflows_t.py +3 -4
  292. uncountablepythonsdk-0.0.132.dist-info/METADATA +64 -0
  293. uncountablepythonsdk-0.0.132.dist-info/RECORD +363 -0
  294. {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/WHEEL +1 -1
  295. UncountablePythonSDK-0.0.83.dist-info/METADATA +0 -60
  296. UncountablePythonSDK-0.0.83.dist-info/RECORD +0 -292
  297. docs/quickstart.md +0 -19
  298. {UncountablePythonSDK-0.0.83.dist-info → uncountablepythonsdk-0.0.132.dist-info}/top_level.txt +0 -0
@@ -3,15 +3,15 @@ import dataclasses
3
3
  import decimal
4
4
  import io
5
5
  import json
6
- from typing import Any, Optional, Union, cast
6
+ from enum import Enum
7
+ from typing import Any, cast
8
+
9
+ import yaml
7
10
 
8
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
@@ -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
- type MapType = 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[type_info_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 = type_info_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)
@@ -230,7 +287,9 @@ 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:
@@ -262,9 +321,27 @@ def _extract_and_validate_layout(
262
321
  return layout
263
322
 
264
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
+
265
342
  def _validate_type_ext_info(
266
343
  stype: builder.SpecTypeDefnObject,
267
- ) -> tuple[ExtInfoLayout | None, Optional[type_info_t.ExtInfo]]:
344
+ ) -> tuple[ExtInfoLayout | None, type_info_t.ExtInfo | None]:
268
345
  ext_info = _parse_ext_info(stype.ext_info)
269
346
  if ext_info is None:
270
347
  return None, None
@@ -272,9 +349,19 @@ def _validate_type_ext_info(
272
349
  if ext_info.label_fields is not None:
273
350
  assert stype.properties is not None
274
351
  for name in ext_info.label_fields:
275
- prop = stype.properties.get(name)
352
+ if name == DISCRIMINATOR_COMMON_NAME:
353
+ continue
354
+ prop = _pull_property_from_type_recursively(stype, name)
276
355
  assert prop is not None, f"missing-label-field:{name}"
277
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
+
278
365
  if not stype.is_base and isinstance(stype.base, builder.SpecTypeDefnObject):
279
366
  base_layout, _ = _validate_type_ext_info(stype.base)
280
367
  else:
@@ -356,7 +443,11 @@ def _build_map_type(
356
443
  # IMPROVE: We probably want the label here, but this requires a change
357
444
  # to the front-end type-info and form code to handle
358
445
  values={
359
- 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
+ )
360
451
  for entry in stype.values.values()
361
452
  },
362
453
  )
@@ -364,7 +455,7 @@ def _build_map_type(
364
455
  return None
365
456
 
366
457
 
367
- def _parse_ext_info(in_ext: Any) -> Optional[type_info_t.ExtInfo]:
458
+ def _parse_ext_info(in_ext: Any) -> type_info_t.ExtInfo | None:
368
459
  if in_ext is None:
369
460
  return None
370
461
  assert isinstance(in_ext, dict)
@@ -382,10 +473,12 @@ def _parse_ext_info(in_ext: Any) -> Optional[type_info_t.ExtInfo]:
382
473
  df["result_type"] = serialize_for_storage(converted)
383
474
  mod_ext["data_format"] = df
384
475
 
476
+ if "open_api" in mod_ext:
477
+ del mod_ext["open_api"]
385
478
  return ext_info_parser.parse_storage(mod_ext)
386
479
 
387
480
 
388
- def _convert_ext_info(in_ext: Any) -> Optional[PureJsonValue]:
481
+ def _convert_ext_info(in_ext: Any) -> PureJsonValue | None:
389
482
  # we need to convert this to API storage since it'll be used as-is in the UI
390
483
  parsed = _parse_ext_info(in_ext)
391
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]
@@ -13,11 +13,14 @@ One of the following can be specified on the name of a argument:
13
13
  After that you can also specify a `!` indicating the argument may not be null.
14
14
  If this is not specified, then a null input on this argument should produce a null output.
15
15
  We prefer not to use `!` as we want to encourage null pass-through where possible.
16
- If null is allowed as a legitimate value, such as in conditionals like `if`, then `!` must be specified.
16
+
17
+ If null is allowed as a legitimate value, such as in conditionals like `is_null`,
18
+ then `!usenull` must be specified, this distinguishes it from the pass-through case.
19
+ The accepted argument type must accept "None", it is not implied.
17
20
  """
18
21
 
19
22
  import sys
20
- from typing import TypeVar, cast
23
+ from typing import Match, Pattern, TypeVar, cast
21
24
 
22
25
  import regex as re
23
26
 
@@ -53,7 +56,7 @@ class Source:
53
56
  def has_more(self) -> bool:
54
57
  return self._at < len(self._text)
55
58
 
56
- def match(self, expression: re.Pattern) -> re.Match | None:
59
+ def match(self, expression: Pattern[str]) -> Match[str] | None:
57
60
  self.skip_space()
58
61
  m = expression.match(self._text, self._at)
59
62
  if m is not None:
@@ -84,7 +87,7 @@ class Source:
84
87
  return self._text[start : self._at]
85
88
 
86
89
 
87
- _re_argument_name = re.compile(r"([a-z_]+)(\?|\+)?(!)?:")
90
+ _re_argument_name = re.compile(r"([a-z_]+)(\?|\+)?(!|!usenull)?:")
88
91
 
89
92
 
90
93
  def parse_function_signature(text: str) -> ParsedFunctionSignature:
@@ -101,11 +104,18 @@ def parse_function_signature(text: str) -> ParsedFunctionSignature:
101
104
 
102
105
  type_str = source.extract_type()
103
106
  ref_name = arg_group.group(1)
104
- is_missing = arg_group.group(2) == "?"
105
- is_repeating = arg_group.group(2) == "+"
106
- pass_null = arg_group.group(3) is None
107
+ # is_missing = arg_group.group(2) == "?"
108
+ # is_repeating = arg_group.group(2) == "+"
107
109
  type_path = parse_type_str(type_str)
108
110
 
111
+ match arg_group.group(3):
112
+ case "!":
113
+ on_null = value_spec_t.OnNull.DISALLOW
114
+ case "!usenull":
115
+ on_null = value_spec_t.OnNull.USE
116
+ case _:
117
+ on_null = value_spec_t.OnNull.PASS
118
+
109
119
  extant = value_spec_t.ArgumentExtant.REQUIRED
110
120
  extant_marker = arg_group.group(2)
111
121
  if extant_marker == "?":
@@ -116,7 +126,7 @@ def parse_function_signature(text: str) -> ParsedFunctionSignature:
116
126
  arguments.append(
117
127
  ParsedFunctionArgument(
118
128
  ref_name=ref_name,
119
- pass_null=pass_null,
129
+ on_null=on_null,
120
130
  extant=extant,
121
131
  type_path=type_path,
122
132
  )
@@ -145,6 +155,7 @@ key_return = "return"
145
155
  key_description = "description"
146
156
  key_brief = "brief"
147
157
  key_name = "name"
158
+ key_draft = "draft"
148
159
 
149
160
 
150
161
  TypeT = TypeVar("TypeT")
@@ -208,7 +219,7 @@ def main() -> None:
208
219
  name=arg_name,
209
220
  description=arg_description,
210
221
  type=convert_to_value_spec_type(in_argument.type_path),
211
- pass_null=in_argument.pass_null,
222
+ on_null=in_argument.on_null,
212
223
  extant=in_argument.extant,
213
224
  )
214
225
  )
@@ -219,6 +230,11 @@ def main() -> None:
219
230
 
220
231
  brief = get_as(spec, key_brief, str)
221
232
  description = get_as(spec, key_description, str)
233
+ draft = (
234
+ get_as(spec, key_draft, bool)
235
+ if spec.get(key_draft) is not None
236
+ else None
237
+ )
222
238
 
223
239
  return_value = get(spec, key_return)
224
240
  where.append("return")
@@ -235,6 +251,7 @@ def main() -> None:
235
251
  type=convert_to_value_spec_type(parsed.return_type_path),
236
252
  description=return_description,
237
253
  ),
254
+ draft=draft,
238
255
  )
239
256
  )
240
257
  where.pop()