datasinking 0.2.7__tar.gz → 0.2.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.
- {datasinking-0.2.7 → datasinking-0.2.8}/PKG-INFO +3 -2
- {datasinking-0.2.7 → datasinking-0.2.8}/README.md +1 -1
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking/__init__.py +1 -1
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking/_version.py +1 -1
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking/client.py +9 -10
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking/mcp_server.py +57 -28
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/PKG-INFO +3 -2
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/requires.txt +1 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/pyproject.toml +1 -1
- {datasinking-0.2.7 → datasinking-0.2.8}/LICENSE +0 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/SOURCES.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/dependency_links.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/entry_points.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/datasinking.egg-info/top_level.txt +0 -0
- {datasinking-0.2.7 → datasinking-0.2.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,7 @@ License-File: LICENSE
|
|
|
18
18
|
Provides-Extra: mcp
|
|
19
19
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
20
20
|
Requires-Dist: requests>=2.28; extra == "mcp"
|
|
21
|
+
Requires-Dist: pydantic>=2.0; extra == "mcp"
|
|
21
22
|
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# DataSinking
|
|
@@ -111,7 +112,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
112
|
|
|
112
113
|
Every example pulls from the live API and runs as-is.
|
|
113
114
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
115
|
+
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**, over a rolling 7-day window: a free key gets 3 req/s and 8,191 documents per 7 days, inside a pool of 524,287 per 7 days shared by all free users and website visitors. A whole exchange will therefore take well over a week on a free key — a **paid (yearly)** key (31 req/s, 524,287 documents per 7 days) is strongly recommended.
|
|
115
116
|
|
|
116
117
|
## Research (`research/`)
|
|
117
118
|
|
|
@@ -89,7 +89,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
89
89
|
|
|
90
90
|
Every example pulls from the live API and runs as-is.
|
|
91
91
|
|
|
92
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
92
|
+
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**, over a rolling 7-day window: a free key gets 3 req/s and 8,191 documents per 7 days, inside a pool of 524,287 per 7 days shared by all free users and website visitors. A whole exchange will therefore take well over a week on a free key — a **paid (yearly)** key (31 req/s, 524,287 documents per 7 days) is strongly recommended.
|
|
93
93
|
|
|
94
94
|
## Research (`research/`)
|
|
95
95
|
|
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
try:
|
|
12
12
|
ds.get_stock_reports("600519.SS", limit=-1)
|
|
13
13
|
except QuotaExceeded as e:
|
|
14
|
-
print(e.code, e) # e.g.
|
|
14
|
+
print(e.code, e) # e.g. quota_7d / free_quota_key / free_quota_global
|
|
15
15
|
"""
|
|
16
16
|
from ._version import __version__
|
|
17
17
|
from .client import DataSinking, QuotaExceeded
|
|
@@ -24,14 +24,13 @@ class QuotaExceeded(RuntimeError):
|
|
|
24
24
|
"""额度用尽(HTTP 429 + 服务端返回的 `code`)。
|
|
25
25
|
|
|
26
26
|
和「限流」不是一回事:限流等几秒就好(客户端自己会重试),
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
常见 code
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
free_quota_month 免费共享池 最近 31 天额度用尽(所有免费用户合计)
|
|
27
|
+
额度类要等**7 天窗口滚动**才有用 —— 重试没有意义,所以直接抛出来,不再重试。
|
|
28
|
+
|
|
29
|
+
常见 code(2026-09-21 起配额统一成「每 7 天 rolling」,日/月两层已取消):
|
|
30
|
+
quota_7d 年费 key 的 7 天额度用尽(每 7 天 524,287 篇)
|
|
31
|
+
free_quota_key 免费 key 的 7 天额度用尽(每 7 天 8,191 篇)
|
|
32
|
+
free_quota_global 免费共享池的 7 天额度用尽
|
|
33
|
+
(所有免费 key + 网页访客合计每 7 天 524,287 篇)
|
|
35
34
|
"""
|
|
36
35
|
|
|
37
36
|
def __init__(self, code=None, detail=None):
|
|
@@ -81,8 +80,8 @@ class DataSinking:
|
|
|
81
80
|
raise # 401/403/404/400… 直接抛给上层
|
|
82
81
|
code, detail = _error_info(e)
|
|
83
82
|
if code:
|
|
84
|
-
# 额度类 429
|
|
85
|
-
# (以前这里不分青红皂白 sleep(2)
|
|
83
|
+
# 额度类 429:要等 7 天窗口滚动,重试没有意义 —— 立刻抛,并带上服务端的原因。
|
|
84
|
+
# (以前这里不分青红皂白 sleep(2) 重试,额度打满时会空转,
|
|
86
85
|
# 最后还抛出一句 "Request failed after retries: None" —— last 变量
|
|
87
86
|
# 只在网络异常分支被赋值,429 分支根本不赋值。)
|
|
88
87
|
raise QuotaExceeded(code, detail)
|
|
@@ -19,9 +19,10 @@ https://datasink.ing).
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import os
|
|
22
|
-
from typing import Optional
|
|
22
|
+
from typing import Annotated, Optional
|
|
23
23
|
|
|
24
24
|
import requests
|
|
25
|
+
from pydantic import Field
|
|
25
26
|
|
|
26
27
|
from ._version import __version__ # 版本号唯一来源(原来是硬编码,漂到了 0.2.3)
|
|
27
28
|
|
|
@@ -31,6 +32,11 @@ try:
|
|
|
31
32
|
except ImportError: # pragma: no cover
|
|
32
33
|
from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
|
|
33
34
|
|
|
35
|
+
# ⚠️ 参数描述必须写成 `Annotated[T, Field(description=...)]`,**不能只靠 docstring 的 Args 段**。
|
|
36
|
+
# mcp v2(MCPServer)不再解析 docstring 的 Args —— 实测把整段 Args 当散文塞进工具描述,
|
|
37
|
+
# 参数级 description 全是空的(2026-09-22 用真实 stdio 握手验证)。v1 两种都认,所以这样写两边通用。
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
BASE_URL = "https://api.datasink.ing"
|
|
35
41
|
API_KEY = os.environ.get("DATASINK_API_KEY", "")
|
|
36
42
|
|
|
@@ -66,64 +72,87 @@ def _get(path: str, params: Optional[dict] = None) -> dict:
|
|
|
66
72
|
def list_exchanges() -> list:
|
|
67
73
|
"""List the exchanges DataSinking covers and their report counts.
|
|
68
74
|
|
|
69
|
-
Returns exchange codes (sse / szse / bj / ksc / koe / knx / jpx) with
|
|
70
|
-
of reports available per exchange. Call this first to discover coverage.
|
|
75
|
+
Returns exchange codes (sse / szse / bj / ksc / koe / knx / jpx / twse / tpex) with
|
|
76
|
+
the number of reports available per exchange. Call this first to discover coverage.
|
|
77
|
+
Sources: A-shares = cninfo.com.cn, Korea = DART, Japan = EDINET, Taiwan = MOPS.
|
|
71
78
|
"""
|
|
72
79
|
return _get("/exchanges").get("exchanges", [])
|
|
73
80
|
|
|
74
81
|
|
|
75
82
|
@mcp.tool()
|
|
76
|
-
def list_stocks(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
def list_stocks(
|
|
84
|
+
exchange: Annotated[
|
|
85
|
+
str, Field(description="Exchange code: sse / szse / bj / ksc / koe / knx / jpx / twse / tpex")
|
|
86
|
+
],
|
|
87
|
+
limit: Annotated[
|
|
88
|
+
int, Field(description="Return only the first N companies (default 20) to keep the response short.")
|
|
89
|
+
] = 20,
|
|
90
|
+
) -> dict:
|
|
91
|
+
"""List stocks on an exchange, including the report count per company."""
|
|
83
92
|
data = _get("/stocks", {"exchange": exchange})
|
|
84
93
|
return {"exchange": exchange, "total": data.get("total", 0), "items": data.get("items", [])[:limit]}
|
|
85
94
|
|
|
86
95
|
|
|
87
96
|
@mcp.tool()
|
|
88
|
-
def list_reports(
|
|
97
|
+
def list_reports(
|
|
98
|
+
symbol: Annotated[
|
|
99
|
+
str, Field(description="FMP-style symbol, e.g. 600519.SS / 005930.KS / 7203.T / 2330.TW")
|
|
100
|
+
],
|
|
101
|
+
doc_type: Annotated[
|
|
102
|
+
str, Field(description="annual / semiannual / q1 / q3")
|
|
103
|
+
] = "annual",
|
|
104
|
+
size: Annotated[int, Field(description="Number of reports to return (default 10).")] = 10,
|
|
105
|
+
) -> dict:
|
|
89
106
|
"""List a company's reports — metadata only (id, title, period), no body text.
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
doc_type: annual / semiannual / q1 / q3
|
|
94
|
-
size: Number of reports to return (default 10).
|
|
108
|
+
Each item carries a ``source`` field naming the official disclosure platform;
|
|
109
|
+
keep that attribution when you cite it.
|
|
95
110
|
"""
|
|
96
111
|
return _get("/documents", {"symbol": symbol, "doc_type": doc_type, "size": size})
|
|
97
112
|
|
|
98
113
|
|
|
99
114
|
@mcp.tool()
|
|
100
|
-
def get_report(
|
|
115
|
+
def get_report(
|
|
116
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
117
|
+
) -> dict:
|
|
101
118
|
"""Fetch a single report's full text (metadata + Markdown body).
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
The ``source`` field names the official disclosure platform; keep that attribution
|
|
121
|
+
when you cite it. Expensive in tokens — prefer get_section when you only need one chapter.
|
|
105
122
|
"""
|
|
106
123
|
return _get(f"/documents/{document_id}")
|
|
107
124
|
|
|
108
125
|
|
|
109
126
|
@mcp.tool()
|
|
110
|
-
def list_sections(
|
|
127
|
+
def list_sections(
|
|
128
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
129
|
+
) -> dict:
|
|
111
130
|
"""List every section heading in a report (feed the headings to get_section).
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
Call this before get_section to see the exact headings — the headings are in the
|
|
133
|
+
report's own language.
|
|
115
134
|
"""
|
|
116
135
|
return _get(f"/documents/{document_id}/sections")
|
|
117
136
|
|
|
118
137
|
|
|
119
138
|
@mcp.tool()
|
|
120
|
-
def get_section(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
def get_section(
|
|
140
|
+
document_id: Annotated[int, Field(description="Report id, from list_reports items[].id")],
|
|
141
|
+
section: Annotated[
|
|
142
|
+
str,
|
|
143
|
+
Field(
|
|
144
|
+
description=(
|
|
145
|
+
"Heading keyword, matched as a substring against the report's OWN headings, "
|
|
146
|
+
"so pass it in the report's language. A-share reports have Chinese headings "
|
|
147
|
+
"(e.g. 第三节管理层讨论与分析) — use 管理层讨论与分析 / 财务报告 there. "
|
|
148
|
+
"For English-language filings, \"MD&A\" / \"financial statements\" / \"notes\" work. "
|
|
149
|
+
"If nothing matches, the API returns 404 with the real headings — retry with one "
|
|
150
|
+
"of those, or call list_sections first."
|
|
151
|
+
)
|
|
152
|
+
),
|
|
153
|
+
],
|
|
154
|
+
) -> dict:
|
|
155
|
+
"""Fetch only one section of a report by keyword — cheaper than get_report for RAG."""
|
|
127
156
|
return _get(f"/documents/{document_id}", {"section": section})
|
|
128
157
|
|
|
129
158
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datasinking
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
|
|
5
5
|
Author: DataSinking
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,7 @@ License-File: LICENSE
|
|
|
18
18
|
Provides-Extra: mcp
|
|
19
19
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
20
20
|
Requires-Dist: requests>=2.28; extra == "mcp"
|
|
21
|
+
Requires-Dist: pydantic>=2.0; extra == "mcp"
|
|
21
22
|
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# DataSinking
|
|
@@ -111,7 +112,7 @@ coverage, list a company's reports, and extract a figure with correct units.
|
|
|
111
112
|
|
|
112
113
|
Every example pulls from the live API and runs as-is.
|
|
113
114
|
|
|
114
|
-
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**,
|
|
115
|
+
> `03_download_exchange.py` fetches every report on an exchange (e.g. all of Shenzhen — 150k+ documents). Quotas count **documents, not requests**, over a rolling 7-day window: a free key gets 3 req/s and 8,191 documents per 7 days, inside a pool of 524,287 per 7 days shared by all free users and website visitors. A whole exchange will therefore take well over a week on a free key — a **paid (yearly)** key (31 req/s, 524,287 documents per 7 days) is strongly recommended.
|
|
115
116
|
|
|
116
117
|
## Research (`research/`)
|
|
117
118
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|