UncountablePythonSDK 0.0.8__tar.gz → 0.0.10__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 (152) hide show
  1. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/PKG-INFO +6 -2
  2. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/UncountablePythonSDK.egg-info/PKG-INFO +6 -2
  3. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/UncountablePythonSDK.egg-info/SOURCES.txt +11 -0
  4. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/UncountablePythonSDK.egg-info/requires.txt +5 -1
  5. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/argument_parser/argument_parser.py +2 -2
  6. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_open_api.py +34 -17
  7. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_open_api_util.py +4 -9
  8. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/open_api_util.py +13 -12
  9. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pyproject.toml +5 -1
  10. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/inputs/get_input_data.yaml +1 -1
  11. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/outputs/get_output_data.yaml +2 -2
  12. UncountablePythonSDK-0.0.10/uncountable/integration/construct_client.py +30 -0
  13. UncountablePythonSDK-0.0.10/uncountable/integration/cron.py +29 -0
  14. UncountablePythonSDK-0.0.10/uncountable/integration/db/__init__.py +0 -0
  15. UncountablePythonSDK-0.0.10/uncountable/integration/db/connect.py +8 -0
  16. UncountablePythonSDK-0.0.10/uncountable/integration/entrypoint.py +41 -0
  17. UncountablePythonSDK-0.0.10/uncountable/integration/executors/__init__.py +0 -0
  18. UncountablePythonSDK-0.0.10/uncountable/integration/executors/script_executor.py +18 -0
  19. UncountablePythonSDK-0.0.10/uncountable/integration/job.py +39 -0
  20. UncountablePythonSDK-0.0.10/uncountable/integration/server.py +86 -0
  21. UncountablePythonSDK-0.0.10/uncountable/integration/types.py +89 -0
  22. UncountablePythonSDK-0.0.10/uncountable/py.typed +0 -0
  23. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/get_input_data.py +1 -1
  24. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/outputs/get_output_data.py +2 -2
  25. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/workflows.py +1 -1
  26. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/.github/workflows/publish.yml +0 -0
  27. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/.gitignore +0 -0
  28. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/UncountablePythonSDK.egg-info/dependency_links.txt +0 -0
  29. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/UncountablePythonSDK.egg-info/top_level.txt +0 -0
  30. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/examples/create_entity.py +0 -0
  31. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/examples/recipe-import/importer.py +0 -0
  32. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/__init__.py +0 -0
  33. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/argument_parser/__init__.py +0 -0
  34. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/argument_parser/_is_enum.py +0 -0
  35. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/argument_parser/_is_namedtuple.py +0 -0
  36. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/argument_parser/case_convert.py +0 -0
  37. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization/__init__.py +0 -0
  38. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization/missing_sentry.py +0 -0
  39. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization/opaque_key.py +0 -0
  40. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization/serial_class.py +0 -0
  41. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization_util/__init__.py +0 -0
  42. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization_util/_get_type_for_serialization.py +0 -0
  43. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/serialization_util/serialization_helpers.py +0 -0
  44. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/strenum_compat/__init__.py +0 -0
  45. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/strenum_compat/strenum_compat.py +0 -0
  46. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/__init__.py +0 -0
  47. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/__main__.py +0 -0
  48. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/builder.py +0 -0
  49. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/config.py +0 -0
  50. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_io_ts.py +0 -0
  51. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_python.py +0 -0
  52. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_typescript.py +0 -0
  53. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/emit_typescript_util.py +0 -0
  54. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/load_types.py +0 -0
  55. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/parts/base.py.prepart +0 -0
  56. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/parts/base.ts.prepart +0 -0
  57. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/test.py +0 -0
  58. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/type_info/__main__.py +0 -0
  59. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/type_info/emit_type_info.py +0 -0
  60. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/util.py +0 -0
  61. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/value_spec/__init__.py +0 -0
  62. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/value_spec/__main__.py +0 -0
  63. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/value_spec/convert_type.py +0 -0
  64. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/value_spec/emit_python.py +0 -0
  65. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/pkgs/type_spec/value_spec/types.py +0 -0
  66. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/requirements.txt +0 -0
  67. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/setup.cfg +0 -0
  68. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/batch/execute_batch.yaml +0 -0
  69. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/create_entities.yaml +0 -0
  70. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/create_entity.yaml +0 -0
  71. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/get_entities_data.yaml +0 -0
  72. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/list_entities.yaml +0 -0
  73. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/resolve_entity_ids.yaml +0 -0
  74. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/entity/set_values.yaml +0 -0
  75. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/input_groups/get_input_group_names.yaml +0 -0
  76. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/inputs/create_inputs.yaml +0 -0
  77. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/inputs/get_input_names.yaml +0 -0
  78. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/inputs/get_inputs_data.yaml +0 -0
  79. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/inputs/set_input_attribute_values.yaml +0 -0
  80. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/outputs/get_output_names.yaml +0 -0
  81. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/outputs/resolve_output_conditions.yaml +0 -0
  82. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/project/get_projects.yaml +0 -0
  83. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/project/get_projects_data.yaml +0 -0
  84. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipe_metadata/get_recipe_metadata_data.yaml +0 -0
  85. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/create_recipes.yaml +0 -0
  86. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_curve.yaml +0 -0
  87. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_recipe_calculations.yaml +0 -0
  88. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_recipe_links.yaml +0 -0
  89. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_recipe_names.yaml +0 -0
  90. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_recipe_output_metadata.yaml +0 -0
  91. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/get_recipes_data.yaml +0 -0
  92. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/set_recipe_inputs.yaml +0 -0
  93. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/type_spec/external/api/recipes/set_recipe_outputs.yaml +0 -0
  94. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/__init__.py +0 -0
  95. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/core/__init__.py +0 -0
  96. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/core/client.py +0 -0
  97. /UncountablePythonSDK-0.0.8/uncountable/py.typed → /UncountablePythonSDK-0.0.10/uncountable/integration/__init__.py +0 -0
  98. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/__init__.py +0 -0
  99. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/__init__.py +0 -0
  100. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/batch/__init__.py +0 -0
  101. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/batch/execute_batch.py +0 -0
  102. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/__init__.py +0 -0
  103. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/create_entities.py +0 -0
  104. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/create_entity.py +0 -0
  105. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/get_entities_data.py +0 -0
  106. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/list_entities.py +0 -0
  107. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/resolve_entity_ids.py +0 -0
  108. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/entity/set_values.py +0 -0
  109. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/input_groups/__init__.py +0 -0
  110. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/input_groups/get_input_group_names.py +0 -0
  111. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/__init__.py +0 -0
  112. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/create_inputs.py +0 -0
  113. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/get_input_names.py +0 -0
  114. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/get_inputs_data.py +0 -0
  115. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/inputs/set_input_attribute_values.py +0 -0
  116. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/outputs/__init__.py +0 -0
  117. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/outputs/get_output_names.py +0 -0
  118. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/outputs/resolve_output_conditions.py +0 -0
  119. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/project/__init__.py +0 -0
  120. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/project/get_projects.py +0 -0
  121. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/project/get_projects_data.py +0 -0
  122. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipe_metadata/__init__.py +0 -0
  123. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +0 -0
  124. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/__init__.py +0 -0
  125. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/create_recipes.py +0 -0
  126. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_curve.py +0 -0
  127. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_recipe_calculations.py +0 -0
  128. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_recipe_links.py +0 -0
  129. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_recipe_names.py +0 -0
  130. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -0
  131. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/get_recipes_data.py +0 -0
  132. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/set_recipe_inputs.py +0 -0
  133. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/api/recipes/set_recipe_outputs.py +0 -0
  134. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/base.py +0 -0
  135. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/calculations.py +0 -0
  136. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/client_base.py +0 -0
  137. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/curves.py +0 -0
  138. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/entity.py +0 -0
  139. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/experiment_groups.py +0 -0
  140. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/field_values.py +0 -0
  141. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/fields.py +0 -0
  142. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/input_attributes.py +0 -0
  143. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/inputs.py +0 -0
  144. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/outputs.py +0 -0
  145. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/phases.py +0 -0
  146. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/recipe_links.py +0 -0
  147. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/recipe_metadata.py +0 -0
  148. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/recipe_output_metadata.py +0 -0
  149. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/recipe_tags.py +0 -0
  150. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/response.py +0 -0
  151. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/units.py +0 -0
  152. {UncountablePythonSDK-0.0.8 → UncountablePythonSDK-0.0.10}/uncountable/types/users.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.8
