예제 #1
0
    def load(self) -> None:
        """Load configuration from the configured YAML file."""
        try:
            with open(self._filename, 'r', encoding='utf-8') as f:
                yaml_data = utils.yaml_load(f)
        except FileNotFoundError:
            return
        except OSError as e:
            desc = configexc.ConfigErrorDesc("While reading", e)
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
        except yaml.YAMLError as e:
            desc = configexc.ConfigErrorDesc("While parsing", e)
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])

        config_version = self._pop_object(yaml_data, 'config_version', int)
        if config_version == 1:
            settings = self._load_legacy_settings_object(yaml_data)
            self._mark_changed()
        elif config_version > self.VERSION:
            desc = configexc.ConfigErrorDesc(
                "While reading",
                "Can't read config from incompatible newer version")
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
        else:
            settings = self._load_settings_object(yaml_data)
            self._dirty = False

        settings = self._handle_migrations(settings)
        self._validate(settings)
        self._build_values(settings)
예제 #2
0
def test_late_init(init_patch, monkeypatch, fake_save_manager, args, mocker,
                   errors):
    configinit.early_init(args)
    if errors:
        err = configexc.ConfigErrorDesc("Error text", Exception("Exception"))
        errs = configexc.ConfigFileErrors("config.py", [err])
        monkeypatch.setattr(configinit, '_init_errors', errs)
    msgbox_mock = mocker.patch(
        'glimpsebrowser.config.configinit.msgbox.msgbox', autospec=True)

    configinit.late_init(fake_save_manager)

    fake_save_manager.add_saveable.assert_any_call('state-config',
                                                   unittest.mock.ANY)
    fake_save_manager.add_saveable.assert_any_call('yaml-config',
                                                   unittest.mock.ANY,
                                                   unittest.mock.ANY)
    if errors:
        assert len(msgbox_mock.call_args_list) == 1
        _call_posargs, call_kwargs = msgbox_mock.call_args_list[0]
        text = call_kwargs['text'].strip()
        assert text.startswith('Errors occurred while reading config.py:')
        assert '<b>Error text</b>: Exception' in text
    else:
        assert not msgbox_mock.called
예제 #3
0
 def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
     """Catch config-related exceptions and save them in self.errors."""
     try:
         yield
     except configexc.ConfigFileErrors as e:
         for err in e.errors:
             new_err = err.with_text(e.basename)
             self.errors.append(new_err)
     except configexc.Error as e:
         text = "While {} '{}'".format(action, name)
         self.errors.append(configexc.ConfigErrorDesc(text, e))
     except urlmatch.ParseError as e:
         text = "While {} '{}' and parsing pattern".format(action, name)
         self.errors.append(configexc.ConfigErrorDesc(text, e))
     except keyutils.KeyParseError as e:
         text = "While {} '{}' and parsing key".format(action, name)
         self.errors.append(configexc.ConfigErrorDesc(text, e))
예제 #4
0
 def _handle_error(self, action: str, name: str) -> typing.Iterator[None]:
     try:
         yield
     except configexc.Error as e:
         if self._configapi is None:
             raise
         text = "While {} '{}'".format(action, name)
         self._configapi.errors.append(configexc.ConfigErrorDesc(text, e))
예제 #5
0
def read_autoconfig() -> None:
    """Read the autoconfig.yml file."""
    try:
        config.instance.read_yaml()
    except configexc.ConfigFileErrors:
        raise  # caught in outer block
    except configexc.Error as e:
        desc = configexc.ConfigErrorDesc("Error", e)
        raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
예제 #6
0
    def _validate(self, settings: _SettingsType) -> None:
        """Make sure all settings exist."""
        unknown = []
        for name in settings:
            if name not in configdata.DATA:
                unknown.append(name)

        if unknown:
            errors = [
                configexc.ConfigErrorDesc("While loading options",
                                          "Unknown option {}".format(e))
                for e in sorted(unknown)
            ]
            raise configexc.ConfigFileErrors('autoconfig.yml', errors)
