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

1"""Vector-store provider registry and factory. 

2 

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""" 

10 

11from typing import Dict, Optional, Type 

12 

13from .base import BaseVectorStore 

14 

15VALID_VECTOR_STORE_PROVIDERS = ["faiss"] 

16 

17_DEFAULT_PROVIDER = "faiss" 

18 

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"}) 

32 

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]] = {} 

36 

37 

38def _load_provider_classes() -> Dict[str, Type[BaseVectorStore]]: 

39 if not _PROVIDER_CLASSES: 

40 from .implementations.faiss_store import FaissVectorStore 

41 

42 _PROVIDER_CLASSES[FaissVectorStore.provider_key] = FaissVectorStore 

43 return _PROVIDER_CLASSES 

44 

45 

46def resolve_provider(settings_snapshot: Optional[dict] = None) -> str: 

47 """Return the configured vector-store provider key. 

48 

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 

54 

55 

56def _normalize_provider_key(key: str) -> str: 

57 """Normalize a provider key from a settings/DB value. 

58 

59 Such a value may carry surrounding whitespace/quotes/case (mirrors 

60 ``embeddings_config.get_embeddings``). 

61 """ 

62 return key.strip().strip("\"'").strip().lower() 

63 

64 

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. 

70 

71 This is the discriminator between "unlink the index files" and "drop the 

72 remote collection" on deletion paths (see ``BaseVectorStore.is_local_file``). 

73 

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 

83 

84 

85def only_local_file_providers_registered() -> bool: 

86 """Return whether every known provider is a local-file store. 

87 

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 ) 

101 

102 

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``. 

108 

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]