quantdb-sdk 0.4.7__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.7 → quantdb_sdk-0.4.8}/CHANGELOG.md +14 -0
- {quantdb_sdk-0.4.7/quantdb_sdk.egg-info → quantdb_sdk-0.4.8}/PKG-INFO +1 -1
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/pyproject.toml +1 -1
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/__main__.py +36 -10
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/async_client.py +1 -1
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/client.py +1 -1
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8/quantdb_sdk.egg-info}/PKG-INFO +1 -1
- {quantdb_sdk-0.4.7 → 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.7 → quantdb_sdk-0.4.8}/LICENSE +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/MANIFEST.in +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/README.md +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/__init__.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/_utils.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/errors.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/__init__.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_01_connect_basics.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_02_kline_v2_daily.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_03_sync_full_market.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_04_duckdb_warehouse.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_05_read_local_data.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_06_tick_and_factors.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/examples/demo_07_data_hub.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/hub.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/layout.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/mcp_download.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk/py.typed +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/dependency_links.txt +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/entry_points.txt +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/requires.txt +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/quantdb_sdk.egg-info/top_level.txt +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/setup.cfg +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_async_client.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_client.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_data_hub.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_hub_models.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_layout.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_mcp_download.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_technical_indicators.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_update_pipeline.py +0 -0
- {quantdb_sdk-0.4.7 → quantdb_sdk-0.4.8}/tests/test_utils.py +0 -0
|
@@ -3,6 +3,20 @@
|
|
|
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
|
+
|
|
6
20
|
## \[0.4.7] - 2026-10-27
|
|
7
21
|
|
|
8
22
|
### 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,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
|