UncountablePythonSDK 0.0.114__py3-none-any.whl → 0.0.115__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.
- examples/integration-server/jobs/materials_auto/example_http.py +35 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +6 -0
- pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +18 -5
- uncountable/core/environment.py +1 -1
- uncountable/integration/http_server/__init__.py +5 -0
- uncountable/integration/http_server/types.py +67 -0
- uncountable/integration/job.py +129 -5
- uncountable/integration/server.py +2 -2
- uncountable/integration/telemetry.py +1 -1
- uncountable/integration/webhook_server/entrypoint.py +37 -112
- uncountable/types/api/entity/lookup_entity.py +15 -1
- uncountable/types/client_base.py +49 -0
- uncountable/types/entity_t.py +2 -0
- uncountable/types/integration_server_t.py +2 -0
- uncountable/types/job_definition.py +2 -0
- uncountable/types/job_definition_t.py +25 -2
- {uncountablepythonsdk-0.0.114.dist-info → uncountablepythonsdk-0.0.115.dist-info}/METADATA +1 -1
- {uncountablepythonsdk-0.0.114.dist-info → uncountablepythonsdk-0.0.115.dist-info}/RECORD +20 -17
- {uncountablepythonsdk-0.0.114.dist-info → uncountablepythonsdk-0.0.115.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.114.dist-info → uncountablepythonsdk-0.0.115.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from uncountable.integration.http_server import (
|
|
4
|
+
GenericHttpRequest,
|
|
5
|
+
GenericHttpResponse,
|
|
6
|
+
)
|
|
7
|
+
from uncountable.integration.job import CustomHttpJob, register_job
|
|
8
|
+
from uncountable.types import job_definition_t
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(kw_only=True)
|
|
12
|
+
class ExampleWebhookPayload:
|
|
13
|
+
id: int
|
|
14
|
+
message: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register_job
|
|
18
|
+
class HttpExample(CustomHttpJob):
|
|
19
|
+
@staticmethod
|
|
20
|
+
def validate_request(
|
|
21
|
+
*,
|
|
22
|
+
request: GenericHttpRequest,
|
|
23
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
24
|
+
profile_meta: job_definition_t.ProfileMetadata,
|
|
25
|
+
) -> None:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def handle_request(
|
|
30
|
+
*,
|
|
31
|
+
request: GenericHttpRequest,
|
|
32
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
33
|
+
profile_meta: job_definition_t.ProfileMetadata,
|
|
34
|
+
) -> GenericHttpResponse:
|
|
35
|
+
return GenericHttpResponse(response="OK", status_code=200)
|
|
@@ -60,6 +60,12 @@ jobs:
|
|
|
60
60
|
executor:
|
|
61
61
|
type: script
|
|
62
62
|
import_path: example_wh
|
|
63
|
+
- id: example_http
|
|
64
|
+
type: custom_http
|
|
65
|
+
name: Custom HTTP
|
|
66
|
+
executor:
|
|
67
|
+
type: script
|
|
68
|
+
import_path: example_http
|
|
63
69
|
- id: example_runsheet_wh
|
|
64
70
|
type: webhook
|
|
65
71
|
name: Runsheet Webhook
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from io import StringIO
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import assert_never
|
|
6
7
|
|
|
7
8
|
from main.base.types import (
|
|
9
|
+
base_t,
|
|
8
10
|
ui_entry_actions_t,
|
|
9
11
|
)
|
|
10
|
-
from pkgs.argument_parser import snake_to_camel_case
|
|
11
12
|
from pkgs.serialization_util import serialize_for_api
|
|
12
13
|
from pkgs.type_spec import emit_typescript_util
|
|
13
14
|
from pkgs.type_spec.builder import (
|
|
@@ -45,8 +46,8 @@ def ui_entry_variable_to_type_spec_type(
|
|
|
45
46
|
match variable:
|
|
46
47
|
case ui_entry_actions_t.UiEntryActionVariableString():
|
|
47
48
|
return BaseTypeName.s_string
|
|
48
|
-
case ui_entry_actions_t.
|
|
49
|
-
return "
|
|
49
|
+
case ui_entry_actions_t.UiEntryActionVariableSingleEntity():
|
|
50
|
+
return "ObjectId"
|
|
50
51
|
case _:
|
|
51
52
|
assert_never(variable)
|
|
52
53
|
|
|
@@ -157,6 +158,15 @@ def emit_entry_action_definition(
|
|
|
157
158
|
)
|
|
158
159
|
|
|
159
160
|
|
|
161
|
+
def _validate_input(input: ui_entry_actions_t.UiEntryActionVariable) -> None:
|
|
162
|
+
if "_" in input.vs_var_name:
|
|
163
|
+
raise ValueError(f"Expected camelCase for variable {input.vs_var_name}")
|
|
164
|
+
if not re.fullmatch(base_t.REF_NAME_STRICT_REGEX, input.vs_var_name):
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Variable {input.vs_var_name} has invalid syntax. See REF_NAME_STRICT_REGEX"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
160
170
|
def emit_query_index(
|
|
161
171
|
ctx: emit_typescript_util.EmitTypescriptContext,
|
|
162
172
|
defn_infos: list[EntryActionTypeInfo],
|
|
@@ -182,6 +192,7 @@ def emit_query_index(
|
|
|
182
192
|
"type": BaseTypeName.s_object,
|
|
183
193
|
"properties": {
|
|
184
194
|
"value_spec_var": {"type": "String"},
|
|
195
|
+
"type": {"type": "ui_entry_actions.UiEntryActionDataType"},
|
|
185
196
|
"variable": {"type": "ui_entry_actions.UiEntryActionVariable"},
|
|
186
197
|
},
|
|
187
198
|
},
|
|
@@ -215,10 +226,12 @@ def emit_query_index(
|
|
|
215
226
|
for scope, defn in definitions.items():
|
|
216
227
|
inputs = []
|
|
217
228
|
outputs = []
|
|
218
|
-
for
|
|
229
|
+
for input in defn.inputs.values():
|
|
230
|
+
_validate_input(input)
|
|
219
231
|
inputs.append(
|
|
220
232
|
serialize_for_api({
|
|
221
|
-
"value_spec_var":
|
|
233
|
+
"value_spec_var": input.vs_var_name,
|
|
234
|
+
"type": input.type,
|
|
222
235
|
"variable": input,
|
|
223
236
|
})
|
|
224
237
|
)
|
uncountable/core/environment.py
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import functools
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from flask.wrappers import Response
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HttpException(Exception):
|
|
9
|
+
error_code: int
|
|
10
|
+
message: str
|
|
11
|
+
|
|
12
|
+
def __init__(self, *, error_code: int, message: str) -> None:
|
|
13
|
+
self.error_code = error_code
|
|
14
|
+
self.message = message
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def payload_failed_signature() -> "HttpException":
|
|
18
|
+
return HttpException(
|
|
19
|
+
error_code=401, message="webhook payload did not match signature"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def no_signature_passed() -> "HttpException":
|
|
24
|
+
return HttpException(error_code=400, message="missing signature")
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def body_parse_error() -> "HttpException":
|
|
28
|
+
return HttpException(error_code=400, message="body parse error")
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def unknown_error() -> "HttpException":
|
|
32
|
+
return HttpException(error_code=500, message="internal server error")
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def configuration_error(
|
|
36
|
+
message: str = "internal configuration error",
|
|
37
|
+
) -> "HttpException":
|
|
38
|
+
return HttpException(error_code=500, message=message)
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
return f"[{self.error_code}]: {self.message}"
|
|
42
|
+
|
|
43
|
+
def make_error_response(self) -> Response:
|
|
44
|
+
return Response(
|
|
45
|
+
status=self.error_code, response={"error": {"message": str(self)}}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(kw_only=True, frozen=True)
|
|
50
|
+
class GenericHttpRequest:
|
|
51
|
+
body_base64: str
|
|
52
|
+
headers: dict[str, str]
|
|
53
|
+
|
|
54
|
+
@functools.cached_property
|
|
55
|
+
def body_bytes(self) -> bytes:
|
|
56
|
+
return base64.b64decode(self.body_base64)
|
|
57
|
+
|
|
58
|
+
@functools.cached_property
|
|
59
|
+
def body_text(self) -> str:
|
|
60
|
+
return self.body_bytes.decode()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(kw_only=True)
|
|
64
|
+
class GenericHttpResponse:
|
|
65
|
+
response: str
|
|
66
|
+
status_code: int
|
|
67
|
+
headers: dict[str, str] | None = None
|
uncountable/integration/job.py
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import hmac
|
|
2
3
|
import typing
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
|
|
7
|
+
import simplejson
|
|
8
|
+
|
|
6
9
|
from pkgs.argument_parser import CachedParser
|
|
10
|
+
from pkgs.serialization_util import serialize_for_api
|
|
7
11
|
from uncountable.core.async_batch import AsyncBatchProcessor
|
|
8
12
|
from uncountable.core.client import Client
|
|
13
|
+
from uncountable.core.environment import get_local_admin_server_port
|
|
9
14
|
from uncountable.core.file_upload import FileUpload
|
|
15
|
+
from uncountable.integration.http_server import (
|
|
16
|
+
GenericHttpRequest,
|
|
17
|
+
GenericHttpResponse,
|
|
18
|
+
HttpException,
|
|
19
|
+
)
|
|
20
|
+
from uncountable.integration.queue_runner.command_server.command_client import (
|
|
21
|
+
send_job_queue_message,
|
|
22
|
+
)
|
|
23
|
+
from uncountable.integration.queue_runner.command_server.types import (
|
|
24
|
+
CommandServerException,
|
|
25
|
+
)
|
|
26
|
+
from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
|
|
10
27
|
from uncountable.integration.telemetry import JobLogger
|
|
11
|
-
from uncountable.types import
|
|
12
|
-
|
|
28
|
+
from uncountable.types import (
|
|
29
|
+
base_t,
|
|
30
|
+
entity_t,
|
|
31
|
+
job_definition_t,
|
|
32
|
+
queued_job_t,
|
|
33
|
+
webhook_job_t,
|
|
34
|
+
)
|
|
35
|
+
from uncountable.types.job_definition_t import (
|
|
36
|
+
HttpJobDefinitionBase,
|
|
37
|
+
JobDefinition,
|
|
38
|
+
JobResult,
|
|
39
|
+
ProfileMetadata,
|
|
40
|
+
)
|
|
13
41
|
|
|
14
42
|
|
|
15
43
|
@dataclass(kw_only=True)
|
|
@@ -26,9 +54,6 @@ class JobArguments:
|
|
|
26
54
|
CronJobArguments = JobArguments
|
|
27
55
|
|
|
28
56
|
|
|
29
|
-
PT = typing.TypeVar("PT")
|
|
30
|
-
|
|
31
|
-
|
|
32
57
|
class Job[PT](ABC):
|
|
33
58
|
_unc_job_registered: bool = False
|
|
34
59
|
|
|
@@ -63,6 +88,51 @@ class CronJob(Job):
|
|
|
63
88
|
WPT = typing.TypeVar("WPT")
|
|
64
89
|
|
|
65
90
|
|
|
91
|
+
@dataclass(kw_only=True)
|
|
92
|
+
class WebhookResponse:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class CustomHttpJob(Job[GenericHttpRequest]):
|
|
97
|
+
@property
|
|
98
|
+
def payload_type(self) -> type[GenericHttpRequest]:
|
|
99
|
+
return GenericHttpRequest
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def validate_request(
|
|
104
|
+
*,
|
|
105
|
+
request: GenericHttpRequest,
|
|
106
|
+
job_definition: HttpJobDefinitionBase,
|
|
107
|
+
profile_meta: ProfileMetadata,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Validate that the request is valid. If the request is invalid, raise an
|
|
111
|
+
exception.
|
|
112
|
+
"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def handle_request(
|
|
118
|
+
*,
|
|
119
|
+
request: GenericHttpRequest,
|
|
120
|
+
job_definition: HttpJobDefinitionBase,
|
|
121
|
+
profile_meta: ProfileMetadata,
|
|
122
|
+
) -> GenericHttpResponse:
|
|
123
|
+
"""
|
|
124
|
+
Handle the request synchronously. Normally this should just enqueue a job
|
|
125
|
+
and return immediately (see WebhookJob as an example).
|
|
126
|
+
"""
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
def run_outer(self, args: JobArguments) -> JobResult:
|
|
130
|
+
args.logger.log_warning(
|
|
131
|
+
message=f"Unexpected call to run_outer for CustomHttpJob: {args.job_definition.id}"
|
|
132
|
+
)
|
|
133
|
+
return JobResult(success=False)
|
|
134
|
+
|
|
135
|
+
|
|
66
136
|
class WebhookJob[WPT](Job[webhook_job_t.WebhookEventPayload]):
|
|
67
137
|
@property
|
|
68
138
|
def payload_type(self) -> type[webhook_job_t.WebhookEventPayload]:
|
|
@@ -72,6 +142,60 @@ class WebhookJob[WPT](Job[webhook_job_t.WebhookEventPayload]):
|
|
|
72
142
|
@abstractmethod
|
|
73
143
|
def webhook_payload_type(self) -> type[WPT]: ...
|
|
74
144
|
|
|
145
|
+
@staticmethod
|
|
146
|
+
def validate_request(
|
|
147
|
+
*,
|
|
148
|
+
request: GenericHttpRequest,
|
|
149
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
150
|
+
profile_meta: ProfileMetadata,
|
|
151
|
+
) -> None:
|
|
152
|
+
assert isinstance(job_definition, job_definition_t.WebhookJobDefinition)
|
|
153
|
+
signature_key = retrieve_secret(
|
|
154
|
+
profile_metadata=profile_meta,
|
|
155
|
+
secret_retrieval=job_definition.signature_key_secret,
|
|
156
|
+
)
|
|
157
|
+
passed_signature = request.headers.get("Uncountable-Webhook-Signature")
|
|
158
|
+
if passed_signature is None:
|
|
159
|
+
raise HttpException.no_signature_passed()
|
|
160
|
+
|
|
161
|
+
request_body_signature = hmac.new(
|
|
162
|
+
signature_key.encode("utf-8"), msg=request.body_bytes, digestmod="sha256"
|
|
163
|
+
).hexdigest()
|
|
164
|
+
|
|
165
|
+
if request_body_signature != passed_signature:
|
|
166
|
+
raise HttpException.payload_failed_signature()
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def handle_request(
|
|
170
|
+
*,
|
|
171
|
+
request: GenericHttpRequest,
|
|
172
|
+
job_definition: job_definition_t.HttpJobDefinitionBase,
|
|
173
|
+
profile_meta: ProfileMetadata, # noqa: ARG004
|
|
174
|
+
) -> GenericHttpResponse:
|
|
175
|
+
try:
|
|
176
|
+
request_body = simplejson.loads(request.body_text)
|
|
177
|
+
webhook_payload = typing.cast(base_t.JsonValue, request_body)
|
|
178
|
+
except (simplejson.JSONDecodeError, ValueError) as e:
|
|
179
|
+
raise HttpException.body_parse_error() from e
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
send_job_queue_message(
|
|
183
|
+
job_ref_name=job_definition.id,
|
|
184
|
+
payload=queued_job_t.QueuedJobPayload(
|
|
185
|
+
invocation_context=queued_job_t.InvocationContextWebhook(
|
|
186
|
+
webhook_payload=webhook_payload
|
|
187
|
+
)
|
|
188
|
+
),
|
|
189
|
+
port=get_local_admin_server_port(),
|
|
190
|
+
)
|
|
191
|
+
except CommandServerException as e:
|
|
192
|
+
raise HttpException.unknown_error() from e
|
|
193
|
+
|
|
194
|
+
return GenericHttpResponse(
|
|
195
|
+
response=simplejson.dumps(serialize_for_api(WebhookResponse())),
|
|
196
|
+
status_code=200,
|
|
197
|
+
)
|
|
198
|
+
|
|
75
199
|
def run_outer(self, args: JobArguments) -> JobResult:
|
|
76
200
|
webhook_body = self.get_payload(args.payload)
|
|
77
201
|
inner_payload = CachedParser(self.webhook_payload_type).parse_api(
|
|
@@ -16,7 +16,7 @@ from uncountable.integration.telemetry import Logger
|
|
|
16
16
|
from uncountable.types import base_t, job_definition_t
|
|
17
17
|
from uncountable.types.job_definition_t import (
|
|
18
18
|
CronJobDefinition,
|
|
19
|
-
|
|
19
|
+
HttpJobDefinitionBase,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
_MAX_APSCHEDULER_CONCURRENT_JOBS = 1
|
|
@@ -86,7 +86,7 @@ class IntegrationServer:
|
|
|
86
86
|
misfire_grace_time=None,
|
|
87
87
|
**job_opts,
|
|
88
88
|
)
|
|
89
|
-
case
|
|
89
|
+
case HttpJobDefinitionBase():
|
|
90
90
|
pass
|
|
91
91
|
case _:
|
|
92
92
|
assert_never(job_defn)
|
|
@@ -177,7 +177,7 @@ class JobLogger(Logger):
|
|
|
177
177
|
patched_attributes["job.definition.cron_spec"] = (
|
|
178
178
|
self.job_definition.cron_spec
|
|
179
179
|
)
|
|
180
|
-
case job_definition_t.
|
|
180
|
+
case job_definition_t.HttpJobDefinitionBase():
|
|
181
181
|
pass
|
|
182
182
|
case _:
|
|
183
183
|
assert_never(self.job_definition)
|
|
@@ -1,146 +1,71 @@
|
|
|
1
|
-
import
|
|
2
|
-
import typing
|
|
3
|
-
from dataclasses import dataclass
|
|
1
|
+
import base64
|
|
4
2
|
|
|
5
3
|
import flask
|
|
6
|
-
import simplejson
|
|
7
4
|
from flask.typing import ResponseReturnValue
|
|
8
|
-
from flask.wrappers import Response
|
|
9
5
|
from opentelemetry.trace import get_current_span
|
|
10
6
|
from uncountable.core.environment import (
|
|
11
|
-
|
|
7
|
+
get_http_server_port,
|
|
12
8
|
get_server_env,
|
|
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
9
|
)
|
|
10
|
+
from uncountable.integration.executors.script_executor import resolve_script_executor
|
|
11
|
+
from uncountable.integration.http_server import GenericHttpRequest, HttpException
|
|
12
|
+
from uncountable.integration.job import CustomHttpJob, WebhookJob
|
|
21
13
|
from uncountable.integration.scan_profiles import load_profiles
|
|
22
|
-
from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
|
|
23
14
|
from uncountable.integration.telemetry import Logger
|
|
24
|
-
from uncountable.types import
|
|
25
|
-
|
|
26
|
-
from pkgs.argument_parser import CachedParser
|
|
15
|
+
from uncountable.types import job_definition_t
|
|
27
16
|
|
|
28
17
|
app = flask.Flask(__name__)
|
|
29
18
|
|
|
30
19
|
|
|
31
|
-
@dataclass(kw_only=True)
|
|
32
|
-
class WebhookResponse:
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
webhook_payload_parser = CachedParser(webhook_job_t.WebhookEventBody)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class WebhookException(BaseException):
|
|
40
|
-
error_code: int
|
|
41
|
-
message: str
|
|
42
|
-
|
|
43
|
-
def __init__(self, *, error_code: int, message: str) -> None:
|
|
44
|
-
self.error_code = error_code
|
|
45
|
-
self.message = message
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def payload_failed_signature() -> "WebhookException":
|
|
49
|
-
return WebhookException(
|
|
50
|
-
error_code=401, message="webhook payload did not match signature"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
@staticmethod
|
|
54
|
-
def no_signature_passed() -> "WebhookException":
|
|
55
|
-
return WebhookException(error_code=400, message="missing signature")
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def body_parse_error() -> "WebhookException":
|
|
59
|
-
return WebhookException(error_code=400, message="body parse error")
|
|
60
|
-
|
|
61
|
-
@staticmethod
|
|
62
|
-
def unknown_error() -> "WebhookException":
|
|
63
|
-
return WebhookException(error_code=500, message="internal server error")
|
|
64
|
-
|
|
65
|
-
def __str__(self) -> str:
|
|
66
|
-
return f"[{self.error_code}]: {self.message}"
|
|
67
|
-
|
|
68
|
-
def make_error_response(self) -> Response:
|
|
69
|
-
return Response(
|
|
70
|
-
status=self.error_code, response={"error": {"message": str(self)}}
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _parse_webhook_payload(
|
|
75
|
-
*, raw_request_body: bytes, signature_key: str, passed_signature: str
|
|
76
|
-
) -> base_t.JsonValue:
|
|
77
|
-
request_body_signature = hmac.new(
|
|
78
|
-
signature_key.encode("utf-8"), msg=raw_request_body, digestmod="sha256"
|
|
79
|
-
).hexdigest()
|
|
80
|
-
|
|
81
|
-
if request_body_signature != passed_signature:
|
|
82
|
-
raise WebhookException.payload_failed_signature()
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
request_body = simplejson.loads(raw_request_body.decode())
|
|
86
|
-
return typing.cast(base_t.JsonValue, request_body)
|
|
87
|
-
except (simplejson.JSONDecodeError, ValueError) as e:
|
|
88
|
-
raise WebhookException.body_parse_error() from e
|
|
89
|
-
|
|
90
|
-
|
|
91
20
|
def register_route(
|
|
92
21
|
*,
|
|
93
22
|
server_logger: Logger,
|
|
94
23
|
profile_meta: job_definition_t.ProfileMetadata,
|
|
95
|
-
job: job_definition_t.
|
|
24
|
+
job: job_definition_t.HttpJobDefinitionBase,
|
|
96
25
|
) -> None:
|
|
97
26
|
route = f"/{profile_meta.name}/{job.id}"
|
|
98
27
|
|
|
99
|
-
def
|
|
28
|
+
def handle_request() -> ResponseReturnValue:
|
|
100
29
|
with server_logger.push_scope(route):
|
|
101
30
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
31
|
+
if not isinstance(job.executor, job_definition_t.JobExecutorScript):
|
|
32
|
+
raise HttpException.configuration_error(
|
|
33
|
+
message="[internal] http job must use a script executor"
|
|
34
|
+
)
|
|
35
|
+
job_instance = resolve_script_executor(
|
|
36
|
+
executor=job.executor, profile_metadata=profile_meta
|
|
105
37
|
)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
38
|
+
if not isinstance(job_instance, (CustomHttpJob, WebhookJob)):
|
|
39
|
+
raise HttpException.configuration_error(
|
|
40
|
+
message="[internal] http job must descend from CustomHttpJob"
|
|
41
|
+
)
|
|
42
|
+
http_request = GenericHttpRequest(
|
|
43
|
+
body_base64=base64.b64encode(flask.request.get_data()).decode(),
|
|
44
|
+
headers=dict(flask.request.headers),
|
|
109
45
|
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
signature_key=signature_key,
|
|
116
|
-
passed_signature=passed_signature,
|
|
46
|
+
job_instance.validate_request(
|
|
47
|
+
request=http_request, job_definition=job, profile_meta=profile_meta
|
|
48
|
+
)
|
|
49
|
+
http_response = job_instance.handle_request(
|
|
50
|
+
request=http_request, job_definition=job, profile_meta=profile_meta
|
|
117
51
|
)
|
|
118
52
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
),
|
|
127
|
-
port=get_local_admin_server_port(),
|
|
128
|
-
)
|
|
129
|
-
except CommandServerException as e:
|
|
130
|
-
raise WebhookException.unknown_error() from e
|
|
131
|
-
|
|
132
|
-
return flask.jsonify(WebhookResponse())
|
|
133
|
-
except WebhookException as e:
|
|
53
|
+
return flask.make_response(
|
|
54
|
+
http_response.response,
|
|
55
|
+
http_response.status_code,
|
|
56
|
+
http_response.headers,
|
|
57
|
+
)
|
|
58
|
+
except HttpException as e:
|
|
134
59
|
server_logger.log_exception(e)
|
|
135
60
|
return e.make_error_response()
|
|
136
61
|
except Exception as e:
|
|
137
62
|
server_logger.log_exception(e)
|
|
138
|
-
return
|
|
63
|
+
return HttpException.unknown_error().make_error_response()
|
|
139
64
|
|
|
140
65
|
app.add_url_rule(
|
|
141
66
|
route,
|
|
142
|
-
endpoint=f"
|
|
143
|
-
view_func=
|
|
67
|
+
endpoint=f"handle_request_{job.id}",
|
|
68
|
+
view_func=handle_request,
|
|
144
69
|
methods=["POST"],
|
|
145
70
|
)
|
|
146
71
|
|
|
@@ -152,7 +77,7 @@ def main() -> None:
|
|
|
152
77
|
for profile_metadata in profiles:
|
|
153
78
|
server_logger = Logger(get_current_span())
|
|
154
79
|
for job in profile_metadata.jobs:
|
|
155
|
-
if isinstance(job, job_definition_t.
|
|
80
|
+
if isinstance(job, job_definition_t.HttpJobDefinitionBase):
|
|
156
81
|
register_route(
|
|
157
82
|
server_logger=server_logger, profile_meta=profile_metadata, job=job
|
|
158
83
|
)
|
|
@@ -164,7 +89,7 @@ main()
|
|
|
164
89
|
if __name__ == "__main__":
|
|
165
90
|
app.run(
|
|
166
91
|
host="0.0.0.0",
|
|
167
|
-
port=
|
|
92
|
+
port=get_http_server_port(),
|
|
168
93
|
debug=get_server_env() == "playground",
|
|
169
94
|
exclude_patterns=[],
|
|
170
95
|
)
|
|
@@ -21,6 +21,7 @@ __all__: list[str] = [
|
|
|
21
21
|
"Data",
|
|
22
22
|
"ENDPOINT_METHOD",
|
|
23
23
|
"ENDPOINT_PATH",
|
|
24
|
+
"LookupEntityCompositeFieldValues",
|
|
24
25
|
"LookupEntityFieldValue",
|
|
25
26
|
"LookupEntityQuery",
|
|
26
27
|
"LookupEntityQueryBase",
|
|
@@ -35,6 +36,7 @@ ENDPOINT_PATH = "api/external/entity/lookup_entity"
|
|
|
35
36
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
36
37
|
class LookupEntityQueryType(StrEnum):
|
|
37
38
|
FIELD_VALUE = "field_value"
|
|
39
|
+
COMPOSITE_FIELD_VALUES = "composite_field_values"
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -69,14 +71,26 @@ class LookupEntityFieldValue:
|
|
|
69
71
|
value: LookupFieldArgumentValue
|
|
70
72
|
|
|
71
73
|
|
|
74
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
75
|
+
@serial_class(
|
|
76
|
+
named_type_path="sdk.api.entity.lookup_entity.LookupEntityCompositeFieldValues",
|
|
77
|
+
parse_require={"type"},
|
|
78
|
+
)
|
|
79
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
80
|
+
class LookupEntityCompositeFieldValues:
|
|
81
|
+
type: typing.Literal[LookupEntityQueryType.COMPOSITE_FIELD_VALUES] = LookupEntityQueryType.COMPOSITE_FIELD_VALUES
|
|
82
|
+
values: list[LookupFieldArgumentValue]
|
|
83
|
+
|
|
84
|
+
|
|
72
85
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
73
86
|
LookupEntityQuery = typing.Annotated[
|
|
74
|
-
|
|
87
|
+
LookupEntityFieldValue | LookupEntityCompositeFieldValues,
|
|
75
88
|
serial_union_annotation(
|
|
76
89
|
named_type_path="sdk.api.entity.lookup_entity.LookupEntityQuery",
|
|
77
90
|
discriminator="type",
|
|
78
91
|
discriminator_map={
|
|
79
92
|
"field_value": LookupEntityFieldValue,
|
|
93
|
+
"composite_field_values": LookupEntityCompositeFieldValues,
|
|
80
94
|
},
|
|
81
95
|
),
|
|
82
96
|
]
|
uncountable/types/client_base.py
CHANGED
|
@@ -22,6 +22,7 @@ import uncountable.types.api.chemical.convert_chemical_formats as convert_chemic
|
|
|
22
22
|
import uncountable.types.api.entity.create_entities as create_entities_t
|
|
23
23
|
import uncountable.types.api.entity.create_entity as create_entity_t
|
|
24
24
|
import uncountable.types.api.inputs.create_inputs as create_inputs_t
|
|
25
|
+
import uncountable.types.api.recipes.create_mix_order as create_mix_order_t
|
|
25
26
|
import uncountable.types.api.entity.create_or_update_entity as create_or_update_entity_t
|
|
26
27
|
import uncountable.types.api.recipes.create_recipe as create_recipe_t
|
|
27
28
|
import uncountable.types.api.recipe_links.create_recipe_link as create_recipe_link_t
|
|
@@ -31,6 +32,8 @@ import uncountable.types.api.recipes.edit_recipe_inputs as edit_recipe_inputs_t
|
|
|
31
32
|
from uncountable.types import entity_t
|
|
32
33
|
import uncountable.types.api.batch.execute_batch as execute_batch_t
|
|
33
34
|
import uncountable.types.api.batch.execute_batch_load_async as execute_batch_load_async_t
|
|
35
|
+
import uncountable.types.api.entity.export_entities as export_entities_t
|
|
36
|
+
from uncountable.types import exports_t
|
|
34
37
|
from uncountable.types import field_values_t
|
|
35
38
|
from uncountable.types import generic_upload_t
|
|
36
39
|
import uncountable.types.api.recipes.get_column_calculation_values as get_column_calculation_values_t
|
|
@@ -382,6 +385,26 @@ class ClientMethods(ABC):
|
|
|
382
385
|
)
|
|
383
386
|
return self.do_request(api_request=api_request, return_type=create_inputs_t.Data)
|
|
384
387
|
|
|
388
|
+
def create_mix_order(
|
|
389
|
+
self,
|
|
390
|
+
*,
|
|
391
|
+
recipe_key: identifier_t.IdentifierKey,
|
|
392
|
+
recipe_workflow_step_identifier: recipe_workflow_steps_t.RecipeWorkflowStepIdentifier,
|
|
393
|
+
) -> create_mix_order_t.Data:
|
|
394
|
+
"""Creates mix order on a recipe workflow step
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
args = create_mix_order_t.Arguments(
|
|
398
|
+
recipe_key=recipe_key,
|
|
399
|
+
recipe_workflow_step_identifier=recipe_workflow_step_identifier,
|
|
400
|
+
)
|
|
401
|
+
api_request = APIRequest(
|
|
402
|
+
method=create_mix_order_t.ENDPOINT_METHOD,
|
|
403
|
+
endpoint=create_mix_order_t.ENDPOINT_PATH,
|
|
404
|
+
args=args,
|
|
405
|
+
)
|
|
406
|
+
return self.do_request(api_request=api_request, return_type=create_mix_order_t.Data)
|
|
407
|
+
|
|
385
408
|
def create_or_update_entity(
|
|
386
409
|
self,
|
|
387
410
|
*,
|
|
@@ -576,6 +599,32 @@ class ClientMethods(ABC):
|
|
|
576
599
|
)
|
|
577
600
|
return self.do_request(api_request=api_request, return_type=execute_batch_load_async_t.Data)
|
|
578
601
|
|
|
602
|
+
def export_entities(
|
|
603
|
+
self,
|
|
604
|
+
*,
|
|
605
|
+
config_key: identifier_t.IdentifierKey,
|
|
606
|
+
type: exports_t.ExportType = exports_t.ExportType.EXCEL,
|
|
607
|
+
client_timezone: exports_t.ListingExportUserTimezone | None = None,
|
|
608
|
+
limit: int | None = None,
|
|
609
|
+
) -> export_entities_t.Data:
|
|
610
|
+
"""Uses a structured loading configuration to export entities in the system. This endpoint is asynchronous, and returns the job ID that can be used to query the status of the export.
|
|
611
|
+
|
|
612
|
+
:param config_key: The configuration reference for the listing config
|
|
613
|
+
:param limit: The number of data points to return. If not filled in, all filtered entities will be included in the export.
|
|
614
|
+
"""
|
|
615
|
+
args = export_entities_t.Arguments(
|
|
616
|
+
config_key=config_key,
|
|
617
|
+
client_timezone=client_timezone,
|
|
618
|
+
limit=limit,
|
|
619
|
+
type=type,
|
|
620
|
+
)
|
|
621
|
+
api_request = APIRequest(
|
|
622
|
+
method=export_entities_t.ENDPOINT_METHOD,
|
|
623
|
+
endpoint=export_entities_t.ENDPOINT_PATH,
|
|
624
|
+
args=args,
|
|
625
|
+
)
|
|
626
|
+
return self.do_request(api_request=api_request, return_type=export_entities_t.Data)
|
|
627
|
+
|
|
579
628
|
def get_column_calculation_values(
|
|
580
629
|
self,
|
|
581
630
|
*,
|
uncountable/types/entity_t.py
CHANGED
|
@@ -131,6 +131,7 @@ __all__: list[str] = [
|
|
|
131
131
|
"recipe_audit_log": "Experiment Audit Log",
|
|
132
132
|
"recipe_calculation": "Recipe Calculation",
|
|
133
133
|
"recipe_check": "Experiment Check",
|
|
134
|
+
"recipe_component_witness": "Recipe Component Witness",
|
|
134
135
|
"recipe_export": "Recipe Export",
|
|
135
136
|
"recipe_goal": "Experiment Goal",
|
|
136
137
|
"recipe_ingredient": "Recipe Ingredient",
|
|
@@ -312,6 +313,7 @@ class EntityType(StrEnum):
|
|
|
312
313
|
RECIPE_AUDIT_LOG = "recipe_audit_log"
|
|
313
314
|
RECIPE_CALCULATION = "recipe_calculation"
|
|
314
315
|
RECIPE_CHECK = "recipe_check"
|
|
316
|
+
RECIPE_COMPONENT_WITNESS = "recipe_component_witness"
|
|
315
317
|
RECIPE_EXPORT = "recipe_export"
|
|
316
318
|
RECIPE_GOAL = "recipe_goal"
|
|
317
319
|
RECIPE_INGREDIENT = "recipe_ingredient"
|
|
@@ -18,6 +18,8 @@ from .job_definition_t import JobExecutor as JobExecutor
|
|
|
18
18
|
from .job_definition_t import JobLoggingSettings as JobLoggingSettings
|
|
19
19
|
from .job_definition_t import JobDefinitionBase as JobDefinitionBase
|
|
20
20
|
from .job_definition_t import CronJobDefinition as CronJobDefinition
|
|
21
|
+
from .job_definition_t import HttpJobDefinitionBase as HttpJobDefinitionBase
|
|
22
|
+
from .job_definition_t import CustomHttpJobDefinition as CustomHttpJobDefinition
|
|
21
23
|
from .job_definition_t import WebhookJobDefinition as WebhookJobDefinition
|
|
22
24
|
from .job_definition_t import JobDefinition as JobDefinition
|
|
23
25
|
from .job_definition_t import ProfileDefinition as ProfileDefinition
|
|
@@ -20,11 +20,13 @@ from . import secret_retrieval_t
|
|
|
20
20
|
|
|
21
21
|
__all__: list[str] = [
|
|
22
22
|
"CronJobDefinition",
|
|
23
|
+
"CustomHttpJobDefinition",
|
|
23
24
|
"GenericUploadDataSource",
|
|
24
25
|
"GenericUploadDataSourceBase",
|
|
25
26
|
"GenericUploadDataSourceS3",
|
|
26
27
|
"GenericUploadDataSourceSFTP",
|
|
27
28
|
"GenericUploadDataSourceType",
|
|
29
|
+
"HttpJobDefinitionBase",
|
|
28
30
|
"JobDefinition",
|
|
29
31
|
"JobDefinitionBase",
|
|
30
32
|
"JobDefinitionType",
|
|
@@ -46,6 +48,7 @@ __all__: list[str] = [
|
|
|
46
48
|
class JobDefinitionType(StrEnum):
|
|
47
49
|
CRON = "cron"
|
|
48
50
|
WEBHOOK = "webhook"
|
|
51
|
+
CUSTOM_HTTP = "custom_http"
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -201,25 +204,45 @@ class CronJobDefinition(JobDefinitionBase):
|
|
|
201
204
|
cron_spec: str
|
|
202
205
|
|
|
203
206
|
|
|
207
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
208
|
+
@serial_class(
|
|
209
|
+
named_type_path="sdk.job_definition.HttpJobDefinitionBase",
|
|
210
|
+
)
|
|
211
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
212
|
+
class HttpJobDefinitionBase(JobDefinitionBase):
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
217
|
+
@serial_class(
|
|
218
|
+
named_type_path="sdk.job_definition.CustomHttpJobDefinition",
|
|
219
|
+
parse_require={"type"},
|
|
220
|
+
)
|
|
221
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
222
|
+
class CustomHttpJobDefinition(HttpJobDefinitionBase):
|
|
223
|
+
type: typing.Literal[JobDefinitionType.CUSTOM_HTTP] = JobDefinitionType.CUSTOM_HTTP
|
|
224
|
+
|
|
225
|
+
|
|
204
226
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
205
227
|
@serial_class(
|
|
206
228
|
named_type_path="sdk.job_definition.WebhookJobDefinition",
|
|
207
229
|
parse_require={"type"},
|
|
208
230
|
)
|
|
209
231
|
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
210
|
-
class WebhookJobDefinition(
|
|
232
|
+
class WebhookJobDefinition(HttpJobDefinitionBase):
|
|
211
233
|
type: typing.Literal[JobDefinitionType.WEBHOOK] = JobDefinitionType.WEBHOOK
|
|
212
234
|
signature_key_secret: secret_retrieval_t.SecretRetrieval
|
|
213
235
|
|
|
214
236
|
|
|
215
237
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
216
238
|
JobDefinition = typing.Annotated[
|
|
217
|
-
CronJobDefinition | WebhookJobDefinition,
|
|
239
|
+
CronJobDefinition | CustomHttpJobDefinition | WebhookJobDefinition,
|
|
218
240
|
serial_union_annotation(
|
|
219
241
|
named_type_path="sdk.job_definition.JobDefinition",
|
|
220
242
|
discriminator="type",
|
|
221
243
|
discriminator_map={
|
|
222
244
|
"cron": CronJobDefinition,
|
|
245
|
+
"custom_http": CustomHttpJobDefinition,
|
|
223
246
|
"webhook": WebhookJobDefinition,
|
|
224
247
|
},
|
|
225
248
|
),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UncountablePythonSDK
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.115
|
|
4
4
|
Summary: Uncountable SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
|
|
@@ -26,9 +26,10 @@ examples/upload_files.py,sha256=qMaSvMSdTMPOOP55y1AwEurc0SOdZAMvEydlqJPsGpg,432
|
|
|
26
26
|
examples/integration-server/pyproject.toml,sha256=i4Px7I__asDvP4WlAd2PncfRRQ-U4t5xp0tqT9YYs3s,9149
|
|
27
27
|
examples/integration-server/jobs/materials_auto/concurrent_cron.py,sha256=xsK3H9ZEaniedC2nJUB0rqOcFI8y-ojfl_nLSJb9AMM,312
|
|
28
28
|
examples/integration-server/jobs/materials_auto/example_cron.py,sha256=7VVQ-UJsq3DbGpN3XPnorRVZYo-vCwbfSU3VVDluIzA,699
|
|
29
|
+
examples/integration-server/jobs/materials_auto/example_http.py,sha256=h97_IBC5EQiAUA4A5xyPpgFIqPTZWKIhMpmVAgVZEBE,941
|
|
29
30
|
examples/integration-server/jobs/materials_auto/example_runsheet_wh.py,sha256=_wILTnbzzLf9zrcQb_KQKytxxcya1ej6MqQnoUSS4fA,1180
|
|
30
31
|
examples/integration-server/jobs/materials_auto/example_wh.py,sha256=PN-skP27yJwDZboWk5g5EZEc3AKfVayQLfnopjsDKJc,659
|
|
31
|
-
examples/integration-server/jobs/materials_auto/profile.yaml,sha256=
|
|
32
|
+
examples/integration-server/jobs/materials_auto/profile.yaml,sha256=MNKRn09iaiNF8dxOZ7Y6558UW6aCZ-9l9hQAwzYN8zs,1685
|
|
32
33
|
pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
34
|
pkgs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
35
|
pkgs/argument_parser/__init__.py,sha256=VWUOOtJ-ueRF2lkIJzgQe4xhBKR9IPkgf9vY28nF35s,870
|
|
@@ -84,7 +85,7 @@ pkgs/type_spec/parts/base.ts.prepart,sha256=2FJJvpg2olCcavxj0nbYWdwKl6KeScour2Jj
|
|
|
84
85
|
pkgs/type_spec/type_info/__main__.py,sha256=TLNvCHGcmaj_8Sj5bAQNpuNaaw2dpDzoFDWZds0V4Qo,1002
|
|
85
86
|
pkgs/type_spec/type_info/emit_type_info.py,sha256=xRjZiwDDii4Bq8yVfcgE8YFechoKAcGmYXBk3Dq-K-s,15387
|
|
86
87
|
pkgs/type_spec/ui_entry_actions/__init__.py,sha256=WiHE_BexOEZWbkkbD7EnFau1aMLNmfgQywG9PTQNCkw,135
|
|
87
|
-
pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py,sha256=
|
|
88
|
+
pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py,sha256=IPBdVfJReLLGVaZOkb0QYGGrm73JqMXuAGSbeoBVbDg,9477
|
|
88
89
|
pkgs/type_spec/value_spec/__init__.py,sha256=Z-grlcZtxAfEXhPHsK0nD7PFLGsv4eqvunaPN7_TA84,83
|
|
89
90
|
pkgs/type_spec/value_spec/__main__.py,sha256=oM5lcV6Hv_03okjtfWn2fzSHsarFVa9ArU_g02XnQJw,8879
|
|
90
91
|
pkgs/type_spec/value_spec/convert_type.py,sha256=OvP7dwUMHXNHVXWYT4jkaYJ96S3a2SnFuC_iMdYVB7s,2927
|
|
@@ -95,7 +96,7 @@ uncountable/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
95
96
|
uncountable/core/__init__.py,sha256=RFv0kO6rKFf1PtBPu83hCGmxqkJamRtsgQ9_-ztw7tA,341
|
|
96
97
|
uncountable/core/async_batch.py,sha256=9pYGFzVCQXt8059qFHgutweGIFPquJ5Xfq6NT5P-1K0,1206
|
|
97
98
|
uncountable/core/client.py,sha256=nzUkHIVbYQmMUEBJhQdLzRC70eeYOACEbKSRY4w0Jlw,13367
|
|
98
|
-
uncountable/core/environment.py,sha256=
|
|
99
|
+
uncountable/core/environment.py,sha256=4gdJB0ZhRxKlqSKLaE4vUvEUGZ5fy8IAwXcGDRdYt7E,1037
|
|
99
100
|
uncountable/core/file_upload.py,sha256=bgvXk9vfF5qlhy2NAUcEEG7Q7i-c1wr2HrpaWD7HldU,4516
|
|
100
101
|
uncountable/core/types.py,sha256=s2CjqYJpsmbC7xMwxxT7kJ_V9bwokrjjWVVjpMcQpKI,333
|
|
101
102
|
uncountable/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -103,11 +104,11 @@ uncountable/integration/cli.py,sha256=h3RE0l1SdjkveOKeY2amlmrJppK4HEQJXk8VG9UJRW
|
|
|
103
104
|
uncountable/integration/construct_client.py,sha256=I53mGcdS88hba3HFwgXmWQaTd1d5u0jWNSwyc_vlVsQ,1937
|
|
104
105
|
uncountable/integration/cron.py,sha256=6eH-kIs3sdYPCyb62_L2M7U_uQTdMTdwY5hreEJb0hw,887
|
|
105
106
|
uncountable/integration/entrypoint.py,sha256=BHOYPQgKvZE6HG8Rv15MkdYl8lRkvfDgv1OdLo0oQ9Q,433
|
|
106
|
-
uncountable/integration/job.py,sha256=
|
|
107
|
+
uncountable/integration/job.py,sha256=X8mNoy01Q6h26eNuPi50XwV6YLgaqYCGWt2PFDEddZU,7111
|
|
107
108
|
uncountable/integration/scan_profiles.py,sha256=RHBmPc5E10YZzf4cmglwrn2yAy0jHBhQ-P_GlAk2TeU,2919
|
|
108
109
|
uncountable/integration/scheduler.py,sha256=t75ANJN21DElUFvEdtgueTluF7y17jTtBDDF8f3NRDM,4812
|
|
109
|
-
uncountable/integration/server.py,sha256=
|
|
110
|
-
uncountable/integration/telemetry.py,sha256=
|
|
110
|
+
uncountable/integration/server.py,sha256=lL9zmzqkQRf7V1fBT20SvIy-7ryz5hFf7DF4QX4pj1E,4699
|
|
111
|
+
uncountable/integration/telemetry.py,sha256=VunRaMC9ykPaxUE_s6SarQieKrGNtTSyAr9omc315OI,7419
|
|
111
112
|
uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
112
113
|
uncountable/integration/db/connect.py,sha256=mE3bdV0huclH2iT_dXCQdRL4LkjIuf_myAR64RTWXEs,498
|
|
113
114
|
uncountable/integration/db/session.py,sha256=96cGQXpe6IugBTdSsjdP0S5yhJ6toSmbVB6qhc3FJzE,693
|
|
@@ -115,6 +116,8 @@ uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
|
|
|
115
116
|
uncountable/integration/executors/executors.py,sha256=Kzisp1eKufGCWrHIw4mmAj-l1UQ2oJsJR7I-_mksnVs,5441
|
|
116
117
|
uncountable/integration/executors/generic_upload_executor.py,sha256=z0HfvuBR1wUbRpMVxJQ5Jlzbdk8G7YmAGENmze85Tr8,12076
|
|
117
118
|
uncountable/integration/executors/script_executor.py,sha256=BBQ9f0l7uH2hgKf60jtm-pONzwk-EeOhM2qBAbv_URo,846
|
|
119
|
+
uncountable/integration/http_server/__init__.py,sha256=WY2HMcL0UCAGYv8y6Pz-j0azbDGXwubFF21EH_zNPkc,189
|
|
120
|
+
uncountable/integration/http_server/types.py,sha256=zVXXN8FPstrF9qFduwQBtxPG8I4AOK41nXAnxrtSgxw,1832
|
|
118
121
|
uncountable/integration/queue_runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
119
122
|
uncountable/integration/queue_runner/job_scheduler.py,sha256=lLP3R8RVE_4CJ9D-AsJSsZVciKCISsvgUMRs4tIZZpE,6557
|
|
120
123
|
uncountable/integration/queue_runner/queue_runner.py,sha256=0BmYu5zHdothTevGsB-nXg6MBd1UD-WkP3h1WCKMdQg,710
|
|
@@ -135,7 +138,7 @@ uncountable/integration/queue_runner/datastore/interface.py,sha256=j4D-zVvLq-48V
|
|
|
135
138
|
uncountable/integration/queue_runner/datastore/model.py,sha256=8-RI5A2yPZVGBLWINVmMd6VOl_oHtqGtnaNXcapAChw,577
|
|
136
139
|
uncountable/integration/secret_retrieval/__init__.py,sha256=3QXVj35w8rRMxVvmmsViFYDi3lcb3g70incfalOEm6o,87
|
|
137
140
|
uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=LBEf18KHtXZxg-ZZ80stJ1vW39AWf0CQllP6pNu3Eq8,2994
|
|
138
|
-
uncountable/integration/webhook_server/entrypoint.py,sha256=
|
|
141
|
+
uncountable/integration/webhook_server/entrypoint.py,sha256=NQawXl_JCRojdVniS5RF7dobQQKW_Wy03bwy-uXknuA,3441
|
|
139
142
|
uncountable/types/__init__.py,sha256=eqeDMCXTr9Mwo10RSGMa4znfu_7TVp_0gYJxPKmLCfQ,9990
|
|
140
143
|
uncountable/types/async_batch.py,sha256=yCCWrrLQfxXVqZp-KskxLBNkNmuELdz4PJjx8ULppgs,662
|
|
141
144
|
uncountable/types/async_batch_processor.py,sha256=Y8rp-GtuhwUBg18yb5T7bQpb48vsQpACGrvaIiFYLrU,21765
|
|
@@ -150,7 +153,7 @@ uncountable/types/calculations.py,sha256=fApOFpgBemt_t7IVneVR0VdI3X5EOxiG6Xhzr6R
|
|
|
150
153
|
uncountable/types/calculations_t.py,sha256=pl-lhjyDQuj11Sf9g1-0BsSkN7Ez8UxDp8-KMQ_3enM,709
|
|
151
154
|
uncountable/types/chemical_structure.py,sha256=ujyragaD26-QG5jgKnWhO7TN3N1V9b_04T2WhqNYxxo,281
|
|
152
155
|
uncountable/types/chemical_structure_t.py,sha256=VFFyits_vx4t5L2euu_qFiSpsGJjURkDPr3ISnr3nPc,855
|
|
153
|
-
uncountable/types/client_base.py,sha256=
|
|
156
|
+
uncountable/types/client_base.py,sha256=m4AI0E4ET-FFmewHicW1t2oayraIhS_VYMuyTCnOk1g,76085
|
|
154
157
|
uncountable/types/client_config.py,sha256=qLpHt4O_B098CyN6qQajoxZ2zjZ1DILXLUEGyyGP0TQ,280
|
|
155
158
|
uncountable/types/client_config_t.py,sha256=yTFIYAitMrcc4oV9J-HADODS_Hwi45z-piz7rr7QT04,781
|
|
156
159
|
uncountable/types/curves.py,sha256=QyEyC20jsG-LGKVx6miiF-w70vKMwNkILFBDIJ5Ok9g,345
|
|
@@ -158,7 +161,7 @@ uncountable/types/curves_t.py,sha256=DxYepdC3QKKR7mepOOBoyarNcFZQdUa5ZYH-hwCY3BI
|
|
|
158
161
|
uncountable/types/data.py,sha256=u2isf4XEug3Eu-xSIoqGaCQmW2dFaKBHCkP_WKYwwBc,500
|
|
159
162
|
uncountable/types/data_t.py,sha256=vFoypK_WMGfN28r1sSlDYHZNUdBQC0XCN7-_Mlo4FJk,2832
|
|
160
163
|
uncountable/types/entity.py,sha256=Zclk1LYcRaYrMDhqyCjMSLEg0fE6_q8LHvV22Qvscgs,566
|
|
161
|
-
uncountable/types/entity_t.py,sha256=
|
|
164
|
+
uncountable/types/entity_t.py,sha256=I_dJ08Wd7NsVFkZx3p9-VAARx8nCkLJCtY--hv0zR8c,20288
|
|
162
165
|
uncountable/types/experiment_groups.py,sha256=qUpFOx1AKgzaT_4khCOv5Xs6jwiQGbvHH-GUh3v1nv4,288
|
|
163
166
|
uncountable/types/experiment_groups_t.py,sha256=29Ct-WPejpYMuGfnFfOoosU9iSfjzxpabpBX6oTPFUA,761
|
|
164
167
|
uncountable/types/exports.py,sha256=VMmxUO2PpV1Y63hZ2AnVor4H-B6aswJ7YpSru_u89lU,334
|
|
@@ -178,9 +181,9 @@ uncountable/types/input_attributes_t.py,sha256=8NJQeq_8MkUNn5BlDx34opp3eeZl8Sw1n
|
|
|
178
181
|
uncountable/types/inputs.py,sha256=3ghg39_oiLF5HqWF_wNwYv4HMR1lrKLfeRLn5ptIGw4,446
|
|
179
182
|
uncountable/types/inputs_t.py,sha256=eSVA7LNgLI3ja83GJm4sA9KhPICVV4zj2Dd4OhbuY9g,2158
|
|
180
183
|
uncountable/types/integration_server.py,sha256=VonA8h8TGnVBiss5W8-K82lA01JQa7TLk0ubFo8iiBQ,364
|
|
181
|
-
uncountable/types/integration_server_t.py,sha256=
|
|
182
|
-
uncountable/types/job_definition.py,sha256=
|
|
183
|
-
uncountable/types/job_definition_t.py,sha256=
|
|
184
|
+
uncountable/types/integration_server_t.py,sha256=pgtoyuW6QvGRawidJZFB-WnOdwCE4OIoJAvGfussZKU,1304
|
|
185
|
+
uncountable/types/job_definition.py,sha256=hYp5jPYLLYm3NKEqzQrQfXL0Ms5KgEQGTON13YWSPYk,1804
|
|
186
|
+
uncountable/types/job_definition_t.py,sha256=E4IQvcYF3VDHbwRlvopy8y-HNAyEMZpwy7jkmp74fgQ,9563
|
|
184
187
|
uncountable/types/outputs.py,sha256=I6zP2WHXg_jXgMqmuEJuJOlsjKjQGHjfs1JOwW9YxBM,260
|
|
185
188
|
uncountable/types/outputs_t.py,sha256=atsOkBBgnMeCgPaKPidk9eNouWVnynSrMI_ZbqxRJeY,795
|
|
186
189
|
uncountable/types/overrides.py,sha256=fOvj8P9K9ul8fnTwA--l140EWHuc1BFq8tXgtBkYld4,410
|
|
@@ -238,7 +241,7 @@ uncountable/types/api/entity/get_entities_data.py,sha256=hu0UfkU4PTyv3_CBZ7YmR8L
|
|
|
238
241
|
uncountable/types/api/entity/grant_entity_permissions.py,sha256=4CvVIMvpdok8K1Bh6wMlwuUmoeP_-nL9y2GCEM6uAhY,1536
|
|
239
242
|
uncountable/types/api/entity/list_entities.py,sha256=LLc_QRH2LI7qPamxwF8DAPJCnfDo1Nw_0VGNDl6CMXI,2139
|
|
240
243
|
uncountable/types/api/entity/lock_entity.py,sha256=nwkjtF89ZWV6_1cLe8R47-G542b8i3FvBIjauOJlObE,1311
|
|
241
|
-
uncountable/types/api/entity/lookup_entity.py,sha256=
|
|
244
|
+
uncountable/types/api/entity/lookup_entity.py,sha256=NIDdYl0iscueC68tfZ1lKp5vTq2NMDYtQ3cG6o2tBaI,3905
|
|
242
245
|
uncountable/types/api/entity/resolve_entity_ids.py,sha256=2FyZxTjPHwCzCg92JjH-akcbPu2d9L14Oh6hRVkKDxA,1523
|
|
243
246
|
uncountable/types/api/entity/set_entity_field_values.py,sha256=oiNjAfdMvuFaLbppEaGejzr7Br6XB2D11WaXVCyx8c4,1324
|
|
244
247
|
uncountable/types/api/entity/set_values.py,sha256=7pG15cAos1gem7-HtEMJ4AXisopXrzWsiuqiqh8AzQc,1249
|
|
@@ -315,7 +318,7 @@ uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr
|
|
|
315
318
|
uncountable/types/api/triggers/run_trigger.py,sha256=dgDX_sRWSJ36UuzMZhG25oHV1HIOUKYY2G3fjKugZrw,1204
|
|
316
319
|
uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
|
|
317
320
|
uncountable/types/api/uploader/invoke_uploader.py,sha256=Bj7Dq4A90k00suacwk3bLA_dCb2aovS1kAbVam2AQnM,1395
|
|
318
|
-
uncountablepythonsdk-0.0.
|
|
319
|
-
uncountablepythonsdk-0.0.
|
|
320
|
-
uncountablepythonsdk-0.0.
|
|
321
|
-
uncountablepythonsdk-0.0.
|
|
321
|
+
uncountablepythonsdk-0.0.115.dist-info/METADATA,sha256=-CVaJt1Lh0NBrGUWB8G7L3jEsniPCdVd_1un50_IUQo,2143
|
|
322
|
+
uncountablepythonsdk-0.0.115.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
323
|
+
uncountablepythonsdk-0.0.115.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
|
|
324
|
+
uncountablepythonsdk-0.0.115.dist-info/RECORD,,
|
|
File without changes
|
{uncountablepythonsdk-0.0.114.dist-info → uncountablepythonsdk-0.0.115.dist-info}/top_level.txt
RENAMED
|
File without changes
|