UncountablePythonSDK 0.0.49__tar.gz → 0.0.51__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 (256) hide show
  1. {uncountablepythonsdk-0.0.49/UncountablePythonSDK.egg-info → uncountablepythonsdk-0.0.51}/PKG-INFO +5 -1
  2. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51/UncountablePythonSDK.egg-info}/PKG-INFO +5 -1
  3. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/UncountablePythonSDK.egg-info/SOURCES.txt +2 -0
  4. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/UncountablePythonSDK.egg-info/requires.txt +4 -0
  5. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/requirements.txt +1 -1
  6. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pyproject.toml +4 -0
  7. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/core/client.py +14 -9
  8. uncountablepythonsdk-0.0.51/uncountable/core/version.py +11 -0
  9. uncountablepythonsdk-0.0.51/uncountable/integration/construct_client.py +51 -0
  10. uncountablepythonsdk-0.0.51/uncountable/integration/cron.py +55 -0
  11. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/executors/generic_upload_executor.py +2 -1
  12. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/job.py +1 -15
  13. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/secret_retrieval/retrieve_secret.py +32 -1
  14. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/server.py +9 -0
  15. uncountablepythonsdk-0.0.51/uncountable/integration/telemetry.py +130 -0
  16. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/create_entities.py +1 -1
  17. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/create_entity.py +1 -1
  18. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/uploader/invoke_uploader.py +1 -0
  19. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/async_batch_processor.py +2 -0
  20. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/client_base.py +4 -2
  21. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/job_definition.py +2 -1
  22. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/job_definition_t.py +22 -6
  23. uncountablepythonsdk-0.0.49/uncountable/integration/construct_client.py +0 -47
  24. uncountablepythonsdk-0.0.49/uncountable/integration/cron.py +0 -47
  25. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/.github/workflows/documentation.yml +0 -0
  26. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/.github/workflows/publish.yml +0 -0
  27. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/.gitignore +0 -0
  28. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/README.md +0 -0
  29. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/UncountablePythonSDK.egg-info/dependency_links.txt +0 -0
  30. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/UncountablePythonSDK.egg-info/top_level.txt +0 -0
  31. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/.gitignore +0 -0
  32. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/conf.py +0 -0
  33. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/index.md +0 -0
  34. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/justfile +0 -0
  35. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/quickstart.md +0 -0
  36. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/android-chrome-192x192.png +0 -0
  37. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/android-chrome-512x512.png +0 -0
  38. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/apple-touch-icon.png +0 -0
  39. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/browserconfig.xml +0 -0
  40. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/favicon-16x16.png +0 -0
  41. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/favicon-32x32.png +0 -0
  42. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/manifest.json +0 -0
  43. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/mstile-150x150.png +0 -0
  44. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/favicons/safari-pinned-tab.svg +0 -0
  45. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/docs/static/logo_blue.png +0 -0
  46. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/examples/async_batch.py +0 -0
  47. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/examples/create_entity.py +0 -0
  48. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/examples/edit_recipe_inputs.py +0 -0
  49. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/examples/invoke_uploader.py +0 -0
  50. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/examples/upload_files.py +0 -0
  51. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/__init__.py +0 -0
  52. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/argument_parser/__init__.py +0 -0
  53. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/argument_parser/_is_enum.py +0 -0
  54. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/argument_parser/_is_namedtuple.py +0 -0
  55. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/argument_parser/argument_parser.py +0 -0
  56. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/argument_parser/case_convert.py +0 -0
  57. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/__init__.py +0 -0
  58. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/_gdrive_session.py +0 -0
  59. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/_local_session.py +0 -0
  60. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/_s3_session.py +0 -0
  61. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/_sftp_session.py +0 -0
  62. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/file_type_utils.py +0 -0
  63. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/filesystem_utils/filesystem_session.py +0 -0
  64. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/__init__.py +0 -0
  65. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/missing_sentry.py +0 -0
  66. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/opaque_key.py +0 -0
  67. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/serial_class.py +0 -0
  68. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/serial_union.py +0 -0
  69. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization/yaml.py +0 -0
  70. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization_util/__init__.py +0 -0
  71. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization_util/_get_type_for_serialization.py +0 -0
  72. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization_util/convert_to_snakecase.py +0 -0
  73. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/serialization_util/serialization_helpers.py +0 -0
  74. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/strenum_compat/__init__.py +0 -0
  75. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/strenum_compat/strenum_compat.py +0 -0
  76. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/__init__.py +0 -0
  77. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/__main__.py +0 -0
  78. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/actions_registry/__init__.py +0 -0
  79. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/actions_registry/__main__.py +0 -0
  80. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/actions_registry/emit_typescript.py +0 -0
  81. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/builder.py +0 -0
  82. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/config.py +0 -0
  83. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_io_ts.py +0 -0
  84. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_open_api.py +0 -0
  85. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_open_api_util.py +0 -0
  86. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_python.py +0 -0
  87. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_typescript.py +0 -0
  88. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/emit_typescript_util.py +0 -0
  89. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/load_types.py +0 -0
  90. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/open_api_util.py +0 -0
  91. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/parts/base.py.prepart +0 -0
  92. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/parts/base.ts.prepart +0 -0
  93. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/test.py +0 -0
  94. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/type_info/__main__.py +0 -0
  95. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/type_info/emit_type_info.py +0 -0
  96. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/util.py +0 -0
  97. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/value_spec/__init__.py +0 -0
  98. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/value_spec/__main__.py +0 -0
  99. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/value_spec/convert_type.py +0 -0
  100. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/value_spec/emit_python.py +0 -0
  101. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/pkgs/type_spec/value_spec/types.py +0 -0
  102. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/setup.cfg +0 -0
  103. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/__init__.py +0 -0
  104. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/core/__init__.py +0 -0
  105. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/core/async_batch.py +0 -0
  106. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/core/file_upload.py +0 -0
  107. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/core/types.py +0 -0
  108. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/__init__.py +0 -0
  109. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/db/__init__.py +0 -0
  110. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/db/connect.py +0 -0
  111. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/entrypoint.py +0 -0
  112. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/executors/__init__.py +0 -0
  113. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/executors/executors.py +0 -0
  114. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/executors/script_executor.py +0 -0
  115. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/integration/secret_retrieval/__init__.py +0 -0
  116. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/py.typed +0 -0
  117. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/__init__.py +0 -0
  118. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/__init__.py +0 -0
  119. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/batch/__init__.py +0 -0
  120. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/batch/execute_batch.py +0 -0
  121. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/batch/execute_batch_load_async.py +0 -0
  122. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/chemical/__init__.py +0 -0
  123. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/chemical/convert_chemical_formats.py +0 -0
  124. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/__init__.py +0 -0
  125. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/get_entities_data.py +0 -0
  126. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/list_entities.py +0 -0
  127. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/lock_entity.py +0 -0
  128. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/resolve_entity_ids.py +0 -0
  129. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/set_values.py +0 -0
  130. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/transition_entity_phase.py +0 -0
  131. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/entity/unlock_entity.py +0 -0
  132. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/equipment/__init__.py +0 -0
  133. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/equipment/associate_equipment_input.py +0 -0
  134. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/field_options/__init__.py +0 -0
  135. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/field_options/upsert_field_options.py +0 -0
  136. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/id_source/__init__.py +0 -0
  137. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/id_source/list_id_source.py +0 -0
  138. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/id_source/match_id_source.py +0 -0
  139. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/input_groups/__init__.py +0 -0
  140. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/input_groups/get_input_group_names.py +0 -0
  141. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/__init__.py +0 -0
  142. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/create_inputs.py +0 -0
  143. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/get_input_data.py +0 -0
  144. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/get_input_names.py +0 -0
  145. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/get_inputs_data.py +0 -0
  146. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/set_input_attribute_values.py +0 -0
  147. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/set_input_category.py +0 -0
  148. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/set_input_subcategories.py +0 -0
  149. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/inputs/set_intermediate_type.py +0 -0
  150. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/material_families/__init__.py +0 -0
  151. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/material_families/update_entity_material_families.py +0 -0
  152. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/outputs/__init__.py +0 -0
  153. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/outputs/get_output_data.py +0 -0
  154. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/outputs/get_output_names.py +0 -0
  155. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/outputs/resolve_output_conditions.py +0 -0
  156. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/permissions/__init__.py +0 -0
  157. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/permissions/set_core_permissions.py +0 -0
  158. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/project/__init__.py +0 -0
  159. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/project/get_projects.py +0 -0
  160. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/project/get_projects_data.py +0 -0
  161. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipe_links/__init__.py +0 -0
  162. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipe_links/create_recipe_link.py +0 -0
  163. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipe_links/remove_recipe_link.py +0 -0
  164. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipe_metadata/__init__.py +0 -0
  165. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +0 -0
  166. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/__init__.py +0 -0
  167. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/add_recipe_to_project.py +0 -0
  168. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/archive_recipes.py +0 -0
  169. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/associate_recipe_as_input.py +0 -0
  170. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/associate_recipe_as_lot.py +0 -0
  171. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/create_recipe.py +0 -0
  172. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/create_recipes.py +0 -0
  173. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/disassociate_recipe_as_input.py +0 -0
  174. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/edit_recipe_inputs.py +0 -0
  175. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_curve.py +0 -0
  176. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_recipe_calculations.py +0 -0
  177. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_recipe_links.py +0 -0
  178. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_recipe_names.py +0 -0
  179. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -0
  180. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/get_recipes_data.py +0 -0
  181. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/lock_recipes.py +0 -0
  182. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/remove_recipe_from_project.py +0 -0
  183. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/set_recipe_inputs.py +0 -0
  184. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/set_recipe_metadata.py +0 -0
  185. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/set_recipe_output_annotations.py +0 -0
  186. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/set_recipe_outputs.py +0 -0
  187. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/set_recipe_tags.py +0 -0
  188. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/unarchive_recipes.py +0 -0
  189. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/recipes/unlock_recipes.py +0 -0
  190. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/triggers/__init__.py +0 -0
  191. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/triggers/run_trigger.py +0 -0
  192. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/api/uploader/__init__.py +0 -0
  193. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/async_batch.py +0 -0
  194. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/async_batch_t.py +0 -0
  195. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/base.py +0 -0
  196. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/base_t.py +0 -0
  197. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/calculations.py +0 -0
  198. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/calculations_t.py +0 -0
  199. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/chemical_structure.py +0 -0
  200. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/chemical_structure_t.py +0 -0
  201. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/client_config.py +0 -0
  202. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/client_config_t.py +0 -0
  203. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/curves.py +0 -0
  204. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/curves_t.py +0 -0
  205. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/entity.py +0 -0
  206. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/entity_t.py +0 -0
  207. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/experiment_groups.py +0 -0
  208. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/experiment_groups_t.py +0 -0
  209. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/field_values.py +0 -0
  210. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/field_values_t.py +0 -0
  211. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/fields.py +0 -0
  212. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/fields_t.py +0 -0
  213. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/generic_upload.py +0 -0
  214. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/generic_upload_t.py +0 -0
  215. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/id_source.py +0 -0
  216. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/id_source_t.py +0 -0
  217. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/identifier.py +0 -0
  218. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/identifier_t.py +0 -0
  219. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/input_attributes.py +0 -0
  220. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/input_attributes_t.py +0 -0
  221. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/inputs.py +0 -0
  222. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/inputs_t.py +0 -0
  223. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/outputs.py +0 -0
  224. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/outputs_t.py +0 -0
  225. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/permissions.py +0 -0
  226. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/permissions_t.py +0 -0
  227. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/phases.py +0 -0
  228. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/phases_t.py +0 -0
  229. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/post_base.py +0 -0
  230. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/post_base_t.py +0 -0
  231. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_identifiers.py +0 -0
  232. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_identifiers_t.py +0 -0
  233. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_inputs.py +0 -0
  234. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_inputs_t.py +0 -0
  235. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_links.py +0 -0
  236. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_links_t.py +0 -0
  237. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_metadata.py +0 -0
  238. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_metadata_t.py +0 -0
  239. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_output_metadata.py +0 -0
  240. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_output_metadata_t.py +0 -0
  241. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_tags.py +0 -0
  242. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_tags_t.py +0 -0
  243. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_workflow_steps.py +0 -0
  244. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipe_workflow_steps_t.py +0 -0
  245. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipes.py +0 -0
  246. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/recipes_t.py +0 -0
  247. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/response.py +0 -0
  248. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/response_t.py +0 -0
  249. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/secret_retrieval.py +0 -0
  250. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/secret_retrieval_t.py +0 -0
  251. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/units.py +0 -0
  252. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/units_t.py +0 -0
  253. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/users.py +0 -0
  254. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/users_t.py +0 -0
  255. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/workflows.py +0 -0
  256. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.51}/uncountable/types/workflows_t.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.49
