doc-store-client-dev 0.1.185__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.
- doc_store_client/__init__.py +194 -0
- doc_store_client/cli.py +192 -0
- doc_store_client/client.py +1509 -0
- doc_store_client/models.py +1635 -0
- doc_store_client/owned_document_cleanup.py +143 -0
- doc_store_client/py.typed +0 -0
- doc_store_client/text_verification.py +21 -0
- doc_store_client_cli.py +16 -0
- doc_store_client_dev-0.1.185.dist-info/METADATA +133 -0
- doc_store_client_dev-0.1.185.dist-info/RECORD +13 -0
- doc_store_client_dev-0.1.185.dist-info/WHEEL +5 -0
- doc_store_client_dev-0.1.185.dist-info/entry_points.txt +2 -0
- doc_store_client_dev-0.1.185.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1509 @@
|
|
|
1
|
+
"""Thin typed facade over an injected ``mcp-proxy-adapter`` client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import inspect
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
9
|
+
from dataclasses import is_dataclass, replace
|
|
10
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar, cast
|
|
11
|
+
|
|
12
|
+
from chunk_metadata_adapter import ChunkQuery
|
|
13
|
+
|
|
14
|
+
from .models import (
|
|
15
|
+
BackupEmergencyResult,
|
|
16
|
+
BackupInspectResult,
|
|
17
|
+
BackupPointsResult,
|
|
18
|
+
BackupRecoverCatalogResult,
|
|
19
|
+
BackupRestoreResult,
|
|
20
|
+
BackupRunResult,
|
|
21
|
+
BackupStatusResult,
|
|
22
|
+
ChapterGetRequest,
|
|
23
|
+
ChapterGetResult,
|
|
24
|
+
ChapterTextGetRequest,
|
|
25
|
+
DocumentChunkRequest,
|
|
26
|
+
DocumentChunkResult,
|
|
27
|
+
DocumentCreateRequest,
|
|
28
|
+
DocumentCreateResult,
|
|
29
|
+
DocumentDeleteRequest,
|
|
30
|
+
DocumentDeleteResult,
|
|
31
|
+
DocumentGetRequest,
|
|
32
|
+
DocumentGetResult,
|
|
33
|
+
DocumentRebindRequest,
|
|
34
|
+
DocumentRebindResult,
|
|
35
|
+
DocumentUpdateRequest,
|
|
36
|
+
DocumentUpdateResult,
|
|
37
|
+
EntityGetRequest,
|
|
38
|
+
EntityGetResult,
|
|
39
|
+
EntityTextGetRequest,
|
|
40
|
+
EntityTextGetResult,
|
|
41
|
+
EntityIdsRequest,
|
|
42
|
+
EntityLifecycleResult,
|
|
43
|
+
EntityListRequest,
|
|
44
|
+
EntityListResult,
|
|
45
|
+
EntityOwnerTreeRequest,
|
|
46
|
+
EntityOwnerTreeResult,
|
|
47
|
+
EntityReferencesRequest,
|
|
48
|
+
EntityReferencesResult,
|
|
49
|
+
ParagraphGetByNumberRequest,
|
|
50
|
+
ParagraphGetByNumberResult,
|
|
51
|
+
ParagraphGetRequest,
|
|
52
|
+
ParagraphGetResult,
|
|
53
|
+
ProcessingStatusRequest,
|
|
54
|
+
ProcessingStatusResult,
|
|
55
|
+
SearchResult,
|
|
56
|
+
ServerError,
|
|
57
|
+
SemanticChunkMetadataUpdateRequest,
|
|
58
|
+
SemanticChunkMetadataUpdateResult,
|
|
59
|
+
SourceFileReconstructRequest,
|
|
60
|
+
TextReconstructionResult,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
from .models import (
|
|
65
|
+
ChunkHistoryRequest,
|
|
66
|
+
ChunkHistoryResult,
|
|
67
|
+
ChunkVersionAddRequest,
|
|
68
|
+
ChunkVersionAddResult,
|
|
69
|
+
ChunkVersionDeleteRequest,
|
|
70
|
+
ChunkVersionDeleteResult,
|
|
71
|
+
ChunkVersionDiffRequest,
|
|
72
|
+
ChunkVersionDiffResult,
|
|
73
|
+
ChunkVersionGetRequest,
|
|
74
|
+
ChunkVersionGetResult,
|
|
75
|
+
ChunkVersionListRequest,
|
|
76
|
+
ChunkVersionListResult,
|
|
77
|
+
ChunkVersionRestoreRequest,
|
|
78
|
+
ChunkVersionRestoreResult,
|
|
79
|
+
ChunkVersionRetireRequest,
|
|
80
|
+
ChunkVersionRetireResult,
|
|
81
|
+
ChunkVersionSetCurrentRequest,
|
|
82
|
+
ChunkVersionSetCurrentResult,
|
|
83
|
+
ChunkVersionUpdateRequest,
|
|
84
|
+
ChunkVersionUpdateResult,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
DOC_STORE_COMMANDS: tuple[str, ...] = (
|
|
88
|
+
"echo",
|
|
89
|
+
"long_task",
|
|
90
|
+
"job_status",
|
|
91
|
+
"queue_add_job",
|
|
92
|
+
"queue_start_job",
|
|
93
|
+
"queue_stop_job",
|
|
94
|
+
"queue_delete_job",
|
|
95
|
+
"queue_get_job_status",
|
|
96
|
+
"queue_get_job_logs",
|
|
97
|
+
"queue_list_jobs",
|
|
98
|
+
"queue_health",
|
|
99
|
+
"health",
|
|
100
|
+
"info",
|
|
101
|
+
"uuid4",
|
|
102
|
+
"document_get",
|
|
103
|
+
"chapter_get",
|
|
104
|
+
"paragraph_get",
|
|
105
|
+
"paragraph_get_by_number",
|
|
106
|
+
"document_create",
|
|
107
|
+
"document_update",
|
|
108
|
+
"document_chunk",
|
|
109
|
+
"document_export",
|
|
110
|
+
"chapter_text_get",
|
|
111
|
+
"source_file_reconstruct",
|
|
112
|
+
"chunk_version_list",
|
|
113
|
+
"chunk_history",
|
|
114
|
+
"chunk_version_get",
|
|
115
|
+
"chunk_version_add",
|
|
116
|
+
"chunk_version_update",
|
|
117
|
+
"chunk_version_set_current",
|
|
118
|
+
"chunk_version_restore",
|
|
119
|
+
"chunk_version_retire",
|
|
120
|
+
"chunk_version_diff",
|
|
121
|
+
"chunk_version_delete",
|
|
122
|
+
"document_rebind",
|
|
123
|
+
"processing_status",
|
|
124
|
+
"document_delete",
|
|
125
|
+
"entity_create",
|
|
126
|
+
"entity_list",
|
|
127
|
+
"entity_get",
|
|
128
|
+
"entity_text_get",
|
|
129
|
+
"entity_content_get",
|
|
130
|
+
"entity_update",
|
|
131
|
+
"entity_rebind_owner",
|
|
132
|
+
"entity_owner_tree",
|
|
133
|
+
"entity_soft_delete",
|
|
134
|
+
"entity_undelete",
|
|
135
|
+
"entity_hard_delete",
|
|
136
|
+
"entity_references",
|
|
137
|
+
"chunk_query_search",
|
|
138
|
+
"semantic_relations",
|
|
139
|
+
"semantic_chunk_metadata_update",
|
|
140
|
+
"corpus_audit",
|
|
141
|
+
"embeddings_rebuild",
|
|
142
|
+
"help",
|
|
143
|
+
"config",
|
|
144
|
+
"reload",
|
|
145
|
+
"settings",
|
|
146
|
+
"load",
|
|
147
|
+
"unload",
|
|
148
|
+
"plugins",
|
|
149
|
+
"transport_management",
|
|
150
|
+
"proxy_registration",
|
|
151
|
+
"roletest",
|
|
152
|
+
"transfer_upload_begin",
|
|
153
|
+
"transfer_upload_status",
|
|
154
|
+
"transfer_upload_complete",
|
|
155
|
+
"transfer_download_begin",
|
|
156
|
+
"transfer_download_status",
|
|
157
|
+
"backup_run",
|
|
158
|
+
"backup_emergency",
|
|
159
|
+
"backup_status",
|
|
160
|
+
"backup_points",
|
|
161
|
+
"backup_inspect",
|
|
162
|
+
"backup_restore",
|
|
163
|
+
"backup_recover_catalog",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class _Omitted:
|
|
168
|
+
"""Sentinel that distinguishes an omitted field from an explicit JSON null."""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
_OMITTED = _Omitted()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class JsonRpcClientLike(Protocol):
|
|
175
|
+
"""Minimal adapter surface used by the typed facade."""
|
|
176
|
+
|
|
177
|
+
async def execute_command_unified(self, command: str, params: Mapping[str, Any]) -> Any:
|
|
178
|
+
"""Execute a doc-store command through the adapter."""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TransferClientLike(JsonRpcClientLike, Protocol):
|
|
182
|
+
"""Adapter surface when file-transfer ingestion is used."""
|
|
183
|
+
|
|
184
|
+
async def upload_file(
|
|
185
|
+
self,
|
|
186
|
+
source_path: str,
|
|
187
|
+
*,
|
|
188
|
+
filename: str | None = None,
|
|
189
|
+
compression: str = "identity",
|
|
190
|
+
chunk_size: int | None = None,
|
|
191
|
+
on_progress: Any = None,
|
|
192
|
+
) -> Any:
|
|
193
|
+
"""Delegate file transfer to the adapter implementation."""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
T = TypeVar("T")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class DocStoreClientError(RuntimeError):
|
|
200
|
+
"""Raised when the adapter or server returns a structured command error."""
|
|
201
|
+
|
|
202
|
+
def __init__(self, error: ServerError) -> None:
|
|
203
|
+
self.error = error
|
|
204
|
+
super().__init__(error.message)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
_LOCK_ERROR_CODES = ("FILE_ALREADY_LOCKED", "SESSION_NOT_FOUND", "LOCK_SERVICE_UNAVAILABLE")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _lock_field(error: ServerError, name: str) -> Any:
|
|
211
|
+
"""Read a lock data field: ``details['data']`` when it is a mapping, else ``details``."""
|
|
212
|
+
|
|
213
|
+
details = error.details
|
|
214
|
+
if not isinstance(details, Mapping):
|
|
215
|
+
return None
|
|
216
|
+
data = details.get("data")
|
|
217
|
+
source = data if isinstance(data, Mapping) else details
|
|
218
|
+
return source.get(name)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FileAlreadyLockedError(DocStoreClientError):
|
|
222
|
+
"""The write was refused because another session holds the document."""
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def document_id(self) -> Any:
|
|
226
|
+
return _lock_field(self.error, "document_id")
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def holder_session_id(self) -> Any:
|
|
230
|
+
return _lock_field(self.error, "holder_session_id")
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def holder_idle_seconds(self) -> Any:
|
|
234
|
+
return _lock_field(self.error, "holder_idle_seconds")
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def seconds_left_before_timeout(self) -> Any:
|
|
238
|
+
return _lock_field(self.error, "seconds_left_before_timeout")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class SessionNotFoundError(DocStoreClientError):
|
|
242
|
+
"""The write was refused because the CAS session is unknown or expired."""
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def session_id(self) -> Any:
|
|
246
|
+
return _lock_field(self.error, "session_id")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class LockServiceUnavailableError(DocStoreClientError):
|
|
250
|
+
"""The write was refused because the lock check could not be made."""
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def reason(self) -> Any:
|
|
254
|
+
return _lock_field(self.error, "reason")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
_LOCK_ERROR_CLASSES: dict[str, type[DocStoreClientError]] = {
|
|
258
|
+
"FILE_ALREADY_LOCKED": FileAlreadyLockedError,
|
|
259
|
+
"SESSION_NOT_FOUND": SessionNotFoundError,
|
|
260
|
+
"LOCK_SERVICE_UNAVAILABLE": LockServiceUnavailableError,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _lock_code(error: ServerError) -> str | None:
|
|
265
|
+
"""The lock code of ``error``: ``error.code``, else the code carried in its data."""
|
|
266
|
+
|
|
267
|
+
if error.code in _LOCK_ERROR_CODES:
|
|
268
|
+
return str(error.code)
|
|
269
|
+
details = error.details
|
|
270
|
+
if isinstance(details, Mapping):
|
|
271
|
+
data = details.get("data")
|
|
272
|
+
for source in (data, details):
|
|
273
|
+
if isinstance(source, Mapping) and source.get("code") in _LOCK_ERROR_CODES:
|
|
274
|
+
return str(source["code"])
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
_NOT_FOUND = "NOT_FOUND"
|
|
279
|
+
_UUID_IN_TEXT = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class DocStoreNotFoundError(DocStoreClientError):
|
|
283
|
+
"""The requested target does not exist (server code ``NOT_FOUND``).
|
|
284
|
+
|
|
285
|
+
``code`` is always ``"NOT_FOUND"``. ``missing_id`` is the id of the absent target when the
|
|
286
|
+
server named it (in the error data or in the message), else ``None``. ``str()`` keeps the
|
|
287
|
+
server message, so callers that read the text keep working.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
code: str = _NOT_FOUND
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def missing_id(self) -> str | None:
|
|
294
|
+
details = self.error.details
|
|
295
|
+
if isinstance(details, Mapping):
|
|
296
|
+
data = details.get("data")
|
|
297
|
+
for source in (data, details):
|
|
298
|
+
if not isinstance(source, Mapping):
|
|
299
|
+
continue
|
|
300
|
+
for key in ("missing_id", "document_id", "chapter_id", "paragraph_id", "chunk_id", "entity_id", "id"):
|
|
301
|
+
value = source.get(key)
|
|
302
|
+
if isinstance(value, str) and value:
|
|
303
|
+
return value
|
|
304
|
+
match = _UUID_IN_TEXT.search(self.error.message)
|
|
305
|
+
return match.group(0) if match else None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _is_not_found(error: ServerError) -> bool:
|
|
309
|
+
if error.code == _NOT_FOUND:
|
|
310
|
+
return True
|
|
311
|
+
details = error.details
|
|
312
|
+
if isinstance(details, Mapping):
|
|
313
|
+
data = details.get("data")
|
|
314
|
+
for source in (data, details):
|
|
315
|
+
if isinstance(source, Mapping) and source.get("code") == _NOT_FOUND:
|
|
316
|
+
return True
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _client_error(error: ServerError) -> DocStoreClientError:
|
|
321
|
+
"""The typed lock or not-found error for its code, else the plain ``DocStoreClientError``."""
|
|
322
|
+
|
|
323
|
+
code = _lock_code(error)
|
|
324
|
+
if code is not None:
|
|
325
|
+
return _LOCK_ERROR_CLASSES[code](error)
|
|
326
|
+
if _is_not_found(error):
|
|
327
|
+
if error.code != _NOT_FOUND:
|
|
328
|
+
error = replace(error, code=_NOT_FOUND)
|
|
329
|
+
return DocStoreNotFoundError(error)
|
|
330
|
+
return DocStoreClientError(error)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class DocStoreClient:
|
|
334
|
+
"""Typed doc-store operations backed by an injected adapter client."""
|
|
335
|
+
|
|
336
|
+
commands: ClassVar[tuple[str, ...]] = DOC_STORE_COMMANDS
|
|
337
|
+
|
|
338
|
+
def __init__(
|
|
339
|
+
self,
|
|
340
|
+
adapter_client: JsonRpcClientLike,
|
|
341
|
+
*,
|
|
342
|
+
event_callback: Callable[[Mapping[str, Any]], Awaitable[None] | None] | None = None,
|
|
343
|
+
) -> None:
|
|
344
|
+
self._adapter_client = adapter_client
|
|
345
|
+
self._event_callback = event_callback
|
|
346
|
+
|
|
347
|
+
async def call(
|
|
348
|
+
self,
|
|
349
|
+
command: str,
|
|
350
|
+
params: Mapping[str, Any] | None = None,
|
|
351
|
+
**kwargs: Any,
|
|
352
|
+
) -> Any:
|
|
353
|
+
"""Execute any server command through the injected adapter client.
|
|
354
|
+
|
|
355
|
+
The adapter owns networking, TLS/mTLS, authentication, queue delivery, and
|
|
356
|
+
retries. This method only gives callers a stable command-level facade.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
command_name = command.strip()
|
|
360
|
+
if not command_name:
|
|
361
|
+
raise ValueError("command must be non-empty")
|
|
362
|
+
return _unwrap_response(
|
|
363
|
+
await self._dispatch(command_name, _merge_params(params, kwargs))
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
async def echo(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
367
|
+
return await self.call("echo", params, **kwargs)
|
|
368
|
+
|
|
369
|
+
async def long_task(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
370
|
+
return await self.call("long_task", params, **kwargs)
|
|
371
|
+
|
|
372
|
+
async def job_status(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
373
|
+
return await self.call("job_status", params, **kwargs)
|
|
374
|
+
|
|
375
|
+
async def queue_add_job(
|
|
376
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
377
|
+
) -> Any:
|
|
378
|
+
return await self.call("queue_add_job", params, **kwargs)
|
|
379
|
+
|
|
380
|
+
async def queue_start_job(
|
|
381
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
382
|
+
) -> Any:
|
|
383
|
+
return await self.call("queue_start_job", params, **kwargs)
|
|
384
|
+
|
|
385
|
+
async def queue_stop_job(
|
|
386
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
387
|
+
) -> Any:
|
|
388
|
+
return await self.call("queue_stop_job", params, **kwargs)
|
|
389
|
+
|
|
390
|
+
async def queue_delete_job(
|
|
391
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
392
|
+
) -> Any:
|
|
393
|
+
return await self.call("queue_delete_job", params, **kwargs)
|
|
394
|
+
|
|
395
|
+
async def queue_get_job_status(
|
|
396
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
397
|
+
) -> Any:
|
|
398
|
+
return await self.call("queue_get_job_status", params, **kwargs)
|
|
399
|
+
|
|
400
|
+
async def queue_get_job_logs(
|
|
401
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
402
|
+
) -> Any:
|
|
403
|
+
return await self.call("queue_get_job_logs", params, **kwargs)
|
|
404
|
+
|
|
405
|
+
async def queue_list_jobs(
|
|
406
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
407
|
+
) -> Any:
|
|
408
|
+
return await self.call("queue_list_jobs", params, **kwargs)
|
|
409
|
+
|
|
410
|
+
async def queue_health(
|
|
411
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
412
|
+
) -> Any:
|
|
413
|
+
return await self.call("queue_health", params, **kwargs)
|
|
414
|
+
|
|
415
|
+
async def document_get(
|
|
416
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
417
|
+
) -> Any:
|
|
418
|
+
return await self.call("document_get", params, **kwargs)
|
|
419
|
+
|
|
420
|
+
async def chapter_get(
|
|
421
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
422
|
+
) -> Any:
|
|
423
|
+
return await self.call("chapter_get", params, **kwargs)
|
|
424
|
+
|
|
425
|
+
async def paragraph_get(
|
|
426
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
427
|
+
) -> Any:
|
|
428
|
+
return await self.call("paragraph_get", params, **kwargs)
|
|
429
|
+
|
|
430
|
+
async def paragraph_get_by_number(
|
|
431
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
432
|
+
) -> Any:
|
|
433
|
+
return await self.call("paragraph_get_by_number", params, **kwargs)
|
|
434
|
+
|
|
435
|
+
async def document_create(
|
|
436
|
+
self,
|
|
437
|
+
params: Mapping[str, Any] | None = None,
|
|
438
|
+
*,
|
|
439
|
+
session_id: str | None = None,
|
|
440
|
+
**kwargs: Any,
|
|
441
|
+
) -> Any:
|
|
442
|
+
return await self.call("document_create", params, **kwargs, **_session_kwargs(session_id))
|
|
443
|
+
|
|
444
|
+
async def document_update(
|
|
445
|
+
self,
|
|
446
|
+
params: Mapping[str, Any] | None = None,
|
|
447
|
+
*,
|
|
448
|
+
session_id: str | None = None,
|
|
449
|
+
**kwargs: Any,
|
|
450
|
+
) -> Any:
|
|
451
|
+
return await self.call("document_update", params, **kwargs, **_session_kwargs(session_id))
|
|
452
|
+
|
|
453
|
+
async def document_chunk(
|
|
454
|
+
self,
|
|
455
|
+
params: Mapping[str, Any] | None = None,
|
|
456
|
+
*,
|
|
457
|
+
session_id: str | None = None,
|
|
458
|
+
**kwargs: Any,
|
|
459
|
+
) -> Any:
|
|
460
|
+
return await self.call("document_chunk", params, **kwargs, **_session_kwargs(session_id))
|
|
461
|
+
|
|
462
|
+
async def document_export(
|
|
463
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
464
|
+
) -> Any:
|
|
465
|
+
return await self.call("document_export", params, **kwargs)
|
|
466
|
+
|
|
467
|
+
async def chapter_text_get(
|
|
468
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
469
|
+
) -> Any:
|
|
470
|
+
return await self.call("chapter_text_get", params, **kwargs)
|
|
471
|
+
|
|
472
|
+
async def source_file_reconstruct(
|
|
473
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
474
|
+
) -> Any:
|
|
475
|
+
return await self.call("source_file_reconstruct", params, **kwargs)
|
|
476
|
+
|
|
477
|
+
async def chunk_version_list(
|
|
478
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
479
|
+
) -> Any:
|
|
480
|
+
return await self.call("chunk_version_list", params, **kwargs)
|
|
481
|
+
|
|
482
|
+
async def chunk_history(
|
|
483
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
484
|
+
) -> Any:
|
|
485
|
+
return await self.call("chunk_history", params, **kwargs)
|
|
486
|
+
|
|
487
|
+
async def chunk_version_get(
|
|
488
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
489
|
+
) -> Any:
|
|
490
|
+
return await self.call("chunk_version_get", params, **kwargs)
|
|
491
|
+
|
|
492
|
+
async def chunk_version_add(
|
|
493
|
+
self,
|
|
494
|
+
params: Mapping[str, Any] | None = None,
|
|
495
|
+
*,
|
|
496
|
+
session_id: str | None = None,
|
|
497
|
+
**kwargs: Any,
|
|
498
|
+
) -> Any:
|
|
499
|
+
return await self.call("chunk_version_add", params, **kwargs, **_session_kwargs(session_id))
|
|
500
|
+
|
|
501
|
+
async def chunk_version_update(
|
|
502
|
+
self,
|
|
503
|
+
params: Mapping[str, Any] | None = None,
|
|
504
|
+
*,
|
|
505
|
+
session_id: str | None = None,
|
|
506
|
+
**kwargs: Any,
|
|
507
|
+
) -> Any:
|
|
508
|
+
return await self.call("chunk_version_update", params, **kwargs, **_session_kwargs(session_id))
|
|
509
|
+
|
|
510
|
+
async def chunk_version_set_current(
|
|
511
|
+
self,
|
|
512
|
+
params: Mapping[str, Any] | None = None,
|
|
513
|
+
*,
|
|
514
|
+
session_id: str | None = None,
|
|
515
|
+
**kwargs: Any,
|
|
516
|
+
) -> Any:
|
|
517
|
+
return await self.call("chunk_version_set_current", params, **kwargs, **_session_kwargs(session_id))
|
|
518
|
+
|
|
519
|
+
async def chunk_version_restore(
|
|
520
|
+
self,
|
|
521
|
+
params: Mapping[str, Any] | None = None,
|
|
522
|
+
*,
|
|
523
|
+
session_id: str | None = None,
|
|
524
|
+
**kwargs: Any,
|
|
525
|
+
) -> Any:
|
|
526
|
+
return await self.call("chunk_version_restore", params, **kwargs, **_session_kwargs(session_id))
|
|
527
|
+
|
|
528
|
+
async def chunk_version_retire(
|
|
529
|
+
self,
|
|
530
|
+
params: Mapping[str, Any] | None = None,
|
|
531
|
+
*,
|
|
532
|
+
session_id: str | None = None,
|
|
533
|
+
**kwargs: Any,
|
|
534
|
+
) -> Any:
|
|
535
|
+
return await self.call("chunk_version_retire", params, **kwargs, **_session_kwargs(session_id))
|
|
536
|
+
|
|
537
|
+
async def chunk_version_diff(
|
|
538
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
539
|
+
) -> Any:
|
|
540
|
+
return await self.call("chunk_version_diff", params, **kwargs)
|
|
541
|
+
|
|
542
|
+
async def chunk_version_delete(
|
|
543
|
+
self,
|
|
544
|
+
params: Mapping[str, Any] | None = None,
|
|
545
|
+
*,
|
|
546
|
+
session_id: str | None = None,
|
|
547
|
+
**kwargs: Any,
|
|
548
|
+
) -> Any:
|
|
549
|
+
return await self.call("chunk_version_delete", params, **kwargs, **_session_kwargs(session_id))
|
|
550
|
+
|
|
551
|
+
async def document_rebind(
|
|
552
|
+
self,
|
|
553
|
+
params: Mapping[str, Any] | None = None,
|
|
554
|
+
*,
|
|
555
|
+
session_id: str | None = None,
|
|
556
|
+
**kwargs: Any,
|
|
557
|
+
) -> Any:
|
|
558
|
+
return await self.call("document_rebind", params, **kwargs, **_session_kwargs(session_id))
|
|
559
|
+
|
|
560
|
+
async def processing_status(
|
|
561
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
562
|
+
) -> Any:
|
|
563
|
+
return await self.call("processing_status", params, **kwargs)
|
|
564
|
+
|
|
565
|
+
async def document_delete(
|
|
566
|
+
self,
|
|
567
|
+
params: Mapping[str, Any] | None = None,
|
|
568
|
+
*,
|
|
569
|
+
session_id: str | None = None,
|
|
570
|
+
**kwargs: Any,
|
|
571
|
+
) -> Any:
|
|
572
|
+
return await self.call("document_delete", params, **kwargs, **_session_kwargs(session_id))
|
|
573
|
+
|
|
574
|
+
async def entity_create(
|
|
575
|
+
self,
|
|
576
|
+
params: Mapping[str, Any] | None = None,
|
|
577
|
+
*,
|
|
578
|
+
session_id: str | None = None,
|
|
579
|
+
**kwargs: Any,
|
|
580
|
+
) -> Any:
|
|
581
|
+
return await self.call("entity_create", params, **kwargs, **_session_kwargs(session_id))
|
|
582
|
+
|
|
583
|
+
async def entity_list(
|
|
584
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
585
|
+
) -> Any:
|
|
586
|
+
return await self.call("entity_list", params, **kwargs)
|
|
587
|
+
|
|
588
|
+
async def entity_get(
|
|
589
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
590
|
+
) -> Any:
|
|
591
|
+
return await self.call("entity_get", params, **kwargs)
|
|
592
|
+
|
|
593
|
+
async def entity_update(
|
|
594
|
+
self,
|
|
595
|
+
params: Mapping[str, Any] | None = None,
|
|
596
|
+
*,
|
|
597
|
+
session_id: str | None = None,
|
|
598
|
+
**kwargs: Any,
|
|
599
|
+
) -> Any:
|
|
600
|
+
return await self.call("entity_update", params, **kwargs, **_session_kwargs(session_id))
|
|
601
|
+
|
|
602
|
+
async def entity_text_get(
|
|
603
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
604
|
+
) -> EntityTextGetResult:
|
|
605
|
+
"""Raw public invocation preserving scalar or list text results."""
|
|
606
|
+
return await self.call("entity_text_get", params, **kwargs)
|
|
607
|
+
|
|
608
|
+
async def entity_content_get(
|
|
609
|
+
self, ids: list[str], as_file: bool = False, **kwargs: Any
|
|
610
|
+
) -> Any:
|
|
611
|
+
return await self.call(
|
|
612
|
+
"entity_content_get", {"ids": list(ids), "as_file": as_file}, **kwargs
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
async def entity_content_get_download(
|
|
616
|
+
self, receipt: Mapping[str, Any], **kwargs: Any
|
|
617
|
+
) -> Any:
|
|
618
|
+
return await self.transfer_download_begin(receipt["download"], **kwargs)
|
|
619
|
+
|
|
620
|
+
async def entity_rebind_owner(
|
|
621
|
+
self,
|
|
622
|
+
params: Mapping[str, Any] | None = None,
|
|
623
|
+
*,
|
|
624
|
+
session_id: str | None = None,
|
|
625
|
+
**kwargs: Any,
|
|
626
|
+
) -> Any:
|
|
627
|
+
return await self.call("entity_rebind_owner", params, **kwargs, **_session_kwargs(session_id))
|
|
628
|
+
|
|
629
|
+
async def entity_owner_tree(
|
|
630
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
631
|
+
) -> Any:
|
|
632
|
+
return await self.call("entity_owner_tree", params, **kwargs)
|
|
633
|
+
|
|
634
|
+
async def entity_soft_delete(
|
|
635
|
+
self,
|
|
636
|
+
params: Mapping[str, Any] | None = None,
|
|
637
|
+
*,
|
|
638
|
+
session_id: str | None = None,
|
|
639
|
+
**kwargs: Any,
|
|
640
|
+
) -> Any:
|
|
641
|
+
return await self.call("entity_soft_delete", params, **kwargs, **_session_kwargs(session_id))
|
|
642
|
+
|
|
643
|
+
async def entity_undelete(
|
|
644
|
+
self,
|
|
645
|
+
params: Mapping[str, Any] | None = None,
|
|
646
|
+
*,
|
|
647
|
+
session_id: str | None = None,
|
|
648
|
+
**kwargs: Any,
|
|
649
|
+
) -> Any:
|
|
650
|
+
return await self.call("entity_undelete", params, **kwargs, **_session_kwargs(session_id))
|
|
651
|
+
|
|
652
|
+
async def entity_hard_delete(
|
|
653
|
+
self,
|
|
654
|
+
params: Mapping[str, Any] | None = None,
|
|
655
|
+
*,
|
|
656
|
+
session_id: str | None = None,
|
|
657
|
+
**kwargs: Any,
|
|
658
|
+
) -> Any:
|
|
659
|
+
return await self.call("entity_hard_delete", params, **kwargs, **_session_kwargs(session_id))
|
|
660
|
+
|
|
661
|
+
async def entity_references(
|
|
662
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
663
|
+
) -> Any:
|
|
664
|
+
return await self.call("entity_references", params, **kwargs)
|
|
665
|
+
|
|
666
|
+
async def chunk_query_search(
|
|
667
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
668
|
+
) -> Any:
|
|
669
|
+
return await self.call("chunk_query_search", params, **kwargs)
|
|
670
|
+
|
|
671
|
+
async def semantic_relations(
|
|
672
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
673
|
+
) -> Any:
|
|
674
|
+
return await self.call("semantic_relations", params, **kwargs)
|
|
675
|
+
|
|
676
|
+
async def semantic_chunk_metadata_update(
|
|
677
|
+
self,
|
|
678
|
+
params: Mapping[str, Any] | None = None,
|
|
679
|
+
*,
|
|
680
|
+
session_id: str | None = None,
|
|
681
|
+
**kwargs: Any,
|
|
682
|
+
) -> Any:
|
|
683
|
+
return await self.call("semantic_chunk_metadata_update", params, **kwargs, **_session_kwargs(session_id))
|
|
684
|
+
|
|
685
|
+
async def corpus_audit(
|
|
686
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
687
|
+
) -> Any:
|
|
688
|
+
return await self.call("corpus_audit", params, **kwargs)
|
|
689
|
+
|
|
690
|
+
async def embeddings_rebuild(
|
|
691
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
692
|
+
) -> Any:
|
|
693
|
+
return await self.call("embeddings_rebuild", params, **kwargs)
|
|
694
|
+
|
|
695
|
+
async def help(
|
|
696
|
+
self,
|
|
697
|
+
cmdname: str | None = None,
|
|
698
|
+
params: Mapping[str, Any] | None = None,
|
|
699
|
+
**kwargs: Any,
|
|
700
|
+
) -> Any:
|
|
701
|
+
request = _merge_params(params, kwargs)
|
|
702
|
+
if cmdname is not None:
|
|
703
|
+
if "cmdname" in request:
|
|
704
|
+
raise ValueError("cmdname supplied twice")
|
|
705
|
+
request["cmdname"] = cmdname
|
|
706
|
+
return await self.call("help", request)
|
|
707
|
+
|
|
708
|
+
async def health(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
709
|
+
return await self.call("health", params, **kwargs)
|
|
710
|
+
|
|
711
|
+
async def info(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
712
|
+
return await self.call("info", params, **kwargs)
|
|
713
|
+
|
|
714
|
+
async def uuid4(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
715
|
+
return await self.call("uuid4", params, **kwargs)
|
|
716
|
+
|
|
717
|
+
async def config(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
718
|
+
return await self.call("config", params, **kwargs)
|
|
719
|
+
|
|
720
|
+
async def reload(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
721
|
+
return await self.call("reload", params, **kwargs)
|
|
722
|
+
|
|
723
|
+
async def settings(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
724
|
+
return await self.call("settings", params, **kwargs)
|
|
725
|
+
|
|
726
|
+
async def load(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
727
|
+
return await self.call("load", params, **kwargs)
|
|
728
|
+
|
|
729
|
+
async def unload(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
730
|
+
return await self.call("unload", params, **kwargs)
|
|
731
|
+
|
|
732
|
+
async def plugins(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
733
|
+
return await self.call("plugins", params, **kwargs)
|
|
734
|
+
|
|
735
|
+
async def transport_management(
|
|
736
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
737
|
+
) -> Any:
|
|
738
|
+
return await self.call("transport_management", params, **kwargs)
|
|
739
|
+
|
|
740
|
+
async def proxy_registration(
|
|
741
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
742
|
+
) -> Any:
|
|
743
|
+
return await self.call("proxy_registration", params, **kwargs)
|
|
744
|
+
|
|
745
|
+
async def roletest(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
746
|
+
return await self.call("roletest", params, **kwargs)
|
|
747
|
+
|
|
748
|
+
async def transfer_upload_begin(
|
|
749
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
750
|
+
) -> Any:
|
|
751
|
+
return await self.call("transfer_upload_begin", params, **kwargs)
|
|
752
|
+
|
|
753
|
+
async def transfer_upload_status(
|
|
754
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
755
|
+
) -> Any:
|
|
756
|
+
return await self.call("transfer_upload_status", params, **kwargs)
|
|
757
|
+
|
|
758
|
+
async def transfer_upload_complete(
|
|
759
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
760
|
+
) -> Any:
|
|
761
|
+
return await self.call("transfer_upload_complete", params, **kwargs)
|
|
762
|
+
|
|
763
|
+
async def transfer_download_begin(
|
|
764
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
765
|
+
) -> Any:
|
|
766
|
+
return await self.call("transfer_download_begin", params, **kwargs)
|
|
767
|
+
|
|
768
|
+
async def transfer_download_status(
|
|
769
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
770
|
+
) -> Any:
|
|
771
|
+
return await self.call("transfer_download_status", params, **kwargs)
|
|
772
|
+
|
|
773
|
+
async def backup_emergency(self) -> BackupEmergencyResult:
|
|
774
|
+
"""Save an explicit independent FULL salvage archive and await its final result."""
|
|
775
|
+
return cast(BackupEmergencyResult, await self.call("backup_emergency", {}))
|
|
776
|
+
|
|
777
|
+
async def backup_run(
|
|
778
|
+
self,
|
|
779
|
+
*,
|
|
780
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
781
|
+
dry_run: bool | _Omitted = _OMITTED,
|
|
782
|
+
) -> BackupRunResult:
|
|
783
|
+
"""Create or preview a backup using the configured repository.
|
|
784
|
+
|
|
785
|
+
Omitted options stay off the wire, so server configuration and the
|
|
786
|
+
command's ``dry_run=False`` default remain authoritative. Explicit
|
|
787
|
+
``catalog_directory=None`` is sent as JSON null, which also means the
|
|
788
|
+
configured catalog directory. Queued delivery is handled by the
|
|
789
|
+
adapter exactly once.
|
|
790
|
+
"""
|
|
791
|
+
|
|
792
|
+
result = await self.call(
|
|
793
|
+
"backup_run",
|
|
794
|
+
_provided_params(
|
|
795
|
+
catalog_directory=catalog_directory,
|
|
796
|
+
dry_run=dry_run,
|
|
797
|
+
),
|
|
798
|
+
)
|
|
799
|
+
return cast(BackupRunResult, result)
|
|
800
|
+
|
|
801
|
+
async def backup_status(
|
|
802
|
+
self,
|
|
803
|
+
*,
|
|
804
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
805
|
+
) -> BackupStatusResult:
|
|
806
|
+
"""Read backup configuration, worker state, and journal progress."""
|
|
807
|
+
|
|
808
|
+
result = await self.call(
|
|
809
|
+
"backup_status",
|
|
810
|
+
_provided_params(catalog_directory=catalog_directory),
|
|
811
|
+
)
|
|
812
|
+
return cast(BackupStatusResult, result)
|
|
813
|
+
|
|
814
|
+
async def backup_points(
|
|
815
|
+
self,
|
|
816
|
+
*,
|
|
817
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
818
|
+
) -> BackupPointsResult:
|
|
819
|
+
"""List committed saved points in package order."""
|
|
820
|
+
|
|
821
|
+
result = await self.call(
|
|
822
|
+
"backup_points",
|
|
823
|
+
_provided_params(catalog_directory=catalog_directory),
|
|
824
|
+
)
|
|
825
|
+
return cast(BackupPointsResult, result)
|
|
826
|
+
|
|
827
|
+
async def backup_inspect(
|
|
828
|
+
self,
|
|
829
|
+
*,
|
|
830
|
+
point: str,
|
|
831
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
832
|
+
) -> BackupInspectResult:
|
|
833
|
+
"""Inspect a saved point's chain, files, and restorable state."""
|
|
834
|
+
|
|
835
|
+
result = await self.call(
|
|
836
|
+
"backup_inspect",
|
|
837
|
+
_provided_params(
|
|
838
|
+
point=point,
|
|
839
|
+
catalog_directory=catalog_directory,
|
|
840
|
+
),
|
|
841
|
+
)
|
|
842
|
+
return cast(BackupInspectResult, result)
|
|
843
|
+
|
|
844
|
+
async def backup_restore(
|
|
845
|
+
self,
|
|
846
|
+
*,
|
|
847
|
+
point: str,
|
|
848
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
849
|
+
dry_run: bool | _Omitted = _OMITTED,
|
|
850
|
+
replace: bool | _Omitted = _OMITTED,
|
|
851
|
+
) -> BackupRestoreResult:
|
|
852
|
+
"""Restore a saved point, or preview the restore when ``dry_run`` is true.
|
|
853
|
+
|
|
854
|
+
Omitted booleans preserve the server's false defaults. The adapter
|
|
855
|
+
owns queued delivery and final-result polling.
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
result = await self.call(
|
|
859
|
+
"backup_restore",
|
|
860
|
+
_provided_params(
|
|
861
|
+
point=point,
|
|
862
|
+
catalog_directory=catalog_directory,
|
|
863
|
+
dry_run=dry_run,
|
|
864
|
+
replace=replace,
|
|
865
|
+
),
|
|
866
|
+
)
|
|
867
|
+
return cast(BackupRestoreResult, result)
|
|
868
|
+
|
|
869
|
+
async def backup_recover_catalog(
|
|
870
|
+
self,
|
|
871
|
+
*,
|
|
872
|
+
destination: str,
|
|
873
|
+
catalog_copy: str,
|
|
874
|
+
catalog_directory: str | None | _Omitted = _OMITTED,
|
|
875
|
+
dry_run: bool | _Omitted = _OMITTED,
|
|
876
|
+
) -> BackupRecoverCatalogResult:
|
|
877
|
+
"""Recover a catalog copy to a new destination without activating it.
|
|
878
|
+
|
|
879
|
+
Omitted options preserve the configured catalog and server default.
|
|
880
|
+
The adapter owns queued delivery and final-result polling.
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
result = await self.call(
|
|
884
|
+
"backup_recover_catalog",
|
|
885
|
+
_provided_params(
|
|
886
|
+
destination=destination,
|
|
887
|
+
catalog_copy=catalog_copy,
|
|
888
|
+
catalog_directory=catalog_directory,
|
|
889
|
+
dry_run=dry_run,
|
|
890
|
+
),
|
|
891
|
+
)
|
|
892
|
+
return cast(BackupRecoverCatalogResult, result)
|
|
893
|
+
|
|
894
|
+
async def create_document(
|
|
895
|
+
self,
|
|
896
|
+
request: DocumentCreateRequest,
|
|
897
|
+
*,
|
|
898
|
+
source_path: str | None = None,
|
|
899
|
+
filename: str | None = None,
|
|
900
|
+
session_id: str | None = None,
|
|
901
|
+
) -> DocumentCreateResult:
|
|
902
|
+
params = await self._write_params(request, source_path=source_path, filename=filename)
|
|
903
|
+
return await self._execute("document_create", _with_session(params, session_id), DocumentCreateResult)
|
|
904
|
+
|
|
905
|
+
async def update_document(
|
|
906
|
+
self,
|
|
907
|
+
request: DocumentUpdateRequest,
|
|
908
|
+
*,
|
|
909
|
+
source_path: str | None = None,
|
|
910
|
+
filename: str | None = None,
|
|
911
|
+
session_id: str | None = None,
|
|
912
|
+
) -> DocumentUpdateResult:
|
|
913
|
+
params = await self._write_params(request, source_path=source_path, filename=filename)
|
|
914
|
+
return await self._execute("document_update", _with_session(params, session_id), DocumentUpdateResult)
|
|
915
|
+
|
|
916
|
+
async def chunk_document(
|
|
917
|
+
self, request: DocumentChunkRequest, *, session_id: str | None = None
|
|
918
|
+
) -> DocumentChunkResult:
|
|
919
|
+
return await self._execute("document_chunk", _with_session(request.to_params(), session_id), DocumentChunkResult)
|
|
920
|
+
|
|
921
|
+
async def rebind_document(
|
|
922
|
+
self, request: DocumentRebindRequest, *, session_id: str | None = None
|
|
923
|
+
) -> DocumentRebindResult:
|
|
924
|
+
return await self._execute(
|
|
925
|
+
"document_rebind", _with_session(request.to_params(), session_id), DocumentRebindResult
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
async def get_processing_status(
|
|
929
|
+
self, request: ProcessingStatusRequest
|
|
930
|
+
) -> ProcessingStatusResult:
|
|
931
|
+
return await self._execute(
|
|
932
|
+
"processing_status", request.to_params(), ProcessingStatusResult
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
async def get_document(self, request: DocumentGetRequest) -> DocumentGetResult:
|
|
936
|
+
return await self._execute("document_get", request.to_params(), DocumentGetResult)
|
|
937
|
+
|
|
938
|
+
async def get_chapter(self, request: ChapterGetRequest) -> ChapterGetResult:
|
|
939
|
+
return await self._execute("chapter_get", request.to_params(), ChapterGetResult)
|
|
940
|
+
|
|
941
|
+
async def get_chapter_text(
|
|
942
|
+
self,
|
|
943
|
+
request: ChapterTextGetRequest,
|
|
944
|
+
) -> TextReconstructionResult:
|
|
945
|
+
"""Reconstruct chapter text (chapter_text_get).
|
|
946
|
+
|
|
947
|
+
The request field text_offset (0 to 1000000000, default absent means 0) selects the
|
|
948
|
+
start in unicode code points. The result attributes total_chars, total_sha256,
|
|
949
|
+
text_offset and next_text_offset allow continuation: repeat the call with
|
|
950
|
+
text_offset=next_text_offset until next_text_offset is None.
|
|
951
|
+
"""
|
|
952
|
+
return await self._execute(
|
|
953
|
+
"chapter_text_get",
|
|
954
|
+
request.to_params(),
|
|
955
|
+
TextReconstructionResult,
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
async def reconstruct_source_file(
|
|
959
|
+
self,
|
|
960
|
+
request: SourceFileReconstructRequest,
|
|
961
|
+
) -> TextReconstructionResult:
|
|
962
|
+
"""Reconstruct stored source text (source_file_reconstruct).
|
|
963
|
+
|
|
964
|
+
The request field text_offset (0 to 1000000000, default absent means 0) selects the
|
|
965
|
+
start in unicode code points. The result attributes total_chars, total_sha256,
|
|
966
|
+
text_offset and next_text_offset allow continuation: repeat the call with
|
|
967
|
+
text_offset=next_text_offset until next_text_offset is None.
|
|
968
|
+
"""
|
|
969
|
+
return await self._execute(
|
|
970
|
+
"source_file_reconstruct",
|
|
971
|
+
request.to_params(),
|
|
972
|
+
TextReconstructionResult,
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
async def list_chunk_versions(
|
|
976
|
+
self,
|
|
977
|
+
request: "ChunkVersionListRequest",
|
|
978
|
+
) -> "ChunkVersionListResult":
|
|
979
|
+
return await self._execute(
|
|
980
|
+
"chunk_version_list",
|
|
981
|
+
request.to_params(),
|
|
982
|
+
_model_type("ChunkVersionListResult"),
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
async def get_chunk_history(
|
|
986
|
+
self,
|
|
987
|
+
request: "ChunkHistoryRequest",
|
|
988
|
+
) -> "ChunkHistoryResult":
|
|
989
|
+
return await self._execute(
|
|
990
|
+
"chunk_history",
|
|
991
|
+
request.to_params(),
|
|
992
|
+
_model_type("ChunkHistoryResult"),
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
async def get_chunk_version(
|
|
996
|
+
self,
|
|
997
|
+
request: "ChunkVersionGetRequest",
|
|
998
|
+
) -> "ChunkVersionGetResult":
|
|
999
|
+
return await self._execute(
|
|
1000
|
+
"chunk_version_get",
|
|
1001
|
+
request.to_params(),
|
|
1002
|
+
_model_type("ChunkVersionGetResult"),
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
async def add_chunk_version(
|
|
1006
|
+
self,
|
|
1007
|
+
request: "ChunkVersionAddRequest",
|
|
1008
|
+
*,
|
|
1009
|
+
session_id: str | None = None,
|
|
1010
|
+
) -> "ChunkVersionAddResult":
|
|
1011
|
+
return await self._execute(
|
|
1012
|
+
"chunk_version_add",
|
|
1013
|
+
_with_session(request.to_params(), session_id),
|
|
1014
|
+
_model_type("ChunkVersionAddResult"),
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
async def update_chunk_version(
|
|
1018
|
+
self,
|
|
1019
|
+
request: "ChunkVersionUpdateRequest",
|
|
1020
|
+
*,
|
|
1021
|
+
session_id: str | None = None,
|
|
1022
|
+
) -> "ChunkVersionUpdateResult":
|
|
1023
|
+
return await self._execute(
|
|
1024
|
+
"chunk_version_update",
|
|
1025
|
+
_with_session(request.to_params(), session_id),
|
|
1026
|
+
_model_type("ChunkVersionUpdateResult"),
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
async def set_current_chunk_version(
|
|
1030
|
+
self,
|
|
1031
|
+
request: "ChunkVersionSetCurrentRequest",
|
|
1032
|
+
*,
|
|
1033
|
+
session_id: str | None = None,
|
|
1034
|
+
) -> "ChunkVersionSetCurrentResult":
|
|
1035
|
+
return await self._execute(
|
|
1036
|
+
"chunk_version_set_current",
|
|
1037
|
+
_with_session(request.to_params(), session_id),
|
|
1038
|
+
_model_type("ChunkVersionSetCurrentResult"),
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
async def restore_chunk_version(
|
|
1042
|
+
self,
|
|
1043
|
+
request: "ChunkVersionRestoreRequest",
|
|
1044
|
+
*,
|
|
1045
|
+
session_id: str | None = None,
|
|
1046
|
+
) -> "ChunkVersionRestoreResult":
|
|
1047
|
+
return await self._execute(
|
|
1048
|
+
"chunk_version_restore",
|
|
1049
|
+
_with_session(request.to_params(), session_id),
|
|
1050
|
+
_model_type("ChunkVersionRestoreResult"),
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
async def retire_chunk_version(
|
|
1054
|
+
self,
|
|
1055
|
+
request: "ChunkVersionRetireRequest",
|
|
1056
|
+
*,
|
|
1057
|
+
session_id: str | None = None,
|
|
1058
|
+
) -> "ChunkVersionRetireResult":
|
|
1059
|
+
return await self._execute(
|
|
1060
|
+
"chunk_version_retire",
|
|
1061
|
+
_with_session(request.to_params(), session_id),
|
|
1062
|
+
_model_type("ChunkVersionRetireResult"),
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
async def diff_chunk_versions(
|
|
1066
|
+
self,
|
|
1067
|
+
request: "ChunkVersionDiffRequest",
|
|
1068
|
+
) -> "ChunkVersionDiffResult":
|
|
1069
|
+
return await self._execute(
|
|
1070
|
+
"chunk_version_diff",
|
|
1071
|
+
request.to_params(),
|
|
1072
|
+
_model_type("ChunkVersionDiffResult"),
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
async def delete_chunk_version(
|
|
1076
|
+
self,
|
|
1077
|
+
request: "ChunkVersionDeleteRequest",
|
|
1078
|
+
*,
|
|
1079
|
+
session_id: str | None = None,
|
|
1080
|
+
) -> "ChunkVersionDeleteResult":
|
|
1081
|
+
return await self._execute(
|
|
1082
|
+
"chunk_version_delete",
|
|
1083
|
+
_with_session(request.to_params(), session_id),
|
|
1084
|
+
_model_type("ChunkVersionDeleteResult"),
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
async def get_paragraph(self, request: ParagraphGetRequest) -> ParagraphGetResult:
|
|
1088
|
+
return await self._execute("paragraph_get", request.to_params(), ParagraphGetResult)
|
|
1089
|
+
|
|
1090
|
+
async def get_paragraph_by_number(
|
|
1091
|
+
self,
|
|
1092
|
+
request: ParagraphGetByNumberRequest,
|
|
1093
|
+
) -> ParagraphGetByNumberResult:
|
|
1094
|
+
return await self._execute(
|
|
1095
|
+
"paragraph_get_by_number",
|
|
1096
|
+
request.to_params(),
|
|
1097
|
+
ParagraphGetByNumberResult,
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
async def delete_document(
|
|
1101
|
+
self, request: DocumentDeleteRequest, *, session_id: str | None = None
|
|
1102
|
+
) -> DocumentDeleteResult:
|
|
1103
|
+
return await self._execute("document_delete", _with_session(request.to_params(), session_id), DocumentDeleteResult)
|
|
1104
|
+
|
|
1105
|
+
async def list_entities(self, request: EntityListRequest) -> EntityListResult:
|
|
1106
|
+
return await self._execute("entity_list", request.to_params(), EntityListResult)
|
|
1107
|
+
|
|
1108
|
+
async def get_entity(self, request: EntityGetRequest) -> EntityGetResult:
|
|
1109
|
+
return await self._execute("entity_get", request.to_params(), EntityGetResult)
|
|
1110
|
+
|
|
1111
|
+
async def get_entity_text(self, request: EntityTextGetRequest) -> EntityTextGetResult:
|
|
1112
|
+
"""Return canonical text, preserving scalar/list shape, order and duplicates."""
|
|
1113
|
+
result = await self.call("entity_text_get", request.to_params())
|
|
1114
|
+
batch = isinstance(request.entity_id, list) or "," in request.entity_id
|
|
1115
|
+
if batch:
|
|
1116
|
+
count = len(request.entity_id) if isinstance(request.entity_id, list) else len(request.entity_id.split(","))
|
|
1117
|
+
if not isinstance(result, list) or len(result) != count or any(not isinstance(item, str) for item in result):
|
|
1118
|
+
raise TypeError("entity_text_get batch result must be an ordered list of strings matching the request count")
|
|
1119
|
+
elif not isinstance(result, str):
|
|
1120
|
+
raise TypeError("entity_text_get scalar result must be a string")
|
|
1121
|
+
return result
|
|
1122
|
+
|
|
1123
|
+
async def soft_delete_entities(
|
|
1124
|
+
self, request: EntityIdsRequest, *, session_id: str | None = None
|
|
1125
|
+
) -> EntityLifecycleResult:
|
|
1126
|
+
return await self._execute("entity_soft_delete", _with_session(request.to_params(), session_id), EntityLifecycleResult)
|
|
1127
|
+
|
|
1128
|
+
async def undelete_entities(
|
|
1129
|
+
self, request: EntityIdsRequest, *, session_id: str | None = None
|
|
1130
|
+
) -> EntityLifecycleResult:
|
|
1131
|
+
return await self._execute("entity_undelete", _with_session(request.to_params(), session_id), EntityLifecycleResult)
|
|
1132
|
+
|
|
1133
|
+
async def hard_delete_entities(
|
|
1134
|
+
self, request: EntityIdsRequest, *, session_id: str | None = None
|
|
1135
|
+
) -> EntityLifecycleResult:
|
|
1136
|
+
return await self._execute("entity_hard_delete", _with_session(request.to_params(), session_id), EntityLifecycleResult)
|
|
1137
|
+
|
|
1138
|
+
async def get_entity_references(self, request: EntityReferencesRequest) -> EntityReferencesResult:
|
|
1139
|
+
return await self._execute("entity_references", request.to_params(), EntityReferencesResult)
|
|
1140
|
+
|
|
1141
|
+
async def get_entity_owner_tree(self, request: EntityOwnerTreeRequest) -> EntityOwnerTreeResult:
|
|
1142
|
+
return await self._execute("entity_owner_tree", request.to_params(), EntityOwnerTreeResult)
|
|
1143
|
+
|
|
1144
|
+
async def update_semantic_chunk_metadata(
|
|
1145
|
+
self,
|
|
1146
|
+
request: SemanticChunkMetadataUpdateRequest,
|
|
1147
|
+
*,
|
|
1148
|
+
session_id: str | None = None,
|
|
1149
|
+
) -> SemanticChunkMetadataUpdateResult:
|
|
1150
|
+
return await self._execute(
|
|
1151
|
+
"semantic_chunk_metadata_update",
|
|
1152
|
+
_with_session(request.to_params(), session_id),
|
|
1153
|
+
SemanticChunkMetadataUpdateResult,
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
async def search(self, query: ChunkQuery) -> SearchResult:
|
|
1157
|
+
return await self._execute("chunk_query_search", {"query": _dump_query(query)}, SearchResult)
|
|
1158
|
+
|
|
1159
|
+
async def upload_file(
|
|
1160
|
+
self,
|
|
1161
|
+
source_path: str,
|
|
1162
|
+
*,
|
|
1163
|
+
filename: str | None = None,
|
|
1164
|
+
compression: str = "identity",
|
|
1165
|
+
chunk_size: int | None = None,
|
|
1166
|
+
on_progress: Any = None,
|
|
1167
|
+
) -> Mapping[str, Any]:
|
|
1168
|
+
"""Upload a file through the injected adapter and return a transfer reference."""
|
|
1169
|
+
|
|
1170
|
+
upload = getattr(self._adapter_client, "upload_file", None)
|
|
1171
|
+
if upload is None:
|
|
1172
|
+
raise TypeError("adapter client does not expose upload_file")
|
|
1173
|
+
receipt = await upload(
|
|
1174
|
+
source_path,
|
|
1175
|
+
filename=filename,
|
|
1176
|
+
compression=compression,
|
|
1177
|
+
chunk_size=chunk_size,
|
|
1178
|
+
on_progress=on_progress,
|
|
1179
|
+
)
|
|
1180
|
+
return _completed_transfer_reference(receipt)
|
|
1181
|
+
|
|
1182
|
+
async def _write_params(
|
|
1183
|
+
self,
|
|
1184
|
+
request: DocumentCreateRequest | DocumentUpdateRequest,
|
|
1185
|
+
*,
|
|
1186
|
+
source_path: str | None,
|
|
1187
|
+
filename: str | None,
|
|
1188
|
+
) -> dict[str, Any]:
|
|
1189
|
+
if source_path is None:
|
|
1190
|
+
if request.raw_text is None and request.transferred_file is None:
|
|
1191
|
+
raise ValueError("raw_text, transferred_file, or source_path is required")
|
|
1192
|
+
return request.to_params()
|
|
1193
|
+
transfer_ref = await self.upload_file(source_path, filename=filename)
|
|
1194
|
+
params = request.to_params()
|
|
1195
|
+
params.pop("raw_text", None)
|
|
1196
|
+
params["transferred_file"] = transfer_ref
|
|
1197
|
+
return params
|
|
1198
|
+
|
|
1199
|
+
async def _execute(
|
|
1200
|
+
self, command: str, params: Mapping[str, Any], result_type: type[T]
|
|
1201
|
+
) -> T:
|
|
1202
|
+
response = await self._dispatch(command, dict(params))
|
|
1203
|
+
payload = _unwrap_response(response, expect_mapping=True)
|
|
1204
|
+
if hasattr(result_type, "from_payload"):
|
|
1205
|
+
return result_type.from_payload(payload) # type: ignore[attr-defined,no-any-return]
|
|
1206
|
+
return result_type(**payload) # type: ignore[call-arg]
|
|
1207
|
+
|
|
1208
|
+
async def _dispatch(self, command: str, params: Mapping[str, Any]) -> Any:
|
|
1209
|
+
"""Detach accepted writes, observe their jobs, and fetch the final RPC result."""
|
|
1210
|
+
dispatch = self._adapter_client.execute_command_unified
|
|
1211
|
+
try:
|
|
1212
|
+
signature = inspect.signature(dispatch)
|
|
1213
|
+
supports_manual = "manual_event_handling" in signature.parameters or any(
|
|
1214
|
+
parameter.kind is inspect.Parameter.VAR_KEYWORD
|
|
1215
|
+
for parameter in signature.parameters.values()
|
|
1216
|
+
)
|
|
1217
|
+
except (TypeError, ValueError):
|
|
1218
|
+
supports_manual = False
|
|
1219
|
+
if (
|
|
1220
|
+
command not in {"document_create", "document_update", "document_import"}
|
|
1221
|
+
or not supports_manual
|
|
1222
|
+
):
|
|
1223
|
+
return await dispatch(command, dict(params))
|
|
1224
|
+
|
|
1225
|
+
admission = await dispatch(
|
|
1226
|
+
command,
|
|
1227
|
+
dict(params),
|
|
1228
|
+
expect_queue=True,
|
|
1229
|
+
auto_poll=False,
|
|
1230
|
+
timeout=30.0,
|
|
1231
|
+
status_hook=self._event_callback,
|
|
1232
|
+
manual_event_handling=False,
|
|
1233
|
+
)
|
|
1234
|
+
if not isinstance(admission, Mapping) or admission.get("mode") != "queued":
|
|
1235
|
+
return admission
|
|
1236
|
+
job_id = str(admission.get("job_id") or "")
|
|
1237
|
+
if not job_id:
|
|
1238
|
+
raise RuntimeError(f"queued command {command!r} returned no job_id")
|
|
1239
|
+
terminal: Mapping[str, Any]
|
|
1240
|
+
try:
|
|
1241
|
+
try:
|
|
1242
|
+
waiter = getattr(self._adapter_client, "wait_for_job", None)
|
|
1243
|
+
if waiter is None:
|
|
1244
|
+
terminal = await self._recover_job(job_id)
|
|
1245
|
+
else:
|
|
1246
|
+
terminal = await waiter(
|
|
1247
|
+
job_id, timeout=0, on_event=self._event_callback
|
|
1248
|
+
)
|
|
1249
|
+
except Exception as first_error:
|
|
1250
|
+
try:
|
|
1251
|
+
terminal = await self._recover_job(job_id)
|
|
1252
|
+
except Exception as recovery_error:
|
|
1253
|
+
raise RuntimeError(
|
|
1254
|
+
f"queued command {command!r} lost WebSocket for job {job_id}; "
|
|
1255
|
+
f"recovery failed: {recovery_error}"
|
|
1256
|
+
) from first_error
|
|
1257
|
+
except asyncio.CancelledError:
|
|
1258
|
+
# Explicit caller cancellation stops the job; observer loss never does.
|
|
1259
|
+
await self.queue_stop_job(job_id=job_id)
|
|
1260
|
+
raise
|
|
1261
|
+
event_name = str(terminal.get("event", "")).lower()
|
|
1262
|
+
status = str(terminal.get("status", "")).lower()
|
|
1263
|
+
if event_name not in {"command_completed", "job_completed"} and status != "completed":
|
|
1264
|
+
fetch_error: Exception | None = None
|
|
1265
|
+
try:
|
|
1266
|
+
failed = await self._fetch_job_result(job_id)
|
|
1267
|
+
except Exception as exc:
|
|
1268
|
+
fetch_error = exc
|
|
1269
|
+
else:
|
|
1270
|
+
if isinstance(failed, Mapping) and failed.get("success") is False:
|
|
1271
|
+
typed = _error_from_payload(failed)
|
|
1272
|
+
if _lock_code(typed) is not None:
|
|
1273
|
+
raise _client_error(typed)
|
|
1274
|
+
raise RuntimeError(
|
|
1275
|
+
f"queued command {command!r} failed for job {job_id}: {dict(terminal)!r}"
|
|
1276
|
+
) from fetch_error
|
|
1277
|
+
result = await self._fetch_job_result(job_id)
|
|
1278
|
+
return {
|
|
1279
|
+
"mode": "queued",
|
|
1280
|
+
"job_id": job_id,
|
|
1281
|
+
"command": command,
|
|
1282
|
+
"result": result,
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
async def _recover_job(self, job_id: str) -> Mapping[str, Any]:
|
|
1286
|
+
waiter = getattr(self._adapter_client, "wait_for_job", None)
|
|
1287
|
+
status_getter = getattr(self._adapter_client, "queue_get_job_status", None)
|
|
1288
|
+
if waiter is not None:
|
|
1289
|
+
try:
|
|
1290
|
+
return await waiter(job_id, timeout=0, on_event=self._event_callback)
|
|
1291
|
+
except Exception:
|
|
1292
|
+
if status_getter is None:
|
|
1293
|
+
raise
|
|
1294
|
+
if status_getter is None:
|
|
1295
|
+
raise RuntimeError("adapter exposes neither wait_for_job nor queue_get_job_status")
|
|
1296
|
+
while True:
|
|
1297
|
+
status = await status_getter(job_id)
|
|
1298
|
+
payload = status.get("data", status) if isinstance(status, Mapping) else status
|
|
1299
|
+
if isinstance(payload, Mapping):
|
|
1300
|
+
state = str(payload.get("status", "")).lower()
|
|
1301
|
+
if state in {"completed", "failed", "stopped", "cancelled"}:
|
|
1302
|
+
return {
|
|
1303
|
+
"event": "job_completed" if state == "completed" else "job_failed",
|
|
1304
|
+
"status": state,
|
|
1305
|
+
"job_id": job_id,
|
|
1306
|
+
}
|
|
1307
|
+
await asyncio.sleep(1.0)
|
|
1308
|
+
|
|
1309
|
+
async def _fetch_job_result(self, job_id: str) -> Any:
|
|
1310
|
+
fetcher = getattr(self._adapter_client, "fetch_command_session_result", None)
|
|
1311
|
+
if fetcher is not None:
|
|
1312
|
+
return _final_result_payload(await fetcher(job_id))
|
|
1313
|
+
getter = getattr(self._adapter_client, "queue_get_job_status", None)
|
|
1314
|
+
if getter is None:
|
|
1315
|
+
raise RuntimeError(f"adapter cannot fetch final result for job {job_id}")
|
|
1316
|
+
return _final_result_payload(await getter(job_id))
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
def _merge_params(
|
|
1320
|
+
params: Mapping[str, Any] | None,
|
|
1321
|
+
kwargs: Mapping[str, Any],
|
|
1322
|
+
) -> dict[str, Any]:
|
|
1323
|
+
if params is None:
|
|
1324
|
+
result: dict[str, Any] = {}
|
|
1325
|
+
elif isinstance(params, Mapping):
|
|
1326
|
+
result = dict(params)
|
|
1327
|
+
else:
|
|
1328
|
+
raise TypeError("params must be a mapping")
|
|
1329
|
+
duplicate = sorted(set(result).intersection(kwargs))
|
|
1330
|
+
if duplicate:
|
|
1331
|
+
raise ValueError(f"duplicate parameters: {', '.join(duplicate)}")
|
|
1332
|
+
result.update(kwargs)
|
|
1333
|
+
return result
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
def _session_kwargs(session_id: str | None) -> dict[str, Any]:
|
|
1337
|
+
"""The keyword carrying ``session_id``; only ``None`` is omitted, nothing is validated."""
|
|
1338
|
+
|
|
1339
|
+
return {} if session_id is None else {"session_id": session_id}
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
def _with_session(params: Mapping[str, Any], session_id: str | None) -> dict[str, Any]:
|
|
1343
|
+
"""Copy of ``params`` with ``session_id`` added unchanged when it is not ``None``."""
|
|
1344
|
+
|
|
1345
|
+
result = dict(params)
|
|
1346
|
+
if session_id is not None:
|
|
1347
|
+
result["session_id"] = session_id
|
|
1348
|
+
return result
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def _provided_params(**values: Any) -> dict[str, Any]:
|
|
1352
|
+
"""Build a command payload while preserving explicit ``None`` values."""
|
|
1353
|
+
|
|
1354
|
+
return {name: value for name, value in values.items() if not isinstance(value, _Omitted)}
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
def _final_result_payload(value: Any) -> Any:
|
|
1358
|
+
"""Normalize queue status wrappers while retaining the server result."""
|
|
1359
|
+
if isinstance(value, Mapping) and "result" in value:
|
|
1360
|
+
value = value["result"]
|
|
1361
|
+
if isinstance(value, Mapping) and isinstance(value.get("data"), Mapping):
|
|
1362
|
+
return value["data"]
|
|
1363
|
+
return value
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def _model_type(name: str) -> type[Any]:
|
|
1367
|
+
"""Resolve concurrently added public models without importing them eagerly."""
|
|
1368
|
+
|
|
1369
|
+
from . import models
|
|
1370
|
+
|
|
1371
|
+
return getattr(models, name)
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def _unwrap_response(response: Any, *, expect_mapping: bool = False) -> Any:
|
|
1375
|
+
if isinstance(response, Mapping):
|
|
1376
|
+
if _is_jsonrpc_envelope(response):
|
|
1377
|
+
if "error" in response:
|
|
1378
|
+
return _unwrap_response(
|
|
1379
|
+
{"success": False, "error": response["error"]},
|
|
1380
|
+
expect_mapping=expect_mapping,
|
|
1381
|
+
)
|
|
1382
|
+
return _unwrap_response(response["result"], expect_mapping=expect_mapping)
|
|
1383
|
+
if _is_adapter_envelope(response):
|
|
1384
|
+
return _unwrap_response(response["result"], expect_mapping=expect_mapping)
|
|
1385
|
+
if _is_queue_status_payload(response):
|
|
1386
|
+
return _unwrap_response(response["result"], expect_mapping=expect_mapping)
|
|
1387
|
+
if response.get("success") is False:
|
|
1388
|
+
raise _client_error(_error_from_payload(response))
|
|
1389
|
+
data = response.get("data", response)
|
|
1390
|
+
if isinstance(data, Mapping) or not expect_mapping:
|
|
1391
|
+
return data
|
|
1392
|
+
data = getattr(response, "data", response)
|
|
1393
|
+
success = getattr(response, "success", True)
|
|
1394
|
+
if success is False:
|
|
1395
|
+
raise _client_error(
|
|
1396
|
+
ServerError(
|
|
1397
|
+
code=str(getattr(response, "code", "SERVER_ERROR")),
|
|
1398
|
+
message=str(getattr(response, "error", "command failed")),
|
|
1399
|
+
details=getattr(response, "details", None),
|
|
1400
|
+
)
|
|
1401
|
+
)
|
|
1402
|
+
if isinstance(data, Mapping) or not expect_mapping:
|
|
1403
|
+
return data
|
|
1404
|
+
raise TypeError("adapter response data must be an object")
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
def _is_adapter_envelope(response: Mapping[str, Any]) -> bool:
|
|
1408
|
+
return "mode" in response and "result" in response
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
def _is_jsonrpc_envelope(response: Mapping[str, Any]) -> bool:
|
|
1412
|
+
return response.get("jsonrpc") == "2.0" and (
|
|
1413
|
+
"result" in response or "error" in response
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
def _is_queue_status_payload(response: Mapping[str, Any]) -> bool:
|
|
1418
|
+
result = response.get("result")
|
|
1419
|
+
return (
|
|
1420
|
+
"job_id" in response
|
|
1421
|
+
and "command" in response
|
|
1422
|
+
and isinstance(result, Mapping)
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
def _error_from_payload(payload: Mapping[str, Any]) -> ServerError:
|
|
1427
|
+
details = payload.get("details")
|
|
1428
|
+
error = payload.get("error")
|
|
1429
|
+
if isinstance(error, Mapping):
|
|
1430
|
+
values = dict(error)
|
|
1431
|
+
values.setdefault("code", str(payload.get("code", "SERVER_ERROR")))
|
|
1432
|
+
values.setdefault("message", str(values.get("message", "command failed")))
|
|
1433
|
+
return ServerError.from_payload(values)
|
|
1434
|
+
return ServerError(
|
|
1435
|
+
code=str(payload.get("code", "SERVER_ERROR")),
|
|
1436
|
+
message=str(error or "command failed"),
|
|
1437
|
+
details=details if isinstance(details, Mapping) else None,
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
def _completed_transfer_reference(receipt: Any) -> Mapping[str, Any]:
|
|
1442
|
+
completed = (
|
|
1443
|
+
receipt.get("completed")
|
|
1444
|
+
if isinstance(receipt, Mapping)
|
|
1445
|
+
else getattr(receipt, "completed", False)
|
|
1446
|
+
)
|
|
1447
|
+
if completed is not True:
|
|
1448
|
+
raise DocStoreClientError(
|
|
1449
|
+
ServerError(code="TRANSFER_INCOMPLETE", message="file transfer did not complete")
|
|
1450
|
+
)
|
|
1451
|
+
if isinstance(receipt, Mapping):
|
|
1452
|
+
transfer_id = receipt.get("transfer_id") or receipt.get("id")
|
|
1453
|
+
values = dict(receipt)
|
|
1454
|
+
else:
|
|
1455
|
+
transfer_id = getattr(receipt, "transfer_id", None) or getattr(receipt, "id", None)
|
|
1456
|
+
values = {
|
|
1457
|
+
key: getattr(receipt, key, None)
|
|
1458
|
+
for key in (
|
|
1459
|
+
"transfer_id",
|
|
1460
|
+
"id",
|
|
1461
|
+
"filename",
|
|
1462
|
+
"path",
|
|
1463
|
+
"size_bytes",
|
|
1464
|
+
"checksum_algorithm",
|
|
1465
|
+
"checksum_value",
|
|
1466
|
+
"compression",
|
|
1467
|
+
"chunk_size",
|
|
1468
|
+
"status",
|
|
1469
|
+
"plaintext_size_bytes",
|
|
1470
|
+
)
|
|
1471
|
+
}
|
|
1472
|
+
values["transfer_id"] = transfer_id
|
|
1473
|
+
result = {
|
|
1474
|
+
key: value
|
|
1475
|
+
for key, value in values.items()
|
|
1476
|
+
if key != "completed" and value is not None and key != "id"
|
|
1477
|
+
}
|
|
1478
|
+
if not result:
|
|
1479
|
+
raise DocStoreClientError(
|
|
1480
|
+
ServerError(code="TRANSFER_REFERENCE_MISSING", message="file transfer reference is missing")
|
|
1481
|
+
)
|
|
1482
|
+
return result
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
def _dump_query(query: ChunkQuery) -> Mapping[str, Any]:
|
|
1486
|
+
if hasattr(query, "model_dump"):
|
|
1487
|
+
return query.model_dump(mode="python", exclude_none=True, exclude_unset=True)
|
|
1488
|
+
if is_dataclass(query):
|
|
1489
|
+
return {
|
|
1490
|
+
key: value
|
|
1491
|
+
for key, value in query.__dict__.items()
|
|
1492
|
+
if value is not None
|
|
1493
|
+
}
|
|
1494
|
+
if isinstance(query, Mapping):
|
|
1495
|
+
return dict(query)
|
|
1496
|
+
raise TypeError("query must be a chunk_metadata_adapter.ChunkQuery")
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
__all__ = [
|
|
1500
|
+
"DOC_STORE_COMMANDS",
|
|
1501
|
+
"DocStoreClient",
|
|
1502
|
+
"DocStoreClientError",
|
|
1503
|
+
"DocStoreNotFoundError",
|
|
1504
|
+
"FileAlreadyLockedError",
|
|
1505
|
+
"JsonRpcClientLike",
|
|
1506
|
+
"LockServiceUnavailableError",
|
|
1507
|
+
"SessionNotFoundError",
|
|
1508
|
+
"TransferClientLike",
|
|
1509
|
+
]
|