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
@@ -1,15 +1,18 @@
1
1
  import io
2
2
  import os
3
+ from typing import assert_never
3
4
 
4
5
  from . import builder, util
6
+ from .builder import EndpointKey, EndpointSpecificPath, PathMapping
5
7
  from .config import TypeScriptConfig
8
+ from .cross_output_links import get_path_links
6
9
  from .emit_io_ts import emit_type_io_ts
7
10
  from .emit_typescript_util import (
8
11
  MODIFY_NOTICE,
9
12
  EmitTypescriptContext,
13
+ emit_constant_ts,
10
14
  emit_namespace_imports_ts,
11
15
  emit_type_ts,
12
- emit_value_ts,
13
16
  resolve_namespace_name,
14
17
  resolve_namespace_ref,
15
18
  ts_type_name,
@@ -31,7 +34,12 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
31
34
  builder.namespaces.values(),
32
35
  key=lambda ns: resolve_namespace_name(ns),
33
36
  ):
34
- ctx = EmitTypescriptContext(out=io.StringIO(), namespace=namespace)
37
+ ctx = EmitTypescriptContext(
38
+ out=io.StringIO(),
39
+ namespace=namespace,
40
+ cross_output_paths=builder.cross_output_paths,
41
+ api_endpoints=builder.api_endpoints,
42
+ )
35
43
 
36
44
  _emit_namespace(ctx, config, namespace)
37
45
 
@@ -46,7 +54,10 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
46
54
  and len(namespace.constants) == 0
47
55
  ):
48
56
  # Try to capture some common incompleteness errors
49
- if namespace.endpoint is None or namespace.endpoint.function is None:
57
+ if namespace.endpoint is None or any(
58
+ endpoint_specific_path.function is None
59
+ for endpoint_specific_path in namespace.endpoint.path_per_api_endpoint.values()
60
+ ):
50
61
  raise Exception(
51
62
  f"Namespace {'/'.join(namespace.path)} is incomplete. It should have an endpoint with function, types, and/or constants"
52
63
  )