3
+ Version: 0.0.10
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
@@ -16,7 +16,11 @@ Classifier: Topic :: Utilities
16
16
  Classifier: Typing :: Typed
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: requests>=2.31.0
19
+ Requires-Dist: requests==2.31.0
20
+ Requires-Dist: SQLAlchemy==2.0.29
21
+ Requires-Dist: APScheduler==3.10.4
22
+ Requires-Dist: dateutil==2.9.0
23
+ Requires-Dist: shelljob==0.6.3
20
24
  Provides-Extra: test
21
25
  Requires-Dist: mypy>=1.8.1; extra == "test"
22
26
  Requires-Dist: ruff>=0.2.1; extra == "test"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.8
3
+ Version: 0.0.10
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
@@ -16,7 +16,11 @@ Classifier: Topic :: Utilities
16
16
  Classifier: Typing :: Typed
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
- Requires-Dist: requests>=2.31.0
19
+ Requires-Dist: requests==2.31.0
20
+ Requires-Dist: SQLAlchemy==2.0.29
21
+ Requires-Dist: APScheduler==3.10.4
22
+ Requires-Dist: dateutil==2.9.0
23
+ Requires-Dist: shelljob==0.6.3
20
24
  Provides-Extra: test
21
25
  Requires-Dist: mypy>=1.8.1; extra == "test"
22
26
  Requires-Dist: ruff>=0.2.1; extra == "test"
@@ -79,6 +79,17 @@ uncountable/__init__.py
79
79
  uncountable/py.typed
80
80
  uncountable/core/__init__.py
81
81
  uncountable/core/client.py
82
+ uncountable/integration/__init__.py
83
+ uncountable/integration/construct_client.py
84
+ uncountable/integration/cron.py
85
+ uncountable/integration/entrypoint.py
86
+ uncountable/integration/job.py
87
+ uncountable/integration/server.py
88
+ uncountable/integration/types.py
89
+ uncountable/integration/db/__init__.py
90
+ uncountable/integration/db/connect.py
91
+ uncountable/integration/executors/__init__.py
92
+ uncountable/integration/executors/script_executor.py
82
93
  uncountable/types/__init__.py
83
94
  uncountable/types/base.py
84
95
  uncountable/types/calculations.py
@@ -1,4 +1,8 @@
1
- requests>=2.31.0
1
+ requests==2.31.0
2
+ SQLAlchemy==2.0.29
3
+ APScheduler==3.10.4
4
+ dateutil==2.9.0
5
+ shelljob==0.6.3
2
6
 
3
7
  [test]
4
8
  mypy>=1.8.1
@@ -407,5 +407,5 @@ class CachedParser(typing.Generic[T]):
407
407
  return self.parse_storage(yaml.safe_load(data_in))
408
408
 
409
409
  def parse_yaml_resource(self, package: resources.Package, resource: str) -> T:
410
- raw_data = resources.read_text(package, resource)
411
- return self.parse_storage(yaml.safe_load(raw_data))
410
+ with resources.open_text(package, resource) as fp:
411
+ return self.parse_storage(yaml.safe_load(fp))
@@ -141,6 +141,18 @@ def _serialize_global_context(ctx: EmitOpenAPIGlobalContext) -> str:
141
141
  return yaml.dump(oa_root, sort_keys=False)
142
142
 
143
143
 
