Metadata-Version: 2.5
Name: ultrastiching-sdk
Version: 0.1.7
Summary: UltraStiching SDK for the UltraStiching Data API
Project-URL: Homepage, https://www.ultrastiching.com
Author: UltraStiching
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: requests<3,>=2.31
Requires-Dist: urllib3<2,>=1.26.20; python_version < '3.10'
Requires-Dist: websocket-client<1.9,>=1.8
Provides-Extra: arrow
Requires-Dist: pyarrow>=14; extra == 'arrow'
Provides-Extra: marketdb
Requires-Dist: duckdb<1.6,>=1.4.1; extra == 'marketdb'
Provides-Extra: test
Requires-Dist: duckdb<1.6,>=1.4.1; extra == 'test'
Requires-Dist: pyarrow>=14; extra == 'test'
Requires-Dist: pytest<10,>=8; extra == 'test'
Description-Content-Type: text/markdown

# UltraStiching SDK for Python

`ultrastiching-sdk` 是 UltraStiching 数据 API 的 Python 客户端，导入名为 `meridian_kit_sdk`。它包含从 [2026-10-07 API 目录](https://www.ultrastiching.com/api-catalog/endpoints.json)生成的 2049 个同步 HTTP 方法（2040 POST、9 GET；另有 2 项 SSE 仅入目录）、1 个 WebSocket 快讯方法、离线端点与字段发现、JSON/Arrow 结果工具，以及可选的 DuckDB marketdb。

目录是当日生产元数据的导出，不是当前账号权限或全部端点现时可用性的证明。本版按当前公开字段生成平铺请求；既有方法名按 UUID 保留，新增名称为已确认的 SDK 约定，来源和差异标记在包内注册表中，可通过 `MeridianClient.endpoints()` 查询。在线示例需要有效 API Key；尚未逐项验收示例端点、权限和服务端限制。

本版为 0.1.6，默认数据库路径为 `~/.ultrastiching/data/marketdb.duckdb`；显式路径、既有 `MERIDIAN_DB` 和 `MERIDIAN_BASE_URL` 继续按原有优先级使用。

## 安装与依赖

要求 Python 3.9 或更高版本。新版本发布后，从 PyPI 安装基础客户端；发布前使用下文的源码安装：

```bash
python -m pip install ultrastiching-sdk==0.1.6
python -c "import meridian_kit_sdk; print(meridian_kit_sdk.__version__)"
```

基础依赖是 `requests` 和 `websocket-client`；Python 3.9 的 `urllib3` 约束由包元数据处理。需要 Arrow 转换和本地数据库时，执行 `python -m pip install "ultrastiching-sdk[arrow,marketdb]==0.1.6"`；`arrow` 额外安装 `pyarrow`，`marketdb` 额外安装 `duckdb`。

`ultrastiching-sdk` 是安装包名；Python 导入名是 `meridian_kit_sdk`，客户端类是 `MeridianClient`。API Key、地址和本地数据库的配置方式见下文。

拿到 wheel 时可直接安装 `python -m pip install ./ultrastiching_sdk-0.1.6-py3-none-any.whl`。源码交付 ZIP 包含源码归档、`demo.py` 和本说明，解压后执行 `python -m pip install ./ultrastiching_sdk-0.1.6.tar.gz`。维护仓库的开发安装方式为 `python -m pip install -e "./python[arrow,marketdb]"`。

## 配置 API Key 和地址

Python 客户端的 Key 优先级：显式 `api_key=`、`ULTRASTICHING_API_KEY`、`~/.ultrastiching/credentials.env`。默认 HTTP 地址是 `https://api.ultrastiching.com`，显式 `base_url=` 优先于 `MERIDIAN_BASE_URL`。`base_url` 必须是纯 HTTP(S) origin，不要附带 `/api/wd`、查询参数或片段；生成方法会使用注册表中的完整相对路径。

API Key 在 [UltraStiching 网站](https://www.ultrastiching.com)的账号 API Key 页面创建。环境变量与私有文件任选一种；项目 `.env` 不会自动加载。维护仓库的 `.env.example` 只是模板。只由用户在 SDK 实际执行环境配置 Key。

```bash
export ULTRASTICHING_API_KEY="<your-api-key>"
```

推荐用户在本机终端运行 `ultrastiching auth configure` 隐藏输入一次，同时保存私有凭据文件和持久环境，供 CLI、Python SDK 与客户端共用；更换 Key 使用 `--replace`，确认后更新两处，最后重启客户端。已有文件而环境没有值时，统一安装器自动同步，无需重复输入。也可以把一行 `ULTRASTICHING_API_KEY=<your-api-key>` 写入 `~/.ultrastiching/credentials.env`。用户文件回退要求 POSIX 当前用户拥有该文件、文件权限为 `0600` 或更严，且 `~/.ultrastiching` 目录不能被其他用户写入；Windows 使用当前用户私有 ACL；其他无法验证文件权限的平台使用显式 Key 或环境变量。不要把真实 Key 写进仓库、URL、日志或截图。

测试环境与正式环境使用同一个 `MeridianClient`、相同方法和错误语义，只通过地址与 Key 等配置区分。

## 离线查端点和字段

注册表随包安装，以下示例无需 API Key 或网络；类上的发现方法不必构造客户端。

```python
from meridian_kit_sdk import MeridianClient

meta = MeridianClient.endpoints()["plasma_daily"]
print(meta["path"])                         # /api/wd/v1/data/daily
print(meta["response"]["return_format"])  # JSON
print(MeridianClient.fields("plasma_daily")[:3])
print(MeridianClient.fields("plasma_daily", search="收盘"))
```

`endpoints()` 返回按生成方法名索引的端点资料；`fields(name, search=...)` 查该端点在目录中记录的响应字段，未知名称会提供近似名称建议。函数名、参数、请求映射、地址和来源示例均在 `endpoints()` 返回的同一份资料中；方法签名也可通过 `help(MeridianClient.<方法名>)` 查看。字段目录只说明已记录的元数据，不能替代当前服务端的在线返回检查。

## 调用 JSON 和 Arrow 端点

**以下代码会发送真实请求。本轮无 Key，未对线上返回值和账号权限进行验收。**

```python
from meridian_kit_sdk import MeridianClient

with MeridianClient() as client:
    # JSON 响应保持服务端对象形状；rows() 提取 data_frame 行列表。
    result = client.get_s_stockname(code=["600745.SH"])
    print(client.rows(result))

    # 通用 Arrow 下载接口返回 bytes，需显式转成 Arrow Table。
    raw = client.arrow(
        handler="get_s_stockname", params={"code": ["600745.SH"]}, fields=["code", "sename"]
    )
    table = client.arrow_to_table(raw)
    print(table.num_rows)
```

`rows()` 仅接受含 `data_frame` 列表的 JSON 结果；错误或非预期形状会抛出 `MeridianResultError`，不会伪装为空表。`arrow_to_table()` 需要安装 `pyarrow`，未安装时提示安装 `ultrastiching-sdk[arrow]`。HTTP 核心按响应 `Content-Type` 处理：JSON 解码为 Python 对象，Arrow IPC 保留原始 bytes。默认不自动重试，也不自动转换成 pandas DataFrame。

SDK 业务函数按注册表路径发送请求。例如 `get_s_stockname` 使用 `/api/wd/v1/data/get_s_stockname`，`plasma_daily` 使用 `/api/wd/v1/data/daily`。调用通用下载接口 `client.arrow(handler="get_s_stockname", params=..., fields=...)` 时，`params` 按目标端点的当前平铺参数提供，`fields` 选择输出列；当前 `plasma_*` 返回格式为 JSON；按目录和实际响应 Content-Type 处理。

如果需要直接调用路径，`request()` 返回原始 `requests.Response`。它要求以单个 `/` 开头的相对路径，并限制请求体最大 1 MiB：

```python
from meridian_kit_sdk import MeridianClient

with MeridianClient() as client:
    response = client.request(
        "POST",
        "/api/wd/v1/data/get_s_stockname",
        json={"code": ["600745.SH"]},
    )
    print(response.json())
```

## 读取主题跟踪资讯

主题跟踪公开 API 包含 9 项内容查询、2 项 SSE 推送和 4 项主题管理；内容读取使用 GET，管理按契约使用 GET／POST／DELETE，共用同一个 UltraStiching API Key。2026-10-07 的 0.2.46 生产发布已启用 `/v1/tracer/feed`、`/v1/tracer/search` 和快讯最多 50 条窗口；0.2.47 已更新纪要预览标记。以下路径按该接口版本使用，账号权限与实际返回仍需单独核验。

| 内容 | 路径 |
| --- | --- |
| 关注范围新闻与快讯 | `/v1/tracer/feed` |
| 已入库新闻与快讯搜索 | `/v1/tracer/search` |
| 合并快讯列表 | `/v1/tracer/newsflash` |
| 纪要列表／整理正文 | `/v1/tracer/library/minutes`、`/v1/tracer/library/minutes/{id}` |
| 研报列表／摘要与整理稿 | `/v1/tracer/library/report`、`/v1/tracer/library/report/{id}` |
| X／公众号列表；X 正文详情 | `/v1/tracer/library/media`、`/v1/tracer/library/media/{id}` |
| 快讯／媒体 SSE | `/v1/tracer/newsflash/events`、`/v1/tracer/library/media/events` |

这 11 项已纳入统一目录，可通过 `endpoints()/fields()` 和 CLI `list/info` 查询。9 项普通 GET 使用 `tracer_` 前缀的生成方法，也支持 CLI `call`。这只是 SDK 名称约定，例如 `tracer_feed()` 对应 `/v1/tracer/feed`，`tracer_report(id=123)` 对应 `/v1/tracer/library/report/123`；服务器路径保持原样。两个 SSE 不生成同步方法，CLI `call` 明确拒绝。通用 `request()` 仍可读取普通 GET；按服务端 `items` 读取，不用金融表格的 `rows()` 提取。

以下示例会请求在线服务并使用账号额度：

```python
from meridian_kit_sdk import MeridianClient

# Key 由既有环境变量或用户凭据文件读取。
with MeridianClient() as client:
    params = {
        "q": "美债",
        "scope": "following",
        "from": "2026-10-06T00:00:00+08:00",
        "to": "2026-10-06T23:59:59+08:00",
        "limit": 20,
    }
    page = client.request("GET", "/v1/tracer/search", params=params).json()
    if page["has_more"]:
        # 保持查询条件，并使用服务端返回的固定时间窗续页。
        next_page = client.request(
            "GET", "/v1/tracer/search",
            params={
                **params,
                "from": page["window"]["from"],
                "to": page["window"]["to"],
                "cursor": page["next_cursor"],
            },
        ).json()

    newsflash = client.request(
        "GET", "/v1/tracer/newsflash",
        params={"limit": 50, "important_only": "true"},
    ).json()
    minutes = client.request(
        "GET", "/v1/tracer/library/minutes",
        params={"subtype": "meeting_minute", "days": 7, "limit": 30, "offset": 0},
    ).json()
    for item in minutes["items"][:1]:
        detail = client.request(
            "GET", f"/v1/tracer/library/minutes/{item['id']}",
        ).json()
        print(detail["content"], detail["content_truncated"])
```

`feed` 仅读取网站中当前账号的关注范围，不传 `q`；`search` 必填 `q`，支持 `scope=following/global`，其中 `global` 只表示公开已入库内容。新闻查询使用 `cursor`，续页保留全部筛选及响应时间窗，游标 15 分钟有效；默认读取范围分别为最近 7 天与 30 天。`topic_id` 只用于已经关注的主题，使用 UUID 或 `catalog:<slug>`，不是资料内容 ID。关注列表和主题管理见下节；新闻正文详情仍为网站内部功能。

快讯 `limit` 默认 50，最多 50 条，没有历史游标。生成方法 `client.tracer_newsflash(important_only=True)` 会自动编码为小写 `true`；使用通用 `request()` 时传字符串 `"true"/"false"`。资料列表按 `offset` 分页，默认 `limit=30`、最多 60 条；`days` 只取 `0/7/30/365`，0 表示不限时间。媒体列表必须传 `platform=x/wechat`，公众号只有标题与原文链接，详情只允许 X。

纪要列表预览最多 700 字符，`content_truncated` 按当前正文长度判断；详情的 `content` 是整理正文。研报详情分别返回 `summary` 与 `edited_content`，不返回原文 `content` 或附件；X 使用已采集的 `content`。保留空值与实际截断标记，不把预览或空整理稿当作完整原文。

两个 SSE 接口返回 `text/event-stream`。当前 `request()` 缓冲响应且没有 `stream` 参数，持续订阅须使用能带 Authorization 请求头并逐帧读取的 HTTP 流式传输。先读取同筛选列表，再以 `snapshot_revision` 作为 `baseline_revision` 建连；媒体基线列表固定 `limit=50&offset=0`。断线或 `refresh` 后重读列表并重建基线，不提供离线历史回放。金融数据的 `ws_newsflash()` 属于另一套 WebSocket 接口。

## 订阅实时快讯

WebSocket 使用同一个 API Key，在握手时发送 `Authorization: Bearer <key>`。目录中的地址为 `wss://ws.ultrastiching.com/api/wd/ws/newsflash`；可以用 `websocket_base_url=` 覆盖 origin。以下也属于未在线验收的真实请求：

```python
from meridian_kit_sdk import MeridianClient

with MeridianClient() as client:
    with client.ws_newsflash(limit=10) as stream:
        message = stream.receive_json()
        print(message)
```

SDK 不自动重连或重发订阅。需要补取时，由调用方保存**已处理完成**消息的游标，并自行决定何时重连。测试和正式环境使用同一接口。

## 本地 DuckDB marketdb

`MarketDB` 位于 `meridian_kit_sdk.marketdb`，与 Node.js CLI 的 `ultrastiching db` 共用同一个 DuckDB 文件。默认路径为 `~/.ultrastiching/data/marketdb.duckdb`；显式 `path=`、`MERIDIAN_DB`、默认路径依次决定位置。`duckdb` 是可选依赖，使用前执行 `python -m pip install "ultrastiching-sdk[marketdb]"`。

以下示例只操作临时本地文件，可离线运行：

```python
from pathlib import Path
from tempfile import TemporaryDirectory
from meridian_kit_sdk.marketdb import MarketDB

with TemporaryDirectory() as directory:
    db = MarketDB(path=str(Path(directory) / "marketdb.duckdb"))
    print(db.init())
    print(db.sql("SELECT schema_version FROM _marketdb_meta"))
    print(db.status())
    print(db.validate())
```

`sync(dataset, **overrides)` 会访问远端；`repair(dataset=None)` 在存在最新的失败或未完整同步时，按日志中的原参数重试。`sql(query, limit=None)` 只执行单条只读查询，`export(table, path, fmt=None)` 支持 CSV/Parquet。`status()` 查看本地表和最近同步记录，`validate()` 检查本地日志、类型、键及完整性证据；它不能仅凭本地行数证明远端历史已完整。

**14 个预设数据集的服务端分页、截断和键唯一性还缺当前环境的逐项证据；真实服务端验证留待后续。** `sync` 只有在服务端分页元数据与请求范围逐页吻合时，才写入数据并报告 `complete`；否则记录 `incomplete`、写入 0 行。一次同步在受控内存中缓存结果，上限为 100,000 行或约 64 MiB 规范 JSON；超限报错且不写入正式业务表。请检查返回对象中的 `status`、`reason`、`received_rows` 和 `written_rows`，不要把 HTTP 成功或原型中的固定行数上限当成完整性证明。

## 错误与诊断

```python
from meridian_kit_sdk import (
    MeridianApiError,
    MeridianClient,
    MeridianResponseError,
    MeridianResultError,
    MeridianTransportError,
)

try:
    with MeridianClient() as client:
        result = client.get_s_stockname(code=["600745.SH"])
        print(client.rows(result))
except MeridianApiError as error:
    print(error.status_code, error.error_code, error.request_id)
except MeridianTransportError as error:
    print(error.cause)
except MeridianResponseError as error:
    print(error.request_id)
except MeridianResultError as error:
    print(error.error_code)
```

`MeridianApiError` 保留 HTTP 状态、服务端错误码、request ID 和原始 `requests.Response`。`MeridianTransportError` 保留底层原因；成功状态但 JSON 非法、或 JSON 结果形状不合预期，分别由响应和结果异常指出。不要将 `401`、`403`、`429` 统一归成一种“认证失败”。默认不自动重试；是否重试要结合端点语义、限流和计费规则判断。记录诊断信息时，只脱敏真实 Key 等必要字段，不要丢弃错误链路。

## 本地验证与许可证

在仓库根目录运行离线测试：

```bash
python -m pip install -e './python[test]'
python -m pytest python
```

本地测试和注册表对账不代表测试环境或正式环境的在线验收。许可证为 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)，发行包内也包含完整许可证文件。

## 2026-10-07 参数更新

金融 POST 使用当前公开平铺字段，例如 `{"code":["600745.SH"]}`。调用生成方法时使用关键字参数；`get_s_tdaysoffset` 的日期和频率参数为 `calendar_date/frequency`，`get_fd_getfundid` 使用 `code_format`。因子分析的 `horizon_days` 按当前目录使用整数。生成验证不能代替线上调用验收。


## 管理关注主题

```python
# 输入关注方向，创建并自动关注；返回初始化回执。
receipt = client.tracer_create_topic(query="跟踪国产芯片的技术与产业进展")
# 读取关注的主题列表；id 直接用于关注或取消关注。
following = client.tracer_topics()
client.tracer_follow_topic(id="catalog:example")
client.tracer_unfollow_topic(id="<关注列表返回的主题 UUID>")
```

创建必填 `query`，可选 `title`、UUIDv4 `request_id`；202 初始化回执不表示任务已经完成。主题 `id` 是 UUIDv4 或 `catalog:<slug>` 字符串，区别于纪要、研报、媒体内容的整数编号。SDK 自动编码路径；关注 POST 和取消关注 DELETE 不带 JSON 请求体。管理接口不扣内容行数或数据积分，体验用户仍不能创建主题；关注上限为体验用户 15 个、其他用户 50 个。新增 4 项管理接口与原有 11 项内容接口共用目录和 API Key。
