Skip to content

Commit 8100715

Browse files
authored
Merge pull request #712 from MannLabs/multiple_config_dicts
allow for multiple config dicts
2 parents f44507a + e815bb7 commit 8100715

File tree

3 files changed

+50
-27
lines changed

3 files changed

+50
-27
lines changed

alphadia/cli.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"--directory",
6868
"-d",
6969
type=str,
70-
help="Directory containing raw data input files.",
70+
help="Directory containing raw data input files. Can be passed multiple times.",
7171
action="append",
7272
default=[],
7373
)
@@ -107,9 +107,10 @@
107107
"--config-dict",
108108
type=str,
109109
help="Python dictionary which will be used to update the default config. Keys and string values need to be surrounded by "
110-
'escaped double quotes, e.g. "{\\"key1\\": \\"value1\\"}".',
110+
'escaped double quotes, e.g. "{\\"key1\\": \\"value1\\"}". Can be passed multiple times (later items take precedence in case of key clashes).',
111111
nargs="?",
112-
default="{}",
112+
action="append",
113+
default=[],
113114
)
114115
parser.add_argument(
115116
"--quant-dir", # TODO deprecate
@@ -121,47 +122,49 @@
121122
)
122123

123124

124-
def _recursive_update(
125-
full_dict: dict, update_dict: dict
126-
): # TODO merge with Config._update
127-
"""recursively update a dict with a second dict. The dict is updated inplace.
125+
def _recursive_update(target_dict: dict, source_dict: dict):
126+
"""Recursively update a dict with a second dict inplace.
128127
129128
Parameters
130129
----------
131-
full_dict : dict
130+
target_dict : dict
132131
dict to be updated, is updated inplace.
133132
134-
update_dict : dict
133+
source_dict : dict
135134
dict with new values
136-
137135
"""
138-
for key, value in update_dict.items():
139-
if key in full_dict:
140-
if isinstance(value, dict):
141-
_recursive_update(full_dict[key], update_dict[key])
142-
else:
143-
full_dict[key] = value
136+
137+
for key, value in source_dict.items():
138+
if key not in target_dict:
139+
target_dict[key] = {}
140+
if isinstance(value, dict):
141+
_recursive_update(target_dict[key], value)
144142
else:
145-
full_dict[key] = value
143+
target_dict[key] = value
146144

147145

148146
def _get_config_from_args(
149147
args: argparse.Namespace,
150-
) -> tuple[dict, str | None, str | None]:
148+
) -> tuple[dict, str | None, dict]:
151149
"""Parse config file from `args.config` if given and update with optional JSON string `args.config_dict`."""
152150

153151
config = {}
154152
if args.config is not None:
155153
with open(args.config) as f:
156154
config = yaml.safe_load(f)
157155

156+
merged_config_dict = {}
158157
if args.config_dict:
159158
try:
160-
_recursive_update(config, json.loads(args.config_dict))
159+
for config_dict_str in args.config_dict:
160+
config_dict = json.loads(config_dict_str)
161+
_recursive_update(merged_config_dict, config_dict)
162+
163+
_recursive_update(config, merged_config_dict)
161164
except Exception as e:
162165
raise ValueError(f"Could not parse config dict: {e}") from e
163166

164-
return config, args.config, args.config_dict
167+
return config, args.config, merged_config_dict
165168

166169

167170
def _get_from_args_or_config(

docs/methods/command-line.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Sometimes, for example when optimizing a single parameter or building multi step
106106
```
107107
--config-dict "{\"library_prediction\":{\"nce\":26}}"
108108
```
109+
Note: this can be passed multiple times, with the later ones taking precedence in case of overlapping keys.
109110

110111
### 5. Summary
111112
The full script looks like:
@@ -146,7 +147,7 @@ can be used to specify the directory containing quantification results.
146147
On startup, the current configuration is dumped as `frozen_config.yaml`, which contains all information to reproduce this run.
147148

148149
Combining these three concepts, here's an example how to reuse an existing quantification (from the `previous_run` directory), but create additional
149-
output (`peptide_level_lfq`)
150+
output (`peptide_level_lfq`) through a custom `--config-dict`:
150151
```
151152
alphadia -o ./output_dir --quant-dir ./previous_run/quant --config ./previous_run/frozen_config.yaml --config-dict '{"general": {"reuse_quant": "True"}, "search_output": {"peptide_level_lfq": "True"}}'
152153
```

tests/unit_tests/test_cli.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,35 @@ def test_get_config_from_args():
3333

3434
def test_get_config_from_config_dict():
3535
"""Test the _get_config_from_args function correctly parses config dict."""
36-
mock_args = MagicMock(config=None, config_dict='{"key3": "value3"}')
36+
mock_args = MagicMock(config=None, config_dict=['{"key3": "value3"}'])
3737

3838
result = _get_config_from_args(mock_args)
3939

40-
assert result == ({"key3": "value3"}, None, '{"key3": "value3"}')
40+
assert result == ({"key3": "value3"}, None, {"key3": "value3"})
41+
42+
43+
def test_get_config_from_config_dict_multiple():
44+
"""Test the _get_config_from_args function correctly parses multiple config dicts."""
45+
mock_args = MagicMock(
46+
config=None,
47+
config_dict=[
48+
'{"key3": "value3", "key4": "value4"}',
49+
'{"key4": "value4b", "key5": "value5"}',
50+
],
51+
)
52+
53+
result = _get_config_from_args(mock_args)
54+
55+
assert result == (
56+
{"key3": "value3", "key4": "value4b", "key5": "value5"},
57+
None,
58+
{"key3": "value3", "key4": "value4b", "key5": "value5"},
59+
)
4160

4261

4362
def test_get_config_from_args_and_config_dict():
4463
"""Test the _get_config_from_args function correctly merges config file and dict."""
45-
mock_args = MagicMock(config="config.yaml", config_dict='{"key3": "value3"}')
64+
mock_args = MagicMock(config="config.yaml", config_dict=['{"key3": "value3"}'])
4665

4766
yaml_content = {"key1": "value1", "key2": "value2"}
4867
mock_yaml = yaml.dump(yaml_content)
@@ -53,7 +72,7 @@ def test_get_config_from_args_and_config_dict():
5372
assert result == (
5473
{"key1": "value1", "key2": "value2", "key3": "value3"},
5574
"config.yaml",
56-
'{"key3": "value3"}',
75+
{"key3": "value3"},
5776
)
5877

5978

@@ -109,7 +128,7 @@ def test_cli_minimal_args(mock_parse_known_args):
109128
version=None,
110129
check=None,
111130
output="/output",
112-
config_dict="{}",
131+
config_dict=[],
113132
file=[],
114133
directory=[],
115134
regex=".*",
@@ -156,7 +175,7 @@ def test_cli_minimal_args_all_none(mock_parse_known_args):
156175
fasta=None,
157176
library=None,
158177
quant_dir=None,
159-
config_dict="{}",
178+
config_dict=[],
160179
file=[],
161180
directory=[],
162181
regex=".*",

0 commit comments

Comments
 (0)