"""Unit tests for lazy adapter loading in adapters subpackage.""" from __future__ import annotations import builtins import importlib import os import subprocess import sys from collections.abc import Callable, Mapping, Sequence from pathlib import Path from types import ModuleType from unittest.mock import patch import pytest import python_repositories _ROOT = Path(__file__).resolve().parents[2] def _block_backend_import(blocked_prefix: str) -> Callable[..., ModuleType]: real_import = builtins.__import__ def fake_import( name: str, globals: Mapping[str, object] | None = None, locals: Mapping[str, object] | None = None, fromlist: Sequence[str] = (), level: int = 0, ) -> ModuleType: if name == blocked_prefix or name.startswith(f"{blocked_prefix}."): raise ImportError(f"No module named '{name}'") return real_import(name, globals, locals, fromlist, level) return fake_import def test_base_import_does_not_load_adapters() -> None: """Base package import does not eagerly load backend adapter modules.""" script = """ import sys from python_repositories import JsonRepositoryInterface assert JsonRepositoryInterface is not None assert "python_repositories.adapters.redis_adapter" not in sys.modules assert "python_repositories.adapters.minio_adapter" not in sys.modules """ result = subprocess.run( [sys.executable, "-c", script], cwd=_ROOT, env={**os.environ, "PYTHONPATH": str(_ROOT)}, capture_output=True, text=True, ) assert result.returncode == 0, result.stderr or result.stdout def test_lazy_adapter_load_succeeds_when_extra_present() -> None: """Adapters load when their optional dependencies are installed.""" from python_repositories import MinioAdapter, RedisAdapter assert RedisAdapter.__name__ == "RedisAdapter" assert MinioAdapter.__name__ == "MinioAdapter" def test_redis_adapter_import_error_without_extra() -> None: """Missing redis extra raises ImportError with install hint.""" import python_repositories.adapters.redis_adapter as redis_adapter_module with patch.object(builtins, "__import__", new=_block_backend_import("redis")): with pytest.raises(ImportError, match=r"python-repositories\[redis\]"): importlib.reload(redis_adapter_module) importlib.reload(redis_adapter_module) def test_minio_adapter_import_error_without_extra() -> None: """Missing minio extra raises ImportError with install hint.""" import python_repositories.adapters.minio_adapter as minio_adapter_module with patch.object(builtins, "__import__", new=_block_backend_import("minio")): with pytest.raises(ImportError, match=r"python-repositories\[minio\]"): importlib.reload(minio_adapter_module) importlib.reload(minio_adapter_module) def test_top_level_lazy_import_propagates_redis_import_error() -> None: """Top-level RedisAdapter access surfaces adapter import errors.""" with patch( "importlib.import_module", side_effect=ImportError( "Redis support requires the redis extra. " "Install with: pip install python-repositories[redis]" ), ): with pytest.raises(ImportError, match=r"python-repositories\[redis\]"): _ = python_repositories.RedisAdapter def test_top_level_lazy_import_propagates_minio_import_error() -> None: """Top-level MinioAdapter access surfaces adapter import errors.""" with patch( "importlib.import_module", side_effect=ImportError( "MinIO support requires the minio extra. " "Install with: pip install python-repositories[minio]" ), ): with pytest.raises(ImportError, match=r"python-repositories\[minio\]"): _ = python_repositories.MinioAdapter def test_adapters_subpackage_lazy_import_succeeds() -> None: """Adapter subpackage imports delegate to the same lazy loader.""" from python_repositories.adapters import RedisAdapter assert RedisAdapter.__name__ == "RedisAdapter"