3
+ Version: 0.0.51
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
@@ -27,6 +27,10 @@ Requires-Dist: PyYAML==6.*
27
27
  Requires-Dist: google-api-python-client==2.*
28
28
  Requires-Dist: tqdm==4.*
29
29
  Requires-Dist: pysftp==0.*
30
+ Requires-Dist: opentelemetry-api==1.*
31
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.*
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.*
33
+ Requires-Dist: opentelemetry-sdk==1.*
30
34
  Requires-Dist: paramiko==3.*
31
35
  Requires-Dist: boto3==1.*
32
36
  Provides-Extra: test
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.49
3
+ Version: 0.0.51
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
@@ -27,6 +27,10 @@ Requires-Dist: PyYAML==6.*
27
27
  Requires-Dist: google-api-python-client==2.*
28
28
  Requires-Dist: tqdm==4.*
29
29
  Requires-Dist: pysftp==0.*
30
+ Requires-Dist: opentelemetry-api==1.*
31
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.*
32
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.*
33
+ Requires-Dist: opentelemetry-sdk==1.*
30
34
  Requires-Dist: paramiko==3.*
31
35
  Requires-Dist: boto3==1.*
32
36
  Provides-Extra: test
@@ -87,12 +87,14 @@ uncountable/core/async_batch.py
87
87
  uncountable/core/client.py
