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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ doc_store_client