UncountablePythonSDK 0.0.31__tar.gz → 0.0.34__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 (195) hide show
  1. {uncountablepythonsdk-0.0.31/UncountablePythonSDK.egg-info → uncountablepythonsdk-0.0.34}/PKG-INFO +2 -2
  2. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34/UncountablePythonSDK.egg-info}/PKG-INFO +2 -2
  3. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/UncountablePythonSDK.egg-info/SOURCES.txt +2 -0
  4. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/UncountablePythonSDK.egg-info/requires.txt +1 -1
  5. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/argument_parser/argument_parser.py +41 -2
  6. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization/__init__.py +2 -0
  7. uncountablepythonsdk-0.0.34/pkgs/serialization/serial_union.py +81 -0
  8. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization_util/__init__.py +1 -7
  9. uncountablepythonsdk-0.0.34/pkgs/serialization_util/convert_to_snakecase.py +27 -0
  10. uncountablepythonsdk-0.0.34/pkgs/serialization_util/serialization_helpers.py +230 -0
  11. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/builder.py +57 -1
  12. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_open_api.py +6 -0
  13. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_python.py +23 -0
  14. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_typescript.py +6 -0
  15. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/type_info/emit_type_info.py +30 -4
  16. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pyproject.toml +1 -1
  17. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/id_source/match_id_source.py +0 -4
  18. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/edit_recipe_inputs.py +13 -1
  19. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_recipes_data.py +0 -3
  20. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_workflow_steps.py +14 -3
  21. uncountablepythonsdk-0.0.31/pkgs/serialization_util/serialization_helpers.py +0 -175
  22. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/.github/workflows/documentation.yml +0 -0
  23. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/.github/workflows/publish.yml +0 -0
  24. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/.gitignore +0 -0
  25. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/README.md +0 -0
  26. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/UncountablePythonSDK.egg-info/dependency_links.txt +0 -0
  27. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/UncountablePythonSDK.egg-info/top_level.txt +0 -0
  28. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/.gitignore +0 -0
  29. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/conf.py +0 -0
  30. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/index.md +0 -0
  31. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/justfile +0 -0
  32. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/quickstart.md +0 -0
  33. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/requirements.txt +0 -0
  34. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/android-chrome-192x192.png +0 -0
  35. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/android-chrome-512x512.png +0 -0
  36. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/apple-touch-icon.png +0 -0
  37. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/browserconfig.xml +0 -0
  38. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/favicon-16x16.png +0 -0
  39. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/favicon-32x32.png +0 -0
  40. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/manifest.json +0 -0
  41. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/mstile-150x150.png +0 -0
  42. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/favicons/safari-pinned-tab.svg +0 -0
  43. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/docs/static/logo_blue.png +0 -0
  44. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/examples/async_batch.py +0 -0
  45. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/examples/create_entity.py +0 -0
  46. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/examples/upload_files.py +0 -0
  47. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/__init__.py +0 -0
  48. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/argument_parser/__init__.py +0 -0
  49. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/argument_parser/_is_enum.py +0 -0
  50. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/argument_parser/_is_namedtuple.py +0 -0
  51. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/argument_parser/case_convert.py +0 -0
  52. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization/missing_sentry.py +0 -0
  53. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization/opaque_key.py +0 -0
  54. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization/serial_class.py +0 -0
  55. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/serialization_util/_get_type_for_serialization.py +0 -0
  56. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/strenum_compat/__init__.py +0 -0
  57. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/strenum_compat/strenum_compat.py +0 -0
  58. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/__init__.py +0 -0
  59. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/__main__.py +0 -0
  60. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/actions_registry/__init__.py +0 -0
  61. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/actions_registry/__main__.py +0 -0
  62. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/actions_registry/emit_typescript.py +0 -0
  63. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/config.py +0 -0
  64. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_io_ts.py +0 -0
  65. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_open_api_util.py +0 -0
  66. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/emit_typescript_util.py +0 -0
  67. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/load_types.py +0 -0
  68. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/open_api_util.py +0 -0
  69. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/parts/base.py.prepart +0 -0
  70. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/parts/base.ts.prepart +0 -0
  71. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/test.py +0 -0
  72. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/type_info/__main__.py +0 -0
  73. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/util.py +0 -0
  74. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/value_spec/__init__.py +0 -0
  75. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/value_spec/__main__.py +0 -0
  76. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/value_spec/convert_type.py +0 -0
  77. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/value_spec/emit_python.py +0 -0
  78. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/pkgs/type_spec/value_spec/types.py +0 -0
  79. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/setup.cfg +0 -0
  80. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/__init__.py +0 -0
  81. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/core/__init__.py +0 -0
  82. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/core/async_batch.py +0 -0
  83. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/core/client.py +0 -0
  84. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/core/file_upload.py +0 -0
  85. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/core/types.py +0 -0
  86. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/__init__.py +0 -0
  87. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/construct_client.py +0 -0
  88. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/cron.py +0 -0
  89. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/db/__init__.py +0 -0
  90. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/db/connect.py +0 -0
  91. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/entrypoint.py +0 -0
  92. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/executors/__init__.py +0 -0
  93. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/executors/script_executor.py +0 -0
  94. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/job.py +0 -0
  95. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/server.py +0 -0
  96. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/integration/types.py +0 -0
  97. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/py.typed +0 -0
  98. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/__init__.py +0 -0
  99. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/__init__.py +0 -0
  100. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/batch/__init__.py +0 -0
  101. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/batch/execute_batch.py +0 -0
  102. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/batch/execute_batch_load_async.py +0 -0
  103. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/chemical/__init__.py +0 -0
  104. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/chemical/convert_chemical_formats.py +0 -0
  105. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/__init__.py +0 -0
  106. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/create_entities.py +0 -0
  107. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/create_entity.py +0 -0
  108. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/get_entities_data.py +0 -0
  109. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/list_entities.py +0 -0
  110. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/lock_entity.py +0 -0
  111. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/resolve_entity_ids.py +0 -0
  112. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/set_values.py +0 -0
  113. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/transition_entity_phase.py +0 -0
  114. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/entity/unlock_entity.py +0 -0
  115. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/field_options/__init__.py +0 -0
  116. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/field_options/upsert_field_options.py +0 -0
  117. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/id_source/__init__.py +0 -0
  118. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/id_source/list_id_source.py +0 -0
  119. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/input_groups/__init__.py +0 -0
  120. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/input_groups/get_input_group_names.py +0 -0
  121. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/__init__.py +0 -0
  122. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/create_inputs.py +0 -0
  123. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/get_input_data.py +0 -0
  124. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/get_input_names.py +0 -0
  125. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/get_inputs_data.py +0 -0
  126. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/set_input_attribute_values.py +0 -0
  127. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/set_input_category.py +0 -0
  128. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/set_input_subcategories.py +0 -0
  129. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/inputs/set_intermediate_type.py +0 -0
  130. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/material_families/__init__.py +0 -0
  131. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/material_families/update_entity_material_families.py +0 -0
  132. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/outputs/__init__.py +0 -0
  133. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/outputs/get_output_data.py +0 -0
  134. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/outputs/get_output_names.py +0 -0
  135. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/outputs/resolve_output_conditions.py +0 -0
  136. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/permissions/__init__.py +0 -0
  137. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/permissions/set_core_permissions.py +0 -0
  138. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/project/__init__.py +0 -0
  139. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/project/get_projects.py +0 -0
  140. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/project/get_projects_data.py +0 -0
  141. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipe_links/__init__.py +0 -0
  142. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipe_links/create_recipe_link.py +0 -0
  143. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipe_metadata/__init__.py +0 -0
  144. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +0 -0
  145. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/__init__.py +0 -0
  146. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/add_recipe_to_project.py +0 -0
  147. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/archive_recipes.py +0 -0
  148. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/associate_recipe_as_input.py +0 -0
  149. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/associate_recipe_as_lot.py +0 -0
  150. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/create_recipe.py +0 -0
  151. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/create_recipes.py +0 -0
  152. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/disassociate_recipe_as_input.py +0 -0
  153. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_curve.py +0 -0
  154. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_recipe_calculations.py +0 -0
  155. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_recipe_links.py +0 -0
  156. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_recipe_names.py +0 -0
  157. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -0
  158. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/remove_recipe_from_project.py +0 -0
  159. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/set_recipe_inputs.py +0 -0
  160. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/set_recipe_metadata.py +0 -0
  161. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/set_recipe_outputs.py +0 -0
  162. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/set_recipe_tags.py +0 -0
  163. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/recipes/unarchive_recipes.py +0 -0
  164. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/triggers/__init__.py +0 -0
  165. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/api/triggers/run_trigger.py +0 -0
  166. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/async_batch.py +0 -0
  167. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/async_batch_processor.py +0 -0
  168. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/base.py +0 -0
  169. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/calculations.py +0 -0
  170. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/chemical_structure.py +0 -0
  171. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/client_base.py +0 -0
  172. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/curves.py +0 -0
  173. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/entity.py +0 -0
  174. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/experiment_groups.py +0 -0
  175. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/field_values.py +0 -0
  176. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/fields.py +0 -0
  177. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/id_source.py +0 -0
  178. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/identifier.py +0 -0
  179. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/input_attributes.py +0 -0
  180. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/inputs.py +0 -0
  181. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/outputs.py +0 -0
  182. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/permissions.py +0 -0
  183. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/phases.py +0 -0
  184. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/post_base.py +0 -0
  185. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_identifiers.py +0 -0
  186. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_inputs.py +0 -0
  187. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_links.py +0 -0
  188. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_metadata.py +0 -0
  189. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_output_metadata.py +0 -0
  190. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipe_tags.py +0 -0
  191. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/recipes.py +0 -0
  192. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/response.py +0 -0
  193. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/units.py +0 -0
  194. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/users.py +0 -0
  195. {uncountablepythonsdk-0.0.31 → uncountablepythonsdk-0.0.34}/uncountable/types/workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.31
