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:
parent
f27daac4f4
commit
ea25cce38f
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue