mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
refactor(webui): deduplicate _extract_checkbox (3 copies → 1)
Extracted extract_checkbox from settings.py, form.py (x2) into webui/routes/utils/common.py. Moved coerce_bool from settings.py to common.py to break circular import. Fixed bug in second form.py copy (was missing __contains__ check). 1006 tests pass.
This commit is contained in:
@@ -19,6 +19,7 @@ from abogen.webui.routes.utils.settings import (
|
||||
_NORMALIZATION_STRING_KEYS,
|
||||
_DEFAULT_ANALYSIS_THRESHOLD,
|
||||
)
|
||||
from abogen.webui.routes.utils.common import extract_checkbox
|
||||
from abogen.webui.routes.utils.voice import template_options
|
||||
from abogen.webui.debug_tts_runner import run_debug_tts_wavs
|
||||
from abogen.debug_tts_samples import DEBUG_TTS_SAMPLES
|
||||
@@ -93,17 +94,9 @@ def update_settings() -> ResponseReturnValue:
|
||||
maximum=25,
|
||||
)
|
||||
|
||||
def _extract_checkbox(name: str, default: bool) -> bool:
|
||||
values = form.getlist(name) if hasattr(form, "getlist") else []
|
||||
if values:
|
||||
return coerce_bool(values[-1], default)
|
||||
if hasattr(form, "__contains__") and name in form:
|
||||
return False
|
||||
return default
|
||||
|
||||
# Normalization settings
|
||||
for key in _NORMALIZATION_BOOLEAN_KEYS:
|
||||
current[key] = _extract_checkbox(key, bool(current.get(key, True)))
|
||||
current[key] = extract_checkbox(form, key, bool(current.get(key, True)))
|
||||
for key in _NORMALIZATION_STRING_KEYS:
|
||||
if hasattr(form, "__contains__") and key in form:
|
||||
current[key] = (form.get(key) or "").strip()
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from typing import Any, Optional, Tuple, Iterable, List
|
||||
from typing import Any, Optional, Tuple, Iterable, List, Mapping
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def coerce_bool(value: Any, default: bool) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
return value.lower() in {"true", "1", "yes", "on"}
|
||||
if value is None:
|
||||
return default
|
||||
return bool(value)
|
||||
|
||||
|
||||
def split_profile_spec(value: Any) -> Tuple[str, Optional[str]]:
|
||||
text = str(value or "").strip()
|
||||
if not text:
|
||||
@@ -18,7 +29,31 @@ def split_speaker_spec(value: Any) -> Tuple[str, Optional[str]]:
|
||||
|
||||
return split_profile_spec(value)
|
||||
|
||||
|
||||
def existing_paths(paths: Optional[Iterable[Path]]) -> List[Path]:
|
||||
if not paths:
|
||||
return []
|
||||
return [p for p in paths if p.exists()]
|
||||
|
||||
|
||||
def extract_checkbox(form: Mapping[str, Any], name: str, default: bool) -> bool:
|
||||
"""Extract a boolean checkbox value from a form-like mapping.
|
||||
|
||||
Handles both multi-value forms (Flask's `getlist`) and simple mappings.
|
||||
If the checkbox name is present but has no value, it means unchecked (False).
|
||||
"""
|
||||
values: List[str] = []
|
||||
getter = getattr(form, "getlist", None)
|
||||
if callable(getter):
|
||||
raw_values = getter(name)
|
||||
if raw_values:
|
||||
values = list(raw_values)
|
||||
else:
|
||||
raw_flag = form.get(name)
|
||||
if raw_flag is not None:
|
||||
values = [raw_flag]
|
||||
if values:
|
||||
return coerce_bool(values[-1], default)
|
||||
if name in form:
|
||||
return False
|
||||
return default
|
||||
|
||||
@@ -29,7 +29,7 @@ from abogen.webui.routes.utils.voice import (
|
||||
)
|
||||
from abogen.webui.routes.utils.entity import sync_pronunciation_overrides
|
||||
from abogen.webui.routes.utils.epub import job_download_flags
|
||||
from abogen.webui.routes.utils.common import split_profile_spec
|
||||
from abogen.webui.routes.utils.common import split_profile_spec, extract_checkbox
|
||||
from abogen.utils import calculate_text_length
|
||||
from abogen.voice_profiles import serialize_profiles, normalize_profile_entry
|
||||
from abogen.chunking import ChunkLevel, build_chunks_for_chapters
|
||||
@@ -537,28 +537,11 @@ def apply_book_step_form(
|
||||
else:
|
||||
pending.normalize_chapter_opening_caps = caps_default
|
||||
|
||||
def _extract_checkbox(name: str, default: bool) -> bool:
|
||||
values: List[str] = []
|
||||
getter = getattr(form, "getlist", None)
|
||||
if callable(getter):
|
||||
raw_values = getter(name)
|
||||
if raw_values:
|
||||
values = list(cast(Iterable[str], raw_values))
|
||||
else:
|
||||
raw_flag = form.get(name)
|
||||
if raw_flag is not None:
|
||||
values = [raw_flag]
|
||||
if values:
|
||||
return coerce_bool(values[-1], default)
|
||||
if hasattr(form, "__contains__") and name in form:
|
||||
return False
|
||||
return default
|
||||
|
||||
overrides_existing = getattr(pending, "normalization_overrides", None)
|
||||
overrides: Dict[str, Any] = dict(overrides_existing or {})
|
||||
for key in _NORMALIZATION_BOOLEAN_KEYS:
|
||||
default_toggle = overrides.get(key, bool(settings.get(key, True)))
|
||||
overrides[key] = _extract_checkbox(key, default_toggle)
|
||||
overrides[key] = extract_checkbox(form, key, default_toggle)
|
||||
for key in _NORMALIZATION_STRING_KEYS:
|
||||
default_val = overrides.get(key, str(settings.get(key, "")))
|
||||
val = form.get(key)
|
||||
@@ -886,25 +869,10 @@ def build_pending_job_from_extraction(
|
||||
apply_config=bool(speaker_config_payload),
|
||||
)
|
||||
|
||||
def _extract_checkbox(name: str, default: bool) -> bool:
|
||||
values: List[str] = []
|
||||
getter = getattr(form, "getlist", None)
|
||||
if callable(getter):
|
||||
raw_values = getter(name)
|
||||
if raw_values:
|
||||
values = list(cast(Iterable[str], raw_values))
|
||||
else:
|
||||
raw_flag = form.get(name)
|
||||
if raw_flag is not None:
|
||||
values = [raw_flag]
|
||||
if values:
|
||||
return coerce_bool(values[-1], default)
|
||||
return default
|
||||
|
||||
normalization_overrides = {}
|
||||
for key in _NORMALIZATION_BOOLEAN_KEYS:
|
||||
default_val = bool(settings.get(key, True))
|
||||
normalization_overrides[key] = _extract_checkbox(key, default_val)
|
||||
normalization_overrides[key] = extract_checkbox(form, key, default_val)
|
||||
|
||||
for key in _NORMALIZATION_STRING_KEYS:
|
||||
default_val = str(settings.get(key, ""))
|
||||
|
||||
@@ -15,7 +15,7 @@ from abogen.normalization_settings import (
|
||||
from abogen.utils import load_config, save_config
|
||||
from abogen.integrations.calibre_opds import CalibreOPDSClient
|
||||
from abogen.integrations.audiobookshelf import AudiobookshelfConfig
|
||||
from abogen.webui.routes.utils.common import split_profile_spec
|
||||
from abogen.webui.routes.utils.common import split_profile_spec, coerce_bool
|
||||
|
||||
SAVE_MODE_LABELS = {
|
||||
"save_next_to_input": "Save next to input file",
|
||||
@@ -245,16 +245,6 @@ def render_prompt_template(template: str, context: Mapping[str, str]) -> str:
|
||||
return _PROMPT_TOKEN_RE.sub(_replace, template)
|
||||
|
||||
|
||||
def coerce_bool(value: Any, default: bool) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
return value.lower() in {"true", "1", "yes", "on"}
|
||||
if value is None:
|
||||
return default
|
||||
return bool(value)
|
||||
|
||||
|
||||
def coerce_float(value: Any, default: float) -> float:
|
||||
try:
|
||||
return max(0.0, float(value))
|
||||
|
||||
Reference in New Issue
Block a user