3
+ Version: 0.0.34
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
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
19
19
  Requires-Dist: aiotus==0.*
20
20
  Requires-Dist: aiohttp==3.*
21
21
  Requires-Dist: requests==2.*
22
- Requires-Dist: SQLAlchemy==1.4.*
22
+ Requires-Dist: SQLAlchemy>=1.4.0
23
23
  Requires-Dist: APScheduler==3.*
24
24
  Requires-Dist: python-dateutil==2.*
25
25
  Requires-Dist: shelljob==0.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.31
3
+ Version: 0.0.34
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
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
19
19
  Requires-Dist: aiotus==0.*
20
20
  Requires-Dist: aiohttp==3.*
21
21
  Requires-Dist: requests==2.*
22
- Requires-Dist: SQLAlchemy==1.4.*
22
+ Requires-Dist: SQLAlchemy>=1.4.0
23
23
  Requires-Dist: APScheduler==3.*
24
24
  Requires-Dist: python-dateutil==2.*
25
25
  Requires-Dist: shelljob==0.*
@@ -37,8 +37,10 @@ pkgs/serialization/__init__.py
37
37
  pkgs/serialization/missing_sentry.py
38
38
  pkgs/serialization/opaque_key.py
39
39
  pkgs/serialization/serial_class.py
40
+ pkgs/serialization/serial_union.py
40
41
  pkgs/serialization_util/__init__.py
