quantdb-sdk 0.4.6__tar.gz → 0.4.8__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.
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/CHANGELOG.md +28 -0
- {quantdb_sdk-0.4.6/quantdb_sdk.egg-info → quantdb_sdk-0.4.8}/PKG-INFO +1 -1
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/pyproject.toml +1 -1
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/__main__.py +36 -10
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/async_client.py +5 -6
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/client.py +11 -17
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8/quantdb_sdk.egg-info}/PKG-INFO +1 -1
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/SOURCES.txt +1 -0
- quantdb_sdk-0.4.8/tests/test_cli_encoding.py +92 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/LICENSE +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/MANIFEST.in +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/README.md +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/__init__.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/_utils.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/errors.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/__init__.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_01_connect_basics.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_02_kline_v2_daily.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_03_sync_full_market.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_04_duckdb_warehouse.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_05_read_local_data.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_06_tick_and_factors.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_07_data_hub.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/hub.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/layout.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/mcp_download.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk/py.typed +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/dependency_links.txt +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/entry_points.txt +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/requires.txt +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/top_level.txt +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/setup.cfg +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_async_client.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_client.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_data_hub.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_hub_models.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_layout.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_mcp_download.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_technical_indicators.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_update_pipeline.py +0 -0
- {quantdb_sdk-0.4.6 → quantdb_sdk-0.4.8}/tests/test_utils.py +0 -0
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
所有 notable 变更都会记录在此文件。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## \[0.4.8] - 2026-10-27
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **`quantdb usage` 在中文 Windows 控制台崩溃**:`余额` 一行输出的是窄人民币符号 U+00A5,而 Windows 中文控制台默认编码是 GBK/cp936,**GBK 中不存在该字符**(GBK 用 0x80 表示),`print` 抛 `UnicodeEncodeError: 'gbk' codec can't encode character '\xa5'`,命令以异常收场、后续输出(订阅状态等)全部丢失。
|
|
11
|
+
修复两处:① 金额改用**全角人民币符号 U+FFE5**(GBK 可编码,中文终端显示正常);② CLI 入口新增 `_configure_stdout()`,把 stdout/stderr 的编码错误处理降级为 `replace`——以后任何不可编码字符只会显示为 `?`,不再中断命令。
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- 新增回归测试 `tests/test_cli_encoding.py`(仓库根 `tests/`,不进 wheel):
|
|
16
|
+
- 扫描 SDK 全部源码的**字符串字面量**,断言每个字符都能被 GBK 编码——防止再次引入不可编码字符;
|
|
17
|
+
- 在 GBK 编码的 stdout 下渲染用量输出,断言不抛异常;
|
|
18
|
+
- 断言 `_configure_stdout()` 确实把编码错误处理改为 `replace`。
|
|
19
|
+
|
|
20
|
+
## \[0.4.7] - 2026-10-27
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **适配下载 302 单跳直连**:服务端已取消「一次性下载令牌 + `/api/v1/data/cdn-bridge` 中转」链路,`/api/v1/data/download` 预扣成功后直接 302 到 CDN 签名 URL(单跳)。同步/异步客户端原有的两跳跟随假设已相应调整:
|
|
25
|
+
- 新增 **CDN 专用裸会话**(`QuantDBClient._cdn_session`):直连 CDN 时**不携带 API Key / JWT**,避免鉴权头泄露到 CDN 域;同一会话跨文件复用 TLS 连接,避免逐文件新建连接反复握手。
|
|
26
|
+
- 手动跟随重定向并保留每跳的 connect timeout 重试,网络抖动不会中断整个同步。
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- 直连 CDN 模式**签发即按对象大小计费、不再退款**(服务端无法观测直连传输的真实字节,退款只能依赖客户端自报,不可信)。需要按真实传输计费、断点续传可退款的用户,应在请求头发送 `X-QuantDB-Delivery: proxy` 走网关代理模式(网关可观测字节并按未发送部分退款)。
|
|
31
|
+
|
|
32
|
+
> 兼容性:0.4.7 对接 2026-10-27 上线的服务端版本;旧服务端(两跳 cdn-bridge)仍可正常同步,无需回退。
|
|
33
|
+
|
|
6
34
|
## \[0.4.6] - 2026-10-27
|
|
7
35
|
|
|
8
36
|
### Fixed
|
|
@@ -63,7 +63,42 @@ def cmd_datasets(name: Optional[str] = None) -> int:
|
|
|
63
63
|
return 0
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
def _configure_stdout() -> None:
|
|
67
|
+
"""让 CLI 在非 UTF-8 控制台(中文 Windows 默认 GBK/cp936)下不因不可编码字符崩溃。
|
|
68
|
+
|
|
69
|
+
Windows 中文控制台的默认编码是 GBK,而窄人民币符号 U+00A5 在 GBK 中**不存在**
|
|
70
|
+
(GBK 用 0x80 表示该符号),直接 print 会抛
|
|
71
|
+
``UnicodeEncodeError: 'gbk' codec can't encode character '\\xa5'``,
|
|
72
|
+
导致整条命令以异常收场、后续输出全部丢失。
|
|
73
|
+
|
|
74
|
+
这里把 stdout/stderr 的编码错误处理改成 replace:无法编码的字符降级为 '?',
|
|
75
|
+
命令本身不再中断。配合正文中改用全角 ¥(U+FFE5,GBK 可编码),
|
|
76
|
+
中文 Windows 终端可正常显示金额。
|
|
77
|
+
"""
|
|
78
|
+
for stream in (sys.stdout, sys.stderr):
|
|
79
|
+
try:
|
|
80
|
+
stream.reconfigure(errors="replace") # type: ignore[attr-defined]
|
|
81
|
+
except Exception: # pragma: no cover - 老版本/非 TextIOWrapper 忽略
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def print_usage(usage: dict) -> None:
|
|
86
|
+
"""打印账户用量(抽成独立函数便于单测覆盖非 UTF-8 控制台场景)。"""
|
|
87
|
+
print("账户用量:")
|
|
88
|
+
print(f" 套餐: 已用 {usage['plan_used_gb']:.2f} GB / {usage['plan_total_gb']:.2f} GB"
|
|
89
|
+
f"(剩余 {usage['plan_remaining_gb']:.2f} GB)")
|
|
90
|
+
print(f" 流量包: 已用 {usage['package_used_gb']:.2f} GB / {usage['package_total_gb']:.2f} GB"
|
|
91
|
+
f"(剩余 {usage['package_remaining_gb']:.2f} GB)")
|
|
92
|
+
print(f" 合计: 已用 {usage['used_gb']:.2f} GB / {usage['total_gb']:.2f} GB"
|
|
93
|
+
f"(剩余 {usage['remaining_gb']:.2f} GB)")
|
|
94
|
+
# 全角人民币符号 U+FFE5:GBK 可编码;窄符号 U+00A5 在 GBK 中不存在会崩溃
|
|
95
|
+
print(f" 余额: ¥{usage['balance_yuan']:.2f}")
|
|
96
|
+
sub = usage.get("subscription", {})
|
|
97
|
+
print(f" 订阅: {sub.get('status', 'none')}")
|
|
98
|
+
|
|
99
|
+
|
|
66
100
|
def main() -> int:
|
|
101
|
+
_configure_stdout()
|
|
67
102
|
parser = argparse.ArgumentParser(
|
|
68
103
|
prog="quantdb",
|
|
69
104
|
description="QuantDB 量化数据平台官方 Python SDK",
|
|
@@ -158,16 +193,7 @@ def main() -> int:
|
|
|
158
193
|
|
|
159
194
|
if args.command == "usage":
|
|
160
195
|
usage = client.get_usage()
|
|
161
|
-
|
|
162
|
-
print(f" 套餐: 已用 {usage['plan_used_gb']:.2f} GB / {usage['plan_total_gb']:.2f} GB"
|
|
163
|
-
f"(剩余 {usage['plan_remaining_gb']:.2f} GB)")
|
|
164
|
-
print(f" 流量包: 已用 {usage['package_used_gb']:.2f} GB / {usage['package_total_gb']:.2f} GB"
|
|
165
|
-
f"(剩余 {usage['package_remaining_gb']:.2f} GB)")
|
|
166
|
-
print(f" 合计: 已用 {usage['used_gb']:.2f} GB / {usage['total_gb']:.2f} GB"
|
|
167
|
-
f"(剩余 {usage['remaining_gb']:.2f} GB)")
|
|
168
|
-
print(f" 余额: ¥{usage['balance_yuan']:.2f}")
|
|
169
|
-
sub = usage.get("subscription", {})
|
|
170
|
-
print(f" 订阅: {sub.get('status', 'none')}")
|
|
196
|
+
print_usage(usage)
|
|
171
197
|
return 0
|
|
172
198
|
|
|
173
199
|
if args.command == "sync":
|
|
@@ -28,7 +28,7 @@ from .errors import (
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
# 维护提示:每次发版必须与 pyproject.toml 版本号同步
|
|
31
|
-
_USER_AGENT = "QuantDB-Python-SDK/0.4.
|
|
31
|
+
_USER_AGENT = "QuantDB-Python-SDK/0.4.8"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def _etag_hex(value: Optional[str]) -> Optional[str]:
|
|
@@ -139,8 +139,8 @@ class AsyncQuantDBClient:
|
|
|
139
139
|
裸客户端直连 Location,鉴权头不会泄露给 CDN 域。
|
|
140
140
|
|
|
141
141
|
重定向链路(CDN 直连模式):
|
|
142
|
-
/download 302→
|
|
143
|
-
|
|
142
|
+
/download 302→ https://cdn.xxx/key?sign=...(单跳)
|
|
143
|
+
手动跟随,每跳带重试(connect timeout 网络抖动容错)。
|
|
144
144
|
"""
|
|
145
145
|
async with self.client.stream(
|
|
146
146
|
"GET", f"{self.api_host}/api/v1/data/download", params=params, headers=headers
|
|
@@ -166,9 +166,8 @@ class AsyncQuantDBClient:
|
|
|
166
166
|
) -> httpx.Response:
|
|
167
167
|
"""手动跟随 302 重定向链路,每跳带重试。
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
sync 中断。
|
|
169
|
+
CDN 直连请求通过此方法,connect timeout 网络抖动时自动重试,避免
|
|
170
|
+
单次抖动导致整个 sync 中断。
|
|
172
171
|
|
|
173
172
|
返回已进入 stream 模式的 httpx.Response,调用方需用 async with 管理。
|
|
174
173
|
"""
|
|
@@ -32,7 +32,7 @@ from .errors import (
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
# 维护提示:每次发版必须与 pyproject.toml 版本号同步
|
|
35
|
-
_USER_AGENT = "QuantDB-Python-SDK/0.4.
|
|
35
|
+
_USER_AGENT = "QuantDB-Python-SDK/0.4.8"
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _etag_hex(value: Optional[str]) -> Optional[str]:
|
|
@@ -75,6 +75,9 @@ class QuantDBClient:
|
|
|
75
75
|
self.headers: Dict[str, str] = {}
|
|
76
76
|
self.session = requests.Session()
|
|
77
77
|
self.session.headers.update({"User-Agent": _USER_AGENT})
|
|
78
|
+
# CDN 直连专用会话:不携带 API Key / JWT,且跨文件复用 TLS 连接(逐文件新建连接会反复握手)。
|
|
79
|
+
self._cdn_session = requests.Session()
|
|
80
|
+
self._cdn_session.headers.update({"User-Agent": _USER_AGENT})
|
|
78
81
|
|
|
79
82
|
if api_key:
|
|
80
83
|
self.headers = {"X-API-Key": api_key}
|
|
@@ -168,13 +171,10 @@ class QuantDBClient:
|
|
|
168
171
|
def _download_stream(self, params: dict, headers: Optional[dict] = None) -> requests.Response:
|
|
169
172
|
"""下载专用流式请求:手动处理服务端 302 CDN 直连跳转。
|
|
170
173
|
|
|
171
|
-
服务端开启 CDN 直连后,/api/v1/data/download
|
|
172
|
-
302 -> CDN 签名 URL
|
|
173
|
-
透传给 CDN
|
|
174
|
-
|
|
175
|
-
重定向链路(CDN 直连模式):
|
|
176
|
-
/download 302→ /cdn-bridge?token=xxx 302→ https://cdn.xxx/key?sign=...
|
|
177
|
-
两跳均手动跟随,每跳带重试(connect timeout 网络抖动容错)。
|
|
174
|
+
服务端开启 CDN 直连后,/api/v1/data/download 预扣流量成功时直接返回
|
|
175
|
+
302 -> CDN 签名 URL(单跳)。requests 自动跟随会把自定义 X-API-Key 头
|
|
176
|
+
透传给 CDN 域,故禁用自动跟随,改用不带鉴权头的 CDN 专用会话直连。
|
|
177
|
+
每跳带重试(connect timeout 网络抖动容错)。
|
|
178
178
|
"""
|
|
179
179
|
resp = self._request(
|
|
180
180
|
"GET", "/api/v1/data/download", params=params, stream=True,
|
|
@@ -200,21 +200,15 @@ class QuantDBClient:
|
|
|
200
200
|
) -> requests.Response:
|
|
201
201
|
"""手动跟随 302 重定向链路,每跳带重试。
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
sync 中断。
|
|
203
|
+
CDN 直连请求通过此方法,connect timeout 网络抖动时自动重试,避免
|
|
204
|
+
单次抖动导致整个 sync 中断。
|
|
206
205
|
"""
|
|
207
206
|
current_etag = etag
|
|
208
207
|
for _ in range(max_hops):
|
|
209
208
|
last_exc = None
|
|
210
209
|
for attempt in range(retries + 1):
|
|
211
210
|
try:
|
|
212
|
-
cdn_resp =
|
|
213
|
-
url,
|
|
214
|
-
stream=True,
|
|
215
|
-
timeout=self.timeout,
|
|
216
|
-
headers={"User-Agent": _USER_AGENT},
|
|
217
|
-
)
|
|
211
|
+
cdn_resp = self._cdn_session.get(url, stream=True, timeout=self.timeout)
|
|
218
212
|
break
|
|
219
213
|
except (requests.ConnectionError, requests.Timeout) as exc:
|
|
220
214
|
last_exc = exc
|
|
@@ -28,6 +28,7 @@ quantdb_sdk/examples/demo_05_read_local_data.py
|
|
|
28
28
|
quantdb_sdk/examples/demo_06_tick_and_factors.py
|
|
29
29
|
quantdb_sdk/examples/demo_07_data_hub.py
|
|
30
30
|
tests/test_async_client.py
|
|
31
|
+
tests/test_cli_encoding.py
|
|
31
32
|
tests/test_client.py
|
|
32
33
|
tests/test_data_hub.py
|
|
33
34
|
tests/test_hub_models.py
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""CLI 输出编码回归测试。
|
|
2
|
+
|
|
3
|
+
背景:Windows 中文控制台默认编码是 GBK/cp936,而 U+00A5(窄人民币符号)在 GBK
|
|
4
|
+
中不存在,`print` 会抛 UnicodeEncodeError,导致命令以异常收场、后续输出全部丢失
|
|
5
|
+
(0.4.8 修复:改用全角 U+FFE5 + stdout 错误处理降级为 replace)。
|
|
6
|
+
|
|
7
|
+
本文件放在仓库根的 tests/ 下(不在 quantdb_sdk 包内),不会被打进 wheel。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
import pathlib
|
|
12
|
+
import sys
|
|
13
|
+
import tokenize
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
SDK_DIR = pathlib.Path(__file__).resolve().parents[1] / "quantdb_sdk"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _string_literal_chars(path: pathlib.Path):
|
|
21
|
+
"""收集源码中所有字符串字面量(含 docstring)里出现过的字符。"""
|
|
22
|
+
source = path.read_text(encoding="utf-8")
|
|
23
|
+
chars = set()
|
|
24
|
+
for token in tokenize.generate_tokens(io.StringIO(source).readline):
|
|
25
|
+
if token.type == tokenize.STRING:
|
|
26
|
+
chars.update(token.string)
|
|
27
|
+
return chars
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_sdk_string_literals_are_gbk_encodable():
|
|
31
|
+
"""所有会被打印的字符串都必须能被 GBK 编码,否则中文 Windows 控制台会崩溃。"""
|
|
32
|
+
offenders = []
|
|
33
|
+
for path in sorted(SDK_DIR.rglob("*.py")):
|
|
34
|
+
for ch in _string_literal_chars(path):
|
|
35
|
+
try:
|
|
36
|
+
ch.encode("gbk")
|
|
37
|
+
except UnicodeEncodeError:
|
|
38
|
+
offenders.append((path.relative_to(SDK_DIR.parent).as_posix(), hex(ord(ch))))
|
|
39
|
+
assert not offenders, (
|
|
40
|
+
"以下字符在 GBK 控制台无法编码(会崩溃 CLI 输出): "
|
|
41
|
+
+ ", ".join(f"{f}:{cp}" for f, cp in offenders)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_print_usage_under_gbk_stdout(monkeypatch, capsys):
|
|
46
|
+
"""在 GBK 编码的 stdout 下打印用量,不得抛 UnicodeEncodeError。"""
|
|
47
|
+
from quantdb_sdk.__main__ import print_usage
|
|
48
|
+
|
|
49
|
+
buffer = io.BytesIO()
|
|
50
|
+
gbk_stdout = io.TextIOWrapper(buffer, encoding="gbk", errors="strict")
|
|
51
|
+
monkeypatch.setattr(sys, "stdout", gbk_stdout)
|
|
52
|
+
|
|
53
|
+
print_usage({
|
|
54
|
+
"plan_used_gb": 50.0, "plan_total_gb": 50.0, "plan_remaining_gb": 0.0,
|
|
55
|
+
"package_used_gb": 15.8, "package_total_gb": 110.0, "package_remaining_gb": 94.2,
|
|
56
|
+
"used_gb": 50.0, "total_gb": 160.0, "remaining_gb": 94.2,
|
|
57
|
+
"balance_yuan": 12.34,
|
|
58
|
+
"subscription": {"status": "active"},
|
|
59
|
+
})
|
|
60
|
+
gbk_stdout.flush()
|
|
61
|
+
|
|
62
|
+
out = buffer.getvalue().decode("gbk")
|
|
63
|
+
assert "账户用量" in out
|
|
64
|
+
assert "12.34" in out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_configure_stdout_downgrades_encoding_errors(monkeypatch):
|
|
68
|
+
"""_configure_stdout 应把 stdout/stderr 的编码错误处理改为 replace。"""
|
|
69
|
+
from quantdb_sdk.__main__ import _configure_stdout
|
|
70
|
+
|
|
71
|
+
buffer = io.BytesIO()
|
|
72
|
+
stream = io.TextIOWrapper(buffer, encoding="gbk", errors="strict")
|
|
73
|
+
monkeypatch.setattr(sys, "stdout", stream)
|
|
74
|
+
monkeypatch.setattr(sys, "stderr", stream)
|
|
75
|
+
|
|
76
|
+
_configure_stdout()
|
|
77
|
+
|
|
78
|
+
assert stream.errors == "replace"
|
|
79
|
+
# 不可编码字符应降级为 '?' 而不是抛异常
|
|
80
|
+
stream.write("\u00a5")
|
|
81
|
+
stream.flush()
|
|
82
|
+
assert "?" in buffer.getvalue().decode("gbk")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_main_version_smoke():
|
|
86
|
+
"""--version 走 argparse 的 version action,不应因控制台编码崩溃。"""
|
|
87
|
+
from quantdb_sdk.__main__ import main
|
|
88
|
+
|
|
89
|
+
with pytest.raises(SystemExit) as exc:
|
|
90
|
+
sys.argv = ["quantdb", "--version"]
|
|
91
|
+
main()
|
|
92
|
+
assert exc.value.code == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|