quantdb-sdk 0.2.0__tar.gz → 0.2.1__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.2.0 → quantdb_sdk-0.2.1}/CHANGELOG.md +6 -0
- {quantdb_sdk-0.2.0/quantdb_sdk.egg-info → quantdb_sdk-0.2.1}/PKG-INFO +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/pyproject.toml +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/__init__.py +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/async_client.py +7 -2
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/client.py +7 -2
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1/quantdb_sdk.egg-info}/PKG-INFO +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/tests/test_async_client.py +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/tests/test_client.py +1 -1
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/LICENSE +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/MANIFEST.in +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/README.md +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/__main__.py +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/_utils.py +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/errors.py +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk/py.typed +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk.egg-info/SOURCES.txt +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk.egg-info/dependency_links.txt +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk.egg-info/entry_points.txt +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk.egg-info/requires.txt +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/quantdb_sdk.egg-info/top_level.txt +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/setup.cfg +0 -0
- {quantdb_sdk-0.2.0 → quantdb_sdk-0.2.1}/tests/test_technical_indicators.py +0 -0
|
@@ -3,6 +3,12 @@
|
|
|
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.2.1] - 2026-07-26
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **历史发布清单兼容**:同步客户端自动归一化旧 release manifest 遗留的 `v2/` 前缀,并可下载 release patch 对象。
|
|
10
|
+
- **布局回退**:服务端以 COS Manifest 为可用性权威,V2 元数据缺失时可正确回退 V1,而非将数据误判为不可下载。
|
|
11
|
+
|
|
6
12
|
## [0.2.0] - 2026-07-26
|
|
7
13
|
|
|
8
14
|
### Added
|
|
@@ -37,7 +37,7 @@ class AsyncQuantDBClient:
|
|
|
37
37
|
):
|
|
38
38
|
self.api_host = api_host.rstrip("/")
|
|
39
39
|
self.timeout = timeout
|
|
40
|
-
headers = {"User-Agent": "QuantDB-Python-SDK/0.2.
|
|
40
|
+
headers = {"User-Agent": "QuantDB-Python-SDK/0.2.1"}
|
|
41
41
|
if api_key:
|
|
42
42
|
headers["X-API-Key"] = api_key
|
|
43
43
|
elif token:
|
|
@@ -66,6 +66,10 @@ class AsyncQuantDBClient:
|
|
|
66
66
|
raise ValidationError("layout 仅支持 auto、v1 或 v2")
|
|
67
67
|
return layout # type: ignore[return-value]
|
|
68
68
|
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _normalise_release_key(key: str) -> str:
|
|
71
|
+
return key.strip().lstrip("/").removeprefix("v2/")
|
|
72
|
+
|
|
69
73
|
@staticmethod
|
|
70
74
|
def _normalise_kline(df: pd.DataFrame, start_date: Optional[str], end_date: Optional[str], fields: str, limit: Optional[int]) -> pd.DataFrame:
|
|
71
75
|
if "time" in df.columns:
|
|
@@ -544,7 +548,8 @@ class AsyncQuantDBClient:
|
|
|
544
548
|
if releases:
|
|
545
549
|
for release in releases:
|
|
546
550
|
for obj in release.get("objects", []):
|
|
547
|
-
key
|
|
551
|
+
key = self._normalise_release_key(obj["key"])
|
|
552
|
+
target = os.path.join(root, *key.split("/"))
|
|
548
553
|
old = state.execute("SELECT etag,sha256,path FROM objects WHERE key=?", (key,)).fetchone()
|
|
549
554
|
if old and old[0] == obj.get("etag") and old[1] == obj.get("sha256") and os.path.exists(old[2]): continue
|
|
550
555
|
os.makedirs(os.path.dirname(target), exist_ok=True)
|
|
@@ -48,7 +48,7 @@ class QuantDBClient:
|
|
|
48
48
|
self.session = requests.Session()
|
|
49
49
|
# User-Agent 中的版本与 pyproject.toml 同步,用于服务端日志归因
|
|
50
50
|
# 维护提示:每次版本号变化必须同步改这里(init 里的 __version__ 走 metadata 自动同步)
|
|
51
|
-
self.session.headers.update({"User-Agent": "QuantDB-Python-SDK/0.2.
|
|
51
|
+
self.session.headers.update({"User-Agent": "QuantDB-Python-SDK/0.2.1"})
|
|
52
52
|
|
|
53
53
|
if api_key:
|
|
54
54
|
self.headers = {"X-API-Key": api_key}
|
|
@@ -164,6 +164,11 @@ class QuantDBClient:
|
|
|
164
164
|
raise ValidationError("layout 仅支持 auto、v1 或 v2")
|
|
165
165
|
return layout # type: ignore[return-value]
|
|
166
166
|
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _normalise_release_key(key: str) -> str:
|
|
169
|
+
"""兼容历史 release manifest 遗留的 ``v2/`` 对象前缀。"""
|
|
170
|
+
return key.strip().lstrip("/").removeprefix("v2/")
|
|
171
|
+
|
|
167
172
|
@staticmethod
|
|
168
173
|
def _normalise_kline(df: pd.DataFrame, start_date: Optional[str], end_date: Optional[str], fields: str, limit: Optional[int]) -> pd.DataFrame:
|
|
169
174
|
"""统一 V1/V2 日线结果并在客户端按标的、日期与字段过滤。"""
|
|
@@ -641,7 +646,7 @@ class QuantDBClient:
|
|
|
641
646
|
for release in releases:
|
|
642
647
|
release_id = release["release_id"]
|
|
643
648
|
for obj in release.get("objects", []):
|
|
644
|
-
key = obj["key"]
|
|
649
|
+
key = self._normalise_release_key(obj["key"])
|
|
645
650
|
relative_path = obj.get("relative_path") or key
|
|
646
651
|
target = os.path.join(root, *relative_path.split("/"))
|
|
647
652
|
old = state.execute("SELECT etag, sha256, path FROM objects WHERE key=?", (key,)).fetchone()
|
|
@@ -55,7 +55,7 @@ async def test_a_query_kline_without_range_forces_v1_layout():
|
|
|
55
55
|
async def test_a_sync_dataset_uses_release_cursor(tmp_path):
|
|
56
56
|
payload = b"release-parquet"
|
|
57
57
|
digest = __import__("hashlib").sha256(payload).hexdigest()
|
|
58
|
-
respx.get(f"{API_HOST}/api/v1/data/releases").mock(return_value=httpx.Response(200, json={"releases": [{"release_id": "20260726_180000", "objects": [{"key": "1_kline_data/daily_forward/dt=20260726/data.parquet", "etag": "etag-1", "sha256": digest}]}]}))
|
|
58
|
+
respx.get(f"{API_HOST}/api/v1/data/releases").mock(return_value=httpx.Response(200, json={"releases": [{"release_id": "20260726_180000", "objects": [{"key": "v2/1_kline_data/daily_forward/dt=20260726/data.parquet", "etag": "etag-1", "sha256": digest}]}]}))
|
|
59
59
|
route = respx.get(f"{API_HOST}/api/v1/data/download").mock(return_value=httpx.Response(200, content=payload))
|
|
60
60
|
async with AsyncQuantDBClient(api_host=API_HOST, api_key="test-key") as client:
|
|
61
61
|
result = await client.a_sync_dataset("daily_forward", str(tmp_path))
|
|
@@ -79,7 +79,7 @@ def test_sync_dataset_uses_release_cursor_and_object_key(tmp_path):
|
|
|
79
79
|
digest = __import__("hashlib").sha256(payload).hexdigest()
|
|
80
80
|
responses.get(
|
|
81
81
|
f"{API_HOST}/api/v1/data/releases",
|
|
82
|
-
json={"releases": [{"release_id": "20260726_180000", "objects": [{"key": "1_kline_data/daily_forward/dt=20260726/data.parquet", "etag": "etag-1", "sha256": digest}]}]}, status=200,
|
|
82
|
+
json={"releases": [{"release_id": "20260726_180000", "objects": [{"key": "v2/1_kline_data/daily_forward/dt=20260726/data.parquet", "etag": "etag-1", "sha256": digest}]}]}, status=200,
|
|
83
83
|
)
|
|
84
84
|
responses.get(f"{API_HOST}/api/v1/data/download", body=payload, status=200)
|
|
85
85
|
client = QuantDBClient(api_host=API_HOST, api_key="test-key")
|
|
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
|