confighelper: require core component sections

This is a reimplementation of commit d61540cad5,
however this attempt provides a fallback for legacy configurations
that still have configuration in the server section.

This will change how `/server/config` reports configuration.  Options
will always be reported in their respective component's field, even if
they are currently configured in the `[server]` section.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-04-05 08:03:06 -04:00
parent f27daac4f4
commit ea25cce38f
No known key found for this signature in database
GPG Key ID: 7027245FBBDDF59A
2 changed files with 42 additions and 18 deletions

View File

@ -36,6 +36,7 @@ if TYPE_CHECKING:
ConfigVal = Union[None, int, float, bool, str, dict, list] ConfigVal = Union[None, int, float, bool, str, dict, list]
SENTINEL = SentinelClass.get_instance() SENTINEL = SentinelClass.get_instance()
DOCS_URL = "https://moonraker.readthedocs.io/en/latest"
class ConfigError(Exception): class ConfigError(Exception):
pass pass
@ -47,11 +48,13 @@ class ConfigHelper:
server: Server, server: Server,
config: configparser.ConfigParser, config: configparser.ConfigParser,
section: str, section: str,
parsed: Dict[str, Dict[str, ConfigVal]] = {} parsed: Dict[str, Dict[str, ConfigVal]] = {},
fallback_section: Optional[str] = None
) -> None: ) -> None:
self.server = server self.server = server
self.config = config self.config = config
self.section = section self.section = section
self.fallback_section: Optional[str] = fallback_section
self.parsed = parsed self.parsed = parsed
if self.section not in self.parsed: if self.section not in self.parsed:
self.parsed[self.section] = {} self.parsed[self.section] = {}
@ -77,11 +80,16 @@ class ConfigHelper:
return self.section return self.section
def get_options(self) -> Dict[str, str]: def get_options(self) -> Dict[str, str]:
if self.section not in self.config:
return {}
return dict(self.config[self.section]) return dict(self.config[self.section])
def get_hash(self) -> hashlib._Hash: def get_hash(self) -> hashlib._Hash:
hash = hashlib.sha256() hash = hashlib.sha256()
for option, val in self.config[self.section].items(): section = self.section
if self.section not in self.config:
return hash
for option, val in self.config[section].items():
hash.update(option.encode()) hash.update(option.encode())
hash.update(val.encode()) hash.update(val.encode())
return hash return hash
@ -89,10 +97,12 @@ class ConfigHelper:
def get_prefix_sections(self, prefix: str) -> List[str]: def get_prefix_sections(self, prefix: str) -> List[str]:
return [s for s in self.sections() if s.startswith(prefix)] return [s for s in self.sections() if s.startswith(prefix)]
def getsection(self, section: str) -> ConfigHelper: def getsection(
if section not in self.config: self, section: str, fallback: Optional[str] = None
raise ConfigError(f"No section [{section}] in config") ) -> ConfigHelper:
return ConfigHelper(self.server, self.config, section, self.parsed) return ConfigHelper(
self.server, self.config, section, self.parsed, fallback
)
def _get_option(self, def _get_option(self,
func: Callable[..., Any], func: Callable[..., Any],
@ -104,14 +114,21 @@ class ConfigHelper:
maxval: Optional[Union[int, float]] = None, maxval: Optional[Union[int, float]] = None,
deprecate: bool = False deprecate: bool = False
) -> _T: ) -> _T:
section = self.section
warn_fallback = False
if (
self.section not in self.config and
self.fallback_section is not None
):
section = self.fallback_section
warn_fallback = True
try: try:
val = func(self.section, option) val = func(section, option)
except configparser.NoOptionError: except (configparser.NoOptionError, configparser.NoSectionError) as e:
if isinstance(default, SentinelClass): if isinstance(default, SentinelClass):
raise ConfigError( raise ConfigError(str(e)) from None
f"No option found ({option}) in section [{self.section}]"
) from None
val = default val = default
section = self.section
except Exception: except Exception:
raise ConfigError( raise ConfigError(
f"Error parsing option ({option}) from " f"Error parsing option ({option}) from "
@ -119,19 +136,25 @@ class ConfigHelper:
else: else:
if deprecate: if deprecate:
self.server.add_warning( self.server.add_warning(
f"[{self.section}]: Option '{option}' in is " f"[{self.section}]: Option '{option}' is "
"deprecated, see the configuration documention " "deprecated, see the configuration documention "
"at https://moonraker.readthedocs.io") f"at {DOCS_URL}/configuration")
if warn_fallback:
self.server.add_warning(
f"[{section}]: Option '{option}' has been moved "
f"to section [{self.section}]. Please correct your "
f"configuration, see {DOCS_URL}/configuration for "
f"detailed documentation."
)
self._check_option(option, val, above, below, minval, maxval) self._check_option(option, val, above, below, minval, maxval)
# Only track sections included in the original config
if ( if (
val is None or val is None or
isinstance(val, (int, float, bool, str, dict, list)) isinstance(val, (int, float, bool, str, dict, list))
): ):
self.parsed[self.section][option] = val self.parsed[section][option] = val
else: else:
# If the item cannot be encoded to json serialize to a string # If the item cannot be encoded to json serialize to a string
self.parsed[self.section][option] = str(val) self.parsed[section][option] = str(val)
return val return val
def _check_option(self, def _check_option(self,

View File

@ -228,8 +228,9 @@ class Server:
return self.components[component_name] return self.components[component_name]
try: try:
module = importlib.import_module("components." + component_name) module = importlib.import_module("components." + component_name)
if component_name in config: is_core = component_name in CORE_COMPONENTS
config = config[component_name] fallback: Optional[str] = "server" if is_core else None
config = config.getsection(component_name, fallback)
load_func = getattr(module, "load_component") load_func = getattr(module, "load_component")
component = load_func(config) component = load_func(config)
except Exception: except Exception: