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 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 load(self): """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]) try: global_obj = yaml_data['global'] except KeyError: desc = configexc.ConfigErrorDesc( "While loading data", "Toplevel object does not contain 'global' key") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) except TypeError: desc = configexc.ConfigErrorDesc("While loading data", "Toplevel object is not a dict") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) if not isinstance(global_obj, dict): desc = configexc.ConfigErrorDesc("While loading data", "'global' object is not a dict") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) self._values = global_obj self._dirty = False self._handle_migrations()
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() if api.errors: raise configexc.ConfigFileErrors('config.py', api.errors)
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 test_late_init(self, 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]) if errors == 'fatal': errs.fatal = True monkeypatch.setattr(configinit, '_init_errors', errs) msgbox_mock = mocker.patch( 'qutebrowser.config.configinit.msgbox.msgbox', autospec=True) exit_mock = mocker.patch( 'qutebrowser.config.configinit.sys.exit', 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 assert exit_mock.called == (errors == 'fatal') else: assert not msgbox_mock.called
def early_init(args): """Initialize the part of the config which works without a QApplication.""" configdata.init() yaml_config = configfiles.YamlConfig() global val, instance, key_instance instance = Config(yaml_config=yaml_config) val = ConfigContainer(instance) key_instance = KeyConfig(instance) for cf in _change_filters: cf.validate() configtypes.Font.monospace_fonts = val.fonts.monospace config_commands = ConfigCommands(instance, key_instance) objreg.register('config-commands', config_commands) config_api = None try: config_api = configfiles.read_config_py() # Raised here so we get the config_api back. if config_api.errors: raise configexc.ConfigFileErrors('config.py', config_api.errors) except configexc.ConfigFileErrors as e: log.config.exception("Error while loading config.py") _init_errors.append(e) try: if getattr(config_api, 'load_autoconfig', True): try: instance.read_yaml() except configexc.ConfigFileErrors as e: raise # caught in outer block except configexc.Error as e: desc = configexc.ConfigErrorDesc("Error", e) raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) except configexc.ConfigFileErrors as e: log.config.exception("Error while loading config.py") _init_errors.append(e) configfiles.init() objects.backend = get_backend(args) earlyinit.init_with_backend(objects.backend)
def read_config_py(filename=None): """Read a config.py file.""" api = ConfigAPI(config.instance, config.key_instance) if filename is None: filename = os.path.join(standarddir.config(), 'config.py') if not os.path.exists(filename): return api container = config.ConfigContainer(config.instance, configapi=api) basename = os.path.basename(filename) module = types.ModuleType('config') module.config = api module.c = container 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, TypeError) 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("Syntax Error", e, traceback=traceback.format_exc()) raise configexc.ConfigFileErrors(basename, [desc]) try: exec(code, module.__dict__) except Exception as e: api.errors.append(configexc.ConfigErrorDesc( "Unhandled exception", exception=e, traceback=traceback.format_exc())) api.finalize() return api
def read_autoconfig(): """Read the autoconfig.yml file.""" try: config.instance.read_yaml() except configexc.ConfigFileErrors: # pylint: disable=try-except-raise raise # caught in outer block except configexc.Error as e: desc = configexc.ConfigErrorDesc("Error", e) raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
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): """Make sure all settings exist.""" unknown = set(self._values) - set(configdata.DATA) if unknown: errors = [ configexc.ConfigErrorDesc("While loading options", "Unknown option {}".format(e)) for e in sorted(unknown) ] raise configexc.ConfigFileErrors('autoconfig.yml', errors)
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 load(self): """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]) try: global_obj = yaml_data['global'] except KeyError: desc = configexc.ConfigErrorDesc( "While loading data", "Toplevel object does not contain 'global' key") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) except TypeError: desc = configexc.ConfigErrorDesc("While loading data", "Toplevel object is not a dict") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) if not isinstance(global_obj, dict): desc = configexc.ConfigErrorDesc( "While loading data", "'global' object is not a dict") raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) # Delete unknown values # (e.g. options which were removed from configdata.yml) for name in list(global_obj): if name not in configdata.DATA: del global_obj[name] self._values = global_obj self._dirty = False
def _handle_migrations(self): """Handle unknown/renamed keys.""" for name in list(self._values): if name in configdata.MIGRATIONS.renamed: new_name = configdata.MIGRATIONS.renamed[name] log.config.debug("Renaming {} to {}".format(name, new_name)) self._values[new_name] = self._values[name] del self._values[name] elif name in configdata.MIGRATIONS.deleted: log.config.debug("Removing {}".format(name)) del self._values[name] elif name in configdata.DATA: pass else: desc = configexc.ConfigErrorDesc( "While loading options", "Unknown option {}".format(name)) raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
def init() -> None: """Initialize config storage not related to the main config.""" global state try: state = StateConfig() except configparser.Error as e: msg = "While loading state file from {}".format(standarddir.data()) desc = configexc.ConfigErrorDesc(msg, e) raise configexc.ConfigFileErrors('state', [desc], fatal=True) # Set the QSettings path to something like # ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it # doesn't overwrite our config. # # This fixes one of the corruption issues here: # https://github.com/qutebrowser/qutebrowser/issues/515 path = os.path.join(standarddir.config(auto=True), 'qsettings') for fmt in [QSettings.NativeFormat, QSettings.IniFormat]: QSettings.setPath(fmt, QSettings.UserScope, path)
def test_config_file_errors_fatal(): err = configexc.ConfigErrorDesc("Text", Exception("Text")) errors = configexc.ConfigFileErrors("state", [err], fatal=True) assert errors.fatal
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])