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,264 @@
1
+ # mypy: disable-error-code="no-untyped-def"
2
+ # ruff: noqa
3
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
4
+ """Client and server classes corresponding to protobuf-defined services."""
5
+
6
+ import grpc
7
+
8
+ from uncountable.integration.queue_runner.command_server.protocol import (
9
+ command_server_pb2 as uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2,
10
+ )
11
+
12
+
13
+ class CommandServerStub(object):
14
+ """Missing associated documentation comment in .proto file."""
15
+
16
+ def __init__(self, channel):
17
+ """Constructor.
18
+
19
+ Args:
20
+ channel: A grpc.Channel.
21
+ """
22
+ self.EnqueueJob = channel.unary_unary(
23
+ "/CommandServer/EnqueueJob",
24
+ request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobRequest.SerializeToString,
25
+ response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobResult.FromString,
26
+ )
27
+ self.RetryJob = channel.unary_unary(
28
+ "/CommandServer/RetryJob",
29
+ request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobRequest.SerializeToString,
30
+ response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobResult.FromString,
31
+ )
32
+ self.CheckHealth = channel.unary_unary(
33
+ "/CommandServer/CheckHealth",
34
+ request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthRequest.SerializeToString,
35
+ response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthResult.FromString,
36
+ )
37
+ self.ListQueuedJobs = channel.unary_unary(
38
+ "/CommandServer/ListQueuedJobs",
39
+ request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsRequest.SerializeToString,
40
+ response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsResult.FromString,
41
+ )
42
+ self.VaccuumQueuedJobs = channel.unary_unary(
43
+ "/CommandServer/VaccuumQueuedJobs",
44
+ request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsRequest.SerializeToString,
45
+ response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsResult.FromString,
46
+ )
47
+
48
+
49
+ class CommandServerServicer(object):
50
+ """Missing associated documentation comment in .proto file."""
51
+
52
+ def EnqueueJob(self, request, context):
53
+ """Missing associated documentation comment in .proto file."""
54
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
55
+ context.set_details("Method not implemented!")
56
+ raise NotImplementedError("Method not implemented!")
57
+
58
+ def RetryJob(self, request, context):
59
+ """Missing associated documentation comment in .proto file."""
60
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
61
+ context.set_details("Method not implemented!")
62
+ raise NotImplementedError("Method not implemented!")
63
+
64
+ def CheckHealth(self, request, context):
65
+ """Missing associated documentation comment in .proto file."""
66
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
67
+ context.set_details("Method not implemented!")
68
+ raise NotImplementedError("Method not implemented!")
69
+
70
+ def ListQueuedJobs(self, request, context):
71
+ """Missing associated documentation comment in .proto file."""
72
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
73
+ context.set_details("Method not implemented!")
74
+ raise NotImplementedError("Method not implemented!")
75
+
76
+ def VaccuumQueuedJobs(self, request, context):
77
+ """Missing associated documentation comment in .proto file."""
78
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
79
+ context.set_details("Method not implemented!")
80
+ raise NotImplementedError("Method not implemented!")
81
+
82
+
83
+ def add_CommandServerServicer_to_server(servicer, server):
84
+ rpc_method_handlers = {
85
+ "EnqueueJob": grpc.unary_unary_rpc_method_handler(
86
+ servicer.EnqueueJob,
87
+ request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobRequest.FromString,
88
+ response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobResult.SerializeToString,
89
+ ),
90
+ "RetryJob": grpc.unary_unary_rpc_method_handler(
91
+ servicer.RetryJob,
92
+ request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobRequest.FromString,
93
+ response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobResult.SerializeToString,
94
+ ),
95
+ "CheckHealth": grpc.unary_unary_rpc_method_handler(
96
+ servicer.CheckHealth,
97
+ request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthRequest.FromString,
98
+ response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthResult.SerializeToString,
99
+ ),
100
+ "ListQueuedJobs": grpc.unary_unary_rpc_method_handler(
101
+ servicer.ListQueuedJobs,
102
+ request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsRequest.FromString,
103
+ response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsResult.SerializeToString,
104
+ ),
105
+ "VaccuumQueuedJobs": grpc.unary_unary_rpc_method_handler(
106
+ servicer.VaccuumQueuedJobs,
107
+ request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsRequest.FromString,
108
+ response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsResult.SerializeToString,
109
+ ),
110
+ }
111
+ generic_handler = grpc.method_handlers_generic_handler(
112
+ "CommandServer", rpc_method_handlers
113
+ )
114
+ server.add_generic_rpc_handlers((generic_handler,))
115
+
116
+
117
+ # This class is part of an EXPERIMENTAL API.
118
+ class CommandServer(object):
119
+ """Missing associated documentation comment in .proto file."""
120
+
121
+ @staticmethod
122
+ def EnqueueJob(
123
+ request,
124
+ target,
125
+ options=(),
126
+ channel_credentials=None,
127
+ call_credentials=None,
128
+ insecure=False,
129
+ compression=None,
130
+ wait_for_ready=None,
131
+ timeout=None,
132
+ metadata=None,
133
+ ):
134
+ return grpc.experimental.unary_unary(
135
+ request,
136
+ target,
137
+ "/CommandServer/EnqueueJob",
138
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobRequest.SerializeToString,
139
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobResult.FromString,
140
+ options,
141
+ channel_credentials,
142
+ insecure,
143
+ call_credentials,
144
+ compression,
145
+ wait_for_ready,
146
+ timeout,
147
+ metadata,
148
+ )
149
+
150
+ @staticmethod
151
+ def RetryJob(
152
+ request,
153
+ target,
154
+ options=(),
155
+ channel_credentials=None,
156
+ call_credentials=None,
157
+ insecure=False,
158
+ compression=None,
159
+ wait_for_ready=None,
160
+ timeout=None,
161
+ metadata=None,
162
+ ):
163
+ return grpc.experimental.unary_unary(
164
+ request,
165
+ target,
166
+ "/CommandServer/RetryJob",
167
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobRequest.SerializeToString,
168
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobResult.FromString,
169
+ options,
170
+ channel_credentials,
171
+ insecure,
172
+ call_credentials,
173
+ compression,
174
+ wait_for_ready,
175
+ timeout,
176
+ metadata,
177
+ )
178
+
179
+ @staticmethod
180
+ def CheckHealth(
181
+ request,
182
+ target,
183
+ options=(),
184
+ channel_credentials=None,
185
+ call_credentials=None,
186
+ insecure=False,
187
+ compression=None,
188
+ wait_for_ready=None,
189
+ timeout=None,
190
+ metadata=None,
191
+ ):
192
+ return grpc.experimental.unary_unary(
193
+ request,
194
+ target,
195
+ "/CommandServer/CheckHealth",
196
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthRequest.SerializeToString,
197
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthResult.FromString,
198
+ options,
199
+ channel_credentials,
200
+ insecure,
201
+ call_credentials,
202
+ compression,
203
+ wait_for_ready,
204
+ timeout,
205
+ metadata,
206
+ )
207
+
208
+ @staticmethod
209
+ def ListQueuedJobs(
210
+ request,
211
+ target,
212
+ options=(),
213
+ channel_credentials=None,
214
+ call_credentials=None,
215
+ insecure=False,
216
+ compression=None,
217
+ wait_for_ready=None,
218
+ timeout=None,
219
+ metadata=None,
220
+ ):
221
+ return grpc.experimental.unary_unary(
222
+ request,
223
+ target,
224
+ "/CommandServer/ListQueuedJobs",
225
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsRequest.SerializeToString,
226
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsResult.FromString,
227
+ options,
228
+ channel_credentials,
229
+ insecure,
230
+ call_credentials,
231
+ compression,
232
+ wait_for_ready,
233
+ timeout,
234
+ metadata,
235
+ )
236
+
237
+ @staticmethod
238
+ def VaccuumQueuedJobs(
239
+ request,
240
+ target,
241
+ options=(),
242
+ channel_credentials=None,
243
+ call_credentials=None,
244
+ insecure=False,
245
+ compression=None,
246
+ wait_for_ready=None,
247
+ timeout=None,
248
+ metadata=None,
249
+ ):
250
+ return grpc.experimental.unary_unary(
251
+ request,
252
+ target,
253
+ "/CommandServer/VaccuumQueuedJobs",
254
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsRequest.SerializeToString,
255
+ uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsResult.FromString,
256
+ options,
257
+ channel_credentials,
258
+ insecure,
259
+ call_credentials,
260
+ compression,
261
+ wait_for_ready,
262
+ timeout,
263
+ metadata,
264
+ )
@@ -0,0 +1,75 @@
1
+ import asyncio
2
+ import typing
3
+ from dataclasses import dataclass
4
+ from enum import StrEnum
5
+
6
+ from uncountable.types import queued_job_t
7
+
8
+
9
+ class CommandType(StrEnum):
10
+ ENQUEUE_JOB = "enqueue_job"
11
+ RETRY_JOB = "retry_job"
12
+ VACCUUM_QUEUED_JOBS = "vaccuum_queued_jobs"
13
+
14
+
15
+ RT = typing.TypeVar("RT")
16
+
17
+
18
+ @dataclass(kw_only=True)
19
+ class CommandBase[RT]:
20
+ type: CommandType
21
+ response_queue: asyncio.Queue[RT]
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class CommandEnqueueJobResponse:
26
+ queued_job_uuid: str
27
+
28
+
29
+ @dataclass(kw_only=True)
30
+ class CommandRetryJobResponse:
31
+ queued_job_uuid: str | None
32
+
33
+
34
+ @dataclass(kw_only=True)
35
+ class CommandVaccuumQueuedJobsResponse:
36
+ pass
37
+
38
+
39
+ @dataclass(kw_only=True)
40
+ class CommandEnqueueJob(CommandBase[CommandEnqueueJobResponse]):
41
+ type: CommandType = CommandType.ENQUEUE_JOB
42
+ job_ref_name: str
43
+ payload: queued_job_t.QueuedJobPayload
44
+ response_queue: asyncio.Queue[CommandEnqueueJobResponse]
45
+
46
+
47
+ @dataclass(kw_only=True)
48
+ class CommandRetryJob(CommandBase[CommandRetryJobResponse]):
49
+ type: CommandType = CommandType.RETRY_JOB
50
+ queued_job_uuid: str
51
+
52
+
53
+ @dataclass(kw_only=True)
54
+ class CommandVaccuumQueuedJobs(CommandBase[CommandVaccuumQueuedJobsResponse]):
55
+ type: CommandType = CommandType.VACCUUM_QUEUED_JOBS
56
+
57
+
58
+ _Command = CommandEnqueueJob | CommandRetryJob | CommandVaccuumQueuedJobs
59
+
60
+
61
+ CommandQueue = asyncio.Queue[_Command]
62
+
63
+ CommandTask = asyncio.Task[_Command]
64
+
65
+
66
+ class CommandServerException(Exception):
67
+ pass
68
+
69
+
70
+ class CommandServerTimeout(CommandServerException):
71
+ pass
72
+
73
+
74
+ class CommandServerBadResponse(CommandServerException):
75
+ pass
@@ -0,0 +1,3 @@
1
+ from .datastore_sqlite import DatastoreSqlite
2
+
3
+ __all__: list[str] = ["DatastoreSqlite"]
@@ -0,0 +1,250 @@
1
+ import datetime
2
+ import uuid
3
+ from datetime import UTC
4
+
5
+ from sqlalchemy import delete, insert, or_, select, text, update
6
+ from sqlalchemy.engine import Engine
7
+
8
+ from pkgs.argument_parser import CachedParser
9
+ from pkgs.serialization_util import serialize_for_storage
10
+ from uncountable.integration.db.session import DBSessionMaker
11
+ from uncountable.integration.queue_runner.datastore.interface import Datastore
12
+ from uncountable.integration.queue_runner.datastore.model import Base, QueuedJob
13
+ from uncountable.types import queued_job_t
14
+
15
+ queued_job_payload_parser = CachedParser(queued_job_t.QueuedJobPayload)
16
+
17
+ MAX_QUEUE_WINDOW_DAYS = 30
18
+
19
+
20
+ class DatastoreSqlite(Datastore):
21
+ def __init__(self, session_maker: DBSessionMaker) -> None:
22
+ self.session_maker = session_maker
23
+ super().__init__()
24
+
25
+ @classmethod
26
+ def setup(cls, engine: Engine) -> None:
27
+ Base.metadata.create_all(engine)
28
+ with engine.connect() as connection:
29
+ if not bool(
30
+ connection.execute(
31
+ text(
32
+ "select exists (select 1 from pragma_table_info('queued_jobs') where name='status');"
33
+ )
34
+ ).scalar()
35
+ ):
36
+ connection.execute(
37
+ text("alter table queued_jobs add column status VARCHAR")
38
+ )
39
+
40
+ def add_job_to_queue(
41
+ self, job_payload: queued_job_t.QueuedJobPayload, job_ref_name: str
42
+ ) -> queued_job_t.QueuedJob:
43
+ with self.session_maker() as session:
44
+ serialized_payload = serialize_for_storage(job_payload)
45
+ queued_job_uuid = str(uuid.uuid4())
46
+ num_attempts = 0
47
+ submitted_at = datetime.datetime.now(UTC)
48
+ insert_stmt = insert(QueuedJob).values({
49
+ QueuedJob.id.key: queued_job_uuid,
50
+ QueuedJob.job_ref_name.key: job_ref_name,
51
+ QueuedJob.payload.key: serialized_payload,
52
+ QueuedJob.status.key: queued_job_t.JobStatus.QUEUED,
53
+ QueuedJob.num_attempts: num_attempts,
54
+ QueuedJob.submitted_at: submitted_at,
55
+ })
56
+ session.execute(insert_stmt)
57
+ return queued_job_t.QueuedJob(
58
+ queued_job_uuid=queued_job_uuid,
59
+ job_ref_name=job_ref_name,
60
+ payload=job_payload,
61
+ status=queued_job_t.JobStatus.QUEUED,
62
+ submitted_at=submitted_at,
63
+ num_attempts=num_attempts,
64
+ )
65
+
66
+ def retry_job(
67
+ self,
68
+ queued_job_uuid: str,
69
+ ) -> queued_job_t.QueuedJob | None:
70
+ with self.session_maker() as session:
71
+ select_stmt = select(
72
+ QueuedJob.id,
73
+ QueuedJob.payload,
74
+ QueuedJob.num_attempts,
75
+ QueuedJob.job_ref_name,
76
+ QueuedJob.status,
77
+ QueuedJob.submitted_at,
78
+ ).filter(QueuedJob.id == queued_job_uuid)
79
+ existing_job = session.execute(select_stmt).one_or_none()
80
+
81
+ if (
82
+ existing_job is None
83
+ or existing_job.status != queued_job_t.JobStatus.FAILED
84
+ ):
85
+ return None
86
+
87
+ update_stmt = (
88
+ update(QueuedJob)
89
+ .values({QueuedJob.status.key: queued_job_t.JobStatus.QUEUED})
90
+ .filter(QueuedJob.id == queued_job_uuid)
91
+ )
92
+ session.execute(update_stmt)
93
+
94
+ return queued_job_t.QueuedJob(
95
+ queued_job_uuid=existing_job.id,
96
+ job_ref_name=existing_job.job_ref_name,
97
+ num_attempts=existing_job.num_attempts,
98
+ status=queued_job_t.JobStatus.QUEUED,
99
+ submitted_at=existing_job.submitted_at,
100
+ payload=queued_job_payload_parser.parse_storage(existing_job.payload),
101
+ )
102
+
103
+ def increment_num_attempts(self, queued_job_uuid: str) -> int:
104
+ with self.session_maker() as session:
105
+ update_stmt = (
106
+ update(QueuedJob)
107
+ .values({QueuedJob.num_attempts.key: QueuedJob.num_attempts + 1})
108
+ .filter(QueuedJob.id == queued_job_uuid)
109
+ )
110
+ session.execute(update_stmt)
111
+ session.flush()
112
+ # IMPROVE: python3's sqlite does not support the RETURNING clause
113
+ select_stmt = select(QueuedJob.num_attempts).filter(
114
+ QueuedJob.id == queued_job_uuid
115
+ )
116
+ return int(session.execute(select_stmt).one().num_attempts)
117
+
118
+ def remove_job_from_queue(self, queued_job_uuid: str) -> None:
119
+ with self.session_maker() as session:
120
+ delete_stmt = delete(QueuedJob).filter(QueuedJob.id == queued_job_uuid)
121
+ session.execute(delete_stmt)
122
+
123
+ def update_job_status(
124
+ self, queued_job_uuid: str, status: queued_job_t.JobStatus
125
+ ) -> None:
126
+ with self.session_maker() as session:
127
+ update_stmt = (
128
+ update(QueuedJob)
129
+ .values({QueuedJob.status.key: status})
130
+ .filter(QueuedJob.id == queued_job_uuid)
131
+ )
132
+ session.execute(update_stmt)
133
+
134
+ def list_queued_job_metadata(
135
+ self, offset: int = 0, limit: int | None = 100
136
+ ) -> list[queued_job_t.QueuedJobMetadata]:
137
+ with self.session_maker() as session:
138
+ select_statement = (
139
+ select(
140
+ QueuedJob.id,
141
+ QueuedJob.job_ref_name,
142
+ QueuedJob.num_attempts,
143
+ QueuedJob.status,
144
+ QueuedJob.submitted_at,
145
+ )
146
+ .order_by(QueuedJob.submitted_at)
147
+ .offset(offset)
148
+ .limit(limit)
149
+ )
150
+
151
+ queued_job_metadata: list[queued_job_t.QueuedJobMetadata] = [
152
+ queued_job_t.QueuedJobMetadata(
153
+ queued_job_uuid=row.id,
154
+ job_ref_name=row.job_ref_name,
155
+ num_attempts=row.num_attempts,
156
+ status=row.status or queued_job_t.JobStatus.QUEUED,
157
+ submitted_at=row.submitted_at,
158
+ )
159
+ for row in session.execute(select_statement)
160
+ ]
161
+
162
+ return queued_job_metadata
163
+
164
+ def get_next_queued_job_for_ref_name(
165
+ self, job_ref_name: str
166
+ ) -> queued_job_t.QueuedJob | None:
167
+ with self.session_maker() as session:
168
+ select_stmt = (
169
+ select(
170
+ QueuedJob.id,
171
+ QueuedJob.payload,
172
+ QueuedJob.num_attempts,
173
+ QueuedJob.job_ref_name,
174
+ QueuedJob.status,
175
+ QueuedJob.submitted_at,
176
+ )
177
+ .filter(QueuedJob.job_ref_name == job_ref_name)
178
+ .filter(
179
+ or_(
180
+ QueuedJob.status == queued_job_t.JobStatus.QUEUED,
181
+ QueuedJob.status.is_(None),
182
+ )
183
+ )
184
+ .limit(1)
185
+ .order_by(QueuedJob.submitted_at)
186
+ )
187
+
188
+ for row in session.execute(select_stmt):
189
+ parsed_payload = queued_job_payload_parser.parse_storage(row.payload)
190
+ return queued_job_t.QueuedJob(
191
+ queued_job_uuid=row.id,
192
+ job_ref_name=row.job_ref_name,
193
+ num_attempts=row.num_attempts,
194
+ status=row.status or queued_job_t.JobStatus.QUEUED,
195
+ submitted_at=row.submitted_at,
196
+ payload=parsed_payload,
197
+ )
198
+
199
+ return None
200
+
201
+ def load_job_queue(self) -> list[queued_job_t.QueuedJob]:
202
+ with self.session_maker() as session:
203
+ select_stmt = (
204
+ select(
205
+ QueuedJob.id,
206
+ QueuedJob.payload,
207
+ QueuedJob.num_attempts,
208
+ QueuedJob.job_ref_name,
209
+ QueuedJob.status,
210
+ QueuedJob.submitted_at,
211
+ )
212
+ .filter(
213
+ or_(
214
+ QueuedJob.status == queued_job_t.JobStatus.QUEUED,
215
+ QueuedJob.status.is_(None),
216
+ )
217
+ )
218
+ .order_by(QueuedJob.submitted_at)
219
+ )
220
+
221
+ queued_jobs: list[queued_job_t.QueuedJob] = []
222
+ for row in session.execute(select_stmt):
223
+ parsed_payload = queued_job_payload_parser.parse_storage(row.payload)
224
+ queued_jobs.append(
225
+ queued_job_t.QueuedJob(
226
+ queued_job_uuid=row.id,
227
+ job_ref_name=row.job_ref_name,
228
+ num_attempts=row.num_attempts,
229
+ status=row.status or queued_job_t.JobStatus.QUEUED,
230
+ submitted_at=row.submitted_at,
231
+ payload=parsed_payload,
232
+ )
233
+ )
234
+
235
+ return queued_jobs
236
+
237
+ def vaccuum_queued_jobs(self) -> None:
238
+ with self.session_maker() as session:
239
+ delete_stmt = (
240
+ delete(QueuedJob)
241
+ .filter(QueuedJob.status == queued_job_t.JobStatus.QUEUED)
242
+ .filter(
243
+ QueuedJob.submitted_at
244
+ <= (
245
+ datetime.datetime.now(UTC)
246
+ - datetime.timedelta(days=MAX_QUEUE_WINDOW_DAYS)
247
+ )
248
+ )
249
+ )
250
+ session.execute(delete_stmt)
@@ -0,0 +1,29 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from uncountable.types import queued_job_t
4
+
5
+
6
+ class Datastore(ABC):
7
+ @abstractmethod
8
+ def add_job_to_queue(
9
+ self, job_payload: queued_job_t.QueuedJobPayload, job_ref_name: str
10
+ ) -> queued_job_t.QueuedJob: ...
11
+
12
+ @abstractmethod
13
+ def remove_job_from_queue(self, queued_job_uuid: str) -> None: ...
14
+
15
+ @abstractmethod
16
+ def increment_num_attempts(self, queued_job_uuid: str) -> int: ...
17
+
18
+ @abstractmethod
19
+ def load_job_queue(self) -> list[queued_job_t.QueuedJob]: ...
20
+
21
+ @abstractmethod
22
+ def get_next_queued_job_for_ref_name(
23
+ self, job_ref_name: str
24
+ ) -> queued_job_t.QueuedJob | None: ...
25
+
26
+ @abstractmethod
27
+ def list_queued_job_metadata(
28
+ self, offset: int, limit: int | None
29
+ ) -> list[queued_job_t.QueuedJobMetadata]: ...
@@ -0,0 +1,24 @@
1
+ from sqlalchemy import JSON, BigInteger, Column, DateTime, Enum, Text
2
+ from sqlalchemy.orm import declarative_base
3
+ from sqlalchemy.sql import func
4
+
5
+ from uncountable.types import queued_job_t
6
+
7
+ Base = declarative_base()
8
+
9
+
10
+ class QueuedJob(Base):
11
+ __tablename__ = "queued_jobs"
12
+
13
+ id = Column(Text, primary_key=True)
14
+ job_ref_name = Column(Text, nullable=False, index=True)
15
+ submitted_at = Column(
16
+ DateTime(timezone=True), server_default=func.current_timestamp(), nullable=False
17
+ )
18
+ payload = Column(JSON, nullable=False)
19
+ num_attempts = Column(BigInteger, nullable=False, default=0, server_default="0")
20
+ status = Column(
21
+ Enum(queued_job_t.JobStatus, length=None),
22
+ default=queued_job_t.JobStatus.QUEUED,
23
+ nullable=True,
24
+ )