UncountablePythonSDK 0.0.24__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 (373) hide show
  1. docs/conf.py +60 -8
  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 +7 -5
  8. examples/async_batch.py +5 -6
  9. examples/basic_auth.py +7 -0
  10. examples/create_entity.py +4 -6
  11. examples/create_ingredient_sdk.py +34 -0
  12. examples/download_files.py +26 -0
  13. examples/edit_recipe_inputs.py +50 -0
  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 +26 -0
  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 +2 -3
  29. pkgs/argument_parser/__init__.py +9 -0
  30. pkgs/argument_parser/_is_namedtuple.py +3 -0
  31. pkgs/argument_parser/argument_parser.py +295 -74
  32. pkgs/argument_parser/case_convert.py +4 -3
  33. pkgs/filesystem_utils/__init__.py +20 -0
  34. pkgs/filesystem_utils/_blob_session.py +144 -0
  35. pkgs/filesystem_utils/_gdrive_session.py +309 -0
  36. pkgs/filesystem_utils/_local_session.py +69 -0
  37. pkgs/filesystem_utils/_s3_session.py +118 -0
  38. pkgs/filesystem_utils/_sftp_session.py +151 -0
  39. pkgs/filesystem_utils/file_type_utils.py +91 -0
  40. pkgs/filesystem_utils/filesystem_session.py +39 -0
  41. pkgs/py.typed +0 -0
  42. pkgs/serialization/__init__.py +8 -1
  43. pkgs/serialization/annotation.py +64 -0
  44. pkgs/serialization/missing_sentry.py +1 -1
  45. pkgs/serialization/opaque_key.py +1 -1
  46. pkgs/serialization/serial_alias.py +47 -0
  47. pkgs/serialization/serial_class.py +69 -54
  48. pkgs/serialization/serial_generic.py +16 -0
  49. pkgs/serialization/serial_union.py +84 -0
  50. pkgs/serialization/yaml.py +57 -0
  51. pkgs/serialization_util/__init__.py +7 -7
  52. pkgs/serialization_util/convert_to_snakecase.py +27 -0
  53. pkgs/serialization_util/dataclasses.py +14 -0
  54. pkgs/serialization_util/serialization_helpers.py +117 -71
  55. pkgs/type_spec/actions_registry/__main__.py +0 -4
  56. pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
  57. pkgs/type_spec/builder.py +438 -109
  58. pkgs/type_spec/builder_types.py +9 -0
  59. pkgs/type_spec/config.py +52 -24
  60. pkgs/type_spec/cross_output_links.py +99 -0
  61. pkgs/type_spec/emit_io_ts.py +1 -1
  62. pkgs/type_spec/emit_open_api.py +160 -41
  63. pkgs/type_spec/emit_open_api_util.py +13 -7
  64. pkgs/type_spec/emit_python.py +450 -136
  65. pkgs/type_spec/emit_typescript.py +117 -250
  66. pkgs/type_spec/emit_typescript_util.py +293 -4
  67. pkgs/type_spec/load_types.py +20 -5
  68. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  69. pkgs/type_spec/open_api_util.py +29 -4
  70. pkgs/type_spec/parts/base.py.prepart +13 -10
  71. pkgs/type_spec/parts/base.ts.prepart +4 -0
  72. pkgs/type_spec/type_info/__main__.py +3 -1
  73. pkgs/type_spec/type_info/emit_type_info.py +161 -32
  74. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  75. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  76. pkgs/type_spec/util.py +4 -4
  77. pkgs/type_spec/value_spec/__main__.py +27 -10
  78. pkgs/type_spec/value_spec/convert_type.py +21 -1
  79. pkgs/type_spec/value_spec/emit_python.py +25 -7
  80. pkgs/type_spec/value_spec/types.py +1 -1
  81. uncountable/__init__.py +1 -2
  82. uncountable/core/__init__.py +11 -3
  83. uncountable/core/async_batch.py +16 -1
  84. uncountable/core/client.py +247 -52
  85. uncountable/core/environment.py +41 -0
  86. uncountable/core/file_upload.py +67 -22
  87. uncountable/core/types.py +8 -13
  88. uncountable/integration/cli.py +142 -0
  89. uncountable/integration/construct_client.py +43 -27
  90. uncountable/integration/cron.py +12 -11
  91. uncountable/integration/db/connect.py +12 -2
  92. uncountable/integration/db/session.py +25 -0
  93. uncountable/integration/entrypoint.py +4 -34
  94. uncountable/integration/executors/executors.py +147 -0
  95. uncountable/integration/executors/generic_upload_executor.py +336 -0
  96. uncountable/integration/executors/script_executor.py +15 -9
  97. uncountable/integration/http_server/__init__.py +5 -0
  98. uncountable/integration/http_server/types.py +69 -0
  99. uncountable/integration/job.py +246 -19
  100. uncountable/integration/queue_runner/__init__.py +0 -0
  101. uncountable/integration/queue_runner/command_server/__init__.py +28 -0
  102. uncountable/integration/queue_runner/command_server/command_client.py +133 -0
  103. uncountable/integration/queue_runner/command_server/command_server.py +142 -0
  104. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  105. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  106. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
  107. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
  108. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
  109. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
  110. uncountable/integration/queue_runner/command_server/types.py +75 -0
  111. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  112. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
  113. uncountable/integration/queue_runner/datastore/interface.py +29 -0
  114. uncountable/integration/queue_runner/datastore/model.py +24 -0
  115. uncountable/integration/queue_runner/job_scheduler.py +200 -0
  116. uncountable/integration/queue_runner/queue_runner.py +34 -0
  117. uncountable/integration/queue_runner/types.py +7 -0
  118. uncountable/integration/queue_runner/worker.py +116 -0
  119. uncountable/integration/scan_profiles.py +67 -0
  120. uncountable/integration/scheduler.py +199 -0
  121. uncountable/integration/secret_retrieval/__init__.py +3 -0
  122. uncountable/integration/secret_retrieval/retrieve_secret.py +93 -0
  123. uncountable/integration/server.py +103 -54
  124. uncountable/integration/telemetry.py +251 -0
  125. uncountable/integration/webhook_server/entrypoint.py +97 -0
  126. uncountable/types/__init__.py +149 -30
  127. uncountable/types/api/batch/execute_batch.py +16 -9
  128. uncountable/types/api/batch/execute_batch_load_async.py +13 -7
  129. uncountable/types/api/chemical/convert_chemical_formats.py +20 -8
  130. uncountable/types/api/condition_parameters/__init__.py +1 -0
  131. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  132. uncountable/types/api/entity/create_entities.py +24 -12
  133. uncountable/types/api/entity/create_entity.py +22 -13
  134. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  135. uncountable/types/api/entity/export_entities.py +59 -0
  136. uncountable/types/api/entity/get_entities_data.py +18 -9
  137. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  138. uncountable/types/api/entity/list_aggregate.py +79 -0
  139. uncountable/types/api/entity/list_entities.py +53 -14
  140. uncountable/types/api/entity/lock_entity.py +45 -0
  141. uncountable/types/api/entity/lookup_entity.py +116 -0
  142. uncountable/types/api/entity/resolve_entity_ids.py +19 -10
  143. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  144. uncountable/types/api/entity/set_values.py +15 -8
  145. uncountable/types/api/entity/transition_entity_phase.py +27 -12
  146. uncountable/types/api/entity/unlock_entity.py +44 -0
  147. uncountable/types/api/equipment/__init__.py +1 -0
  148. uncountable/types/api/equipment/associate_equipment_input.py +43 -0
  149. uncountable/types/api/field_options/__init__.py +1 -0
  150. uncountable/types/api/field_options/upsert_field_options.py +55 -0
  151. uncountable/types/api/files/__init__.py +1 -0
  152. uncountable/types/api/files/download_file.py +77 -0
  153. uncountable/types/api/id_source/list_id_source.py +20 -11
  154. uncountable/types/api/id_source/match_id_source.py +15 -10
  155. uncountable/types/api/input_groups/get_input_group_names.py +16 -7
  156. uncountable/types/api/inputs/create_inputs.py +28 -14
  157. uncountable/types/api/inputs/get_input_data.py +34 -16
  158. uncountable/types/api/inputs/get_input_names.py +19 -10
  159. uncountable/types/api/inputs/get_inputs_data.py +29 -11
  160. uncountable/types/api/inputs/set_input_attribute_values.py +16 -10
  161. uncountable/types/api/inputs/set_input_category.py +44 -0
  162. uncountable/types/api/inputs/set_input_subcategories.py +45 -0
  163. uncountable/types/api/inputs/set_intermediate_type.py +50 -0
  164. uncountable/types/api/integrations/__init__.py +1 -0
  165. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  166. uncountable/types/api/integrations/push_notification.py +49 -0
  167. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  168. uncountable/types/api/listing/__init__.py +1 -0
  169. uncountable/types/api/listing/fetch_listing.py +58 -0
  170. uncountable/types/api/material_families/__init__.py +1 -0
  171. uncountable/types/api/material_families/update_entity_material_families.py +47 -0
  172. uncountable/types/api/notebooks/__init__.py +1 -0
  173. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  174. uncountable/types/api/outputs/get_output_data.py +32 -17
  175. uncountable/types/api/outputs/get_output_names.py +18 -9
  176. uncountable/types/api/outputs/get_output_organization.py +173 -0
  177. uncountable/types/api/outputs/resolve_output_conditions.py +23 -11
  178. uncountable/types/api/permissions/set_core_permissions.py +31 -15
  179. uncountable/types/api/project/get_projects.py +20 -11
  180. uncountable/types/api/project/get_projects_data.py +23 -14
  181. uncountable/types/api/recipe_links/create_recipe_link.py +17 -10
  182. uncountable/types/api/recipe_links/remove_recipe_link.py +45 -0
  183. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +19 -10
  184. uncountable/types/api/recipes/add_recipe_to_project.py +42 -0
  185. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  186. uncountable/types/api/recipes/archive_recipes.py +14 -7
  187. uncountable/types/api/recipes/associate_recipe_as_input.py +16 -8
  188. uncountable/types/api/recipes/associate_recipe_as_lot.py +14 -7
  189. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  190. uncountable/types/api/recipes/create_mix_order.py +44 -0
  191. uncountable/types/api/recipes/create_recipe.py +21 -14
  192. uncountable/types/api/recipes/create_recipes.py +25 -13
  193. uncountable/types/api/recipes/disassociate_recipe_as_input.py +14 -7
  194. uncountable/types/api/recipes/edit_recipe_inputs.py +208 -19
  195. uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
  196. uncountable/types/api/recipes/get_curve.py +15 -9
  197. uncountable/types/api/recipes/get_recipe_calculations.py +17 -11
  198. uncountable/types/api/recipes/get_recipe_links.py +14 -8
  199. uncountable/types/api/recipes/get_recipe_names.py +16 -7
  200. uncountable/types/api/recipes/get_recipe_output_metadata.py +16 -10
  201. uncountable/types/api/recipes/get_recipes_data.py +96 -45
  202. uncountable/types/api/recipes/lock_recipes.py +64 -0
  203. uncountable/types/api/recipes/remove_recipe_from_project.py +42 -0
  204. uncountable/types/api/recipes/set_recipe_inputs.py +19 -13
  205. uncountable/types/api/recipes/set_recipe_metadata.py +14 -7
  206. uncountable/types/api/recipes/set_recipe_output_annotations.py +114 -0
  207. uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
  208. uncountable/types/api/recipes/set_recipe_outputs.py +40 -15
  209. uncountable/types/api/recipes/set_recipe_tags.py +30 -13
  210. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  211. uncountable/types/api/recipes/unarchive_recipes.py +41 -0
  212. uncountable/types/api/recipes/unlock_recipes.py +51 -0
  213. uncountable/types/api/runsheet/__init__.py +1 -0
  214. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  215. uncountable/types/api/triggers/run_trigger.py +15 -8
  216. uncountable/types/api/uploader/__init__.py +1 -0
  217. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  218. uncountable/types/api/uploader/invoke_uploader.py +46 -0
  219. uncountable/types/api/user/__init__.py +1 -0
  220. uncountable/types/api/user/get_current_user_info.py +40 -0
  221. uncountable/types/async_batch.py +8 -52
  222. uncountable/types/async_batch_processor.py +694 -18
  223. uncountable/types/async_batch_t.py +108 -0
  224. uncountable/types/async_jobs.py +8 -0
  225. uncountable/types/async_jobs_t.py +52 -0
  226. uncountable/types/auth_retrieval.py +11 -0
  227. uncountable/types/auth_retrieval_t.py +75 -0
  228. uncountable/types/base.py +5 -80
  229. uncountable/types/base_t.py +87 -0
  230. uncountable/types/calculations.py +3 -19
  231. uncountable/types/calculations_t.py +26 -0
  232. uncountable/types/chemical_structure.py +3 -23
  233. uncountable/types/chemical_structure_t.py +28 -0
  234. uncountable/types/client_base.py +1170 -88
  235. uncountable/types/client_config.py +8 -0
  236. uncountable/types/client_config_t.py +36 -0
  237. uncountable/types/curves.py +5 -43
  238. uncountable/types/curves_t.py +50 -0
  239. uncountable/types/data.py +12 -0
  240. uncountable/types/data_t.py +103 -0
  241. uncountable/types/entity.py +8 -270
  242. uncountable/types/entity_t.py +446 -0
  243. uncountable/types/experiment_groups.py +3 -19
  244. uncountable/types/experiment_groups_t.py +26 -0
  245. uncountable/types/exports.py +8 -0
  246. uncountable/types/exports_t.py +34 -0
  247. uncountable/types/field_values.py +25 -61
  248. uncountable/types/field_values_t.py +302 -0
  249. uncountable/types/fields.py +3 -20
  250. uncountable/types/fields_t.py +27 -0
  251. uncountable/types/generic_upload.py +14 -0
  252. uncountable/types/generic_upload_t.py +119 -0
  253. uncountable/types/id_source.py +7 -45
  254. uncountable/types/id_source_t.py +68 -0
  255. uncountable/types/identifier.py +6 -50
  256. uncountable/types/identifier_t.py +62 -0
  257. uncountable/types/input_attributes.py +3 -25
  258. uncountable/types/input_attributes_t.py +29 -0
  259. uncountable/types/inputs.py +6 -57
  260. uncountable/types/inputs_t.py +82 -0
  261. uncountable/types/integration_server.py +8 -0
  262. uncountable/types/integration_server_t.py +46 -0
  263. uncountable/types/integration_session.py +10 -0
  264. uncountable/types/integration_session_t.py +60 -0
  265. uncountable/types/integrations.py +10 -0
  266. uncountable/types/integrations_t.py +62 -0
  267. uncountable/types/job_definition.py +28 -0
  268. uncountable/types/job_definition_t.py +285 -0
  269. uncountable/types/listing.py +9 -0
  270. uncountable/types/listing_t.py +51 -0
  271. uncountable/types/notices.py +8 -0
  272. uncountable/types/notices_t.py +37 -0
  273. uncountable/types/notifications.py +11 -0
  274. uncountable/types/notifications_t.py +74 -0
  275. uncountable/types/outputs.py +3 -22
  276. uncountable/types/outputs_t.py +29 -0
  277. uncountable/types/overrides.py +9 -0
  278. uncountable/types/overrides_t.py +49 -0
  279. uncountable/types/permissions.py +3 -42
  280. uncountable/types/permissions_t.py +45 -0
  281. uncountable/types/phases.py +3 -19
  282. uncountable/types/phases_t.py +26 -0
  283. uncountable/types/post_base.py +3 -26
  284. uncountable/types/post_base_t.py +29 -0
  285. uncountable/types/queued_job.py +17 -0
  286. uncountable/types/queued_job_t.py +140 -0
  287. uncountable/types/recipe_identifiers.py +7 -58
  288. uncountable/types/recipe_identifiers_t.py +75 -0
  289. uncountable/types/recipe_inputs.py +4 -26
  290. uncountable/types/recipe_inputs_t.py +29 -0
  291. uncountable/types/recipe_links.py +4 -46
  292. uncountable/types/recipe_links_t.py +53 -0
  293. uncountable/types/recipe_metadata.py +5 -48
  294. uncountable/types/recipe_metadata_t.py +57 -0
  295. uncountable/types/recipe_output_metadata.py +3 -20
  296. uncountable/types/recipe_output_metadata_t.py +27 -0
  297. uncountable/types/recipe_tags.py +3 -19
  298. uncountable/types/recipe_tags_t.py +26 -0
  299. uncountable/types/recipe_workflow_steps.py +9 -73
  300. uncountable/types/recipe_workflow_steps_t.py +95 -0
  301. uncountable/types/recipes.py +7 -0
  302. uncountable/types/recipes_t.py +25 -0
  303. uncountable/types/response.py +3 -21
  304. uncountable/types/response_t.py +26 -0
  305. uncountable/types/secret_retrieval.py +11 -0
  306. uncountable/types/secret_retrieval_t.py +75 -0
  307. uncountable/types/sockets.py +20 -0
  308. uncountable/types/sockets_t.py +169 -0
  309. uncountable/types/structured_filters.py +25 -0
  310. uncountable/types/structured_filters_t.py +248 -0
  311. uncountable/types/units.py +3 -19
  312. uncountable/types/units_t.py +26 -0
  313. uncountable/types/uploader.py +24 -0
  314. uncountable/types/uploader_t.py +222 -0
  315. uncountable/types/users.py +3 -20
  316. uncountable/types/users_t.py +27 -0
  317. uncountable/types/webhook_job.py +9 -0
  318. uncountable/types/webhook_job_t.py +48 -0
  319. uncountable/types/workflows.py +4 -28
  320. uncountable/types/workflows_t.py +38 -0
  321. uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
  322. uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
  323. {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
  324. {UncountablePythonSDK-0.0.24.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -1
  325. UncountablePythonSDK-0.0.24.dist-info/METADATA +0 -47
  326. UncountablePythonSDK-0.0.24.dist-info/RECORD +0 -216
  327. docs/quickstart.md +0 -19
  328. examples/recipe-import/importer.py +0 -39
  329. type_spec/external/api/batch/execute_batch.yaml +0 -56
  330. type_spec/external/api/batch/execute_batch_load_async.yaml +0 -18
  331. type_spec/external/api/chemical/convert_chemical_formats.yaml +0 -33
  332. type_spec/external/api/entity/create_entities.yaml +0 -45
  333. type_spec/external/api/entity/create_entity.yaml +0 -51
  334. type_spec/external/api/entity/get_entities_data.yaml +0 -29
  335. type_spec/external/api/entity/list_entities.yaml +0 -52
  336. type_spec/external/api/entity/resolve_entity_ids.yaml +0 -29
  337. type_spec/external/api/entity/set_values.yaml +0 -18
  338. type_spec/external/api/entity/transition_entity_phase.yaml +0 -44
  339. type_spec/external/api/id_source/list_id_source.yaml +0 -35
  340. type_spec/external/api/id_source/match_id_source.yaml +0 -32
  341. type_spec/external/api/input_groups/get_input_group_names.yaml +0 -29
  342. type_spec/external/api/inputs/create_inputs.yaml +0 -48
  343. type_spec/external/api/inputs/get_input_data.yaml +0 -95
  344. type_spec/external/api/inputs/get_input_names.yaml +0 -38
  345. type_spec/external/api/inputs/get_inputs_data.yaml +0 -82
  346. type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -33
  347. type_spec/external/api/outputs/get_output_data.yaml +0 -92
  348. type_spec/external/api/outputs/get_output_names.yaml +0 -35
  349. type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -50
  350. type_spec/external/api/permissions/set_core_permissions.yaml +0 -69
  351. type_spec/external/api/project/get_projects.yaml +0 -42
  352. type_spec/external/api/project/get_projects_data.yaml +0 -50
  353. type_spec/external/api/recipe_links/create_recipe_link.yaml +0 -25
  354. type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -41
  355. type_spec/external/api/recipes/archive_recipes.yaml +0 -20
  356. type_spec/external/api/recipes/associate_recipe_as_input.yaml +0 -19
  357. type_spec/external/api/recipes/associate_recipe_as_lot.yaml +0 -19
  358. type_spec/external/api/recipes/create_recipe.yaml +0 -39
  359. type_spec/external/api/recipes/create_recipes.yaml +0 -47
  360. type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +0 -16
  361. type_spec/external/api/recipes/edit_recipe_inputs.yaml +0 -85
  362. type_spec/external/api/recipes/get_curve.yaml +0 -21
  363. type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -39
  364. type_spec/external/api/recipes/get_recipe_links.yaml +0 -26
  365. type_spec/external/api/recipes/get_recipe_names.yaml +0 -29
  366. type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -36
  367. type_spec/external/api/recipes/get_recipes_data.yaml +0 -244
  368. type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -42
  369. type_spec/external/api/recipes/set_recipe_metadata.yaml +0 -20
  370. type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -52
  371. type_spec/external/api/recipes/set_recipe_tags.yaml +0 -62
  372. type_spec/external/api/triggers/run_trigger.yaml +0 -18
  373. uncountable/integration/types.py +0 -89
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(kw_only=True, frozen=True)
5
+ class CrossOutputPaths:
6
+ python_types_output: str
7
+ typescript_types_output: str
8
+ typescript_routes_output_by_endpoint: dict[str, str]
9
+ typespec_files_input: list[str]
pkgs/type_spec/config.py CHANGED
@@ -1,10 +1,10 @@
1
1
  import os
2
2
  from collections.abc import Callable, Mapping
3
3
  from dataclasses import dataclass
4
- from decimal import Decimal
5
4
  from typing import Self, TypeVar
6
5
 
7
- import yaml
6
+ from pkgs.serialization import yaml
7
+ from pkgs.type_spec.builder import APIEndpointInfo, EndpointKey
8
8
 
9
9
  ConfigValueType = str | None | Mapping[str, str | None] | list[str]
10
10
 
@@ -20,6 +20,22 @@ def _parse_string_lookup(
20
20
  }
21
21
 
22
22
 
23
+ VT = TypeVar("VT")
24
+
25
+
26
+ def _parse_data_lookup(
27
+ key: str,
28
+ raw_value: ConfigValueType,
29
+ conv_func: type[VT],
30
+ ) -> dict[str, VT]:
31
+ assert isinstance(raw_value, dict), f"{key} must be key/values"
32
+ return {
33
+ k: conv_func(**v)
34
+ for k, v in raw_value.items()
35
+ if v is not None and isinstance(v, dict)
36
+ }
37
+
38
+
23
39
  @dataclass(kw_only=True)
24
40
  class BaseLanguageConfig:
25
41
  types_output: (
@@ -32,18 +48,28 @@ class BaseLanguageConfig:
32
48
 
33
49
  @dataclass(kw_only=True)
34
50
  class TypeScriptConfig(BaseLanguageConfig):
35
- routes_output: str # folder for generate route files will be located.
51
+ endpoint_to_routes_output: dict[
52
+ EndpointKey, str
53
+ ] # folder for generate route files will be located.
36
54
  type_info_output: str # folder for generated type info files
37
55
  id_source_output: str | None = None # folder for emitted id source maps.
56
+ endpoint_to_frontend_app_type: dict[
57
+ str, str
58
+ ] # map from api_endpoint to frontend app type
38
59
 
39
60
  def __post_init__(self: Self) -> None:
40
- self.routes_output = self.routes_output
61
+ self.endpoint_to_routes_output = self.endpoint_to_routes_output
41
62
  self.type_info_output = os.path.abspath(self.type_info_output)
42
63
  self.id_source_output = (
43
64
  os.path.abspath(self.id_source_output)
44
65
  if self.id_source_output is not None
45
66
  else None
46
67
  )
68
+ self.endpoint_to_frontend_app_type = _parse_string_lookup(
69
+ "typescript_endpoint_to_frontend_app_type",
70
+ self.endpoint_to_frontend_app_type,
71
+ lambda x: x,
72
+ )
47
73
 
48
74
 
49
75
  @dataclass(kw_only=True)
@@ -60,6 +86,7 @@ class PythonConfig(BaseLanguageConfig):
60
86
  emit_client_class: bool = False # emit the base class for the api client
61
87
  all_named_type_exports: bool = False # emit __all__ for all named type exports
62
88
  sdk_endpoints_only: bool = False # only emit is_sdk endpoints
89
+ type_info_output: str | None = None # folder for generated type info files
63
90
 
64
91
  def __post_init__(self: Self) -> None:
65
92
  self.routes_output = _parse_string_lookup(
@@ -71,6 +98,9 @@ class PythonConfig(BaseLanguageConfig):
71
98
  else None
72
99
  )
73
100
 
101
+ if self.type_info_output is not None:
102
+ self.type_info_output = os.path.abspath(self.type_info_output)
103
+
74
104
 
75
105
  @dataclass(kw_only=True)
76
106
  class OpenAPIConfig(BaseLanguageConfig):
@@ -87,45 +117,37 @@ class OpenAPIConfig(BaseLanguageConfig):
87
117
 
88
118
  @dataclass(kw_only=True)
89
119
  class Config:
120
+ top_namespace: str
90
121
  type_spec_types: list[str] # folders containing the yaml type spec definitions
91
- api_endpoint: dict[str, str]
122
+ api_endpoint: dict[str, APIEndpointInfo]
92
123
  # languages
93
124
  typescript: TypeScriptConfig | None
94
125
  python: PythonConfig
95
126
  open_api: OpenAPIConfig | None
96
127
 
97
128
 
98
- _T = TypeVar("_T")
129
+ T = TypeVar("T")
99
130
 
100
131
 
101
- def _parse_language(config_class: type[_T], raw_value: ConfigValueType) -> _T:
132
+ def _parse_language(config_class: type[T], raw_value: ConfigValueType) -> T:
102
133
  assert isinstance(raw_value, dict), "expecting language config to have key/values."
103
134
  return config_class(**raw_value)
104
135
 
105
136
 
106
- def _decimal_constructor(loader, node): # type:ignore
107
- value = loader.construct_scalar(node)
108
- return Decimal(value)
109
-
110
-
111
- # A semi-acceptable patch to force a number to be parsed as a decimal, since pyyaml
112
- # parses them as lossy floats otherwise. Though a bit ugly, at least this way we have
113
- # support for decimal constants
114
- yaml.SafeLoader.add_constructor("!decimal", _decimal_constructor)
115
-
116
-
117
137
  def parse_yaml_config(config_file: str) -> Config:
118
138
  with open(config_file, encoding="utf-8") as input:
119
139
  raw_config: dict[str, ConfigValueType] = yaml.safe_load(input)
120
140
 
121
141
  raw_type_spec_types = raw_config["type_spec_types"]
122
- assert isinstance(
123
- raw_type_spec_types, list
124
- ), "type_spec_types, must be a list of folders"
142
+ assert isinstance(raw_type_spec_types, list), (
143
+ "type_spec_types, must be a list of folders"
144
+ )
125
145
  type_spec_types = [os.path.abspath(folder) for folder in raw_type_spec_types]
126
146
 
127
- api_endpoint = _parse_string_lookup(
128
- "api_endpoint", raw_config.get("api_endpoint", {}), lambda x: x
147
+ api_endpoint = _parse_data_lookup(
148
+ "api_endpoint",
149
+ raw_config.get("api_endpoint", {}),
150
+ APIEndpointInfo,
129
151
  )
130
152
 
131
153
  raw_typescript = raw_config.get("typescript")
@@ -137,10 +159,16 @@ def parse_yaml_config(config_file: str) -> Config:
137
159
  python = _parse_language(PythonConfig, raw_config["python"])
138
160
  raw_open_api = raw_config.get("open_api")
139
161
  open_api = (
140
- _parse_language(OpenAPIConfig, raw_open_api) if raw_open_api is not None else None
162
+ _parse_language(OpenAPIConfig, raw_open_api)
163
+ if raw_open_api is not None
164
+ else None
141
165
  )
142
166
 
167
+ top_namespace = raw_config["top_namespace"]
168
+ assert isinstance(top_namespace, str)
169
+
143
170
  return Config(
171
+ top_namespace=top_namespace,
144
172
  type_spec_types=type_spec_types,
145
173
  api_endpoint=api_endpoint,
146
174
  typescript=typescript,
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ from . import builder
6
+ from .builder_types import CrossOutputPaths
7
+
8
+
9
+ def get_python_stub_file_path(
10
+ function_name: str | None,
11
+ ) -> str | None:
12
+ if function_name is None:
13
+ return None
14
+ module_dir, file_name, _func_name = function_name.rsplit(".", 2)
15
+ module_path = os.path.relpath(module_dir.replace(".", "/"))
16
+ api_stub_file = f"{module_path}/{file_name}.py"
17
+ return api_stub_file
18
+
19
+
20
+ def get_python_api_file_path(
21
+ cross_output_paths: CrossOutputPaths,
22
+ namespace: builder.SpecNamespace,
23
+ ) -> str:
24
+ return f"{cross_output_paths.python_types_output}/{'/'.join(namespace.path)}{'' if len(namespace.path) > 1 else '_t'}.py"
25
+
26
+
27
+ def get_typescript_api_file_path(
28
+ cross_output_paths: CrossOutputPaths,
29
+ namespace: builder.SpecNamespace,
30
+ endpoint_key: builder.EndpointKey,
31
+ ) -> str:
32
+ return f"{cross_output_paths.typescript_routes_output_by_endpoint[endpoint_key]}/{'/'.join(namespace.path)}.tsx"
33
+
34
+
35
+ def get_yaml_api_file_path(
36
+ cross_output_paths: CrossOutputPaths,
37
+ namespace: builder.SpecNamespace,
38
+ ) -> str:
39
+ abs_path = next(
40
+ (
41
+ path
42
+ for path in cross_output_paths.typespec_files_input
43
+ if (
44
+ namespace.endpoint is None
45
+ or namespace.endpoint.default_endpoint_key in path
46
+ )
47
+ ),
48
+ cross_output_paths.typespec_files_input[0],
49
+ )
50
+ return f"{os.path.relpath(abs_path)}/{'/'.join(namespace.path)}.yaml"
51
+
52
+
53
+ def get_return_to_root_path(path: str) -> str:
54
+ return "../" * (path.count("/"))
55
+
56
+
57
+ def get_path_links(
58
+ cross_output_paths: CrossOutputPaths | None,
59
+ namespace: builder.SpecNamespace,
60
+ *,
61
+ current_path_type: str,
62
+ endpoint: builder.SpecEndpoint,
63
+ ) -> str:
64
+ if cross_output_paths is None:
65
+ return ""
66
+
67
+ api_paths = {
68
+ "Python": get_python_api_file_path(cross_output_paths, namespace),
69
+ "TypeScript": get_typescript_api_file_path(
70
+ cross_output_paths, namespace, endpoint.default_endpoint_key
71
+ ),
72
+ "YAML": get_yaml_api_file_path(cross_output_paths, namespace),
73
+ }
74
+
75
+ assert current_path_type in api_paths
76
+
77
+ comment_prefix = "#"
78
+ if current_path_type == "TypeScript":
79
+ comment_prefix = "//"
80
+
81
+ return_to_root_path = get_return_to_root_path(api_paths[current_path_type])
82
+ del api_paths[current_path_type]
83
+
84
+ paths_string = ""
85
+ for path_name, path in api_paths.items():
86
+ paths_string += (
87
+ f"{comment_prefix} {path_name}: file://./{return_to_root_path}{path}\n"
88
+ )
89
+
90
+ if namespace.endpoint is not None:
91
+ for (
92
+ endpoint_key,
93
+ path_specific_endpoint,
94
+ ) in namespace.endpoint.path_per_api_endpoint.items():
95
+ path_from_root = get_python_stub_file_path(path_specific_endpoint.function)
96
+ if path_from_root is None:
97
+ continue
98
+ paths_string += f"{comment_prefix} Implementation for {endpoint_key}: file://./{return_to_root_path}{path_from_root}\n"
99
+ return paths_string
@@ -125,7 +125,7 @@ def refer_to_io_ts(
125
125
  spec = refer_to_io_ts(ctx, stype.parameters[0])
126
126
  return f"IO.array({spec})"
127
127
  if stype.defn_type.name == builder.BaseTypeName.s_union:
128
- return f'IO.union([{", ".join([refer_to_io_ts(ctx, p) for p in stype.parameters])}])'
128
+ return f"IO.union([{', '.join([refer_to_io_ts(ctx, p) for p in stype.parameters])}])"
129
129
  if stype.defn_type.name == builder.BaseTypeName.s_optional:
130
130
  return f"IO.optional({refer_to_io_ts(ctx, stype.parameters[0])})"
131
131
  if stype.defn_type.name == builder.BaseTypeName.s_tuple:
@@ -7,9 +7,11 @@ WORK-IN-PROGRESS, DON'T USE!
7
7
  import dataclasses
8
8
  import json
9
9
  import re
10
- from typing import Collection, cast
10
+ from enum import StrEnum
11
+ from typing import Collection, assert_never, cast
11
12
 
12
- import yaml
13
+ from pkgs.serialization import yaml
14
+ from pkgs.serialization_util import serialize_for_api
13
15
 
14
16
  from . import builder, util
15
17
  from .builder import EndpointGuideKey, RootGuideKey
@@ -23,6 +25,7 @@ from .emit_open_api_util import (
23
25
  EmitOpenAPIGuide,
24
26
  EmitOpenAPIPath,
25
27
  EmitOpenAPIServer,
28
+ EmitOpenAPIStabilityLevel,
26
29
  EmitOpenAPITag,
27
30
  GlobalContextInfo,
28
31
  TagGroupToNamedTags,
@@ -60,10 +63,14 @@ base_name_map = {
60
63
  }
61
64
 
62
65
 
66
+ class OpenAPIDefaultBehavior(StrEnum):
67
+ OPTIONAL_WITH_DEFAULT = "optional_with_default"
68
+
69
+
63
70
  def _rewrite_with_notice(
64
71
  file_path: str, file_content: str, *, notice: str = MODIFY_NOTICE
65
72
  ) -> bool:
66
- pattern = re.compile("^\S", re.MULTILINE)
73
+ pattern = re.compile(r"^\S", re.MULTILINE)
67
74
 
68
75
  file_lines = file_content.split("\n")
69
76
  comment_lines = []
@@ -78,9 +85,9 @@ def _rewrite_with_notice(
78
85
  return util.rewrite_file(file_path, f"{notice}\n{modified_file_content}")
79
86
 
80
87
 
81
- def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
88
+ def _write_guide_as_html(guide: EmitOpenAPIGuide, *, is_open: bool) -> str:
82
89
  return f"""
83
- <details>
90
+ <details id="{guide.ref_name}" {"open" if is_open else ""}>
84
91
  <summary>{guide.title}</summary>
85
92
  {guide.html_content}
86
93
  </details>"""
@@ -89,7 +96,10 @@ def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
89
96
  def _open_api_info(
90
97
  config: OpenAPIConfig, guides: list[EmitOpenAPIGuide]
91
98
  ) -> GlobalContextInfo:
92
- full_guides = "<br/>".join([_write_guide_as_html(guide) for guide in guides])
99
+ full_guides = "<br/>".join([
100
+ _write_guide_as_html(guide, is_open=True)
101
+ for guide in sorted(guides, key=lambda g: g.ref_name)
102
+ ])
93
103
  full_description = f"{config.description}<br/>{full_guides}"
94
104
  info: GlobalContextInfo = dict()
95
105
  info["version"] = "1.0.0"
@@ -107,7 +117,9 @@ def _open_api_servers(config: OpenAPIConfig) -> list[EmitOpenAPIServer]:
107
117
  def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> None:
108
118
  root_guides = builder.guides.get(RootGuideKey(), [])
109
119
  openapi_guides = [
110
- EmitOpenAPIGuide(title=guide.title, html_content=guide.html_content)
120
+ EmitOpenAPIGuide(
121
+ ref_name=guide.ref_name, title=guide.title, html_content=guide.html_content
122
+ )
111
123
  for guide in root_guides
112
124
  ]
113
125
  gctx = EmitOpenAPIGlobalContext(
@@ -119,7 +131,11 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
119
131
  for namespace in sorted(builder.namespaces.values(), key=lambda ns: ns.name):
120
132
  ctx = EmitOpenAPIContext(namespace=namespace)
121
133
 
122
- if ctx.namespace.endpoint is not None and ctx.namespace.endpoint.is_beta:
134
+ if (
135
+ ctx.namespace.endpoint is not None
136
+ and ctx.namespace.endpoint.stability_level
137
+ == EmitOpenAPIStabilityLevel.draft
138
+ ):
123
139
  continue
124
140
 
125
141
  if ctx.namespace.name == "base":
@@ -163,7 +179,7 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
163
179
  oa_paths[path.path] = {"$ref": path.ref}
164
180
  oa_root["paths"] = oa_paths
165
181
 
166
- return yaml.dump(oa_root, sort_keys=False)
182
+ return yaml.dumps(oa_root, sort_keys=False)
167
183
 
168
184
 
169
185
  def _is_empty_object_type(typ: OpenAPIType) -> bool:
@@ -245,10 +261,35 @@ def _emit_endpoint_parameters(
245
261
  } | _emit_endpoint_parameter_examples(examples)
246
262
 
247
263
 
248
- def _emit_is_beta(is_beta: bool) -> DictApiSchema:
249
- if is_beta:
250
- return {"x-beta": True}
251
- return {}
264
+ def _emit_endpoint_deprecated(deprecated: bool) -> DictApiSchema:
265
+ return {"deprecated": True} if deprecated else {}
266
+
267
+
268
+ def _emit_stability_level(
269
+ stability_level: EmitOpenAPIStabilityLevel | None,
270
+ ) -> DictApiSchema:
271
+ stability_info: dict[str, ApiSchema] = {}
272
+ resolved_stability_level = (
273
+ stability_level
274
+ if stability_level is not None
275
+ else EmitOpenAPIStabilityLevel.stable
276
+ )
277
+ stability_info["x-stability-level"] = str(resolved_stability_level)
278
+ match resolved_stability_level:
279
+ case EmitOpenAPIStabilityLevel.draft:
280
+ stability_info["x-beta"] = True
281
+ case EmitOpenAPIStabilityLevel.beta:
282
+ stability_info["x-badges"] = [
283
+ {
284
+ "name": "Beta",
285
+ "color": "DarkOrange",
286
+ }
287
+ ]
288
+ case EmitOpenAPIStabilityLevel.stable:
289
+ pass
290
+ case _:
291
+ assert_never(stability_level)
292
+ return stability_info
252
293
 
253
294
 
254
295
  def _emit_endpoint_request_body(
@@ -271,7 +312,9 @@ def _emit_endpoint_request_body(
271
312
  "type": "object",
272
313
  "title": "Body",
273
314
  "required": ["data"],
274
- "properties": {"data": {"$ref": "#/components/schema/Arguments"}},
315
+ "properties": {
316
+ "data": {"$ref": "#/components/schema/Arguments"}
317
+ },
275
318
  }
276
319
  }
277
320
  | _emit_endpoint_argument_examples(examples)
@@ -296,15 +339,57 @@ def _emit_endpoint_response_examples(
296
339
  return {"examples": response_examples}
297
340
 
298
341
 
342
+ def _create_warning_banner(api_type: str, message: str) -> str:
343
+ return (
344
+ f'<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; '
345
+ f'border-radius: 4px; padding: 12px; margin-bottom: 16px;">'
346
+ f"<strong>⚠️ {api_type} API:</strong> {message}"
347
+ f"</div>"
348
+ )
349
+
350
+
351
+ def _get_stability_warning(
352
+ stability_level: EmitOpenAPIStabilityLevel | None,
353
+ ) -> str:
354
+ resolved_stability_level = (
355
+ stability_level
356
+ if stability_level is not None
357
+ else EmitOpenAPIStabilityLevel.stable
358
+ )
359
+
360
+ match resolved_stability_level:
361
+ case EmitOpenAPIStabilityLevel.draft:
362
+ return _create_warning_banner(
363
+ "Draft",
364
+ "This endpoint is in draft status and may change significantly. Not recommended for production use.",
365
+ )
366
+ case EmitOpenAPIStabilityLevel.beta:
367
+ return _create_warning_banner(
368
+ "Beta",
369
+ "This endpoint is in beta and its required parameters may change. Use with caution in production environments.",
370
+ )
371
+ case EmitOpenAPIStabilityLevel.stable:
372
+ return ""
373
+
374
+
299
375
  def _emit_endpoint_description(
300
- description: str, guides: list[EmitOpenAPIGuide]
376
+ description: str,
377
+ guides: list[EmitOpenAPIGuide],
378
+ stability_level: EmitOpenAPIStabilityLevel | None = None,
301
379
  ) -> dict[str, str]:
302
- full_guides = "<br/>".join([_write_guide_as_html(guide) for guide in guides])
303
- return {
304
- "description": description
305
- if len(guides) == 0
306
- else f"{description}<br/>{full_guides}"
307
- }
380
+ stability_warning = _get_stability_warning(stability_level)
381
+
382
+ full_guides = "<br/>".join([
383
+ _write_guide_as_html(guide, is_open=False)
384
+ for guide in sorted(guides, key=lambda g: g.ref_name)
385
+ ])
386
+
387
+ full_description_parts = [
388
+ part for part in [stability_warning, description, full_guides] if part
389
+ ]
390
+ full_description = "<br/>".join(full_description_parts)
391
+
392
+ return {"description": full_description}
308
393
 
309
394
 
310
395
  def _emit_namespace(
@@ -339,10 +424,15 @@ def _emit_namespace(
339
424
  "tags": endpoint.tags,
340
425
  "summary": endpoint.summary,
341
426
  }
342
- | _emit_endpoint_description(endpoint.description, ctx.endpoint.guides)
343
- | _emit_is_beta(endpoint.is_beta)
427
+ | _emit_endpoint_deprecated(endpoint.deprecated)
428
+ | _emit_endpoint_description(
429
+ endpoint.description, ctx.endpoint.guides, endpoint.stability_level
430
+ )
431
+ | _emit_stability_level(endpoint.stability_level)
344
432
  | _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
345
- | _emit_endpoint_request_body(endpoint, argument_type, ctx.endpoint.examples)
433
+ | _emit_endpoint_request_body(
434
+ endpoint, argument_type, ctx.endpoint.examples
435
+ )
346
436
  | {
347
437
  "responses": {
348
438
  "200": {
@@ -395,7 +485,19 @@ def _emit_namespace(
395
485
 
396
486
  path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
397
487
  oa_namespace = {"components": oa_components}
398
- _rewrite_with_notice(path, yaml.dump(oa_namespace, sort_keys=False))
488
+ _rewrite_with_notice(path, yaml.dumps(oa_namespace, sort_keys=False))
489
+
490
+
491
+ def _get_openapi_default_behavior(
492
+ prop: builder.SpecProperty,
493
+ ) -> OpenAPIDefaultBehavior | None:
494
+ if prop.ext_info is None or prop.ext_info.get("open_api") is None:
495
+ return None
496
+ value_passed = prop.ext_info["open_api"].get("default_required_behavior")
497
+ if value_passed is None:
498
+ return None
499
+ assert isinstance(value_passed, str)
500
+ return OpenAPIDefaultBehavior(value_passed)
399
501
 
400
502
 
401
503
  def _emit_type(
@@ -422,6 +524,22 @@ def _emit_type(
422
524
  ctx.types[stype.name] = open_api_type(ctx, stype.alias, config=config)
423
525
  return
424
526
 
527
+ if isinstance(stype, builder.SpecTypeDefnUnion):
528
+ converted_discriminator_map: dict[str, OpenAPIRefType] = dict()
529
+ if stype.discriminator_map is not None:
530
+ for discriminator_value, base_type in stype.discriminator_map.items():
531
+ converted_base_type = open_api_type(ctx, base_type, config=config)
532
+ assert isinstance(converted_base_type, OpenAPIRefType)
533
+ converted_discriminator_map[discriminator_value] = converted_base_type
534
+ ctx.types[stype.name] = OpenAPIUnionType(
535
+ [open_api_type(ctx, p, config=config) for p in stype.types],
536
+ discriminator=stype.discriminator,
537
+ discriminator_map=converted_discriminator_map
538
+ if stype.discriminator_map is not None
539
+ else None,
540
+ )
541
+ return
542
+
425
543
  if isinstance(stype, builder.SpecTypeDefnStringEnum):
426
544
  # TODO: check that these are always string enums
427
545
  # IMPROVE: reflect the enum names in the description
@@ -455,6 +573,16 @@ def _emit_type(
455
573
  # arguments, thus treat like extant==missing
456
574
  # IMPROVE: if we can decide they are meant as output instead, then
457
575
  # they should be marked as required
576
+ openapi_default_beahvior = _get_openapi_default_behavior(prop)
577
+ match openapi_default_beahvior:
578
+ case None:
579
+ pass
580
+ case OpenAPIDefaultBehavior.OPTIONAL_WITH_DEFAULT:
581
+ ref_type.nullable = True
582
+ assert prop.default is not None, (
583
+ "optional_with_default requires default"
584
+ )
585
+ ref_type.default = prop.default
458
586
  properties[prop_name] = ref_type
459
587
  elif prop.extant == builder.PropertyExtant.missing:
460
588
  # Unlike optional below, missing does not imply null is possible. They
@@ -482,18 +610,6 @@ def _emit_type(
482
610
  ctx.types[stype.name] = final_type
483
611
 
484
612
 
485
- def _emit_constant(ctx: EmitOpenAPIContext, sconst: builder.SpecConstant) -> None:
486
- if sconst.value_type.is_base_type(builder.BaseTypeName.s_string):
487
- value = util.encode_common_string(cast(str, sconst.value))
488
- elif sconst.value_type.is_base_type(builder.BaseTypeName.s_integer):
489
- value = str(sconst.value)
490
- else:
491
- raise Exception("invalid constant type", sconst.name)
492
-
493
- const_name = sconst.name.upper()
494
- print("_emit_constant", value, const_name)
495
-
496
-
497
613
  def _emit_endpoint(
498
614
  gctx: EmitOpenAPIGlobalContext,
499
615
  ctx: EmitOpenAPIContext,
@@ -534,7 +650,7 @@ def _emit_endpoint(
534
650
  ep = namespace.endpoint
535
651
  gctx.paths.append(
536
652
  EmitOpenAPIPath(
537
- path=f"/{ep.path_root}/{ep.path_dirname}/{ep.path_basename}",
653
+ path=f"/{ep.resolved_path}",
538
654
  ref=ref_path,
539
655
  )
540
656
  )
@@ -544,24 +660,27 @@ def _emit_endpoint(
544
660
  description = f"**[External API-Endpoint]** <br/> {description}"
545
661
 
546
662
  path_cutoff = min(3, len(namespace.path) - 1)
663
+
547
664
  ctx.endpoint = EmitOpenAPIEndpoint(
548
665
  method=namespace.endpoint.method.lower(),
549
666
  tags=[tag_name],
550
667
  summary=f"{'/'.join(namespace.path[path_cutoff:])}",
551
668
  description=description,
552
- is_beta=namespace.endpoint.is_beta,
669
+ deprecated=namespace.endpoint.deprecated,
670
+ stability_level=namespace.endpoint.stability_level,
553
671
  examples=[
554
672
  EmitOpenAPIEndpointExample(
555
673
  ref_name=f"ex_{i}",
556
674
  summary=example.summary,
557
675
  description=example.description,
558
- arguments=example.arguments,
559
- data=example.data,
676
+ arguments=serialize_for_api(example.arguments),
677
+ data=serialize_for_api(example.data),
560
678
  )
561
679
  for i, example in enumerate(endpoint_examples)
562
680
  ],
563
681
  guides=[
564
682
  EmitOpenAPIGuide(
683
+ ref_name=guide.ref_name,
565
684
  title=guide.title,
566
685
  html_content=guide.html_content,
567
686
  )
@@ -6,16 +6,17 @@ WORK-IN-PROGRESS, DON'T USE!
6
6
 
7
7
  from collections import defaultdict
8
8
  from dataclasses import dataclass, field
9
- from typing import TypeAlias
9
+
10
+ from pkgs.serialization_util import JsonValue
10
11
 
11
12
  from . import builder
12
13
  from .open_api_util import OpenAPIType
13
14
 
14
15
  MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
15
16
 
16
- GlobalContextInfo: TypeAlias = dict[str, str | dict[str, str]]
17
- TagGroupToNamedTags: TypeAlias = dict[str, str | list[str]]
18
- TagPathsToRef: TypeAlias = dict[str, dict[str, str]]
17
+ GlobalContextInfo = dict[str, str | dict[str, str]]
18
+ TagGroupToNamedTags = dict[str, str | list[str]]
19
+ TagPathsToRef = dict[str, dict[str, str]]
19
20
 
20
21
 
21
22
  @dataclass
@@ -45,6 +46,7 @@ class EmitOpenAPIServer:
45
46
 
46
47
  @dataclass(kw_only=True)
47
48
  class EmitOpenAPIGuide:
49
+ ref_name: str
48
50
  title: str
49
51
  html_content: str
50
52
 
@@ -67,8 +69,11 @@ class EmitOpenAPIEndpointExample:
67
69
  ref_name: str
68
70
  summary: str
69
71
  description: str
70
- arguments: dict[str, object]
71
- data: dict[str, object]
72
+ arguments: dict[str, JsonValue]
73
+ data: dict[str, JsonValue]
74
+
75
+
76
+ EmitOpenAPIStabilityLevel = builder.StabilityLevel
72
77
 
73
78
 
74
79
  @dataclass
@@ -77,7 +82,8 @@ class EmitOpenAPIEndpoint:
77
82
  tags: list[str]
78
83
  summary: str
79
84
  description: str
80
- is_beta: bool
85
+ deprecated: bool
86
+ stability_level: EmitOpenAPIStabilityLevel | None
81
87
  examples: list[EmitOpenAPIEndpointExample]
82
88
  guides: list[EmitOpenAPIGuide]
83
89