tsshare 1.0.0__py3-none-any.whl
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.
- tsshare/__init__.py +19 -0
- tsshare/client.py +417 -0
- tsshare-1.0.0.dist-info/METADATA +69 -0
- tsshare-1.0.0.dist-info/RECORD +7 -0
- tsshare-1.0.0.dist-info/WHEEL +5 -0
- tsshare-1.0.0.dist-info/licenses/LICENSE +21 -0
- tsshare-1.0.0.dist-info/top_level.txt +1 -0
tsshare/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""tsshare SDK
|
|
2
|
+
|
|
3
|
+
用法示例(tinyshare 风格,只需改一行代码):
|
|
4
|
+
|
|
5
|
+
import tsshare as ts
|
|
6
|
+
ts.set_token("your-auth-code")
|
|
7
|
+
pro = ts.pro_api()
|
|
8
|
+
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201")
|
|
9
|
+
|
|
10
|
+
也支持显式传参风格:
|
|
11
|
+
|
|
12
|
+
import tsshare as ts
|
|
13
|
+
pro = ts.pro_api("your-auth-code", base_url="http://localhost:8000")
|
|
14
|
+
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .client import MyShareClient, pro_api, set_token, set_base_url
|
|
18
|
+
|
|
19
|
+
__all__ = ["MyShareClient", "pro_api", "set_token", "set_base_url"]
|
tsshare/client.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""myshare 客户端 SDK
|
|
2
|
+
|
|
3
|
+
提供与 tushare.pro_api 类似的调用方式:
|
|
4
|
+
|
|
5
|
+
import myshare as ts
|
|
6
|
+
ts.set_token("your-auth-code")
|
|
7
|
+
pro = ts.pro_api()
|
|
8
|
+
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201")
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import base64 as _b64
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
18
|
+
import subprocess
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable, Optional
|
|
21
|
+
|
|
22
|
+
import requests
|
|
23
|
+
from requests.adapters import HTTPAdapter
|
|
24
|
+
from urllib3.util.retry import Retry
|
|
25
|
+
import pandas as pd
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# -------------------- 默认网关(混淆存储,防止字符串搜索直接提取)--------------------
|
|
30
|
+
# 以下常量是 base64 编码的默认服务器地址,运行时解码使用。
|
|
31
|
+
# 修改说明:将你的真实服务器地址做 base64 编码后替换下方常量即可:
|
|
32
|
+
# python -c "import base64; print(base64.b64encode(b'http://YOUR_IP:PORT').decode())"
|
|
33
|
+
_E = b"aHR0cHM6Ly80Ny4xMTIuMTkxLjc1" # base64("https://47.112.191.75")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _default_base_url() -> str:
|
|
37
|
+
"""运行时解码默认网关地址。"""
|
|
38
|
+
return _b64.b64decode(_E).decode("utf-8")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# -------------------- 全局配置(兼容 tinyshare 风格) --------------------
|
|
42
|
+
_global_auth_code: Optional[str] = None
|
|
43
|
+
_global_base_url: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def set_token(token: str) -> None:
|
|
47
|
+
"""全局设置 myshare auth_code(兼容 tinyshare 的 ts.set_token() 调用风格)
|
|
48
|
+
|
|
49
|
+
示例:
|
|
50
|
+
import myshare as ts
|
|
51
|
+
ts.set_token("msu_xxx")
|
|
52
|
+
pro = ts.pro_api()
|
|
53
|
+
"""
|
|
54
|
+
global _global_auth_code
|
|
55
|
+
_global_auth_code = token
|
|
56
|
+
logger.debug("全局 auth_code 已设置")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def set_base_url(url: str) -> None:
|
|
60
|
+
"""全局设置 myshare 网关地址(可选,覆盖内置默认值)
|
|
61
|
+
|
|
62
|
+
示例:
|
|
63
|
+
ts.set_base_url("http://192.168.1.100:8000")
|
|
64
|
+
"""
|
|
65
|
+
global _global_base_url
|
|
66
|
+
_global_base_url = url
|
|
67
|
+
logger.debug("全局 base_url 已设置")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# -------------------- 设备指纹采集 --------------------
|
|
71
|
+
|
|
72
|
+
def _get_device_id() -> str:
|
|
73
|
+
"""获取本机设备唯一 ID,并缓存到 ~/.myshare/device_id.json。
|
|
74
|
+
|
|
75
|
+
采集逻辑:
|
|
76
|
+
- Windows : PowerShell Get-CimInstance 读取 CPU ProcessorId + 硬盘序列号 → SHA256
|
|
77
|
+
- macOS : system_profiler 读取 Hardware UUID → SHA256
|
|
78
|
+
- Linux : 读取 /etc/machine-id 或 /var/lib/dbus/machine-id → SHA256
|
|
79
|
+
|
|
80
|
+
如果所有方式都失败,退化为 hostname + platform 信息的 SHA256。
|
|
81
|
+
"""
|
|
82
|
+
cache_dir = Path.home() / ".myshare"
|
|
83
|
+
cache_file = cache_dir / "device_id.json"
|
|
84
|
+
|
|
85
|
+
# 优先读取已缓存的 ID,并校验格式(SHA256 = 64位十六进制)
|
|
86
|
+
if cache_file.exists():
|
|
87
|
+
try:
|
|
88
|
+
data = json.loads(cache_file.read_text(encoding="utf-8"))
|
|
89
|
+
device_id = data.get("device_id", "")
|
|
90
|
+
if (
|
|
91
|
+
device_id
|
|
92
|
+
and len(device_id) == 64
|
|
93
|
+
and all(c in "0123456789abcdef" for c in device_id)
|
|
94
|
+
):
|
|
95
|
+
return device_id
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
raw_id = _collect_hardware_fingerprint()
|
|
100
|
+
device_id = hashlib.sha256(raw_id.encode("utf-8")).hexdigest()
|
|
101
|
+
|
|
102
|
+
# 持久化缓存
|
|
103
|
+
try:
|
|
104
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
cache_file.write_text(
|
|
106
|
+
json.dumps({"device_id": device_id}, ensure_ascii=False),
|
|
107
|
+
encoding="utf-8",
|
|
108
|
+
)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.debug("写入设备指纹缓存失败: %s", e)
|
|
111
|
+
|
|
112
|
+
logger.debug("设备 ID 已生成: %s...", device_id[:8])
|
|
113
|
+
return device_id
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _collect_hardware_fingerprint() -> str:
|
|
117
|
+
"""根据操作系统收集硬件指纹原始字符串。"""
|
|
118
|
+
system = platform.system()
|
|
119
|
+
|
|
120
|
+
if system == "Windows":
|
|
121
|
+
return _fingerprint_windows()
|
|
122
|
+
elif system == "Darwin":
|
|
123
|
+
return _fingerprint_macos()
|
|
124
|
+
else:
|
|
125
|
+
return _fingerprint_linux()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _fingerprint_windows() -> str:
|
|
129
|
+
"""Windows:使用 PowerShell Get-CimInstance 读取 CPU 序列号 + 硬盘序列号(替代已弃用的 wmic)。"""
|
|
130
|
+
parts = []
|
|
131
|
+
|
|
132
|
+
# CPU ProcessorId - 使用 PowerShell Get-CimInstance 替代 wmic
|
|
133
|
+
try:
|
|
134
|
+
result = subprocess.check_output(
|
|
135
|
+
["powershell", "-Command", "Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty ProcessorId"],
|
|
136
|
+
stderr=subprocess.DEVNULL,
|
|
137
|
+
timeout=5,
|
|
138
|
+
).decode(errors="replace").strip()
|
|
139
|
+
if result:
|
|
140
|
+
parts.append(result)
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# 物理硬盘序列号 - 使用 PowerShell Get-CimInstance 替代 wmic
|
|
145
|
+
try:
|
|
146
|
+
result = subprocess.check_output(
|
|
147
|
+
["powershell", "-Command", "Get-CimInstance -ClassName Win32_DiskDrive | Select-Object -ExpandProperty SerialNumber | Select-Object -First 1"],
|
|
148
|
+
stderr=subprocess.DEVNULL,
|
|
149
|
+
timeout=5,
|
|
150
|
+
).decode(errors="replace").strip()
|
|
151
|
+
if result:
|
|
152
|
+
parts.append(result)
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
if parts:
|
|
157
|
+
return "|".join(parts)
|
|
158
|
+
|
|
159
|
+
# 退化:hostname
|
|
160
|
+
return platform.node() + "|" + platform.machine()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _fingerprint_macos() -> str:
|
|
164
|
+
"""macOS:system_profiler 读取 Hardware UUID。"""
|
|
165
|
+
try:
|
|
166
|
+
result = subprocess.check_output(
|
|
167
|
+
["system_profiler", "SPHardwareDataType"],
|
|
168
|
+
stderr=subprocess.DEVNULL,
|
|
169
|
+
timeout=5,
|
|
170
|
+
).decode(errors="replace")
|
|
171
|
+
for line in result.splitlines():
|
|
172
|
+
# 精确匹配 "Hardware UUID",避免匹配到其他含 UUID 的行
|
|
173
|
+
if "Hardware UUID" in line:
|
|
174
|
+
return line.split(":")[-1].strip()
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
return platform.node() + "|" + platform.machine()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _fingerprint_linux() -> str:
|
|
181
|
+
"""Linux:读取 /etc/machine-id。"""
|
|
182
|
+
for path in ("/etc/machine-id", "/var/lib/dbus/machine-id"):
|
|
183
|
+
try:
|
|
184
|
+
machine_id = Path(path).read_text().strip()
|
|
185
|
+
if machine_id:
|
|
186
|
+
return machine_id
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
return platform.node() + "|" + platform.machine()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# -------------------- 客户端 --------------------
|
|
193
|
+
|
|
194
|
+
class MyShareError(Exception):
|
|
195
|
+
"""myshare 调用错误"""
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_retry_session() -> requests.Session:
|
|
199
|
+
"""构建带重试策略的 requests.Session。
|
|
200
|
+
|
|
201
|
+
重试条件:网络连接错误、502/503/504 状态码,最多重试 3 次,指数退避。
|
|
202
|
+
|
|
203
|
+
SSL 证书:
|
|
204
|
+
生产环境 Nginx 通常配置 Let's Encrypt 证书(自动验证通过)。
|
|
205
|
+
如果使用自签名证书,设置环境变量 MYSHARE_SSL_VERIFY=false 跳过验证。
|
|
206
|
+
"""
|
|
207
|
+
session = requests.Session()
|
|
208
|
+
retry = Retry(
|
|
209
|
+
total=3,
|
|
210
|
+
backoff_factor=0.5,
|
|
211
|
+
status_forcelist=[502, 503, 504],
|
|
212
|
+
allowed_methods=["POST", "GET"],
|
|
213
|
+
)
|
|
214
|
+
adapter = HTTPAdapter(max_retries=retry)
|
|
215
|
+
session.mount("http://", adapter)
|
|
216
|
+
session.mount("https://", adapter)
|
|
217
|
+
# 通过环境变量控制 SSL 证书验证(默认开启)
|
|
218
|
+
session.verify = os.environ.get("MYSHARE_SSL_VERIFY", "true").lower() != "false"
|
|
219
|
+
return session
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class MyShareClient:
|
|
223
|
+
"""myshare 客户端
|
|
224
|
+
|
|
225
|
+
参数:
|
|
226
|
+
auth_code: 由 myshare 服务端签发的用户访问 token
|
|
227
|
+
base_url: 网关地址(留空则使用内置默认值)
|
|
228
|
+
timeout: 请求超时时间(秒)
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def __init__(
|
|
232
|
+
self,
|
|
233
|
+
auth_code: Optional[str] = None,
|
|
234
|
+
base_url: Optional[str] = None,
|
|
235
|
+
timeout: int = 30,
|
|
236
|
+
) -> None:
|
|
237
|
+
self.auth_code = auth_code
|
|
238
|
+
# 优先级:显式传入 > 环境变量 > 内置混淆默认值
|
|
239
|
+
if base_url is not None:
|
|
240
|
+
self.base_url = base_url
|
|
241
|
+
else:
|
|
242
|
+
env_url = os.getenv("MYSHARE_BASE_URL")
|
|
243
|
+
self.base_url = env_url if env_url else _default_base_url()
|
|
244
|
+
self.timeout = timeout
|
|
245
|
+
self._session = _build_retry_session()
|
|
246
|
+
# 懒加载设备 ID(第一次调用时才采集,避免导入慢)
|
|
247
|
+
self._device_id: Optional[str] = None
|
|
248
|
+
|
|
249
|
+
def _get_device_id(self) -> str:
|
|
250
|
+
if self._device_id is None:
|
|
251
|
+
self._device_id = _get_device_id()
|
|
252
|
+
return self._device_id
|
|
253
|
+
|
|
254
|
+
# -------------------- 公共 API --------------------
|
|
255
|
+
def query(self, api_name: str, **params: Any) -> pd.DataFrame:
|
|
256
|
+
"""通用查询接口,对应 tushare 的 pro.query(api_name, **params)"""
|
|
257
|
+
# 提前校验 auth_code,给出明确的错误提示
|
|
258
|
+
if not self.auth_code:
|
|
259
|
+
raise MyShareError(
|
|
260
|
+
"auth_code 未设置,请先调用 ts.set_token('your_code') 或在 pro_api() 中传入 auth_code 参数"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
payload = {
|
|
264
|
+
"api_name": api_name,
|
|
265
|
+
"params": params or {},
|
|
266
|
+
"auth_code": self.auth_code,
|
|
267
|
+
"meta": {
|
|
268
|
+
"client": "python-sdk",
|
|
269
|
+
"device_id": self._get_device_id(),
|
|
270
|
+
},
|
|
271
|
+
}
|
|
272
|
+
url = self.base_url.rstrip("/") + "/api/v1/queue"
|
|
273
|
+
logger.debug("myshare request api=%s (async)", api_name)
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
resp = self._session.post(url, json=payload, timeout=self.timeout)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.exception("请求 myshare 失败: %s", e)
|
|
279
|
+
raise MyShareError(f"request failed: {e}") from e
|
|
280
|
+
|
|
281
|
+
if resp.status_code != 200:
|
|
282
|
+
raise MyShareError(f"http status {resp.status_code}: {resp.text}")
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
obj = resp.json()
|
|
286
|
+
except ValueError as e:
|
|
287
|
+
raise MyShareError(f"invalid json response: {e}") from e
|
|
288
|
+
|
|
289
|
+
code = obj.get("code", -1)
|
|
290
|
+
message = obj.get("message", "unknown error")
|
|
291
|
+
data = obj.get("data")
|
|
292
|
+
|
|
293
|
+
if code != 0:
|
|
294
|
+
raise MyShareError(f"error code {code}: {message}")
|
|
295
|
+
|
|
296
|
+
# 使用 is None 而非 not data,避免将空字典/空列表误判为"无数据"
|
|
297
|
+
if data is None:
|
|
298
|
+
return pd.DataFrame()
|
|
299
|
+
|
|
300
|
+
# 检查是否是异步任务响应(包含task_id)
|
|
301
|
+
if isinstance(data, dict) and "task_id" in data:
|
|
302
|
+
# 异步任务,需要轮询获取结果
|
|
303
|
+
task_id = data["task_id"]
|
|
304
|
+
return self._poll_async_result(task_id)
|
|
305
|
+
else:
|
|
306
|
+
# 同步响应或直接返回数据(兼容模式)
|
|
307
|
+
fields = data.get("fields") or []
|
|
308
|
+
items = data.get("items") or []
|
|
309
|
+
return pd.DataFrame(items, columns=fields)
|
|
310
|
+
|
|
311
|
+
def _poll_async_result(self, task_id: str, timeout: int = 60) -> pd.DataFrame:
|
|
312
|
+
"""轮询异步任务结果
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
task_id: 异步任务ID
|
|
316
|
+
timeout: 轮询超时时间(秒)
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
pandas DataFrame
|
|
320
|
+
|
|
321
|
+
Raises:
|
|
322
|
+
MyShareError: 任务失败或超时
|
|
323
|
+
"""
|
|
324
|
+
import time
|
|
325
|
+
|
|
326
|
+
start_time = time.time()
|
|
327
|
+
poll_interval = 0.5 # 轮询间隔0.5秒
|
|
328
|
+
|
|
329
|
+
while time.time() - start_time < timeout:
|
|
330
|
+
# 查询任务状态
|
|
331
|
+
result_url = self.base_url.rstrip("/") + f"/api/v1/task/{task_id}"
|
|
332
|
+
try:
|
|
333
|
+
result_resp = self._session.get(result_url, timeout=self.timeout)
|
|
334
|
+
except Exception as e:
|
|
335
|
+
logger.exception("查询异步任务结果失败: %s", e)
|
|
336
|
+
raise MyShareError(f"query async result failed: {e}") from e
|
|
337
|
+
|
|
338
|
+
if result_resp.status_code != 200:
|
|
339
|
+
raise MyShareError(f"http status {result_resp.status_code}: {result_resp.text}")
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
result_obj = result_resp.json()
|
|
343
|
+
except ValueError as e:
|
|
344
|
+
raise MyShareError(f"invalid json response: {e}") from e
|
|
345
|
+
|
|
346
|
+
result_code = result_obj.get("code", -1)
|
|
347
|
+
|
|
348
|
+
if result_code != 0:
|
|
349
|
+
raise MyShareError(f"error code {result_code}: {result_obj.get('message')}")
|
|
350
|
+
|
|
351
|
+
result_data = result_obj.get("data", {})
|
|
352
|
+
status = result_data.get("status")
|
|
353
|
+
|
|
354
|
+
if status == "done":
|
|
355
|
+
# 任务完成,返回数据
|
|
356
|
+
final_result = result_data.get("result", {})
|
|
357
|
+
fields = final_result.get("fields") or []
|
|
358
|
+
items = final_result.get("items") or []
|
|
359
|
+
logger.debug("异步任务完成: %s, 耗时: %.2f秒", task_id, time.time() - start_time)
|
|
360
|
+
return pd.DataFrame(items, columns=fields)
|
|
361
|
+
elif status == "failed":
|
|
362
|
+
# 任务失败
|
|
363
|
+
error = result_data.get("error", "unknown error")
|
|
364
|
+
logger.error("异步任务失败: %s, 错误: %s", task_id, error)
|
|
365
|
+
raise MyShareError(f"async task failed: {error}")
|
|
366
|
+
elif status == "pending":
|
|
367
|
+
# 任务还在处理中,继续等待
|
|
368
|
+
pass
|
|
369
|
+
else:
|
|
370
|
+
# 未知状态
|
|
371
|
+
logger.error("未知的异步任务状态: %s, task_id: %s", status, task_id)
|
|
372
|
+
raise MyShareError(f"unknown task status: {status}")
|
|
373
|
+
|
|
374
|
+
# 等待后继续轮询
|
|
375
|
+
time.sleep(poll_interval)
|
|
376
|
+
|
|
377
|
+
# 超时
|
|
378
|
+
logger.error("异步任务超时: %s, 超时时间: %d秒", task_id, timeout)
|
|
379
|
+
raise MyShareError(f"async task timeout after {timeout} seconds")
|
|
380
|
+
|
|
381
|
+
# -------------------- 动态 API 映射 --------------------
|
|
382
|
+
def __getattr__(self, name: str) -> Callable[..., pd.DataFrame]:
|
|
383
|
+
"""将未知属性当作 api_name 动态映射
|
|
384
|
+
|
|
385
|
+
例如:pro.daily(ts_code="000001.SZ") 等价于 query("daily", ts_code="000001.SZ")
|
|
386
|
+
|
|
387
|
+
注意:__getattr__ 只在普通属性查找失败时触发,无需再做 hasattr 检查
|
|
388
|
+
(hasattr 检查会导致递归调用 __getattr__ 进而引发 RecursionError)。
|
|
389
|
+
"""
|
|
390
|
+
if name.startswith("_"):
|
|
391
|
+
raise AttributeError(name)
|
|
392
|
+
|
|
393
|
+
def _wrapper(**kwargs: Any) -> pd.DataFrame:
|
|
394
|
+
return self.query(name, **kwargs)
|
|
395
|
+
|
|
396
|
+
_wrapper.__name__ = name
|
|
397
|
+
_wrapper.__doc__ = f"Dynamic API proxy for '{name}' via myshare."
|
|
398
|
+
return _wrapper
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def pro_api(
|
|
402
|
+
auth_code: Optional[str] = None,
|
|
403
|
+
base_url: Optional[str] = None,
|
|
404
|
+
timeout: int = 30,
|
|
405
|
+
) -> MyShareClient:
|
|
406
|
+
"""创建 myshare 客户端实例(兼容 tushare.pro_api 接口风格)
|
|
407
|
+
|
|
408
|
+
auth_code 可省略,若已通过 set_token() 全局配置则自动读取。
|
|
409
|
+
|
|
410
|
+
示例:
|
|
411
|
+
ts.set_token("msu_xxx")
|
|
412
|
+
pro = ts.pro_api()
|
|
413
|
+
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201")
|
|
414
|
+
"""
|
|
415
|
+
final_auth_code = auth_code if auth_code is not None else _global_auth_code
|
|
416
|
+
final_base_url = base_url if base_url is not None else _global_base_url
|
|
417
|
+
return MyShareClient(auth_code=final_auth_code, base_url=final_base_url, timeout=timeout)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tsshare
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: tsshare — Tushare data proxy SDK, drop-in replacement for tushare pro_api
|
|
5
|
+
Author:
|
|
6
|
+
Author-email: tsshare team <tsshare@example.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/LinMason168/tsshare
|
|
9
|
+
Project-URL: Documentation, https://github.com/LinMason168/tsshare#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/LinMason168/tsshare
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: requests>=2.28.0
|
|
26
|
+
Requires-Dist: pandas>=1.5.0
|
|
27
|
+
Requires-Dist: urllib3>=1.26.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
|
|
31
|
+
# myshare SDK
|
|
32
|
+
|
|
33
|
+
A drop-in replacement for Tushare's `pro_api` — same API, no quota limits.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install myshare
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import myshare as ts
|
|
45
|
+
|
|
46
|
+
# One-line setup: paste your auth_code
|
|
47
|
+
ts.set_token("your-auth-code")
|
|
48
|
+
|
|
49
|
+
# Use exactly like tushare pro_api
|
|
50
|
+
pro = ts.pro_api()
|
|
51
|
+
df = pro.daily(ts_code="000001.SZ", start_date="20240101", end_date="20240201")
|
|
52
|
+
print(df)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Supported APIs
|
|
56
|
+
|
|
57
|
+
All Tushare APIs are supported: `daily`, `stock_basic`, `trade_cal`, `income`, `balancesheet`, `moneyflow`, and more.
|
|
58
|
+
|
|
59
|
+
## Platform Support
|
|
60
|
+
|
|
61
|
+
- Windows (x86_64)
|
|
62
|
+
- macOS (Apple Silicon / Intel)
|
|
63
|
+
- Linux (x86_64 / aarch64)
|
|
64
|
+
|
|
65
|
+
Python 3.8 or later required.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
tsshare/__init__.py,sha256=04Rs88HupzmrOHkE9FypDtRgGF4nF2BtCCrhXSfzZoU,622
|
|
2
|
+
tsshare/client.py,sha256=Ah1fEdJjk2uZfAxn2VtiK8b1I2O4jY9B_qFVmlHFYzQ,15286
|
|
3
|
+
tsshare-1.0.0.dist-info/licenses/LICENSE,sha256=mfZBsenkJrxFQ7Gidx0tef34i03o8ectL7JUSypwsYk,1064
|
|
4
|
+
tsshare-1.0.0.dist-info/METADATA,sha256=fFMYGPo4-Pmn7X8B23yaZ5Lk89qKPO7EU1rg2QZmlzA,1979
|
|
5
|
+
tsshare-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
6
|
+
tsshare-1.0.0.dist-info/top_level.txt,sha256=y3Fcx9aqp9-L9yGQ2v8kEFexuqRRIfOKwHKDQeAMwn4,8
|
|
7
|
+
tsshare-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 myshare
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tsshare
|