From 98e21c199e25f4ca99bc5376de5bcc21da265129 Mon Sep 17 00:00:00 2001 From: sellth Date: Thu, 25 Jan 2024 23:14:05 +0100 Subject: [PATCH] feat: Add new command sodar landing-zone-validate (#219) Co-authored-by: your-highness --- cubi_tk/sodar/__init__.py | 6 +- cubi_tk/sodar/lz_validate.py | 105 +++++++++++++++++++++++++++++++++++ tests/test_sodar_lz.py | 36 ++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 cubi_tk/sodar/lz_validate.py create mode 100644 tests/test_sodar_lz.py diff --git a/cubi_tk/sodar/__init__.py b/cubi_tk/sodar/__init__.py index 88575ad0..42550452 100644 --- a/cubi_tk/sodar/__init__.py +++ b/cubi_tk/sodar/__init__.py @@ -18,7 +18,7 @@ ``landing-zone-create`` Create a landing zone. -``landing-zone-validate`` (planned) +``landing-zone-validate`` Validate a landing zone. ``landing-zone-move`` @@ -61,6 +61,7 @@ from .lz_create import setup_argparse as setup_argparse_lz_create from .lz_list import setup_argparse as setup_argparse_lz_list from .lz_move import setup_argparse as setup_argparse_lz_move +from .lz_validate import setup_argparse as setup_argparse_lz_validate from .pull_raw_data import setup_argparse as setup_argparse_pull_raw_data from .upload_sheet import setup_argparse as setup_argparse_upload_sheet @@ -86,6 +87,9 @@ def setup_argparse(parser: argparse.ArgumentParser) -> None: setup_argparse_lz_move( subparsers.add_parser("landing-zone-move", help="Submit landing zone for moving") ) + setup_argparse_lz_validate( + subparsers.add_parser("landing-zone-validate", help="Submit landing zone for validation") + ) setup_argparse_ingest_fastq( subparsers.add_parser( "ingest-fastq", help="Upload external files to SODAR (defaults for fastq)" diff --git a/cubi_tk/sodar/lz_validate.py b/cubi_tk/sodar/lz_validate.py new file mode 100644 index 00000000..48a0ca39 --- /dev/null +++ b/cubi_tk/sodar/lz_validate.py @@ -0,0 +1,105 @@ +"""``cubi-tk sodar landing-zone-validate`` command line program +""" + +import argparse +import json +import os +import typing + +import cattr +import logzero +from logzero import logger +from sodar_cli import api + +from ..common import load_toml_config + +# no-frills logger +formatter = logzero.LogFormatter(fmt="%(message)s") +output_logger = logzero.setup_logger(formatter=formatter) + +# for testing +output_logger.propagate = True + + +class ValidateLandingZoneCommand: + """Implementation of the ``landing-zone-validate`` command.""" + + def __init__(self, args): + #: Command line arguments. + self.args = args + + @classmethod + def setup_argparse(cls, parser: argparse.ArgumentParser) -> None: + """Setup argument parser.""" + parser.add_argument( + "--hidden-cmd", dest="sodar_cmd", default=cls.run, help=argparse.SUPPRESS + ) + + group_sodar = parser.add_argument_group("SODAR-related") + group_sodar.add_argument( + "--sodar-url", + default=os.environ.get("SODAR_URL", "https://sodar.bihealth.org/"), + help="URL to SODAR, defaults to SODAR_URL environment variable or fallback to https://sodar.bihealth.org/", + ) + group_sodar.add_argument( + "--sodar-api-token", + default=os.environ.get("SODAR_API_TOKEN", None), + help="Authentication token when talking to SODAR. Defaults to SODAR_API_TOKEN environment variable.", + ) + + parser.add_argument( + "--format", + dest="format_string", + default=None, + help="Format string for printing, e.g. %%(uuid)s", + ) + + parser.add_argument("landing_zone_uuid", help="UUID of landing zone to validate.") + + @classmethod + def run( + cls, args, _parser: argparse.ArgumentParser, _subparser: argparse.ArgumentParser + ) -> typing.Optional[int]: + """Entry point into the command.""" + return cls(args).execute() # pragma: nocover + + def check_args(self, args): + """Called for checking arguments, override to change behaviour.""" + res = 0 + + toml_config = load_toml_config(args) + args.sodar_url = args.sodar_url or toml_config.get("global", {}).get("sodar_server_url") + args.sodar_api_token = args.sodar_api_token or toml_config.get("global", {}).get( + "sodar_api_token" + ) + + return res + + def execute(self) -> typing.Optional[int]: + """Execute the landing zone validation.""" + res = self.check_args(self.args) + if res: # pragma: nocover + return res + + logger.info("Starting cubi-tk sodar landing-zone-validate.") + logger.debug("args: %s", self.args) + + landing_zone = api.landingzone.submit_validate( + sodar_url=self.args.sodar_url, + sodar_api_token=self.args.sodar_api_token, + landingzone_uuid=self.args.landing_zone_uuid, + ) + values = cattr.unstructure(landing_zone) + if self.args.format_string: + logger.info("Formatted server response:") + output_logger.info(self.args.format_string.replace(r"\t", "\t") % values) + else: + logger.info("Server response:") + output_logger.info(json.dumps(values)) + + return 0 + + +def setup_argparse(parser: argparse.ArgumentParser) -> None: # pragma: nocover + """Setup argument parser for ``cubi-tk sodar landing-zone-validate``.""" + return ValidateLandingZoneCommand.setup_argparse(parser) diff --git a/tests/test_sodar_lz.py b/tests/test_sodar_lz.py new file mode 100644 index 00000000..6efbbbd8 --- /dev/null +++ b/tests/test_sodar_lz.py @@ -0,0 +1,36 @@ +"""``Tests for sodar_lz functions``""" +from argparse import ArgumentParser +from unittest.mock import patch + +from cubi_tk.sodar.lz_validate import ValidateLandingZoneCommand + + +@patch("cubi_tk.sodar.lz_validate.api.landingzone.submit_validate") +@patch("cubi_tk.sodar.lz_validate.load_toml_config") +def test_validate(mocktoml, mockapi, caplog): + mockapi.return_value = {"a": 1, "b": 2} + argv = [ + "--sodar-url", + "sodar_url", + "--sodar-api-token", + "token", + "u-u-i-d", + ] + + parser = ArgumentParser() + ValidateLandingZoneCommand.setup_argparse(parser) + + # No format string + args = parser.parse_args(argv) + ValidateLandingZoneCommand(args).execute() + mockapi.assert_called_with( + sodar_url="sodar_url", sodar_api_token="token", landingzone_uuid="u-u-i-d" + ) + assert '{"a": 1, "b": 2}' in caplog.messages + + # With format string + argv.insert(-1, "--format") + argv.insert(-1, "%(a)s") + args = parser.parse_args(argv) + ValidateLandingZoneCommand(args).execute() + assert "1" in caplog.messages