Skip to content

Commit f3c745a

Browse files
authored
Merge pull request #37 from elixir-europe/update_isa_json_based_on_response
Update isa json based on response
2 parents 4107ff8 + dac38e3 commit f3c745a

19 files changed

+2964
-370
lines changed

.github/workflows/test-mars.yml

+4
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ jobs:
4242
- name: Linting
4343
run: ruff check mars_lib/
4444
working-directory: ${{ env.working-directory }}
45+
46+
- name: Type checking
47+
run: mypy --install-types --non-interactive mars_lib/
48+
working-directory: ${{ env.working-directory }}

mars-cli/.coveragerc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[run]
2-
omit = mars_lib/__init__.py, mars_lib/submit.py, mars_lib/credential.py
2+
omit = mars_lib/__init__.py, mars_lib/submit.py, mars_lib/credential.py, mars_lib/models/__init__.py

mars-cli/mars_cli.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pathlib
44
from configparser import ConfigParser
55
from mars_lib.target_repo import TargetRepository
6-
from mars_lib.model import Investigation, IsaJson
6+
from mars_lib.models.isa_json import Investigation, IsaJson
77
from mars_lib.isa_json import load_isa_json
88
from logging.handlers import RotatingFileHandler
99
import requests
@@ -13,7 +13,7 @@
1313

1414
# Load CLI configuration
1515
home_dir = (
16-
pathlib.Path(os.getenv("MARS_SETTINGS_DIR"))
16+
pathlib.Path(str(os.getenv("MARS_SETTINGS_DIR")))
1717
if os.getenv("MARS_SETTINGS_DIR")
1818
else pathlib.Path.home()
1919
)

mars-cli/mars_lib/authentication.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1+
from typing import Optional
12
import requests
23
import json
34

45

56
def get_webin_auth_token(
6-
credentials_dict,
7-
header={"Content-Type": "application/json"},
8-
auth_base_url="https://wwwdev.ebi.ac.uk/ena/dev/submit/webin/auth/token",
9-
token_expiration_time=1,
10-
):
7+
credentials_dict: dict[str, str],
8+
header: dict[str, str] = {"Content-Type": "application/json"},
9+
auth_base_url: str = "https://wwwdev.ebi.ac.uk/ena/dev/submit/webin/auth/token",
10+
token_expiration_time: int = 1,
11+
) -> Optional[str]:
1112
"""
1213
Obtain Webin authentication token.
1314
1415
Args:
1516
credentials_dict (dict): The password dictionary for authentication.
1617
header (dict): The header information.
1718
auth_base_url (str): The base URL for authentication.
18-
token_expiration_time(int): Toke expiration time in hours.
19+
token_expiration_time(int): Token expiration time in hours.
1920
2021
Returns:
2122
str: The obtained token.

mars-cli/mars_lib/biosamples_external_references.py

+37-30
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
from jsonschema import validate
88
from jsonschema.exceptions import ValidationError, SchemaError
9-
from typing import Union
9+
from typing import Union, Any, Optional, List
1010

1111
# -- #
1212
# Hardcoded values
@@ -21,7 +21,7 @@
2121
# -- #
2222
# Code blocks
2323
# -- #
24-
def load_json_file(file):
24+
def load_json_file(file: str) -> Any:
2525
"""
2626
Function to load a JSON file as a dictionary.
2727
Args:
@@ -46,7 +46,7 @@ def load_json_file(file):
4646
)
4747

4848

49-
def handle_input_dict(input):
49+
def handle_input_dict(input: dict[str, str]) -> Optional[dict[str, str]]:
5050
"""
5151
Function to handle the input: assert that it's either a dictionary or
5252
the filepath to an existing file containing the dictionary
@@ -73,7 +73,7 @@ def handle_input_dict(input):
7373
raise ValueError(f"The file '{input}' is not a valid JSON file.")
7474

7575

76-
def get_header(token):
76+
def get_header(token: str) -> dict[str, str]:
7777
"""
7878
Obtain the header using a token.
7979
@@ -90,7 +90,7 @@ def get_header(token):
9090
}
9191

9292

93-
def validate_bs_accession(accession_str):
93+
def validate_bs_accession(accession_str: str) -> None:
9494
"""
9595
Validates that the given accession string conforms to the specified regex format.
9696
See: https://registry.identifiers.org/registry/biosample
@@ -108,8 +108,8 @@ def validate_bs_accession(accession_str):
108108

109109

110110
def validate_json_against_schema(
111-
json_doc: Union[dict, str], json_schema: Union[dict, str]
112-
):
111+
json_doc: Union[dict[str, List[str]], str], json_schema: Union[dict[str, str], str]
112+
) -> Optional[bool]:
113113
"""
114114
Validates a JSON document against a given JSON Schema.
115115
@@ -150,7 +150,7 @@ class BiosamplesRecord:
150150
production: boolean indicating environment mode
151151
"""
152152

153-
def __init__(self, bs_accession):
153+
def __init__(self, bs_accession: str) -> None:
154154
"""
155155
Initialize the BiosamplesRecord with provided arguments.
156156
@@ -159,16 +159,19 @@ def __init__(self, bs_accession):
159159
"""
160160
validate_bs_accession(bs_accession)
161161
self.bs_accession = bs_accession
162+
self.biosamples_credentials: Optional[dict[str, str]] = None
163+
self.biosamples_externalReferences: List[str] = []
164+
self.production: bool = False
162165

163-
def display(self):
166+
def display(self) -> None:
164167
"""
165168
Display the attributes for demonstration purposes.
166169
"""
167170
print("Biosamples Credentials:", self.biosamples_credentials)
168171
print("Biosamples External References:", self.biosamples_externalReferences)
169172
print("Production Mode:", self.production)
170173

171-
def fetch_bs_json(self, biosamples_endpoint):
174+
def fetch_bs_json(self, biosamples_endpoint: str) -> Optional[dict[str, str]]:
172175
"""
173176
Fetches the BioSample's record (JSON) of the accession.
174177
@@ -206,47 +209,49 @@ def fetch_bs_json(self, biosamples_endpoint):
206209
self.bs_json = response_json
207210
return self.bs_json
208211

