Compare commits

...

7 Commits
cli ... master

Author SHA1 Message Date
Charles Reid f3b60856c2 add tags to readme 5 years ago
Charles Reid 96821aa1db
Fix travis shield link 5 years ago
Charles Reid c4fe27e883 upgrade pyyaml 5 years ago
Charles Reid 7da5a5e4ba
Update README.md 5 years ago
Charles Reid 05816b006d typo sniper snipes a typo 5 years ago
Charles Reid 31e2d0c3e1 banana -> bananas 5 years ago
Charles Reid d013ff2ddd
Add command line interface, python package, and tests (#1) 5 years ago
  1. 5
      .gitignore
  2. 14
      .travis.yml
  3. 19
      LICENSE
  4. 1
      MANIFEST.in
  5. 86
      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/

14
.travis.yml

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
# https://docs.travis-ci.com/user/languages/python/
language: python
python:
- "3.5"
- "3.6"
#- "3.7-dev" # fails due to datrie build failure (snakemake dependency)
# 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

86
README.md

@ -1,37 +1,89 @@ @@ -1,37 +1,89 @@
# 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)
[![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 bananas
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
```
Now you can run
```
which bananas
```
creates `hello.txt` with "hello amy" in it, while
and you should see `bananas` in your virtual
environment's `bin/` directory.
## Running bananas
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:
```
rm -f hello.txt
bananas workflow-hello params-beth
```
Likewise,
Run the goodbye workflow with Beth params:
```
rm -f goodbye.txt
./run workflow-goodbye params-beth
bananas 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.
# Tags
* `v1.0` - initial version, [ctb/2018-snakemake-cli](https://github.com/ctb/2018-snakemake-cli)
* `v2.0` - Snakemake workflow bundled as installable Python package, Snakefile bundled with
Python package, command line interface provided to wrap Snakemake API call
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>=4.2b1
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