eva-sub-cli 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eva_sub_cli/__init__.py +27 -0
- eva_sub_cli/_version.py +21 -0
- eva_sub_cli/auth.py +106 -0
- eva_sub_cli/date_utils.py +32 -0
- eva_sub_cli/etc/EVA_Submission_template.xlsx +0 -0
- eva_sub_cli/etc/eva-biosamples.json +174 -0
- eva_sub_cli/etc/eva_logo.png +0 -0
- eva_sub_cli/etc/eva_schema.json +369 -0
- eva_sub_cli/etc/spreadsheet2json_conf.yaml +135 -0
- eva_sub_cli/exceptions/__init__.py +0 -0
- eva_sub_cli/exceptions/invalid_file_type_exception.py +4 -0
- eva_sub_cli/exceptions/submission_not_found_exception.py +4 -0
- eva_sub_cli/exceptions/submission_status_exception.py +5 -0
- eva_sub_cli/executables/__init__.py +0 -0
- eva_sub_cli/executables/check_fasta_insdc.py +159 -0
- eva_sub_cli/executables/check_metadata_semantics.py +18 -0
- eva_sub_cli/executables/cli.py +115 -0
- eva_sub_cli/executables/samples_checker.py +144 -0
- eva_sub_cli/executables/trim_down.py +78 -0
- eva_sub_cli/executables/xlsx2json.py +335 -0
- eva_sub_cli/file_utils.py +117 -0
- eva_sub_cli/jinja_templates/fasta_check.html +86 -0
- eva_sub_cli/jinja_templates/file_validation.html +93 -0
- eva_sub_cli/jinja_templates/html_report.html +142 -0
- eva_sub_cli/jinja_templates/metadata_validation.html +52 -0
- eva_sub_cli/jinja_templates/project_details.html +37 -0
- eva_sub_cli/jinja_templates/sample_name_check.html +60 -0
- eva_sub_cli/jinja_templates/shallow_validation.html +29 -0
- eva_sub_cli/metadata_utils.py +46 -0
- eva_sub_cli/nextflow/validation.nf +303 -0
- eva_sub_cli/orchestrator.py +233 -0
- eva_sub_cli/report.py +40 -0
- eva_sub_cli/semantic_metadata.py +243 -0
- eva_sub_cli/submission_ws.py +61 -0
- eva_sub_cli/submit.py +111 -0
- eva_sub_cli/validators/__init__.py +0 -0
- eva_sub_cli/validators/docker_validator.py +205 -0
- eva_sub_cli/validators/native_validator.py +68 -0
- eva_sub_cli/validators/validation_results_parsers.py +210 -0
- eva_sub_cli/validators/validator.py +510 -0
- eva_sub_cli-0.4.4.dist-info/METADATA +164 -0
- eva_sub_cli-0.4.4.dist-info/RECORD +46 -0
- eva_sub_cli-0.4.4.dist-info/WHEEL +5 -0
- eva_sub_cli-0.4.4.dist-info/entry_points.txt +7 -0
- eva_sub_cli-0.4.4.dist-info/licenses/LICENSE +201 -0
- eva_sub_cli-0.4.4.dist-info/top_level.txt +1 -0
eva_sub_cli/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # This is your Project Root
|
|
5
|
+
PACKAGE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
6
|
+
NEXTFLOW_DIR = os.path.join(PACKAGE_DIR, 'nextflow')
|
|
7
|
+
ETC_DIR = os.path.join(PACKAGE_DIR, 'etc')
|
|
8
|
+
LSRI_CLIENT_ID = "aa0fcc42-096a-4f9d-b871-aceb1a97d174"
|
|
9
|
+
|
|
10
|
+
SUB_CLI_CONFIG_FILE = ".eva_sub_cli_config.yml"
|
|
11
|
+
|
|
12
|
+
# Environment variable
|
|
13
|
+
SUBMISSION_WS_VAR = 'SUBMISSION_WS_URL'
|
|
14
|
+
ENA_WEBIN_ACCOUNT_VAR = 'ENA_WEBIN_ACCOUNT'
|
|
15
|
+
ENA_WEBIN_PASSWORD_VAR = 'ENA_WEBIN_PASSWORD'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
# If setuptools_scm is installed we can get the version directly from it
|
|
20
|
+
from setuptools_scm import get_version
|
|
21
|
+
__version__ = get_version(root='..', relative_to=__file__)
|
|
22
|
+
del get_version
|
|
23
|
+
except:
|
|
24
|
+
# otherwise assume that we're working in a deployed instance which should have the _version file
|
|
25
|
+
from ._version import version as __version__
|
|
26
|
+
|
|
27
|
+
|
eva_sub_cli/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.4.4'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 4, 4)
|
eva_sub_cli/auth.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from getpass import getpass
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from ebi_eva_common_pyutils.logger import AppLogger
|
|
8
|
+
from urllib3.exceptions import ResponseError
|
|
9
|
+
|
|
10
|
+
from eva_sub_cli import LSRI_CLIENT_ID, ENA_WEBIN_ACCOUNT_VAR, ENA_WEBIN_PASSWORD_VAR
|
|
11
|
+
|
|
12
|
+
ENA_AUTH_URL = "https://www.ebi.ac.uk/ena/submit/webin/auth/token"
|
|
13
|
+
LSRI_AUTH_URL = "https://www.ebi.ac.uk/eva/v1/submission/auth/lsri"
|
|
14
|
+
DEVICE_AUTHORISATION_URL = "https://login.elixir-czech.org/oidc/devicecode"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LSRIAuth(AppLogger):
|
|
18
|
+
def __init__(self, client_id=LSRI_CLIENT_ID, device_authorization_url=DEVICE_AUTHORISATION_URL,
|
|
19
|
+
auth_url=LSRI_AUTH_URL):
|
|
20
|
+
self.client_id = client_id
|
|
21
|
+
self.device_authorization_url = device_authorization_url
|
|
22
|
+
self.auth_url = auth_url
|
|
23
|
+
|
|
24
|
+
@cached_property
|
|
25
|
+
def token(self):
|
|
26
|
+
# Step 1: Get device code using device auth url
|
|
27
|
+
payload = {
|
|
28
|
+
'client_id': self.client_id,
|
|
29
|
+
'scope': 'openid'
|
|
30
|
+
}
|
|
31
|
+
response = requests.post(self.device_authorization_url, data=payload)
|
|
32
|
+
response.raise_for_status()
|
|
33
|
+
|
|
34
|
+
response_json = response.json()
|
|
35
|
+
device_code = response_json['device_code']
|
|
36
|
+
user_code = response_json['user_code']
|
|
37
|
+
verification_uri = response_json['verification_uri']
|
|
38
|
+
expires_in = response_json['expires_in']
|
|
39
|
+
|
|
40
|
+
# Display the user code and verification URI to the user
|
|
41
|
+
print(f'Please visit {verification_uri} and enter this user code: {user_code}')
|
|
42
|
+
# Delegate subsequent post-authentication processing (which requires LSRI client secret) to eva-submission-ws
|
|
43
|
+
# so that we can avoid storing that client secret in eva-sub-eva
|
|
44
|
+
response = requests.post(self.auth_url, timeout=expires_in,
|
|
45
|
+
headers={'Accept': 'application/hal+json'},
|
|
46
|
+
params={"deviceCode": device_code, "expiresIn": expires_in})
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
self.info("LSRI authentication successful!")
|
|
49
|
+
return response.text
|
|
50
|
+
else:
|
|
51
|
+
raise ResponseError('LSRI Authentication Error')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class WebinAuth(AppLogger):
|
|
55
|
+
|
|
56
|
+
def __init__(self, ena_auth_url=ENA_AUTH_URL, username=None, password=None):
|
|
57
|
+
self.ena_auth_url = ena_auth_url
|
|
58
|
+
self.cmd_line_username = username
|
|
59
|
+
self.cmd_line_password = password
|
|
60
|
+
|
|
61
|
+
@cached_property
|
|
62
|
+
def token(self):
|
|
63
|
+
self.info("ENA Webin authentication")
|
|
64
|
+
username, password = self._get_webin_username_password()
|
|
65
|
+
headers = {"accept": "*/*", "Content-Type": "application/json"}
|
|
66
|
+
data = {"authRealms": ["ENA"], "username": username, "password": password}
|
|
67
|
+
response = requests.post(self.ena_auth_url, headers=headers, data=json.dumps(data))
|
|
68
|
+
|
|
69
|
+
if response.status_code == 200:
|
|
70
|
+
self.info("Webin authentication successful!")
|
|
71
|
+
return response.text
|
|
72
|
+
else:
|
|
73
|
+
raise ResponseError('Webin Authentication Error')
|
|
74
|
+
|
|
75
|
+
def _get_webin_username_password(self):
|
|
76
|
+
username = self.cmd_line_username
|
|
77
|
+
password = self.cmd_line_password
|
|
78
|
+
if not username or not password:
|
|
79
|
+
self.debug('No username and password provided on the command line')
|
|
80
|
+
username, password = self._get_webin_username_password_from_env()
|
|
81
|
+
if not username or not password:
|
|
82
|
+
self.debug('No username and password provided in environment variables')
|
|
83
|
+
username, password = self._get_webin_username_password_from_stdin()
|
|
84
|
+
return username, password
|
|
85
|
+
|
|
86
|
+
def _get_webin_username_password_from_env(self):
|
|
87
|
+
username = os.environ.get(ENA_WEBIN_ACCOUNT_VAR)
|
|
88
|
+
password = os.environ.get(ENA_WEBIN_PASSWORD_VAR)
|
|
89
|
+
return username, password
|
|
90
|
+
|
|
91
|
+
def _get_webin_username_password_from_stdin(self):
|
|
92
|
+
username = input("Enter your ENA Webin username: ")
|
|
93
|
+
password = getpass("Enter your ENA Webin password: ")
|
|
94
|
+
return username, password
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Global auth for the session
|
|
98
|
+
auth = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_auth(username=None, password=None):
|
|
102
|
+
global auth
|
|
103
|
+
if auth:
|
|
104
|
+
return auth
|
|
105
|
+
auth = WebinAuth(username=username, password=password)
|
|
106
|
+
return auth
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
# Values coming from https://www.ebi.ac.uk/ena/browser/view/ERC000011
|
|
4
|
+
not_provided_check_list = ['not provided', 'not collected', 'restricted access', 'missing: control sample',
|
|
5
|
+
'missing: sample group', 'missing: synthetic construct', 'missing: lab stock',
|
|
6
|
+
'missing: third party data', 'missing: data agreement established pre-2023',
|
|
7
|
+
'missing: endangered species', 'missing: human-identifiable']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_date(date):
|
|
11
|
+
return isinstance(date, datetime.date) or \
|
|
12
|
+
isinstance(date, datetime.datetime) or \
|
|
13
|
+
check_date_str_format(date) or \
|
|
14
|
+
str(date).lower() in not_provided_check_list
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_date_str_format(d):
|
|
18
|
+
try:
|
|
19
|
+
datetime.datetime.strptime(d, "%Y-%m-%d")
|
|
20
|
+
return True
|
|
21
|
+
except ValueError:
|
|
22
|
+
pass
|
|
23
|
+
try:
|
|
24
|
+
datetime.datetime.strptime(d, "%Y-%m")
|
|
25
|
+
return True
|
|
26
|
+
except ValueError:
|
|
27
|
+
pass
|
|
28
|
+
try:
|
|
29
|
+
datetime.datetime.strptime(d, "%Y")
|
|
30
|
+
return True
|
|
31
|
+
except ValueError:
|
|
32
|
+
return False
|
|
Binary file
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "eva-biosamples.json",
|
|
4
|
+
"additionalProperties": true,
|
|
5
|
+
"definitions": {
|
|
6
|
+
"nonEmptyString": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"minLength": 1
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"required": [
|
|
12
|
+
"name",
|
|
13
|
+
"characteristics"
|
|
14
|
+
],
|
|
15
|
+
"title": "sample",
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"name": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"accession": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"characteristics": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"anyOf": [
|
|
27
|
+
{
|
|
28
|
+
"required": [
|
|
29
|
+
"organism"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"required": [
|
|
34
|
+
"Organism"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"required": [
|
|
39
|
+
"species"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"required": [
|
|
44
|
+
"Species"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"properties": {
|
|
49
|
+
"organism": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"properties": {
|
|
53
|
+
"text": {
|
|
54
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": [
|
|
58
|
+
"text"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"Organism": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"properties": {
|
|
66
|
+
"text": {
|
|
67
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"required": [
|
|
71
|
+
"text"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"Species": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": {
|
|
78
|
+
"properties": {
|
|
79
|
+
"text": {
|
|
80
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"required": [
|
|
84
|
+
"text"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"species": {
|
|
89
|
+
"type": "array",
|
|
90
|
+
"items": {
|
|
91
|
+
"properties": {
|
|
92
|
+
"text": {
|
|
93
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"required": [
|
|
97
|
+
"text"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"title": {
|
|
102
|
+
"description": "The sample title is a short, preferably a single sentence, description of the sample",
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": {
|
|
105
|
+
"properties": {
|
|
106
|
+
"text": {
|
|
107
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"required": [
|
|
111
|
+
"text"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"description": {
|
|
116
|
+
"description": "Free-text description of the sample",
|
|
117
|
+
"type": "array",
|
|
118
|
+
"items": {
|
|
119
|
+
"properties": {
|
|
120
|
+
"text": {
|
|
121
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"required": [
|
|
125
|
+
"text"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"taxId": {
|
|
130
|
+
"description": "The Tax Id indicates the NCBI taxonomic classification (http://www.ncbi.nlm.nih.gov/taxonomy)",
|
|
131
|
+
"type": "array",
|
|
132
|
+
"items": {
|
|
133
|
+
"properties": {
|
|
134
|
+
"text": {
|
|
135
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": [
|
|
139
|
+
"text"
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"scientificName": {
|
|
144
|
+
"description": "The scientific name of the sequenced organism",
|
|
145
|
+
"type": "array",
|
|
146
|
+
"items": {
|
|
147
|
+
"properties": {
|
|
148
|
+
"text": {
|
|
149
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"required": [
|
|
153
|
+
"text"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"commonName": {
|
|
158
|
+
"description": "The common name of the sequenced organism",
|
|
159
|
+
"type": "array",
|
|
160
|
+
"items": {
|
|
161
|
+
"properties": {
|
|
162
|
+
"text": {
|
|
163
|
+
"$ref": "#/definitions/nonEmptyString"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"required": [
|
|
167
|
+
"text"
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
Binary file
|