209-
def load_bs_json(self, bs_json_file: str = None, bs_json: dict = None):
212+
def load_bs_json(
213+
self, bs_json: Union[str, dict[str, str]]
214+
) -> Optional[dict[str, str]]:
210215
"""
211216
Loads a given JSON, or the file containing it, as the BioSample's record (JSON) for this instance.
212217
It is an alternative to fetching it directly from BioSample.
213218
214219
Args:
215-
bs_json_file (str): The file containing the Biosamples JSON metadata of the accession
216-
bs_json (dict): The already loaded Biosamples JSON metadata of the accession
220+
bs_json Union[str, dict]: The already Biosamples JSON metadata of the accession either path to file or dictionary.
217221
"""
218-
if bs_json:
219-
if isinstance(bs_json, dict):
220-
self.bs_json = bs_json
221-
return self.bs_json
222-
else:
223-
raise TypeError(
224-
f"Given 'bs_json' is of type '{type(bs_json)}' instead of type 'dict'."
225-
)
226-
elif bs_json_file:
227-
bs_json = load_json_file(bs_json_file)
222+
if isinstance(bs_json, dict):
228223
self.bs_json = bs_json
229224
return self.bs_json
225+
elif isinstance(bs_json, str):
226+
bs_json_data = load_json_file(bs_json)
227+
self.bs_json = bs_json_data
228+
return self.bs_json
230229
else:
231230
raise ValueError(
232231
"Neither the file containing the Biosamples JSON nor the Biosamples JSON itself were given to load it into the instance."
233232
)
234233

235-
def pop_links(self):
234+
def pop_links(self) -> dict[str, str]:
236235
"""
237236
Removes "_links" array (which is added automatically after updating the biosamples on the BioSample's side).
238237
"""
239238

240-
if "_links" not in self.bs_json:
241-
return self.bs_json
239+
if "_links" in self.bs_json:
240+
self.bs_json.pop("_links")
242241

243-
self.bs_json.pop("_links")
244242
return self.bs_json
245243

246-
def extend_externalReferences(self, new_ext_refs_list):
244+
def extend_externalReferences(
245+
self, new_ext_refs_list: List[dict[str, str]]
246+
) -> dict[str, str]:
247247
"""Extends the JSON of the BioSample's record with new externalReferences"""
248248
if not self.bs_json:
249-
self.fetch_bs_json()
249+
endpoint = (
250+
biosamples_endpoints["prod"]
251+
if self.production
252+
else biosamples_endpoints["dev"]
253+
)
254+
self.fetch_bs_json(endpoint)
250255
self.pop_links()
251256

252257
if "externalReferences" not in self.bs_json:
@@ -265,7 +270,9 @@ def extend_externalReferences(self, new_ext_refs_list):
265270
self.bs_json["externalReferences"] = ext_refs_list
266271
return self.bs_json
267272

268-
def update_remote_record(self, header, webin_auth="?authProvider=WEBIN"):
273+
def update_remote_record(
274+
self, header: dict[str, str], webin_auth: str = "?authProvider=WEBIN"
275+
) -> Optional[str]:
269276
"""
270277
Updates the remote record of the BioSample's accession with the current sample JSON.
271278

mars-cli/mars_lib/credential.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,31 @@
5252

5353

5454
class CredentialManager:
55-
def __init__(self, service_name):
55+
def __init__(self, service_name: str) -> None:
5656
self.service_name = service_name
5757

58-
def get_credential_env(self, username):
58+
def get_credential_env(self, username: str) -> str:
5959
"""
6060
Retrieves a credential from environment variables.
6161
6262
:param username: The environment variable username.
6363
:return: The value of the environment variable or None if not found.
6464
"""
65-
return os.getenv(username)
65+
result = os.getenv(username)
66+
if result is None:
67+
raise ValueError(f"Environment variable '{username}' not found.")
6668

67-
def prompt_for_password(self):
69+
return result
70+
71+
def prompt_for_password(self) -> str:
6872
"""
6973
Securely prompts the user to enter a password in the console.
7074
7175
:return: The password entered by the user.
7276
"""
7377
return getpass.getpass(prompt="Enter your password: ")
7478

75-
def set_password_keyring(self, username, password):
79+
def set_password_keyring(self, username: str, password: str) -> None:
7680
"""
7781
Stores a password in the keyring under the given username.
7882
@@ -81,16 +85,19 @@ def set_password_keyring(self, username, password):
8185
"""
8286
keyring.set_password(self.service_name, username, password)
8387

84-
def get_password_keyring(self, username):
88+
def get_password_keyring(self, username: str) -> str:
8589
"""
8690
Retrieves a password from the keyring for the given username.
8791
8892
:param username: The username whose password to retrieve.
8993
:return: The password or None if not found.
9094
"""
91-
return keyring.get_password(self.service_name, username)
95+
pwd = keyring.get_password(self.service_name, username)
96+
if pwd is None:
97+
raise ValueError(f"Password not found for username '{username}'.")
98+
return pwd
9299

93-
def delete_password_keyring(self, username):
100+
def delete_password_keyring(self, username: str) -> None:
94101
"""
95102
Deletes a password from the keyring for the given username.
96103

0 commit comments

Comments
 (0)