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.
Files changed (46) hide show
  1. eva_sub_cli/__init__.py +27 -0
  2. eva_sub_cli/_version.py +21 -0
  3. eva_sub_cli/auth.py +106 -0
  4. eva_sub_cli/date_utils.py +32 -0
  5. eva_sub_cli/etc/EVA_Submission_template.xlsx +0 -0
  6. eva_sub_cli/etc/eva-biosamples.json +174 -0
  7. eva_sub_cli/etc/eva_logo.png +0 -0
  8. eva_sub_cli/etc/eva_schema.json +369 -0
  9. eva_sub_cli/etc/spreadsheet2json_conf.yaml +135 -0
  10. eva_sub_cli/exceptions/__init__.py +0 -0
  11. eva_sub_cli/exceptions/invalid_file_type_exception.py +4 -0
  12. eva_sub_cli/exceptions/submission_not_found_exception.py +4 -0
  13. eva_sub_cli/exceptions/submission_status_exception.py +5 -0
  14. eva_sub_cli/executables/__init__.py +0 -0
  15. eva_sub_cli/executables/check_fasta_insdc.py +159 -0
  16. eva_sub_cli/executables/check_metadata_semantics.py +18 -0
  17. eva_sub_cli/executables/cli.py +115 -0
  18. eva_sub_cli/executables/samples_checker.py +144 -0
  19. eva_sub_cli/executables/trim_down.py +78 -0
  20. eva_sub_cli/executables/xlsx2json.py +335 -0
  21. eva_sub_cli/file_utils.py +117 -0
  22. eva_sub_cli/jinja_templates/fasta_check.html +86 -0
  23. eva_sub_cli/jinja_templates/file_validation.html +93 -0
  24. eva_sub_cli/jinja_templates/html_report.html +142 -0
  25. eva_sub_cli/jinja_templates/metadata_validation.html +52 -0
  26. eva_sub_cli/jinja_templates/project_details.html +37 -0
  27. eva_sub_cli/jinja_templates/sample_name_check.html +60 -0
  28. eva_sub_cli/jinja_templates/shallow_validation.html +29 -0
  29. eva_sub_cli/metadata_utils.py +46 -0
  30. eva_sub_cli/nextflow/validation.nf +303 -0
  31. eva_sub_cli/orchestrator.py +233 -0
  32. eva_sub_cli/report.py +40 -0
  33. eva_sub_cli/semantic_metadata.py +243 -0
  34. eva_sub_cli/submission_ws.py +61 -0
  35. eva_sub_cli/submit.py +111 -0
  36. eva_sub_cli/validators/__init__.py +0 -0
  37. eva_sub_cli/validators/docker_validator.py +205 -0
  38. eva_sub_cli/validators/native_validator.py +68 -0
  39. eva_sub_cli/validators/validation_results_parsers.py +210 -0
  40. eva_sub_cli/validators/validator.py +510 -0
  41. eva_sub_cli-0.4.4.dist-info/METADATA +164 -0
  42. eva_sub_cli-0.4.4.dist-info/RECORD +46 -0
  43. eva_sub_cli-0.4.4.dist-info/WHEEL +5 -0
  44. eva_sub_cli-0.4.4.dist-info/entry_points.txt +7 -0
  45. eva_sub_cli-0.4.4.dist-info/licenses/LICENSE +201 -0
  46. eva_sub_cli-0.4.4.dist-info/top_level.txt +1 -0
@@ -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
+
@@ -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
@@ -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