viralunity 1.2.0__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.
Files changed (80) hide show
  1. viralunity/__init__.py +4 -0
  2. viralunity/_orchestrator.py +135 -0
  3. viralunity/_subprocess.py +52 -0
  4. viralunity/config_generator.py +587 -0
  5. viralunity/constants.py +151 -0
  6. viralunity/exceptions.py +100 -0
  7. viralunity/logging_config.py +101 -0
  8. viralunity/provenance.py +102 -0
  9. viralunity/scripts/__init__.py +0 -0
  10. viralunity/scripts/consensus_illumina.smk +142 -0
  11. viralunity/scripts/consensus_illumina_segmented.smk +203 -0
  12. viralunity/scripts/consensus_nanopore.smk +128 -0
  13. viralunity/scripts/consensus_nanopore_segmented.smk +172 -0
  14. viralunity/scripts/envs/alignment.yaml +11 -0
  15. viralunity/scripts/envs/assembly.yaml +6 -0
  16. viralunity/scripts/envs/clair3.yaml +7 -0
  17. viralunity/scripts/envs/consensus.yaml +10 -0
  18. viralunity/scripts/envs/genome_selection.yaml +8 -0
  19. viralunity/scripts/envs/medaka.yaml +6 -0
  20. viralunity/scripts/envs/qc.yaml +8 -0
  21. viralunity/scripts/envs/taxonomy.yaml +8 -0
  22. viralunity/scripts/envs/utils.yaml +10 -0
  23. viralunity/scripts/metagenomics_illumina.smk +279 -0
  24. viralunity/scripts/metagenomics_nanopore.smk +278 -0
  25. viralunity/scripts/python/__init__.py +0 -0
  26. viralunity/scripts/python/add_RPM_to_summary.py +143 -0
  27. viralunity/scripts/python/add_negative_control_enrichment.py +418 -0
  28. viralunity/scripts/python/add_rpkm_to_summary.py +125 -0
  29. viralunity/scripts/python/annotate_diamond_taxonomy.py +90 -0
  30. viralunity/scripts/python/apply_max_rpm_bleed_filter.py +172 -0
  31. viralunity/scripts/python/build_genome_length_table.py +197 -0
  32. viralunity/scripts/python/calculate_assembly_stats.py +151 -0
  33. viralunity/scripts/python/convert_diamond_output_to_krona_input.py +142 -0
  34. viralunity/scripts/python/filter_diamond_by_idxstats.py +131 -0
  35. viralunity/scripts/python/filter_krona_by_pass_taxids.py +224 -0
  36. viralunity/scripts/python/filter_taxids.py +82 -0
  37. viralunity/scripts/python/rename_sequences.py +67 -0
  38. viralunity/scripts/python/select_reference_genomes.py +470 -0
  39. viralunity/scripts/python/summarize_krona_taxa.py +163 -0
  40. viralunity/scripts/python/taxonomy.py +77 -0
  41. viralunity/scripts/rules/__init__.py +0 -0
  42. viralunity/scripts/rules/alignment_illumina.smk +73 -0
  43. viralunity/scripts/rules/alignment_nanopore.smk +66 -0
  44. viralunity/scripts/rules/consensus_illumina.smk +105 -0
  45. viralunity/scripts/rules/consensus_illumina_common.smk +86 -0
  46. viralunity/scripts/rules/consensus_nanopore.smk +67 -0
  47. viralunity/scripts/rules/consensus_nanopore_common.smk +57 -0
  48. viralunity/scripts/rules/metagenomics_assembly_illumina.smk +39 -0
  49. viralunity/scripts/rules/metagenomics_assembly_nanopore.smk +109 -0
  50. viralunity/scripts/rules/metagenomics_dehost_illumina.smk +130 -0
  51. viralunity/scripts/rules/metagenomics_dehost_nanopore.smk +97 -0
  52. viralunity/scripts/rules/metagenomics_diamond_contigs_illumina.smk +338 -0
  53. viralunity/scripts/rules/metagenomics_diamond_contigs_nanopore.smk +379 -0
  54. viralunity/scripts/rules/metagenomics_diamond_reads_illumina.smk +226 -0
  55. viralunity/scripts/rules/metagenomics_diamond_reads_nanopore.smk +226 -0
  56. viralunity/scripts/rules/metagenomics_genome_lengths.smk +37 -0
  57. viralunity/scripts/rules/metagenomics_kraken2_contigs_illumina.smk +210 -0
  58. viralunity/scripts/rules/metagenomics_kraken2_contigs_nanopore.smk +210 -0
  59. viralunity/scripts/rules/metagenomics_kraken2_reads_illumina.smk +214 -0
  60. viralunity/scripts/rules/metagenomics_kraken2_reads_nanopore.smk +213 -0
  61. viralunity/scripts/rules/metagenomics_multiqc_illumina.smk +16 -0
  62. viralunity/scripts/rules/metagenomics_reference_assembly.smk +151 -0
  63. viralunity/scripts/rules/qc_illumina.smk +65 -0
  64. viralunity/scripts/rules/stats.smk +28 -0
  65. viralunity/validators.py +614 -0
  66. viralunity/viralunity_build_deacon_index_cli.py +60 -0
  67. viralunity/viralunity_cli.py +60 -0
  68. viralunity/viralunity_consensus.py +160 -0
  69. viralunity/viralunity_consensus_cli.py +423 -0
  70. viralunity/viralunity_create_samplesheet.py +277 -0
  71. viralunity/viralunity_get_databases_cli.py +862 -0
  72. viralunity/viralunity_meta.py +200 -0
  73. viralunity/viralunity_meta_cli.py +469 -0
  74. viralunity/viralunity_setup_cli.py +212 -0
  75. viralunity-1.2.0.dist-info/METADATA +121 -0
  76. viralunity-1.2.0.dist-info/RECORD +80 -0
  77. viralunity-1.2.0.dist-info/WHEEL +5 -0
  78. viralunity-1.2.0.dist-info/entry_points.txt +2 -0
  79. viralunity-1.2.0.dist-info/licenses/LICENSE +21 -0
  80. viralunity-1.2.0.dist-info/top_level.txt +1 -0