88
88
  uncountable/core/file_upload.py
89
89
  uncountable/core/types.py
90
+ uncountable/core/version.py
90
91
  uncountable/integration/__init__.py
91
92
  uncountable/integration/construct_client.py
92
93
  uncountable/integration/cron.py
93
94
  uncountable/integration/entrypoint.py
94
95
  uncountable/integration/job.py
95
96
  uncountable/integration/server.py
97
+ uncountable/integration/telemetry.py
96
98
  uncountable/integration/db/__init__.py
97
99
  uncountable/integration/db/connect.py
98
100
  uncountable/integration/executors/__init__.py
@@ -9,6 +9,10 @@ PyYAML==6.*
9
9
  google-api-python-client==2.*
10
10
  tqdm==4.*
11
11
  pysftp==0.*
12
+ opentelemetry-api==1.*
13
+ opentelemetry-exporter-otlp-proto-common==1.*
14
+ opentelemetry-exporter-otlp-proto-http==1.*
15
+ opentelemetry-sdk==1.*
12
16
  paramiko==3.*
13
17
  boto3==1.*
14
18
 
@@ -2,6 +2,6 @@ furo==2024.7.18
2
2
  myst-parser==3.0.1
3
3
  sphinx-autoapi==3.2.0
4
4
  sphinx-copybutton==0.5.2
