Skip to content

Commit

Permalink
Merge pull request #25 from tcezard/EVA3438_reduce_log_level
Browse files Browse the repository at this point in the history
EVA-3438 - Lower log level
  • Loading branch information
tcezard authored Feb 26, 2024
2 parents 37b2a60 + 580f5bb commit 4b20164
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 56 deletions.
10 changes: 9 additions & 1 deletion bin/eva-sub-cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python
import logging
import os
import sys
from argparse import ArgumentParser
Expand All @@ -7,6 +8,7 @@

from eva_sub_cli import main
from eva_sub_cli.main import VALIDATE, SUBMIT, DOCKER, NATIVE
from eva_sub_cli.utils import is_submission_dir_writable


def validate_command_line_arguments(args, argparser):
Expand All @@ -27,6 +29,10 @@ def validate_command_line_arguments(args, argparser):
argparser.print_usage()
sys.exit(1)

if not is_submission_dir_writable(args.submission_dir):
print(f"'{args.submission_dir}' does not have write permissions or is not a directory.")
sys.exit(1)


if __name__ == "__main__":
argparser = ArgumentParser(description='EVA Submission CLI - validate and submit data to EVA')
Expand Down Expand Up @@ -66,8 +72,10 @@ def validate_command_line_arguments(args, argparser):

args = argparser.parse_args()

validate_command_line_arguments(args, argparser)

logging_config.add_stdout_handler()
logging_config.add_file_handler(os.path.join(args.submission_dir, 'eva_submission.log'), logging.DEBUG)

validate_command_line_arguments(args, argparser)
# Pass on all the arguments
main.orchestrate_process(**args.__dict__)
1 change: 1 addition & 0 deletions eva_sub_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
SUBMISSION_WS_VAR = 'SUBMISSION_WS_URL'
ENA_WEBIN_ACCOUNT_VAR = 'ENA_WEBIN_ACCOUNT'
ENA_WEBIN_PASSWORD_VAR = 'ENA_WEBIN_PASSWORD'

51 changes: 26 additions & 25 deletions eva_sub_cli/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ def update_config_with_submission_id_and_upload_url(self, submission_id, upload_
self.sub_config.set(SUB_CLI_CONFIG_KEY_SUBMISSION_ID, value=submission_id)
self.sub_config.set(SUB_CLI_CONFIG_KEY_SUBMISSION_UPLOAD_URL, value=upload_url)


def _upload_submission(self):
if READY_FOR_SUBMISSION_TO_EVA not in self.sub_config or not self.sub_config[READY_FOR_SUBMISSION_TO_EVA]:
raise Exception(f'There are still validation errors that needs to be addressed. '
Expand All @@ -76,42 +75,44 @@ def _upload_submission(self):
@retry(tries=5, delay=10, backoff=5)
def _upload_file(self, submission_upload_url, input_file):
base_name = os.path.basename(input_file)
self.info(f'Transfer {base_name} to EVA FTP')
self.debug(f'Transfer {base_name} to EVA FTP')
r = requests.put(urljoin(submission_upload_url, base_name), data=open(input_file, 'rb'))
r.raise_for_status()
self.info(f'Upload of {base_name} completed')
self.debug(f'Upload of {base_name} completed')

def verify_submission_dir(self, submission_dir):
if not os.path.exists(submission_dir):
os.makedirs(submission_dir)
if not os.access(submission_dir, os.W_OK):
raise Exception(f"The directory '{submission_dir}' does not have write permissions.")
def _initiate_submission(self):
response = requests.post(self.submission_initiate_url,
headers={'Accept': 'application/hal+json',
'Authorization': 'Bearer ' + self.auth.token})
response.raise_for_status()
response_json = response.json()
self.debug(f'Submission ID {response_json["submissionId"]} received!!')
# update config with submission id and upload url
self.update_config_with_submission_id_and_upload_url(response_json["submissionId"], response_json["uploadUrl"])

def _complete_submission(self):
response = requests.put(
self.submission_uploaded_url.format(submissionId=self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID)),
headers={'Accept': 'application/hal+json', 'Authorization': 'Bearer ' + self.auth.token}
)
response.raise_for_status()
self.debug("Submission ID {} Complete".format(self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID)))
# update config with completion of the submission
self.sub_config.set(SUB_CLI_CONFIG_KEY_COMPLETE, value=True)

