UncountablePythonSDK 0.0.21__tar.gz → 0.0.22__tar.gz

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 (223) hide show
  1. {uncountablepythonsdk-0.0.21/UncountablePythonSDK.egg-info → uncountablepythonsdk-0.0.22}/PKG-INFO +1 -1
  2. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22/UncountablePythonSDK.egg-info}/PKG-INFO +1 -1
  3. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/UncountablePythonSDK.egg-info/SOURCES.txt +2 -0
  4. uncountablepythonsdk-0.0.22/examples/async_batch.py +36 -0
  5. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/builder.py +112 -8
  6. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_open_api.py +188 -18
  7. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_open_api_util.py +17 -0
  8. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_python.py +1 -1
  9. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/load_types.py +48 -5
  10. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/open_api_util.py +13 -33
  11. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/create_entities.yaml +1 -0
  12. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/create_entity.yaml +1 -0
  13. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/edit_recipe_inputs.yaml +1 -3
  14. uncountablepythonsdk-0.0.22/uncountable/core/__init__.py +5 -0
  15. uncountablepythonsdk-0.0.22/uncountable/core/async_batch.py +22 -0
  16. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/core/client.py +78 -6
  17. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/create_entities.py +1 -1
  18. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/create_entity.py +1 -1
  19. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/edit_recipe_inputs.py +2 -3
  20. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/async_batch.py +1 -0
  21. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/async_batch_processor.py +7 -7
  22. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/client_base.py +2 -2
  23. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/identifier.py +3 -3
  24. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_workflow_steps.py +1 -1
  25. uncountablepythonsdk-0.0.21/uncountable/core/__init__.py +0 -4
  26. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/.github/workflows/documentation.yml +0 -0
  27. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/.github/workflows/publish.yml +0 -0
  28. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/.gitignore +0 -0
  29. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/README.md +0 -0
  30. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/UncountablePythonSDK.egg-info/dependency_links.txt +0 -0
  31. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/UncountablePythonSDK.egg-info/requires.txt +0 -0
  32. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/UncountablePythonSDK.egg-info/top_level.txt +0 -0
  33. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/.gitignore +0 -0
  34. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/conf.py +0 -0
  35. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/index.md +0 -0
  36. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/justfile +0 -0
  37. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/quickstart.md +0 -0
  38. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/requirements.txt +0 -0
  39. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/android-chrome-192x192.png +0 -0
  40. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/android-chrome-512x512.png +0 -0
  41. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/apple-touch-icon.png +0 -0
  42. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/browserconfig.xml +0 -0
  43. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/favicon-16x16.png +0 -0
  44. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/favicon-32x32.png +0 -0
  45. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/manifest.json +0 -0
  46. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/mstile-150x150.png +0 -0
  47. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/favicons/safari-pinned-tab.svg +0 -0
  48. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/docs/static/logo_blue.png +0 -0
  49. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/examples/create_entity.py +0 -0
  50. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/examples/recipe-import/importer.py +0 -0
  51. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/examples/upload_files.py +0 -0
  52. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/__init__.py +0 -0
  53. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/argument_parser/__init__.py +0 -0
  54. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/argument_parser/_is_enum.py +0 -0
  55. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/argument_parser/_is_namedtuple.py +0 -0
  56. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/argument_parser/argument_parser.py +0 -0
  57. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/argument_parser/case_convert.py +0 -0
  58. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization/__init__.py +0 -0
  59. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization/missing_sentry.py +0 -0
  60. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization/opaque_key.py +0 -0
  61. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization/serial_class.py +0 -0
  62. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization_util/__init__.py +0 -0
  63. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization_util/_get_type_for_serialization.py +0 -0
  64. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/serialization_util/serialization_helpers.py +0 -0
  65. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/strenum_compat/__init__.py +0 -0
  66. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/strenum_compat/strenum_compat.py +0 -0
  67. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/__init__.py +0 -0
  68. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/__main__.py +0 -0
  69. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/actions_registry/__init__.py +0 -0
  70. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/actions_registry/__main__.py +0 -0
  71. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/actions_registry/emit_typescript.py +0 -0
  72. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/config.py +0 -0
  73. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_io_ts.py +0 -0
  74. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_typescript.py +0 -0
  75. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/emit_typescript_util.py +0 -0
  76. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/parts/base.py.prepart +0 -0
  77. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/parts/base.ts.prepart +0 -0
  78. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/test.py +0 -0
  79. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/type_info/__main__.py +0 -0
  80. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/type_info/emit_type_info.py +0 -0
  81. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/util.py +0 -0
  82. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/value_spec/__init__.py +0 -0
  83. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/value_spec/__main__.py +0 -0
  84. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/value_spec/convert_type.py +0 -0
  85. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/value_spec/emit_python.py +0 -0
  86. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pkgs/type_spec/value_spec/types.py +0 -0
  87. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/pyproject.toml +0 -0
  88. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/setup.cfg +0 -0
  89. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/batch/execute_batch.yaml +0 -0
  90. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/batch/execute_batch_load_async.yaml +0 -0
  91. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/chemical/convert_chemical_formats.yaml +0 -0
  92. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/get_entities_data.yaml +0 -0
  93. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/list_entities.yaml +0 -0
  94. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/resolve_entity_ids.yaml +0 -0
  95. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/set_values.yaml +0 -0
  96. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/entity/transition_entity_phase.yaml +0 -0
  97. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/id_source/list_id_source.yaml +0 -0
  98. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/id_source/match_id_source.yaml +0 -0
  99. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/input_groups/get_input_group_names.yaml +0 -0
  100. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/inputs/create_inputs.yaml +0 -0
  101. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/inputs/get_input_data.yaml +0 -0
  102. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/inputs/get_input_names.yaml +0 -0
  103. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/inputs/get_inputs_data.yaml +0 -0
  104. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -0
  105. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/outputs/get_output_data.yaml +0 -0
  106. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/outputs/get_output_names.yaml +0 -0
  107. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -0
  108. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/permissions/set_core_permissions.yaml +0 -0
  109. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/project/get_projects.yaml +0 -0
  110. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/project/get_projects_data.yaml +0 -0
  111. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipe_links/create_recipe_link.yaml +0 -0
  112. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -0
  113. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/associate_recipe_as_input.yaml +0 -0
  114. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/associate_recipe_as_lot.yaml +0 -0
  115. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/create_recipe.yaml +0 -0
  116. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/create_recipes.yaml +0 -0
  117. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/disassociate_recipe_as_input.yaml +0 -0
  118. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_curve.yaml +0 -0
  119. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -0
  120. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_recipe_links.yaml +0 -0
  121. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_recipe_names.yaml +0 -0
  122. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -0
  123. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/get_recipes_data.yaml +0 -0
  124. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -0
  125. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/set_recipe_metadata.yaml +0 -0
  126. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -0
  127. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/recipes/set_recipe_tags.yaml +0 -0
  128. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/type_spec/external/api/triggers/run_trigger.yaml +0 -0
  129. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/__init__.py +0 -0
  130. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/core/file_upload.py +0 -0
  131. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/core/types.py +0 -0
  132. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/__init__.py +0 -0
  133. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/construct_client.py +0 -0
  134. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/cron.py +0 -0
  135. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/db/__init__.py +0 -0
  136. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/db/connect.py +0 -0
  137. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/entrypoint.py +0 -0
  138. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/executors/__init__.py +0 -0
  139. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/executors/script_executor.py +0 -0
  140. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/job.py +0 -0
  141. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/server.py +0 -0
  142. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/integration/types.py +0 -0
  143. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/py.typed +0 -0
  144. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/__init__.py +0 -0
  145. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/__init__.py +0 -0
  146. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/batch/__init__.py +0 -0
  147. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/batch/execute_batch.py +0 -0
  148. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/batch/execute_batch_load_async.py +0 -0
  149. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/chemical/__init__.py +0 -0
  150. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/chemical/convert_chemical_formats.py +0 -0
  151. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/__init__.py +0 -0
  152. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/get_entities_data.py +0 -0
  153. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/list_entities.py +0 -0
  154. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/resolve_entity_ids.py +0 -0
  155. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/set_values.py +0 -0
  156. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/entity/transition_entity_phase.py +0 -0
  157. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/id_source/__init__.py +0 -0
  158. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/id_source/list_id_source.py +0 -0
  159. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/id_source/match_id_source.py +0 -0
  160. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/input_groups/__init__.py +0 -0
  161. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/input_groups/get_input_group_names.py +0 -0
  162. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/__init__.py +0 -0
  163. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/create_inputs.py +0 -0
  164. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/get_input_data.py +0 -0
  165. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/get_input_names.py +0 -0
  166. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/get_inputs_data.py +0 -0
  167. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/inputs/set_input_attribute_values.py +0 -0
  168. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/outputs/__init__.py +0 -0
  169. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/outputs/get_output_data.py +0 -0
  170. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/outputs/get_output_names.py +0 -0
  171. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/outputs/resolve_output_conditions.py +0 -0
  172. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/permissions/__init__.py +0 -0
  173. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/permissions/set_core_permissions.py +0 -0
  174. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/project/__init__.py +0 -0
  175. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/project/get_projects.py +0 -0
  176. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/project/get_projects_data.py +0 -0
  177. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipe_links/__init__.py +0 -0
  178. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipe_links/create_recipe_link.py +0 -0
  179. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipe_metadata/__init__.py +0 -0
  180. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +0 -0
  181. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/__init__.py +0 -0
  182. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/associate_recipe_as_input.py +0 -0
  183. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/associate_recipe_as_lot.py +0 -0
  184. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/create_recipe.py +0 -0
  185. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/create_recipes.py +0 -0
  186. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/disassociate_recipe_as_input.py +0 -0
  187. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_curve.py +0 -0
  188. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_recipe_calculations.py +0 -0
  189. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_recipe_links.py +0 -0
  190. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_recipe_names.py +0 -0
  191. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -0
  192. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/get_recipes_data.py +0 -0
  193. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/set_recipe_inputs.py +0 -0
  194. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/set_recipe_metadata.py +0 -0
  195. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/set_recipe_outputs.py +0 -0
  196. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/recipes/set_recipe_tags.py +0 -0
  197. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/triggers/__init__.py +0 -0
  198. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/api/triggers/run_trigger.py +0 -0
  199. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/base.py +0 -0
  200. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/calculations.py +0 -0
  201. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/chemical_structure.py +0 -0
  202. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/curves.py +0 -0
  203. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/entity.py +0 -0
  204. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/experiment_groups.py +0 -0
  205. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/field_values.py +0 -0
  206. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/fields.py +0 -0
  207. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/id_source.py +0 -0
  208. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/input_attributes.py +0 -0
  209. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/inputs.py +0 -0
  210. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/outputs.py +0 -0
  211. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/permissions.py +0 -0
  212. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/phases.py +0 -0
  213. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/post_base.py +0 -0
  214. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_identifiers.py +0 -0
  215. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_inputs.py +0 -0
  216. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_links.py +0 -0
  217. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_metadata.py +0 -0
  218. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_output_metadata.py +0 -0
  219. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/recipe_tags.py +0 -0
  220. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/response.py +0 -0
  221. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/units.py +0 -0
  222. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/users.py +0 -0
  223. {uncountablepythonsdk-0.0.21 → uncountablepythonsdk-0.0.22}/uncountable/types/workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.21
