Skip to content

Commit cc76ded

Browse files
authored
Merge pull request #56 from Albireo-B/Refactor/Vip_optional_inputs
VIP input maps handling
2 parents 261d842 + e4cbe32 commit cc76ded

7 files changed

Lines changed: 89 additions & 69 deletions

File tree

‎src/vip_client/classes/VipGirder.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def output_location(self, value: str) -> None:
119119
################ Constructor ##################
120120
#############
121121
def __init__(
122-
self, output_location='girder', output_dir=None, pipeline_id: str=None, input_settings: dict=None,
122+
self, output_location='girder', output_dir=None, pipeline_id: str=None, input_settings: dict | list[dict]=None,
123123
session_name: str=None, verbose: bool=None, custom_wf_metadata: dict=None
124124
) -> None:
125125
"""
@@ -260,7 +260,7 @@ def upload_inputs(self, input_dir=None, update_files=True) -> VipSession:
260260

261261
# Launch the pipeline on VIP
262262
def launch_pipeline(
263-
self, pipeline_id: str=None, input_settings: dict=None, nb_runs=1
263+
self, pipeline_id: str=None, input_settings: dict | list[dict]=None, nb_runs=1
264264
) -> VipGirder:
265265
"""
266266
Launches pipeline executions on VIP.
@@ -738,7 +738,7 @@ def parse_value(input):
738738
# ------------------------------------------------
739739

740740
# Get the input settings after files are parsed as PathLib objects
741-
def _get_input_settings(self, location="girder") -> dict:
741+
def _get_input_settings(self, location="girder") -> list[dict]:
742742
"""
743743
Returns the input settings with filenames adapted to `location`.
744744
- if `location` = "girder", returns Girder paths string format.
@@ -747,7 +747,7 @@ def _get_input_settings(self, location="girder") -> dict:
747747
Returns a string version of any other parameter.
748748
"""
749749
# Function to get the VIP-Girder standard from 1 input path
750-
def get_input(value, location) -> str:
750+
def get_input(value, location) -> str | list[str]:
751751
"""
752752
If `value` is a path, returns the corresponding string.
753753
Value can be a single input or a list of inputs.
@@ -768,10 +768,13 @@ def get_input(value, location) -> str:
768768
if location not in ("girder", "vip-girder"):
769769
return super()._get_input_settings(location)
770770
# Browse input settings
771-
return {
772-
key: get_input(value, location)
773-
for key, value in self._input_settings.items()
774-
}
771+
return [
772+
{
773+
key: get_input(value, location)
774+
for key, value in input_dict.items()
775+
}
776+
for input_dict in self._input_settings
777+
]
775778
# ------------------------------------------------
776779

777780
######################################################

‎src/vip_client/classes/VipLauncher.py‎

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -130,34 +130,40 @@ def pipeline_id(self) -> None:
130130

131131
# Input settings
132132
@property
133-
def input_settings(self) -> dict:
133+
def input_settings(self) -> list[dict] | None:
134134
"""All parameters needed to run the pipeline
135135
Run show_pipeline() for more information"""
136136
# Return None if the private attribute is unset
137137
return self._get_input_settings() if self._is_defined("_input_settings") else None
138138

139139
@input_settings.setter
140-
def input_settings(self, input_settings: dict):
140+
def input_settings(self, input_settings: dict | list[dict]):
141141
# Call deleter if agument is None
142142
if input_settings is None:
143143
del self.input_settings
144144
return
145145
# Display
146146
self._print("Input Settings --> ", end="", flush=True)
147147
# Check type
148-
if not isinstance(input_settings, dict):
149-
raise TypeError("`input_settings` should be a dictionary")
150-
148+
is_dict = isinstance(input_settings, dict)
149+
is_nonempty_dict_list = isinstance(input_settings, list) and input_settings and all(isinstance(item, dict) for item in input_settings)
150+
if not (is_dict or is_nonempty_dict_list):
151+
raise TypeError("`input_settings` should be a dictionary or a non-empty list of dictionaries")
152+
151153
# Check if each input can be converted to a string with valid characters and no empty strings
152-
self._check_invalid_input(input_settings)
153-
154-
# Parse the input settings
155-
new_settings = self._parse_input_settings(input_settings)
156-
self._print("parsed")
157-
# Check conflicts with private attribute
158-
self._check_value("_input_settings", new_settings)
159-
# Update
160-
self._input_settings = new_settings
154+
inputs = input_settings if is_nonempty_dict_list else [input_settings]
155+
new_input_settings = []
156+
for input_dict in inputs:
157+
self._check_invalid_input(input_dict)
158+
# Parse the input settings
159+
new_settings = self._parse_input_settings(input_dict)
160+
self._print("parsed")
161+
# Check conflicts with private attribute
162+
self._check_value("_input_settings", new_settings)
163+
# Update
164+
new_input_settings.append(new_settings)
165+
166+
self._input_settings = new_input_settings
161167

162168
@input_settings.deleter
163169
def input_settings(self) -> None:
@@ -308,7 +314,7 @@ def _pipeline_def(self) -> dict:
308314

309315
def __init__(
310316
self, output_dir=None, pipeline_id: str=None,
311-
input_settings: dict=None, session_name: str=None, verbose: bool=None
317+
input_settings: dict | list[dict]=None, session_name: str=None, verbose: bool=None
312318
) -> None:
313319
"""
314320
Create a VipLauncher instance and sets properties from keyword arguments.
@@ -439,7 +445,7 @@ def init(cls, api_key="VIP_API_KEY", verbose=True, vip_portal_url=None,
439445

440446
# Launch executions on VIP
441447
def launch_pipeline(
442-
self, pipeline_id: str=None, input_settings: dict=None, output_dir=None, nb_runs=1,
448+
self, pipeline_id: str=None, input_settings: dict | list[dict]=None, output_dir=None, nb_runs=1,
443449
) -> VipLauncher:
444450
"""
445451
Launches pipeline executions on VIP.
@@ -556,7 +562,7 @@ def monitor_workflows(self, refresh_time=30) -> VipLauncher:
556562
self._print("Run launch_pipeline() to launch workflows on VIP.")
557563
return self
558564
# Update existing workflows
559-
self._print("Updating worflow inventory ... ", end="", flush=True)
565+
self._print("Updating workflow inventory ... ", end="", flush=True)
560566
self._update_workflows()
561567
self._print("Done.")
562568
# Check if workflows are still running
@@ -565,7 +571,7 @@ def monitor_workflows(self, refresh_time=30) -> VipLauncher:
565571
self._execution_report()
566572
# Display standby
567573
self._print("\n-------------------------------------------------------------")
568-
self._print("The current proccess will wait until all executions are over.")
574+
self._print("The current process will wait until all executions are over.")
569575
self._print("Their progress can be monitored on VIP portal:")
570576
self._print(f"\t{self._VIP_PORTAL}")
571577
self._print("-------------------------------------------------------------")
@@ -1538,21 +1544,21 @@ def parse_value(input):
15381544
}
15391545

15401546
# Get the input settings after files are parsed as PathLib objects
1541-
def _get_input_settings(self, location="vip") -> dict:
1547+
def _get_input_settings(self, location="vip") -> list[dict]:
15421548
"""
15431549
Returns the input settings with their orignal values in string format.
15441550
`location` is destined to subclasses.
15451551
"""
15461552
if location != "vip":
15471553
raise NotImplementedError(f"Unknown location: {location}")
1548-
return {
1549-
key: [str(v) for v in value] if isinstance(value, list) else str(value)
1550-
for key, value in self._input_settings.items()
1551-
}
1554+
return [
1555+
{key: [str(v) for v in value] if isinstance(value, list) else str(value) for key, value in input_dict.items()}
1556+
for input_dict in self._input_settings
1557+
]
15521558
# ------------------------------------------------
15531559

15541560
# Check the input settings based on the pipeline descriptor
1555-
def _check_input_settings(self, input_settings: dict=None, location: str=None) -> None:
1561+
def _check_input_settings(self, input_settings: list[dict]=None, location: str=None) -> None:
15561562
"""
15571563
Checks `input_settings` with respect to pipeline descriptor. If not provided, checks the instance property.
15581564
Prerequisite: input_settings contains only strings or lists of strings.
@@ -1580,10 +1586,12 @@ def _check_input_settings(self, input_settings: dict=None, location: str=None) -
15801586
# Check the pipeline identifier
15811587
if not self._is_defined("_pipeline_id"):
15821588
raise AttributeError("Input settings could not be checked without a pipeline identifier.")
1583-
# Parameter names
1584-
self._check_input_keys(input_settings)
1585-
# Parameter values
1586-
self._check_input_values(input_settings, location=location)
1589+
1590+
for input_dict in input_settings:
1591+
# Parameter names
1592+
self._check_input_keys(input_dict)
1593+
# Parameter values
1594+
self._check_input_values(input_dict, location=location)
15871595
# Return True when all checks are complete
15881596
return True
15891597
# ------------------------------------------------

‎src/vip_client/classes/VipSession.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def _vip_dir(self) -> str:
218218
#############
219219
def __init__(
220220
self, session_name: str=None, input_dir=None, pipeline_id: str=None,
221-
input_settings: dict=None, output_dir=None, verbose: bool=None
221+
input_settings: dict | list[dict]=None, output_dir=None, verbose: bool=None
222222
) -> None:
223223
"""
224224
Create a VipSession instance and sets its properties from keyword arguments.
@@ -396,7 +396,7 @@ def upload_inputs(self, input_dir=None, update_files=True) -> VipSession:
396396

397397
# Launch executions on VIP
398398
def launch_pipeline(
399-
self, pipeline_id: str=None, input_settings: dict=None, nb_runs=1
399+
self, pipeline_id: str=None, input_settings: dict | list[dict]=None, nb_runs=1
400400
) -> VipSession:
401401
"""
402402
Launches pipeline executions on VIP.
@@ -1140,7 +1140,7 @@ def parse_value(input):
11401140
# ------------------------------------------------
11411141

11421142
# Get the input settings after they are parsed
1143-
def _get_input_settings(self, location="vip") -> dict:
1143+
def _get_input_settings(self, location="vip") -> list[dict]:
11441144
"""
11451145
Fits `self._input_settings` to `location`, i.e. write the input paths relatively to `location`.
11461146
Returns the modified settings.
@@ -1173,10 +1173,10 @@ def get_input(value, location) -> str:
11731173
if location not in ("vip", "local"):
11741174
raise NotImplementedError(f"Unknown location: {location}")
11751175
# Browse input settings
1176-
return {
1177-
key: get_input(value, location)
1178-
for key, value in self._input_settings.items()
1179-
}
1176+
return [
1177+
{key: get_input(value, location) for key, value in input_dict.items()}
1178+
for input_dict in self._input_settings
1179+
]
11801180
# ------------------------------------------------
11811181

11821182
def _update_input_settings(self) -> None:
@@ -1185,7 +1185,7 @@ def _update_input_settings(self) -> None:
11851185
This method does nothing if `input_settings` is unset.
11861186
"""
11871187
if self._is_defined('_input_settings'):
1188-
self._input_settings = self._parse_input_settings(self._input_settings)
1188+
self._input_settings = [self._parse_input_settings(input_dict) for input_dict in self._input_settings]
11891189
# ------------------------------------------------
11901190

11911191
# Function to convert a VIP path to local output directory

‎src/vip_client/utils/vip.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def count_executions()->int:
350350
return int(rq.text)
351351

352352
# -----------------------------------------------------------------------------
353-
def init_exec(pipeline, name="default", inputValues={}, resultsLocation="/vip/Home") -> str:
353+
def init_exec(pipeline, name="default", inputValues=[], resultsLocation="/vip/Home") -> str:
354354
url = __PREFIX + 'executions'
355355
headers = {
356356
'apikey': __apikey,
@@ -359,15 +359,15 @@ def init_exec(pipeline, name="default", inputValues={}, resultsLocation="/vip/Ho
359359
data_ = {
360360
"name": name,
361361
'pipelineIdentifier': pipeline,
362-
"inputValues": inputValues,
362+
"inputValues": [inputValues] if isinstance(inputValues, dict) else inputValues,
363363
"resultsLocation": resultsLocation
364364
}
365365
rq = SESSION.post(url, headers=headers, json=data_)
366366
manage_errors(rq)
367367
return rq.json()["identifier"]
368368
# -----------------------------------------------------------------------------
369369

370-
def init_exec_without_resultsLocation(pipeline, name="default", inputValues={}) -> str:
370+
def init_exec_without_resultsLocation(pipeline, name="default", inputValues=[]) -> str:
371371
"""Initiate executions with "results-directory" in the `inputValues`"""
372372
url = __PREFIX + 'executions'
373373
headers = {
@@ -377,7 +377,7 @@ def init_exec_without_resultsLocation(pipeline, name="default", inputValues={})
377377
data_ = {
378378
"name": name,
379379
'pipelineIdentifier': pipeline,
380-
"inputValues": inputValues
380+
"inputValues": [inputValues] if isinstance(inputValues, dict) else inputValues,
381381
}
382382
rq = requests.post(url, headers=headers, json=data_)
383383
manage_errors(rq)

‎tests/test_VipGirder.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,14 @@ def test_properties_interface(mocker):
163163
# Backup the inputs
164164
backup = s.input_settings
165165
# Run a subtest for each property
166-
for prop in s.input_settings:
167-
setattr(s, prop, None) # Calls deleter
168-
assert getattr(s, prop) is None # Public attribute must be None
169-
assert not s._is_defined("_" + prop) # Private attribute must be unset
170-
setattr(s, prop, backup[prop]) # Reset
166+
for i, map in enumerate(s.input_settings):
167+
for key, value in map.items():
168+
setattr(s, key, None) # Calls deleter
169+
assert getattr(s, key) is None # Public attribute must be None
170+
assert not s._is_defined("_" + key) # Private attribute must be unset
171+
setattr(s, key, backup[i][key]) # Reset
172+
171173
# Test correct reset
172-
for key, value in s.input_settings.items():
173-
assert getattr(s, key) == value
174+
for map in s.input_settings:
175+
for key, value in map.items():
176+
assert getattr(s, key) == value

‎tests/test_VipLauncher.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,14 @@ def test_properties_interface(mocker):
202202
# Backup the inputs
203203
backup = s.input_settings
204204
# Run a subtest for each property
205-
for prop in s.input_settings:
206-
setattr(s, prop, None) # Calls deleter
207-
assert getattr(s, prop) is None # Public attribute must be None
208-
assert not s._is_defined("_" + prop) # Private attribute must be unset
209-
setattr(s, prop, backup[prop]) # Reset
205+
for i, map in enumerate(s.input_settings):
206+
for key, value in map.items():
207+
setattr(s, key, None) # Calls deleter
208+
assert getattr(s, key) is None # Public attribute must be None
209+
assert not s._is_defined("_" + key) # Private attribute must be unset
210+
setattr(s, key, backup[i][key]) # Reset
211+
210212
# Test correct reset
211-
for key, value in s.input_settings.items():
212-
assert getattr(s, key) == value
213+
for map in s.input_settings:
214+
for key, value in map.items():
215+
assert getattr(s, key) == value

‎tests/test_VipSession.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,14 @@ def test_properties_interface(mocker):
189189
# Backup the inputs
190190
backup = s.input_settings
191191
# Run a subtest for each property
192-
for prop in s.input_settings:
193-
setattr(s, prop, None) # Calls deleter
194-
assert getattr(s, prop) is None # Public attribute must be None
195-
assert not s._is_defined("_" + prop) # Private attribute must be unset
196-
setattr(s, prop, backup[prop]) # Reset
192+
for i, map in enumerate(s.input_settings):
193+
for key, value in map.items():
194+
setattr(s, key, None) # Calls deleter
195+
assert getattr(s, key) is None # Public attribute must be None
196+
assert not s._is_defined("_" + key) # Private attribute must be unset
197+
setattr(s, key, backup[i][key]) # Reset
198+
197199
# Test correct reset
198-
for key, value in s.input_settings.items():
199-
assert getattr(s, key) == value
200+
for map in s.input_settings:
201+
for key, value in map.items():
202+
assert getattr(s, key) == value

0 commit comments

Comments
 (0)