小雫DNS Python SDK 和 CLI - 零依赖的 DNSHE 子域名管理工具
Project description
小雫DNS Python SDK + CLI (xndns)
零依赖的 DNSHE 子域名管理 Python 包,含同步/异步 SDK 和命令行工具
特性
- 零依赖:仅用 Python 3.9+ 标准库(
urllib/asyncio/json),无需pip install requests - 同步 + 异步:提供
Client(同步)和AsyncClient(异步)两种客户端 - 三传输层:支持
rest/rpc/graphql,可通过参数或配置文件切换 - 完整功能:子域名 CRUD / DNS 记录 CRUD / API 密钥管理 / 配额查询 / WHOIS / 安装检测
- 多种配置方式:环境变量 / 配置文件(
~/.xndnsrc)/ 构造参数 - JSON 输出:CLI 所有命令支持
--json切换为机器可读格式 - 跨平台:Linux / macOS / Windows
安装
方式 1:从源码安装(推荐)
# 解压后进入目录
cd xndns-py
# 用户级安装(推荐,无需 sudo)
python install.py
# 或系统级安装(需要 sudo)
sudo python install.py --system
安装脚本会:
- 通过
pip install .安装xndns包到当前 Python 环境 - 在
~/.local/bin(或/usr/local/bin)创建xndns包装脚本 - 验证安装并提示如何配置 PATH
方式 2:开发模式安装
cd xndns-py
pip install -e .
适合开发调试,修改源码立即生效。
方式 3:直接运行(不安装)
# 直接通过 Python 模块运行
python -m xndns quota
# 或直接调用 cli.py
python xndns/cli.py help
需要在脚本所在目录或设置 PYTHONPATH。
方式 4:发布到 PyPI 后
pip install xndns
卸载
python uninstall.py
会移除 pip 包、包装脚本和 ~/.xndnsrc 配置文件。
配置凭证
方式 1:使用 login 命令(推荐)
xndns login cfsd_xxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyy --host=http://dnshe.xiaona.cc.cd
凭证保存在 ~/.xndnsrc(权限 600)。
方式 2:环境变量
export XNDNS_API_KEY=cfsd_xxxxxxxxxxxxxxxx
export XNDNS_API_SECRET=yyyyyyyyyyyyyyyyyyyy
export XNDNS_HOST=http://dnshe.xiaona.cc.cd
# 可选:传输层(默认 rest)
export XNDNS_TRANSPORT=rpc
适合 CI/CD 或临时使用。
方式 3:配置文件
直接编辑 ~/.xndnsrc:
{
"apiKey": "cfsd_xxx",
"apiSecret": "yyyy",
"host": "http://dnshe.xiaona.cc.cd",
"transport": "rest"
}
或用 config 命令:
xndns config show
xndns config set transport rpc
xndns config set host https://other-host
方式 4:在代码中直接传参
from xndns import Client
client = Client(
api_key="cfsd_xxx",
api_secret="yyyy",
host="http://dnshe.xiaona.cc.cd",
transport="rest",
)
SDK 使用
同步客户端
from xndns import Client
# 初始化(也可以用 Client.from_config() 从 ~/.xndnsrc 加载)
client = Client(
api_key="cfsd_xxxxxxxxxxxxxxxx",
api_secret="yyyyyyyyyyyyyyyyyyyy",
host="http://dnshe.xiaona.cc.cd",
transport="rest", # "rest" | "rpc" | "graphql"
)
# 验证凭证
verify = client.auth.verify()
print(verify["account"]["quota"])
# {'used': 3, 'total': 7, 'available': 4}
# 列出子域名
subs = client.subdomains.list(per_page=10)
for s in subs["subdomains"]:
print(s["full_domain"])
# 注册新子域名
created = client.subdomains.create(subdomain="myapp", rootdomain="us.kg")
print(created["full_domain"])
# 创建 DNS 记录
client.dns_records.create(
subdomain_id=created["subdomain_id"],
type="A",
content="192.0.2.1",
ttl=600,
)
# 查询 WHOIS
whois = client.whois.query("foo.example.com")
# 检测某域名是否安装了小雫DNS
detect = client.system.detect("example.com")
if detect["detected"]:
print(f"检测到小雫DNS v{detect['version']}")
异步客户端
import asyncio
from xndns import AsyncClient
async def main():
client = AsyncClient(
api_key="cfsd_xxx",
api_secret="yyyy",
host="http://dnshe.xiaona.cc.cd",
)
# 并发查询配额和子域名列表
quota, subs = await asyncio.gather(
client.quota.get(),
client.subdomains.list(per_page=10),
)
print(quota)
print(subs)
# 注册新子域名
created = await client.subdomains.create("myapp", "us.kg")
print(created["full_domain"])
asyncio.run(main())
错误处理
from xndns import Client, ApiError
client = Client(api_key="...", api_secret="...")
try:
client.subdomains.create(subdomain="x", rootdomain="us.kg")
except ApiError as e:
print(e.code) # "quota_exceeded"
print(e.message) # "Quota exceeded"
print(e.http_status) # 429
print(e.details) # {"limit": 60, "reset_at": "..."}
SDK API 总览
| 命名空间 | 方法 | 对应 RPC method | 说明 |
|---|---|---|---|
auth |
verify() |
auth.verify |
验证凭证并返回配额摘要 |
subdomains |
list(...) |
subdomains.list |
分页列出子域名 |
subdomains |
get(id) |
subdomains.get |
获取子域名详情 |
subdomains |
create(subdomain, rootdomain) |
subdomains.create |
注册子域名 |
subdomains |
delete(id) |
subdomains.delete |
删除子域名 |
subdomains |
renew(id) |
subdomains.renew |
续费子域名 |
dns_records |
list(subdomain_id) |
dns_records.list |
列出 DNS 记录 |
dns_records |
create(subdomain_id, type, content, ...) |
dns_records.create |
创建 DNS 记录 |
dns_records |
update(id, ...) |
dns_records.update |
更新 DNS 记录 |
dns_records |
delete(id) |
dns_records.delete |
删除 DNS 记录 |
api_keys |
list() |
api_keys.list |
列出 API 密钥 |
api_keys |
create(name, ip_whitelist?) |
api_keys.create |
创建 API 密钥 |
api_keys |
delete(key_id) |
api_keys.delete |
删除 API 密钥 |
api_keys |
regenerate(key_id) |
api_keys.regenerate |
重置 API Secret |
quota |
get() |
quota.get |
查询配额 |
whois |
query(domain) |
whois.query |
WHOIS 查询 |
system |
detect(domain) |
system.detect |
检测目标域名 |
system |
selftest() |
- | 自检 |
注:
dnsRecords和apiKeys是dns_records和api_keys的 camelCase 别名,方便 JS 习惯的用户。
CLI 命令一览
基础
| 命令 | 说明 |
|---|---|
xndns login <key> <secret> [--host=URL] |
保存凭证 |
xndns logout |
清除凭证 |
xndns whoami |
显示当前凭证 |
xndns config [show|set <key> <value>] |
查看/修改配置 |
xndns help |
显示帮助 |
xndns version |
显示版本 |
子域名
| 命令 | 说明 |
|---|---|
xndns subdomains list [opts] |
列出子域名 |
xndns subdomains get <id> |
查看详情 |
xndns subdomains create <prefix> <root> |
注册 |
xndns subdomains delete <id> |
删除 |
xndns subdomains renew <id> |
续费 |
list 选项:--page=N --per-page=N --search=KEY --status=STATUS --rootdomain=ROOT
DNS 记录
| 命令 | 说明 |
|---|---|
xndns dns list <subdomain_id> |
列出记录 |
xndns dns create <subdomain_id> <type> <content> [--ttl=N] [--priority=N] [--name=NAME] |
创建 |
xndns dns update <id> [--type=T] [--content=V] [--ttl=N] |
更新 |
xndns dns delete <id> |
删除 |
类型支持:A / AAAA / CNAME / MX / TXT / NS / SRV / CAA
密钥 / 配额 / WHOIS
| 命令 | 说明 |
|---|---|
xndns quota |
查询配额 |
xndns whois <domain> |
WHOIS 查询 |
xndns keys list |
列出 API 密钥 |
xndns keys create <name> [--ip-whitelist=IPS] |
创建密钥 |
xndns keys delete <key_id> |
删除密钥 |
xndns keys regenerate <key_id> |
重置 Secret |
检测
| 命令 | 说明 |
|---|---|
xndns detect <domain> |
检测目标域名是否安装小雫DNS |
xndns selftest |
检测当前配置的主机 |
全局选项
| 选项 | 说明 |
|---|---|
--json |
输出原始 JSON |
--host=URL |
临时覆盖主机 |
--transport=rest|rpc|graphql |
临时覆盖传输层 |
--debug |
显示完整错误堆栈 |
使用示例
# 登录
xndns login cfsd_0adc0ce77bc62a2903caf044ddb877a5 b190ba15... --host=http://dnshe.xiaona.cc.cd
# 查看配额
xndns quota
# 配额: 1/3 已用
# 基础: 3
# 邀请奖励: 0
# 可用: 2
# 列出子域名
xndns subdomains list --per-page=5
# 共 1 条,本页 1 条
#
# #7477265796 mynmail.bbroot.com [Registered] 2036-07-07 01:08:40
# 注册新子域名
xndns subdomains create myapp us.kg
# ✓ 已注册 myapp.us.kg (#7477265797)
# 创建 DNS 记录
xndns dns create 7477265797 A 192.0.2.1 --ttl=600
# ✓ 已创建 [A] 记录 (id=100)
# WHOIS 查询
xndns whois mynmail.bbroot.com
# 域名: mynmail.bbroot.com
# 状态: active
# 注册: 2026-07-07 01:08
# 到期: 2036-07-07 01:08
# 邮箱: abuse@dnshe.com
# NS: ns1.dnshe.com, ns2.dnshe.com
# 切换到 RPC 传输层
xndns config set transport rpc
xndns quota
# (相同输出,但底层走 JSON-RPC)
# JSON 输出(适合脚本)
xndns quota --json | python -c "import sys, json; print(json.load(sys.stdin)['quota']['available'])"
# 2
# 检测某域名是否安装小雫DNS
xndns detect example.com
# ✗ example.com 未安装小雫DNS
xndns selftest
# ✓ http://dnshe.xiaona.cc.cd 是小雫DNS v1.0.0
# 功能: rest, rpc, graphql, cli, sdk, docs
Python 脚本示例
# 找出所有即将到期的子域名(30 天内)
import time
from datetime import datetime
from xndns import Client
client = Client.from_config()
subs = client.subdomains.list(per_page=100)["subdomains"]
now = time.time()
for s in subs:
if not s.get("expires_at"):
continue
try:
dt = datetime.strptime(s["expires_at"], "%Y-%m-%d %H:%M:%S").timestamp()
if dt - now < 30 * 86400:
print(f"即将到期: {s['full_domain']} ({s['expires_at']})")
except ValueError:
pass
# 批量备份所有子域名的 DNS 记录
import json
from xndns import Client
client = Client.from_config()
subs = client.subdomains.list(per_page=500)["subdomains"]
for s in subs:
recs = client.dns_records.list(s["id"])
with open(f"dns-backup-{s['id']}.json", "w", encoding="utf-8") as f:
json.dump({"subdomain": s, "dns_records": recs["records"]}, f, indent=2, ensure_ascii=False)
print(f"已备份 {s['full_domain']}: {recs['count']} 条记录")
# 异步并发查询多个域名的 WHOIS
import asyncio
from xndns import AsyncClient
async def main():
client = AsyncClient.from_config()
domains = ["foo.example.com", "bar.example.com", "baz.example.com"]
results = await asyncio.gather(*[client.whois.query(d) for d in domains])
for d, r in zip(domains, results):
status = "已注册" if r.get("registered") else "未注册"
print(f"{d}: {status}")
asyncio.run(main())
传输层对比
| 传输层 | 优势 | 何时使用 |
|---|---|---|
rest (默认) |
资源化 URL,缓存友好 | 日常使用 |
rpc |
单端点,支持批量调用 | 需要批量操作时 |
graphql |
按需取字段,减少传输量 | 只需要部分字段时 |
三种传输层在功能上完全等价,可以随时通过 xndns config set transport <value> 或构造参数 transport=... 切换。
错误处理
CLI 在出错时会返回非零退出码,错误信息格式:
[error_code] error_message
Python SDK 抛出 ApiError 异常,包含 code / message / http_status / details 字段。
常见错误码:
| error_code | HTTP | 说明 |
|---|---|---|
auth_invalid_credentials |
401 | 凭证无效 |
quota_exceeded |
429 | 配额超限 |
rate_limit_exceeded |
429 | 频率超限 |
subdomain_not_found |
404 | 子域名不存在 |
provider_operation_failed |
502 | 上游失败 |
完整错误码列表见 API 文档。
配置文件
~/.xndnsrc 是 JSON 格式,权限 600:
{
"apiKey": "cfsd_xxx",
"apiSecret": "yyyy",
"host": "http://dnshe.xiaona.cc.cd",
"transport": "rest"
}
优先级:命令行参数 > 环境变量 > 配置文件 > 构造参数默认值。
系统要求
- Python 3.9+
- Linux / macOS / Windows
- 网络可访问小雫DNS 主机
项目结构
xndns-py/
├── xndns/
│ ├── __init__.py # 包入口,暴露 Client / AsyncClient / ApiError
│ ├── __main__.py # python -m xndns 入口
│ ├── cli.py # CLI 实现(与 Node 版功能对齐)
│ ├── client.py # Client + AsyncClient + 命名空间
│ ├── config.py # Config + ApiError
│ └── transport.py # REST / RPC / GraphQL 传输层
├── pyproject.toml # 包定义(PEP 621)
├── install.py # 安装脚本
├── uninstall.py # 卸载脚本
├── README.md
├── LICENSE
└── CHANGELOG.md
开发
# 开发模式安装
pip install -e .
# 运行测试(如果安装了 dev 依赖)
pytest
# 代码检查
ruff check xndns/
License
MIT
相关
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xndns-1.0.2.tar.gz.
File metadata
- Download URL: xndns-1.0.2.tar.gz
- Upload date:
- Size: 24.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
236700fa5d1ca9545db1d3dc2cf8e6d218528f7e85b38fcd6e71d5e7daf579cb
|
|
| MD5 |
34a9a09dc2c31970c4a1113f0e756585
|
|
| BLAKE2b-256 |
0a15bca44e02cbb648a0d4e2a56cf5bd0ef3513d89e230eac69be55c503af085
|
File details
Details for the file xndns-1.0.2-py3-none-any.whl.
File metadata
- Download URL: xndns-1.0.2-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
532b212e211479c2f9f1f948bb3561f6e50664e8de2b488909a6b76fe3b750df
|
|
| MD5 |
92dea853a1a329d96aa2a99b6f121cfc
|
|
| BLAKE2b-256 |
a7beeac2256d27ea76a30ebe6c2b50b7512691b797d3df70b9756f791ebb95f1
|