3
+ Version: 0.0.22
4
4
  Summary: Uncountable SDK
5
5
  Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
6
  Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.21
3
+ Version: 0.0.22
4
4
  Summary: Uncountable SDK
5
5
  Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
6
  Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
@@ -24,6 +24,7 @@ docs/static/favicons/favicon-32x32.png
24
24
  docs/static/favicons/manifest.json
25
25
  docs/static/favicons/mstile-150x150.png
26
26
  docs/static/favicons/safari-pinned-tab.svg
27
+ examples/async_batch.py
27
28
  examples/create_entity.py
28
29
  examples/upload_files.py
29
30
  examples/recipe-import/importer.py
@@ -114,6 +115,7 @@ type_spec/external/api/triggers/run_trigger.yaml
114
115
  uncountable/__init__.py
115
116
  uncountable/py.typed
116
117
  uncountable/core/__init__.py
118
+ uncountable/core/async_batch.py
117
119
  uncountable/core/client.py
118
120
  uncountable/core/file_upload.py
119
121
  uncountable/core/types.py
@@ -0,0 +1,36 @@
1
+ from decimal import Decimal
2
+ from uncountable.core import AuthDetailsApiKey, Client
3
+ from uncountable.core import AsyncBatchProcessor
4
+ from uncountable.types import (
5
+ recipe_metadata,
6
+ )
7
+ from uncountable.types.identifier import IdentifierKeyBatchReference
8
+ from uncountable.types.recipe_identifiers import (
9
+ RecipeIdentifierEditableName,
10
+ RecipeIdentifiers,
11
+ )
12
+
13
+
14
+ client = Client(
15
+ base_url="https://app.uncountable.com",
16
+ auth_details=AuthDetailsApiKey(
17
+ api_id="X",
18
+ api_secret_key="X",
19
+ ),
20
+ )
21
+ batch_loader = AsyncBatchProcessor(client=client)
22
+ recipe_identifiers: RecipeIdentifiers = []
23
+ recipe_identifiers.append(
24
+ RecipeIdentifierEditableName(editable_name="My recipe from API")
25
+ )
26
+ req = batch_loader.create_recipe(
27
+ material_family_id=1, workflow_id=1, identifiers=recipe_identifiers
28
+ )
29
+ created_recipe_reference = req.batch_reference
30
+ batch_loader.set_recipe_metadata(
31
+ recipe_key=IdentifierKeyBatchReference(reference=created_recipe_reference),
32
+ recipe_metadata=[
33
+ recipe_metadata.MetadataValue(metadata_id=7, value_numeric=Decimal(38))
34
+ ],
35
+ )
36
+ job_id = batch_loader.send()
@@ -10,7 +10,7 @@ import re
10
10
  from collections import defaultdict
