viralunity 1.2.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.
Files changed (86) hide show
  1. viralunity-1.2.0/LICENSE +21 -0
  2. viralunity-1.2.0/MANIFEST.in +4 -0
  3. viralunity-1.2.0/PKG-INFO +121 -0
  4. viralunity-1.2.0/README.md +64 -0
  5. viralunity-1.2.0/pyproject.toml +108 -0
  6. viralunity-1.2.0/setup.cfg +9 -0
  7. viralunity-1.2.0/viralunity/__init__.py +4 -0
  8. viralunity-1.2.0/viralunity/_orchestrator.py +135 -0
  9. viralunity-1.2.0/viralunity/_subprocess.py +52 -0
  10. viralunity-1.2.0/viralunity/config_generator.py +587 -0
  11. viralunity-1.2.0/viralunity/constants.py +151 -0
  12. viralunity-1.2.0/viralunity/exceptions.py +100 -0
  13. viralunity-1.2.0/viralunity/logging_config.py +101 -0
  14. viralunity-1.2.0/viralunity/provenance.py +102 -0
  15. viralunity-1.2.0/viralunity/scripts/__init__.py +0 -0
  16. viralunity-1.2.0/viralunity/scripts/consensus_illumina.smk +142 -0
  17. viralunity-1.2.0/viralunity/scripts/consensus_illumina_segmented.smk +203 -0
  18. viralunity-1.2.0/viralunity/scripts/consensus_nanopore.smk +128 -0
  19. viralunity-1.2.0/viralunity/scripts/consensus_nanopore_segmented.smk +172 -0
  20. viralunity-1.2.0/viralunity/scripts/envs/alignment.yaml +11 -0
  21. viralunity-1.2.0/viralunity/scripts/envs/assembly.yaml +6 -0
  22. viralunity-1.2.0/viralunity/scripts/envs/clair3.yaml +7 -0
  23. viralunity-1.2.0/viralunity/scripts/envs/consensus.yaml +10 -0
  24. viralunity-1.2.0/viralunity/scripts/envs/genome_selection.yaml +8 -0
  25. viralunity-1.2.0/viralunity/scripts/envs/medaka.yaml +6 -0
  26. viralunity-1.2.0/viralunity/scripts/envs/qc.yaml +8 -0
  27. viralunity-1.2.0/viralunity/scripts/envs/taxonomy.yaml +8 -0
  28. viralunity-1.2.0/viralunity/scripts/envs/utils.yaml +10 -0
  29. viralunity-1.2.0/viralunity/scripts/metagenomics_illumina.smk +279 -0
  30. viralunity-1.2.0/viralunity/scripts/metagenomics_nanopore.smk +278 -0
  31. viralunity-1.2.0/viralunity/scripts/python/__init__.py +0 -0
  32. viralunity-1.2.0/viralunity/scripts/python/add_RPM_to_summary.py +143 -0
  33. viralunity-1.2.0/viralunity/scripts/python/add_negative_control_enrichment.py +418 -0
  34. viralunity-1.2.0/viralunity/scripts/python/add_rpkm_to_summary.py +125 -0
  35. viralunity-1.2.0/viralunity/scripts/python/annotate_diamond_taxonomy.py +90 -0
  36. viralunity-1.2.0/viralunity/scripts/python/apply_max_rpm_bleed_filter.py +172 -0
  37. viralunity-1.2.0/viralunity/scripts/python/build_genome_length_table.py +197 -0
  38. viralunity-1.2.0/viralunity/scripts/python/calculate_assembly_stats.py +151 -0
  39. viralunity-1.2.0/viralunity/scripts/python/convert_diamond_output_to_krona_input.py +142 -0
  40. viralunity-1.2.0/viralunity/scripts/python/filter_diamond_by_idxstats.py +131 -0
  41. viralunity-1.2.0/viralunity/scripts/python/filter_krona_by_pass_taxids.py +224 -0
  42. viralunity-1.2.0/viralunity/scripts/python/filter_taxids.py +82 -0
  43. viralunity-1.2.0/viralunity/scripts/python/rename_sequences.py +67 -0
  44. viralunity-1.2.0/viralunity/scripts/python/select_reference_genomes.py +470 -0
  45. viralunity-1.2.0/viralunity/scripts/python/summarize_krona_taxa.py +163 -0
  46. viralunity-1.2.0/viralunity/scripts/python/taxonomy.py +77 -0
  47. viralunity-1.2.0/viralunity/scripts/rules/__init__.py +0 -0
  48. viralunity-1.2.0/viralunity/scripts/rules/alignment_illumina.smk +73 -0
  49. viralunity-1.2.0/viralunity/scripts/rules/alignment_nanopore.smk +66 -0
  50. viralunity-1.2.0/viralunity/scripts/rules/consensus_illumina.smk +105 -0
  51. viralunity-1.2.0/viralunity/scripts/rules/consensus_illumina_common.smk +86 -0
  52. viralunity-1.2.0/viralunity/scripts/rules/consensus_nanopore.smk +67 -0
  53. viralunity-1.2.0/viralunity/scripts/rules/consensus_nanopore_common.smk +57 -0
  54. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_assembly_illumina.smk +39 -0
  55. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_assembly_nanopore.smk +109 -0
  56. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_dehost_illumina.smk +130 -0
  57. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_dehost_nanopore.smk +97 -0
  58. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_diamond_contigs_illumina.smk +338 -0
  59. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_diamond_contigs_nanopore.smk +379 -0
  60. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_diamond_reads_illumina.smk +226 -0
  61. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_diamond_reads_nanopore.smk +226 -0
  62. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_genome_lengths.smk +37 -0
  63. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_kraken2_contigs_illumina.smk +210 -0
  64. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_kraken2_contigs_nanopore.smk +210 -0
  65. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_kraken2_reads_illumina.smk +214 -0
  66. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_kraken2_reads_nanopore.smk +213 -0
  67. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_multiqc_illumina.smk +16 -0
  68. viralunity-1.2.0/viralunity/scripts/rules/metagenomics_reference_assembly.smk +151 -0
  69. viralunity-1.2.0/viralunity/scripts/rules/qc_illumina.smk +65 -0
  70. viralunity-1.2.0/viralunity/scripts/rules/stats.smk +28 -0
  71. viralunity-1.2.0/viralunity/validators.py +614 -0
  72. viralunity-1.2.0/viralunity/viralunity_build_deacon_index_cli.py +60 -0
  73. viralunity-1.2.0/viralunity/viralunity_cli.py +60 -0
  74. viralunity-1.2.0/viralunity/viralunity_consensus.py +160 -0
  75. viralunity-1.2.0/viralunity/viralunity_consensus_cli.py +423 -0
  76. viralunity-1.2.0/viralunity/viralunity_create_samplesheet.py +277 -0
  77. viralunity-1.2.0/viralunity/viralunity_get_databases_cli.py +862 -0
  78. viralunity-1.2.0/viralunity/viralunity_meta.py +200 -0
  79. viralunity-1.2.0/viralunity/viralunity_meta_cli.py +469 -0
  80. viralunity-1.2.0/viralunity/viralunity_setup_cli.py +212 -0
  81. viralunity-1.2.0/viralunity.egg-info/PKG-INFO +121 -0
  82. viralunity-1.2.0/viralunity.egg-info/SOURCES.txt +85 -0
  83. viralunity-1.2.0/viralunity.egg-info/dependency_links.txt +1 -0
  84. viralunity-1.2.0/viralunity.egg-info/entry_points.txt +2 -0
  85. viralunity-1.2.0/viralunity.egg-info/requires.txt +13 -0
  86. viralunity-1.2.0/viralunity.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2026 filiperomero2
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ recursive-include viralunity/scripts *.yaml
2
+ recursive-include viralunity/scripts *.smk
3
+ recursive-exclude viralunity/scripts __pycache__
4
+ recursive-exclude viralunity/scripts *.pyc
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: viralunity
3
+ Version: 1.2.0
4
+ Summary: A pipeline for viral metagenomics analysis.
5
+ Author: Felippe Nacif
6
+ Author-email: Filipe Moreira <filiperomero2@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2021-2026 filiperomero2
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/InstitutoTodosPelaSaude/ViralUnity
30
+ Project-URL: Documentation, https://viralunity.readthedocs.io/en/latest/
31
+ Project-URL: Changelog, https://github.com/InstitutoTodosPelaSaude/ViralUnity/blob/main/CHANGELOG.md
32
+ Project-URL: Issues, https://github.com/InstitutoTodosPelaSaude/ViralUnity/issues
33
+ Keywords: metagenomics,viral,high-throughput sequencing,bioinformatics,viral genome assembly
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: POSIX
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3 :: Only
40
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
41
+ Requires-Python: <3.12,>=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: pyyaml>=6.0
45
+ Requires-Dist: click>=8.0
46
+ Requires-Dist: biopython>=1.81
47
+ Requires-Dist: snakemake<8,>=7.32
48
+ Requires-Dist: pandas>=1.5
49
+ Requires-Dist: pulp<2.8
50
+ Provides-Extra: dev
51
+ Requires-Dist: black<27,>=26; extra == "dev"
52
+ Requires-Dist: pytest>=9.0; extra == "dev"
53
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
54
+ Requires-Dist: mypy>=1.0; extra == "dev"
55
+ Requires-Dist: ruff>=0.5; extra == "dev"
56
+ Dynamic: license-file
57
+
58
+ # ViralUnity
59
+
60
+ ViralUnity is a tool for analysing viral high-throughput sequencing data. It is a Python package that orchestrates Snakemake workflows for data quality control, taxonomic assignment, and reference genome assembly. ViralUnity runs on *nix systems and can process entire sequencing runs in minimal time on a regular computer.
61
+
62
+ > **Full documentation:** <https://viralunity.readthedocs.io/en/latest/>
63
+
64
+ ## Installation
65
+
66
+ Install the ViralUnity CLI from PyPI:
67
+
68
+ ```bash
69
+ pip install viralunity
70
+ ```
71
+
72
+ > **conda/mamba is still required at runtime.** ViralUnity orchestrates Snakemake, which
73
+ > builds the per-rule tool environments (aligners, classifiers, assemblers) via
74
+ > `--use-conda` on first run. Make sure conda or mamba is installed and on your `PATH`;
75
+ > you can pre-build those environments up front with `viralunity setup --pipelines all`.
76
+
77
+ To install from source for development instead:
78
+
79
+ ```bash
80
+ git clone https://github.com/InstitutoTodosPelaSaude/ViralUnity.git
81
+ cd ViralUnity
82
+ conda env create -n viralunity -f environment.yml
83
+ conda activate viralunity
84
+ pip install -e .
85
+ ```
86
+
87
+ Per-rule conda environments under `viralunity/scripts/envs/` are managed automatically by Snakemake; the top-level `environment.yml` only installs ViralUnity itself and its core runtime dependencies.
88
+
89
+ ## Quick start
90
+
91
+ Six top-level subcommands are exposed via the `viralunity` CLI:
92
+
93
+ ```bash
94
+ viralunity create-samplesheet --input <runs-dir> --output samples.csv
95
+ viralunity get-databases all --output databases/
96
+ viralunity setup --pipelines all # pre-build per-rule conda envs
97
+ viralunity consensus illumina --sample-sheet samples.csv --reference ref.fasta --output run/
98
+ viralunity meta illumina --sample-sheet samples.csv --kraken2-database <db> --output run/
99
+ viralunity build-deacon-index --input host.fasta --output host.dcn
100
+ ```
101
+
102
+ Global options: `--log-level {DEBUG,INFO,WARNING,ERROR}` and `--json-logs`
103
+ (e.g. `viralunity --log-level DEBUG meta ...`).
104
+
105
+ Each subcommand has its own `--help`; the same information is exhaustively documented in the `docs/` Sphinx site (rendered on ReadTheDocs at the link above).
106
+
107
+ ## Tests
108
+
109
+ ```bash
110
+ make test
111
+ ```
112
+
113
+ This installs the package in editable mode (if not already installed) and runs the `unittest` suite under `test/`. Snakemake dry-run tests live in `test/viralunity_dryrun_test.py` and use `pytest`.
114
+
115
+ ## Citation
116
+
117
+ A scientific publication describing this pipeline is being prepared. Meanwhile, please cite this repository. Primary references for upstream tools (fastp, MultiQC, Minimap2, Samtools, BCFtools, BEDtools, gofasta, MEGAHIT, Racon, BLAST, Kraken2, Krona, DIAMOND, Clair3, Medaka, Deacon) are listed in the ReadTheDocs site.
118
+
119
+ ## License
120
+
121
+ MIT — see `LICENSE`.
@@ -0,0 +1,64 @@
1
+ # ViralUnity
2
+
3
+ ViralUnity is a tool for analysing viral high-throughput sequencing data. It is a Python package that orchestrates Snakemake workflows for data quality control, taxonomic assignment, and reference genome assembly. ViralUnity runs on *nix systems and can process entire sequencing runs in minimal time on a regular computer.
4
+
5
+ > **Full documentation:** <https://viralunity.readthedocs.io/en/latest/>
6
+
7
+ ## Installation
8
+
9
+ Install the ViralUnity CLI from PyPI:
10
+
11
+ ```bash
12
+ pip install viralunity
13
+ ```
14
+
15
+ > **conda/mamba is still required at runtime.** ViralUnity orchestrates Snakemake, which
16
+ > builds the per-rule tool environments (aligners, classifiers, assemblers) via
17
+ > `--use-conda` on first run. Make sure conda or mamba is installed and on your `PATH`;
18
+ > you can pre-build those environments up front with `viralunity setup --pipelines all`.
19
+
20
+ To install from source for development instead:
21
+
22
+ ```bash
23
+ git clone https://github.com/InstitutoTodosPelaSaude/ViralUnity.git
24
+ cd ViralUnity
25
+ conda env create -n viralunity -f environment.yml
26
+ conda activate viralunity
27
+ pip install -e .
28
+ ```
29
+
30
+ Per-rule conda environments under `viralunity/scripts/envs/` are managed automatically by Snakemake; the top-level `environment.yml` only installs ViralUnity itself and its core runtime dependencies.
31
+
32
+ ## Quick start
33
+
34
+ Six top-level subcommands are exposed via the `viralunity` CLI:
35
+
36
+ ```bash
37
+ viralunity create-samplesheet --input <runs-dir> --output samples.csv
38
+ viralunity get-databases all --output databases/
39
+ viralunity setup --pipelines all # pre-build per-rule conda envs
40
+ viralunity consensus illumina --sample-sheet samples.csv --reference ref.fasta --output run/
41
+ viralunity meta illumina --sample-sheet samples.csv --kraken2-database <db> --output run/
42
+ viralunity build-deacon-index --input host.fasta --output host.dcn
43
+ ```
44
+
45
+ Global options: `--log-level {DEBUG,INFO,WARNING,ERROR}` and `--json-logs`
46
+ (e.g. `viralunity --log-level DEBUG meta ...`).
47
+
48
+ Each subcommand has its own `--help`; the same information is exhaustively documented in the `docs/` Sphinx site (rendered on ReadTheDocs at the link above).
49
+
50
+ ## Tests
51
+
52
+ ```bash
53
+ make test
54
+ ```
55
+
56
+ This installs the package in editable mode (if not already installed) and runs the `unittest` suite under `test/`. Snakemake dry-run tests live in `test/viralunity_dryrun_test.py` and use `pytest`.
57
+
58
+ ## Citation
59
+
60
+ A scientific publication describing this pipeline is being prepared. Meanwhile, please cite this repository. Primary references for upstream tools (fastp, MultiQC, Minimap2, Samtools, BCFtools, BEDtools, gofasta, MEGAHIT, Racon, BLAST, Kraken2, Krona, DIAMOND, Clair3, Medaka, Deacon) are listed in the ReadTheDocs site.
61
+
62
+ ## License
63
+
64
+ MIT — see `LICENSE`.
@@ -0,0 +1,108 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "viralunity"
7
+ dynamic = ["version"]
8
+ description = "A pipeline for viral metagenomics analysis."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Filipe Moreira", email = "filiperomero2@gmail.com" },
12
+ { name = "Felippe Nacif" },
13
+ ]
14
+ license = { file = "LICENSE" }
15
+ requires-python = ">=3.10,<3.12"
16
+ keywords = [
17
+ "metagenomics",
18
+ "viral",
19
+ "high-throughput sequencing",
20
+ "bioinformatics",
21
+ "viral genome assembly",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Science/Research",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: POSIX",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
31
+ ]
32
+ dependencies = [
33
+ "pyyaml>=6.0",
34
+ "click>=8.0",
35
+ "biopython>=1.81",
36
+ "snakemake>=7.32,<8",
37
+ "pandas>=1.5",
38
+ # Snakemake 7.32 calls pulp.list_solvers(), which was removed in pulp 2.8.
39
+ "pulp<2.8",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "black>=26,<27",
45
+ "pytest>=9.0",
46
+ "pytest-cov>=5.0",
47
+ "mypy>=1.0",
48
+ "ruff>=0.5",
49
+ ]
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/InstitutoTodosPelaSaude/ViralUnity"
53
+ Documentation = "https://viralunity.readthedocs.io/en/latest/"
54
+ Changelog = "https://github.com/InstitutoTodosPelaSaude/ViralUnity/blob/main/CHANGELOG.md"
55
+ Issues = "https://github.com/InstitutoTodosPelaSaude/ViralUnity/issues"
56
+
57
+ [project.scripts]
58
+ viralunity = "viralunity.viralunity_cli:main"
59
+
60
+ [tool.setuptools.dynamic]
61
+ version = { attr = "viralunity.__version__" }
62
+
63
+ [tool.setuptools.packages.find]
64
+ include = ["viralunity*"]
65
+ exclude = ["test*", "docs*", "docs-pt*"]
66
+
67
+ [tool.setuptools.package-data]
68
+ "viralunity.scripts" = ["*.smk", "**/*.smk", "envs/*.yaml"]
69
+
70
+ [tool.black]
71
+ line-length = 100
72
+ target-version = ["py310", "py311"]
73
+
74
+ [tool.ruff]
75
+ line-length = 100
76
+ target-version = "py310"
77
+
78
+ [tool.ruff.lint]
79
+ # Conservative starting set; can expand later.
80
+ select = ["E", "F", "W", "I"]
81
+ ignore = [
82
+ "E501", # line length is handled by black where it cares; do not double-flag
83
+ ]
84
+
85
+ [tool.ruff.lint.per-file-ignores]
86
+ # Snakemake injects a `snakemake` global into `script:` files at runtime.
87
+ # Ruff cannot see this, so silence F821 for those files.
88
+ "viralunity/scripts/python/*.py" = ["F821"]
89
+
90
+ [tool.mypy]
91
+ python_version = "3.10"
92
+ ignore_missing_imports = true
93
+ warn_unused_ignores = true
94
+
95
+ [[tool.mypy.overrides]]
96
+ # Snakemake injects a `snakemake` global into `script:` files at runtime.
97
+ module = "viralunity.scripts.python.*"
98
+ ignore_errors = true
99
+
100
+ [tool.pytest.ini_options]
101
+ testpaths = ["test"]
102
+ python_files = ["*_test.py"]
103
+ python_classes = ["Test_*", "Test*"]
104
+ python_functions = ["test_*"]
105
+ addopts = "-m 'not empirical'"
106
+ markers = [
107
+ "empirical: end-to-end tests that download real data and run full pipelines (slow, opt-in)",
108
+ ]
@@ -0,0 +1,9 @@
1
+ [coverage:run]
2
+ omit =
3
+ */test/*
4
+ setup.py
5
+
6
+ [egg_info]
7
+ tag_build =
8
+ tag_date = 0
9
+
@@ -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
+ )