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.

Files changed (119) hide show
  1. docs/conf.py +52 -5
  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 +1 -1
  7. docs/requirements.txt +3 -2
  8. examples/basic_auth.py +7 -0
  9. examples/integration-server/jobs/materials_auto/example_cron.py +3 -0
  10. examples/integration-server/jobs/materials_auto/example_http.py +19 -7
  11. examples/integration-server/jobs/materials_auto/example_instrument.py +100 -0
  12. examples/integration-server/jobs/materials_auto/example_parse.py +140 -0
  13. examples/integration-server/jobs/materials_auto/example_predictions.py +61 -0
  14. examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +57 -16
  15. examples/integration-server/jobs/materials_auto/profile.yaml +27 -0
  16. examples/integration-server/pyproject.toml +4 -4
  17. examples/oauth.py +7 -0
  18. pkgs/argument_parser/__init__.py +1 -0
  19. pkgs/argument_parser/_is_namedtuple.py +3 -0
  20. pkgs/argument_parser/argument_parser.py +22 -3
  21. pkgs/serialization_util/serialization_helpers.py +3 -1
  22. pkgs/type_spec/builder.py +66 -19
  23. pkgs/type_spec/builder_types.py +9 -0
  24. pkgs/type_spec/config.py +26 -5
  25. pkgs/type_spec/cross_output_links.py +10 -16
  26. pkgs/type_spec/emit_open_api.py +72 -22
  27. pkgs/type_spec/emit_open_api_util.py +1 -0
  28. pkgs/type_spec/emit_python.py +76 -12
  29. pkgs/type_spec/emit_typescript.py +48 -32
  30. pkgs/type_spec/emit_typescript_util.py +44 -6
  31. pkgs/type_spec/load_types.py +2 -2
  32. pkgs/type_spec/open_api_util.py +16 -1
  33. pkgs/type_spec/parts/base.ts.prepart +4 -0
  34. pkgs/type_spec/type_info/emit_type_info.py +37 -4
  35. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +1 -0
  36. pkgs/type_spec/value_spec/__main__.py +2 -2
  37. pkgs/type_spec/value_spec/emit_python.py +6 -1
  38. uncountable/core/client.py +10 -3
  39. uncountable/integration/cli.py +175 -23
  40. uncountable/integration/executors/executors.py +1 -2
  41. uncountable/integration/executors/generic_upload_executor.py +1 -1
  42. uncountable/integration/http_server/types.py +3 -1
  43. uncountable/integration/job.py +35 -3
  44. uncountable/integration/queue_runner/command_server/__init__.py +4 -0
  45. uncountable/integration/queue_runner/command_server/command_client.py +89 -0
  46. uncountable/integration/queue_runner/command_server/command_server.py +117 -5
  47. uncountable/integration/queue_runner/command_server/constants.py +4 -0
  48. uncountable/integration/queue_runner/command_server/protocol/command_server.proto +51 -0
  49. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +34 -11
  50. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +102 -1
  51. uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +180 -0
  52. uncountable/integration/queue_runner/command_server/types.py +44 -1
  53. uncountable/integration/queue_runner/datastore/datastore_sqlite.py +189 -8
  54. uncountable/integration/queue_runner/datastore/interface.py +13 -0
  55. uncountable/integration/queue_runner/datastore/model.py +8 -1
  56. uncountable/integration/queue_runner/job_scheduler.py +85 -21
  57. uncountable/integration/queue_runner/queue_runner.py +10 -2
  58. uncountable/integration/queue_runner/types.py +2 -0
  59. uncountable/integration/queue_runner/worker.py +28 -29
  60. uncountable/integration/scheduler.py +121 -23
  61. uncountable/integration/server.py +36 -6
  62. uncountable/integration/telemetry.py +129 -8
  63. uncountable/integration/webhook_server/entrypoint.py +2 -0
  64. uncountable/types/__init__.py +38 -0
  65. uncountable/types/api/entity/create_or_update_entity.py +1 -0
  66. uncountable/types/api/entity/export_entities.py +13 -0
  67. uncountable/types/api/entity/list_aggregate.py +79 -0
  68. uncountable/types/api/entity/list_entities.py +25 -0
  69. uncountable/types/api/entity/set_barcode.py +43 -0
  70. uncountable/types/api/entity/transition_entity_phase.py +2 -1
  71. uncountable/types/api/files/download_file.py +15 -1
  72. uncountable/types/api/integrations/__init__.py +1 -0
  73. uncountable/types/api/integrations/publish_realtime_data.py +41 -0
  74. uncountable/types/api/integrations/push_notification.py +49 -0
  75. uncountable/types/api/integrations/register_sockets_token.py +41 -0
  76. uncountable/types/api/listing/__init__.py +1 -0
  77. uncountable/types/api/listing/fetch_listing.py +57 -0
  78. uncountable/types/api/notebooks/__init__.py +1 -0
  79. uncountable/types/api/notebooks/add_notebook_content.py +119 -0
  80. uncountable/types/api/outputs/get_output_organization.py +173 -0
  81. uncountable/types/api/recipes/edit_recipe_inputs.py +1 -1
  82. uncountable/types/api/recipes/get_recipe_output_metadata.py +2 -2
  83. uncountable/types/api/recipes/get_recipes_data.py +29 -0
  84. uncountable/types/api/recipes/lock_recipes.py +2 -1
  85. uncountable/types/api/recipes/set_recipe_total.py +59 -0
  86. uncountable/types/api/recipes/unlock_recipes.py +2 -1
  87. uncountable/types/api/runsheet/export_default_runsheet.py +44 -0
  88. uncountable/types/api/uploader/complete_async_parse.py +46 -0
  89. uncountable/types/api/user/__init__.py +1 -0
  90. uncountable/types/api/user/get_current_user_info.py +40 -0
  91. uncountable/types/async_batch_processor.py +266 -0
  92. uncountable/types/async_batch_t.py +5 -0
  93. uncountable/types/client_base.py +432 -2
  94. uncountable/types/client_config.py +1 -0
  95. uncountable/types/client_config_t.py +10 -0
  96. uncountable/types/entity_t.py +9 -1
  97. uncountable/types/exports_t.py +1 -0
  98. uncountable/types/integration_server_t.py +2 -0
  99. uncountable/types/integration_session.py +10 -0
  100. uncountable/types/integration_session_t.py +60 -0
  101. uncountable/types/integrations.py +10 -0
  102. uncountable/types/integrations_t.py +62 -0
  103. uncountable/types/listing.py +46 -0
  104. uncountable/types/listing_t.py +533 -0
  105. uncountable/types/notices.py +8 -0
  106. uncountable/types/notices_t.py +37 -0
  107. uncountable/types/notifications.py +11 -0
  108. uncountable/types/notifications_t.py +74 -0
  109. uncountable/types/queued_job.py +2 -0
  110. uncountable/types/queued_job_t.py +20 -2
  111. uncountable/types/sockets.py +20 -0
  112. uncountable/types/sockets_t.py +169 -0
  113. uncountable/types/uploader.py +24 -0
  114. uncountable/types/uploader_t.py +222 -0
  115. {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/METADATA +5 -2
  116. {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/RECORD +118 -79
  117. docs/quickstart.md +0 -19
  118. {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/WHEEL +0 -0
  119. {uncountablepythonsdk-0.0.115.dist-info → uncountablepythonsdk-0.0.142.dev0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from uncountable.core.async_batch import AsyncBatchProcessor
12
12
  from uncountable.core.client import Client
13
13
  from uncountable.core.environment import get_local_admin_server_port
14
14
  from uncountable.core.file_upload import FileUpload
15
+ from uncountable.core.types import AuthDetailsOAuth
15
16
  from uncountable.integration.http_server import (
16
17
  GenericHttpRequest,
17
18
  GenericHttpResponse,
@@ -27,7 +28,6 @@ from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_se
27
28
  from uncountable.integration.telemetry import JobLogger
28
29
  from uncountable.types import (
29
30
  base_t,
30
- entity_t,
31
31
  job_definition_t,
32
32
  queued_job_t,
33
33
  webhook_job_t,
@@ -48,6 +48,7 @@ class JobArguments:
48
48
  batch_processor: AsyncBatchProcessor
49
49
  logger: JobLogger
50
50
  payload: base_t.JsonValue
51
+ job_uuid: str
51
52
 
52
53
 
53
54
  # only for compatibility:
@@ -93,6 +94,19 @@ class WebhookResponse:
93
94
  pass
94
95
 
95
96
 
97
+ class _RequestValidatorClient(Client):
98
+ def __init__(self, *, base_url: str, oauth_bearer_token: str):
99
+ super().__init__(
100
+ base_url=base_url,
101
+ auth_details=AuthDetailsOAuth(refresh_token=""),
102
+ config=None,
103
+ )
104
+ self._oauth_bearer_token = oauth_bearer_token
105
+
106
+ def _get_oauth_bearer_token(self, *, oauth_details: AuthDetailsOAuth) -> str:
107
+ return self._oauth_bearer_token
108
+
109
+
96
110
  class CustomHttpJob(Job[GenericHttpRequest]):
97
111
  @property
98
112
  def payload_type(self) -> type[GenericHttpRequest]:
@@ -112,6 +126,24 @@ class CustomHttpJob(Job[GenericHttpRequest]):
112
126
  """
113
127
  ...
114
128
 
129
+ @staticmethod
130
+ def get_validated_oauth_request_user_id(
131
+ *, profile_metadata: ProfileMetadata, request: GenericHttpRequest
132
+ ) -> base_t.ObjectId:
133
+ token = request.headers.get("Authorization", "").replace("Bearer ", "")
134
+ if token == "":
135
+ raise HttpException(
136
+ message="unauthorized; no bearer token in request", error_code=401
137
+ )
138
+ return (
139
+ _RequestValidatorClient(
140
+ base_url=profile_metadata.base_url,
141
+ oauth_bearer_token=token,
142
+ )
143
+ .get_current_user_info()
144
+ .user_id
145
+ )
146
+
115
147
  @staticmethod
116
148
  @abstractmethod
117
149
  def handle_request(
@@ -222,13 +254,13 @@ class RunsheetWebhookJob(WebhookJob[webhook_job_t.RunsheetWebhookPayload]):
222
254
  self,
223
255
  *,
224
256
  args: JobArguments,
225
- entities: list[entity_t.Entity],
257
+ payload: webhook_job_t.RunsheetWebhookPayload,
226
258
  ) -> FileUpload: ...
227
259
 
228
260
  def run(
229
261
  self, args: JobArguments, payload: webhook_job_t.RunsheetWebhookPayload
230
262
  ) -> JobResult:
231
- runsheet = self.build_runsheet(args=args, entities=payload.entities)
263
+ runsheet = self.build_runsheet(args=args, payload=payload)
232
264
 
233
265
  files = args.client.upload_files(file_uploads=[runsheet])
234
266
  args.client.complete_async_upload(
@@ -4,6 +4,8 @@ from .types import (
4
4
  CommandEnqueueJob,
5
5
  CommandEnqueueJobResponse,
6
6
  CommandQueue,
7
+ CommandRetryJob,
8
+ CommandRetryJobResponse,
7
9
  CommandServerBadResponse,
8
10
  CommandServerException,
9
11
  CommandServerTimeout,
@@ -16,6 +18,8 @@ __all__: list[str] = [
16
18
  "send_job_queue_message",
17
19
  "CommandEnqueueJob",
18
20
  "CommandEnqueueJobResponse",
21
+ "CommandRetryJob",
22
+ "CommandRetryJobResponse",
19
23
  "CommandTask",
20
24
  "CommandQueue",
21
25
  "CommandServerTimeout",
@@ -6,12 +6,22 @@ import simplejson as json
6
6
 
7
7
  from pkgs.serialization_util import serialize_for_api
8
8
  from uncountable.integration.queue_runner.command_server.protocol.command_server_pb2 import (
9
+ CancelJobRequest,
10
+ CancelJobResult,
11
+ CancelJobStatus,
9
12
  CheckHealthRequest,
10
13
  CheckHealthResult,
11
14
  EnqueueJobRequest,
12
15
  EnqueueJobResult,
16
+ ListQueuedJobsRequest,
17
+ ListQueuedJobsResult,
18
+ RetryJobRequest,
19
+ RetryJobResult,
20
+ VaccuumQueuedJobsRequest,
21
+ VaccuumQueuedJobsResult,
13
22
  )
14
23
  from uncountable.integration.queue_runner.command_server.types import (
24
+ CommandCancelJobStatus,
15
25
  CommandServerBadResponse,
16
26
  CommandServerTimeout,
17
27
  )
@@ -57,6 +67,46 @@ def send_job_queue_message(
57
67
  return response.queued_job_uuid
58
68
 
59
69
 
70
+ def send_job_cancellation_message(
71
+ *, queued_job_uuid: str, host: str = "localhost", port: int
72
+ ) -> CommandCancelJobStatus:
73
+ with command_server_connection(host=host, port=port) as stub:
74
+ request = CancelJobRequest(job_uuid=queued_job_uuid)
75
+
76
+ response = stub.CancelJob(request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS)
77
+
78
+ assert isinstance(response, CancelJobResult)
79
+ match response.status:
80
+ case CancelJobStatus.NO_JOB_FOUND:
81
+ return CommandCancelJobStatus.NO_JOB_FOUND
82
+ case CancelJobStatus.CANCELLED_WITH_RESTART:
83
+ return CommandCancelJobStatus.CANCELLED_WITH_RESTART
84
+ case CancelJobStatus.JOB_ALREADY_COMPLETED:
85
+ return CommandCancelJobStatus.JOB_ALREADY_COMPLETED
86
+ case _:
87
+ raise CommandServerBadResponse(f"unknown status: {response.status}")
88
+
89
+
90
+ def send_retry_job_message(
91
+ *,
92
+ job_uuid: str,
93
+ host: str = "localhost",
94
+ port: int,
95
+ ) -> str:
96
+ with command_server_connection(host=host, port=port) as stub:
97
+ request = RetryJobRequest(uuid=job_uuid)
98
+
99
+ try:
100
+ response = stub.RetryJob(request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS)
101
+ assert isinstance(response, RetryJobResult)
102
+ if not response.successfully_queued:
103
+ raise CommandServerBadResponse("queue operation was not successful")
104
+
105
+ return response.queued_job_uuid
106
+ except grpc.RpcError as e:
107
+ raise ValueError(e.details()) # type: ignore
108
+
109
+
60
110
  def check_health(*, host: str = _LOCAL_RPC_HOST, port: int) -> bool:
61
111
  with command_server_connection(host=host, port=port) as stub:
62
112
  request = CheckHealthRequest()
@@ -66,3 +116,42 @@ def check_health(*, host: str = _LOCAL_RPC_HOST, port: int) -> bool:
66
116
  assert isinstance(response, CheckHealthResult)
67
117
 
68
118
  return response.success
119
+
120
+
121
+ def send_list_queued_jobs_message(
122
+ *,
123
+ offset: int,
124
+ limit: int,
125
+ host: str = "localhost",
126
+ port: int,
127
+ ) -> list[ListQueuedJobsResult.ListQueuedJobsResultItem]:
128
+ with command_server_connection(host=host, port=port) as stub:
129
+ request = ListQueuedJobsRequest(
130
+ offset=offset,
131
+ limit=limit,
132
+ )
133
+
134
+ try:
135
+ response = stub.ListQueuedJobs(
136
+ request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS
137
+ )
138
+ except grpc.RpcError as e:
139
+ raise ValueError(e.details()) # type: ignore
140
+
141
+ assert isinstance(response, ListQueuedJobsResult)
142
+ return list(response.queued_jobs)
143
+
144
+
145
+ def send_vaccuum_queued_jobs_message(*, host: str = "localhost", port: int) -> None:
146
+ with command_server_connection(host=host, port=port) as stub:
147
+ request = VaccuumQueuedJobsRequest()
148
+
149
+ try:
150
+ response = stub.VaccuumQueuedJobs(
151
+ request, timeout=_DEFAULT_MESSAGE_TIMEOUT_SECS
152
+ )
153
+ except grpc.RpcError as e:
154
+ raise ValueError(e.details()) # type: ignore
155
+
156
+ assert isinstance(response, VaccuumQueuedJobsResult)
157
+ return None
@@ -1,23 +1,44 @@
1
1
  import asyncio
2
+ from typing import assert_never
2
3
 
4
+ import grpc.aio as grpc_aio
3
5
  import simplejson as json
4
- from grpc import aio
6
+ from google.protobuf.timestamp_pb2 import Timestamp
7
+ from grpc import StatusCode
5
8
 
6
9
  from pkgs.argument_parser import CachedParser
7
10
  from uncountable.core.environment import get_local_admin_server_port
8
11
  from uncountable.integration.queue_runner.command_server.protocol.command_server_pb2 import (
12
+ CancelJobRequest,
13
+ CancelJobResult,
14
+ CancelJobStatus,
9
15
  CheckHealthRequest,
10
16
  CheckHealthResult,
11
17
  EnqueueJobRequest,
12
18
  EnqueueJobResult,
19
+ ListQueuedJobsRequest,
20
+ ListQueuedJobsResult,
21
+ RetryJobRequest,
22
+ RetryJobResult,
23
+ VaccuumQueuedJobsRequest,
24
+ VaccuumQueuedJobsResult,
13
25
  )
14
26
  from uncountable.integration.queue_runner.command_server.types import (
27
+ CommandCancelJob,
28
+ CommandCancelJobResponse,
29
+ CommandCancelJobStatus,
15
30
  CommandEnqueueJob,
16
31
  CommandEnqueueJobResponse,
17
32
  CommandQueue,
33
+ CommandRetryJob,
34
+ CommandRetryJobResponse,
35
+ CommandVaccuumQueuedJobs,
36
+ CommandVaccuumQueuedJobsResponse,
18
37
  )
38
+ from uncountable.integration.queue_runner.datastore import DatastoreSqlite
19
39
  from uncountable.types import queued_job_t
20
40
 
41
+ from .constants import ListQueuedJobsConstants
21
42
  from .protocol.command_server_pb2_grpc import (
22
43
  CommandServerServicer,
23
44
  add_CommandServerServicer_to_server,
@@ -26,12 +47,12 @@ from .protocol.command_server_pb2_grpc import (
26
47
  queued_job_payload_parser = CachedParser(queued_job_t.QueuedJobPayload)
27
48
 
28
49
 
29
- async def serve(command_queue: CommandQueue) -> None:
30
- server = aio.server()
50
+ async def serve(command_queue: CommandQueue, datastore: DatastoreSqlite) -> None:
51
+ server = grpc_aio.server()
31
52
 
32
53
  class CommandServerHandler(CommandServerServicer):
33
54
  async def EnqueueJob(
34
- self, request: EnqueueJobRequest, context: aio.ServicerContext
55
+ self, request: EnqueueJobRequest, context: grpc_aio.ServicerContext
35
56
  ) -> EnqueueJobResult:
36
57
  payload_json = json.loads(request.serialized_payload)
37
58
  payload = queued_job_payload_parser.parse_api(payload_json)
@@ -49,11 +70,102 @@ async def serve(command_queue: CommandQueue) -> None:
49
70
  )
50
71
  return result
51
72
 
73
+ async def CancelJob(
74
+ self, request: CancelJobRequest, context: grpc_aio.ServicerContext
75
+ ) -> CancelJobResult:
76
+ response_queue: asyncio.Queue[CommandCancelJobResponse] = asyncio.Queue()
77
+ await command_queue.put(
78
+ CommandCancelJob(
79
+ queued_job_uuid=request.job_uuid,
80
+ response_queue=response_queue,
81
+ )
82
+ )
83
+
84
+ response = await response_queue.get()
85
+
86
+ proto_status: CancelJobStatus
87
+ match response.status:
88
+ case CommandCancelJobStatus.NO_JOB_FOUND:
89
+ proto_status = CancelJobStatus.NO_JOB_FOUND
90
+ case CommandCancelJobStatus.CANCELLED_WITH_RESTART:
91
+ proto_status = CancelJobStatus.CANCELLED_WITH_RESTART
92
+ case CommandCancelJobStatus.JOB_ALREADY_COMPLETED:
93
+ proto_status = CancelJobStatus.JOB_ALREADY_COMPLETED
94
+ case _:
95
+ assert_never(response.status)
96
+
97
+ result = CancelJobResult(status=proto_status)
98
+ return result
99
+
100
+ async def RetryJob(
101
+ self, request: RetryJobRequest, context: grpc_aio.ServicerContext
102
+ ) -> RetryJobResult:
103
+ response_queue: asyncio.Queue[CommandRetryJobResponse] = asyncio.Queue()
104
+ await command_queue.put(
105
+ CommandRetryJob(
106
+ queued_job_uuid=request.uuid, response_queue=response_queue
107
+ )
108
+ )
109
+ response = await response_queue.get()
110
+ if response.queued_job_uuid is not None:
111
+ return RetryJobResult(
112
+ successfully_queued=True, queued_job_uuid=response.queued_job_uuid
113
+ )
114
+ else:
115
+ return RetryJobResult(successfully_queued=False, queued_job_uuid="")
116
+
52
117
  async def CheckHealth(
53
- self, request: CheckHealthRequest, context: aio.ServicerContext
118
+ self, request: CheckHealthRequest, context: grpc_aio.ServicerContext
54
119
  ) -> CheckHealthResult:
55
120
  return CheckHealthResult(success=True)
56
121
 
122
+ async def ListQueuedJobs(
123
+ self, request: ListQueuedJobsRequest, context: grpc_aio.ServicerContext
124
+ ) -> ListQueuedJobsResult:
125
+ if (
126
+ request.limit < ListQueuedJobsConstants.LIMIT_MIN
127
+ or request.limit > ListQueuedJobsConstants.LIMIT_MAX
128
+ ):
129
+ await context.abort(
130
+ StatusCode.INVALID_ARGUMENT, "Limit must be between 1 and 100."
131
+ )
132
+
133
+ if request.offset < ListQueuedJobsConstants.OFFSET_MIN:
134
+ await context.abort(
135
+ StatusCode.INVALID_ARGUMENT, "Offset cannot be negative."
136
+ )
137
+
138
+ queued_job_metadata = datastore.list_queued_job_metadata(
139
+ offset=request.offset, limit=request.limit
140
+ )
141
+
142
+ response_list: list[ListQueuedJobsResult.ListQueuedJobsResultItem] = []
143
+ for item in queued_job_metadata:
144
+ proto_timestamp = Timestamp()
145
+ proto_timestamp.FromDatetime(item.submitted_at)
146
+
147
+ response_list.append(
148
+ ListQueuedJobsResult.ListQueuedJobsResultItem(
149
+ uuid=item.queued_job_uuid,
150
+ job_ref_name=item.job_ref_name,
151
+ num_attempts=item.num_attempts,
152
+ submitted_at=proto_timestamp,
153
+ status=item.status,
154
+ )
155
+ )
156
+ return ListQueuedJobsResult(queued_jobs=response_list)
157
+
158
+ async def VaccuumQueuedJobs(
159
+ self, request: VaccuumQueuedJobsRequest, context: grpc_aio.ServicerContext
160
+ ) -> VaccuumQueuedJobsResult:
161
+ response_queue: asyncio.Queue[CommandVaccuumQueuedJobsResponse] = (
162
+ asyncio.Queue()
163
+ )
164
+ await command_queue.put(
165
+ CommandVaccuumQueuedJobs(response_queue=response_queue)
166
+ )
167
+ return VaccuumQueuedJobsResult()
168
+
57
169
  add_CommandServerServicer_to_server(CommandServerHandler(), server)
58
170
 
59
171
  listen_addr = f"[::]:{get_local_admin_server_port()}"
@@ -0,0 +1,4 @@
1
+ class ListQueuedJobsConstants:
2
+ LIMIT_MIN = 1
3
+ LIMIT_MAX = 100
4
+ OFFSET_MIN = 0
@@ -1,8 +1,13 @@
1
1
  syntax = "proto3";
2
+ import "google/protobuf/timestamp.proto";
2
3
 
3
4
  service CommandServer {
4
5
  rpc EnqueueJob(EnqueueJobRequest) returns (EnqueueJobResult) {}
6
+ rpc RetryJob(RetryJobRequest) returns (RetryJobResult) {}
5
7
  rpc CheckHealth(CheckHealthRequest) returns (CheckHealthResult) {}
8
+ rpc ListQueuedJobs(ListQueuedJobsRequest) returns (ListQueuedJobsResult) {}
9
+ rpc VaccuumQueuedJobs(VaccuumQueuedJobsRequest) returns (VaccuumQueuedJobsResult) {}
10
+ rpc CancelJob(CancelJobRequest) returns (CancelJobResult) {}
6
11
  }
7
12
 
8
13
  message EnqueueJobRequest {
@@ -15,8 +20,54 @@ message EnqueueJobResult {
15
20
  string queued_job_uuid = 2;
16
21
  }
17
22
 
23
+ message RetryJobRequest {
24
+ string uuid = 1;
25
+ }
26
+
27
+ message RetryJobResult {
28
+ bool successfully_queued = 1;
29
+ string queued_job_uuid = 2;
30
+ }
31
+
32
+ message VaccuumQueuedJobsRequest {
33
+ }
34
+
35
+ message VaccuumQueuedJobsResult {
36
+ }
37
+
18
38
  message CheckHealthRequest {}
19
39
 
20
40
  message CheckHealthResult {
21
41
  bool success = 1;
22
42
  }
43
+
44
+ message ListQueuedJobsRequest {
45
+ uint32 offset = 1;
46
+ uint32 limit = 2;
47
+ }
48
+
49
+ message ListQueuedJobsResult {
50
+ message ListQueuedJobsResultItem {
51
+ string uuid = 1;
52
+ string job_ref_name = 2;
53
+ int64 num_attempts = 3;
54
+ google.protobuf.Timestamp submitted_at = 4;
55
+ string status = 5;
56
+ }
57
+
58
+ repeated ListQueuedJobsResultItem queued_jobs = 1;
59
+ }
60
+
61
+ message CancelJobRequest {
62
+ string job_uuid = 1;
63
+ }
64
+
65
+ enum CancelJobStatus {
66
+ CANCELLED_WITH_RESTART = 0;
67
+ NO_JOB_FOUND = 1;
68
+ JOB_ALREADY_COMPLETED = 2;
69
+ }
70
+
71
+ message CancelJobResult {
72
+ CancelJobStatus status = 1;
73
+ }
@@ -14,8 +14,11 @@ from google.protobuf.internal import builder as _builder
14
14
  _sym_db = _symbol_database.Default()
15
15
 
16
16
 
17
+ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
18
+
19
+
17
20
  DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
18
- b'\nQuncountable/integration/queue_runner/command_server/protocol/command_server.proto"E\n\x11\x45nqueueJobRequest\x12\x14\n\x0cjob_ref_name\x18\x01 \x01(\t\x12\x1a\n\x12serialized_payload\x18\x02 \x01(\t"H\n\x10\x45nqueueJobResult\x12\x1b\n\x13successfully_queued\x18\x01 \x01(\x08\x12\x17\n\x0fqueued_job_uuid\x18\x02 \x01(\t"\x14\n\x12\x43heckHealthRequest"$\n\x11\x43heckHealthResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x32\x80\x01\n\rCommandServer\x12\x35\n\nEnqueueJob\x12\x12.EnqueueJobRequest\x1a\x11.EnqueueJobResult"\x00\x12\x38\n\x0b\x43heckHealth\x12\x13.CheckHealthRequest\x1a\x12.CheckHealthResult"\x00\x62\x06proto3'
21
+ b'\nQuncountable/integration/queue_runner/command_server/protocol/command_server.proto\x1a\x1fgoogle/protobuf/timestamp.proto"E\n\x11\x45nqueueJobRequest\x12\x14\n\x0cjob_ref_name\x18\x01 \x01(\t\x12\x1a\n\x12serialized_payload\x18\x02 \x01(\t"H\n\x10\x45nqueueJobResult\x12\x1b\n\x13successfully_queued\x18\x01 \x01(\x08\x12\x17\n\x0fqueued_job_uuid\x18\x02 \x01(\t"\x1f\n\x0fRetryJobRequest\x12\x0c\n\x04uuid\x18\x01 \x01(\t"F\n\x0eRetryJobResult\x12\x1b\n\x13successfully_queued\x18\x01 \x01(\x08\x12\x17\n\x0fqueued_job_uuid\x18\x02 \x01(\t"\x1a\n\x18VaccuumQueuedJobsRequest"\x19\n\x17VaccuumQueuedJobsResult"\x14\n\x12\x43heckHealthRequest"$\n\x11\x43heckHealthResult\x12\x0f\n\x07success\x18\x01 \x01(\x08"6\n\x15ListQueuedJobsRequest\x12\x0e\n\x06offset\x18\x01 \x01(\r\x12\r\n\x05limit\x18\x02 \x01(\r"\xf4\x01\n\x14ListQueuedJobsResult\x12\x43\n\x0bqueued_jobs\x18\x01 \x03(\x0b\x32..ListQueuedJobsResult.ListQueuedJobsResultItem\x1a\x96\x01\n\x18ListQueuedJobsResultItem\x12\x0c\n\x04uuid\x18\x01 \x01(\t\x12\x14\n\x0cjob_ref_name\x18\x02 \x01(\t\x12\x14\n\x0cnum_attempts\x18\x03 \x01(\x03\x12\x30\n\x0csubmitted_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06status\x18\x05 \x01(\t"$\n\x10\x43\x61ncelJobRequest\x12\x10\n\x08job_uuid\x18\x01 \x01(\t"3\n\x0f\x43\x61ncelJobResult\x12 \n\x06status\x18\x01 \x01(\x0e\x32\x10.CancelJobStatus*Z\n\x0f\x43\x61ncelJobStatus\x12\x1a\n\x16\x43\x41NCELLED_WITH_RESTART\x10\x00\x12\x10\n\x0cNO_JOB_FOUND\x10\x01\x12\x19\n\x15JOB_ALREADY_COMPLETED\x10\x02\x32\xf4\x02\n\rCommandServer\x12\x35\n\nEnqueueJob\x12\x12.EnqueueJobRequest\x1a\x11.EnqueueJobResult"\x00\x12/\n\x08RetryJob\x12\x10.RetryJobRequest\x1a\x0f.RetryJobResult"\x00\x12\x38\n\x0b\x43heckHealth\x12\x13.CheckHealthRequest\x1a\x12.CheckHealthResult"\x00\x12\x41\n\x0eListQueuedJobs\x12\x16.ListQueuedJobsRequest\x1a\x15.ListQueuedJobsResult"\x00\x12J\n\x11VaccuumQueuedJobs\x12\x19.VaccuumQueuedJobsRequest\x1a\x18.VaccuumQueuedJobsResult"\x00\x12\x32\n\tCancelJob\x12\x11.CancelJobRequest\x1a\x10.CancelJobResult"\x00\x62\x06proto3'
19
22
  )
20
23
 
21
24
  _globals = globals()
@@ -27,14 +30,34 @@ _builder.BuildTopDescriptorsAndMessages(
27
30
  )
28
31
  if _descriptor._USE_C_DESCRIPTORS == False:
29
32
  DESCRIPTOR._options = None
30
- _globals["_ENQUEUEJOBREQUEST"]._serialized_start = 85
31
- _globals["_ENQUEUEJOBREQUEST"]._serialized_end = 154
32
- _globals["_ENQUEUEJOBRESULT"]._serialized_start = 156
33
- _globals["_ENQUEUEJOBRESULT"]._serialized_end = 228
34
- _globals["_CHECKHEALTHREQUEST"]._serialized_start = 230
35
- _globals["_CHECKHEALTHREQUEST"]._serialized_end = 250
36
- _globals["_CHECKHEALTHRESULT"]._serialized_start = 252
37
- _globals["_CHECKHEALTHRESULT"]._serialized_end = 288
38
- _globals["_COMMANDSERVER"]._serialized_start = 291
39
- _globals["_COMMANDSERVER"]._serialized_end = 419
33
+ _globals["_CANCELJOBSTATUS"]._serialized_start = 877
34
+ _globals["_CANCELJOBSTATUS"]._serialized_end = 967
35
+ _globals["_ENQUEUEJOBREQUEST"]._serialized_start = 118
36
+ _globals["_ENQUEUEJOBREQUEST"]._serialized_end = 187
37
+ _globals["_ENQUEUEJOBRESULT"]._serialized_start = 189
38
+ _globals["_ENQUEUEJOBRESULT"]._serialized_end = 261
39
+ _globals["_RETRYJOBREQUEST"]._serialized_start = 263
40
+ _globals["_RETRYJOBREQUEST"]._serialized_end = 294
41
+ _globals["_RETRYJOBRESULT"]._serialized_start = 296
42
+ _globals["_RETRYJOBRESULT"]._serialized_end = 366
43
+ _globals["_VACCUUMQUEUEDJOBSREQUEST"]._serialized_start = 368
44
+ _globals["_VACCUUMQUEUEDJOBSREQUEST"]._serialized_end = 394
45
+ _globals["_VACCUUMQUEUEDJOBSRESULT"]._serialized_start = 396
46
+ _globals["_VACCUUMQUEUEDJOBSRESULT"]._serialized_end = 421
47
+ _globals["_CHECKHEALTHREQUEST"]._serialized_start = 423
48
+ _globals["_CHECKHEALTHREQUEST"]._serialized_end = 443
49
+ _globals["_CHECKHEALTHRESULT"]._serialized_start = 445
50
+ _globals["_CHECKHEALTHRESULT"]._serialized_end = 481
51
+ _globals["_LISTQUEUEDJOBSREQUEST"]._serialized_start = 483
52
+ _globals["_LISTQUEUEDJOBSREQUEST"]._serialized_end = 537
53
+ _globals["_LISTQUEUEDJOBSRESULT"]._serialized_start = 540
54
+ _globals["_LISTQUEUEDJOBSRESULT"]._serialized_end = 784
55
+ _globals["_LISTQUEUEDJOBSRESULT_LISTQUEUEDJOBSRESULTITEM"]._serialized_start = 634
56
+ _globals["_LISTQUEUEDJOBSRESULT_LISTQUEUEDJOBSRESULTITEM"]._serialized_end = 784
57
+ _globals["_CANCELJOBREQUEST"]._serialized_start = 786
58
+ _globals["_CANCELJOBREQUEST"]._serialized_end = 822
59
+ _globals["_CANCELJOBRESULT"]._serialized_start = 824
60
+ _globals["_CANCELJOBRESULT"]._serialized_end = 875
61
+ _globals["_COMMANDSERVER"]._serialized_start = 970
62
+ _globals["_COMMANDSERVER"]._serialized_end = 1342
40
63
  # @@protoc_insertion_point(module_scope)
@@ -1,10 +1,29 @@
1
1
  # ruff: noqa
2
+ from google.protobuf import timestamp_pb2 as _timestamp_pb2
3
+ from google.protobuf.internal import containers as _containers
4
+ from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
2
5
  from google.protobuf import descriptor as _descriptor
3
6
  from google.protobuf import message as _message
4
- from typing import ClassVar as _ClassVar, Optional as _Optional
7
+ from typing import (
8
+ ClassVar as _ClassVar,
9
+ Iterable as _Iterable,
10
+ Mapping as _Mapping,
11
+ Optional as _Optional,
12
+ Union as _Union,
13
+ )
5
14
 
6
15
  DESCRIPTOR: _descriptor.FileDescriptor
7
16
 
17
+ class CancelJobStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
18
+ __slots__ = ()
19
+ CANCELLED_WITH_RESTART: _ClassVar[CancelJobStatus]
20
+ NO_JOB_FOUND: _ClassVar[CancelJobStatus]
21
+ JOB_ALREADY_COMPLETED: _ClassVar[CancelJobStatus]
22
+
23
+ CANCELLED_WITH_RESTART: CancelJobStatus
24
+ NO_JOB_FOUND: CancelJobStatus
25
+ JOB_ALREADY_COMPLETED: CancelJobStatus
26
+
8
27
  class EnqueueJobRequest(_message.Message):
9
28
  __slots__ = ("job_ref_name", "serialized_payload")
10
29
  JOB_REF_NAME_FIELD_NUMBER: _ClassVar[int]
@@ -27,6 +46,30 @@ class EnqueueJobResult(_message.Message):
27
46
  self, successfully_queued: bool = ..., queued_job_uuid: _Optional[str] = ...
28
47
  ) -> None: ...
29
48
 
49
+ class RetryJobRequest(_message.Message):
50
+ __slots__ = ("uuid",)
51
+ UUID_FIELD_NUMBER: _ClassVar[int]
52
+ uuid: str
53
+ def __init__(self, uuid: _Optional[str] = ...) -> None: ...
54
+
55
+ class RetryJobResult(_message.Message):
56
+ __slots__ = ("successfully_queued", "queued_job_uuid")
57
+ SUCCESSFULLY_QUEUED_FIELD_NUMBER: _ClassVar[int]
58
+ QUEUED_JOB_UUID_FIELD_NUMBER: _ClassVar[int]
59
+ successfully_queued: bool
60
+ queued_job_uuid: str
61
+ def __init__(
62
+ self, successfully_queued: bool = ..., queued_job_uuid: _Optional[str] = ...
63
+ ) -> None: ...
64
+
65
+ class VaccuumQueuedJobsRequest(_message.Message):
66
+ __slots__ = ()
67
+ def __init__(self) -> None: ...
68
+
69
+ class VaccuumQueuedJobsResult(_message.Message):
70
+ __slots__ = ()
71
+ def __init__(self) -> None: ...
72
+
30
73
  class CheckHealthRequest(_message.Message):
31
74
  __slots__ = ()
32
75
  def __init__(self) -> None: ...
@@ -36,3 +79,61 @@ class CheckHealthResult(_message.Message):
36
79
  SUCCESS_FIELD_NUMBER: _ClassVar[int]
37
80
  success: bool
38
81
  def __init__(self, success: bool = ...) -> None: ...
82
+
83
+ class ListQueuedJobsRequest(_message.Message):
84
+ __slots__ = ("offset", "limit")
85
+ OFFSET_FIELD_NUMBER: _ClassVar[int]
86
+ LIMIT_FIELD_NUMBER: _ClassVar[int]
87
+ offset: int
88
+ limit: int
89
+ def __init__(
90
+ self, offset: _Optional[int] = ..., limit: _Optional[int] = ...
91
+ ) -> None: ...
92
+
93
+ class ListQueuedJobsResult(_message.Message):
94
+ __slots__ = ("queued_jobs",)
95
+ class ListQueuedJobsResultItem(_message.Message):
96
+ __slots__ = ("uuid", "job_ref_name", "num_attempts", "submitted_at", "status")
97
+ UUID_FIELD_NUMBER: _ClassVar[int]
98
+ JOB_REF_NAME_FIELD_NUMBER: _ClassVar[int]
99
+ NUM_ATTEMPTS_FIELD_NUMBER: _ClassVar[int]
100
+ SUBMITTED_AT_FIELD_NUMBER: _ClassVar[int]
101
+ STATUS_FIELD_NUMBER: _ClassVar[int]
102
+ uuid: str
103
+ job_ref_name: str
104
+ num_attempts: int
105
+ submitted_at: _timestamp_pb2.Timestamp
106
+ status: str
107
+ def __init__(
108
+ self,
109
+ uuid: _Optional[str] = ...,
110
+ job_ref_name: _Optional[str] = ...,
111
+ num_attempts: _Optional[int] = ...,
112
+ submitted_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...,
113
+ status: _Optional[str] = ...,
114
+ ) -> None: ...
115
+
116
+ QUEUED_JOBS_FIELD_NUMBER: _ClassVar[int]
117
+ queued_jobs: _containers.RepeatedCompositeFieldContainer[
118
+ ListQueuedJobsResult.ListQueuedJobsResultItem
119
+ ]
120
+ def __init__(
121
+ self,
122
+ queued_jobs: _Optional[
123
+ _Iterable[_Union[ListQueuedJobsResult.ListQueuedJobsResultItem, _Mapping]]
124
+ ] = ...,
125
+ ) -> None: ...
126
+
127
+ class CancelJobRequest(_message.Message):
128
+ __slots__ = ("job_uuid",)
129
+ JOB_UUID_FIELD_NUMBER: _ClassVar[int]
130
+ job_uuid: str
131
+ def __init__(self, job_uuid: _Optional[str] = ...) -> None: ...
132
+
133
+ class CancelJobResult(_message.Message):
134
+ __slots__ = ("status",)
135
+ STATUS_FIELD_NUMBER: _ClassVar[int]
136
+ status: CancelJobStatus
137
+ def __init__(
138
+ self, status: _Optional[_Union[CancelJobStatus, str]] = ...
139
+ ) -> None: ...