Fix package structure and lazy-import ODT formatter
Add missing __init__.py to formatters/ so setuptools.find_packages includes the subpackage in wheel/sdist builds (#16). Move ODTFormatter import from top-level to lazy import inside get_formatter_by_extension() so missing odfpy no longer crashes module load before main() runs. Remove dead except ImportError handler in cli.py save() block; surface a clear 'odfpy is not installed' message when the formatter factory returns None for .odt (#25). Closes #16, closes #25
This commit is contained in:
@@ -128,6 +128,97 @@ def odt_formatter():
|
||||
yield ODTFormatter(author="Тест Автор", from_date="2026-01-01", to_date="2026-01-31")
|
||||
|
||||
|
||||
# -- Тесты упаковки formatters как полноценного пакета --
|
||||
|
||||
|
||||
def test_formatters_is_regular_package():
|
||||
"""redmine_reporter.formatters — полноценный пакет с __init__.py, не namespace."""
|
||||
import redmine_reporter.formatters
|
||||
|
||||
assert hasattr(redmine_reporter.formatters, "__file__")
|
||||
assert redmine_reporter.formatters.__file__.endswith("__init__.py")
|
||||
|
||||
|
||||
def test_formatters_found_by_setuptools():
|
||||
"""setuptools.find_packages находит formatters как полноценный пакет."""
|
||||
import os
|
||||
|
||||
from setuptools import find_packages
|
||||
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
found = find_packages(where=project_root, include=["redmine_reporter*"])
|
||||
assert "redmine_reporter.formatters" in found
|
||||
|
||||
|
||||
# -- Тесты ленивого импорта ODT (#25) --
|
||||
|
||||
|
||||
def _simulate_missing_odfpy():
|
||||
"""Вспомогательная функция: подготавливает очистку кэша модулей odf и odt.
|
||||
|
||||
Возвращает словарь сохранённых модулей для последующего восстановления.
|
||||
"""
|
||||
import sys
|
||||
|
||||
saved = {}
|
||||
for key in list(sys.modules.keys()):
|
||||
if key == "odf" or key.startswith("odf.") or key == "redmine_reporter.formatters.odt":
|
||||
saved[key] = sys.modules.pop(key)
|
||||
return saved
|
||||
|
||||
|
||||
def _restore_modules(saved):
|
||||
import sys
|
||||
|
||||
sys.modules.update(saved)
|
||||
|
||||
|
||||
def test_get_formatter_odt_returns_none_without_odfpy():
|
||||
"""Без odfpy — get_formatter_by_extension('.odt') возвращает None, не падает."""
|
||||
import builtins
|
||||
|
||||
from redmine_reporter.formatters.factory import get_formatter_by_extension
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def blocking_import(name, *args, **kwargs):
|
||||
if name == "odf" or name.startswith("odf."):
|
||||
raise ImportError(f"No module named '{name}'")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
saved = _simulate_missing_odfpy()
|
||||
try:
|
||||
with mock.patch("builtins.__import__", side_effect=blocking_import):
|
||||
result = get_formatter_by_extension(
|
||||
".odt", author="test", from_date="2026-01-01", to_date="2026-01-31"
|
||||
)
|
||||
assert result is None
|
||||
finally:
|
||||
_restore_modules(saved)
|
||||
|
||||
|
||||
def test_get_formatter_csv_works_without_odfpy():
|
||||
"""Без odfpy — get_formatter_by_extension('.csv') работает нормально."""
|
||||
import builtins
|
||||
|
||||
from redmine_reporter.formatters.factory import get_formatter_by_extension
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def blocking_import(name, *args, **kwargs):
|
||||
if name == "odf" or name.startswith("odf."):
|
||||
raise ImportError(f"No module named '{name}'")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
saved = _simulate_missing_odfpy()
|
||||
try:
|
||||
with mock.patch("builtins.__import__", side_effect=blocking_import):
|
||||
result = get_formatter_by_extension(".csv")
|
||||
assert result is not None
|
||||
finally:
|
||||
_restore_modules(saved)
|
||||
|
||||
|
||||
# -- Параметризованные тесты текстовых форматтеров --
|
||||
|
||||
TEXT_FORMATTER_FACTORIES = [
|
||||
|
||||
Reference in New Issue
Block a user