11
11
  from dataclasses import MISSING, dataclass
12
12
  from enum import Enum, StrEnum, auto
13
- from typing import Any, Optional
13
+ from typing import Any, Optional, Self
14
14
 
15
15
  from . import util
16
16
  from .util import parse_type_str, unused
@@ -184,6 +184,34 @@ class SpecTypeInstance(SpecType):
184
184
  return defn_type + self.parameters
185
185
 
186
186
 
187
+ @dataclass(kw_only=True)
188
+ class SpecEndpointExample:
189
+ summary: str
190
+ description: str
191
+ arguments: dict[str, object]
192
+ data: dict[str, object]
193
+
194
+
195
+ @dataclass(kw_only=True)
196
+ class SpecGuide:
197
+ title: str
198
+ markdown_content: str
199
+ html_content: str
200
+
201
+
202
+ @dataclass(kw_only=True, frozen=True)
203
+ class RootGuideKey:
204
+ pass
205
+
206
+
207
+ @dataclass(kw_only=True, frozen=True)
208
+ class EndpointGuideKey:
209
+ path: str
210
+
211
+
212
+ SpecGuideKey = RootGuideKey | EndpointGuideKey
213
+
214
+
187
215
  class SpecTypeLiteralWrapper(SpecType):
188
216
  def __init__(
189
217
  self,
@@ -672,6 +700,32 @@ class ResultType(StrEnum):
672
700
  RE_ENDPOINT_ROOT = re.compile(r"\${([_a-z]+)}")
673
701
 
674
702
 
703
+ @dataclass(kw_only=True, frozen=True)
704
+ class _EndpointPathDetails:
705
+ root: str
706
+ root_path: str
707
+ resolved_path: str
708
+
709
+
710
+ def _resolve_endpoint_path(
711
+ path: str, api_endpoints: dict[str, str]
712
+ ) -> _EndpointPathDetails:
713
+ root_path_source = path.split("/")[0]
714
+ root_match = RE_ENDPOINT_ROOT.fullmatch(root_path_source)
715
+ if root_match is None:
716
+ raise Exception(f"invalid-api-path-root:{root_path_source}")
717
+
718
+ root_var = root_match.group(1)
719
+ root_path = api_endpoints[root_var]
720
+
721
+ _, *rest_path = path.split("/", 1)
722
+ resolved_path = "/".join([root_path] + rest_path)
723
+
724
+ return _EndpointPathDetails(
725
+ root=root_var, root_path=root_path, resolved_path=resolved_path
726
+ )
727
+
728
+
675
729
  class SpecEndpoint:
676
730
  method: RouteMethod
677
731
  root: str
@@ -748,14 +802,10 @@ class SpecEndpoint:
748
802
 
749
803
  self.result_type = ResultType(data.get("result_type", ResultType.json.value))
750
804
 
805
+ path_details = _resolve_endpoint_path(data["path"], builder.api_endpoints)
806
+ self.root = path_details.root
807
+ self.path_root = path_details.root_path
751
808
  self.desc = data.get("desc")
752
-
753
- root_match = RE_ENDPOINT_ROOT.fullmatch(path[0])
754
- if root_match is None:
755
- raise Exception(f"invalid-api-path-root:{path[0]}")
756
-
757
- self.root = root_match.group(1)
758
- self.path_root = builder.api_endpoints[self.root]
759
809
  # IMPROVE: remove need for is_external flag
760
810
  self.is_external = self.path_root == "api/external"
761
811
  self.has_attachment = data.get("has_attachment", False)
@@ -764,6 +814,10 @@ class SpecEndpoint:
764
814
  not is_sdk or self.desc is not None
765
815
  ), f"Endpoint description required for SDK endpoints, missing: {path}"
