vikingdb-python-sdk 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/PKG-INFO +22 -32
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/README.md +20 -28
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/pyproject.toml +2 -3
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/__init__.py +2 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/_client.py +87 -20
- vikingdb_python_sdk-0.1.2/vikingdb/exceptions.py +160 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/client.py +13 -56
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/exceptions.py +19 -13
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/__init__.py +1 -3
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/client.py +41 -35
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/collection.py +0 -1
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/embedding.py +1 -1
- vikingdb_python_sdk-0.1.2/vikingdb/vector/exceptions.py +40 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/index.py +0 -1
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/version.py +1 -1
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/PKG-INFO +22 -32
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/SOURCES.txt +1 -0
- vikingdb_python_sdk-0.1.0/vikingdb/vector/exceptions.py +0 -345
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/LICENSE.txt +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/setup.cfg +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/auth.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/__init__.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/collection.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/types.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/request_options.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/base.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/__init__.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/base.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/collection.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/embedding.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/index.py +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/dependency_links.txt +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/requires.txt +0 -0
- {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vikingdb-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: vikingdb Python SDK
|
|
5
|
-
License: Apache-2.0
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://github.com/volcengine/volc-vikingdb-python-sdk/README.md
|
|
7
7
|
Project-URL: Source, https://github.com/volcengine/volc-vikingdb-python-sdk
|
|
8
8
|
Keywords: vikingdb,vector,bytedance,volcengine,sdk
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.8
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -44,7 +42,7 @@ This package provides an idiomatic Python interface to the VikingDB v2 data-plan
|
|
|
44
42
|
Clone the repository and install the SDK in editable mode:
|
|
45
43
|
|
|
46
44
|
```bash
|
|
47
|
-
|
|
45
|
+
uv add vikingdb-python-sdk
|
|
48
46
|
```
|
|
49
47
|
|
|
50
48
|
> **Dependencies:** The SDK relies on `requests`, `pydantic>=2.5`, and the Volcano Engine base SDK (`volcengine`) for request signing.
|
|
@@ -54,34 +52,24 @@ pip install -e .
|
|
|
54
52
|
#### Vector Database
|
|
55
53
|
|
|
56
54
|
```python
|
|
55
|
+
import os
|
|
57
56
|
from vikingdb import IAM
|
|
58
|
-
from vikingdb.vector import
|
|
57
|
+
from vikingdb.vector import SearchByRandomRequest, VikingVector
|
|
59
58
|
|
|
60
|
-
auth = IAM(ak="
|
|
59
|
+
auth = IAM(ak=os.environ["VIKINGDB_AK"], sk=os.environ["VIKINGDB_SK"])
|
|
61
60
|
client = VikingVector(
|
|
62
|
-
host=
|
|
63
|
-
region="
|
|
61
|
+
host=os.environ["VIKINGDB_HOST"],
|
|
62
|
+
region=os.environ["VIKINGDB_REGION"],
|
|
64
63
|
auth=auth,
|
|
65
64
|
scheme="https",
|
|
66
65
|
timeout=30,
|
|
67
66
|
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
embedding = client.embedding()
|
|
72
|
-
|
|
73
|
-
# Upsert documents into a collection
|
|
74
|
-
upsert_request = UpsertDataRequest(
|
|
75
|
-
data=[
|
|
76
|
-
{"title": "Demo Chapter", "paragraph": 1, "score": 42.0, "text": "hello vikingdb"},
|
|
77
|
-
]
|
|
67
|
+
index = client.index(
|
|
68
|
+
collection_name=os.environ["VIKINGDB_COLLECTION"],
|
|
69
|
+
index_name=os.environ["VIKINGDB_INDEX"],
|
|
78
70
|
)
|
|
79
|
-
|
|
80
|
-
print("request_id
|
|
81
|
-
|
|
82
|
-
# Run a quick search
|
|
83
|
-
search_response = index.search_by_random({"limit": 1})
|
|
84
|
-
print("search hits:", len(search_response.result.data) if search_response.result else 0)
|
|
71
|
+
resp = index.search_by_random(SearchByRandomRequest(limit=1))
|
|
72
|
+
print(f"request_id={resp.request_id} hits={len(resp.result.data or [])}")
|
|
85
73
|
```
|
|
86
74
|
|
|
87
75
|
#### Memory Management
|
|
@@ -128,15 +116,17 @@ print("search results:", result)
|
|
|
128
116
|
|
|
129
117
|
### Example Guides
|
|
130
118
|
|
|
131
|
-
#### Vector Examples
|
|
119
|
+
#### Vector Examples
|
|
132
120
|
|
|
133
|
-
The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`
|
|
121
|
+
The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`1`–`6`). Each test connects to a live VikingDB environment and exercises a specific workflow.
|
|
134
122
|
|
|
135
123
|
1. Set the required environment variables (or create a `.env` file in the project root):
|
|
136
124
|
|
|
137
125
|
```
|
|
138
126
|
VIKINGDB_AK=your-access-key
|
|
139
127
|
VIKINGDB_SK=your-secret-key
|
|
128
|
+
VIKINGDB_COLLECTION=demo_collection
|
|
129
|
+
VIKINGDB_INDEX=demo_index
|
|
140
130
|
# Optional:
|
|
141
131
|
# VIKINGDB_PROJECT=project-name
|
|
142
132
|
# VIKINGDB_RESOURCE_ID=resource-id
|
|
@@ -152,13 +142,13 @@ The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`
|
|
|
152
142
|
2. Install pytest (if not already available):
|
|
153
143
|
|
|
154
144
|
```bash
|
|
155
|
-
|
|
145
|
+
uv add --dev pytest
|
|
156
146
|
```
|
|
157
147
|
|
|
158
148
|
3. Execute the guides:
|
|
159
149
|
|
|
160
150
|
```bash
|
|
161
|
-
pytest examples/vector -k
|
|
151
|
+
uv run pytest examples/vector -k scenario
|
|
162
152
|
```
|
|
163
153
|
|
|
164
154
|
Each scenario writes temporary documents using unique session tags and cleans them up afterwards.
|
|
@@ -223,9 +213,9 @@ vikingdb/
|
|
|
223
213
|
|
|
224
214
|
examples/
|
|
225
215
|
├── vector/ # Vector integration guides (pytest)
|
|
226
|
-
│ ├──
|
|
227
|
-
│ ├──
|
|
228
|
-
│ ├──
|
|
216
|
+
│ ├── 1_connectivity_test.py
|
|
217
|
+
│ ├── 2_collection_lifecycle_test.py
|
|
218
|
+
│ ├── 3_*_test.py # Search and indexing examples
|
|
229
219
|
│ └── ...
|
|
230
220
|
└── memory/ # Memory usage examples
|
|
231
221
|
├── 01_init_client_and_collection.py
|
|
@@ -14,7 +14,7 @@ This package provides an idiomatic Python interface to the VikingDB v2 data-plan
|
|
|
14
14
|
Clone the repository and install the SDK in editable mode:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
uv add vikingdb-python-sdk
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
> **Dependencies:** The SDK relies on `requests`, `pydantic>=2.5`, and the Volcano Engine base SDK (`volcengine`) for request signing.
|
|
@@ -24,34 +24,24 @@ pip install -e .
|
|
|
24
24
|
#### Vector Database
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
|
+
import os
|
|
27
28
|
from vikingdb import IAM
|
|
28
|
-
from vikingdb.vector import
|
|
29
|
+
from vikingdb.vector import SearchByRandomRequest, VikingVector
|
|
29
30
|
|
|
30
|
-
auth = IAM(ak="
|
|
31
|
+
auth = IAM(ak=os.environ["VIKINGDB_AK"], sk=os.environ["VIKINGDB_SK"])
|
|
31
32
|
client = VikingVector(
|
|
32
|
-
host=
|
|
33
|
-
region="
|
|
33
|
+
host=os.environ["VIKINGDB_HOST"],
|
|
34
|
+
region=os.environ["VIKINGDB_REGION"],
|
|
34
35
|
auth=auth,
|
|
35
36
|
scheme="https",
|
|
36
37
|
timeout=30,
|
|
37
38
|
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
embedding = client.embedding()
|
|
42
|
-
|
|
43
|
-
# Upsert documents into a collection
|
|
44
|
-
upsert_request = UpsertDataRequest(
|
|
45
|
-
data=[
|
|
46
|
-
{"title": "Demo Chapter", "paragraph": 1, "score": 42.0, "text": "hello vikingdb"},
|
|
47
|
-
]
|
|
39
|
+
index = client.index(
|
|
40
|
+
collection_name=os.environ["VIKINGDB_COLLECTION"],
|
|
41
|
+
index_name=os.environ["VIKINGDB_INDEX"],
|
|
48
42
|
)
|
|
49
|
-
|
|
50
|
-
print("request_id
|
|
51
|
-
|
|
52
|
-
# Run a quick search
|
|
53
|
-
search_response = index.search_by_random({"limit": 1})
|
|
54
|
-
print("search hits:", len(search_response.result.data) if search_response.result else 0)
|
|
43
|
+
resp = index.search_by_random(SearchByRandomRequest(limit=1))
|
|
44
|
+
print(f"request_id={resp.request_id} hits={len(resp.result.data or [])}")
|
|
55
45
|
```
|
|
56
46
|
|
|
57
47
|
#### Memory Management
|
|
@@ -98,15 +88,17 @@ print("search results:", result)
|
|
|
98
88
|
|
|
99
89
|
### Example Guides
|
|
100
90
|
|
|
101
|
-
#### Vector Examples
|
|
91
|
+
#### Vector Examples
|
|
102
92
|
|
|
103
|
-
The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`
|
|
93
|
+
The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`1`–`6`). Each test connects to a live VikingDB environment and exercises a specific workflow.
|
|
104
94
|
|
|
105
95
|
1. Set the required environment variables (or create a `.env` file in the project root):
|
|
106
96
|
|
|
107
97
|
```
|
|
108
98
|
VIKINGDB_AK=your-access-key
|
|
109
99
|
VIKINGDB_SK=your-secret-key
|
|
100
|
+
VIKINGDB_COLLECTION=demo_collection
|
|
101
|
+
VIKINGDB_INDEX=demo_index
|
|
110
102
|
# Optional:
|
|
111
103
|
# VIKINGDB_PROJECT=project-name
|
|
112
104
|
# VIKINGDB_RESOURCE_ID=resource-id
|
|
@@ -122,13 +114,13 @@ The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`
|
|
|
122
114
|
2. Install pytest (if not already available):
|
|
123
115
|
|
|
124
116
|
```bash
|
|
125
|
-
|
|
117
|
+
uv add --dev pytest
|
|
126
118
|
```
|
|
127
119
|
|
|
128
120
|
3. Execute the guides:
|
|
129
121
|
|
|
130
122
|
```bash
|
|
131
|
-
pytest examples/vector -k
|
|
123
|
+
uv run pytest examples/vector -k scenario
|
|
132
124
|
```
|
|
133
125
|
|
|
134
126
|
Each scenario writes temporary documents using unique session tags and cleans them up afterwards.
|
|
@@ -193,9 +185,9 @@ vikingdb/
|
|
|
193
185
|
|
|
194
186
|
examples/
|
|
195
187
|
├── vector/ # Vector integration guides (pytest)
|
|
196
|
-
│ ├──
|
|
197
|
-
│ ├──
|
|
198
|
-
│ ├──
|
|
188
|
+
│ ├── 1_connectivity_test.py
|
|
189
|
+
│ ├── 2_collection_lifecycle_test.py
|
|
190
|
+
│ ├── 3_*_test.py # Search and indexing examples
|
|
199
191
|
│ └── ...
|
|
200
192
|
└── memory/ # Memory usage examples
|
|
201
193
|
├── 01_init_client_and_collection.py
|
|
@@ -8,14 +8,12 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "vikingdb Python SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
|
-
license =
|
|
11
|
+
license = "Apache-2.0"
|
|
12
12
|
keywords = ["vikingdb", "vector", "bytedance", "volcengine", "sdk"]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 4 - Beta",
|
|
15
15
|
"Intended Audience :: Developers",
|
|
16
|
-
"License :: OSI Approved :: Apache Software License",
|
|
17
16
|
"Programming Language :: Python :: 3",
|
|
18
|
-
"Programming Language :: Python :: 3.7",
|
|
19
17
|
"Programming Language :: Python :: 3.8",
|
|
20
18
|
"Programming Language :: Python :: 3.9",
|
|
21
19
|
"Programming Language :: Python :: 3.10",
|
|
@@ -46,4 +44,5 @@ version = {attr = "vikingdb.version.__version__"}
|
|
|
46
44
|
[dependency-groups]
|
|
47
45
|
dev = [
|
|
48
46
|
"pytest>=7.4.4",
|
|
47
|
+
"ruff>=0.14.3",
|
|
49
48
|
]
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
+
from json import JSONDecodeError
|
|
10
11
|
from typing import Any, Mapping, Optional
|
|
11
12
|
|
|
12
13
|
import aiohttp
|
|
@@ -17,6 +18,13 @@ from volcengine.base.Request import Request
|
|
|
17
18
|
from volcengine.base.Service import Service
|
|
18
19
|
|
|
19
20
|
from .auth import Auth
|
|
21
|
+
from .exceptions import (
|
|
22
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
23
|
+
VikingAPIException,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_REQUEST_ID_HEADER = "X-Tt-Logid"
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
class Client(Service, ABC):
|
|
@@ -73,7 +81,7 @@ class Client(Service, ABC):
|
|
|
73
81
|
def prepare_request(self, api_info: ApiInfo, params: Optional[Mapping[str, Any]], doseq: int = 0):
|
|
74
82
|
"""Prepare a volcengine request without adding implicit headers."""
|
|
75
83
|
request = Request()
|
|
76
|
-
request.
|
|
84
|
+
request.set_schema(self.service_info.scheme)
|
|
77
85
|
request.set_method(api_info.method)
|
|
78
86
|
request.set_host(self.service_info.host)
|
|
79
87
|
request.set_path(api_info.path)
|
|
@@ -113,6 +121,9 @@ class Client(Service, ABC):
|
|
|
113
121
|
self.auth_provider.sign_request(request)
|
|
114
122
|
url = request.build()
|
|
115
123
|
|
|
124
|
+
request_id_value = request.headers.get(_REQUEST_ID_HEADER)
|
|
125
|
+
request_id = str(request_id_value) if request_id_value else "unknown"
|
|
126
|
+
|
|
116
127
|
# Use custom timeout if provided, otherwise use default
|
|
117
128
|
if timeout is not None:
|
|
118
129
|
request_timeout = (timeout, timeout)
|
|
@@ -122,15 +133,49 @@ class Client(Service, ABC):
|
|
|
122
133
|
self.service_info.socket_timeout,
|
|
123
134
|
)
|
|
124
135
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
try:
|
|
137
|
+
response = self.session.post(
|
|
138
|
+
url,
|
|
139
|
+
headers=request.headers,
|
|
140
|
+
data=request.body,
|
|
141
|
+
timeout=request_timeout,
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
raise VikingAPIException(
|
|
145
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
146
|
+
request_id=request_id,
|
|
147
|
+
message=f"failed to run session.post {api}: {exc}",
|
|
148
|
+
) from exc
|
|
149
|
+
|
|
150
|
+
payload_text_attr = getattr(response, "text", "")
|
|
151
|
+
payload_text = payload_text_attr if isinstance(payload_text_attr, str) else ""
|
|
152
|
+
payload_text = payload_text or ""
|
|
153
|
+
|
|
154
|
+
if response.status_code != 200:
|
|
155
|
+
error = VikingAPIException.from_response(
|
|
156
|
+
payload_text,
|
|
157
|
+
request_id=request_id,
|
|
158
|
+
status_code=response.status_code,
|
|
159
|
+
)
|
|
160
|
+
raise error
|
|
161
|
+
|
|
162
|
+
try:
|
|
132
163
|
return response.json()
|
|
133
|
-
|
|
164
|
+
except (ValueError, JSONDecodeError) as exc:
|
|
165
|
+
raise VikingAPIException(
|
|
166
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
167
|
+
request_id=request_id,
|
|
168
|
+
message=f"failed to decode JSON response for {api}: {exc}",
|
|
169
|
+
status_code=response.status_code,
|
|
170
|
+
) from exc
|
|
171
|
+
|
|
172
|
+
except Exception as exc: # pragma: no cover - defensive fallback
|
|
173
|
+
raise VikingAPIException(
|
|
174
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
175
|
+
request_id=request_id,
|
|
176
|
+
message=f"unexpected error parsing response for {api}: {exc}",
|
|
177
|
+
status_code=response.status_code,
|
|
178
|
+
) from exc
|
|
134
179
|
|
|
135
180
|
async def async_json(
|
|
136
181
|
self,
|
|
@@ -175,14 +220,36 @@ class Client(Service, ABC):
|
|
|
175
220
|
)
|
|
176
221
|
|
|
177
222
|
url = request.build()
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
223
|
+
try:
|
|
224
|
+
async with aiohttp.request(
|
|
225
|
+
"POST",
|
|
226
|
+
url,
|
|
227
|
+
headers=request.headers,
|
|
228
|
+
data=request.body,
|
|
229
|
+
timeout=client_timeout,
|
|
230
|
+
) as response:
|
|
231
|
+
request_id_value = response.headers.get(_REQUEST_ID_HEADER)
|
|
232
|
+
request_id = str(request_id_value) if request_id_value else "unknown"
|
|
233
|
+
payload = await response.text(encoding="utf-8")
|
|
234
|
+
if response.status != 200:
|
|
235
|
+
error = VikingAPIException.from_response(
|
|
236
|
+
payload,
|
|
237
|
+
request_id=request_id,
|
|
238
|
+
status_code=response.status,
|
|
239
|
+
)
|
|
240
|
+
raise error
|
|
241
|
+
try:
|
|
242
|
+
return json.loads(payload)
|
|
243
|
+
except JSONDecodeError as exc:
|
|
244
|
+
raise VikingAPIException(
|
|
245
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
246
|
+
request_id=request_id,
|
|
247
|
+
message=f"failed to decode JSON response for {api}: {exc}",
|
|
248
|
+
status_code=response.status,
|
|
249
|
+
) from exc
|
|
250
|
+
except Exception as exc:
|
|
251
|
+
raise VikingAPIException(
|
|
252
|
+
DEFAULT_UNKNOWN_ERROR_CODE,
|
|
253
|
+
request_id=request_id,
|
|
254
|
+
message=f"failed to run aiohttp {api}: {exc}",
|
|
255
|
+
) from exc
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Mapping, Optional, Type, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
DEFAULT_UNKNOWN_ERROR_CODE: Union[int, str] = 1000028
|
|
8
|
+
NETWORK_ERROR_CODE = 1001
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ParsedError:
|
|
13
|
+
code: Union[int, str]
|
|
14
|
+
request_id: str
|
|
15
|
+
message: Optional[str]
|
|
16
|
+
payload: Any
|
|
17
|
+
raw: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _normalize_code(value: Any, default: Union[int, str]) -> Union[int, str]:
|
|
21
|
+
if value is None:
|
|
22
|
+
return default
|
|
23
|
+
if isinstance(value, int):
|
|
24
|
+
return value
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
stripped = value.strip()
|
|
27
|
+
return stripped or default
|
|
28
|
+
try:
|
|
29
|
+
return int(value)
|
|
30
|
+
except (TypeError, ValueError):
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_error_payload(payload: Any) -> ParsedError:
|
|
35
|
+
"""
|
|
36
|
+
Parse a server error payload into a consistent structure.
|
|
37
|
+
|
|
38
|
+
Supports both the legacy {"ResponseMetadata": {"Error": {...}}} format and the
|
|
39
|
+
newer flat {"code": ..., "request_id": ..., "message": ...} shape. When JSON
|
|
40
|
+
decoding fails, the raw text is preserved as the message.
|
|
41
|
+
"""
|
|
42
|
+
code = DEFAULT_UNKNOWN_ERROR_CODE
|
|
43
|
+
request_id = "unknown"
|
|
44
|
+
message: Optional[str] = None
|
|
45
|
+
raw_text: Optional[str] = None
|
|
46
|
+
parsed_payload: Any = payload
|
|
47
|
+
|
|
48
|
+
if isinstance(payload, bytes):
|
|
49
|
+
raw_text = payload.decode("utf-8", errors="replace")
|
|
50
|
+
elif isinstance(payload, str):
|
|
51
|
+
raw_text = payload
|
|
52
|
+
|
|
53
|
+
if raw_text is not None:
|
|
54
|
+
try:
|
|
55
|
+
parsed_payload = json.loads(raw_text)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
message = raw_text.strip() or None
|
|
58
|
+
return ParsedError(code, request_id, message, raw_text, raw_text)
|
|
59
|
+
|
|
60
|
+
if isinstance(parsed_payload, Mapping):
|
|
61
|
+
metadata = parsed_payload.get("ResponseMetadata")
|
|
62
|
+
if isinstance(metadata, Mapping):
|
|
63
|
+
error = metadata.get("Error")
|
|
64
|
+
if isinstance(error, Mapping):
|
|
65
|
+
code_value = error.get("Code") if "Code" in error else error.get("code")
|
|
66
|
+
code = _normalize_code(code_value, code)
|
|
67
|
+
request_id = str(error.get("RequestId") or request_id)
|
|
68
|
+
if error.get("Message"):
|
|
69
|
+
message = str(error["Message"])
|
|
70
|
+
if metadata.get("RequestId"):
|
|
71
|
+
request_id = str(metadata["RequestId"])
|
|
72
|
+
else:
|
|
73
|
+
code_value = parsed_payload.get("code")
|
|
74
|
+
if code_value is None and "Code" in parsed_payload:
|
|
75
|
+
code_value = parsed_payload.get("Code")
|
|
76
|
+
code = _normalize_code(code_value, code)
|
|
77
|
+
if parsed_payload.get("request_id"):
|
|
78
|
+
request_id = str(parsed_payload["request_id"])
|
|
79
|
+
if parsed_payload.get("message"):
|
|
80
|
+
message = str(parsed_payload["message"])
|
|
81
|
+
else:
|
|
82
|
+
if message is None and raw_text is None:
|
|
83
|
+
message = str(parsed_payload)
|
|
84
|
+
|
|
85
|
+
if raw_text is None and isinstance(parsed_payload, (dict, list)):
|
|
86
|
+
try:
|
|
87
|
+
raw_text = json.dumps(parsed_payload, ensure_ascii=False)
|
|
88
|
+
except Exception:
|
|
89
|
+
raw_text = None
|
|
90
|
+
|
|
91
|
+
return ParsedError(code, request_id, message, parsed_payload, raw_text)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
T_VikingException = TypeVar("T_VikingException", bound="VikingException")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class VikingException(Exception):
|
|
98
|
+
"""
|
|
99
|
+
Base exception for all Viking SDK errors.
|
|
100
|
+
|
|
101
|
+
Captures standard fields returned by the service for consistent error handling.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
code: Union[int, str],
|
|
107
|
+
request_id: str = "unknown",
|
|
108
|
+
message: Optional[str] = None,
|
|
109
|
+
*,
|
|
110
|
+
status_code: Optional[int] = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
self.code = code
|
|
113
|
+
self.request_id = request_id or "unknown"
|
|
114
|
+
self.status_code = status_code
|
|
115
|
+
self.message = message or f"request failed (code={self.code})"
|
|
116
|
+
super().__init__(self.message)
|
|
117
|
+
|
|
118
|
+
def __str__(self) -> str:
|
|
119
|
+
status = f", http_status={self.status_code}" if self.status_code is not None else ""
|
|
120
|
+
return f"{self.message} (code={self.code}, request_id={self.request_id}{status})"
|
|
121
|
+
|
|
122
|
+
def promote(self, target_cls: Type[T_VikingException]) -> T_VikingException:
|
|
123
|
+
"""
|
|
124
|
+
Promote this exception to a more specific subclass, reusing captured context.
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(self, target_cls):
|
|
127
|
+
return self # type: ignore[return-value]
|
|
128
|
+
return target_cls(
|
|
129
|
+
self.code,
|
|
130
|
+
self.request_id,
|
|
131
|
+
self.message,
|
|
132
|
+
status_code=self.status_code,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class VikingAPIException(VikingException):
|
|
137
|
+
"""Raised when the remote API returns an error payload."""
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_response(cls, payload: Any, *, request_id: Optional[str] = "unknown", status_code: Optional[int] = None) -> "VikingAPIException":
|
|
141
|
+
parsed = parse_error_payload(payload)
|
|
142
|
+
return cls(
|
|
143
|
+
parsed.code,
|
|
144
|
+
parsed.request_id or request_id,
|
|
145
|
+
parsed.message or "unknown api error",
|
|
146
|
+
status_code=status_code,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def promote_exception(
|
|
151
|
+
exc: VikingException,
|
|
152
|
+
*,
|
|
153
|
+
exception_map: Optional[Mapping[int, Type[T_VikingException]]] = None,
|
|
154
|
+
default_cls: Type[T_VikingException],
|
|
155
|
+
) -> T_VikingException:
|
|
156
|
+
"""
|
|
157
|
+
Promote a VikingException to a service-specific subclass using the provided map.
|
|
158
|
+
"""
|
|
159
|
+
target_cls = (exception_map or {}).get(exc.code, default_cls)
|
|
160
|
+
return exc.promote(target_cls)
|
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import json
|
|
10
|
-
|
|
11
9
|
from volcengine.ApiInfo import ApiInfo
|
|
12
10
|
|
|
13
11
|
from .._client import Client
|
|
14
12
|
from ..auth import Auth
|
|
13
|
+
from ..exceptions import VikingException, promote_exception
|
|
15
14
|
from .collection import Collection
|
|
16
15
|
from .exceptions import EXCEPTION_MAP, VikingMemException
|
|
17
16
|
from ..version import __version__
|
|
@@ -193,33 +192,12 @@ class VikingMem(Client):
|
|
|
193
192
|
"""
|
|
194
193
|
try:
|
|
195
194
|
res = self._json(api, params, body, headers=headers, timeout=timeout)
|
|
196
|
-
except
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
res_json = json.loads(err_msg)
|
|
204
|
-
except:
|
|
205
|
-
raise VikingMemException(
|
|
206
|
-
1000028, "missed", "json load res error, res:{}".format(str(e))
|
|
207
|
-
) from None
|
|
208
|
-
if "ResponseMetadata" in res_json:
|
|
209
|
-
error = res_json["ResponseMetadata"].get("Error", {})
|
|
210
|
-
code = error.get("Code", 1000028)
|
|
211
|
-
request_id = error.get("RequestId", "unknown")
|
|
212
|
-
message = error.get("Message", None)
|
|
213
|
-
raise EXCEPTION_MAP.get(code, VikingMemException)(
|
|
214
|
-
code, request_id, message
|
|
215
|
-
) from None
|
|
216
|
-
else:
|
|
217
|
-
code = res_json.get("code", 1000028)
|
|
218
|
-
request_id = res_json.get("request_id", "unknown")
|
|
219
|
-
message = res_json.get("message", None)
|
|
220
|
-
raise VikingMemException(
|
|
221
|
-
code, request_id, message
|
|
222
|
-
) from None
|
|
195
|
+
except VikingException as exc:
|
|
196
|
+
raise promote_exception(
|
|
197
|
+
exc,
|
|
198
|
+
exception_map=EXCEPTION_MAP,
|
|
199
|
+
default_cls=VikingMemException,
|
|
200
|
+
) from None
|
|
223
201
|
if res is None:
|
|
224
202
|
raise VikingMemException(
|
|
225
203
|
1000028,
|
|
@@ -241,33 +219,12 @@ class VikingMem(Client):
|
|
|
241
219
|
"""
|
|
242
220
|
try:
|
|
243
221
|
res = await self.async_json(api, params, body, headers=headers, timeout=timeout)
|
|
244
|
-
except
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
251
|
-
res_json = json.loads(err_msg)
|
|
252
|
-
except:
|
|
253
|
-
raise VikingMemException(
|
|
254
|
-
1000028, "missed", "json load res error, res:{}".format(str(e))
|
|
255
|
-
) from None
|
|
256
|
-
if "ResponseMetadata" in res_json:
|
|
257
|
-
error = res_json["ResponseMetadata"].get("Error", {})
|
|
258
|
-
code = error.get("Code", 1000028)
|
|
259
|
-
request_id = error.get("RequestId", "unknown")
|
|
260
|
-
message = error.get("Message", None)
|
|
261
|
-
raise EXCEPTION_MAP.get(code, VikingMemException)(
|
|
262
|
-
code, request_id, message
|
|
263
|
-
) from None
|
|
264
|
-
else:
|
|
265
|
-
code = res_json.get("code", 1000028)
|
|
266
|
-
request_id = res_json.get("request_id", "unknown")
|
|
267
|
-
message = res_json.get("message", None)
|
|
268
|
-
raise VikingMemException(
|
|
269
|
-
code, request_id, message
|
|
270
|
-
) from None
|
|
222
|
+
except VikingException as exc:
|
|
223
|
+
raise promote_exception(
|
|
224
|
+
exc,
|
|
225
|
+
exception_map=EXCEPTION_MAP,
|
|
226
|
+
default_cls=VikingMemException,
|
|
227
|
+
) from None
|
|
271
228
|
if res is None:
|
|
272
229
|
raise VikingMemException(
|
|
273
230
|
1000028,
|