def submit(self, resume=False):
if READY_FOR_SUBMISSION_TO_EVA not in self.sub_config or not self.sub_config[READY_FOR_SUBMISSION_TO_EVA]:
raise Exception(f'There are still validation errors that need to be addressed. '
f'Please review, address and re-validate before submitting.')
if not (resume or self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_UPLOAD_URL)):
self.verify_submission_dir(self.submission_dir)
response = requests.post(self.submission_initiate_url,
headers={'Accept': 'application/hal+json',
'Authorization': 'Bearer ' + self.auth.token})
response.raise_for_status()
response_json = response.json()
self.info(f'Submission ID {response_json["submissionId"]} received!!')
# update config with submission id and upload url
self.update_config_with_submission_id_and_upload_url(response_json["submissionId"], response_json["uploadUrl"])
self.info(f'Initiate submission')
self._initiate_submission()

# upload submission
self.info(f'Upload data')
self._upload_submission()

# Complete the submission
response = requests.put(
self.submission_uploaded_url.format(submissionId=self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID)),
headers={'Accept': 'application/hal+json', 'Authorization': 'Bearer ' + self.auth.token}
)
response.raise_for_status()
self.info("Submission ID {} Complete".format(self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID)))
# update config with completion of the submission
self.sub_config.set(SUB_CLI_CONFIG_KEY_COMPLETE, value=True)
self.info(f'Complete submission')
self._complete_submission()

11 changes: 11 additions & 0 deletions eva_sub_cli/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os


def is_submission_dir_writable(submission_dir):
if not os.path.exists(submission_dir):
os.makedirs(submission_dir)
if not os.path.isdir(submission_dir):
return False
if not os.access(submission_dir, os.W_OK):
return False
return True
27 changes: 13 additions & 14 deletions eva_sub_cli/validators/docker_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import subprocess
import time

from ebi_eva_common_pyutils.command_utils import run_command_with_output
from ebi_eva_common_pyutils.logger import logging_config

from eva_sub_cli.validators.validator import Validator, VALIDATION_OUTPUT_DIR
Expand Down Expand Up @@ -60,7 +59,7 @@ def run_docker_validator(self):