766
816
 
817
+ @property
818
+ def resolved_path(self: Self) -> str:
819
+ return f"{self.path_root}/{self.path_dirname}/{self.path_basename}"
820
+
767
821
 
768
822
  def _parse_const(
769
823
  builder: SpecBuilder,
@@ -1014,6 +1068,8 @@ class SpecBuilder:
1014
1068
  self.pending: list[NamespaceDataPair] = []
1015
1069
  self.parts: dict[str, dict[str, str]] = defaultdict(dict)
1016
1070
  self.preparts: dict[str, dict[str, str]] = defaultdict(dict)
1071
+ self.examples: dict[str, list[SpecEndpointExample]] = defaultdict(list)
1072
+ self.guides: dict[SpecGuideKey, list[SpecGuide]] = defaultdict(list)
1017
1073
  self.api_endpoints = api_endpoints
1018
1074
  base_namespace = SpecNamespace(name=base_namespace_name)
1019
1075
  for base_type in BaseTypeName:
@@ -1198,5 +1254,53 @@ class SpecBuilder:
1198
1254
  def add_prepart_file(self, target: str, name: str, data: str) -> None:
1199
1255
  self.preparts[target][name] = data
1200
1256
 
1257
+ def add_example_file(self, data: dict[str, object]) -> None:
1258
+ path_details = _resolve_endpoint_path(str(data["path"]), self.api_endpoints)
1259
+
1260
+ examples_data = data["examples"]
1261
+ if not isinstance(examples_data, list):
1262
+ raise Exception(
1263
+ f"'examples' in example files are expected to be a list, endpoint_path={path_details.resolved_path}"
1264
+ )
1265
+ for example in examples_data:
1266
+ arguments = example["arguments"]
1267
+ data_example = example["data"]
1268
+ if not isinstance(arguments, dict) or not isinstance(data_example, dict):
1269
+ raise Exception(
1270
+ f"'arguments' and 'data' fields must be dictionaries for each endpoint example, endpoint={path_details.resolved_path}"
1271
+ )
1272
+ self.examples[path_details.resolved_path].append(
1273
+ SpecEndpointExample(
1274
+ summary=str(example["summary"]),
1275
+ description=str(example["description"]),
1276
+ arguments=arguments,
1277
+ data=data_example,
1278
+ )
1279
+ )
1280
+
1281
+ def add_guide_file(self, file_content: str) -> None:
1282
+ import markdown
1283
+
1284
+ md = markdown.Markdown(extensions=["meta"])
1285
+ html = md.convert(file_content)
1286
+ meta: dict[str, list[str]] = md.Meta # type: ignore[attr-defined]
1287
+ title_meta: list[str] | None = meta.get("title")
1288
+ if title_meta is None:
1289
+ raise Exception("guides requier a title in the meta section")
1290
+
1291
+ path_meta: list[str] | None = meta.get("path")
1292
+ guide_key: SpecGuideKey = RootGuideKey()
1293
+ if path_meta is not None:
1294
+ path_details = _resolve_endpoint_path("".join(path_meta), self.api_endpoints)
1295
+ guide_key = EndpointGuideKey(path=path_details.resolved_path)
1296
+
1297
+ self.guides[guide_key].append(
1298
+ SpecGuide(
1299
+ title="".join(title_meta),
1300
+ html_content=html,
1301
+ markdown_content=file_content,
1302
+ )
1303
+ )
1304
+
1201
1305
  def resolve_proper_name(self, stype: SpecTypeDefn) -> str:
1202
1306
  return f"{'.'.join(stype.namespace.path)}.{stype.name}"
@@ -5,18 +5,22 @@ WORK-IN-PROGRESS, DON'T USE!
5
5
  """
6
6
 
7
7
  import dataclasses
8
+ import json
8
9
  import re
9
- from typing import cast
10
+ from typing import Collection, cast
10
11
 
11
12
  import yaml
12
13
 
13
14
  from . import builder, util
15
+ from .builder import EndpointGuideKey, RootGuideKey
14
16
  from .config import OpenAPIConfig
15
17
  from .emit_open_api_util import (
16
18
  MODIFY_NOTICE,
17
19
  EmitOpenAPIContext,
18
20
  EmitOpenAPIEndpoint,
21
+ EmitOpenAPIEndpointExample,
19
22
  EmitOpenAPIGlobalContext,
23
+ EmitOpenAPIGuide,
20
24
  EmitOpenAPIPath,
21
25
  EmitOpenAPIServer,
22
26
  EmitOpenAPITag,
@@ -74,12 +78,23 @@ def _rewrite_with_notice(
74
78
  return util.rewrite_file(file_path, f"{notice}\n{modified_file_content}")
75
79
 
76
80
 
77
- def _open_api_info(config: OpenAPIConfig) -> GlobalContextInfo:
78
- description = config.description
81
+ def _write_guide_as_html(guide: EmitOpenAPIGuide) -> str:
82
+ return f"""
83
+ <details>
84
+ <summary>{guide.title}</summary>
85
+ {guide.html_content}
86
+ </details>"""
87
+
88
+
89
+ def _open_api_info(
90
+ config: OpenAPIConfig, guides: list[EmitOpenAPIGuide]
91
+ ) -> GlobalContextInfo:
92
+ full_guides = "<br/>".join([_write_guide_as_html(guide) for guide in guides])
93
+ full_description = f"{config.description}<br/>{full_guides}"
79
94
  info: GlobalContextInfo = dict()
80
95
  info["version"] = "1.0.0"
81
96
  info["title"] = "Uncountable API Documentation"
82
- info["description"] = description
97
+ info["description"] = full_description
83
98
  info["x-logo"] = {"url": "../static/images/logo_blue.png", "altText": "Logo"}
84
99
  return info
85
100
 
@@ -90,9 +105,14 @@ def _open_api_servers(config: OpenAPIConfig) -> list[EmitOpenAPIServer]:
90
105
 
91
106
 
92
107
  def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> None:
108
+ root_guides = builder.guides.get(RootGuideKey(), [])
109
+ openapi_guides = [
110
+ EmitOpenAPIGuide(title=guide.title, html_content=guide.html_content)
111
+ for guide in root_guides
112
+ ]
93
113
  gctx = EmitOpenAPIGlobalContext(
94
114
  version="3.0.0",
95
- info=_open_api_info(config),
115
+ info=_open_api_info(config, openapi_guides),
96
116
  servers=_open_api_servers(config),
97
117
  )
98
118
 
@@ -112,6 +132,8 @@ def emit_open_api(builder: builder.SpecBuilder, *, config: OpenAPIConfig) -> Non
112
132
  ctx,
113
133
  namespace=namespace,
114
134
  config=config,
135
+ examples=builder.examples,
136
+ guides=builder.guides,
115
137
  )
116
138
 
117
139
  _rewrite_with_notice(
@@ -144,52 +166,183 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
144
166
  return yaml.dump(oa_root, sort_keys=False)
145
167
 
146
168
 
147
- def _emit_endpoint_parameters(typ: OpenAPIType | None) -> dict[str, list[dict[str, str]]]:
169
+ def _is_empty_object_type(typ: OpenAPIType) -> bool:
148
170
  if not isinstance(typ, OpenAPIObjectType):
171
+ return False
172
+ return len(typ.properties) == 0
173
+
174
+
175
+ _QUERY_PARM_METHODS = ("get", "head", "options")
176
+ _REQUEST_BODY_METHODS = ("put", "post", "patch", "delete")
177
+
178
+ ApiSchema = dict[str, "ApiSchema"] | Collection["ApiSchema"] | str | bool
179
+ DictApiSchema = dict[str, ApiSchema]
180
+
181
+
182
+ def _emit_endpoint_argument_examples(
183
+ examples: list[EmitOpenAPIEndpointExample],
184
+ ) -> DictApiSchema:
185
+ if len(examples) == 0:
186
+ return {}
187
+
188
+ response_examples = {}
189
+ for example in examples:
190
+ response_examples[example.ref_name] = {
191
+ "summary": example.summary,
192
+ "description": example.description,
193
+ "value": example.arguments,
194
+ }
195
+ return {"examples": response_examples}
196
+
197
+
198
+ def _emit_endpoint_parameter_examples(
199
+ examples: list[EmitOpenAPIEndpointExample],
200
+ ) -> DictApiSchema:
201
+ if len(examples) == 0:
202
+ return {}
203
+
204
+ paramater_examples = []
205
+ comment_new_line = "\n// "
206
+ new_line = "\n"
207
+ for example in examples:
208
+ javascript_description = (
209
+ f"// {comment_new_line.join(example.description.split(new_line))}"
210
+ )
211
+ javascript_json_payload = f"{json.dumps(example.arguments, indent=2)}"
212
+ paramater_examples.append({
213
+ "lang": "JavaScript",
214
+ "label": f"Payload - {example.summary}",
215
+ "source": f"{javascript_description}\n{javascript_json_payload}",
216
+ })
217
+ return {"x-codeSamples": paramater_examples}
218
+
219
+
220
+ def _emit_endpoint_parameters(
221
+ endpoint: EmitOpenAPIEndpoint,
222
+ argument_type: OpenAPIType | None,
223
+ examples: list[EmitOpenAPIEndpointExample],
224
+ ) -> DictApiSchema:
225
+ if (
226
+ endpoint.method.lower() not in _QUERY_PARM_METHODS
227
+ or argument_type is None
228
+ or _is_empty_object_type(argument_type)
229
+ ):
149
230
  return {}
150
231
 
151
232
  return {
152
233
  "parameters": [
153
- {"$ref": f"#/components/schema/Arguments/{prop_name}"}
154
- for prop_name in typ.properties
234
+ {
235
+ "name": "data",
236
+ "required": True,
237
+ "in": "query",
238
+ "content": {
239
+ "application/json": {
240
+ "schema": {"$ref": "#/components/schema/Arguments"}
241
+ }
242
+ },
243
+ }
155
244
  ]
156
- }
245
+ } | _emit_endpoint_parameter_examples(examples)
157
246
 
158
247
 
159
- def _emit_is_beta(is_beta: bool) -> dict[str, bool]:
248
+ def _emit_is_beta(is_beta: bool) -> DictApiSchema:
160
249
  if is_beta:
161
250
  return {"x-beta": True}
162
251
  return {}
163
252
 
164
253
 
254
+ def _emit_endpoint_request_body(
255
+ endpoint: EmitOpenAPIEndpoint,
256
+ arguments_type: OpenAPIType | None,
257
+ examples: list[EmitOpenAPIEndpointExample],
258
+ ) -> DictApiSchema:
259
+ if (
260
+ endpoint.method.lower() not in _REQUEST_BODY_METHODS
261
+ or arguments_type is None
262
+ or _is_empty_object_type(arguments_type)
263
+ ):
264
+ return {}
265
+
266
+ return {
267
+ "requestBody": {
268
+ "content": {
269
+ "application/json": {
270
+ "schema": {
271
+ "type": "object",
272
+ "title": "Body",
273
+ "required": ["data"],
274
+ "properties": {"data": {"$ref": "#/components/schema/Arguments"}},
275
+ }
276
+ }
277
+ | _emit_endpoint_argument_examples(examples)
278
+ },
279
+ }
280
+ }
281
+
282
+
283
+ def _emit_endpoint_response_examples(
284
+ examples: list[EmitOpenAPIEndpointExample],
285
+ ) -> dict[str, dict[str, object]]:
286
+ if len(examples) == 0:
287
+ return {}
288
+
289
+ response_examples: dict[str, object] = {}
290
+ for example in examples:
291
+ response_examples[example.ref_name] = {
292
+ "summary": example.summary,
293
+ "description": example.description,
294
+ "value": example.data,
295
+ }
296
+ return {"examples": response_examples}
297
+
298
+
299
+ def _emit_endpoint_description(
300
+ description: str, guides: list[EmitOpenAPIGuide]
301
+ ) -> 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
+ }
308
+
309
+
165
310
  def _emit_namespace(
166
311
  gctx: EmitOpenAPIGlobalContext,
167
312
  ctx: EmitOpenAPIContext,
168
313
  namespace: builder.SpecNamespace,
169
314
  *,
170
315
  config: OpenAPIConfig,
316
+ examples: dict[str, list[builder.SpecEndpointExample]],
317
+ guides: dict[builder.SpecGuideKey, list[builder.SpecGuide]],
171
318
  ) -> None:
172
319
  for stype in namespace.types.values():
173
320
  _emit_type(ctx, stype, config=config)
174
321
 
175
322
  if namespace.endpoint is not None:
176
- _emit_endpoint(gctx, ctx, namespace, namespace.endpoint)
323
+ endpoint_examples = examples.get(namespace.endpoint.resolved_path, [])
324
+ endpoint_guides = guides.get(
325
+ EndpointGuideKey(path=namespace.endpoint.resolved_path), []
326
+ )
327
+ _emit_endpoint(
328
+ gctx, ctx, namespace, namespace.endpoint, endpoint_examples, endpoint_guides
329
+ )
177
330
 
178
331
  oa_components: dict[str, object] = dict()
179
332
 
180
333
  if ctx.endpoint is not None:
181
334
  endpoint = ctx.endpoint
182
-
183
335
  argument_type = ctx.types.get("Arguments")
184
336
  oa_endpoint = dict()
185
337
  oa_endpoint[endpoint.method] = (
186
338
  {
187
339
  "tags": endpoint.tags,
188
340
  "summary": endpoint.summary,
189
- "description": endpoint.description,
190
341
  }
342
+ | _emit_endpoint_description(endpoint.description, ctx.endpoint.guides)
191
343
  | _emit_is_beta(endpoint.is_beta)
192
- | _emit_endpoint_parameters(argument_type)
344
+ | _emit_endpoint_parameters(endpoint, argument_type, ctx.endpoint.examples)
345
+ | _emit_endpoint_request_body(endpoint, argument_type, ctx.endpoint.examples)
193
346
  | {
194
347
  "responses": {
195
348
  "200": {
@@ -198,6 +351,7 @@ def _emit_namespace(
198
351
  "application/json": {
199
352
  "schema": {"$ref": "#/components/schema/Data"}
200
353
  }
354
+ | _emit_endpoint_response_examples(ctx.endpoint.examples)
201
355
  },
202
356
  }
203
357
  },
@@ -236,10 +390,7 @@ def _emit_namespace(
236
390
 
237
391
  oa_components["schema"] = cast(
238
392
  object,
239
- {
240
- name: (value.asdict() if name != "Arguments" else value.asarguments())
241
- for name, value in types.items()
242
- },
393
+ {name: value.asdict() for name, value in types.items()},
243
394
  )
244
395
 
245
396
  path = f"{config.types_output}/common/{'/'.join(namespace.path)}.yaml"
@@ -348,6 +499,8 @@ def _emit_endpoint(
348
499
  ctx: EmitOpenAPIContext,
349
500
  namespace: builder.SpecNamespace,
350
501
  endpoint: builder.SpecEndpoint,
502
+ endpoint_examples: list[builder.SpecEndpointExample],
503
+ endpoint_guides: list[builder.SpecGuide],
351
504
  ) -> None:
352
505
  assert namespace.endpoint is not None
353
506
  assert namespace.path[0] == "api"
@@ -397,6 +550,23 @@ def _emit_endpoint(
397
550
  summary=f"{'/'.join(namespace.path[path_cutoff:])}",
398
551
  description=description,
399
552
  is_beta=namespace.endpoint.is_beta,
553
+ examples=[
554
+ EmitOpenAPIEndpointExample(
555
+ ref_name=f"ex_{i}",
556
+ summary=example.summary,
557
+ description=example.description,
558
+ arguments=example.arguments,
559
+ data=example.data,
560
+ )
561
+ for i, example in enumerate(endpoint_examples)
562
+ ],
563
+ guides=[
564
+ EmitOpenAPIGuide(
565
+ title=guide.title,
566
+ html_content=guide.html_content,
567
+ )
568
+ for guide in endpoint_guides
569
+ ],
400
570
  )
401
571
 
402
572
 
@@ -43,6 +43,12 @@ class EmitOpenAPIServer:
43
43
  url: str
44
44
 
45
45
 
46
+ @dataclass(kw_only=True)
47
+ class EmitOpenAPIGuide:
48
+ title: str
49
+ html_content: str
50
+
51
+
46
52
  @dataclass
47
53
  class EmitOpenAPIGlobalContext:
48
54
  version: str
@@ -56,6 +62,15 @@ class EmitOpenAPIGlobalContext:
56
62
  paths: list[EmitOpenAPIPath] = field(default_factory=list)
57
63
 
58
64
 
65
+ @dataclass(kw_only=True)
66
+ class EmitOpenAPIEndpointExample:
67
+ ref_name: str
68
+ summary: str
69
+ description: str
70
+ arguments: dict[str, object]
71
+ data: dict[str, object]
72
+
73
+
59
74
  @dataclass
60
75
  class EmitOpenAPIEndpoint:
61
76
  method: str
@@ -63,6 +78,8 @@ class EmitOpenAPIEndpoint:
63
78
  summary: str
64
79
  description: str
65
80
  is_beta: bool
81
+ examples: list[EmitOpenAPIEndpointExample]
82
+ guides: list[EmitOpenAPIGuide]
66
83
 
67
84
 
68
85
  @dataclass
@@ -29,7 +29,7 @@ ASYNC_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
29
29
  namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="AsyncBatchRequest"
30
30
  )
31
31
  QUEUED_BATCH_REQUEST_STYPE = builder.SpecTypeDefnObject(
32
- namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedBatchRequest"
32
+ namespace=ASYNC_BATCH_TYPE_NAMESPACE, name="QueuedAsyncBatchRequest"
33
33
  )
34
34
 
35
35
 
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from collections.abc import Callable
3
+ from io import StringIO
3
4
  from typing import Optional
4
5
 
5
6
  import yaml
@@ -13,11 +14,28 @@ ext_map = {
13
14
  ".py": "python",
14
15
  }
15
16
 
17
+ _DOC_FILE_REFEX = ".*/docs/(examples|guides)/.*yaml"
18
+ _EXAMPLE_FILE_REGEX = ".*/docs/examples/.*yaml"
19
+ _GUIDE_FILE_REGEX = ".*/docs/guides/.*md"
20
+
16
21
 
17
22
  def find_and_handle_files(
18
- root_folder: str, name_regex: str, handler: Callable[[str, str], None]
23
+ *,
24
+ root_folder: str,
25
+ handler: Callable[[str, str], None],
26
+ name_regex: str | None = None,
27
+ not_name_regex: str | None = None,
28
+ whole_name_regex: str | None = None,
29
+ not_whole_name_regex: str | None = None,
19
30
  ) -> None:
20
- for file_name in fs.find(root_folder, name_regex=name_regex, relative=True):
31
+ for file_name in fs.find(
32
+ root_folder,
33
+ name_regex=name_regex,
34
+ not_name_regex=not_name_regex,
35
+ whole_name_regex=whole_name_regex,
36
+ not_whole_name_regex=not_whole_name_regex,
37
+ relative=True,
38
+ ):
21
39
  with open(os.path.join(root_folder, file_name), encoding="utf-8") as file:
22
40
  handler(file_name, file.read())
23
41
 
@@ -32,9 +50,16 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
32
50
  name, ext = os.path.splitext(by_name)
33
51
  handler(ext_map[ext], name, file_content)
34
52
 
53
+ def handle_builder_example_add(file_name: str, file_content: str) -> None:
54
+ yaml_content = yaml.safe_load(StringIO(file_content))
55
+ builder.add_example_file(yaml_content)
56
+
57
+ def handle_builder_guide_add(file_name: str, file_content: str) -> None:
58
+ builder.add_guide_file(file_content)
59
+
35
60
  for folder in config.type_spec_types:
36
61
  find_and_handle_files(
37
- folder,
62
+ root_folder=folder,
38
63
  name_regex=".*\\.(ts|py)\\.part",
39
64
  handler=lambda file_name, file_content: handle_builder_add(
40
65
  file_name, file_content, builder.add_part_file
@@ -43,7 +68,7 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
43
68
 
44
69
  for folder in config.type_spec_types:
45
70
  find_and_handle_files(
46
- folder,
71
+ root_folder=folder,
47
72
  name_regex=".*\\.(ts|py)\\.prepart",
48
73
  handler=lambda file_name, file_content: handle_builder_add(
49
74
  file_name, file_content, builder.add_prepart_file
@@ -64,9 +89,27 @@ def load_types(config: Config) -> Optional[SpecBuilder]:
64
89
 
65
90
  for folder in config.type_spec_types:
66
91
  find_and_handle_files(
67
- folder, name_regex=".*\\.yaml", handler=builder_prescan_file
92
+ root_folder=folder,
93
+ name_regex=".*\\.yaml",
94
+ not_whole_name_regex=_DOC_FILE_REFEX,
95
+ handler=builder_prescan_file,
68
96
  )
69
97
 
98
+ if config.open_api is not None:
99
+ for folder in config.type_spec_types:
100
+ find_and_handle_files(
101
+ root_folder=folder,
102
+ whole_name_regex=_EXAMPLE_FILE_REGEX,
103
+ handler=handle_builder_example_add,
104
+ )
105
+
106
+ for folder in config.type_spec_types:
107
+ find_and_handle_files(
108
+ root_folder=folder,
109
+ whole_name_regex=_GUIDE_FILE_REGEX,
110
+ handler=handle_builder_guide_add,
111
+ )
112
+
70
113
  if not builder.process():
71
114
  return None
72
115