Skip to content

Commit

Permalink
EVA-3590 update initial check for validation required or not (#40)
Browse files Browse the repository at this point in the history
* update validation required or not logic
  • Loading branch information
nitin-ebi authored Jun 17, 2024
1 parent 523d1ca commit 0233459
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 81 deletions.
7 changes: 7 additions & 0 deletions bin/eva-sub-cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env python
import sys

from eva_sub_cli.exceptions.submission_not_found_exception import SubmissionNotFoundException
from eva_sub_cli.exceptions.submission_status_exception import SubmissionStatusException

if not sys.warnoptions:
import warnings
warnings.simplefilter("ignore")
Expand Down Expand Up @@ -83,3 +86,7 @@ def get_version():
main.orchestrate_process(**args.__dict__)
except FileNotFoundError as fne:
print(fne)
except SubmissionNotFoundException as snfe:
print(f'{snfe}. Please contact EVA Helpdesk')
except SubmissionStatusException as sse:
print(f'{sse}. Please try again later. If the problem persists, please contact EVA Helpdesk')
4 changes: 4 additions & 0 deletions eva_sub_cli/exceptions/submission_not_found_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class SubmissionNotFoundException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
5 changes: 5 additions & 0 deletions eva_sub_cli/exceptions/submission_status_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

class SubmissionStatusException(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
40 changes: 35 additions & 5 deletions eva_sub_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@
import os
from collections import defaultdict

import requests
from ebi_eva_common_pyutils.config import WritableConfig
from ebi_eva_common_pyutils.logger import logging_config
from openpyxl.reader.excel import load_workbook

from eva_sub_cli import SUB_CLI_CONFIG_FILE, __version__
from eva_sub_cli.exceptions.submission_not_found_exception import SubmissionNotFoundException
from eva_sub_cli.exceptions.submission_status_exception import SubmissionStatusException
from eva_sub_cli.submission_ws import SubmissionWSClient
from eva_sub_cli.submit import StudySubmitter, SUB_CLI_CONFIG_KEY_SUBMISSION_ID
from eva_sub_cli.validators.docker_validator import DockerValidator
from eva_sub_cli.validators.native_validator import NativeValidator
from eva_sub_cli.validators.validator import READY_FOR_SUBMISSION_TO_EVA
from eva_sub_cli.submit import StudySubmitter

VALIDATE = 'validate'
SUBMIT = 'submit'
DOCKER = 'docker'
NATIVE = 'native'

logger = logging_config.get_logger(__name__)

def get_vcf_files(mapping_file):
vcf_files = []
Expand Down Expand Up @@ -110,6 +116,32 @@ def get_project_and_vcf_fasta_mapping_from_metadata_xlsx(metadata_xlsx, mapping_
return project_title, vcf_fasta_report_mapping


def check_validation_required(tasks, sub_config):
# Validation is mandatory so if submit is requested then VALIDATE must have run before or be requested as well
if SUBMIT in tasks:
if not sub_config.get(READY_FOR_SUBMISSION_TO_EVA, False):
return True
submission_id = sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID, None)
if submission_id:
try:
submission_status = SubmissionWSClient().get_submission_status(submission_id)
if submission_status == 'FAILED':
return True
else:
return False
except requests.HTTPError as ex:
if ex.response.status_code == 404:
logger.error(
f'Submission with id {submission_id} could not be found: statuc code: {ex.response.status_code} response: {ex.response.text}')
raise SubmissionNotFoundException(f'Submission with id {submission_id} could not be found')
else:
logger.error(f'Error occurred while getting status of the submission with Id {submission_id}: status code: {ex.response.status_code} response: {ex.response.text}')
raise SubmissionStatusException(f'Error occurred while getting status of the submission with Id {submission_id}')

logger.info(f'submission id not found in config. This might be the first time user is submitting')
return False


def orchestrate_process(submission_dir, vcf_files, reference_fasta, metadata_json, metadata_xlsx,
tasks, executor, username=None, password=None, **kwargs):
# load config
Expand All @@ -124,10 +156,8 @@ def orchestrate_process(submission_dir, vcf_files, reference_fasta, metadata_jso
project_title, vcf_files_mapping = get_project_title_and_create_vcf_files_mapping(submission_dir, vcf_files, reference_fasta, metadata_json, metadata_xlsx)
vcf_files = get_vcf_files(vcf_files_mapping)

# Validation is mandatory so if submit is requested then VALIDATE must have run before or be requested as well
if SUBMIT in tasks and not sub_config.get(READY_FOR_SUBMISSION_TO_EVA, False):
if VALIDATE not in tasks:
tasks.append(VALIDATE)
if VALIDATE not in tasks and check_validation_required(tasks, sub_config):
tasks.append(VALIDATE)

if VALIDATE in tasks:
if executor == DOCKER:
Expand Down
61 changes: 61 additions & 0 deletions eva_sub_cli/submission_ws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os

import requests
from ebi_eva_common_pyutils.logger import AppLogger
from requests import HTTPError
from retry import retry

from eva_sub_cli import SUBMISSION_WS_VAR
from eva_sub_cli.auth import get_auth


class SubmissionWSClient(AppLogger):
"""
Python client for interfacing with the Submission WS API.
"""

def __init__(self, username=None, password=None):
self.auth = get_auth(username, password)
self.base_url = self._submission_ws_url

SUBMISSION_WS_URL = 'https://www.ebi.ac.uk/eva/webservices/submission-ws/v1/'
SUBMISSION_INITIATE_PATH = 'submission/initiate'
SUBMISSION_UPLOADED_PATH = 'submission/{submissionId}/uploaded'
SUBMISSION_STATUS_PATH = 'submission/{submissionId}/status'

@property
def _submission_ws_url(self):
"""Retrieve the base URL for the submission web services.
In order of preference from the environment variable or the hardcoded value."""
if os.environ.get(SUBMISSION_WS_VAR):
return os.environ.get(SUBMISSION_WS_VAR)
else:
return self.SUBMISSION_WS_URL

def _submission_initiate_url(self):
return os.path.join(self.base_url, self.SUBMISSION_INITIATE_PATH)

def _submission_uploaded_url(self, submission_id):
return os.path.join(self.base_url, self.SUBMISSION_UPLOADED_PATH.format(submissionId=submission_id))

def _submission_status_url(self, submission_id):
return os.path.join(self.base_url, self.SUBMISSION_STATUS_PATH.format(submissionId=submission_id))

def mark_submission_uploaded(self, submission_id, metadata_json):
response = requests.put(self._submission_uploaded_url(submission_id),
headers={'Accept': 'application/hal+json', 'Authorization': 'Bearer ' + self.auth.token},
data=metadata_json)
response.raise_for_status()
return response.json()

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()
return response.json()

@retry(exceptions=(HTTPError,), tries=3, delay=2, backoff=1.2, jitter=(1, 3))
def get_submission_status(self, submission_id):
response = requests.get(self.get_submission_status_url(submission_id))
response.raise_for_status()
return response.text
42 changes: 6 additions & 36 deletions eva_sub_cli/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@
from ebi_eva_common_pyutils.logger import AppLogger
from retry import retry

from eva_sub_cli import SUB_CLI_CONFIG_FILE, __version__, SUBMISSION_WS_VAR
from eva_sub_cli.auth import get_auth
from eva_sub_cli import SUB_CLI_CONFIG_FILE, __version__
from eva_sub_cli.submission_ws import SubmissionWSClient
from eva_sub_cli.validators.validator import READY_FOR_SUBMISSION_TO_EVA

SUB_CLI_CONFIG_KEY_SUBMISSION_ID = "submission_id"
SUB_CLI_CONFIG_KEY_SUBMISSION_UPLOAD_URL = "submission_upload_url"
SUB_CLI_CONFIG_KEY_COMPLETE = "submission_complete"

SUBMISSION_WS_URL = "https://www.ebi.ac.uk/eva/webservices/submission-ws/v1/"


class StudySubmitter(AppLogger):
def __init__(self, submission_dir, submission_ws_url=None,
submission_config: WritableConfig = None, username=None, password=None):
self.auth = get_auth(username, password)
self.submission_ws_url = submission_ws_url
def __init__(self, submission_dir, submission_config: WritableConfig = None, username=None, password=None):
self.submission_ws_client = SubmissionWSClient(username, password)
self.submission_dir = submission_dir
if submission_config:
self.sub_config = submission_config
Expand Down Expand Up @@ -50,24 +46,6 @@ def metadata_json(self):
def vcf_files(self):
return self.sub_config.get('vcf_files')

def _get_submission_ws_url(self):
"""Retrieve the base URL for the submission web services. In order of preference from the user of this class,
the environment variable or the hardcoded value."""
if self.submission_ws_url:
return self.submission_ws_url
if os.environ.get(SUBMISSION_WS_VAR):
return os.environ.get(SUBMISSION_WS_VAR)
else:
return SUBMISSION_WS_URL

@property
def submission_initiate_url(self):
return os.path.join(self._get_submission_ws_url(), 'submission/initiate')

@property
def submission_uploaded_url(self):
return os.path.join(self._get_submission_ws_url(), 'submission/{submissionId}/uploaded')

def update_config_with_submission_id_and_upload_url(self, submission_id, upload_url):
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)
Expand All @@ -91,21 +69,13 @@ def _upload_file(self, submission_upload_url, input_file):
self.debug(f'Upload of {base_name} completed')

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()
response_json = self.submission_ws_client.initiate_submission()
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}, data=self.metadata_json
)
response.raise_for_status()
self.submission_ws_client.mark_submission_uploaded(self.sub_config.get(SUB_CLI_CONFIG_KEY_SUBMISSION_ID), self.metadata_json)
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)
Expand Down
39 changes: 37 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
import os
import shutil
import unittest
from unittest.mock import patch
from unittest.mock import patch, Mock