144
+ def _emit_endpoint_parameters(typ: OpenAPIType | None) -> dict[str, list[dict[str, str]]]:
145
+ if not isinstance(typ, OpenAPIObjectType):
146
+ return {}
147
+
148
+ return {
149
+ "parameters": [
150
+ {"$ref": f"#/components/schema/Arguments/{prop_name}"}
151
+ for prop_name in typ.properties
152
+ ]
153
+ }
154
+
155
+
144
156
  def _emit_namespace(
145
157
  gctx: EmitOpenAPIGlobalContext,
146
158
  ctx: EmitOpenAPIContext,
@@ -159,23 +171,28 @@ def _emit_namespace(
159
171
  if ctx.endpoint is not None:
160
172
  endpoint = ctx.endpoint
161
173
 
174
+ argument_type = ctx.types.get("Arguments")
162
175
  oa_endpoint = dict()
163
- oa_endpoint[endpoint.method] = {
164
- "tags": endpoint.tags,
165
- "summary": endpoint.summary,
166
- "description": endpoint.description,
167
- "parameters": {"$ref": "#/components/schema/Arguments"},
168
- "responses": {
169
- "200": {
170
- "description": "OK",
171
- "content": {
172
- "application/json": {
173
- "schema": {"$ref": "#/components/schema/Data"}
174
- }
175
- },
176
- }
177
- },
178
- }
176
+ oa_endpoint[endpoint.method] = (
177
+ {
178
+ "tags": endpoint.tags,
179
+ "summary": endpoint.summary,
180
+ "description": endpoint.description,
181
+ }
182
+ | _emit_endpoint_parameters(argument_type)
183
+ | {
184
+ "responses": {
185
+ "200": {
186
+ "description": "OK",
187
+ "content": {
188
+ "application/json": {
189
+ "schema": {"$ref": "#/components/schema/Data"}
190
+ }
191
+ },
192
+ }
193
+ },
194
+ }
195
+ )
179
196
  oa_components["endpoint"] = oa_endpoint
180
197
 
181
198
  types = ctx.types
@@ -460,5 +477,5 @@ def open_api_type(
460
477
  ctx.namespaces.add(stype.namespace)
461
478
  # external namespace resolution
462
479
  return OpenAPIRefType(
463
- source=f"{resolve_namespace_ref(ctx, stype.namespace, config=config)}/{stype.name}"
480
+ source=f"{resolve_namespace_ref(source_path=ctx.namespace.path, ref_path=stype.namespace.path, ref='/components/schema')}/{stype.name}"
464
481
  )
@@ -9,7 +9,6 @@ from dataclasses import dataclass, field
9
9
  from typing import TypeAlias
10
10
 
11
11
  from . import builder
12
- from .config import OpenAPIConfig
13
12
  from .open_api_util import OpenAPIType
14
13
 
15
14
  MODIFY_NOTICE = "# DO NOT MODIFY -- This file is generated by type_spec"
@@ -75,12 +74,8 @@ class EmitOpenAPIContext:
75
74
 
76
75
 
77
76
  def resolve_namespace_ref(
78
- ctx: EmitOpenAPIContext,
79
- namespace: builder.SpecNamespace,
80
- *,
81
- config: OpenAPIConfig,
77
+ *, source_path: list[str], ref_path: list[str], ref: str
82
78
  ) -> str:
83
- # TODO: Handle namespaces not in root directory
84
- if len(ctx.namespace.path) == 1:
85
- return f"{namespace.name}.yaml#/components/schema"
86
- return f"{config.static_url_path}/common/{namespace.name}.yaml#/components/schema"
79
+ to_root = "/".join(".." for _ in source_path)
80
+ location = "/".join(ref_path)
81
+ return f"{to_root}/common/{location}.yaml#{ref}"
@@ -13,9 +13,10 @@ class OpenAPIType(ABC):
13
13
  self.nullable = nullable
14
14
 
15
15
  @abstractmethod
16
- def asdict(self) -> dict[str, object]: ...
16
+ def asdict(self) -> dict[str, object]:
17
+ pass
17
18
 
18
- def asarguments(self) -> list[dict[str, object]]:
19
+ def asarguments(self) -> dict[str, dict[str, object]]:
19
20
  raise UnsupportedOperation
20
21
 
21
22
  def add_addl_info(self, emitted: dict[str, object]) -> dict[str, object]:
@@ -147,8 +148,8 @@ class OpenAPIFreeFormObjectType(OpenAPIType):
147
148
  def asdict(self) -> dict[str, object]:
148
149
  return self.add_addl_info({"type": "object"})
149
150
 
150
- def asarguments(self) -> list[dict[str, object]]:
151
- return []
151
+ def asarguments(self) -> dict[str, dict[str, object]]:
152
+ return {}
152
153
 
153
154
 
154
155
  class OpenAPIObjectType(OpenAPIType):
@@ -185,17 +186,17 @@ class OpenAPIObjectType(OpenAPIType):
185
186
  },
186
187
  })
