Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions bin/deepstate/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ def parse_args(cls) -> Optional[argparse.Namespace]:
if args.config:
_args.update(cls.build_from_config(args.config)) # type: ignore

# configparser gives us strings -- apply argparse types (issue #311)
cls._coerce_config_types(parser, _args)

# Cleanup: force --no_exit_compile to be on, meaning if user specifies a `[test]` section,
# execution will continue. Delete config as well
_args["no_exit_compile"] = True # type: ignore
Expand Down Expand Up @@ -269,6 +272,32 @@ def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, inc
return context # type: ignore


@staticmethod
def _coerce_config_types(parser: argparse.ArgumentParser,
_args: Dict[str, Any]) -> None:
"""Apply argparse types to values loaded from a config file (issue #311)."""
for action in parser._actions:
if action.dest not in _args:
continue
value = _args[action.dest]
if not isinstance(value, str):
continue

if isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction)):
v = value.strip().lower()
if v in ("true", "1", "yes"):
_args[action.dest] = True
elif v in ("false", "0", "no"):
_args[action.dest] = False
else:
raise AnalysisBackendError(f"Invalid boolean for --{action.dest}: {value!r}")
elif action.type not in (None, str):
try:
_args[action.dest] = action.type(value)
except (ValueError, TypeError):
raise AnalysisBackendError(f"Invalid value for --{action.dest}: {value!r}")


def init_from_dict(self, _args: Optional[Dict[str, str]] = None) -> None:
"""
Builder initialization routine used to instantiate the attributes of the frontend object, either from the stored
Expand Down
45 changes: 45 additions & 0 deletions tests/test_config_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import argparse
import unittest

from deepstate.core.base import AnalysisBackend, AnalysisBackendError


def make_parser():
p = argparse.ArgumentParser()
p.add_argument("--timeout", type=int, default=0)
p.add_argument("--ratio", type=float, default=0.0)
p.add_argument("--blackbox", action="store_true")
return p


class ConfigTypesTest(unittest.TestCase):

def test_int(self):
args = {"timeout": "3600"}
AnalysisBackend._coerce_config_types(make_parser(), args)
self.assertEqual(args["timeout"], 3600)
self.assertIsInstance(args["timeout"], int)

def test_float(self):
args = {"ratio": "0.25"}
AnalysisBackend._coerce_config_types(make_parser(), args)
self.assertEqual(args["ratio"], 0.25)

def test_bool_true(self):
args = {"blackbox": "true"}
AnalysisBackend._coerce_config_types(make_parser(), args)
self.assertTrue(args["blackbox"])

def test_bool_false(self):
args = {"blackbox": "false"}
AnalysisBackend._coerce_config_types(make_parser(), args)
self.assertFalse(args["blackbox"])

def test_invalid_int_raises(self):
args = {"timeout": "not a number"}
with self.assertRaises(AnalysisBackendError):
AnalysisBackend._coerce_config_types(make_parser(), args)


if __name__ == "__main__":
unittest.main()