-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Modularizes cli.py
- Loading branch information
Showing
6 changed files
with
312 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,140 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import logging | ||
import sys | ||
from argparse import ArgumentTypeError | ||
from functools import partial | ||
|
||
from haddock.version import CURRENT_VERSION | ||
from haddock.workflow import WorkflowManager | ||
from haddock.gear.greetings import get_adieu, get_initial_greeting | ||
from haddock.gear.prepare_run import setup_run | ||
from haddock.error import HaddockError, ConfigurationError | ||
|
||
|
||
def main(args=None): | ||
|
||
def positive_int(n): | ||
n = int(n) | ||
if n < 0: | ||
raise argparse.ArgumentTypeError("Minimum value is 0") | ||
return n | ||
|
||
# Command line interface parser | ||
parser = argparse.ArgumentParser() | ||
# Add logging to CLI parser | ||
levels = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL") | ||
parser.add_argument("--log-level", default="INFO", choices=levels) | ||
# Restart option | ||
parser.add_argument("--restart", type=positive_int, default=0, | ||
help="Restart the recipe from this course") | ||
# The recipe to be used | ||
parser.add_argument("recipe", type=argparse.FileType("r"), | ||
help="The input recipe file name") | ||
|
||
parser.add_argument( | ||
"--setup", | ||
help="Only setup the run, do not execute", | ||
action="store_true", | ||
) | ||
from haddock.libs.libutil import file_exists, non_negative_int | ||
|
||
|
||
# Command line interface parser | ||
ap = argparse.ArgumentParser() | ||
|
||
_arg_file_exist = partial( | ||
file_exists, | ||
exception=ArgumentTypeError, | ||
emsg="File {!r} does not exist or is not a file.") | ||
ap.add_argument( | ||
"recipe", | ||
type=_arg_file_exist, | ||
help="The input recipe file path", | ||
) | ||
|
||
_arg_pos_int = partial( | ||
non_negative_int, | ||
exception=ArgumentTypeError, | ||
emsg="Minimum value is 0, {!r} given.", | ||
) | ||
ap.add_argument( | ||
"--restart", | ||
type=_arg_pos_int, | ||
default=0, | ||
help="Restart the recipe from this course", | ||
) | ||
|
||
ap.add_argument( | ||
"--setup", | ||
help="Only setup the run, do not execute", | ||
action="store_true", | ||
dest='setup_only', | ||
) | ||
|
||
_log_levels = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL") | ||
ap.add_argument( | ||
"--log-level", | ||
default="INFO", | ||
choices=_log_levels, | ||
) | ||
|
||
ap.add_argument( | ||
"-v", | ||
"--version", | ||
help="show version", | ||
action="version", | ||
version=f'{ap.prog} - {CURRENT_VERSION}', | ||
) | ||
|
||
|
||
def load_args(ap): | ||
"""Load argument parser args.""" | ||
return ap.parse_args() | ||
|
||
|
||
def cli(ap, main): | ||
"""Command-line interface entry point.""" | ||
cmd = load_args(ap) | ||
main(**vars(cmd)) | ||
|
||
|
||
def maincli(): | ||
"""Main client execution.""" | ||
cli(ap, main) | ||
|
||
|
||
def main( | ||
recipe, | ||
restart=0, | ||
setup_only=False, | ||
log_level="INFO", | ||
): | ||
""" | ||
Execute HADDOCK3 client logic. | ||
Parameters | ||
---------- | ||
recipe : str or pathlib.Path | ||
The path to the recipe (config file). | ||
restart : int | ||
At which step to restart haddock3 run. | ||
setup_only : bool | ||
Whether to setup the run without running it. | ||
log_level : str | ||
The logging level: INFO, DEBUG, ERROR, WARNING, CRITICAL. | ||
""" | ||
# anti-pattern to speed up CLI initiation | ||
from haddock.workflow import WorkflowManager | ||
from haddock.gear.greetings import get_adieu, get_initial_greeting | ||
from haddock.gear.prepare_run import setup_run | ||
from haddock.error import HaddockError, ConfigurationError | ||
|
||
# Version | ||
parser.add_argument("-V", "-v", "--version", help="show version", | ||
action="version", | ||
version="%s %s" % (parser.prog, CURRENT_VERSION)) | ||
# Configuring logging | ||
logging.basicConfig( | ||
level=log_level, | ||
format="[%(asctime)s] %(name)s:L%(lineno)d %(levelname)s - %(message)s", | ||
) | ||
|
||
# Special case only using print instead of logging | ||
options = parser.parse_args() | ||
if not hasattr(options, "version"): | ||
print(get_initial_greeting()) | ||
|
||
# Configuring logging | ||
logging.basicConfig(level=options.log_level, | ||
format=("[%(asctime)s] %(name)s:L%(lineno)d" | ||
" %(levelname)s - %(message)s")) | ||
logging.info(get_initial_greeting()) | ||
|
||
try: | ||
params, other_params = setup_run(options.recipe.name) | ||
params, other_params = setup_run(recipe) | ||
|
||
except ConfigurationError as se: | ||
logging.error(se) | ||
except ConfigurationError as err: | ||
logging.error(err) | ||
sys.exit() | ||
|
||
if not options.setup: | ||
if not setup_only: | ||
try: | ||
workflow = WorkflowManager( | ||
workflow_params=params, | ||
start=options.restart, | ||
start=restart, | ||
**other_params, | ||
) | ||
|
||
# Main loop of execution | ||
workflow.run() | ||
|
||
except HaddockError as he: | ||
logging.error(he) | ||
except HaddockError as err: | ||
logging.error(err) | ||
|
||
# Finish | ||
logging.info(get_adieu()) | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) | ||
sys.exit(maincli()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
from haddock.clis.cli import ap | ||
|
||
|
||
recipe = Path(Path(__file__).resolve().parent, 'recipe.toml') | ||
|
||
|
||
def test_ap_recipe_does_not_exist(): | ||
with pytest.raises(SystemExit) as exit: | ||
ap.parse_args('does_not_exit.toml'.split()) | ||
assert exit.type == SystemExit | ||
assert exit.value.code == 2 | ||
|
||
|
||
def test_ap_recipe_exists(): | ||
cmd = ap.parse_args(str(recipe).split()) | ||
with open(cmd.recipe) as fin: | ||
fin.readlines() | ||
|
||
|
||
def test_ap_setup_true(): | ||
cmd = ap.parse_args(f'{recipe} --setup'.split()) | ||
assert cmd.setup_only == True | ||
|
||
|
||
def test_ap_setup_false(): | ||
cmd = ap.parse_args(str(recipe).split()) | ||
assert cmd.setup_only == False | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'n', | ||
(0, 1, 10, 1230, 50000), | ||
) | ||
def test_ap_restart(n): | ||
cmd = ap.parse_args(f'{recipe} --restart {n}'.split()) | ||
assert cmd.restart == n | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'n', | ||
(-1, -10, -1230, -50000), | ||
) | ||
def test_ap_restart_error(n): | ||
with pytest.raises(SystemExit) as exit: | ||
cmd = ap.parse_args(f'{recipe} --restart {n}'.split()) | ||
assert exit.type == SystemExit | ||
assert exit.value.code == 2 | ||
|
||
|
||
def test_ap_version(): | ||
with pytest.raises(SystemExit) as exit: | ||
ap.parse_args('-v'.split()) | ||
assert exit.type == SystemExit | ||
assert exit.value.code == 0 | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'level', | ||
("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), | ||
) | ||
def test_ap_log_level(level): | ||
cmd = ap.parse_args(f'{recipe} --log-level {level}'.split()) | ||
assert cmd.log_level == level | ||
|
||
|
||
def test_ap_log_level_error(): | ||
with pytest.raises(SystemExit) as exit: | ||
ap.parse_args(f'{recipe} --log-level BAD'.split()) | ||
assert exit.type == SystemExit | ||
assert exit.value.code == 2 |
Oops, something went wrong.