diff --git a/feeluown/config.py b/feeluown/config.py index 2bcfe9167..067d6e370 100644 --- a/feeluown/config.py +++ b/feeluown/config.py @@ -1,3 +1,4 @@ +from typing import Optional import logging import warnings from collections import namedtuple @@ -15,19 +16,33 @@ class Config: 用户可以在 rc 文件中配置各个选项的值 """ - def __init__(self): + def __init__(self, name: str = 'config', parent: Optional['Config'] = None): + object.__setattr__(self, '_name', name) + object.__setattr__(self, '_parent', parent) object.__setattr__(self, '_fields', {}) + object.__setattr__(self, '_undeclared_fields', {}) def __getattr__(self, name): # tips: 这里不能用 getattr 来获取值, 否则会死循环 - if name == '_fields': - return object.__getattribute__(self, '_fields') + if name in ('_fields', '_parent', '_name', '_undeclared_fields'): + return object.__getattribute__(self, name) if name in self._fields: try: - object.__getattribute__(self, name) + return object.__getattribute__(self, name) except AttributeError: return self._fields[name].default - return object.__getattribute__(self, name) + elif name in self._undeclared_fields: + return self._undeclared_fields[name] + + # Requirement: + # User may define config like + # app.plugin.X = Y + # When 'plugin' is not installed, such config should not raise an error. + # To achieve this, return a subconfig when accessing an undeclared key. + logger.warning(f'Undeclared subconfig: {self.fullname}.{name}') + tmpconfig = Config(name=name, parent=self) + self._undeclared_fields[name] = tmpconfig + return tmpconfig def __setattr__(self, name, value): if name in self._fields: @@ -38,7 +53,13 @@ def __setattr__(self, name, value): # TODO: 校验值类型 object.__setattr__(self, name, value) else: - logger.warning('Assign to an undeclared config key.') + logger.warning(f'Assign to an undeclared config key: {self.fullname}.{name}') + + @property + def fullname(self) -> str: + if self._parent is None: + return self._name + return f'{self._parent.fullname}.{self._name}' def deffield(self, name, type_=None, default=None, desc='', warn=None): """Define a configuration field diff --git a/feeluown/plugin.py b/feeluown/plugin.py index 19ef1301e..7e8df443a 100644 --- a/feeluown/plugin.py +++ b/feeluown/plugin.py @@ -91,7 +91,7 @@ def init_config(self, config: Config): .. versionadded: 3.7.15 """ - myconfig = Config() + myconfig = Config(name=self.name, parent=config) names = [self.name] # Currently, plugin name looks like fuo_xxx and xxx is the real name. diff --git a/tests/test_config.py b/tests/test_config.py index 0b8b62a7f..f0a5410b1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -17,10 +17,14 @@ def test_config_set_known_field(config): def test_config_set_unknown_field(config): - """Set unknown field should cause no effects""" + """ + Set unknown field should cause no effects. + Access unknown field should return a Config object. + """ config.hey = 0 - with pytest.raises(AttributeError): - config.hey + assert isinstance(config.hey, Config) + config.plugin.X = 0 # should not raise error + assert isinstance(config.plugin.X, Config) def test_config_set_subconfig(config):