Stop the start with a clear message for a bad setting

A bad UPDATE_INTERVAL_HOURS or LOG_LEVEL no longer ends in a traceback.
Warn about a CWA_PASSWORD without a CWA_USERNAME.
This commit is contained in:
tim
2026-10-02 23:37:33 +02:00
parent 0506086533
commit 0edfa1308c
2 changed files with 53 additions and 2 deletions
+20 -2
View File
@@ -27,6 +27,22 @@ def _optional_path(name: str) -> Path | None:
return Path(value).absolute() if value else None
# A bad value stops the start with one clear line, not with a traceback.
def _number(name: str, default: str) -> float:
value = os.getenv(name, default).strip()
try:
return float(value)
except ValueError:
raise SystemExit(f"{name} must be a number, not {value!r}") from None
def _choice(name: str, default: str, choices: tuple[str, ...]) -> str:
value = os.getenv(name, default).strip().upper()
if value not in choices:
raise SystemExit(f"{name} must be one of {', '.join(choices)}, not {value!r}")
return value
def _base_path(value: str) -> str:
path = value.strip().strip("/")
return f"/{path}" if path else ""
@@ -37,7 +53,7 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent
CONFIG_DIR = _path("CONFIG_DIR", "/config")
INGEST_DIR = _optional_path("INGEST_DIR")
TMP_DIR = _path("TMP_DIR", "/tmp/bindery")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").strip().upper()
LOG_LEVEL = _choice("LOG_LEVEL", "INFO", ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"))
# A path prefix, such as /bindery, for a reverse proxy that forwards the full path.
URL_BASE = _base_path(os.getenv("URL_BASE", ""))
@@ -49,7 +65,7 @@ CWA_PASSWORD = os.getenv("CWA_PASSWORD", "")
CWA_DB_PATH = _optional_path("CWA_DB_PATH") # Calibre-Web app.db, for logins
CALIBRE_DB_PATH = _optional_path("CALIBRE_DB_PATH") # calibre metadata.db, for book IDs
UPDATE_INTERVAL_HOURS = float(os.getenv("UPDATE_INTERVAL_HOURS", "24"))
UPDATE_INTERVAL_HOURS = _number("UPDATE_INTERVAL_HOURS", "24")
DEFAULT_LOCKED_FIELDS = _list("DEFAULT_LOCKED_FIELDS", "title")
# Logins: none, local, cwa or proxy. An API key works in every mode.
@@ -89,6 +105,8 @@ def prepare() -> None:
if not PERSONAL_INI.exists():
shutil.copy(DEFAULT_PERSONAL_INI, PERSONAL_INI)
logger.info(f"Created the default FanFicFare configuration at {PERSONAL_INI}")
if CWA_PASSWORD and not CWA_USERNAME:
logger.warning("CWA_PASSWORD is set, but CWA_USERNAME is not. Bindery sends no login to CWA.")
if INGEST_DIR and not (CWA_URL or CALIBRE_DB_PATH):
logger.warning(
"INGEST_DIR is set, but CWA_URL and CALIBRE_DB_PATH are not. Bindery cannot find the library book "
+33
View File
@@ -155,3 +155,36 @@ def test_prepare_warns_about_an_ingest_folder_without_a_library(monkeypatch, tmp
with caplog.at_level("WARNING"):
config.prepare()
assert ("each update adds a new book" in caplog.text) is warns
@pytest.mark.parametrize(
("name", "value", "error"),
[
("UPDATE_INTERVAL_HOURS", "daily", "UPDATE_INTERVAL_HOURS must be a number, not 'daily'"),
("LOG_LEVEL", "loud", "LOG_LEVEL must be one of DEBUG, INFO, WARNING, ERROR, CRITICAL, not 'LOUD'"),
],
)
def test_a_bad_value_stops_the_start_with_a_clear_message(monkeypatch, reload_config, name, value, error):
monkeypatch.setenv(name, value)
with pytest.raises(SystemExit) as stop:
reload_config()
assert str(stop.value) == error
def test_log_level_in_lower_case(monkeypatch, reload_config):
monkeypatch.setenv("LOG_LEVEL", " debug ")
reload_config()
assert config.LOG_LEVEL == "DEBUG"
@pytest.mark.parametrize(("username", "warns"), [("", True), ("bindery", False)])
def test_prepare_warns_about_a_cwa_password_without_a_user_name(monkeypatch, tmp_path, caplog, username, warns):
monkeypatch.setattr(config, "CWA_URL", "https://cwa.invalid")
monkeypatch.setattr(config, "CWA_USERNAME", username)
monkeypatch.setattr(config, "CWA_PASSWORD", "secret")
for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp"), ("INGEST_DIR", "in")]:
monkeypatch.setattr(config, name, tmp_path / folder)
monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini")
with caplog.at_level("WARNING"):
config.prepare()
assert ("CWA_PASSWORD is set" in caplog.text) is warns