diff --git a/python_repositories/__init__.py b/python_repositories/__init__.py index 6730e45..dfdd8d4 100644 --- a/python_repositories/__init__.py +++ b/python_repositories/__init__.py @@ -2,10 +2,10 @@ from __future__ import annotations -import importlib from typing import TYPE_CHECKING # Interfaces are always available; they have no optional backend dependencies. +from . import adapters from .interfaces import ( ConnectionAwareInterface, ContextAwareInterface, @@ -13,34 +13,24 @@ from .interfaces import ( ObjectRepositoryInterface, ) -# Adapters are imported only for static type checkers; runtime loading is deferred below. +# Adapters are imported only for static type checkers; runtime loading is delegated below. if TYPE_CHECKING: - from .adapters.minio_adapter import MinioAdapter - from .adapters.redis_adapter import RedisAdapter - -# Map public adapter names to their defining module and class. -# Each adapter module fails fast with an install hint if its extra is missing. -_LAZY_EXPORTS = { - "RedisAdapter": (".adapters.redis_adapter", "RedisAdapter"), - "MinioAdapter": (".adapters.minio_adapter", "MinioAdapter"), -} + from .adapters.minio_adapter import MinioAdapter as MinioAdapter + from .adapters.redis_adapter import RedisAdapter as RedisAdapter __all__ = [ "ConnectionAwareInterface", "ContextAwareInterface", "JsonRepositoryInterface", "ObjectRepositoryInterface", - "RedisAdapter", - "MinioAdapter", + *adapters.__all__, ] def __getattr__(name: str) -> object: - """Load adapters on first access so the base package installs without redis/minio.""" - if name in _LAZY_EXPORTS: - module_path, attr = _LAZY_EXPORTS[name] - module = importlib.import_module(module_path, __package__) - return getattr(module, attr) + """Delegate adapter lookups to adapters; lazy loading is defined there.""" + if name in adapters.__all__: + return getattr(adapters, name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/python_repositories/adapters/__init__.py b/python_repositories/adapters/__init__.py index c7c7aad..eb0ef57 100644 --- a/python_repositories/adapters/__init__.py +++ b/python_repositories/adapters/__init__.py @@ -9,10 +9,14 @@ from __future__ import annotations import importlib from typing import TYPE_CHECKING +# Adapters are imported only for static type checkers; runtime loading is deferred below. if TYPE_CHECKING: from .minio_adapter import MinioAdapter from .redis_adapter import RedisAdapter +# Map public adapter names to their defining module and class. +# Each adapter module fails fast with an install hint if its extra is missing. +# When adding a new adapter, update this dict and __all__ only. _LAZY_EXPORTS = { "RedisAdapter": (".redis_adapter", "RedisAdapter"), "MinioAdapter": (".minio_adapter", "MinioAdapter"), @@ -25,6 +29,7 @@ __all__ = [ def __getattr__(name: str) -> object: + """Load an adapter on first access so the base package installs without backend clients.""" if name in _LAZY_EXPORTS: module_path, attr = _LAZY_EXPORTS[name] module = importlib.import_module(module_path, __package__) @@ -33,4 +38,5 @@ def __getattr__(name: str) -> object: def __dir__() -> list[str]: + """Expose lazy adapter names in tab completion and dir().""" return sorted(__all__) diff --git a/tests/unit/optional_dependencies_test.py b/tests/unit/optional_dependencies_test.py index 0af4acc..92f36d3 100644 --- a/tests/unit/optional_dependencies_test.py +++ b/tests/unit/optional_dependencies_test.py @@ -93,3 +93,10 @@ def test_top_level_lazy_import_propagates_minio_import_error() -> None: ): 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"