Compare commits

...

17 Commits
master ... cli

Author SHA1 Message Date
Charles Reid f9cc1d93f2 travis.yml: python 3.7 -> 3.7-dev 5 years ago
Charles Reid 09417f7c23 remove unused import 5 years ago
Charles Reid cf4145aee5 python version >= 3.5 5 years ago
Charles Reid 98ef3fa49a add shields 5 years ago
Charles Reid 4b69492d40 change newline to trigger travis 5 years ago
Charles Reid c8e1760470 add .travis.yml 5 years ago
Charles Reid 861dc16a64 banana -> bananas. update gitignore. 5 years ago
Charles Reid fc0479ff77 clean up after test 5 years ago
Charles Reid dae3b1dd45 fix tests and fix the way we are looking for param and config files (use os.getcwd()) 5 years ago
Charles Reid 88cd2f71a6 update gh-pages url in setup.py 5 years ago
Charles Reid 6c29958c13 manifest should include files, not graft them (graft is for directories) 5 years ago
Charles Reid 682f760c10 update readme instructions 5 years ago
Charles Reid 742f24d0e5 include Snakefile with Manifest.in to make it run automagically 5 years ago
Charles Reid abd4a07dec move config and params to test/ 5 years ago
Charles Reid 48f4d30903 add Snakefile to manifest 5 years ago
Charles Reid a908ddf8c2 update how/where we look for Snakefile 5 years ago
Charles Reid 71f48e0f5e convert 2018-snakemake-cli to python package (setup.py) 5 years ago
  1. 5
      .gitignore
  2. 13
      .travis.yml
  3. 19
      LICENSE
  4. 1
      MANIFEST.in
  5. 77
      README.md
  6. 0
      cli/Snakefile
  7. 2
      cli/__init__.py
  8. 103
      cli/command.py
  9. 17
      requirements-to-freeze.txt
  10. 1
      requirements.txt
  11. 91
      run
  12. 29
      setup.py
  13. 0
      test/params-amy.json
  14. 0
      test/params-beth.json
  15. 76
      test/test_bananas.py
  16. 0
      test/workflow-goodbye.json
  17. 0
      test/workflow-hello.json

5
.gitignore vendored

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
.snakemake/
vp/
*.egg-info/
build/
dist/

13
.travis.yml

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
# https://docs.travis-ci.com/user/languages/python/
language: python
python:
- "3.5"
- "3.6"
- "3.7-dev"
# command to install dependencies
install:
- pip install -r requirements.txt
- python setup.py build install
# command to run tests
script:
- pytest

19
LICENSE

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
Copyright (c) 2019 Charles Reid
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
MANIFEST.in

@ -0,0 +1 @@ @@ -0,0 +1 @@
include cli/Snakefile

77
README.md

