config: sanity check via types (MR 2252)

Replace the "sanity_check" code with type checking built into the Config
__setattr__ operator.

This keeps all the Config related code in one place.

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-06-09 03:27:45 +02:00 committed by Oliver Smith
parent 7a8deb0f5e
commit 1a01738d50
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
6 changed files with 39 additions and 43 deletions

View file

@ -11,7 +11,7 @@ from typing import Dict, List, Sequence
# #
# FIXME (#2324): this sucks, we should re-organise this and not rely on "lifting" # FIXME (#2324): this sucks, we should re-organise this and not rely on "lifting"
# this functions this way # this functions this way
from pmb.config.load import load, sanity_checks, save, serialize from pmb.config.load import load, save, serialize
from pmb.config.sudo import which_sudo from pmb.config.sudo import which_sudo
from pmb.config.other import is_systemd_selected from pmb.config.other import is_systemd_selected
@ -113,10 +113,6 @@ defaults = {
"iter_time": "200", "iter_time": "200",
} }
allowed_values = {
"systemd": ["default", "always", "never"],
}
# Whether we're connected to a TTY (which allows things like e.g. printing # Whether we're connected to a TTY (which allows things like e.g. printing
# progress bars) # progress bars)
is_interactive = sys.stdout.isatty() and \ is_interactive = sys.stdout.isatty() and \

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core import get_context from pmb.core import get_context
from pmb.core.chroot import Chroot from pmb.core.chroot import Chroot
from pmb.core.config import SystemdConfig
from pmb.core.context import Context from pmb.core.context import Context
from pmb.core.pkgrepo import pkgrepo_default_path from pmb.core.pkgrepo import pkgrepo_default_path
from pmb.helpers import logging from pmb.helpers import logging
@ -211,7 +212,7 @@ def ask_for_systemd(config: Config, ui):
logging.info("Based on your UI selection, 'default' will result" logging.info("Based on your UI selection, 'default' will result"
f" in{not_str}installing systemd.") f" in{not_str}installing systemd.")
choices = pmb.config.allowed_values["systemd"] choices = SystemdConfig.choices()
answer = pmb.helpers.cli.ask("Install systemd?", answer = pmb.helpers.cli.ask("Install systemd?",
choices, choices,
config.systemd, config.systemd,

View file

@ -1,35 +1,13 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path, PosixPath from pathlib import Path, PosixPath
from typing import Any, Dict, List, Mapping, Optional from typing import List
from pmb.helpers import logging from pmb.helpers import logging
import configparser import configparser
import os import os
import sys
import pmb.config
from pmb.core import Config from pmb.core import Config
def sanity_check(cfg: Config, key, allowed, path: Optional[Path] = None):
value = getattr(cfg, key)
if value in allowed:
return
logging.error(f"pmbootstrap.cfg: invalid value for {key}: '{value}'")
logging.error(f"Allowed: {', '.join(allowed)}")
if path:
logging.error(f"Fix it here and try again: {path}")
sys.exit(1)
def sanity_checks(cfg: Config, path: Optional[Path] = None):
for key, allowed in pmb.config.allowed_values.items():
sanity_check(cfg, key, allowed, path)
def load(path: Path) -> Config: def load(path: Path) -> Config:
config = Config() config = Config()
@ -67,7 +45,6 @@ def load(path: Path) -> Config:
elif key in cfg["pmbootstrap"]: elif key in cfg["pmbootstrap"]:
setattr(config, key, cfg["pmbootstrap"][key]) setattr(config, key, cfg["pmbootstrap"][key])
sanity_checks(config, path)
return config return config

View file

@ -1,6 +1,7 @@
# Copyright 2024 Oliver Smith # Copyright 2024 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core import Config from pmb.core import Config
from pmb.core.config import SystemdConfig
import pmb.helpers.ui import pmb.helpers.ui
import pmb.config.pmaports import pmb.config.pmaports
@ -10,9 +11,9 @@ def is_systemd_selected(config: Config):
return False return False
if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never", skip_extra_repos=True): if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never", skip_extra_repos=True):
return False return False
if config.systemd == "always": if config.systemd == SystemdConfig.ALWAYS:
return True return True
if config.systemd == "never": if config.systemd == SystemdConfig.NEVER:
return False return False
return pmb.helpers.ui.check_option(config.ui, "pmb:systemd", skip_extra_repos=True) return pmb.helpers.ui.check_option(config.ui, "pmb:systemd", skip_extra_repos=True)
@ -22,9 +23,9 @@ def systemd_selected_str(config: Config):
return "no", "not supported by pmaports branch" return "no", "not supported by pmaports branch"
if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never"): if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never"):
return "no", "not supported by selected UI" return "no", "not supported by selected UI"
if config.systemd == "always": if config.systemd == SystemdConfig.ALWAYS:
return "yes", "'always' selected in 'pmbootstrap init'" return "yes", "'always' selected in 'pmbootstrap init'"
if config.systemd == "never": if config.systemd == SystemdConfig.NEVER:
return "no", "'never' selected in 'pmbootstrap init'" return "no", "'never' selected in 'pmbootstrap init'"
if pmb.helpers.ui.check_option(config.ui, "pmb:systemd"): if pmb.helpers.ui.check_option(config.ui, "pmb:systemd"):
return "yes", "default for selected UI" return "yes", "default for selected UI"

