Skip to content

Commit 3839852

Browse files
committed
resolving issues mentioned in issue #114 with water_entropy argument beign ignored:
- within the `arg_config_manager.py` a new function `str2bool` ensures all types of boolean are accpeted - `setup_argsparse` has been updated to ensure if `property` is a type `bool` it is handled correctly - additional test cases to ensure that the modified `setup_argsparse` and the `str2bool` function works as intended
1 parent 04b927f commit 3839852

3 files changed

Lines changed: 113 additions & 3 deletions

File tree

CodeEntropy/config/arg_config_manager.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,54 @@ def load_config(self, file_path):
8585

8686
return config
8787

88+
def str2bool(self, value):
89+
"""
90+
Convert a string or boolean input into a boolean value.
91+
92+
Accepts common string representations of boolean values such as:
93+
- True values: "true", "t", "yes", "1"
94+
- False values: "false", "f", "no", "0"
95+
96+
If the input is already a boolean, it is returned as-is.
97+
Raises:
98+
argparse.ArgumentTypeError: If the input cannot be interpreted as a boolean.
99+
100+
Args:
101+
value (str or bool): The input value to convert.
102+
103+
Returns:
104+
bool: The corresponding boolean value.
105+
"""
106+
if isinstance(value, bool):
107+
return value
108+
value = value.lower()
109+
if value in {"true", "t", "yes", "1"}:
110+
return True
111+
elif value in {"false", "f", "no", "0"}:
112+
return False
113+
else:
114+
raise argparse.ArgumentTypeError("Boolean value expected (True/False).")
115+
88116
def setup_argparse(self):
89117
"""Setup argument parsing dynamically based on arg_map."""
90118
parser = argparse.ArgumentParser(
91119
description="CodeEntropy: Entropy calculation with MCC method."
92120
)
93121

94122
for arg, properties in self.arg_map.items():
95-
kwargs = {key: properties[key] for key in properties if key != "help"}
96-
parser.add_argument(f"--{arg}", **kwargs, help=properties.get("help"))
123+
help_text = properties.get("help", "")
124+
default = properties.get("default", None)
125+
126+
if properties.get("type") == bool:
127+
parser.add_argument(
128+
f"--{arg}",
129+
type=self.str2bool,
130+
default=default,
131+
help=f"{help_text} (default: {default})",
132+
)
133+
else:
134+
kwargs = {k: v for k, v in properties.items() if k != "help"}
135+
parser.add_argument(f"--{arg}", **kwargs, help=help_text)
97136

98137
return parser
99138

config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ run1:
1212
thread:
1313
output_file:
1414
force_partitioning:
15-
disable_water_entropy:
15+
water_entropy:

tests/test_CodeEntropy/test_arg_config_manager.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,77 @@ def test_setup_argparse(self, mock_args):
137137
self.assertEqual(args.top_traj_file, ["/path/to/tpr", "/path/to/trr"])
138138
self.assertEqual(args.selection_string, "all")
139139

140+
@patch(
141+
"argparse.ArgumentParser.parse_args",
142+
return_value=MagicMock(
143+
top_traj_file=["/path/to/tpr", "/path/to/trr"],
144+
start=10,
145+
water_entropy=False,
146+
),
147+
)
148+
def test_setup_argparse_false_boolean(self, mock_args):
149+
"""
150+
Test that non-boolean arguments are parsed correctly.
151+
"""
152+
arg_config = ConfigManager()
153+
parser = arg_config.setup_argparse()
154+
args = parser.parse_args()
155+
156+
self.assertEqual(args.top_traj_file, ["/path/to/tpr", "/path/to/trr"])
157+
self.assertEqual(args.start, 10)
158+
self.assertFalse(args.water_entropy)
159+
160+
def test_str2bool_true_variants(self):
161+
"""Test that various string representations of True are correctly parsed."""
162+
arg_config = ConfigManager()
163+
164+
self.assertTrue(arg_config.str2bool("true"))
165+
self.assertTrue(arg_config.str2bool("True"))
166+
self.assertTrue(arg_config.str2bool("t"))
167+
self.assertTrue(arg_config.str2bool("yes"))
168+
self.assertTrue(arg_config.str2bool("1"))
169+
170+
def test_str2bool_false_variants(self):
171+
"""Test that various string representations of False are correctly parsed."""
172+
arg_config = ConfigManager()
173+
174+
self.assertFalse(arg_config.str2bool("false"))
175+
self.assertFalse(arg_config.str2bool("False"))
176+
self.assertFalse(arg_config.str2bool("f"))
177+
self.assertFalse(arg_config.str2bool("no"))
178+
self.assertFalse(arg_config.str2bool("0"))
179+
180+
def test_str2bool_boolean_passthrough(self):
181+
"""Test that boolean values passed directly are returned unchanged."""
182+
arg_config = ConfigManager()
183+
184+
self.assertTrue(arg_config.str2bool(True))
185+
self.assertFalse(arg_config.str2bool(False))
186+
187+
def test_str2bool_invalid_input(self):
188+
"""Test that invalid string inputs raise an ArgumentTypeError."""
189+
arg_config = ConfigManager()
190+
191+
with self.assertRaises(Exception) as context:
192+
arg_config.str2bool("maybe")
193+
self.assertIn("Boolean value expected", str(context.exception))
194+
195+
def test_str2bool_empty_string(self):
196+
"""Test that an empty string raises an ArgumentTypeError."""
197+
arg_config = ConfigManager()
198+
199+
with self.assertRaises(Exception) as context:
200+
arg_config.str2bool("")
201+
self.assertIn("Boolean value expected", str(context.exception))
202+
203+
def test_str2bool_unexpected_number(self):
204+
"""Test that unexpected numeric strings raise an ArgumentTypeError."""
205+
arg_config = ConfigManager()
206+
207+
with self.assertRaises(Exception) as context:
208+
arg_config.str2bool("2")
209+
self.assertIn("Boolean value expected", str(context.exception))
210+
140211
def test_cli_overrides_defaults(self):
141212
"""
142213
Test if CLI parameters override default values.

0 commit comments

Comments
 (0)