Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVA-3590 update initial check for validation required or not #40

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}. Retry with correct submission id. If the problem persists, please contact EVA Helpdesk')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the user will know the submission ID since it's just stored in the config, there's not a command line option to set it or anything. Since this is just when it's not found in the WS maybe just contact helpdesk in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

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

apriltuesday marked this conversation as resolved.
Show resolved Hide resolved

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 user of this class,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Retrieve the base URL for the submission web services. In order of preference from the user of this class,
"""Retrieve the base URL for the submission web services. In order of preference from

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

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
Loading