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.
Files changed (34) hide show
  1. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/PKG-INFO +22 -32
  2. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/README.md +20 -28
  3. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/pyproject.toml +2 -3
  4. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/__init__.py +2 -0
  5. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/_client.py +87 -20
  6. vikingdb_python_sdk-0.1.2/vikingdb/exceptions.py +160 -0
  7. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/client.py +13 -56
  8. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/exceptions.py +19 -13
  9. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/__init__.py +1 -3
  10. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/client.py +41 -35
  11. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/collection.py +0 -1
  12. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/embedding.py +1 -1
  13. vikingdb_python_sdk-0.1.2/vikingdb/vector/exceptions.py +40 -0
  14. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/index.py +0 -1
  15. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/version.py +1 -1
  16. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/PKG-INFO +22 -32
  17. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/SOURCES.txt +1 -0
  18. vikingdb_python_sdk-0.1.0/vikingdb/vector/exceptions.py +0 -345
  19. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/LICENSE.txt +0 -0
  20. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/setup.cfg +0 -0
  21. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/auth.py +0 -0
  22. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/__init__.py +0 -0
  23. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/collection.py +0 -0
  24. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/memory/types.py +0 -0
  25. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/request_options.py +0 -0
  26. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/base.py +0 -0
  27. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/__init__.py +0 -0
  28. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/base.py +0 -0
  29. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/collection.py +0 -0
  30. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/embedding.py +0 -0
  31. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb/vector/models/index.py +0 -0
  32. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/dependency_links.txt +0 -0
  33. {vikingdb_python_sdk-0.1.0 → vikingdb_python_sdk-0.1.2}/vikingdb_python_sdk.egg-info/requires.txt +0 -0
  34. {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.0
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
- pip install -e .
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 UpsertDataRequest, VikingVector
57
+ from vikingdb.vector import SearchByRandomRequest, VikingVector
59
58
 
60
- auth = IAM(ak="<AK>", sk="<SK>")
59
+ auth = IAM(ak=os.environ["VIKINGDB_AK"], sk=os.environ["VIKINGDB_SK"])
61
60
  client = VikingVector(
62
- host="api.vector.bytedance.com",
63
- region="cn-beijing",
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
- collection = client.collection(collection_name="demo_collection")
70
- index = client.index(collection_name="demo_collection", index_name="demo_index")
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
- response = collection.upsert(upsert_request)
80
- print("request_id:", response.request_id, "result:", response.result)
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 (Pytest)
119
+ #### Vector Examples
132
120
 
133
- The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`E1`–`E5`). Each test connects to a live VikingDB environment and exercises a specific workflow.
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
- pip install pytest
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 guide
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
- │ ├── E1_connectivity_test.py
227
- │ ├── E2_collection_lifecycle_test.py
228
- │ ├── E3_*_test.py # Search and indexing examples
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
- pip install -e .
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 UpsertDataRequest, VikingVector
29
+ from vikingdb.vector import SearchByRandomRequest, VikingVector
29
30
 
30
- auth = IAM(ak="<AK>", sk="<SK>")
31
+ auth = IAM(ak=os.environ["VIKINGDB_AK"], sk=os.environ["VIKINGDB_SK"])
31
32
  client = VikingVector(
32
- host="api.vector.bytedance.com",
33
- region="cn-beijing",
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
- collection = client.collection(collection_name="demo_collection")
40
- index = client.index(collection_name="demo_collection", index_name="demo_index")
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
- response = collection.upsert(upsert_request)
50
- print("request_id:", response.request_id, "result:", response.result)
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 (Pytest)
91
+ #### Vector Examples
102
92
 
103
- The integration guides under `examples/vector` mirror the Go SDK walkthroughs (`E1`–`E5`). Each test connects to a live VikingDB environment and exercises a specific workflow.
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
- pip install pytest
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 guide
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
- │ ├── E1_connectivity_test.py
197
- │ ├── E2_collection_lifecycle_test.py
198
- │ ├── E3_*_test.py # Search and indexing examples
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 = {text = "Apache-2.0"}
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
  ]
@@ -17,6 +17,7 @@ from .memory import (
17
17
  VikingMem,
18
18
  Collection,
19
19
  )
20
+ from .version import __version__
20
21
  __all__ = [
21
22
  "IAM",
22
23
  "APIKey",
@@ -29,4 +30,5 @@ __all__ = [
29
30
  "memory",
30
31
  "VikingMem",
31
32
  "Collection",
33
+ "__version__",
32
34
  ]
@@ -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.set_shema(self.service_info.scheme)
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
- response = self.session.post(
126
- url,
127
- headers=request.headers,
128
- data=request.body,
129
- timeout=request_timeout,
130
- )
131
- if response.status_code == 200:
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
- raise Exception(response.text.encode("utf-8"))
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
- async with aiohttp.request(
179
- "POST",
180
- url,
181
- headers=request.headers,
182
- data=request.body,
183
- timeout=client_timeout,
184
- ) as response:
185
- payload = await response.text(encoding="utf-8")
186
- if response.status == 200:
187
- return json.loads(payload)
188
- raise Exception(payload)
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 Exception as e:
197
- try:
198
- err_msg = (
199
- e.args[0].decode("utf-8")
200
- if isinstance(e.args[0], bytes)
201
- else str(e.args[0])
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 Exception as e:
245
- try:
246
- err_msg = (
247
- e.args[0].decode("utf-8")
248
- if isinstance(e.args[0], bytes)
249
- else str(e.args[0])
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,