uapi-sdk-python 0.1.10__tar.gz → 0.1.14__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.
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/PKG-INFO +65 -24
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/README.md +64 -23
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/pyproject.toml +1 -1
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi/client.py +33 -88
- uapi_sdk_python-0.1.14/uapi/errors.py +374 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/PKG-INFO +65 -24
- uapi_sdk_python-0.1.10/uapi/errors.py +0 -153
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/setup.cfg +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/tests/test_client.py +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi/__init__.py +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/SOURCES.txt +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/dependency_links.txt +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/requires.txt +0 -0
- {uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uapi-sdk-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Idiomatic UAPI SDK for Python
|
|
5
5
|
Author-email: UAPI <dev@uapis.cn>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -32,11 +32,13 @@ pip install uapi-sdk-python
|
|
|
32
32
|
```python
|
|
33
33
|
from uapi import UapiClient
|
|
34
34
|
|
|
35
|
-
client = UapiClient("https://uapis.cn
|
|
36
|
-
result = client.
|
|
35
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
36
|
+
result = client.misc.get_misc_hotboard(type="weibo")
|
|
37
37
|
print(result)
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
这个接口默认只要传 `type` 就可以拿当前热榜。`time`、`keyword`、`time_start`、`time_end`、`limit`、`sources` 都是按场景再传的可选参数。
|
|
41
|
+
|
|
40
42
|
> [!TIP]
|
|
41
43
|
> 请使用与运行脚本相同的 Python 解释器安装依赖,例如执行 `python -m pip install uapi-sdk-python` 后再运行 `python main.py`。在 VS Code / Pyright 中若提示 “Import uapi could not be resolved”,将解释器切换到当前虚拟环境即可恢复补全。
|
|
42
44
|
|
|
@@ -54,6 +56,60 @@ print(result)
|
|
|
54
56
|
|
|
55
57
|
如果你需要查看字段细节或内部逻辑,仓库中的 `./internal` 目录同步保留了由 `openapi-generator` 生成的完整结构体,随时可供参考。
|
|
56
58
|
|
|
59
|
+
## 响应元信息
|
|
60
|
+
|
|
61
|
+
每次请求完成后,SDK 会自动把响应 Header 解析成结构化的 `ResponseMeta`,你不用自己拆原始字符串。
|
|
62
|
+
|
|
63
|
+
成功时可以通过 `client.last_response_meta` 读取,失败时可以通过 `err.meta` 读取,两条路径拿到的是同一套字段。
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from uapi import UapiClient, UapiError
|
|
67
|
+
|
|
68
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
69
|
+
|
|
70
|
+
# 成功路径
|
|
71
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
72
|
+
meta = client.last_response_meta
|
|
73
|
+
if meta:
|
|
74
|
+
print("这次请求原价:", meta.credits_requested or 0, "积分")
|
|
75
|
+
print("这次实际扣费:", meta.credits_charged or 0, "积分")
|
|
76
|
+
print("特殊计价:", meta.credits_pricing or "原价")
|
|
77
|
+
print("余额剩余:", meta.balance_remaining_cents or 0, "分")
|
|
78
|
+
print("资源包剩余:", meta.quota_remaining_credits or 0, "积分")
|
|
79
|
+
print("当前有效额度桶:", meta.active_quota_buckets or 0)
|
|
80
|
+
print("额度用空即停:", meta.stop_on_empty)
|
|
81
|
+
print("Key QPS:", meta.billing_key_rate_remaining or 0, "/", meta.billing_key_rate_limit or 0, meta.billing_key_rate_unit or "req")
|
|
82
|
+
print("Request ID:", meta.request_id)
|
|
83
|
+
|
|
84
|
+
# 失败路径
|
|
85
|
+
try:
|
|
86
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
87
|
+
except UapiError as err:
|
|
88
|
+
if err.meta:
|
|
89
|
+
print("Retry-After 秒数:", err.meta.retry_after_seconds)
|
|
90
|
+
print("Retry-After 原始值:", err.meta.retry_after_raw)
|
|
91
|
+
print("访客 QPS:", err.meta.visitor_rate_remaining or 0, "/", err.meta.visitor_rate_limit or 0)
|
|
92
|
+
print("Request ID:", err.meta.request_id)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
常用字段一览:
|
|
96
|
+
|
|
97
|
+
| 字段 | 说明 |
|
|
98
|
+
|------|------|
|
|
99
|
+
| `credits_requested` | 这次请求原本要扣多少积分,也就是请求价 |
|
|
100
|
+
| `credits_charged` | 这次请求实际扣了多少积分 |
|
|
101
|
+
| `credits_pricing` | 特殊计价原因,例如缓存半价 `cache-hit-half-price` |
|
|
102
|
+
| `balance_remaining_cents` | 账户余额剩余(分) |
|
|
103
|
+
| `quota_remaining_credits` | 资源包剩余积分 |
|
|
104
|
+
| `active_quota_buckets` | 当前还有多少个有效额度桶参与计费 |
|
|
105
|
+
| `stop_on_empty` | 额度耗尽后是否直接停止服务 |
|
|
106
|
+
| `retry_after_seconds` / `retry_after_raw` | 限流后的等待时长;当服务端返回 HTTP 时间字符串时看 `retry_after_raw` |
|
|
107
|
+
| `request_id` | 请求唯一 ID,排障时使用 |
|
|
108
|
+
| `billing_key_rate_limit` / `billing_key_rate_remaining` | Billing Key 当前 QPS 规则的上限与剩余 |
|
|
109
|
+
| `billing_ip_rate_limit` / `billing_ip_rate_remaining` | Billing Key 单 IP 当前 QPS 规则的上限与剩余 |
|
|
110
|
+
| `visitor_rate_limit` / `visitor_rate_remaining` | 访客当前 QPS 规则的上限与剩余 |
|
|
111
|
+
| `rate_limit_policies` / `rate_limits` | 完整结构化限流策略数据 |
|
|
112
|
+
|
|
57
113
|
## 进阶实践
|
|
58
114
|
|
|
59
115
|
### 缓存与幂等
|
|
@@ -62,7 +118,7 @@ print(result)
|
|
|
62
118
|
from functools import lru_cache
|
|
63
119
|
from uapi import UapiClient
|
|
64
120
|
|
|
65
|
-
client = UapiClient("https://uapis.cn
|
|
121
|
+
client = UapiClient("https://uapis.cn", token="YOUR_API_KEY")
|
|
66
122
|
|
|
67
123
|
@lru_cache(maxsize=128)
|
|
68
124
|
def cached_lookup(qq: str):
|
|
@@ -73,34 +129,19 @@ user = cached_lookup("10001")
|
|
|
73
129
|
|
|
74
130
|
也可以在 FastAPI / Django 项目里配合 Redis,将 SDK 的响应序列化后写入缓存,命中即直接返回。
|
|
75
131
|
|
|
76
|
-
###
|
|
132
|
+
### 调整超时与环境
|
|
77
133
|
|
|
78
134
|
```python
|
|
79
|
-
import httpx
|
|
80
|
-
from httpx import Auth
|
|
81
135
|
from uapi import UapiClient
|
|
82
136
|
|
|
83
|
-
class StaticToken(Auth):
|
|
84
|
-
def __init__(self, token: str):
|
|
85
|
-
self.token = token
|
|
86
|
-
def auth_flow(self, request):
|
|
87
|
-
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
88
|
-
yield request
|
|
89
|
-
|
|
90
|
-
http_client = httpx.Client(
|
|
91
|
-
timeout=5,
|
|
92
|
-
transport=httpx.HTTPTransport(retries=3),
|
|
93
|
-
event_hooks={"request": [lambda request: print("->", request.url)]},
|
|
94
|
-
)
|
|
95
|
-
|
|
96
137
|
client = UapiClient(
|
|
97
|
-
"https://uapis.cn
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
"https://uapis.cn",
|
|
139
|
+
token="YOUR_API_KEY",
|
|
140
|
+
timeout=5.0,
|
|
100
141
|
)
|
|
101
142
|
```
|
|
102
143
|
|
|
103
|
-
|
|
144
|
+
如果你需要切换到别的环境,直接改 `base_url` 就可以;如果你只想缩短等待时间,传 `timeout` 就够了。
|
|
104
145
|
|
|
105
146
|
## 错误模型概览
|
|
106
147
|
|
|
@@ -18,11 +18,13 @@ pip install uapi-sdk-python
|
|
|
18
18
|
```python
|
|
19
19
|
from uapi import UapiClient
|
|
20
20
|
|
|
21
|
-
client = UapiClient("https://uapis.cn
|
|
22
|
-
result = client.
|
|
21
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
22
|
+
result = client.misc.get_misc_hotboard(type="weibo")
|
|
23
23
|
print(result)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
这个接口默认只要传 `type` 就可以拿当前热榜。`time`、`keyword`、`time_start`、`time_end`、`limit`、`sources` 都是按场景再传的可选参数。
|
|
27
|
+
|
|
26
28
|
> [!TIP]
|
|
27
29
|
> 请使用与运行脚本相同的 Python 解释器安装依赖,例如执行 `python -m pip install uapi-sdk-python` 后再运行 `python main.py`。在 VS Code / Pyright 中若提示 “Import uapi could not be resolved”,将解释器切换到当前虚拟环境即可恢复补全。
|
|
28
30
|
|
|
@@ -40,6 +42,60 @@ print(result)
|
|
|
40
42
|
|
|
41
43
|
如果你需要查看字段细节或内部逻辑,仓库中的 `./internal` 目录同步保留了由 `openapi-generator` 生成的完整结构体,随时可供参考。
|
|
42
44
|
|
|
45
|
+
## 响应元信息
|
|
46
|
+
|
|
47
|
+
每次请求完成后,SDK 会自动把响应 Header 解析成结构化的 `ResponseMeta`,你不用自己拆原始字符串。
|
|
48
|
+
|
|
49
|
+
成功时可以通过 `client.last_response_meta` 读取,失败时可以通过 `err.meta` 读取,两条路径拿到的是同一套字段。
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from uapi import UapiClient, UapiError
|
|
53
|
+
|
|
54
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
55
|
+
|
|
56
|
+
# 成功路径
|
|
57
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
58
|
+
meta = client.last_response_meta
|
|
59
|
+
if meta:
|
|
60
|
+
print("这次请求原价:", meta.credits_requested or 0, "积分")
|
|
61
|
+
print("这次实际扣费:", meta.credits_charged or 0, "积分")
|
|
62
|
+
print("特殊计价:", meta.credits_pricing or "原价")
|
|
63
|
+
print("余额剩余:", meta.balance_remaining_cents or 0, "分")
|
|
64
|
+
print("资源包剩余:", meta.quota_remaining_credits or 0, "积分")
|
|
65
|
+
print("当前有效额度桶:", meta.active_quota_buckets or 0)
|
|
66
|
+
print("额度用空即停:", meta.stop_on_empty)
|
|
67
|
+
print("Key QPS:", meta.billing_key_rate_remaining or 0, "/", meta.billing_key_rate_limit or 0, meta.billing_key_rate_unit or "req")
|
|
68
|
+
print("Request ID:", meta.request_id)
|
|
69
|
+
|
|
70
|
+
# 失败路径
|
|
71
|
+
try:
|
|
72
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
73
|
+
except UapiError as err:
|
|
74
|
+
if err.meta:
|
|
75
|
+
print("Retry-After 秒数:", err.meta.retry_after_seconds)
|
|
76
|
+
print("Retry-After 原始值:", err.meta.retry_after_raw)
|
|
77
|
+
print("访客 QPS:", err.meta.visitor_rate_remaining or 0, "/", err.meta.visitor_rate_limit or 0)
|
|
78
|
+
print("Request ID:", err.meta.request_id)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
常用字段一览:
|
|
82
|
+
|
|
83
|
+
| 字段 | 说明 |
|
|
84
|
+
|------|------|
|
|
85
|
+
| `credits_requested` | 这次请求原本要扣多少积分,也就是请求价 |
|
|
86
|
+
| `credits_charged` | 这次请求实际扣了多少积分 |
|
|
87
|
+
| `credits_pricing` | 特殊计价原因,例如缓存半价 `cache-hit-half-price` |
|
|
88
|
+
| `balance_remaining_cents` | 账户余额剩余(分) |
|
|
89
|
+
| `quota_remaining_credits` | 资源包剩余积分 |
|
|
90
|
+
| `active_quota_buckets` | 当前还有多少个有效额度桶参与计费 |
|
|
91
|
+
| `stop_on_empty` | 额度耗尽后是否直接停止服务 |
|
|
92
|
+
| `retry_after_seconds` / `retry_after_raw` | 限流后的等待时长;当服务端返回 HTTP 时间字符串时看 `retry_after_raw` |
|
|
93
|
+
| `request_id` | 请求唯一 ID,排障时使用 |
|
|
94
|
+
| `billing_key_rate_limit` / `billing_key_rate_remaining` | Billing Key 当前 QPS 规则的上限与剩余 |
|
|
95
|
+
| `billing_ip_rate_limit` / `billing_ip_rate_remaining` | Billing Key 单 IP 当前 QPS 规则的上限与剩余 |
|
|
96
|
+
| `visitor_rate_limit` / `visitor_rate_remaining` | 访客当前 QPS 规则的上限与剩余 |
|
|
97
|
+
| `rate_limit_policies` / `rate_limits` | 完整结构化限流策略数据 |
|
|
98
|
+
|
|
43
99
|
## 进阶实践
|
|
44
100
|
|
|
45
101
|
### 缓存与幂等
|
|
@@ -48,7 +104,7 @@ print(result)
|
|
|
48
104
|
from functools import lru_cache
|
|
49
105
|
from uapi import UapiClient
|
|
50
106
|
|
|
51
|
-
client = UapiClient("https://uapis.cn
|
|
107
|
+
client = UapiClient("https://uapis.cn", token="YOUR_API_KEY")
|
|
52
108
|
|
|
53
109
|
@lru_cache(maxsize=128)
|
|
54
110
|
def cached_lookup(qq: str):
|
|
@@ -59,34 +115,19 @@ user = cached_lookup("10001")
|
|
|
59
115
|
|
|
60
116
|
也可以在 FastAPI / Django 项目里配合 Redis,将 SDK 的响应序列化后写入缓存,命中即直接返回。
|
|
61
117
|
|
|
62
|
-
###
|
|
118
|
+
### 调整超时与环境
|
|
63
119
|
|
|
64
120
|
```python
|
|
65
|
-
import httpx
|
|
66
|
-
from httpx import Auth
|
|
67
121
|
from uapi import UapiClient
|
|
68
122
|
|
|
69
|
-
class StaticToken(Auth):
|
|
70
|
-
def __init__(self, token: str):
|
|
71
|
-
self.token = token
|
|
72
|
-
def auth_flow(self, request):
|
|
73
|
-
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
74
|
-
yield request
|
|
75
|
-
|
|
76
|
-
http_client = httpx.Client(
|
|
77
|
-
timeout=5,
|
|
78
|
-
transport=httpx.HTTPTransport(retries=3),
|
|
79
|
-
event_hooks={"request": [lambda request: print("->", request.url)]},
|
|
80
|
-
)
|
|
81
|
-
|
|
82
123
|
client = UapiClient(
|
|
83
|
-
"https://uapis.cn
|
|
84
|
-
|
|
85
|
-
|
|
124
|
+
"https://uapis.cn",
|
|
125
|
+
token="YOUR_API_KEY",
|
|
126
|
+
timeout=5.0,
|
|
86
127
|
)
|
|
87
128
|
```
|
|
88
129
|
|
|
89
|
-
|
|
130
|
+
如果你需要切换到别的环境,直接改 `base_url` 就可以;如果你只想缩短等待时间,传 `timeout` 就够了。
|
|
90
131
|
|
|
91
132
|
## 错误模型概览
|
|
92
133
|
|
|
@@ -12,10 +12,18 @@ class _Config:
|
|
|
12
12
|
token: Optional[str] = None
|
|
13
13
|
timeout: float = 15.0
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
def _normalize_base_url(base_url: str) -> str:
|
|
17
|
+
normalized = base_url.rstrip("/")
|
|
18
|
+
if normalized.endswith("/api/v1"):
|
|
19
|
+
normalized = normalized[: -len("/api/v1")]
|
|
20
|
+
return normalized
|
|
21
|
+
|
|
15
22
|
class _HTTP:
|
|
16
23
|
def __init__(self, cfg: _Config):
|
|
17
24
|
self._cfg = cfg
|
|
18
|
-
self._client = httpx.Client(timeout=cfg.timeout)
|
|
25
|
+
self._client = httpx.Client(timeout=cfg.timeout, trust_env=False)
|
|
26
|
+
self.last_response_meta: Optional[ResponseMeta] = None
|
|
19
27
|
|
|
20
28
|
def request(self, method: str, path: str, *, params: Dict[str, Any] | None = None, json: Any | None = None, headers: Dict[str, str] | None = None):
|
|
21
29
|
url = self._cfg.base_url.rstrip("/") + path
|
|
@@ -24,7 +32,10 @@ class _HTTP:
|
|
|
24
32
|
headers["Authorization"] = f"Bearer {self._cfg.token}"
|
|
25
33
|
r = self._client.request(method, url, params=params, json=json, headers=headers)
|
|
26
34
|
if r.status_code >= 400:
|
|
27
|
-
|
|
35
|
+
err = map_error(r)
|
|
36
|
+
self.last_response_meta = err.meta
|
|
37
|
+
raise err
|
|
38
|
+
self.last_response_meta = extract_meta(r.headers)
|
|
28
39
|
# try json else bytes
|
|
29
40
|
try:
|
|
30
41
|
return r.json()
|
|
@@ -39,7 +50,7 @@ class UapiClient:
|
|
|
39
50
|
"""
|
|
40
51
|
|
|
41
52
|
def __init__(self, base_url: str, token: str | None = None, timeout: float = 15.0):
|
|
42
|
-
self._http = _HTTP(_Config(base_url, token, timeout))
|
|
53
|
+
self._http = _HTTP(_Config(_normalize_base_url(base_url), token, timeout))
|
|
43
54
|
# 动态挂载每个 Tag 的 API 门面
|
|
44
55
|
_clipzy_zai_xian_jian_tie_ban = _ClipzyZaiXianJianTieBanApi(self._http)
|
|
45
56
|
self.clipzy_zai_xian_jian_tie_ban = _clipzy_zai_xian_jian_tie_ban
|
|
@@ -90,6 +101,10 @@ class UapiClient:
|
|
|
90
101
|
self.zhi_neng_sou_suo = _zhi_neng_sou_suo
|
|
91
102
|
setattr(self, "智能搜索", _zhi_neng_sou_suo)
|
|
92
103
|
|
|
104
|
+
@property
|
|
105
|
+
def last_response_meta(self) -> Optional[ResponseMeta]:
|
|
106
|
+
return self._http.last_response_meta
|
|
107
|
+
|
|
93
108
|
|
|
94
109
|
class _ClipzyZaiXianJianTieBanApi:
|
|
95
110
|
def __init__(self, http: _HTTP):
|
|
@@ -428,12 +443,7 @@ class _ImageApi:
|
|
|
428
443
|
这个接口会获取 Bing 搜索引擎当天全球同步的每日壁纸,并直接以图片形式返回。你可以用它来做应用的启动页、网站背景,或者任何需要每日更新精美图片的地方。
|
|
429
444
|
|
|
430
445
|
## 使用须知
|
|
431
|
-
|
|
432
|
-
> [!NOTE]
|
|
433
|
-
> **响应格式是图片**
|
|
434
|
-
> 请注意,此接口成功时直接返回图片二进制数据(通常为 `image/jpeg`),而非 JSON 格式。请确保客户端能够正确处理。
|
|
435
|
-
|
|
436
|
-
我们内置了备用方案:如果从必应官方获取图片失败,系统会尝试返回一张预存的高质量风景图,以保证服务的稳定性。
|
|
446
|
+
此接口成功时直接返回图片二进制数据,通常是 `image/jpeg`,不是 JSON 格式。接入时请按图片响应来处理。
|
|
437
447
|
"""
|
|
438
448
|
params = {}
|
|
439
449
|
body = {}
|
|
@@ -472,21 +482,6 @@ class _ImageApi:
|
|
|
472
482
|
|
|
473
483
|
## 功能概述
|
|
474
484
|
你提供一段文本内容,我们为你生成对应的二维码图片。你可以自定义尺寸、前景色、背景色,还支持透明背景,并选择不同的返回格式以适应不同场景。
|
|
475
|
-
|
|
476
|
-
## 使用须知
|
|
477
|
-
|
|
478
|
-
> [!IMPORTANT]
|
|
479
|
-
> **关键参数 `format`**
|
|
480
|
-
> 此参数决定了成功响应的内容类型和结构,请务必根据你的需求选择并正确处理响应:
|
|
481
|
-
> - **`image`** (默认): 直接返回 `image/png` 格式的图片二进制数据,适合在 `<img>` 标签中直接使用。
|
|
482
|
-
> - **`json`**: 返回一个包含 Base64 Data URI 的 JSON 对象,适合需要在前端直接嵌入CSS或HTML的场景。
|
|
483
|
-
> - **`json_url`**: 返回一个包含图片临时URL的JSON对象,适合需要图片链接的场景。
|
|
484
|
-
|
|
485
|
-
> [!TIP]
|
|
486
|
-
> **颜色参数说明**
|
|
487
|
-
> - 颜色参数使用十六进制格式(如 `#FF0000`)
|
|
488
|
-
> - URL 中需要对 `#` 进行编码,即 `%23`(例如:`fgcolor=%23FF0000`)
|
|
489
|
-
> - 当 `transparent=true` 时,`bgcolor` 参数会被忽略
|
|
490
485
|
"""
|
|
491
486
|
params = {}
|
|
492
487
|
body = {}
|
|
@@ -638,9 +633,6 @@ class _ImageApi:
|
|
|
638
633
|
r"""图片敏感检测
|
|
639
634
|
这是一个图片内容审核接口,自动识别图片中的违规内容并返回处理建议。
|
|
640
635
|
|
|
641
|
-
> [!VIP]
|
|
642
|
-
> 此接口限时免费开放,无需企业认证即可使用。
|
|
643
|
-
|
|
644
636
|
## 功能概述
|
|
645
637
|
上传图片文件或提供图片URL,接口会自动分析图片内容,返回是否违规、风险等级和处理建议。适合对接到用户上传流程中,实现自动化内容审核。
|
|
646
638
|
|
|
@@ -872,19 +864,6 @@ class _MiscApi:
|
|
|
872
864
|
|
|
873
865
|
### 数据源列表
|
|
874
866
|
传 `sources=true`,返回所有支持历史数据的平台列表。
|
|
875
|
-
|
|
876
|
-
## 可选值
|
|
877
|
-
`type` 参数接受多种不同的值,每种值对应一个不同的热榜来源。以下是目前支持的所有值:
|
|
878
|
-
|
|
879
|
-
| 分类 | 支持的 type 值 |
|
|
880
|
-
|------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
|
881
|
-
| 视频/社区 | bilibili(哔哩哔哩弹幕网), acfun(A站弹幕视频网站), weibo(新浪微博热搜), zhihu(知乎热榜), zhihu-daily(知乎日报热榜), douyin(抖音热榜), kuaishou(快手热榜), douban-movie(豆瓣电影榜单), douban-group(豆瓣小组话题), tieba(百度贴吧热帖), hupu(虎扑热帖), ngabbs(NGA游戏论坛热帖), v2ex(V2EX技术社区热帖), 52pojie(吾爱破解热帖), hostloc(全球主机交流论坛), coolapk(酷安热榜) |
|
|
882
|
-
| 新闻/资讯 | baidu(百度热搜), thepaper(澎湃新闻热榜), toutiao(今日头条热榜), qq-news(腾讯新闻热榜), sina(新浪热搜), sina-news(新浪新闻热榜), netease-news(网易新闻热榜), huxiu(虎嗅网热榜), ifanr(爱范儿热榜) |
|
|
883
|
-
| 技术/IT | sspai(少数派热榜), ithome(IT之家热榜), ithome-xijiayi(IT之家·喜加一栏目), juejin(掘金社区热榜), jianshu(简书热榜), guokr(果壳热榜), 36kr(36氪热榜), 51cto(51CTO热榜), csdn(CSDN博客热榜), nodeseek(NodeSeek 技术社区), hellogithub(HelloGitHub 项目推荐) |
|
|
884
|
-
| 游戏 | lol(英雄联盟热帖), genshin(原神热榜), honkai(崩坏3热榜), starrail(星穹铁道热榜) |
|
|
885
|
-
| 音乐 | netease-music(网易云音乐热歌榜), qq-music(QQ音乐热歌榜) |
|
|
886
|
-
| 其他 | weread(微信读书热门书籍), weatheralarm(天气预警信息), earthquake(地震速报), history(历史上的今天) |
|
|
887
|
-
|
|
888
867
|
"""
|
|
889
868
|
params = {}
|
|
890
869
|
body = {}
|
|
@@ -1034,9 +1013,6 @@ graph TD
|
|
|
1034
1013
|
r"""获取支持的快递公司列表
|
|
1035
1014
|
不确定系统支持哪些快递公司?这个接口返回完整的支持列表。
|
|
1036
1015
|
|
|
1037
|
-
> [!VIP]
|
|
1038
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者集成和测试。未来,它将转为付费API,为用户提供更稳定和强大的服务。
|
|
1039
|
-
|
|
1040
1016
|
## 功能概述
|
|
1041
1017
|
获取系统当前支持的所有快递公司列表,包括每家公司的标准编码(code)和中文名称(name)。
|
|
1042
1018
|
|
|
@@ -1056,9 +1032,6 @@ graph TD
|
|
|
1056
1032
|
r"""识别快递公司
|
|
1057
1033
|
不确定手里的快递单号属于哪家快递公司?这个接口专门做识别,不查物流。
|
|
1058
1034
|
|
|
1059
|
-
> [!VIP]
|
|
1060
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者集成和测试。未来,它将转为付费API,为用户提供更稳定和强大的服务。
|
|
1061
|
-
|
|
1062
1035
|
## 功能概述
|
|
1063
1036
|
输入快递单号,系统会根据单号规则快速识别出最可能的快递公司。如果存在多个可能的匹配结果,还会在 `alternatives` 字段中返回备选项,供你参考选择。
|
|
1064
1037
|
|
|
@@ -1081,16 +1054,15 @@ graph TD
|
|
|
1081
1054
|
r"""查询快递物流信息
|
|
1082
1055
|
买了东西想知道快递到哪儿了?这个接口帮你实时追踪物流状态。
|
|
1083
1056
|
|
|
1084
|
-
> [!VIP]
|
|
1085
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者集成和测试。未来,它将转为付费API,为用户提供更稳定和强大的服务。
|
|
1086
|
-
|
|
1087
1057
|
## 功能概述
|
|
1088
|
-
|
|
1058
|
+
提供一个快递单号,系统会自动识别快递公司并返回完整的物流轨迹信息。这个接口目前可以查询中通、圆通、韵达、申通、极兔、京东、EMS、德邦等主流快递公司的物流信息。
|
|
1089
1059
|
|
|
1090
1060
|
## 使用须知
|
|
1061
|
+
目前暂不支持顺丰快递单号的物流查询。
|
|
1062
|
+
|
|
1091
1063
|
- **自动识别**:不知道是哪家快递?系统会根据单号规则自动识别快递公司(推荐使用)
|
|
1092
1064
|
- **手动指定**:如果已知快递公司,可以传递 `carrier_code` 参数,查询速度会更快
|
|
1093
|
-
-
|
|
1065
|
+
- **手机尾号验证**:部分快递公司需要验证收件人手机尾号才能查询详细物流,如果返回 `暂无物流信息`,建议尝试传入 `phone` 参数
|
|
1094
1066
|
- **查询时效**:物流信息实时查询,响应时间通常在1-2秒内
|
|
1095
1067
|
"""
|
|
1096
1068
|
params = {}
|
|
@@ -1123,9 +1095,9 @@ graph TD
|
|
|
1123
1095
|
|
|
1124
1096
|
## 可选功能模块
|
|
1125
1097
|
- `extended=true`:扩展气象字段(体感温度、能见度、气压、紫外线、空气质量及污染物分项数据)
|
|
1126
|
-
- `forecast=true`:多天预报(最多7
|
|
1098
|
+
- `forecast=true`:多天预报(最多7天,会额外返回每天的最高温度、最低温度,以及日出日落、风速等详细数据)
|
|
1127
1099
|
- `hourly=true`:逐小时预报(24小时)
|
|
1128
|
-
- `minutely=true
|
|
1100
|
+
- `minutely=true`:分钟级降水预报(仅国内城市,精确到2分钟)
|
|
1129
1101
|
- `indices=true`:18项生活指数(穿衣、紫外线、洗车、运动、花粉等)
|
|
1130
1102
|
|
|
1131
1103
|
## 天气字段说明
|
|
@@ -1133,7 +1105,7 @@ graph TD
|
|
|
1133
1105
|
|
|
1134
1106
|
常见值包括:晴、多云、阴、小雨、中雨、大雨、雷阵雨、小雪、中雪、大雪、雨夹雪、雾、霾、沙尘。
|
|
1135
1107
|
|
|
1136
|
-
|
|
1108
|
+
如果你的业务需要稳定的天气分类,建议使用 `weather_code` 进行映射。完整的天气图标代码请参考[天气图标代码表](#enum-list)。
|
|
1137
1109
|
"""
|
|
1138
1110
|
params = {}
|
|
1139
1111
|
body = {}
|
|
@@ -1770,10 +1742,7 @@ class _SocialApi:
|
|
|
1770
1742
|
|
|
1771
1743
|
def get_social_qq_groupinfo(self, **kwargs):
|
|
1772
1744
|
r"""查询 QQ 群信息
|
|
1773
|
-
想在你的应用里展示QQ
|
|
1774
|
-
|
|
1775
|
-
> [!VIP]
|
|
1776
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者集成和测试。未来,它将转为付费API,为用户提供更稳定和强大的服务。
|
|
1745
|
+
想在你的应用里展示QQ群信息?这个接口让你轻松获取群名称、群头像、群简介、成员数量等详细公开信息。
|
|
1777
1746
|
|
|
1778
1747
|
## 功能概述
|
|
1779
1748
|
你只需要提供一个QQ群号(5-12位纯数字),接口就会返回该群的完整公开信息。我们会先验证群号的有效性,确保返回的数据准确可靠。接口响应速度快,数据结构清晰,非常适合集成到各类应用场景中。
|
|
@@ -1813,9 +1782,6 @@ class _SocialApi:
|
|
|
1813
1782
|
r"""查询 QQ 信息
|
|
1814
1783
|
这是一个功能丰富的QQ用户信息查询接口,能够获取QQ用户的详细公开信息。
|
|
1815
1784
|
|
|
1816
|
-
> [!VIP]
|
|
1817
|
-
> 我们在近日优化了此接口,速度应该会更加快了。
|
|
1818
|
-
|
|
1819
1785
|
## 功能概述
|
|
1820
1786
|
通过QQ号查询用户的详细信息,包括基础资料、等级信息、VIP状态等。返回的信息丰富全面,适合用于用户画像分析、社交应用集成等场景。
|
|
1821
1787
|
|
|
@@ -2299,19 +2265,16 @@ class _TranslateApi:
|
|
|
2299
2265
|
r"""AI智能翻译
|
|
2300
2266
|
这是一个商业级的AI智能翻译服务,采用最新的神经网络翻译技术和大语言模型,提供远超传统机器翻译的质量。
|
|
2301
2267
|
|
|
2302
|
-
> [!VIP]
|
|
2303
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者深度集成和测试。未来,它将转为付费API,为用户提供更稳定、更智能的翻译服务。
|
|
2304
|
-
|
|
2305
2268
|
## 功能概述
|
|
2306
2269
|
|
|
2307
|
-
-
|
|
2270
|
+
- **单文本翻译**: 专注处理单条文本翻译,适合需要高质量译文的业务场景。
|
|
2308
2271
|
- **多风格适配**: 提供随意口语化、专业商务、学术正式、文学艺术四种翻译风格,能够根据不同场景需求调整翻译的语言风格和表达方式。
|
|
2309
2272
|
- **上下文感知**: 支持通用、商务、技术、医疗、法律、市场营销、娱乐、教育、新闻等九种专业领域的上下文翻译,确保术语准确性和表达地道性。
|
|
2310
|
-
- **高质量保证**: 内置质量评估系统,对每次翻译结果进行流畅度、准确度、完整性评分,并提供置信度分数和替代翻译建议。
|
|
2311
|
-
- **智能解释**: 提供关键词组翻译注释、文化背景说明和语法结构分析,帮助用户理解翻译逻辑和文化差异。
|
|
2312
|
-
- **高效批量**: 批量翻译支持最多50条文本,总计10万字符,配备智能并发控制(1-10并发)和失败重试机制。
|
|
2313
|
-
- **快速模式**: 提供快速模式选项,在保证95%+准确率的前提下,响应时间缩短至800ms内,适合实时翻译和聊天应用。
|
|
2314
2273
|
- **格式保留**: 智能识别并保持原文的格式结构,包括换行、缩进、特殊符号等,确保翻译后的文本保持良好的可读性。
|
|
2274
|
+
|
|
2275
|
+
## 支持的语言
|
|
2276
|
+
|
|
2277
|
+
我们支持超过100种语言的互译,详见下方参数列表。
|
|
2315
2278
|
"""
|
|
2316
2279
|
params = {}
|
|
2317
2280
|
body = {}
|
|
@@ -2323,12 +2286,6 @@ class _TranslateApi:
|
|
|
2323
2286
|
if "context" in kwargs:
|
|
2324
2287
|
body["context"] = kwargs["context"]
|
|
2325
2288
|
|
|
2326
|
-
if "fast_mode" in kwargs:
|
|
2327
|
-
body["fast_mode"] = kwargs["fast_mode"]
|
|
2328
|
-
|
|
2329
|
-
if "max_concurrency" in kwargs:
|
|
2330
|
-
body["max_concurrency"] = kwargs["max_concurrency"]
|
|
2331
|
-
|
|
2332
2289
|
if "preserve_format" in kwargs:
|
|
2333
2290
|
body["preserve_format"] = kwargs["preserve_format"]
|
|
2334
2291
|
|
|
@@ -2341,9 +2298,6 @@ class _TranslateApi:
|
|
|
2341
2298
|
if "text" in kwargs:
|
|
2342
2299
|
body["text"] = kwargs["text"]
|
|
2343
2300
|
|
|
2344
|
-
if "texts" in kwargs:
|
|
2345
|
-
body["texts"] = kwargs["texts"]
|
|
2346
|
-
|
|
2347
2301
|
path = "/api/v1/ai/translate"
|
|
2348
2302
|
|
|
2349
2303
|
return self._http.request("POST", path, params=params, json=body if body else None)
|
|
@@ -2490,9 +2444,6 @@ class _WebparseApi:
|
|
|
2490
2444
|
|
|
2491
2445
|
## 功能概述
|
|
2492
2446
|
|
|
2493
|
-
> [!VIP]
|
|
2494
|
-
> 本 API 目前处于**限时免费**阶段,未来将转为付费服务。
|
|
2495
|
-
|
|
2496
2447
|
提交一个网页 URL,我们会自动抓取主体内容,剔除广告、导航栏等干扰元素,并转换为 Markdown 格式。同时会提取标题、作者、发布日期等元数据,生成 YAML Front Matter。
|
|
2497
2448
|
|
|
2498
2449
|
任务提交后会立即返回任务 ID,你可以用它来查询处理进度和结果。单个任务最长处理 60 秒,结果缓存 30 分钟。
|
|
@@ -2531,14 +2482,12 @@ class _MinGanCiShiBieApi:
|
|
|
2531
2482
|
r"""分析敏感词
|
|
2532
2483
|
分析单个或多个关键词的敏感程度,返回标准化风险标签与置信度结果。
|
|
2533
2484
|
|
|
2534
|
-
> [!VIP]
|
|
2535
|
-
> 本API基于先进的分析模型,提供三级缓存策略和并发处理能力。
|
|
2536
|
-
|
|
2537
2485
|
## 功能概述
|
|
2538
2486
|
|
|
2539
2487
|
- **模型驱动**: 使用先进的分析模型进行语义分析。
|
|
2540
2488
|
- **高性能**: 采用三级缓存策略(持久化存储 → 统一缓存 → 模型分析),确保高频请求的响应速度。
|
|
2541
2489
|
- **并发支持**: 支持批量并发处理,单次最多可分析100个关键词。
|
|
2490
|
+
- **输入限制**: 单条关键词最多 1,000 字符,总字符数最多 20,000。
|
|
2542
2491
|
- **标准标签**: 返回 `label` 字段,明确区分 `sensitive` 与 `normal`。
|
|
2543
2492
|
- **分类清晰**: 返回 `category` 字段,用于标识具体风险类别。
|
|
2544
2493
|
- **置信度输出**: 返回 `confidence` 字段,范围为0.0到1.0。
|
|
@@ -2635,9 +2584,6 @@ UAPI Pro Search 是一个智能搜索引擎,采用机器学习算法对搜索
|
|
|
2635
2584
|
- **时间范围过滤**: 支持按天/周/月/年过滤结果
|
|
2636
2585
|
- **站内搜索**: 支持 `site:` 操作符,在指定网站内搜索
|
|
2637
2586
|
- **文件类型过滤**: 支持 `filetype:` 操作符,快速找到 PDF、Word 等特定格式文件
|
|
2638
|
-
|
|
2639
|
-
> [!VIP]
|
|
2640
|
-
> 本API目前处于**限时免费**阶段,我们鼓励开发者集成和测试。未来,它将转为付费API,为用户提供更稳定和强大的服务。
|
|
2641
2587
|
|
|
2642
2588
|
"""
|
|
2643
2589
|
params = {}
|
|
@@ -2669,4 +2615,3 @@ UAPI Pro Search 是一个智能搜索引擎,采用机器学习算法对搜索
|
|
|
2669
2615
|
|
|
2670
2616
|
return self._http.request("POST", path, params=params, json=body if body else None)
|
|
2671
2617
|
|
|
2672
|
-
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Dict, Mapping, Optional
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class RateLimitPolicyEntry:
|
|
8
|
+
name: str
|
|
9
|
+
quota: Optional[int] = None
|
|
10
|
+
unit: Optional[str] = None
|
|
11
|
+
window_seconds: Optional[int] = None
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class RateLimitStateEntry:
|
|
15
|
+
name: str
|
|
16
|
+
remaining: Optional[int] = None
|
|
17
|
+
unit: Optional[str] = None
|
|
18
|
+
reset_after_seconds: Optional[int] = None
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ResponseMeta:
|
|
22
|
+
request_id: Optional[str] = None
|
|
23
|
+
retry_after_raw: Optional[str] = None
|
|
24
|
+
retry_after_seconds: Optional[int] = None
|
|
25
|
+
debit_status: Optional[str] = None
|
|
26
|
+
credits_requested: Optional[int] = None
|
|
27
|
+
credits_charged: Optional[int] = None
|
|
28
|
+
credits_pricing: Optional[str] = None
|
|
29
|
+
active_quota_buckets: Optional[int] = None
|
|
30
|
+
stop_on_empty: Optional[bool] = None
|
|
31
|
+
rate_limit_policy_raw: Optional[str] = None
|
|
32
|
+
rate_limit_raw: Optional[str] = None
|
|
33
|
+
rate_limit_policies: Dict[str, RateLimitPolicyEntry] = field(default_factory=dict)
|
|
34
|
+
rate_limits: Dict[str, RateLimitStateEntry] = field(default_factory=dict)
|
|
35
|
+
balance_limit_cents: Optional[int] = None
|
|
36
|
+
balance_remaining_cents: Optional[int] = None
|
|
37
|
+
quota_limit_credits: Optional[int] = None
|
|
38
|
+
quota_remaining_credits: Optional[int] = None
|
|
39
|
+
visitor_quota_limit_credits: Optional[int] = None
|
|
40
|
+
visitor_quota_remaining_credits: Optional[int] = None
|
|
41
|
+
billing_key_rate_limit: Optional[int] = None
|
|
42
|
+
billing_key_rate_remaining: Optional[int] = None
|
|
43
|
+
billing_key_rate_unit: Optional[str] = None
|
|
44
|
+
billing_key_rate_window_seconds: Optional[int] = None
|
|
45
|
+
billing_key_rate_reset_after_seconds: Optional[int] = None
|
|
46
|
+
billing_ip_rate_limit: Optional[int] = None
|
|
47
|
+
billing_ip_rate_remaining: Optional[int] = None
|
|
48
|
+
billing_ip_rate_unit: Optional[str] = None
|
|
49
|
+
billing_ip_rate_window_seconds: Optional[int] = None
|
|
50
|
+
billing_ip_rate_reset_after_seconds: Optional[int] = None
|
|
51
|
+
visitor_rate_limit: Optional[int] = None
|
|
52
|
+
visitor_rate_remaining: Optional[int] = None
|
|
53
|
+
visitor_rate_unit: Optional[str] = None
|
|
54
|
+
visitor_rate_window_seconds: Optional[int] = None
|
|
55
|
+
visitor_rate_reset_after_seconds: Optional[int] = None
|
|
56
|
+
raw_headers: Dict[str, str] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
class UapiError(Exception):
|
|
59
|
+
code: str
|
|
60
|
+
status: int
|
|
61
|
+
message: str
|
|
62
|
+
details: Any
|
|
63
|
+
payload: Any
|
|
64
|
+
meta: Optional[ResponseMeta]
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
code: str,
|
|
69
|
+
status: int,
|
|
70
|
+
message: str,
|
|
71
|
+
details: Any = None,
|
|
72
|
+
payload: Any = None,
|
|
73
|
+
meta: Optional[ResponseMeta] = None,
|
|
74
|
+
):
|
|
75
|
+
super().__init__(f"[{status}] {code}: {message}")
|
|
76
|
+
self.code = code
|
|
77
|
+
self.status = status
|
|
78
|
+
self.message = message
|
|
79
|
+
self.details = details
|
|
80
|
+
self.payload = payload
|
|
81
|
+
self.meta = meta
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ApiErrorError(UapiError):
|
|
85
|
+
"""上游/内部错误 (API_ERROR)"""
|
|
86
|
+
DEFAULT_STATUS = 502
|
|
87
|
+
|
|
88
|
+
class AvatarNotFoundError(UapiError):
|
|
89
|
+
"""头像未找到 (AVATAR_NOT_FOUND)"""
|
|
90
|
+
DEFAULT_STATUS = 404
|
|
91
|
+
|
|
92
|
+
class ConversionFailedError(UapiError):
|
|
93
|
+
"""转换失败 (CONVERSION_FAILED)"""
|
|
94
|
+
DEFAULT_STATUS = 400
|
|
95
|
+
|
|
96
|
+
class FileOpenErrorError(UapiError):
|
|
97
|
+
"""文件打开错误 (FILE_OPEN_ERROR)"""
|
|
98
|
+
DEFAULT_STATUS = 500
|
|
99
|
+
|
|
100
|
+
class FileRequiredError(UapiError):
|
|
101
|
+
"""文件必需 (FILE_REQUIRED)"""
|
|
102
|
+
DEFAULT_STATUS = 400
|
|
103
|
+
|
|
104
|
+
class InsufficientCreditsError(UapiError):
|
|
105
|
+
"""账户积分不足 (INSUFFICIENT_CREDITS)"""
|
|
106
|
+
DEFAULT_STATUS = 402
|
|
107
|
+
|
|
108
|
+
class InternalServerErrorError(UapiError):
|
|
109
|
+
"""服务器内部错误 (INTERNAL_SERVER_ERROR)"""
|
|
110
|
+
DEFAULT_STATUS = 500
|
|
111
|
+
|
|
112
|
+
class InvalidParameterError(UapiError):
|
|
113
|
+
"""请求参数错误 (INVALID_PARAMETER)"""
|
|
114
|
+
DEFAULT_STATUS = 400
|
|
115
|
+
|
|
116
|
+
class InvalidParamsError(UapiError):
|
|
117
|
+
"""无效参数 (INVALID_PARAMS)"""
|
|
118
|
+
DEFAULT_STATUS = 400
|
|
119
|
+
|
|
120
|
+
class NotFoundError(UapiError):
|
|
121
|
+
"""资源不存在 (NOT_FOUND)"""
|
|
122
|
+
DEFAULT_STATUS = 404
|
|
123
|
+
|
|
124
|
+
class NoMatchError(UapiError):
|
|
125
|
+
"""无匹配 (NO_MATCH)"""
|
|
126
|
+
DEFAULT_STATUS = 404
|
|
127
|
+
|
|
128
|
+
class NoTrackingDataError(UapiError):
|
|
129
|
+
"""无物流数据 (NO_TRACKING_DATA)"""
|
|
130
|
+
DEFAULT_STATUS = 404
|
|
131
|
+
|
|
132
|
+
class PhoneInfoFailedError(UapiError):
|
|
133
|
+
"""手机号信息查询失败 (PHONE_INFO_FAILED)"""
|
|
134
|
+
DEFAULT_STATUS = 500
|
|
135
|
+
|
|
136
|
+
class RecognitionFailedError(UapiError):
|
|
137
|
+
"""识别失败 (RECOGNITION_FAILED)"""
|
|
138
|
+
DEFAULT_STATUS = 404
|
|
139
|
+
|
|
140
|
+
class RequestEntityTooLargeError(UapiError):
|
|
141
|
+
"""错误 (REQUEST_ENTITY_TOO_LARGE)"""
|
|
142
|
+
DEFAULT_STATUS = 413
|
|
143
|
+
|
|
144
|
+
class ServiceBusyError(UapiError):
|
|
145
|
+
"""请求过于频繁 (SERVICE_BUSY)"""
|
|
146
|
+
DEFAULT_STATUS = 429
|
|
147
|
+
|
|
148
|
+
class TimezoneNotFoundError(UapiError):
|
|
149
|
+
"""时区未找到 (TIMEZONE_NOT_FOUND)"""
|
|
150
|
+
DEFAULT_STATUS = 404
|
|
151
|
+
|
|
152
|
+
class UnauthorizedError(UapiError):
|
|
153
|
+
"""请求未授权 (UNAUTHORIZED)"""
|
|
154
|
+
DEFAULT_STATUS = 401
|
|
155
|
+
|
|
156
|
+
class UnsupportedCarrierError(UapiError):
|
|
157
|
+
"""不支持的承运商 (UNSUPPORTED_CARRIER)"""
|
|
158
|
+
DEFAULT_STATUS = 404
|
|
159
|
+
|
|
160
|
+
class UnsupportedFormatError(UapiError):
|
|
161
|
+
"""格式不支持 (UNSUPPORTED_FORMAT)"""
|
|
162
|
+
DEFAULT_STATUS = 400
|
|
163
|
+
|
|
164
|
+
class VisitorMonthlyQuotaExhaustedError(UapiError):
|
|
165
|
+
"""访客月度免费额度已用尽 (VISITOR_MONTHLY_QUOTA_EXHAUSTED)"""
|
|
166
|
+
DEFAULT_STATUS = 429
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _default_code(status: int) -> str:
|
|
170
|
+
if status == 400:
|
|
171
|
+
return "INVALID_PARAMETER"
|
|
172
|
+
if status == 401:
|
|
173
|
+
return "UNAUTHORIZED"
|
|
174
|
+
if status == 402:
|
|
175
|
+
return "INSUFFICIENT_CREDITS"
|
|
176
|
+
if status == 404:
|
|
177
|
+
return "NOT_FOUND"
|
|
178
|
+
if status == 413:
|
|
179
|
+
return "REQUEST_ENTITY_TOO_LARGE"
|
|
180
|
+
if status == 429:
|
|
181
|
+
return "SERVICE_BUSY"
|
|
182
|
+
if status >= 500:
|
|
183
|
+
return "INTERNAL_SERVER_ERROR"
|
|
184
|
+
return "API_ERROR"
|
|
185
|
+
|
|
186
|
+
def _parse_int(value: Optional[str]) -> Optional[int]:
|
|
187
|
+
if value is None:
|
|
188
|
+
return None
|
|
189
|
+
try:
|
|
190
|
+
return int(value)
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _parse_bool(value: Optional[str]) -> Optional[bool]:
|
|
195
|
+
if value is None:
|
|
196
|
+
return None
|
|
197
|
+
lowered = value.strip().lower()
|
|
198
|
+
if lowered == "true":
|
|
199
|
+
return True
|
|
200
|
+
if lowered == "false":
|
|
201
|
+
return False
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
def _unquote(value: str) -> str:
|
|
205
|
+
text = value.strip()
|
|
206
|
+
if len(text) >= 2 and text[0] == '"' and text[-1] == '"':
|
|
207
|
+
return text[1:-1]
|
|
208
|
+
return text
|
|
209
|
+
|
|
210
|
+
def _parse_structured_items(raw: Optional[str]) -> list[tuple[str, Dict[str, str]]]:
|
|
211
|
+
if not raw:
|
|
212
|
+
return []
|
|
213
|
+
items: list[tuple[str, Dict[str, str]]] = []
|
|
214
|
+
for chunk in [part.strip() for part in raw.split(",") if part.strip()]:
|
|
215
|
+
segments = [segment.strip() for segment in chunk.split(";") if segment.strip()]
|
|
216
|
+
if not segments:
|
|
217
|
+
continue
|
|
218
|
+
name = _unquote(segments[0])
|
|
219
|
+
params: Dict[str, str] = {}
|
|
220
|
+
for segment in segments[1:]:
|
|
221
|
+
if "=" not in segment:
|
|
222
|
+
continue
|
|
223
|
+
key, value = segment.split("=", 1)
|
|
224
|
+
params[key.strip()] = _unquote(value)
|
|
225
|
+
items.append((name, params))
|
|
226
|
+
return items
|
|
227
|
+
|
|
228
|
+
def extract_meta(headers: Mapping[str, str]) -> ResponseMeta:
|
|
229
|
+
raw_headers = {str(key).lower(): str(value) for key, value in headers.items()}
|
|
230
|
+
rate_limit_policies: Dict[str, RateLimitPolicyEntry] = {}
|
|
231
|
+
rate_limits: Dict[str, RateLimitStateEntry] = {}
|
|
232
|
+
|
|
233
|
+
for name, params in _parse_structured_items(raw_headers.get("ratelimit-policy")):
|
|
234
|
+
rate_limit_policies[name] = RateLimitPolicyEntry(
|
|
235
|
+
name=name,
|
|
236
|
+
quota=_parse_int(params.get("q")),
|
|
237
|
+
unit=params.get("uapi-unit"),
|
|
238
|
+
window_seconds=_parse_int(params.get("w")),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
for name, params in _parse_structured_items(raw_headers.get("ratelimit")):
|
|
242
|
+
rate_limits[name] = RateLimitStateEntry(
|
|
243
|
+
name=name,
|
|
244
|
+
remaining=_parse_int(params.get("r")),
|
|
245
|
+
unit=params.get("uapi-unit"),
|
|
246
|
+
reset_after_seconds=_parse_int(params.get("t")),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
billing_key_rate_policy = rate_limit_policies.get("billing-key-rate")
|
|
250
|
+
billing_key_rate_state = rate_limits.get("billing-key-rate")
|
|
251
|
+
billing_ip_rate_policy = rate_limit_policies.get("billing-ip-rate")
|
|
252
|
+
billing_ip_rate_state = rate_limits.get("billing-ip-rate")
|
|
253
|
+
visitor_rate_policy = rate_limit_policies.get("visitor-rate")
|
|
254
|
+
visitor_rate_state = rate_limits.get("visitor-rate")
|
|
255
|
+
|
|
256
|
+
return ResponseMeta(
|
|
257
|
+
request_id=raw_headers.get("x-request-id"),
|
|
258
|
+
retry_after_raw=raw_headers.get("retry-after"),
|
|
259
|
+
retry_after_seconds=_parse_int(raw_headers.get("retry-after")),
|
|
260
|
+
debit_status=raw_headers.get("uapi-debit-status"),
|
|
261
|
+
credits_requested=_parse_int(raw_headers.get("uapi-credits-requested")),
|
|
262
|
+
credits_charged=_parse_int(raw_headers.get("uapi-credits-charged")),
|
|
263
|
+
credits_pricing=raw_headers.get("uapi-credits-pricing"),
|
|
264
|
+
active_quota_buckets=_parse_int(raw_headers.get("uapi-quota-active-buckets")),
|
|
265
|
+
stop_on_empty=_parse_bool(raw_headers.get("uapi-stop-on-empty")),
|
|
266
|
+
rate_limit_policy_raw=raw_headers.get("ratelimit-policy"),
|
|
267
|
+
rate_limit_raw=raw_headers.get("ratelimit"),
|
|
268
|
+
rate_limit_policies=rate_limit_policies,
|
|
269
|
+
rate_limits=rate_limits,
|
|
270
|
+
balance_limit_cents=rate_limit_policies.get("billing-balance").quota if "billing-balance" in rate_limit_policies else None,
|
|
271
|
+
balance_remaining_cents=rate_limits.get("billing-balance").remaining if "billing-balance" in rate_limits else None,
|
|
272
|
+
quota_limit_credits=rate_limit_policies.get("billing-quota").quota if "billing-quota" in rate_limit_policies else None,
|
|
273
|
+
quota_remaining_credits=rate_limits.get("billing-quota").remaining if "billing-quota" in rate_limits else None,
|
|
274
|
+
visitor_quota_limit_credits=rate_limit_policies.get("visitor-quota").quota if "visitor-quota" in rate_limit_policies else None,
|
|
275
|
+
visitor_quota_remaining_credits=rate_limits.get("visitor-quota").remaining if "visitor-quota" in rate_limits else None,
|
|
276
|
+
billing_key_rate_limit=billing_key_rate_policy.quota if billing_key_rate_policy else None,
|
|
277
|
+
billing_key_rate_remaining=billing_key_rate_state.remaining if billing_key_rate_state else None,
|
|
278
|
+
billing_key_rate_unit=billing_key_rate_policy.unit if billing_key_rate_policy and billing_key_rate_policy.unit is not None else (billing_key_rate_state.unit if billing_key_rate_state else None),
|
|
279
|
+
billing_key_rate_window_seconds=billing_key_rate_policy.window_seconds if billing_key_rate_policy else None,
|
|
280
|
+
billing_key_rate_reset_after_seconds=billing_key_rate_state.reset_after_seconds if billing_key_rate_state else None,
|
|
281
|
+
billing_ip_rate_limit=billing_ip_rate_policy.quota if billing_ip_rate_policy else None,
|
|
282
|
+
billing_ip_rate_remaining=billing_ip_rate_state.remaining if billing_ip_rate_state else None,
|
|
283
|
+
billing_ip_rate_unit=billing_ip_rate_policy.unit if billing_ip_rate_policy and billing_ip_rate_policy.unit is not None else (billing_ip_rate_state.unit if billing_ip_rate_state else None),
|
|
284
|
+
billing_ip_rate_window_seconds=billing_ip_rate_policy.window_seconds if billing_ip_rate_policy else None,
|
|
285
|
+
billing_ip_rate_reset_after_seconds=billing_ip_rate_state.reset_after_seconds if billing_ip_rate_state else None,
|
|
286
|
+
visitor_rate_limit=visitor_rate_policy.quota if visitor_rate_policy else None,
|
|
287
|
+
visitor_rate_remaining=visitor_rate_state.remaining if visitor_rate_state else None,
|
|
288
|
+
visitor_rate_unit=visitor_rate_policy.unit if visitor_rate_policy and visitor_rate_policy.unit is not None else (visitor_rate_state.unit if visitor_rate_state else None),
|
|
289
|
+
visitor_rate_window_seconds=visitor_rate_policy.window_seconds if visitor_rate_policy else None,
|
|
290
|
+
visitor_rate_reset_after_seconds=visitor_rate_state.reset_after_seconds if visitor_rate_state else None,
|
|
291
|
+
raw_headers=raw_headers,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def _pick_details(data: Any) -> Any:
|
|
295
|
+
if not isinstance(data, dict):
|
|
296
|
+
return None
|
|
297
|
+
if "details" in data:
|
|
298
|
+
return data["details"]
|
|
299
|
+
if "quota" in data:
|
|
300
|
+
return data["quota"]
|
|
301
|
+
if "docs" in data:
|
|
302
|
+
return data["docs"]
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
def map_error(r: httpx.Response) -> UapiError:
|
|
306
|
+
code = None
|
|
307
|
+
msg = r.text
|
|
308
|
+
data: Any = None
|
|
309
|
+
try:
|
|
310
|
+
data = r.json()
|
|
311
|
+
code = data.get("code") or data.get("error") or data.get("errCode") or _default_code(r.status_code)
|
|
312
|
+
msg = data.get("message") or data.get("errMsg") or msg
|
|
313
|
+
except Exception:
|
|
314
|
+
code = _default_code(r.status_code)
|
|
315
|
+
status = r.status_code
|
|
316
|
+
meta = extract_meta(r.headers)
|
|
317
|
+
cls = _class_by_code(code, status)
|
|
318
|
+
return cls(code, status, msg, _pick_details(data), data, meta)
|
|
319
|
+
|
|
320
|
+
def _class_by_code(code: str, status: int):
|
|
321
|
+
c = (code or "").upper()
|
|
322
|
+
mapping = {
|
|
323
|
+
|
|
324
|
+
"API_ERROR": ApiErrorError,
|
|
325
|
+
|
|
326
|
+
"AVATAR_NOT_FOUND": AvatarNotFoundError,
|
|
327
|
+
|
|
328
|
+
"CONVERSION_FAILED": ConversionFailedError,
|
|
329
|
+
|
|
330
|
+
"FILE_OPEN_ERROR": FileOpenErrorError,
|
|
331
|
+
|
|
332
|
+
"FILE_REQUIRED": FileRequiredError,
|
|
333
|
+
|
|
334
|
+
"INSUFFICIENT_CREDITS": InsufficientCreditsError,
|
|
335
|
+
|
|
336
|
+
"INTERNAL_SERVER_ERROR": InternalServerErrorError,
|
|
337
|
+
|
|
338
|
+
"INVALID_PARAMETER": InvalidParameterError,
|
|
339
|
+
|
|
340
|
+
"INVALID_PARAMS": InvalidParamsError,
|
|
341
|
+
|
|
342
|
+
"NOT_FOUND": NotFoundError,
|
|
343
|
+
|
|
344
|
+
"NO_MATCH": NoMatchError,
|
|
345
|
+
|
|
346
|
+
"NO_TRACKING_DATA": NoTrackingDataError,
|
|
347
|
+
|
|
348
|
+
"PHONE_INFO_FAILED": PhoneInfoFailedError,
|
|
349
|
+
|
|
350
|
+
"RECOGNITION_FAILED": RecognitionFailedError,
|
|
351
|
+
|
|
352
|
+
"REQUEST_ENTITY_TOO_LARGE": RequestEntityTooLargeError,
|
|
353
|
+
|
|
354
|
+
"SERVICE_BUSY": ServiceBusyError,
|
|
355
|
+
|
|
356
|
+
"TIMEZONE_NOT_FOUND": TimezoneNotFoundError,
|
|
357
|
+
|
|
358
|
+
"UNAUTHORIZED": UnauthorizedError,
|
|
359
|
+
|
|
360
|
+
"UNSUPPORTED_CARRIER": UnsupportedCarrierError,
|
|
361
|
+
|
|
362
|
+
"UNSUPPORTED_FORMAT": UnsupportedFormatError,
|
|
363
|
+
|
|
364
|
+
"VISITOR_MONTHLY_QUOTA_EXHAUSTED": VisitorMonthlyQuotaExhaustedError,
|
|
365
|
+
|
|
366
|
+
}
|
|
367
|
+
return mapping.get(c) or ({
|
|
368
|
+
400: InvalidParameterError,
|
|
369
|
+
401: UnauthorizedError,
|
|
370
|
+
402: InsufficientCreditsError,
|
|
371
|
+
404: NotFoundError,
|
|
372
|
+
429: ServiceBusyError,
|
|
373
|
+
500: InternalServerErrorError,
|
|
374
|
+
}.get(status) or UapiError)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uapi-sdk-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Idiomatic UAPI SDK for Python
|
|
5
5
|
Author-email: UAPI <dev@uapis.cn>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -32,11 +32,13 @@ pip install uapi-sdk-python
|
|
|
32
32
|
```python
|
|
33
33
|
from uapi import UapiClient
|
|
34
34
|
|
|
35
|
-
client = UapiClient("https://uapis.cn
|
|
36
|
-
result = client.
|
|
35
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
36
|
+
result = client.misc.get_misc_hotboard(type="weibo")
|
|
37
37
|
print(result)
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
这个接口默认只要传 `type` 就可以拿当前热榜。`time`、`keyword`、`time_start`、`time_end`、`limit`、`sources` 都是按场景再传的可选参数。
|
|
41
|
+
|
|
40
42
|
> [!TIP]
|
|
41
43
|
> 请使用与运行脚本相同的 Python 解释器安装依赖,例如执行 `python -m pip install uapi-sdk-python` 后再运行 `python main.py`。在 VS Code / Pyright 中若提示 “Import uapi could not be resolved”,将解释器切换到当前虚拟环境即可恢复补全。
|
|
42
44
|
|
|
@@ -54,6 +56,60 @@ print(result)
|
|
|
54
56
|
|
|
55
57
|
如果你需要查看字段细节或内部逻辑,仓库中的 `./internal` 目录同步保留了由 `openapi-generator` 生成的完整结构体,随时可供参考。
|
|
56
58
|
|
|
59
|
+
## 响应元信息
|
|
60
|
+
|
|
61
|
+
每次请求完成后,SDK 会自动把响应 Header 解析成结构化的 `ResponseMeta`,你不用自己拆原始字符串。
|
|
62
|
+
|
|
63
|
+
成功时可以通过 `client.last_response_meta` 读取,失败时可以通过 `err.meta` 读取,两条路径拿到的是同一套字段。
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from uapi import UapiClient, UapiError
|
|
67
|
+
|
|
68
|
+
client = UapiClient("https://uapis.cn", "YOUR_API_KEY")
|
|
69
|
+
|
|
70
|
+
# 成功路径
|
|
71
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
72
|
+
meta = client.last_response_meta
|
|
73
|
+
if meta:
|
|
74
|
+
print("这次请求原价:", meta.credits_requested or 0, "积分")
|
|
75
|
+
print("这次实际扣费:", meta.credits_charged or 0, "积分")
|
|
76
|
+
print("特殊计价:", meta.credits_pricing or "原价")
|
|
77
|
+
print("余额剩余:", meta.balance_remaining_cents or 0, "分")
|
|
78
|
+
print("资源包剩余:", meta.quota_remaining_credits or 0, "积分")
|
|
79
|
+
print("当前有效额度桶:", meta.active_quota_buckets or 0)
|
|
80
|
+
print("额度用空即停:", meta.stop_on_empty)
|
|
81
|
+
print("Key QPS:", meta.billing_key_rate_remaining or 0, "/", meta.billing_key_rate_limit or 0, meta.billing_key_rate_unit or "req")
|
|
82
|
+
print("Request ID:", meta.request_id)
|
|
83
|
+
|
|
84
|
+
# 失败路径
|
|
85
|
+
try:
|
|
86
|
+
client.social.get_social_qq_userinfo(qq="10001")
|
|
87
|
+
except UapiError as err:
|
|
88
|
+
if err.meta:
|
|
89
|
+
print("Retry-After 秒数:", err.meta.retry_after_seconds)
|
|
90
|
+
print("Retry-After 原始值:", err.meta.retry_after_raw)
|
|
91
|
+
print("访客 QPS:", err.meta.visitor_rate_remaining or 0, "/", err.meta.visitor_rate_limit or 0)
|
|
92
|
+
print("Request ID:", err.meta.request_id)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
常用字段一览:
|
|
96
|
+
|
|
97
|
+
| 字段 | 说明 |
|
|
98
|
+
|------|------|
|
|
99
|
+
| `credits_requested` | 这次请求原本要扣多少积分,也就是请求价 |
|
|
100
|
+
| `credits_charged` | 这次请求实际扣了多少积分 |
|
|
101
|
+
| `credits_pricing` | 特殊计价原因,例如缓存半价 `cache-hit-half-price` |
|
|
102
|
+
| `balance_remaining_cents` | 账户余额剩余(分) |
|
|
103
|
+
| `quota_remaining_credits` | 资源包剩余积分 |
|
|
104
|
+
| `active_quota_buckets` | 当前还有多少个有效额度桶参与计费 |
|
|
105
|
+
| `stop_on_empty` | 额度耗尽后是否直接停止服务 |
|
|
106
|
+
| `retry_after_seconds` / `retry_after_raw` | 限流后的等待时长;当服务端返回 HTTP 时间字符串时看 `retry_after_raw` |
|
|
107
|
+
| `request_id` | 请求唯一 ID,排障时使用 |
|
|
108
|
+
| `billing_key_rate_limit` / `billing_key_rate_remaining` | Billing Key 当前 QPS 规则的上限与剩余 |
|
|
109
|
+
| `billing_ip_rate_limit` / `billing_ip_rate_remaining` | Billing Key 单 IP 当前 QPS 规则的上限与剩余 |
|
|
110
|
+
| `visitor_rate_limit` / `visitor_rate_remaining` | 访客当前 QPS 规则的上限与剩余 |
|
|
111
|
+
| `rate_limit_policies` / `rate_limits` | 完整结构化限流策略数据 |
|
|
112
|
+
|
|
57
113
|
## 进阶实践
|
|
58
114
|
|
|
59
115
|
### 缓存与幂等
|
|
@@ -62,7 +118,7 @@ print(result)
|
|
|
62
118
|
from functools import lru_cache
|
|
63
119
|
from uapi import UapiClient
|
|
64
120
|
|
|
65
|
-
client = UapiClient("https://uapis.cn
|
|
121
|
+
client = UapiClient("https://uapis.cn", token="YOUR_API_KEY")
|
|
66
122
|
|
|
67
123
|
@lru_cache(maxsize=128)
|
|
68
124
|
def cached_lookup(qq: str):
|
|
@@ -73,34 +129,19 @@ user = cached_lookup("10001")
|
|
|
73
129
|
|
|
74
130
|
也可以在 FastAPI / Django 项目里配合 Redis,将 SDK 的响应序列化后写入缓存,命中即直接返回。
|
|
75
131
|
|
|
76
|
-
###
|
|
132
|
+
### 调整超时与环境
|
|
77
133
|
|
|
78
134
|
```python
|
|
79
|
-
import httpx
|
|
80
|
-
from httpx import Auth
|
|
81
135
|
from uapi import UapiClient
|
|
82
136
|
|
|
83
|
-
class StaticToken(Auth):
|
|
84
|
-
def __init__(self, token: str):
|
|
85
|
-
self.token = token
|
|
86
|
-
def auth_flow(self, request):
|
|
87
|
-
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
88
|
-
yield request
|
|
89
|
-
|
|
90
|
-
http_client = httpx.Client(
|
|
91
|
-
timeout=5,
|
|
92
|
-
transport=httpx.HTTPTransport(retries=3),
|
|
93
|
-
event_hooks={"request": [lambda request: print("->", request.url)]},
|
|
94
|
-
)
|
|
95
|
-
|
|
96
137
|
client = UapiClient(
|
|
97
|
-
"https://uapis.cn
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
"https://uapis.cn",
|
|
139
|
+
token="YOUR_API_KEY",
|
|
140
|
+
timeout=5.0,
|
|
100
141
|
)
|
|
101
142
|
```
|
|
102
143
|
|
|
103
|
-
|
|
144
|
+
如果你需要切换到别的环境,直接改 `base_url` 就可以;如果你只想缩短等待时间,传 `timeout` 就够了。
|
|
104
145
|
|
|
105
146
|
## 错误模型概览
|
|
106
147
|
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from typing import Any, Dict, Optional
|
|
3
|
-
import httpx
|
|
4
|
-
|
|
5
|
-
class UapiError(Exception):
|
|
6
|
-
code: str
|
|
7
|
-
status: int
|
|
8
|
-
message: str
|
|
9
|
-
details: Optional[Dict[str, Any]]
|
|
10
|
-
|
|
11
|
-
def __init__(self, code: str, status: int, message: str, details: Optional[Dict[str, Any]] = None):
|
|
12
|
-
super().__init__(f"[{status}] {code}: {message}")
|
|
13
|
-
self.code = code
|
|
14
|
-
self.status = status
|
|
15
|
-
self.message = message
|
|
16
|
-
self.details = details
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ApiErrorError(UapiError):
|
|
20
|
-
"""上游/内部错误 (API_ERROR)"""
|
|
21
|
-
DEFAULT_STATUS = 502
|
|
22
|
-
|
|
23
|
-
class AvatarNotFoundError(UapiError):
|
|
24
|
-
"""头像未找到 (AVATAR_NOT_FOUND)"""
|
|
25
|
-
DEFAULT_STATUS = 404
|
|
26
|
-
|
|
27
|
-
class ConversionFailedError(UapiError):
|
|
28
|
-
"""转换失败 (CONVERSION_FAILED)"""
|
|
29
|
-
DEFAULT_STATUS = 400
|
|
30
|
-
|
|
31
|
-
class FileOpenErrorError(UapiError):
|
|
32
|
-
"""文件打开错误 (FILE_OPEN_ERROR)"""
|
|
33
|
-
DEFAULT_STATUS = 500
|
|
34
|
-
|
|
35
|
-
class FileRequiredError(UapiError):
|
|
36
|
-
"""文件必需 (FILE_REQUIRED)"""
|
|
37
|
-
DEFAULT_STATUS = 400
|
|
38
|
-
|
|
39
|
-
class InternalServerErrorError(UapiError):
|
|
40
|
-
"""服务器内部错误 (INTERNAL_SERVER_ERROR)"""
|
|
41
|
-
DEFAULT_STATUS = 500
|
|
42
|
-
|
|
43
|
-
class InvalidParameterError(UapiError):
|
|
44
|
-
"""请求参数错误 (INVALID_PARAMETER)"""
|
|
45
|
-
DEFAULT_STATUS = 400
|
|
46
|
-
|
|
47
|
-
class InvalidParamsError(UapiError):
|
|
48
|
-
"""无效参数 (INVALID_PARAMS)"""
|
|
49
|
-
DEFAULT_STATUS = 400
|
|
50
|
-
|
|
51
|
-
class NotFoundError(UapiError):
|
|
52
|
-
"""资源不存在 (NOT_FOUND)"""
|
|
53
|
-
DEFAULT_STATUS = 404
|
|
54
|
-
|
|
55
|
-
class NoMatchError(UapiError):
|
|
56
|
-
"""无匹配 (NO_MATCH)"""
|
|
57
|
-
DEFAULT_STATUS = 404
|
|
58
|
-
|
|
59
|
-
class NoTrackingDataError(UapiError):
|
|
60
|
-
"""无物流数据 (NO_TRACKING_DATA)"""
|
|
61
|
-
DEFAULT_STATUS = 404
|
|
62
|
-
|
|
63
|
-
class PhoneInfoFailedError(UapiError):
|
|
64
|
-
"""手机号信息查询失败 (PHONE_INFO_FAILED)"""
|
|
65
|
-
DEFAULT_STATUS = 500
|
|
66
|
-
|
|
67
|
-
class RecognitionFailedError(UapiError):
|
|
68
|
-
"""识别失败 (RECOGNITION_FAILED)"""
|
|
69
|
-
DEFAULT_STATUS = 404
|
|
70
|
-
|
|
71
|
-
class RequestEntityTooLargeError(UapiError):
|
|
72
|
-
"""错误 (REQUEST_ENTITY_TOO_LARGE)"""
|
|
73
|
-
DEFAULT_STATUS = 413
|
|
74
|
-
|
|
75
|
-
class ServiceBusyError(UapiError):
|
|
76
|
-
"""请求过于频繁 (SERVICE_BUSY)"""
|
|
77
|
-
DEFAULT_STATUS = 429
|
|
78
|
-
|
|
79
|
-
class TimezoneNotFoundError(UapiError):
|
|
80
|
-
"""时区未找到 (TIMEZONE_NOT_FOUND)"""
|
|
81
|
-
DEFAULT_STATUS = 404
|
|
82
|
-
|
|
83
|
-
class UnauthorizedError(UapiError):
|
|
84
|
-
"""请求未授权 (UNAUTHORIZED)"""
|
|
85
|
-
DEFAULT_STATUS = 401
|
|
86
|
-
|
|
87
|
-
class UnsupportedCarrierError(UapiError):
|
|
88
|
-
"""不支持的承运商 (UNSUPPORTED_CARRIER)"""
|
|
89
|
-
DEFAULT_STATUS = 404
|
|
90
|
-
|
|
91
|
-
class UnsupportedFormatError(UapiError):
|
|
92
|
-
"""格式不支持 (UNSUPPORTED_FORMAT)"""
|
|
93
|
-
DEFAULT_STATUS = 400
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def map_error(r: httpx.Response) -> UapiError:
|
|
97
|
-
code = None
|
|
98
|
-
msg = r.text
|
|
99
|
-
try:
|
|
100
|
-
data = r.json()
|
|
101
|
-
code = data.get("code") or data.get("error") or data.get("errCode") or "API_ERROR"
|
|
102
|
-
msg = data.get("message") or data.get("errMsg") or msg
|
|
103
|
-
details = data.get("details")
|
|
104
|
-
except Exception:
|
|
105
|
-
details = None
|
|
106
|
-
status = r.status_code
|
|
107
|
-
cls = _class_by_code(code, status)
|
|
108
|
-
return cls(code, status, msg, details)
|
|
109
|
-
|
|
110
|
-
def _class_by_code(code: str, status: int):
|
|
111
|
-
c = (code or "").upper()
|
|
112
|
-
mapping = {
|
|
113
|
-
|
|
114
|
-
"API_ERROR": ApiErrorError,
|
|
115
|
-
|
|
116
|
-
"AVATAR_NOT_FOUND": AvatarNotFoundError,
|
|
117
|
-
|
|
118
|
-
"CONVERSION_FAILED": ConversionFailedError,
|
|
119
|
-
|
|
120
|
-
"FILE_OPEN_ERROR": FileOpenErrorError,
|
|
121
|
-
|
|
122
|
-
"FILE_REQUIRED": FileRequiredError,
|
|
123
|
-
|
|
124
|
-
"INTERNAL_SERVER_ERROR": InternalServerErrorError,
|
|
125
|
-
|
|
126
|
-
"INVALID_PARAMETER": InvalidParameterError,
|
|
127
|
-
|
|
128
|
-
"INVALID_PARAMS": InvalidParamsError,
|
|
129
|
-
|
|
130
|
-
"NOT_FOUND": NotFoundError,
|
|
131
|
-
|
|
132
|
-
"NO_MATCH": NoMatchError,
|
|
133
|
-
|
|
134
|
-
"NO_TRACKING_DATA": NoTrackingDataError,
|
|
135
|
-
|
|
136
|
-
"PHONE_INFO_FAILED": PhoneInfoFailedError,
|
|
137
|
-
|
|
138
|
-
"RECOGNITION_FAILED": RecognitionFailedError,
|
|
139
|
-
|
|
140
|
-
"REQUEST_ENTITY_TOO_LARGE": RequestEntityTooLargeError,
|
|
141
|
-
|
|
142
|
-
"SERVICE_BUSY": ServiceBusyError,
|
|
143
|
-
|
|
144
|
-
"TIMEZONE_NOT_FOUND": TimezoneNotFoundError,
|
|
145
|
-
|
|
146
|
-
"UNAUTHORIZED": UnauthorizedError,
|
|
147
|
-
|
|
148
|
-
"UNSUPPORTED_CARRIER": UnsupportedCarrierError,
|
|
149
|
-
|
|
150
|
-
"UNSUPPORTED_FORMAT": UnsupportedFormatError,
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return mapping.get(c) or ( {400: InvalidParameterError, 401: UnauthorizedError, 404: NotFoundError, 429: ServiceBusyError, 500: InternalServerErrorError}.get(status) or UapiError )
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{uapi_sdk_python-0.1.10 → uapi_sdk_python-0.1.14}/uapi_sdk_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|