myastro 0.3.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.
- myastro/__init__.py +102 -0
- myastro/__main__.py +6 -0
- myastro/cli.py +404 -0
- myastro/descriptors.py +245 -0
- myastro/errors.py +30 -0
- myastro/facade.py +265 -0
- myastro/imaging/__init__.py +0 -0
- myastro/io/__init__.py +1 -0
- myastro/io/aria2.py +84 -0
- myastro/io/backup.py +164 -0
- myastro/io/config.py +34 -0
- myastro/io/coords.py +133 -0
- myastro/io/core.py +132 -0
- myastro/io/db.py +126 -0
- myastro/io/docsync.py +58 -0
- myastro/io/doctor.py +280 -0
- myastro/io/health.py +95 -0
- myastro/io/index.py +191 -0
- myastro/io/integrity.py +83 -0
- myastro/io/jobs/__init__.py +71 -0
- myastro/io/jobs/aria2_exec.py +114 -0
- myastro/io/jobs/lifecycle.py +171 -0
- myastro/io/jobs/query.py +82 -0
- myastro/io/jobs/rebuild.py +50 -0
- myastro/io/jobs/runner.py +160 -0
- myastro/io/jobs/spec.py +89 -0
- myastro/io/jobs/store.py +95 -0
- myastro/io/manifest.py +47 -0
- myastro/io/schedule.py +101 -0
- myastro/io/search.py +139 -0
- myastro/io/sources/__init__.py +128 -0
- myastro/io/sources/asassn.py +64 -0
- myastro/io/sources/exoplanet.py +59 -0
- myastro/io/sources/gaia.py +46 -0
- myastro/io/sources/mpc.py +38 -0
- myastro/io/sources/simbad.py +44 -0
- myastro/io/sources/tess.py +76 -0
- myastro/io/sources/unwise.py +97 -0
- myastro/io/sources/vizier.py +49 -0
- myastro/io/sources/ztf.py +120 -0
- myastro/io/sources/ztf_lc.py +57 -0
- myastro/io/testmap.py +189 -0
- myastro/io/top/__init__.py +13 -0
- myastro/io/top/app.py +412 -0
- myastro/io/top/batchscreen.py +146 -0
- myastro/io/top/panes.py +388 -0
- myastro/io/top/screens.py +270 -0
- myastro/io/top/suggest.py +46 -0
- myastro/io/types.py +45 -0
- myastro/io/urls.py +47 -0
- myastro/lc/__init__.py +250 -0
- myastro/phot/__init__.py +0 -0
- myastro/tasks.py +64 -0
- myastro-0.3.0.dist-info/METADATA +211 -0
- myastro-0.3.0.dist-info/RECORD +58 -0
- myastro-0.3.0.dist-info/WHEEL +4 -0
- myastro-0.3.0.dist-info/entry_points.txt +2 -0
- myastro-0.3.0.dist-info/licenses/LICENSE +21 -0
myastro/__init__.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""myastro — 天文数据获取与管理库(公开门面)。
|
|
2
|
+
|
|
3
|
+
第三方唯一应导入的模块。io/ 以下为内核私有,重构不另行通知。
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
import myastro as ma
|
|
7
|
+
r = ma.fetch("tess", target="WASP-12", sector=20) # 首次下载,此后缓存命中
|
|
8
|
+
df = pd.read_parquet(r.paths[0])
|
|
9
|
+
ma.status(); ma.wait(id); ma.retry(id); ma.delete([id])
|
|
10
|
+
|
|
11
|
+
稳定性承诺:
|
|
12
|
+
- __all__ 中的符号按语义化版本演进(破坏性改动走 deprecation)
|
|
13
|
+
- 产物 schema(如光变 parquet 的 time/flux/flux_err/quality 列)是契约,
|
|
14
|
+
变更走 schema_version 字段(当前 SCHEMA_VERSION = 1)
|
|
15
|
+
- 异常体系见 errors.py:ParamError / NetworkError / NotFoundError / StateError
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _detect_version() -> str:
|
|
21
|
+
"""版本单一来源:以 pyproject.toml 为唯一手写处。
|
|
22
|
+
|
|
23
|
+
已安装时读包元数据(importlib.metadata);直接从源码树运行时元数据可能缺失,
|
|
24
|
+
回退到 "0.3.0+source",避免出现第二个手写版本号(此前 pyproject 0.1.0 与
|
|
25
|
+
这里 0.2.0 已经漂移过)。
|
|
26
|
+
"""
|
|
27
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
return version("myastro")
|
|
31
|
+
except PackageNotFoundError: # 源码直跑(未安装)
|
|
32
|
+
return "0.3.0+source"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__version__ = _detect_version()
|
|
36
|
+
SCHEMA_VERSION = 1
|
|
37
|
+
|
|
38
|
+
from .descriptors import FieldSpec, SourceDescriptor
|
|
39
|
+
from .errors import NetworkError, NotFoundError, ParamError, StateError
|
|
40
|
+
from .facade import (
|
|
41
|
+
audit,
|
|
42
|
+
backup,
|
|
43
|
+
batch,
|
|
44
|
+
coverage,
|
|
45
|
+
delete,
|
|
46
|
+
describe,
|
|
47
|
+
doctor,
|
|
48
|
+
fetch,
|
|
49
|
+
health,
|
|
50
|
+
index_list,
|
|
51
|
+
index_search,
|
|
52
|
+
index_update,
|
|
53
|
+
init_root,
|
|
54
|
+
prefill,
|
|
55
|
+
query,
|
|
56
|
+
register_source,
|
|
57
|
+
restore,
|
|
58
|
+
retry,
|
|
59
|
+
retry_batch,
|
|
60
|
+
schedule_install,
|
|
61
|
+
schedule_list,
|
|
62
|
+
search,
|
|
63
|
+
sources,
|
|
64
|
+
status,
|
|
65
|
+
wait,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
"SCHEMA_VERSION",
|
|
70
|
+
"FieldSpec",
|
|
71
|
+
"NetworkError",
|
|
72
|
+
"NotFoundError",
|
|
73
|
+
"ParamError",
|
|
74
|
+
"SourceDescriptor",
|
|
75
|
+
"StateError",
|
|
76
|
+
"__version__",
|
|
77
|
+
"audit",
|
|
78
|
+
"backup",
|
|
79
|
+
"batch",
|
|
80
|
+
"coverage",
|
|
81
|
+
"delete",
|
|
82
|
+
"describe",
|
|
83
|
+
"doctor",
|
|
84
|
+
"fetch",
|
|
85
|
+
"health",
|
|
86
|
+
"index_list",
|
|
87
|
+
"index_search",
|
|
88
|
+
"index_update",
|
|
89
|
+
"init_root",
|
|
90
|
+
"prefill",
|
|
91
|
+
"query",
|
|
92
|
+
"register_source",
|
|
93
|
+
"restore",
|
|
94
|
+
"retry",
|
|
95
|
+
"retry_batch",
|
|
96
|
+
"schedule_install",
|
|
97
|
+
"schedule_list",
|
|
98
|
+
"search",
|
|
99
|
+
"sources",
|
|
100
|
+
"status",
|
|
101
|
+
"wait",
|
|
102
|
+
]
|
myastro/__main__.py
ADDED
myastro/cli.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""CLI 入口:myastro <子命令>。每命令一个处理函数,main 负责分发与错误→退出码。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .io import jobs
|
|
8
|
+
from .io.db import DB
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _cmd_jobs(args, db) -> int:
|
|
12
|
+
import myastro as ma
|
|
13
|
+
|
|
14
|
+
rows = ma.status()
|
|
15
|
+
if not rows:
|
|
16
|
+
print("(无作业)")
|
|
17
|
+
for r in rows:
|
|
18
|
+
err = f" err={r['error'][:60]}" if r.get("error") else ""
|
|
19
|
+
print(f"#{r['id']:<4} {r['executor']:<7} {r['status']:<10}"
|
|
20
|
+
f" {r['updated_at']}{err}")
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cmd_wait(args, db) -> int:
|
|
25
|
+
import myastro as ma
|
|
26
|
+
|
|
27
|
+
result = ma.wait(args.job_id)
|
|
28
|
+
for p in result.paths:
|
|
29
|
+
print(p)
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _cmd_retry(args, db) -> int:
|
|
34
|
+
import myastro as ma
|
|
35
|
+
|
|
36
|
+
ma.retry(args.job_id)
|
|
37
|
+
print(f"job {args.job_id} 已重新提交")
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _cmd_delete(args, db) -> int:
|
|
42
|
+
import myastro as ma
|
|
43
|
+
|
|
44
|
+
out = ma.delete(args.job_ids)
|
|
45
|
+
if out["deleted"]:
|
|
46
|
+
print(f"已删除 {len(out['deleted'])}: {out['deleted']}"
|
|
47
|
+
f"(dataset/files 资产保留)")
|
|
48
|
+
for job_id, reason in out["refused"]:
|
|
49
|
+
print(f"拒绝 job {job_id}: {reason}")
|
|
50
|
+
return 0 if not out["refused"] else 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cmd_orphans(args, db) -> int:
|
|
54
|
+
n = jobs.find_orphans(db=db)
|
|
55
|
+
print(f"标记孤儿作业 {n} 个")
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _cmd_index(args, db) -> int:
|
|
60
|
+
import myastro as ma
|
|
61
|
+
|
|
62
|
+
if args.index_cmd == "list":
|
|
63
|
+
for r in ma.index_list():
|
|
64
|
+
print(f"{r['name']:<16} "
|
|
65
|
+
f"{'已构建' if r['built'] else '未构建'} {r['desc']}")
|
|
66
|
+
else:
|
|
67
|
+
print(f"索引已更新: {ma.index_update(args.name)}")
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _cmd_fetch(args, db) -> int:
|
|
72
|
+
import myastro as ma
|
|
73
|
+
params = {k: v for k, v in vars(args).items()
|
|
74
|
+
if k not in ("fn", "cmd", "source") and v is not None}
|
|
75
|
+
r = ma.fetch(args.source, **params)
|
|
76
|
+
for p in r.paths:
|
|
77
|
+
print(p, f"(cache_hit={r.cache_hit})")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _cmd_audit(args, db) -> int:
|
|
82
|
+
import myastro as ma
|
|
83
|
+
r = ma.audit()
|
|
84
|
+
print(f"核对 {r['total']}:ok {r['ok']} | 基线新建 {r['baselined']} | "
|
|
85
|
+
f"缺失 {r['missing']} | 尺寸不符 {r['size_mismatch']} | "
|
|
86
|
+
f"损坏 {r['corrupt']}")
|
|
87
|
+
for kind in ("missing", "size_mismatch", "corrupt"):
|
|
88
|
+
for item in (__import__("myastro.io.integrity", fromlist=["x"])
|
|
89
|
+
.audit_detail(kind, limit=10)):
|
|
90
|
+
print(f" [{kind}] {item['path']} ({item['why']})")
|
|
91
|
+
return 0 if (r["missing"] + r["size_mismatch"] + r["corrupt"]) == 0 else 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _cmd_search(args, db) -> int:
|
|
95
|
+
import myastro as ma
|
|
96
|
+
near = tuple(args.near) if args.near else None
|
|
97
|
+
rows = ma.search(source=args.source, target=args.target,
|
|
98
|
+
name=args.name, since=args.since, until=args.until,
|
|
99
|
+
near=near, radius_deg=args.radius)
|
|
100
|
+
for r in rows:
|
|
101
|
+
sep = f" sep={r['sep_deg']:.4f}°" if "sep_deg" in r else ""
|
|
102
|
+
print(f"#{r['id']:<3} {r['source']:<10} {r['target'] or '-':<14}"
|
|
103
|
+
f"{sep} {r['created']} {r['path'] or ''}")
|
|
104
|
+
print(f"共 {len(rows)} 条")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _cmd_coverage(args, db) -> int:
|
|
109
|
+
import myastro as ma
|
|
110
|
+
r = ma.coverage(args.index)
|
|
111
|
+
print(f"{r['index']}: 本地 {r['local']} / 全量 {r['total']}"
|
|
112
|
+
f"({r['pct']}%)")
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _cmd_retry_batch(args, db) -> int:
|
|
117
|
+
import myastro as ma
|
|
118
|
+
out = ma.retry_batch(args.batch_id)
|
|
119
|
+
print(f"重试 {len(out['retried'])}: {out['retried']}")
|
|
120
|
+
for jid, why in out["skipped"]:
|
|
121
|
+
print(f" 跳过 {jid}: {why}")
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _cmd_test_map(args, db) -> int:
|
|
126
|
+
from .errors import ParamError
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
return _test_map(args)
|
|
130
|
+
except ValueError as e: # 不在源码仓库(如已安装的 wheel)
|
|
131
|
+
raise ParamError(f"该命令仅用于源码仓库:{e}") from e
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _test_map(args) -> int:
|
|
135
|
+
from .io.testmap import problems, sync
|
|
136
|
+
if args.check:
|
|
137
|
+
issues = problems()
|
|
138
|
+
for i in issues:
|
|
139
|
+
print("✗", i)
|
|
140
|
+
print("测试能力地图:", "一致" if not issues else f"{len(issues)} 处不一致")
|
|
141
|
+
return 1 if issues else 0
|
|
142
|
+
path = sync()
|
|
143
|
+
print(f"已生成 {path}")
|
|
144
|
+
for i in problems():
|
|
145
|
+
print(" ⚠", i)
|
|
146
|
+
return 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _cmd_health(args, db) -> int:
|
|
150
|
+
import myastro as ma
|
|
151
|
+
for r in ma.health(args.source):
|
|
152
|
+
mark = {"ok": "✅", "degraded": "⚠", "unreachable": "❌"}.get(
|
|
153
|
+
r["status"], "?")
|
|
154
|
+
print(f"{mark} {r['source']:<10} {r['status']:<12} {r['detail']}")
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _cmd_schedule(args, db) -> int:
|
|
159
|
+
import myastro as ma
|
|
160
|
+
|
|
161
|
+
from .io.schedule import remove
|
|
162
|
+
if args.schedule_cmd == "list":
|
|
163
|
+
for r in ma.schedule_list():
|
|
164
|
+
mark = "✅已装" if r["installed"] else "—未装"
|
|
165
|
+
print(f"{mark} {r['task']:<13} {r['when']} {r['desc']}")
|
|
166
|
+
elif args.schedule_cmd == "install":
|
|
167
|
+
p = ma.schedule_install(args.task)
|
|
168
|
+
print(f"已安装 launchd 任务: {p}")
|
|
169
|
+
else:
|
|
170
|
+
print("已移除" if remove(args.task) else "本就未安装")
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _cmd_docsync(args, db) -> int:
|
|
175
|
+
from .errors import ParamError
|
|
176
|
+
from .io.docsync import sync
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
print("文档已更新" if sync() else "文档已是最新(无变化)")
|
|
180
|
+
except ValueError as e: # 不在源码仓库(如已安装的 wheel)
|
|
181
|
+
raise ParamError(f"该命令仅用于源码仓库:{e}") from e
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_init(args, db) -> int:
|
|
186
|
+
import myastro as ma
|
|
187
|
+
|
|
188
|
+
paths = ma.init_root()
|
|
189
|
+
print("数据根:", paths["data"])
|
|
190
|
+
print("缓存: ", paths["cache"])
|
|
191
|
+
print("账本: ", paths["db"])
|
|
192
|
+
return 0
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _cmd_doctor(args, db) -> int:
|
|
196
|
+
import myastro as ma
|
|
197
|
+
|
|
198
|
+
checks = ma.doctor()
|
|
199
|
+
symbol = {"ok": "✓", "warn": "⚠", "fail": "✗"}
|
|
200
|
+
for c in checks:
|
|
201
|
+
line = f"{symbol.get(c['status'], '?')} {c['name']}"
|
|
202
|
+
if c["detail"]:
|
|
203
|
+
line += f":{c['detail']}"
|
|
204
|
+
print(line)
|
|
205
|
+
if c["fix"] and c["status"] != "ok":
|
|
206
|
+
print(f" → {c['fix']}")
|
|
207
|
+
fails = sum(1 for c in checks if c["status"] == "fail")
|
|
208
|
+
warns = sum(1 for c in checks if c["status"] == "warn")
|
|
209
|
+
print(f"自检完成:{len(checks)} 项,{warns} 个警告,{fails} 个问题")
|
|
210
|
+
return 1 if fails else 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _cmd_backup(args, db) -> int:
|
|
214
|
+
import myastro as ma
|
|
215
|
+
|
|
216
|
+
out = ma.backup(args.out, with_index=args.with_index)
|
|
217
|
+
print(f"备份目录: {out['dir']}")
|
|
218
|
+
print(f"账本: {out['db']}({out['bytes'] / 1024:.0f} KB)")
|
|
219
|
+
print(f"清单: {out['manifest']}")
|
|
220
|
+
if out["with_index"]:
|
|
221
|
+
print("(含索引副本)")
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _cmd_restore(args, db) -> int:
|
|
226
|
+
import myastro as ma
|
|
227
|
+
|
|
228
|
+
out = ma.restore(args.source, force=args.force)
|
|
229
|
+
print(f"已从 {out['restored_from']} 恢复 → {out['db']}")
|
|
230
|
+
if out["safety_copy"]:
|
|
231
|
+
print(f"覆盖前的账本已留存: {out['safety_copy']}")
|
|
232
|
+
print(f"计数: {out['counts']}")
|
|
233
|
+
return 0
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _cmd_top(args, db) -> int:
|
|
237
|
+
from .io.top import AstroTop
|
|
238
|
+
AstroTop().run()
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _add_fetch(parser: argparse.ArgumentParser) -> None:
|
|
243
|
+
"""fetch 子命令:参数由源描述符的 FieldSpec 声明生成。"""
|
|
244
|
+
from .descriptors import REGISTRY
|
|
245
|
+
sub = parser.add_subparsers(dest="source", required=True,
|
|
246
|
+
metavar="SOURCE")
|
|
247
|
+
for name, d in REGISTRY.items():
|
|
248
|
+
p = sub.add_parser(name, help=d.desc)
|
|
249
|
+
for f in d.fields:
|
|
250
|
+
extra = {}
|
|
251
|
+
if f.choices:
|
|
252
|
+
extra["choices"] = f.choices
|
|
253
|
+
p.add_argument(f"--{f.name.replace('_', '-')}",
|
|
254
|
+
dest=f.name,
|
|
255
|
+
default=f.default,
|
|
256
|
+
type=str if f.type is str else f.type,
|
|
257
|
+
help=f.label or None, **extra)
|
|
258
|
+
p.set_defaults(fn=_cmd_fetch)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
262
|
+
from . import __version__
|
|
263
|
+
|
|
264
|
+
parser = argparse.ArgumentParser(prog="myastro",
|
|
265
|
+
description="天文数据获取与管理")
|
|
266
|
+
# --version(版本单一来源见 src/myastro/__init__.py::_detect_version)
|
|
267
|
+
parser.add_argument("--version", action="version",
|
|
268
|
+
version=f"myastro {__version__}")
|
|
269
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
270
|
+
|
|
271
|
+
sub.add_parser("jobs", help="列出作业(最近 100 条)").set_defaults(
|
|
272
|
+
fn=_cmd_jobs)
|
|
273
|
+
p_wait = sub.add_parser("wait", help="阻塞等待作业完成")
|
|
274
|
+
p_wait.add_argument("job_id", type=int)
|
|
275
|
+
p_wait.set_defaults(fn=_cmd_wait)
|
|
276
|
+
p_retry = sub.add_parser("retry", help="重试失败/僵尸/done(force)作业")
|
|
277
|
+
p_retry.add_argument("job_id", type=int)
|
|
278
|
+
p_retry.set_defaults(fn=_cmd_retry)
|
|
279
|
+
p_fetch = sub.add_parser("fetch", help="获取数据(参数由描述符生成)")
|
|
280
|
+
_add_fetch(p_fetch)
|
|
281
|
+
p_sched = sub.add_parser("schedule", help="定时任务(launchd)管理")
|
|
282
|
+
sc = p_sched.add_subparsers(dest="schedule_cmd", required=True)
|
|
283
|
+
sc.add_parser("list", help="列出任务与安装状态")
|
|
284
|
+
p_si = sc.add_parser("install", help="安装 launchd 任务")
|
|
285
|
+
p_si.add_argument("task", choices=list(
|
|
286
|
+
__import__("myastro.io.schedule", fromlist=["TASKS"]).TASKS))
|
|
287
|
+
p_sr = sc.add_parser("remove", help="移除任务")
|
|
288
|
+
p_sr.add_argument("task", choices=list(
|
|
289
|
+
__import__("myastro.io.schedule", fromlist=["TASKS"]).TASKS))
|
|
290
|
+
p_sched.set_defaults(fn=_cmd_schedule)
|
|
291
|
+
sub.add_parser("init", help="创建数据根与账本(幂等)").set_defaults(
|
|
292
|
+
fn=_cmd_init)
|
|
293
|
+
sub.add_parser("doctor", help="环境自检(依赖/路径/账本/索引/平台)").set_defaults(
|
|
294
|
+
fn=_cmd_doctor)
|
|
295
|
+
p_bk = sub.add_parser("backup", help="备份账本(SQLite 在线备份)")
|
|
296
|
+
p_bk.add_argument("--out", help="备份输出目录(默认 账本目录/backups/时间戳)")
|
|
297
|
+
p_bk.add_argument("--with-index", action="store_true", dest="with_index",
|
|
298
|
+
help="连同索引 parquet 一起复制(可上百 MB)")
|
|
299
|
+
p_bk.set_defaults(fn=_cmd_backup)
|
|
300
|
+
p_rs = sub.add_parser("restore", help="从备份恢复账本(默认拒绝覆盖)")
|
|
301
|
+
p_rs.add_argument("source", help="备份目录或 astro.db 路径")
|
|
302
|
+
p_rs.add_argument("--force", action="store_true",
|
|
303
|
+
help="覆盖现有账本(会先留存 .pre-restore-*.bak)")
|
|
304
|
+
p_rs.set_defaults(fn=_cmd_restore)
|
|
305
|
+
p_tmap = sub.add_parser("test-map", help="生成/检查测试能力地图")
|
|
306
|
+
p_tmap.add_argument("--check", action="store_true",
|
|
307
|
+
help="只检查不写文件(守卫用)")
|
|
308
|
+
p_tmap.set_defaults(fn=_cmd_test_map)
|
|
309
|
+
p_health = sub.add_parser("health", help="源健康探测")
|
|
310
|
+
p_health.add_argument("source", nargs="?", default=None)
|
|
311
|
+
p_health.set_defaults(fn=_cmd_health)
|
|
312
|
+
p_audit = sub.add_parser("audit", help="完整性巡检(sha 基线+核对)")
|
|
313
|
+
p_audit.set_defaults(fn=_cmd_audit)
|
|
314
|
+
p_search = sub.add_parser("search", help="本地检索已有数据")
|
|
315
|
+
p_search.add_argument("--source"); p_search.add_argument("--target")
|
|
316
|
+
p_search.add_argument("--name"); p_search.add_argument("--since")
|
|
317
|
+
p_search.add_argument("--until")
|
|
318
|
+
p_search.add_argument("--near", nargs=2, type=float, metavar=("RA", "DEC"),
|
|
319
|
+
help="精确圆锥检索中心(度)")
|
|
320
|
+
p_search.add_argument("--radius", type=float, default=0.1,
|
|
321
|
+
help="圆锥半径(度,默认 0.1)")
|
|
322
|
+
p_search.set_defaults(fn=_cmd_search)
|
|
323
|
+
p_cov = sub.add_parser("coverage", help="索引覆盖率")
|
|
324
|
+
p_cov.add_argument("index")
|
|
325
|
+
p_cov.set_defaults(fn=_cmd_coverage)
|
|
326
|
+
p_rb = sub.add_parser("retry-batch", help="整批重试失败作业")
|
|
327
|
+
p_rb.add_argument("batch_id")
|
|
328
|
+
p_rb.set_defaults(fn=_cmd_retry_batch)
|
|
329
|
+
p_del = sub.add_parser("delete", help="删除作业记录(资产保留)")
|
|
330
|
+
p_del.add_argument("job_ids", nargs="+", type=int)
|
|
331
|
+
p_del.set_defaults(fn=_cmd_delete)
|
|
332
|
+
sub.add_parser("orphans", help="扫描并标记心跳超时的孤儿作业"
|
|
333
|
+
).set_defaults(fn=_cmd_orphans)
|
|
334
|
+
|
|
335
|
+
p_idx = sub.add_parser("index", help="本地索引管理(TUI 候选列表)")
|
|
336
|
+
p_idx_sub = p_idx.add_subparsers(dest="index_cmd", required=True)
|
|
337
|
+
p_idx_sub.add_parser("list", help="列出索引").set_defaults(fn=_cmd_index)
|
|
338
|
+
p_upd = p_idx_sub.add_parser("update", help="强制更新(下载→解析→删源)")
|
|
339
|
+
p_upd.add_argument("name")
|
|
340
|
+
p_upd.set_defaults(fn=_cmd_index)
|
|
341
|
+
|
|
342
|
+
sub.add_parser("doc-sync",
|
|
343
|
+
help="再生 design-io.md 数据源矩阵(文档=渲染产物)"
|
|
344
|
+
).set_defaults(fn=_cmd_docsync)
|
|
345
|
+
sub.add_parser("top", help="数据管理 TUI").set_defaults(fn=_cmd_top)
|
|
346
|
+
return parser
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# 退出码契约(脚本可据此分流;错误消息走 stderr,不打印栈):
|
|
350
|
+
# 0 成功 | 1 部分失败(如删除时部分作业被拒绝)| 2 参数错 | 3 未找到
|
|
351
|
+
# | 4 网络 | 5 状态不允许 | 130 键盘中断
|
|
352
|
+
EXIT_CODES = {
|
|
353
|
+
"ParamError": 2, # 参数/未知源:调用方传错了
|
|
354
|
+
"NotFoundError": 3, # 目标不存在(作业/索引/批次)
|
|
355
|
+
"NetworkError": 4, # 网络侧失败(重试后)
|
|
356
|
+
"StateError": 5, # 状态不允许(如 done 重试、健康作业删除)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def main(argv: list[str] | None = None) -> int:
|
|
361
|
+
"""CLI 入口:异常转成清晰的 stderr 消息 + 退出码。
|
|
362
|
+
|
|
363
|
+
早期版本没有这一层——`myastro retry 999999` 直接抛栈崩溃。
|
|
364
|
+
对"第三个客户端"(脚本/第三方)而言,退出码是可编程契约的一部分。
|
|
365
|
+
"""
|
|
366
|
+
from . import errors
|
|
367
|
+
|
|
368
|
+
args = _build_parser().parse_args(argv)
|
|
369
|
+
if args.cmd == "top":
|
|
370
|
+
try:
|
|
371
|
+
return args.fn(args, None)
|
|
372
|
+
except KeyboardInterrupt:
|
|
373
|
+
print("已中断", file=sys.stderr)
|
|
374
|
+
return 130
|
|
375
|
+
db = DB().init() # 单连接:用完即关(不再每命令各开一个)
|
|
376
|
+
try:
|
|
377
|
+
return args.fn(args, db)
|
|
378
|
+
except errors.ParamError as e:
|
|
379
|
+
print(f"参数错误:{e}", file=sys.stderr)
|
|
380
|
+
return EXIT_CODES["ParamError"]
|
|
381
|
+
except errors.NotFoundError as e:
|
|
382
|
+
print(f"未找到:{e}", file=sys.stderr)
|
|
383
|
+
return EXIT_CODES["NotFoundError"]
|
|
384
|
+
except errors.NetworkError as e:
|
|
385
|
+
print(f"网络错误:{e}", file=sys.stderr)
|
|
386
|
+
return EXIT_CODES["NetworkError"]
|
|
387
|
+
except errors.StateError as e:
|
|
388
|
+
print(f"状态不允许:{e}", file=sys.stderr)
|
|
389
|
+
return EXIT_CODES["StateError"]
|
|
390
|
+
except LookupError as e: # 内核侧"不存在"(如未知任务/索引)
|
|
391
|
+
print(f"未找到:{e}", file=sys.stderr)
|
|
392
|
+
return EXIT_CODES["NotFoundError"]
|
|
393
|
+
except ValueError as e: # 内核侧参数问题
|
|
394
|
+
print(f"参数错误:{e}", file=sys.stderr)
|
|
395
|
+
return EXIT_CODES["ParamError"]
|
|
396
|
+
except KeyboardInterrupt:
|
|
397
|
+
print("已中断", file=sys.stderr)
|
|
398
|
+
return 130
|
|
399
|
+
finally:
|
|
400
|
+
db.close()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if __name__ == "__main__":
|
|
404
|
+
sys.exit(main())
|