UncountablePythonSDK 0.0.52__py3-none-any.whl → 0.0.131__py3-none-any.whl

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 (316) hide show
  1. docs/conf.py +54 -7
  2. docs/index.md +107 -4
  3. docs/integration_examples/create_ingredient.md +43 -0
  4. docs/integration_examples/create_output.md +56 -0
  5. docs/integration_examples/index.md +6 -0
  6. docs/justfile +2 -2
  7. docs/requirements.txt +6 -4
  8. examples/async_batch.py +3 -3
  9. examples/basic_auth.py +7 -0
  10. examples/create_entity.py +3 -1
  11. examples/create_ingredient_sdk.py +34 -0
  12. examples/download_files.py +26 -0
  13. examples/edit_recipe_inputs.py +4 -2
  14. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  15. examples/integration-server/jobs/materials_auto/example_cron.py +21 -0
  16. examples/integration-server/jobs/materials_auto/example_http.py +47 -0
  17. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  18. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  19. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  20. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +39 -0
  21. examples/integration-server/jobs/materials_auto/example_wh.py +23 -0
  22. examples/integration-server/jobs/materials_auto/profile.yaml +104 -0
  23. examples/integration-server/pyproject.toml +224 -0
  24. examples/invoke_uploader.py +4 -1
  25. examples/oauth.py +7 -0
  26. examples/set_recipe_metadata_file.py +40 -0
  27. examples/set_recipe_output_file_sdk.py +26 -0
  28. examples/upload_files.py +1 -2
  29. pkgs/argument_parser/__init__.py +9 -0
  30. pkgs/argument_parser/_is_namedtuple.py +3 -0
  31. pkgs/argument_parser/argument_parser.py +217 -70
  32. pkgs/filesystem_utils/__init__.py +1 -0
  33. pkgs/filesystem_utils/_blob_session.py +144 -0
  34. pkgs/filesystem_utils/_gdrive_session.py +10 -7
  35. pkgs/filesystem_utils/_s3_session.py +15 -13
  36. pkgs/filesystem_utils/_sftp_session.py +11 -7
  37. pkgs/filesystem_utils/file_type_utils.py +30 -10
  38. pkgs/py.typed +0 -0
  39. pkgs/serialization/__init__.py +7 -2
  40. pkgs/serialization/annotation.py +64 -0
  41. pkgs/serialization/missing_sentry.py +1 -1
  42. pkgs/serialization/opaque_key.py +1 -1
  43. pkgs/serialization/serial_alias.py +47 -0
  44. pkgs/serialization/serial_class.py +47 -26
  45. pkgs/serialization/serial_generic.py +16 -0
  46. pkgs/serialization/serial_union.py +17 -14
  47. pkgs/serialization/yaml.py +4 -1
  48. pkgs/serialization_util/__init__.py +6 -0
  49. pkgs/serialization_util/dataclasses.py +14 -0
  50. pkgs/serialization_util/serialization_helpers.py +15 -5
  51. pkgs/type_spec/actions_registry/__main__.py +0 -4
  52. pkgs/type_spec/actions_registry/emit_typescript.py +5 -5
  53. pkgs/type_spec/builder.py +354 -119
  54. pkgs/type_spec/builder_types.py +9 -0
  55. pkgs/type_spec/config.py +51 -11
  56. pkgs/type_spec/cross_output_links.py +99 -0
  57. pkgs/type_spec/emit_io_ts.py +1 -1
  58. pkgs/type_spec/emit_open_api.py +127 -36
  59. pkgs/type_spec/emit_open_api_util.py +5 -6
  60. pkgs/type_spec/emit_python.py +329 -121
  61. pkgs/type_spec/emit_typescript.py +117 -256
  62. pkgs/type_spec/emit_typescript_util.py +291 -2
  63. pkgs/type_spec/load_types.py +18 -4
  64. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  65. pkgs/type_spec/open_api_util.py +29 -4
  66. pkgs/type_spec/parts/base.py.prepart +13 -10
  67. pkgs/type_spec/parts/base.ts.prepart +4 -0
  68. pkgs/type_spec/type_info/__main__.py +3 -1
  69. pkgs/type_spec/type_info/emit_type_info.py +124 -29
  70. pkgs/type_spec/ui_entry_actions/__init__.py +4 -0
  71. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +308 -0
  72. pkgs/type_spec/util.py +4 -4
  73. pkgs/type_spec/value_spec/__main__.py +26 -9
  74. pkgs/type_spec/value_spec/convert_type.py +21 -1
  75. pkgs/type_spec/value_spec/emit_python.py +25 -7
  76. pkgs/type_spec/value_spec/types.py +1 -1
  77. uncountable/core/async_batch.py +1 -1
  78. uncountable/core/client.py +142 -39
  79. uncountable/core/environment.py +41 -0
  80. uncountable/core/file_upload.py +52 -18
  81. uncountable/integration/cli.py +142 -0
  82. uncountable/integration/construct_client.py +8 -8
  83. uncountable/integration/cron.py +11 -37
  84. uncountable/integration/db/connect.py +12 -2
  85. uncountable/integration/db/session.py +25 -0
  86. uncountable/integration/entrypoint.py +8 -37
  87. uncountable/integration/executors/executors.py +125 -2
  88. uncountable/integration/executors/generic_upload_executor.py +87 -29
  89. uncountable/integration/executors/script_executor.py +3 -3
  90. uncountable/integration/http_server/__init__.py +5 -0
  91. uncountable/integration/http_server/types.py +69 -0
  92. uncountable/integration/job.py +242 -12
  93. uncountable/integration/queue_runner/__init__.py +0 -0
  94. uncountable/integration/queue_runner/command_server/__init__.py +28 -0
  95. uncountable/integration/queue_runner/command_server/command_client.py +133 -0
  96. uncountable/integration/queue_runner/command_server/command_server.py +142 -0
  97. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  98. uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
  99. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +58 -0
  100. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +57 -0
  101. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +114 -0
  102. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +264 -0
  103. uncountable/integration/queue_runner/command_server/types.py +75 -0
  104. uncountable/integration/queue_runner/datastore/__init__.py +3 -0
  105. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +250 -0
  106. uncountable/integration/queue_runner/datastore/interface.py +29 -0
  107. uncountable/integration/queue_runner/datastore/model.py +24 -0
  108. uncountable/integration/queue_runner/job_scheduler.py +200 -0
  109. uncountable/integration/queue_runner/queue_runner.py +34 -0
  110. uncountable/integration/queue_runner/types.py +7 -0
  111. uncountable/integration/queue_runner/worker.py +116 -0
  112. uncountable/integration/scan_profiles.py +67 -0
  113. uncountable/integration/scheduler.py +199 -0
  114. uncountable/integration/secret_retrieval/retrieve_secret.py +26 -4
  115. uncountable/integration/server.py +94 -69
  116. uncountable/integration/telemetry.py +150 -34
  117. uncountable/integration/webhook_server/entrypoint.py +97 -0
  118. uncountable/types/__init__.py +78 -1
  119. uncountable/types/api/batch/execute_batch.py +13 -6
  120. uncountable/types/api/batch/execute_batch_load_async.py +9 -3
  121. uncountable/types/api/chemical/convert_chemical_formats.py +17 -5
  122. uncountable/types/api/condition_parameters/__init__.py +1 -0
  123. uncountable/types/api/condition_parameters/upsert_condition_match.py +72 -0
  124. uncountable/types/api/entity/create_entities.py +19 -7
  125. uncountable/types/api/entity/create_entity.py +17 -8
  126. uncountable/types/api/entity/create_or_update_entity.py +48 -0
  127. uncountable/types/api/entity/export_entities.py +59 -0
  128. uncountable/types/api/entity/get_entities_data.py +13 -4
  129. uncountable/types/api/entity/grant_entity_permissions.py +48 -0
  130. uncountable/types/api/entity/list_aggregate.py +79 -0
  131. uncountable/types/api/entity/list_entities.py +42 -10
  132. uncountable/types/api/entity/lock_entity.py +11 -4
  133. uncountable/types/api/entity/lookup_entity.py +116 -0
  134. uncountable/types/api/entity/resolve_entity_ids.py +15 -6
  135. uncountable/types/api/entity/set_entity_field_values.py +44 -0
  136. uncountable/types/api/entity/set_values.py +10 -3
  137. uncountable/types/api/entity/transition_entity_phase.py +22 -7
  138. uncountable/types/api/entity/unlock_entity.py +10 -3
  139. uncountable/types/api/equipment/associate_equipment_input.py +9 -3
  140. uncountable/types/api/field_options/upsert_field_options.py +17 -7
  141. uncountable/types/api/files/__init__.py +1 -0
  142. uncountable/types/api/files/download_file.py +77 -0
  143. uncountable/types/api/id_source/list_id_source.py +16 -7
  144. uncountable/types/api/id_source/match_id_source.py +14 -5
  145. uncountable/types/api/input_groups/get_input_group_names.py +13 -4
  146. uncountable/types/api/inputs/create_inputs.py +23 -9
  147. uncountable/types/api/inputs/get_input_data.py +30 -12
  148. uncountable/types/api/inputs/get_input_names.py +16 -7
  149. uncountable/types/api/inputs/get_inputs_data.py +25 -7
  150. uncountable/types/api/inputs/set_input_attribute_values.py +12 -6
  151. uncountable/types/api/inputs/set_input_category.py +12 -5
  152. uncountable/types/api/inputs/set_input_subcategories.py +10 -3
  153. uncountable/types/api/inputs/set_intermediate_type.py +11 -4
  154. uncountable/types/api/integrations/__init__.py +1 -0
  155. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  156. uncountable/types/api/integrations/push_notification.py +49 -0
  157. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  158. uncountable/types/api/listing/__init__.py +1 -0
  159. uncountable/types/api/listing/fetch_listing.py +58 -0
  160. uncountable/types/api/material_families/update_entity_material_families.py +10 -4
  161. uncountable/types/api/notebooks/__init__.py +1 -0
  162. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  163. uncountable/types/api/outputs/get_output_data.py +28 -13
  164. uncountable/types/api/outputs/get_output_names.py +15 -6
  165. uncountable/types/api/outputs/get_output_organization.py +173 -0
  166. uncountable/types/api/outputs/resolve_output_conditions.py +20 -8
  167. uncountable/types/api/permissions/set_core_permissions.py +26 -10
  168. uncountable/types/api/project/get_projects.py +16 -7
  169. uncountable/types/api/project/get_projects_data.py +17 -8
  170. uncountable/types/api/recipe_links/create_recipe_link.py +12 -5
  171. uncountable/types/api/recipe_links/remove_recipe_link.py +11 -4
  172. uncountable/types/api/recipe_metadata/get_recipe_metadata_data.py +16 -7
  173. uncountable/types/api/recipes/add_recipe_to_project.py +10 -3
  174. uncountable/types/api/recipes/add_time_series_data.py +64 -0
  175. uncountable/types/api/recipes/archive_recipes.py +11 -4
  176. uncountable/types/api/recipes/associate_recipe_as_input.py +12 -5
  177. uncountable/types/api/recipes/associate_recipe_as_lot.py +10 -3
  178. uncountable/types/api/recipes/clear_recipe_outputs.py +42 -0
  179. uncountable/types/api/recipes/create_mix_order.py +44 -0
  180. uncountable/types/api/recipes/create_recipe.py +15 -9
  181. uncountable/types/api/recipes/create_recipes.py +21 -9
  182. uncountable/types/api/recipes/disassociate_recipe_as_input.py +10 -3
  183. uncountable/types/api/recipes/edit_recipe_inputs.py +134 -22
  184. uncountable/types/api/recipes/get_column_calculation_values.py +57 -0
  185. uncountable/types/api/recipes/get_curve.py +11 -5
  186. uncountable/types/api/recipes/get_recipe_calculations.py +13 -7
  187. uncountable/types/api/recipes/get_recipe_links.py +10 -4
  188. uncountable/types/api/recipes/get_recipe_names.py +13 -4
  189. uncountable/types/api/recipes/get_recipe_output_metadata.py +12 -6
  190. uncountable/types/api/recipes/get_recipes_data.py +87 -33
  191. uncountable/types/api/recipes/lock_recipes.py +19 -8
  192. uncountable/types/api/recipes/remove_recipe_from_project.py +10 -3
  193. uncountable/types/api/recipes/set_recipe_inputs.py +16 -10
  194. uncountable/types/api/recipes/set_recipe_metadata.py +10 -3
  195. uncountable/types/api/recipes/set_recipe_output_annotations.py +24 -12
  196. uncountable/types/api/recipes/set_recipe_output_file.py +55 -0
  197. uncountable/types/api/recipes/set_recipe_outputs.py +35 -12
  198. uncountable/types/api/recipes/set_recipe_tags.py +26 -9
  199. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  200. uncountable/types/api/recipes/unarchive_recipes.py +10 -3
  201. uncountable/types/api/recipes/unlock_recipes.py +14 -6
  202. uncountable/types/api/runsheet/__init__.py +1 -0
  203. uncountable/types/api/runsheet/complete_async_upload.py +41 -0
  204. uncountable/types/api/triggers/run_trigger.py +11 -4
  205. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  206. uncountable/types/api/uploader/invoke_uploader.py +13 -6
  207. uncountable/types/api/user/__init__.py +1 -0
  208. uncountable/types/api/user/get_current_user_info.py +40 -0
  209. uncountable/types/async_batch.py +2 -1
  210. uncountable/types/async_batch_processor.py +618 -18
  211. uncountable/types/async_batch_t.py +54 -7
  212. uncountable/types/async_jobs.py +8 -0
  213. uncountable/types/async_jobs_t.py +52 -0
  214. uncountable/types/auth_retrieval.py +11 -0
  215. uncountable/types/auth_retrieval_t.py +75 -0
  216. uncountable/types/base.py +0 -1
  217. uncountable/types/base_t.py +13 -11
  218. uncountable/types/calculations.py +0 -1
  219. uncountable/types/calculations_t.py +5 -2
  220. uncountable/types/chemical_structure.py +0 -1
  221. uncountable/types/chemical_structure_t.py +6 -5
  222. uncountable/types/client_base.py +751 -70
  223. uncountable/types/client_config.py +1 -1
  224. uncountable/types/client_config_t.py +17 -3
  225. uncountable/types/curves.py +0 -1
  226. uncountable/types/curves_t.py +10 -7
  227. uncountable/types/data.py +12 -0
  228. uncountable/types/data_t.py +103 -0
  229. uncountable/types/entity.py +4 -1
  230. uncountable/types/entity_t.py +125 -7
  231. uncountable/types/experiment_groups.py +0 -1
  232. uncountable/types/experiment_groups_t.py +5 -2
  233. uncountable/types/exports.py +8 -0
  234. uncountable/types/exports_t.py +34 -0
  235. uncountable/types/field_values.py +19 -1
  236. uncountable/types/field_values_t.py +246 -9
  237. uncountable/types/fields.py +0 -1
  238. uncountable/types/fields_t.py +5 -2
  239. uncountable/types/generic_upload.py +6 -1
  240. uncountable/types/generic_upload_t.py +88 -9
  241. uncountable/types/id_source.py +0 -1
  242. uncountable/types/id_source_t.py +26 -7
  243. uncountable/types/identifier.py +0 -1
  244. uncountable/types/identifier_t.py +13 -5
  245. uncountable/types/input_attributes.py +0 -1
  246. uncountable/types/input_attributes_t.py +4 -4
  247. uncountable/types/inputs.py +1 -1
  248. uncountable/types/inputs_t.py +24 -4
  249. uncountable/types/integration_server.py +8 -0
  250. uncountable/types/integration_server_t.py +46 -0
  251. uncountable/types/integration_session.py +10 -0
  252. uncountable/types/integration_session_t.py +60 -0
  253. uncountable/types/integrations.py +10 -0
  254. uncountable/types/integrations_t.py +62 -0
  255. uncountable/types/job_definition.py +4 -6
  256. uncountable/types/job_definition_t.py +96 -65
  257. uncountable/types/listing.py +9 -0
  258. uncountable/types/listing_t.py +51 -0
  259. uncountable/types/notices.py +8 -0
  260. uncountable/types/notices_t.py +37 -0
  261. uncountable/types/notifications.py +11 -0
  262. uncountable/types/notifications_t.py +74 -0
  263. uncountable/types/outputs.py +0 -1
  264. uncountable/types/outputs_t.py +6 -3
  265. uncountable/types/overrides.py +9 -0
  266. uncountable/types/overrides_t.py +49 -0
  267. uncountable/types/permissions.py +0 -1
  268. uncountable/types/permissions_t.py +1 -2
  269. uncountable/types/phases.py +0 -1
  270. uncountable/types/phases_t.py +5 -2
  271. uncountable/types/post_base.py +0 -1
  272. uncountable/types/post_base_t.py +1 -2
  273. uncountable/types/queued_job.py +17 -0
  274. uncountable/types/queued_job_t.py +140 -0
  275. uncountable/types/recipe_identifiers.py +0 -1
  276. uncountable/types/recipe_identifiers_t.py +21 -8
  277. uncountable/types/recipe_inputs.py +0 -1
  278. uncountable/types/recipe_inputs_t.py +1 -2
  279. uncountable/types/recipe_links.py +0 -1
  280. uncountable/types/recipe_links_t.py +7 -4
  281. uncountable/types/recipe_metadata.py +0 -1
  282. uncountable/types/recipe_metadata_t.py +14 -9
  283. uncountable/types/recipe_output_metadata.py +0 -1
  284. uncountable/types/recipe_output_metadata_t.py +5 -2
  285. uncountable/types/recipe_tags.py +0 -1
  286. uncountable/types/recipe_tags_t.py +5 -2
  287. uncountable/types/recipe_workflow_steps.py +0 -1
  288. uncountable/types/recipe_workflow_steps_t.py +14 -7
  289. uncountable/types/recipes.py +0 -1
  290. uncountable/types/recipes_t.py +6 -2
  291. uncountable/types/response.py +0 -1
  292. uncountable/types/response_t.py +3 -2
  293. uncountable/types/secret_retrieval.py +0 -1
  294. uncountable/types/secret_retrieval_t.py +13 -7
  295. uncountable/types/sockets.py +20 -0
  296. uncountable/types/sockets_t.py +169 -0
  297. uncountable/types/structured_filters.py +25 -0
  298. uncountable/types/structured_filters_t.py +248 -0
  299. uncountable/types/units.py +0 -1
  300. uncountable/types/units_t.py +5 -2
  301. uncountable/types/uploader.py +24 -0
  302. uncountable/types/uploader_t.py +222 -0
  303. uncountable/types/users.py +0 -1
  304. uncountable/types/users_t.py +5 -2
  305. uncountable/types/webhook_job.py +9 -0
  306. uncountable/types/webhook_job_t.py +48 -0
  307. uncountable/types/workflows.py +0 -1
  308. uncountable/types/workflows_t.py +10 -4
  309. uncountablepythonsdk-0.0.131.dist-info/METADATA +64 -0
  310. uncountablepythonsdk-0.0.131.dist-info/RECORD +363 -0
  311. {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/WHEEL +1 -1
  312. UncountablePythonSDK-0.0.52.dist-info/METADATA +0 -56
  313. UncountablePythonSDK-0.0.52.dist-info/RECORD +0 -246
  314. docs/quickstart.md +0 -19
  315. uncountable/core/version.py +0 -11
  316. {UncountablePythonSDK-0.0.52.dist-info → uncountablepythonsdk-0.0.131.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,142 @@
1
+ import argparse
2
+
3
+ from dateutil import tz
4
+ from opentelemetry.trace import get_current_span
5
+ from tabulate import tabulate
6
+
7
+ from uncountable.core.environment import get_local_admin_server_port
8
+ from uncountable.integration.queue_runner.command_server.command_client import (
9
+ send_job_queue_message,
10
+ send_list_queued_jobs_message,
11
+ send_retry_job_message,
12
+ )
13
+ from uncountable.integration.telemetry import Logger
14
+ from uncountable.types import queued_job_t
15
+
16
+
17
+ def register_enqueue_job_parser(
18
+ sub_parser_manager: argparse._SubParsersAction,
19
+ parents: list[argparse.ArgumentParser],
20
+ ) -> None:
21
+ run_parser = sub_parser_manager.add_parser(
22
+ "run",
23
+ parents=parents,
24
+ help="Process a job with a given host and job ID",
25
+ description="Process a job with a given host and job ID",
26
+ )
27
+ run_parser.add_argument("job_id", type=str, help="The ID of the job to process")
28
+
29
+ def _handle_enqueue_job(args: argparse.Namespace) -> None:
30
+ send_job_queue_message(
31
+ job_ref_name=args.job_id,
32
+ payload=queued_job_t.QueuedJobPayload(
33
+ invocation_context=queued_job_t.InvocationContextManual()
34
+ ),
35
+ host=args.host,
36
+ port=get_local_admin_server_port(),
37
+ )
38
+
39
+ run_parser.set_defaults(func=_handle_enqueue_job)
40
+
41
+
42
+ def register_list_queued_jobs(
43
+ sub_parser_manager: argparse._SubParsersAction,
44
+ parents: list[argparse.ArgumentParser],
45
+ ) -> None:
46
+ list_queued_jobs_parser = sub_parser_manager.add_parser(
47
+ "list-queued-jobs",
48
+ parents=parents,
49
+ help="List all jobs queued on the integration server",
50
+ description="List all jobs queued on the integration server",
51
+ )
52
+
53
+ list_queued_jobs_parser.add_argument(
54
+ "--offset",
55
+ type=int,
56
+ default=0,
57
+ help="Number of jobs to skip. Should be non-negative.",
58
+ )
59
+ list_queued_jobs_parser.add_argument(
60
+ "--limit",
61
+ type=int,
62
+ default=100,
63
+ help="A number between 1 and 100 specifying the number of jobs to return in the result set.",
64
+ )
65
+
66
+ def _handle_list_queued_jobs(args: argparse.Namespace) -> None:
67
+ queued_jobs = send_list_queued_jobs_message(
68
+ offset=args.offset,
69
+ limit=args.limit,
70
+ host=args.host,
71
+ port=get_local_admin_server_port(),
72
+ )
73
+
74
+ headers = ["UUID", "Job Ref Name", "Attempts", "Status", "Submitted At"]
75
+ rows = [
76
+ [
77
+ job.uuid,
78
+ job.job_ref_name,
79
+ job.num_attempts,
80
+ job.status,
81
+ job.submitted_at.ToDatetime(tz.UTC).astimezone(tz.tzlocal()),
82
+ ]
83
+ for job in queued_jobs
84
+ ]
85
+ print(tabulate(rows, headers=headers, tablefmt="grid"))
86
+
87
+ list_queued_jobs_parser.set_defaults(func=_handle_list_queued_jobs)
88
+
89
+
90
+ def register_retry_job_parser(
91
+ sub_parser_manager: argparse._SubParsersAction,
92
+ parents: list[argparse.ArgumentParser],
93
+ ) -> None:
94
+ retry_failed_jobs_parser = sub_parser_manager.add_parser(
95
+ "retry-job",
96
+ parents=parents,
97
+ help="Retry failed job on the integration server",
98
+ description="Retry failed job on the integration server",
99
+ )
100
+
101
+ retry_failed_jobs_parser.add_argument(
102
+ "job_uuid", type=str, help="The uuid of the job to retry"
103
+ )
104
+
105
+ def _handle_retry_job(args: argparse.Namespace) -> None:
106
+ send_retry_job_message(
107
+ job_uuid=args.job_uuid,
108
+ host=args.host,
109
+ port=get_local_admin_server_port(),
110
+ )
111
+
112
+ retry_failed_jobs_parser.set_defaults(func=_handle_retry_job)
113
+
114
+
115
+ def main() -> None:
116
+ logger = Logger(get_current_span())
117
+
118
+ main_parser = argparse.ArgumentParser(
119
+ description="Execute a given integrations server command."
120
+ )
121
+
122
+ base_parser = argparse.ArgumentParser(add_help=False)
123
+ base_parser.add_argument(
124
+ "--host", type=str, default="localhost", nargs="?", help="The host to run on"
125
+ )
126
+
127
+ subparser_action = main_parser.add_subparsers(
128
+ dest="command",
129
+ required=True,
130
+ help="The command to execute (e.g., 'run')",
131
+ )
132
+
133
+ register_enqueue_job_parser(subparser_action, parents=[base_parser])
134
+ register_retry_job_parser(subparser_action, parents=[base_parser])
135
+ register_list_queued_jobs(subparser_action, parents=[base_parser])
136
+
137
+ args = main_parser.parse_args()
138
+ with logger.push_scope(args.command):
139
+ args.func(args)
140
+
141
+
142
+ main()
@@ -3,27 +3,27 @@ from uncountable.core.client import ClientConfig
3
3
  from uncountable.core.types import AuthDetailsAll, AuthDetailsOAuth
4
4
  from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
5
5
  from uncountable.integration.telemetry import JobLogger
6
+ from uncountable.types import auth_retrieval_t
6
7
  from uncountable.types.job_definition_t import (
7
- AuthRetrievalBasic,
8
- AuthRetrievalOAuth,
9
8
  ProfileMetadata,
10
9
  )
11
10
 
12
11
 
13
12
  def _construct_auth_details(profile_meta: ProfileMetadata) -> AuthDetailsAll:
14
13
  match profile_meta.auth_retrieval:
15
- case AuthRetrievalOAuth():
14
+ case auth_retrieval_t.AuthRetrievalOAuth():
16
15
  refresh_token = retrieve_secret(
17
16
  profile_meta.auth_retrieval.refresh_token_secret,
18
17
  profile_metadata=profile_meta,
19
18
  )
20
19
  return AuthDetailsOAuth(refresh_token=refresh_token)
21
- case AuthRetrievalBasic():
20
+ case auth_retrieval_t.AuthRetrievalBasic():
22
21
  api_id = retrieve_secret(
23
22
  profile_meta.auth_retrieval.api_id_secret, profile_metadata=profile_meta
24
23
  )
25
24
  api_key = retrieve_secret(
26
- profile_meta.auth_retrieval.api_key_secret, profile_metadata=profile_meta
25
+ profile_meta.auth_retrieval.api_key_secret,
26
+ profile_metadata=profile_meta,
27
27
  )
28
28
 
29
29
  return AuthDetailsApiKey(api_id=api_id, api_secret_key=api_key)
@@ -37,15 +37,15 @@ def _construct_client_config(
37
37
  return ClientConfig(
38
38
  allow_insecure_tls=profile_meta.client_options.allow_insecure_tls,
39
39
  extra_headers=profile_meta.client_options.extra_headers,
40
- job_logger=job_logger,
40
+ logger=job_logger,
41
41
  )
42
42
 
43
43
 
44
44
  def construct_uncountable_client(
45
- profile_meta: ProfileMetadata, job_logger: JobLogger
45
+ profile_meta: ProfileMetadata, logger: JobLogger
46
46
  ) -> Client:
47
47
  return Client(
48
48
  base_url=profile_meta.base_url,
49
49
  auth_details=_construct_auth_details(profile_meta),
50
- config=_construct_client_config(profile_meta, job_logger),
50
+ config=_construct_client_config(profile_meta, logger),
51
51
  )
@@ -1,11 +1,11 @@
1
1
  from dataclasses import dataclass
2
2
 
3
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
4
+ from uncountable.core.environment import get_local_admin_server_port
5
+ from uncountable.integration.queue_runner.command_server.command_client import (
6
+ send_job_queue_message,
7
+ )
8
+ from uncountable.types import queued_job_t
9
9
  from uncountable.types.job_definition_t import JobDefinition, ProfileMetadata
10
10
 
11
11
 
@@ -20,36 +20,10 @@ cron_args_parser = CachedParser(CronJobArgs)
20
20
 
21
21
  def cron_job_executor(**kwargs: dict) -> None:
22
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,
23
+ send_job_queue_message(
24
+ job_ref_name=args_passed.definition.id,
25
+ payload=queued_job_t.QueuedJobPayload(
26
+ invocation_context=queued_job_t.InvocationContextCron()
27
+ ),
28
+ port=get_local_admin_server_port(),
26
29
  )
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
- )
@@ -1,8 +1,18 @@
1
1
  import os
2
+ from enum import StrEnum
2
3
 
3
4
  from sqlalchemy import create_engine
4
5
  from sqlalchemy.engine.base import Engine
5
6
 
6
7
 
7
- def create_db_engine() -> Engine:
8
- return create_engine(os.environ["UNC_SQLITE_URI"])
8
+ class IntegrationDBService(StrEnum):
9
+ CRON = "cron"
10
+ RUNNER = "runner"
11
+
12
+
13
+ def create_db_engine(service: IntegrationDBService) -> Engine:
14
+ match service:
15
+ case IntegrationDBService.CRON:
16
+ return create_engine(os.environ["UNC_CRON_SQLITE_URI"])
17
+ case IntegrationDBService.RUNNER:
18
+ return create_engine(os.environ["UNC_RUNNER_SQLITE_URI"])
@@ -0,0 +1,25 @@
1
+ from contextlib import _GeneratorContextManager, contextmanager
2
+ from typing import Callable, Generator
3
+
4
+ from sqlalchemy.engine import Engine
5
+ from sqlalchemy.orm import Session, sessionmaker
6
+
7
+ DBSessionMaker = Callable[[], _GeneratorContextManager[Session]]
8
+
9
+
10
+ def get_session_maker(engine: Engine) -> DBSessionMaker:
11
+ session_maker = sessionmaker(bind=engine)
12
+
13
+ @contextmanager
14
+ def session_manager() -> Generator[Session, None, None]:
15
+ session = session_maker()
16
+ try:
17
+ yield session
18
+ session.commit()
19
+ except Exception:
20
+ session.rollback()
21
+ raise
22
+ finally:
23
+ session.close()
24
+
25
+ return session_manager
@@ -1,42 +1,13 @@
1
- import os
2
- from importlib import resources
3
-
4
- from pkgs.argument_parser import CachedParser
5
- from uncountable.integration.db.connect import create_db_engine
1
+ from uncountable.integration.db.connect import IntegrationDBService, create_db_engine
2
+ from uncountable.integration.scan_profiles import load_profiles
6
3
  from uncountable.integration.server import IntegrationServer
7
- from uncountable.types.job_definition_t import ProfileDefinition
8
-
9
- profile_parser = CachedParser(ProfileDefinition)
10
-
11
4
 
12
- def main(blocking: bool) -> None:
13
- profiles_module = os.environ["UNC_PROFILES_MODULE"]
14
- with IntegrationServer(create_db_engine()) as server:
15
- profiles = [
16
- entry
17
- for entry in resources.files(profiles_module).iterdir()
18
- if entry.is_dir()
19
- ]
20
- for profile_file in profiles:
21
- profile_name = profile_file.name
22
- try:
23
- profile = profile_parser.parse_yaml_resource(
24
- package=".".join([profiles_module, profile_name]),
25
- resource="profile.yaml",
26
- )
27
- except FileNotFoundError as e:
28
- print(f"WARN: profile.yaml not found for {profile_name}", e)
29
- continue
30
- server.register_profile(
31
- profile_name=profile_name,
32
- base_url=profile.base_url,
33
- auth_retrieval=profile.auth_retrieval,
34
- jobs=profile.jobs,
35
- client_options=profile.client_options,
36
- )
37
5
 
38
- if blocking:
39
- server.serve_forever()
6
+ def main() -> None:
7
+ with IntegrationServer(create_db_engine(IntegrationDBService.CRON)) as server:
8
+ server.register_jobs(load_profiles())
9
+ server.serve_forever()
40
10
 
41
11
 
42
- main(__name__ == "__main__")
12
+ if __name__ == "__main__":
13
+ main()
@@ -1,9 +1,18 @@
1
1
  from typing import assert_never
2
2
 
3
+ from uncountable.core.client import Client
3
4
  from uncountable.integration.executors.generic_upload_executor import GenericUploadJob
4
5
  from uncountable.integration.executors.script_executor import resolve_script_executor
5
- from uncountable.integration.job import Job
6
- from uncountable.types import job_definition_t
6
+ from uncountable.integration.job import Job, JobArguments
7
+ from uncountable.types import (
8
+ async_jobs_t,
9
+ entity_t,
10
+ field_values_t,
11
+ identifier_t,
12
+ integration_server_t,
13
+ job_definition_t,
14
+ transition_entity_phase_t,
15
+ )
7
16
 
8
17
 
9
18
  def resolve_executor(
@@ -22,3 +31,117 @@ def resolve_executor(
22
31
  data_source=job_executor.data_source,
23
32
  )
24
33
  assert_never(job_executor)
34
+
35
+
36
+ def _create_run_entity(
37
+ *,
38
+ client: Client,
39
+ logging_settings: job_definition_t.JobLoggingSettings,
40
+ job_uuid: str,
41
+ ) -> entity_t.Entity:
42
+ run_entity = client.create_entity(
43
+ entity_type=entity_t.EntityType.ASYNC_JOB,
44
+ definition_key=identifier_t.IdentifierKeyRefName(
45
+ ref_name="unc_integration_server_run_definition"
46
+ ),
47
+ field_values=[
48
+ field_values_t.FieldRefNameValue(
49
+ field_ref_name=async_jobs_t.ASYNC_JOB_TYPE_FIELD_REF_NAME,
50
+ value=async_jobs_t.AsyncJobType.INTEGRATION_SERVER_RUN,
51
+ ),
52
+ field_values_t.FieldRefNameValue(
53
+ field_ref_name=async_jobs_t.ASYNC_JOB_STATUS_FIELD_REF_NAME,
54
+ value=async_jobs_t.AsyncJobStatus.IN_PROGRESS,
55
+ ),
56
+ field_values_t.FieldRefNameValue(
57
+ field_ref_name=integration_server_t.INTEGRATION_SERVER_RUN_UUID_FIELD_REF_NAME,
58
+ value=job_uuid,
59
+ ),
60
+ ],
61
+ ).entity
62
+ client.transition_entity_phase(
63
+ entity=run_entity,
64
+ transition=transition_entity_phase_t.TransitionIdentifierPhases(
65
+ phase_from_key=identifier_t.IdentifierKeyRefName(
66
+ ref_name="unc_integration_server_run__queued"
67
+ ),
68
+ phase_to_key=identifier_t.IdentifierKeyRefName(
69
+ ref_name="unc_integration_server_run__started"
70
+ ),
71
+ ),
72
+ )
73
+ if logging_settings.share_with_user_groups is not None:
74
+ client.grant_entity_permissions(
75
+ entity_type=entity_t.EntityType.ASYNC_JOB,
76
+ entity_key=identifier_t.IdentifierKeyId(id=run_entity.id),
77
+ permission_types=[
78
+ entity_t.EntityPermissionType.READ,
79
+ entity_t.EntityPermissionType.WRITE,
80
+ ],
81
+ user_group_keys=logging_settings.share_with_user_groups,
82
+ )
83
+ return run_entity
84
+
85
+
86
+ def execute_job(
87
+ *,
88
+ job_definition: job_definition_t.JobDefinition,
89
+ profile_metadata: job_definition_t.ProfileMetadata,
90
+ args: JobArguments,
91
+ ) -> job_definition_t.JobResult:
92
+ with args.logger.push_scope(job_definition.name) as job_logger:
93
+ job = resolve_executor(job_definition.executor, profile_metadata)
94
+
95
+ job_logger.log_info("running job")
96
+
97
+ run_entity: entity_t.Entity | None = None
98
+ try:
99
+ if (
100
+ job_definition.logging_settings is not None
101
+ and job_definition.logging_settings.enabled
102
+ ):
103
+ run_entity = _create_run_entity(
104
+ client=args.client,
105
+ logging_settings=job_definition.logging_settings,
106
+ job_uuid=args.job_uuid,
107
+ )
108
+ result = job.run_outer(args=args)
109
+ except Exception as e:
110
+ job_logger.log_exception(e)
111
+ if run_entity is not None:
112
+ args.client.set_values(
113
+ entity=run_entity,
114
+ values=[
115
+ field_values_t.ArgumentValueRefName(
116
+ field_ref_name=async_jobs_t.ASYNC_JOB_STATUS_FIELD_REF_NAME,
117
+ value=async_jobs_t.AsyncJobStatus.ERROR,
118
+ ),
119
+ ],
120
+ )
121
+ return job_definition_t.JobResult(success=False)
122
+
123
+ if args.batch_processor.current_queue_size() != 0:
124
+ args.batch_processor.send()
125
+
126
+ submitted_batch_job_ids = args.batch_processor.get_submitted_job_ids()
127
+ job_logger.log_info(
128
+ "completed job",
129
+ attributes={
130
+ "submitted_batch_job_ids": submitted_batch_job_ids,
131
+ "success": result.success,
132
+ },
133
+ )
134
+ if run_entity is not None:
135
+ args.client.set_values(
136
+ entity=run_entity,
137
+ values=[
138
+ field_values_t.ArgumentValueRefName(
139
+ field_ref_name=async_jobs_t.ASYNC_JOB_STATUS_FIELD_REF_NAME,
140
+ value=async_jobs_t.AsyncJobStatus.COMPLETED
141
+ if result.success
142
+ else async_jobs_t.AsyncJobStatus.ERROR,
143
+ ),
144
+ ],
145
+ )
146
+
147
+ return result