UncountablePythonSDK 0.0.133__py3-none-any.whl → 0.0.136.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/integration-server/jobs/materials_auto/example_cron.py +1 -1
- examples/integration-server/jobs/materials_auto/example_runsheet_wh.py +51 -14
- pkgs/type_spec/builder.py +15 -1
- uncountable/integration/cli.py +60 -1
- uncountable/integration/queue_runner/command_server/command_client.py +24 -0
- uncountable/integration/queue_runner/command_server/command_server.py +34 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server.proto +15 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.py +9 -3
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2.pyi +25 -0
- uncountable/integration/queue_runner/command_server/protocol/command_server_pb2_grpc.py +45 -0
- uncountable/integration/queue_runner/command_server/types.py +21 -1
- uncountable/integration/queue_runner/datastore/datastore_sqlite.py +25 -0
- uncountable/integration/queue_runner/datastore/interface.py +3 -0
- uncountable/integration/queue_runner/job_scheduler.py +36 -1
- uncountable/integration/queue_runner/types.py +2 -0
- uncountable/integration/queue_runner/worker.py +28 -26
- uncountable/integration/scheduler.py +64 -13
- uncountable/integration/telemetry.py +79 -0
- uncountable/types/__init__.py +2 -2
- uncountable/types/api/files/download_file.py +15 -1
- uncountable/types/api/listing/fetch_listing.py +1 -2
- uncountable/types/api/runsheet/export_default_runsheet.py +44 -0
- uncountable/types/client_base.py +56 -0
- uncountable/types/listing.py +37 -0
- uncountable/types/listing_t.py +483 -1
- {uncountablepythonsdk-0.0.133.dist-info → uncountablepythonsdk-0.0.136.dev0.dist-info}/METADATA +2 -2
- {uncountablepythonsdk-0.0.133.dist-info → uncountablepythonsdk-0.0.136.dev0.dist-info}/RECORD +29 -30
- uncountable/types/structured_filters.py +0 -25
- uncountable/types/structured_filters_t.py +0 -248
- {uncountablepythonsdk-0.0.133.dist-info → uncountablepythonsdk-0.0.136.dev0.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.133.dist-info → uncountablepythonsdk-0.0.136.dev0.dist-info}/top_level.txt +0 -0
|
@@ -87,30 +87,32 @@ def run_queued_job(
|
|
|
87
87
|
base_span=span,
|
|
88
88
|
profile_metadata=job_details.profile_metadata,
|
|
89
89
|
job_definition=job_details.job_definition,
|
|
90
|
+
queued_job_uuid=queued_job.queued_job_uuid,
|
|
90
91
|
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
92
|
+
with job_logger.resource_tracking():
|
|
93
|
+
try:
|
|
94
|
+
client = construct_uncountable_client(
|
|
95
|
+
profile_meta=job_details.profile_metadata, logger=job_logger
|
|
96
|
+
)
|
|
97
|
+
batch_processor = AsyncBatchProcessor(client=client)
|
|
98
|
+
|
|
99
|
+
payload = _resolve_queued_job_payload(queued_job)
|
|
100
|
+
|
|
101
|
+
args = JobArguments(
|
|
102
|
+
job_definition=job_details.job_definition,
|
|
103
|
+
client=client,
|
|
104
|
+
batch_processor=batch_processor,
|
|
105
|
+
profile_metadata=job_details.profile_metadata,
|
|
106
|
+
logger=job_logger,
|
|
107
|
+
payload=payload,
|
|
108
|
+
job_uuid=queued_job.queued_job_uuid,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return execute_job(
|
|
112
|
+
args=args,
|
|
113
|
+
profile_metadata=job_details.profile_metadata,
|
|
114
|
+
job_definition=job_details.job_definition,
|
|
115
|
+
)
|
|
116
|
+
except BaseException as e:
|
|
117
|
+
job_logger.log_exception(e)
|
|
118
|
+
return job_definition_t.JobResult(success=False)
|
|
@@ -6,6 +6,7 @@ import time
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from datetime import UTC
|
|
8
8
|
from enum import StrEnum
|
|
9
|
+
from typing import assert_never
|
|
9
10
|
|
|
10
11
|
from opentelemetry.trace import get_current_span
|
|
11
12
|
|
|
@@ -16,6 +17,7 @@ from uncountable.integration.queue_runner.command_server import (
|
|
|
16
17
|
check_health,
|
|
17
18
|
)
|
|
18
19
|
from uncountable.integration.queue_runner.queue_runner import start_queue_runner
|
|
20
|
+
from uncountable.integration.queue_runner.types import RESTART_EXIT_CODE
|
|
19
21
|
from uncountable.integration.telemetry import Logger
|
|
20
22
|
|
|
21
23
|
SHUTDOWN_TIMEOUT_SECS = 30
|
|
@@ -55,6 +57,19 @@ class ProcessInfo:
|
|
|
55
57
|
return self.process.poll()
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
@dataclass(kw_only=True)
|
|
61
|
+
class ProcessAlarmRestart:
|
|
62
|
+
process: ProcessInfo
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(kw_only=True)
|
|
66
|
+
class ProcessAlarmShutdownAll:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
ProcessAlarm = ProcessAlarmRestart | ProcessAlarmShutdownAll
|
|
71
|
+
|
|
72
|
+
|
|
58
73
|
def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -> None:
|
|
59
74
|
logger.log_info("received shutdown command, shutting down sub-processes")
|
|
60
75
|
for proc_info in processes.values():
|
|
@@ -129,12 +144,21 @@ def restart_process(
|
|
|
129
144
|
logger.log_info("uwsgi restarted successfully")
|
|
130
145
|
|
|
131
146
|
|
|
132
|
-
def
|
|
147
|
+
def check_process_alarms(
|
|
133
148
|
logger: Logger, processes: dict[ProcessName, ProcessInfo]
|
|
134
|
-
) -> None:
|
|
149
|
+
) -> ProcessAlarm | None:
|
|
135
150
|
for proc_info in processes.values():
|
|
136
151
|
if not proc_info.is_alive:
|
|
137
|
-
|
|
152
|
+
if proc_info.exitcode == RESTART_EXIT_CODE:
|
|
153
|
+
logger.log_warning(
|
|
154
|
+
f"process {proc_info.name} requested restart! restarting"
|
|
155
|
+
)
|
|
156
|
+
return ProcessAlarmRestart(process=proc_info)
|
|
157
|
+
logger.log_error(
|
|
158
|
+
f"process {proc_info.name} shut down unexpectedly! shutting down scheduler; exit code is {proc_info.exitcode}"
|
|
159
|
+
)
|
|
160
|
+
return ProcessAlarmShutdownAll()
|
|
161
|
+
return None
|
|
138
162
|
|
|
139
163
|
|
|
140
164
|
def _wait_queue_runner_online() -> None:
|
|
@@ -166,16 +190,24 @@ def main() -> None:
|
|
|
166
190
|
processes[process.name] = process
|
|
167
191
|
logger.log_info(f"started process {process.name}")
|
|
168
192
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
193
|
+
def _start_queue_runner() -> None:
|
|
194
|
+
runner_process = multiprocessing.Process(target=start_queue_runner)
|
|
195
|
+
runner_process.start()
|
|
196
|
+
add_process(
|
|
197
|
+
ProcessInfo(
|
|
198
|
+
name=ProcessName.QUEUE_RUNNER,
|
|
199
|
+
process=runner_process,
|
|
200
|
+
)
|
|
201
|
+
)
|
|
172
202
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
203
|
+
try:
|
|
204
|
+
_wait_queue_runner_online()
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.log_exception(e)
|
|
207
|
+
handle_shutdown(logger, processes=processes)
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
_start_queue_runner()
|
|
179
211
|
|
|
180
212
|
cron_process = multiprocessing.Process(target=cron_target)
|
|
181
213
|
cron_process.start()
|
|
@@ -189,7 +221,26 @@ def main() -> None:
|
|
|
189
221
|
|
|
190
222
|
try:
|
|
191
223
|
while True:
|
|
192
|
-
|
|
224
|
+
process_alarm = check_process_alarms(logger, processes=processes)
|
|
225
|
+
match process_alarm:
|
|
226
|
+
case ProcessAlarmRestart():
|
|
227
|
+
match process_alarm.process.name:
|
|
228
|
+
case ProcessName.QUEUE_RUNNER:
|
|
229
|
+
del processes[ProcessName.QUEUE_RUNNER]
|
|
230
|
+
_start_queue_runner()
|
|
231
|
+
case ProcessName.CRON_SERVER | ProcessName.UWSGI:
|
|
232
|
+
raise NotImplementedError(
|
|
233
|
+
f"restarting {process_alarm.process.name} not yet implemented"
|
|
234
|
+
)
|
|
235
|
+
case _:
|
|
236
|
+
assert_never(process_alarm.process.name)
|
|
237
|
+
case ProcessAlarmShutdownAll():
|
|
238
|
+
handle_shutdown(logger, processes)
|
|
239
|
+
sys.exit(1)
|
|
240
|
+
case None:
|
|
241
|
+
pass
|
|
242
|
+
case _:
|
|
243
|
+
assert_never(process_alarm)
|
|
193
244
|
time.sleep(1)
|
|
194
245
|
except KeyboardInterrupt:
|
|
195
246
|
handle_shutdown(logger, processes=processes)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
import threading
|
|
4
5
|
import time
|
|
5
6
|
import traceback
|
|
7
|
+
import types
|
|
6
8
|
import typing
|
|
7
9
|
from contextlib import contextmanager
|
|
8
10
|
from enum import StrEnum
|
|
9
11
|
from typing import Generator, assert_never, cast
|
|
10
12
|
|
|
13
|
+
import psutil
|
|
11
14
|
from opentelemetry import _logs, trace
|
|
12
15
|
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
|
13
16
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
@@ -185,6 +188,76 @@ class Logger:
|
|
|
185
188
|
yield self
|
|
186
189
|
|
|
187
190
|
|
|
191
|
+
class PerJobResourceTracker:
|
|
192
|
+
def __init__(self, logger: "JobLogger", sample_interval: float = 0.5) -> None:
|
|
193
|
+
self.logger = logger
|
|
194
|
+
self.sample_interval = sample_interval
|
|
195
|
+
self._process = psutil.Process(os.getpid())
|
|
196
|
+
self._stop_event = threading.Event()
|
|
197
|
+
self._thread: threading.Thread | None = None
|
|
198
|
+
|
|
199
|
+
self.max_rss: int = 0
|
|
200
|
+
self.start_cpu_times: psutil._common.pcputimes | None = None
|
|
201
|
+
self.end_cpu_times: psutil._common.pcputimes | None = None
|
|
202
|
+
self.start_wall_time: float | None = None
|
|
203
|
+
self.end_wall_time: float | None = None
|
|
204
|
+
|
|
205
|
+
def start(self) -> None:
|
|
206
|
+
self.start_cpu_times = self._process.cpu_times()
|
|
207
|
+
self.start_wall_time = time.monotonic()
|
|
208
|
+
|
|
209
|
+
def _monitor() -> None:
|
|
210
|
+
try:
|
|
211
|
+
while not self._stop_event.is_set():
|
|
212
|
+
rss = self._process.memory_info().rss
|
|
213
|
+
self.max_rss = max(self.max_rss, rss)
|
|
214
|
+
time.sleep(self.sample_interval)
|
|
215
|
+
except Exception:
|
|
216
|
+
self._stop_event.set()
|
|
217
|
+
|
|
218
|
+
self._thread = threading.Thread(target=_monitor, daemon=True)
|
|
219
|
+
self._thread.start()
|
|
220
|
+
|
|
221
|
+
def stop(self) -> None:
|
|
222
|
+
self._stop_event.set()
|
|
223
|
+
if self._thread is not None:
|
|
224
|
+
self._thread.join()
|
|
225
|
+
self.end_cpu_times = self._process.cpu_times()
|
|
226
|
+
self.end_wall_time = time.monotonic()
|
|
227
|
+
|
|
228
|
+
def __enter__(self) -> typing.Self:
|
|
229
|
+
self.start()
|
|
230
|
+
return self
|
|
231
|
+
|
|
232
|
+
def __exit__(
|
|
233
|
+
self,
|
|
234
|
+
exc_type: type[BaseException] | None,
|
|
235
|
+
exc_value: BaseException | None,
|
|
236
|
+
traceback: types.TracebackType | None,
|
|
237
|
+
) -> None:
|
|
238
|
+
self.stop()
|
|
239
|
+
stats = dict(self.summary())
|
|
240
|
+
self.logger.log_info("Job resource usage summary", attributes=stats)
|
|
241
|
+
|
|
242
|
+
def summary(self) -> Attributes:
|
|
243
|
+
assert self.start_cpu_times is not None
|
|
244
|
+
assert self.end_cpu_times is not None
|
|
245
|
+
assert self.start_wall_time is not None
|
|
246
|
+
assert self.end_wall_time is not None
|
|
247
|
+
|
|
248
|
+
cpu_user = self.end_cpu_times.user - self.start_cpu_times.user
|
|
249
|
+
cpu_sys = self.end_cpu_times.system - self.start_cpu_times.system
|
|
250
|
+
cpu_total = cpu_user + cpu_sys
|
|
251
|
+
elapsed = self.end_wall_time - self.start_wall_time
|
|
252
|
+
return {
|
|
253
|
+
"cpu_user_s": round(cpu_user, 3),
|
|
254
|
+
"cpu_system_s": round(cpu_sys, 3),
|
|
255
|
+
"cpu_total_s": round(cpu_total, 3),
|
|
256
|
+
"wall_time_s": round(elapsed, 3),
|
|
257
|
+
"peak_rss_mb": round(self.max_rss / (1024 * 1024), 2),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
188
261
|
class JobLogger(Logger):
|
|
189
262
|
def __init__(
|
|
190
263
|
self,
|
|
@@ -192,9 +265,11 @@ class JobLogger(Logger):
|
|
|
192
265
|
base_span: Span,
|
|
193
266
|
profile_metadata: job_definition_t.ProfileMetadata,
|
|
194
267
|
job_definition: job_definition_t.JobDefinition,
|
|
268
|
+
queued_job_uuid: str,
|
|
195
269
|
) -> None:
|
|
196
270
|
self.profile_metadata = profile_metadata
|
|
197
271
|
self.job_definition = job_definition
|
|
272
|
+
self.queued_job_uuid = queued_job_uuid
|
|
198
273
|
super().__init__(base_span)
|
|
199
274
|
|
|
200
275
|
def _patch_attributes(
|
|
@@ -214,6 +289,7 @@ class JobLogger(Logger):
|
|
|
214
289
|
patched_attributes["job.name"] = self.job_definition.name
|
|
215
290
|
patched_attributes["job.id"] = self.job_definition.id
|
|
216
291
|
patched_attributes["job.definition_type"] = self.job_definition.type
|
|
292
|
+
patched_attributes["job.queued_job_uuid"] = self.queued_job_uuid
|
|
217
293
|
match self.job_definition:
|
|
218
294
|
case job_definition_t.CronJobDefinition():
|
|
219
295
|
patched_attributes["job.definition.cron_spec"] = (
|
|
@@ -239,6 +315,9 @@ class JobLogger(Logger):
|
|
|
239
315
|
assert_never(self.job_definition.executor)
|
|
240
316
|
return _cast_attributes(patched_attributes)
|
|
241
317
|
|
|
318
|
+
def resource_tracking(self) -> PerJobResourceTracker:
|
|
319
|
+
return PerJobResourceTracker(self)
|
|
320
|
+
|
|
242
321
|
|
|
243
322
|
@contextmanager
|
|
244
323
|
def push_scope_optional(
|
uncountable/types/__init__.py
CHANGED
|
@@ -37,6 +37,7 @@ from . import entity_t as entity_t
|
|
|
37
37
|
from .api.batch import execute_batch as execute_batch_t
|
|
38
38
|
from .api.batch import execute_batch_load_async as execute_batch_load_async_t
|
|
39
39
|
from . import experiment_groups_t as experiment_groups_t
|
|
40
|
+
from .api.runsheet import export_default_runsheet as export_default_runsheet_t
|
|
40
41
|
from .api.entity import export_entities as export_entities_t
|
|
41
42
|
from . import exports_t as exports_t
|
|
42
43
|
from .api.listing import fetch_listing as fetch_listing_t
|
|
@@ -121,7 +122,6 @@ from .api.recipes import set_recipe_tags as set_recipe_tags_t
|
|
|
121
122
|
from .api.recipes import set_recipe_total as set_recipe_total_t
|
|
122
123
|
from .api.entity import set_values as set_values_t
|
|
123
124
|
from . import sockets_t as sockets_t
|
|
124
|
-
from . import structured_filters_t as structured_filters_t
|
|
125
125
|
from .api.entity import transition_entity_phase as transition_entity_phase_t
|
|
126
126
|
from .api.recipes import unarchive_recipes as unarchive_recipes_t
|
|
127
127
|
from . import units_t as units_t
|
|
@@ -172,6 +172,7 @@ __all__: list[str] = [
|
|
|
172
172
|
"execute_batch_t",
|
|
173
173
|
"execute_batch_load_async_t",
|
|
174
174
|
"experiment_groups_t",
|
|
175
|
+
"export_default_runsheet_t",
|
|
175
176
|
"export_entities_t",
|
|
176
177
|
"exports_t",
|
|
177
178
|
"fetch_listing_t",
|
|
@@ -256,7 +257,6 @@ __all__: list[str] = [
|
|
|
256
257
|
"set_recipe_total_t",
|
|
257
258
|
"set_values_t",
|
|
258
259
|
"sockets_t",
|
|
259
|
-
"structured_filters_t",
|
|
260
260
|
"transition_entity_phase_t",
|
|
261
261
|
"unarchive_recipes_t",
|
|
262
262
|
"units_t",
|
|
@@ -21,6 +21,7 @@ __all__: list[str] = [
|
|
|
21
21
|
"FileDownloadQuery",
|
|
22
22
|
"FileDownloadQueryBase",
|
|
23
23
|
"FileDownloadQueryEntityField",
|
|
24
|
+
"FileDownloadQueryTextDocumentId",
|
|
24
25
|
"FileDownloadQueryType",
|
|
25
26
|
]
|
|
26
27
|
|
|
@@ -31,6 +32,7 @@ ENDPOINT_PATH = "api/external/files/download_file"
|
|
|
31
32
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
32
33
|
class FileDownloadQueryType(StrEnum):
|
|
33
34
|
ENTITY_FIELD = "entity_field"
|
|
35
|
+
TEXT_DOCUMENT_ID = "text_document_id"
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -54,14 +56,26 @@ class FileDownloadQueryEntityField(FileDownloadQueryBase):
|
|
|
54
56
|
field_key: identifier_t.IdentifierKey
|
|
55
57
|
|
|
56
58
|
|
|
59
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
60
|
+
@serial_class(
|
|
61
|
+
named_type_path="sdk.api.files.download_file.FileDownloadQueryTextDocumentId",
|
|
62
|
+
parse_require={"type"},
|
|
63
|
+
)
|
|
64
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
65
|
+
class FileDownloadQueryTextDocumentId(FileDownloadQueryBase):
|
|
66
|
+
type: typing.Literal[FileDownloadQueryType.TEXT_DOCUMENT_ID] = FileDownloadQueryType.TEXT_DOCUMENT_ID
|
|
67
|
+
text_document_id: base_t.ObjectId
|
|
68
|
+
|
|
69
|
+
|
|
57
70
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
58
71
|
FileDownloadQuery = typing.Annotated[
|
|
59
|
-
|
|
72
|
+
FileDownloadQueryEntityField | FileDownloadQueryTextDocumentId,
|
|
60
73
|
serial_union_annotation(
|
|
61
74
|
named_type_path="sdk.api.files.download_file.FileDownloadQuery",
|
|
62
75
|
discriminator="type",
|
|
63
76
|
discriminator_map={
|
|
64
77
|
"entity_field": FileDownloadQueryEntityField,
|
|
78
|
+
"text_document_id": FileDownloadQueryTextDocumentId,
|
|
65
79
|
},
|
|
66
80
|
),
|
|
67
81
|
]
|
|
@@ -11,7 +11,6 @@ from pkgs.serialization import serial_class
|
|
|
11
11
|
from ... import base_t
|
|
12
12
|
from ... import entity_t
|
|
13
13
|
from ... import listing_t
|
|
14
|
-
from ... import structured_filters_t
|
|
15
14
|
|
|
16
15
|
__all__: list[str] = [
|
|
17
16
|
"Arguments",
|
|
@@ -33,7 +32,7 @@ ENDPOINT_PATH = "api/external/listing/fetch_listing"
|
|
|
33
32
|
class Arguments:
|
|
34
33
|
entity_type: entity_t.EntityType
|
|
35
34
|
columns: list[listing_t.ColumnIdentifier]
|
|
36
|
-
filters:
|
|
35
|
+
filters: listing_t.FilterNode | None = None
|
|
37
36
|
limit: int | None = None
|
|
38
37
|
offset: int | None = None
|
|
39
38
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
2
|
+
# ruff: noqa: E402 Q003
|
|
3
|
+
# fmt: off
|
|
4
|
+
# isort: skip_file
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import typing # noqa: F401
|
|
7
|
+
import datetime # noqa: F401
|
|
8
|
+
from decimal import Decimal # noqa: F401
|
|
9
|
+
import dataclasses
|
|
10
|
+
from pkgs.serialization import serial_class
|
|
11
|
+
from ... import base_t
|
|
12
|
+
from ... import entity_t
|
|
13
|
+
from ... import identifier_t
|
|
14
|
+
|
|
15
|
+
__all__: list[str] = [
|
|
16
|
+
"Arguments",
|
|
17
|
+
"Data",
|
|
18
|
+
"ENDPOINT_METHOD",
|
|
19
|
+
"ENDPOINT_PATH",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
ENDPOINT_METHOD = "POST"
|
|
23
|
+
ENDPOINT_PATH = "api/external/runsheet/export_default_runsheet"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
27
|
+
@serial_class(
|
|
28
|
+
named_type_path="sdk.api.runsheet.export_default_runsheet.Arguments",
|
|
29
|
+
)
|
|
30
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
31
|
+
class Arguments:
|
|
32
|
+
entities: list[identifier_t.IdentifierKey]
|
|
33
|
+
entity_type: entity_t.EntityType = entity_t.EntityType.RECIPE
|
|
34
|
+
runsheet_key: identifier_t.IdentifierKey
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
38
|
+
@serial_class(
|
|
39
|
+
named_type_path="sdk.api.runsheet.export_default_runsheet.Data",
|
|
40
|
+
)
|
|
41
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
42
|
+
class Data:
|
|
43
|
+
text_document_id: base_t.ObjectId
|
|
44
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
uncountable/types/client_base.py
CHANGED
|
@@ -37,6 +37,7 @@ import uncountable.types.api.batch.execute_batch as execute_batch_t
|
|
|
37
37
|
import uncountable.types.api.batch.execute_batch_load_async as execute_batch_load_async_t
|
|
38
38
|
import uncountable.types.api.entity.export_entities as export_entities_t
|
|
39
39
|
from uncountable.types import exports_t
|
|
40
|
+
import uncountable.types.api.listing.fetch_listing as fetch_listing_t
|
|
40
41
|
from uncountable.types import field_values_t
|
|
41
42
|
from uncountable.types import generic_upload_t
|
|
42
43
|
import uncountable.types.api.recipes.get_column_calculation_values as get_column_calculation_values_t
|
|
@@ -49,6 +50,7 @@ import uncountable.types.api.inputs.get_input_names as get_input_names_t
|
|
|
49
50
|
import uncountable.types.api.inputs.get_inputs_data as get_inputs_data_t
|
|
50
51
|
import uncountable.types.api.outputs.get_output_data as get_output_data_t
|
|
51
52
|
import uncountable.types.api.outputs.get_output_names as get_output_names_t
|
|
53
|
+
import uncountable.types.api.outputs.get_output_organization as get_output_organization_t
|
|
52
54
|
import uncountable.types.api.project.get_projects as get_projects_t
|
|
53
55
|
import uncountable.types.api.project.get_projects_data as get_projects_data_t
|
|
54
56
|
import uncountable.types.api.recipes.get_recipe_calculations as get_recipe_calculations_t
|
|
@@ -64,6 +66,7 @@ import uncountable.types.api.uploader.invoke_uploader as invoke_uploader_t
|
|
|
64
66
|
import uncountable.types.api.entity.list_aggregate as list_aggregate_t
|
|
65
67
|
import uncountable.types.api.entity.list_entities as list_entities_t
|
|
66
68
|
import uncountable.types.api.id_source.list_id_source as list_id_source_t
|
|
69
|
+
from uncountable.types import listing_t
|
|
67
70
|
import uncountable.types.api.entity.lock_entity as lock_entity_t
|
|
68
71
|
import uncountable.types.api.recipes.lock_recipes as lock_recipes_t
|
|
69
72
|
import uncountable.types.api.entity.lookup_entity as lookup_entity_t
|
|
@@ -738,6 +741,39 @@ class ClientMethods(ABC):
|
|
|
738
741
|
)
|
|
739
742
|
return self.do_request(api_request=api_request, return_type=export_entities_t.Data)
|
|
740
743
|
|
|
744
|
+
def fetch_listing(
|
|
745
|
+
self,
|
|
746
|
+
*,
|
|
747
|
+
entity_type: entity_t.EntityType,
|
|
748
|
+
columns: list[listing_t.ColumnIdentifier],
|
|
749
|
+
filters: listing_t.FilterNode | None = None,
|
|
750
|
+
limit: int | None = None,
|
|
751
|
+
offset: int | None = None,
|
|
752
|
+
_request_options: client_config_t.RequestOptions | None = None,
|
|
753
|
+
) -> fetch_listing_t.Data:
|
|
754
|
+
"""External API for fetching a listing based on some structured columns and filters
|
|
755
|
+
|
|
756
|
+
:param entity_type: The entity type to fetch listing entries for
|
|
757
|
+
:param columns: The columns to include in the results
|
|
758
|
+
:param filters: Structured filters to apply to the listing, represented by a FilterNode object
|
|
759
|
+
:param limit: The number of entries to return. If not provided, defaults to 100. Note a maximum of 100 entries can be returned.
|
|
760
|
+
:param offset: The number of entries to skip for pagination
|
|
761
|
+
"""
|
|
762
|
+
args = fetch_listing_t.Arguments(
|
|
763
|
+
entity_type=entity_type,
|
|
764
|
+
columns=columns,
|
|
765
|
+
filters=filters,
|
|
766
|
+
limit=limit,
|
|
767
|
+
offset=offset,
|
|
768
|
+
)
|
|
769
|
+
api_request = APIRequest(
|
|
770
|
+
method=fetch_listing_t.ENDPOINT_METHOD,
|
|
771
|
+
endpoint=fetch_listing_t.ENDPOINT_PATH,
|
|
772
|
+
args=args,
|
|
773
|
+
request_options=_request_options,
|
|
774
|
+
)
|
|
775
|
+
return self.do_request(api_request=api_request, return_type=fetch_listing_t.Data)
|
|
776
|
+
|
|
741
777
|
def get_column_calculation_values(
|
|
742
778
|
self,
|
|
743
779
|
*,
|
|
@@ -989,6 +1025,26 @@ class ClientMethods(ABC):
|
|
|
989
1025
|
)
|
|
990
1026
|
return self.do_request(api_request=api_request, return_type=get_output_names_t.Data)
|
|
991
1027
|
|
|
1028
|
+
def get_output_organization(
|
|
1029
|
+
self,
|
|
1030
|
+
*,
|
|
1031
|
+
request: get_output_organization_t.OutputOrganizationRequest,
|
|
1032
|
+
_request_options: client_config_t.RequestOptions | None = None,
|
|
1033
|
+
) -> get_output_organization_t.Data:
|
|
1034
|
+
"""Returns the output organization for the specified material family, project, or user
|
|
1035
|
+
|
|
1036
|
+
"""
|
|
1037
|
+
args = get_output_organization_t.Arguments(
|
|
1038
|
+
request=request,
|
|
1039
|
+
)
|
|
1040
|
+
api_request = APIRequest(
|
|
1041
|
+
method=get_output_organization_t.ENDPOINT_METHOD,
|
|
1042
|
+
endpoint=get_output_organization_t.ENDPOINT_PATH,
|
|
1043
|
+
args=args,
|
|
1044
|
+
request_options=_request_options,
|
|
1045
|
+
)
|
|
1046
|
+
return self.do_request(api_request=api_request, return_type=get_output_organization_t.Data)
|
|
1047
|
+
|
|
992
1048
|
def get_projects(
|
|
993
1049
|
self,
|
|
994
1050
|
*,
|
uncountable/types/listing.py
CHANGED
|
@@ -5,5 +5,42 @@
|
|
|
5
5
|
# Kept only for SDK backwards compatibility
|
|
6
6
|
from .listing_t import ColumnType as ColumnType
|
|
7
7
|
from .listing_t import ColumnIdentifierEntityRefName as ColumnIdentifierEntityRefName
|
|
8
|
+
from .listing_t import ColumnIdentifierTransitive as ColumnIdentifierTransitive
|
|
9
|
+
from .listing_t import LoadAggregateType as LoadAggregateType
|
|
10
|
+
from .listing_t import LoadAggregateGroupBy as LoadAggregateGroupBy
|
|
11
|
+
from .listing_t import LoadAggregateCollect as LoadAggregateCollect
|
|
12
|
+
from .listing_t import LoadAggregateCollectDistinct as LoadAggregateCollectDistinct
|
|
13
|
+
from .listing_t import LoadAggregateCount as LoadAggregateCount
|
|
14
|
+
from .listing_t import LoadAggregateCountDistinct as LoadAggregateCountDistinct
|
|
15
|
+
from .listing_t import LoadAggregateFirst as LoadAggregateFirst
|
|
16
|
+
from .listing_t import LoadAggregateUniqueValue as LoadAggregateUniqueValue
|
|
17
|
+
from .listing_t import LoadAggregateMin as LoadAggregateMin
|
|
18
|
+
from .listing_t import LoadAggregateMax as LoadAggregateMax
|
|
19
|
+
from .listing_t import LoadAggregateMean as LoadAggregateMean
|
|
20
|
+
from .listing_t import LoadAggregateStddev as LoadAggregateStddev
|
|
21
|
+
from .listing_t import LoadAggregateSum as LoadAggregateSum
|
|
22
|
+
from .listing_t import DateGroupType as DateGroupType
|
|
23
|
+
from .listing_t import LoadAggregateGroupByDate as LoadAggregateGroupByDate
|
|
24
|
+
from .listing_t import AggregateLoadTypes as AggregateLoadTypes
|
|
25
|
+
from .listing_t import ColumnIdentifierTransitiveAggregate as ColumnIdentifierTransitiveAggregate
|
|
8
26
|
from .listing_t import ColumnIdentifier as ColumnIdentifier
|
|
27
|
+
from .listing_t import FilterRelation as FilterRelation
|
|
28
|
+
from .listing_t import FilterSpecBase as FilterSpecBase
|
|
29
|
+
from .listing_t import FilterScalarType as FilterScalarType
|
|
30
|
+
from .listing_t import FilterIdType as FilterIdType
|
|
31
|
+
from .listing_t import StringFilterValue as StringFilterValue
|
|
32
|
+
from .listing_t import FilterSpecEquals as FilterSpecEquals
|
|
33
|
+
from .listing_t import FilterSpecInclude as FilterSpecInclude
|
|
34
|
+
from .listing_t import FilterSpecIStrContains as FilterSpecIStrContains
|
|
35
|
+
from .listing_t import FilterSpecIStrStartsWith as FilterSpecIStrStartsWith
|
|
36
|
+
from .listing_t import FilterSpecExists as FilterSpecExists
|
|
37
|
+
from .listing_t import FilterSpecGreater as FilterSpecGreater
|
|
38
|
+
from .listing_t import FilterSpecLess as FilterSpecLess
|
|
39
|
+
from .listing_t import FilterSpecGeq as FilterSpecGeq
|
|
40
|
+
from .listing_t import FilterSpecLeq as FilterSpecLeq
|
|
41
|
+
from .listing_t import FilterSpec as FilterSpec
|
|
42
|
+
from .listing_t import FilterNodeType as FilterNodeType
|
|
43
|
+
from .listing_t import FilterNodeBase as FilterNodeBase
|
|
44
|
+
from .listing_t import FilterNodeColumnAnd as FilterNodeColumnAnd
|
|
45
|
+
from .listing_t import FilterNode as FilterNode
|
|
9
46
|
# DO NOT MODIFY -- This file is generated by type_spec
|