refactor(webui): rename preview.py to synthesize.py and remove dead code

- Rename abogen/webui/routes/utils/preview.py → synthesize.py
  The file contains the core TTS synthesis pipeline (generate_preview_audio,
  synthesize_preview), not just preview logic. Name now matches responsibility.
- Remove dead code from voice.py: get_preview_pipeline(), synthesize_audio_from_normalized(),
  _preview_pipeline_lock, _preview_pipelines, and unused imports (threading, numpy,
  create_pipeline, get_new_voice, _select_device, _to_float32, SAMPLE_RATE, SPLIT_PATTERN).
  These were never called — identical logic lives in synthesize.py.
- Update imports in api.py, voices.py, and test_preview_applies_manual_overrides.py
- 7 new tests in test_synthesize_module.py enforce file naming and import rules
- 7 tests in test_domain_imports.py updated for renamed module
This commit is contained in:
Artem Akymenko
2026-07-16 10:19:01 +03:00
parent ef07a8b5b2
commit 8ccdc85ccb
7 changed files with 124 additions and 135 deletions
+48 -47
View File
@@ -2,8 +2,15 @@
import pathlib
_MODULE_SOURCE_CACHE: dict[str, str] = {}
def _read_source(module_file: str) -> str:
return pathlib.Path(module_file).read_text(encoding="utf-8")
if module_file not in _MODULE_SOURCE_CACHE:
_MODULE_SOURCE_CACHE[module_file] = pathlib.Path(module_file).read_text(
encoding="utf-8"
)
return _MODULE_SOURCE_CACHE[module_file]
def test_voice_module_does_not_import_conversion_runner():
@@ -17,56 +24,50 @@ def test_voice_module_does_not_import_conversion_runner():
)
def test_voice_module_imports_select_device_from_domain():
"""voice.py must import select_device from abogen.domain.device."""
import abogen.webui.routes.utils.voice as voice_mod
def test_synthesize_module_does_not_import_conversion_runner():
"""synthesize.py must not import from conversion_runner."""
import abogen.webui.routes.utils.synthesize as synthesize_mod
with open(voice_mod.__file__, "r", encoding="utf-8") as fh:
source_text = fh.read()
assert "from abogen.domain.device import" in source_text
def test_voice_module_imports_to_float32_from_domain():
"""voice.py must import to_float32 from abogen.domain.audio_helpers."""
import abogen.webui.routes.utils.voice as voice_mod
with open(voice_mod.__file__, "r", encoding="utf-8") as fh:
source_text = fh.read()
assert "from abogen.domain.audio_helpers import" in source_text
def test_voice_module_has_sample_rate():
"""voice.py must define SAMPLE_RATE = 24000."""
from abogen.webui.routes.utils.voice import SAMPLE_RATE
assert SAMPLE_RATE == 24000
def test_voice_module_has_split_pattern():
"""voice.py must define SPLIT_PATTERN."""
from abogen.webui.routes.utils.voice import SPLIT_PATTERN
assert isinstance(SPLIT_PATTERN, str)
assert len(SPLIT_PATTERN) > 0
def test_preview_module_does_not_import_conversion_runner():
"""preview.py must not import from conversion_runner."""
import abogen.webui.routes.utils.preview as preview_mod
with open(preview_mod.__file__, "r", encoding="utf-8") as fh:
source_text = fh.read()
source_text = _read_source(synthesize_mod.__file__)
assert "from abogen.webui.conversion_runner import" not in source_text
def test_preview_module_imports_select_device_from_domain():
"""preview.py must import select_device from abogen.domain.device."""
import abogen.webui.routes.utils.preview as preview_mod
with open(preview_mod.__file__, "r", encoding="utf-8") as fh:
source_text = fh.read()
def test_synthesize_module_imports_select_device_from_domain():
"""synthesize.py must import select_device from abogen.domain.device."""
import abogen.webui.routes.utils.synthesize as synthesize_mod
source_text = _read_source(synthesize_mod.__file__)
assert "from abogen.domain.device import" in source_text
def test_voice_module_does_not_define_synthesize_audio_from_normalized():
"""Dead code: synthesize_audio_from_normalized must be removed from voice.py."""
import abogen.webui.routes.utils.voice as voice_mod
source_text = _read_source(voice_mod.__file__)
assert "def synthesize_audio_from_normalized(" not in source_text
def test_voice_module_does_not_define_get_preview_pipeline():
"""Dead code: get_preview_pipeline must be removed from voice.py."""
import abogen.webui.routes.utils.voice as voice_mod
source_text = _read_source(voice_mod.__file__)
assert "def get_preview_pipeline(" not in source_text
def test_voice_module_does_not_import_domain_audio_helpers():
"""After removing dead code, voice.py no longer needs audio_helpers imports."""
import abogen.webui.routes.utils.voice as voice_mod
source_text = _read_source(voice_mod.__file__)
assert "from abogen.domain.audio_helpers import" not in source_text
def test_voice_module_does_not_import_domain_device():
"""After removing dead code, voice.py no longer needs device imports."""
import abogen.webui.routes.utils.voice as voice_mod
source_text = _read_source(voice_mod.__file__)
assert "from abogen.domain.device import" not in source_text
@@ -1,11 +1,11 @@
from abogen.webui.routes.utils import preview
from abogen.webui.routes.utils import synthesize
def test_preview_applies_manual_override_before_normalization(monkeypatch):
# Don't run real TTS/normalization; just exercise the override stage by
# forcing provider=kokoro and then stubbing normalize_for_pipeline.
monkeypatch.setattr(preview, "get_preview_pipeline", lambda language, device: None)
monkeypatch.setattr(synthesize, "get_preview_pipeline", lambda language, device: None)
# Stub normalize_for_pipeline to be identity; we only care that overrides run.
class _Norm:
@@ -42,7 +42,7 @@ def test_preview_applies_manual_override_before_normalization(monkeypatch):
monkeypatch.setattr(utils, "create_pipeline", _mock_create_pipeline)
try:
preview.generate_preview_audio(
synthesize.generate_preview_audio(
text="He said Unfu*k loudly.",
voice_spec="M1",
language="en",
+70
View File
@@ -0,0 +1,70 @@
"""Tests that preview/synthesis module is correctly named and importable."""
import pathlib
def _read_source(module_file: str) -> str:
return pathlib.Path(module_file).read_text(encoding="utf-8")
def test_preview_file_renamed_to_synthesize():
"""preview.py must be renamed to synthesize.py."""
import abogen.webui.routes.utils.synthesize as synthesize_mod
synthesize_path = pathlib.Path(synthesize_mod.__file__)
assert synthesize_path.name == "synthesize.py"
assert synthesize_path.exists()
assert not synthesize_path.with_name("preview.py").exists()
def test_synthesize_module_has_generate_preview_audio():
"""synthesize.py must export generate_preview_audio."""
from abogen.webui.routes.utils.synthesize import generate_preview_audio
assert callable(generate_preview_audio)
def test_synthesize_module_has_synthesize_preview():
"""synthesize.py must export synthesize_preview."""
from abogen.webui.routes.utils.synthesize import synthesize_preview
assert callable(synthesize_preview)
def test_synthesize_module_has_get_preview_pipeline():
"""synthesize.py must export get_preview_pipeline."""
from abogen.webui.routes.utils.synthesize import get_preview_pipeline
assert callable(get_preview_pipeline)
def test_api_imports_from_synthesize():
"""api.py must import from synthesize, not preview."""
import abogen.webui.routes.api as api_mod
source = _read_source(api_mod.__file__)
assert "from abogen.webui.routes.utils.synthesize import" in source
assert "from abogen.webui.routes.utils.preview import" not in source
def test_voices_imports_from_synthesize():
"""voices.py must import from synthesize, not preview."""
import abogen.webui.routes.voices as voices_mod
source = _read_source(voices_mod.__file__)
assert "from abogen.webui.routes.utils.synthesize import" in source
assert "from abogen.webui.routes.utils.preview import" not in source
def test_no_module_imports_preview():
"""No module should import from the old preview path."""
import glob
import os
project_root = pathlib.Path(__file__).parent.parent
py_files = glob.glob(str(project_root / "abogen" / "**" / "*.py"), recursive=True)
for py_file in py_files:
content = pathlib.Path(py_file).read_text(encoding="utf-8")
assert "from abogen.webui.routes.utils.preview import" not in content, (
f"{py_file} still imports from preview"
)