merge-cli 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,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: merge-cli
3
+ Version: 1.0.0
4
+ Summary: CLI for MERGE variant pathogenicity prediction
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: click>=8.1
9
+ Requires-Dist: requests>=2.31
10
+ Requires-Dist: rich>=13.0
11
+ Requires-Dist: keyring>=24.0
12
+
13
+ # MERGE CLI
14
+
15
+ MERGE 变异致病性预测命令行工具。所有计算在服务器端完成,本地只需 Python 3.9+。
16
+
17
+ ## 安装
18
+
19
+ ```bash
20
+ pip install merge-cli
21
+ ```
22
+
23
+ ## 快速开始
24
+
25
+ ```bash
26
+ # 1. 配置服务器地址(只需配置一次)
27
+ merge config set-url https://your-server.com
28
+ merge config set-token YOUR_API_TOKEN
29
+
30
+ # 2. 单变异预测
31
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G
32
+
33
+ # 3. 批量预测(上传 VCF,结果发邮件)
34
+ merge batch my_variants.vcf --email you@example.com
35
+ ```
36
+
37
+ ## 命令详解
38
+
39
+ ### `merge predict` — 单变异预测
40
+
41
+ ```
42
+ 选项:
43
+ --chrom 染色体(chr17 或 17 均可) [必填]
44
+ --pos 变异位置(1-based) [必填]
45
+ --ref 参考碱基 [必填]
46
+ --alt 突变碱基 [必填]
47
+ --genome hg38 / hg19 [默认: hg38]
48
+ --format table / json / tsv [默认: table]
49
+ --no-ensemble 跳过 MERGE 集成打分
50
+ --no-alphagenome / --no-hyenadna / --no-nt
51
+ --no-alphamissense / --no-esm1b / --no-gpn-msa
52
+ --no-popeve / --no-evo2 / --no-evo1
53
+ ```
54
+
55
+ **输出示例(table 模式):**
56
+
57
+ ```
58
+ ╭──────────────────────────────────────────╮
59
+ │ MERGE 预测结果 │
60
+ ├──────────────────┬───────────────────────┤
61
+ │ 变异位点 │ chr17:43092919 A → G │
62
+ │ 基因 / 转录本 │ BRCA1 / ENST00000357654│
63
+ ├──────────────────┼───────────────────────┤
64
+ │ MERGE 致病性 │ 87.3% Likely Pathogenic│
65
+ │ 使用模型 │ ClinVar │
66
+ ├──────────────────┼───────────────────────┤
67
+ │ AlphaMissense │ 0.9341 │
68
+ │ ESM1b │ -3.2180 │
69
+ │ GPN-MSA │ -1.4420 │
70
+ │ popEVE │ 0.8812 │
71
+ │ AlphaGenome │ 0.0234 │
72
+ │ HyenaDNA │ -2.1100 │
73
+ │ NT │ -1.8830 │
74
+ │ Evo2 LLR │ -4.2210 │
75
+ │ Evo1 Delta │ -3.9910 │
76
+ ╰──────────────────┴───────────────────────╯
77
+ ```
78
+
79
+ **管道输出(TSV 模式,方便写脚本):**
80
+
81
+ ```bash
82
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G --format tsv >> results.tsv
83
+ ```
84
+
85
+ **脚本批量调用单变异(小于 20 个时比上传 VCF 更快):**
86
+
87
+ ```bash
88
+ while IFS=$'\t' read -r chrom pos ref alt; do
89
+ merge predict --chrom "$chrom" --pos "$pos" --ref "$ref" --alt "$alt" --format tsv
90
+ done < variants.tsv >> results.tsv
91
+ ```
92
+
93
+ ---
94
+
95
+ ### `merge batch` — 批量 VCF 预测
96
+
97
+ ```bash
98
+ merge batch variants.vcf --email you@example.com
99
+ merge batch variants.vcf.gz --email you@example.com --genome hg19 --no-evo2
100
+ ```
101
+
102
+ 任务提交成功后返回 Job ID,结果通过邮件发送,附件包含:
103
+ - `batch_predictions.csv` — 所有模型分数汇总(可直接用 Excel 打开)
104
+ - `batch_predictions.vcf` — 标准 VCF 格式,含 MERGE 及各模型分数
105
+ - `imputation_details.csv` — MERGE 各特征缺失填补情况
106
+
107
+ 服务端限制:每 IP 每日 5 次,同一邮箱间隔 1 小时,单次最多 2000 个变异。
108
+
109
+ ---
110
+
111
+ ### `merge status` — 查询批量任务
112
+
113
+ ```bash
114
+ merge status A3F2C1B0 # 查看一次
115
+ merge status A3F2C1B0 --watch # 每 30 秒轮询
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 环境变量(适合 CI / 服务器环境)
121
+
122
+ ```bash
123
+ export MERGE_API_URL=https://your-server.com
124
+ export MERGE_API_TOKEN=your-token
125
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T
126
+ ```
@@ -0,0 +1,114 @@
1
+ # MERGE CLI
2
+
3
+ MERGE 变异致病性预测命令行工具。所有计算在服务器端完成,本地只需 Python 3.9+。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install merge-cli
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ```bash
14
+ # 1. 配置服务器地址(只需配置一次)
15
+ merge config set-url https://your-server.com
16
+ merge config set-token YOUR_API_TOKEN
17
+
18
+ # 2. 单变异预测
19
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G
20
+
21
+ # 3. 批量预测(上传 VCF,结果发邮件)
22
+ merge batch my_variants.vcf --email you@example.com
23
+ ```
24
+
25
+ ## 命令详解
26
+
27
+ ### `merge predict` — 单变异预测
28
+
29
+ ```
30
+ 选项:
31
+ --chrom 染色体(chr17 或 17 均可) [必填]
32
+ --pos 变异位置(1-based) [必填]
33
+ --ref 参考碱基 [必填]
34
+ --alt 突变碱基 [必填]
35
+ --genome hg38 / hg19 [默认: hg38]
36
+ --format table / json / tsv [默认: table]
37
+ --no-ensemble 跳过 MERGE 集成打分
38
+ --no-alphagenome / --no-hyenadna / --no-nt
39
+ --no-alphamissense / --no-esm1b / --no-gpn-msa
40
+ --no-popeve / --no-evo2 / --no-evo1
41
+ ```
42
+
43
+ **输出示例(table 模式):**
44
+
45
+ ```
46
+ ╭──────────────────────────────────────────╮
47
+ │ MERGE 预测结果 │
48
+ ├──────────────────┬───────────────────────┤
49
+ │ 变异位点 │ chr17:43092919 A → G │
50
+ │ 基因 / 转录本 │ BRCA1 / ENST00000357654│
51
+ ├──────────────────┼───────────────────────┤
52
+ │ MERGE 致病性 │ 87.3% Likely Pathogenic│
53
+ │ 使用模型 │ ClinVar │
54
+ ├──────────────────┼───────────────────────┤
55
+ │ AlphaMissense │ 0.9341 │
56
+ │ ESM1b │ -3.2180 │
57
+ │ GPN-MSA │ -1.4420 │
58
+ │ popEVE │ 0.8812 │
59
+ │ AlphaGenome │ 0.0234 │
60
+ │ HyenaDNA │ -2.1100 │
61
+ │ NT │ -1.8830 │
62
+ │ Evo2 LLR │ -4.2210 │
63
+ │ Evo1 Delta │ -3.9910 │
64
+ ╰──────────────────┴───────────────────────╯
65
+ ```
66
+
67
+ **管道输出(TSV 模式,方便写脚本):**
68
+
69
+ ```bash
70
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G --format tsv >> results.tsv
71
+ ```
72
+
73
+ **脚本批量调用单变异(小于 20 个时比上传 VCF 更快):**
74
+
75
+ ```bash
76
+ while IFS=$'\t' read -r chrom pos ref alt; do
77
+ merge predict --chrom "$chrom" --pos "$pos" --ref "$ref" --alt "$alt" --format tsv
78
+ done < variants.tsv >> results.tsv
79
+ ```
80
+
81
+ ---
82
+
83
+ ### `merge batch` — 批量 VCF 预测
84
+
85
+ ```bash
86
+ merge batch variants.vcf --email you@example.com
87
+ merge batch variants.vcf.gz --email you@example.com --genome hg19 --no-evo2
88
+ ```
89
+
90
+ 任务提交成功后返回 Job ID,结果通过邮件发送,附件包含:
91
+ - `batch_predictions.csv` — 所有模型分数汇总(可直接用 Excel 打开)
92
+ - `batch_predictions.vcf` — 标准 VCF 格式,含 MERGE 及各模型分数
93
+ - `imputation_details.csv` — MERGE 各特征缺失填补情况
94
+
95
+ 服务端限制:每 IP 每日 5 次,同一邮箱间隔 1 小时,单次最多 2000 个变异。
96
+
97
+ ---
98
+
99
+ ### `merge status` — 查询批量任务
100
+
101
+ ```bash
102
+ merge status A3F2C1B0 # 查看一次
103
+ merge status A3F2C1B0 --watch # 每 30 秒轮询
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 环境变量(适合 CI / 服务器环境)
109
+
110
+ ```bash
111
+ export MERGE_API_URL=https://your-server.com
112
+ export MERGE_API_TOKEN=your-token
113
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T
114
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,190 @@
1
+ """
2
+ merge_cli/api.py
3
+ 封装所有对 Django 后端的 HTTP 请求。
4
+ 每个函数对应 views.py 里的一个端点,参数名与 views.py 完全一致。
5
+ """
6
+ import sys
7
+ from typing import Optional
8
+
9
+ import requests
10
+
11
+ from .config import get_api_url, get_token
12
+
13
+ # 所有请求的默认超时(秒)
14
+ _TIMEOUT_SINGLE = 180 # 单变异:Evo2 最慢,约 2 分钟
15
+ _TIMEOUT_BATCH = 60 # 批量提交:只是上传文件,后台异步处理
16
+
17
+
18
+ def _headers() -> dict:
19
+ token = get_token()
20
+ h = {"Accept": "application/json"}
21
+ if token:
22
+ h["Authorization"] = f"Token {token}"
23
+ return h
24
+
25
+
26
+ def _base() -> str:
27
+ url = get_api_url()
28
+ if not url:
29
+ print("错误:未配置 API 地址。请先运行:merge config set-url https://your-server.com")
30
+ sys.exit(1)
31
+ return url
32
+
33
+
34
+ # ─────────────────────────────────────────────────────────────
35
+ # 1. 单变异预测 POST /predict/
36
+ # 对应 views.py: combined_prediction_view
37
+ # ─────────────────────────────────────────────────────────────
38
+ def predict_single(
39
+ chrom: str, pos: int, ref: str, alt: str,
40
+ genome_version: str = "hg38",
41
+ # 模型开关,与 views.py POST 参数名一一对应
42
+ use_alphagenome: bool = True,
43
+ use_hyenadna: bool = True,
44
+ use_nt: bool = True,
45
+ use_alphamissense: bool = True,
46
+ use_esm1b: bool = True,
47
+ use_gpn_msa: bool = True,
48
+ use_popeve: bool = True,
49
+ use_evo2: bool = True,
50
+ use_evo1: bool = True,
51
+ ) -> dict:
52
+ """
53
+ 调用 /predict/ 端点,返回完整 prediction 字典。
54
+ 包含 dbnsfp / alphagenome / hyenadna / nt / gpn_msa / popeve / evo2 / evo1 / errors。
55
+ """
56
+ payload = {
57
+ "chrom": chrom, "pos": pos, "ref": ref, "alt": alt,
58
+ "genome_version": genome_version,
59
+ "use_alphagenome": str(use_alphagenome).lower(),
60
+ "use_hyenadna": str(use_hyenadna).lower(),
61
+ "use_nt": str(use_nt).lower(),
62
+ "use_alphamissense": str(use_alphamissense).lower(),
63
+ "use_esm1b": str(use_esm1b).lower(),
64
+ "use_gpn_msa": str(use_gpn_msa).lower(),
65
+ "use_popeve": str(use_popeve).lower(),
66
+ "use_evo2": str(use_evo2).lower(),
67
+ "use_evo1": str(use_evo1).lower(),
68
+ }
69
+ resp = requests.post(
70
+ f"{_base()}/predict/",
71
+ json=payload,
72
+ headers=_headers(),
73
+ timeout=_TIMEOUT_SINGLE,
74
+ )
75
+ resp.raise_for_status()
76
+ return resp.json()
77
+
78
+
79
+ # ─────────────────────────────────────────────────────────────
80
+ # 2. 集成模型评分 POST /ensemble/
81
+ # 对应 views.py: ensemble_predict_view
82
+ # 通常在 predict_single 成功后自动调用
83
+ # ─────────────────────────────────────────────────────────────
84
+ def predict_ensemble(prediction: dict, all_transcripts: list) -> dict:
85
+ """
86
+ 用 /predict/ 返回的 prediction 字典再调用 /ensemble/,
87
+ 获取 MERGE 集成得分和 SHAP 图(base64 PNG)。
88
+ """
89
+ payload = {"prediction": prediction, "all_transcripts": all_transcripts}
90
+ resp = requests.post(
91
+ f"{_base()}/ensemble/",
92
+ json=payload,
93
+ headers=_headers(),
94
+ timeout=_TIMEOUT_SINGLE,
95
+ )
96
+ resp.raise_for_status()
97
+ return resp.json()
98
+
99
+
100
+ # ─────────────────────────────────────────────────────────────
101
+ # 3. 批量提交 POST /api/submit_batch_job/
102
+ # 对应 views.py: submit_batch_job
103
+ # ─────────────────────────────────────────────────────────────
104
+ def submit_batch(
105
+ vcf_path: str,
106
+ email: str,
107
+ genome_version: str = "hg38",
108
+ use_alphagenome: bool = True,
109
+ use_hyenadna: bool = True,
110
+ use_nt: bool = True,
111
+ use_alphamissense: bool = True,
112
+ use_esm1b: bool = True,
113
+ use_gpn_msa: bool = True,
114
+ use_popeve: bool = True,
115
+ use_evo2: bool = True,
116
+ use_evo1: bool = True,
117
+ ) -> dict:
118
+ """
119
+ 上传 VCF 文件,异步批量预测,结果发送至 email。
120
+ 返回 job_id 供后续查询状态。
121
+ """
122
+ data = {
123
+ "notify_email": email,
124
+ "genome_version": genome_version,
125
+ "use_alphagenome": str(use_alphagenome).lower(),
126
+ "use_hyenadna": str(use_hyenadna).lower(),
127
+ "use_nt": str(use_nt).lower(),
128
+ "use_alphamissense": str(use_alphamissense).lower(),
129
+ "use_esm1b": str(use_esm1b).lower(),
130
+ "use_gpn_msa": str(use_gpn_msa).lower(),
131
+ "use_popeve": str(use_popeve).lower(),
132
+ "use_evo2": str(use_evo2).lower(),
133
+ "use_evo1": str(use_evo1).lower(),
134
+ }
135
+ with open(vcf_path, "rb") as f:
136
+ resp = requests.post(
137
+ f"{_base()}/api/submit_batch_job/",
138
+ data=data,
139
+ files={"vcf_file": (vcf_path.split("/")[-1], f)},
140
+ headers=_headers(),
141
+ timeout=_TIMEOUT_BATCH,
142
+ )
143
+ resp.raise_for_status()
144
+ return resp.json()
145
+
146
+
147
+ # ─────────────────────────────────────────────────────────────
148
+ # 4. 查询批量任务状态 GET /api/batch_job/<job_id>/
149
+ # 对应 views.py: batch_job_status
150
+ # ─────────────────────────────────────────────────────────────
151
+ def get_batch_status(job_id: str) -> dict:
152
+ resp = requests.get(
153
+ f"{_base()}/api/batch_job/{job_id}/",
154
+ headers=_headers(),
155
+ timeout=30,
156
+ )
157
+ resp.raise_for_status()
158
+ return resp.json()
159
+
160
+
161
+ # ─────────────────────────────────────────────────────────────
162
+ # 5. 开放注册 POST /api/register/
163
+ # 对应 views.py: register_and_get_token
164
+ # 公开接口,不需要携带 Token
165
+ # ─────────────────────────────────────────────────────────────
166
+ def register(username: str, email: str, password: str) -> dict:
167
+ resp = requests.post(
168
+ f"{_base()}/api/register/",
169
+ json={"username": username, "email": email, "password": password},
170
+ headers={"Content-Type": "application/json", "Accept": "application/json"},
171
+ timeout=30,
172
+ )
173
+ resp.raise_for_status()
174
+ return resp.json()
175
+
176
+
177
+ # ─────────────────────────────────────────────────────────────
178
+ # 6. 已有账号重新登录取 Token POST /api/login/
179
+ # 对应 views.py: login_and_get_token
180
+ # 公开接口,不需要携带 Token
181
+ # ─────────────────────────────────────────────────────────────
182
+ def login(username: str, password: str) -> dict:
183
+ resp = requests.post(
184
+ f"{_base()}/api/login/",
185
+ json={"username": username, "password": password},
186
+ headers={"Content-Type": "application/json", "Accept": "application/json"},
187
+ timeout=30,
188
+ )
189
+ resp.raise_for_status()
190
+ return resp.json()
@@ -0,0 +1,318 @@
1
+ """
2
+ merge_cli/cli.py
3
+ CLI 入口,四条主命令:
4
+ merge config — 配置 API 地址和 Token
5
+ merge predict — 单变异预测
6
+ merge batch — 批量 VCF 提交
7
+ merge status — 查询批量任务进度
8
+ """
9
+ import sys
10
+ import click
11
+ from rich.console import Console
12
+
13
+ from . import api, output
14
+ from .config import get_api_url, get_token, set_api_url, set_token, show_config
15
+
16
+ console = Console()
17
+
18
+ # ── 全局模型开关选项(两条命令共用)────────────────────────────
19
+ _MODEL_OPTIONS = [
20
+ click.option("--no-alphagenome", is_flag=True, default=False, help="跳过 AlphaGenome"),
21
+ click.option("--no-hyenadna", is_flag=True, default=False, help="跳过 HyenaDNA"),
22
+ click.option("--no-nt", is_flag=True, default=False, help="跳过 Nucleotide Transformer"),
23
+ click.option("--no-alphamissense", is_flag=True, default=False, help="跳过 AlphaMissense"),
24
+ click.option("--no-esm1b", is_flag=True, default=False, help="跳过 ESM1b"),
25
+ click.option("--no-gpn-msa", is_flag=True, default=False, help="跳过 GPN-MSA"),
26
+ click.option("--no-popeve", is_flag=True, default=False, help="跳过 popEVE"),
27
+ click.option("--no-evo2", is_flag=True, default=False, help="跳过 Evo2"),
28
+ click.option("--no-evo1", is_flag=True, default=False, help="跳过 Evo1"),
29
+ ]
30
+
31
+ def _add_model_options(f):
32
+ for opt in reversed(_MODEL_OPTIONS):
33
+ f = opt(f)
34
+ return f
35
+
36
+ def _model_flags(**kwargs) -> dict:
37
+ """把 --no-xxx 转成 use_xxx=True/False 传给 api 层。"""
38
+ return {
39
+ "use_alphagenome": not kwargs.get("no_alphagenome", False),
40
+ "use_hyenadna": not kwargs.get("no_hyenadna", False),
41
+ "use_nt": not kwargs.get("no_nt", False),
42
+ "use_alphamissense": not kwargs.get("no_alphamissense", False),
43
+ "use_esm1b": not kwargs.get("no_esm1b", False),
44
+ "use_gpn_msa": not kwargs.get("no_gpn_msa", False),
45
+ "use_popeve": not kwargs.get("no_popeve", False),
46
+ "use_evo2": not kwargs.get("no_evo2", False),
47
+ "use_evo1": not kwargs.get("no_evo1", False),
48
+ }
49
+
50
+
51
+ # ─────────────────────────────────────────────────────────────
52
+ # 根命令
53
+ # ─────────────────────────────────────────────────────────────
54
+ @click.group()
55
+ @click.version_option("1.0.0", prog_name="merge")
56
+ def cli():
57
+ """MERGE 变异致病性预测 CLI
58
+
59
+ 快速开始:
60
+
61
+ \b
62
+ merge config set-url https://your-server.com
63
+ merge config set-token YOUR_API_TOKEN
64
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G
65
+ """
66
+
67
+
68
+ # ─────────────────────────────────────────────────────────────
69
+ # merge config
70
+ # ─────────────────────────────────────────────────────────────
71
+ @cli.group()
72
+ def config():
73
+ """管理 API 地址和认证 Token。"""
74
+
75
+
76
+ @config.command("set-url")
77
+ @click.argument("url")
78
+ def config_set_url(url: str):
79
+ """设置 API 服务器地址,例如 https://your-server.com"""
80
+ set_api_url(url)
81
+ console.print(f"[green]✓[/green] API 地址已保存:{url}")
82
+
83
+
84
+ @config.command("set-token")
85
+ @click.argument("token")
86
+ def config_set_token(token: str):
87
+ """设置 API 认证 Token(保存在系统 keyring,不写入配置文件)"""
88
+ set_token(token)
89
+ console.print("[green]✓[/green] Token 已保存。")
90
+
91
+
92
+ @config.command("show")
93
+ def config_show():
94
+ """查看当前配置。"""
95
+ info = show_config()
96
+ console.print(f" API 地址 : [bold]{info['api_url']}[/bold]")
97
+ console.print(f" Token : [bold]{info['token']}[/bold]")
98
+ console.print(f" 配置文件 : [dim]{info['config_file']}[/dim]")
99
+
100
+
101
+ # ─────────────────────────────────────────────────────────────
102
+ # merge predict
103
+ # ─────────────────────────────────────────────────────────────
104
+ @cli.command()
105
+ @click.option("--chrom", required=True, help="染色体,如 chr17 或 17")
106
+ @click.option("--pos", required=True, type=int, help="变异位置(1-based)")
107
+ @click.option("--ref", required=True, help="参考碱基,如 A")
108
+ @click.option("--alt", required=True, help="突变碱基,如 G")
109
+ @click.option("--genome", default="hg38", show_default=True,
110
+ type=click.Choice(["hg38", "hg19"]), help="参考基因组版本")
111
+ @click.option("--format", "fmt",
112
+ default="table", show_default=True,
113
+ type=click.Choice(["table", "json", "tsv"]),
114
+ help="输出格式:table(彩色表格)/ json / tsv")
115
+ @click.option("--no-ensemble", is_flag=True, default=False,
116
+ help="跳过 MERGE 集成打分(只返回各模型原始分数)")
117
+ @_add_model_options
118
+ def predict(chrom, pos, ref, alt, genome, fmt, no_ensemble, **kwargs):
119
+ """单变异致病性预测。
120
+
121
+ \b
122
+ 示例:
123
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G
124
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G --genome hg19
125
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T --no-evo2 --no-evo1
126
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T --format json > result.json
127
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T --format tsv >> results.tsv
128
+ """
129
+ flags = _model_flags(**kwargs)
130
+
131
+ # 规范化 chrom(兼容用户输入 17 或 chr17)
132
+ if not chrom.startswith("chr"):
133
+ chrom = "chr" + chrom
134
+
135
+ with console.status("[bold cyan]正在预测…[/bold cyan]"):
136
+ try:
137
+ resp = api.predict_single(chrom, pos, ref, alt, genome, **flags)
138
+ except Exception as e:
139
+ console.print(f"[red]预测失败:{e}[/red]")
140
+ sys.exit(1)
141
+
142
+ if not resp.get("success"):
143
+ console.print(f"[red]服务端错误:{resp.get('error')}[/red]")
144
+ details = resp.get("details", {})
145
+ for src, msg in details.items():
146
+ console.print(f" [dim]{src}: {msg}[/dim]")
147
+ sys.exit(1)
148
+
149
+ prediction = resp["prediction"]
150
+ ensemble = None
151
+
152
+ if not no_ensemble:
153
+ with console.status("[bold cyan]正在计算 MERGE 集成分数…[/bold cyan]"):
154
+ try:
155
+ # 用 dbnsfp 构造最简单的 all_transcripts
156
+ dbnsfp = prediction.get("dbnsfp") or {}
157
+ transcripts = [dbnsfp] if dbnsfp else []
158
+ ens_resp = api.predict_ensemble(prediction, transcripts)
159
+ if ens_resp.get("success"):
160
+ ensemble = ens_resp
161
+ except Exception as e:
162
+ console.print(f"[yellow]MERGE 集成分数获取失败(不影响原始分数):{e}[/yellow]")
163
+
164
+ output.render_single(prediction, ensemble, fmt=fmt, errors=prediction.get("errors"))
165
+
166
+
167
+ # ─────────────────────────────────────────────────────────────
168
+ # merge batch
169
+ # ─────────────────────────────────────────────────────────────
170
+ @cli.command()
171
+ @click.argument("vcf_file", type=click.Path(exists=True))
172
+ @click.option("--email", required=True, help="结果发送邮箱(必填)")
173
+ @click.option("--genome", default="hg38", show_default=True,
174
+ type=click.Choice(["hg38", "hg19"]))
175
+ @_add_model_options
176
+ def batch(vcf_file, email, genome, **kwargs):
177
+ """批量 VCF 文件预测(异步,结果通过邮件发送)。
178
+
179
+ \b
180
+ 服务器限制(由 views.py 中 RATE_LIMIT_CONFIG 控制):
181
+ - 每个 IP 每日最多 5 次异步任务
182
+ - 同一邮箱两次提交间隔 ≥ 1 小时
183
+ - 单次最多 2000 个变异
184
+
185
+ \b
186
+ 示例:
187
+ merge batch variants.vcf --email you@example.com
188
+ merge batch variants.vcf.gz --email you@example.com --genome hg19 --no-evo2
189
+ """
190
+ flags = _model_flags(**kwargs)
191
+
192
+ with console.status("[bold cyan]上传 VCF 文件…[/bold cyan]"):
193
+ try:
194
+ resp = api.submit_batch(vcf_file, email, genome, **flags)
195
+ except Exception as e:
196
+ console.print(f"[red]提交失败:{e}[/red]")
197
+ sys.exit(1)
198
+
199
+ if not resp.get("success"):
200
+ console.print(f"[red]提交失败:{resp.get('error')}[/red]")
201
+ sys.exit(1)
202
+
203
+ output.render_batch_submitted(resp["job_id"], email)
204
+
205
+
206
+ # ─────────────────────────────────────────────────────────────
207
+ # merge register
208
+ # ─────────────────────────────────────────────────────────────
209
+ @cli.command()
210
+ @click.option("--username", prompt="用户名", help="3-30 位,字母/数字/_/-/.")
211
+ @click.option("--email", prompt="邮箱")
212
+ @click.option("--password", prompt="密码", hide_input=True, confirmation_prompt="再次输入密码确认")
213
+ def register(username, email, password):
214
+ """注册账号并自动保存 Token,注册后即可直接使用。
215
+
216
+ \b
217
+ 示例:
218
+ merge register
219
+ merge register --username alice --email alice@lab.com --password MyPass123!
220
+ """
221
+ if not get_api_url():
222
+ console.print("[red]错误:请先配置服务器地址:[/red]")
223
+ console.print(" merge config set-url https://your-server.com")
224
+ raise SystemExit(1)
225
+
226
+ with console.status("[bold cyan]注册中…[/bold cyan]"):
227
+ try:
228
+ resp = api.register(username, email, password)
229
+ except Exception as e:
230
+ console.print(f"[red]注册失败:{e}[/red]")
231
+ raise SystemExit(1)
232
+
233
+ if not resp.get("success"):
234
+ console.print(f"[red]注册失败:{resp.get('error')}[/red]")
235
+ raise SystemExit(1)
236
+
237
+ # 自动保存 Token,用户无需手动 set-token
238
+ token = resp["token"]
239
+ set_token(token)
240
+
241
+ console.print(f"\n[bold green]✓ 注册成功![/bold green]")
242
+ console.print(f" 用户名 : [bold]{username}[/bold]")
243
+ console.print(f" Token : [dim]{token[:8]}…[/dim](已自动保存)")
244
+ console.print(f"\n现在可以直接开始使用:")
245
+ console.print(f" [bold]merge predict --chrom chr17 --pos 43092919 --ref A --alt G[/bold]")
246
+
247
+
248
+ # ─────────────────────────────────────────────────────────────
249
+ # merge login (已有账号,重新获取 Token)
250
+ # ─────────────────────────────────────────────────────────────
251
+ @cli.command()
252
+ @click.option("--username", prompt="用户名")
253
+ @click.option("--password", prompt="密码", hide_input=True)
254
+ def login(username, password):
255
+ """已有账号时重新获取并保存 Token。
256
+
257
+ \b
258
+ 示例:
259
+ merge login
260
+ merge login --username alice --password MyPass123!
261
+ """
262
+ if not get_api_url():
263
+ console.print("[red]错误:请先配置服务器地址:[/red]")
264
+ console.print(" merge config set-url https://your-server.com")
265
+ raise SystemExit(1)
266
+
267
+ with console.status("[bold cyan]登录中…[/bold cyan]"):
268
+ try:
269
+ resp = api.login(username, password)
270
+ except Exception as e:
271
+ console.print(f"[red]登录失败:{e}[/red]")
272
+ raise SystemExit(1)
273
+
274
+ if not resp.get("success"):
275
+ console.print(f"[red]登录失败:{resp.get('error')}[/red]")
276
+ raise SystemExit(1)
277
+
278
+ set_token(resp["token"])
279
+ console.print(f"[bold green]✓ 登录成功,Token 已更新。[/bold green]")
280
+
281
+
282
+ # ─────────────────────────────────────────────────────────────
283
+ # merge status
284
+ # ─────────────────────────────────────────────────────────────
285
+ @cli.command()
286
+ @click.argument("job_id")
287
+ @click.option("--watch", is_flag=True, default=False,
288
+ help="每 30 秒轮询一次,直到任务完成")
289
+ def status(job_id, watch):
290
+ """查询批量任务状态。
291
+
292
+ \b
293
+ 示例:
294
+ merge status A3F2C1B0
295
+ merge status A3F2C1B0 --watch
296
+ """
297
+ import time
298
+
299
+ def _poll():
300
+ try:
301
+ data = api.get_batch_status(job_id)
302
+ except Exception as e:
303
+ console.print(f"[red]查询失败:{e}[/red]")
304
+ return None
305
+ output.render_status(data)
306
+ return data
307
+
308
+ data = _poll()
309
+ if not watch or not data:
310
+ return
311
+
312
+ while data and data.get("status") == "processing":
313
+ console.print("\n[dim]30 秒后再次查询… Ctrl+C 退出[/dim]")
314
+ try:
315
+ time.sleep(30)
316
+ except KeyboardInterrupt:
317
+ break
318
+ data = _poll()
@@ -0,0 +1,71 @@
1
+ """
2
+ merge_cli/config.py
3
+ 管理 API 地址和 Token 的持久化存储。
4
+ Token 优先级:命令行 --token > 环境变量 MERGE_API_TOKEN > keyring 存储
5
+ """
6
+ import os
7
+ import json
8
+ from pathlib import Path
9
+
10
+ import keyring
11
+
12
+ _SERVICE = "merge-cli"
13
+ _CONFIG_FILE = Path.home() / ".config" / "merge-cli" / "config.json"
14
+
15
+
16
+ def _load_file() -> dict:
17
+ if _CONFIG_FILE.exists():
18
+ try:
19
+ return json.loads(_CONFIG_FILE.read_text())
20
+ except Exception:
21
+ pass
22
+ return {}
23
+
24
+
25
+ def _save_file(data: dict) -> None:
26
+ _CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
27
+ _CONFIG_FILE.write_text(json.dumps(data, indent=2))
28
+
29
+
30
+ def get_api_url() -> str:
31
+ env = os.environ.get("MERGE_API_URL")
32
+ if env:
33
+ return env.rstrip("/")
34
+ cfg = _load_file()
35
+ return cfg.get("api_url", "").rstrip("/")
36
+
37
+
38
+ def set_api_url(url: str) -> None:
39
+ cfg = _load_file()
40
+ cfg["api_url"] = url.rstrip("/")
41
+ _save_file(cfg)
42
+
43
+
44
+ def get_token() -> str:
45
+ env = os.environ.get("MERGE_API_TOKEN")
46
+ if env:
47
+ return env
48
+ try:
49
+ tok = keyring.get_password(_SERVICE, "token")
50
+ return tok or ""
51
+ except Exception:
52
+ cfg = _load_file()
53
+ return cfg.get("token", "")
54
+
55
+
56
+ def set_token(token: str) -> None:
57
+ try:
58
+ keyring.set_password(_SERVICE, "token", token)
59
+ except Exception:
60
+ # keyring 不可用时回退到配置文件
61
+ cfg = _load_file()
62
+ cfg["token"] = token
63
+ _save_file(cfg)
64
+
65
+
66
+ def show_config() -> dict:
67
+ return {
68
+ "api_url": get_api_url() or "(未设置)",
69
+ "token": ("***" + get_token()[-4:]) if get_token() else "(未设置)",
70
+ "config_file": str(_CONFIG_FILE),
71
+ }
@@ -0,0 +1,202 @@
1
+ """
2
+ merge_cli/output.py
3
+ 把 API 返回的原始 JSON 渲染成三种格式:
4
+ - table : Rich 彩色表格(终端默认)
5
+ - json : 紧凑 JSON(管道/脚本用)
6
+ - tsv : 制表符分隔(直接导入 Excel / R / Python)
7
+ 字段来源完全对应 views.py 里 batch_predictions.csv 的列定义。
8
+ """
9
+ import json as _json
10
+ import sys
11
+ from typing import Optional
12
+
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+ from rich import box
16
+ from rich.text import Text
17
+
18
+ console = Console()
19
+
20
+
21
+ # ─── 颜色映射(对应 ensemble_predict.py: interpret_score 返回的 label)───
22
+ _LABEL_STYLE = {
23
+ "Pathogenic": "bold red",
24
+ "Likely Pathogenic": "red",
25
+ "Uncertain": "yellow",
26
+ "Likely Benign": "green",
27
+ "Benign": "bold green",
28
+ }
29
+
30
+
31
+ def _fmt(val, decimals: int = 4) -> str:
32
+ """把 None / 'N/A' / float 统一格式化为字符串。"""
33
+ if val is None or val in ("N/A", ".", ""):
34
+ return "-"
35
+ try:
36
+ return f"{float(val):.{decimals}f}"
37
+ except (ValueError, TypeError):
38
+ return str(val)
39
+
40
+
41
+ def _score_text(score: Optional[float], label: Optional[str]) -> Text:
42
+ """把 MERGE 分数 + 解读标签合并成带颜色的 Rich Text。"""
43
+ if score is None:
44
+ return Text("-", style="dim")
45
+ pct = f"{score * 100:.1f}%"
46
+ style = _LABEL_STYLE.get(label or "", "white")
47
+ t = Text(pct, style=style)
48
+ if label:
49
+ t.append(f" {label}", style=style)
50
+ return t
51
+
52
+
53
+ def _extract_flat(prediction: dict, ensemble: Optional[dict] = None) -> dict:
54
+ """
55
+ 把 /predict/ + /ensemble/ 的嵌套 JSON 压平成一行字典,
56
+ 字段顺序与 batch_predictions.csv 的列头一致。
57
+ """
58
+ inp = prediction.get("input", {})
59
+ db = prediction.get("dbnsfp") or {}
60
+ ann = db.get("annotations") or {}
61
+ dlm = db.get("dl_models") or {}
62
+ ag = prediction.get("alphagenome") or {}
63
+ hy = prediction.get("hyenadna") or {}
64
+ nt = prediction.get("nt") or {}
65
+ gpn = prediction.get("gpn_msa") or {}
66
+ pop = prediction.get("popeve") or {}
67
+ evo2 = prediction.get("evo2") or {}
68
+ evo1 = prediction.get("evo1") or {}
69
+
70
+ # ensemble 分数
71
+ ens_score = ens_label = ens_model = None
72
+ if ensemble:
73
+ for _key, _val in ensemble.get("ensemble_results", {}).items():
74
+ ens_score = _val.get("score")
75
+ ens_label = (_val.get("interpretation") or {}).get("label")
76
+ ens_model = _key
77
+ break
78
+
79
+ return {
80
+ "Chr": inp.get("chrom", ann.get("chr", "-")),
81
+ "Pos": inp.get("pos", ann.get("pos", "-")),
82
+ "Ref": inp.get("ref", ann.get("ref", "-")),
83
+ "Alt": inp.get("alt", ann.get("alt", "-")),
84
+ "Gene": ann.get("genename", "-"),
85
+ "Transcript": ann.get("Ensembl_transcriptid", "-"),
86
+ "MERGE_Score": ens_score,
87
+ "MERGE_Label": ens_label,
88
+ "MERGE_Model": ens_model,
89
+ # dbNSFP 里的 DL 模型(来自 DL_MODELS 字典,col 132/129)
90
+ "AlphaMissense": (dlm.get("AlphaMissense") or {}).get("score"),
91
+ "ESM1b": (dlm.get("ESM1b") or {}).get("score"),
92
+ # 独立模型
93
+ "GPN_MSA": gpn.get("score"),
94
+ "popEVE": pop.get("score"),
95
+ "AlphaGenome_Mean": (ag.get("statistics") or {}).get("raw_score_mean"),
96
+ "HyenaDNA": hy.get("score"),
97
+ "NT": nt.get("score"),
98
+ "Evo2_LLR": evo2.get("llr_score"),
99
+ "Evo2_Ref": evo2.get("ref_score"),
100
+ "Evo2_Var": evo2.get("var_score"),
101
+ "Evo2_Interp": evo2.get("interpretation"),
102
+ "Evo1_Delta": evo1.get("evo1_delta_score"),
103
+ "Evo1_Ref": evo1.get("ref_score"),
104
+ "Evo1_Var": evo1.get("var_score"),
105
+ }
106
+
107
+
108
+ # ─────────────────────────────────────────────────────────────
109
+ # 公开接口
110
+ # ─────────────────────────────────────────────────────────────
111
+
112
+ def render_single(prediction: dict, ensemble: Optional[dict],
113
+ fmt: str = "table", errors: dict = None) -> None:
114
+ """渲染单变异预测结果。"""
115
+ flat = _extract_flat(prediction, ensemble)
116
+
117
+ if fmt == "json":
118
+ _json.dump({"prediction": prediction, "ensemble": ensemble}, sys.stdout, indent=2, ensure_ascii=False)
119
+ print()
120
+ return
121
+
122
+ if fmt == "tsv":
123
+ print("\t".join(flat.keys()))
124
+ print("\t".join(_fmt(v) for v in flat.values()))
125
+ return
126
+
127
+ # ── Rich table ──────────────────────────────────────────
128
+ t = Table(box=box.ROUNDED, show_header=True, header_style="bold cyan",
129
+ title="MERGE 预测结果", title_style="bold")
130
+ t.add_column("项目", style="dim", no_wrap=True, min_width=18)
131
+ t.add_column("结果", min_width=30)
132
+
133
+ # 变异基本信息
134
+ t.add_section()
135
+ t.add_row("变异位点",
136
+ f"[bold]{flat['Chr']}:{flat['Pos']} {flat['Ref']} → {flat['Alt']}[/bold]")
137
+ t.add_row("基因 / 转录本",
138
+ f"{_fmt(flat['Gene'])} / {_fmt(flat['Transcript'])}")
139
+
140
+ # MERGE 集成分数
141
+ t.add_section()
142
+ t.add_row("MERGE 致病性",
143
+ _score_text(flat["MERGE_Score"], flat["MERGE_Label"]))
144
+ t.add_row("使用模型", _fmt(flat["MERGE_Model"]))
145
+
146
+ # 各模型分数
147
+ t.add_section()
148
+ for label, key in [
149
+ ("AlphaMissense", "AlphaMissense"),
150
+ ("ESM1b", "ESM1b"),
151
+ ("GPN-MSA", "GPN_MSA"),
152
+ ("popEVE", "popEVE"),
153
+ ("AlphaGenome", "AlphaGenome_Mean"),
154
+ ("HyenaDNA", "HyenaDNA"),
155
+ ("NT", "NT"),
156
+ ("Evo2 LLR", "Evo2_LLR"),
157
+ ("Evo1 Delta", "Evo1_Delta"),
158
+ ]:
159
+ val = flat[key]
160
+ t.add_row(label, _fmt(val))
161
+
162
+ console.print(t)
163
+
164
+ # 警告/错误
165
+ if errors:
166
+ console.print("\n[yellow]部分模型未返回结果:[/yellow]")
167
+ for src, msg in errors.items():
168
+ console.print(f" [dim]{src}:[/dim] {msg}")
169
+
170
+
171
+ def render_batch_submitted(job_id: str, email: str) -> None:
172
+ console.print(f"\n[bold green]✓ 批量任务已提交[/bold green]")
173
+ console.print(f" Job ID : [bold]{job_id}[/bold]")
174
+ console.print(f" 邮箱 : {email}")
175
+ console.print(f" 完成后结果将发送至邮箱(附件含 3 个文件)")
176
+ console.print(f"\n查询进度:[bold]merge status {job_id}[/bold]")
177
+
178
+
179
+ def render_status(status_data: dict) -> None:
180
+ """渲染批量任务状态。"""
181
+ if not status_data.get("found"):
182
+ console.print(f"[red]任务不存在或服务已重启。[/red]")
183
+ return
184
+
185
+ state = status_data.get("status", "unknown")
186
+ color = {"processing": "yellow", "complete": "green", "error": "red"}.get(state, "white")
187
+
188
+ t = Table(box=box.SIMPLE, show_header=False)
189
+ t.add_column("key", style="dim")
190
+ t.add_column("value")
191
+ t.add_row("状态", f"[{color}]{state}[/{color}]")
192
+ t.add_row("提交时间", status_data.get("submitted_at", "-"))
193
+ t.add_row("成功变异数", str(status_data.get("n_ok", "-")))
194
+ t.add_row("警告/错误", str(status_data.get("n_err", "-")))
195
+ t.add_row("邮件已发送", "是" if status_data.get("mail_sent") else "否")
196
+ console.print(t)
197
+
198
+ logs = status_data.get("log", [])
199
+ if logs:
200
+ console.print("\n[dim]── 任务日志 ──────────────────────────[/dim]")
201
+ for line in logs[-20:]: # 最多显示最后 20 行
202
+ console.print(f" [dim]{line}[/dim]")
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: merge-cli
3
+ Version: 1.0.0
4
+ Summary: CLI for MERGE variant pathogenicity prediction
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: click>=8.1
9
+ Requires-Dist: requests>=2.31
10
+ Requires-Dist: rich>=13.0
11
+ Requires-Dist: keyring>=24.0
12
+
13
+ # MERGE CLI
14
+
15
+ MERGE 变异致病性预测命令行工具。所有计算在服务器端完成,本地只需 Python 3.9+。
16
+
17
+ ## 安装
18
+
19
+ ```bash
20
+ pip install merge-cli
21
+ ```
22
+
23
+ ## 快速开始
24
+
25
+ ```bash
26
+ # 1. 配置服务器地址(只需配置一次)
27
+ merge config set-url https://your-server.com
28
+ merge config set-token YOUR_API_TOKEN
29
+
30
+ # 2. 单变异预测
31
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G
32
+
33
+ # 3. 批量预测(上传 VCF,结果发邮件)
34
+ merge batch my_variants.vcf --email you@example.com
35
+ ```
36
+
37
+ ## 命令详解
38
+
39
+ ### `merge predict` — 单变异预测
40
+
41
+ ```
42
+ 选项:
43
+ --chrom 染色体(chr17 或 17 均可) [必填]
44
+ --pos 变异位置(1-based) [必填]
45
+ --ref 参考碱基 [必填]
46
+ --alt 突变碱基 [必填]
47
+ --genome hg38 / hg19 [默认: hg38]
48
+ --format table / json / tsv [默认: table]
49
+ --no-ensemble 跳过 MERGE 集成打分
50
+ --no-alphagenome / --no-hyenadna / --no-nt
51
+ --no-alphamissense / --no-esm1b / --no-gpn-msa
52
+ --no-popeve / --no-evo2 / --no-evo1
53
+ ```
54
+
55
+ **输出示例(table 模式):**
56
+
57
+ ```
58
+ ╭──────────────────────────────────────────╮
59
+ │ MERGE 预测结果 │
60
+ ├──────────────────┬───────────────────────┤
61
+ │ 变异位点 │ chr17:43092919 A → G │
62
+ │ 基因 / 转录本 │ BRCA1 / ENST00000357654│
63
+ ├──────────────────┼───────────────────────┤
64
+ │ MERGE 致病性 │ 87.3% Likely Pathogenic│
65
+ │ 使用模型 │ ClinVar │
66
+ ├──────────────────┼───────────────────────┤
67
+ │ AlphaMissense │ 0.9341 │
68
+ │ ESM1b │ -3.2180 │
69
+ │ GPN-MSA │ -1.4420 │
70
+ │ popEVE │ 0.8812 │
71
+ │ AlphaGenome │ 0.0234 │
72
+ │ HyenaDNA │ -2.1100 │
73
+ │ NT │ -1.8830 │
74
+ │ Evo2 LLR │ -4.2210 │
75
+ │ Evo1 Delta │ -3.9910 │
76
+ ╰──────────────────┴───────────────────────╯
77
+ ```
78
+
79
+ **管道输出(TSV 模式,方便写脚本):**
80
+
81
+ ```bash
82
+ merge predict --chrom chr17 --pos 43092919 --ref A --alt G --format tsv >> results.tsv
83
+ ```
84
+
85
+ **脚本批量调用单变异(小于 20 个时比上传 VCF 更快):**
86
+
87
+ ```bash
88
+ while IFS=$'\t' read -r chrom pos ref alt; do
89
+ merge predict --chrom "$chrom" --pos "$pos" --ref "$ref" --alt "$alt" --format tsv
90
+ done < variants.tsv >> results.tsv
91
+ ```
92
+
93
+ ---
94
+
95
+ ### `merge batch` — 批量 VCF 预测
96
+
97
+ ```bash
98
+ merge batch variants.vcf --email you@example.com
99
+ merge batch variants.vcf.gz --email you@example.com --genome hg19 --no-evo2
100
+ ```
101
+
102
+ 任务提交成功后返回 Job ID,结果通过邮件发送,附件包含:
103
+ - `batch_predictions.csv` — 所有模型分数汇总(可直接用 Excel 打开)
104
+ - `batch_predictions.vcf` — 标准 VCF 格式,含 MERGE 及各模型分数
105
+ - `imputation_details.csv` — MERGE 各特征缺失填补情况
106
+
107
+ 服务端限制:每 IP 每日 5 次,同一邮箱间隔 1 小时,单次最多 2000 个变异。
108
+
109
+ ---
110
+
111
+ ### `merge status` — 查询批量任务
112
+
113
+ ```bash
114
+ merge status A3F2C1B0 # 查看一次
115
+ merge status A3F2C1B0 --watch # 每 30 秒轮询
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 环境变量(适合 CI / 服务器环境)
121
+
122
+ ```bash
123
+ export MERGE_API_URL=https://your-server.com
124
+ export MERGE_API_TOKEN=your-token
125
+ merge predict --chrom chr1 --pos 100000 --ref C --alt T
126
+ ```
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ merge_cli/__init__.py
4
+ merge_cli/api.py
5
+ merge_cli/cli.py
6
+ merge_cli/config.py
7
+ merge_cli/output.py
8
+ merge_cli.egg-info/PKG-INFO
9
+ merge_cli.egg-info/SOURCES.txt
10
+ merge_cli.egg-info/dependency_links.txt
11
+ merge_cli.egg-info/entry_points.txt
12
+ merge_cli.egg-info/requires.txt
13
+ merge_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ merge = merge_cli.cli:cli
@@ -0,0 +1,4 @@
1
+ click>=8.1
2
+ requests>=2.31
3
+ rich>=13.0
4
+ keyring>=24.0
@@ -0,0 +1 @@
1
+ merge_cli
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "merge-cli"
7
+ version = "1.0.0"
8
+ description = "CLI for MERGE variant pathogenicity prediction"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+
13
+ dependencies = [
14
+ "click>=8.1",
15
+ "requests>=2.31",
16
+ "rich>=13.0",
17
+ "keyring>=24.0",
18
+ ]
19
+
20
+ [project.scripts]
21
+ merge = "merge_cli.cli:cli"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["."]
25
+ include = ["merge_cli*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+