try:
# remove all existing files from container
run_command_with_output(
self._run_quiet_command(
"Remove existing files from validation directory in container",
f"{self.docker_path} exec {self.container_name} rm -rf work {container_validation_dir}"
)
Expand All @@ -71,9 +70,9 @@ def run_docker_validator(self):
docker_cmd = self.get_docker_validation_cmd()
# start validation
# FIXME: If nextflow fails in the docker exec still exit with error code 0
run_command_with_output("Run Validation using Nextflow", docker_cmd)
self._run_quiet_command("Run Validation using Nextflow", docker_cmd)
# copy validation result to user host
run_command_with_output(
self._run_quiet_command(
"Copy validation output from container to host",
f"{self.docker_path} cp {self.container_name}:{container_validation_dir}/{container_validation_output_dir} {self.output_dir}"
)
Expand All @@ -82,7 +81,7 @@ def run_docker_validator(self):

def verify_docker_is_installed(self):
try:
run_command_with_output(
self._run_quiet_command(
"check docker is installed and available on the path",
f"{self.docker_path} --version"
)
Expand All @@ -91,7 +90,7 @@ def verify_docker_is_installed(self):
raise RuntimeError(f"Please make sure docker ({self.docker_path}) is installed and available on the path")

def verify_container_is_running(self):
container_run_cmd_output = run_command_with_output("check if container is running", f"{self.docker_path} ps", return_process_output=True)
container_run_cmd_output = self._run_quiet_command("check if container is running", f"{self.docker_path} ps", return_process_output=True)
if container_run_cmd_output is not None and self.container_name in container_run_cmd_output:
logger.info(f"Container ({self.container_name}) is running")
return True
Expand All @@ -100,7 +99,7 @@ def verify_container_is_running(self):
return False

def verify_container_is_stopped(self):
container_stop_cmd_output = run_command_with_output(
container_stop_cmd_output = self._run_quiet_command(
"check if container is stopped", f"{self.docker_path} ps -a", return_process_output=True
)
if container_stop_cmd_output is not None and self.container_name in container_stop_cmd_output:
Expand All @@ -113,15 +112,15 @@ def verify_container_is_stopped(self):
def try_restarting_container(self):
logger.info(f"Trying to restart container {self.container_name}")
try:
run_command_with_output("Try restarting container", f"{self.docker_path} start {self.container_name}")
self._run_quiet_command("Try restarting container", f"{self.docker_path} start {self.container_name}")
if not self.verify_container_is_running():
raise RuntimeError(f"Container ({self.container_name}) could not be restarted")
except subprocess.CalledProcessError as ex:
logger.error(ex)
raise RuntimeError(f"Container ({self.container_name}) could not be restarted")

def verify_image_available_locally(self):
container_images_cmd_ouptut = run_command_with_output(
container_images_cmd_ouptut = self._run_quiet_command(
"Check if validator image is present",
f"{self.docker_path} images",
return_process_output=True
Expand All @@ -136,7 +135,7 @@ def verify_image_available_locally(self):
def run_container(self):
logger.info(f"Trying to run container {self.container_name}")
try:
run_command_with_output(
self._run_quiet_command(
"Try running container",
f"{self.docker_path} run -it --rm -d --name {self.container_name} {container_image}:{container_tag}"
)
Expand All @@ -150,15 +149,15 @@ def run_container(self):

def stop_running_container(self):
if not self.verify_container_is_stopped():
run_command_with_output(
self._run_quiet_command(
"Stop the running container",
f"{self.docker_path} stop {self.container_name}"
)

def download_container_image(self):
logger.info(f"Pulling container ({container_image}) image")
try:
run_command_with_output("pull container image", f"{self.docker_path} pull {container_image}:{container_tag}")
self._run_quiet_command("pull container image", f"{self.docker_path} pull {container_image}:{container_tag}")
except subprocess.CalledProcessError as ex:
logger.error(ex)
raise RuntimeError(f"Cannot pull container ({container_image}) image")
Expand All @@ -180,12 +179,12 @@ def verify_docker_env(self):

def copy_files_to_container(self):
def _copy(file_description, file_path):
run_command_with_output(
self._run_quiet_command(
f"Create directory structure for copying {file_description} into container",
(f"{self.docker_path} exec {self.container_name} "
f"mkdir -p {container_validation_dir}/{os.path.dirname(file_path)}")
)
run_command_with_output(
self._run_quiet_command(
f"Copy {file_description} to container",
(f"{self.docker_path} cp {file_path} "
f"{self.container_name}:{container_validation_dir}/{file_path}")
Expand Down
5 changes: 2 additions & 3 deletions eva_sub_cli/validators/native_validator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
import subprocess

from ebi_eva_common_pyutils.command_utils import run_command_with_output
from ebi_eva_common_pyutils.logger import logging_config

from eva_sub_cli.validators.validator import Validator
Expand All @@ -27,7 +26,7 @@ def run_validator(self):
self.verify_executables_installed()
try:
command = self.get_validation_cmd()
run_command_with_output("Run Validation using Nextflow", command)
self._run_quiet_command("Run Validation using Nextflow", command)
except subprocess.CalledProcessError as ex:
logger.error(ex)

Expand All @@ -53,7 +52,7 @@ def verify_executables_installed(self):
('vcf-assembly-checker', self.assembly_checker_path),
('biovalidator', self.biovalidator_path)]:
try:
run_command_with_output(
self._run_quiet_command(
f"Check {name} is installed and available on the path",
f"{path} --version"
)
Expand Down
16 changes: 12 additions & 4 deletions eva_sub_cli/validators/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
import csv
import datetime
import glob
import logging
import os
import re
from functools import lru_cache, cached_property

import yaml
from ebi_eva_common_pyutils.command_utils import run_command_with_output
from ebi_eva_common_pyutils.config import WritableConfig

from eva_sub_cli import ETC_DIR, SUB_CLI_CONFIG_FILE, __version__
from eva_sub_cli.report import generate_html_report
from ebi_eva_common_pyutils.logger import logging_config
from ebi_eva_common_pyutils.logger import logging_config, AppLogger

VALIDATION_OUTPUT_DIR = "validation_output"
VALIDATION_RESULTS = 'validation_results'
Expand All @@ -28,7 +30,7 @@ def resolve_single_file_path(file_path):
return files[0]


class Validator:
class Validator(AppLogger):

def __init__(self, mapping_file, output_dir, metadata_json=None, metadata_xlsx=None,
submission_config: WritableConfig = None):
Expand All @@ -54,7 +56,10 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
self.sub_config.backup()
self.sub_config.write()

@staticmethod
def _run_quiet_command(command_description, command, **kwargs):
return run_command_with_output(command_description, command, stdout_log_level=logging.DEBUG,
stderr_log_level=logging.DEBUG, **kwargs)
def _find_vcf_and_fasta_files(self):
vcf_files = []
fasta_files = []
Expand All @@ -66,7 +71,9 @@ def _find_vcf_and_fasta_files(self):
return vcf_files, fasta_files

def validate_and_report(self):
self.info('Start validation')
self.validate()
self.info('Create report')
self.report()

def validate(self):
Expand Down Expand Up @@ -104,7 +111,8 @@ def check_if_file_missing(self):
if not os.path.exists(row['fasta']):
files_missing = True
missing_files_list.append(row['fasta'])
if not os.path.exists(row['report']):
# Assembly report is optional but should exist if it is set.
if row.get('report') and not os.path.exists(row['report']):
files_missing = True
missing_files_list.append(row['report'])
return files_missing, missing_files_list
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ minify_html==0.11.1
openpyxl
requests
jsonschema
ebi_eva_common_pyutils==0.6.1
ebi_eva_common_pyutils==0.6.3
1 change: 1 addition & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_orchestrate_validate(self):
self.mapping_file, self.test_sub_dir, self.metadata_json, self.metadata_xlsx,
submission_config=m_config.return_value
)

with m_docker_validator() as validator:
validator.validate_and_report.assert_called_once_with()

Expand Down
12 changes: 4 additions & 8 deletions tests/test_submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ebi_eva_common_pyutils.config import WritableConfig

from eva_sub_cli import SUB_CLI_CONFIG_FILE
from eva_sub_cli.utils import is_submission_dir_writable
from eva_sub_cli.validators.validator import READY_FOR_SUBMISSION_TO_EVA
from eva_sub_cli.submit import StudySubmitter, SUB_CLI_CONFIG_KEY_SUBMISSION_ID, SUB_CLI_CONFIG_KEY_SUBMISSION_UPLOAD_URL

Expand Down Expand Up @@ -43,7 +44,6 @@ def test_submit(self):
with patch('eva_sub_cli.submit.requests.post', return_value=mock_initiate_response) as mock_post, \
patch('eva_sub_cli.submit.requests.put', return_value=mock_uploaded_response) as mock_put, \
patch.object(StudySubmitter, '_upload_submission'), \
patch.object(StudySubmitter, 'verify_submission_dir'), \
patch.object(self.submitter, 'submission_dir', self.test_sub_dir):

self.submitter.sub_config.set(READY_FOR_SUBMISSION_TO_EVA, value=True)
Expand All @@ -68,7 +68,7 @@ def test_submit_with_config(self):
mock_uploaded_response = MagicMock()
mock_uploaded_response.status_code = 200

self.submitter.verify_submission_dir(self.test_sub_dir)
assert is_submission_dir_writable(self.test_sub_dir)
sub_config = WritableConfig(self.config_file, version='version1.0')
sub_config.set(READY_FOR_SUBMISSION_TO_EVA, value=True)
sub_config.write()
Expand All @@ -88,12 +88,8 @@ def test_submit_with_config(self):
assert sub_config_data[SUB_CLI_CONFIG_KEY_SUBMISSION_ID] == "mock_submission_id"
assert sub_config_data[SUB_CLI_CONFIG_KEY_SUBMISSION_UPLOAD_URL] == "directory to use for upload"

def test_verify_submission_dir(self):
self.submitter.verify_submission_dir(self.test_sub_dir)
assert os.path.exists(self.test_sub_dir)

def test_sub_config_file_creation(self):
self.submitter.verify_submission_dir(self.test_sub_dir)
assert is_submission_dir_writable(self.test_sub_dir)
self.submitter.sub_config.set('test_key', value='test_value')
self.submitter.sub_config.write()

Expand All @@ -102,9 +98,9 @@ def test_sub_config_file_creation(self):

def test_sub_config_passed_as_param(self):
with patch('eva_sub_cli.submit.get_auth', return_value=Mock(token=self.token)):
assert is_submission_dir_writable(self.test_sub_dir)
sub_config = WritableConfig(self.config_file)
with StudySubmitter(self.test_sub_dir, vcf_files=None, metadata_file=None, submission_config=sub_config) as submitter:
submitter.verify_submission_dir(self.test_sub_dir)
submitter.sub_config.set('test_key', value='test_value')

assert os.path.exists(self.config_file)
Expand Down

0 comments on commit 4b20164

Please sign in to comment.