@@ -69,6 +80,7 @@ def _emit_types(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
69
80
  full.write("\n")
70
81
  full.write(MODIFY_NOTICE)
71
82
  full.write(f"// === START section from {namespace.name}.ts.part ===\n")
83
+ full.write("\n")
72
84
  full.write(part)
73
85
  full.write(f"// === END section from {namespace.name}.ts.part ===\n")
74
86
 
@@ -103,7 +115,7 @@ def _emit_namespace(
103
115
  emit_type_ts(ctx, stype)
104
116
 
105
117
  for sconst in namespace.constants.values():
106
- _emit_constant(ctx, sconst)
118
+ emit_constant_ts(ctx, sconst)
107
119
 
108
120
  if namespace.endpoint is not None:
109
121
  _emit_endpoint(ctx, config, namespace, namespace.endpoint)
@@ -123,6 +135,7 @@ def _emit_endpoint(
123
135
  has_data = "Data" in namespace.types
124
136
  has_deprecated_result = "DeprecatedResult" in namespace.types
125
137
  is_binary = endpoint.result_type == builder.ResultType.binary
138
+ has_multiple_endpoints = len(endpoint.path_per_api_endpoint) > 1
126
139
 
127
140
  result_type_count = sum([has_data, has_deprecated_result, is_binary])
128
141
  assert result_type_count < 2
@@ -134,6 +147,13 @@ def _emit_endpoint(
134
147
  if not is_binary:
135
148
  assert endpoint.result_type == builder.ResultType.json
136
149
 
150
+ paths_string = get_path_links(
151
+ ctx.cross_output_paths,
152
+ namespace,
153
+ current_path_type="TypeScript",
154
+ endpoint=endpoint,
155
+ )
156
+
137
157
  data_loader_head = ""
138
158
  data_loader_body = ""
139
159
  if endpoint.data_loader:
@@ -141,7 +161,7 @@ def _emit_endpoint(
141
161
  assert has_data
142
162
 
143
163
  data_loader_head = (
144
- 'import { buildApiDataLoader, argsKey } from "unc_base/data_manager"\n'
164
+ 'import { argsKey, buildApiDataLoader } from "unc_base/data_manager"\n'
145
165
  )
146
166
  data_loader_body = (
147
167
  "\nexport const data = buildApiDataLoader(argsKey(), apiCall)\n"
@@ -157,24 +177,49 @@ def _emit_endpoint(
157
177
  wrap_call = (
158
178
  f"{wrap_name}<Arguments>" if is_binary else f"{wrap_name}<Arguments, Response>"
159
179
  )
160
- type_path = f"unc_mat/types/{'/'.join(namespace.path)}"
180
+
181
+ unc_base_api_imports = (
182
+ f"appSpecificApiPath, {wrap_name}" if has_multiple_endpoints else wrap_name
183
+ )
184
+ path_mapping = ctx.api_endpoints[endpoint.default_endpoint_key].path_mapping
185
+
186
+ match path_mapping:
187
+ case PathMapping.NO_MAPPING:
188
+ path_mapping_part = (
189
+ "\n { pathMapping: ApplicationT.APIPathMapping.noMapping },"
190
+ )
191
+ case PathMapping.DEFAULT_MAPPING:
192
+ path_mapping_part = ""
193
+ case _:
194
+ assert_never(path_mapping)
195
+
196
+ unc_types_imports = (
197
+ 'import { ApplicationT } from "unc_types"\n'
198
+ if has_multiple_endpoints or path_mapping_part != ""
199
+ else ""
200
+ )
201
+
202
+ type_path = f"unc_types/{'/'.join(namespace.path)}"
161
203
 
162
204
  if is_binary:
163
- tsx_response_part = f"""import {{ {wrap_name} }} from "unc_base/api"
164
- import type {{ Arguments }} from "{type_path}"
205
+ tsx_response_head = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
206
+ """
207
+ tsx_response_part = f"""import type {{ Arguments }} from "{type_path}"
165
208
 
166
209
  export type {{ Arguments }}
167
210
  """
168
211
  elif has_data and endpoint.has_attachment:
169
- tsx_response_part = f"""import {{ {wrap_name}, type AttachmentResponse }} from "unc_base/api"
170
- import type {{ Arguments, Data }} from "{type_path}"
212
+ tsx_response_head = f"""import {{ type AttachmentResponse, {unc_base_api_imports} }} from "unc_base/api"
213
+ """
214
+ tsx_response_part = f"""import type {{ Arguments, Data }} from "{type_path}"
171
215
 
172
216
  export type {{ Arguments, Data }}
173
217
  export type Response = AttachmentResponse<Data>
174
218
  """
175
219
  elif has_data:
176
- tsx_response_part = f"""import {{ {wrap_name}, type JsonResponse }} from "unc_base/api"
177
- import type {{ Arguments, Data }} from "{type_path}"
220
+ tsx_response_head = f"""import {{ {unc_base_api_imports}, type JsonResponse }} from "unc_base/api"
221
+ """
222
+ tsx_response_part = f"""import type {{ Arguments, Data }} from "{type_path}"
178
223
 
179
224
  export type {{ Arguments, Data }}
180
225
  export type Response = JsonResponse<Data>
@@ -182,49 +227,77 @@ export type Response = JsonResponse<Data>
182
227
 
183
228
  else:
184
229
  assert has_deprecated_result
185
- tsx_response_part = f"""import {{ {wrap_name} }} from "unc_base/api"
186
- import type {{ Arguments, DeprecatedResult }} from "{type_path}"
230
+ tsx_response_head = f"""import {{ {unc_base_api_imports} }} from "unc_base/api"
231
+ """
232
+ tsx_response_part = f"""import type {{ Arguments, DeprecatedResult }} from "{type_path}"
187
233
 
188
234
  export type {{ Arguments }}
189
235
  export type Response = DeprecatedResult
190
236
  """
191
237
 
192
- tsx_api = f"""{MODIFY_NOTICE}
193
- {data_loader_head}{tsx_response_part}
238
+ """
239
+
240
+ export const apiCall = buildWrappedGetCall<Arguments, Response>(
241
+ appSpecificApiPath({
242
+ [ApplicationT.FrontendApplication.materials]: "api/materials/common/list_id_source",
243
+ }),
244
+ )
245
+
246
+
247
+ """
248
+
249
+ if not has_multiple_endpoints:
250
+ default_endpoint_path = endpoint.path_per_api_endpoint[
251
+ endpoint.default_endpoint_key
252
+ ]
253
+ endpoint_path_part = f'"{default_endpoint_path.path_root}/{default_endpoint_path.path_dirname}/{default_endpoint_path.path_basename}",'
254
+ else:
255
+ path_lookup_map = ""
256
+ api_endpoint_key: EndpointKey
257
+ endpoint_specific_path: EndpointSpecificPath
258
+ for (
259
+ api_endpoint_key,
260
+ endpoint_specific_path,
261
+ ) in endpoint.path_per_api_endpoint.items():
262
+ full_path = f"{endpoint_specific_path.path_root}/{endpoint_specific_path.path_dirname}/{endpoint_specific_path.path_basename}"
263
+ frontend_app_value = config.endpoint_to_frontend_app_type[api_endpoint_key]
264
+
265
+ path_lookup_map += (
266
+ f'\n [ApplicationT.{frontend_app_value}]: "{full_path}",'
267
+ )
268
+
269
+ endpoint_path_part = f"""appSpecificApiPath({{{path_lookup_map}
270
+ }}),"""
271
+
272
+ # tsx_api = f"""{MODIFY_NOTICE}
273
+ tsx_api = f"""{MODIFY_NOTICE}{paths_string}
274
+ {tsx_response_head}{data_loader_head}{unc_types_imports}{tsx_response_part}
194
275
  export const apiCall = {wrap_call}(
195
- "{endpoint.path_root}/{endpoint.path_dirname}/{endpoint.path_basename}",
276
+ {endpoint_path_part}{path_mapping_part}
196
277
  )
197
278
  {data_loader_body}"""
198
279
 
199
- output = f"{config.routes_output}/{'/'.join(namespace.path)}.tsx"
280
+ output = f"{config.endpoint_to_routes_output[endpoint.default_endpoint_key]}/{'/'.join(namespace.path)}.tsx"
200
281
  util.rewrite_file(output, tsx_api)
201
282
 
202
283
  # Hacky index support, until enough is migrated to regen entirely
203
284
  # Emits the import into the UI API index file
204
- index_path = f"{config.routes_output}/{'/'.join(namespace.path[0:-1])}/index.tsx"
285
+ index_path = f"{config.endpoint_to_routes_output[endpoint.default_endpoint_key]}/{'/'.join(namespace.path[0:-1])}/index.tsx"
205
286
  api_name = f"Api{ts_type_name(namespace.path[0 - 1])}"
206
287
  if os.path.exists(index_path):
207
- with open(index_path) as index:
288
+ with open(index_path, encoding="utf-8") as index:
208
289
  index_data = index.read()
209
290
  need_index = index_data.find(api_name) == -1
210
291
  else:
211
292
  need_index = True
212
293
 
213
294
  if need_index:
214
- with open(index_path, "a") as index:
295
+ with open(index_path, "a", encoding="utf-8") as index:
215
296
  print(f"Updated API Index {index_path}")
216
297
  index.write(f'import * as {api_name} from "./{namespace.path[-1]}"\n\n')
217
298
  index.write(f"export {{ {api_name} }}\n")
218
299
 
219
300
 
220
- def _emit_constant(ctx: EmitTypescriptContext, sconst: builder.SpecConstant) -> None:
221
- ctx.out.write("\n\n")
222
- ctx.out.write(MODIFY_NOTICE)
223
- value = emit_value_ts(ctx, sconst.value_type, sconst.value)
224
- const_name = sconst.name.upper()
225
- ctx.out.write(f"export const {const_name} = {value}\n")
226
-
227
-
228
301
  def _emit_id_source(builder: builder.SpecBuilder, config: TypeScriptConfig) -> None:
229
302
  id_source_output = config.id_source_output
230
303
  if id_source_output is None:
@@ -3,12 +3,12 @@ import typing
3
3
  from dataclasses import dataclass, field
4
4
 
5
5
  from . import builder, util
6
+ from .builder_types import CrossOutputPaths
6
7
 
7
8
  INDENT = " "
8
9
 
9
10
  MODIFY_NOTICE = "// DO NOT MODIFY -- This file is generated by type_spec\n"
10
11
 
11
-
12
12
  base_name_map = {
13
13
  builder.BaseTypeName.s_boolean: "boolean",
14
14
  builder.BaseTypeName.s_date: "string", # IMPROVE: Aliased DateStr
@@ -31,6 +31,8 @@ class EmitTypescriptContext:
31
31
  out: io.StringIO
32
32
  namespace: builder.SpecNamespace
33
33
  namespaces: set[builder.SpecNamespace] = field(default_factory=set)
34
+ cross_output_paths: CrossOutputPaths | None = None
35
+ api_endpoints: dict[builder.EndpointKey, builder.APIEndpointInfo]
34
36
 
35
37
 
36
38
  def ts_type_name(name: str) -> str:
@@ -49,7 +51,10 @@ def ts_name(name: str, name_case: builder.NameCase) -> str:
49
51
 
50
52
 
51
53
  def emit_value_ts(
52
- ctx: EmitTypescriptContext, stype: builder.SpecType, value: typing.Any
54
+ ctx: EmitTypescriptContext,
55
+ stype: builder.SpecType,
56
+ value: typing.Any,
57
+ indent: int = 0,
53
58
  ) -> str:
54
59
  """Mimics emit_python even if not all types are used in TypeScript yet"""
55
60
  literal = builder.unwrap_literal_type(stype)
@@ -79,16 +84,24 @@ def emit_value_ts(
79
84
  if stype.defn_type.is_base_type(builder.BaseTypeName.s_dict):
80
85
  key_type = stype.parameters[0]
81
86
  value_type = stype.parameters[1]
87
+
88
+ if not key_type.is_base_type(
89
+ builder.BaseTypeName.s_string
90
+ ) and not isinstance(key_type, builder.SpecTypeDefnStringEnum):
91
+ raise Exception("invalid dict keys -- dict keys must be string or enum")
92
+
82
93
  return (
83
- "{\n\t"
84
- + ",\n\t".join(
85
- "["
86
- + emit_value_ts(ctx, key_type, dkey)
87
- + "]: "
88
- + emit_value_ts(ctx, value_type, dvalue)
94
+ f"{{\n{INDENT * (indent + 1)}"
95
+ + f",\n{INDENT * (indent + 1)}".join(
96
+ (
97
+ f"[{emit_value_ts(ctx, key_type, dkey)}]: "
98
+ if not key_type.is_base_type(builder.BaseTypeName.s_string)
99
+ else f"{dkey}: "
100
+ )
101
+ + emit_value_ts(ctx, value_type, dvalue, indent=indent + 1)
89
102
  for dkey, dvalue in value.items()
90
103
  )
91
- + "\n}"
104
+ + f"\n{INDENT * (indent)}}}"
92
105
  )
93
106
 
94
107
  if stype.defn_type.is_base_type(builder.BaseTypeName.s_optional):
@@ -99,8 +112,35 @@ def emit_value_ts(
99
112
 
100
113
  elif isinstance(stype, builder.SpecTypeDefnStringEnum):
101
114
  return f"{refer_to(ctx, stype)}.{ts_enum_name(value, stype.name_case)}"
115
+ elif isinstance(stype, builder.SpecTypeDefnObject):
116
+ assert isinstance(value, dict), (
117
+ f"Expected dict value for {stype.name} but got {value}"
118
+ )
119
+ obj_out = "{"
120
+ did_emit = False
121
+ for prop_name, prop in (stype.properties or {}).items():
122
+ if prop_name not in value and prop.has_default:
123
+ value_to_emit = prop.default
124
+ elif prop_name not in value:
125
+ continue
126
+ else:
127
+ value_to_emit = value[prop_name]
128
+ did_emit = True
129
+ typescript_name = ts_name(prop.name, prop.name_case)
130
+ obj_out += f"\n{INDENT * (indent + 1)}{typescript_name}: {emit_value_ts(ctx, prop.spec_type, value_to_emit, indent=indent + 1)},"
131
+ whitespace = f"\n{INDENT * indent}" if did_emit else ""
132
+ obj_out += f"{whitespace}}} as const"
133
+ return obj_out
102
134
 
103
- raise Exception("invalid constant type", value, stype)
135
+ raise Exception("invalid constant type", value, stype, type(stype))
136
+
137
+
138
+ def emit_constant_ts(ctx: EmitTypescriptContext, sconst: builder.SpecConstant) -> None:
139
+ ctx.out.write("\n\n")
140
+ ctx.out.write(MODIFY_NOTICE)
141
+ value = emit_value_ts(ctx, sconst.value_type, sconst.value)
142
+ const_name = sconst.name.upper()
143
+ ctx.out.write(f"export const {const_name} = {value}\n")
104
144
 
105
145
 
106
146
  def emit_type_ts(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
@@ -115,6 +155,7 @@ def emit_type_ts(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
115
155
 
116
156
  if isinstance(stype, builder.SpecTypeDefnExternal):
117
157
  assert not stype.is_exported, "expecting private names"
158
+ ctx.out.write("\n")
118
159
  ctx.out.write(stype.external_map["ts"])
119
160
  ctx.out.write("\n")
120
161
  return
@@ -263,3 +304,18 @@ def emit_namespace_imports_ts(
263
304
  )
264
305
  import_from = f"{import_path}{resolve_namespace_name(ns)}"
265
306
  out.write(f'import * as {import_as} from "{import_from}"\n') # noqa: E501
307
+
308
+
309
+ def emit_namespace_imports_from_root_ts(
310
+ namespaces: set[builder.SpecNamespace],
311
+ out: io.StringIO,
312
+ root: str,
313
+ ) -> None:
314
+ for ns in sorted(
315
+ namespaces,
316
+ key=lambda name: resolve_namespace_name(name),
317
+ ):
318
+ import_as = resolve_namespace_ref(ns)
319
+ out.write(
320
+ f'import * as {import_as} from "{root}/{resolve_namespace_name(ns)}"\n'
321
+ ) # noqa: E501
@@ -1,13 +1,13 @@
1
1
  import os
2
2
  from collections.abc import Callable
3
3
  from io import StringIO
4
- from typing import Optional
5
4
 
6
5
  from shelljob import fs
7
6
 
8
7
  from pkgs.serialization import yaml
9
8
 
10
9
  from .builder import SpecBuilder
10
+ from .builder_types import CrossOutputPaths
11
11
  from .config import Config
12
12
 
13
13
  ext_map = {
@@ -41,9 +41,22 @@ def find_and_handle_files(
41
41
  handler(file_name, file.read())
42
42
 
43
43
 
44
- def load_types(config: Config) -> Optional[SpecBuilder]:
44
+ def load_types(config: Config) -> SpecBuilder | None:
45
+ if config.typescript is not None and config.python is not None:
46
+ cross_output_paths = CrossOutputPaths(
47
+ python_types_output=config.python.types_output,
48
+ typescript_types_output=config.typescript.types_output,
49
+ typescript_routes_output_by_endpoint=config.typescript.endpoint_to_routes_output,
50
+ typespec_files_input=config.type_spec_types,
51
+ # IMPROVE not sure how to know which one is the correct one in emit_typescript
52
+ )
53
+ else:
54
+ cross_output_paths = None
55
+
45
56
  builder = SpecBuilder(
46
- api_endpoints=config.api_endpoint, top_namespace=config.top_namespace
57
+ api_endpoints=config.api_endpoint,
58
+ top_namespace=config.top_namespace,
59
+ cross_output_paths=cross_output_paths,
47
60
  )
48
61
 
49
62
  def handle_builder_add(
@@ -0,0 +1,14 @@
1
+ NON_DISCRIMINATED_UNION_EXCEPTIONS = [
2
+ "generate_tool_parameters.UnionWithoutDiscrim",
3
+ "output_calculation_entities.ConditionParameterFilterCondition",
4
+ "output_parameters.AnalyticalMethodParameterOptions",
5
+ "output_parameters.AnalyticalMethodLinkedOptionValue",
6
+ "recipes_redirect.RecipesRedirectResult",
7
+ "value_spec.ResolvedPathAll",
8
+ "weighted_sum.WeightedSumEntities",
9
+ "workflows.WorkflowTotalDisplay",
10
+ "type_info.TypeFormActionConstraint",
11
+ "structured_loading.CompatibleFilterNode",
12
+ "structured_display_element.DisplayElementColumn",
13
+ "field_values.FieldValue",
14
+ ]
@@ -1,15 +1,23 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import StrEnum
3
- from typing import Optional
3
+
4
+ from pkgs.serialization_util import JsonValue
4
5
 
5
6
 
6
7
  class OpenAPIType(ABC):
7
8
  description: str | None = None
8
9
  nullable: bool = False
10
+ default: JsonValue
9
11
 
10
- def __init__(self, description: str | None = None, nullable: bool = False) -> None:
12
+ def __init__(
13
+ self,
14
+ description: str | None = None,
15
+ nullable: bool = False,
16
+ default: JsonValue = None,
17
+ ) -> None:
11
18
  self.description = description
12
19
  self.nullable = nullable
20
+ self.default = default
13
21
 
14
22
  @abstractmethod
15
23
  def asdict(self) -> dict[str, object]:
@@ -20,6 +28,8 @@ class OpenAPIType(ABC):
20
28
  emitted["description"] = self.description
21
29
  if self.nullable:
22
30
  emitted["nullable"] = self.nullable
31
+ if self.default is not None:
32
+ emitted["default"] = self.default
23
33
  return emitted
24
34
 
25
35
 
@@ -158,7 +168,7 @@ class OpenAPIObjectType(OpenAPIType):
158
168
  description: str | None = None,
159
169
  nullable: bool = False,
160
170
  *,
161
- property_desc: Optional[dict[str, str]] = None,
171
+ property_desc: dict[str, str] | None = None,
162
172
  ) -> None:
163
173
  self.properties = properties
164
174
  if property_desc is None:
@@ -213,13 +223,28 @@ class OpenAPIUnionType(OpenAPIType):
213
223
  base_types: list[OpenAPIType],
214
224
  description: str | None = None,
215
225
  nullable: bool = False,
226
+ discriminator: str | None = None,
227
+ discriminator_map: dict[str, OpenAPIRefType] | None = None,
216
228
  ) -> None:
217
229
  self.base_types = base_types
230
+ self._discriminator = discriminator
231
+ self._discriminator_map = discriminator_map
218
232
  super().__init__(description=description, nullable=nullable)
219
233
 
220
234
  def asdict(self) -> dict[str, object]:
221
235
  # TODO: use parents description and nullable
222
- return {"oneOf": [base_type.asdict() for base_type in self.base_types]}
236
+ return {
237
+ "oneOf": [base_type.asdict() for base_type in self.base_types],
238
+ "discriminator": {
239
+ "propertyName": self._discriminator,
240
+ "mapping": {
241
+ discriminator_value: base_type.source
242
+ for discriminator_value, base_type in self._discriminator_map.items()
243
+ },
244
+ }
245
+ if self._discriminator is not None and self._discriminator_map is not None
246
+ else None,
247
+ }
223
248
 
224
249
 
225
250
  class OpenAPIIntersectionType(OpenAPIType):
@@ -22,10 +22,16 @@ PureJsonScalar = Union[str, float, bool, None]
22
22
  # Regular expressions for identifying ref names and IDs. Ref names should be
23
23
  # using this regular expression as a constriant in the database.
24
24
  REF_NAME_REGEX = r"^[a-zA-Z0-9_/-]+$"
25
+ REF_NAME_STRICT_REGEX_STRING = "^[a-zA-Z_][a-zA-Z0-9_]*$"
26
+ REF_NAME_STRICT_REGEX = rf"{REF_NAME_STRICT_REGEX_STRING}"
25
27
  # Ids matching a strict integer number are converted to integers
26
28
  ID_REGEX = r"-?[1-9][0-9]{0,20}"
27
29
 
28
30
 
31
+ # ENABLE_SLOTS should be removed after slots have been tested locally
32
+ import os
33
+ ENABLE_SLOTS = os.environ.get("UNC_ENABLE_DATACLASS_SLOTS") == "true"
34
+
29
35
  if TYPE_CHECKING:
30
36
  JsonValue = Union[JsonScalar, Mapping[str, "JsonValue"], Sequence["JsonValue"]]
31
37
  ExtJsonValue = JsonValue
@@ -52,15 +58,12 @@ def is_pure_json_value(value: ExtJsonValue) -> bool:
52
58
  return True
53
59
 
54
60
  if isinstance(value, list):
55
- for item in value:
56
- if not is_pure_json_value(item):
57
- return False
58
- return True
61
+ return all(is_pure_json_value(item) for item in value)
59
62
 
60
63
  if isinstance(value, dict):
61
- for key, item in value.items():
62
- if not is_pure_json_value(key) or not is_pure_json_value(item):
63
- return False
64
- return True
64
+ return all(
65
+ is_pure_json_value(key) and is_pure_json_value(item)
66
+ for key, item in value.items()
67
+ )
65
68
 
66
69
  return False
@@ -3,6 +3,7 @@
3
3
  // doesn't allow referring explicitly to global names (thus cannot override here)
4
4
  // IMPROVE: invert relationship for global.d.ts looks here instead
5
5
  import * as IO from 'io-ts';
6
+
6
7
  type localJsonScalar = JsonScalar
7
8
  type localJsonValue = JsonValue
8
9
  type localObjectId = ObjectId
@@ -28,3 +29,6 @@ export const IOJsonValue: IO.Type<JsonValue> = IO.recursion('JsonValue', () =>
28
29
  export interface nominal<T> {
29
30
  "nominal structural brand": T
30
31
  }
32
+
33
+ // Ids matching a strict integer number are converted to integers
34
+ export const ID_REGEX = /^-?[1-9][0-9]{0,20}$/
@@ -9,7 +9,7 @@ import sys
9
9
 
10
10
  from ..config import parse_yaml_config
11
11
  from ..load_types import load_types
12
- from .emit_type_info import emit_type_info
12
+ from .emit_type_info import emit_type_info, emit_type_info_python
13
13
 
14
14
 
15
15
  def main() -> bool:
@@ -26,6 +26,8 @@ def main() -> bool:
26
26
 
27
27
  assert config.typescript is not None
28
28
  emit_type_info(builder, config.typescript.type_info_output)
29
+ if config.python.type_info_output is not None:
30
+ emit_type_info_python(builder, config.python.type_info_output)
29
31
 
30
32
  return True
31
33