"""Tests for optional dependency import behavior.""" from __future__ import annotations import builtins import importlib import sys from collections.abc import Callable, Mapping, Sequence from types import ModuleType from unittest.mock import patch import pytest import python_repositories from python_repositories import JsonRepositoryInterface 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: """Interfaces are available without loading adapter modules.""" 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 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"