187
188
 
188
- def asarguments(self) -> list[dict[str, object]]:
189
- argument_types: list[dict[str, object]] = []
189
+ def asarguments(self) -> dict[str, dict[str, object]]:
190
+ argument_types: dict[str, dict[str, object]] = {}
190
191
  for property_name, property_type in self.properties.items():
191
192
  desc = self.property_desc.get(property_name)
192
- argument_types.append({
193
+ argument_types[property_name] = {
193
194
  "name": property_name,
194
195
  "in": "query",
195
196
  "schema": property_type.asdict(),
196
197
  "required": not property_type.nullable,
197
198
  "description": desc or "",
198
- })
199
+ }
199
200
  return argument_types
200
201
 
201
202
 
@@ -219,11 +220,11 @@ class OpenAPIUnionType(OpenAPIType):
219
220
  # TODO: use parents description and nullable
220
221
  return {"oneOf": [base_type.asdict() for base_type in self.base_types]}
221
222
 
222
- def asarguments(self) -> list[dict[str, object]]:
223
+ def asarguments(self) -> dict[str, dict[str, object]]:
223
224
  # TODO handle inheritence (allOf and refs); need to inline here...
224
225
  # for now skip this endpoint
225
226
 
226
- return []
227
+ return {}
227
228
 
228
229
 
229
230
  class OpenAPIIntersectionType(OpenAPIType):
@@ -246,8 +247,8 @@ class OpenAPIIntersectionType(OpenAPIType):
246
247
  # TODO: use parents description and nullable
247
248
  return {"allOf": [base_type.asdict() for base_type in self.base_types]}
248
249
 
249
- def asarguments(self) -> list[dict[str, object]]:
250
+ def asarguments(self) -> dict[str, dict[str, object]]:
250
251
  # TODO handle inheritence (allOf and refs); need to inline here...
251
252
  # for now skip this endpoint
252
253
 
253
- return []
254
+ return {}
@@ -48,7 +48,11 @@ keywords = [ "uncountable", "sdk", "api", "uncountable-sdk" ]
48
48
  requires-python = ">=3.11"
49
49
  # May be compatible with older versions
50
50
  dependencies = [
51
- "requests >= 2.31.0",
51
+ "requests == 2.31.0",
52
+ "SQLAlchemy == 2.0.29",
53
+ "APScheduler == 3.10.4",
54
+ "dateutil == 2.9.0",
55
+ "shelljob == 0.6.3"
52
56
  ]
53
57
  dynamic = ["version"]
54
58
 
@@ -76,7 +76,7 @@ FullInput:
76
76
  type: List<input_attributes.InputAttributeValue>
77
77
  desc: "Attributes associated with an input, such as CAS number, density, etc."
78
78
  global_category_id:
79
- type: ObjectId
79
+ type: Optional<ObjectId>
80
80
  desc: "The global category ID associated with the input"
81
81
  subcategory_ids:
82
82
  type: List<ObjectId>
@@ -73,10 +73,10 @@ FullOutput:
73
73
  type: List<OutputAttrVal>
74
74
  desc: "Attributes associated with an output, such as a test documentation"
75
75
  global_category_id:
76
- type: ObjectId
76
+ type: Optional<ObjectId>
77
77
  desc: "The global category ID associated with the output"
78
78
  unit_id:
79
- type: ObjectId
79
+ type: Optional<ObjectId>
80
80
  desc: "The unit for the output, which all data is stored in. This may be different than the users default displayed information for the output"
81
81
 
82
82
  Data:
