doc-store-client 0.1.13__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 +95 -0
- doc_store_client/client.py +620 -0
- doc_store_client/models.py +491 -0
- doc_store_client/py.typed +0 -0
- doc_store_client-0.1.13.dist-info/METADATA +28 -0
- doc_store_client-0.1.13.dist-info/RECORD +8 -0
- doc_store_client-0.1.13.dist-info/WHEEL +5 -0
- doc_store_client-0.1.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Public API for the transport-neutral doc-store client package."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _distribution_version
|
|
5
|
+
from pathlib import Path as _Path
|
|
6
|
+
import tomllib as _tomllib
|
|
7
|
+
|
|
8
|
+
from .client import DOC_STORE_COMMANDS, DocStoreClient, DocStoreClientError
|
|
9
|
+
from .models import (
|
|
10
|
+
ChapterGetRequest,
|
|
11
|
+
ChapterGetResult,
|
|
12
|
+
DocumentChunkRequest,
|
|
13
|
+
DocumentChunkResult,
|
|
14
|
+
DocumentCreateRequest,
|
|
15
|
+
DocumentCreateResult,
|
|
16
|
+
DocumentDeleteRequest,
|
|
17
|
+
DocumentDeleteResult,
|
|
18
|
+
DocumentGetRequest,
|
|
19
|
+
DocumentGetResult,
|
|
20
|
+
DocumentRebindRequest,
|
|
21
|
+
DocumentRebindResult,
|
|
22
|
+
DocumentUpdateRequest,
|
|
23
|
+
DocumentUpdateResult,
|
|
24
|
+
DocumentWriteRequest,
|
|
25
|
+
DocumentWriteResult,
|
|
26
|
+
EntityGetRequest,
|
|
27
|
+
EntityGetResult,
|
|
28
|
+
EntityIdsRequest,
|
|
29
|
+
EntityLifecycleResult,
|
|
30
|
+
EntityListRequest,
|
|
31
|
+
EntityListResult,
|
|
32
|
+
EntityReferencesRequest,
|
|
33
|
+
EntityReferencesResult,
|
|
34
|
+
OperationState,
|
|
35
|
+
ParagraphGetByNumberRequest,
|
|
36
|
+
ParagraphGetByNumberResult,
|
|
37
|
+
ParagraphGetRequest,
|
|
38
|
+
ParagraphGetResult,
|
|
39
|
+
ProcessingStatusRequest,
|
|
40
|
+
ProcessingStatusResult,
|
|
41
|
+
RankedSearchHit,
|
|
42
|
+
RetrievalRequest,
|
|
43
|
+
RetrievalResult,
|
|
44
|
+
SearchResult,
|
|
45
|
+
ServerError,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
__version__ = _distribution_version("doc-store-client")
|
|
50
|
+
except _PackageNotFoundError:
|
|
51
|
+
_pyproject = _Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
52
|
+
__version__ = _tomllib.loads(_pyproject.read_text(encoding="utf-8"))["project"]["version"]
|
|
53
|
+
|
|
54
|
+
# The package includes ``py.typed`` in its distribution metadata.
|
|
55
|
+
__all__ = [
|
|
56
|
+
"ChapterGetRequest",
|
|
57
|
+
"ChapterGetResult",
|
|
58
|
+
"DOC_STORE_COMMANDS",
|
|
59
|
+
"DocStoreClient",
|
|
60
|
+
"DocStoreClientError",
|
|
61
|
+
"DocumentCreateRequest",
|
|
62
|
+
"DocumentCreateResult",
|
|
63
|
+
"DocumentChunkRequest",
|
|
64
|
+
"DocumentChunkResult",
|
|
65
|
+
"DocumentDeleteRequest",
|
|
66
|
+
"DocumentDeleteResult",
|
|
67
|
+
"DocumentGetRequest",
|
|
68
|
+
"DocumentGetResult",
|
|
69
|
+
"DocumentRebindRequest",
|
|
70
|
+
"DocumentRebindResult",
|
|
71
|
+
"DocumentUpdateRequest",
|
|
72
|
+
"DocumentUpdateResult",
|
|
73
|
+
"DocumentWriteRequest",
|
|
74
|
+
"DocumentWriteResult",
|
|
75
|
+
"EntityGetRequest",
|
|
76
|
+
"EntityGetResult",
|
|
77
|
+
"EntityIdsRequest",
|
|
78
|
+
"EntityLifecycleResult",
|
|
79
|
+
"EntityListRequest",
|
|
80
|
+
"EntityListResult",
|
|
81
|
+
"EntityReferencesRequest",
|
|
82
|
+
"EntityReferencesResult",
|
|
83
|
+
"OperationState",
|
|
84
|
+
"ParagraphGetByNumberRequest",
|
|
85
|
+
"ParagraphGetByNumberResult",
|
|
86
|
+
"ParagraphGetRequest",
|
|
87
|
+
"ParagraphGetResult",
|
|
88
|
+
"ProcessingStatusRequest",
|
|
89
|
+
"ProcessingStatusResult",
|
|
90
|
+
"RankedSearchHit",
|
|
91
|
+
"RetrievalRequest",
|
|
92
|
+
"RetrievalResult",
|
|
93
|
+
"SearchResult",
|
|
94
|
+
"ServerError",
|
|
95
|
+
]
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
"""Thin typed facade over an injected ``mcp-proxy-adapter`` client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import is_dataclass
|
|
6
|
+
from typing import Any, ClassVar, Mapping, Protocol, TypeVar
|
|
7
|
+
|
|
8
|
+
from chunk_metadata_adapter import ChunkQuery
|
|
9
|
+
|
|
10
|
+
from .models import (
|
|
11
|
+
ChapterGetRequest,
|
|
12
|
+
ChapterGetResult,
|
|
13
|
+
DocumentChunkRequest,
|
|
14
|
+
DocumentChunkResult,
|
|
15
|
+
DocumentCreateRequest,
|
|
16
|
+
DocumentCreateResult,
|
|
17
|
+
DocumentDeleteRequest,
|
|
18
|
+
DocumentDeleteResult,
|
|
19
|
+
DocumentGetRequest,
|
|
20
|
+
DocumentGetResult,
|
|
21
|
+
DocumentRebindRequest,
|
|
22
|
+
DocumentRebindResult,
|
|
23
|
+
DocumentUpdateRequest,
|
|
24
|
+
DocumentUpdateResult,
|
|
25
|
+
EntityGetRequest,
|
|
26
|
+
EntityGetResult,
|
|
27
|
+
EntityIdsRequest,
|
|
28
|
+
EntityLifecycleResult,
|
|
29
|
+
EntityListRequest,
|
|
30
|
+
EntityListResult,
|
|
31
|
+
EntityReferencesRequest,
|
|
32
|
+
EntityReferencesResult,
|
|
33
|
+
ParagraphGetByNumberRequest,
|
|
34
|
+
ParagraphGetByNumberResult,
|
|
35
|
+
ParagraphGetRequest,
|
|
36
|
+
ParagraphGetResult,
|
|
37
|
+
ProcessingStatusRequest,
|
|
38
|
+
ProcessingStatusResult,
|
|
39
|
+
SearchResult,
|
|
40
|
+
ServerError,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
DOC_STORE_COMMANDS: tuple[str, ...] = (
|
|
44
|
+
"echo",
|
|
45
|
+
"long_task",
|
|
46
|
+
"job_status",
|
|
47
|
+
"queue_add_job",
|
|
48
|
+
"queue_start_job",
|
|
49
|
+
"queue_stop_job",
|
|
50
|
+
"queue_delete_job",
|
|
51
|
+
"queue_get_job_status",
|
|
52
|
+
"queue_get_job_logs",
|
|
53
|
+
"queue_list_jobs",
|
|
54
|
+
"queue_health",
|
|
55
|
+
"document_get",
|
|
56
|
+
"chapter_get",
|
|
57
|
+
"paragraph_get",
|
|
58
|
+
"paragraph_get_by_number",
|
|
59
|
+
"document_create",
|
|
60
|
+
"document_update",
|
|
61
|
+
"document_chunk",
|
|
62
|
+
"document_rebind",
|
|
63
|
+
"processing_status",
|
|
64
|
+
"document_delete",
|
|
65
|
+
"entity_list",
|
|
66
|
+
"entity_get",
|
|
67
|
+
"entity_soft_delete",
|
|
68
|
+
"entity_undelete",
|
|
69
|
+
"entity_hard_delete",
|
|
70
|
+
"entity_references",
|
|
71
|
+
"chunk_query_search",
|
|
72
|
+
"help",
|
|
73
|
+
"health",
|
|
74
|
+
"config",
|
|
75
|
+
"reload",
|
|
76
|
+
"settings",
|
|
77
|
+
"load",
|
|
78
|
+
"unload",
|
|
79
|
+
"plugins",
|
|
80
|
+
"transport_management",
|
|
81
|
+
"proxy_registration",
|
|
82
|
+
"roletest",
|
|
83
|
+
"transfer_upload_begin",
|
|
84
|
+
"transfer_upload_status",
|
|
85
|
+
"transfer_upload_complete",
|
|
86
|
+
"transfer_download_begin",
|
|
87
|
+
"transfer_download_status",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class JsonRpcClientLike(Protocol):
|
|
92
|
+
"""Minimal adapter surface used by the typed facade."""
|
|
93
|
+
|
|
94
|
+
async def execute_command_unified(self, command: str, params: Mapping[str, Any]) -> Any:
|
|
95
|
+
"""Execute a doc-store command through the adapter."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TransferClientLike(JsonRpcClientLike, Protocol):
|
|
99
|
+
"""Adapter surface when file-transfer ingestion is used."""
|
|
100
|
+
|
|
101
|
+
async def upload_file(
|
|
102
|
+
self,
|
|
103
|
+
source_path: str,
|
|
104
|
+
*,
|
|
105
|
+
filename: str | None = None,
|
|
106
|
+
compression: str = "identity",
|
|
107
|
+
chunk_size: int | None = None,
|
|
108
|
+
on_progress: Any = None,
|
|
109
|
+
) -> Any:
|
|
110
|
+
"""Delegate file transfer to the adapter implementation."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
T = TypeVar("T")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DocStoreClientError(RuntimeError):
|
|
117
|
+
"""Raised when the adapter or server returns a structured command error."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, error: ServerError) -> None:
|
|
120
|
+
self.error = error
|
|
121
|
+
super().__init__(error.message)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DocStoreClient:
|
|
125
|
+
"""Typed doc-store operations backed by an injected adapter client."""
|
|
126
|
+
|
|
127
|
+
commands: ClassVar[tuple[str, ...]] = DOC_STORE_COMMANDS
|
|
128
|
+
|
|
129
|
+
def __init__(self, adapter_client: JsonRpcClientLike) -> None:
|
|
130
|
+
self._adapter_client = adapter_client
|
|
131
|
+
|
|
132
|
+
async def call(
|
|
133
|
+
self,
|
|
134
|
+
command: str,
|
|
135
|
+
params: Mapping[str, Any] | None = None,
|
|
136
|
+
**kwargs: Any,
|
|
137
|
+
) -> Any:
|
|
138
|
+
"""Execute any server command through the injected adapter client.
|
|
139
|
+
|
|
140
|
+
The adapter owns networking, TLS/mTLS, authentication, queue delivery, and
|
|
141
|
+
retries. This method only gives callers a stable command-level facade.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
command_name = command.strip()
|
|
145
|
+
if not command_name:
|
|
146
|
+
raise ValueError("command must be non-empty")
|
|
147
|
+
return _unwrap_response(
|
|
148
|
+
await self._adapter_client.execute_command_unified(
|
|
149
|
+
command_name, _merge_params(params, kwargs)
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def echo(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
154
|
+
return await self.call("echo", params, **kwargs)
|
|
155
|
+
|
|
156
|
+
async def long_task(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
157
|
+
return await self.call("long_task", params, **kwargs)
|
|
158
|
+
|
|
159
|
+
async def job_status(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
160
|
+
return await self.call("job_status", params, **kwargs)
|
|
161
|
+
|
|
162
|
+
async def queue_add_job(
|
|
163
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
164
|
+
) -> Any:
|
|
165
|
+
return await self.call("queue_add_job", params, **kwargs)
|
|
166
|
+
|
|
167
|
+
async def queue_start_job(
|
|
168
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
169
|
+
) -> Any:
|
|
170
|
+
return await self.call("queue_start_job", params, **kwargs)
|
|
171
|
+
|
|
172
|
+
async def queue_stop_job(
|
|
173
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
174
|
+
) -> Any:
|
|
175
|
+
return await self.call("queue_stop_job", params, **kwargs)
|
|
176
|
+
|
|
177
|
+
async def queue_delete_job(
|
|
178
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
179
|
+
) -> Any:
|
|
180
|
+
return await self.call("queue_delete_job", params, **kwargs)
|
|
181
|
+
|
|
182
|
+
async def queue_get_job_status(
|
|
183
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
184
|
+
) -> Any:
|
|
185
|
+
return await self.call("queue_get_job_status", params, **kwargs)
|
|
186
|
+
|
|
187
|
+
async def queue_get_job_logs(
|
|
188
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
189
|
+
) -> Any:
|
|
190
|
+
return await self.call("queue_get_job_logs", params, **kwargs)
|
|
191
|
+
|
|
192
|
+
async def queue_list_jobs(
|
|
193
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
194
|
+
) -> Any:
|
|
195
|
+
return await self.call("queue_list_jobs", params, **kwargs)
|
|
196
|
+
|
|
197
|
+
async def queue_health(
|
|
198
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
199
|
+
) -> Any:
|
|
200
|
+
return await self.call("queue_health", params, **kwargs)
|
|
201
|
+
|
|
202
|
+
async def document_get(
|
|
203
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
204
|
+
) -> Any:
|
|
205
|
+
return await self.call("document_get", params, **kwargs)
|
|
206
|
+
|
|
207
|
+
async def chapter_get(
|
|
208
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
209
|
+
) -> Any:
|
|
210
|
+
return await self.call("chapter_get", params, **kwargs)
|
|
211
|
+
|
|
212
|
+
async def paragraph_get(
|
|
213
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
214
|
+
) -> Any:
|
|
215
|
+
return await self.call("paragraph_get", params, **kwargs)
|
|
216
|
+
|
|
217
|
+
async def paragraph_get_by_number(
|
|
218
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
219
|
+
) -> Any:
|
|
220
|
+
return await self.call("paragraph_get_by_number", params, **kwargs)
|
|
221
|
+
|
|
222
|
+
async def document_create(
|
|
223
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
224
|
+
) -> Any:
|
|
225
|
+
return await self.call("document_create", params, **kwargs)
|
|
226
|
+
|
|
227
|
+
async def document_update(
|
|
228
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
229
|
+
) -> Any:
|
|
230
|
+
return await self.call("document_update", params, **kwargs)
|
|
231
|
+
|
|
232
|
+
async def document_chunk(
|
|
233
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
234
|
+
) -> Any:
|
|
235
|
+
return await self.call("document_chunk", params, **kwargs)
|
|
236
|
+
|
|
237
|
+
async def document_rebind(
|
|
238
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
239
|
+
) -> Any:
|
|
240
|
+
return await self.call("document_rebind", params, **kwargs)
|
|
241
|
+
|
|
242
|
+
async def processing_status(
|
|
243
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
244
|
+
) -> Any:
|
|
245
|
+
return await self.call("processing_status", params, **kwargs)
|
|
246
|
+
|
|
247
|
+
async def document_delete(
|
|
248
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
249
|
+
) -> Any:
|
|
250
|
+
return await self.call("document_delete", params, **kwargs)
|
|
251
|
+
|
|
252
|
+
async def entity_list(
|
|
253
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
254
|
+
) -> Any:
|
|
255
|
+
return await self.call("entity_list", params, **kwargs)
|
|
256
|
+
|
|
257
|
+
async def entity_get(
|
|
258
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
259
|
+
) -> Any:
|
|
260
|
+
return await self.call("entity_get", params, **kwargs)
|
|
261
|
+
|
|
262
|
+
async def entity_soft_delete(
|
|
263
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
264
|
+
) -> Any:
|
|
265
|
+
return await self.call("entity_soft_delete", params, **kwargs)
|
|
266
|
+
|
|
267
|
+
async def entity_undelete(
|
|
268
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
269
|
+
) -> Any:
|
|
270
|
+
return await self.call("entity_undelete", params, **kwargs)
|
|
271
|
+
|
|
272
|
+
async def entity_hard_delete(
|
|
273
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
274
|
+
) -> Any:
|
|
275
|
+
return await self.call("entity_hard_delete", params, **kwargs)
|
|
276
|
+
|
|
277
|
+
async def entity_references(
|
|
278
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
279
|
+
) -> Any:
|
|
280
|
+
return await self.call("entity_references", params, **kwargs)
|
|
281
|
+
|
|
282
|
+
async def chunk_query_search(
|
|
283
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
284
|
+
) -> Any:
|
|
285
|
+
return await self.call("chunk_query_search", params, **kwargs)
|
|
286
|
+
|
|
287
|
+
async def help(
|
|
288
|
+
self,
|
|
289
|
+
cmdname: str | None = None,
|
|
290
|
+
params: Mapping[str, Any] | None = None,
|
|
291
|
+
**kwargs: Any,
|
|
292
|
+
) -> Any:
|
|
293
|
+
request = _merge_params(params, kwargs)
|
|
294
|
+
if cmdname is not None:
|
|
295
|
+
if "cmdname" in request:
|
|
296
|
+
raise ValueError("cmdname supplied twice")
|
|
297
|
+
request["cmdname"] = cmdname
|
|
298
|
+
return await self.call("help", request)
|
|
299
|
+
|
|
300
|
+
async def health(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
301
|
+
return await self.call("health", params, **kwargs)
|
|
302
|
+
|
|
303
|
+
async def config(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
304
|
+
return await self.call("config", params, **kwargs)
|
|
305
|
+
|
|
306
|
+
async def reload(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
307
|
+
return await self.call("reload", params, **kwargs)
|
|
308
|
+
|
|
309
|
+
async def settings(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
310
|
+
return await self.call("settings", params, **kwargs)
|
|
311
|
+
|
|
312
|
+
async def load(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
313
|
+
return await self.call("load", params, **kwargs)
|
|
314
|
+
|
|
315
|
+
async def unload(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
316
|
+
return await self.call("unload", params, **kwargs)
|
|
317
|
+
|
|
318
|
+
async def plugins(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
319
|
+
return await self.call("plugins", params, **kwargs)
|
|
320
|
+
|
|
321
|
+
async def transport_management(
|
|
322
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
323
|
+
) -> Any:
|
|
324
|
+
return await self.call("transport_management", params, **kwargs)
|
|
325
|
+
|
|
326
|
+
async def proxy_registration(
|
|
327
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
328
|
+
) -> Any:
|
|
329
|
+
return await self.call("proxy_registration", params, **kwargs)
|
|
330
|
+
|
|
331
|
+
async def roletest(self, params: Mapping[str, Any] | None = None, **kwargs: Any) -> Any:
|
|
332
|
+
return await self.call("roletest", params, **kwargs)
|
|
333
|
+
|
|
334
|
+
async def transfer_upload_begin(
|
|
335
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
336
|
+
) -> Any:
|
|
337
|
+
return await self.call("transfer_upload_begin", params, **kwargs)
|
|
338
|
+
|
|
339
|
+
async def transfer_upload_status(
|
|
340
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
341
|
+
) -> Any:
|
|
342
|
+
return await self.call("transfer_upload_status", params, **kwargs)
|
|
343
|
+
|
|
344
|
+
async def transfer_upload_complete(
|
|
345
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
346
|
+
) -> Any:
|
|
347
|
+
return await self.call("transfer_upload_complete", params, **kwargs)
|
|
348
|
+
|
|
349
|
+
async def transfer_download_begin(
|
|
350
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
351
|
+
) -> Any:
|
|
352
|
+
return await self.call("transfer_download_begin", params, **kwargs)
|
|
353
|
+
|
|
354
|
+
async def transfer_download_status(
|
|
355
|
+
self, params: Mapping[str, Any] | None = None, **kwargs: Any
|
|
356
|
+
) -> Any:
|
|
357
|
+
return await self.call("transfer_download_status", params, **kwargs)
|
|
358
|
+
|
|
359
|
+
async def create_document(
|
|
360
|
+
self,
|
|
361
|
+
request: DocumentCreateRequest,
|
|
362
|
+
*,
|
|
363
|
+
source_path: str | None = None,
|
|
364
|
+
filename: str | None = None,
|
|
365
|
+
) -> DocumentCreateResult:
|
|
366
|
+
params = await self._write_params(request, source_path=source_path, filename=filename)
|
|
367
|
+
return await self._execute("document_create", params, DocumentCreateResult)
|
|
368
|
+
|
|
369
|
+
async def update_document(
|
|
370
|
+
self,
|
|
371
|
+
request: DocumentUpdateRequest,
|
|
372
|
+
*,
|
|
373
|
+
source_path: str | None = None,
|
|
374
|
+
filename: str | None = None,
|
|
375
|
+
) -> DocumentUpdateResult:
|
|
376
|
+
params = await self._write_params(request, source_path=source_path, filename=filename)
|
|
377
|
+
return await self._execute("document_update", params, DocumentUpdateResult)
|
|
378
|
+
|
|
379
|
+
async def chunk_document(self, request: DocumentChunkRequest) -> DocumentChunkResult:
|
|
380
|
+
return await self._execute("document_chunk", request.to_params(), DocumentChunkResult)
|
|
381
|
+
|
|
382
|
+
async def rebind_document(self, request: DocumentRebindRequest) -> DocumentRebindResult:
|
|
383
|
+
return await self._execute(
|
|
384
|
+
"document_rebind", request.to_params(), DocumentRebindResult
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
async def get_processing_status(
|
|
388
|
+
self, request: ProcessingStatusRequest
|
|
389
|
+
) -> ProcessingStatusResult:
|
|
390
|
+
return await self._execute(
|
|
391
|
+
"processing_status", request.to_params(), ProcessingStatusResult
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
async def get_document(self, request: DocumentGetRequest) -> DocumentGetResult:
|
|
395
|
+
return await self._execute("document_get", request.to_params(), DocumentGetResult)
|
|
396
|
+
|
|
397
|
+
async def get_chapter(self, request: ChapterGetRequest) -> ChapterGetResult:
|
|
398
|
+
return await self._execute("chapter_get", request.to_params(), ChapterGetResult)
|
|
399
|
+
|
|
400
|
+
async def get_paragraph(self, request: ParagraphGetRequest) -> ParagraphGetResult:
|
|
401
|
+
return await self._execute("paragraph_get", request.to_params(), ParagraphGetResult)
|
|
402
|
+
|
|
403
|
+
async def get_paragraph_by_number(
|
|
404
|
+
self,
|
|
405
|
+
request: ParagraphGetByNumberRequest,
|
|
406
|
+
) -> ParagraphGetByNumberResult:
|
|
407
|
+
return await self._execute(
|
|
408
|
+
"paragraph_get_by_number",
|
|
409
|
+
request.to_params(),
|
|
410
|
+
ParagraphGetByNumberResult,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
async def delete_document(self, request: DocumentDeleteRequest) -> DocumentDeleteResult:
|
|
414
|
+
return await self._execute("document_delete", request.to_params(), DocumentDeleteResult)
|
|
415
|
+
|
|
416
|
+
async def list_entities(self, request: EntityListRequest) -> EntityListResult:
|
|
417
|
+
return await self._execute("entity_list", request.to_params(), EntityListResult)
|
|
418
|
+
|
|
419
|
+
async def get_entity(self, request: EntityGetRequest) -> EntityGetResult:
|
|
420
|
+
return await self._execute("entity_get", request.to_params(), EntityGetResult)
|
|
421
|
+
|
|
422
|
+
async def soft_delete_entities(self, request: EntityIdsRequest) -> EntityLifecycleResult:
|
|
423
|
+
return await self._execute("entity_soft_delete", request.to_params(), EntityLifecycleResult)
|
|
424
|
+
|
|
425
|
+
async def undelete_entities(self, request: EntityIdsRequest) -> EntityLifecycleResult:
|
|
426
|
+
return await self._execute("entity_undelete", request.to_params(), EntityLifecycleResult)
|
|
427
|
+
|
|
428
|
+
async def hard_delete_entities(self, request: EntityIdsRequest) -> EntityLifecycleResult:
|
|
429
|
+
return await self._execute("entity_hard_delete", request.to_params(), EntityLifecycleResult)
|
|
430
|
+
|
|
431
|
+
async def get_entity_references(self, request: EntityReferencesRequest) -> EntityReferencesResult:
|
|
432
|
+
return await self._execute("entity_references", request.to_params(), EntityReferencesResult)
|
|
433
|
+
|
|
434
|
+
async def search(self, query: ChunkQuery) -> SearchResult:
|
|
435
|
+
return await self._execute("chunk_query_search", {"query": _dump_query(query)}, SearchResult)
|
|
436
|
+
|
|
437
|
+
async def upload_file(
|
|
438
|
+
self,
|
|
439
|
+
source_path: str,
|
|
440
|
+
*,
|
|
441
|
+
filename: str | None = None,
|
|
442
|
+
compression: str = "identity",
|
|
443
|
+
chunk_size: int | None = None,
|
|
444
|
+
on_progress: Any = None,
|
|
445
|
+
) -> Mapping[str, Any]:
|
|
446
|
+
"""Upload a file through the injected adapter and return a transfer reference."""
|
|
447
|
+
|
|
448
|
+
upload = getattr(self._adapter_client, "upload_file", None)
|
|
449
|
+
if upload is None:
|
|
450
|
+
raise TypeError("adapter client does not expose upload_file")
|
|
451
|
+
receipt = await upload(
|
|
452
|
+
source_path,
|
|
453
|
+
filename=filename,
|
|
454
|
+
compression=compression,
|
|
455
|
+
chunk_size=chunk_size,
|
|
456
|
+
on_progress=on_progress,
|
|
457
|
+
)
|
|
458
|
+
return _completed_transfer_reference(receipt)
|
|
459
|
+
|
|
460
|
+
async def _write_params(
|
|
461
|
+
self,
|
|
462
|
+
request: DocumentCreateRequest | DocumentUpdateRequest,
|
|
463
|
+
*,
|
|
464
|
+
source_path: str | None,
|
|
465
|
+
filename: str | None,
|
|
466
|
+
) -> dict[str, Any]:
|
|
467
|
+
if source_path is None:
|
|
468
|
+
if request.raw_text is None and request.transferred_file is None:
|
|
469
|
+
raise ValueError("raw_text, transferred_file, or source_path is required")
|
|
470
|
+
return request.to_params()
|
|
471
|
+
transfer_ref = await self.upload_file(source_path, filename=filename)
|
|
472
|
+
params = request.to_params()
|
|
473
|
+
params.pop("raw_text", None)
|
|
474
|
+
params["transferred_file"] = transfer_ref
|
|
475
|
+
return params
|
|
476
|
+
|
|
477
|
+
async def _execute(self, command: str, params: Mapping[str, Any], result_type: type[T]) -> T:
|
|
478
|
+
response = await self._adapter_client.execute_command_unified(command, dict(params))
|
|
479
|
+
payload = _unwrap_response(response, expect_mapping=True)
|
|
480
|
+
if hasattr(result_type, "from_payload"):
|
|
481
|
+
return result_type.from_payload(payload) # type: ignore[attr-defined,no-any-return]
|
|
482
|
+
return result_type(**payload) # type: ignore[call-arg]
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _merge_params(
|
|
486
|
+
params: Mapping[str, Any] | None,
|
|
487
|
+
kwargs: Mapping[str, Any],
|
|
488
|
+
) -> dict[str, Any]:
|
|
489
|
+
if params is None:
|
|
490
|
+
result: dict[str, Any] = {}
|
|
491
|
+
elif isinstance(params, Mapping):
|
|
492
|
+
result = dict(params)
|
|
493
|
+
else:
|
|
494
|
+
raise TypeError("params must be a mapping")
|
|
495
|
+
duplicate = sorted(set(result).intersection(kwargs))
|
|
496
|
+
if duplicate:
|
|
497
|
+
raise ValueError(f"duplicate parameters: {', '.join(duplicate)}")
|
|
498
|
+
result.update(kwargs)
|
|
499
|
+
return result
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _unwrap_response(response: Any, *, expect_mapping: bool = False) -> Any:
|
|
503
|
+
if isinstance(response, Mapping):
|
|
504
|
+
if _is_adapter_envelope(response):
|
|
505
|
+
return _unwrap_response(response["result"], expect_mapping=expect_mapping)
|
|
506
|
+
if _is_queue_status_payload(response):
|
|
507
|
+
return _unwrap_response(response["result"], expect_mapping=expect_mapping)
|
|
508
|
+
if response.get("success") is False:
|
|
509
|
+
raise DocStoreClientError(_error_from_payload(response))
|
|
510
|
+
data = response.get("data", response)
|
|
511
|
+
if isinstance(data, Mapping) or not expect_mapping:
|
|
512
|
+
return data
|
|
513
|
+
data = getattr(response, "data", response)
|
|
514
|
+
success = getattr(response, "success", True)
|
|
515
|
+
if success is False:
|
|
516
|
+
raise DocStoreClientError(
|
|
517
|
+
ServerError(
|
|
518
|
+
code=str(getattr(response, "code", "SERVER_ERROR")),
|
|
519
|
+
message=str(getattr(response, "error", "command failed")),
|
|
520
|
+
details=getattr(response, "details", None),
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
if isinstance(data, Mapping) or not expect_mapping:
|
|
524
|
+
return data
|
|
525
|
+
raise TypeError("adapter response data must be an object")
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _is_adapter_envelope(response: Mapping[str, Any]) -> bool:
|
|
529
|
+
return "mode" in response and "result" in response
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _is_queue_status_payload(response: Mapping[str, Any]) -> bool:
|
|
533
|
+
result = response.get("result")
|
|
534
|
+
return (
|
|
535
|
+
"job_id" in response
|
|
536
|
+
and "command" in response
|
|
537
|
+
and isinstance(result, Mapping)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _error_from_payload(payload: Mapping[str, Any]) -> ServerError:
|
|
542
|
+
details = payload.get("details")
|
|
543
|
+
error = payload.get("error")
|
|
544
|
+
if isinstance(error, Mapping):
|
|
545
|
+
values = dict(error)
|
|
546
|
+
values.setdefault("code", str(payload.get("code", "SERVER_ERROR")))
|
|
547
|
+
values.setdefault("message", str(values.get("message", "command failed")))
|
|
548
|
+
return ServerError.from_payload(values)
|
|
549
|
+
return ServerError(
|
|
550
|
+
code=str(payload.get("code", "SERVER_ERROR")),
|
|
551
|
+
message=str(error or "command failed"),
|
|
552
|
+
details=details if isinstance(details, Mapping) else None,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _completed_transfer_reference(receipt: Any) -> Mapping[str, Any]:
|
|
557
|
+
completed = (
|
|
558
|
+
receipt.get("completed")
|
|
559
|
+
if isinstance(receipt, Mapping)
|
|
560
|
+
else getattr(receipt, "completed", False)
|
|
561
|
+
)
|
|
562
|
+
if completed is not True:
|
|
563
|
+
raise DocStoreClientError(
|
|
564
|
+
ServerError(code="TRANSFER_INCOMPLETE", message="file transfer did not complete")
|
|
565
|
+
)
|
|
566
|
+
if isinstance(receipt, Mapping):
|
|
567
|
+
transfer_id = receipt.get("transfer_id") or receipt.get("id")
|
|
568
|
+
values = dict(receipt)
|
|
569
|
+
else:
|
|
570
|
+
transfer_id = getattr(receipt, "transfer_id", None) or getattr(receipt, "id", None)
|
|
571
|
+
values = {
|
|
572
|
+
key: getattr(receipt, key, None)
|
|
573
|
+
for key in (
|
|
574
|
+
"transfer_id",
|
|
575
|
+
"id",
|
|
576
|
+
"filename",
|
|
577
|
+
"path",
|
|
578
|
+
"size_bytes",
|
|
579
|
+
"checksum_algorithm",
|
|
580
|
+
"checksum_value",
|
|
581
|
+
"compression",
|
|
582
|
+
"chunk_size",
|
|
583
|
+
"status",
|
|
584
|
+
"plaintext_size_bytes",
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
values["transfer_id"] = transfer_id
|
|
588
|
+
result = {
|
|
589
|
+
key: value
|
|
590
|
+
for key, value in values.items()
|
|
591
|
+
if key != "completed" and value is not None and key != "id"
|
|
592
|
+
}
|
|
593
|
+
if not result:
|
|
594
|
+
raise DocStoreClientError(
|
|
595
|
+
ServerError(code="TRANSFER_REFERENCE_MISSING", message="file transfer reference is missing")
|
|
596
|
+
)
|
|
597
|
+
return result
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _dump_query(query: ChunkQuery) -> Mapping[str, Any]:
|
|
601
|
+
if hasattr(query, "model_dump"):
|
|
602
|
+
return query.model_dump(mode="python", exclude_none=True, exclude_unset=True)
|
|
603
|
+
if is_dataclass(query):
|
|
604
|
+
return {
|
|
605
|
+
key: value
|
|
606
|
+
for key, value in query.__dict__.items()
|
|
607
|
+
if value is not None
|
|
608
|
+
}
|
|
609
|
+
if isinstance(query, Mapping):
|
|
610
|
+
return dict(query)
|
|
611
|
+
raise TypeError("query must be a chunk_metadata_adapter.ChunkQuery")
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
__all__ = [
|
|
615
|
+
"DOC_STORE_COMMANDS",
|
|
616
|
+
"DocStoreClient",
|
|
617
|
+
"DocStoreClientError",
|
|
618
|
+
"JsonRpcClientLike",
|
|
619
|
+
"TransferClientLike",
|
|
620
|
+
]
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""Stable, transport-neutral public contracts for ``doc-store-client``.
|
|
2
|
+
|
|
3
|
+
The models intentionally contain validation and payload conversion only. They
|
|
4
|
+
do not know how a command is sent, queued, retried, persisted, or transferred.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field, fields
|
|
10
|
+
from typing import Any, ClassVar, Mapping, Self
|
|
11
|
+
|
|
12
|
+
from chunk_metadata_adapter import ChunkQuery
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Payload = Mapping[str, Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _payload(model: Any, *, omit_none: bool = True) -> dict[str, Any]:
|
|
19
|
+
"""Return fields in declaration order using their public server names."""
|
|
20
|
+
|
|
21
|
+
result: dict[str, Any] = {}
|
|
22
|
+
for model_field in fields(model):
|
|
23
|
+
value = getattr(model, model_field.name)
|
|
24
|
+
if omit_none and value is None:
|
|
25
|
+
continue
|
|
26
|
+
if isinstance(value, tuple):
|
|
27
|
+
value = list(value)
|
|
28
|
+
result[model_field.name] = value
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read(cls: type[Any], payload: Payload) -> Any:
|
|
33
|
+
"""Build a model while rejecting unknown server fields deterministically."""
|
|
34
|
+
|
|
35
|
+
known = {model_field.name for model_field in fields(cls)}
|
|
36
|
+
unknown = sorted(set(payload) - known)
|
|
37
|
+
if unknown:
|
|
38
|
+
raise ValueError(f"unknown {cls.__name__} fields: {', '.join(unknown)}")
|
|
39
|
+
return cls(
|
|
40
|
+
**{
|
|
41
|
+
model_field.name: payload[model_field.name]
|
|
42
|
+
for model_field in fields(cls)
|
|
43
|
+
if model_field.name in payload
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PublicModel:
|
|
49
|
+
"""Small common API shared by all public payload models."""
|
|
50
|
+
|
|
51
|
+
_omit_none: ClassVar[bool] = True
|
|
52
|
+
|
|
53
|
+
def to_params(self) -> dict[str, Any]:
|
|
54
|
+
return _payload(self, omit_none=self._omit_none)
|
|
55
|
+
|
|
56
|
+
def to_payload(self) -> dict[str, Any]:
|
|
57
|
+
return self.to_params()
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
61
|
+
if not isinstance(payload, Mapping):
|
|
62
|
+
raise TypeError(f"{cls.__name__} payload must be an object")
|
|
63
|
+
return _read(cls, payload)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True, kw_only=True)
|
|
67
|
+
class DocumentWriteRequest(PublicModel):
|
|
68
|
+
"""Shared source contract for document create and update."""
|
|
69
|
+
|
|
70
|
+
document_id: str
|
|
71
|
+
source_version_id: str
|
|
72
|
+
raw_text: str | None = None
|
|
73
|
+
transferred_file: Mapping[str, Any] | None = None
|
|
74
|
+
chunking_strategy: str | None = None
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
if not self.document_id.strip() or not self.source_version_id.strip():
|
|
78
|
+
raise ValueError("document_id and source_version_id must be non-empty")
|
|
79
|
+
if self.raw_text is not None and self.transferred_file is not None:
|
|
80
|
+
raise ValueError("raw_text and transferred_file are mutually exclusive")
|
|
81
|
+
if self.raw_text is not None and not self.raw_text:
|
|
82
|
+
raise ValueError("raw_text must be non-empty")
|
|
83
|
+
if self.chunking_strategy is not None and self.chunking_strategy not in {
|
|
84
|
+
"paragraph",
|
|
85
|
+
"sentence",
|
|
86
|
+
"semantic",
|
|
87
|
+
}:
|
|
88
|
+
raise ValueError("chunking_strategy must be paragraph, sentence, or semantic")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True, kw_only=True)
|
|
92
|
+
class DocumentCreateRequest(DocumentWriteRequest):
|
|
93
|
+
"""Request payload for ``document_create``."""
|
|
94
|
+
|
|
95
|
+
def __post_init__(self) -> None:
|
|
96
|
+
super().__post_init__()
|
|
97
|
+
if self.chunking_strategy is None:
|
|
98
|
+
raise ValueError("chunking_strategy is required")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, kw_only=True)
|
|
102
|
+
class DocumentUpdateRequest(DocumentWriteRequest):
|
|
103
|
+
"""Request payload for ``document_update``."""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True, kw_only=True)
|
|
107
|
+
class DocumentWriteResult(PublicModel):
|
|
108
|
+
"""Stable result returned by document create/update operations."""
|
|
109
|
+
|
|
110
|
+
status: str
|
|
111
|
+
operation_id: str
|
|
112
|
+
document_id: str
|
|
113
|
+
source_version_id: str
|
|
114
|
+
details: Mapping[str, Any] | None = None
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
118
|
+
values = dict(payload)
|
|
119
|
+
known = {model_field.name for model_field in fields(cls)}
|
|
120
|
+
extras = {key: values.pop(key) for key in sorted(set(values) - known)}
|
|
121
|
+
if extras:
|
|
122
|
+
details = dict(values.get("details") or {})
|
|
123
|
+
details.update(extras)
|
|
124
|
+
values["details"] = details
|
|
125
|
+
return _read(cls, values)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
DocumentCreateResult = DocumentWriteResult
|
|
129
|
+
DocumentUpdateResult = DocumentWriteResult
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, kw_only=True)
|
|
133
|
+
class DocumentChunkRequest(PublicModel):
|
|
134
|
+
document_id: str
|
|
135
|
+
chunking_strategy: str | None = None
|
|
136
|
+
|
|
137
|
+
def __post_init__(self) -> None:
|
|
138
|
+
if not self.document_id.strip():
|
|
139
|
+
raise ValueError("document_id must be non-empty")
|
|
140
|
+
if self.chunking_strategy is not None and self.chunking_strategy not in {
|
|
141
|
+
"paragraph",
|
|
142
|
+
"sentence",
|
|
143
|
+
"semantic",
|
|
144
|
+
}:
|
|
145
|
+
raise ValueError("chunking_strategy must be paragraph, sentence, or semantic")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
DocumentChunkResult = DocumentWriteResult
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True, kw_only=True)
|
|
152
|
+
class DocumentRebindRequest(PublicModel):
|
|
153
|
+
document_id: str
|
|
154
|
+
project: str | None = None
|
|
155
|
+
document_properties: Mapping[str, Any] | None = None
|
|
156
|
+
chunk_properties: Mapping[str, Any] | None = None
|
|
157
|
+
|
|
158
|
+
def __post_init__(self) -> None:
|
|
159
|
+
if not self.document_id.strip():
|
|
160
|
+
raise ValueError("document_id must be non-empty")
|
|
161
|
+
if self.project is not None and not self.project.strip():
|
|
162
|
+
raise ValueError("project must be non-empty when supplied")
|
|
163
|
+
if not any(
|
|
164
|
+
value is not None
|
|
165
|
+
for value in (self.project, self.document_properties, self.chunk_properties)
|
|
166
|
+
):
|
|
167
|
+
raise ValueError("at least one rebind field is required")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass(frozen=True, kw_only=True)
|
|
171
|
+
class DocumentRebindResult(PublicModel):
|
|
172
|
+
outcome: str
|
|
173
|
+
document_id: str
|
|
174
|
+
project: str | None = None
|
|
175
|
+
document_properties: Mapping[str, Any] | None = None
|
|
176
|
+
chunk_properties: Mapping[str, Any] | None = None
|
|
177
|
+
updated: Mapping[str, Any] | None = None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass(frozen=True, kw_only=True)
|
|
181
|
+
class DocumentDeleteRequest(PublicModel):
|
|
182
|
+
document_id: str
|
|
183
|
+
version_token: str
|
|
184
|
+
|
|
185
|
+
def __post_init__(self) -> None:
|
|
186
|
+
if not self.document_id.strip() or not self.version_token.strip():
|
|
187
|
+
raise ValueError("document_id and version_token must be non-empty")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True, kw_only=True)
|
|
191
|
+
class DocumentDeleteResult(PublicModel):
|
|
192
|
+
outcome: str
|
|
193
|
+
document_id: str
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass(frozen=True, kw_only=True)
|
|
197
|
+
class EntityListRequest(PublicModel):
|
|
198
|
+
entity_type: str
|
|
199
|
+
fields: tuple[str, ...] | None = None
|
|
200
|
+
filters: Mapping[str, Any] | None = None
|
|
201
|
+
limit: int = 50
|
|
202
|
+
offset: int = 0
|
|
203
|
+
show_deleted: bool = False
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass(frozen=True, kw_only=True)
|
|
207
|
+
class EntityGetRequest(PublicModel):
|
|
208
|
+
entity_type: str
|
|
209
|
+
entity_id: str
|
|
210
|
+
fields: tuple[str, ...] | None = None
|
|
211
|
+
show_deleted: bool = False
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True, kw_only=True)
|
|
215
|
+
class EntityIdsRequest(PublicModel):
|
|
216
|
+
entity_type: str
|
|
217
|
+
ids: tuple[str, ...]
|
|
218
|
+
|
|
219
|
+
def __post_init__(self) -> None:
|
|
220
|
+
if not self.ids:
|
|
221
|
+
raise ValueError("ids must be non-empty")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass(frozen=True, kw_only=True)
|
|
225
|
+
class EntityReferencesRequest(PublicModel):
|
|
226
|
+
entity_type: str
|
|
227
|
+
entity_id: str
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass(frozen=True, kw_only=True)
|
|
231
|
+
class EntityListResult(PublicModel):
|
|
232
|
+
entity_type: str
|
|
233
|
+
items: tuple[Mapping[str, Any], ...] = ()
|
|
234
|
+
limit: int = 50
|
|
235
|
+
offset: int = 0
|
|
236
|
+
total: int = 0
|
|
237
|
+
show_deleted: bool = False
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
241
|
+
values = dict(payload)
|
|
242
|
+
values["items"] = tuple(values.get("items", ()))
|
|
243
|
+
return _read(cls, values)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True, kw_only=True)
|
|
247
|
+
class EntityGetResult(PublicModel):
|
|
248
|
+
entity_type: str
|
|
249
|
+
id: str
|
|
250
|
+
value: Mapping[str, Any]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass(frozen=True, kw_only=True)
|
|
254
|
+
class EntityLifecycleResult(PublicModel):
|
|
255
|
+
outcome: str
|
|
256
|
+
updated: Mapping[str, int] | None = None
|
|
257
|
+
deleted: Mapping[str, int] | None = None
|
|
258
|
+
blocked: tuple[Mapping[str, Any], ...] = ()
|
|
259
|
+
is_deleted: bool | None = None
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
263
|
+
values = dict(payload)
|
|
264
|
+
values["blocked"] = tuple(values.get("blocked", ()))
|
|
265
|
+
return _read(cls, values)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(frozen=True, kw_only=True)
|
|
269
|
+
class EntityReferencesResult(PublicModel):
|
|
270
|
+
entity_type: str
|
|
271
|
+
id: str
|
|
272
|
+
references: tuple[Mapping[str, Any], ...] = ()
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
276
|
+
values = dict(payload)
|
|
277
|
+
values["references"] = tuple(values.get("references", ()))
|
|
278
|
+
return _read(cls, values)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@dataclass(frozen=True, kw_only=True)
|
|
282
|
+
class ProcessingStatusRequest(PublicModel):
|
|
283
|
+
operation_id: str
|
|
284
|
+
document_id: str | None = None
|
|
285
|
+
|
|
286
|
+
def __post_init__(self) -> None:
|
|
287
|
+
if not self.operation_id.strip() or (
|
|
288
|
+
self.document_id is not None and not self.document_id.strip()
|
|
289
|
+
):
|
|
290
|
+
raise ValueError("operation_id and document_id must be non-empty when supplied")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass(frozen=True, kw_only=True)
|
|
294
|
+
class ServerError(PublicModel):
|
|
295
|
+
"""Structured error data returned by a server command."""
|
|
296
|
+
|
|
297
|
+
code: str
|
|
298
|
+
message: str
|
|
299
|
+
details: Mapping[str, Any] | None = None
|
|
300
|
+
type: str | None = None
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
304
|
+
values = dict(payload)
|
|
305
|
+
known = {model_field.name for model_field in fields(cls)}
|
|
306
|
+
extras = {key: values.pop(key) for key in sorted(set(values) - known)}
|
|
307
|
+
if extras:
|
|
308
|
+
details = dict(values.get("details") or {})
|
|
309
|
+
details.update(extras)
|
|
310
|
+
values["details"] = details
|
|
311
|
+
return _read(cls, values)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass(frozen=True, kw_only=True)
|
|
315
|
+
class ProcessingStatusResult(PublicModel):
|
|
316
|
+
operation_id: str
|
|
317
|
+
status: str
|
|
318
|
+
progress: int | float | None = None
|
|
319
|
+
timestamps: Mapping[str, Any] = field(default_factory=dict)
|
|
320
|
+
document_reference: str | None = None
|
|
321
|
+
version_reference: str | None = None
|
|
322
|
+
failure: ServerError | Mapping[str, Any] | None = None
|
|
323
|
+
document_id: str | None = None
|
|
324
|
+
requested_document_id: str | None = None
|
|
325
|
+
|
|
326
|
+
def to_params(self) -> dict[str, Any]:
|
|
327
|
+
result = _payload(self)
|
|
328
|
+
if isinstance(self.failure, ServerError):
|
|
329
|
+
result["failure"] = self.failure.to_payload()
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
334
|
+
values = dict(payload)
|
|
335
|
+
failure = values.get("failure")
|
|
336
|
+
if isinstance(failure, Mapping):
|
|
337
|
+
values["failure"] = ServerError.from_payload(failure)
|
|
338
|
+
return _read(cls, values)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass(frozen=True, kw_only=True)
|
|
342
|
+
class RetrievalRequest(PublicModel):
|
|
343
|
+
source_version: int | None = None
|
|
344
|
+
|
|
345
|
+
def __post_init__(self) -> None:
|
|
346
|
+
if self.source_version is not None and (
|
|
347
|
+
isinstance(self.source_version, bool) or self.source_version <= 0
|
|
348
|
+
):
|
|
349
|
+
raise ValueError("source_version must be a positive integer")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclass(frozen=True, kw_only=True)
|
|
353
|
+
class DocumentGetRequest(RetrievalRequest):
|
|
354
|
+
document_id: str
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True, kw_only=True)
|
|
358
|
+
class ChapterGetRequest(RetrievalRequest):
|
|
359
|
+
chapter_id: str
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass(frozen=True, kw_only=True)
|
|
363
|
+
class ParagraphGetRequest(RetrievalRequest):
|
|
364
|
+
paragraph_id: str
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@dataclass(frozen=True, kw_only=True)
|
|
368
|
+
class ParagraphGetByNumberRequest(RetrievalRequest):
|
|
369
|
+
document_id: str
|
|
370
|
+
paragraph_number: int
|
|
371
|
+
|
|
372
|
+
def __post_init__(self) -> None:
|
|
373
|
+
super().__post_init__()
|
|
374
|
+
if not self.document_id.strip():
|
|
375
|
+
raise ValueError("document_id must be non-empty")
|
|
376
|
+
if isinstance(self.paragraph_number, bool) or self.paragraph_number < 1:
|
|
377
|
+
raise ValueError("paragraph_number must be a positive integer")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@dataclass(frozen=True, kw_only=True)
|
|
381
|
+
class RetrievalResult(PublicModel):
|
|
382
|
+
entity: str
|
|
383
|
+
identifier: str
|
|
384
|
+
source_version: int | None = None
|
|
385
|
+
value: Any = None
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
DocumentGetResult = RetrievalResult
|
|
389
|
+
ChapterGetResult = RetrievalResult
|
|
390
|
+
ParagraphGetResult = RetrievalResult
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@dataclass(frozen=True, kw_only=True)
|
|
394
|
+
class ParagraphGetByNumberResult(PublicModel):
|
|
395
|
+
entity: str
|
|
396
|
+
document_id: str
|
|
397
|
+
paragraph_number: int
|
|
398
|
+
identifier: str | None = None
|
|
399
|
+
source_version: int | None = None
|
|
400
|
+
text: str | None = None
|
|
401
|
+
value: Any = None
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@dataclass(frozen=True, kw_only=True)
|
|
405
|
+
class RankedSearchHit(PublicModel):
|
|
406
|
+
chunk_id: str
|
|
407
|
+
chunk: Mapping[str, Any]
|
|
408
|
+
bm25_score: float | None = None
|
|
409
|
+
semantic_score: float | None = None
|
|
410
|
+
hybrid_score: float | None = None
|
|
411
|
+
rank: int = 0
|
|
412
|
+
matched_fields: tuple[str, ...] | None = None
|
|
413
|
+
highlights: Mapping[str, tuple[str, ...]] | None = None
|
|
414
|
+
search_metadata: Mapping[str, Any] | None = None
|
|
415
|
+
|
|
416
|
+
def __post_init__(self) -> None:
|
|
417
|
+
for name in ("bm25_score", "semantic_score", "hybrid_score"):
|
|
418
|
+
score = getattr(self, name)
|
|
419
|
+
if score is not None and not 0 <= score <= 1:
|
|
420
|
+
raise ValueError(f"{name} must be between 0 and 1")
|
|
421
|
+
if self.rank < 0:
|
|
422
|
+
raise ValueError("rank must be non-negative")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@dataclass(frozen=True, kw_only=True)
|
|
426
|
+
class SearchResult(PublicModel):
|
|
427
|
+
status: str
|
|
428
|
+
results: tuple[RankedSearchHit, ...] = ()
|
|
429
|
+
total_results: int | None = None
|
|
430
|
+
search_time: float | None = None
|
|
431
|
+
query_time: float | None = None
|
|
432
|
+
metadata: Mapping[str, Any] | None = None
|
|
433
|
+
error: ServerError | Mapping[str, Any] | None = None
|
|
434
|
+
|
|
435
|
+
def to_params(self) -> dict[str, Any]:
|
|
436
|
+
result = _payload(self)
|
|
437
|
+
result["results"] = [hit.to_payload() for hit in self.results]
|
|
438
|
+
if isinstance(self.error, ServerError):
|
|
439
|
+
result["error"] = self.error.to_payload()
|
|
440
|
+
return result
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
444
|
+
values = dict(payload)
|
|
445
|
+
nested = values.pop("data", None)
|
|
446
|
+
if isinstance(nested, Mapping):
|
|
447
|
+
values = {**dict(nested), **values}
|
|
448
|
+
values.setdefault("status", "success")
|
|
449
|
+
values["results"] = tuple(
|
|
450
|
+
RankedSearchHit.from_payload(item) for item in values.get("results", ())
|
|
451
|
+
)
|
|
452
|
+
if isinstance(values.get("error"), Mapping):
|
|
453
|
+
values["error"] = ServerError.from_payload(values["error"])
|
|
454
|
+
return _read(cls, values)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@dataclass(frozen=True, kw_only=True)
|
|
458
|
+
class OperationState(PublicModel):
|
|
459
|
+
operation_id: str
|
|
460
|
+
status: str
|
|
461
|
+
document_id: str | None = None
|
|
462
|
+
source_version_id: str | None = None
|
|
463
|
+
message: str | None = None
|
|
464
|
+
error: ServerError | Mapping[str, Any] | None = None
|
|
465
|
+
|
|
466
|
+
def to_params(self) -> dict[str, Any]:
|
|
467
|
+
result = _payload(self)
|
|
468
|
+
if isinstance(self.error, ServerError):
|
|
469
|
+
result["error"] = self.error.to_payload()
|
|
470
|
+
return result
|
|
471
|
+
|
|
472
|
+
@classmethod
|
|
473
|
+
def from_payload(cls, payload: Payload) -> Self:
|
|
474
|
+
values = dict(payload)
|
|
475
|
+
if isinstance(values.get("error"), Mapping):
|
|
476
|
+
values["error"] = ServerError.from_payload(values["error"])
|
|
477
|
+
return _read(cls, values)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
__all__ = [
|
|
481
|
+
"ChapterGetRequest", "ChapterGetResult", "ChunkQuery", "DocumentCreateRequest",
|
|
482
|
+
"DocumentCreateResult", "DocumentChunkRequest", "DocumentChunkResult",
|
|
483
|
+
"DocumentDeleteRequest", "DocumentDeleteResult", "DocumentGetRequest", "DocumentGetResult",
|
|
484
|
+
"DocumentRebindRequest", "DocumentRebindResult", "DocumentUpdateRequest", "DocumentUpdateResult",
|
|
485
|
+
"DocumentWriteRequest", "DocumentWriteResult", "EntityGetRequest", "EntityGetResult",
|
|
486
|
+
"EntityIdsRequest", "EntityLifecycleResult", "EntityListRequest", "EntityListResult",
|
|
487
|
+
"EntityReferencesRequest", "EntityReferencesResult", "OperationState",
|
|
488
|
+
"ParagraphGetByNumberRequest", "ParagraphGetByNumberResult", "ParagraphGetRequest",
|
|
489
|
+
"ParagraphGetResult", "ProcessingStatusRequest", "ProcessingStatusResult", "RankedSearchHit",
|
|
490
|
+
"RetrievalRequest", "RetrievalResult", "SearchResult", "ServerError",
|
|
491
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doc-store-client
|
|
3
|
+
Version: 0.1.13
|
|
4
|
+
Summary: Client library for the doc-store documentation service.
|
|
5
|
+
Author: Vasiliy Zdanovskiy
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: documentation,client,search,json-rpc
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Documentation
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: chunk-metadata-adapter>=1.0
|
|
20
|
+
Requires-Dist: mcp-proxy-adapter>=8.10.19
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: doc-store-client[test]; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
28
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
doc_store_client/__init__.py,sha256=zGdwYZUcKnJOKOkiIoC-_sOUTZhfl8eLN_MS4eAevzY,2645
|
|
2
|
+
doc_store_client/client.py,sha256=WKclGLa5bMwyYd2x3StyFdS67RyPSMP7PJ5DeIeUQ7M,22347
|
|
3
|
+
doc_store_client/models.py,sha256=N0cs9xqQThIdTY7HKLzztkMh3wW1TmN3pOsvuo24yQo,15867
|
|
4
|
+
doc_store_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
doc_store_client-0.1.13.dist-info/METADATA,sha256=eEIhhS_nFF7QSaU8nLyyFUWvid24MtCDfh4VmoGKkrc,1143
|
|
6
|
+
doc_store_client-0.1.13.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
doc_store_client-0.1.13.dist-info/top_level.txt,sha256=cSF9OOihvUVfWDssZhp9dafdIvYdC6y3qgsHn57o85U,17
|
|
8
|
+
doc_store_client-0.1.13.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
doc_store_client
|