From 81d282e1d7623724e456fac79e68f28ed8e84390 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Tue, 26 Mar 2019 13:38:40 -0400 Subject: [PATCH 01/19] update version, reqs, and changelog --- divvy/_version.py | 2 +- docs/changelog.md | 5 +++++ requirements/requirements-all.txt | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/divvy/_version.py b/divvy/_version.py index 2995a62..b61d113 100644 --- a/divvy/_version.py +++ b/divvy/_version.py @@ -1,2 +1,2 @@ -__version__ = "0.2.1" +__version__ = "0.3dev" diff --git a/docs/changelog.md b/docs/changelog.md index 582b337..5b67aab 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Changed +- Safer use of `yaml` + ## divvy [0.2.1] - 2019-03-19 ### Changed diff --git a/requirements/requirements-all.txt b/requirements/requirements-all.txt index 071a003..84bc55b 100644 --- a/requirements/requirements-all.txt +++ b/requirements/requirements-all.txt @@ -1,5 +1,4 @@ pandas>=0.20.2 -pyyaml>=3.12 -nbsphinx +pyyaml>=5.1 attmap>=0.4 From 54f817aa7db640353e4b6a8ee8480731a4c1ee36 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Tue, 26 Mar 2019 13:40:57 -0400 Subject: [PATCH 02/19] add yaml loaders --- divvy/compute.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 8009451..6b2a0ea 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -3,8 +3,9 @@ import argparse import logging import os -from sys import stdout +import sys import yaml +from yaml import SafeLoader from attmap import PathExAttMap from .const import \ @@ -220,7 +221,7 @@ def update_packages(self, config_file): """ with open(config_file, 'r') as f: _LOGGER.info("Loading divvy config file: %s", config_file) - env_settings = yaml.load(f) + env_settings = yaml.load(f, SafeLoader) _LOGGER.debug("Parsed environment settings: %s", str(env_settings)) @@ -289,6 +290,7 @@ def format_help(self): return "version: {}\n".format(__version__) + \ super(_VersionInHelpParser, self).format_help() + def main(): """ Primary workflow """ @@ -330,11 +332,12 @@ def main(): if args.settings: with open(args.settings, 'r') as f: _LOGGER.info("Loading yaml settings file: %s", args.settings) - yaml_vars = yaml.load(f) + yaml_vars = yaml.load(f, SafeLoader) dcc.write_script(args.outfile, [custom_vars, yaml_vars]) else: dcc.write_script(args.outfile, custom_vars) + if __name__ == '__main__': try: sys.exit(main()) From c57da97f885c7fc544c9ec9ec2df7ea0551db3a0 Mon Sep 17 00:00:00 2001 From: nsheff Date: Wed, 10 Apr 2019 12:08:52 -0400 Subject: [PATCH 03/19] Add divvy list and divvy write subcommands --- divvy/compute.py | 27 ++++++++++++++++++++------- docs/changelog.md | 3 ++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 6b2a0ea..80eaddd 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -128,6 +128,7 @@ def templates_folder(self): """ return os.path.join(os.path.dirname(__file__), "submit_templates") + def activate_package(self, package_name): """ Activates a compute package. @@ -228,7 +229,7 @@ def update_packages(self, config_file): # Any compute.submission_template variables should be made # absolute, relative to current divvy configuration file. if "compute" in env_settings: - _LOGGER.warning("In your divvy config file, please use 'compute_packages' instead of 'compute'") + _LOGGER.warning("DEPRECATION WARNING: Divvy compute configuration 'compute' section changed to 'compute_packages'") env_settings["compute_packages"] = env_settings["compute"] loaded_packages = env_settings["compute_packages"] @@ -245,7 +246,7 @@ def update_packages(self, config_file): self.compute_packages = PathExAttMap(loaded_packages) else: self.compute_packages.add_entries(loaded_packages) - _LOGGER.info("Available packages: {}".format(', '.join(self.list_compute_packages()))) + _LOGGER.debug("Available divvy packages: {}".format(', '.join(self.list_compute_packages()))) self.config_file = config_file def write_script(self, output_path, extra_vars=None): @@ -294,7 +295,7 @@ def format_help(self): def main(): """ Primary workflow """ - banner = "%(prog)s - write compute jobs that can be submitted to any computing resource" + banner = "%(prog)s - write compute job scripts that can be submitted to any computing resource" additional_description = "\nhttps://github.com/pepkit/divvy" parser = _VersionInHelpParser( @@ -310,24 +311,36 @@ def main(): "-C", "--config", help="Divvy configuration file.") - parser.add_argument( + subparsers = parser.add_subparsers(dest="command") + list_subparser = subparsers.add_parser("list", description="Lists available packages", + help="Run 'divvy list' to list available packages") + + write_subparser = subparsers.add_parser("write", description="write compute job script", + help="write compute job script") + + write_subparser.add_argument( "-S", "--settings", help="YAML file with job settings to populate the template.") - parser.add_argument( + write_subparser.add_argument( "-P", "--package", default="default", help="Compute package") - parser.add_argument( + write_subparser.add_argument( "-O", "--outfile", required=True, help="Output filepath") args, remaining_args = parser.parse_known_args() - keys = [str.replace(x, "--", "") for x in remaining_args[::2]] custom_vars = dict(zip(keys, remaining_args[1::2])) dcc = ComputingConfiguration(args.config) + + + if args.command == "list": + _LOGGER.info("Available compute packages: {}".format(', '.join(dcc.list_compute_packages()))) + sys.exit(1) + dcc.activate_package(args.package) if args.settings: with open(args.settings, 'r') as f: diff --git a/docs/changelog.md b/docs/changelog.md index 5b67aab..ef5e64d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,9 +1,10 @@ # Changelog -## [Unreleased] +## divvy [0.3] - Unreleased ### Changed - Safer use of `yaml` +- Reduced verbosity for clearly usage ## divvy [0.2.1] - 2019-03-19 From 6aafd1fbcedc2439ea7222431658aa775761ec63 Mon Sep 17 00:00:00 2001 From: nsheff Date: Wed, 10 Apr 2019 12:16:25 -0400 Subject: [PATCH 04/19] cleanup, add docs for new cli API --- divvy/compute.py | 15 +++++--- docs_jupyter/cli.ipynb | 81 +++++++++++++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 80eaddd..2ace3c8 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -312,11 +312,18 @@ def main(): help="Divvy configuration file.") subparsers = parser.add_subparsers(dest="command") - list_subparser = subparsers.add_parser("list", description="Lists available packages", - help="Run 'divvy list' to list available packages") - write_subparser = subparsers.add_parser("write", description="write compute job script", - help="write compute job script") + # Individual subcommands + msg_by_cmd = { + "list": "List available compute packages", + "write": "Write a submit script" + } + + def add_subparser(cmd): + return subparsers.add_parser(cmd, description=msg_by_cmd[cmd], help=msg_by_cmd[cmd]) + + list_subparser = add_subparser("list") + write_subparser = add_subparser("write") write_subparser.add_argument( "-S", "--settings", diff --git a/docs_jupyter/cli.ipynb b/docs_jupyter/cli.ipynb index c18ec99..4219666 100644 --- a/docs_jupyter/cli.ipynb +++ b/docs_jupyter/cli.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, @@ -25,22 +25,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "version: 0.2-dev\n", - "usage: divvy [-h] [-V] [-C CONFIG] [-S SETTINGS] [-P PACKAGE] -O OUTFILE\n", + "version: 0.3dev\n", + "usage: divvy [-h] [-V] [-C CONFIG] {list,write} ...\n", "\n", - "divvy - write compute jobs that can be submitted to any computing resource\n", + "divvy - write compute job scripts that can be submitted to any computing\n", + "resource\n", + "\n", + "positional arguments:\n", + " {list,write}\n", + " list List available compute packages\n", + " write Write a submit script\n", "\n", "optional arguments:\n", " -h, --help show this help message and exit\n", " -V, --version show program's version number and exit\n", " -C CONFIG, --config CONFIG\n", " Divvy configuration file.\n", - " -S SETTINGS, --settings SETTINGS\n", - " YAML file with job settings to populate the template.\n", - " -P PACKAGE, --package PACKAGE\n", - " Compute package\n", - " -O OUTFILE, --outfile OUTFILE\n", - " Output filepath\n", "\n", "https://github.com/pepkit/divvy\n" ] @@ -50,6 +50,43 @@ "divvy --help" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# The `list` command\n", + "\n", + "Let's first use `divvy list` to show us our available computing packages:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using default config file, no global config file provided in environment variable(s): ['DIVCFG', 'PEPENV']\n", + "Loading divvy config file: /home/nsheff/.local/lib/python3.5/site-packages/divvy/submit_templates/default_compute_settings.yaml\n", + "Activating compute package 'default'\n", + "Available compute packages: default, local, slurm\n" + ] + }, + { + "ename": "", + "evalue": "1", + "output_type": "error", + "traceback": [] + } + ], + "source": [ + "divvy list" + ] + }, { "cell_type": "markdown", "metadata": { @@ -57,6 +94,11 @@ "editable": true }, "source": [ + "# The `write` command\n", + "\n", + "Use `divvy write` to actually write a new script using a template. To do this, you'll need to provide 3 things: a template (which comes from your compute package), a settings file with variables, and an outfile.\n", + "\n", + "\n", "## The settings file\n", "\n", "The settings argument is where you can pass an existing `yaml` file with key-value pairs. Here's a simple example:" @@ -143,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 5, "metadata": { "collapsed": false, "deletable": true, @@ -155,8 +197,7 @@ "output_type": "stream", "text": [ "Using default config file, no global config file provided in environment variable(s): ['DIVCFG', 'PEPENV']\n", - "Loading divvy config file: /home/nsheff/.local/lib/python2.7/site-packages/divvy/submit_templates/default_compute_settings.yaml\n", - "Available packages: default, local, slurm\n", + "Loading divvy config file: /home/nsheff/.local/lib/python3.5/site-packages/divvy/submit_templates/default_compute_settings.yaml\n", "Activating compute package 'default'\n", "Activating compute package 'slurm'\n", "Loading yaml settings file: settings.yaml\n", @@ -165,7 +206,7 @@ } ], "source": [ - "divvy -P slurm -S settings.yaml -O test.sub" + "divvy write -P slurm -S settings.yaml -O test.sub" ] }, { @@ -180,7 +221,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 6, "metadata": { "collapsed": false, "deletable": true, @@ -224,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 7, "metadata": { "collapsed": false, "deletable": true, @@ -235,10 +276,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Custom vars: {}\n", "Using default config file, no global config file provided in environment variable(s): ['DIVCFG', 'PEPENV']\n", - "Loading divvy config file: /home/nsheff/.local/lib/python2.7/site-packages/divvy/submit_templates/default_compute_settings.yaml\n", - "Available packages: default, local, slurm\n", + "Loading divvy config file: /home/nsheff/.local/lib/python3.5/site-packages/divvy/submit_templates/default_compute_settings.yaml\n", "Activating compute package 'default'\n", "Activating compute package 'slurm'\n", "Loading yaml settings file: settings.yaml\n", @@ -247,12 +286,12 @@ } ], "source": [ - "divvy -P slurm -S settings.yaml -O test.sub --code run-this-cmd --jobname 12345 --time 6-0-0" + "divvy write -P slurm -S settings.yaml -O test.sub --code run-this-cmd --jobname 12345 --time 6-0-0" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 8, "metadata": { "collapsed": false, "deletable": true, From 1d0e96338877f5b35578394e720bb311beda30cd Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Wed, 10 Apr 2019 13:02:30 -0400 Subject: [PATCH 05/19] depr warn --- divvy/compute.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 2ace3c8..d78022a 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -4,6 +4,7 @@ import logging import os import sys +import warnings import yaml from yaml import SafeLoader @@ -12,7 +13,7 @@ COMPUTE_SETTINGS_VARNAME, \ DEFAULT_COMPUTE_RESOURCES_NAME from .utils import write_submit_script, get_first_env_var -from . import __version__ +from . import __version__ _LOGGER = logging.getLogger(__name__) @@ -226,11 +227,16 @@ def update_packages(self, config_file): _LOGGER.debug("Parsed environment settings: %s", str(env_settings)) + old_compute_key = "compute" + new_compute_key = "compute_packages" + # Any compute.submission_template variables should be made # absolute, relative to current divvy configuration file. - if "compute" in env_settings: - _LOGGER.warning("DEPRECATION WARNING: Divvy compute configuration 'compute' section changed to 'compute_packages'") - env_settings["compute_packages"] = env_settings["compute"] + if old_compute_key in env_settings: + warnings.warn("Divvy compute configuration '{}' section changed " + "to '{}'".format(old_compute_key, new_compute_key), + DeprecationWarning) + env_settings[new_compute_key] = env_settings[old_compute_key] loaded_packages = env_settings["compute_packages"] for key, value in loaded_packages.items(): From 0ae36b480f244ab60103db3bc0e54564fe886699 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Wed, 10 Apr 2019 13:06:19 -0400 Subject: [PATCH 06/19] tidy; remove unused var --- divvy/compute.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index d78022a..5b09f7a 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -328,7 +328,6 @@ def main(): def add_subparser(cmd): return subparsers.add_parser(cmd, description=msg_by_cmd[cmd], help=msg_by_cmd[cmd]) - list_subparser = add_subparser("list") write_subparser = add_subparser("write") write_subparser.add_argument( @@ -343,13 +342,11 @@ def add_subparser(cmd): "-O", "--outfile", required=True, help="Output filepath") - args, remaining_args = parser.parse_known_args() keys = [str.replace(x, "--", "") for x in remaining_args[::2]] custom_vars = dict(zip(keys, remaining_args[1::2])) dcc = ComputingConfiguration(args.config) - if args.command == "list": _LOGGER.info("Available compute packages: {}".format(', '.join(dcc.list_compute_packages()))) sys.exit(1) From e9f08d05906b958b0ad302d2a170f00154f2dd96 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Wed, 10 Apr 2019 17:27:14 -0400 Subject: [PATCH 07/19] update test data; close #29 --- tests/data/pepenv-master/cemm.yaml | 4 ++-- tests/data/pepenv-master/compute_config.yaml | 4 ++-- tests/data/pepenv-master/local_containers.yaml | 2 +- tests/data/pepenv-master/nih_biowulf2.yaml | 2 +- tests/data/pepenv-master/ski-cer_lilac.yaml | 2 +- tests/data/pepenv-master/stanford_sherlock.yaml | 2 +- tests/data/pepenv-master/uva_rivanna.yaml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/data/pepenv-master/cemm.yaml b/tests/data/pepenv-master/cemm.yaml index 646e8ad..5527d73 100644 --- a/tests/data/pepenv-master/cemm.yaml +++ b/tests/data/pepenv-master/cemm.yaml @@ -1,7 +1,7 @@ # Environment configuration file for looper # This version describes the compute environment at CeMM -compute: +compute_packages: default: submission_template: templates/slurm_template.sub submission_command: sbatch @@ -12,4 +12,4 @@ compute: partition: develop local: submission_template: templates/localhost_template.sub - submission_command: sh \ No newline at end of file + submission_command: sh diff --git a/tests/data/pepenv-master/compute_config.yaml b/tests/data/pepenv-master/compute_config.yaml index c7f2ad5..8d1d797 100644 --- a/tests/data/pepenv-master/compute_config.yaml +++ b/tests/data/pepenv-master/compute_config.yaml @@ -1,4 +1,4 @@ -compute: +compute_packages: default: submission_template: templates/slurm_template.sub submission_command: sbatch @@ -9,4 +9,4 @@ compute: partition: economy local: submission_template: templates/localhost_template.sub - submission_command: sh \ No newline at end of file + submission_command: sh diff --git a/tests/data/pepenv-master/local_containers.yaml b/tests/data/pepenv-master/local_containers.yaml index 90b1b4b..771dfe0 100644 --- a/tests/data/pepenv-master/local_containers.yaml +++ b/tests/data/pepenv-master/local_containers.yaml @@ -2,7 +2,7 @@ # This version describes the compute environment for a local computer that uses # docker or singularity containers -compute: +compute_packages: default: submission_template: templates/localhost_template.sub submission_command: sh diff --git a/tests/data/pepenv-master/nih_biowulf2.yaml b/tests/data/pepenv-master/nih_biowulf2.yaml index fe9fbb2..4defa60 100644 --- a/tests/data/pepenv-master/nih_biowulf2.yaml +++ b/tests/data/pepenv-master/nih_biowulf2.yaml @@ -1,7 +1,7 @@ # Environment configuration file for looper # This version describes the compute environment on biowulf2 at the NIH -compute: +compute_packages: default: submission_template: templates/slurm_template.sub submission_command: sbatch --mail-type=BEGIN,TIME_LIMIT_90,END diff --git a/tests/data/pepenv-master/ski-cer_lilac.yaml b/tests/data/pepenv-master/ski-cer_lilac.yaml index 601e3e0..b6ce9c3 100644 --- a/tests/data/pepenv-master/ski-cer_lilac.yaml +++ b/tests/data/pepenv-master/ski-cer_lilac.yaml @@ -1,7 +1,7 @@ # Environment configuration file for looper # This version describes the compute environment on lilac at SKI, MSKCC -compute: +compute_packages: default: submission_template: templates/lsf_template.sub submission_command: sh diff --git a/tests/data/pepenv-master/stanford_sherlock.yaml b/tests/data/pepenv-master/stanford_sherlock.yaml index a3eed37..2b940bf 100644 --- a/tests/data/pepenv-master/stanford_sherlock.yaml +++ b/tests/data/pepenv-master/stanford_sherlock.yaml @@ -1,4 +1,4 @@ -compute: +compute_packages: default: submission_template: templates/localhost_template.sub submission_command: sh diff --git a/tests/data/pepenv-master/uva_rivanna.yaml b/tests/data/pepenv-master/uva_rivanna.yaml index 88ca7a7..fc98e06 100644 --- a/tests/data/pepenv-master/uva_rivanna.yaml +++ b/tests/data/pepenv-master/uva_rivanna.yaml @@ -1,7 +1,7 @@ # Environment configuration file for looper # This version describes the compute environment on Rivanna at UVA -compute: +compute_packages: default: submission_template: templates/slurm_template.sub submission_command: sbatch From 2b96eba3d44954815b86578e9547f6cc343743cd Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Mon, 15 Apr 2019 16:15:56 -0400 Subject: [PATCH 08/19] bump req --- requirements/requirements-all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-all.txt b/requirements/requirements-all.txt index 84bc55b..c7e251a 100644 --- a/requirements/requirements-all.txt +++ b/requirements/requirements-all.txt @@ -1,4 +1,4 @@ pandas>=0.20.2 pyyaml>=5.1 -attmap>=0.4 +attmap>=0.6 From f9e80d96e47af25a32f4634e6dcca948c0a3ef2c Mon Sep 17 00:00:00 2001 From: nsheff Date: Fri, 19 Apr 2019 15:07:07 -0400 Subject: [PATCH 09/19] docs update --- docs/README.md | 9 ++++++--- docs/autodoc_build/divvy.md | 29 ++++++++++++++++------------- docs/changelog.md | 3 ++- docs/configuration.md | 4 ++++ mkdocs.yml | 2 +- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/README.md b/docs/README.md index 502f900..28f52bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,13 +3,15 @@ [![PEP compatible](http://pepkit.github.io/img/PEP-compatible-green.svg)](http://pepkit.github.io) -`divvy` is a simple templating system written in python that allows users to write compute jobs that can be submitted to any computing resource (laptop, cluster, cloud). It works using a simple configuration file, which we call *computing configuration files*, where you specify variables for your computing environment. It uses these variables to populate simple, Jinja-like templates to make computing job submission flexible. +`Divvy` is a computing resource configuration manager. It reads a standard configuration file describing available compute resources and then uses a simple Jinja-like templating system to enable users to write custom job submission scripts. + +In `divvy`, computing resources are organized as *compute packages*, which define job submission templates and other variables. Users then select a compute package and provide variable values, and `divvy` populates the templates to write compute jobs. The flexible templating system means users can quickly switch jobs to submit to any computing resource (laptop, cluster, cloud). `Divvy` provides both an interactive python API and a command-line interface. ## Installing +Releases are posted as [GitHub releases](https://github.com/databio/divvy/releases), or you can install from PyPI using `pip`: -Release versions are posted on the GitHub [divvy releases page](https://github.com/databio/divvy/releases). You can install the latest release directly from PyPI using pip: ```{console} pip install --user divvy @@ -38,7 +40,8 @@ dcc.write_script("test_script.sub", {"code": "bowtie2 input.bam output.bam"}) Or via command-line: ```{console} -divvy --package slurm --settings myjob.yaml --sample sample1 --outfile submit_script.txt +divvy list +divvy write --package slurm --settings myjob.yaml --sample sample1 --outfile submit_script.txt ``` To begin, check out the [tutorial](tutorial). diff --git a/docs/autodoc_build/divvy.md b/docs/autodoc_build/divvy.md index e5a311d..a821974 100644 --- a/docs/autodoc_build/divvy.md +++ b/docs/autodoc_build/divvy.md @@ -23,7 +23,7 @@ This copies the computing attributes from the configuration file into the `compute` attribute, where the class stores current compute settings. ```python -def activate_package(self, package_name): +def activate_package(self, package_name) ``` **Parameters:** @@ -41,7 +41,7 @@ def activate_package(self, package_name): ### clean\_start Clear current active settings and then activate the given package. ```python -def clean_start(self, package_name): +def clean_start(self, package_name) ``` **Parameters:** @@ -59,7 +59,7 @@ def clean_start(self, package_name): ### compute\_env\_var Environment variable through which to access compute settings. ```python -def compute_env_var: +def compute_env_var(self) ``` **Returns:** @@ -72,7 +72,7 @@ def compute_env_var: ### default\_config\_file Path to default compute environment settings file. ```python -def default_config_file: +def default_config_file(self) ``` **Returns:** @@ -85,12 +85,12 @@ def default_config_file: ### get\_active\_package Returns settings for the currently active compute package ```python -def get_active_package(self): +def get_active_package(self) ``` **Returns:** -`AttMap`: data defining the active compute package +`PathExAttMap`: data defining the active compute package @@ -98,7 +98,7 @@ def get_active_package(self): ### list\_compute\_packages Returns a list of available compute packages. ```python -def list_compute_packages(self): +def list_compute_packages(self) ``` **Returns:** @@ -111,7 +111,7 @@ def list_compute_packages(self): ### reset\_active\_settings Clear out current compute settings. ```python -def reset_active_settings(self): +def reset_active_settings(self) ``` **Returns:** @@ -124,7 +124,7 @@ def reset_active_settings(self): ### template Get the currently active submission template. ```python -def template: +def template(self) ``` **Returns:** @@ -137,7 +137,7 @@ def template: ### templates\_folder Path to folder with default submission templates. ```python -def templates_folder: +def templates_folder(self) ``` **Returns:** @@ -154,7 +154,7 @@ Given a divvy configuration file, this function will update (not overwrite) existing compute packages with existing values. It does not affect any currently active settings. ```python -def update_packages(self, config_file): +def update_packages(self, config_file) ``` **Parameters:** @@ -167,7 +167,7 @@ def update_packages(self, config_file): ### write\_script Given currently active settings, populate the active template to write a submission script. ```python -def write_script(self, output_path, extra_vars=None): +def write_script(self, output_path, extra_vars=None) ``` **Parameters:** @@ -186,7 +186,7 @@ def write_script(self, output_path, extra_vars=None): ### write\_submit\_script Write a submission script by populating a template with data. ```python -def write_submit_script(fp, content, data): +def write_submit_script(fp, content, data) ``` **Parameters:** @@ -202,3 +202,6 @@ def write_submit_script(fp, content, data): + + +**Version Information**: `divvy` v0.3dev, generated by `lucidoc` v0.3 \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index ef5e64d..36e26c8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,7 +4,8 @@ ### Changed - Safer use of `yaml` -- Reduced verbosity for clearly usage +- Reduced verbosity for clearer usage +- The CLI now uses `divvy write` and `divvy list` subcommands ## divvy [0.2.1] - 2019-03-19 diff --git a/docs/configuration.md b/docs/configuration.md index 715c500..201f3e6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -42,6 +42,10 @@ The `submission_command` attribute is the string your cluster resource manager u Each compute package specifies a path to a template file (`submission_template`). The template file provides a skeleton that `divvy` will populate with job-specific attributes. These paths can be relative or absolute; relative paths are considered *relative to the DIVCFG file*. +## Resources + +You may notice that the compute config file does not specify resources to request (like memory, CPUs, or time). Yet, these are required in order to submit a job to a cluster. **Resources are not handled by the divcfg file** because they not relative to a particular computing environment; instead they vary by pipeline and sample. As such, these items should be defined at other stages. + ## Template files Each compute package must point to a template file with the `submission_template` attribute. These template files are typically stored relative to the `divvy` configuration file. Template files are taken by `divvy`, populated with job-specific information, and then run as scripts. Here's an example of a generic SLURM template file: diff --git a/mkdocs.yml b/mkdocs.yml index 73dd4e9..8e03e3f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,7 +10,7 @@ nav: - "Tutorial: divvy in python": tutorial.md - "Tutorial: divvy on the command line": cli.md - How-to Guides: - - Write a DIVCFG file: configuration.md + - Configuring divvy: configuration.md - Configuring containers: containers.md - Reference: - API: autodoc_build/divvy.md From 45e30a70d9a1ffabf5c6b0ff1391a1b8c86aba6c Mon Sep 17 00:00:00 2001 From: nsheff Date: Fri, 19 Apr 2019 15:12:00 -0400 Subject: [PATCH 10/19] version bump, changelog --- divvy/_version.py | 2 +- docs/changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/divvy/_version.py b/divvy/_version.py index b61d113..5c3e1be 100644 --- a/divvy/_version.py +++ b/divvy/_version.py @@ -1,2 +1,2 @@ -__version__ = "0.3dev" +__version__ = "0.3" diff --git a/docs/changelog.md b/docs/changelog.md index 36e26c8..c346c63 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,6 @@ # Changelog -## divvy [0.3] - Unreleased +## divvy [0.3] - 2019-04-19 ### Changed - Safer use of `yaml` From 1fccd36750ab787e0e1f87ea00f7910f6cb35450 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 19 Apr 2019 16:11:10 -0400 Subject: [PATCH 11/19] spacing --- divvy/utils.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/divvy/utils.py b/divvy/utils.py index 5bb8c0a..5e47980 100644 --- a/divvy/utils.py +++ b/divvy/utils.py @@ -14,17 +14,13 @@ else: from urllib.parse import urlparse import warnings - - import yaml - from .const import GENERIC_PROTOCOL_KEY, SAMPLE_INDEPENDENT_PROJECT_SECTIONS _LOGGER = logging.getLogger(__name__) - def add_project_sample_constants(sample, project): """ Update a Sample with constants declared by a Project. @@ -40,7 +36,6 @@ def add_project_sample_constants(sample, project): return sample - def alpha_cased(text, lower=False): """ Filter text to just letters and homogenize case. @@ -54,7 +49,6 @@ def alpha_cased(text, lower=False): return text.lower() if lower else text.upper() - def check_bam(bam, o): """ Check reads in BAM file for read type and lengths. @@ -87,13 +81,11 @@ def check_bam(bam, o): return read_lengths, paired - def check_fastq(fastq, o): raise NotImplementedError("Detection of read type/length for " "fastq input is not yet implemented.") - def check_sample_sheet_row_count(sheet, filepath): """ Quick-and-dirt proxy for Sample count validation. @@ -113,7 +105,6 @@ def check_sample_sheet_row_count(sheet, filepath): return len(sheet) == len(lines) - deduction - def copy(obj): def copy(self): """ @@ -126,7 +117,6 @@ def copy(self): return obj - def expandpath(path): """ Expand a filesystem path that may or may not contain user/env vars. @@ -137,7 +127,6 @@ def expandpath(path): return os.path.expandvars(os.path.expanduser(path)).replace("//", "/") - def get_file_size(filename): """ Get size of all files in gigabytes (Gb). @@ -160,7 +149,6 @@ def get_file_size(filename): return float(total_bytes) / (1024 ** 3) - def fetch_samples(proj, inclusion=None, exclusion=None): """ Collect samples of particular protocol(s). @@ -217,7 +205,6 @@ def keep(s): return list(filter(keep, proj.samples)) - def grab_project_data(prj): """ From the given Project, grab Sample-independent data. @@ -246,7 +233,6 @@ def grab_project_data(prj): return data - def import_from_source(module_filepath): """ Import a module from a particular filesystem location. @@ -285,7 +271,6 @@ def import_from_source(module_filepath): return mod - def is_url(maybe_url): """ Determine whether a path is a URL. @@ -296,7 +281,6 @@ def is_url(maybe_url): return urlparse(maybe_url).scheme != "" - def parse_ftype(input_file): """ Checks determine filetype from extension. @@ -317,7 +301,6 @@ def parse_ftype(input_file): "nor '.fastq' [file: '" + input_file + "']") - def parse_text_data(lines_or_path, delimiter=os.linesep): """ Interpret input argument as lines of data. This is intended to support @@ -346,7 +329,6 @@ def parse_text_data(lines_or_path, delimiter=os.linesep): format(lines_or_path, type(lines_or_path))) - def sample_folder(prj, sample): """ Get the path to this Project's root folder for the given Sample. @@ -360,7 +342,6 @@ def sample_folder(prj, sample): sample["sample_name"]) - def write_submit_script(fp, content, data): """ Write a submission script by populating a template with data. @@ -390,7 +371,6 @@ def write_submit_script(fp, content, data): return fp - @contextlib.contextmanager def standard_stream_redirector(stream): """ @@ -411,7 +391,6 @@ def standard_stream_redirector(stream): sys.stdout, sys.stderr = genuine_stdout, genuine_stderr - def warn_derived_cols(): _warn_cols_to_attrs("derived") @@ -425,7 +404,6 @@ def _warn_cols_to_attrs(prefix): "as {pfx}_attributes".format(pfx=prefix), DeprecationWarning) - class CommandChecker(object): """ Validate PATH availability of executables referenced by a config file. @@ -440,7 +418,6 @@ class CommandChecker(object): the check names parameter, but for specific sections to skip. """ - def __init__(self, path_conf_file, sections_to_check=None, sections_to_skip=None): @@ -506,7 +483,6 @@ def __init__(self, path_conf_file, self._logger.debug("Command '%s': %s", command, "SUCCESS" if success else "FAILURE") - def _store_status(self, section, command, name): """ Based on new command execution attempt, update instance's @@ -522,7 +498,6 @@ def _store_status(self, section, command, name): self.failures.add(command) return succeeded - @property def failed(self): """ @@ -540,7 +515,6 @@ def failed(self): return 0 == len(self.failures) - def is_command_callable(command, name=""): """ Check if command can be called. From 3776a341104d7a9a5d9e1a8d4dd7cdc71499b5ec Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 19 Apr 2019 16:11:48 -0400 Subject: [PATCH 12/19] only manage context where relevant --- divvy/compute.py | 58 +++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 5b09f7a..8bbd0e7 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -224,34 +224,36 @@ def update_packages(self, config_file): with open(config_file, 'r') as f: _LOGGER.info("Loading divvy config file: %s", config_file) env_settings = yaml.load(f, SafeLoader) - _LOGGER.debug("Parsed environment settings: %s", - str(env_settings)) - - old_compute_key = "compute" - new_compute_key = "compute_packages" - - # Any compute.submission_template variables should be made - # absolute, relative to current divvy configuration file. - if old_compute_key in env_settings: - warnings.warn("Divvy compute configuration '{}' section changed " - "to '{}'".format(old_compute_key, new_compute_key), - DeprecationWarning) - env_settings[new_compute_key] = env_settings[old_compute_key] - - loaded_packages = env_settings["compute_packages"] - for key, value in loaded_packages.items(): - if type(loaded_packages[key]) is dict: - for key2, value2 in loaded_packages[key].items(): - if key2 == "submission_template": - if not os.path.isabs(loaded_packages[key][key2]): - loaded_packages[key][key2] = os.path.join( - os.path.dirname(config_file), - loaded_packages[key][key2]) - - if self.compute_packages is None: - self.compute_packages = PathExAttMap(loaded_packages) - else: - self.compute_packages.add_entries(loaded_packages) + + _LOGGER.debug("Parsed environment settings: %s", + str(env_settings)) + + old_compute_key = "compute" + new_compute_key = "compute_packages" + + # Any compute.submission_template variables should be made + # absolute, relative to current divvy configuration file. + if old_compute_key in env_settings: + warnings.warn("Divvy compute configuration '{}' section changed " + "to '{}'".format(old_compute_key, new_compute_key), + DeprecationWarning) + env_settings[new_compute_key] = env_settings[old_compute_key] + + loaded_packages = env_settings["compute_packages"] + for key, value in loaded_packages.items(): + if type(loaded_packages[key]) is dict: + for key2, value2 in loaded_packages[key].items(): + if key2 == "submission_template": + if not os.path.isabs(loaded_packages[key][key2]): + loaded_packages[key][key2] = os.path.join( + os.path.dirname(config_file), + loaded_packages[key][key2]) + + if self.compute_packages is None: + self.compute_packages = PathExAttMap(loaded_packages) + else: + self.compute_packages.add_entries(loaded_packages) + _LOGGER.debug("Available divvy packages: {}".format(', '.join(self.list_compute_packages()))) self.config_file = config_file From 321245ce1b540f72362f037b02e2e9096ce6a9e0 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 19 Apr 2019 16:21:33 -0400 Subject: [PATCH 13/19] modularity; variable sharing --- divvy/compute.py | 29 +++++------------------------ divvy/const.py | 2 ++ divvy/utils.py | 25 ++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 8bbd0e7..01ae598 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -4,7 +4,6 @@ import logging import os import sys -import warnings import yaml from yaml import SafeLoader @@ -12,7 +11,7 @@ from .const import \ COMPUTE_SETTINGS_VARNAME, \ DEFAULT_COMPUTE_RESOURCES_NAME -from .utils import write_submit_script, get_first_env_var +from .utils import parse_config_file, write_submit_script, get_first_env_var from . import __version__ _LOGGER = logging.getLogger(__name__) @@ -129,7 +128,6 @@ def templates_folder(self): """ return os.path.join(os.path.dirname(__file__), "submit_templates") - def activate_package(self, package_name): """ Activates a compute package. @@ -218,27 +216,9 @@ def update_packages(self, config_file): overwrite) existing compute packages with existing values. It does not affect any currently active settings. - :param str config_file: path to file with - new divvy configuration data + :param str config_file: path to file with new divvy configuration data """ - with open(config_file, 'r') as f: - _LOGGER.info("Loading divvy config file: %s", config_file) - env_settings = yaml.load(f, SafeLoader) - - _LOGGER.debug("Parsed environment settings: %s", - str(env_settings)) - - old_compute_key = "compute" - new_compute_key = "compute_packages" - - # Any compute.submission_template variables should be made - # absolute, relative to current divvy configuration file. - if old_compute_key in env_settings: - warnings.warn("Divvy compute configuration '{}' section changed " - "to '{}'".format(old_compute_key, new_compute_key), - DeprecationWarning) - env_settings[new_compute_key] = env_settings[old_compute_key] - + env_settings = parse_config_file(config_file) loaded_packages = env_settings["compute_packages"] for key, value in loaded_packages.items(): if type(loaded_packages[key]) is dict: @@ -254,7 +234,8 @@ def update_packages(self, config_file): else: self.compute_packages.add_entries(loaded_packages) - _LOGGER.debug("Available divvy packages: {}".format(', '.join(self.list_compute_packages()))) + _LOGGER.debug("Available divvy packages: {}". + format(', '.join(self.list_compute_packages()))) self.config_file = config_file def write_script(self, output_path, extra_vars=None): diff --git a/divvy/const.py b/divvy/const.py index f7ed6db..34e9bed 100644 --- a/divvy/const.py +++ b/divvy/const.py @@ -7,6 +7,8 @@ # Compute-related COMPUTE_SETTINGS_VARNAME = ["DIVCFG", "PEPENV"] DEFAULT_COMPUTE_RESOURCES_NAME = "default" +OLD_COMPUTE_KEY = "compute" +NEW_COMPUTE_KEY = "compute_packages" COMPUTE_CONSTANTS = ["COMPUTE_SETTINGS_VARNAME", "DEFAULT_COMPUTE_RESOURCES_NAME"] diff --git a/divvy/utils.py b/divvy/utils.py index 5e47980..18fda54 100644 --- a/divvy/utils.py +++ b/divvy/utils.py @@ -15,7 +15,8 @@ from urllib.parse import urlparse import warnings import yaml -from .const import GENERIC_PROTOCOL_KEY, SAMPLE_INDEPENDENT_PROJECT_SECTIONS +from .const import GENERIC_PROTOCOL_KEY, NEW_COMPUTE_KEY, OLD_COMPUTE_KEY, \ + SAMPLE_INDEPENDENT_PROJECT_SECTIONS _LOGGER = logging.getLogger(__name__) @@ -281,6 +282,28 @@ def is_url(maybe_url): return urlparse(maybe_url).scheme != "" +def parse_config_file(conf_file): + """ + Parse a divvy configuration file. + + :param str conf_file: path to divvy configuration file + :return Mapping: compute settings as declared in config file + """ + with open(conf_file, 'r') as f: + _LOGGER.info("Loading divvy config file: %s", conf_file) + env_settings = yaml.load(f, yaml.SafeLoader) + _LOGGER.debug("Parsed environment settings: %s", + str(env_settings)) + # Any compute.submission_template variables should be made + # absolute, relative to current divvy configuration file. + if OLD_COMPUTE_KEY in env_settings: + warnings.warn("Divvy compute configuration '{}' section changed " + "to '{}'".format(OLD_COMPUTE_KEY, NEW_COMPUTE_KEY), + DeprecationWarning) + env_settings[NEW_COMPUTE_KEY] = env_settings[OLD_COMPUTE_KEY] + return conf_file + + def parse_ftype(input_file): """ Checks determine filetype from extension. From 126a3b998901f0582a368176ade164cea4ea36b9 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 19 Apr 2019 16:22:57 -0400 Subject: [PATCH 14/19] fix retval --- divvy/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divvy/utils.py b/divvy/utils.py index 18fda54..51ef5e2 100644 --- a/divvy/utils.py +++ b/divvy/utils.py @@ -301,7 +301,7 @@ def parse_config_file(conf_file): "to '{}'".format(OLD_COMPUTE_KEY, NEW_COMPUTE_KEY), DeprecationWarning) env_settings[NEW_COMPUTE_KEY] = env_settings[OLD_COMPUTE_KEY] - return conf_file + return env_settings def parse_ftype(input_file): From ed40f2b45e2a4c99755cf1dd7034f15ac3debe09 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 19 Apr 2019 16:24:49 -0400 Subject: [PATCH 15/19] use the constant for new compute key --- divvy/compute.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 01ae598..b91e3e4 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -8,9 +8,8 @@ from yaml import SafeLoader from attmap import PathExAttMap -from .const import \ - COMPUTE_SETTINGS_VARNAME, \ - DEFAULT_COMPUTE_RESOURCES_NAME +from .const import COMPUTE_SETTINGS_VARNAME, DEFAULT_COMPUTE_RESOURCES_NAME, \ + NEW_COMPUTE_KEY from .utils import parse_config_file, write_submit_script, get_first_env_var from . import __version__ @@ -219,7 +218,7 @@ def update_packages(self, config_file): :param str config_file: path to file with new divvy configuration data """ env_settings = parse_config_file(config_file) - loaded_packages = env_settings["compute_packages"] + loaded_packages = env_settings[NEW_COMPUTE_KEY] for key, value in loaded_packages.items(): if type(loaded_packages[key]) is dict: for key2, value2 in loaded_packages[key].items(): @@ -262,7 +261,7 @@ def write_script(self, output_path, extra_vars=None): def _handle_missing_env_attrs(self, config_file, when_missing): """ Default environment settings aren't required; warn, though. """ missing_env_attrs = \ - [attr for attr in ["compute_packages", "config_file"] + [attr for attr in [NEW_COMPUTE_KEY, "config_file"] if not hasattr(self, attr) or getattr(self, attr) is None] if not missing_env_attrs: return From 367f2078ac916e12ff2a18da21ef5acf6296e110 Mon Sep 17 00:00:00 2001 From: Vince Date: Fri, 19 Apr 2019 17:18:41 -0400 Subject: [PATCH 16/19] remove redundancy --- divvy/compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divvy/compute.py b/divvy/compute.py index b91e3e4..47f26e7 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -262,7 +262,7 @@ def _handle_missing_env_attrs(self, config_file, when_missing): """ Default environment settings aren't required; warn, though. """ missing_env_attrs = \ [attr for attr in [NEW_COMPUTE_KEY, "config_file"] - if not hasattr(self, attr) or getattr(self, attr) is None] + if getattr(self, attr, None) is None] if not missing_env_attrs: return message = "'{}' lacks environment attributes: {}". \ From f67e1b0da4df40483e163c3775c168c9b30e7539 Mon Sep 17 00:00:00 2001 From: Vince Date: Fri, 19 Apr 2019 17:20:30 -0400 Subject: [PATCH 17/19] localize variable --- divvy/compute.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 47f26e7..03c8963 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -301,14 +301,15 @@ def main(): subparsers = parser.add_subparsers(dest="command") - # Individual subcommands - msg_by_cmd = { - "list": "List available compute packages", - "write": "Write a submit script" - } def add_subparser(cmd): - return subparsers.add_parser(cmd, description=msg_by_cmd[cmd], help=msg_by_cmd[cmd]) + # Individual subcommands + msg_by_cmd = { + "list": "List available compute packages", + "write": "Write a submit script" + } + return subparsers.add_parser( + cmd, description=msg_by_cmd[cmd], help=msg_by_cmd[cmd]) write_subparser = add_subparser("write") From 90d93324a664c8717c9ed4536eb78670270e59be Mon Sep 17 00:00:00 2001 From: Vince Date: Fri, 19 Apr 2019 17:22:27 -0400 Subject: [PATCH 18/19] reserve logging for behavioral messages rather than requested output --- divvy/compute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/divvy/compute.py b/divvy/compute.py index 03c8963..54ece44 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -331,7 +331,8 @@ def add_subparser(cmd): dcc = ComputingConfiguration(args.config) if args.command == "list": - _LOGGER.info("Available compute packages: {}".format(', '.join(dcc.list_compute_packages()))) + print("Available compute packages:\n{}".format( + "\n".join(dcc.list_compute_packages()))) sys.exit(1) dcc.activate_package(args.package) From 8cda3515c67e2c07c7f47a80a336684ae5bb46c0 Mon Sep 17 00:00:00 2001 From: Vince Date: Fri, 19 Apr 2019 17:29:07 -0400 Subject: [PATCH 19/19] variable naming, context management localization, more uniform control flow --- divvy/compute.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/divvy/compute.py b/divvy/compute.py index 54ece44..19a60b6 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -327,7 +327,7 @@ def add_subparser(cmd): args, remaining_args = parser.parse_known_args() keys = [str.replace(x, "--", "") for x in remaining_args[::2]] - custom_vars = dict(zip(keys, remaining_args[1::2])) + cli_vars = dict(zip(keys, remaining_args[1::2])) dcc = ComputingConfiguration(args.config) if args.command == "list": @@ -337,12 +337,12 @@ def add_subparser(cmd): dcc.activate_package(args.package) if args.settings: + _LOGGER.info("Loading settings file: %s", args.settings) with open(args.settings, 'r') as f: - _LOGGER.info("Loading yaml settings file: %s", args.settings) - yaml_vars = yaml.load(f, SafeLoader) - dcc.write_script(args.outfile, [custom_vars, yaml_vars]) + vars_groups = [cli_vars, yaml.load(f, SafeLoader)] else: - dcc.write_script(args.outfile, custom_vars) + vars_groups = [cli_vars] + dcc.write_script(args.outfile, vars_groups) if __name__ == '__main__':