示例#1
0
    def test_create_from_all_supported_data_types_correctly(self):
        class _Config(object):

            DEFAULT_BOOL = True

            def __init__(self):
                self._bool = self.DEFAULT_BOOL
                self._float = None
                self._int = None
                self._str = None

            @property
            def bool(self) -> bool:
                return self._bool

            @bool.setter
            def bool(self, x: bool) -> None:
                self._bool = x

            @property
            def float(self) -> float:
                return self._float

            @float.setter
            def float(self, x: float) -> None:
                self._float = x

            @property
            def int(self) -> int:
                return self._int

            @int.setter
            def int(self, x: int) -> None:
                self._int = x

            @property
            def str(self) -> str:
                return self._str

            @str.setter
            def str(self, x: str) -> None:
                self._str = x

        spec = config_spec.ConfigSpec.create_from(_Config)
        self.assertEqual(4, len(spec))
        self.assertEqual(
            value_spec.ValueSpec("bool", "No description available.", bool,
                                 False, True), spec.get_value_by_name("bool"))
        self.assertEqual(
            value_spec.ValueSpec("float", "No description available.", float,
                                 True, None), spec.get_value_by_name("float"))
        self.assertEqual(
            value_spec.ValueSpec("int", "No description available.", int, True,
                                 None), spec.get_value_by_name("int"))
        self.assertEqual(
            value_spec.ValueSpec("str", "No description available.", str, True,
                                 None), spec.get_value_by_name("str"))
示例#2
0
    def setUp(self):

        self.spec = config_spec.ConfigSpec()
        self.spec.add_value(
            value_spec.ValueSpec("conf_1", "No description available.", bool,
                                 False, False))
        self.spec.add_value(
            value_spec.ValueSpec("conf_2", "No description available.", int,
                                 True, None))

        self.parser = magiq_parser.MagiqParser(_TestConfig, "name",
                                               "description")
示例#3
0
    def test_add_value_stores_value_specs_as_expected(self):

        spec = config_spec.ConfigSpec()
        value_1 = value_spec.ValueSpec("val-1", "val-1", str, False, None)
        value_2 = value_spec.ValueSpec("val-2", "val-2", str, False, None)

        self.assertEqual(0, len(spec))

        spec.add_value(value_1)
        self.assertEqual(1, len(spec))
        self.assertEqual({value_1}, set(spec))

        spec.add_value(value_2)
        self.assertEqual(2, len(spec))
        self.assertEqual({value_1, value_2}, set(spec))
示例#4
0
    def test_parse_raises_a_value_error_if_an_illegal_value_is_provided(self):

        parser = float_parser.FloatParser(value_spec.ValueSpec("some_config", "Just a test", float, True, None))
        argv = "--some-config", "not-a-number"

        self.assertTrue(parser.fires(argv))
        with self.assertRaises(ValueError):
            parser._parse(argv)
示例#5
0
    def test_get_value_by_name_retrieves_existing_config_values_as_expected(
            self):

        spec = config_spec.ConfigSpec()
        value = value_spec.ValueSpec("val", "val", str, False, None)
        spec.add_value(value)

        self.assertIs(value, spec.get_value_by_name("val"))
    def test_parse_json_returns_the_provided_value_if_its_of_the_correct_type(
            self):

        parser = _DummyParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        self.assertEqual("works", parser.parse_json("works"))
示例#7
0
    def test_parse_raises_a_value_error_if_an_expected_value_is_missing(self):

        parser = float_parser.FloatParser(value_spec.ValueSpec("some_config", "Just a test", float, True, None))
        argv = "--some-config",

        self.assertTrue(parser.fires(argv))
        with self.assertRaises(ValueError):
            parser._parse(argv)
    def test_parse_json_raises_a_type_error_if_the_provided_value_does_not_comply_with_the_spec(
            self):

        parser = _DummyParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        with self.assertRaises(TypeError):
            parser.parse_json(123)
    def test_parse_raises_a_value_error_if_the_parser_does_not_fire(self):

        parser = _DummyParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        with self.assertRaises(ValueError):
            parser.parse(("--smth-else-first", "--some-config", "value"))
        with self.assertRaises(ValueError):
            parser.parse(("smth different entirely", ))
示例#10
0
    def test_parse_raises_a_value_error_if_an_expected_value_is_missing(self):

        parser = str_parser.StrParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        with self.assertRaises(ValueError):

            self.assertEqual(("value", tuple()),
                             parser._parse(("--some-config", )))
示例#11
0
    def test_parse_extracts_provided_args_as_expected(self):

        parser = str_parser.StrParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        self.assertEqual(("value", ("--another-config", "another value")),
                         parser._parse(("--some-config", "value",
                                        "--another-config", "another value")))
        self.assertEqual(("value", tuple()),
                         parser._parse(("--some-config", "value")))
示例#12
0
    def test_parse_json_processes_values_correctly(self):

        parser = float_parser.FloatParser(value_spec.ValueSpec("some_config", "Just a test", float, True, None))

        value = parser.parse_json(666.666)
        self.assertIsInstance(value, float)
        self.assertEqual(666.666, value)

        value = parser.parse_json(666)
        self.assertIsInstance(value, float)
        self.assertEqual(666.0, value)
    def test_fires_yields_the_expected_values(self):

        parser = _DummyParser(
            value_spec.ValueSpec("some_config", "Just a test", str, True,
                                 None))

        self.assertTrue(parser.fires(("--some-config", )))
        self.assertTrue(parser.fires(("--some-config", "value")))
        self.assertFalse(
            parser.fires(("--smth-else-first", "--some-config", "value")))
        self.assertFalse(parser.fires(("smth different entirely", )))
