Coverage for src/local_deep_research/vector_stores/config.py: 100%
30 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""Vector-store provider registry and factory.
3Mirrors ``embeddings/embeddings_config.py``: a flat list of valid providers, a
4lazily-populated class registry, and a single resolution entry point. Only
5FAISS is registered today, and there is no settings key selecting a provider
6yet — ``resolve_provider`` always returns ``"faiss"``. The seam exists so a
7future backend is a bounded addition (register the class, add a setting) rather
8than a rewrite of the call sites.
9"""
11from typing import Dict, Optional, Type
13from .base import BaseVectorStore
15VALID_VECTOR_STORE_PROVIDERS = ["faiss"]
17_DEFAULT_PROVIDER = "faiss"
19# Providers whose store persists to a local file this process owns end to end,
20# i.e. ``BaseVectorStore.is_local_file``. Duplicated here as a *cheap* lookup so
21# callers that only need to branch on it -- notably the deletion cascade, which
22# must choose between unlinking index files and dropping a remote collection --
23# don't have to import the backend module just to read a class attribute.
24# Importing ``faiss_store`` pulls in the native ``faiss`` extension, which is a
25# lot of machinery to load on a path that is about to delete things.
26#
27# MUST stay in sync with each class's ``is_local_file``; a test asserts it.
28# Only local-file providers are listed: an unlisted provider falls through to
29# the real class (see ``provider_is_local_file``), which a remote provider has
30# to be imported for anyway to call ``drop_collection``.
31_LOCAL_FILE_PROVIDERS = frozenset({"faiss"})
33# Lazily-populated {provider_key: class}. Populated on first use to avoid
34# importing faiss at module import time.
35_PROVIDER_CLASSES: Dict[str, Type[BaseVectorStore]] = {}
38def _load_provider_classes() -> Dict[str, Type[BaseVectorStore]]:
39 if not _PROVIDER_CLASSES:
40 from .implementations.faiss_store import FaissVectorStore
42 _PROVIDER_CLASSES[FaissVectorStore.provider_key] = FaissVectorStore
43 return _PROVIDER_CLASSES
46def resolve_provider(settings_snapshot: Optional[dict] = None) -> str:
47 """Return the configured vector-store provider key.
49 No settings key selects this yet — always ``"faiss"``. The
50 ``settings_snapshot`` parameter is accepted now so callers don't change when
51 a setting is introduced later.
52 """
53 return _DEFAULT_PROVIDER
56def _normalize_provider_key(key: str) -> str:
57 """Normalize a provider key from a settings/DB value.
59 Such a value may carry surrounding whitespace/quotes/case (mirrors
60 ``embeddings_config.get_embeddings``).
61 """
62 return key.strip().strip("\"'").strip().lower()
65def provider_is_local_file(
66 provider: Optional[str] = None,
67 settings_snapshot: Optional[dict] = None,
68) -> bool:
69 """Return whether ``provider`` stores its vectors in a local file.
71 This is the discriminator between "unlink the index files" and "drop the
72 remote collection" on deletion paths (see ``BaseVectorStore.is_local_file``).
74 Answers from :data:`_LOCAL_FILE_PROVIDERS` without importing the backend
75 when it can; otherwise falls back to the authoritative class attribute.
76 """
77 key = _normalize_provider_key(
78 provider or resolve_provider(settings_snapshot)
79 ) # gitleaks:allow
80 if key in _LOCAL_FILE_PROVIDERS:
81 return True
82 return get_vector_store_class(key, settings_snapshot).is_local_file
85def only_local_file_providers_registered() -> bool:
86 """Return whether every known provider is a local-file store.
88 True today (FAISS only). While it holds, which provider a user selected
89 cannot change how a deletion cleans up vectors -- every row's files are
90 unlinked and nothing is dropped remotely -- so a deletion does not need
91 the user's settings snapshot to be trustworthy. The first server-backed
92 provider (listed in :data:`VALID_VECTOR_STORE_PROVIDERS` or registered in
93 the class registry) makes this False, and callers must then fail closed
94 when the snapshot cannot be read (see
95 ``cascade_helper.read_vector_cleanup_settings_snapshot``).
96 """
97 known = set(VALID_VECTOR_STORE_PROVIDERS) | set(_PROVIDER_CLASSES)
98 return {_normalize_provider_key(key) for key in known} <= (
99 _LOCAL_FILE_PROVIDERS
100 )
103def get_vector_store_class(
104 provider: Optional[str] = None,
105 settings_snapshot: Optional[dict] = None,
106) -> Type[BaseVectorStore]:
107 """Return the :class:`BaseVectorStore` subclass for ``provider``.
109 Callers construct instances via the class's ``.create(...)`` / ``.load(...)``
110 classmethods (a vector store is stateful, unlike the stateless embeddings
111 providers, so the factory hands back the class rather than an instance).
112 """
113 key = provider or resolve_provider(settings_snapshot) # gitleaks:allow
114 key = _normalize_provider_key(key)
115 classes = _load_provider_classes()
116 if key not in classes:
117 raise ValueError(
118 f"Unknown vector-store provider '{key}'. "
119 f"Valid providers: {sorted(classes)}"
120 )
121 return classes[key]