@ -1,37 +1,82 @@ @@ -1,37 +1,82 @@
# 2018-snakemake-cli
# 2019-snakemake-cli
An example of parameterizing snakemake workflows with a simple CLI.
[![travis](https://img.shields.io/travis/charlesreid1/2019-snakemake-cli.svg)](https://travis-ci.org/charlesreid1/2019-snakemake-cli.svg)
[![license](https://img.shields.io/github/license/charlesreid1/2019-snakemake-cli.svg)](https://github.com/charlesreid1/2019-snakemake-cli/blob/master/LICENSE)
An example of a Snakemake command line interface
bundled up as an installable Python package.
This example bundles the Snakefile with the
command line tool, but this tool can also look
in the user's working directory for Snakefiles.
Snakemake functionality is provided through
a command line tool called `bananas`.
# Quickstart
This runs through the installation and usage
of 2019-snakemake-cli.
## Installing banana
Start by setting up a virtual environment,
and install the required packages into the
virtual environment:
Usage:
```
./run <workflow_file> <parameters_file>
pip install -r requirements.txt
```
e.g.
Now install the `bananas` command line tool:
```
rm -f hello.txt
./run workflow-hello params-amy
python setup.py build install
```
creates `hello.txt` with "hello amy" in it, while
Now you can run
```
which banana
```
and you should see `bananas` in your Python
distribution's `bin/` directory.
## Running banana
Move to the `test/` directory and run the tests
with the provided config and params files.
Run the hello workflow with Amy params:
```
rm -f hello.txt
./run workflow-hello params-beth
bananas workflow-hello params-amy
```
creates `hello.txt` with "hello beth" in it.
Here, the workflow file `workflow-hello.json` specifes the target
`hello.txt`, while the parameters file `params-amy` parameterizes
the workflow with the name "amy".
Run the hello workflow with Beth params:
Likewise,
```
rm -f hello.txt
bananas workflow-hello params-beth
```
Run the goodbye workflow with Beth params:
```
rm -f goodbye.txt
./run workflow-goodbye params-beth
```
will put `goodbye beth` in `goodbye.txt`.
# Details
The entrypoint of the command line interface is
the `main()` function of `cli/command.py`.
The location of the Snakefile is `cli/Snakefile`.
An alternative arrangement would be for users
to provide a Snakefile via rules in the working
directory, or via a Github URL or a remote URL.
All workflows use the same set of Snakemake rules in `Snakefile`.

0
Snakefile → cli/Snakefile

2
cli/__init__.py

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
_program = "bananas"
__version__ = "0.1.0"

103
cli/command.py

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
"""
Command line interface driver for snakemake workflows
"""
import argparse
import os.path
import snakemake
import sys
import pprint
import json
from . import _program
thisdir = os.path.abspath(os.path.dirname(__file__))
parentdir = os.path.join(thisdir,'..')
cwd = os.getcwd()
def main(sysargs = sys.argv[1:]):
parser = argparse.ArgumentParser(prog = _program, description='bananas: run snakemake workflows', usage='''bananas <workflow> <parameters> [<target>]
bananas: run snakemake workflows, using the given workflow name & parameters file.
''')
parser.add_argument('workflowfile')
parser.add_argument('paramsfile')
parser.add_argument('-n', '--dry-run', action='store_true')
parser.add_argument('-f', '--force', action='store_true')
args = parser.parse_args(sysargs)
# first, find the Snakefile
snakefile_this = os.path.join(thisdir,"Snakefile")
snakefile_parent = os.path.join(parentdir,"Snakefile")
if os.path.exists(snakefile_this):
snakefile = snakefile_this
elif os.path.exists(snakefile_parent):
snakefile = snakefile_parent
else:
msg = 'Error: cannot find Snakefile at any of the following locations:\n'
msg += '{}\n'.format(snakefile_this)
msg += '{}\n'.format(snakefile_parent)
sys.stderr.write(msg)
sys.exit(-1)
# next, find the workflow config file
workflowfile = None
w1 = os.path.join(cwd,args.workflowfile)
w2 = os.path.join(cwd,args.workflowfile+'.json')
if os.path.exists(w1) and not os.path.isdir(w1):
workflowfile = w1
elif os.path.exists(w2) and not os.path.isdir(w2):
workflowfile = w2
if not workflowfile:
msg = 'Error: cannot find workflowfile {} or {} '.format(w1,w2)
msg += 'in directory {}\n'.format(cwd)
sys.stderr.write(msg)
sys.exit(-1)
# next, find the workflow params file
paramsfile = None
p1 = os.path.join(cwd,args.paramsfile)
p2 = os.path.join(cwd,args.paramsfile+'.json')
if os.path.exists(p1) and not os.path.isdir(p1):
paramsfile = p1
elif os.path.exists(p2) and not os.path.isdir(p2):
paramsfile = p2
if not paramsfile:
msg = 'Error: cannot find paramsfile {} or {} '.format(p1,p2)
msg += 'in directory {}\n'.format(cwd)
sys.stderr.write(msg)
sys.exit(-1)
with open(workflowfile, 'rt') as fp:
workflow_info = json.load(fp)
target = workflow_info['workflow_target']
config = dict()
print('--------')
print('details!')
print('\tsnakefile: {}'.format(snakefile))
print('\tconfig: {}'.format(workflowfile))
print('\tparams: {}'.format(paramsfile))
print('\ttarget: {}'.format(target))
print('--------')
# run bananas!!
status = snakemake.snakemake(snakefile, configfile=paramsfile,
targets=[target], printshellcmds=True,
dryrun=args.dry_run, forceall=args.force,
config=config)
if status: # translate "success" into shell exit code of 0
return 0
return 1
if __name__ == '__main__':
main()

17
requirements-to-freeze.txt

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
appdirs==1.4.3
certifi==2018.11.29
chardet==3.0.4
ConfigArgParse==0.14.0
datrie==0.7.1
docutils==0.14
gitdb2==2.0.5
GitPython==2.1.11
idna==2.8
jsonschema==2.6.0
PyYAML==3.13
ratelimiter==1.2.0.post0
requests==2.21.0
smmap2==2.0.5
snakemake==5.4.0
urllib3==1.24.1
wrapt==1.11.0

1
requirements.txt

@ -0,0 +1 @@ @@ -0,0 +1 @@
snakemake>=5.4.0

91
run

@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
#! /usr/bin/env python
"""
Execution script for snakemake workflows.
"""
import argparse
import os.path
import snakemake
import sys
import pprint
import json
thisdir = os.path.abspath(os.path.dirname(__file__))
def main(args):
# first, find the Snakefile
snakefile = os.path.join(thisdir, 'Snakefile')
if not os.path.exists(snakefile):
sys.stderr.write('Error: cannot find Snakefile at {}\n'.format(snakefile))
sys.exit(-1)
# next, find the workflow config file
workflowfile = None
if os.path.exists(args.workflowfile) and not os.path.isdir(args.workflowfile):
workflowfile = args.workflowfile
else:
for suffix in ('', '.json'):
tryfile = os.path.join(thisdir, args.workflowfile + suffix)
if os.path.exists(tryfile) and not os.path.isdir(tryfile):
sys.stderr.write('Found workflowfile at {}\n'.format(tryfile))
workflowfile = tryfile
break
if not workflowfile:
sys.stderr.write('Error: cannot find workflowfile {}\n'.format(args.workflowfile))
sys.exit(-1)
# next, find the workflow params file
paramsfile = None
if os.path.exists(args.paramsfile) and not os.path.isdir(args.paramsfile):
paramsfile = args.paramsfile
else:
for suffix in ('', '.json'):
tryfile = os.path.join(thisdir, args.paramsfile + suffix)
if os.path.exists(tryfile) and not os.path.isdir(tryfile):
sys.stderr.write('Found paramsfile at {}\n'.format(tryfile))
paramsfile = tryfile
break
if not paramsfile:
sys.stderr.write('Error: cannot find paramsfile {}\n'.format(args.paramsfile))
sys.exit(-1)
with open(workflowfile, 'rt') as fp:
workflow_info = json.load(fp)
target = workflow_info['workflow_target']
config = dict()
print('--------')
print('details!')
print('\tsnakefile: {}'.format(snakefile))
print('\tconfig: {}'.format(workflowfile))
print('\tparams: {}'.format(paramsfile))
print('\ttarget: {}'.format(target))
print('--------')
# run!!
status = snakemake.snakemake(snakefile, configfile=paramsfile,
targets=[target], printshellcmds=True,
dryrun=args.dry_run, config=config)
if status: # translate "success" into shell exit code of 0
return 0
return 1
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='run snakemake workflows', usage='''run <workflow> <parameters> [<target>]
Run snakemake workflows, using the given workflow name & parameters file.
''')
parser.add_argument('workflowfile')
parser.add_argument('paramsfile')
parser.add_argument('-n', '--dry-run', action='store_true')
args = parser.parse_args()
sys.exit(main(args))

29
setup.py

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
from setuptools import setup, find_packages
import glob
import os
with open('requirements.txt') as f:
required = [x for x in f.read().splitlines() if not x.startswith("#")]
# Note: the _program variable is set in __init__.py.
# it determines the name of the package/final command line tool.
from cli import __version__, _program
setup(name='bananas',
version=__version__,
packages=['cli'],
test_suite='pytest.collector',
tests_require=['pytest'],
description='bananas command line interface',
url='https://charlesreid1.github.io/2019-snakemake-cli',
author='@charlesreid1',
author_email='cmreid@ucdavis.edu',
license='MIT',
entry_points="""
[console_scripts]
{program} = cli.command:main
""".format(program = _program),
install_requires=required,
include_package_data=True,
keywords=[],
zip_safe=False)

0
params-amy.json → test/params-amy.json

0
params-beth.json → test/params-beth.json

76
test/test_bananas.py

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
from unittest import TestCase
from subprocess import call, Popen, PIPE
import os
import shutil, tempfile
from os.path import isdir, join
"""
test banana
this test will run bananas with the test
config and params provided in the test dir.
this test will also show how to run tests where
failure is expected (i.e., checking that we handle
invalid parameters).
each test has a unittest TestCase defined.
pytest will automatically find these tests.
"""
class TestBananas(TestCase):
"""
simple bananas test class
This uses the subprocess PIPE var
to capture system input and output,
since we are running bananas from the
command line directly using subprocess.
"""
@classmethod
def setUpClass(self):
"""
set up a bananas workflow test.
we are using the existing test/ dir
as our working dir, so no setup to do.
if we were expecting the user to provide
a Snakefile, this is where we would set
up a test Snakefile.
"""
pass
def test_hello(self):
"""
test hello workflow
"""
command_prefix = ['bananas','workflow-hello']
params = ['params-amy','params-beth']
pwd = os.path.abspath(os.path.dirname(__file__))
for param in params:
command = command_prefix + [param]
p = Popen(command, cwd=pwd, stdout=PIPE, stderr=PIPE).communicate()
p_out = p[0].decode('utf-8').strip()
p_err = p[1].decode('utf-8').strip()
self.assertIn('details',p_out)
# clean up
call(['rm','-f','hello.txt'])
@classmethod
def tearDownClass(self):
"""
clean up after the tests
"""
pass

0
workflow-goodbye.json → test/workflow-goodbye.json

0
workflow-hello.json → test/workflow-hello.json

Loading…
Cancel
Save