xndns 1.0.0__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.
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## v1.0.0 (2026-07-09)
4
+
5
+ 首次发布。
6
+
7
+ ### 功能
8
+
9
+ - **SDK**: 同步 `Client` 和异步 `AsyncClient` 两种客户端
10
+ - **传输层**: `rest` / `rpc` / `graphql` 三选一,运行时可切换
11
+ - **CLI 命令**:
12
+ - 基础: `login`, `logout`, `whoami`, `config`, `help`, `version`
13
+ - 子域名: `subdomains list/get/create/delete/renew`
14
+ - DNS 记录: `dns list/create/update/delete`
15
+ - API 密钥: `keys list/create/delete/regenerate`
16
+ - 配额: `quota`
17
+ - WHOIS: `whois <domain>`
18
+ - 检测: `detect <domain>`, `selftest`
19
+ - **配置**: `~/.xndnsrc` + 环境变量 + 命令行参数 + 构造参数
20
+ - **输出**: 默认人类可读,`--json` 切换 JSON
21
+ - **跨平台**: Linux / macOS / Windows
22
+ - **零依赖**: 仅用 Python 3.9+ 标准库
xndns-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 小雫DNS
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,21 @@
1
+ # MANIFEST.in - 控制 sdist 包含哪些文件
2
+ # 参考: https://packaging.python.org/en/latest/guides/using-manifest-in/
3
+
4
+ include README.md
5
+ include LICENSE
6
+ include CHANGELOG.md
7
+ include pyproject.toml
8
+ include install.py
9
+ include uninstall.py
10
+
11
+ # 不包含测试时的临时文件
12
+ exclude test_sdk.py
13
+ exclude .DS_Store
14
+
15
+ # 不包含构建产物
16
+ global-exclude __pycache__/*
17
+ global-exclude *.pyc
18
+ global-exclude *.pyo
19
+ global-exclude *.egg-info/*
20
+ global-exclude .git/*
21
+ global-exclude .gitignore
xndns-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,545 @@
1
+ Metadata-Version: 2.4
2
+ Name: xndns
3
+ Version: 1.0.0
4
+ Summary: 小雫DNS Python SDK 和 CLI - 零依赖的 DNSHE 子域名管理工具
5
+ Author-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
6
+ Maintainer-email: 小雫DNS <noreply@dnshe.xiaona.cc.cd>
7
+ License: MIT
8
+ Project-URL: Homepage, http://dnshe.xiaona.cc.cd
9
+ Project-URL: Documentation, http://dnshe.xiaona.cc.cd/sdk
10
+ Project-URL: Repository, http://dnshe.xiaona.cc.cd
11
+ Project-URL: Bug Tracker, http://dnshe.xiaona.cc.cd
12
+ Project-URL: Changelog, http://dnshe.xiaona.cc.cd
13
+ Keywords: dnshe,subdomain,dns,cli,sdk,xiaoniao,小雫DNS,free-domain,cloudflare
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3 :: Only
26
+ Classifier: Topic :: Internet :: Name Service (DNS)
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Topic :: System :: Systems Administration
29
+ Classifier: Topic :: Utilities
30
+ Classifier: Typing :: Typed
31
+ Requires-Python: >=3.9
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
37
+ Requires-Dist: ruff>=0.1; extra == "dev"
38
+ Requires-Dist: build>=1.0; extra == "dev"
39
+ Requires-Dist: twine>=4.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # 小雫DNS Python SDK + CLI (`xndns`)
43
+
44
+ > 零依赖的 DNSHE 子域名管理 Python 包,含同步/异步 SDK 和命令行工具
45
+
46
+ ## 特性
47
+
48
+ - **零依赖**:仅用 Python 3.9+ 标准库(`urllib` / `asyncio` / `json`),无需 `pip install requests`
49
+ - **同步 + 异步**:提供 `Client`(同步)和 `AsyncClient`(异步)两种客户端
50
+ - **三传输层**:支持 `rest` / `rpc` / `graphql`,可通过参数或配置文件切换
51
+ - **完整功能**:子域名 CRUD / DNS 记录 CRUD / API 密钥管理 / 配额查询 / WHOIS / 安装检测
52
+ - **多种配置方式**:环境变量 / 配置文件(`~/.xndnsrc`)/ 构造参数
53
+ - **JSON 输出**:CLI 所有命令支持 `--json` 切换为机器可读格式
54
+ - **跨平台**:Linux / macOS / Windows
55
+
56
+ ## 安装
57
+
58
+ ### 方式 1:从源码安装(推荐)
59
+
60
+ ```bash
61
+ # 解压后进入目录
62
+ cd xndns-py
63
+
64
+ # 用户级安装(推荐,无需 sudo)
65
+ python install.py
66
+
67
+ # 或系统级安装(需要 sudo)
68
+ sudo python install.py --system
69
+ ```
70
+
71
+ 安装脚本会:
72
+ 1. 通过 `pip install .` 安装 `xndns` 包到当前 Python 环境
73
+ 2. 在 `~/.local/bin`(或 `/usr/local/bin`)创建 `xndns` 包装脚本
74
+ 3. 验证安装并提示如何配置 PATH
75
+
76
+ ### 方式 2:开发模式安装
77
+
78
+ ```bash
79
+ cd xndns-py
80
+ pip install -e .
81
+ ```
82
+
83
+ 适合开发调试,修改源码立即生效。
84
+
85
+ ### 方式 3:直接运行(不安装)
86
+
87
+ ```bash
88
+ # 直接通过 Python 模块运行
89
+ python -m xndns quota
90
+
91
+ # 或直接调用 cli.py
92
+ python xndns/cli.py help
93
+ ```
94
+
95
+ 需要在脚本所在目录或设置 `PYTHONPATH`。
96
+
97
+ ### 方式 4:发布到 PyPI 后
98
+
99
+ ```bash
100
+ pip install xndns
101
+ ```
102
+
103
+ ## 卸载
104
+
105
+ ```bash
106
+ python uninstall.py
107
+ ```
108
+
109
+ 会移除 pip 包、包装脚本和 `~/.xndnsrc` 配置文件。
110
+
111
+ ## 配置凭证
112
+
113
+ ### 方式 1:使用 `login` 命令(推荐)
114
+
115
+ ```bash
116
+ xndns login cfsd_xxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyy --host=http://dnshe.xiaona.cc.cd
117
+ ```
118
+
119
+ 凭证保存在 `~/.xndnsrc`(权限 600)。
120
+
121
+ ### 方式 2:环境变量
122
+
123
+ ```bash
124
+ export XNDNS_API_KEY=cfsd_xxxxxxxxxxxxxxxx
125
+ export XNDNS_API_SECRET=yyyyyyyyyyyyyyyyyyyy
126
+ export XNDNS_HOST=http://dnshe.xiaona.cc.cd
127
+ # 可选:传输层(默认 rest)
128
+ export XNDNS_TRANSPORT=rpc
129
+ ```
130
+
131
+ 适合 CI/CD 或临时使用。
132
+
133
+ ### 方式 3:配置文件
134
+
135
+ 直接编辑 `~/.xndnsrc`:
136
+
137
+ ```json
138
+ {
139
+ "apiKey": "cfsd_xxx",
140
+ "apiSecret": "yyyy",
141
+ "host": "http://dnshe.xiaona.cc.cd",
142
+ "transport": "rest"
143
+ }
144
+ ```
145
+
146
+ 或用 `config` 命令:
147
+
148
+ ```bash
149
+ xndns config show
150
+ xndns config set transport rpc
151
+ xndns config set host https://other-host
152
+ ```
153
+
154
+ ### 方式 4:在代码中直接传参
155
+
156
+ ```python
157
+ from xndns import Client
158
+
159
+ client = Client(
160
+ api_key="cfsd_xxx",
161
+ api_secret="yyyy",
162
+ host="http://dnshe.xiaona.cc.cd",
163
+ transport="rest",
164
+ )
165
+ ```
166
+
167
+ ## SDK 使用
168
+
169
+ ### 同步客户端
170
+
171
+ ```python
172
+ from xndns import Client
173
+
174
+ # 初始化(也可以用 Client.from_config() 从 ~/.xndnsrc 加载)
175
+ client = Client(
176
+ api_key="cfsd_xxxxxxxxxxxxxxxx",
177
+ api_secret="yyyyyyyyyyyyyyyyyyyy",
178
+ host="http://dnshe.xiaona.cc.cd",
179
+ transport="rest", # "rest" | "rpc" | "graphql"
180
+ )
181
+
182
+ # 验证凭证
183
+ verify = client.auth.verify()
184
+ print(verify["account"]["quota"])
185
+ # {'used': 3, 'total': 7, 'available': 4}
186
+
187
+ # 列出子域名
188
+ subs = client.subdomains.list(per_page=10)
189
+ for s in subs["subdomains"]:
190
+ print(s["full_domain"])
191
+
192
+ # 注册新子域名
193
+ created = client.subdomains.create(subdomain="myapp", rootdomain="us.kg")
194
+ print(created["full_domain"])
195
+
196
+ # 创建 DNS 记录
197
+ client.dns_records.create(
198
+ subdomain_id=created["subdomain_id"],
199
+ type="A",
200
+ content="192.0.2.1",
201
+ ttl=600,
202
+ )
203
+
204
+ # 查询 WHOIS
205
+ whois = client.whois.query("foo.example.com")
206
+
207
+ # 检测某域名是否安装了小雫DNS
208
+ detect = client.system.detect("example.com")
209
+ if detect["detected"]:
210
+ print(f"检测到小雫DNS v{detect['version']}")
211
+ ```
212
+
213
+ ### 异步客户端
214
+
215
+ ```python
216
+ import asyncio
217
+ from xndns import AsyncClient
218
+
219
+ async def main():
220
+ client = AsyncClient(
221
+ api_key="cfsd_xxx",
222
+ api_secret="yyyy",
223
+ host="http://dnshe.xiaona.cc.cd",
224
+ )
225
+
226
+ # 并发查询配额和子域名列表
227
+ quota, subs = await asyncio.gather(
228
+ client.quota.get(),
229
+ client.subdomains.list(per_page=10),
230
+ )
231
+ print(quota)
232
+ print(subs)
233
+
234
+ # 注册新子域名
235
+ created = await client.subdomains.create("myapp", "us.kg")
236
+ print(created["full_domain"])
237
+
238
+ asyncio.run(main())
239
+ ```
240
+
241
+ ### 错误处理
242
+
243
+ ```python
244
+ from xndns import Client, ApiError
245
+
246
+ client = Client(api_key="...", api_secret="...")
247
+
248
+ try:
249
+ client.subdomains.create(subdomain="x", rootdomain="us.kg")
250
+ except ApiError as e:
251
+ print(e.code) # "quota_exceeded"
252
+ print(e.message) # "Quota exceeded"
253
+ print(e.http_status) # 429
254
+ print(e.details) # {"limit": 60, "reset_at": "..."}
255
+ ```
256
+
257
+ ### SDK API 总览
258
+
259
+ | 命名空间 | 方法 | 对应 RPC method | 说明 |
260
+ |---|---|---|---|
261
+ | `auth` | `verify()` | `auth.verify` | 验证凭证并返回配额摘要 |
262
+ | `subdomains` | `list(...)` | `subdomains.list` | 分页列出子域名 |
263
+ | `subdomains` | `get(id)` | `subdomains.get` | 获取子域名详情 |
264
+ | `subdomains` | `create(subdomain, rootdomain)` | `subdomains.create` | 注册子域名 |
265
+ | `subdomains` | `delete(id)` | `subdomains.delete` | 删除子域名 |
266
+ | `subdomains` | `renew(id)` | `subdomains.renew` | 续费子域名 |
267
+ | `dns_records` | `list(subdomain_id)` | `dns_records.list` | 列出 DNS 记录 |
268
+ | `dns_records` | `create(subdomain_id, type, content, ...)` | `dns_records.create` | 创建 DNS 记录 |
269
+ | `dns_records` | `update(id, ...)` | `dns_records.update` | 更新 DNS 记录 |
270
+ | `dns_records` | `delete(id)` | `dns_records.delete` | 删除 DNS 记录 |
271
+ | `api_keys` | `list()` | `api_keys.list` | 列出 API 密钥 |
272
+ | `api_keys` | `create(name, ip_whitelist?)` | `api_keys.create` | 创建 API 密钥 |
273
+ | `api_keys` | `delete(key_id)` | `api_keys.delete` | 删除 API 密钥 |
274
+ | `api_keys` | `regenerate(key_id)` | `api_keys.regenerate` | 重置 API Secret |
275
+ | `quota` | `get()` | `quota.get` | 查询配额 |
276
+ | `whois` | `query(domain)` | `whois.query` | WHOIS 查询 |
277
+ | `system` | `detect(domain)` | `system.detect` | 检测目标域名 |
278
+ | `system` | `selftest()` | - | 自检 |
279
+
280
+ > 注:`dnsRecords` 和 `apiKeys` 是 `dns_records` 和 `api_keys` 的 camelCase 别名,方便 JS 习惯的用户。
281
+
282
+ ## CLI 命令一览
283
+
284
+ ### 基础
285
+
286
+ | 命令 | 说明 |
287
+ |---|---|
288
+ | `xndns login <key> <secret> [--host=URL]` | 保存凭证 |
289
+ | `xndns logout` | 清除凭证 |
290
+ | `xndns whoami` | 显示当前凭证 |
291
+ | `xndns config [show\|set <key> <value>]` | 查看/修改配置 |
292
+ | `xndns help` | 显示帮助 |
293
+ | `xndns version` | 显示版本 |
294
+
295
+ ### 子域名
296
+
297
+ | 命令 | 说明 |
298
+ |---|---|
299
+ | `xndns subdomains list [opts]` | 列出子域名 |
300
+ | `xndns subdomains get <id>` | 查看详情 |
301
+ | `xndns subdomains create <prefix> <root>` | 注册 |
302
+ | `xndns subdomains delete <id>` | 删除 |
303
+ | `xndns subdomains renew <id>` | 续费 |
304
+
305
+ `list` 选项:`--page=N --per-page=N --search=KEY --status=STATUS --rootdomain=ROOT`
306
+
307
+ ### DNS 记录
308
+
309
+ | 命令 | 说明 |
310
+ |---|---|
311
+ | `xndns dns list <subdomain_id>` | 列出记录 |
312
+ | `xndns dns create <subdomain_id> <type> <content> [--ttl=N] [--priority=N] [--name=NAME]` | 创建 |
313
+ | `xndns dns update <id> [--type=T] [--content=V] [--ttl=N]` | 更新 |
314
+ | `xndns dns delete <id>` | 删除 |
315
+
316
+ 类型支持:`A / AAAA / CNAME / MX / TXT / NS / SRV / CAA`
317
+
318
+ ### 密钥 / 配额 / WHOIS
319
+
320
+ | 命令 | 说明 |
321
+ |---|---|
322
+ | `xndns quota` | 查询配额 |
323
+ | `xndns whois <domain>` | WHOIS 查询 |
324
+ | `xndns keys list` | 列出 API 密钥 |
325
+ | `xndns keys create <name> [--ip-whitelist=IPS]` | 创建密钥 |
326
+ | `xndns keys delete <key_id>` | 删除密钥 |
327
+ | `xndns keys regenerate <key_id>` | 重置 Secret |
328
+
329
+ ### 检测
330
+
331
+ | 命令 | 说明 |
332
+ |---|---|
333
+ | `xndns detect <domain>` | 检测目标域名是否安装小雫DNS |
334
+ | `xndns selftest` | 检测当前配置的主机 |
335
+
336
+ ### 全局选项
337
+
338
+ | 选项 | 说明 |
339
+ |---|---|
340
+ | `--json` | 输出原始 JSON |
341
+ | `--host=URL` | 临时覆盖主机 |
342
+ | `--transport=rest\|rpc\|graphql` | 临时覆盖传输层 |
343
+ | `--debug` | 显示完整错误堆栈 |
344
+
345
+ ## 使用示例
346
+
347
+ ```bash
348
+ # 登录
349
+ xndns login cfsd_0adc0ce77bc62a2903caf044ddb877a5 b190ba15... --host=http://dnshe.xiaona.cc.cd
350
+
351
+ # 查看配额
352
+ xndns quota
353
+ # 配额: 1/3 已用
354
+ # 基础: 3
355
+ # 邀请奖励: 0
356
+ # 可用: 2
357
+
358
+ # 列出子域名
359
+ xndns subdomains list --per-page=5
360
+ # 共 1 条,本页 1 条
361
+ #
362
+ # #7477265796 mynmail.bbroot.com [Registered] 2036-07-07 01:08:40
363
+
364
+ # 注册新子域名
365
+ xndns subdomains create myapp us.kg
366
+ # ✓ 已注册 myapp.us.kg (#7477265797)
367
+
368
+ # 创建 DNS 记录
369
+ xndns dns create 7477265797 A 192.0.2.1 --ttl=600
370
+ # ✓ 已创建 [A] 记录 (id=100)
371
+
372
+ # WHOIS 查询
373
+ xndns whois mynmail.bbroot.com
374
+ # 域名: mynmail.bbroot.com
375
+ # 状态: active
376
+ # 注册: 2026-07-07 01:08
377
+ # 到期: 2036-07-07 01:08
378
+ # 邮箱: abuse@dnshe.com
379
+ # NS: ns1.dnshe.com, ns2.dnshe.com
380
+
381
+ # 切换到 RPC 传输层
382
+ xndns config set transport rpc
383
+ xndns quota
384
+ # (相同输出,但底层走 JSON-RPC)
385
+
386
+ # JSON 输出(适合脚本)
387
+ xndns quota --json | python -c "import sys, json; print(json.load(sys.stdin)['quota']['available'])"
388
+ # 2
389
+
390
+ # 检测某域名是否安装小雫DNS
391
+ xndns detect example.com
392
+ # ✗ example.com 未安装小雫DNS
393
+
394
+ xndns selftest
395
+ # ✓ http://dnshe.xiaona.cc.cd 是小雫DNS v1.0.0
396
+ # 功能: rest, rpc, graphql, cli, sdk, docs
397
+ ```
398
+
399
+ ## Python 脚本示例
400
+
401
+ ```python
402
+ # 找出所有即将到期的子域名(30 天内)
403
+ import time
404
+ from datetime import datetime
405
+ from xndns import Client
406
+
407
+ client = Client.from_config()
408
+ subs = client.subdomains.list(per_page=100)["subdomains"]
409
+ now = time.time()
410
+ for s in subs:
411
+ if not s.get("expires_at"):
412
+ continue
413
+ try:
414
+ dt = datetime.strptime(s["expires_at"], "%Y-%m-%d %H:%M:%S").timestamp()
415
+ if dt - now < 30 * 86400:
416
+ print(f"即将到期: {s['full_domain']} ({s['expires_at']})")
417
+ except ValueError:
418
+ pass
419
+ ```
420
+
421
+ ```python
422
+ # 批量备份所有子域名的 DNS 记录
423
+ import json
424
+ from xndns import Client
425
+
426
+ client = Client.from_config()
427
+ subs = client.subdomains.list(per_page=500)["subdomains"]
428
+ for s in subs:
429
+ recs = client.dns_records.list(s["id"])
430
+ with open(f"dns-backup-{s['id']}.json", "w", encoding="utf-8") as f:
431
+ json.dump({"subdomain": s, "dns_records": recs["records"]}, f, indent=2, ensure_ascii=False)
432
+ print(f"已备份 {s['full_domain']}: {recs['count']} 条记录")
433
+ ```
434
+
435
+ ```python
436
+ # 异步并发查询多个域名的 WHOIS
437
+ import asyncio
438
+ from xndns import AsyncClient
439
+
440
+ async def main():
441
+ client = AsyncClient.from_config()
442
+ domains = ["foo.example.com", "bar.example.com", "baz.example.com"]
443
+ results = await asyncio.gather(*[client.whois.query(d) for d in domains])
444
+ for d, r in zip(domains, results):
445
+ status = "已注册" if r.get("registered") else "未注册"
446
+ print(f"{d}: {status}")
447
+
448
+ asyncio.run(main())
449
+ ```
450
+
451
+ ## 传输层对比
452
+
453
+ | 传输层 | 优势 | 何时使用 |
454
+ |---|---|---|
455
+ | `rest` (默认) | 资源化 URL,缓存友好 | 日常使用 |
456
+ | `rpc` | 单端点,支持批量调用 | 需要批量操作时 |
457
+ | `graphql` | 按需取字段,减少传输量 | 只需要部分字段时 |
458
+
459
+ 三种传输层在功能上完全等价,可以随时通过 `xndns config set transport <value>` 或构造参数 `transport=...` 切换。
460
+
461
+ ## 错误处理
462
+
463
+ CLI 在出错时会返回非零退出码,错误信息格式:
464
+
465
+ ```
466
+ [error_code] error_message
467
+ ```
468
+
469
+ Python SDK 抛出 `ApiError` 异常,包含 `code` / `message` / `http_status` / `details` 字段。
470
+
471
+ 常见错误码:
472
+
473
+ | error_code | HTTP | 说明 |
474
+ |---|---|---|
475
+ | `auth_invalid_credentials` | 401 | 凭证无效 |
476
+ | `quota_exceeded` | 429 | 配额超限 |
477
+ | `rate_limit_exceeded` | 429 | 频率超限 |
478
+ | `subdomain_not_found` | 404 | 子域名不存在 |
479
+ | `provider_operation_failed` | 502 | 上游失败 |
480
+
481
+ 完整错误码列表见 [API 文档](http://dnshe.xiaona.cc.cd/api-docs)。
482
+
483
+ ## 配置文件
484
+
485
+ `~/.xndnsrc` 是 JSON 格式,权限 600:
486
+
487
+ ```json
488
+ {
489
+ "apiKey": "cfsd_xxx",
490
+ "apiSecret": "yyyy",
491
+ "host": "http://dnshe.xiaona.cc.cd",
492
+ "transport": "rest"
493
+ }
494
+ ```
495
+
496
+ 优先级:命令行参数 > 环境变量 > 配置文件 > 构造参数默认值。
497
+
498
+ ## 系统要求
499
+
500
+ - Python 3.9+
501
+ - Linux / macOS / Windows
502
+ - 网络可访问小雫DNS 主机
503
+
504
+ ## 项目结构
505
+
506
+ ```
507
+ xndns-py/
508
+ ├── xndns/
509
+ │ ├── __init__.py # 包入口,暴露 Client / AsyncClient / ApiError
510
+ │ ├── __main__.py # python -m xndns 入口
511
+ │ ├── cli.py # CLI 实现(与 Node 版功能对齐)
512
+ │ ├── client.py # Client + AsyncClient + 命名空间
513
+ │ ├── config.py # Config + ApiError
514
+ │ └── transport.py # REST / RPC / GraphQL 传输层
515
+ ├── pyproject.toml # 包定义(PEP 621)
516
+ ├── install.py # 安装脚本
517
+ ├── uninstall.py # 卸载脚本
518
+ ├── README.md
519
+ ├── LICENSE
520
+ └── CHANGELOG.md
521
+ ```
522
+
523
+ ## 开发
524
+
525
+ ```bash
526
+ # 开发模式安装
527
+ pip install -e .
528
+
529
+ # 运行测试(如果安装了 dev 依赖)
530
+ pytest
531
+
532
+ # 代码检查
533
+ ruff check xndns/
534
+ ```
535
+
536
+ ## License
537
+
538
+ MIT
539
+
540
+ ## 相关
541
+
542
+ - [小雫DNS 控制台](http://dnshe.xiaona.cc.cd)
543
+ - [API 文档](http://dnshe.xiaona.cc.cd/api-docs)
544
+ - [SDK 文档](http://dnshe.xiaona.cc.cd/sdk)
545
+ - [Node.js 版 CLI](http://dnshe.xiaona.cc.cd)