5
- Sphinx==7.4.4
5
+ Sphinx==8.0.0
6
6
  sphinx_design==0.6.0
7
7
  sphinx-favicon==1.0.1
@@ -59,6 +59,10 @@ dependencies = [
59
59
  "google-api-python-client == 2.*",
60
60
  "tqdm == 4.*",
61
61
  "pysftp == 0.*",
62
+ "opentelemetry-api==1.*",
63
+ "opentelemetry-exporter-otlp-proto-common==1.*",
64
+ "opentelemetry-exporter-otlp-proto-http==1.*",
65
+ "opentelemetry-sdk==1.*",
62
66
  "paramiko == 3.*",
63
67
  "boto3 == 1.*"
64
68
  ]
@@ -4,16 +4,18 @@ import typing
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime, timedelta
6
6
  from enum import StrEnum
7
- from importlib.metadata import PackageNotFoundError, version
8
7
  from urllib.parse import urljoin
9
8
  from uuid import uuid4
10
9
 
11
10
  import requests
11
+ from opentelemetry.sdk.resources import Attributes
12
12
  from requests.exceptions import JSONDecodeError
13
13
 
14
14
  from pkgs.argument_parser import CachedParser
15
15
  from pkgs.serialization_util import serialize_for_api
16
16
  from pkgs.serialization_util.serialization_helpers import JsonValue
17
+ from uncountable.core.version import get_version
18
+ from uncountable.integration.telemetry import JobLogger
17
19
  from uncountable.types.client_base import APIRequest, ClientMethods
18
20
  from uncountable.types.client_config import ClientConfigOptions
19
21
 
@@ -25,12 +27,6 @@ UNC_REQUEST_ID_HEADER = "X-UNC-REQUEST-ID"
25
27
  UNC_SDK_VERSION_HEADER = "X-UNC-SDK-VERSION"
26
28
 
27
29
 
28
- try:
29
- __version__ = version("UncountablePythonSDK")
30
- except PackageNotFoundError:
31
- __version__ = "unknown"
32
-
33
-
34
30
  class EndpointMethod(StrEnum):
35
31
  POST = "POST"
36
32
  GET = "GET"
@@ -61,6 +57,7 @@ HTTPRequest = HTTPPostRequest | HTTPGetRequest
61
57
  @dataclass(kw_only=True)
62
58
  class ClientConfig(ClientConfigOptions):
63
59
  transform_request: typing.Callable[[requests.Request], requests.Request] | None = None
60
+ job_logger: typing.Optional[JobLogger] = None
64
61
 
65
62
 
66
63
  OAUTH_REFRESH_WINDOW_SECONDS = 60 * 5
@@ -204,7 +201,15 @@ class Client(ClientMethods):
204
201
  case _:
205
202
  typing.assert_never(http_request)
206
203
  request.headers = http_request.headers
207
- response = self._send_request(request)
204
+ if self._cfg.job_logger is not None:
205
+ attributes: Attributes = {
206
+ "method": http_request.method,
207
+ "endpoint": api_request.endpoint,
208
+ }
209
+ with self._cfg.job_logger.push_scope("api_call", attributes=attributes):
210
+ response = self._send_request(request)
211
+ else:
212
+ response = self._send_request(request)
208
213
  response_data = self._get_response_json(response, request_id=request_id)
209
214
  cached_parser = self._get_cached_parser(return_type)
210
215
  try:
@@ -260,7 +265,7 @@ class Client(ClientMethods):
260
265
  ) -> HTTPRequest:
261
266
  headers = self._build_auth_headers()
262
267
  headers[UNC_REQUEST_ID_HEADER] = request_id
263
- headers[UNC_SDK_VERSION_HEADER] = __version__
268
+ headers[UNC_SDK_VERSION_HEADER] = get_version()
264
269
  method = api_request.method.lower()
265
270
  data = {"data": json.dumps(serialize_for_api(api_request.args))}
266
271
  match method:
