UncountablePythonSDK 0.0.49__tar.gz → 0.0.50__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.
Files changed (255) hide show
  1. {uncountablepythonsdk-0.0.49/UncountablePythonSDK.egg-info → uncountablepythonsdk-0.0.50}/PKG-INFO +5 -1
  2. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50/UncountablePythonSDK.egg-info}/PKG-INFO +5 -1
  3. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/UncountablePythonSDK.egg-info/SOURCES.txt +2 -0
  4. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/UncountablePythonSDK.egg-info/requires.txt +4 -0
  5. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/requirements.txt +1 -1
  6. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pyproject.toml +4 -0
  7. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/core/client.py +14 -9
  8. uncountablepythonsdk-0.0.50/uncountable/core/version.py +11 -0
  9. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/construct_client.py +9 -3
  10. uncountablepythonsdk-0.0.50/uncountable/integration/cron.py +55 -0
  11. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/executors/generic_upload_executor.py +2 -1
  12. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/job.py +1 -15
  13. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/server.py +9 -0
  14. uncountablepythonsdk-0.0.50/uncountable/integration/telemetry.py +130 -0
  15. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/uploader/invoke_uploader.py +1 -0
  16. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/async_batch_processor.py +2 -0
  17. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/client_base.py +2 -0
  18. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/job_definition_t.py +1 -0
  19. uncountablepythonsdk-0.0.49/uncountable/integration/cron.py +0 -47
  20. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/.github/workflows/documentation.yml +0 -0
  21. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/.github/workflows/publish.yml +0 -0
  22. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/.gitignore +0 -0
  23. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/README.md +0 -0
  24. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/UncountablePythonSDK.egg-info/dependency_links.txt +0 -0
  25. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/UncountablePythonSDK.egg-info/top_level.txt +0 -0
  26. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/.gitignore +0 -0
  27. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/conf.py +0 -0
  28. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/index.md +0 -0
  29. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/justfile +0 -0
  30. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/quickstart.md +0 -0
  31. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/android-chrome-192x192.png +0 -0
  32. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/android-chrome-512x512.png +0 -0
  33. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/apple-touch-icon.png +0 -0
  34. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/browserconfig.xml +0 -0
  35. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/favicon-16x16.png +0 -0
  36. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/favicon-32x32.png +0 -0
  37. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/manifest.json +0 -0
  38. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/mstile-150x150.png +0 -0
  39. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/favicons/safari-pinned-tab.svg +0 -0
  40. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/docs/static/logo_blue.png +0 -0
  41. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/examples/async_batch.py +0 -0
  42. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/examples/create_entity.py +0 -0
  43. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/examples/edit_recipe_inputs.py +0 -0
  44. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/examples/invoke_uploader.py +0 -0
  45. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/examples/upload_files.py +0 -0
  46. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/__init__.py +0 -0
  47. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/argument_parser/__init__.py +0 -0
  48. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/argument_parser/_is_enum.py +0 -0
  49. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/argument_parser/_is_namedtuple.py +0 -0
  50. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/argument_parser/argument_parser.py +0 -0
  51. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/argument_parser/case_convert.py +0 -0
  52. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/__init__.py +0 -0
  53. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/_gdrive_session.py +0 -0
  54. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/_local_session.py +0 -0
  55. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/_s3_session.py +0 -0
  56. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/_sftp_session.py +0 -0
  57. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/file_type_utils.py +0 -0
  58. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/filesystem_utils/filesystem_session.py +0 -0
  59. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/__init__.py +0 -0
  60. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/missing_sentry.py +0 -0
  61. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/opaque_key.py +0 -0
  62. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/serial_class.py +0 -0
  63. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/serial_union.py +0 -0
  64. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization/yaml.py +0 -0
  65. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization_util/__init__.py +0 -0
  66. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization_util/_get_type_for_serialization.py +0 -0
  67. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization_util/convert_to_snakecase.py +0 -0
  68. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/serialization_util/serialization_helpers.py +0 -0
  69. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/strenum_compat/__init__.py +0 -0
  70. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/strenum_compat/strenum_compat.py +0 -0
  71. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/__init__.py +0 -0
  72. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/__main__.py +0 -0
  73. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/actions_registry/__init__.py +0 -0
  74. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/actions_registry/__main__.py +0 -0
  75. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/actions_registry/emit_typescript.py +0 -0
  76. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/builder.py +0 -0
  77. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/config.py +0 -0
  78. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_io_ts.py +0 -0
  79. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_open_api.py +0 -0
  80. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_open_api_util.py +0 -0
  81. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_python.py +0 -0
  82. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_typescript.py +0 -0
  83. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/emit_typescript_util.py +0 -0
  84. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/load_types.py +0 -0
  85. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/open_api_util.py +0 -0
  86. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/parts/base.py.prepart +0 -0
  87. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/parts/base.ts.prepart +0 -0
  88. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/test.py +0 -0
  89. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/type_info/__main__.py +0 -0
  90. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/type_info/emit_type_info.py +0 -0
  91. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/util.py +0 -0
  92. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/value_spec/__init__.py +0 -0
  93. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/value_spec/__main__.py +0 -0
  94. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/value_spec/convert_type.py +0 -0
  95. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/value_spec/emit_python.py +0 -0
  96. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/pkgs/type_spec/value_spec/types.py +0 -0
  97. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/setup.cfg +0 -0
  98. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/__init__.py +0 -0
  99. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/core/__init__.py +0 -0
  100. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/core/async_batch.py +0 -0
  101. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/core/file_upload.py +0 -0
  102. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/core/types.py +0 -0
  103. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/__init__.py +0 -0
  104. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/db/__init__.py +0 -0
  105. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/db/connect.py +0 -0
  106. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/entrypoint.py +0 -0
  107. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/executors/__init__.py +0 -0
  108. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/executors/executors.py +0 -0
  109. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/executors/script_executor.py +0 -0
  110. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/secret_retrieval/__init__.py +0 -0
  111. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/integration/secret_retrieval/retrieve_secret.py +0 -0
  112. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/py.typed +0 -0
  113. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/__init__.py +0 -0
  114. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/__init__.py +0 -0
  115. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/batch/__init__.py +0 -0
  116. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/batch/execute_batch.py +0 -0
  117. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/batch/execute_batch_load_async.py +0 -0
  118. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/chemical/__init__.py +0 -0
  119. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/chemical/convert_chemical_formats.py +0 -0
  120. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/__init__.py +0 -0
  121. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/create_entities.py +0 -0
  122. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/create_entity.py +0 -0
  123. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/get_entities_data.py +0 -0
  124. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/list_entities.py +0 -0
  125. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/lock_entity.py +0 -0
  126. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/resolve_entity_ids.py +0 -0
  127. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/set_values.py +0 -0
  128. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/transition_entity_phase.py +0 -0
  129. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/entity/unlock_entity.py +0 -0
  130. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/equipment/__init__.py +0 -0
  131. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/equipment/associate_equipment_input.py +0 -0
  132. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/field_options/__init__.py +0 -0
  133. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/field_options/upsert_field_options.py +0 -0
  134. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/id_source/__init__.py +0 -0
  135. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/id_source/list_id_source.py +0 -0
  136. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/id_source/match_id_source.py +0 -0
  137. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/input_groups/__init__.py +0 -0
  138. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/input_groups/get_input_group_names.py +0 -0
  139. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/__init__.py +0 -0
  140. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/create_inputs.py +0 -0
  141. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/get_input_data.py +0 -0
  142. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/get_input_names.py +0 -0
  143. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/get_inputs_data.py +0 -0
  144. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/set_input_attribute_values.py +0 -0
  145. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/set_input_category.py +0 -0
  146. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/set_input_subcategories.py +0 -0
  147. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/inputs/set_intermediate_type.py +0 -0
  148. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/material_families/__init__.py +0 -0
  149. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/material_families/update_entity_material_families.py +0 -0
  150. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/outputs/__init__.py +0 -0
  151. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/outputs/get_output_data.py +0 -0
  152. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/outputs/get_output_names.py +0 -0
  153. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/outputs/resolve_output_conditions.py +0 -0
  154. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/permissions/__init__.py +0 -0
  155. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/permissions/set_core_permissions.py +0 -0
  156. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/project/__init__.py +0 -0
  157. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/project/get_projects.py +0 -0
  158. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/project/get_projects_data.py +0 -0
  159. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipe_links/__init__.py +0 -0
  160. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipe_links/create_recipe_link.py +0 -0
  161. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipe_links/remove_recipe_link.py +0 -0
  162. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipe_metadata/__init__.py +0 -0
  163. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +0 -0
  164. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/__init__.py +0 -0
  165. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/add_recipe_to_project.py +0 -0
  166. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/archive_recipes.py +0 -0
  167. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/associate_recipe_as_input.py +0 -0
  168. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/associate_recipe_as_lot.py +0 -0
  169. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/create_recipe.py +0 -0
  170. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/create_recipes.py +0 -0
  171. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/disassociate_recipe_as_input.py +0 -0
  172. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/edit_recipe_inputs.py +0 -0
  173. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_curve.py +0 -0
  174. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_recipe_calculations.py +0 -0
  175. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_recipe_links.py +0 -0
  176. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_recipe_names.py +0 -0
  177. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_recipe_output_metadata.py +0 -0
  178. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/get_recipes_data.py +0 -0
  179. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/lock_recipes.py +0 -0
  180. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/remove_recipe_from_project.py +0 -0
  181. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/set_recipe_inputs.py +0 -0
  182. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/set_recipe_metadata.py +0 -0
  183. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/set_recipe_output_annotations.py +0 -0
  184. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/set_recipe_outputs.py +0 -0
  185. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/set_recipe_tags.py +0 -0
  186. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/unarchive_recipes.py +0 -0
  187. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/recipes/unlock_recipes.py +0 -0
  188. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/triggers/__init__.py +0 -0
  189. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/triggers/run_trigger.py +0 -0
  190. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/api/uploader/__init__.py +0 -0
  191. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/async_batch.py +0 -0
  192. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/async_batch_t.py +0 -0
  193. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/base.py +0 -0
  194. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/base_t.py +0 -0
  195. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/calculations.py +0 -0
  196. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/calculations_t.py +0 -0
  197. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/chemical_structure.py +0 -0
  198. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/chemical_structure_t.py +0 -0
  199. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/client_config.py +0 -0
  200. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/client_config_t.py +0 -0
  201. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/curves.py +0 -0
  202. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/curves_t.py +0 -0
  203. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/entity.py +0 -0
  204. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/entity_t.py +0 -0
  205. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/experiment_groups.py +0 -0
  206. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/experiment_groups_t.py +0 -0
  207. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/field_values.py +0 -0
  208. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/field_values_t.py +0 -0
  209. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/fields.py +0 -0
  210. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/fields_t.py +0 -0
  211. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/generic_upload.py +0 -0
  212. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/generic_upload_t.py +0 -0
  213. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/id_source.py +0 -0
  214. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/id_source_t.py +0 -0
  215. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/identifier.py +0 -0
  216. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/identifier_t.py +0 -0
  217. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/input_attributes.py +0 -0
  218. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/input_attributes_t.py +0 -0
  219. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/inputs.py +0 -0
  220. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/inputs_t.py +0 -0
  221. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/job_definition.py +0 -0
  222. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/outputs.py +0 -0
  223. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/outputs_t.py +0 -0
  224. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/permissions.py +0 -0
  225. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/permissions_t.py +0 -0
  226. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/phases.py +0 -0
  227. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/phases_t.py +0 -0
  228. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/post_base.py +0 -0
  229. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/post_base_t.py +0 -0
  230. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_identifiers.py +0 -0
  231. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_identifiers_t.py +0 -0
  232. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_inputs.py +0 -0
  233. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_inputs_t.py +0 -0
  234. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_links.py +0 -0
  235. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_links_t.py +0 -0
  236. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_metadata.py +0 -0
  237. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_metadata_t.py +0 -0
  238. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_output_metadata.py +0 -0
  239. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_output_metadata_t.py +0 -0
  240. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_tags.py +0 -0
  241. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_tags_t.py +0 -0
  242. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_workflow_steps.py +0 -0
  243. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipe_workflow_steps_t.py +0 -0
  244. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipes.py +0 -0
  245. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/recipes_t.py +0 -0
  246. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/response.py +0 -0
  247. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/response_t.py +0 -0
  248. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/secret_retrieval.py +0 -0
  249. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/secret_retrieval_t.py +0 -0
  250. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/units.py +0 -0
  251. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/units_t.py +0 -0
  252. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/users.py +0 -0
  253. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/users_t.py +0 -0
  254. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/uncountable/types/workflows.py +0 -0
  255. {uncountablepythonsdk-0.0.49 → uncountablepythonsdk-0.0.50}/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.50
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.50
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
@@ -3,6 +3,7 @@ import os
3
3
  from uncountable.core import AuthDetailsApiKey, Client