from ebi_eva_common_pyutils.config import WritableConfig
from requests import HTTPError

from eva_sub_cli import SUB_CLI_CONFIG_FILE
from eva_sub_cli.main import orchestrate_process, VALIDATE, SUBMIT, DOCKER
from eva_sub_cli.exceptions.submission_not_found_exception import SubmissionNotFoundException
from eva_sub_cli.exceptions.submission_status_exception import SubmissionStatusException
from eva_sub_cli.main import orchestrate_process, VALIDATE, SUBMIT, DOCKER, check_validation_required
from eva_sub_cli.submit import SUB_CLI_CONFIG_KEY_SUBMISSION_ID
from eva_sub_cli.validators.validator import READY_FOR_SUBMISSION_TO_EVA


Expand All @@ -33,6 +37,37 @@ def setUp(self) -> None:
def tearDown(self) -> None:
shutil.rmtree(self.test_sub_dir)

def test_check_validation_required(self):
tasks = ['submit']

sub_config = {READY_FOR_SUBMISSION_TO_EVA: False}
self.assertTrue(check_validation_required(tasks, sub_config))

sub_config = {READY_FOR_SUBMISSION_TO_EVA: True}
self.assertFalse(check_validation_required(tasks, sub_config))

with patch('eva_sub_cli.submission_ws.SubmissionWSClient.get_submission_status') as get_submission_status_mock:
sub_config = {READY_FOR_SUBMISSION_TO_EVA: True,
SUB_CLI_CONFIG_KEY_SUBMISSION_ID: 'test123'}

get_submission_status_mock.return_value = 'OPEN'
self.assertFalse(check_validation_required(tasks, sub_config))

get_submission_status_mock.return_value = 'FAILED'
self.assertTrue(check_validation_required(tasks, sub_config))

mock_response = Mock()
mock_response.status_code = 404
get_submission_status_mock.side_effect = HTTPError(response=mock_response)
with self.assertRaises(SubmissionNotFoundException):
check_validation_required(tasks, sub_config)

mock_response = Mock()
mock_response.status_code = 500
get_submission_status_mock.side_effect = HTTPError(response=mock_response)
with self.assertRaises(SubmissionStatusException):
check_validation_required(tasks, sub_config)

def test_orchestrate_validate(self):
with patch('eva_sub_cli.main.get_vcf_files') as m_get_vcf, \
patch('eva_sub_cli.main.WritableConfig') as m_config, \
Expand Down
Loading

0 comments on commit 0233459

Please sign in to comment.