@@ -0,0 +1,11 @@
1
+ import functools
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+
5
+ @functools.cache
6
+ def get_version() -> str:
7
+ try:
8
+ version_str = version("UncountablePythonSDK")
9
+ except PackageNotFoundError:
10
+ version_str = "unknown"
11
+ return version_str
@@ -0,0 +1,51 @@
1
+ from uncountable.core import AuthDetailsApiKey, Client
2
+ from uncountable.core.client import ClientConfig
3
+ from uncountable.core.types import AuthDetailsAll, AuthDetailsOAuth
4
+ from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
5
+ from uncountable.integration.telemetry import JobLogger
6
+ from uncountable.types.job_definition_t import (
7
+ AuthRetrievalBasic,
8
+ AuthRetrievalOAuth,
9
+ ProfileMetadata,
10
+ )
11
+
12
+
13
+ def _construct_auth_details(profile_meta: ProfileMetadata) -> AuthDetailsAll:
14
+ match profile_meta.auth_retrieval:
15
+ case AuthRetrievalOAuth():
16
+ refresh_token = retrieve_secret(
17
+ profile_meta.auth_retrieval.refresh_token_secret,
18
+ profile_metadata=profile_meta,
19
+ )
20
+ return AuthDetailsOAuth(refresh_token=refresh_token)
21
+ case AuthRetrievalBasic():
22
+ api_id = retrieve_secret(
23
+ profile_meta.auth_retrieval.api_id_secret, profile_metadata=profile_meta
24
+ )
25
+ api_key = retrieve_secret(
26
+ profile_meta.auth_retrieval.api_key_secret, profile_metadata=profile_meta
27
+ )
28
+
29
+ return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_key)
30
+
31
+
32
+ def _construct_client_config(
33
+ profile_meta: ProfileMetadata, job_logger: JobLogger
34
+ ) -> ClientConfig | None:
35
+ if profile_meta.client_options is None:
36
+ return None
37
+ return ClientConfig(
38
+ allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
39
+ extra_headers=profile_meta.client_options.extra_headers,
40
+ job_logger=job_logger,
41
+ )
42
+
43
+
44
+ def construct_uncountable_client(
45
+ profile_meta: ProfileMetadata, job_logger: JobLogger
46
+ ) -> Client:
47
+ return Client(
48
+ base_url=profile_meta.base_url,
49
+ auth_details=_construct_auth_details(profile_meta),
50
+ config=_construct_client_config(profile_meta, job_logger),
51
+ )
@@ -0,0 +1,55 @@
1
+ from dataclasses import dataclass
2
+
3
+ from pkgs.argument_parser import CachedParser
4
+ from uncountable.core.async_batch import AsyncBatchProcessor
5
+ from uncountable.integration.construct_client import construct_uncountable_client
6
+ from uncountable.integration.executors.executors import resolve_executor
7
+ from uncountable.integration.job import CronJobArguments
8
+ from uncountable.integration.telemetry import JobLogger
9
+ from uncountable.types.job_definition_t import JobDefinition, ProfileMetadata
10
+
11
+
12
+ @dataclass
13
+ class CronJobArgs:
14
+ definition: JobDefinition
15
+ profile_metadata: ProfileMetadata
16
+
17
+
18
+ cron_args_parser = CachedParser(CronJobArgs)
19
+
20
+
21
+ def cron_job_executor(**kwargs: dict) -> None:
22
+ args_passed = cron_args_parser.parse_storage(kwargs)
23
+ job_logger = JobLogger(
24
+ profile_metadata=args_passed.profile_metadata,
25
+ job_definition=args_passed.definition,
26
+ )
27
+ client = construct_uncountable_client(
28
+ profile_meta=args_passed.profile_metadata, job_logger=job_logger
29
+ )
30
+ batch_processor = AsyncBatchProcessor(client=client)
31
+ args = CronJobArguments(
32
+ job_definition=args_passed.definition,
33
+ client=client,
34
+ batch_processor=batch_processor,
35
+ profile_metadata=args_passed.profile_metadata,
36
+ logger=job_logger,
37
+ )
38
+
39
+ with job_logger.push_scope(args_passed.definition.name) as job_logger:
40
+ job = resolve_executor(
41
+ args_passed.definition.executor, args_passed.profile_metadata
42
+ )
43
+
44
+ job_logger.log_info("running job")
45
+
46
+ job.run(args=args)
47
+
48
+ if batch_processor.current_queue_size() != 0:
49
+ batch_processor.send()
50
+
51
+ submitted_batch_job_ids = batch_processor.get_submitted_job_ids()
52
+ job_logger.log_info(
53
+ "completed job",
54
+ attributes={"submitted_batch_job_ids": submitted_batch_job_ids},
55
+ )
@@ -17,8 +17,9 @@ from pkgs.filesystem_utils import (
17
17
  )
18
18
  from pkgs.filesystem_utils.filesystem_session import FileSystemSession
19
19
  from uncountable.core.file_upload import DataFileUpload, FileUpload
20
- from uncountable.integration.job import Job, JobArguments, JobLogger
20
+ from uncountable.integration.job import Job, JobArguments
21
21
  from uncountable.integration.secret_retrieval import retrieve_secret
22
+ from uncountable.integration.telemetry import JobLogger
22
23
  from uncountable.types.generic_upload_t import (
23
24
  GenericRemoteDirectoryScope,
24
25
  GenericUploadStrategy,
@@ -3,24 +3,10 @@ from dataclasses import dataclass
3
3
 
4
4
  from uncountable.core.async_batch import AsyncBatchProcessor
5
5
  from uncountable.core.client import Client
6
+ from uncountable.integration.telemetry import JobLogger
6
7
  from uncountable.types.job_definition_t import JobDefinition, JobResult, ProfileMetadata
7
8
 
8
9
 
9
- class JobLogger:
10
- def __init__(
11
- self, *, profile_metadata: ProfileMetadata, job_definition: JobDefinition
12
- ) -> None:
13
- self.profile_metadata = profile_metadata
14
- self.job_definition = job_definition
15
-
16
- def log_info(self, *log_objects: object) -> None:
17
- # IMPROVE: log a json message with context that can be parsed by OT
18
- print(
19
- f"[{self.job_definition.id}] in profile ({self.profile_metadata.name}): ",
20
- *log_objects,
21
- )
22
-
23
-
24
10
  @dataclass
25
11
  class JobArgumentsBase:
26
12
  job_definition: JobDefinition
@@ -1,5 +1,10 @@
1
+ import base64
2
+ import functools
3
+ import json
1
4
  import os
2
5
 
6
+ import boto3
7
+
3
8
  from uncountable.types.job_definition_t import ProfileMetadata
4
9
  from uncountable.types.secret_retrieval_t import (
5
10
  SecretRetrieval,
@@ -22,6 +27,25 @@ class SecretRetrievalError(BaseException):
22
27
  return f"{self.secret_retrieval.type} secret retrieval failed{append_message}"
23
28
 
24
29
 
30
+ @functools.cache
31
+ def _get_aws_secret(*, secret_name: str, region_name: str, sub_key: str | None) -> str:
32
+ client = boto3.client("secretsmanager", region_name=region_name)
33
+ response = client.get_secret_value(SecretId=secret_name)
34
+
35
+ if "SecretString" in response:
36
+ secret = response["SecretString"]
37
+ else:
38
+ secret = base64.b64decode(response["SecretBinary"])
39
+
40
+ value = json.loads(secret)
41
+
42
+ if sub_key is not None:
43
+ assert isinstance(value, dict)
44
+ return str(value[sub_key])
45
+ else:
46
+ return str(value)
47
+
48
+
25
49
  def retrieve_secret(
26
50
  secret_retrieval: SecretRetrieval, profile_metadata: ProfileMetadata
27
51
  ) -> str:
@@ -37,4 +61,11 @@ def retrieve_secret(
37
61
  )
38
62
  return secret
39
63
  case SecretRetrievalAWS():
40
- raise NotImplementedError("aws secret retrieval not yet implemented")
64
+ try:
65
+ return _get_aws_secret(
66
+ secret_name=secret_retrieval.secret_name,
67
+ region_name=secret_retrieval.region,
68
+ sub_key=secret_retrieval.sub_key,
69
+ )
70
+ except Exception as e:
71
+ raise SecretRetrievalError(secret_retrieval) from e
@@ -11,6 +11,7 @@ from apscheduler.triggers.cron import CronTrigger
11
11
  from sqlalchemy.engine.base import Engine
12
12
 
13
13
  from uncountable.integration.cron import CronJobArgs, cron_job_executor
14
+ from uncountable.types import base_t
14
15
  from uncountable.types.client_config_t import ClientConfigOptions
15
16
  from uncountable.types.job_definition_t import (
16
17
  AuthRetrieval,
@@ -67,7 +68,14 @@ class IntegrationServer:
67
68
  existing_job.reschedule(
68
69
  CronTrigger.from_crontab(job_defn.cron_spec)
69
70
  )
71
+ if not job_defn.enabled:
72
+ existing_job.pause()
73
+ else:
74
+ existing_job.resume()
70
75
  else:
76
+ job_opts: dict[str, base_t.JsonValue] = {}
77
+ if not job_defn.enabled:
78
+ job_opts["next_run_time"] = None
71
79
  self._scheduler.add_job(
72
80
  cron_job_executor,
73
81
  # IMPROVE: reconsider these defaults
@@ -77,6 +85,7 @@ class IntegrationServer:
77
85
  name=job_defn.name,
78
86
  id=job_defn.id,
79
87
  kwargs=job_kwargs,
88
+ **job_opts,
80
89
  )
81
90
  case _:
82
91
  assert_never(job_defn.trigger)
@@ -0,0 +1,130 @@
1
+ import functools
2
+ import os
3
+ import sys
4
+ import time
5
+ from contextlib import contextmanager
6
+ from enum import StrEnum
7
+ from typing import Generator, TextIO, assert_never, cast
8
+
9
+ from opentelemetry import trace
10
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11
+ from opentelemetry.sdk._logs import LogRecord
12
+ from opentelemetry.sdk.resources import Attributes, Resource
13
+ from opentelemetry.sdk.trace import TracerProvider
14
+ from opentelemetry.sdk.trace.export import (
15
+ SimpleSpanProcessor,
16
+ )
17
+ from opentelemetry.trace import Tracer
18
+
19
+ from uncountable.core.version import get_version
20
+ from uncountable.types import base_t, job_definition_t
21
+
22
+
23
+ def _cast_attributes(attributes: dict[str, base_t.JsonValue]) -> Attributes:
24
+ return cast(Attributes, attributes)
25
+
26
+
27
+ @functools.cache
28
+ def get_tracer() -> Tracer:
29
+ attributes: dict[str, base_t.JsonValue] = {
30
+ "service.name": "integration-server",
31
+ "sdk.version": get_version(),
32
+ }
33
+ unc_version = os.environ.get("UNC_VERSION")
34
+ if unc_version is not None:
35
+ attributes["service.version"] = unc_version
36
+ unc_env = os.environ.get("UNC_INTEGRATION_ENV")
37
+ if unc_env is not None:
38
+ attributes["deployment.environment"] = unc_env
39
+ resource = Resource.create(attributes=_cast_attributes(attributes))
40
+ provider = TracerProvider(resource=resource)
41
+ provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))
42
+ trace.set_tracer_provider(provider)
43
+ return provider.get_tracer("integration.telemetry")
44
+
45
+
46
+ class LogSeverity(StrEnum):
47
+ INFO = "Info"
48
+ WARN = "Warn"
49
+ ERROR = "Error"
50
+
51
+
52
+ class JobLogger:
53
+ current_span_id: int | None = None
54
+ current_trace_id: int | None = None
55
+
56
+ def __init__(
57
+ self,
58
+ *,
59
+ profile_metadata: job_definition_t.ProfileMetadata,
60
+ job_definition: job_definition_t.JobDefinition,
61
+ ) -> None:
62
+ self.profile_metadata = profile_metadata
63
+ self.job_definition = job_definition
64
+
65
+ def _patch_attributes(self, attributes: Attributes | None) -> Attributes:
66
+ patched_attributes: dict[str, base_t.JsonValue] = {
67
+ **(attributes if attributes is not None else {})
68
+ }
69
+ patched_attributes["profile.name"] = self.profile_metadata.name
70
+ patched_attributes["profile.base_url"] = self.profile_metadata.base_url
71
+ patched_attributes["job.name"] = self.job_definition.name
72
+ patched_attributes["job.id"] = self.job_definition.id
73
+ patched_attributes["job.definition_type"] = self.job_definition.type
74
+ match self.job_definition:
75
+ case job_definition_t.CronJobDefinition():
76
+ patched_attributes["job.definition.cron_spec"] = (
77
+ self.job_definition.cron_spec
78
+ )
79
+ case _:
80
+ assert_never(self.job_definition)
81
+ patched_attributes["job.definition.executor.type"] = (
82
+ self.job_definition.executor.type
83
+ )
84
+ match self.job_definition.executor:
85
+ case job_definition_t.JobExecutorScript():
86
+ patched_attributes["job.definition.executor.import_path"] = (
87
+ self.job_definition.executor.import_path
88
+ )
89
+ case job_definition_t.JobExecutorGenericUpload():
90
+ patched_attributes["job.definition.executor.data_source.type"] = (
91
+ self.job_definition.executor.data_source.type
92
+ )
93
+ case _:
94
+ assert_never(self.job_definition.executor)
95
+ return _cast_attributes(patched_attributes)
96
+
97
+ @contextmanager
98
+ def push_scope(
99
+ self, scope_name: str, *, attributes: Attributes | None = None
100
+ ) -> Generator["JobLogger", None, None]:
101
+ with get_tracer().start_as_current_span(
102
+ scope_name, attributes=self._patch_attributes(attributes)
103
+ ) as span:
104
+ self.current_span_id = span.get_span_context().span_id
105
+ self.current_trace_id = span.get_span_context().trace_id
106
+ yield self
107
+
108
+ def _emit_log(
109
+ self, message: str, *, severity: LogSeverity, attributes: Attributes | None
110
+ ) -> None:
111
+ log_record = LogRecord(
112
+ body=message,
113
+ severity_text=severity,
114
+ timestamp=time.time_ns(),
115
+ attributes=self._patch_attributes(attributes),
116
+ span_id=self.current_span_id,
117
+ trace_id=self.current_trace_id,
118
+ )
119
+ log_file: TextIO = sys.stderr if severity == LogSeverity.ERROR else sys.stdout
120
+ log_file.write(log_record.to_json())
121
+ log_file.flush()
122
+
123
+ def log_info(self, message: str, *, attributes: Attributes | None = None) -> None:
124
+ self._emit_log(message=message, severity=LogSeverity.INFO, attributes=attributes)
125
+
126
+ def log_warning(self, message: str, *, attributes: Attributes | None = None) -> None:
127
+ self._emit_log(message=message, severity=LogSeverity.WARN, attributes=attributes)
128
+
129
+ def log_error(self, message: str, *, attributes: Attributes | None = None) -> None:
130
+ self._emit_log(message=message, severity=LogSeverity.ERROR, attributes=attributes)
@@ -34,7 +34,7 @@ class EntityToCreate:
34
34
  @dataclasses.dataclass(kw_only=True)
35
35
  class Arguments:
36
36
  definition_id: base_t.ObjectId
37
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]]
37
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]]
38
38
  entities_to_create: list[EntityToCreate]
39
39
 
40
40
 
@@ -40,7 +40,7 @@ class EntityFieldInitialValue:
40
40
  @dataclasses.dataclass(kw_only=True)
41
41
  class Arguments:
42
42
  definition_id: base_t.ObjectId
43
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]]
43
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]]
44
44
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None
45
45
 
46
46
 
@@ -29,6 +29,7 @@ class Arguments:
29
29
  file_id: base_t.ObjectId
30
30
  uploader_key: identifier_t.IdentifierKey
31
31
  material_family_key: identifier_t.IdentifierKey
32
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None
32
33
 
33
34
 
34
35
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -163,6 +163,7 @@ class AsyncBatchProcessorBase(ABC):
163
163
  file_id: base_t.ObjectId,
164
164
  uploader_key: identifier_t.IdentifierKey,
165
165
  material_family_key: identifier_t.IdentifierKey,
166
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None,
166
167
  depends_on: typing.Optional[list[str]] = None,
167
168
  ) -> async_batch_t.QueuedAsyncBatchRequest:
168
169
  """Runs a file through an uploader.
@@ -173,6 +174,7 @@ class AsyncBatchProcessorBase(ABC):
173
174
  file_id=file_id,
174
175
  uploader_key=uploader_key,
175
176
  material_family_key=material_family_key,
177
+ recipe_key=recipe_key,
176
178
  )
177
179
  json_data = serialize_for_api(args)
178
180
 
@@ -235,7 +235,7 @@ class ClientMethods(ABC):
235
235
  self,
236
236
  *,
237
237
  definition_id: base_t.ObjectId,
238
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]],
238
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]],
239
239
  entities_to_create: list[create_entities_t.EntityToCreate],
240
240
  ) -> create_entities_t.Data:
241
241
  """Creates new Uncountable entities