41
42
  pkgs/serialization_util/_get_type_for_serialization.py
43
+ pkgs/serialization_util/convert_to_snakecase.py
42
44
  pkgs/serialization_util/serialization_helpers.py
43
45
  pkgs/strenum_compat/__init__.py
44
46
  pkgs/strenum_compat/strenum_compat.py
@@ -1,7 +1,7 @@
1
1
  aiotus==0.*
2
2
  aiohttp==3.*
3
3
  requests==2.*
4
- SQLAlchemy==1.4.*
4
+ SQLAlchemy>=1.4.0
5
5
  APScheduler==3.*
6
6
  python-dateutil==2.*
7
7
  shelljob==0.*
@@ -10,7 +10,12 @@ from importlib import resources
10
10
  import dateutil.parser
11
11
  import yaml
12
12
 
13
- from pkgs.serialization import MissingSentryType, OpaqueKey, get_serial_class_data
13
+ from pkgs.serialization import (
14
+ MissingSentryType,
15
+ OpaqueKey,
16
+ get_serial_class_data,
17
+ get_serial_union_data,
18
+ )
14
19
 
15
20
  from ._is_enum import is_string_enum_class
16
21
  from ._is_namedtuple import is_namedtuple_type
@@ -112,6 +117,23 @@ def _invoke_membership_parser(
112
117
  raise ValueError(f"Expected value from {expected_values} but got value {value}")
113
118
 
114
119
 
120
+ def _build_parser_discriminated_union(
121
+ discriminator: str, discriminator_map: dict[str, ParserFunction[T]]
122
+ ) -> ParserFunction[T]:
123
+ def parse(value: typing.Any) -> typing.Any:
124
+ discriminant = value.get(discriminator)
125
+ if discriminant is None:
126
+ raise ValueError("missing-union-discriminant")
127
+ if not isinstance(discriminant, str):
128
+ raise ValueError("union-discriminant-is-not-string")
129
+ parser = discriminator_map.get(discriminant)
130
+ if parser is None:
131
+ raise ValueError("missing-type-for-union-discriminant", discriminant)
132
+ return parser(value)
133
+
134
+ return parse
135
+
136
+
115
137
  def _build_parser_inner(
116
138
  parsed_type: type[T],
117
139
  context: ParserContext,
@@ -130,6 +152,23 @@ def _build_parser_inner(
130
152
  are cached now, as they don't use the argument, and they're known to be safe.
131
153
  This is also enough to support some recursion.
132
154
  """
155
+
156
+ serial_union = get_serial_union_data(parsed_type)
157
+ if serial_union is not None:
158
+ discriminator = serial_union.discriminator
159
+ discriminator_map = serial_union.discriminator_map
160
+ if discriminator is None or discriminator_map is None:
161
+ # fallback to standard union parsing
162
+ parsed_type = serial_union.get_union_underlying()
163
+ else:
164
+ return _build_parser_discriminated_union(
165
+ discriminator,
166
+ {
167
+ key: _build_parser_inner(value, context)
168
+ for key, value in discriminator_map.items()
169
+ },
170
+ )
171
+
133
172
  if dataclasses.is_dataclass(parsed_type):
134
173
  return _build_parser_dataclass(parsed_type, context) # type: ignore[arg-type]
135
174
 
@@ -341,7 +380,7 @@ def _build_parser_dataclass(
341
380
 
342
381
  except Exception as e:
343
382
  raise ValueError(
344
- f"unable to parse field:{field.name}", field_raw_value
383
+ f"unable-to-parse-field:{field.name}", field_raw_value
345
384
  ) from e
346
385
 
347
386
  if context.options.strict_property_parsing:
@@ -8,3 +8,5 @@ from .serial_class import get_serial_class_data as get_serial_class_data
8
8
  from .serial_class import get_serial_string_enum_data as get_serial_string_enum_data
9
9
  from .serial_class import serial_class as serial_class
10
10
  from .serial_class import serial_string_enum as serial_string_enum
11
+ from .serial_union import serial_union_annotation as serial_union_annotation
12
+ from .serial_union import get_serial_union_data as get_serial_union_data
@@ -0,0 +1,81 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ T = typing.TypeVar("T")
5
+
6
+
7
+ class IdentityHashWrapper(typing.Generic[T]):
8
+ """This allows unhashable types to be used in the SerialUnion, like dict.
9
+ Since we have only one copy of the types themselves, we rely on
10
+ object identity for the hashing."""
11
+
12
+ def __init__(self, inner: T) -> None:
13
+ self.inner = inner
14
+
15
+ def __hash__(self) -> int:
16
+ return id(self.inner)
17
+
18
+
19
+ @dataclasses.dataclass(kw_only=True, frozen=True, eq=True)
20
+ class _SerialUnion:
21
+ """
22
+ This class is to be kept private, to provide flexibility in registration/lookup.
23
+ Places that need the data should access it via help classes/methods.
24
+ """
25
+
26
+ # If specified, indicates the Union has a discriminator which should be used to
27
+ # determine which type to parse.
28
+ discriminator: typing.Optional[str] = None
29
+ discriminator_map: typing.Optional[IdentityHashWrapper[dict[str, type]]] = None
30
+
31
+
32
+ def serial_union_annotation(
33
+ *,
34
+ discriminator: typing.Optional[str] = None,
35
+ discriminator_map: typing.Optional[dict[str, type]] = None,
36
+ ) -> _SerialUnion:
37
+ return _SerialUnion(
38
+ discriminator=discriminator,
39
+ discriminator_map=IdentityHashWrapper(discriminator_map)
40
+ if discriminator_map is not None
41
+ else None,
42
+ )
43
+
44
+
45
+ def _get_serial_union(parsed_type: type[T]) -> _SerialUnion | None:
46
+ if not hasattr(parsed_type, "__metadata__"):
47
+ return None
48
+ metadata = parsed_type.__metadata__ # type:ignore[attr-defined]
49
+ if not isinstance(metadata, tuple) or len(metadata) != 1:
50
+ return None
51
+ serial = metadata[0]
52
+ if not isinstance(serial, _SerialUnion):
53
+ return None
54
+ return serial
55
+
56
+
57
+ class SerialClassInspector(typing.Generic[T]):
58
+ def __init__(self, parsed_type: type[T], serial_union: _SerialUnion) -> None:
59
+ self._parsed_type = parsed_type
60
+ self._serial_union = serial_union
61
+
62
+ def get_union_underlying(self) -> type[T]:
63
+ return typing.get_args(self._parsed_type)[0] # type:ignore[no-any-return]
64
+
65
+ @property
66
+ def discriminator(self) -> typing.Optional[str]:
67
+ return self._serial_union.discriminator
68
+
69
+ @property
70
+ def discriminator_map(self) -> typing.Optional[dict[str, type]]:
71
+ if self._serial_union.discriminator_map is None:
72
+ return None
73
+ return self._serial_union.discriminator_map.inner
74
+
75
+
76
+ def get_serial_union_data(parsed_type: type[T]) -> SerialClassInspector[T] | None:
77
+ serial = _get_serial_union(parsed_type)
78
+ if serial is None:
79
+ return None
80
+
81
+ return SerialClassInspector(parsed_type, serial)
@@ -1,8 +1,5 @@
1
+ from .convert_to_snakecase import convert_dict_to_snake_case
1
2
  from .serialization_helpers import (
2
- convert_dict_to_snake_case,
3
- convert_to_camelcase,
4
- resolve_missing_to_none,
5
- serialize,
6
3
  serialize_for_api,
7
4
  serialize_for_storage,
8
5
  serialize_for_storage_dict,
@@ -10,9 +7,6 @@ from .serialization_helpers import (
10
7
 
11
8
  __all__: list[str] = [
12
9
  "convert_dict_to_snake_case",
13
- "convert_to_camelcase",
14
- "resolve_missing_to_none",
15
- "serialize",
16
10
  "serialize_for_api",
17
11
  "serialize_for_storage",
18
12
  "serialize_for_storage_dict",
@@ -0,0 +1,27 @@
1
+ from typing import (
2
+ Any,
3
+ )
4
+
5
+ from pkgs.argument_parser import camel_to_snake_case
6
+ from pkgs.serialization import (
7
+ MISSING_SENTRY,
8
+ OpaqueKey,
9
+ )
10
+
11
+
12
+ def _key_convert_to_snake_case(o: Any) -> Any:
13
+ if isinstance(o, OpaqueKey):
14
+ return o
15
+ if isinstance(o, str):
16
+ return camel_to_snake_case(o)
17
+ return o
18
+
19
+
20
+ def convert_dict_to_snake_case(data: Any) -> Any:
21
+ return {
22
+ _key_convert_to_snake_case(k): convert_dict_to_snake_case(v)
23
+ if isinstance(v, dict)
24
+ else v
25
+ for k, v in data.items()
26
+ if v != MISSING_SENTRY
27
+ }
@@ -0,0 +1,230 @@
1
+ import dataclasses
2
+ import datetime
3
+ import enum
4
+ import functools
5
+ from collections.abc import Callable, Mapping, Sequence
6
+ from decimal import Decimal
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ ClassVar,
11
+ Protocol,
12
+ TypeVar,
13
+ Union,
14
+ overload,
15
+ )
16
+
17
+ try:
18
+ import flask
19
+ except Exception:
20
+ pass
21
+
22
+ from pkgs.argument_parser import snake_to_camel_case
23
+ from pkgs.serialization import (
24
+ MISSING_SENTRY,
25
+ OpaqueKey,
26
+ get_serial_class_data,
27
+ )
28
+
29
+ from ._get_type_for_serialization import SerializationType, get_serialization_type
30
+
31
+ # Inlined types which otherwise would import from types/base.py
32
+ JsonScalar = Union[str, float, bool, Decimal, None, datetime.datetime, datetime.date]
33
+ if TYPE_CHECKING:
34
+ JsonValue = Union[JsonScalar, Mapping[str, "JsonValue"], Sequence["JsonValue"]]
35
+ else:
36
+ JsonValue = Union[JsonScalar, dict[str, Any], list[Any]]
37
+
38
+ T = TypeVar("T")
39
+
40
+
41
+ class Dataclass(Protocol):
42
+ __dataclass_fields__: ClassVar[dict] # type: ignore[type-arg,unused-ignore]
43
+
44
+
45
+ def identity(x: T) -> T:
46
+ return x
47
+
48
+
49
+ def key_convert_to_camelcase(o: Any) -> str:
50
+ if isinstance(o, OpaqueKey):
51
+ return o
52
+ if isinstance(o, enum.StrEnum):
53
+ return o.value
54
+ if isinstance(o, enum.Enum):
55
+ try:
56
+ print(
57
+ "DEPRECATED_SERIALIZATION--non-string-enum-used-as-key",
58
+ flask.request.path,
59
+ )
60
+ except Exception:
61
+ pass
62
+ return o.value # type: ignore[no-any-return]
63
+ if isinstance(o, str):
64
+ return snake_to_camel_case(o)
65
+ if isinstance(o, int):
66
+ # temporary bypass to maintain behavior
67
+ return o # type: ignore[return-value]
68
+ try:
69
+ print("DEPRECATED_SERIALIZATION--non-string-used-as-key", o, flask.request.path)
70
+ except Exception:
71
+ pass
72
+ return o # type: ignore[no-any-return]
73
+
74
+
75
+ def _convert_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
76
+ return {
77
+ key_convert_to_camelcase(k): serialize_for_api(v)
78
+ for k, v in d.items()
79
+ if v != MISSING_SENTRY
80
+ }
81
+
82
+
83
+ def _serialize_dict(d: dict[str, Any]) -> dict[str, JsonValue]:
84
+ return {k: serialize_for_storage(v) for k, v in d.items() if v != MISSING_SENTRY}
85
+
86
+
87
+ def _to_string_value(value: Any) -> str:
88
+ assert isinstance(value, (Decimal, int))
89
+ return str(value)
90
+
91
+
92
+ @dataclasses.dataclass(kw_only=True)
93
+ class DataclassConversions:
94
+ key_conversions: dict[str, str]
95
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]]
96
+
97
+
98
+ @functools.lru_cache(maxsize=10000)
99
+ def _get_dataclass_conversion_lookups(dataclass_type: Any) -> DataclassConversions:
100
+ scd = get_serial_class_data(dataclass_type)
101
+
102
+ key_conversions: dict[str, str] = {}
103
+ value_conversion_functions: dict[str, Callable[[Any], JsonValue]] = {}
104
+
105
+ for field in dataclasses.fields(dataclass_type):
106
+ key = field.name
107
+ if scd.has_unconverted_key(key):
108
+ key_conversions[key] = key
109
+ else:
110
+ key_conversions[key] = key_convert_to_camelcase(key)
111
+
112
+ if scd.has_to_string_value(key):
113
+ value_conversion_functions[key] = _to_string_value
114
+ elif scd.has_unconverted_value(key):
115
+ value_conversion_functions[key] = identity
116
+ else:
117
+ value_conversion_functions[key] = serialize_for_api
118
+
119
+ return DataclassConversions(
120
+ key_conversions=key_conversions,
121
+ value_conversion_functions=value_conversion_functions,
122
+ )
123
+
124
+
125
+ def _convert_dataclass(d: Any) -> dict[str, JsonValue]:
126
+ conversions = _get_dataclass_conversion_lookups(type(d)) # type: ignore[arg-type]
127
+ # return {
128
+ # conversions.key_conversions[k]: (
129
+ # conversions.value_conversion_functions[k](v) if v is not None else None
130
+ # )
131
+ # for k, v in d.__dict__.items()
132
+ # if v != MISSING_SENTRY
133
+ # }
134
+
135
+ serialized_data_class: dict[str, JsonValue] = {}
136
+ for k, v in d.__dict__.items():
137
+ if v == MISSING_SENTRY:
138
+ continue
139
+ if k not in conversions.key_conversions:
140
+ try:
141
+ print(
142
+ "DEPRECATED_SERIALIZATION--missing-dataclass-key",
143
+ k,
144
+ flask.request.path,
145
+ )
146
+ except Exception:
147
+ pass
148
+ continue
149
+ serialized_data_class[conversions.key_conversions[k]] = (
150
+ conversions.value_conversion_functions[k](v) if v is not None else None
151
+ )
152
+
153
+ return serialized_data_class
154
+
155
+
156
+ _SERIALIZATION_FUNCS_STANDARD = {
157
+ SerializationType.ENUM: lambda x: str(x.value),
158
+ SerializationType.DATE: lambda x: x.isoformat(),
159
+ SerializationType.TIMEDELTA: lambda x: x.total_seconds(),
160
+ SerializationType.UNKNOWN: identity,
161
+ }
162
+
163
+ _CONVERSION_SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
164
+ **_SERIALIZATION_FUNCS_STANDARD,
165
+ SerializationType.NAMED_TUPLE: lambda x: _convert_dict(x._asdict()),
166
+ SerializationType.ITERABLE: lambda x: [serialize_for_api(v) for v in x],
167
+ SerializationType.DICT: _convert_dict,
168
+ SerializationType.DATACLASS: _convert_dataclass,
169
+ }
170
+
171
+
172
+ @overload
173
+ def serialize_for_api(obj: None) -> None: ...
174
+
175
+
176
+ @overload
177
+ def serialize_for_api(obj: dict[str, Any]) -> dict[str, JsonValue]: ...
178
+
179
+
180
+ @overload
181
+ def serialize_for_api(obj: Dataclass) -> dict[str, JsonValue]: ...
182
+
183
+
184
+ @overload
185
+ def serialize_for_api(obj: Any) -> JsonValue: ...
186
+
187
+
188
+ def serialize_for_api(obj: Any) -> JsonValue:
189
+ """
190
+ Serialize to a parsed-JSON format suitably encoded for API output.
191
+
192
+ Use the CachedParser.parse_api to parse this data.
193
+ """
194
+ serialization_type = get_serialization_type(type(obj)) # type: ignore
195
+ return _CONVERSION_SERIALIZATION_FUNCS[serialization_type](obj)
196
+
197
+
198
+ _SERIALIZATION_FUNCS_DICT: dict[
199
+ SerializationType, Callable[[Any], dict[str, JsonValue]]
200
+ ] = {
201
+ SerializationType.DICT: _serialize_dict,
202
+ SerializationType.DATACLASS: lambda x: _serialize_dict(x.__dict__),
203
+ }
204
+
205
+
206
+ _SERIALIZATION_FUNCS: dict[SerializationType, Callable[[Any], JsonValue]] = {
207
+ **_SERIALIZATION_FUNCS_STANDARD,
208
+ **_SERIALIZATION_FUNCS_DICT,
209
+ SerializationType.NAMED_TUPLE: lambda x: _serialize_dict(x._asdict()),
210
+ SerializationType.ITERABLE: lambda x: [serialize_for_storage(v) for v in x],
211
+ }
212
+
213
+
214
+ def serialize_for_storage(obj: Any) -> JsonValue:
215
+ """
216
+ Convert a value into the pseudo-JSON form for
217
+ storage in the DB, file, or other non-API use.
218
+
219
+ Use the CachedParser.parse_storage to parse this data.
220
+ """
221
+ serialization_type = get_serialization_type(type(obj)) # type: ignore
222
+ return _SERIALIZATION_FUNCS[serialization_type](obj)
223
+
224
+
225
+ def serialize_for_storage_dict(obj: dict | Dataclass) -> dict[str, JsonValue]: # type: ignore[type-arg]
226
+ """
227
+ Same as serialize for storage but guarantees outer object is a dictionary
228
+ """
229
+ serialization_type = get_serialization_type(type(obj))
230
+ return _SERIALIZATION_FUNCS_DICT[serialization_type](obj)
@@ -102,6 +102,8 @@ class DefnTypeName(StrEnum):
102
102
  s_string_enum = "StringEnum"
103
103
  # a particular literal value
104
104
  s_string_literal = "_StringLiteral"
105
+ # A union of several other types
106
+ s_union = "Union"
105
107
 
106
108
 
107
109
  base_namespace_name = "base"
@@ -547,13 +549,65 @@ class SpecTypeDefnAlias(SpecTypeDefn):
547
549
  super().base_process(builder, data, ["type", "desc", "alias", "discriminator"])
548
550
  self.alias = builder.parse_type(self.namespace, data["alias"])
549
551
  self.desc = data.get("desc", None)
550
- # Should be limited to Union type aliases
551
552
  self.discriminator = data.get("discriminator", None)
552
553
 
553
554
  def get_referenced_types(self) -> list[SpecType]:
554
555
  return [self.alias]
555
556
 
556
557
 
558
+ class SpecTypeDefnUnion(SpecTypeDefn):
559
+ def __init__(self, namespace: SpecNamespace, name: str) -> None:
560
+ super().__init__(namespace, name)
561
+ self.discriminator: str | None = None
562
+ self.types: list[SpecType] = []
563
+ self._alias_type: SpecType | None = None
564
+ self.discriminator_map: dict[str, SpecType] | None = None
565
+ self.desc: str | None = None
566
+
567
+ def process(self, builder: SpecBuilder, data: RawDict) -> None:
568
+ super().base_process(builder, data, ["type", "desc", "types", "discriminator"])
569
+
570
+ self.desc = data.get("desc", None)
571
+ self.discriminator = data.get("discriminator", None)
572
+
573
+ for sub_type_str in data["types"]:
574
+ sub_type = builder.parse_type(self.namespace, sub_type_str)
575
+ self.types.append(sub_type)
576
+
577
+ base_type = builder.namespaces[base_namespace_name].types[BaseTypeName.s_union]
578
+ self._backing_type = SpecTypeInstance(base_type, self.types)
579
+
580
+ if self.discriminator is not None:
581
+ self.discriminator_map = {}
582
+ for sub_type in self.types:
583
+ builder.push_where(sub_type.name)
584
+ assert isinstance(
585
+ sub_type, SpecTypeDefnObject
586
+ ), "union-type-must-be-object"
587
+ assert sub_type.properties is not None
588
+ discriminator_type = sub_type.properties.get(self.discriminator)
589
+ assert (
590
+ discriminator_type is not None
591
+ ), f"missing-discriminator-field: {sub_type}"
592
+ prop_type = unwrap_literal_type(discriminator_type.spec_type)
593
+ assert prop_type is not None
594
+ assert prop_type.is_value_to_string()
595
+ discriminant = str(prop_type.value)
596
+ assert (
597
+ discriminant not in self.discriminator_map
598
+ ), f"duplicated-discriminant, {discriminant} in {sub_type}"
599
+ self.discriminator_map[discriminant] = sub_type
600
+
601
+ builder.pop_where()
602
+
603
+ def get_referenced_types(self) -> list[SpecType]:
604
+ return self.types
605
+
606
+ def get_backing_type(self) -> SpecType:
607
+ assert self._backing_type is not None
608
+ return self._backing_type
609
+
610
+
557
611
  class SpecTypeDefnExternal(SpecTypeDefn):
558
612
  external_map: dict[str, str]
559
613
 
@@ -1017,6 +1071,8 @@ class SpecNamespace:
1017
1071
  spec_type: SpecTypeDefn
1018
1072
  if defn_type == DefnTypeName.s_alias:
1019
1073
  spec_type = SpecTypeDefnAlias(self, name)
1074
+ elif defn_type == DefnTypeName.s_union:
1075
+ spec_type = SpecTypeDefnUnion(self, name)
1020
1076
  elif defn_type == DefnTypeName.s_external:
1021
1077
  spec_type = SpecTypeDefnExternal(self, name)
1022
1078
  elif defn_type == DefnTypeName.s_string_enum:
@@ -430,6 +430,12 @@ def _emit_type(
430
430
  ctx.types[stype.name] = open_api_type(ctx, stype.alias, config=config)
431
431
  return
432
432
 
433
+ if isinstance(stype, builder.SpecTypeDefnUnion):
434
+ ctx.types[stype.name] = open_api_type(
435
+ ctx, stype.get_backing_type(), config=config
436
+ )
437
+ return
438
+
433
439
  if isinstance(stype, builder.SpecTypeDefnStringEnum):
434
440
  # TODO: check that these are always string enums
435
441
  # IMPROVE: reflect the enum names in the description
@@ -43,6 +43,7 @@ class TrackingContext:
43
43
  use_serial_string_enum: bool = False
44
44
  use_dataclass: bool = False
45
45
  use_serial_class: bool = False
46
+ use_serial_union: bool = False
46
47
  use_missing: bool = False
47
48
  use_opaque_key: bool = False
48
49
 
@@ -219,6 +220,8 @@ def _emit_types_imports(*, out: io.StringIO, ctx: Context) -> None:
219
220
  out.write("from dataclasses import dataclass\n")
220
221
  if ctx.use_serial_class:
221
222
  out.write("from pkgs.serialization import serial_class\n")
223
+ if ctx.use_serial_union:
224
+ out.write("from pkgs.serialization import serial_union_annotation\n")
222
225
  if ctx.use_serial_string_enum:
223
226
  out.write("from pkgs.serialization import serial_string_enum\n")
224
227
  if ctx.use_missing:
@@ -727,6 +730,26 @@ def _emit_type(ctx: Context, stype: builder.SpecType) -> None:
727
730
  ctx.out.write(f"{stype.name} = {refer_to(ctx, stype.alias)}\n")
728
731
  return
729
732
 
733
+ if isinstance(stype, builder.SpecTypeDefnUnion):
734
+ ctx.use_serial_union = True
735
+ ctx.out.write(f"{stype.name} = typing.Annotated[\n")
736
+ ctx.out.write(f"{INDENT}{refer_to(ctx, stype.get_backing_type())},\n")
737
+ ctx.out.write(f"{INDENT}serial_union_annotation(\n")
738
+ if stype.discriminator is not None:
739
+ ctx.out.write(
740
+ f"{INDENT * 2}discriminator={util.encode_common_string(stype.discriminator)},\n"
741
+ )
742
+ if stype.discriminator_map is not None:
743
+ ctx.out.write(f"{INDENT * 2}discriminator_map={{\n")
744
+ for key, value in stype.discriminator_map.items():
745
+ ctx.out.write(
746
+ f"{INDENT * 3}{util.encode_common_string(key)}: {refer_to(ctx, value)},\n"
747
+ )
748
+ ctx.out.write(f"{INDENT * 2}}},\n")
749
+ ctx.out.write(f"{INDENT}),\n")
750
+ ctx.out.write("]\n")
751
+ return
752
+
730
753
  if isinstance(stype, builder.SpecTypeDefnStringEnum):
731
754
  return _emit_string_enum(ctx, stype)
732
755
 
@@ -303,6 +303,12 @@ def _emit_type(ctx: EmitTypescriptContext, stype: builder.SpecType) -> None:
303
303
  ctx.out.write(f"export type {stype.name} = {refer_to(ctx, stype.alias)}\n")
304
304
  return
305
305
 
306
+ if isinstance(stype, builder.SpecTypeDefnUnion):
307
+ ctx.out.write(
308
+ f"export type {stype.name} = {refer_to(ctx, stype.get_backing_type())}\n"
309
+ )
310
+ return
311
+
306
312
  if isinstance(stype, builder.SpecTypeDefnStringEnum):
307
313
  ctx.out.write(f"export enum {stype.name} {{\n")
308
314
  assert stype.values