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.

@@ -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.UiEntryActionVariableEntityFields():
49
- return "entity.Entity"
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 name, input in defn.inputs.items():
229
+ for input in defn.inputs.values():
230
+ _validate_input(input)
219
231
  inputs.append(
220
232
  serialize_for_api({
221
- "value_spec_var": snake_to_camel_case(name),
233
+ "value_spec_var": input.vs_var_name,
234
+ "type": input.type,
222
235
  "variable": input,
223
236
  })
224
237
  )
@@ -18,7 +18,7 @@ def get_server_env() -> str | None:
18
18
  return os.environ.get("UNC_SERVER_ENV")
19
19
 
20
20
 
21
- def get_webhook_server_port() -> int:
21
+ def get_http_server_port() -> int:
22
22
  return int(os.environ.get("UNC_WEBHOOK_SERVER_PORT", "5001"))
23
23
 
24
24
 
@@ -0,0 +1,5 @@
1
+ # CLOSED MODULE
2
+
3
+ from .types import GenericHttpRequest as GenericHttpRequest
4
+ from .types import GenericHttpResponse as GenericHttpResponse
5
+ from .types import HttpException as HttpException
@@ -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
@@ -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 base_t, entity_t, webhook_job_t
12
- from uncountable.types.job_definition_t import JobDefinition, JobResult, ProfileMetadata
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
- WebhookJobDefinition,
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 WebhookJobDefinition():
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.WebhookJobDefinition():
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 hmac
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
- get_local_admin_server_port,
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 base_t, job_definition_t, queued_job_t, webhook_job_t
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.WebhookJobDefinition,
24
+ job: job_definition_t.HttpJobDefinitionBase,
96
25
  ) -> None:
97
26
  route = f"/{profile_meta.name}/{job.id}"
98
27
 
99
- def handle_webhook() -> ResponseReturnValue:
28
+ def handle_request() -> ResponseReturnValue:
100
29
  with server_logger.push_scope(route):
101
30
  try:
102
- signature_key = retrieve_secret(
103
- profile_metadata=profile_meta,
104
- secret_retrieval=job.signature_key_secret,
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
- passed_signature = flask.request.headers.get(
108
- "Uncountable-Webhook-Signature"
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
- if passed_signature is None:
111
- raise WebhookException.no_signature_passed()
112
-
113
- webhook_payload = _parse_webhook_payload(
114
- raw_request_body=flask.request.data,
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
- try:
120
- send_job_queue_message(
121
- job_ref_name=job.id,
122
- payload=queued_job_t.QueuedJobPayload(
123
- invocation_context=queued_job_t.InvocationContextWebhook(
124
- webhook_payload=webhook_payload
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 WebhookException.unknown_error().make_error_response()
63
+ return HttpException.unknown_error().make_error_response()
139
64
 
140
65
  app.add_url_rule(
141
66
  route,
142
- endpoint=f"handle_webhook_{job.id}",
143
- view_func=handle_webhook,
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.WebhookJobDefinition):
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=get_webhook_server_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
- typing.Union[LookupEntityFieldValue],
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
  ]
@@ -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
  *,
@@ -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"
@@ -23,6 +23,8 @@ __all__: list[str] = [
23
23
  # DO NOT MODIFY -- This file is generated by type_spec
24
24
  class IntegrationEnvironment(StrEnum):
25
25
  LOCAL = "local"
26
+ DEV = "dev"
27
+ TEST = "test"
26
28
  PROD = "prod"
27
29
 
28
30
 
@@ -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(JobDefinitionBase):
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.114
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=T9Q4Oh-BVHCoxAIY_Po4TWeEQNrXJ-d9w8tr3voYkM0,1555
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=iVjThgDpfkuqeq0LENaCRrrGkWQ6kMIhVa-yZWXSflM,8981
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=6cc-nUvUIbJMI0OSEg3dWI-iNgSYhCdUKKn607Z3QpM,1040
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=lRQhOfm8kRlUnT1BMQRMZ49n1IzOup5VRH0AiZ2AFo4,3214
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=m_DYRosGbHuPhygM32Xo-jRBl_oaUhOYsBBD1qwwKh4,4697
110
- uncountable/integration/telemetry.py,sha256=jeeycEnGcW0Fk-f7JI5d-k8Ih4FLoNBCxvSJlJ98wwU,7418
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=yQWQq_k3kbJkSsEEt6k22YwhXekezJZfV0rnn-hP-Yo,5516
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=4Aq_o11-T_HpZ5-6-y4TP8-Uj4xP0Hv99yvvqLCwl7I,73993
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=tyRdNrRCcWRyOO_dXEUa6G7KTHLCMJWBueK5cIBSx34,20166
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=QAwAB-rlfYh14ZzfVUQd-Bfky3LkPcsSEBhRH3UGvZE,1270
182
- uncountable/types/job_definition.py,sha256=6BkLZrmTfIYh45XFGZ5HOYveued0YXvl17YTlXblXjw,1646
183
- uncountable/types/job_definition_t.py,sha256=6ZJ9rlqXwbOB-G_U8YoWUDPvPak6_11kV2qT2jx8t9Q,8688
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=zwCpQRtAiJoDH9gUlGL1fmHhUEW-PTL_jMMBNDfWOjk,3232
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.114.dist-info/METADATA,sha256=58rVRRIi9Ru8HTpgr8TOE5xNOxwkKfkDlbM7-i1Ptd8,2143
319
- uncountablepythonsdk-0.0.114.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
320
- uncountablepythonsdk-0.0.114.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
321
- uncountablepythonsdk-0.0.114.dist-info/RECORD,,
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,,