View file

@ -1,5 +1,6 @@
from copy import deepcopy from copy import deepcopy
import enum
import multiprocessing import multiprocessing
from typing import Any, List, Dict, TypedDict from typing import Any, List, Dict, TypedDict
from pathlib import Path from pathlib import Path
@ -11,6 +12,20 @@ class Mirrors(TypedDict):
systemd: str systemd: str
class SystemdConfig(enum.Enum):
DEFAULT = "default"
ALWAYS = "always"
NEVER = "never"
def __str__(self) -> str:
return self.value
@staticmethod
def choices() -> List[str]:
return [e.value for e in SystemdConfig]
class Config(): class Config():
aports: List[Path] = [Path(os.path.expanduser("~") + aports: List[Path] = [Path(os.path.expanduser("~") +
"/.local/var/pmbootstrap/cache_git/pmaports")] "/.local/var/pmbootstrap/cache_git/pmaports")]
@ -41,7 +56,7 @@ class Config():
ssh_key_glob: str = "~/.ssh/id_*.pub" ssh_key_glob: str = "~/.ssh/id_*.pub"
ssh_keys: bool = False ssh_keys: bool = False
sudo_timer: bool = False sudo_timer: bool = False
systemd: str = "default" systemd: SystemdConfig = SystemdConfig.DEFAULT
timezone: str = "GMT" timezone: str = "GMT"
ui: str = "console" ui: str = "console"
ui_extras: bool = False ui_extras: bool = False
@ -55,7 +70,7 @@ class Config():
# Make sure we aren't modifying the class defaults # Make sure we aren't modifying the class defaults
for key in Config.__annotations__.keys(): for key in Config.__annotations__.keys():
setattr(self, key, deepcopy(Config.get_default(key))) setattr(self, key, deepcopy(Config.get_default(key)))
@staticmethod @staticmethod
def keys() -> List[str]: def keys() -> List[str]:
@ -78,22 +93,29 @@ class Config():
raise ValueError(f"Invalid dotted key: {dotted_key}") raise ValueError(f"Invalid dotted key: {dotted_key}")
def __setattr__(self, key: str, value: str): def __setattr__(self, key: str, value: Any):
"""Allow for setattr() to be used with a dotted key """Allow for setattr() to be used with a dotted key
to set nested dictionaries (e.g. "mirrors.alpine").""" to set nested dictionaries (e.g. "mirrors.alpine")."""
keys = key.split(".") keys = key.split(".")
if len(keys) == 1: if len(keys) == 1:
super(Config, self).__setattr__(key, value) _type = type(getattr(Config, key))
try:
super(Config, self).__setattr__(key, _type(value))
except ValueError:
msg = f"Invalid value for '{key}': '{value}' "
if issubclass(_type, enum.Enum):
valid = [x.value for x in _type]
msg += f"(valid values: {', '.join(valid)})"
else:
msg += f"(expected {_type}, got {type(value)})"
raise ValueError(msg)
elif len(keys) == 2: elif len(keys) == 2:
#print(f"cfgset, before: {super(Config, self).__getattribute__(keys[0])[keys[1]]}")
super(Config, self).__getattribute__(keys[0])[keys[1]] = value super(Config, self).__getattribute__(keys[0])[keys[1]] = value
#print(f"cfgset, after: {super(Config, self).__getattribute__(keys[0])[keys[1]]}")
else: else:
raise ValueError(f"Invalid dotted key: {key}") raise ValueError(f"Invalid dotted key: {key}")
def __getattribute__(self, key: str) -> str: def __getattribute__(self, key: str) -> Any:
#print(repr(self))
"""Allow for getattr() to be used with a dotted key """Allow for getattr() to be used with a dotted key
to get nested dictionaries (e.g. "mirrors.alpine").""" to get nested dictionaries (e.g. "mirrors.alpine")."""
keys = key.split(".") keys = key.split(".")

View file

@ -228,7 +228,6 @@ def config(args: PmbArgs):
pmb.config.save(args.config, config) pmb.config.save(args.config, config)
elif args.value is not None: elif args.value is not None:
setattr(config, args.name, args.value) setattr(config, args.name, args.value)
pmb.config.sanity_checks(config)
logging.info("Config changed: " + args.name + "='" + args.value + "'") logging.info("Config changed: " + args.name + "='" + args.value + "'")
pmb.config.save(args.config, config) pmb.config.save(args.config, config)
elif args.name: elif args.name: