diff --git a/.travis.yml b/.travis.yml index 0df3206..1714424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,14 +43,15 @@ before_install: install: - source tests/travis_install.sh - pip install -r requirements.txt + - pip install -r requirements-dev.txt - pip install twine - - pip install . + - pip install -e . before_script: - git config --global user.email "support@travis-ci.org" - git config --global user.name "Travis" - singularity pull shub://FCP-INDI/C-PAC || singularity pull docker://fcpindi/c-pac:latest script: - - coverage run -m pytest + - coverage run --append -m pytest - coverage report -m after_success: - if [[ "$COVERAGE" == "true" ]]; then coveralls || echo "failed"; fi diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a2aa180..dac710e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,9 +2,15 @@ Changelog ========= `Version 0.2.5 `_ -======================================================================================= +======================================================================================== +* 📚 Update the main usage string to better articulate functionality * 📢🐳 Provide a clearer error message if package cannot connect to Docker. * 🐳 Fix a bug introduced in `v0.2.4 ` where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` +* 🚑 Fix some installation issues: + * All required packages are now installed with ``pip install cpac-py`` + * Version is now set correctly +* 🐳 Fix a bug introduced in `v0.2.4 `_ where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` +* 🔬 Set `coverage reports `_ to report local paths `Version 0.2.4 `_ ======================================================================================= diff --git a/README.rst b/README.rst index 1b46167..31d46a5 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,7 @@ -=============================================================== -C-PAC Python Package |github-version| |build-status| |coverage| -=============================================================== +======================================================================== +C-PAC Python Package |build-status| |github-version| |upload| |coverage| +======================================================================== + A Python package that wraps `C-PAC `_, enabling users to install cpac with `pip `_ and run from the command line. @@ -28,42 +29,72 @@ Usage .. code-block:: shell cpac --help - usage: cpac [-h] [--platform {docker,singularity}] [--image IMAGE] [--tag TAG] - [--version] [-v] [-vv] [--working_dir PATH] [--temp_dir PATH] - [--output_dir PATH] [-o OPT [OPT ...]] - [-B CUSTOM_BINDING [CUSTOM_BINDING ...]] + usage: cpac [-h] [--version] [-o [OPT [OPT ...]]] + [-B [CUSTOM_BINDING [CUSTOM_BINDING ...]]] + [--platform {docker,singularity}] [--image IMAGE] [--tag TAG] + [--working_dir PATH] [--temp_dir PATH] [-v] [-vv] {run,group,utils,crash} ... - cpac: a Python package that simplifies using C-PAC - containerized images. If no platform nor image is specified, cpac will try - Docker first, then try Singularity if Docker fails. + cpac: a Python package that simplifies using C-PAC containerized images. + + This commandline interface package is designed to minimize repetition. + As such, nearly all arguments are optional. + + When launching a container, this package will try to bind any paths mentioned in + • the command + • the data configuration + + An example minimal run command: + cpac run /path/to/data /path/for/outputs + + An example run command with optional arguments: + cpac -B /path/to/data/configs:/configs \ + --image fcpindi/c-pac --tag latest \ + run /path/to/data /path/for/outputs \ + --data_config_file /configs/data_config.yml \ + --save_working_dir + + Each command can take "--help" to provide additonal usage information, e.g., + + cpac run --help positional arguments: {run,group,utils,crash} optional arguments: -h, --help show this help message and exit - --platform {docker,singularity} - --image IMAGE path to Singularity image file OR name of Docker image - (eg, "fcpindi/c-pac"). Will attempt to pull from - Singularity Hub or Docker Hub if not provided. - --tag TAG tag of the Docker image to use (eg, "latest" or - "nightly"). --version show program's version number and exit - -v, --verbose set loglevel to INFO - -vv, --very-verbose set loglevel to DEBUG + -o [OPT [OPT ...]], --container_option [OPT [OPT ...]] + parameters and flags to pass through to Docker or Singularity + + This flag can take multiple arguments so cannot be + the final argument before the command argument (i.e., + run or any other command that does not start with - or --) + -B [CUSTOM_BINDING [CUSTOM_BINDING ...]], --custom_binding [CUSTOM_BINDING [CUSTOM_BINDING ...]] + directories to bind with a different path in + the container than the real path of the directory. + One or more pairs in the format: + real_path:container_path + (eg, /home/C-PAC/run5/outputs:/outputs). + Use absolute paths for both paths. + + This flag can take multiple arguments so cannot be + the final argument before the command argument (i.e., + run or any other command that does not start with - or --) + --platform {docker,singularity} + If neither platform nor image is specified, + cpac will try Docker first, then try + Singularity if Docker fails. + --image IMAGE path to Singularity image file OR name of Docker image (eg, "fcpindi/c-pac"). + Will attempt to pull from Singularity Hub or Docker Hub if not provided. + If image is specified but platform is not, platform is + assumed to be Singularity if image is a path or + Docker if image is an image name. + --tag TAG tag of the Docker image to use (eg, "latest" or "nightly"). --working_dir PATH working directory --temp_dir PATH directory for temporary files - --output_dir PATH directory where output files should be stored - -o OPT [OPT ...], --container_option OPT [OPT ...] - parameters and flags to pass through to Docker or - Singularity - -B CUSTOM_BINDING [CUSTOM_BINDING ...], --custom_binding CUSTOM_BINDING [CUSTOM_BINDING ...] - directory to bind to container with a different path - than the real path in the format - real_path:container_path (eg, - /home/C-PAC/run5/outputs:/outputs). Use absolute paths - for both paths + -v, --verbose set loglevel to INFO + -vv, --very-verbose set loglevel to DEBUG .. END USAGE @@ -76,3 +107,7 @@ Usage .. |coverage| image:: https://coveralls.io/repos/github/shnizzedy/cpac-python-package/badge.svg?branch=trunk :target: https://coveralls.io/github/shnizzedy/cpac-python-package?branch=trunk :alt: coverage badge +.. |upload| image:: https://github.com/shnizzedy/cpac-python-package/workflows/Upload%20Python%20Package/badge.svg + :target: https://pypi.org/project/cpac-py/ + :alt: upload Python package + diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..6196be4 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +coveralls +pytest +pytest-remotedata >= 0.3.2 +pytest-runner +sphinx \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1a86f3a..b4807e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,10 @@ -coveralls docker >= 4.2.1 docker-pycreds nipype pandas >= 0.23.4 -pytest -pytest-remotedata>=0.3.2 -pytest-runner pyyaml setuptools -sphinx spython >= 0.0.81 tabulate >= 0.8.6 tornado -websocket-client +websocket-client \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 10947f1..961895d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ # http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files [metadata] -name = cpac_py +name = cpac description = C-PAC Python Package author = C-PAC developers author-email = cpac@cnl.childmind.org @@ -41,6 +41,7 @@ install_requires = nipype pandas >= 0.23.4 spython >= 0.0.81 + pyyaml tabulate >= 0.8.6 tornado websocket-client diff --git a/src/cpac/__main__.py b/src/cpac/__main__.py index fbfac84..e9ae4c0 100644 --- a/src/cpac/__main__.py +++ b/src/cpac/__main__.py @@ -33,30 +33,86 @@ def parse_args(args): parser = argparse.ArgumentParser( description='cpac: a Python package that simplifies using C-PAC ' - ' containerized images. If no ' - 'platform nor image is specified, cpac will try Docker ' - 'first, then try Singularity if Docker fails.', - conflict_handler='resolve' + ' containerized images. \n\n' + 'This commandline interface package is designed to ' 'minimize repetition.\nAs such, nearly all arguments are ' + 'optional.\n\nWhen launching a container, this package ' + 'will try to bind any paths mentioned in \n • the command' + '\n • the data configuration\n\nAn example minimal run ' + 'command:\n\tcpac run /path/to/data /path/for/outputs' + '\n\nAn example run command with optional arguments:\n\t' + 'cpac -B /path/to/data/configs:/configs \\\n\t\t' + '--image fcpindi/c-pac --tag latest \\\n\t\t' + 'run /path/to/data /path/for/outputs \\\n\t\t' + '--data_config_file /configs/data_config.yml \\\n\t\t' + '--save_working_dir\n\n' + 'Each command can take "--help" to provide additonal ' + 'usage information, e.g.,\n\n\tcpac run --help', + conflict_handler='resolve', + formatter_class=argparse.RawTextHelpFormatter + ) + + parser.add_argument( + '--version', + action='version', + version='cpac {ver}'.format(ver=__version__) + ) + + parser.add_argument( + '-o', '--container_option', + dest='container_option', + nargs='*', + help='parameters and flags to pass through to Docker or Singularity\n' + '\nThis flag can take multiple arguments so cannot ' + 'be\nthe final argument before the command argument (i.e.,\nrun ' + 'or any other command that does not start with - or --)\n', + metavar='OPT' ) - parser.add_argument('--platform', choices=['docker', 'singularity']) + parser.add_argument( + '-B', '--custom_binding', + dest='custom_binding', + nargs='*', + help='directories to bind with a different path in\nthe container ' + 'than the real path of the directory.\nOne or more pairs in the ' 'format:\n\treal_path:container_path\n(eg, ' + '/home/C-PAC/run5/outputs:/outputs).\nUse absolute paths for ' + 'both paths.\n\nThis flag can take multiple arguments so cannot ' + 'be\nthe final argument before the command argument (i.e.,\nrun ' + 'or any other command that does not start with - or --)\n' + ) + + parser.add_argument( + '--platform', + choices=['docker', 'singularity'], + help='If neither platform nor image is specified,\ncpac will try ' + 'Docker first, then try\nSingularity if Docker fails.' + ) parser.add_argument( '--image', help='path to Singularity image file OR name of Docker image (eg, ' - '"fcpindi/c-pac"). Will attempt to pull from Singularity Hub or ' - 'Docker Hub if not provided.' + '"fcpindi/c-pac").\nWill attempt to pull from Singularity Hub or ' + 'Docker Hub if not provided.\nIf image is specified but platform ' + 'is not, platform is\nassumed to be Singularity if image is a ' + 'path or \nDocker if image is an image name.' ) parser.add_argument( '--tag', - help='tag of the Docker image to use (eg, "latest" or "nightly"). ' + help='tag of the Docker image to use (eg, "latest" or "nightly").' ) parser.add_argument( - '--version', - action='version', - version='cpac {ver}'.format(ver=__version__) + '--working_dir', + default=cwd, + help='working directory', + metavar='PATH' + ) + + parser.add_argument( + '--temp_dir', + default='/tmp', + help='directory for temporary files', + metavar='PATH' ) parser.add_argument( @@ -77,45 +133,6 @@ def parse_args(args): const=logging.DEBUG ) - parser.add_argument( - '--working_dir', - default=cwd, - help="working directory", - metavar="PATH" - ) - - parser.add_argument( - '--temp_dir', - default='/tmp', - help="directory for temporary files", - metavar="PATH" - ) - - parser.add_argument( - '--output_dir', - default=os.path.join(cwd, 'outputs'), - help="directory where output files should be stored", - metavar="PATH" - ) - - parser.add_argument( - '-o', '--container_option', - dest='container_option', - nargs='+', - help="parameters and flags to pass through to Docker or Singularity", - metavar="OPT" - ) - - parser.add_argument( - '-B', '--custom_binding', - dest="custom_binding", - nargs="+", - help="directory to bind to container with a different path than the " - "real path in the format real_path:container_path (eg, " - "/home/C-PAC/run5/outputs:/outputs). Use absolute paths for both " - "paths" - ) - subparsers = parser.add_subparsers(dest='command') run_parser = subparsers.add_parser( diff --git a/tests/test_cpac_installation.py b/tests/test_cpac_installation.py new file mode 100644 index 0000000..454f23e --- /dev/null +++ b/tests/test_cpac_installation.py @@ -0,0 +1,95 @@ +import setuptools + +from pip._internal.utils.misc import get_installed_distributions +from setuptools.config import read_configuration + + +def test_requirements(): + requirements = { + 'setup.cfg': requirements_list(read_configuration( + 'setup.cfg' + )['options']['install_requires'] + ) + } + with open('requirements.txt', 'r') as req: + requirements['requirements.txt']=requirements_list( + req.readlines() + ) + for req in requirements['requirements.txt']: + if req.package.lower() != 'setuptools': + assert package_in_list( + req, requirements['setup.cfg'] + ), ( + f'package {req} is in requirements.txt ' + 'but not in setup.cfg' + ) + assert package_in_list( + req, requirements_list(get_installed_distributions() + )), ( + f'package {req} is in requirements.txt ' + 'but not installed' + ) + + +def test_version(): + from cpac import __version__ + assert __version__ != 'undefined', f'version is {__version__}' + + +class Requirement(): + def __init__(self, requirement): + package = str(requirement).rstrip().split(' ') + self.package = package[0] + self.version = { + "==": package[1] + } if len(package) == 2 else { + package[i]: package[i+1] for i in range(len(package)) if i%2 + } + + def __repr__(self): + return ' '.join([ + f'{self.package}', + ', '.join([ + f'{key} {self.version[key]}' for key in self.version + ]) + ]).strip() + + +def package_in_list(package, version_list): + """ + Helper function to check if a case-insensitive named package + is included in a list of Requirements + + Parameters + ---------- + package: str + + version_list: list + + Returns + ------- + bool + """ + return( + package.package.lower() in [ + p.package.lower() for p in version_list + ] + ) + + +def requirements_list(requirements): + """ + Helper function to split coerce a list of requirements into + a list of Requirements + + Parameters + ---------- + requirements: list + list of requirment strings + + Returns + ------- + list + list of Requirements + """ + return([Requirement(r) for r in requirements]) \ No newline at end of file diff --git a/tests/test_cpac_utils.py b/tests/test_cpac_utils.py index 1513ddf..3cd1e43 100644 --- a/tests/test_cpac_utils.py +++ b/tests/test_cpac_utils.py @@ -27,7 +27,7 @@ def test_utils_help(args, capsys, platform): def test_utils_new_settings_template(args, tmp_path): wd = tmp_path argv = ( - f'cpac {args} --working_dir {wd} --temp_dir {wd} --output_dir {wd} ' + f'cpac {args} --working_dir {wd} --temp_dir {wd} ' f'utils data_config new_settings_template' ).split(' ') with mock.patch.object(sys, 'argv', argv):