sqlseed-web 0.2.4__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.
- sqlseed_web/AGENTS.md +106 -0
- sqlseed_web/__init__.py +24 -0
- sqlseed_web/__main__.py +8 -0
- sqlseed_web/_application.py +205 -0
- sqlseed_web/ai_settings.py +290 -0
- sqlseed_web/api.py +1102 -0
- sqlseed_web/app.py +26 -0
- sqlseed_web/managed_worker.py +184 -0
- sqlseed_web/operation_errors.py +42 -0
- sqlseed_web/plugin_environment.py +231 -0
- sqlseed_web/plugin_management.py +322 -0
- sqlseed_web/plugin_process.py +131 -0
- sqlseed_web/runtime_lifecycle.py +130 -0
- sqlseed_web/runtime_session.py +97 -0
- sqlseed_web/settings_environment.py +368 -0
- sqlseed_web/sqlite_target.py +86 -0
- sqlseed_web/state.py +348 -0
- sqlseed_web/static/AGENTS.md +180 -0
- sqlseed_web/static/ai.css +57 -0
- sqlseed_web/static/configs.css +50 -0
- sqlseed_web/static/date-picker.css +230 -0
- sqlseed_web/static/disclosure.css +149 -0
- sqlseed_web/static/graph-clarity.css +103 -0
- sqlseed_web/static/index.html +29 -0
- sqlseed_web/static/js/api.js +228 -0
- sqlseed_web/static/js/app.js +97 -0
- sqlseed_web/static/js/dropdown.js +432 -0
- sqlseed_web/static/js/filepicker.js +203 -0
- sqlseed_web/static/js/genform.js +1066 -0
- sqlseed_web/static/js/labels.js +195 -0
- sqlseed_web/static/js/pages/browse.js +209 -0
- sqlseed_web/static/js/pages/configs.js +424 -0
- sqlseed_web/static/js/pages/connect.js +332 -0
- sqlseed_web/static/js/pages/heal.js +395 -0
- sqlseed_web/static/js/pages/meta.js +110 -0
- sqlseed_web/static/js/pages/runs.js +293 -0
- sqlseed_web/static/js/pages/settings.js +942 -0
- sqlseed_web/static/js/pages/wizard.js +751 -0
- sqlseed_web/static/js/pages/workbench.js +3123 -0
- sqlseed_web/static/js/tree.js +126 -0
- sqlseed_web/static/js/workbench/ai-eligibility.js +33 -0
- sqlseed_web/static/js/workbench/ai-handoff.js +31 -0
- sqlseed_web/static/js/workbench/ai-stream.js +116 -0
- sqlseed_web/static/js/workbench/ai.js +888 -0
- sqlseed_web/static/js/workbench/connection.js +508 -0
- sqlseed_web/static/js/workbench/date-picker.js +445 -0
- sqlseed_web/static/js/workbench/dependency-view.js +119 -0
- sqlseed_web/static/js/workbench/editor.js +1236 -0
- sqlseed_web/static/js/workbench/focus.js +11 -0
- sqlseed_web/static/js/workbench/graph-layout.js +332 -0
- sqlseed_web/static/js/workbench/graph.js +970 -0
- sqlseed_web/static/js/workbench/guidance.js +29 -0
- sqlseed_web/static/js/workbench/model.js +124 -0
- sqlseed_web/static/js/workbench/plugin-management.js +512 -0
- sqlseed_web/static/js/workbench/preview-scroll-layout.js +94 -0
- sqlseed_web/static/js/workbench/preview.js +572 -0
- sqlseed_web/static/js/workbench/provider-guide.js +33 -0
- sqlseed_web/static/js/workbench/recovery.js +28 -0
- sqlseed_web/static/js/workbench/scroll-lock.js +26 -0
- sqlseed_web/static/js/workbench/session.js +174 -0
- sqlseed_web/static/js/workbench/table-data.js +186 -0
- sqlseed_web/static/js/workbench/ui.js +262 -0
- sqlseed_web/static/navigation.css +92 -0
- sqlseed_web/static/preview.css +29 -0
- sqlseed_web/static/runs.css +53 -0
- sqlseed_web/static/scrollbars.css +42 -0
- sqlseed_web/static/settings.css +108 -0
- sqlseed_web/static/style.css +3382 -0
- sqlseed_web/static/table-data.css +27 -0
- sqlseed_web/static/workbench.css +509 -0
- sqlseed_web/supervised_plugins.py +173 -0
- sqlseed_web/supervisor.py +238 -0
- sqlseed_web/workbench.py +381 -0
- sqlseed_web/workbench_ai.py +887 -0
- sqlseed_web/workbench_ai_relations.py +285 -0
- sqlseed_web/workbench_ai_stream.py +172 -0
- sqlseed_web/workbench_data.py +163 -0
- sqlseed_web/workbench_execution.py +199 -0
- sqlseed_web/workbench_runtime.py +1218 -0
- sqlseed_web/workbench_schema.py +277 -0
- sqlseed_web/workbench_store.py +458 -0
- sqlseed_web/worker_control.py +192 -0
- sqlseed_web-0.2.4.dist-info/METADATA +105 -0
- sqlseed_web-0.2.4.dist-info/RECORD +87 -0
- sqlseed_web-0.2.4.dist-info/WHEEL +4 -0
- sqlseed_web-0.2.4.dist-info/entry_points.txt +2 -0
- sqlseed_web-0.2.4.dist-info/licenses/LICENSE +679 -0
sqlseed_web/workbench.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""HTTP contracts for the persistent, offline generation workbench."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from http import HTTPStatus
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
12
|
+
from fastapi.encoders import jsonable_encoder
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
|
+
|
|
15
|
+
from sqlseed_web.state import ConnectionBusyError, state
|
|
16
|
+
from sqlseed_web.workbench_runtime import (
|
|
17
|
+
WorkbenchError,
|
|
18
|
+
check_document,
|
|
19
|
+
export_document,
|
|
20
|
+
normalize_document,
|
|
21
|
+
parse_document,
|
|
22
|
+
plan_execution,
|
|
23
|
+
public_error,
|
|
24
|
+
start_run,
|
|
25
|
+
)
|
|
26
|
+
from sqlseed_web.workbench_schema import _target_identity, generator_catalog, inspect_connection
|
|
27
|
+
from sqlseed_web.workbench_store import RevisionConflict, get_store
|
|
28
|
+
|
|
29
|
+
router = APIRouter(prefix="/api/workbench", tags=["workbench"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DocumentRequest(BaseModel):
|
|
33
|
+
model_config = ConfigDict(extra="forbid")
|
|
34
|
+
conn_id: str
|
|
35
|
+
document: dict[str, Any]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DraftRequest(DocumentRequest):
|
|
39
|
+
name: str = Field(min_length=1, max_length=200)
|
|
40
|
+
schema_hash: str
|
|
41
|
+
view_state: dict[str, Any] = Field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DraftUpdateRequest(DraftRequest):
|
|
45
|
+
revision: int = Field(ge=1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DraftNameRequest(BaseModel):
|
|
49
|
+
model_config = ConfigDict(extra="forbid")
|
|
50
|
+
revision: int = Field(ge=1, strict=True)
|
|
51
|
+
name: str = Field(min_length=1, max_length=200)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CheckRequest(DocumentRequest):
|
|
55
|
+
schema_hash: str
|
|
56
|
+
count: int = Field(default=3, ge=1, le=100)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ParseRequest(BaseModel):
|
|
60
|
+
model_config = ConfigDict(extra="forbid")
|
|
61
|
+
conn_id: str
|
|
62
|
+
text: str = Field(max_length=2 * 1024 * 1024)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RunRequest(BaseModel):
|
|
66
|
+
model_config = ConfigDict(extra="forbid")
|
|
67
|
+
conn_id: str
|
|
68
|
+
draft_id: str
|
|
69
|
+
revision: int = Field(ge=1)
|
|
70
|
+
schema_hash: str
|
|
71
|
+
config_hash: str
|
|
72
|
+
execution: dict[str, Any] | None = None
|
|
73
|
+
plan_hash: str = Field(default="", max_length=128)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@contextmanager
|
|
77
|
+
def _request_errors() -> Iterator[None]:
|
|
78
|
+
try:
|
|
79
|
+
yield
|
|
80
|
+
except HTTPException:
|
|
81
|
+
raise
|
|
82
|
+
except WorkbenchError as exc:
|
|
83
|
+
raise HTTPException(exc.status, detail={"code": exc.code, "message": public_error(exc)}) from exc
|
|
84
|
+
except ConnectionBusyError as exc:
|
|
85
|
+
raise HTTPException(409, detail={"code": "connection_busy", "message": public_error(exc)}) from exc
|
|
86
|
+
except RevisionConflict as exc:
|
|
87
|
+
raise HTTPException(409, detail={"code": "conflict", "message": public_error(exc)}) from exc
|
|
88
|
+
except KeyError as exc:
|
|
89
|
+
raise HTTPException(404, detail={"code": "not_found", "message": public_error(exc)}) from exc
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
raise HTTPException(422, detail={"code": "request_failed", "message": public_error(exc)}) from exc
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.get(
|
|
95
|
+
"/connections/{conn_id}/schema",
|
|
96
|
+
responses={
|
|
97
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
98
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
99
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
def connection_schema(conn_id: str) -> dict[str, Any]:
|
|
103
|
+
with _request_errors(), state.connection_operation(conn_id) as conn:
|
|
104
|
+
return inspect_connection(conn)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@router.get(
|
|
108
|
+
"/generators",
|
|
109
|
+
responses={
|
|
110
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
111
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
112
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
def generators() -> dict[str, Any]:
|
|
116
|
+
with _request_errors():
|
|
117
|
+
return generator_catalog()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@router.get(
|
|
121
|
+
"/drafts",
|
|
122
|
+
responses={
|
|
123
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
124
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
125
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
def list_drafts(conn_id: str | None = None) -> list[dict[str, Any]]:
|
|
129
|
+
with _request_errors():
|
|
130
|
+
target_key = None
|
|
131
|
+
if conn_id:
|
|
132
|
+
# Filtering saved metadata needs only connection identity, not a
|
|
133
|
+
# live schema scan or the orchestrator's generation lock.
|
|
134
|
+
conn = state.get_connection(conn_id)
|
|
135
|
+
target_key, _ = _target_identity(conn)
|
|
136
|
+
return get_store().list_drafts(target_key=target_key)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _save_draft(body: DraftRequest, draft_id: str | None = None, revision: int | None = None) -> dict[str, Any]:
|
|
140
|
+
with _request_errors(), state.connection_operation(body.conn_id) as conn:
|
|
141
|
+
schema = inspect_connection(conn)
|
|
142
|
+
if schema["schema_hash"] != body.schema_hash:
|
|
143
|
+
raise WorkbenchError("schema 已变化,请刷新后保存", code="schema_changed", status=409)
|
|
144
|
+
return get_store().save_draft(
|
|
145
|
+
{
|
|
146
|
+
"name": body.name.strip(),
|
|
147
|
+
"document": normalize_document(conn, body.document),
|
|
148
|
+
"schema_hash": body.schema_hash,
|
|
149
|
+
"view_state": body.view_state,
|
|
150
|
+
"target_key": schema["target_key"],
|
|
151
|
+
"target_label": schema["target_label"],
|
|
152
|
+
},
|
|
153
|
+
draft_id=draft_id,
|
|
154
|
+
expected_revision=revision,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@router.post(
|
|
159
|
+
"/drafts",
|
|
160
|
+
responses={
|
|
161
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
162
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
163
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
def create_draft(body: DraftRequest) -> dict[str, Any]:
|
|
167
|
+
return _save_draft(body)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@router.get(
|
|
171
|
+
"/drafts/{draft_id}",
|
|
172
|
+
responses={
|
|
173
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
174
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
175
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
def get_draft(draft_id: str) -> dict[str, Any]:
|
|
179
|
+
with _request_errors():
|
|
180
|
+
return get_store().get_draft(draft_id)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@router.put(
|
|
184
|
+
"/drafts/{draft_id}",
|
|
185
|
+
responses={
|
|
186
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
187
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
188
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
def update_draft(draft_id: str, body: DraftUpdateRequest) -> dict[str, Any]:
|
|
192
|
+
return _save_draft(body, draft_id, body.revision)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@router.patch(
|
|
196
|
+
"/drafts/{draft_id}",
|
|
197
|
+
responses={
|
|
198
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
199
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
200
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
def rename_draft(draft_id: str, body: DraftNameRequest) -> dict[str, Any]:
|
|
204
|
+
with _request_errors():
|
|
205
|
+
return get_store().rename_draft(draft_id, body.name, expected_revision=body.revision)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@router.post(
|
|
209
|
+
"/drafts/{draft_id}/copy",
|
|
210
|
+
responses={
|
|
211
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
212
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
213
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
def copy_draft(draft_id: str, body: DraftNameRequest) -> dict[str, Any]:
|
|
217
|
+
with _request_errors():
|
|
218
|
+
return get_store().copy_draft(draft_id, body.name, expected_revision=body.revision)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@router.delete(
|
|
222
|
+
"/drafts/{draft_id}",
|
|
223
|
+
responses={
|
|
224
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
225
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
226
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
def delete_draft(draft_id: str, revision: Annotated[int, Query(ge=1)]) -> dict[str, Any]:
|
|
230
|
+
with _request_errors():
|
|
231
|
+
return get_store().delete_draft(draft_id, expected_revision=revision)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@router.get(
|
|
235
|
+
"/drafts/{draft_id}/export",
|
|
236
|
+
responses={
|
|
237
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
238
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
239
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
def export_draft(draft_id: str) -> dict[str, Any]:
|
|
243
|
+
"""Export the saved document without reopening a business database."""
|
|
244
|
+
with _request_errors():
|
|
245
|
+
record = get_store().get_draft(draft_id)
|
|
246
|
+
return {
|
|
247
|
+
"id": record["id"],
|
|
248
|
+
"name": record["name"],
|
|
249
|
+
"revision": record["revision"],
|
|
250
|
+
"json": record["document"],
|
|
251
|
+
"yaml": yaml.safe_dump(record["document"], sort_keys=False, allow_unicode=True),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@router.post(
|
|
256
|
+
"/parse",
|
|
257
|
+
responses={
|
|
258
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
259
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
260
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
def parse(body: ParseRequest) -> dict[str, Any]:
|
|
264
|
+
with _request_errors(), state.connection_operation(body.conn_id) as conn:
|
|
265
|
+
return {"document": parse_document(conn, body.text)}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@router.post(
|
|
269
|
+
"/export",
|
|
270
|
+
responses={
|
|
271
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
272
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
273
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
def export(body: DocumentRequest) -> dict[str, Any]:
|
|
277
|
+
with _request_errors(), state.connection_operation(body.conn_id) as conn:
|
|
278
|
+
return export_document(conn, body.document)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _check(body: CheckRequest, *, include_preview: bool) -> dict[str, Any]:
|
|
282
|
+
with _request_errors(), state.connection_operation(body.conn_id) as conn:
|
|
283
|
+
result = check_document(conn, body.document, body.schema_hash, count=body.count, preview=include_preview)
|
|
284
|
+
encoded: dict[str, Any] = jsonable_encoder(result, custom_encoder={bytes: lambda value: value.hex()})
|
|
285
|
+
return encoded
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@router.post(
|
|
289
|
+
"/check",
|
|
290
|
+
responses={
|
|
291
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
292
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
293
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
294
|
+
},
|
|
295
|
+
)
|
|
296
|
+
def check(body: CheckRequest) -> dict[str, Any]:
|
|
297
|
+
return _check(body, include_preview=False)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@router.post(
|
|
301
|
+
"/preview",
|
|
302
|
+
responses={
|
|
303
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
304
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
305
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
306
|
+
},
|
|
307
|
+
)
|
|
308
|
+
def preview(body: CheckRequest) -> dict[str, Any]:
|
|
309
|
+
return _check(body, include_preview=True)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@router.post(
|
|
313
|
+
"/execution-plan",
|
|
314
|
+
responses={
|
|
315
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
316
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
317
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
318
|
+
},
|
|
319
|
+
)
|
|
320
|
+
def execution_plan(body: RunRequest) -> dict[str, Any]:
|
|
321
|
+
with _request_errors():
|
|
322
|
+
return plan_execution(
|
|
323
|
+
body.conn_id,
|
|
324
|
+
body.draft_id,
|
|
325
|
+
body.revision,
|
|
326
|
+
body.schema_hash,
|
|
327
|
+
body.config_hash,
|
|
328
|
+
execution=body.execution,
|
|
329
|
+
registry=state,
|
|
330
|
+
store=get_store(),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@router.post(
|
|
335
|
+
"/runs",
|
|
336
|
+
status_code=202,
|
|
337
|
+
responses={
|
|
338
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
339
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
340
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
341
|
+
},
|
|
342
|
+
)
|
|
343
|
+
def create_run(body: RunRequest) -> dict[str, Any]:
|
|
344
|
+
with _request_errors():
|
|
345
|
+
return start_run(
|
|
346
|
+
body.conn_id,
|
|
347
|
+
body.draft_id,
|
|
348
|
+
body.revision,
|
|
349
|
+
body.schema_hash,
|
|
350
|
+
body.config_hash,
|
|
351
|
+
execution=body.execution,
|
|
352
|
+
plan_hash=body.plan_hash,
|
|
353
|
+
registry=state,
|
|
354
|
+
store=get_store(),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@router.get(
|
|
359
|
+
"/runs",
|
|
360
|
+
responses={
|
|
361
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
362
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
363
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
def list_runs(limit: Annotated[int, Query(ge=1, le=200)] = 50) -> list[dict[str, Any]]:
|
|
367
|
+
with _request_errors():
|
|
368
|
+
return get_store().list_runs(limit=limit)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@router.get(
|
|
372
|
+
"/runs/{run_id}",
|
|
373
|
+
responses={
|
|
374
|
+
404: {"description": HTTPStatus(404).phrase},
|
|
375
|
+
409: {"description": HTTPStatus(409).phrase},
|
|
376
|
+
422: {"description": HTTPStatus(422).phrase},
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
def get_run(run_id: str) -> dict[str, Any]:
|
|
380
|
+
with _request_errors():
|
|
381
|
+
return get_store().get_run(run_id)
|