UncountablePythonSDK 0.0.115__py3-none-any.whl → 0.0.142.dev0__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.
- docs/conf.py +52 -5
- docs/index.md +107 -4
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/justfile +1 -1
- docs/requirements.txt +3 -2
- examples/basic_auth.py +7 -0
- examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
- examples/integration-server/jobs/materials_auto/example_http.py +19 -7
- examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
- examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
- examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +57 -16
- examples/integration-server/jobs/materials_auto/profile.yaml +27 -0
- examples/integration-server/pyproject.toml +4 -4
- examples/oauth.py +7 -0
- pkgs/argument_parser/__init__.py +1 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/argument_parser/argument_parser.py +22 -3
- pkgs/serialization_util/serialization_helpers.py +3 -1
- pkgs/type_spec/builder.py +66 -19
- pkgs/type_spec/builder_types.py +9 -0
- pkgs/type_spec/config.py +26 -5
- pkgs/type_spec/cross_output_links.py +10 -16
- pkgs/type_spec/emit_open_api.py +72 -22
- pkgs/type_spec/emit_open_api_util.py +1 -0
- pkgs/type_spec/emit_python.py +76 -12
- pkgs/type_spec/emit_typescript.py +48 -32
- pkgs/type_spec/emit_typescript_util.py +44 -6
- pkgs/type_spec/load_types.py +2 -2
- pkgs/type_spec/open_api_util.py +16 -1
- pkgs/type_spec/parts/base.ts.prepart +4 -0
- pkgs/type_spec/type_info/emit_type_info.py +37 -4
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +1 -0
- pkgs/type_spec/value_spec/__main__.py +2 -2
- pkgs/type_spec/value_spec/emit_python.py +6 -1
- uncountable/core/client.py +10 -3
- uncountable/integration/cli.py +175 -23
- uncountable/integration/executors/executors.py +1 -2
- uncountable/integration/executors/generic_upload_executor.py +1 -1
- uncountable/integration/http_server/types.py +3 -1
- uncountable/integration/job.py +35 -3
- uncountable/integration/queue_runner/command_server/__init__.py +4 -0
- uncountable/integration/queue_runner/command_server/command_client.py +89 -0
- uncountable/integration/queue_runner/command_server/command_server.py +117 -5
- uncountable/integration/queue_runner/command_server/constants.py +4 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +51 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +34 -11
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +102 -1
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +180 -0
- uncountable/integration/queue_runner/command_server/types.py +44 -1
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +189 -8
- uncountable/integration/queue_runner/datastore/interface.py +13 -0
- uncountable/integration/queue_runner/datastore/model.py +8 -1
- uncountable/integration/queue_runner/job_scheduler.py +85 -21
- uncountable/integration/queue_runner/queue_runner.py +10 -2
- uncountable/integration/queue_runner/types.py +2 -0
- uncountable/integration/queue_runner/worker.py +28 -29
- uncountable/integration/scheduler.py +121 -23
- uncountable/integration/server.py +36 -6
- uncountable/integration/telemetry.py +129 -8
- uncountable/integration/webhook_server/entrypoint.py +2 -0
- uncountable/types/__init__.py +38 -0
- uncountable/types/api/entity/create_or_update_entity.py +1 -0
- uncountable/types/api/entity/export_entities.py +13 -0
- uncountable/types/api/entity/list_aggregate.py +79 -0
- uncountable/types/api/entity/list_entities.py +25 -0
- uncountable/types/api/entity/set_barcode.py +43 -0
- uncountable/types/api/entity/transition_entity_phase.py +2 -1
- uncountable/types/api/files/download_file.py +15 -1
- uncountable/types/api/integrations/__init__.py +1 -0
- uncountable/types/api/integrations/publish_realtime_data.py +41 -0
- uncountable/types/api/integrations/push_notification.py +49 -0
- uncountable/types/api/integrations/register_sockets_token.py +41 -0
- uncountable/types/api/listing/__init__.py +1 -0
- uncountable/types/api/listing/fetch_listing.py +57 -0
- uncountable/types/api/notebooks/__init__.py +1 -0
- uncountable/types/api/notebooks/add_notebook_content.py +119 -0
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/api/recipes/edit_recipe_inputs.py +1 -1
- uncountable/types/api/recipes/get_recipe_output_metadata.py +2 -2
- uncountable/types/api/recipes/get_recipes_data.py +29 -0
- uncountable/types/api/recipes/lock_recipes.py +2 -1
- uncountable/types/api/recipes/set_recipe_total.py +59 -0
- uncountable/types/api/recipes/unlock_recipes.py +2 -1
- uncountable/types/api/runsheet/export_default_runsheet.py +44 -0
- uncountable/types/api/uploader/complete_async_parse.py +46 -0
- uncountable/types/api/user/__init__.py +1 -0
- uncountable/types/api/user/get_current_user_info.py +40 -0
- uncountable/types/async_batch_processor.py +266 -0
- uncountable/types/async_batch_t.py +5 -0
- uncountable/types/client_base.py +432 -2
- uncountable/types/client_config.py +1 -0
- uncountable/types/client_config_t.py +10 -0
- uncountable/types/entity_t.py +9 -1
- uncountable/types/exports_t.py +1 -0
- uncountable/types/integration_server_t.py +2 -0
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/integrations.py +10 -0
- uncountable/types/integrations_t.py +62 -0
- uncountable/types/listing.py +46 -0
- uncountable/types/listing_t.py +533 -0
- uncountable/types/notices.py +8 -0
- uncountable/types/notices_t.py +37 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/queued_job.py +2 -0
- uncountable/types/queued_job_t.py +20 -2
- uncountable/types/sockets.py +20 -0
- uncountable/types/sockets_t.py +169 -0
- uncountable/types/uploader.py +24 -0
- uncountable/types/uploader_t.py +222 -0
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/METADATA +5 -2
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/RECORD +118 -79
- docs/quickstart.md +0 -19
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/top_level.txt +0 -0
|
@@ -24,11 +24,31 @@ class CommandServerStub(object):
|
|
|
24
24
|
request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobRequest.SerializeToString,
|
|
25
25
|
response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobResult.FromString,
|
|
26
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
|
+
)
|
|
27
32
|
self.CheckHealth = channel.unary_unary(
|
|
28
33
|
"/CommandServer/CheckHealth",
|
|
29
34
|
request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthRequest.SerializeToString,
|
|
30
35
|
response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthResult.FromString,
|
|
31
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
|
+
self.CancelJob = channel.unary_unary(
|
|
48
|
+
"/CommandServer/CancelJob",
|
|
49
|
+
request_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobRequest.SerializeToString,
|
|
50
|
+
response_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobResult.FromString,
|
|
51
|
+
)
|
|
32
52
|
|
|
33
53
|
|
|
34
54
|
class CommandServerServicer(object):
|
|
@@ -40,12 +60,36 @@ class CommandServerServicer(object):
|
|
|
40
60
|
context.set_details("Method not implemented!")
|
|
41
61
|
raise NotImplementedError("Method not implemented!")
|
|
42
62
|
|
|
63
|
+
def RetryJob(self, request, context):
|
|
64
|
+
"""Missing associated documentation comment in .proto file."""
|
|
65
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
66
|
+
context.set_details("Method not implemented!")
|
|
67
|
+
raise NotImplementedError("Method not implemented!")
|
|
68
|
+
|
|
43
69
|
def CheckHealth(self, request, context):
|
|
44
70
|
"""Missing associated documentation comment in .proto file."""
|
|
45
71
|
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
46
72
|
context.set_details("Method not implemented!")
|
|
47
73
|
raise NotImplementedError("Method not implemented!")
|
|
48
74
|
|
|
75
|
+
def ListQueuedJobs(self, request, context):
|
|
76
|
+
"""Missing associated documentation comment in .proto file."""
|
|
77
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
78
|
+
context.set_details("Method not implemented!")
|
|
79
|
+
raise NotImplementedError("Method not implemented!")
|
|
80
|
+
|
|
81
|
+
def VaccuumQueuedJobs(self, request, context):
|
|
82
|
+
"""Missing associated documentation comment in .proto file."""
|
|
83
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
84
|
+
context.set_details("Method not implemented!")
|
|
85
|
+
raise NotImplementedError("Method not implemented!")
|
|
86
|
+
|
|
87
|
+
def CancelJob(self, request, context):
|
|
88
|
+
"""Missing associated documentation comment in .proto file."""
|
|
89
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
90
|
+
context.set_details("Method not implemented!")
|
|
91
|
+
raise NotImplementedError("Method not implemented!")
|
|
92
|
+
|
|
49
93
|
|
|
50
94
|
def add_CommandServerServicer_to_server(servicer, server):
|
|
51
95
|
rpc_method_handlers = {
|
|
@@ -54,11 +98,31 @@ def add_CommandServerServicer_to_server(servicer, server):
|
|
|
54
98
|
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobRequest.FromString,
|
|
55
99
|
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.EnqueueJobResult.SerializeToString,
|
|
56
100
|
),
|
|
101
|
+
"RetryJob": grpc.unary_unary_rpc_method_handler(
|
|
102
|
+
servicer.RetryJob,
|
|
103
|
+
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobRequest.FromString,
|
|
104
|
+
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobResult.SerializeToString,
|
|
105
|
+
),
|
|
57
106
|
"CheckHealth": grpc.unary_unary_rpc_method_handler(
|
|
58
107
|
servicer.CheckHealth,
|
|
59
108
|
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthRequest.FromString,
|
|
60
109
|
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CheckHealthResult.SerializeToString,
|
|
61
110
|
),
|
|
111
|
+
"ListQueuedJobs": grpc.unary_unary_rpc_method_handler(
|
|
112
|
+
servicer.ListQueuedJobs,
|
|
113
|
+
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsRequest.FromString,
|
|
114
|
+
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsResult.SerializeToString,
|
|
115
|
+
),
|
|
116
|
+
"VaccuumQueuedJobs": grpc.unary_unary_rpc_method_handler(
|
|
117
|
+
servicer.VaccuumQueuedJobs,
|
|
118
|
+
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsRequest.FromString,
|
|
119
|
+
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsResult.SerializeToString,
|
|
120
|
+
),
|
|
121
|
+
"CancelJob": grpc.unary_unary_rpc_method_handler(
|
|
122
|
+
servicer.CancelJob,
|
|
123
|
+
request_deserializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobRequest.FromString,
|
|
124
|
+
response_serializer=uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobResult.SerializeToString,
|
|
125
|
+
),
|
|
62
126
|
}
|
|
63
127
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
64
128
|
"CommandServer", rpc_method_handlers
|
|
@@ -99,6 +163,35 @@ class CommandServer(object):
|
|
|
99
163
|
metadata,
|
|
100
164
|
)
|
|
101
165
|
|
|
166
|
+
@staticmethod
|
|
167
|
+
def RetryJob(
|
|
168
|
+
request,
|
|
169
|
+
target,
|
|
170
|
+
options=(),
|
|
171
|
+
channel_credentials=None,
|
|
172
|
+
call_credentials=None,
|
|
173
|
+
insecure=False,
|
|
174
|
+
compression=None,
|
|
175
|
+
wait_for_ready=None,
|
|
176
|
+
timeout=None,
|
|
177
|
+
metadata=None,
|
|
178
|
+
):
|
|
179
|
+
return grpc.experimental.unary_unary(
|
|
180
|
+
request,
|
|
181
|
+
target,
|
|
182
|
+
"/CommandServer/RetryJob",
|
|
183
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobRequest.SerializeToString,
|
|
184
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.RetryJobResult.FromString,
|
|
185
|
+
options,
|
|
186
|
+
channel_credentials,
|
|
187
|
+
insecure,
|
|
188
|
+
call_credentials,
|
|
189
|
+
compression,
|
|
190
|
+
wait_for_ready,
|
|
191
|
+
timeout,
|
|
192
|
+
metadata,
|
|
193
|
+
)
|
|
194
|
+
|
|
102
195
|
@staticmethod
|
|
103
196
|
def CheckHealth(
|
|
104
197
|
request,
|
|
@@ -127,3 +220,90 @@ class CommandServer(object):
|
|
|
127
220
|
timeout,
|
|
128
221
|
metadata,
|
|
129
222
|
)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def ListQueuedJobs(
|
|
226
|
+
request,
|
|
227
|
+
target,
|
|
228
|
+
options=(),
|
|
229
|
+
channel_credentials=None,
|
|
230
|
+
call_credentials=None,
|
|
231
|
+
insecure=False,
|
|
232
|
+
compression=None,
|
|
233
|
+
wait_for_ready=None,
|
|
234
|
+
timeout=None,
|
|
235
|
+
metadata=None,
|
|
236
|
+
):
|
|
237
|
+
return grpc.experimental.unary_unary(
|
|
238
|
+
request,
|
|
239
|
+
target,
|
|
240
|
+
"/CommandServer/ListQueuedJobs",
|
|
241
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsRequest.SerializeToString,
|
|
242
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.ListQueuedJobsResult.FromString,
|
|
243
|
+
options,
|
|
244
|
+
channel_credentials,
|
|
245
|
+
insecure,
|
|
246
|
+
call_credentials,
|
|
247
|
+
compression,
|
|
248
|
+
wait_for_ready,
|
|
249
|
+
timeout,
|
|
250
|
+
metadata,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def VaccuumQueuedJobs(
|
|
255
|
+
request,
|
|
256
|
+
target,
|
|
257
|
+
options=(),
|
|
258
|
+
channel_credentials=None,
|
|
259
|
+
call_credentials=None,
|
|
260
|
+
insecure=False,
|
|
261
|
+
compression=None,
|
|
262
|
+
wait_for_ready=None,
|
|
263
|
+
timeout=None,
|
|
264
|
+
metadata=None,
|
|
265
|
+
):
|
|
266
|
+
return grpc.experimental.unary_unary(
|
|
267
|
+
request,
|
|
268
|
+
target,
|
|
269
|
+
"/CommandServer/VaccuumQueuedJobs",
|
|
270
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsRequest.SerializeToString,
|
|
271
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.VaccuumQueuedJobsResult.FromString,
|
|
272
|
+
options,
|
|
273
|
+
channel_credentials,
|
|
274
|
+
insecure,
|
|
275
|
+
call_credentials,
|
|
276
|
+
compression,
|
|
277
|
+
wait_for_ready,
|
|
278
|
+
timeout,
|
|
279
|
+
metadata,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def CancelJob(
|
|
284
|
+
request,
|
|
285
|
+
target,
|
|
286
|
+
options=(),
|
|
287
|
+
channel_credentials=None,
|
|
288
|
+
call_credentials=None,
|
|
289
|
+
insecure=False,
|
|
290
|
+
compression=None,
|
|
291
|
+
wait_for_ready=None,
|
|
292
|
+
timeout=None,
|
|
293
|
+
metadata=None,
|
|
294
|
+
):
|
|
295
|
+
return grpc.experimental.unary_unary(
|
|
296
|
+
request,
|
|
297
|
+
target,
|
|
298
|
+
"/CommandServer/CancelJob",
|
|
299
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobRequest.SerializeToString,
|
|
300
|
+
uncountable_dot_integration_dot_queue__runner_dot_command__server_dot_protocol_dot_command__server__pb2.CancelJobResult.FromString,
|
|
301
|
+
options,
|
|
302
|
+
channel_credentials,
|
|
303
|
+
insecure,
|
|
304
|
+
call_credentials,
|
|
305
|
+
compression,
|
|
306
|
+
wait_for_ready,
|
|
307
|
+
timeout,
|
|
308
|
+
metadata,
|
|
309
|
+
)
|
|
@@ -6,8 +6,17 @@ from enum import StrEnum
|
|
|
6
6
|
from uncountable.types import queued_job_t
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class CommandCancelJobStatus(StrEnum):
|
|
10
|
+
CANCELLED_WITH_RESTART = "cancelled_with_restart"
|
|
11
|
+
NO_JOB_FOUND = "no_job_found"
|
|
12
|
+
JOB_ALREADY_COMPLETED = "job_already_completed"
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
class CommandType(StrEnum):
|
|
10
16
|
ENQUEUE_JOB = "enqueue_job"
|
|
17
|
+
RETRY_JOB = "retry_job"
|
|
18
|
+
VACCUUM_QUEUED_JOBS = "vaccuum_queued_jobs"
|
|
19
|
+
CANCEL_JOB = "cancel_job"
|
|
11
20
|
|
|
12
21
|
|
|
13
22
|
RT = typing.TypeVar("RT")
|
|
@@ -24,6 +33,16 @@ class CommandEnqueueJobResponse:
|
|
|
24
33
|
queued_job_uuid: str
|
|
25
34
|
|
|
26
35
|
|
|
36
|
+
@dataclass(kw_only=True)
|
|
37
|
+
class CommandRetryJobResponse:
|
|
38
|
+
queued_job_uuid: str | None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(kw_only=True)
|
|
42
|
+
class CommandVaccuumQueuedJobsResponse:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
27
46
|
@dataclass(kw_only=True)
|
|
28
47
|
class CommandEnqueueJob(CommandBase[CommandEnqueueJobResponse]):
|
|
29
48
|
type: CommandType = CommandType.ENQUEUE_JOB
|
|
@@ -32,7 +51,31 @@ class CommandEnqueueJob(CommandBase[CommandEnqueueJobResponse]):
|
|
|
32
51
|
response_queue: asyncio.Queue[CommandEnqueueJobResponse]
|
|
33
52
|
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
@dataclass(kw_only=True)
|
|
55
|
+
class CommandRetryJob(CommandBase[CommandRetryJobResponse]):
|
|
56
|
+
type: CommandType = CommandType.RETRY_JOB
|
|
57
|
+
queued_job_uuid: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(kw_only=True)
|
|
61
|
+
class CommandVaccuumQueuedJobs(CommandBase[CommandVaccuumQueuedJobsResponse]):
|
|
62
|
+
type: CommandType = CommandType.VACCUUM_QUEUED_JOBS
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(kw_only=True)
|
|
66
|
+
class CommandCancelJobResponse:
|
|
67
|
+
status: CommandCancelJobStatus
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(kw_only=True)
|
|
71
|
+
class CommandCancelJob(CommandBase[CommandCancelJobResponse]):
|
|
72
|
+
type: CommandType = CommandType.CANCEL_JOB
|
|
73
|
+
queued_job_uuid: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_Command = (
|
|
77
|
+
CommandEnqueueJob | CommandRetryJob | CommandVaccuumQueuedJobs | CommandCancelJob
|
|
78
|
+
)
|
|
36
79
|
|
|
37
80
|
|
|
38
81
|
CommandQueue = asyncio.Queue[_Command]
|
|
@@ -2,7 +2,7 @@ import datetime
|
|
|
2
2
|
import uuid
|
|
3
3
|
from datetime import UTC
|
|
4
4
|
|
|
5
|
-
from sqlalchemy import delete, insert, select, update
|
|
5
|
+
from sqlalchemy import delete, insert, or_, select, text, update
|
|
6
6
|
from sqlalchemy.engine import Engine
|
|
7
7
|
|
|
8
8
|
from pkgs.argument_parser import CachedParser
|
|
@@ -14,6 +14,8 @@ from uncountable.types import queued_job_t
|
|
|
14
14
|
|
|
15
15
|
queued_job_payload_parser = CachedParser(queued_job_t.QueuedJobPayload)
|
|
16
16
|
|
|
17
|
+
MAX_QUEUE_WINDOW_DAYS = 30
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
class DatastoreSqlite(Datastore):
|
|
19
21
|
def __init__(self, session_maker: DBSessionMaker) -> None:
|
|
@@ -23,6 +25,17 @@ class DatastoreSqlite(Datastore):
|
|
|
23
25
|
@classmethod
|
|
24
26
|
def setup(cls, engine: Engine) -> None:
|
|
25
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
|
+
)
|
|
26
39
|
|
|
27
40
|
def add_job_to_queue(
|
|
28
41
|
self, job_payload: queued_job_t.QueuedJobPayload, job_ref_name: str
|
|
@@ -36,6 +49,7 @@ class DatastoreSqlite(Datastore):
|
|
|
36
49
|
QueuedJob.id.key: queued_job_uuid,
|
|
37
50
|
QueuedJob.job_ref_name.key: job_ref_name,
|
|
38
51
|
QueuedJob.payload.key: serialized_payload,
|
|
52
|
+
QueuedJob.status.key: queued_job_t.JobStatus.QUEUED,
|
|
39
53
|
QueuedJob.num_attempts: num_attempts,
|
|
40
54
|
QueuedJob.submitted_at: submitted_at,
|
|
41
55
|
})
|
|
@@ -44,10 +58,48 @@ class DatastoreSqlite(Datastore):
|
|
|
44
58
|
queued_job_uuid=queued_job_uuid,
|
|
45
59
|
job_ref_name=job_ref_name,
|
|
46
60
|
payload=job_payload,
|
|
61
|
+
status=queued_job_t.JobStatus.QUEUED,
|
|
47
62
|
submitted_at=submitted_at,
|
|
48
63
|
num_attempts=num_attempts,
|
|
49
64
|
)
|
|
50
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
|
+
|
|
51
103
|
def increment_num_attempts(self, queued_job_uuid: str) -> int:
|
|
52
104
|
with self.session_maker() as session:
|
|
53
105
|
update_stmt = (
|
|
@@ -68,15 +120,103 @@ class DatastoreSqlite(Datastore):
|
|
|
68
120
|
delete_stmt = delete(QueuedJob).filter(QueuedJob.id == queued_job_uuid)
|
|
69
121
|
session.execute(delete_stmt)
|
|
70
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
|
+
|
|
71
201
|
def load_job_queue(self) -> list[queued_job_t.QueuedJob]:
|
|
72
202
|
with self.session_maker() as session:
|
|
73
|
-
select_stmt =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
)
|
|
80
220
|
|
|
81
221
|
queued_jobs: list[queued_job_t.QueuedJob] = []
|
|
82
222
|
for row in session.execute(select_stmt):
|
|
@@ -86,9 +226,50 @@ class DatastoreSqlite(Datastore):
|
|
|
86
226
|
queued_job_uuid=row.id,
|
|
87
227
|
job_ref_name=row.job_ref_name,
|
|
88
228
|
num_attempts=row.num_attempts,
|
|
229
|
+
status=row.status or queued_job_t.JobStatus.QUEUED,
|
|
89
230
|
submitted_at=row.submitted_at,
|
|
90
231
|
payload=parsed_payload,
|
|
91
232
|
)
|
|
92
233
|
)
|
|
93
234
|
|
|
94
235
|
return queued_jobs
|
|
236
|
+
|
|
237
|
+
def get_queued_job(self, *, uuid: str) -> queued_job_t.QueuedJob | None:
|
|
238
|
+
with self.session_maker() as session:
|
|
239
|
+
select_stmt = select(
|
|
240
|
+
QueuedJob.id,
|
|
241
|
+
QueuedJob.payload,
|
|
242
|
+
QueuedJob.num_attempts,
|
|
243
|
+
QueuedJob.job_ref_name,
|
|
244
|
+
QueuedJob.status,
|
|
245
|
+
QueuedJob.submitted_at,
|
|
246
|
+
).filter(QueuedJob.id == uuid)
|
|
247
|
+
|
|
248
|
+
row = session.execute(select_stmt).one_or_none()
|
|
249
|
+
return (
|
|
250
|
+
queued_job_t.QueuedJob(
|
|
251
|
+
queued_job_uuid=row.id,
|
|
252
|
+
job_ref_name=row.job_ref_name,
|
|
253
|
+
num_attempts=row.num_attempts,
|
|
254
|
+
status=row.status or queued_job_t.JobStatus.QUEUED,
|
|
255
|
+
submitted_at=row.submitted_at,
|
|
256
|
+
payload=queued_job_payload_parser.parse_storage(row.payload),
|
|
257
|
+
)
|
|
258
|
+
if row is not None
|
|
259
|
+
else None
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def vaccuum_queued_jobs(self) -> None:
|
|
263
|
+
with self.session_maker() as session:
|
|
264
|
+
delete_stmt = (
|
|
265
|
+
delete(QueuedJob)
|
|
266
|
+
.filter(QueuedJob.status == queued_job_t.JobStatus.QUEUED)
|
|
267
|
+
.filter(
|
|
268
|
+
QueuedJob.submitted_at
|
|
269
|
+
<= (
|
|
270
|
+
datetime.datetime.now(UTC)
|
|
271
|
+
- datetime.timedelta(days=MAX_QUEUE_WINDOW_DAYS)
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
session.execute(delete_stmt)
|
|
@@ -17,3 +17,16 @@ class Datastore(ABC):
|
|
|
17
17
|
|
|
18
18
|
@abstractmethod
|
|
19
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]: ...
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def get_queued_job(self, *, uuid: str) -> queued_job_t.QueuedJob | None: ...
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from sqlalchemy import JSON, BigInteger, Column, DateTime, Text
|
|
1
|
+
from sqlalchemy import JSON, BigInteger, Column, DateTime, Enum, Text
|
|
2
2
|
from sqlalchemy.orm import declarative_base
|
|
3
3
|
from sqlalchemy.sql import func
|
|
4
4
|
|
|
5
|
+
from uncountable.types import queued_job_t
|
|
6
|
+
|
|
5
7
|
Base = declarative_base()
|
|
6
8
|
|
|
7
9
|
|
|
@@ -15,3 +17,8 @@ class QueuedJob(Base):
|
|
|
15
17
|
)
|
|
16
18
|
payload = Column(JSON, nullable=False)
|
|
17
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
|
+
)
|