docforge-sdk 0.1.0__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,264 @@
1
+ # ====== Code Summary ======
2
+ # The auth (API-key management) resource. All URL/body logic lives once in the pure _AuthSpecs mixin,
3
+ # so AsyncAuth and SyncAuth have identical public surfaces whose bodies differ ONLY by ``await``. The
4
+ # rotate path preserves the backend's "absent != null" contract by dumping with exclude_unset (see
5
+ # _AuthSpecs._rotate_spec) and only threading through the fields the caller actually set.
6
+
7
+ # ====== Standard Library Imports ======
8
+ from datetime import datetime
9
+ from types import EllipsisType
10
+
11
+ # ====== Local Project Imports ======
12
+ from .._requestspec import RequestSpec
13
+ from ..models._shared import KeyPermissions
14
+ from ..models.auth import CreatedKey, CreateKeyRequest, KeyInfo, RotateKeyRequest
15
+ from ._base import AsyncResource, SyncResource, _ResourceMixin
16
+
17
+
18
+ class _AuthSpecs(_ResourceMixin):
19
+ """
20
+ Pure ``RequestSpec`` builders for the auth endpoints — the single source of URL/body logic.
21
+
22
+ Shared verbatim by the async and sync resources so neither ever hand-rolls a URL or a body dump.
23
+ """
24
+
25
+ _KEYS_PATH = "/auth/keys"
26
+
27
+ def _create_spec(self, request: CreateKeyRequest) -> RequestSpec:
28
+ """
29
+ Build the spec for creating a key.
30
+
31
+ Args:
32
+ request (CreateKeyRequest): The create-key body.
33
+
34
+ Returns:
35
+ RequestSpec: A POST to the keys collection carrying the full body.
36
+ """
37
+ return RequestSpec("POST", self._KEYS_PATH, json=request.model_dump(mode="json"))
38
+
39
+ def _list_spec(self) -> RequestSpec:
40
+ """
41
+ Build the spec for listing keys.
42
+
43
+ Returns:
44
+ RequestSpec: A GET on the keys collection.
45
+ """
46
+ return RequestSpec("GET", self._KEYS_PATH)
47
+
48
+ def _revoke_spec(self, key_id: str) -> RequestSpec:
49
+ """
50
+ Build the spec for revoking a key.
51
+
52
+ Args:
53
+ key_id (str): The id of the key to revoke.
54
+
55
+ Returns:
56
+ RequestSpec: A DELETE on the key resource.
57
+ """
58
+ return RequestSpec("DELETE", f"{self._KEYS_PATH}/{key_id}")
59
+
60
+ def _rotate_spec(self, key_id: str, request: RotateKeyRequest) -> RequestSpec:
61
+ """
62
+ Build the spec for rotating a key, preserving the "absent != null" body contract.
63
+
64
+ The body is dumped with ``exclude_unset=True`` so a field the caller never set is omitted
65
+ (the backend then clones it from the source key), while an explicitly provided ``None`` is
66
+ serialised as JSON ``null``.
67
+
68
+ Args:
69
+ key_id (str): The id of the key to rotate.
70
+ request (RotateKeyRequest): The rotate body carrying only the caller-set fields.
71
+
72
+ Returns:
73
+ RequestSpec: A POST to the key's rotate endpoint with a minimal, intent-preserving body.
74
+ """
75
+ return RequestSpec(
76
+ "POST",
77
+ f"{self._KEYS_PATH}/{key_id}/rotate",
78
+ json=request.model_dump(mode="json", exclude_unset=True),
79
+ )
80
+
81
+ @staticmethod
82
+ def _build_rotate_request(
83
+ name: str | None | EllipsisType,
84
+ permissions: KeyPermissions | None | EllipsisType,
85
+ expires_at: datetime | None | EllipsisType,
86
+ ) -> RotateKeyRequest:
87
+ """
88
+ Build a RotateKeyRequest from only the caller-supplied fields (``...`` = not supplied).
89
+
90
+ Using ``model_validate`` on a dict of provided fields sets ``model_fields_set`` to exactly
91
+ those keys, which is what makes the later ``exclude_unset`` dump omit the untouched ones.
92
+
93
+ Args:
94
+ name (str | None | EllipsisType): New label, or ``...`` to leave unset.
95
+ permissions (KeyPermissions | None | EllipsisType): New scope, or ``...`` to leave unset.
96
+ expires_at (datetime | None | EllipsisType): New expiry, or ``...`` to leave unset.
97
+
98
+ Returns:
99
+ RotateKeyRequest: A request whose set fields are exactly the supplied ones.
100
+ """
101
+ # 1. Collect only the fields the caller actually passed (Ellipsis means "not supplied").
102
+ provided: dict[str, object] = {}
103
+ if name is not ...:
104
+ provided["name"] = name
105
+ if permissions is not ...:
106
+ provided["permissions"] = permissions
107
+ if expires_at is not ...:
108
+ provided["expires_at"] = expires_at
109
+
110
+ # 2. Validate from the dict so model_fields_set carries exactly the supplied keys.
111
+ return RotateKeyRequest.model_validate(provided)
112
+
113
+
114
+ class AsyncAuth(AsyncResource, _AuthSpecs):
115
+ """Asynchronous API-key management (create / list / revoke / rotate)."""
116
+
117
+ async def create_key(
118
+ self,
119
+ name: str,
120
+ permissions: KeyPermissions | None = None,
121
+ expires_at: datetime | None = None,
122
+ ) -> CreatedKey:
123
+ """
124
+ Create an API key and return its plaintext exactly once.
125
+
126
+ Args:
127
+ name (str): Human-readable label for the key.
128
+ permissions (KeyPermissions | None): Scope; ``None`` = full access (root).
129
+ expires_at (datetime | None): Absolute expiry; ``None`` = never expires.
130
+
131
+ Returns:
132
+ CreatedKey: The key metadata plus the one-time plaintext (never recoverable later).
133
+ """
134
+ return await self._transport.request(
135
+ self._create_spec(
136
+ CreateKeyRequest(name=name, permissions=permissions, expires_at=expires_at)
137
+ ),
138
+ CreatedKey,
139
+ )
140
+
141
+ async def list_keys(self) -> list[KeyInfo]:
142
+ """
143
+ List the account's API keys — metadata only, never a secret.
144
+
145
+ Returns:
146
+ list[KeyInfo]: Every key with its revocation state.
147
+ """
148
+ return await self._transport.request(self._list_spec(), list[KeyInfo])
149
+
150
+ async def revoke_key(self, key_id: str) -> None:
151
+ """
152
+ Soft-revoke an API key (idempotent).
153
+
154
+ Args:
155
+ key_id (str): The id of the key to revoke.
156
+ """
157
+ return await self._transport.request(self._revoke_spec(key_id), type(None))
158
+
159
+ async def rotate_key(
160
+ self,
161
+ key_id: str,
162
+ name: str | None | EllipsisType = ...,
163
+ permissions: KeyPermissions | None | EllipsisType = ...,
164
+ expires_at: datetime | None | EllipsisType = ...,
165
+ ) -> CreatedKey:
166
+ """
167
+ Rotate an API key: issue a fresh secret (optionally re-scoped) and revoke the old one.
168
+
169
+ Only the arguments you pass are sent; an omitted argument (left as ``...``) is cloned from the
170
+ source key by the backend, while an explicit ``None`` sets that field to its null meaning
171
+ (full access / never expires).
172
+
173
+ Args:
174
+ key_id (str): The active key to rotate.
175
+ name (str | None | EllipsisType): New label; omit to keep the source name.
176
+ permissions (KeyPermissions | None | EllipsisType): New scope; omit to keep, ``None`` for
177
+ full access.
178
+ expires_at (datetime | None | EllipsisType): New expiry; omit to keep, ``None`` for never.
179
+
180
+ Returns:
181
+ CreatedKey: The replacement key metadata plus its one-time plaintext.
182
+ """
183
+ return await self._transport.request(
184
+ self._rotate_spec(key_id, self._build_rotate_request(name, permissions, expires_at)),
185
+ CreatedKey,
186
+ )
187
+
188
+
189
+ class SyncAuth(SyncResource, _AuthSpecs):
190
+ """Synchronous API-key management (create / list / revoke / rotate)."""
191
+
192
+ def create_key(
193
+ self,
194
+ name: str,
195
+ permissions: KeyPermissions | None = None,
196
+ expires_at: datetime | None = None,
197
+ ) -> CreatedKey:
198
+ """
199
+ Create an API key and return its plaintext exactly once.
200
+
201
+ Args:
202
+ name (str): Human-readable label for the key.
203
+ permissions (KeyPermissions | None): Scope; ``None`` = full access (root).
204
+ expires_at (datetime | None): Absolute expiry; ``None`` = never expires.
205
+
206
+ Returns:
207
+ CreatedKey: The key metadata plus the one-time plaintext (never recoverable later).
208
+ """
209
+ return self._transport.request(
210
+ self._create_spec(
211
+ CreateKeyRequest(name=name, permissions=permissions, expires_at=expires_at)
212
+ ),
213
+ CreatedKey,
214
+ )
215
+
216
+ def list_keys(self) -> list[KeyInfo]:
217
+ """
218
+ List the account's API keys — metadata only, never a secret.
219
+
220
+ Returns:
221
+ list[KeyInfo]: Every key with its revocation state.
222
+ """
223
+ return self._transport.request(self._list_spec(), list[KeyInfo])
224
+
225
+ def revoke_key(self, key_id: str) -> None:
226
+ """
227
+ Soft-revoke an API key (idempotent).
228
+
229
+ Args:
230
+ key_id (str): The id of the key to revoke.
231
+ """
232
+ return self._transport.request(self._revoke_spec(key_id), type(None))
233
+
234
+ def rotate_key(
235
+ self,
236
+ key_id: str,
237
+ name: str | None | EllipsisType = ...,
238
+ permissions: KeyPermissions | None | EllipsisType = ...,
239
+ expires_at: datetime | None | EllipsisType = ...,
240
+ ) -> CreatedKey:
241
+ """
242
+ Rotate an API key: issue a fresh secret (optionally re-scoped) and revoke the old one.
243
+
244
+ Only the arguments you pass are sent; an omitted argument (left as ``...``) is cloned from the
245
+ source key by the backend, while an explicit ``None`` sets that field to its null meaning
246
+ (full access / never expires).
247
+
248
+ Args:
249
+ key_id (str): The active key to rotate.
250
+ name (str | None | EllipsisType): New label; omit to keep the source name.
251
+ permissions (KeyPermissions | None | EllipsisType): New scope; omit to keep, ``None`` for
252
+ full access.
253
+ expires_at (datetime | None | EllipsisType): New expiry; omit to keep, ``None`` for never.
254
+
255
+ Returns:
256
+ CreatedKey: The replacement key metadata plus its one-time plaintext.
257
+ """
258
+ return self._transport.request(
259
+ self._rotate_spec(key_id, self._build_rotate_request(name, permissions, expires_at)),
260
+ CreatedKey,
261
+ )
262
+
263
+
264
+ __all__ = ["AsyncAuth", "SyncAuth"]
@@ -0,0 +1,63 @@
1
+ # ====== Code Summary ======
2
+ # The blobs resource — fetch a content-addressed blob's raw bytes with its registered mime type. The
3
+ # endpoint streams bytes (not JSON), so both shells route through the transport's get_bytes_typed
4
+ # helper and wrap its (bytes, mime) pair into a BlobContent. Path logic lives once in _BlobsSpecs.
5
+
6
+ # ====== Local Project Imports ======
7
+ from ..models.blobs import BlobContent
8
+ from ._base import AsyncResource, SyncResource, _ResourceMixin
9
+
10
+
11
+ class _BlobsSpecs(_ResourceMixin):
12
+ """Pure path builder for the blobs endpoint — the single source of URL logic."""
13
+
14
+ _BLOBS_PATH = "/blobs"
15
+
16
+ def _blob_path(self, content_hash: str) -> str:
17
+ """
18
+ Build the API-relative path for a content-addressed blob.
19
+
20
+ Args:
21
+ content_hash (str): The blob's content hash (its key).
22
+
23
+ Returns:
24
+ str: The path to the blob resource.
25
+ """
26
+ return f"{self._BLOBS_PATH}/{content_hash}"
27
+
28
+
29
+ class AsyncBlobs(AsyncResource, _BlobsSpecs):
30
+ """Asynchronous content-addressed blob fetch."""
31
+
32
+ async def get(self, content_hash: str) -> BlobContent:
33
+ """
34
+ Fetch a blob's raw bytes and its server-registered media type.
35
+
36
+ Args:
37
+ content_hash (str): The blob's content hash (its key).
38
+
39
+ Returns:
40
+ BlobContent: The raw bytes paired with the registered mime type.
41
+ """
42
+ content, mime_type = await self._transport.get_bytes_typed(self._blob_path(content_hash))
43
+ return BlobContent(content=content, mime_type=mime_type)
44
+
45
+
46
+ class SyncBlobs(SyncResource, _BlobsSpecs):
47
+ """Synchronous content-addressed blob fetch."""
48
+
49
+ def get(self, content_hash: str) -> BlobContent:
50
+ """
51
+ Fetch a blob's raw bytes and its server-registered media type.
52
+
53
+ Args:
54
+ content_hash (str): The blob's content hash (its key).
55
+
56
+ Returns:
57
+ BlobContent: The raw bytes paired with the registered mime type.
58
+ """
59
+ content, mime_type = self._transport.get_bytes_typed(self._blob_path(content_hash))
60
+ return BlobContent(content=content, mime_type=mime_type)
61
+
62
+
63
+ __all__ = ["AsyncBlobs", "SyncBlobs"]
@@ -0,0 +1,204 @@
1
+ # ====== Code Summary ======
2
+ # The collections resource (CRUD over a collection's full contract). All URL/body logic lives once in
3
+ # the pure _CollectionsSpecs mixin, so AsyncCollections and SyncCollections have identical public
4
+ # surfaces whose bodies differ ONLY by ``await``.
5
+
6
+ # ====== Local Project Imports ======
7
+ from .._requestspec import RequestSpec
8
+ from ..models.collections import (
9
+ CollectionModel,
10
+ CreateCollectionRequest,
11
+ UpdateCollectionRequest,
12
+ )
13
+ from ._base import AsyncResource, SyncResource, _ResourceMixin
14
+
15
+
16
+ class _CollectionsSpecs(_ResourceMixin):
17
+ """Pure ``RequestSpec`` builders for the collections endpoints — the single source of URL/body logic."""
18
+
19
+ _COLLECTIONS_PATH = "/collections"
20
+
21
+ def _list_spec(self) -> RequestSpec:
22
+ """
23
+ Build the spec for listing collections.
24
+
25
+ Returns:
26
+ RequestSpec: A GET on the collections collection.
27
+ """
28
+ return RequestSpec("GET", self._COLLECTIONS_PATH)
29
+
30
+ def _get_spec(self, collection_id: str) -> RequestSpec:
31
+ """
32
+ Build the spec for fetching one collection.
33
+
34
+ Args:
35
+ collection_id (str): The collection's UUID.
36
+
37
+ Returns:
38
+ RequestSpec: A GET on the collection resource.
39
+ """
40
+ return RequestSpec("GET", f"{self._COLLECTIONS_PATH}/{collection_id}")
41
+
42
+ def _create_spec(self, request: CreateCollectionRequest) -> RequestSpec:
43
+ """
44
+ Build the spec for creating a collection.
45
+
46
+ Args:
47
+ request (CreateCollectionRequest): The create body (contract + schema + pipeline).
48
+
49
+ Returns:
50
+ RequestSpec: A POST to the collections collection carrying the full body.
51
+ """
52
+ return RequestSpec("POST", self._COLLECTIONS_PATH, json=request.model_dump(mode="json"))
53
+
54
+ def _update_spec(self, collection_id: str, request: UpdateCollectionRequest) -> RequestSpec:
55
+ """
56
+ Build the spec for patching a collection, sending only the caller-set fields.
57
+
58
+ Args:
59
+ collection_id (str): The collection to patch.
60
+ request (UpdateCollectionRequest): The patch body (partial by design).
61
+
62
+ Returns:
63
+ RequestSpec: A PATCH on the collection resource with a minimal body.
64
+ """
65
+ return RequestSpec(
66
+ "PATCH",
67
+ f"{self._COLLECTIONS_PATH}/{collection_id}",
68
+ json=request.model_dump(mode="json", exclude_unset=True),
69
+ )
70
+
71
+ def _delete_spec(self, collection_id: str) -> RequestSpec:
72
+ """
73
+ Build the spec for deleting a collection.
74
+
75
+ Args:
76
+ collection_id (str): The collection to delete.
77
+
78
+ Returns:
79
+ RequestSpec: A DELETE on the collection resource.
80
+ """
81
+ return RequestSpec("DELETE", f"{self._COLLECTIONS_PATH}/{collection_id}")
82
+
83
+
84
+ class AsyncCollections(AsyncResource, _CollectionsSpecs):
85
+ """Asynchronous collection management (list / get / create / update / delete)."""
86
+
87
+ async def list(self) -> list[CollectionModel]:
88
+ """
89
+ List every collection with its full schema.
90
+
91
+ Returns:
92
+ list[CollectionModel]: All contracts, schema included.
93
+ """
94
+ return await self._transport.request(self._list_spec(), list[CollectionModel])
95
+
96
+ async def get(self, collection_id: str) -> CollectionModel:
97
+ """
98
+ Fetch one collection's full contract.
99
+
100
+ Args:
101
+ collection_id (str): The collection's UUID.
102
+
103
+ Returns:
104
+ CollectionModel: Identity, limits, schema and config blobs.
105
+ """
106
+ return await self._transport.request(self._get_spec(collection_id), CollectionModel)
107
+
108
+ async def create(self, request: CreateCollectionRequest) -> CollectionModel:
109
+ """
110
+ Create a collection from its contract, schema and (optional) pipeline.
111
+
112
+ Args:
113
+ request (CreateCollectionRequest): The create body.
114
+
115
+ Returns:
116
+ CollectionModel: The created contract.
117
+ """
118
+ return await self._transport.request(self._create_spec(request), CollectionModel)
119
+
120
+ async def update(self, collection_id: str, request: UpdateCollectionRequest) -> CollectionModel:
121
+ """
122
+ Patch identity/limits, the metadata schema and/or the config blobs.
123
+
124
+ Args:
125
+ collection_id (str): The collection to patch.
126
+ request (UpdateCollectionRequest): The partial patch body.
127
+
128
+ Returns:
129
+ CollectionModel: The updated contract.
130
+ """
131
+ return await self._transport.request(
132
+ self._update_spec(collection_id, request), CollectionModel
133
+ )
134
+
135
+ async def delete(self, collection_id: str) -> None:
136
+ """
137
+ Delete a collection.
138
+
139
+ Args:
140
+ collection_id (str): The collection to delete.
141
+ """
142
+ return await self._transport.request(self._delete_spec(collection_id), type(None))
143
+
144
+
145
+ class SyncCollections(SyncResource, _CollectionsSpecs):
146
+ """Synchronous collection management (list / get / create / update / delete)."""
147
+
148
+ def list(self) -> list[CollectionModel]:
149
+ """
150
+ List every collection with its full schema.
151
+
152
+ Returns:
153
+ list[CollectionModel]: All contracts, schema included.
154
+ """
155
+ return self._transport.request(self._list_spec(), list[CollectionModel])
156
+
157
+ def get(self, collection_id: str) -> CollectionModel:
158
+ """
159
+ Fetch one collection's full contract.
160
+
161
+ Args:
162
+ collection_id (str): The collection's UUID.
163
+
164
+ Returns:
165
+ CollectionModel: Identity, limits, schema and config blobs.
166
+ """
167
+ return self._transport.request(self._get_spec(collection_id), CollectionModel)
168
+
169
+ def create(self, request: CreateCollectionRequest) -> CollectionModel:
170
+ """
171
+ Create a collection from its contract, schema and (optional) pipeline.
172
+
173
+ Args:
174
+ request (CreateCollectionRequest): The create body.
175
+
176
+ Returns:
177
+ CollectionModel: The created contract.
178
+ """
179
+ return self._transport.request(self._create_spec(request), CollectionModel)
180
+
181
+ def update(self, collection_id: str, request: UpdateCollectionRequest) -> CollectionModel:
182
+ """
183
+ Patch identity/limits, the metadata schema and/or the config blobs.
184
+
185
+ Args:
186
+ collection_id (str): The collection to patch.
187
+ request (UpdateCollectionRequest): The partial patch body.
188
+
189
+ Returns:
190
+ CollectionModel: The updated contract.
191
+ """
192
+ return self._transport.request(self._update_spec(collection_id, request), CollectionModel)
193
+
194
+ def delete(self, collection_id: str) -> None:
195
+ """
196
+ Delete a collection.
197
+
198
+ Args:
199
+ collection_id (str): The collection to delete.
200
+ """
201
+ return self._transport.request(self._delete_spec(collection_id), type(None))
202
+
203
+
204
+ __all__ = ["AsyncCollections", "SyncCollections"]
@@ -0,0 +1,153 @@
1
+ # ====== Code Summary ======
2
+ # The documents resource: multipart upload (admission) and the searchability toggle. Upload routes
3
+ # through the transport's multipart helper; the toggle is a plain PATCH. All URL/body/multipart logic
4
+ # lives once in the pure _DocumentsSpecs mixin so the async/sync shells differ ONLY by ``await``.
5
+
6
+ # ====== Standard Library Imports ======
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ # ====== Local Project Imports ======
12
+ from .._requestspec import RequestSpec
13
+ from ..models.documents import DocumentEnabledResponse, EnabledPatch, UploadAccepted
14
+ from ._base import AsyncResource, SyncResource, _ResourceMixin
15
+
16
+
17
+ class _DocumentsSpecs(_ResourceMixin):
18
+ """Pure request builders for the documents endpoints — the single source of URL/body/multipart logic."""
19
+
20
+ _DOCUMENTS_PATH = "/documents"
21
+
22
+ @staticmethod
23
+ def _upload_parts(
24
+ collection_id: str,
25
+ file: str | Path | bytes,
26
+ metadata: dict[str, Any] | None,
27
+ filename: str | None,
28
+ ) -> tuple[dict[str, Any], dict[str, str]]:
29
+ """
30
+ Build the multipart ``files`` and ``data`` parts of an upload from a path or raw bytes.
31
+
32
+ Args:
33
+ collection_id (str): The target collection's UUID.
34
+ file (str | Path | bytes): A local file path or the raw bytes to ingest.
35
+ metadata (dict[str, Any] | None): Declared metadata; serialised to the JSON form field.
36
+ filename (str | None): Override name; defaults to the path's name, else ``"upload"``.
37
+
38
+ Returns:
39
+ tuple[dict[str, Any], dict[str, str]]: The httpx ``files`` mapping and the form ``data``.
40
+ """
41
+ # 1. Resolve raw bytes + a filename from either a local path or in-memory bytes.
42
+ if isinstance(file, bytes):
43
+ content, resolved_name = file, (filename or "upload")
44
+ else:
45
+ path = Path(file)
46
+ content, resolved_name = path.read_bytes(), (filename or path.name)
47
+
48
+ # 2. Assemble the multipart file part and the accompanying form fields.
49
+ files = {"file": (resolved_name, content)}
50
+ data = {"collection_id": collection_id, "metadata": json.dumps(metadata or {})}
51
+ return files, data
52
+
53
+ def _set_enabled_spec(self, document_id: str, enabled: bool) -> RequestSpec:
54
+ """
55
+ Build the spec for toggling a document's searchability.
56
+
57
+ Args:
58
+ document_id (str): The document to toggle.
59
+ enabled (bool): The desired searchability state.
60
+
61
+ Returns:
62
+ RequestSpec: A PATCH on the document's ``/enabled`` sub-resource.
63
+ """
64
+ return RequestSpec(
65
+ "PATCH",
66
+ f"{self._DOCUMENTS_PATH}/{document_id}/enabled",
67
+ json=EnabledPatch(enabled=enabled).model_dump(mode="json"),
68
+ )
69
+
70
+
71
+ class AsyncDocuments(AsyncResource, _DocumentsSpecs):
72
+ """Asynchronous document admission and searchability control."""
73
+
74
+ async def upload(
75
+ self,
76
+ collection_id: str,
77
+ file: str | Path | bytes,
78
+ metadata: dict[str, Any] | None = None,
79
+ filename: str | None = None,
80
+ ) -> UploadAccepted:
81
+ """
82
+ Upload a document for asynchronous ingestion.
83
+
84
+ Args:
85
+ collection_id (str): The target collection's UUID.
86
+ file (str | Path | bytes): A local file path or the raw bytes to ingest.
87
+ metadata (dict[str, Any] | None): Declared metadata (field → value).
88
+ filename (str | None): Override name; defaults to the path's name, else ``"upload"``.
89
+
90
+ Returns:
91
+ UploadAccepted: The admitted document id, its ingestion job id and the duplicate flag.
92
+ """
93
+ files, data = self._upload_parts(collection_id, file, metadata, filename)
94
+ return await self._transport.upload(self._DOCUMENTS_PATH, files, data, UploadAccepted)
95
+
96
+ async def set_enabled(self, document_id: str, enabled: bool) -> DocumentEnabledResponse:
97
+ """
98
+ Toggle a document's searchability (hides or reveals all its chunks).
99
+
100
+ Args:
101
+ document_id (str): The document to toggle.
102
+ enabled (bool): True to make searchable, False to hide from search.
103
+
104
+ Returns:
105
+ DocumentEnabledResponse: The document id and its new searchability state.
106
+ """
107
+ return await self._transport.request(
108
+ self._set_enabled_spec(document_id, enabled), DocumentEnabledResponse
109
+ )
110
+
111
+
112
+ class SyncDocuments(SyncResource, _DocumentsSpecs):
113
+ """Synchronous document admission and searchability control."""
114
+
115
+ def upload(
116
+ self,
117
+ collection_id: str,
118
+ file: str | Path | bytes,
119
+ metadata: dict[str, Any] | None = None,
120
+ filename: str | None = None,
121
+ ) -> UploadAccepted:
122
+ """
123
+ Upload a document for asynchronous ingestion.
124
+
125
+ Args:
126
+ collection_id (str): The target collection's UUID.
127
+ file (str | Path | bytes): A local file path or the raw bytes to ingest.
128
+ metadata (dict[str, Any] | None): Declared metadata (field → value).
129
+ filename (str | None): Override name; defaults to the path's name, else ``"upload"``.
130
+
131
+ Returns:
132
+ UploadAccepted: The admitted document id, its ingestion job id and the duplicate flag.
133
+ """
134
+ files, data = self._upload_parts(collection_id, file, metadata, filename)
135
+ return self._transport.upload(self._DOCUMENTS_PATH, files, data, UploadAccepted)
136
+
137
+ def set_enabled(self, document_id: str, enabled: bool) -> DocumentEnabledResponse:
138
+ """
139
+ Toggle a document's searchability (hides or reveals all its chunks).
140
+
141
+ Args:
142
+ document_id (str): The document to toggle.
143
+ enabled (bool): True to make searchable, False to hide from search.
144
+
145
+ Returns:
146
+ DocumentEnabledResponse: The document id and its new searchability state.
147
+ """
148
+ return self._transport.request(
149
+ self._set_enabled_spec(document_id, enabled), DocumentEnabledResponse
150
+ )
151
+
152
+
153
+ __all__ = ["AsyncDocuments", "SyncDocuments"]