• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Clinical-Genomics / microSALT / #240

31 Jul 2024 01:38PM UTC coverage: 75.808% (-6.0%) from 81.849%
#240

Pull #165

travis-ci

karlnyr
remove bad tests
Pull Request #165: Use of --isolate flag, and update of SPAdes assembler

5 of 11 new or added lines in 3 files covered. (45.45%)

155 existing lines in 3 files now uncovered.

1993 of 2629 relevant lines covered (75.81%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.53
/microSALT/cli.py
1
"""This is the main entry point of microSALT.
2
   By: Isak Sylvin, @sylvinite"""
3

4
#!/usr/bin/env python
5

6
import click
1✔
7
import json
1✔
8
import os
1✔
9
import re
1✔
10
import subprocess
1✔
11
import sys
1✔
12
import yaml
1✔
13

14
from pkg_resources import iter_entry_points
1✔
15
from microSALT import __version__, preset_config, logger, wd
1✔
16
from microSALT.utils.scraper import Scraper
1✔
17
from microSALT.utils.job_creator import Job_Creator
1✔
18
from microSALT.utils.reporter import Reporter
1✔
19
from microSALT.utils.referencer import Referencer
1✔
20

21
default_sampleinfo = {
1✔
22
    "CG_ID_project": "XXX0000",
23
    "CG_ID_sample": "XXX0000A1",
24
    "Customer_ID_project": "100100",
25
    "Customer_ID_sample": "10XY123456",
26
    "Customer_ID": "cust000",
27
    "application_tag": "SOMTIN100",
28
    "date_arrival": "0001-01-01 00:00:00",
29
    "date_libprep": "0001-01-01 00:00:00",
30
    "date_sequencing": "0001-01-01 00:00:00",
31
    "method_libprep": "Not in LIMS",
32
    "method_sequencing": "Not in LIMS",
33
    "organism": "Staphylococcus aureus",
34
    "priority": "standard",
35
    "reference": "None",
36
}
37

38

39
if preset_config == "":
1✔
40
    click.echo(
×
41
        "ERROR - No properly set-up config under neither envvar MICROSALT_CONFIG nor ~/.microSALT/config.json. Exiting."
42
    )
43
    sys.exit(-1)
×
44

45

46
@click.pass_context
1✔
47
def set_cli_config(ctx, config):
48
    if config != "":
1✔
49
        if os.path.exists(config):
1✔
50
            try:
×
51
                t = ctx.obj["config"]
×
52
                with open(os.path.abspath(config), "r") as conf:
×
53
                    ctx.obj["config"] = json.load(conf)
×
54
                ctx.obj["config"]["folders"]["expec"] = t["folders"]["expec"]
×
55
                ctx.obj["config"]["folders"]["adapters"] = t["folders"]["adapters"]
×
56
                ctx.obj["config"]["config_path"] = os.path.abspath(config)
×
57
            except Exception as e:
×
58
                pass
×
59

60

61
def done():
1✔
62
    click.echo("INFO - Execution finished!")
1✔
63
    logger.debug("INFO - Execution finished!")
1✔
64

65

66
def validate_assembly_mode(ctx, param, value):
1✔
NEW
67
    allowed_values = ["careful", "isolate"]
×
NEW
68
    if value not in allowed_values:
×
NEW
69
        raise click.BadParameter(
×
70
            f"Invalid value: {value}. Allowed values are {', '.join(allowed_values)}"
71
        )
NEW
72
    return value
×
73

74

75
def review_sampleinfo(pfile):
1✔
76
    """Reviews sample info. Returns loaded json object"""
77

78
    try:
1✔
79
        with open(pfile) as json_file:
1✔
80
            data = json.load(json_file)
1✔
81
    except Exception as e:
×
82
        click.echo("Unable to read provided sample info file as json. Exiting..")
×
83
        sys.exit(-1)
×
84

85
    if isinstance(data, list):
1✔
86
        for entry in data:
1✔
87
            for k, v in default_sampleinfo.items():
1✔
88
                if not k in entry:
1✔
89
                    click.echo(
×
90
                        "WARNING - Parameter {} needs to be provided in sample json. Formatting example: ({})".format(
91
                            k, v
92
                        )
93
                    )
94
    else:
95
        for k, v in default_sampleinfo.items():
×
96
            if not k in data:
×
97
                click.echo(
×
98
                    "WARNING - Parameter {} needs to be provided in sample json. Formatting example: ({})".format(
99
                        k, v
100
                    )
101
                )
102
    return data
1✔
103

104

105
@click.group()
1✔
106
@click.version_option(__version__)
1✔
107
@click.pass_context
1✔
108
def root(ctx):
109
    """microbial Sequence Analysis and Loci-based Typing (microSALT) pipeline"""
110
    ctx.obj = {}
1✔
111
    ctx.obj["config"] = preset_config
1✔
112
    ctx.obj["log"] = logger
1✔
113

114

115
@root.command()
1✔
116
@click.argument("sampleinfo_file")
1✔
117
@click.option("--input", help="Full path to input folder", default="")
1✔
118
@click.option("--config", help="microSALT config to override default", default="")
1✔
119
@click.option(
1✔
120
    "--dry",
121
    help="Builds instance without posting to SLURM",
122
    default=False,
123
    is_flag=True,
124
)
125
@click.option(
1✔
126
    "--email",
127
    default=preset_config["regex"]["mail_recipient"],
128
    help="Forced e-mail recipient",
129
)
130
@click.option(
1✔
131
    "--skip_update", default=False, help="Skips downloading of references", is_flag=True
132
)
133
@click.option(
1✔
134
    "--force_update",
135
    default=False,
136
    help="Forces downloading of pubMLST references",
137
    is_flag=True,
138
)
139
@click.option(
1✔
140
    "--untrimmed", help="Use untrimmed input data", default=False, is_flag=True
141
)
142
@click.option(
1✔
143
    "--assembly-mode",
144
    help="Runs SPAdes in careful mode",
145
    type=click.Choice(["careful", "isolate"]),
146
    default="isolate",
147
)
148
@click.pass_context
1✔
149
def analyse(
150
    ctx,
151
    sampleinfo_file,
152
    input,
153
    config,
154
    dry,
155
    email,
156
    skip_update,
157
    force_update,
158
    untrimmed,
159
    assembly_mode,
160
):
161
    """Sequence analysis, typing and resistance identification"""
162
    # Run section
UNCOV
163
    pool = []
×
UNCOV
164
    trimmed = not untrimmed
×
UNCOV
165
    set_cli_config(config)
×
UNCOV
166
    ctx.obj["config"]["regex"]["mail_recipient"] = email
×
UNCOV
167
    ctx.obj["config"]["dry"] = dry
×
UNCOV
168
    if not os.path.isdir(input):
×
169
        click.echo("ERROR - Sequence data folder {} does not exist.".format(input))
×
170
        ctx.abort()
×
UNCOV
171
    for subfolder in os.listdir(input):
×
UNCOV
172
        if os.path.isdir("{}/{}".format(input, subfolder)):
×
UNCOV
173
            pool.append(subfolder)
×
174

UNCOV
175
    run_settings = {
×
176
        "input": input,
177
        "dry": dry,
178
        "email": email,
179
        "skip_update": skip_update,
180
        "trimmed": not untrimmed,
181
        "assembly_mode": assembly_mode,
182
        "pool": pool,
183
    }
184

185
    # Samples section
UNCOV
186
    sampleinfo = review_sampleinfo(sampleinfo_file)
×
UNCOV
187
    run_creator = Job_Creator(
×
188
        config=ctx.obj["config"],
189
        log=ctx.obj["log"],
190
        sampleinfo=sampleinfo,
191
        run_settings=run_settings,
192
    )
193

UNCOV
194
    ext_refs = Referencer(
×
195
        config=ctx.obj["config"],
196
        log=ctx.obj["log"],
197
        sampleinfo=sampleinfo,
198
        force=force_update,
199
    )
UNCOV
200
    click.echo("INFO - Checking versions of references..")
×
UNCOV
201
    try:
×
UNCOV
202
        if not skip_update:
×
UNCOV
203
            ext_refs.identify_new(project=True)
×
UNCOV
204
            ext_refs.update_refs()
×
UNCOV
205
            click.echo("INFO - Version check done. Creating sbatch jobs")
×
206
        else:
UNCOV
207
            click.echo("INFO - Skipping version check.")
×
UNCOV
208
    except Exception as e:
×
UNCOV
209
        click.echo("{}".format(e))
×
UNCOV
210
    if len(sampleinfo) > 1:
×
UNCOV
211
        run_creator.project_job()
×
212
    elif len(sampleinfo) == 1:
×
213
        run_creator.project_job(single_sample=True)
×
214
    else:
215
        ctx.abort()
×
216

UNCOV
217
    done()
×
218

219

220
@root.group()
1✔
221
@click.pass_context
1✔
222
def utils(ctx):
223
    """Utilities for specific purposes"""
224
    pass
1✔
225

226

227
@utils.group()
1✔
228
@click.pass_context
1✔
229
def refer(ctx):
230
    """Manipulates MLST organisms"""
231
    pass
1✔
232

233

234
@utils.command()
1✔
235
@click.argument("sampleinfo_file")
1✔
236
@click.option("--input", help="Full path to project folder", default="")
1✔
237
@click.option(
1✔
238
    "--track",
239
    help="Run a specific analysis track",
240
    default="default",
241
    type=click.Choice(["default", "typing", "qc", "cgmlst"]),
242
)
243
@click.option("--config", help="microSALT config to override default", default="")
1✔
244
@click.option(
1✔
245
    "--dry",
246
    help="Builds instance without posting to SLURM",
247
    default=False,
248
    is_flag=True,
249
)
250
@click.option(
1✔
251
    "--email",
252
    default=preset_config["regex"]["mail_recipient"],
253
    help="Forced e-mail recipient",
254
)
255
@click.option(
1✔
256
    "--skip_update", default=False, help="Skips downloading of references", is_flag=True
257
)
258
@click.option(
1✔
259
    "--report",
260
    default="default",
261
    type=click.Choice(
262
        ["default", "typing", "motif_overview", "qc", "json_dump", "st_update"]
263
    ),
264
)
265
@click.option("--output", help="Report output folder", default="")
1✔
266
@click.pass_context
1✔
267
def finish(
268
    ctx, sampleinfo_file, input, track, config, dry, email, skip_update, report, output
269
):
270
    """Sequence analysis, typing and resistance identification"""
271
    # Run section
272
    pool = []
1✔
273
    set_cli_config(config)
1✔
274
    ctx.obj["config"]["regex"]["mail_recipient"] = email
1✔
275
    ctx.obj["config"]["dry"] = dry
1✔
276
    if not os.path.isdir(input):
1✔
277
        click.echo("ERROR - Sequence data folder {} does not exist.".format(input))
×
278
        ctx.abort()
×
279
    if output == "":
1✔
280
        output = input
×
281
    for subfolder in os.listdir(input):
1✔
282
        if os.path.isdir("{}/{}".format(input, subfolder)):
1✔
283
            pool.append(subfolder)
1✔
284

285
    run_settings = {
1✔
286
        "input": input,
287
        "track": track,
288
        "dry": dry,
289
        "email": email,
290
        "skip_update": skip_update,
291
    }
292

293
    # Samples section
294
    sampleinfo = review_sampleinfo(sampleinfo_file)
1✔
295
    ext_refs = Referencer(
1✔
296
        config=ctx.obj["config"], log=ctx.obj["log"], sampleinfo=sampleinfo
297
    )
298
    click.echo("INFO - Checking versions of references..")
1✔
299
    try:
1✔
300
        if not skip_update:
1✔
301
            ext_refs.identify_new(project=True)
1✔
302
            ext_refs.update_refs()
1✔
303
            click.echo("INFO - Version check done. Creating sbatch jobs")
1✔
304
        else:
305
            click.echo("INFO - Skipping version check.")
×
306
    except Exception as e:
×
307
        click.echo("{}".format(e))
×
308

309
    res_scraper = Scraper(
1✔
310
        config=ctx.obj["config"], log=ctx.obj["log"], sampleinfo=sampleinfo, input=input
311
    )
312
    if isinstance(sampleinfo, list) and len(sampleinfo) > 1:
1✔
313
        res_scraper.scrape_project()
1✔
314
        # for subfolder in pool:
315
        #  res_scraper.scrape_sample()
316
    else:
317
        res_scraper.scrape_sample()
×
318

319
    codemonkey = Reporter(
1✔
320
        config=ctx.obj["config"],
321
        log=ctx.obj["log"],
322
        sampleinfo=sampleinfo,
323
        output=output,
324
        collection=True,
325
    )
326
    codemonkey.report(report)
1✔
327
    done()
1✔
328

329

330
@refer.command()
1✔
331
@click.argument("organism")
1✔
332
@click.option(
1✔
333
    "--force", help="Redownloads existing organism", default=False, is_flag=True
334
)
335
@click.pass_context
1✔
336
def add(ctx, organism, force):
337
    """Adds a new internal organism from pubMLST"""
338
    referee = Referencer(config=ctx.obj["config"], log=ctx.obj["log"], force=force)
1✔
339
    try:
1✔
340
        referee.add_pubmlst(organism)
1✔
341
    except Exception as e:
×
342
        click.echo(e.args[0])
×
343
        ctx.abort()
×
344
    click.echo("INFO - Checking versions of all references..")
1✔
345
    referee = Referencer(config=ctx.obj["config"], log=ctx.obj["log"], force=force)
1✔
346
    referee.update_refs()
1✔
347

348

349
@refer.command()
1✔
350
@click.pass_context
1✔
351
def observe(ctx):
352
    """Lists all stored organisms"""
353
    refe = Referencer(config=ctx.obj["config"], log=ctx.obj["log"])
1✔
354
    click.echo("INFO - Currently stored organisms:")
1✔
355
    for org in sorted(refe.existing_organisms()):
1✔
356
        click.echo(org.replace("_", " ").capitalize())
1✔
357

358

359
@utils.command()
1✔
360
@click.argument("sampleinfo_file")
1✔
361
@click.option(
1✔
362
    "--email",
363
    default=preset_config["regex"]["mail_recipient"],
364
    help="Forced e-mail recipient",
365
)
366
@click.option(
1✔
367
    "--type",
368
    default="default",
369
    type=click.Choice(
370
        ["default", "typing", "motif_overview", "qc", "json_dump", "st_update"]
371
    ),
372
)
373
@click.option("--output", help="Full path to output folder", default="")
1✔
374
@click.option("--collection", default=False, is_flag=True)
1✔
375
@click.pass_context
1✔
376
def report(ctx, sampleinfo_file, email, type, output, collection):
377
    """Re-generates report for a project"""
378
    ctx.obj["config"]["regex"]["mail_recipient"] = email
1✔
379
    sampleinfo = review_sampleinfo(sampleinfo_file)
1✔
380
    codemonkey = Reporter(
1✔
381
        config=ctx.obj["config"],
382
        log=ctx.obj["log"],
383
        sampleinfo=sampleinfo,
384
        output=output,
385
        collection=collection,
386
    )
387
    codemonkey.report(type)
1✔
388
    done()
1✔
389

390

391
@utils.command()
1✔
392
@click.pass_context
1✔
393
def view(ctx):
394
    """Starts an interactive webserver for viewing"""
395
    codemonkey = Reporter(config=ctx.obj["config"], log=ctx.obj["log"])
1✔
396
    codemonkey.start_web()
1✔
397

398

399
@utils.command()
1✔
400
@click.option("--input", help="Full path to project folder", default=os.getcwd())
1✔
401
@click.pass_context
1✔
402
def generate(ctx, input):
403
    """Creates a blank sample info json for the given input folder"""
404
    input = os.path.abspath(input)
1✔
405
    project_name = os.path.basename(input)
1✔
406

407
    defaults = default_sampleinfo.copy()
1✔
408

409
    pool = []
1✔
410
    if not os.path.isdir(input):
1✔
411
        click.echo(
×
412
            "ERROR - Sequence data folder {} does not exist.".format(project_name)
413
        )
414
        ctx.abort()
×
415
    elif input != os.getcwd():
1✔
416
        for subfolder in os.listdir(input):
1✔
417
            if os.path.isdir("{}/{}".format(input, subfolder)):
1✔
418
                pool.append(defaults.copy())
1✔
419
                pool[-1]["CG_ID_project"] = project_name
1✔
420
                pool[-1]["CG_ID_sample"] = subfolder
1✔
421
    else:
422
        project_name = "default_sample_info"
1✔
423
        pool.append(defaults.copy())
1✔
424

425
    with open("{}/{}.json".format(os.getcwd(), project_name), "w") as output:
1✔
426
        json.dump(pool, output, indent=2)
1✔
427
    click.echo("INFO - Created {}.json in current folder".format(project_name))
1✔
428
    done()
1✔
429

430

431
@utils.group()
1✔
432
@click.pass_context
1✔
433
def resync(ctx):
434
    """Updates internal ST with pubMLST equivalent"""
435

436

437
@resync.command()
1✔
438
@click.option(
1✔
439
    "--type",
440
    default="list",
441
    type=click.Choice(["report", "list"]),
442
    help="Output format",
443
)
444
@click.option("--customer", default="all", help="Customer id filter")
1✔
445
@click.option(
1✔
446
    "--skip_update", default=False, help="Skips downloading of references", is_flag=True
447
)
448
@click.option(
1✔
449
    "--email",
450
    default=preset_config["regex"]["mail_recipient"],
451
    help="Forced e-mail recipient",
452
)
453
@click.option("--output", help="Full path to output folder", default="")
1✔
454
@click.pass_context
1✔
455
def review(ctx, type, customer, skip_update, email, output):
456
    """Generates information about novel ST"""
457
    # Trace exists by some samples having pubMLST_ST filled in. Make trace function later
458
    ctx.obj["config"]["regex"]["mail_recipient"] = email
1✔
459
    ext_refs = Referencer(config=ctx.obj["config"], log=ctx.obj["log"])
1✔
460
    if not skip_update:
1✔
461
        ext_refs.update_refs()
1✔
462
        ext_refs.resync()
1✔
463
    click.echo("INFO - Version check done. Generating output")
1✔
464
    if type == "report":
1✔
465
        codemonkey = Reporter(
1✔
466
            config=ctx.obj["config"], log=ctx.obj["log"], output=output
467
        )
468
        codemonkey.report(type="st_update", customer=customer)
1✔
469
    elif type == "list":
1✔
470
        ext_refs.resync(type=type)
1✔
471
    done()
1✔
472

473

474
@resync.command()
1✔
475
@click.argument("sample_name")
1✔
476
@click.option(
1✔
477
    "--force",
478
    default=False,
479
    is_flag=True,
480
    help="Resolves sample without checking for pubMLST match",
481
)
482
@click.pass_context
1✔
483
def overwrite(ctx, sample_name, force):
484
    """Flags sample as resolved"""
485
    ext_refs = Referencer(config=ctx.obj["config"], log=ctx.obj["log"])
1✔
486
    ext_refs.resync(type="overwrite", sample=sample_name, ignore=force)
1✔
487
    done()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc