ragwell 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.
- ragwell/__init__.py +50 -0
- ragwell/_common.py +393 -0
- ragwell/_contract/manifest.json +140 -0
- ragwell/_contract/openapi.json +8778 -0
- ragwell/_deadline.py +78 -0
- ragwell/_decoding.py +61 -0
- ragwell/_generated/__init__.py +8 -0
- ragwell/_generated/api/__init__.py +1 -0
- ragwell/_generated/api/document_deletion/__init__.py +1 -0
- ragwell/_generated/api/document_deletion/delete_document_v1_projects_project_id_documents_document_id_delete.py +228 -0
- ragwell/_generated/api/document_deletion/get_document_deletion_v1_projects_project_id_document_deletions_receipt_id_get.py +201 -0
- ragwell/_generated/api/document_deletion/retry_document_deletion_v1_projects_project_id_document_deletions_receipt_id_retry_post.py +233 -0
- ragwell/_generated/api/document_inspection/__init__.py +1 -0
- ragwell/_generated/api/document_inspection/create_document_export_v1_projects_project_id_documents_document_id_exports_post.py +246 -0
- ragwell/_generated/api/document_inspection/download_document_export_part_v1_projects_project_id_documents_document_id_exports_export_id_parts_part_number_get.py +239 -0
- ragwell/_generated/api/document_inspection/get_document_activity_v1_projects_project_id_documents_document_id_activity_get.py +234 -0
- ragwell/_generated/api/document_inspection/get_document_export_v1_projects_project_id_documents_document_id_exports_export_id_get.py +220 -0
- ragwell/_generated/api/document_inspection/inspect_document_source_v1_projects_project_id_documents_document_id_generations_generation_id_sources_source_id_get.py +275 -0
- ragwell/_generated/api/document_inspection/inspect_document_v1_projects_project_id_documents_document_id_inspection_get.py +234 -0
- ragwell/_generated/api/document_inspection/list_document_chunks_v1_projects_project_id_documents_document_id_generations_generation_id_chunks_get.py +306 -0
- ragwell/_generated/api/document_inspection/list_document_generations_v1_projects_project_id_documents_document_id_generations_get.py +249 -0
- ragwell/_generated/api/documents/__init__.py +1 -0
- ragwell/_generated/api/documents/create_upload_v1_projects_project_id_uploads_post.py +273 -0
- ragwell/_generated/api/documents/finalize_upload_v1_projects_project_id_uploads_upload_id_finalize_post.py +211 -0
- ragwell/_generated/api/documents/get_document_upload_policy_v1_document_upload_policy_get.py +144 -0
- ragwell/_generated/api/documents/get_document_v1_projects_project_id_documents_document_id_get.py +201 -0
- ragwell/_generated/api/documents/get_upload_state.py +206 -0
- ragwell/_generated/api/documents/list_documents_v1_projects_project_id_documents_get.py +366 -0
- ragwell/_generated/api/documents/replace_document_metadata_v1_projects_project_id_documents_document_id_metadata_put.py +251 -0
- ragwell/_generated/api/documents/upload_content_v1_projects_project_id_uploads_upload_id_content_put.py +281 -0
- ragwell/_generated/api/embedding_connections/__init__.py +1 -0
- ragwell/_generated/api/embedding_connections/inspect_embedding_connection_v1_projects_project_id_embedding_connection_get.py +192 -0
- ragwell/_generated/api/ingestion/__init__.py +1 -0
- ragwell/_generated/api/ingestion/cancel_job_v1_projects_project_id_jobs_job_id_cancel_post.py +233 -0
- ragwell/_generated/api/ingestion/get_job_v1_projects_project_id_jobs_job_id_get.py +201 -0
- ragwell/_generated/api/ingestion/list_jobs_v1_projects_project_id_jobs_get.py +387 -0
- ragwell/_generated/api/ingestion/retry_job_v1_projects_project_id_jobs_job_id_retry_post.py +233 -0
- ragwell/_generated/api/projects/__init__.py +1 -0
- ragwell/_generated/api/projects/get_project_v1_projects_project_id_get.py +187 -0
- ragwell/_generated/api/retrieval/__init__.py +1 -0
- ragwell/_generated/api/retrieval/search_v1_projects_project_id_search_post.py +235 -0
- ragwell/_generated/client.py +274 -0
- ragwell/_generated/errors.py +16 -0
- ragwell/_generated/models/__init__.py +191 -0
- ragwell/_generated/models/accepted_document_format_response.py +96 -0
- ragwell/_generated/models/accepted_document_format_response_media_type.py +10 -0
- ragwell/_generated/models/attempt_status.py +11 -0
- ragwell/_generated/models/chunk_part.py +171 -0
- ragwell/_generated/models/chunk_part_kind.py +10 -0
- ragwell/_generated/models/chunk_stage_progress.py +53 -0
- ragwell/_generated/models/chunk_stage_progress_phase.py +9 -0
- ragwell/_generated/models/command_acceptance.py +121 -0
- ragwell/_generated/models/command_acceptance_operation.py +12 -0
- ragwell/_generated/models/create_document_export_request.py +48 -0
- ragwell/_generated/models/create_upload_request.py +96 -0
- ragwell/_generated/models/create_upload_request_declared_media_type.py +10 -0
- ragwell/_generated/models/document_annotation_field_response.py +81 -0
- ragwell/_generated/models/document_annotation_field_response_key.py +11 -0
- ragwell/_generated/models/document_annotation_policy_response.py +116 -0
- ragwell/_generated/models/document_chunk_activity.py +78 -0
- ragwell/_generated/models/document_chunk_info.py +228 -0
- ragwell/_generated/models/document_chunk_page.py +105 -0
- ragwell/_generated/models/document_deletion_response.py +268 -0
- ragwell/_generated/models/document_deletion_state.py +12 -0
- ragwell/_generated/models/document_embedding_usage.py +227 -0
- ragwell/_generated/models/document_embedding_usage_totals.py +152 -0
- ragwell/_generated/models/document_export_part_response.py +70 -0
- ragwell/_generated/models/document_export_response.py +205 -0
- ragwell/_generated/models/document_export_response_state.py +12 -0
- ragwell/_generated/models/document_format.py +10 -0
- ragwell/_generated/models/document_generation_info.py +260 -0
- ragwell/_generated/models/document_generation_page.py +95 -0
- ragwell/_generated/models/document_inspection_response.py +325 -0
- ragwell/_generated/models/document_job_info.py +275 -0
- ragwell/_generated/models/document_list_response.py +95 -0
- ragwell/_generated/models/document_metadata_input.py +106 -0
- ragwell/_generated/models/document_metadata_response.py +102 -0
- ragwell/_generated/models/document_profile_info.py +174 -0
- ragwell/_generated/models/document_response.py +158 -0
- ragwell/_generated/models/document_retrieval_activity.py +156 -0
- ragwell/_generated/models/document_source_info.py +102 -0
- ragwell/_generated/models/document_source_preview.py +161 -0
- ragwell/_generated/models/document_stage_attempt.py +133 -0
- ragwell/_generated/models/document_state.py +12 -0
- ragwell/_generated/models/document_upload_policy_response.py +94 -0
- ragwell/_generated/models/document_version_response.py +109 -0
- ragwell/_generated/models/document_version_state.py +11 -0
- ragwell/_generated/models/embedding_connection_notice_response.py +124 -0
- ragwell/_generated/models/embedding_connection_notice_response_lane.py +9 -0
- ragwell/_generated/models/embedding_connection_notice_response_state.py +9 -0
- ragwell/_generated/models/embedding_connection_response.py +196 -0
- ragwell/_generated/models/embedding_connection_response_funding_source.py +9 -0
- ragwell/_generated/models/embedding_connection_response_health.py +10 -0
- ragwell/_generated/models/embedding_connection_response_status.py +11 -0
- ragwell/_generated/models/embedding_serialization.py +65 -0
- ragwell/_generated/models/error_detail.py +91 -0
- ragwell/_generated/models/error_response.py +62 -0
- ragwell/_generated/models/field_error.py +70 -0
- ragwell/_generated/models/finalized_intake_response.py +88 -0
- ragwell/_generated/models/index_generation_status.py +10 -0
- ragwell/_generated/models/ingestion_job_list_response.py +83 -0
- ragwell/_generated/models/ingestion_job_response.py +318 -0
- ragwell/_generated/models/ingestion_job_status.py +14 -0
- ragwell/_generated/models/ingestion_stage.py +13 -0
- ragwell/_generated/models/limit_key.py +13 -0
- ragwell/_generated/models/limit_unit.py +12 -0
- ragwell/_generated/models/plan_limit_error_detail.py +178 -0
- ragwell/_generated/models/plan_limit_error_detail_code.py +9 -0
- ragwell/_generated/models/plan_limit_error_response.py +62 -0
- ragwell/_generated/models/processing_profile_editability_response.py +99 -0
- ragwell/_generated/models/processing_profile_editability_response_reason_code_type_0.py +9 -0
- ragwell/_generated/models/processing_profile_summary_response.py +153 -0
- ragwell/_generated/models/processing_profile_technical_response.py +174 -0
- ragwell/_generated/models/processing_service_class_id.py +10 -0
- ragwell/_generated/models/processing_service_class_response.py +72 -0
- ragwell/_generated/models/project_response.py +126 -0
- ragwell/_generated/models/replace_document_metadata_request.py +57 -0
- ragwell/_generated/models/retrieval_citation_response.py +91 -0
- ragwell/_generated/models/retrieval_item_response.py +178 -0
- ragwell/_generated/models/retrieval_scores_response.py +82 -0
- ragwell/_generated/models/search_filters.py +110 -0
- ragwell/_generated/models/search_request.py +84 -0
- ragwell/_generated/models/search_response.py +98 -0
- ragwell/_generated/models/source_coordinate_kind.py +9 -0
- ragwell/_generated/models/source_span.py +155 -0
- ragwell/_generated/models/structural_unit.py +141 -0
- ragwell/_generated/models/structural_unit_kind.py +13 -0
- ragwell/_generated/models/text_retrieval_citation_response.py +98 -0
- ragwell/_generated/models/upload_session_response.py +262 -0
- ragwell/_generated/models/upload_state.py +12 -0
- ragwell/_generated/types.py +54 -0
- ragwell/_sync_http.py +217 -0
- ragwell/_transport.py +891 -0
- ragwell/async_client.py +1118 -0
- ragwell/client.py +1099 -0
- ragwell/errors.py +205 -0
- ragwell/py.typed +0 -0
- ragwell/types.py +34 -0
- ragwell-0.1.0.dist-info/METADATA +179 -0
- ragwell-0.1.0.dist-info/RECORD +142 -0
- ragwell-0.1.0.dist-info/WHEEL +4 -0
- ragwell-0.1.0.dist-info/licenses/LICENSE +21 -0
ragwell/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Typed synchronous and asynchronous clients for the Ragwell retrieval API."""
|
|
2
|
+
|
|
3
|
+
from .async_client import AsyncRagwell
|
|
4
|
+
from .client import Ragwell
|
|
5
|
+
from .errors import (
|
|
6
|
+
ApiError,
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
ConfigurationError,
|
|
9
|
+
ConflictError,
|
|
10
|
+
DownloadIntegrityError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
OperationFailedError,
|
|
13
|
+
PaginationError,
|
|
14
|
+
PermissionDeniedError,
|
|
15
|
+
ProtocolError,
|
|
16
|
+
QuotaExceededError,
|
|
17
|
+
RagwellError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
ServerError,
|
|
20
|
+
TransportError,
|
|
21
|
+
TransportTimeout,
|
|
22
|
+
ValidationError,
|
|
23
|
+
WaitTimeoutError,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"ApiError",
|
|
30
|
+
"AsyncRagwell",
|
|
31
|
+
"AuthenticationError",
|
|
32
|
+
"ConfigurationError",
|
|
33
|
+
"ConflictError",
|
|
34
|
+
"DownloadIntegrityError",
|
|
35
|
+
"NotFoundError",
|
|
36
|
+
"OperationFailedError",
|
|
37
|
+
"PaginationError",
|
|
38
|
+
"PermissionDeniedError",
|
|
39
|
+
"ProtocolError",
|
|
40
|
+
"QuotaExceededError",
|
|
41
|
+
"Ragwell",
|
|
42
|
+
"RagwellError",
|
|
43
|
+
"RateLimitError",
|
|
44
|
+
"ServerError",
|
|
45
|
+
"TransportError",
|
|
46
|
+
"TransportTimeout",
|
|
47
|
+
"ValidationError",
|
|
48
|
+
"WaitTimeoutError",
|
|
49
|
+
"__version__",
|
|
50
|
+
]
|
ragwell/_common.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Shared validation, pagination, and bounded file helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import io
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import tempfile
|
|
11
|
+
from collections.abc import AsyncIterator, Callable, Iterable, Iterator, Mapping
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
from typing import Any, BinaryIO, Self, TypeAlias, TypeVar
|
|
15
|
+
from urllib.parse import urlsplit
|
|
16
|
+
from uuid import UUID, uuid4
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from .errors import ConfigurationError
|
|
21
|
+
|
|
22
|
+
UploadInput: TypeAlias = str | os.PathLike[str] | bytes | BinaryIO
|
|
23
|
+
_IDEMPOTENCY_RE = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
|
|
24
|
+
_MEDIA_BY_SUFFIX = {
|
|
25
|
+
".pdf": "application/pdf",
|
|
26
|
+
".txt": "text/plain",
|
|
27
|
+
".md": "text/markdown",
|
|
28
|
+
".markdown": "text/markdown",
|
|
29
|
+
}
|
|
30
|
+
SUPPORTED_MEDIA_TYPES = frozenset(_MEDIA_BY_SUFFIX.values())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_configuration(
|
|
34
|
+
*, base_url: str | None, api_key: str | None
|
|
35
|
+
) -> tuple[httpx.URL, str]:
|
|
36
|
+
resolved_base = base_url if base_url is not None else os.getenv("RAGWELL_BASE_URL")
|
|
37
|
+
resolved_key = api_key if api_key is not None else os.getenv("RAGWELL_API_KEY")
|
|
38
|
+
if not resolved_base:
|
|
39
|
+
raise ConfigurationError(
|
|
40
|
+
"Set base_url or RAGWELL_BASE_URL; the beta SDK has no default endpoint"
|
|
41
|
+
)
|
|
42
|
+
if not resolved_key or not resolved_key.strip():
|
|
43
|
+
raise ConfigurationError("Set api_key or RAGWELL_API_KEY")
|
|
44
|
+
parts = urlsplit(resolved_base)
|
|
45
|
+
if parts.scheme not in {"https", "http"} or not parts.hostname:
|
|
46
|
+
raise ConfigurationError("base_url must be an absolute HTTP(S) URL")
|
|
47
|
+
if parts.username is not None or parts.password is not None:
|
|
48
|
+
raise ConfigurationError("base_url must not contain credentials")
|
|
49
|
+
if parts.query or parts.fragment:
|
|
50
|
+
raise ConfigurationError("base_url must not contain a query or fragment")
|
|
51
|
+
if parts.scheme == "http" and parts.hostname not in {
|
|
52
|
+
"localhost",
|
|
53
|
+
"127.0.0.1",
|
|
54
|
+
"::1",
|
|
55
|
+
}:
|
|
56
|
+
raise ConfigurationError("Plain HTTP is allowed only for loopback development")
|
|
57
|
+
return httpx.URL(resolved_base.rstrip("/")), resolved_key.strip()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resource_id(value: UUID | str, name: str) -> UUID:
|
|
61
|
+
if isinstance(value, UUID):
|
|
62
|
+
return value
|
|
63
|
+
try:
|
|
64
|
+
return UUID(value)
|
|
65
|
+
except (TypeError, ValueError, AttributeError) as exc:
|
|
66
|
+
raise ValueError(f"{name} must be a UUID") from exc
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def idempotency_key(value: str | None) -> str:
|
|
70
|
+
key = value if value is not None else f"rw_{uuid4()}"
|
|
71
|
+
if not _IDEMPOTENCY_RE.fullmatch(key):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
"idempotency_key must be 8-128 characters from A-Z, a-z, 0-9, . _ : -"
|
|
74
|
+
)
|
|
75
|
+
return key
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _query_value(value: Any) -> str | int | float | bool | None:
|
|
79
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
80
|
+
return value
|
|
81
|
+
if hasattr(value, "value"):
|
|
82
|
+
return _query_value(value.value)
|
|
83
|
+
return str(value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def query_params(**values: Any) -> list[tuple[str, str | int | float | bool | None]]:
|
|
87
|
+
result: list[tuple[str, str | int | float | bool | None]] = []
|
|
88
|
+
for name, value in values.items():
|
|
89
|
+
if value is None:
|
|
90
|
+
continue
|
|
91
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
92
|
+
result.extend((name, _query_value(item)) for item in value)
|
|
93
|
+
else:
|
|
94
|
+
result.append((name, _query_value(value)))
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def metadata_dict(value: Any) -> Mapping[str, Any]:
|
|
99
|
+
if hasattr(value, "to_dict"):
|
|
100
|
+
result = value.to_dict()
|
|
101
|
+
if isinstance(result, dict):
|
|
102
|
+
return result
|
|
103
|
+
if isinstance(value, Mapping):
|
|
104
|
+
return value
|
|
105
|
+
raise TypeError("request body must be a generated model or mapping")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PreparedUpload:
|
|
109
|
+
"""Seekable upload content whose original cursor/ownership are preserved."""
|
|
110
|
+
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
stream: BinaryIO,
|
|
115
|
+
owned: bool,
|
|
116
|
+
start: int,
|
|
117
|
+
filename: str,
|
|
118
|
+
media_type: str,
|
|
119
|
+
size: int,
|
|
120
|
+
sha256: str,
|
|
121
|
+
) -> None:
|
|
122
|
+
self.stream = stream
|
|
123
|
+
self.owned = owned
|
|
124
|
+
self.start = start
|
|
125
|
+
self.filename = filename
|
|
126
|
+
self.media_type = media_type
|
|
127
|
+
self.size = size
|
|
128
|
+
self.sha256 = sha256
|
|
129
|
+
|
|
130
|
+
def rewind(self) -> None:
|
|
131
|
+
self.stream.seek(self.start)
|
|
132
|
+
|
|
133
|
+
def close(self) -> None:
|
|
134
|
+
if self.owned:
|
|
135
|
+
self.stream.close()
|
|
136
|
+
else:
|
|
137
|
+
self.stream.seek(self.start)
|
|
138
|
+
|
|
139
|
+
def __enter__(self) -> Self:
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def __exit__(
|
|
143
|
+
self,
|
|
144
|
+
exc_type: type[BaseException] | None,
|
|
145
|
+
exc_value: BaseException | None,
|
|
146
|
+
traceback: TracebackType | None,
|
|
147
|
+
) -> None:
|
|
148
|
+
self.close()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AsyncFileContent(httpx.AsyncByteStream):
|
|
152
|
+
"""Read a caller-owned seekable stream without blocking the event loop."""
|
|
153
|
+
|
|
154
|
+
def __init__(self, stream: BinaryIO, *, chunk_size: int = 1024 * 1024) -> None:
|
|
155
|
+
self.stream = stream
|
|
156
|
+
self.chunk_size = chunk_size
|
|
157
|
+
|
|
158
|
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
159
|
+
while True:
|
|
160
|
+
chunk = await file_call(self.stream.read, self.chunk_size)
|
|
161
|
+
if not chunk:
|
|
162
|
+
return
|
|
163
|
+
yield chunk
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class SyncFileContent(httpx.SyncByteStream):
|
|
167
|
+
"""Read a caller-owned stream in fixed-size chunks."""
|
|
168
|
+
|
|
169
|
+
def __init__(self, stream: BinaryIO, *, chunk_size: int = 1024 * 1024) -> None:
|
|
170
|
+
self.stream = stream
|
|
171
|
+
self.chunk_size = chunk_size
|
|
172
|
+
|
|
173
|
+
def __iter__(self) -> Iterator[bytes]:
|
|
174
|
+
while True:
|
|
175
|
+
chunk = self.stream.read(self.chunk_size)
|
|
176
|
+
if not chunk:
|
|
177
|
+
return
|
|
178
|
+
yield chunk
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def prepare_upload(
|
|
182
|
+
source: UploadInput,
|
|
183
|
+
*,
|
|
184
|
+
filename: str | None,
|
|
185
|
+
media_type: str | None,
|
|
186
|
+
chunk_size: int = 1024 * 1024,
|
|
187
|
+
check: Callable[[], None] = lambda: None,
|
|
188
|
+
) -> PreparedUpload:
|
|
189
|
+
check()
|
|
190
|
+
owned = False
|
|
191
|
+
if isinstance(source, bytes):
|
|
192
|
+
if not filename:
|
|
193
|
+
raise ValueError("filename is required when uploading bytes")
|
|
194
|
+
stream: BinaryIO = io.BytesIO(source)
|
|
195
|
+
owned = True
|
|
196
|
+
start = 0
|
|
197
|
+
resolved_filename = filename
|
|
198
|
+
elif isinstance(source, (str, os.PathLike)):
|
|
199
|
+
path = Path(source)
|
|
200
|
+
stream = path.open("rb")
|
|
201
|
+
owned = True
|
|
202
|
+
start = 0
|
|
203
|
+
resolved_filename = filename or path.name
|
|
204
|
+
else:
|
|
205
|
+
stream = source
|
|
206
|
+
if not filename:
|
|
207
|
+
raise ValueError("filename is required when uploading a file object")
|
|
208
|
+
if not stream.seekable():
|
|
209
|
+
raise ValueError(
|
|
210
|
+
"high-level upload requires a seekable stream; use uploads.upload_content "
|
|
211
|
+
"with declared metadata for a bounded non-seekable source"
|
|
212
|
+
)
|
|
213
|
+
try:
|
|
214
|
+
start = stream.tell()
|
|
215
|
+
except (OSError, ValueError) as exc:
|
|
216
|
+
raise ValueError("upload stream must expose a stable cursor") from exc
|
|
217
|
+
resolved_filename = filename
|
|
218
|
+
|
|
219
|
+
suffix = Path(resolved_filename).suffix.lower()
|
|
220
|
+
resolved_media = media_type or _MEDIA_BY_SUFFIX.get(suffix)
|
|
221
|
+
if resolved_media not in SUPPORTED_MEDIA_TYPES:
|
|
222
|
+
if owned:
|
|
223
|
+
stream.close()
|
|
224
|
+
raise ValueError(
|
|
225
|
+
"media_type must be application/pdf, text/plain, or text/markdown"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
digest = hashlib.sha256()
|
|
229
|
+
size = 0
|
|
230
|
+
try:
|
|
231
|
+
while True:
|
|
232
|
+
check()
|
|
233
|
+
chunk = stream.read(chunk_size)
|
|
234
|
+
check()
|
|
235
|
+
if not chunk:
|
|
236
|
+
break
|
|
237
|
+
if not isinstance(chunk, bytes):
|
|
238
|
+
raise TypeError("upload stream must be opened in binary mode")
|
|
239
|
+
size += len(chunk)
|
|
240
|
+
digest.update(chunk)
|
|
241
|
+
stream.seek(start)
|
|
242
|
+
except BaseException:
|
|
243
|
+
if owned:
|
|
244
|
+
stream.close()
|
|
245
|
+
else:
|
|
246
|
+
try:
|
|
247
|
+
stream.seek(start)
|
|
248
|
+
except (OSError, ValueError):
|
|
249
|
+
pass
|
|
250
|
+
raise
|
|
251
|
+
return PreparedUpload(
|
|
252
|
+
stream=stream,
|
|
253
|
+
owned=owned,
|
|
254
|
+
start=start,
|
|
255
|
+
filename=resolved_filename,
|
|
256
|
+
media_type=resolved_media,
|
|
257
|
+
size=size,
|
|
258
|
+
sha256=digest.hexdigest(),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def create_temporary_destination(
|
|
263
|
+
destination: Path, *, overwrite: bool
|
|
264
|
+
) -> tuple[BinaryIO, Path]:
|
|
265
|
+
destination = destination.expanduser()
|
|
266
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
if destination.exists() and not overwrite:
|
|
268
|
+
raise FileExistsError(destination)
|
|
269
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
270
|
+
dir=destination.parent,
|
|
271
|
+
prefix=f".{destination.name}.",
|
|
272
|
+
suffix=".part",
|
|
273
|
+
)
|
|
274
|
+
return os.fdopen(descriptor, "wb"), Path(temporary_name)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def remove_temporary(path: Path) -> None:
|
|
278
|
+
try:
|
|
279
|
+
path.unlink()
|
|
280
|
+
except FileNotFoundError:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def finalize_temporary(temporary: Path, destination: Path, *, overwrite: bool) -> None:
|
|
285
|
+
if destination.exists() and not overwrite:
|
|
286
|
+
raise FileExistsError(destination)
|
|
287
|
+
os.replace(temporary, destination)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def repeated_cursor(cursor: object, seen: set[object]) -> bool:
|
|
291
|
+
if cursor is None:
|
|
292
|
+
return False
|
|
293
|
+
if cursor in seen:
|
|
294
|
+
return True
|
|
295
|
+
seen.add(cursor)
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def as_uuid_list(values: Iterable[UUID | str] | None) -> list[UUID] | None:
|
|
300
|
+
if values is None:
|
|
301
|
+
return None
|
|
302
|
+
return [resource_id(value, "document_id") for value in values]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
__all__ = [
|
|
306
|
+
"AsyncFileContent",
|
|
307
|
+
"PreparedUpload",
|
|
308
|
+
"SUPPORTED_MEDIA_TYPES",
|
|
309
|
+
"SyncFileContent",
|
|
310
|
+
"UploadInput",
|
|
311
|
+
"as_uuid_list",
|
|
312
|
+
"create_temporary_destination",
|
|
313
|
+
"finalize_temporary",
|
|
314
|
+
"idempotency_key",
|
|
315
|
+
"metadata_dict",
|
|
316
|
+
"prepare_upload",
|
|
317
|
+
"query_params",
|
|
318
|
+
"remove_temporary",
|
|
319
|
+
"repeated_cursor",
|
|
320
|
+
"resolve_configuration",
|
|
321
|
+
"resource_id",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
FileT = TypeVar("FileT")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
async def file_call(
|
|
329
|
+
function: Callable[..., FileT],
|
|
330
|
+
*args: Any,
|
|
331
|
+
_cancel_result: Callable[[FileT], None] | None = None,
|
|
332
|
+
**kwargs: Any,
|
|
333
|
+
) -> FileT:
|
|
334
|
+
"""Join in-flight file I/O before restoring/closing its handle on cancellation."""
|
|
335
|
+
task = asyncio.create_task(asyncio.to_thread(function, *args, **kwargs))
|
|
336
|
+
try:
|
|
337
|
+
return await asyncio.shield(task)
|
|
338
|
+
except asyncio.CancelledError:
|
|
339
|
+
try:
|
|
340
|
+
value = await _join_file(task)
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
else:
|
|
344
|
+
if _cancel_result is not None:
|
|
345
|
+
_cancel_result(value)
|
|
346
|
+
raise
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
async def prepare_upload_async(
|
|
350
|
+
source: UploadInput,
|
|
351
|
+
*,
|
|
352
|
+
filename: str | None,
|
|
353
|
+
media_type: str | None,
|
|
354
|
+
check: Callable[[], None],
|
|
355
|
+
) -> PreparedUpload:
|
|
356
|
+
# A worker owns preparation until handoff. Cancellation must not lose an
|
|
357
|
+
# opened SDK file or leave a caller's cursor at the end of the hash pass.
|
|
358
|
+
task = asyncio.create_task(
|
|
359
|
+
asyncio.to_thread(
|
|
360
|
+
prepare_upload,
|
|
361
|
+
source,
|
|
362
|
+
filename=filename,
|
|
363
|
+
media_type=media_type,
|
|
364
|
+
check=check,
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
try:
|
|
368
|
+
return await asyncio.shield(task)
|
|
369
|
+
except asyncio.CancelledError:
|
|
370
|
+
try:
|
|
371
|
+
prepared = await _join_file(task)
|
|
372
|
+
except Exception:
|
|
373
|
+
pass
|
|
374
|
+
else:
|
|
375
|
+
await file_call(prepared.close)
|
|
376
|
+
raise
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def _join_file(task: asyncio.Task[FileT]) -> FileT:
|
|
380
|
+
while True:
|
|
381
|
+
try:
|
|
382
|
+
return await asyncio.shield(task)
|
|
383
|
+
except asyncio.CancelledError:
|
|
384
|
+
if task.cancelled():
|
|
385
|
+
raise
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def discard_temporary(value: tuple[BinaryIO, Path]) -> None:
|
|
389
|
+
output, temporary = value
|
|
390
|
+
try:
|
|
391
|
+
output.close()
|
|
392
|
+
finally:
|
|
393
|
+
remove_temporary(temporary)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api_info_version": "0.1.0",
|
|
3
|
+
"artifact_version": "2026-09-19.1",
|
|
4
|
+
"canonical_sha256": "57a1f942f7ca8b89186753a20320588b57e2425663b8529b36037066c02328bb",
|
|
5
|
+
"compatibility_notes": "See contracts/machine/README.md. Artifact identity is its digest, not runtime version equality. Installed Python SDK qualification and public publication remain separate release gates.",
|
|
6
|
+
"machine_sha256": "8e05de4ae0f3aa76261e97ad07be1fc77756f317c3e2c20ab2be3efb2cb6db90",
|
|
7
|
+
"operation_count": 26,
|
|
8
|
+
"operations": [
|
|
9
|
+
{
|
|
10
|
+
"method": "GET",
|
|
11
|
+
"operation_id": "get_document_upload_policy_v1_document_upload_policy_get",
|
|
12
|
+
"path": "/v1/document-upload-policy"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"method": "GET",
|
|
16
|
+
"operation_id": "get_project_v1_projects__project_id__get",
|
|
17
|
+
"path": "/v1/projects/{project_id}"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"method": "GET",
|
|
21
|
+
"operation_id": "get_document_deletion_v1_projects__project_id__document_deletions__receipt_id__get",
|
|
22
|
+
"path": "/v1/projects/{project_id}/document-deletions/{receipt_id}"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"method": "POST",
|
|
26
|
+
"operation_id": "retry_document_deletion_v1_projects__project_id__document_deletions__receipt_id__retry_post",
|
|
27
|
+
"path": "/v1/projects/{project_id}/document-deletions/{receipt_id}/retry"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"method": "GET",
|
|
31
|
+
"operation_id": "list_documents_v1_projects__project_id__documents_get",
|
|
32
|
+
"path": "/v1/projects/{project_id}/documents"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"method": "DELETE",
|
|
36
|
+
"operation_id": "delete_document_v1_projects__project_id__documents__document_id__delete",
|
|
37
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"method": "GET",
|
|
41
|
+
"operation_id": "get_document_v1_projects__project_id__documents__document_id__get",
|
|
42
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"method": "GET",
|
|
46
|
+
"operation_id": "get_document_activity_v1_projects__project_id__documents__document_id__activity_get",
|
|
47
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/activity"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"method": "POST",
|
|
51
|
+
"operation_id": "create_document_export_v1_projects__project_id__documents__document_id__exports_post",
|
|
52
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/exports"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"method": "GET",
|
|
56
|
+
"operation_id": "get_document_export_v1_projects__project_id__documents__document_id__exports__export_id__get",
|
|
57
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/exports/{export_id}"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"method": "GET",
|
|
61
|
+
"operation_id": "download_document_export_part_v1_projects__project_id__documents__document_id__exports__export_id__parts__part_number__get",
|
|
62
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/exports/{export_id}/parts/{part_number}"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"method": "GET",
|
|
66
|
+
"operation_id": "list_document_generations_v1_projects__project_id__documents__document_id__generations_get",
|
|
67
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/generations"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"method": "GET",
|
|
71
|
+
"operation_id": "list_document_chunks_v1_projects__project_id__documents__document_id__generations__generation_id__chunks_get",
|
|
72
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/generations/{generation_id}/chunks"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"method": "GET",
|
|
76
|
+
"operation_id": "inspect_document_source_v1_projects__project_id__documents__document_id__generations__generation_id__sources__source_id__get",
|
|
77
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/generations/{generation_id}/sources/{source_id}"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"method": "GET",
|
|
81
|
+
"operation_id": "inspect_document_v1_projects__project_id__documents__document_id__inspection_get",
|
|
82
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/inspection"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"method": "PUT",
|
|
86
|
+
"operation_id": "replace_document_metadata_v1_projects__project_id__documents__document_id__metadata_put",
|
|
87
|
+
"path": "/v1/projects/{project_id}/documents/{document_id}/metadata"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"method": "GET",
|
|
91
|
+
"operation_id": "inspect_embedding_connection_v1_projects__project_id__embedding_connection_get",
|
|
92
|
+
"path": "/v1/projects/{project_id}/embedding-connection"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"method": "GET",
|
|
96
|
+
"operation_id": "list_jobs_v1_projects__project_id__jobs_get",
|
|
97
|
+
"path": "/v1/projects/{project_id}/jobs"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"method": "GET",
|
|
101
|
+
"operation_id": "get_job_v1_projects__project_id__jobs__job_id__get",
|
|
102
|
+
"path": "/v1/projects/{project_id}/jobs/{job_id}"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"method": "POST",
|
|
106
|
+
"operation_id": "cancel_job_v1_projects__project_id__jobs__job_id__cancel_post",
|
|
107
|
+
"path": "/v1/projects/{project_id}/jobs/{job_id}/cancel"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"method": "POST",
|
|
111
|
+
"operation_id": "retry_job_v1_projects__project_id__jobs__job_id__retry_post",
|
|
112
|
+
"path": "/v1/projects/{project_id}/jobs/{job_id}/retry"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"method": "POST",
|
|
116
|
+
"operation_id": "search_v1_projects__project_id__search_post",
|
|
117
|
+
"path": "/v1/projects/{project_id}/search"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"method": "POST",
|
|
121
|
+
"operation_id": "create_upload_v1_projects__project_id__uploads_post",
|
|
122
|
+
"path": "/v1/projects/{project_id}/uploads"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"method": "GET",
|
|
126
|
+
"operation_id": "get_upload_state",
|
|
127
|
+
"path": "/v1/projects/{project_id}/uploads/{upload_id}"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"method": "PUT",
|
|
131
|
+
"operation_id": "upload_content_v1_projects__project_id__uploads__upload_id__content_put",
|
|
132
|
+
"path": "/v1/projects/{project_id}/uploads/{upload_id}/content"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"method": "POST",
|
|
136
|
+
"operation_id": "finalize_upload_v1_projects__project_id__uploads__upload_id__finalize_post",
|
|
137
|
+
"path": "/v1/projects/{project_id}/uploads/{upload_id}/finalize"
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|