Skip to content

Commit

Permalink
feat!: addition of proper logging functionality for the ViroConstrict…
Browse files Browse the repository at this point in the history
…or package

feat!: replace snakemake logging output through our own log handler for unified output

fix: suppress snakemake logging output (workaround for snakemake/snakemake#2089)

refactor!: Use a generic (`__main__.py`) top level entry-point instead of the named `ViroConstrictor.py` entrypoint

refactor: re-structure argument parsing functionalities into its own class

refactor: re-structure snakemake run-information and config functionalities into its own class

refactor: remove old shell stdout-coloring method with the rich library

refactor: simplify several functions to ensure a properly defined return

refactor: Use f-strings more consistently for i.e. string concatenation with variables

refactor: add type-hints to all functions

fix: ensure `samples_df` and `samples_dict` always contain the same information
  • Loading branch information
florianzwagemaker committed Feb 27, 2023
1 parent f76d575 commit 20a952b
Show file tree
Hide file tree
Showing 14 changed files with 1,352 additions and 1,030 deletions.
194 changes: 0 additions & 194 deletions ViroConstrictor/ViroConstrictor.py

This file was deleted.

192 changes: 192 additions & 0 deletions ViroConstrictor/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
"""
Starting point of the ViroConstrictor pipeline and wrapper
Copyright © 2021 RIVM
https://github.com/RIVM-bioinformatics/ViroConstrictor
"""

# pylint: disable=C0103

import sys
from typing import Literal, NoReturn

import pandas as pd
import snakemake

import ViroConstrictor.logging
from ViroConstrictor import __version__
from ViroConstrictor.logging import log
from ViroConstrictor.parser import CLIparser
from ViroConstrictor.runconfigs import GetSnakemakeRunDetails, WriteYaml
from ViroConstrictor.runreport import WriteReport
from ViroConstrictor.update import update


def get_preset_warning_list(
sample_info_df: pd.DataFrame,
) -> tuple[list[str], list[str]]:
"""Takes a dataframe with sample information and returns a tuple of two lists of warnings
Parameters
----------
sample_info_df : pd.DataFrame
pd.DataFrame
Returns
-------
A list of warnings.
"""
preset_fallback_warnings = preset_score_warnings = []
for s in sample_info_df.itertuples():
sample, preset, score, input_target = (
s.SAMPLE,
s.PRESET,
s.PRESET_SCORE,
s.VIRUS,
)
if score == 0.0:
warn = f"""[red]Sample '[bold underline]{sample}[/bold underline]' was given the following information as an input-target: '[bold underline]{input_target}[/bold underline]'.
This information could however not be used to determine a preset. Because of this, the preset '[bold underline]{preset}[/bold underline]' was used instead.[/red]
[yellow]Please check your input-target for any significant misspellings, or consider using an alias or a different abbreviation for your input-target to check whether this resolves the issue.[/yellow]
It may be that your input-target does not yet have an associated preset in ViroConstrictor.
If your suspect this to be the case, please open an issue on the ViroConstrictor GitHub page: [magenta underline]https://github.com/RIVM-bioinformatics/ViroConstrictor[/magenta underline]"""
preset_fallback_warnings.append(warn)
continue
if score < 0.8:
warn = f"""[red]Sample '[bold underline]{sample}[/bold underline]' was given the following information as an input-target: '[bold underline]{input_target}[/bold underline]'.
As a result, the preset '{preset}' was chosen. But this was done with less than 80% certainty.[/red]
[yellow]Certainty score: [bold]{score:.0%}[/bold][/yellow]
Please check the input-target and try again if a different preset is required."""
preset_score_warnings.append(warn)
continue
return preset_fallback_warnings, preset_score_warnings


def show_preset_warnings(
warnings: list[str], fallbacks: list[str], disabled: bool
) -> None:
if warnings and not disabled:
for w in warnings:
log.warn(f"{w}")
if fallbacks and not disabled:
for w in fallbacks:
log.warn(f"{w}")


def main() -> NoReturn:
"""
ViroConstrictor starting point
--> Fetch and parse arguments
--> check validity
--> Read (or write, if necessary) the user-config files
--> Change working directories and make necessary local files for snakemake
--> Run snakemake with appropriate settings
"""

parsed_input = CLIparser(input_args=sys.argv[1:])

preset_fallback_warnings, preset_score_warnings = get_preset_warning_list(
parsed_input.samples_df
)
if not parsed_input.flags.skip_updates:
update(sys.argv, parsed_input.user_config)

snakemake_run_details = GetSnakemakeRunDetails(inputs_obj=parsed_input)

log.info(f"{'='*20} [bold yellow] Starting Workflow [/bold yellow] {'='*20}")
status: bool = False

if parsed_input.user_config["COMPUTING"]["compmode"] == "local":
status = snakemake.snakemake(
snakefile=parsed_input.snakefile,
workdir=parsed_input.workdir,
cores=snakemake_run_details.snakemake_run_conf["cores"],
use_conda=snakemake_run_details.snakemake_run_conf["use-conda"],
conda_frontend="mamba",
jobname=snakemake_run_details.snakemake_run_conf["jobname"],
latency_wait=snakemake_run_details.snakemake_run_conf["latency-wait"],
dryrun=snakemake_run_details.snakemake_run_conf["dryrun"],
configfiles=[
WriteYaml(
snakemake_run_details.snakemake_run_parameters,
f"{parsed_input.workdir}/config/run_params.yaml",
)
],
restart_times=3,
keepgoing=True,
quiet=["all"], # type: ignore
log_handler=[
ViroConstrictor.logging.snakemake_logger(logfile=parsed_input.logfile)
],
printshellcmds=False,
)
if parsed_input.user_config["COMPUTING"]["compmode"] == "grid":
status = snakemake.snakemake(
snakefile=parsed_input.snakefile,
workdir=parsed_input.workdir,
cores=snakemake_run_details.snakemake_run_conf["cores"],
nodes=snakemake_run_details.snakemake_run_conf["cores"],
use_conda=snakemake_run_details.snakemake_run_conf["use-conda"],
conda_frontend="mamba",
jobname=snakemake_run_details.snakemake_run_conf["jobname"],
latency_wait=snakemake_run_details.snakemake_run_conf["latency-wait"],
drmaa=snakemake_run_details.snakemake_run_conf["drmaa"],
drmaa_log_dir=snakemake_run_details.snakemake_run_conf["drmaa-log-dir"],
dryrun=snakemake_run_details.snakemake_run_conf["dryrun"],
configfiles=[
WriteYaml(
snakemake_run_details.snakemake_run_parameters,
f"{parsed_input.workdir}/config/run_params.yaml",
)
],
restart_times=3,
keepgoing=True,
quiet=["all"], # type: ignore
log_handler=[
ViroConstrictor.logging.snakemake_logger(logfile=parsed_input.logfile)
],
)

if snakemake_run_details.snakemake_run_conf["dryrun"] is False and status is True:
snakemake.snakemake(
snakefile=parsed_input.snakefile,
workdir=parsed_input.workdir,
report="results/snakemake_report.html",
configfiles=[
WriteYaml(
snakemake_run_details.snakemake_run_parameters,
f"{parsed_input.workdir}/config/run_params.yaml",
)
],
quiet=["all"], # type: ignore
log_handler=[
ViroConstrictor.logging.snakemake_logger(logfile=parsed_input.logfile)
],
)

workflow_state: Literal["Failed", "Success"] = (
"Failed" if status is False else "Success"
)

WriteReport(
parsed_input.workdir,
parsed_input.input_path,
parsed_input.exec_start_path,
parsed_input.user_config,
snakemake_run_details.snakemake_run_parameters,
snakemake_run_details.snakemake_run_conf,
workflow_state,
)

show_preset_warnings(
preset_score_warnings,
preset_fallback_warnings,
parsed_input.flags.disable_presets,
)

if status is False:
exit(1)
exit(0)
Loading

0 comments on commit 20a952b

Please sign in to comment.