예제 #7
0
    def _pop_object(self, yaml_data: typing.Any, key: str,
                    typ: type) -> typing.Any:
        """Get a global object from the given data."""
        if not isinstance(yaml_data, dict):
            desc = configexc.ConfigErrorDesc("While loading data",
                                             "Toplevel object is not a dict")
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])

        if key not in yaml_data:
            desc = configexc.ConfigErrorDesc(
                "While loading data",
                "Toplevel object does not contain '{}' key".format(key))
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])

        data = yaml_data.pop(key)

        if not isinstance(data, typ):
            desc = configexc.ConfigErrorDesc(
                "While loading data",
                "'{}' object is not a {}".format(key, typ.__name__))
            raise configexc.ConfigFileErrors('autoconfig.yml', [desc])

        return data
예제 #8
0
    def _build_values(self, settings: typing.Mapping) -> None:
        """Build up self._values from the values in the given dict."""
        errors = []
        for name, yaml_values in settings.items():
            if not isinstance(yaml_values, dict):
                errors.append(
                    configexc.ConfigErrorDesc(
                        "While parsing {!r}".format(name),
                        "value is not a dict"))
                continue

            values = configutils.Values(configdata.DATA[name])
            if 'global' in yaml_values:
                values.add(yaml_values.pop('global'))

            for pattern, value in yaml_values.items():
                if not isinstance(pattern, str):
                    errors.append(
                        configexc.ConfigErrorDesc(
                            "While parsing {!r}".format(name),
                            "pattern is not of type string"))
                    continue
                try:
                    urlpattern = urlmatch.UrlPattern(pattern)
                except urlmatch.ParseError as e:
                    errors.append(
                        configexc.ConfigErrorDesc(
                            "While parsing pattern {!r} for {!r}".format(
                                pattern, name), e))
                    continue
                values.add(value, urlpattern)

            self._values[name] = values

        if errors:
            raise configexc.ConfigFileErrors('autoconfig.yml', errors)
예제 #9
0
def errors():
    """Get a ConfigFileErrors object."""
    err1 = configexc.ConfigErrorDesc("Error text 1", Exception("Exception 1"))
    err2 = configexc.ConfigErrorDesc("Error text 2", Exception("Exception 2"),
                                     "Fake traceback")
    return configexc.ConfigFileErrors("config.py", [err1, err2])
예제 #10
0
def test_desc_with_text():
    """Test ConfigErrorDesc.with_text."""
    old = configexc.ConfigErrorDesc("Error text", Exception("Exception text"))
    new = old.with_text("additional text")
    assert str(new) == 'Error text (additional text): Exception text'
예제 #11
0
def read_config_py(filename: str, raising: bool = False) -> None:
    """Read a config.py file.

    Arguments;
        filename: The name of the file to read.
        raising: Raise exceptions happening in config.py.
                 This is needed during tests to use pytest's inspection.
    """
    assert config.instance is not None
    assert config.key_instance is not None

    api = ConfigAPI(config.instance, config.key_instance)
    container = config.ConfigContainer(config.instance, configapi=api)
    basename = os.path.basename(filename)

    module = types.ModuleType('config')
    module.config = api  # type: ignore
    module.c = container  # type: ignore
    module.__file__ = filename

    try:
        with open(filename, mode='rb') as f:
            source = f.read()
    except OSError as e:
        text = "Error while reading {}".format(basename)
        desc = configexc.ConfigErrorDesc(text, e)
        raise configexc.ConfigFileErrors(basename, [desc])

    try:
        code = compile(source, filename, 'exec')
    except ValueError as e:
        # source contains NUL bytes
        desc = configexc.ConfigErrorDesc("Error while compiling", e)
        raise configexc.ConfigFileErrors(basename, [desc])
    except SyntaxError as e:
        desc = configexc.ConfigErrorDesc("Unhandled exception",
                                         e,
                                         traceback=traceback.format_exc())
        raise configexc.ConfigFileErrors(basename, [desc])

    try:
        # Save and restore sys variables
        with saved_sys_properties():
            # Add config directory to python path, so config.py can import
            # other files in logical places
            config_dir = os.path.dirname(filename)
            if config_dir not in sys.path:
                sys.path.insert(0, config_dir)

            exec(code, module.__dict__)
    except Exception as e:
        if raising:
            raise
        api.errors.append(
            configexc.ConfigErrorDesc("Unhandled exception",
                                      exception=e,
                                      traceback=traceback.format_exc()))

    api.finalize()
    config.instance.config_py_loaded = True

    if api.errors:
        raise configexc.ConfigFileErrors('config.py', api.errors)