4
4
  from uncountable.core.client import ClientConfig
5
5
  from uncountable.core.types import AuthDetailsAll
6
+ from uncountable.integration.telemetry import JobLogger
6
7
  from uncountable.types.job_definition_t import (
7
8
  AuthRetrievalEnv,
8
9
  ProfileMetadata,
@@ -30,18 +31,23 @@ def _construct_auth_details(profile_meta: ProfileMetadata) -> AuthDetailsAll:
30
31
  return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_secret_key)
31
32
 
32
33
 
33
- def _construct_client_config(profile_meta: ProfileMetadata) -> ClientConfig | None:
34
+ def _construct_client_config(
35
+ profile_meta: ProfileMetadata, job_logger: JobLogger
36
+ ) -> ClientConfig | None:
34
37
  if profile_meta.client_options is None:
35
38
  return None
36
39
  return ClientConfig(
37
40
  allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
38
41
  extra_headers=profile_meta.client_options.extra_headers,
42
+ job_logger=job_logger,
39
43
  )
40
44
 
41
45
 
42
- def construct_uncountable_client(profile_meta: ProfileMetadata) -> Client:
46
+ def construct_uncountable_client(
47
+ profile_meta: ProfileMetadata, job_logger: JobLogger
48
+ ) -> Client:
43
49
  return Client(
44
50
  base_url=profile_meta.base_url,
45
51
  auth_details=_construct_auth_details(profile_meta),
46
- config=_construct_client_config(profile_meta),
52
+ config=_construct_client_config(profile_meta, job_logger),
47
53
  )