@@ -0,0 +1,30 @@
1
+ import os
2
+ from typing import assert_never
3
+
4
+ from uncountable.core.client import AuthDetailsApiKey, Client
5
+ from uncountable.integration.types import (
6
+ AuthRetrievalEnv,
7
+ ProfileMetadata,
8
+ )
9
+
10
+
11
+ def construct_uncountable_client(
12
+ profile_meta: ProfileMetadata
13
+ ) -> Client:
14
+ match profile_meta.auth_retrieval:
15
+ case AuthRetrievalEnv():
16
+ api_id = os.getenv(f"UNC_PROFILE_{profile_meta.name}_API_ID")
17
+ api_secret_key = os.getenv(
18
+ f"UNC_PROFILE_{profile_meta.name}_API_SECRET_KEY"
19
+ )
20
+
21
+ assert api_id is not None
22
+ assert api_secret_key is not None
23
+
24
+ return Client(
25
+ base_url=profile_meta.base_url,
26
+ auth_details=AuthDetailsApiKey(
27
+ api_id=api_id, api_secret_key=api_secret_key
28
+ ),
29
+ )
30
+ assert_never(profile_meta.auth_retrieval)
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass
2
+
3
+ from pkgs.argument_parser import CachedParser
4
+ from uncountable.integration.construct_client import construct_uncountable_client
5
+ from uncountable.integration.executors.script_executor import resolve_script_executor
6
+ from uncountable.integration.job import CronJobArguments
7
+ from uncountable.integration.types import JobDefinition, ProfileMetadata
8
+
9
+
10
+ @dataclass
11
+ class CronJobArgs:
12
+ definition: JobDefinition
13
+ profile_metadata: ProfileMetadata
14
+
15
+
16
+ cron_args_parser = CachedParser(CronJobArgs)
17
+
18
+
19
+ def cron_job_executor(**kwargs: dict) -> None:
20
+ args_passed = cron_args_parser.parse_storage(kwargs)
21
+ args = CronJobArguments(
22
+ job_definition=args_passed.definition,
23
+ client=construct_uncountable_client(profile_meta=args_passed.profile_metadata),
24
+ )
25
+
26
+ job_class = resolve_script_executor(args_passed.definition.executor)
27
+
28
+ job = job_class()
29
+ job.run(args)
@@ -0,0 +1,8 @@
1
+ import os
2
+ from sqlalchemy import create_engine
3
+ from sqlalchemy.engine.base import Engine
4
+
5
+
6
+ def create_db_engine() -> Engine:
7
+ return create_engine(os.environ["UNC_SQLITE_URI"])
8
+
@@ -0,0 +1,41 @@
1
+ import os
2
+ from importlib import resources
3
+
4
+ from uncountable.integration.server import IntegrationServer
5
+ from uncountable.integration.types import ProfileDefinition
6
+ from pkgs.argument_parser import CachedParser
7
+ from uncountable.integration.db.connect import create_db_engine
8
+
9
+
10
+ profile_parser = CachedParser(ProfileDefinition)
11
+
12
+
13
+ def main() -> None:
14
+ profiles_module = os.environ["UNC_PROFILES_MODULE"]
15
+ with IntegrationServer(create_db_engine()) as server:
16
+ # TODO: Loop through all job spec yaml files and call server.add_job
17
+ profiles = [
18
+ entry
19
+ for entry in resources.files(profiles_module).iterdir()
20
+ if entry.is_dir()
21
+ ]
22
+ for profile in profiles:
23
+ profile_name = profile.name
24
+ try:
25
+ profile = profile_parser.parse_yaml_resource(
26
+ package=".".join([profiles_module, profile_name]),
27
+ resource="profile.yaml",
28
+ )
29
+ except FileNotFoundError as e:
30
+ print("WARN: profile.yaml not found", e)
31
+ continue
32
+ server.register_profile(
33
+ profile_name=profile_name,
34
+ base_url=profile.base_url,
35
+ auth_retrieval=profile.auth_retrieval,
36
+ jobs=profile.jobs,
37
+ )
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main()
@@ -0,0 +1,18 @@
1
+
2
+
3
+ import importlib
4
+ import inspect
5
+ from uncountable.integration.job import Job
6
+ from uncountable.integration.types import JobExecutorScript
7
+
8
+
9
+ def resolve_script_executor(executor: JobExecutorScript) -> type[Job]:
10
+ job_module = importlib.import_module(executor.import_path)
11
+ found_jobs: list[type[Job]] = []
12
+ for _, job_class in inspect.getmembers(job_module, inspect.isclass):
13
+ if Job in job_class.__bases__:
14
+ found_jobs.append(job_class())
15
+ assert (
16
+ len(found_jobs) == 1
17
+ ), f"expected exactly one job class in {executor.import_path}"
18
+ return found_jobs[0]
@@ -0,0 +1,39 @@
1
+ from dataclasses import dataclass
2
+ from uncountable.core.client import Client
3
+ from uncountable.integration.types import JobDefinition
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+
8
+ @dataclass
9
+ class JobArgumentsBase:
10
+ job_definition: JobDefinition
11
+ client: Client
12
+
13
+
14
+ @dataclass
15
+ class CronJobArguments(JobArgumentsBase):
16
+ # can imagine passing additional data such as in the sftp or webhook cases
17
+ pass
18
+
19
+
20
+ JobArguments = CronJobArguments
21
+
22
+
23
+ @dataclass
24
+ class JobResult:
25
+ success: bool
26
+
27
+
28
+ class Job(ABC):
29
+
30
+ @abstractmethod
31
+ def run(self, args: JobArguments) -> JobResult:
32
+ ...
33
+
34
+
35
+ class CronJob(Job):
36
+
37
+ @abstractmethod
38
+ def run(self, args: CronJobArguments) -> JobResult:
39
+ ...
@@ -0,0 +1,86 @@
1
+ from dataclasses import asdict
2
+ from typing import assert_never
3
+ from apscheduler.schedulers.background import BackgroundScheduler
4
+ from apscheduler.schedulers.base import BaseScheduler
5
+ from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
6
+ from apscheduler.executors.pool import ThreadPoolExecutor
7
+ from uncountable.integration.cron import CronJobArgs, cron_job_executor
8
+ from apscheduler.triggers.cron import CronTrigger
9
+ from sqlalchemy.engine.base import Engine
10
+
11
+ from uncountable.integration.types import (
12
+ AuthRetrieval,
13
+ CronJobDefinition,
14
+ JobDefinition,
15
+ ProfileMetadata,
16
+ )
17
+
18
+
19
+ _MAX_APSCHEDULER_CONCURRENT_JOBS = 1
20
+
21
+
22
+ class IntegrationServer:
23
+ _scheduler: BaseScheduler
24
+ _engine: Engine
25
+
26
+ def __init__(self, engine: Engine) -> None:
27
+ self._engine = engine
28
+ self._scheduler = BackgroundScheduler(
29
+ timezone="UTC",
30
+ jobstores={"default": SQLAlchemyJobStore(engine=engine)},
31
+ executors={"default": ThreadPoolExecutor(_MAX_APSCHEDULER_CONCURRENT_JOBS)},
32
+ )
33
+
34
+ def register_profile(
35
+ self,
36
+ *,
37
+ profile_name: str,
38
+ base_url: str,
39
+ auth_retrieval: AuthRetrieval,
40
+ jobs: list[JobDefinition],
41
+ ) -> None:
42
+ for job_defn in jobs:
43
+ profile_metadata = ProfileMetadata(
44
+ name=profile_name, auth_retrieval=auth_retrieval, base_url=base_url
45
+ )
46
+ match job_defn:
47
+ case CronJobDefinition():
48
+ # Add to ap scheduler
49
+ job_kwargs = asdict(
50
+ CronJobArgs(
51
+ definition=job_defn, profile_metadata=profile_metadata
52
+ )
53
+ )
54
+ existing_job = self._scheduler.get_job(job_defn.id)
55
+ if existing_job is not None:
56
+ existing_job.modify(
57
+ name=job_defn.name,
58
+ kwargs=job_kwargs,
59
+ )
60
+ existing_job.reschedule(job_defn.cron_spec)
61
+ else:
62
+ self._scheduler.add_job(
63
+ cron_job_executor,
64
+ # IMPROVE: reconsider these defaults
65
+ max_instances=1,
66
+ coalesce=True,
67
+ trigger=CronTrigger.from_crontab(job_defn.cron_spec),
68
+ name=job_defn.name,
69
+ id=job_defn.id,
70
+ kwargs=job_kwargs,
71
+ )
72
+ case _:
73
+ assert_never(job_defn.trigger)
74
+
75
+ def _start_apscheduler(self) -> None:
76
+ self._scheduler.start()
77
+
78
+ def _stop_apscheduler(self) -> None:
79
+ self._scheduler.shutdown()
80
+
81
+ def __enter__(self) -> "IntegrationServer":
82
+ self._start_apscheduler()
83
+ return self
84
+
85
+ def __exit__(self) -> None:
86
+ self._stop_apscheduler()
@@ -0,0 +1,89 @@
1
+ # TODO: move to type spec
2
+
3
+
4
+ from dataclasses import dataclass
5
+ from enum import StrEnum
6
+ from typing import Literal
7
+
8
+
9
+ class JobDefinitionType(StrEnum):
10
+ CRON = "cron"
11
+ # also imagine other job types like webhook-triggered or sftp-triggered,
12
+ # manual, etc
13
+
14
+
15
+ class JobExecutorType(StrEnum):
16
+ SCRIPT = "script"
17
+ # also imagine builtin executors like 'sftp_sync'
18
+
19
+
20
+ class AuthRetrievalType(StrEnum):
21
+ ENV = "env"
22
+ # also imagine secrets manager, keyvault, etc
23
+
24
+
25
+ @dataclass
26
+ class JobExecutorBase:
27
+ type: JobExecutorType
28
+
29
+
30
+ @dataclass
31
+ class JobExecutorScript(JobExecutorBase):
32
+ type: Literal[JobExecutorType.SCRIPT]
33
+ import_path: str
34
+
35
+
36
+ JobExecutor = JobExecutorScript
37
+
38
+
39
+ @dataclass
40
+ class JobDefinitionBase:
41
+ id: str
42
+ name: str
43
+
44
+
45
+ @dataclass
46
+ class CronJobDefinition(JobDefinitionBase):
47
+ type: Literal[JobDefinitionType.CRON]
48
+ cron_spec: str
49
+ # Here we assert that the executor has to be a script, but we could add
50
+ # other builtin executor types that cron jobs can support later
51
+ executor: JobExecutorScript
52
+
53
+
54
+ JobDefinition = CronJobDefinition
55
+
56
+
57
+ @dataclass
58
+ class AuthRetrievalBase:
59
+ type: AuthRetrievalType
60
+
61
+
62
+ @dataclass
63
+ class AuthRetrievalEnv:
64
+ # We don't really need any extra info here, we can enforce that the auth
65
+ # keys are named like UNC_PROFILE_{profile name}_API_SECRET_KEY. For
66
+ # supporting pulling secrets from secrets manager etc it will be nice to
67
+ # use dataclass properties to get the secret name, region etc.
68
+ type: Literal[AuthRetrievalType.ENV]
69
+
70
+
71
+ AuthRetrieval = AuthRetrievalEnv
72
+
73
+
74
+ @dataclass
75
+ class ProfileDefinition:
76
+ # profile name (expected to be something like customer_name) will be
77
+ # obtained from the folder name instead of specified here. Forces jobs to
78
+ # be organized nicely in folders that separate their identities.
79
+ auth_retrieval: AuthRetrieval
80
+ base_url: str
81
+ jobs: list[JobDefinition]
82
+
83
+
84
+ @dataclass
85
+ class ProfileMetadata:
86
+ # supplied by inspecting the folder name that the profile is in
87
+ name: str
88
+ base_url: str
89
+ auth_retrieval: AuthRetrieval
File without changes
@@ -66,7 +66,7 @@ class FullInput:
66
66
  quantity_type: str
67
67
  is_parameter: bool
68
68
  attributes: list[input_attributes_t.InputAttributeValue]
69
- global_category_id: base_t.ObjectId
69
+ global_category_id: typing.Optional[base_t.ObjectId]
70
70
  subcategory_ids: list[base_t.ObjectId]
71
71
 
72
72
 
@@ -70,8 +70,8 @@ class FullOutput:
70
70
  name: str
71
71
  quantity_type: str
72
72
  attributes: list[OutputAttrVal]
73
- global_category_id: base_t.ObjectId
74
- unit_id: base_t.ObjectId
73
+ global_category_id: typing.Optional[base_t.ObjectId]
74
+ unit_id: typing.Optional[base_t.ObjectId]
75
75
 
76
76
 
77
77
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -20,7 +20,7 @@ __all__: list[str] = [
20
20
  @dataclass(kw_only=True)
21
21
  class SimpleWorkflowStep:
22
22
  workflow_step_id: base_t.ObjectId
23
- name: str
23
+ name: typing.Optional[str]
24
24
 
25
25
 
26
26
  # DO NOT MODIFY -- This file is generated by type_spec