Skip to content

Commit bfac26d

Browse files
Several fixes : Changing VIP portal / Girder URL, working route to check api key, check for empty file (#40)
* Adding set_vip_url function * fix bad route used to check api key * allow user to choose the url of the girder instance * better name for girder url * Allow user to change vip portal url when using classes * setting new vip url with utils/vip.py * check for empty file and code refactor * Better way to check file size * refactor * Renaming
1 parent 3992743 commit bfac26d

4 files changed

Lines changed: 60 additions & 29 deletions

File tree

src/vip_client/classes/VipCI.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,12 @@ class VipCI(VipLauncher):
5151
# Default backup location
5252
# (set to None to avoid saving and loading backup files)
5353
_BACKUP_LOCATION = "girder"
54-
55-
# --- New Attributes ---
56-
54+
5755
# Prefix that defines a Girder ID
5856
_GIRDER_ID_PREFIX = "pilotGirder"
5957
# Grider portal
6058
_GIRDER_PORTAL = 'https://pilot-warehouse.creatis.insa-lyon.fr/api/v1'
59+
6160

6261
#################
6362
################ Main Properties ##################
@@ -137,7 +136,15 @@ def __init__(
137136

138137
# Login to VIP and Girder
139138
@classmethod
140-
def init(cls, vip_key="VIP_API_KEY", girder_key="GIRDER_API_KEY", verbose=True, **kwargs) -> VipCI:
139+
def init(
140+
cls,
141+
vip_key="VIP_API_KEY",
142+
girder_key="GIRDER_API_KEY",
143+
verbose=True,
144+
girder_api_url=None,
145+
girder_id_prefix=None,
146+
**kwargs
147+
) -> VipCI:
141148
"""
142149
Handshakes with VIP using your own API key.
143150
Returns a class instance which properties can be provided as keyword arguments.
@@ -161,8 +168,11 @@ def init(cls, vip_key="VIP_API_KEY", girder_key="GIRDER_API_KEY", verbose=True,
161168
super().init(api_key=vip_key, verbose=False)
162169
# Restore the verbose state
163170
cls._VERBOSE = verbose
171+
# Set the Girder ID prefix
172+
cls._GIRDER_ID_PREFIX = girder_id_prefix if girder_id_prefix is not None else cls._GIRDER_ID_PREFIX
173+
cls._GIRDER_PORTAL = girder_api_url if girder_api_url is not None else cls._GIRDER_PORTAL
164174
# Instantiate a Girder client
165-
cls._girder_client = girder_client.GirderClient(apiUrl=cls._GIRDER_PORTAL)
175+
cls._girder_client = girder_client.GirderClient(apiUrl=girder_api_url)
166176
# Check if `girder_key` is in a local file or environment variable
167177
true_key = cls._get_api_key(girder_key)
168178
# Authenticate with Girder API key

src/vip_client/classes/VipLauncher.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ def __init__(
371371

372372
# ($A.1) Login to VIP
373373
@classmethod
374-
def init(cls, api_key="VIP_API_KEY", verbose=True, **kwargs) -> VipLauncher:
374+
def init(cls, api_key="VIP_API_KEY", verbose=True, vip_portal_url=None,
375+
**kwargs) -> VipLauncher:
375376
"""
376377
Handshakes with VIP using your own API key.
377378
Returns a class instance which properties can be provided as keyword arguments.
@@ -391,11 +392,14 @@ def init(cls, api_key="VIP_API_KEY", verbose=True, **kwargs) -> VipLauncher:
391392
"""
392393
# Set the default verbose mode for all sessions
393394
cls._VERBOSE = verbose
395+
# Set the VIP portal URL
396+
cls._VIP_PORTAL = vip_portal_url if vip_portal_url else cls._VIP_PORTAL
394397
# Check if `api_key` is in a local file or environment variable
395398
true_key = cls._get_api_key(api_key)
396399
# Set User API key
397400
try:
398401
# setApiKey() may return False
402+
vip.set_vip_url(cls._VIP_PORTAL)
399403
assert vip.setApiKey(true_key), \
400404
f"(!) Unable to set the VIP API key: {true_key}.\nPlease check the key or retry later."
401405
except RuntimeError as vip_error:
@@ -1641,6 +1645,7 @@ def _check_input_values(self, input_settings: dict, location: str) -> None:
16411645
self._check_invalid_input(input_settings, self._pipeline_def['parameters'])
16421646

16431647
wrong_type_inputs = []
1648+
missing_files = []
16441649
for param in self._pipeline_def['parameters']:
16451650
name = param['name']
16461651
# Check only defined inputs
@@ -1650,11 +1655,10 @@ def _check_input_values(self, input_settings: dict, location: str) -> None:
16501655
# If input is a File, check file(s) existence
16511656
if param["type"] == "File":
16521657
# Ensure every file exists at `location`
1653-
missing_file = self._first_missing_file(value, location)
1654-
if missing_file:
1655-
raise FileNotFoundError(
1656-
f"Parameter '{name}': The following file is missing in the {location.upper()} file system: {missing_file}"
1657-
)
1658+
missing_files_found = self._missing_files(value, location)
1659+
if missing_files_found:
1660+
missing_files.extend(missing_files_found)
1661+
continue
16581662
if param["type"] == "Boolean":
16591663
if value not in ["true", "false"]:
16601664
wrong_type_inputs.append(name)
@@ -1666,6 +1670,10 @@ def _check_input_values(self, input_settings: dict, location: str) -> None:
16661670
raise ValueError(
16671671
f"Wrong type(s) for parameter(s): {', '.join(sorted(wrong_type_inputs))}"
16681672
)
1673+
if missing_files:
1674+
raise FileNotFoundError(
1675+
f"Missing file(s) for parameter(s): {', '.join(sorted(missing_files))}"
1676+
)
16691677

16701678
# ------------------------------------------------
16711679

@@ -1713,21 +1721,27 @@ def _invalid_chars_for_vip(cls, value) -> list:
17131721

17141722
# Function to assert file existence in the input settings
17151723
@classmethod
1716-
def _first_missing_file(cls, value, location: str) -> str:
1724+
def _missing_files(cls, value, location: str) -> list[str]:
17171725
"""
1718-
Returns the path the first non-existent file in `value` (None by default).
1719-
- `value` can contain a single file path or a list of paths.
1720-
- `location` refers to the storage infrastructure (e.g., "vip") to feed in cls._exists().
1726+
Returns a list of missing files for `value` at `location`.
1727+
1728+
- `value` can be either a single file path or a list of paths.
1729+
- `location` refers to the storage infrastructure (e.g., "vip") used by cls._exists().
17211730
"""
1722-
# Case : list of files
1731+
missing_files = []
1732+
1733+
# Case: list of files
17231734
if isinstance(value, list):
1724-
for file in value :
1725-
if cls._first_missing_file(value=file, location=location) is not None:
1726-
return file
1727-
return None
1735+
for file in value:
1736+
if not cls._exists(file, location=location):
1737+
missing_files.append(file)
17281738
# Case: single file
17291739
else:
1730-
return value if not cls._exists(path=value, location=location) else None
1740+
if not cls._exists(value, location=location):
1741+
missing_files.append(value)
1742+
1743+
return missing_files
1744+
17311745
# ------------------------------------------------
17321746

17331747
########################################

src/vip_client/classes/VipSession.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,8 @@ def upload_inputs(self, input_dir=None, update_files=True) -> VipSession:
350350
elif not self._is_defined("_local_input_dir"):
351351
raise TypeError(f"Session '{self._session_name}': Please provide an input directory.")
352352
# Check local input directory
353-
if not self._exists(self._local_input_dir, location="local"):
354-
raise FileNotFoundError(f"Session '{self._session_name}': Input directory does not exist.")
353+
if not self._exists(self._local_input_dir, location="local"):
354+
raise FileNotFoundError(f"Session '{self._session_name}': Input directory '{self._local_input_dir}' does not exist.")
355355
# Check the local values of `input_settings` before uploading
356356
if self._is_defined("_input_settings"):
357357
self._print("Checking references to the dataset within Input Settings ... ", end="", flush=True)
@@ -684,11 +684,12 @@ def _path_to_delete(self) -> dict:
684684
def _exists(cls, path: PurePath, location="local") -> bool:
685685
"""
686686
Checks existence of a distant (`location`="vip") or local (`location`="local") resource.
687-
`path` can be a string or path-like object.
687+
`path` can be a string or path-like object. If `location` is "local", empty folders are
688+
considered as non-existent.
688689
"""
689690
# Check path existence in `location`
690691
if location=="local":
691-
return os.path.exists(path)
692+
return os.path.exists(path) and os.path.isdir(path) and os.listdir(path)
692693
else:
693694
return super()._exists(path=path, location=location)
694695
# ------------------------------------------------
@@ -831,9 +832,10 @@ def _upload_dir(self, local_path: Path, vip_path: PurePosixPath) -> list:
831832
failures = []
832833
for local_file in files_to_upload :
833834
nFile+=1
834-
# Get the file size (if possible)
835-
try: size = f"{local_file.stat().st_size/(1<<20):,.1f}MB"
836-
except: size = "unknown size"
835+
# Check the file size
836+
size = local_file.stat().st_size
837+
if size == 0: raise ValueError(f"{local_file} is an empty file. Empty file are not supported on VIP")
838+
size = f"{size/(1<<20):,.1f}MB"
837839
# Display the current file
838840
self._print(f"\t[{nFile}/{len(files_to_upload)}] Uploading file: {local_file.name} ({size}) ...", end=" ")
839841
# Upload the file on VIP

src/vip_client/utils/vip.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
# API URL
2121
__PREFIX = "https://vip.creatis.insa-lyon.fr/rest/"
2222

23+
def set_vip_url(vip_portal_url: str) -> None:
24+
"""Change the API prefix"""
25+
global __PREFIX
26+
__PREFIX = vip_portal_url + "/rest/"
27+
2328
# API key
2429
__apikey = None
2530
__headers = {'apikey': __apikey}
@@ -80,7 +85,7 @@ def setApiKey(value) -> bool:
8085
Return True is correct apikey, False otherwise.
8186
Raise an error if an other problems occured
8287
"""
83-
url = __PREFIX + 'plateform'
88+
url = __PREFIX + 'platform'
8489
head_test = {
8590
'apikey': value,
8691
}

0 commit comments

Comments
 (0)