From 81f2285c98feb8422ae67327ed682de1f73c6632 Mon Sep 17 00:00:00 2001 From: "Tristan F.-R." Date: Tue, 15 Jul 2025 23:35:20 +0000 Subject: [PATCH 01/10] feat: cli --- .github/workflows/test-spras.yml | 7 ++++++- MANIFEST.in | 3 +++ pyproject.toml | 3 +++ spras/__main__.py | 3 +++ spras/cli.py | 19 +++++++++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 MANIFEST.in create mode 100644 spras/__main__.py create mode 100644 spras/cli.py diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index 48089ab10..b9782daaf 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -47,6 +47,11 @@ jobs: - name: Install spras in conda env shell: bash --login {0} run: pip install . + - name: Get pipx + shell: bash --login {0} + run: pip install pipx + - shell: bash --login {0} + run: pipx install . # Log conda environment contents - name: Log conda environment shell: bash --login {0} @@ -63,7 +68,7 @@ jobs: run: pytest -vs - name: Run Snakemake workflow shell: bash --login {0} - run: snakemake --cores 2 --configfile config/config.yaml --show-failed-logs + run: spras --cores 2 --configfile config/config.yaml --show-failed-logs # Run pre-commit checks on source files pre-commit: diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..5d455adfd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include LICENSE +include Snakefile diff --git a/pyproject.toml b/pyproject.toml index 22acca360..fc20aee66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,9 @@ dev = [ "Homepage" = "https://github.com/Reed-CompBio/spras" "Issues" = "https://github.com/Reed-CompBio/spras/issues" +[project.entry-points."pipx.run"] +spras = "spras.cli:run" + [build-system] requires = ["setuptools>=64.0"] build-backend = "setuptools.build_meta" diff --git a/spras/__main__.py b/spras/__main__.py new file mode 100644 index 000000000..16b442b7e --- /dev/null +++ b/spras/__main__.py @@ -0,0 +1,3 @@ +if __name__ == "__main__": + from spras.cli import run + run() diff --git a/spras/cli.py b/spras/cli.py new file mode 100644 index 000000000..45c643669 --- /dev/null +++ b/spras/cli.py @@ -0,0 +1,19 @@ +import itertools +import os +from pathlib import Path +import subprocess +import sys + +# https://stackoverflow.com/a/5137509/7589775 +# The file we want, visjs.html, is also included in MANIFEST.in +dir_path = os.path.dirname(os.path.realpath(__file__)) +snakefile_path = Path(dir_path, "..", "Snakefile") + +def run(): + subprocess.run(list(itertools.chain( + ["snakemake", "-s", snakefile_path], + sys.argv[1:] + ))) + +if __name__ == '__main__': + run() From f39de85f981324a94d9860d9cdd8152cfa88dbc6 Mon Sep 17 00:00:00 2001 From: "Tristan F.-R." Date: Tue, 15 Jul 2025 23:36:26 +0000 Subject: [PATCH 02/10] style: fmt --- spras/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spras/cli.py b/spras/cli.py index 45c643669..d37d3dd7e 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -1,8 +1,8 @@ import itertools import os -from pathlib import Path import subprocess import sys +from pathlib import Path # https://stackoverflow.com/a/5137509/7589775 # The file we want, visjs.html, is also included in MANIFEST.in From ec9fc43e2f7dcdcc7892196af8a34db11c4904ec Mon Sep 17 00:00:00 2001 From: "Tristan F.-R." Date: Tue, 15 Jul 2025 23:49:38 +0000 Subject: [PATCH 03/10] chore: add scripts spras --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index fc20aee66..bc2b660f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,9 @@ dev = [ [project.entry-points."pipx.run"] spras = "spras.cli:run" +[project.scripts] +spras = "spras.cli:run" + [build-system] requires = ["setuptools>=64.0"] build-backend = "setuptools.build_meta" From e4aebf6aaf16e58e8ec2e74701f5246f51d67414 Mon Sep 17 00:00:00 2001 From: "Tristan F.-R." Date: Tue, 15 Jul 2025 19:36:40 -0700 Subject: [PATCH 04/10] docs: update visjs -> Snakefile --- spras/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spras/cli.py b/spras/cli.py index d37d3dd7e..d5602f8c0 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -5,7 +5,7 @@ from pathlib import Path # https://stackoverflow.com/a/5137509/7589775 -# The file we want, visjs.html, is also included in MANIFEST.in +# The file we want, Snakefile, is also included in MANIFEST.in dir_path = os.path.dirname(os.path.realpath(__file__)) snakefile_path = Path(dir_path, "..", "Snakefile") From 04523e095cb7f63b160ed97bc798be550664fb36 Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Sun, 24 Aug 2025 04:50:13 -0700 Subject: [PATCH 05/10] ci: fix duplicate comments --- .github/workflows/test-spras.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index e731cd648..7fda8475c 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -52,9 +52,7 @@ jobs: run: pip install pipx - shell: bash --login {0} run: pipx install . - # Log conda environment contents - - name: Log conda environment - # Log conda environment contents + - name: Log conda environment contents shell: bash --login {0} run: conda list - name: Install Apptainer From 146443e60029eb817b7437091ad3f6a58da3bfdc Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Thu, 28 Aug 2025 00:17:26 +0000 Subject: [PATCH 06/10] feat(cli): use run subcommand --- spras/cli.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/spras/cli.py b/spras/cli.py index d5602f8c0..642795d16 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -1,3 +1,4 @@ +import argparse import itertools import os import subprocess @@ -9,11 +10,41 @@ dir_path = os.path.dirname(os.path.realpath(__file__)) snakefile_path = Path(dir_path, "..", "Snakefile") +# Removes the very awkwardly phrased "{subcommand1, subcommand2}" from the subcommand help +# from https://stackoverflow.com/a/13429281/7589775 +class SubcommandHelpFormatter(argparse.RawDescriptionHelpFormatter): + def _format_action(self, action): + parts = super(argparse.RawDescriptionHelpFormatter, self)._format_action(action) + if action.nargs == argparse.PARSER: + parts = "\n".join(parts.split("\n")[1:]) + return parts + +def get_parser(): + parser = argparse.ArgumentParser( + prog='SPRAS', + description='The wrapping tool for SPRAS (signaling pathway reconstruction analysis streamliner)', + epilog='SPRAS is in alpha. Report issues or suggest features on GitHub: https://github.com/Reed-CompBio/spras', + formatter_class=SubcommandHelpFormatter) + + subparsers = parser.add_subparsers(title='subcommands', + help='subcommand help', + dest='subcommand') + subparsers = subparsers.add_parser('run', help='Run the SPRAS Snakemake workflow') + + return parser + def run(): - subprocess.run(list(itertools.chain( - ["snakemake", "-s", snakefile_path], - sys.argv[1:] - ))) + parser = get_parser() + args = parser.parse_args() + + if args.subcommand == "run": + subprocess.run(list(itertools.chain( + ["snakemake", "-s", snakefile_path], + sys.argv[1:] + ))) + return + + parser.print_help() if __name__ == '__main__': run() From d4b6ad3ccb57769869c2b66d50e9f860e5291ef9 Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Thu, 28 Aug 2025 00:17:34 +0000 Subject: [PATCH 07/10] style: fmt --- spras/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spras/cli.py b/spras/cli.py index 642795d16..fc16f54f8 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -25,7 +25,7 @@ def get_parser(): description='The wrapping tool for SPRAS (signaling pathway reconstruction analysis streamliner)', epilog='SPRAS is in alpha. Report issues or suggest features on GitHub: https://github.com/Reed-CompBio/spras', formatter_class=SubcommandHelpFormatter) - + subparsers = parser.add_subparsers(title='subcommands', help='subcommand help', dest='subcommand') @@ -43,7 +43,7 @@ def run(): sys.argv[1:] ))) return - + parser.print_help() if __name__ == '__main__': From ed672be32eda58f8007ff310dfd371f6dd05dede Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Thu, 28 Aug 2025 00:26:42 +0000 Subject: [PATCH 08/10] fix: update cli usage across files --- .github/workflows/test-spras.yml | 2 +- README.md | 2 +- docs/contributing/index.rst | 2 +- docs/usage.rst | 4 ++-- spras/cli.py | 9 ++++++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-spras.yml b/.github/workflows/test-spras.yml index 77b96a8f8..93184f132 100644 --- a/.github/workflows/test-spras.yml +++ b/.github/workflows/test-spras.yml @@ -95,7 +95,7 @@ jobs: # We enable high parallelization (cores 4) to test our way out of the experienced # race conditions from #268 and #279 # We also enforce strict DAG evaluation to catch DAG problems before they appear as user errors. (#359) - run: spras --cores 4 --configfile config/config.yaml --show-failed-logs --strict-dag-evaluation cyclic-graph --strict-dag-evaluation functions --strict-dag-evaluation periodic-wildcards + run: spras run --cores 4 --configfile config/config.yaml --show-failed-logs --strict-dag-evaluation cyclic-graph --strict-dag-evaluation functions --strict-dag-evaluation periodic-wildcards # Run pre-commit checks on source files pre-commit: diff --git a/README.md b/README.md index 026b20398..5db214f6e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ After installing Docker, start Docker before running SPRAS. Once you have activated the conda environment and started Docker, you can run SPRAS with the example Snakemake workflow. From the root directory of the `spras` repository, run the command ``` -snakemake --cores 1 --configfile config/config.yaml +spras run --cores 1 --configfile config/config.yaml ``` This will run the SPRAS workflow with the example config file (`config/config.yaml`) and input files. Output files will be written to the `output` directory. diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst index ae1947b38..c4528710f 100644 --- a/docs/contributing/index.rst +++ b/docs/contributing/index.rst @@ -296,7 +296,7 @@ through SPRAS with .. code:: bash - snakemake --cores 1 --configfile config/config.yaml + spras run --cores 1 --configfile config/config.yaml Make sure to run the command inside the ``spras`` conda environment. diff --git a/docs/usage.rst b/docs/usage.rst index 161b39675..5ede42923 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,11 +2,11 @@ Using SPRAS =========== SPRAS is run through `Snakemake `_, which comes -with the SPRAS conda environment. +with both the SPRAS conda environment and as a dependency of SPRAS. To run SPRAS, run the following command inside the ``spras`` directory, specifying a ``config.yaml`` and the number of cores to run SPRAS with: .. code-block:: bash - snakemake --cores 1 --configfile config.yaml + spras run --cores 1 --configfile config.yaml diff --git a/spras/cli.py b/spras/cli.py index fc16f54f8..677e56851 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -29,18 +29,21 @@ def get_parser(): subparsers = parser.add_subparsers(title='subcommands', help='subcommand help', dest='subcommand') - subparsers = subparsers.add_parser('run', help='Run the SPRAS Snakemake workflow') + subparsers = subparsers.add_parser('run', + help='Run the SPRAS Snakemake workflow', + # We let snakemake handle help + add_help=False) return parser def run(): parser = get_parser() - args = parser.parse_args() + (args, unknown_args) = parser.parse_known_args() if args.subcommand == "run": subprocess.run(list(itertools.chain( ["snakemake", "-s", snakefile_path], - sys.argv[1:] + unknown_args ))) return From f9db06051296ee97680ac47f318ebf3710a32019 Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Thu, 28 Aug 2025 03:59:24 +0000 Subject: [PATCH 09/10] chore: resolve snakefile path --- spras/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spras/cli.py b/spras/cli.py index 677e56851..fb58d650f 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -8,7 +8,8 @@ # https://stackoverflow.com/a/5137509/7589775 # The file we want, Snakefile, is also included in MANIFEST.in dir_path = os.path.dirname(os.path.realpath(__file__)) -snakefile_path = Path(dir_path, "..", "Snakefile") +# we resolve to simplify the path name in errors +snakefile_path = Path(dir_path, "..", "Snakefile").resolve() # Removes the very awkwardly phrased "{subcommand1, subcommand2}" from the subcommand help # from https://stackoverflow.com/a/13429281/7589775 From 5d19632fcd25104412d6dd4e5f7ca51c3f30e6fe Mon Sep 17 00:00:00 2001 From: "Tristan F." Date: Wed, 15 Oct 2025 04:39:46 +0000 Subject: [PATCH 10/10] style: fmt --- spras/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/spras/cli.py b/spras/cli.py index fb58d650f..eaef47492 100644 --- a/spras/cli.py +++ b/spras/cli.py @@ -2,7 +2,6 @@ import itertools import os import subprocess -import sys from pathlib import Path # https://stackoverflow.com/a/5137509/7589775