capture-runtime-client 0.4.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.
- capture_runtime_client/__init__.py +41 -0
- capture_runtime_client/client.py +852 -0
- capture_runtime_client/codec.py +124 -0
- capture_runtime_client/contracts.py +272 -0
- capture_runtime_client/errors.py +170 -0
- capture_runtime_client/private/__init__.py +1 -0
- capture_runtime_client/private/assets/__init__.py +1 -0
- capture_runtime_client/private/assets/contract-set.json +1 -0
- capture_runtime_client/private/assets/contract-set.sha256 +1 -0
- capture_runtime_client/private/generated_models.py +627 -0
- capture_runtime_client/private/schemas/__init__.py +1 -0
- capture_runtime_client/private/schemas/capture-block.schema.json +151 -0
- capture_runtime_client/private/schemas/capture-document.schema.json +356 -0
- capture_runtime_client/private/schemas/capture-engine.schema.json +40 -0
- capture_runtime_client/private/schemas/capture-event-v2.schema.json +301 -0
- capture_runtime_client/private/schemas/capture-failure-v2.schema.json +40 -0
- capture_runtime_client/private/schemas/capture-operation-v2.schema.json +207 -0
- capture_runtime_client/private/schemas/capture-source.schema.json +34 -0
- capture_runtime_client/private/schemas/error-body-v2.schema.json +34 -0
- capture_runtime_client/private/schemas/error-envelope-v2.schema.json +49 -0
- capture_runtime_client/private/schemas/finalize-ingestion-v2.schema.json +27 -0
- capture_runtime_client/private/schemas/ingestion-v2.schema.json +127 -0
- capture_runtime_client/private/schemas/open-ingestion-v2.schema.json +79 -0
- capture_runtime_client/private/schemas/page-locator.schema.json +49 -0
- capture_runtime_client/private/schemas/partial-capture-v2.schema.json +267 -0
- capture_runtime_client/private/schemas/raw-capture-segment.schema.json +125 -0
- capture_runtime_client/private/schemas/raw-capture.schema.json +260 -0
- capture_runtime_client/private/schemas/report-structuring-failure-v2.schema.json +28 -0
- capture_runtime_client/private/schemas/runtime-artifact-descriptor-v2.schema.json +34 -0
- capture_runtime_client/private/schemas/runtime-installation-v2.schema.json +126 -0
- capture_runtime_client/private/schemas/runtime-installations-v2.schema.json +143 -0
- capture_runtime_client/private/schemas/runtime-model-installation-v2.schema.json +121 -0
- capture_runtime_client/private/schemas/runtime-model-installations-v2.schema.json +138 -0
- capture_runtime_client/private/schemas/runtime-model-option-v2.schema.json +80 -0
- capture_runtime_client/private/schemas/runtime-model-options-v2.schema.json +103 -0
- capture_runtime_client/private/schemas/runtime-ready.schema.json +88 -0
- capture_runtime_client/private/schemas/runtime-requirement-v2.schema.json +120 -0
- capture_runtime_client/private/schemas/runtime-requirements-v2.schema.json +137 -0
- capture_runtime_client/private/schemas/runtime-streaming-capabilities-v2.schema.json +64 -0
- capture_runtime_client/private/schemas/start-capture-v2.schema.json +59 -0
- capture_runtime_client/private/schemas/start-runtime-installation-v2.schema.json +26 -0
- capture_runtime_client/private/schemas/start-runtime-model-installation-v2.schema.json +21 -0
- capture_runtime_client/private/schemas/time-locator.schema.json +27 -0
- capture_runtime_client/py.typed +1 -0
- capture_runtime_client/transport.py +158 -0
- capture_runtime_client-0.4.0.dist-info/METADATA +18 -0
- capture_runtime_client-0.4.0.dist-info/RECORD +48 -0
- capture_runtime_client-0.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Private wire codecs shared by HTTP and in-memory adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from pydantic import BaseModel, ValidationError
|
|
10
|
+
|
|
11
|
+
from .errors import (
|
|
12
|
+
CaptureAuthenticationError,
|
|
13
|
+
CaptureRemoteError,
|
|
14
|
+
CaptureRuntimeProtocolError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def decode_model[ModelT: BaseModel](response: httpx.Response, model: type[ModelT]) -> ModelT:
|
|
19
|
+
payload = decode_json(response)
|
|
20
|
+
try:
|
|
21
|
+
return model.model_validate(payload)
|
|
22
|
+
except ValidationError as error:
|
|
23
|
+
raise CaptureRuntimeProtocolError(
|
|
24
|
+
f"Capture Runtime returned invalid {model.__name__} data.",
|
|
25
|
+
error.errors(include_url=False),
|
|
26
|
+
) from error
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decode_json(response: httpx.Response) -> Any:
|
|
30
|
+
if response.status_code >= 400:
|
|
31
|
+
try:
|
|
32
|
+
envelope = response.json()
|
|
33
|
+
except ValueError:
|
|
34
|
+
envelope = None
|
|
35
|
+
error = envelope.get("error") if isinstance(envelope, dict) else None
|
|
36
|
+
error_map = error if isinstance(error, dict) else {}
|
|
37
|
+
code = str(error_map.get("code", f"http_{response.status_code}"))
|
|
38
|
+
message = str(
|
|
39
|
+
error_map.get("message", f"Capture Runtime request failed ({response.status_code}).")
|
|
40
|
+
)
|
|
41
|
+
details = error_map.get("details")
|
|
42
|
+
details_map = details if isinstance(details, dict) else {}
|
|
43
|
+
category = error_map.get("category", details_map.get("category"))
|
|
44
|
+
retryable = error_map.get(
|
|
45
|
+
"retryable", details_map.get("retryable", response.status_code >= 500)
|
|
46
|
+
)
|
|
47
|
+
issues = error_map.get("issues", details_map.get("issues"))
|
|
48
|
+
request_id = (
|
|
49
|
+
response.headers.get("x-request-id")
|
|
50
|
+
or response.headers.get("x-correlation-id")
|
|
51
|
+
or _optional_text(error_map.get("requestId", details_map.get("requestId")))
|
|
52
|
+
)
|
|
53
|
+
if response.status_code in {401, 403} or code in {"unauthorized", "authentication_failed"}:
|
|
54
|
+
raise CaptureAuthenticationError(
|
|
55
|
+
message,
|
|
56
|
+
status_code=response.status_code,
|
|
57
|
+
details=details,
|
|
58
|
+
request_id=request_id,
|
|
59
|
+
)
|
|
60
|
+
raise CaptureRemoteError(
|
|
61
|
+
status_code=response.status_code,
|
|
62
|
+
code=code,
|
|
63
|
+
message=message,
|
|
64
|
+
category=_optional_text(category),
|
|
65
|
+
retryable=bool(retryable),
|
|
66
|
+
details=details,
|
|
67
|
+
issues=issues if isinstance(issues, list) else None,
|
|
68
|
+
request_id=request_id,
|
|
69
|
+
)
|
|
70
|
+
if response.status_code == 204:
|
|
71
|
+
return None
|
|
72
|
+
try:
|
|
73
|
+
return response.json()
|
|
74
|
+
except ValueError as error:
|
|
75
|
+
raise CaptureRuntimeProtocolError("Capture Runtime returned invalid JSON.") from error
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def iter_sse(lines: Iterator[bytes]) -> Iterator[dict[str, str]]:
|
|
79
|
+
event: dict[str, str] = {}
|
|
80
|
+
data: list[str] = []
|
|
81
|
+
buffer = ""
|
|
82
|
+
|
|
83
|
+
def consume(line: str) -> dict[str, str] | None:
|
|
84
|
+
nonlocal event, data
|
|
85
|
+
if not line:
|
|
86
|
+
if data:
|
|
87
|
+
result = {**event, "data": "\n".join(data)}
|
|
88
|
+
event = {}
|
|
89
|
+
data = []
|
|
90
|
+
return result
|
|
91
|
+
event = {}
|
|
92
|
+
data = []
|
|
93
|
+
return None
|
|
94
|
+
if line.startswith(":"):
|
|
95
|
+
return None
|
|
96
|
+
key, separator, value = line.partition(":")
|
|
97
|
+
if separator and value.startswith(" "):
|
|
98
|
+
value = value[1:]
|
|
99
|
+
if key == "data":
|
|
100
|
+
data.append(value)
|
|
101
|
+
elif key in {"id", "event"}:
|
|
102
|
+
event[key] = value
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
for raw in lines:
|
|
106
|
+
buffer += raw.decode("utf-8")
|
|
107
|
+
while "\n" in buffer:
|
|
108
|
+
line, buffer = buffer.split("\n", 1)
|
|
109
|
+
result = consume(line[:-1] if line.endswith("\r") else line)
|
|
110
|
+
if result is not None:
|
|
111
|
+
yield result
|
|
112
|
+
if buffer:
|
|
113
|
+
result = consume(buffer[:-1] if buffer.endswith("\r") else buffer)
|
|
114
|
+
if result is not None:
|
|
115
|
+
yield result
|
|
116
|
+
if data:
|
|
117
|
+
yield {**event, "data": "\n".join(data)}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = ["decode_json", "decode_model", "iter_sse"]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _optional_text(value: Any) -> str | None:
|
|
124
|
+
return value if isinstance(value, str) and value else None
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Public v2 contract DTOs backed by private generated wire models.
|
|
2
|
+
|
|
3
|
+
The generated module is an SDK build input only. Consumers receive deliberate
|
|
4
|
+
canonical names from this module; generated identifiers never form part of the
|
|
5
|
+
package's public surface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from importlib.resources import files
|
|
13
|
+
from typing import Any, cast
|
|
14
|
+
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from .private import generated_models as _generated
|
|
18
|
+
|
|
19
|
+
for _generated_name in (
|
|
20
|
+
"RuntimeModelInstallationV2",
|
|
21
|
+
"RuntimeModelInstallationsV2",
|
|
22
|
+
"RuntimeInstallationV2",
|
|
23
|
+
"RuntimeInstallationsV2",
|
|
24
|
+
"CaptureOperationV2",
|
|
25
|
+
"PartialCaptureV2",
|
|
26
|
+
"CaptureEventV2",
|
|
27
|
+
):
|
|
28
|
+
getattr(_generated, _generated_name).model_rebuild(
|
|
29
|
+
force=True,
|
|
30
|
+
_types_namespace=vars(_generated),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
CAPTURE_API_VERSION = "2.0"
|
|
34
|
+
CAPTURE_DOCUMENT_SCHEMA_VERSION = _generated.CAPTURE_DOCUMENT_SCHEMA_VERSION
|
|
35
|
+
CAPTURE_RUNTIME_VERSION = "0.4.0"
|
|
36
|
+
CAPTURE_DOCUMENT_SCHEMA_ID = (
|
|
37
|
+
"https://github.com/gx-capture/capture-workbench/schema/capture-document-v2.schema.json"
|
|
38
|
+
)
|
|
39
|
+
CAPTURE_DOCUMENT_SCHEMA_SHA256 = "850afd212d049c25da41d3867ba5477451a6a2c6c7e41f116fe60f26b6a35335"
|
|
40
|
+
_CONTRACT_SET_ASSETS = files("capture_runtime_client.private.assets")
|
|
41
|
+
CAPTURE_CONTRACT_SET_SHA256 = _CONTRACT_SET_ASSETS.joinpath(
|
|
42
|
+
"contract-set.sha256"
|
|
43
|
+
).read_text(encoding="ascii").strip()
|
|
44
|
+
if hashlib.sha256(
|
|
45
|
+
_CONTRACT_SET_ASSETS.joinpath("contract-set.json").read_bytes()
|
|
46
|
+
).hexdigest() != CAPTURE_CONTRACT_SET_SHA256:
|
|
47
|
+
raise RuntimeError("Packaged contract-set asset does not match its SHA-256 allowlist.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Public v2 DTOs. These subclasses intentionally create a stable public seam
|
|
51
|
+
# without exporting the generated module or its model names. Pydantic keeps
|
|
52
|
+
# inherited strict/alias validation, while each class has a canonical v2 name
|
|
53
|
+
# for consumers and package introspection.
|
|
54
|
+
class CaptureSource(_generated.CaptureSource):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CaptureEngine(_generated.CaptureEngine):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RawCaptureSegment(_generated.RawCaptureSegment):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CaptureBlock(_generated.CaptureBlock):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CaptureFailure(_generated.CaptureFailureV2):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CaptureDocument(_generated.CaptureDocument):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RawCapture(_generated.RawCapture):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RuntimeArtifactDescriptor(_generated.RuntimeArtifactDescriptorV2):
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class RuntimeRequirement(_generated.RuntimeRequirementV2):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RuntimeRequirements(_generated.RuntimeRequirementsV2):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class RuntimeModelOption(_generated.RuntimeModelOptionV2):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class RuntimeModelOptions(_generated.RuntimeModelOptionsV2):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RuntimeModelInstallation(_generated.RuntimeModelInstallationV2):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class RuntimeModelInstallations(_generated.RuntimeModelInstallationsV2):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class RuntimeInstallation(_generated.RuntimeInstallationV2):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class RuntimeInstallations(_generated.RuntimeInstallationsV2):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class OpenIngestion(_generated.OpenIngestionV2):
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Ingestion(_generated.IngestionV2):
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class StartCapture(_generated.StartCaptureV2):
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FinalizeIngestion(_generated.FinalizeIngestionV2):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CaptureOperation(_generated.CaptureOperationV2):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class PartialCapture(_generated.PartialCaptureV2):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CaptureEvent(_generated.CaptureEventV2):
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class RuntimeStreamingCapabilities(_generated.RuntimeStreamingCapabilitiesV2):
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class RuntimeReady(_generated.StrictModel):
|
|
151
|
+
"""General v2 readiness payload shared by discovery clients."""
|
|
152
|
+
|
|
153
|
+
ready: bool
|
|
154
|
+
service: str
|
|
155
|
+
api_version: str
|
|
156
|
+
runtime_version: str
|
|
157
|
+
capture_document_schema_version: str
|
|
158
|
+
capture_document_schema_sha256: str | None = None
|
|
159
|
+
schema_sha256: str | None = None
|
|
160
|
+
contract_set_version: str = "2"
|
|
161
|
+
capabilities: dict[str, Any] = Field(default_factory=dict)
|
|
162
|
+
message: str | None = None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ReportStructuringFailure(_generated.ReportStructuringFailureV2):
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ErrorBody(_generated.ErrorBodyV2):
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ErrorEnvelope(_generated.ErrorEnvelopeV2):
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
CaptureLocator = _generated.CaptureLocator
|
|
178
|
+
PageLocator = _generated.PageLocator
|
|
179
|
+
TimeLocator = _generated.TimeLocator
|
|
180
|
+
|
|
181
|
+
CaptureSourceKind = _generated.CaptureSourceKind
|
|
182
|
+
StructuringMode = _generated.StructuringMode
|
|
183
|
+
RuntimeInstallationStatus = _generated.RuntimeInstallationStatus
|
|
184
|
+
RuntimeModelOptionStatus = _generated.RuntimeModelOptionStatus
|
|
185
|
+
RuntimeRequirementStatus = _generated.RuntimeRequirementStatus
|
|
186
|
+
StreamingIngestionMode = _generated.StreamingIngestionMode
|
|
187
|
+
StreamingIngestionStatus = _generated.StreamingIngestionStatus
|
|
188
|
+
StreamingCaptureStatus = _generated.StreamingCaptureStatus
|
|
189
|
+
StreamingEventType = _generated.StreamingEventType
|
|
190
|
+
CaptureRequirementId = _generated.CaptureRequirementId
|
|
191
|
+
project_source_text = _generated.project_source_text
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _load_contract_schema(name: str) -> dict[str, Any]:
|
|
195
|
+
filename = {
|
|
196
|
+
"RawCapture": "raw-capture.schema.json",
|
|
197
|
+
"CaptureDocument": "capture-document.schema.json",
|
|
198
|
+
}.get(name)
|
|
199
|
+
if filename is None:
|
|
200
|
+
raise ValueError(f"Unknown generated contract schema: {name}")
|
|
201
|
+
return cast(
|
|
202
|
+
dict[str, Any],
|
|
203
|
+
json.loads(files("capture_runtime_client.private.schemas").joinpath(filename).read_text()),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _load_contract_manifest() -> dict[str, Any]:
|
|
208
|
+
return {
|
|
209
|
+
"manifestVersion": "1",
|
|
210
|
+
"packageVersion": CAPTURE_RUNTIME_VERSION,
|
|
211
|
+
"runtimeVersion": CAPTURE_RUNTIME_VERSION,
|
|
212
|
+
"apiVersion": CAPTURE_API_VERSION,
|
|
213
|
+
"captureDocumentSchemaVersion": CAPTURE_DOCUMENT_SCHEMA_VERSION,
|
|
214
|
+
"captureDocumentSchemaId": CAPTURE_DOCUMENT_SCHEMA_ID,
|
|
215
|
+
"captureDocumentSchemaSha256": CAPTURE_DOCUMENT_SCHEMA_SHA256,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _load_contract_constraints() -> dict[str, Any]:
|
|
220
|
+
return {"invariants": []}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = [
|
|
224
|
+
"CAPTURE_API_VERSION",
|
|
225
|
+
"CAPTURE_DOCUMENT_SCHEMA_ID",
|
|
226
|
+
"CAPTURE_DOCUMENT_SCHEMA_SHA256",
|
|
227
|
+
"CAPTURE_CONTRACT_SET_SHA256",
|
|
228
|
+
"CAPTURE_DOCUMENT_SCHEMA_VERSION",
|
|
229
|
+
"CAPTURE_RUNTIME_VERSION",
|
|
230
|
+
"CaptureLocator",
|
|
231
|
+
"PageLocator",
|
|
232
|
+
"TimeLocator",
|
|
233
|
+
"CaptureSource",
|
|
234
|
+
"CaptureEngine",
|
|
235
|
+
"RawCaptureSegment",
|
|
236
|
+
"CaptureBlock",
|
|
237
|
+
"CaptureFailure",
|
|
238
|
+
"CaptureDocument",
|
|
239
|
+
"RawCapture",
|
|
240
|
+
"RuntimeArtifactDescriptor",
|
|
241
|
+
"RuntimeRequirement",
|
|
242
|
+
"RuntimeRequirements",
|
|
243
|
+
"RuntimeModelOption",
|
|
244
|
+
"RuntimeModelOptions",
|
|
245
|
+
"RuntimeModelInstallation",
|
|
246
|
+
"RuntimeModelInstallations",
|
|
247
|
+
"RuntimeInstallation",
|
|
248
|
+
"RuntimeInstallations",
|
|
249
|
+
"OpenIngestion",
|
|
250
|
+
"Ingestion",
|
|
251
|
+
"StartCapture",
|
|
252
|
+
"FinalizeIngestion",
|
|
253
|
+
"CaptureOperation",
|
|
254
|
+
"PartialCapture",
|
|
255
|
+
"CaptureEvent",
|
|
256
|
+
"RuntimeStreamingCapabilities",
|
|
257
|
+
"RuntimeReady",
|
|
258
|
+
"ReportStructuringFailure",
|
|
259
|
+
"ErrorBody",
|
|
260
|
+
"ErrorEnvelope",
|
|
261
|
+
"CaptureSourceKind",
|
|
262
|
+
"StructuringMode",
|
|
263
|
+
"RuntimeInstallationStatus",
|
|
264
|
+
"RuntimeModelOptionStatus",
|
|
265
|
+
"RuntimeRequirementStatus",
|
|
266
|
+
"StreamingIngestionMode",
|
|
267
|
+
"StreamingIngestionStatus",
|
|
268
|
+
"StreamingCaptureStatus",
|
|
269
|
+
"StreamingEventType",
|
|
270
|
+
"CaptureRequirementId",
|
|
271
|
+
"project_source_text",
|
|
272
|
+
]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Stable, credential-safe client exception types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
_BEARER = re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]+")
|
|
9
|
+
_SECRET_FIELD = re.compile(r"(?:token|authorization|credential|secret|password)", re.I)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def redact_text(value: str) -> str:
|
|
13
|
+
return _BEARER.sub("Bearer [redacted]", value)[:500]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def redact_value(value: Any, *, depth: int = 0) -> Any:
|
|
17
|
+
if depth > 4:
|
|
18
|
+
return "[redacted]"
|
|
19
|
+
if isinstance(value, str):
|
|
20
|
+
return redact_text(value)
|
|
21
|
+
if isinstance(value, list):
|
|
22
|
+
return [redact_value(item, depth=depth + 1) for item in value]
|
|
23
|
+
if isinstance(value, dict):
|
|
24
|
+
return {
|
|
25
|
+
str(key)[:128]: "[redacted]"
|
|
26
|
+
if _SECRET_FIELD.search(str(key))
|
|
27
|
+
else redact_value(item, depth=depth + 1)
|
|
28
|
+
for key, item in value.items()
|
|
29
|
+
}
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CaptureRuntimeError(RuntimeError):
|
|
34
|
+
"""Base error with stable, redacted wire diagnostics.
|
|
35
|
+
|
|
36
|
+
``CaptureRemoteError`` and the narrower taxonomy subclasses below preserve
|
|
37
|
+
the fields emitted by a runtime error envelope. The historical
|
|
38
|
+
``CaptureRuntime*`` names remain available as compatibility aliases.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
category = "runtime"
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
status_code: int,
|
|
47
|
+
code: str,
|
|
48
|
+
message: str,
|
|
49
|
+
details: Any = None,
|
|
50
|
+
category: str | None = None,
|
|
51
|
+
retryable: bool = False,
|
|
52
|
+
issues: list[dict[str, Any]] | None = None,
|
|
53
|
+
request_id: str | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self.status_code = status_code
|
|
56
|
+
self.code = code
|
|
57
|
+
self.runtime_message = redact_text(message)
|
|
58
|
+
self.details = redact_value(details)
|
|
59
|
+
self.category = category or self.category
|
|
60
|
+
self.retryable = retryable
|
|
61
|
+
self.issues = redact_value(issues) if issues is not None else []
|
|
62
|
+
self.request_id = request_id
|
|
63
|
+
super().__init__(f"Capture Runtime request failed ({code}): {self.runtime_message}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CaptureTransportError(CaptureRuntimeError):
|
|
67
|
+
category = "transport"
|
|
68
|
+
|
|
69
|
+
def __init__(self, message: str, details: Any = None, *, code: str = "transport_error") -> None:
|
|
70
|
+
super().__init__(
|
|
71
|
+
status_code=0,
|
|
72
|
+
code=code,
|
|
73
|
+
message=message,
|
|
74
|
+
details=details,
|
|
75
|
+
category=self.category,
|
|
76
|
+
retryable=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CaptureAuthenticationError(CaptureRuntimeError):
|
|
81
|
+
category = "authentication"
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
message: str = "Capture Runtime authentication failed.",
|
|
86
|
+
*,
|
|
87
|
+
status_code: int = 401,
|
|
88
|
+
details: Any = None,
|
|
89
|
+
request_id: str | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(
|
|
92
|
+
status_code=status_code,
|
|
93
|
+
code="unauthorized",
|
|
94
|
+
message=message,
|
|
95
|
+
details=details,
|
|
96
|
+
category=self.category,
|
|
97
|
+
request_id=request_id,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class CaptureCompatibilityError(CaptureRuntimeError):
|
|
102
|
+
category = "compatibility"
|
|
103
|
+
|
|
104
|
+
def __init__(self, message: str, details: Any = None) -> None:
|
|
105
|
+
super().__init__(
|
|
106
|
+
status_code=0,
|
|
107
|
+
code="incompatible_runtime",
|
|
108
|
+
message=message,
|
|
109
|
+
details=details,
|
|
110
|
+
category=self.category,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CaptureProtocolError(CaptureRuntimeError):
|
|
115
|
+
category = "protocol"
|
|
116
|
+
|
|
117
|
+
def __init__(self, message: str, details: Any = None) -> None:
|
|
118
|
+
super().__init__(
|
|
119
|
+
status_code=0,
|
|
120
|
+
code="protocol_error",
|
|
121
|
+
message=message,
|
|
122
|
+
details=details,
|
|
123
|
+
category=self.category,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class CaptureRemoteError(CaptureRuntimeError):
|
|
128
|
+
category = "remote"
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
*,
|
|
133
|
+
status_code: int,
|
|
134
|
+
code: str,
|
|
135
|
+
message: str,
|
|
136
|
+
category: str | None = None,
|
|
137
|
+
retryable: bool = False,
|
|
138
|
+
details: Any = None,
|
|
139
|
+
issues: list[dict[str, Any]] | None = None,
|
|
140
|
+
request_id: str | None = None,
|
|
141
|
+
) -> None:
|
|
142
|
+
super().__init__(
|
|
143
|
+
status_code=status_code,
|
|
144
|
+
code=code,
|
|
145
|
+
message=message,
|
|
146
|
+
details=details,
|
|
147
|
+
category=category or self.category,
|
|
148
|
+
retryable=retryable,
|
|
149
|
+
issues=issues,
|
|
150
|
+
request_id=request_id,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Compatibility names used by the first SDK preview.
|
|
155
|
+
CaptureRuntimeProtocolError = CaptureProtocolError
|
|
156
|
+
CaptureRuntimeCompatibilityError = CaptureCompatibilityError
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = [
|
|
160
|
+
"CaptureAuthenticationError",
|
|
161
|
+
"CaptureCompatibilityError",
|
|
162
|
+
"CaptureProtocolError",
|
|
163
|
+
"CaptureRemoteError",
|
|
164
|
+
"CaptureTransportError",
|
|
165
|
+
"CaptureRuntimeCompatibilityError",
|
|
166
|
+
"CaptureRuntimeError",
|
|
167
|
+
"CaptureRuntimeProtocolError",
|
|
168
|
+
"redact_text",
|
|
169
|
+
"redact_value",
|
|
170
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Private generated SDK inputs; do not import from consumer applications."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged canonical runtime contract-set assets."""
|