UncountablePythonSDK 0.0.69__py3-none-any.whl → 0.0.71__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.
- {UncountablePythonSDK-0.0.69.dist-info → UncountablePythonSDK-0.0.71.dist-info}/METADATA +3 -1
- {UncountablePythonSDK-0.0.69.dist-info → UncountablePythonSDK-0.0.71.dist-info}/RECORD +39 -18
- examples/integration-server/jobs/materials_auto/example_cron.py +2 -2
- uncountable/core/environment.py +5 -1
- uncountable/integration/cli.py +1 -0
- uncountable/integration/cron.py +12 -28
- uncountable/integration/db/connect.py +12 -2
- uncountable/integration/db/session.py +25 -0
- uncountable/integration/entrypoint.py +6 -6
- uncountable/integration/executors/generic_upload_executor.py +5 -1
- uncountable/integration/job.py +44 -17
- uncountable/integration/queue_runner/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/__init__.py +24 -0
- uncountable/integration/queue_runner/command_server/command_client.py +68 -0
- uncountable/integration/queue_runner/command_server/command_server.py +64 -0
- uncountable/integration/queue_runner/command_server/protocol/__init__.py +0 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +22 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +40 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +38 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +129 -0
- uncountable/integration/queue_runner/command_server/types.py +52 -0
- uncountable/integration/queue_runner/datastore/__init__.py +3 -0
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +93 -0
- uncountable/integration/queue_runner/datastore/interface.py +19 -0
- uncountable/integration/queue_runner/datastore/model.py +17 -0
- uncountable/integration/queue_runner/job_scheduler.py +128 -0
- uncountable/integration/queue_runner/queue_runner.py +26 -0
- uncountable/integration/queue_runner/types.py +7 -0
- uncountable/integration/queue_runner/worker.py +109 -0
- uncountable/integration/scan_profiles.py +2 -0
- uncountable/integration/scheduler.py +40 -3
- uncountable/integration/webhook_server/entrypoint.py +27 -31
- uncountable/types/__init__.py +2 -0
- uncountable/types/api/recipes/get_recipes_data.py +1 -0
- uncountable/types/api/recipes/set_recipe_outputs.py +2 -0
- uncountable/types/queued_job.py +16 -0
- uncountable/types/queued_job_t.py +107 -0
- {UncountablePythonSDK-0.0.69.dist-info → UncountablePythonSDK-0.0.71.dist-info}/WHEEL +0 -0
- {UncountablePythonSDK-0.0.69.dist-info → UncountablePythonSDK-0.0.71.dist-info}/top_level.txt +0 -0
|
@@ -3,10 +3,17 @@ import subprocess
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
6
7
|
|
|
7
8
|
from opentelemetry.trace import get_current_span
|
|
8
9
|
|
|
10
|
+
from uncountable.core.environment import get_local_admin_server_port
|
|
9
11
|
from uncountable.integration.entrypoint import main as cron_target
|
|
12
|
+
from uncountable.integration.queue_runner.command_server import (
|
|
13
|
+
CommandServerTimeout,
|
|
14
|
+
check_health,
|
|
15
|
+
)
|
|
16
|
+
from uncountable.integration.queue_runner.queue_runner import start_queue_runner
|
|
10
17
|
from uncountable.integration.telemetry import Logger
|
|
11
18
|
|
|
12
19
|
SHUTDOWN_TIMEOUT_SECS = 30
|
|
@@ -66,7 +73,7 @@ def handle_shutdown(logger: Logger, processes: list[ProcessInfo]) -> None:
|
|
|
66
73
|
proc_info.process.kill()
|
|
67
74
|
|
|
68
75
|
|
|
69
|
-
def
|
|
76
|
+
def check_process_alive(logger: Logger, processes: list[ProcessInfo]) -> None:
|
|
70
77
|
for proc_info in processes:
|
|
71
78
|
if not proc_info.is_alive:
|
|
72
79
|
logger.log_error(
|
|
@@ -76,6 +83,25 @@ def check_process_health(logger: Logger, processes: list[ProcessInfo]) -> None:
|
|
|
76
83
|
sys.exit(1)
|
|
77
84
|
|
|
78
85
|
|
|
86
|
+
def _wait_queue_runner_online() -> None:
|
|
87
|
+
_MAX_QUEUE_RUNNER_HEALTH_CHECKS = 10
|
|
88
|
+
_QUEUE_RUNNER_HEALTH_CHECK_DELAY_SECS = 1
|
|
89
|
+
|
|
90
|
+
num_attempts = 0
|
|
91
|
+
before = datetime.now(timezone.utc)
|
|
92
|
+
while num_attempts < _MAX_QUEUE_RUNNER_HEALTH_CHECKS:
|
|
93
|
+
try:
|
|
94
|
+
if check_health(port=get_local_admin_server_port()):
|
|
95
|
+
return
|
|
96
|
+
except CommandServerTimeout:
|
|
97
|
+
pass
|
|
98
|
+
num_attempts += 1
|
|
99
|
+
time.sleep(_QUEUE_RUNNER_HEALTH_CHECK_DELAY_SECS)
|
|
100
|
+
after = datetime.now(timezone.utc)
|
|
101
|
+
duration_secs = (after - before).seconds
|
|
102
|
+
raise Exception(f"queue runner failed to come online after {duration_secs} seconds")
|
|
103
|
+
|
|
104
|
+
|
|
79
105
|
def main() -> None:
|
|
80
106
|
logger = Logger(get_current_span())
|
|
81
107
|
processes: list[ProcessInfo] = []
|
|
@@ -84,7 +110,18 @@ def main() -> None:
|
|
|
84
110
|
processes.append(process)
|
|
85
111
|
logger.log_info(f"started process {process.name}")
|
|
86
112
|
|
|
87
|
-
|
|
113
|
+
runner_process = multiprocessing.Process(target=start_queue_runner)
|
|
114
|
+
runner_process.start()
|
|
115
|
+
add_process(ProcessInfo(name="queue runner", process=runner_process))
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
_wait_queue_runner_online()
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.log_exception(e)
|
|
121
|
+
handle_shutdown(logger, processes=processes)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
cron_process = multiprocessing.Process(target=cron_target)
|
|
88
125
|
cron_process.start()
|
|
89
126
|
add_process(ProcessInfo(name="cron server", process=cron_process))
|
|
90
127
|
|
|
@@ -98,7 +135,7 @@ def main() -> None:
|
|
|
98
135
|
|
|
99
136
|
try:
|
|
100
137
|
while True:
|
|
101
|
-
|
|
138
|
+
check_process_alive(logger, processes=processes)
|
|
102
139
|
time.sleep(1)
|
|
103
140
|
except KeyboardInterrupt:
|
|
104
141
|
handle_shutdown(logger, processes=processes)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import hmac
|
|
2
|
+
import typing
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
|
|
4
5
|
import flask
|
|
@@ -6,15 +7,21 @@ import simplejson
|
|
|
6
7
|
from flask.typing import ResponseReturnValue
|
|
7
8
|
from flask.wrappers import Response
|
|
8
9
|
from opentelemetry.trace import get_current_span
|
|
9
|
-
from uncountable.core.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
from uncountable.core.environment import (
|
|
11
|
+
get_integration_env,
|
|
12
|
+
get_local_admin_server_port,
|
|
13
|
+
get_webhook_server_port,
|
|
14
|
+
)
|
|
15
|
+
from uncountable.integration.queue_runner.command_server.command_client import (
|
|
16
|
+
send_job_queue_message,
|
|
17
|
+
)
|
|
18
|
+
from uncountable.integration.queue_runner.command_server.types import (
|
|
19
|
+
CommandServerException,
|
|
20
|
+
)
|
|
14
21
|
from uncountable.integration.scan_profiles import load_profiles
|
|
15
22
|
from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
|
|
16
|
-
from uncountable.integration.telemetry import
|
|
17
|
-
from uncountable.types import job_definition_t, webhook_job_t
|
|
23
|
+
from uncountable.integration.telemetry import Logger
|
|
24
|
+
from uncountable.types import base_t, job_definition_t, queued_job_t, webhook_job_t
|
|
18
25
|
|
|
19
26
|
from pkgs.argument_parser import CachedParser
|
|
20
27
|
|
|
@@ -66,7 +73,7 @@ class WebhookException(BaseException):
|
|
|
66
73
|
|
|
67
74
|
def _parse_webhook_payload(
|
|
68
75
|
*, raw_request_body: bytes, signature_key: str, passed_signature: str
|
|
69
|
-
) ->
|
|
76
|
+
) -> base_t.JsonValue:
|
|
70
77
|
request_body_signature = hmac.new(
|
|
71
78
|
signature_key.encode("utf-8"), msg=raw_request_body, digestmod="sha256"
|
|
72
79
|
).hexdigest()
|
|
@@ -76,7 +83,7 @@ def _parse_webhook_payload(
|
|
|
76
83
|
|
|
77
84
|
try:
|
|
78
85
|
request_body = simplejson.loads(raw_request_body.decode())
|
|
79
|
-
return
|
|
86
|
+
return typing.cast(base_t.JsonValue, request_body)
|
|
80
87
|
except (simplejson.JSONDecodeError, ValueError) as e:
|
|
81
88
|
raise WebhookException.body_parse_error() from e
|
|
82
89
|
|
|
@@ -110,31 +117,20 @@ def register_route(
|
|
|
110
117
|
passed_signature=passed_signature,
|
|
111
118
|
)
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
client = construct_uncountable_client(
|
|
122
|
-
profile_meta=profile_meta, job_logger=job_logger
|
|
123
|
-
)
|
|
124
|
-
execute_job(
|
|
125
|
-
job_definition=job,
|
|
126
|
-
profile_metadata=profile_meta,
|
|
127
|
-
args=WebhookJobArguments(
|
|
128
|
-
job_definition=job,
|
|
129
|
-
profile_metadata=profile_meta,
|
|
130
|
-
client=client,
|
|
131
|
-
batch_processor=AsyncBatchProcessor(client=client),
|
|
132
|
-
logger=job_logger,
|
|
133
|
-
payload=webhook_payload,
|
|
120
|
+
try:
|
|
121
|
+
send_job_queue_message(
|
|
122
|
+
job_ref_name=job.id,
|
|
123
|
+
payload=queued_job_t.QueuedJobPayload(
|
|
124
|
+
invocation_context=queued_job_t.InvocationContextWebhook(
|
|
125
|
+
webhook_payload=webhook_payload
|
|
126
|
+
)
|
|
134
127
|
),
|
|
128
|
+
port=get_local_admin_server_port(),
|
|
135
129
|
)
|
|
130
|
+
except CommandServerException as e:
|
|
131
|
+
raise WebhookException.unknown_error() from e
|
|
136
132
|
|
|
137
|
-
|
|
133
|
+
return flask.jsonify(WebhookResponse())
|
|
138
134
|
except WebhookException as e:
|
|
139
135
|
server_logger.log_exception(e)
|
|
140
136
|
return e.make_error_response()
|
uncountable/types/__init__.py
CHANGED
|
@@ -63,6 +63,7 @@ from . import overrides_t as overrides_t
|
|
|
63
63
|
from . import permissions_t as permissions_t
|
|
64
64
|
from . import phases_t as phases_t
|
|
65
65
|
from . import post_base_t as post_base_t
|
|
66
|
+
from . import queued_job_t as queued_job_t
|
|
66
67
|
from . import recipe_identifiers_t as recipe_identifiers_t
|
|
67
68
|
from . import recipe_inputs_t as recipe_inputs_t
|
|
68
69
|
from . import recipe_links_t as recipe_links_t
|
|
@@ -163,6 +164,7 @@ __all__: list[str] = [
|
|
|
163
164
|
"permissions_t",
|
|
164
165
|
"phases_t",
|
|
165
166
|
"post_base_t",
|
|
167
|
+
"queued_job_t",
|
|
166
168
|
"recipe_identifiers_t",
|
|
167
169
|
"recipe_inputs_t",
|
|
168
170
|
"recipe_links_t",
|
|
@@ -97,6 +97,7 @@ class RecipeInput:
|
|
|
97
97
|
curve_id: typing.Optional[base_t.ObjectId]
|
|
98
98
|
actual_quantity_json: base_t.JsonValue
|
|
99
99
|
behavior: str
|
|
100
|
+
ingredient_role_id: typing.Optional[base_t.ObjectId]
|
|
100
101
|
quantity_dec: typing.Optional[Decimal] = None
|
|
101
102
|
actual_quantity_dec: typing.Optional[Decimal] = None
|
|
102
103
|
|
|
@@ -10,6 +10,7 @@ from decimal import Decimal # noqa: F401
|
|
|
10
10
|
import dataclasses
|
|
11
11
|
from pkgs.serialization import serial_class
|
|
12
12
|
from ... import base_t
|
|
13
|
+
from ... import field_values_t
|
|
13
14
|
from ... import recipes_t
|
|
14
15
|
from ... import response_t
|
|
15
16
|
|
|
@@ -47,6 +48,7 @@ class RecipeOutputValue:
|
|
|
47
48
|
value_str: typing.Optional[str] = None
|
|
48
49
|
value_curve: typing.Optional[CurveValues] = None
|
|
49
50
|
formatting: typing.Optional[recipes_t.RecipeAttributeFormatting] = None
|
|
51
|
+
field_values: typing.Optional[list[typing.Union[field_values_t.ArgumentValueRefName, field_values_t.ArgumentValueId]]] = None
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# flake8: noqa: F821
|
|
2
|
+
# ruff: noqa: E402 Q003
|
|
3
|
+
# fmt: off
|
|
4
|
+
# isort: skip_file
|
|
5
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
6
|
+
# Kept only for SDK backwards compatibility
|
|
7
|
+
from .queued_job_t import InvocationContextType as InvocationContextType
|
|
8
|
+
from .queued_job_t import InvocationContextBase as InvocationContextBase
|
|
9
|
+
from .queued_job_t import InvocationContextCron as InvocationContextCron
|
|
10
|
+
from .queued_job_t import InvocationContextManual as InvocationContextManual
|
|
11
|
+
from .queued_job_t import InvocationContextWebhook as InvocationContextWebhook
|
|
12
|
+
from .queued_job_t import InvocationContext as InvocationContext
|
|
13
|
+
from .queued_job_t import QueuedJobPayload as QueuedJobPayload
|
|
14
|
+
from .queued_job_t import QueuedJobResult as QueuedJobResult
|
|
15
|
+
from .queued_job_t import QueuedJob as QueuedJob
|
|
16
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
2
|
+
# flake8: noqa: F821
|
|
3
|
+
# ruff: noqa: E402 Q003
|
|
4
|
+
# fmt: off
|
|
5
|
+
# isort: skip_file
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import typing # noqa: F401
|
|
8
|
+
import datetime # noqa: F401
|
|
9
|
+
from decimal import Decimal # noqa: F401
|
|
10
|
+
from pkgs.strenum_compat import StrEnum
|
|
11
|
+
import dataclasses
|
|
12
|
+
from pkgs.serialization import serial_class
|
|
13
|
+
from pkgs.serialization import serial_union_annotation
|
|
14
|
+
from . import base_t
|
|
15
|
+
from . import job_definition_t
|
|
16
|
+
|
|
17
|
+
__all__: list[str] = [
|
|
18
|
+
"InvocationContext",
|
|
19
|
+
"InvocationContextBase",
|
|
20
|
+
"InvocationContextCron",
|
|
21
|
+
"InvocationContextManual",
|
|
22
|
+
"InvocationContextType",
|
|
23
|
+
"InvocationContextWebhook",
|
|
24
|
+
"QueuedJob",
|
|
25
|
+
"QueuedJobPayload",
|
|
26
|
+
"QueuedJobResult",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
31
|
+
class InvocationContextType(StrEnum):
|
|
32
|
+
CRON = "cron"
|
|
33
|
+
MANUAL = "manual"
|
|
34
|
+
WEBHOOK = "webhook"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
38
|
+
@dataclasses.dataclass(kw_only=True)
|
|
39
|
+
class InvocationContextBase:
|
|
40
|
+
type: InvocationContextType
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
44
|
+
@serial_class(
|
|
45
|
+
parse_require={"type"},
|
|
46
|
+
)
|
|
47
|
+
@dataclasses.dataclass(kw_only=True)
|
|
48
|
+
class InvocationContextCron(InvocationContextBase):
|
|
49
|
+
type: typing.Literal[InvocationContextType.CRON] = InvocationContextType.CRON
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
53
|
+
@serial_class(
|
|
54
|
+
parse_require={"type"},
|
|
55
|
+
)
|
|
56
|
+
@dataclasses.dataclass(kw_only=True)
|
|
57
|
+
class InvocationContextManual(InvocationContextBase):
|
|
58
|
+
type: typing.Literal[InvocationContextType.MANUAL] = InvocationContextType.MANUAL
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
62
|
+
@serial_class(
|
|
63
|
+
unconverted_values={"webhook_payload"},
|
|
64
|
+
parse_require={"type"},
|
|
65
|
+
)
|
|
66
|
+
@dataclasses.dataclass(kw_only=True)
|
|
67
|
+
class InvocationContextWebhook(InvocationContextBase):
|
|
68
|
+
type: typing.Literal[InvocationContextType.WEBHOOK] = InvocationContextType.WEBHOOK
|
|
69
|
+
webhook_payload: base_t.JsonValue
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
73
|
+
InvocationContext = typing.Annotated[
|
|
74
|
+
typing.Union[InvocationContextCron, InvocationContextManual, InvocationContextWebhook],
|
|
75
|
+
serial_union_annotation(
|
|
76
|
+
discriminator="type",
|
|
77
|
+
discriminator_map={
|
|
78
|
+
"cron": InvocationContextCron,
|
|
79
|
+
"manual": InvocationContextManual,
|
|
80
|
+
"webhook": InvocationContextWebhook,
|
|
81
|
+
},
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
87
|
+
@dataclasses.dataclass(kw_only=True)
|
|
88
|
+
class QueuedJobPayload:
|
|
89
|
+
invocation_context: InvocationContext
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
93
|
+
@dataclasses.dataclass(kw_only=True)
|
|
94
|
+
class QueuedJobResult:
|
|
95
|
+
queued_job_uuid: str
|
|
96
|
+
job_result: job_definition_t.JobResult
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
100
|
+
@dataclasses.dataclass(kw_only=True)
|
|
101
|
+
class QueuedJob:
|
|
102
|
+
queued_job_uuid: str
|
|
103
|
+
job_ref_name: str
|
|
104
|
+
num_attempts: int
|
|
105
|
+
submitted_at: datetime.datetime
|
|
106
|
+
payload: QueuedJobPayload
|
|
107
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
File without changes
|
{UncountablePythonSDK-0.0.69.dist-info → UncountablePythonSDK-0.0.71.dist-info}/top_level.txt
RENAMED
|
File without changes
|