diff --git a/divvy/__main__.py b/divvy/__main__.py new file mode 100644 index 0000000..ef96ac5 --- /dev/null +++ b/divvy/__main__.py @@ -0,0 +1,9 @@ +from .compute import main +import sys + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + print("Program canceled by user!") + sys.exit(1) diff --git a/divvy/_version.py b/divvy/_version.py index 6a9beea..3d26edf 100644 --- a/divvy/_version.py +++ b/divvy/_version.py @@ -1 +1 @@ -__version__ = "0.4.0" +__version__ = "0.4.1" diff --git a/divvy/compute.py b/divvy/compute.py index 308b61c..2967b72 100644 --- a/divvy/compute.py +++ b/divvy/compute.py @@ -12,6 +12,7 @@ from distutils.dir_util import copy_tree # from attmap import PathExAttMap +from ubiquerg import is_writable import yacman from collections import OrderedDict @@ -20,13 +21,14 @@ from .utils import parse_config_file, write_submit_script, get_first_env_var from . import __version__ -DEFAULT_CONFIG_FILEPATH = os.path.join( +DEFAULT_CONFIG_FILEPATH = os.path.join( os.path.dirname(__file__), "default_config", "divvy_config.yaml") _LOGGER = logging.getLogger(__name__) + class ComputingConfiguration(yacman.YacAttMap): """ Represents computing configuration objects. @@ -65,8 +67,8 @@ def __init__(self, entries=None, filepath=None, super(ComputingConfiguration, self).__init__(entries, filepath) if not hasattr(self, "compute_packages"): - raise Exception("Your config file is not in divvy config format (it" - " lacks a compute_packages section)") + raise Exception("Your divvy config file is not in divvy config format (it" + " lacks a compute_packages section): '{}'".format(filepath)) # We require that compute_packages be present, even if empty self.compute_packages = {} @@ -78,7 +80,7 @@ def __init__(self, entries=None, filepath=None, def write(self, filename=None): super(ComputingConfiguration, self).write(filename) - filename = filename or getattr(self, FILEPATH_KEY) + filename = filename or getattr(self, yacman.FILEPATH_KEY) filedir = os.path.dirname(filename) # For this object, we *also* have to write the template files for pkg_name, pkg in self.compute_packages.items(): @@ -313,33 +315,6 @@ def divvy_init(config_path, template_config_path): _LOGGER.warning("Can't initialize, file exists: {} ".format(config_path)) -def _is_writeable(folder, check_exist=False, create=False): - """ - Make sure a folder is writable. - - Given a folder, check that it exists and is writable. Errors if requested on - a non-existent folder. Otherwise, make sure the first existing parent folder - is writable such that this folder could be created. - - :param str folder: Folder to check for writeability. - :param bool check_exist: Throw an error if it doesn't exist? - :param bool create: Create the folder if it doesn't exist? - """ - folder = folder or "." - - if os.path.exists(folder): - return os.access(folder, os.W_OK) and os.access(folder, os.X_OK) - elif create_folder: - os.mkdir(folder) - elif check_exist: - raise OSError("Folder not found: {}".format(folder)) - else: - _LOGGER.debug("Folder not found: {}".format(folder)) - # The folder didn't exist. Recurse up the folder hierarchy to make sure - # all paths are writable - return _is_writeable(os.path.dirname(folder), strict_exists) - - def build_argparser(): """ Builds argument parser. @@ -351,6 +326,7 @@ def build_argparser(): additional_description = "\nhttps://divvy.databio.org" parser = _VersionInHelpParser( + prog="divvy", description=banner, epilog=additional_description) @@ -416,11 +392,10 @@ def main(): keys = [str.replace(x, "--", "") for x in remaining_args[::2]] cli_vars = dict(zip(keys, remaining_args[1::2])) - if args.command == "init": divcfg = args.config _LOGGER.debug("Initializing divvy configuration") - _is_writable(os.path.dirname(divcfg), check_exist=False) + is_writable(os.path.dirname(divcfg), check_exist=False) divvy_init(divcfg, DEFAULT_CONFIG_FILEPATH) sys.exit(0) @@ -449,11 +424,3 @@ def main(): else: vars_groups = [cli_vars] dcc.write_script(args.outfile, vars_groups) - - -if __name__ == '__main__': - try: - sys.exit(main()) - except KeyboardInterrupt: - _LOGGER.error("Program canceled by user!") - sys.exit(1) diff --git a/docs/changelog.md b/docs/changelog.md index 83f7e31..83476d6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,15 @@ # Changelog +## [0.4.1] -- 2020-03-20 +### Fixed +- `NameError` in `divvy init`; [#44](https://github.com/pepkit/divvy/issues/44) + +### Added +- possibility to execute library module as a script: `python -m divvy ...` + +### Changed +- improved error message when config format is incompatible + ## [0.4.0] -- 2019-07-30 ### Added - Default templates for singularity and docker compute packages diff --git a/requirements/requirements-all.txt b/requirements/requirements-all.txt index 73b0710..f72e9fc 100644 --- a/requirements/requirements-all.txt +++ b/requirements/requirements-all.txt @@ -3,3 +3,4 @@ yacman>=0.5.0 pandas>=0.20.2 pyyaml>=5.1 logmuse>=0.2.0 +ubiquerg>=0.5.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 1008add..bb12c5a 100644 --- a/setup.py +++ b/setup.py @@ -62,10 +62,10 @@ url="https://github.com/pepkit/{}/".format(PACKAGE), author=u"Nathan Sheffield, Vince Reuter, Michal Stolarczyk", author_email=u"nathan@code.databio.org, vreuter@virginia.edu, mjs5kd@virginia.edu", - license="BSD2", + license="BSD-2-Clause", entry_points={ "console_scripts": [ - 'divvy = divvy.compute:main' + 'divvy = divvy.__main__:main' ], }, package_data={"divvy": [os.path.join("divvy", "*")]}, diff --git a/tests/conftest.py b/tests/conftest.py index 67ffadb..64e3774 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,8 @@ def dcc(request): return divvy.ComputingConfiguration(request.param) - +@pytest.fixture +def mock_env_missing(monkeypatch): + [monkeypatch.delenv(env_var, raising=False) for env_var in divvy.const.COMPUTE_SETTINGS_VARNAME] diff --git a/tests/divvy_tests/test_divvy.py b/tests/divvy_tests/test_divvy.py index 954c20c..9ad6038 100644 --- a/tests/divvy_tests/test_divvy.py +++ b/tests/divvy_tests/test_divvy.py @@ -3,7 +3,8 @@ import pytest from yacman import YacAttMap, load_yaml from divvy import DEFAULT_COMPUTE_RESOURCES_NAME -from tests.conftest import DCC_ATTRIBUTES, FILES +from tests.conftest import DCC_ATTRIBUTES, FILES, mock_env_missing + class DefaultDCCTests: """ Tests the default divvy.ComputingConfiguration object creation""" @@ -17,6 +18,9 @@ def test_attrs_produced(self, att, empty_dcc): """ Test if compute property is produced and is not empty """ empty_dcc[att] + def test_no_env_var(self, mock_env_missing, empty_dcc): + empty_dcc + class DCCTests: """ Tests the divvy.ComputingConfiguration object creation """