@@ -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
@@ -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)
@@ -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
 
@@ -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,
@@ -162,6 +162,7 @@ class JobDefinitionBase:
162
162
  id: str
163
163
  name: str
164
164
  executor: JobExecutor
165
+ enabled: bool = False
165
166
 
166
167
 
167
168
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -1,47 +0,0 @@
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, JobLogger
8
- from uncountable.types.job_definition_t import JobDefinition, ProfileMetadata
9
-
10
-
11
- @dataclass
12
- class CronJobArgs:
13
- definition: JobDefinition
14
- profile_metadata: ProfileMetadata
15
-
16
-
17
- cron_args_parser = CachedParser(CronJobArgs)
18
-
19
-
20
- def cron_job_executor(**kwargs: dict) -> None:
21
- args_passed = cron_args_parser.parse_storage(kwargs)
22
- client = construct_uncountable_client(profile_meta=args_passed.profile_metadata)
23
- batch_processor = AsyncBatchProcessor(client=client)
24
- args = CronJobArguments(
25
- job_definition=args_passed.definition,
26
- client=client,
27
- batch_processor=batch_processor,
28
- profile_metadata=args_passed.profile_metadata,
29
- logger=JobLogger(
30
- profile_metadata=args_passed.profile_metadata,
31
- job_definition=args_passed.definition,
32
- ),
33
- )
34
-
35
- job = resolve_executor(args_passed.definition.executor, args_passed.profile_metadata)
36
-
37
- print(f"running job {args_passed.definition.name}")
38
-
39
- job.run(args=args)
40
-
41
- if batch_processor.current_queue_size() != 0:
42
- batch_processor.send()
43
-
44
- print(f"completed job {args_passed.definition.name}")
45
- submitted_batch_job_ids = batch_processor.get_submitted_job_ids()
46
- if len(submitted_batch_job_ids) != 0:
47
- print("submitted batch jobs", submitted_batch_job_ids)