示例#14
0
    def test_parse_extracts_legal_integer_args_as_expected(self):

        parser = int_parser.IntParser(
            value_spec.ValueSpec("some_config", "Just a test", int, True,
                                 None))

        value, argv = parser._parse(
            ("--some-config", "666", "--another-config", "another value"))
        self.assertIsInstance(value, int)
        self.assertEqual((666, ("--another-config", "another value")),
                         (value, argv))

        value, argv = parser._parse(("--some-config", "666"))
        self.assertIsInstance(value, int)
        self.assertEqual((666, tuple()), (value, argv))
示例#15
0
    def test_parse_extracts_legal_values_as_expected(self):

        parser = float_parser.FloatParser(value_spec.ValueSpec("some_config", "Just a test", float, True, None))

        value, argv = parser._parse(("--some-config", "666.666", "--another-config", "another value"))
        self.assertIsInstance(value, float)
        self.assertEqual((666.666, ("--another-config", "another value")), (value, argv))

        value, argv = parser._parse(("--some-config", "666.666"))
        self.assertIsInstance(value, float)
        self.assertEqual((666.666, tuple()), (value, argv))

        value, argv = parser._parse(("--some-config", "666"))
        self.assertIsInstance(value, float)
        self.assertEqual((666.0, tuple()), (value, argv))
示例#16
0
    def test_create_from_the_generic_optional_alias_correctly(self):
        class _Config(object):

            DEFAULT_CONF_WITH_DEFAULT_VALUE = 123

            def __init__(self):
                self._value = None

            @property
            def value(self) -> typing.Optional[int]:
                return self._value

            @value.setter
            def value(self, value: int) -> None:
                self._value = value

        spec = config_spec.ConfigSpec.create_from(_Config)
        self.assertEqual(1, len(spec))
        self.assertEqual(
            value_spec.ValueSpec("value", "No description available.", int,
                                 True, None), spec.get_value_by_name("value"))
示例#17
0
    def create_from(cls, config_cls: type):
        """A factory method for creating a configuration specification based on the provided class.

        The created specification defines one option for each property (i.e., methods annotated with ``@property``) of
        the given class except those that start with an underscore (i.e., private properties). Class-level fields whose
        names start with :attr:`argmagiq.DEFAULT_PREFIX` are assumed to define default values for options. Type and
        description for each of the options are extracted from the according property's type annotations and
        docstring.

        Args:
            config_cls (type): The class that the configuration is based on.
        """

        spec = ConfigSpec()

        # load all default values
        default_values = {}
        for name, field in inspect.getmembers(
                config_cls, lambda f: not inspect.isroutine(f)):
            if name.startswith(argmagiq.DEFAULT_PREFIX):
                field_name = name[len(argmagiq.DEFAULT_PREFIX):].lower()
                default_values[field_name] = field

        # find all public mutable properties -> these are considered as config values
        for name, field in inspect.getmembers(
                config_cls, lambda f: isinstance(f, property)):

            # only consider public properties that have an according setter method
            if name.startswith("_") or field.fset is None:
                continue

            # check if the config has a default value specified
            default_value = None
            if name in default_values:
                default_value = default_values[name]

            # check if the option is required
            required = (default_value is None
                        and not (argmagiq.OPTIONAL_KEY in field.fget.__dict__))

            # fetch docstring as description of the config
            if field.__doc__ is None:
                description = "No description available."
            else:
                m = re.match(
                    cls.DOC_REGEX, field.__doc__
                )  # -> this is done to remove any type specifications
                description = m.group(
                    "doc") if m else "No description available."

            # determine the option's data type from the properties type annotation
            sig = inspect.signature(field.fget)
            if sig.return_annotation is None:
                raise ValueError(
                    f"Property <{name}> does not have a return-type annotation"
                )
            data_type = sig.return_annotation

            # if the datatype is a generic alias, then we have to extract the actual data type from it
            if not isinstance(data_type,
                              type):  # -> the type is a generic alias

                # check if the data type is a union
                # -> this also covers typing.Optional[X], which is a shorthand for typing.Union[X, None]
                if "__origin__" in data_type.__dict__ and data_type.__dict__[
                        "__origin__"] == typing.Union:

                    # gather all types in the union except None
                    all_types = [
                        t for t in data_type.__dict__["__args__"]
                        if t is not type(None)
                    ]

                    # ensure that there is just one type left
                    if len(all_types) == 1:
                        data_type = all_types[0]
                    else:
                        raise ValueError(
                            f"Property <{name}> has an ambiguous data type")

                else:

                    # since no other generic aliases are supported, raise an error
                    raise ValueError(
                        f"Property <{name}> has an unsupported generic-alias type: {data_type}"
                    )

            # add the parsed information to the config spec
            spec.add_value(
                value_spec.ValueSpec(name, description, data_type, required,
                                     default_value))

        return spec