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)
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
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))
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))
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])
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)
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
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)
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])
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'
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)