@@ -260,7 +260,7 @@ class ClientMethods(ABC):
260
260
  self,
261
261
  *,
262
262
  definition_id: base_t.ObjectId,
263
- entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS]],
263
+ entity_type: typing.Union[typing.Literal[entity_t.EntityType.LAB_REQUEST], typing.Literal[entity_t.EntityType.APPROVAL], typing.Literal[entity_t.EntityType.CUSTOM_ENTITY], typing.Literal[entity_t.EntityType.INVENTORY_AMOUNT], typing.Literal[entity_t.EntityType.TASK], typing.Literal[entity_t.EntityType.PROJECT], typing.Literal[entity_t.EntityType.EQUIPMENT], typing.Literal[entity_t.EntityType.INV_LOCAL_LOCATIONS], typing.Literal[entity_t.EntityType.FIELD_OPTION_SET], typing.Literal[entity_t.EntityType.WEBHOOK], typing.Literal[entity_t.EntityType.SPECS], typing.Literal[entity_t.EntityType.GOAL]],
264
264
  field_values: typing.Optional[typing.Optional[list[field_values_t.FieldRefNameValue]]] = None,
265
265
  ) -> create_entity_t.Data:
266
266
  """Creates a new Uncountable entity
@@ -857,6 +857,7 @@ class ClientMethods(ABC):
857
857
  file_id: base_t.ObjectId,
858
858
  uploader_key: identifier_t.IdentifierKey,
859
859
  material_family_key: identifier_t.IdentifierKey,
860
+ recipe_key: typing.Optional[identifier_t.IdentifierKey] = None,
860
861
  ) -> invoke_uploader_t.Data:
861
862
  """Runs a file through an uploader.
862
863
 
@@ -865,6 +866,7 @@ class ClientMethods(ABC):
865
866
  file_id=file_id,
866
867
  uploader_key=uploader_key,
867
868
  material_family_key=material_family_key,
869
+ recipe_key=recipe_key,
868
870
  )
869
871
  api_request = APIRequest(
870
872
  method=invoke_uploader_t.ENDPOINT_METHOD,
@@ -21,7 +21,8 @@ from .job_definition_t import JobDefinitionBase as JobDefinitionBase
21
21
  from .job_definition_t import CronJobDefinition as CronJobDefinition
22
22
  from .job_definition_t import JobDefinition as JobDefinition
23
23
  from .job_definition_t import AuthRetrievalBase as AuthRetrievalBase
24
- from .job_definition_t import AuthRetrievalEnv as AuthRetrievalEnv
24
+ from .job_definition_t import AuthRetrievalOAuth as AuthRetrievalOAuth
25
+ from .job_definition_t import AuthRetrievalBasic as AuthRetrievalBasic
25
26
  from .job_definition_t import AuthRetrieval as AuthRetrieval
26
27
  from .job_definition_t import ProfileDefinition as ProfileDefinition
27
28
  from .job_definition_t import ProfileMetadata as ProfileMetadata