dasmixer-cli 0.6.0a2__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.
- dasmixer/cli/__init__.py +3 -0
- dasmixer/cli/commands/__init__.py +3 -0
- dasmixer/cli/commands/import_data.py +269 -0
- dasmixer/cli/commands/import_project.py +75 -0
- dasmixer/cli/commands/portable.py +54 -0
- dasmixer/cli/commands/project.py +49 -0
- dasmixer/cli/commands/subset.py +130 -0
- dasmixer/cli/main.py +47 -0
- dasmixer_cli-0.6.0a2.dist-info/METADATA +51 -0
- dasmixer_cli-0.6.0a2.dist-info/RECORD +12 -0
- dasmixer_cli-0.6.0a2.dist-info/WHEEL +4 -0
- dasmixer_cli-0.6.0a2.dist-info/entry_points.txt +3 -0
dasmixer/cli/__init__.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""CLI commands for importing data files."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
from dasmixer.api.project.project import Project
|
|
8
|
+
from dasmixer.api.inputs.registry import registry
|
|
9
|
+
from dasmixer.api.config import config
|
|
10
|
+
from dasmixer.utils.seek_files import seek_files
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Import data files")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def mgf_pattern(
|
|
17
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
18
|
+
folder: Annotated[str, typer.Option("--folder", "-f", help="Folder to search")] = ...,
|
|
19
|
+
file_pattern: Annotated[str, typer.Option("--pattern", "-p", help="File pattern (e.g., *.mgf)")] = "*.mgf",
|
|
20
|
+
id_pattern: Annotated[str, typer.Option("--id-pattern", "-i", help="Sample ID pattern (e.g., {id}_*.mgf)")] = "{id}*.mgf",
|
|
21
|
+
parser: Annotated[str, typer.Option("--parser", help="Parser name")] = "MGF",
|
|
22
|
+
group: Annotated[str, typer.Option("--group", "-g", help="Group to assign samples")] = "Control"
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Import MGF files using pattern matching.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
dasmixer project.dasmix import mgf-pattern \\
|
|
29
|
+
--folder /data/spectra \\
|
|
30
|
+
--pattern "*.mgf" \\
|
|
31
|
+
--id-pattern "{id}_run*.mgf" \\
|
|
32
|
+
--group Control
|
|
33
|
+
"""
|
|
34
|
+
project_path = Path(project_path)
|
|
35
|
+
|
|
36
|
+
if not project_path.exists():
|
|
37
|
+
typer.echo(f"Error: Project file not found: {project_path}", err=True)
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
folder_path = Path(folder)
|
|
41
|
+
if not folder_path.exists():
|
|
42
|
+
typer.echo(f"Error: Folder not found: {folder}", err=True)
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
|
|
45
|
+
# Find files
|
|
46
|
+
try:
|
|
47
|
+
files = seek_files(folder_path, file_pattern, id_pattern)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
typer.echo(f"Error searching files: {e}", err=True)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
|
|
52
|
+
if not files:
|
|
53
|
+
typer.echo("No files found matching pattern", err=True)
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
# Show found files
|
|
57
|
+
typer.echo(f"\nFound {len(files)} file(s):")
|
|
58
|
+
typer.echo("-" * 60)
|
|
59
|
+
for file_path, sample_id in files:
|
|
60
|
+
display_id = sample_id or "UNKNOWN"
|
|
61
|
+
typer.echo(f" {file_path.name} → Sample ID: {display_id}")
|
|
62
|
+
|
|
63
|
+
if not typer.confirm("\nProceed with import?"):
|
|
64
|
+
typer.echo("Cancelled")
|
|
65
|
+
raise typer.Exit(0)
|
|
66
|
+
|
|
67
|
+
# Get parser
|
|
68
|
+
try:
|
|
69
|
+
parser_class = registry.get_parser(parser, "spectra")
|
|
70
|
+
except KeyError as e:
|
|
71
|
+
typer.echo(f"Error: {e}", err=True)
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
# Import files
|
|
75
|
+
async def _import():
|
|
76
|
+
async with Project(path=project_path, create_if_not_exists=False) as project:
|
|
77
|
+
# Get or create group
|
|
78
|
+
subsets = await project.get_subsets()
|
|
79
|
+
subset = next((s for s in subsets if s.name == group), None)
|
|
80
|
+
|
|
81
|
+
if not subset:
|
|
82
|
+
subset = await project.add_subset(group)
|
|
83
|
+
typer.echo(f"✓ Created group: {group}")
|
|
84
|
+
|
|
85
|
+
# Import with progress
|
|
86
|
+
with typer.progressbar(
|
|
87
|
+
files,
|
|
88
|
+
label="Importing",
|
|
89
|
+
show_pos=True
|
|
90
|
+
) as progress:
|
|
91
|
+
for file_path, sample_id in progress:
|
|
92
|
+
# Use filename as sample_id if not detected
|
|
93
|
+
if not sample_id:
|
|
94
|
+
sample_id = file_path.stem
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Parse file
|
|
98
|
+
parser_instance = parser_class(str(file_path))
|
|
99
|
+
spectra_df = await parser_instance.parse_batch()
|
|
100
|
+
|
|
101
|
+
# Add sample if not exists
|
|
102
|
+
sample = await project.get_sample_by_name(sample_id)
|
|
103
|
+
if not sample:
|
|
104
|
+
sample = await project.add_sample(
|
|
105
|
+
sample_id,
|
|
106
|
+
subset_id=subset.id
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Add spectra file
|
|
110
|
+
spectra_file_id = await project.add_spectra_file(
|
|
111
|
+
sample.id,
|
|
112
|
+
parser,
|
|
113
|
+
str(file_path)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Add spectra
|
|
117
|
+
await project.add_spectra_batch(spectra_file_id, spectra_df)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
typer.echo(f"\n Error importing {file_path.name}: {e}", err=True)
|
|
121
|
+
|
|
122
|
+
typer.echo(f"\n✓ Imported {len(files)} file(s) successfully")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
asyncio.run(_import())
|
|
126
|
+
config.update_last_import_folder(folder)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
typer.echo(f"\nError during import: {e}", err=True)
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@app.command()
|
|
133
|
+
def mgf_file(
|
|
134
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
135
|
+
file: Annotated[str, typer.Option("--file", "-f", help="Path to MGF file")] = ...,
|
|
136
|
+
sample_id: Annotated[str, typer.Option("--sample-id", "-s", help="Sample ID")] = ...,
|
|
137
|
+
parser: Annotated[str, typer.Option("--parser", help="Parser name")] = "MGF",
|
|
138
|
+
group: Annotated[str, typer.Option("--group", "-g", help="Group to assign sample")] = "Control"
|
|
139
|
+
):
|
|
140
|
+
"""
|
|
141
|
+
Import single MGF file.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
dasmixer project.dasmix import mgf-file \\
|
|
145
|
+
--file /data/sample1.mgf \\
|
|
146
|
+
--sample-id "Sample1" \\
|
|
147
|
+
--group Control
|
|
148
|
+
"""
|
|
149
|
+
project_path = Path(project_path)
|
|
150
|
+
file_path = Path(file)
|
|
151
|
+
|
|
152
|
+
if not project_path.exists():
|
|
153
|
+
typer.echo(f"Error: Project file not found: {project_path}", err=True)
|
|
154
|
+
raise typer.Exit(1)
|
|
155
|
+
|
|
156
|
+
if not file_path.exists():
|
|
157
|
+
typer.echo(f"Error: File not found: {file}", err=True)
|
|
158
|
+
raise typer.Exit(1)
|
|
159
|
+
|
|
160
|
+
# Get parser
|
|
161
|
+
try:
|
|
162
|
+
parser_class = registry.get_parser(parser, "spectra")
|
|
163
|
+
except KeyError as e:
|
|
164
|
+
typer.echo(f"Error: {e}", err=True)
|
|
165
|
+
raise typer.Exit(1)
|
|
166
|
+
|
|
167
|
+
# Import file
|
|
168
|
+
async def _import():
|
|
169
|
+
async with Project(path=project_path, create_if_not_exists=False) as project:
|
|
170
|
+
# Get or create group
|
|
171
|
+
subsets = await project.get_subsets()
|
|
172
|
+
subset = next((s for s in subsets if s.name == group), None)
|
|
173
|
+
|
|
174
|
+
if not subset:
|
|
175
|
+
subset = await project.add_subset(group)
|
|
176
|
+
typer.echo(f"✓ Created group: {group}")
|
|
177
|
+
|
|
178
|
+
typer.echo(f"Importing {file_path.name}...")
|
|
179
|
+
|
|
180
|
+
# Parse file
|
|
181
|
+
parser_instance = parser_class(str(file_path))
|
|
182
|
+
spectra_df = await parser_instance.parse_batch()
|
|
183
|
+
|
|
184
|
+
# Add sample if not exists
|
|
185
|
+
sample = await project.get_sample_by_name(sample_id)
|
|
186
|
+
if not sample:
|
|
187
|
+
sample = await project.add_sample(
|
|
188
|
+
sample_id,
|
|
189
|
+
subset_id=subset.id
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Add spectra file
|
|
193
|
+
spectra_file_id = await project.add_spectra_file(
|
|
194
|
+
sample.id,
|
|
195
|
+
parser,
|
|
196
|
+
str(file_path)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Add spectra
|
|
200
|
+
await project.add_spectra_batch(spectra_file_id, spectra_df)
|
|
201
|
+
|
|
202
|
+
typer.echo(f"✓ Imported {len(spectra_df)} spectra from {file_path.name}")
|
|
203
|
+
typer.echo(f" Sample: {sample_id}")
|
|
204
|
+
typer.echo(f" Group: {group}")
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
asyncio.run(_import())
|
|
208
|
+
config.update_last_import_folder(str(file_path.parent))
|
|
209
|
+
except Exception as e:
|
|
210
|
+
typer.echo(f"Error importing file: {e}", err=True)
|
|
211
|
+
raise typer.Exit(1)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Note: ident-pattern and ident-file commands will be similar but for identifications
|
|
215
|
+
# They require knowledge of which tool and spectra file to link to
|
|
216
|
+
# Implementation will be added when identification parsers are registered
|
|
217
|
+
|
|
218
|
+
@app.command()
|
|
219
|
+
def ident_pattern(
|
|
220
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
221
|
+
folder: Annotated[str, typer.Option("--folder", "-f", help="Folder to search")] = ...,
|
|
222
|
+
file_pattern: Annotated[str, typer.Option("--pattern", "-p", help="File pattern")] = "*.csv",
|
|
223
|
+
id_pattern: Annotated[str, typer.Option("--id-pattern", "-i", help="Sample ID pattern")] = "{id}*.csv",
|
|
224
|
+
parser: Annotated[str, typer.Option("--parser", help="Parser name (e.g., PowerNovo2)")] = ...,
|
|
225
|
+
tool: Annotated[str, typer.Option("--tool", help="Tool name (will be created if not exists)")] = ...
|
|
226
|
+
):
|
|
227
|
+
"""
|
|
228
|
+
Import identification files using pattern matching.
|
|
229
|
+
|
|
230
|
+
Note: This command requires that corresponding spectra files
|
|
231
|
+
are already imported for the samples.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
dasmixer project.dasmix import ident-pattern \\
|
|
235
|
+
--folder /data/results \\
|
|
236
|
+
--pattern "*.csv" \\
|
|
237
|
+
--id-pattern "{id}_powernovo.csv" \\
|
|
238
|
+
--parser PowerNovo2 \\
|
|
239
|
+
--tool PowerNovo2
|
|
240
|
+
"""
|
|
241
|
+
typer.echo("This command will be implemented when identification parsers are ready.")
|
|
242
|
+
typer.echo("Coming in next development phase.")
|
|
243
|
+
raise typer.Exit(0)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@app.command()
|
|
247
|
+
def ident_file(
|
|
248
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
249
|
+
file: Annotated[str, typer.Option("--file", "-f", help="Path to identification file")] = ...,
|
|
250
|
+
sample_id: Annotated[str, typer.Option("--sample-id", "-s", help="Sample ID")] = ...,
|
|
251
|
+
parser: Annotated[str, typer.Option("--parser", help="Parser name")] = ...,
|
|
252
|
+
tool: Annotated[str, typer.Option("--tool", help="Tool name")] = ...
|
|
253
|
+
):
|
|
254
|
+
"""
|
|
255
|
+
Import single identification file.
|
|
256
|
+
|
|
257
|
+
Note: This command requires that corresponding spectra file
|
|
258
|
+
is already imported for the sample.
|
|
259
|
+
|
|
260
|
+
Example:
|
|
261
|
+
dasmixer project.dasmix import ident-file \\
|
|
262
|
+
--file /data/sample1_powernovo.csv \\
|
|
263
|
+
--sample-id "Sample1" \\
|
|
264
|
+
--parser PowerNovo2 \\
|
|
265
|
+
--tool PowerNovo2
|
|
266
|
+
"""
|
|
267
|
+
typer.echo("This command will be implemented when identification parsers are ready.")
|
|
268
|
+
typer.echo("Coming in next development phase.")
|
|
269
|
+
raise typer.Exit(0)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""CLI command for importing/merging another project."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
import asyncio
|
|
7
|
+
from dasmixer.api.project.project import Project
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Merge another project into this one")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def import_project(
|
|
14
|
+
project_path: Annotated[str, typer.Argument(help="Target project (.dasmix)")],
|
|
15
|
+
source_path: Annotated[str, typer.Argument(help="Source project to import from (.dasmix)")],
|
|
16
|
+
tool_match: Annotated[str, typer.Option(help="Tool merge strategy: 'parser'|'name'|'none'")] = "parser",
|
|
17
|
+
no_subset_match: Annotated[bool, typer.Option("--no-subset-match", help="Do not merge subsets by name")] = False,
|
|
18
|
+
no_sample_match: Annotated[bool, typer.Option("--no-sample-match", help="Do not merge samples by name")] = False,
|
|
19
|
+
update_settings: Annotated[bool, typer.Option("--update-settings", help="Replace target settings with source")] = False,
|
|
20
|
+
conflict_suffix: Annotated[str, typer.Option(help="Suffix for conflicting names")] = "_1",
|
|
21
|
+
):
|
|
22
|
+
"""Merge another project into target project."""
|
|
23
|
+
tgt = Path(project_path)
|
|
24
|
+
src = Path(source_path)
|
|
25
|
+
|
|
26
|
+
if not tgt.exists():
|
|
27
|
+
typer.echo(f"Error: target project not found: {tgt}", err=True)
|
|
28
|
+
raise typer.Exit(1)
|
|
29
|
+
|
|
30
|
+
if not src.exists():
|
|
31
|
+
typer.echo(f"Error: source project not found: {src}", err=True)
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
# Convert tool_match
|
|
35
|
+
if tool_match == "none":
|
|
36
|
+
tool_match_value = None
|
|
37
|
+
elif tool_match == "name":
|
|
38
|
+
tool_match_value = "name"
|
|
39
|
+
else:
|
|
40
|
+
tool_match_value = "parser"
|
|
41
|
+
|
|
42
|
+
# Show summary
|
|
43
|
+
typer.echo(f"Target: {tgt}")
|
|
44
|
+
typer.echo(f"Source: {src}")
|
|
45
|
+
typer.echo(f"Tool match: {tool_match_value}")
|
|
46
|
+
typer.echo(f"Merge subsets: {not no_subset_match}")
|
|
47
|
+
typer.echo(f"Merge samples: {not no_sample_match}")
|
|
48
|
+
typer.echo(f"Update settings: {update_settings}")
|
|
49
|
+
typer.echo(f"Conflict suffix: {conflict_suffix}")
|
|
50
|
+
|
|
51
|
+
if not typer.confirm("Proceed with merge?"):
|
|
52
|
+
typer.echo("Cancelled")
|
|
53
|
+
raise typer.Exit(0)
|
|
54
|
+
|
|
55
|
+
async def _run():
|
|
56
|
+
async with Project(path=tgt, create_if_not_exists=False) as project:
|
|
57
|
+
def status_callback(table: str, fraction: float):
|
|
58
|
+
typer.echo(f" [{fraction*100:3.0f}%] Importing {table}...")
|
|
59
|
+
|
|
60
|
+
await project.import_project(
|
|
61
|
+
source_path=src,
|
|
62
|
+
tool_match=tool_match_value,
|
|
63
|
+
subset_match=not no_subset_match,
|
|
64
|
+
sample_match=not no_sample_match,
|
|
65
|
+
project_settings_match=update_settings,
|
|
66
|
+
conflict_suffix=conflict_suffix,
|
|
67
|
+
status_callback=status_callback,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
asyncio.run(_run())
|
|
72
|
+
typer.echo("✓ Import complete")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
75
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""CLI commands for portable project utilities (checkpoint, vacuum)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
import asyncio
|
|
7
|
+
from dasmixer.api.project.project import Project
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Portable project utilities")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def checkpoint(
|
|
14
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
15
|
+
):
|
|
16
|
+
"""Save uncommitted WAL changes into the main database file."""
|
|
17
|
+
path = Path(project_path)
|
|
18
|
+
if not path.exists():
|
|
19
|
+
typer.echo(f"Error: file not found: {path}", err=True)
|
|
20
|
+
raise typer.Exit(1)
|
|
21
|
+
|
|
22
|
+
async def _run():
|
|
23
|
+
async with Project(path=path, create_if_not_exists=False) as project:
|
|
24
|
+
await project.save(checkpoint=True)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
asyncio.run(_run())
|
|
28
|
+
typer.echo(f"✓ Checkpoint done: {path}")
|
|
29
|
+
except Exception as e:
|
|
30
|
+
typer.echo(f"Error during checkpoint: {e}", err=True)
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command()
|
|
35
|
+
def vacuum(
|
|
36
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
37
|
+
):
|
|
38
|
+
"""Compact the database file by running SQLite VACUUM."""
|
|
39
|
+
path = Path(project_path)
|
|
40
|
+
if not path.exists():
|
|
41
|
+
typer.echo(f"Error: file not found: {path}", err=True)
|
|
42
|
+
raise typer.Exit(1)
|
|
43
|
+
|
|
44
|
+
async def _run():
|
|
45
|
+
async with Project(path=path, create_if_not_exists=False) as project:
|
|
46
|
+
await project.save(checkpoint=True)
|
|
47
|
+
await project.vacuum()
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
asyncio.run(_run())
|
|
51
|
+
typer.echo(f"✓ Vacuum complete: {path}")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
typer.echo(f"Error during vacuum: {e}", err=True)
|
|
54
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""CLI commands for project management."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
import asyncio
|
|
7
|
+
from dasmixer.api.project.project import Project
|
|
8
|
+
from dasmixer.api.config import config
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Create new project")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.callback(invoke_without_command=True)
|
|
14
|
+
def create_project(
|
|
15
|
+
ctx: typer.Context,
|
|
16
|
+
project_path: Annotated[
|
|
17
|
+
str,
|
|
18
|
+
typer.Argument(help="Path to .dasmix project file to create"),
|
|
19
|
+
],
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Create new empty project with default Control group.
|
|
23
|
+
"""
|
|
24
|
+
path = Path(project_path)
|
|
25
|
+
|
|
26
|
+
# Check if file exists
|
|
27
|
+
if path.exists():
|
|
28
|
+
if not typer.confirm(f"File {path} exists. Overwrite?"):
|
|
29
|
+
typer.echo("Cancelled")
|
|
30
|
+
raise typer.Exit(0)
|
|
31
|
+
path.unlink()
|
|
32
|
+
|
|
33
|
+
# Create project
|
|
34
|
+
async def _create():
|
|
35
|
+
async with Project(path=path, create_if_not_exists=True) as project:
|
|
36
|
+
await project.add_subset(
|
|
37
|
+
"Control",
|
|
38
|
+
details="Default control group",
|
|
39
|
+
display_color="#3B82F6",
|
|
40
|
+
)
|
|
41
|
+
typer.echo(f"✓ Created project: {path}")
|
|
42
|
+
typer.echo("✓ Added default group: Control")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
asyncio.run(_create())
|
|
46
|
+
config.add_recent_project(str(path))
|
|
47
|
+
except Exception as e:
|
|
48
|
+
typer.echo(f"Error creating project: {e}", err=True)
|
|
49
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""CLI commands for managing comparison groups (subsets)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import asyncio
|
|
6
|
+
from dasmixer.api.project.project import Project
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Manage comparison groups")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def add(
|
|
14
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
15
|
+
name: Annotated[str, typer.Option("--name", "-n", help="Group name")] = ...,
|
|
16
|
+
details: Annotated[
|
|
17
|
+
str | None,
|
|
18
|
+
typer.Option("--details", "-d", help="Group description")
|
|
19
|
+
] = None,
|
|
20
|
+
color: Annotated[
|
|
21
|
+
str | None,
|
|
22
|
+
typer.Option("--color", "-c", help="Display color (hex, e.g., #FF5733)")
|
|
23
|
+
] = None
|
|
24
|
+
):
|
|
25
|
+
"""Add new comparison group to project."""
|
|
26
|
+
project_path = Path(project_path)
|
|
27
|
+
|
|
28
|
+
if not project_path.exists():
|
|
29
|
+
typer.echo(f"Error: Project file not found: {project_path}", err=True)
|
|
30
|
+
raise typer.Exit(1)
|
|
31
|
+
|
|
32
|
+
async def _add():
|
|
33
|
+
async with Project(path=project_path, create_if_not_exists=False) as project:
|
|
34
|
+
subset = await project.add_subset(name, details, color)
|
|
35
|
+
typer.echo(f"✓ Added group: {subset.name} (id={subset.id})")
|
|
36
|
+
if details:
|
|
37
|
+
typer.echo(f" Description: {details}")
|
|
38
|
+
if color:
|
|
39
|
+
typer.echo(f" Color: {color}")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
asyncio.run(_add())
|
|
43
|
+
except ValueError as e:
|
|
44
|
+
typer.echo(f"Error: {e}", err=True)
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
typer.echo(f"Error adding group: {e}", err=True)
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command()
|
|
52
|
+
def delete(
|
|
53
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")],
|
|
54
|
+
name: Annotated[str, typer.Option("--name", "-n", help="Group name to delete")] = ...
|
|
55
|
+
):
|
|
56
|
+
"""Delete comparison group from project."""
|
|
57
|
+
project_path = Path(project_path)
|
|
58
|
+
|
|
59
|
+
if not project_path.exists():
|
|
60
|
+
typer.echo(f"Error: Project file not found: {project_path}", err=True)
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
async def _delete():
|
|
64
|
+
async with Project(path=project_path, create_if_not_exists=False) as project:
|
|
65
|
+
# Find subset by name
|
|
66
|
+
subsets = await project.get_subsets()
|
|
67
|
+
subset = next((s for s in subsets if s.name == name), None)
|
|
68
|
+
|
|
69
|
+
if not subset:
|
|
70
|
+
typer.echo(f"Error: Group '{name}' not found", err=True)
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
|
|
73
|
+
# Confirm deletion
|
|
74
|
+
if not typer.confirm(f"Delete group '{name}'?"):
|
|
75
|
+
typer.echo("Cancelled")
|
|
76
|
+
raise typer.Exit(0)
|
|
77
|
+
|
|
78
|
+
await project.delete_subset(subset.id)
|
|
79
|
+
typer.echo(f"✓ Deleted group: {name}")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
asyncio.run(_delete())
|
|
83
|
+
except ValueError as e:
|
|
84
|
+
typer.echo(f"Error: {e}", err=True)
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
except typer.Exit:
|
|
87
|
+
raise
|
|
88
|
+
except Exception as e:
|
|
89
|
+
typer.echo(f"Error deleting group: {e}", err=True)
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.command("list")
|
|
94
|
+
def list_subsets(
|
|
95
|
+
project_path: Annotated[str, typer.Argument(help="Path to .dasmix project file")]
|
|
96
|
+
):
|
|
97
|
+
"""List all comparison groups in project."""
|
|
98
|
+
project_path = Path(project_path)
|
|
99
|
+
|
|
100
|
+
if not project_path.exists():
|
|
101
|
+
typer.echo(f"Error: Project file not found: {project_path}", err=True)
|
|
102
|
+
raise typer.Exit(1)
|
|
103
|
+
|
|
104
|
+
async def _list():
|
|
105
|
+
async with Project(path=project_path, create_if_not_exists=False) as project:
|
|
106
|
+
subsets = await project.get_subsets()
|
|
107
|
+
|
|
108
|
+
if not subsets:
|
|
109
|
+
typer.echo("No groups found in project")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
typer.echo("\nComparison Groups:")
|
|
113
|
+
typer.echo("=" * 60)
|
|
114
|
+
|
|
115
|
+
for subset in subsets:
|
|
116
|
+
typer.echo(f"\n{subset.name} (ID: {subset.id})")
|
|
117
|
+
if subset.details:
|
|
118
|
+
typer.echo(f" Description: {subset.details}")
|
|
119
|
+
if subset.display_color:
|
|
120
|
+
typer.echo(f" Color: {subset.display_color}")
|
|
121
|
+
|
|
122
|
+
# Count samples in this group
|
|
123
|
+
samples = await project.get_samples(subset_id=subset.id)
|
|
124
|
+
typer.echo(f" Samples: {len(samples)}")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
asyncio.run(_list())
|
|
128
|
+
except Exception as e:
|
|
129
|
+
typer.echo(f"Error listing groups: {e}", err=True)
|
|
130
|
+
raise typer.Exit(1)
|
dasmixer/cli/main.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DASMixer CLI — entry point for the `dasmixer-cli` command.
|
|
3
|
+
|
|
4
|
+
Provides command-line tools for project management without GUI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from dasmixer.versions import APP_VERSION
|
|
12
|
+
from dasmixer.cli.commands import project, subset, import_data
|
|
13
|
+
from dasmixer.cli.commands import portable
|
|
14
|
+
from dasmixer.cli.commands import import_project as import_project_cmd
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
name="dasmixer-cli",
|
|
18
|
+
help="DASMixer CLI — manage projects without GUI",
|
|
19
|
+
add_completion=False,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.callback(invoke_without_command=True)
|
|
24
|
+
def main(
|
|
25
|
+
ctx: typer.Context,
|
|
26
|
+
version: Annotated[
|
|
27
|
+
bool,
|
|
28
|
+
typer.Option("--version", "-v", help="Show version and exit"),
|
|
29
|
+
] = False,
|
|
30
|
+
):
|
|
31
|
+
"""DASMixer CLI — управление проектами без GUI."""
|
|
32
|
+
if version:
|
|
33
|
+
typer.echo(f"DASMixer CLI {APP_VERSION}")
|
|
34
|
+
raise typer.Exit(0)
|
|
35
|
+
if ctx.invoked_subcommand is None:
|
|
36
|
+
typer.echo(ctx.get_help())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
app.add_typer(project.app, name="create", help="Create new project")
|
|
40
|
+
app.add_typer(subset.app, name="subset", help="Manage comparison groups")
|
|
41
|
+
app.add_typer(import_data.app, name="import", help="Import data files")
|
|
42
|
+
app.add_typer(portable.app, name="checkpoint", help="Save WAL to database file")
|
|
43
|
+
app.add_typer(portable.app, name="vacuum", help="Compact database file")
|
|
44
|
+
app.add_typer(import_project_cmd.app, name="import-project", help="Merge another project into this one")
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
app()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dasmixer-cli
|
|
3
|
+
Version: 0.6.0a2
|
|
4
|
+
Summary: DASMixer CLI — command-line tools for project management
|
|
5
|
+
Author: gluck
|
|
6
|
+
Author-email: glucksistemi@gmail.com
|
|
7
|
+
Requires-Python: <4.0, >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: dasmixer-core (==0.5.0)
|
|
14
|
+
Project-URL: Homepage, https://github.com/protdb/dasmixer
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# DASMixer CLI
|
|
18
|
+
|
|
19
|
+
Command-line tools for managing DASMixer projects without a graphical interface.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install dasmixer-cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
dasmixer-cli --help
|
|
31
|
+
dasmixer-cli create path/to/project.dasmix
|
|
32
|
+
dasmixer-cli subset list path/to/project.dasmix
|
|
33
|
+
dasmixer-cli subset add path/to/project.dasmix --name "Treatment" --color "#FF5733"
|
|
34
|
+
dasmixer-cli import mgf-file path/to/project.dasmix --sample "Sample1" --file spectra.mgf
|
|
35
|
+
dasmixer-cli import mgf-pattern path/to/project.dasmix --sample "Sample1" --folder ./spectra/ --pattern "*.mgf"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `create <project>` | Create a new empty project |
|
|
43
|
+
| `subset list <project>` | List comparison groups |
|
|
44
|
+
| `subset add <project> --name` | Add a comparison group |
|
|
45
|
+
| `subset delete <project> --name` | Delete a comparison group |
|
|
46
|
+
| `import mgf-file <project>` | Import a single MGF file |
|
|
47
|
+
| `import mgf-pattern <project>` | Batch import MGF files by pattern |
|
|
48
|
+
| `import ident-file <project>` | Import an identification file |
|
|
49
|
+
| `import ident-pattern <project>` | Batch import identification files |
|
|
50
|
+
|
|
51
|
+
Documentation: https://github.com/protdb/dasmixer
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
dasmixer/cli/__init__.py,sha256=gR3zobxDodV8MRqOYZi5q79IfxtsZiJc8Kuh85cIFCs,79
|
|
2
|
+
dasmixer/cli/commands/__init__.py,sha256=UUXhyaTxmBs62aQzk21wEnjbeV7ieB8WI6yEonAg_yE,75
|
|
3
|
+
dasmixer/cli/commands/import_data.py,sha256=h1VflVla2xSStURSMaOFkf5WIAanjhD_DX0H8UuIgOU,10259
|
|
4
|
+
dasmixer/cli/commands/import_project.py,sha256=XmeLNGVa_yzyg_VBjXzcD0aIu28qe-eSOhfk_7LVSzw,2924
|
|
5
|
+
dasmixer/cli/commands/portable.py,sha256=s8wG0tc25paW4f6Clhem7T4RKB4stQNROUsWdrG9-4I,1665
|
|
6
|
+
dasmixer/cli/commands/project.py,sha256=GoIXrZljp3U0vZFGE4AvncqEEaeBVBXy6w0LE0fYeDQ,1389
|
|
7
|
+
dasmixer/cli/commands/subset.py,sha256=qUL25wsVD3iCph1uPyIy1THBim7tN6sKDiY9X3vpDtc,4463
|
|
8
|
+
dasmixer/cli/main.py,sha256=Kq7Nu4rd-B_K6C6wcMNv5DiXkL3aFb5uf8ArbNXOtPA,1518
|
|
9
|
+
dasmixer_cli-0.6.0a2.dist-info/METADATA,sha256=74ZauIAkmuCi7-HzWN7pX22rZgnhX-4yg5HByOyVKWo,1768
|
|
10
|
+
dasmixer_cli-0.6.0a2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
11
|
+
dasmixer_cli-0.6.0a2.dist-info/entry_points.txt,sha256=a4KExsoM5TAa1-M7059iMIH3u9VerPOAyCF4mQz-so0,54
|
|
12
|
+
dasmixer_cli-0.6.0a2.dist-info/RECORD,,
|