qatlas-cli 0.22.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.
- qatlas_cli-0.22.0/.github/workflows/release.yml +65 -0
- qatlas_cli-0.22.0/.gitignore +19 -0
- qatlas_cli-0.22.0/CHANGELOG.md +15 -0
- qatlas_cli-0.22.0/PKG-INFO +114 -0
- qatlas_cli-0.22.0/README.md +93 -0
- qatlas_cli-0.22.0/pyproject.toml +61 -0
- qatlas_cli-0.22.0/src/qatlas/__init__.py +48 -0
- qatlas_cli-0.22.0/src/qatlas/cli.py +218 -0
- qatlas_cli-0.22.0/src/qatlas/client/__init__.py +1 -0
- qatlas_cli-0.22.0/src/qatlas/client/_common.py +231 -0
- qatlas_cli-0.22.0/src/qatlas/client/auth.py +791 -0
- qatlas_cli-0.22.0/src/qatlas/client/config.py +276 -0
- qatlas_cli-0.22.0/src/qatlas/client/contrib.py +180 -0
- qatlas_cli-0.22.0/src/qatlas/client/mineru.py +1345 -0
- qatlas_cli-0.22.0/src/qatlas/client/paper.py +620 -0
- qatlas_cli-0.22.0/src/qatlas/client/plugins/__init__.py +16 -0
- qatlas_cli-0.22.0/src/qatlas/client/plugins/base.py +40 -0
- qatlas_cli-0.22.0/src/qatlas/client/plugins/registry.py +67 -0
- qatlas_cli-0.22.0/src/qatlas/client/upload.py +331 -0
- qatlas_cli-0.22.0/src/qatlas/config.py +299 -0
- qatlas_cli-0.22.0/src/qatlas/config_yaml.py +136 -0
- qatlas_cli-0.22.0/src/qatlas/parser/__init__.py +16 -0
- qatlas_cli-0.22.0/src/qatlas/parser/__main__.py +99 -0
- qatlas_cli-0.22.0/src/qatlas/parser/arxiv_fetcher.py +242 -0
- qatlas_cli-0.22.0/src/qatlas/parser/keyring.py +135 -0
- qatlas_cli-0.22.0/src/qatlas/parser/mineru_client.py +529 -0
- qatlas_cli-0.22.0/src/qatlas/paths.py +70 -0
- qatlas_cli-0.22.0/src/qatlas/py.typed +0 -0
- qatlas_cli-0.22.0/tests/__init__.py +1 -0
- qatlas_cli-0.22.0/tests/client/__init__.py +0 -0
- qatlas_cli-0.22.0/tests/client/test_config_cmd.py +296 -0
- qatlas_cli-0.22.0/tests/client/test_mineru_cli.py +603 -0
- qatlas_cli-0.22.0/tests/client/test_paper_cli.py +408 -0
- qatlas_cli-0.22.0/tests/client/test_upload_cli.py +286 -0
- qatlas_cli-0.22.0/tests/conftest.py +75 -0
- qatlas_cli-0.22.0/tests/parser/__init__.py +0 -0
- qatlas_cli-0.22.0/tests/parser/test_arxiv_fetcher.py +217 -0
- qatlas_cli-0.22.0/tests/parser/test_mineru_client.py +353 -0
- qatlas_cli-0.22.0/tests/test_cli.py +118 -0
- qatlas_cli-0.22.0/tests/test_client_auth.py +634 -0
- qatlas_cli-0.22.0/tests/test_client_common.py +187 -0
- qatlas_cli-0.22.0/tests/test_config_yaml.py +209 -0
- qatlas_cli-0.22.0/tests/test_paths.py +138 -0
- qatlas_cli-0.22.0/uv.lock +515 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Release pipeline: tag push (v*) → build sdist/wheel → PyPI.
|
|
2
|
+
#
|
|
3
|
+
# PyPI publication uses Trusted Publishing via OIDC — no API token is
|
|
4
|
+
# stored anywhere. PyPI verifies the GitHub OIDC token against a
|
|
5
|
+
# per-project Trusted Publisher config that must match:
|
|
6
|
+
#
|
|
7
|
+
# PyPI project name: qatlas-cli
|
|
8
|
+
# Owner: IAI-USTC-Quantum
|
|
9
|
+
# Repository name: qatlas-cli
|
|
10
|
+
# Workflow filename: release.yml
|
|
11
|
+
# Environment name: pypi
|
|
12
|
+
#
|
|
13
|
+
# One-time setup: configure the publisher at
|
|
14
|
+
# https://pypi.org/manage/project/qatlas-cli/settings/publishing/
|
|
15
|
+
# and create a GitHub Environment named `pypi` in repo Settings
|
|
16
|
+
# (no secrets / approval reviewers needed — the name itself is the
|
|
17
|
+
# match key for the OIDC token).
|
|
18
|
+
#
|
|
19
|
+
# `skip-existing: true` makes re-runs against an already-published
|
|
20
|
+
# version a no-op.
|
|
21
|
+
|
|
22
|
+
name: release
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
push:
|
|
26
|
+
tags: ["v*"]
|
|
27
|
+
workflow_dispatch:
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
pypi-publish:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment:
|
|
33
|
+
name: pypi
|
|
34
|
+
url: https://pypi.org/p/qatlas-cli/
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read # checkout the tagged ref
|
|
37
|
+
id-token: write # required for OIDC trusted publishing
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- uses: actions/setup-python@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.11"
|
|
45
|
+
|
|
46
|
+
- name: Verify tag matches pyproject version
|
|
47
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
48
|
+
run: |
|
|
49
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
50
|
+
PYPROJECT_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
|
|
51
|
+
if [ "$TAG" != "$PYPROJECT_VERSION" ]; then
|
|
52
|
+
echo "tag v$TAG does not match pyproject version $PYPROJECT_VERSION" >&2
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
- name: Build Python distributions
|
|
57
|
+
run: |
|
|
58
|
+
python -m pip install build
|
|
59
|
+
python -m build
|
|
60
|
+
|
|
61
|
+
- name: Publish to PyPI
|
|
62
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
63
|
+
with:
|
|
64
|
+
# Silent no-op on re-runs against already-published versions.
|
|
65
|
+
skip-existing: true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.22.0
|
|
4
|
+
|
|
5
|
+
- 仓库迁移至 IAI-USTC-Quantum/qatlas-cli;安装方式与版本血统继承自
|
|
6
|
+
主仓 `quantum-atlas` 包。
|
|
7
|
+
- 新增 PyPI Trusted Publishing 发布流程(tag `v*` 触发)。
|
|
8
|
+
|
|
9
|
+
## 0.21.0a3
|
|
10
|
+
|
|
11
|
+
- CLI 从主仓 QuantumAtlas 拆分为独立仓库;版本号自主仓 `quantum-atlas`
|
|
12
|
+
包的 0.21.0a3 继承延续,不重新从 0.x 计数。
|
|
13
|
+
- 安装方式与拆分前一致:`uv tool install qatlas-cli`(PyPI)。
|
|
14
|
+
- 发布流程:commitizen(`cz bump`,tag 格式 `v<version>`)打 tag 后由
|
|
15
|
+
`.github/workflows/release.yml` 经 PyPI Trusted Publishing 自动发布。
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qatlas-cli
|
|
3
|
+
Version: 0.22.0
|
|
4
|
+
Summary: Command-line client for QuantumAtlas (qatlas CLI)
|
|
5
|
+
Author: IAI-USTC-Quantum
|
|
6
|
+
Keywords: cli,client,papers,quantum
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: platformdirs<5,>=4
|
|
14
|
+
Requires-Dist: pydantic-settings<3,>=2.0.0
|
|
15
|
+
Requires-Dist: pydantic<3,>=2.5.0
|
|
16
|
+
Requires-Dist: pyyaml<7,>=6.0.1
|
|
17
|
+
Requires-Dist: requests<3,>=2.31.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# qatlas-cli — QuantumAtlas 的命令行客户端
|
|
23
|
+
|
|
24
|
+
> QuantumAtlas 的 `qatlas` 命令行客户端:面向用户侧的论文获取、贡献上传、
|
|
25
|
+
> 本地解析等工作流,通过 HTTP 与服务端 qatlasd 交互。本仓库是主仓
|
|
26
|
+
> QuantumAtlas 拆出的独立私有仓库,只保留客户端与本地工具,不含服务端代码。
|
|
27
|
+
|
|
28
|
+
所有服务端通信都是 `requests` 直发的 HTTP 调用,无重量级 SDK 依赖。
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
从 PyPI 安装为全局 CLI 工具(推荐,与拆分前 `quantum-atlas` 的安装方式一致):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 推荐:uv 全局工具(隔离环境 + 升级方便)
|
|
36
|
+
uv tool install qatlas-cli
|
|
37
|
+
|
|
38
|
+
# 或 pipx
|
|
39
|
+
pipx install qatlas-cli
|
|
40
|
+
|
|
41
|
+
# 或 plain pip
|
|
42
|
+
pip install qatlas-cli
|
|
43
|
+
|
|
44
|
+
qatlas --help
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
也可以直接从 GitHub 安装:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv tool install --from git+ssh://git@github.com/IAI-USTC-Quantum/qatlas-cli.git qatlas-cli
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
本地 editable 安装(开发用):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone git@github.com:IAI-USTC-Quantum/qatlas-cli.git
|
|
57
|
+
cd qatlas-cli
|
|
58
|
+
uv sync --extra dev # 项目内 .venv,editable 安装本包
|
|
59
|
+
# 或装入当前环境:
|
|
60
|
+
uv pip install -e .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 命令概览
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
qatlas config # 管理用户级配置文件(~/.config/qatlas/config.yaml)
|
|
67
|
+
qatlas auth # 管理各 host 的 PAT / session token(login / status / token / logout)
|
|
68
|
+
qatlas paper # 从服务端拉取论文 PDF / markdown(静默 fetch + LRO 轮询)
|
|
69
|
+
qatlas contrib # 贡献者工作流:上传 PDF(contrib pdf)或本地跑 MinerU 再推送(contrib mineru)
|
|
70
|
+
qatlas parser # 抓取并解析 arXiv 论文(本地工作区命令)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
别名:`papers` → `paper`,`parse` → `parser`。
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
qatlas auth login # 设备码 / PAT 登录
|
|
77
|
+
qatlas paper get markdown quant-ph/9508027 --output paper.md
|
|
78
|
+
qatlas contrib pdf quant-ph/9508027v1 --pdf paper.pdf
|
|
79
|
+
qatlas contrib mineru 2501.00010v1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`search` 命令由独立插件 qatlas-search 提供(仓库 IAI-USTC-Quantum/qatlas-search),
|
|
83
|
+
未安装时 `qatlas search` 会给出安装提示。
|
|
84
|
+
|
|
85
|
+
### 插件机制
|
|
86
|
+
|
|
87
|
+
第三方包可以通过 entry point 组 **`qatlas.plugins`** 注册插件,向 CLI 贡献
|
|
88
|
+
顶层命令或 `contrib` 子命令。插件协议(基类 `QatlasPlugin` 与
|
|
89
|
+
`CommandSpec`)定义在 `qatlas/client/plugins/base.py`:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
# 插件包自己的 pyproject.toml
|
|
93
|
+
[project.entry-points."qatlas.plugins"]
|
|
94
|
+
myplugin = "my_package.qatlas_plugin:plugin"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
内置命令优先于插件命令;插件不可用时会被静默跳过,不影响 CLI 本体。
|
|
98
|
+
|
|
99
|
+
## 配置
|
|
100
|
+
|
|
101
|
+
用户级配置位于 `~/.config/qatlas/config.yaml`(首次运行非 `config` 命令时
|
|
102
|
+
自动创建),顶层 `server_url` / `token` 等字段供各客户端命令读取。环境变量
|
|
103
|
+
`QATLAS_*` 系列可覆盖对应配置;`QATLAS_SKIP_DOTENV=1` 跳过仓库 `.env` 加载。
|
|
104
|
+
|
|
105
|
+
## 开发与测试
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv sync --extra dev
|
|
109
|
+
uv run pytest -q # 默认全离线;真打外网的用例标了 network,需 -m network 显式开启
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
测试覆盖:CLI 命令分发与版本解析、auth(设备码登录 / token 存储,mock HTTP)、
|
|
113
|
+
paper / contrib / config 命令行为(mock 服务端)、parser 的 arXiv 抓取与
|
|
114
|
+
MinerU 客户端(固定 fixture / mock)。带 `network` 标记的 live 用例默认跳过。
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# qatlas-cli — QuantumAtlas 的命令行客户端
|
|
2
|
+
|
|
3
|
+
> QuantumAtlas 的 `qatlas` 命令行客户端:面向用户侧的论文获取、贡献上传、
|
|
4
|
+
> 本地解析等工作流,通过 HTTP 与服务端 qatlasd 交互。本仓库是主仓
|
|
5
|
+
> QuantumAtlas 拆出的独立私有仓库,只保留客户端与本地工具,不含服务端代码。
|
|
6
|
+
|
|
7
|
+
所有服务端通信都是 `requests` 直发的 HTTP 调用,无重量级 SDK 依赖。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
从 PyPI 安装为全局 CLI 工具(推荐,与拆分前 `quantum-atlas` 的安装方式一致):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 推荐:uv 全局工具(隔离环境 + 升级方便)
|
|
15
|
+
uv tool install qatlas-cli
|
|
16
|
+
|
|
17
|
+
# 或 pipx
|
|
18
|
+
pipx install qatlas-cli
|
|
19
|
+
|
|
20
|
+
# 或 plain pip
|
|
21
|
+
pip install qatlas-cli
|
|
22
|
+
|
|
23
|
+
qatlas --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
也可以直接从 GitHub 安装:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv tool install --from git+ssh://git@github.com/IAI-USTC-Quantum/qatlas-cli.git qatlas-cli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
本地 editable 安装(开发用):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone git@github.com:IAI-USTC-Quantum/qatlas-cli.git
|
|
36
|
+
cd qatlas-cli
|
|
37
|
+
uv sync --extra dev # 项目内 .venv,editable 安装本包
|
|
38
|
+
# 或装入当前环境:
|
|
39
|
+
uv pip install -e .
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 命令概览
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
qatlas config # 管理用户级配置文件(~/.config/qatlas/config.yaml)
|
|
46
|
+
qatlas auth # 管理各 host 的 PAT / session token(login / status / token / logout)
|
|
47
|
+
qatlas paper # 从服务端拉取论文 PDF / markdown(静默 fetch + LRO 轮询)
|
|
48
|
+
qatlas contrib # 贡献者工作流:上传 PDF(contrib pdf)或本地跑 MinerU 再推送(contrib mineru)
|
|
49
|
+
qatlas parser # 抓取并解析 arXiv 论文(本地工作区命令)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
别名:`papers` → `paper`,`parse` → `parser`。
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
qatlas auth login # 设备码 / PAT 登录
|
|
56
|
+
qatlas paper get markdown quant-ph/9508027 --output paper.md
|
|
57
|
+
qatlas contrib pdf quant-ph/9508027v1 --pdf paper.pdf
|
|
58
|
+
qatlas contrib mineru 2501.00010v1
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`search` 命令由独立插件 qatlas-search 提供(仓库 IAI-USTC-Quantum/qatlas-search),
|
|
62
|
+
未安装时 `qatlas search` 会给出安装提示。
|
|
63
|
+
|
|
64
|
+
### 插件机制
|
|
65
|
+
|
|
66
|
+
第三方包可以通过 entry point 组 **`qatlas.plugins`** 注册插件,向 CLI 贡献
|
|
67
|
+
顶层命令或 `contrib` 子命令。插件协议(基类 `QatlasPlugin` 与
|
|
68
|
+
`CommandSpec`)定义在 `qatlas/client/plugins/base.py`:
|
|
69
|
+
|
|
70
|
+
```toml
|
|
71
|
+
# 插件包自己的 pyproject.toml
|
|
72
|
+
[project.entry-points."qatlas.plugins"]
|
|
73
|
+
myplugin = "my_package.qatlas_plugin:plugin"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
内置命令优先于插件命令;插件不可用时会被静默跳过,不影响 CLI 本体。
|
|
77
|
+
|
|
78
|
+
## 配置
|
|
79
|
+
|
|
80
|
+
用户级配置位于 `~/.config/qatlas/config.yaml`(首次运行非 `config` 命令时
|
|
81
|
+
自动创建),顶层 `server_url` / `token` 等字段供各客户端命令读取。环境变量
|
|
82
|
+
`QATLAS_*` 系列可覆盖对应配置;`QATLAS_SKIP_DOTENV=1` 跳过仓库 `.env` 加载。
|
|
83
|
+
|
|
84
|
+
## 开发与测试
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv sync --extra dev
|
|
88
|
+
uv run pytest -q # 默认全离线;真打外网的用例标了 network,需 -m network 显式开启
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
测试覆盖:CLI 命令分发与版本解析、auth(设备码登录 / token 存储,mock HTTP)、
|
|
92
|
+
paper / contrib / config 命令行为(mock 服务端)、parser 的 arXiv 抓取与
|
|
93
|
+
MinerU 客户端(固定 fixture / mock)。带 `network` 标记的 live 用例默认跳过。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qatlas-cli"
|
|
3
|
+
# Version lineage continues from the main-repo `quantum-atlas` package
|
|
4
|
+
# (the CLI was split out at 0.21.0a3); do not restart from 0.x.
|
|
5
|
+
version = "0.22.0"
|
|
6
|
+
description = "Command-line client for QuantumAtlas (qatlas CLI)"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
authors = [{ name = "IAI-USTC-Quantum" }]
|
|
10
|
+
keywords = ["quantum", "papers", "cli", "client"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Science/Research",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
17
|
+
]
|
|
18
|
+
# Deliberately thin: all server communication is plain `requests` HTTP —
|
|
19
|
+
# no heavyweight SDK dependencies.
|
|
20
|
+
dependencies = [
|
|
21
|
+
"requests>=2.31.0,<3",
|
|
22
|
+
"pyyaml>=6.0.1,<7",
|
|
23
|
+
"platformdirs>=4,<5",
|
|
24
|
+
"pydantic>=2.5.0,<3",
|
|
25
|
+
"pydantic-settings>=2.0.0,<3",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
qatlas = "qatlas.cli:main"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["hatchling>=1.24"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/qatlas"]
|
|
42
|
+
|
|
43
|
+
# Commitizen: `cz bump` bumps [project].version via the uv provider and
|
|
44
|
+
# tags v<version>; the tag push triggers .github/workflows/release.yml.
|
|
45
|
+
[tool.commitizen]
|
|
46
|
+
name = "cz_conventional_commits"
|
|
47
|
+
tag_format = "v$version"
|
|
48
|
+
version_scheme = "pep440"
|
|
49
|
+
version_provider = "uv"
|
|
50
|
+
update_changelog_on_bump = true
|
|
51
|
+
major_version_zero = true
|
|
52
|
+
annotated_tag = true
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
# Live-network tests are opt-in: the default run must be fully offline.
|
|
57
|
+
addopts = "-m 'not network'"
|
|
58
|
+
markers = [
|
|
59
|
+
"network: opt-in tests that call real external APIs",
|
|
60
|
+
"e2e: end-to-end tests that load the repository .env and exercise real external services",
|
|
61
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
QuantumAtlas - AI 驱动的量子算法开发辅助系统
|
|
3
|
+
|
|
4
|
+
核心功能:从论文到可执行量子代码的完整转化链路
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import tomllib
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _version_from_pyproject() -> str:
|
|
15
|
+
"""Read [project].version from the nearest repo-root pyproject.toml.
|
|
16
|
+
|
|
17
|
+
Supports both the legacy flat layout (``qatlas/`` at the repo root,
|
|
18
|
+
pyproject at ``parents[1]``) and the src layout (``src/qatlas/``,
|
|
19
|
+
pyproject at ``parents[2]``).
|
|
20
|
+
"""
|
|
21
|
+
here = Path(__file__).resolve()
|
|
22
|
+
for parent in (here.parents[1], here.parents[2]):
|
|
23
|
+
pyproject = parent / "pyproject.toml"
|
|
24
|
+
if not pyproject.is_file():
|
|
25
|
+
continue
|
|
26
|
+
with pyproject.open("rb") as f:
|
|
27
|
+
data = tomllib.load(f)
|
|
28
|
+
try:
|
|
29
|
+
return str(data["project"]["version"])
|
|
30
|
+
except KeyError:
|
|
31
|
+
continue
|
|
32
|
+
raise FileNotFoundError("no pyproject.toml with [project].version found")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_version() -> str:
|
|
36
|
+
"""Prefer repo pyproject when present, else installed distribution metadata."""
|
|
37
|
+
try:
|
|
38
|
+
return _version_from_pyproject()
|
|
39
|
+
except (FileNotFoundError, KeyError, OSError):
|
|
40
|
+
try:
|
|
41
|
+
return version("qatlas-cli")
|
|
42
|
+
except PackageNotFoundError:
|
|
43
|
+
return "0+unknown"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__version__ = _resolve_version()
|
|
47
|
+
|
|
48
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Top-level command-line entry point for QuantumAtlas.
|
|
2
|
+
|
|
3
|
+
This module backs the ``qatlas`` console script declared in
|
|
4
|
+
``pyproject.toml``. It intentionally delegates to the existing module CLIs so
|
|
5
|
+
their behavior stays identical to ``python -m qatlas.<module>``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import runpy
|
|
11
|
+
import sys
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Mapping, Sequence
|
|
14
|
+
|
|
15
|
+
from qatlas import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Command:
|
|
20
|
+
"""A delegated QuantumAtlas CLI command."""
|
|
21
|
+
|
|
22
|
+
module: str
|
|
23
|
+
summary: str
|
|
24
|
+
client_friendly: bool = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
COMMANDS: Mapping[str, Command] = {
|
|
28
|
+
"config": Command(
|
|
29
|
+
"qatlas.client.config",
|
|
30
|
+
"Manage the user-level config file (~/.config/qatlas/config.yaml)",
|
|
31
|
+
),
|
|
32
|
+
"auth": Command(
|
|
33
|
+
"qatlas.client.auth",
|
|
34
|
+
"Manage saved PATs / session tokens per host (login, status, token, logout)",
|
|
35
|
+
),
|
|
36
|
+
"paper": Command(
|
|
37
|
+
"qatlas.client.paper",
|
|
38
|
+
"Fetch paper PDF / markdown from the server (silent fetch + LRO polling for cache misses)",
|
|
39
|
+
),
|
|
40
|
+
"contrib": Command(
|
|
41
|
+
"qatlas.client.contrib",
|
|
42
|
+
"Contributor workflows: upload PDFs (contrib pdf) or run local MinerU and push (contrib mineru)",
|
|
43
|
+
),
|
|
44
|
+
"parser": Command("qatlas.parser.__main__", "Fetch and parse arXiv papers", False),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ALIASES: Mapping[str, str] = {
|
|
48
|
+
"papers": "paper",
|
|
49
|
+
"parse": "parser",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _print_help() -> None:
|
|
54
|
+
"""Print top-level CLI help."""
|
|
55
|
+
|
|
56
|
+
print(
|
|
57
|
+
"""QuantumAtlas command line
|
|
58
|
+
|
|
59
|
+
Usage:
|
|
60
|
+
qatlas <command> [args...]
|
|
61
|
+
qatlas --version
|
|
62
|
+
qatlas --help
|
|
63
|
+
|
|
64
|
+
Commands:"""
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
print(" Client/operator commands:")
|
|
68
|
+
for name, command in COMMANDS.items():
|
|
69
|
+
if not command.client_friendly:
|
|
70
|
+
continue
|
|
71
|
+
print(f" {name:<10} {command.summary}")
|
|
72
|
+
|
|
73
|
+
print("\n Local workspace commands:")
|
|
74
|
+
for name, command in COMMANDS.items():
|
|
75
|
+
if command.client_friendly:
|
|
76
|
+
continue
|
|
77
|
+
print(f" {name:<10} {command.summary}")
|
|
78
|
+
|
|
79
|
+
# Plugin-contributed top-level commands (only those available in the
|
|
80
|
+
# current environment). No first-party plugins ship today; third-party
|
|
81
|
+
# plugins register via the ``qatlas.plugins`` entry-point group.
|
|
82
|
+
try:
|
|
83
|
+
from qatlas.client.plugins import registry
|
|
84
|
+
|
|
85
|
+
plugin_cmds = registry.top_level_commands()
|
|
86
|
+
except Exception:
|
|
87
|
+
plugin_cmds = {}
|
|
88
|
+
if plugin_cmds:
|
|
89
|
+
print("\n Plugin commands:")
|
|
90
|
+
for name in sorted(plugin_cmds):
|
|
91
|
+
print(f" {name:<10} {plugin_cmds[name].summary}")
|
|
92
|
+
|
|
93
|
+
print(
|
|
94
|
+
"""
|
|
95
|
+
Aliases:
|
|
96
|
+
papers -> paper
|
|
97
|
+
parse -> parser
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
qatlas paper get markdown quant-ph/9508027 --output paper.md
|
|
101
|
+
qatlas paper get pdf 10.1103/PhysRevLett.103.150502 -o paper.pdf
|
|
102
|
+
qatlas contrib pdf quant-ph/9508027v1 --pdf paper.pdf
|
|
103
|
+
qatlas contrib mineru 2501.00010v1
|
|
104
|
+
qatlas contrib mineru --watch
|
|
105
|
+
|
|
106
|
+
Use "qatlas <command> --help" for command-specific options."""
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _print_usage_error(message: str) -> None:
|
|
111
|
+
print(f"Error: {message}", file=sys.stderr)
|
|
112
|
+
print("Run 'qatlas --help' to see available commands.", file=sys.stderr)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _print_search_plugin_hint() -> None:
|
|
116
|
+
"""Explain that ``search`` ships as the standalone qatlas-search plugin."""
|
|
117
|
+
|
|
118
|
+
print(
|
|
119
|
+
"The 'search' command is provided by the standalone plugin "
|
|
120
|
+
"qatlas-search, which is not installed.\n"
|
|
121
|
+
"Install it with pip/uv from the private repository "
|
|
122
|
+
"IAI-USTC-Quantum/qatlas-search, e.g.:\n"
|
|
123
|
+
" uv tool install --from git+ssh://git@github.com/IAI-USTC-Quantum/qatlas-search.git qatlas-search\n"
|
|
124
|
+
"or, into the current environment:\n"
|
|
125
|
+
" uv pip install git+ssh://git@github.com/IAI-USTC-Quantum/qatlas-search.git",
|
|
126
|
+
file=sys.stderr,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _exit_code(code: object) -> int:
|
|
131
|
+
"""Normalize a child ``SystemExit.code`` value to an integer exit code."""
|
|
132
|
+
|
|
133
|
+
if code is None:
|
|
134
|
+
return 0
|
|
135
|
+
if isinstance(code, int):
|
|
136
|
+
return code
|
|
137
|
+
print(code, file=sys.stderr)
|
|
138
|
+
return 1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _run_module(module: str, argv0: str, args: Sequence[str]) -> int:
|
|
142
|
+
"""Run a child module as if it had been invoked with ``python -m``."""
|
|
143
|
+
|
|
144
|
+
original_argv = sys.argv[:]
|
|
145
|
+
sys.argv = [argv0, *args]
|
|
146
|
+
try:
|
|
147
|
+
try:
|
|
148
|
+
runpy.run_module(module, run_name="__main__")
|
|
149
|
+
except SystemExit as exc:
|
|
150
|
+
return _exit_code(exc.code)
|
|
151
|
+
return 0
|
|
152
|
+
finally:
|
|
153
|
+
sys.argv = original_argv
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
157
|
+
"""Run the QuantumAtlas CLI."""
|
|
158
|
+
|
|
159
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
160
|
+
|
|
161
|
+
if not args or args[0] in {"-h", "--help"}:
|
|
162
|
+
_print_help()
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
if args[0] in {"-V", "--version"}:
|
|
166
|
+
print(f"qatlas {__version__}")
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
requested_command = args[0].replace("_", "-")
|
|
170
|
+
command_name = ALIASES.get(requested_command, requested_command)
|
|
171
|
+
command = COMMANDS.get(command_name)
|
|
172
|
+
|
|
173
|
+
# Plugin-contributed top-level commands fill gaps the static table
|
|
174
|
+
# doesn't cover. Built-ins always take precedence.
|
|
175
|
+
plugin_spec = None
|
|
176
|
+
if command is None:
|
|
177
|
+
try:
|
|
178
|
+
from qatlas.client.plugins import registry
|
|
179
|
+
|
|
180
|
+
plugin_spec = registry.top_level_commands().get(command_name)
|
|
181
|
+
except Exception:
|
|
182
|
+
plugin_spec = None
|
|
183
|
+
if plugin_spec is None:
|
|
184
|
+
if command_name == "search":
|
|
185
|
+
_print_search_plugin_hint()
|
|
186
|
+
return 2
|
|
187
|
+
_print_usage_error(f"unknown command '{args[0]}'")
|
|
188
|
+
return 2
|
|
189
|
+
|
|
190
|
+
# v0.17.0+: client config lives exclusively in
|
|
191
|
+
# ~/.config/qatlas/config.yaml. Ensure it exists on first run so
|
|
192
|
+
# the user can immediately edit it; idempotent on subsequent runs.
|
|
193
|
+
#
|
|
194
|
+
# Exception: skip for `qatlas config` itself — its `path` / `show`
|
|
195
|
+
# subcommands intentionally tolerate a missing file and would
|
|
196
|
+
# display misleading "auto-created on first read" behaviour
|
|
197
|
+
# otherwise.
|
|
198
|
+
if command_name != "config":
|
|
199
|
+
try:
|
|
200
|
+
from qatlas.config import ensure_default_config_exists
|
|
201
|
+
ensure_default_config_exists()
|
|
202
|
+
except Exception:
|
|
203
|
+
# Defensive: never block a subcommand on config-file IO;
|
|
204
|
+
# the embedded defaults work for any read-only command.
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
if plugin_spec is not None:
|
|
208
|
+
return plugin_spec.handler(args[1:])
|
|
209
|
+
|
|
210
|
+
return _run_module(
|
|
211
|
+
command.module,
|
|
212
|
+
argv0=f"qatlas {command_name}",
|
|
213
|
+
args=args[1:],
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HTTP client commands for QuantumAtlas."""
|