viralunity/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ __program__ = "viralunity"
2
+ _description = "A pipeline for viral metagenomics analysis."
3
+
4
+ __version__ = "1.2.0"
@@ -0,0 +1,135 @@
1
+ """Shared pipeline-orchestration helpers used by ``viralunity_consensus`` and
2
+ ``viralunity_meta``.
3
+
4
+ Each pipeline still owns its own ``validate_args``, ``generate_config_file``,
5
+ ``run_snakemake_workflow``, and ``main`` so that existing test patches at
6
+ those module-level names continue to work; the helpers in this module are
7
+ called from inside those functions to remove what would otherwise be
8
+ duplicated boilerplate.
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ from typing import Any, Callable, Dict, Optional
14
+
15
+ from snakemake import snakemake
16
+
17
+ from viralunity.config_generator import ConfigGenerator
18
+ from viralunity.exceptions import ValidationError, ViralUnityError
19
+ from viralunity.provenance import write_run_manifest
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def start_config(args: Dict[str, Any], samples: Dict[str, list]) -> ConfigGenerator:
25
+ """Instantiate a ``ConfigGenerator`` and add the keys both pipelines
26
+ always set: samples, output, threads.
27
+
28
+ The caller is responsible for adding pipeline-specific keys (consensus
29
+ or metagenomics settings), per-rule resource settings, and calling
30
+ ``generator.save()``.
31
+ """
32
+ logger.info("Generating configuration file")
33
+ generator = ConfigGenerator(args["config_file"])
34
+ generator.add_samples(samples, args["data_type"])
35
+ generator.add_output(args["output"], args["run_name"])
36
+ generator.add_threads(args["threads"])
37
+ return generator
38
+
39
+
40
+ def run_workflow(workflow_path: str, args: Dict[str, Any]) -> bool:
41
+ """Run a Snakemake workflow with the kwargs both pipelines share.
42
+
43
+ Args:
44
+ workflow_path: Absolute path to the ``.smk`` file to execute.
45
+ args: Pipeline argument dict. Must contain ``config_file`` and
46
+ ``threads_total``.
47
+
48
+ Raises:
49
+ ValidationError: If ``workflow_path`` does not exist.
50
+ """
51
+ logger.info("Starting Snakemake workflow")
52
+
53
+ if not os.path.isfile(workflow_path):
54
+ raise ValidationError(f"Workflow file not found: {workflow_path}")
55
+
56
+ successful = snakemake(
57
+ workflow_path,
58
+ configfiles=[args["config_file"]],
59
+ cores=args["threads_total"],
60
+ use_conda=True,
61
+ conda_prefix=args.get("conda_prefix"),
62
+ targets=["all"],
63
+ )
64
+
65
+ if successful:
66
+ logger.info("Snakemake workflow completed successfully")
67
+ else:
68
+ logger.error("Snakemake workflow failed")
69
+
70
+ return successful
71
+
72
+
73
+ def run_pipeline(
74
+ args: Dict[str, Any],
75
+ *,
76
+ resolve_paths: Callable[[Dict[str, Any]], None],
77
+ validate: Callable[[Dict[str, Any]], Optional[Dict[str, list]]],
78
+ generate_config: Callable[[Dict[str, list], Dict[str, Any]], None],
79
+ run_workflow_fn: Callable[[Dict[str, Any]], bool],
80
+ skip_when_no_samples: bool = False,
81
+ ) -> int:
82
+ """The try/except ``main`` skeleton shared by both pipelines.
83
+
84
+ Args:
85
+ args: Pipeline argument dict.
86
+ resolve_paths: Called first to convert relative path args to
87
+ absolute paths.
88
+ validate: Called to validate args and return the samples dict.
89
+ generate_config: Called to write the Snakemake config file.
90
+ run_workflow_fn: Called to execute the Snakemake workflow. The
91
+ pipeline-specific module passes its own
92
+ ``run_snakemake_workflow`` so test patches at that name
93
+ keep working.
94
+ skip_when_no_samples: If ``True``, return 0 (success) when the
95
+ validated samples dict is empty. ``viralunity_meta`` opts in;
96
+ ``viralunity_consensus`` does not.
97
+
98
+ Returns:
99
+ Exit code (0 for success, 1 for failure).
100
+ """
101
+ try:
102
+ resolve_paths(args)
103
+ samples = validate(args)
104
+
105
+ if skip_when_no_samples and (samples is None or len(samples) == 0):
106
+ logger.warning("No samples were provided.")
107
+ return 0
108
+
109
+ generate_config(samples, args)
110
+
111
+ if args.get("create_config_only", False):
112
+ logger.info("Config file created. Exiting without running workflow.")
113
+ return 0
114
+
115
+ # Provenance: record version, config, and input checksums for the run.
116
+ # Best-effort — a manifest failure must never abort an analysis.
117
+ try:
118
+ manifest_path = write_run_manifest(args, samples)
119
+ logger.info(f"Wrote run manifest: {manifest_path}")
120
+ except Exception as e:
121
+ logger.warning(f"Could not write run manifest: {e}")
122
+
123
+ successful = run_workflow_fn(args)
124
+ return 0 if successful else 1
125
+
126
+ except ViralUnityError as e:
127
+ # Expected, user-facing failures (bad inputs, missing files/DBs, config
128
+ # errors). Log a clean message with the machine-readable error code so a
129
+ # caller/service can key off it; no stack trace for these.
130
+ logger.error(f"[{e.code}] {e}")
131
+ return 1
132
+ except Exception as e:
133
+ # Genuinely unexpected: keep the traceback for debugging.
134
+ logger.exception(f"Unexpected error: {e}")
135
+ return 1
@@ -0,0 +1,52 @@
1
+ """Shared subprocess helper for ViralUnity CLI commands.
2
+
3
+ Internal module — not part of the public API. Used by the `get-databases`
4
+ and `build-deacon-index` subcommands to invoke external tools (`wget`,
5
+ `tar`, `diamond`, `deacon`, `datasets`, ...) with consistent echoing and
6
+ error handling.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import subprocess
12
+ from typing import Optional, Sequence
13
+
14
+ import click
15
+
16
+ #: Default wall-clock ceiling for a single external command (seconds). Long
17
+ #: enough for large downloads/DB builds, but bounded so a hung tool cannot block
18
+ #: a run (or a service worker) indefinitely. Override per-call via ``timeout``.
19
+ DEFAULT_TIMEOUT = 6 * 60 * 60 # 6 hours
20
+
21
+
22
+ def run_command(
23
+ cmd: Sequence[object],
24
+ cwd: Optional[str] = None,
25
+ timeout: Optional[float] = DEFAULT_TIMEOUT,
26
+ ) -> None:
27
+ """Run a subprocess command, streaming output and raising on failure.
28
+
29
+ Args:
30
+ cmd: Argument list (no shell). Each element is coerced to ``str``
31
+ when echoed; pass paths and flags as separate elements.
32
+ cwd: Optional working directory in which to run the command.
33
+ timeout: Wall-clock ceiling in seconds. Defaults to
34
+ ``DEFAULT_TIMEOUT``; pass ``None`` to disable.
35
+
36
+ Raises:
37
+ click.ClickException: If the subprocess exits non-zero or times out.
38
+ The exit code/timeout and command are included in the message so
39
+ the failure surfaces cleanly in the CLI output.
40
+ """
41
+ click.echo(f"$ {' '.join(str(c) for c in cmd)}")
42
+ try:
43
+ result = subprocess.run(list(cmd), cwd=cwd, timeout=timeout)
44
+ except subprocess.TimeoutExpired as e:
45
+ raise click.ClickException(
46
+ f"Command timed out after {e.timeout:.0f}s: " f"{' '.join(str(c) for c in cmd)}"
47
+ ) from e
48
+ if result.returncode != 0:
49
+ raise click.ClickException(
50
+ f"Command failed with exit code {result.returncode}: "
51
+ f"{' '.join(str(c) for c in cmd)}"
52
+ )