doc-store-client-dev 0.1.185__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ """Ownership-scoped cleanup for documents created by a single verification run."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass, field
7
+ from typing import Protocol
8
+ from uuid import UUID
9
+
10
+ from .client import DocStoreClientError
11
+ from .models import (
12
+ DocumentDeleteRequest,
13
+ DocumentDeleteResult,
14
+ DocumentGetRequest,
15
+ DocumentGetResult,
16
+ )
17
+
18
+
19
+ class OwnedDocumentCleanupError(RuntimeError):
20
+ """A verification fixture could not be removed through the public API."""
21
+
22
+
23
+ class OwnedDocumentClient(Protocol):
24
+ """The narrow public-client surface required for owned fixture cleanup."""
25
+
26
+ async def get_document(self, request: DocumentGetRequest) -> DocumentGetResult:
27
+ """Read a document and its current mutation token."""
28
+
29
+ async def delete_document(
30
+ self, request: DocumentDeleteRequest, *, session_id: str | None = None
31
+ ) -> DocumentDeleteResult:
32
+ """Delete a document through the typed public command."""
33
+
34
+
35
+ def _exact_uuid4(document_id: str) -> str:
36
+ """Return one canonical UUID4, refusing non-document identifiers early."""
37
+
38
+ try:
39
+ parsed = UUID(document_id)
40
+ except (AttributeError, TypeError, ValueError) as exc:
41
+ raise ValueError("owned document id must be a UUID4") from exc
42
+ if parsed.version != 4:
43
+ raise ValueError("owned document id must be a UUID4")
44
+ return str(parsed)
45
+
46
+
47
+ def _is_exact_not_found(error: DocStoreClientError, document_id: str) -> bool:
48
+ """Accept only an absence result bound to the document just deleted."""
49
+
50
+ server_error = error.error
51
+ details = server_error.details
52
+ detail_document_id: object | None = None
53
+ if isinstance(details, Mapping) and "document_id" in details:
54
+ detail_document_id = details["document_id"]
55
+ if detail_document_id != document_id:
56
+ return False
57
+
58
+ exact_message = f"NOT_FOUND: {document_id}"
59
+ if server_error.code == "NOT_FOUND":
60
+ return detail_document_id == document_id or server_error.message == exact_message
61
+ return server_error.code == "SERVER_ERROR" and server_error.message == exact_message
62
+
63
+
64
+ @dataclass
65
+ class OwnedDocumentCleanup:
66
+ """Track and delete only UUID4 documents created by this caller.
67
+
68
+ ``DocStoreClient`` resolves queued adapter delivery before it returns a typed
69
+ result. Consequently this helper accepts only terminal ``deleted`` or
70
+ ``already_absent`` outcomes and rejects a queue acknowledgement as a
71
+ cleanup success.
72
+ """
73
+
74
+ client: OwnedDocumentClient
75
+ _owned_document_ids: set[str] = field(default_factory=set, init=False, repr=False)
76
+
77
+ @property
78
+ def owned_document_ids(self) -> tuple[str, ...]:
79
+ """Return the remaining owned IDs in deterministic order."""
80
+
81
+ return tuple(sorted(self._owned_document_ids))
82
+
83
+ def register(self, document_id: str) -> None:
84
+ """Register the exact UUID4 returned by this caller's successful create."""
85
+
86
+ self._owned_document_ids.add(_exact_uuid4(document_id))
87
+
88
+ async def cleanup(
89
+ self, document_id: str, *, session_id: str | None = None
90
+ ) -> DocumentDeleteResult:
91
+ """Delete one owned document and prove its public readback is absent."""
92
+
93
+ exact_id = _exact_uuid4(document_id)
94
+ if exact_id not in self._owned_document_ids:
95
+ raise PermissionError(f"refusing to clean up unowned document {exact_id}")
96
+
97
+ current = await self.client.get_document(DocumentGetRequest(document_id=exact_id))
98
+ if current.source_version is None:
99
+ raise OwnedDocumentCleanupError(
100
+ f"owned document {exact_id} has no current source_version for deletion"
101
+ )
102
+
103
+ delete_request = DocumentDeleteRequest(
104
+ document_id=exact_id,
105
+ version_token=str(current.source_version),
106
+ )
107
+ if session_id is None:
108
+ result = await self.client.delete_document(delete_request)
109
+ else:
110
+ result = await self.client.delete_document(delete_request, session_id=session_id)
111
+ if result.document_id != exact_id:
112
+ raise OwnedDocumentCleanupError(
113
+ f"document_delete returned {result.document_id!r} for owned {exact_id!r}"
114
+ )
115
+ if result.outcome not in {"deleted", "already_absent"}:
116
+ raise OwnedDocumentCleanupError(
117
+ f"document_delete did not reach a terminal outcome for {exact_id}: {result.outcome!r}"
118
+ )
119
+
120
+ try:
121
+ await self.client.get_document(DocumentGetRequest(document_id=exact_id))
122
+ except DocStoreClientError as exc:
123
+ if _is_exact_not_found(exc, exact_id):
124
+ self._owned_document_ids.remove(exact_id)
125
+ return result
126
+ raise OwnedDocumentCleanupError(
127
+ f"document_get failed after deleting owned {exact_id}: {exc}"
128
+ ) from exc
129
+ raise OwnedDocumentCleanupError(
130
+ f"owned document {exact_id} is still readable after document_delete"
131
+ )
132
+
133
+ async def cleanup_all(self, *, session_id: str | None = None) -> None:
134
+ """Attempt every owned cleanup and propagate every failure as one error."""
135
+
136
+ failures: list[str] = []
137
+ for document_id in self.owned_document_ids:
138
+ try:
139
+ await self.cleanup(document_id, session_id=session_id)
140
+ except Exception as exc:
141
+ failures.append(f"{document_id}: {exc}")
142
+ if failures:
143
+ raise OwnedDocumentCleanupError("; ".join(failures))
File without changes
@@ -0,0 +1,21 @@
1
+ """Short deterministic phrases for verifying received text payloads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from hashlib import sha256
6
+
7
+ _VERIFICATION_VERSION = "rx1"
8
+ _DIGEST_HEX_CHARS = 24
9
+
10
+
11
+ def text_verification_phrase(text: str) -> str:
12
+ """Return a short fingerprint of the exact UTF-8 text received by a command."""
13
+
14
+ if not isinstance(text, str):
15
+ raise TypeError("text must be a string")
16
+ payload = text.encode("utf-8")
17
+ digest = sha256(payload).hexdigest()[:_DIGEST_HEX_CHARS]
18
+ return f"{_VERIFICATION_VERSION}:{len(payload)}:{digest}"
19
+
20
+
21
+ __all__ = ["text_verification_phrase"]
@@ -0,0 +1,16 @@
1
+ """Installed console entrypoint kept dependency-light for --help and discovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ from pathlib import Path
7
+
8
+
9
+ _CLI_PATH = Path(__file__).with_name("doc_store_client") / "cli.py"
10
+ _SPEC = importlib.util.spec_from_file_location("_doc_store_client_cli", _CLI_PATH)
11
+ if _SPEC is None or _SPEC.loader is None:
12
+ raise ImportError(f"Unable to load CLI implementation: {_CLI_PATH}")
13
+ _MODULE = importlib.util.module_from_spec(_SPEC)
14
+ _SPEC.loader.exec_module(_MODULE)
15
+ main = _MODULE.main
16
+
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: doc-store-client-dev
3
+ Version: 0.1.185
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
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: chunk-metadata-adapter>=3.5.1
21
+ Requires-Dist: mcp-proxy-adapter>=8.11.11
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest<10,>=9.2.0; extra == "test"
24
+ Requires-Dist: pytest-asyncio<2,>=1.4.1.post1; extra == "test"
25
+ Provides-Extra: dev
26
+ Requires-Dist: doc-store-client-dev[test]; extra == "dev"
27
+ Requires-Dist: ruff>=0.17.4; extra == "dev"
28
+ Requires-Dist: mypy>=2.4.1; extra == "dev"
29
+ Requires-Dist: build<2,>=1.6.1; extra == "dev"
30
+
31
+ # doc-store-client backup methods
32
+
33
+ `DocStoreClient` exposes typed wrappers for the six backup commands supported by the server:
34
+
35
+ | Method | Required arguments | Result type |
36
+ | --- | --- | --- |
37
+ | `backup_run` | None | `BackupRunResult` |
38
+ | `backup_status` | None | `BackupStatusResult` |
39
+ | `backup_points` | None | `BackupPointsResult` |
40
+ | `backup_inspect` | `point` | `BackupInspectResult` |
41
+ | `backup_restore` | `point` | `BackupRestoreResult` |
42
+ | `backup_recover_catalog` | `destination`, `catalog_copy` | `BackupRecoverCatalogResult` |
43
+
44
+ All six methods accept the optional `catalog_directory`. `backup_run` and `backup_restore` also
45
+ accept `dry_run`; `backup_restore` accepts `replace`; `backup_recover_catalog` accepts `dry_run`.
46
+ Omitted options remain absent from the command payload, allowing server configuration and command
47
+ defaults to apply. Passing `catalog_directory=None` explicitly sends JSON null, which the server
48
+ defines as unspecified. Boolean defaults are false on the server.
49
+
50
+ The methods return the command's `data` object using the corresponding public `TypedDict` result
51
+ type. In particular, `backup_points` includes committed points only, `backup_inspect` describes
52
+ the requested saved point and its chain, and `backup_recover_catalog` reports `activated=False`:
53
+ catalog recovery creates a separate catalog and never switches the active catalog.
54
+
55
+ Queued commands (`backup_run`, `backup_restore`, and `backup_recover_catalog`) use the injected
56
+ adapter's normal delivery and final-result handling. The client does not submit a second command
57
+ or poll the queue a second time. Synchronous commands (`backup_status`, `backup_points`, and
58
+ `backup_inspect`) return their result directly.
59
+
60
+ Server parameter and backup-domain errors are surfaced through the adapter response. When an
61
+ error response is represented by the standard client envelope, `DocStoreClientError.error`
62
+ contains a `ServerError` with its code, message, and details. Adapter transport exceptions
63
+ continue to propagate from the adapter. The wrappers do not convert failures into success-shaped
64
+ results.
65
+
66
+ Use `DocStoreClient.call(command, params)` when a caller needs to exercise invalid parameters or
67
+ send a command that has no typed wrapper. The typed methods describe the valid command inputs;
68
+ the server remains responsible for validating values and path relationships.
69
+
70
+ ## Write sessions and lock errors
71
+
72
+ The 18 raw methods `document_create`, `document_update`, `document_delete`, `document_rebind`,
73
+ `document_chunk`, `chunk_version_add`, `chunk_version_update`, `chunk_version_set_current`,
74
+ `chunk_version_restore`, `chunk_version_retire`, `chunk_version_delete`,
75
+ `semantic_chunk_metadata_update`, `entity_create`, `entity_update`, `entity_rebind_owner`,
76
+ `entity_soft_delete`, `entity_undelete` and `entity_hard_delete`, and the 15 typed wrappers
77
+ `create_document`, `update_document`, `delete_document`, `rebind_document`, `chunk_document`,
78
+ `add_chunk_version`, `update_chunk_version`, `set_current_chunk_version`,
79
+ `restore_chunk_version`, `retire_chunk_version`, `delete_chunk_version`, `soft_delete_entities`,
80
+ `undelete_entities`, `hard_delete_entities` and `update_semantic_chunk_metadata`, take a
81
+ keyword-only `session_id` with default `None`. `None` is not sent; any other value is sent
82
+ unchanged. The client does not check the format; the server refuses a malformed value with
83
+ -32602, raised by the adapter. A raw method given `session_id` as a keyword and inside `params`
84
+ raises `ValueError` (`duplicate parameters: session_id`).
85
+
86
+ `DocStoreClient` keeps no session, so pass `session_id` on every call, and repeat a CAS write-back
87
+ with the same `session_id`. `DocStoreClient.call` passes a `session_id` inside `params` unchanged,
88
+ the CLI takes `session_id` inside the `--params` JSON object (there is no extra flag), and
89
+ `OwnedDocumentCleanup.cleanup` and `cleanup_all` take `session_id` and pass it to every
90
+ `document_delete`. `cleanup_all` collects failures into one `OwnedDocumentCleanupError`; call
91
+ `cleanup` per document to catch a typed lock error.
92
+
93
+ `document_export` and every other non-gated method take no `session_id`; a `session_id` sent to
94
+ them through `params` is refused by the server with -32602.
95
+
96
+ A refused write raises one of three errors, all subclasses of `DocStoreClientError` and importable
97
+ from `doc_store_client`:
98
+
99
+ | class | code | properties |
100
+ | --- | --- | --- |
101
+ | `FileAlreadyLockedError` | `FILE_ALREADY_LOCKED` | `document_id`, `holder_session_id`, `holder_idle_seconds`, `seconds_left_before_timeout` |
102
+ | `SessionNotFoundError` | `SESSION_NOT_FOUND` | `session_id` |
103
+ | `LockServiceUnavailableError` | `LOCK_SERVICE_UNAVAILABLE` | `reason` |
104
+
105
+ `error.code` and the message are kept. A missing field reads as `None`. Values come from the
106
+ `data` mapping in `error.details`, else from `error.details`. A refused write changes nothing on
107
+ the server. A failed queued `document_create` or `document_update` job with a lock code raises the
108
+ typed error as well.
109
+
110
+ The client never retries a refused write. React to each error yourself:
111
+
112
+ - `FileAlreadyLockedError`: another session holds the document. Do not retry blindly; use
113
+ `holder_idle_seconds` and `seconds_left_before_timeout` to decide when to try again.
114
+ - `SessionNotFoundError`: the session is unknown or expired. Open a new CAS session and repeat the
115
+ call with its id.
116
+ - `LockServiceUnavailableError`: CAS did not answer or answered with an error, and the write was
117
+ refused. Retry later by your own decision; `reason` says why.
118
+
119
+ ## Missing target
120
+
121
+ A command whose target does not exist (a document, chapter, paragraph, chunk, entity) is answered
122
+ by the server with a structured error whose code is `NOT_FOUND` and whose message contains the
123
+ missing id. The client raises `DocStoreNotFoundError`, a subclass of `DocStoreClientError`
124
+ importable from `doc_store_client`:
125
+
126
+ | class | code | properties |
127
+ | --- | --- | --- |
128
+ | `DocStoreNotFoundError` | `NOT_FOUND` | `code`, `missing_id` |
129
+
130
+ `str(error)` is the server message (it contains the id), so callers that read the text keep
131
+ working; `error.error.code` is `NOT_FOUND`. `missing_id` is read from the error data, else from the
132
+ id in the message, else `None`. `entity_content_get` does not raise: it reports an absent id as an
133
+ item with `error.code` `NOT_FOUND`.
@@ -0,0 +1,13 @@
1
+ doc_store_client_cli.py,sha256=iXj4Ohif4SRb5PDJcrSeBWqt62xOQOlKFMRGLQ8kOdM,539
2
+ doc_store_client/__init__.py,sha256=zIz7kfZNSwyrg3Mw-7soPTI7sy6AxveB0GMsLDkNwY4,5557
3
+ doc_store_client/cli.py,sha256=IGRSVUiRxXBJvEOSevqPwtmk2N2DRcpUz-1EosPFFVA,7108
4
+ doc_store_client/client.py,sha256=JliKYkDJL3E5MVGEc7BT_wbEWTLXkiUzSjKJKFeZgx0,53524
5
+ doc_store_client/models.py,sha256=6qFNLA0fYSWbKGJJmZnyEViN5DkJQ5UVmzpM_ZRNihU,56172
6
+ doc_store_client/owned_document_cleanup.py,sha256=KoaVdzuyza-WFALR-3JOSRYfZgHxo1mLH8vgEP8ZTwE,5552
7
+ doc_store_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ doc_store_client/text_verification.py,sha256=B0n5RH8U23DE56qgQvWHLBQALqGtiPFZ7qf44_4TQM4,607
9
+ doc_store_client_dev-0.1.185.dist-info/METADATA,sha256=IAe7ROhOHPTbqY1F5vqUV5-sO2hRKjgltGd3qgJheCs,7514
10
+ doc_store_client_dev-0.1.185.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
11
+ doc_store_client_dev-0.1.185.dist-info/entry_points.txt,sha256=wchMyRXsuJAC6Ajt07CFkjQBGgfYb2ET16sVXRBqrFI,63
12
+ doc_store_client_dev-0.1.185.dist-info/top_level.txt,sha256=MC4GKF-Wn-7Yanr8iePj-77t5NS0UPsr25IzdLp7JaA,38
13
+ doc_store_client_dev-0.1.185.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ doc-store-client = doc_store_client_cli:main
@@ -0,0 +1,2 @@
1
+ doc_store_client
2
+ doc_store_client_cli