vector-inspector 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. vector_inspector/core/connection_manager.py +55 -49
  2. vector_inspector/core/connections/base_connection.py +41 -41
  3. vector_inspector/core/connections/chroma_connection.py +110 -86
  4. vector_inspector/core/connections/pinecone_connection.py +168 -182
  5. vector_inspector/core/connections/qdrant_connection.py +109 -126
  6. vector_inspector/core/connections/qdrant_helpers/__init__.py +4 -0
  7. vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +35 -0
  8. vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +51 -0
  9. vector_inspector/core/connections/template_connection.py +55 -65
  10. vector_inspector/core/embedding_utils.py +32 -32
  11. vector_inspector/core/logging.py +27 -0
  12. vector_inspector/core/model_registry.py +4 -3
  13. vector_inspector/main.py +6 -2
  14. vector_inspector/services/backup_helpers.py +63 -0
  15. vector_inspector/services/backup_restore_service.py +73 -152
  16. vector_inspector/services/credential_service.py +33 -40
  17. vector_inspector/services/import_export_service.py +70 -67
  18. vector_inspector/services/profile_service.py +92 -94
  19. vector_inspector/services/settings_service.py +68 -48
  20. vector_inspector/services/visualization_service.py +40 -39
  21. vector_inspector/ui/components/splash_window.py +57 -0
  22. vector_inspector/ui/dialogs/cross_db_migration.py +6 -5
  23. vector_inspector/ui/main_window.py +200 -146
  24. vector_inspector/ui/views/info_panel.py +208 -127
  25. vector_inspector/ui/views/metadata_view.py +8 -7
  26. vector_inspector/ui/views/search_view.py +97 -75
  27. vector_inspector/ui/views/visualization_view.py +140 -97
  28. vector_inspector/utils/version.py +5 -0
  29. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/METADATA +9 -2
  30. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/RECORD +32 -25
  31. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/WHEEL +0 -0
  32. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/entry_points.txt +0 -0
@@ -6,10 +6,12 @@ from enum import Enum
6
6
  from PySide6.QtCore import QObject, Signal
7
7
 
8
8
  from .connections.base_connection import VectorDBConnection
9
+ from vector_inspector.core.logging import log_error
9
10
 
10
11
 
11
12
  class ConnectionState(Enum):
12
13
  """Possible connection states."""
14
+
13
15
  DISCONNECTED = "disconnected"
14
16
  CONNECTING = "connecting"
15
17
  CONNECTED = "connected"
@@ -18,18 +20,18 @@ class ConnectionState(Enum):
18
20
 
19
21
  class ConnectionInstance:
20
22
  """Represents a single active connection with its state and context."""
21
-
23
+
22
24
  def __init__(
23
25
  self,
24
26
  connection_id: str,
25
27
  name: str,
26
28
  provider: str,
27
29
  connection: VectorDBConnection,
28
- config: Dict[str, Any]
30
+ config: Dict[str, Any],
29
31
  ):
30
32
  """
31
33
  Initialize a connection instance.
32
-
34
+
33
35
  Args:
34
36
  connection_id: Unique connection identifier
35
37
  name: User-friendly connection name
@@ -46,11 +48,11 @@ class ConnectionInstance:
46
48
  self.active_collection: Optional[str] = None
47
49
  self.collections: List[str] = []
48
50
  self.error_message: Optional[str] = None
49
-
51
+
50
52
  def get_display_name(self) -> str:
51
53
  """Get a display-friendly connection name."""
52
54
  return f"{self.name} ({self.provider})"
53
-
55
+
54
56
  def get_breadcrumb(self) -> str:
55
57
  """Get breadcrumb showing connection > collection."""
56
58
  if self.active_collection:
@@ -60,7 +62,7 @@ class ConnectionInstance:
60
62
 
61
63
  class ConnectionManager(QObject):
62
64
  """Manages multiple vector database connections and saved profiles.
63
-
65
+
64
66
  Signals:
65
67
  connection_opened: Emitted when a new connection is opened (connection_id)
66
68
  connection_closed: Emitted when a connection is closed (connection_id)
@@ -69,7 +71,7 @@ class ConnectionManager(QObject):
69
71
  active_collection_changed: Emitted when active collection changes (connection_id, collection_name or None)
70
72
  collections_updated: Emitted when collections list is updated (connection_id, collections)
71
73
  """
72
-
74
+
73
75
  # Signals
74
76
  connection_opened = Signal(str) # connection_id
75
77
  connection_closed = Signal(str) # connection_id
@@ -77,116 +79,119 @@ class ConnectionManager(QObject):
77
79
  active_connection_changed = Signal(object) # connection_id or None
78
80
  active_collection_changed = Signal(str, object) # connection_id, collection_name or None
79
81
  collections_updated = Signal(str, list) # connection_id, collections
80
-
82
+
81
83
  MAX_CONNECTIONS = 10 # Limit to prevent resource exhaustion
82
-
84
+
83
85
  def __init__(self):
84
86
  """Initialize the connection manager."""
85
87
  super().__init__()
86
88
  self._connections: Dict[str, ConnectionInstance] = {}
87
89
  self._active_connection_id: Optional[str] = None
88
-
90
+
89
91
  def create_connection(
90
92
  self,
91
93
  name: str,
92
94
  provider: str,
93
95
  connection: VectorDBConnection,
94
- config: Dict[str, Any]
96
+ config: Dict[str, Any],
97
+ connection_id: str = None,
95
98
  ) -> str:
96
99
  """
97
100
  Create a new connection instance (not yet connected).
98
-
101
+
99
102
  Args:
100
103
  name: User-friendly connection name
101
104
  provider: Provider type
102
105
  connection: The connection object
103
106
  config: Connection configuration
104
-
107
+ connection_id: Optional. Use this ID instead of generating a new one (for profiles).
108
+
105
109
  Returns:
106
110
  The connection ID
107
-
111
+
108
112
  Raises:
109
113
  RuntimeError: If maximum connections limit reached
110
114
  """
111
115
  if len(self._connections) >= self.MAX_CONNECTIONS:
112
116
  raise RuntimeError(f"Maximum number of connections ({self.MAX_CONNECTIONS}) reached")
113
-
114
- connection_id = str(uuid.uuid4())
117
+
118
+ if connection_id is None:
119
+ connection_id = str(uuid.uuid4())
115
120
  instance = ConnectionInstance(connection_id, name, provider, connection, config)
116
121
  self._connections[connection_id] = instance
117
-
122
+
118
123
  # Set as active if it's the first connection
119
124
  if len(self._connections) == 1:
120
125
  self._active_connection_id = connection_id
121
126
  self.active_connection_changed.emit(connection_id)
122
-
127
+
123
128
  # Don't emit connection_opened yet - wait until actually connected
124
129
  return connection_id
125
-
130
+
126
131
  def mark_connection_opened(self, connection_id: str):
127
132
  """
128
133
  Mark a connection as opened (after successful connection).
129
-
134
+
130
135
  Args:
131
136
  connection_id: ID of connection that opened
132
137
  """
133
138
  if connection_id in self._connections:
134
139
  self.connection_opened.emit(connection_id)
135
-
140
+
136
141
  def get_connection(self, connection_id: str) -> Optional[ConnectionInstance]:
137
142
  """Get a connection instance by ID."""
138
143
  return self._connections.get(connection_id)
139
-
144
+
140
145
  def get_active_connection(self) -> Optional[ConnectionInstance]:
141
146
  """Get the currently active connection instance."""
142
147
  if self._active_connection_id:
143
148
  return self._connections.get(self._active_connection_id)
144
149
  return None
145
-
150
+
146
151
  def get_active_connection_id(self) -> Optional[str]:
147
152
  """Get the currently active connection ID."""
148
153
  return self._active_connection_id
149
-
154
+
150
155
  def set_active_connection(self, connection_id: str) -> bool:
151
156
  """
152
157
  Set the active connection.
153
-
158
+
154
159
  Args:
155
160
  connection_id: ID of connection to make active
156
-
161
+
157
162
  Returns:
158
163
  True if successful, False if connection not found
159
164
  """
160
165
  if connection_id not in self._connections:
161
166
  return False
162
-
167
+
163
168
  self._active_connection_id = connection_id
164
169
  self.active_connection_changed.emit(connection_id)
165
170
  return True
166
-
171
+
167
172
  def close_connection(self, connection_id: str) -> bool:
168
173
  """
169
174
  Close and remove a connection.
170
-
175
+
171
176
  Args:
172
177
  connection_id: ID of connection to close
173
-
178
+
174
179
  Returns:
175
180
  True if successful, False if connection not found
176
181
  """
177
182
  instance = self._connections.get(connection_id)
178
183
  if not instance:
179
184
  return False
180
-
185
+
181
186
  # Disconnect the connection
182
187
  try:
183
188
  instance.connection.disconnect()
184
189
  except Exception as e:
185
- print(f"Error disconnecting: {e}")
186
-
190
+ log_error("Error disconnecting: %s", e)
191
+
187
192
  # Remove from connections dict
188
193
  del self._connections[connection_id]
189
-
194
+
190
195
  # If this was the active connection, set a new one or None
191
196
  if self._active_connection_id == connection_id:
192
197
  if self._connections:
@@ -196,14 +201,16 @@ class ConnectionManager(QObject):
196
201
  else:
197
202
  self._active_connection_id = None
198
203
  self.active_connection_changed.emit(None)
199
-
204
+
200
205
  self.connection_closed.emit(connection_id)
201
206
  return True
202
-
203
- def update_connection_state(self, connection_id: str, state: ConnectionState, error: Optional[str] = None):
207
+
208
+ def update_connection_state(
209
+ self, connection_id: str, state: ConnectionState, error: Optional[str] = None
210
+ ):
204
211
  """
205
212
  Update the state of a connection.
206
-
213
+
207
214
  Args:
208
215
  connection_id: ID of connection
209
216
  state: New connection state
@@ -217,11 +224,11 @@ class ConnectionManager(QObject):
217
224
  else:
218
225
  instance.error_message = None
219
226
  self.connection_state_changed.emit(connection_id, state)
220
-
227
+
221
228
  def update_collections(self, connection_id: str, collections: List[str]):
222
229
  """
223
230
  Update the collections list for a connection.
224
-
231
+
225
232
  Args:
226
233
  connection_id: ID of connection
227
234
  collections: List of collection names
@@ -230,11 +237,11 @@ class ConnectionManager(QObject):
230
237
  if instance:
231
238
  instance.collections = collections
232
239
  self.collections_updated.emit(connection_id, collections)
233
-
240
+
234
241
  def set_active_collection(self, connection_id: str, collection_name: Optional[str]):
235
242
  """
236
243
  Set the active collection for a connection.
237
-
244
+
238
245
  Args:
239
246
  connection_id: ID of connection
240
247
  collection_name: Name of collection to make active, or None
@@ -243,29 +250,29 @@ class ConnectionManager(QObject):
243
250
  if instance:
244
251
  instance.active_collection = collection_name
245
252
  self.active_collection_changed.emit(connection_id, collection_name)
246
-
253
+
247
254
  def get_all_connections(self) -> List[ConnectionInstance]:
248
255
  """Get list of all connection instances."""
249
256
  return list(self._connections.values())
250
-
257
+
251
258
  def get_connection_count(self) -> int:
252
259
  """Get the number of active connections."""
253
260
  return len(self._connections)
254
-
261
+
255
262
  def close_all_connections(self):
256
263
  """Close all connections. Typically called on application exit."""
257
264
  connection_ids = list(self._connections.keys())
258
265
  for conn_id in connection_ids:
259
266
  self.close_connection(conn_id)
260
-
267
+
261
268
  def rename_connection(self, connection_id: str, new_name: str) -> bool:
262
269
  """
263
270
  Rename a connection.
264
-
271
+
265
272
  Args:
266
273
  connection_id: ID of connection
267
274
  new_name: New name for the connection
268
-
275
+
269
276
  Returns:
270
277
  True if successful, False if connection not found
271
278
  """
@@ -274,4 +281,3 @@ class ConnectionManager(QObject):
274
281
  instance.name = new_name
275
282
  return True
276
283
  return False
277
-
@@ -2,59 +2,60 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import Optional, List, Dict, Any
5
+ from vector_inspector.core.logging import log_error
5
6
 
6
7
 
7
8
  class VectorDBConnection(ABC):
8
9
  """Abstract base class for vector database connections.
9
-
10
+
10
11
  This class defines the interface that all vector database providers
11
12
  must implement to be compatible with Vector Inspector.
12
13
  """
13
-
14
+
14
15
  @abstractmethod
15
16
  def connect(self) -> bool:
16
17
  """
17
18
  Establish connection to the vector database.
18
-
19
+
19
20
  Returns:
20
21
  True if connection successful, False otherwise
21
22
  """
22
23
  pass
23
-
24
+
24
25
  @abstractmethod
25
26
  def disconnect(self):
26
27
  """Close connection to the vector database."""
27
28
  pass
28
-
29
+
29
30
  @property
30
31
  @abstractmethod
31
32
  def is_connected(self) -> bool:
32
33
  """
33
34
  Check if connected to the vector database.
34
-
35
+
35
36
  Returns:
36
37
  True if connected, False otherwise
37
38
  """
38
39
  pass
39
-
40
+
40
41
  @abstractmethod
41
42
  def list_collections(self) -> List[str]:
42
43
  """
43
44
  Get list of all collections/indexes.
44
-
45
+
45
46
  Returns:
46
47
  List of collection/index names
47
48
  """
48
49
  pass
49
-
50
+
50
51
  @abstractmethod
51
52
  def get_collection_info(self, name: str) -> Optional[Dict[str, Any]]:
52
53
  """
53
54
  Get collection metadata and statistics.
54
-
55
+
55
56
  Args:
56
57
  name: Collection/index name
57
-
58
+
58
59
  Returns:
59
60
  Dictionary with collection info including:
60
61
  - name: Collection name
@@ -94,7 +95,7 @@ class VectorDBConnection(ABC):
94
95
  def count_collection(self, name: str) -> int:
95
96
  """Return the number of items in the collection."""
96
97
  pass
97
-
98
+
98
99
  @abstractmethod
99
100
  def query_collection(
100
101
  self,
@@ -107,7 +108,7 @@ class VectorDBConnection(ABC):
107
108
  ) -> Optional[Dict[str, Any]]:
108
109
  """
109
110
  Query a collection for similar vectors.
110
-
111
+
111
112
  Args:
112
113
  collection_name: Name of collection to query
113
114
  query_texts: Text queries to embed and search
@@ -115,7 +116,7 @@ class VectorDBConnection(ABC):
115
116
  n_results: Number of results to return
116
117
  where: Metadata filter
117
118
  where_document: Document content filter
118
-
119
+
119
120
  Returns:
120
121
  Query results dictionary with keys:
121
122
  - ids: List of result IDs
@@ -125,7 +126,7 @@ class VectorDBConnection(ABC):
125
126
  - embeddings: List of embedding vectors (optional)
126
127
  """
127
128
  pass
128
-
129
+
129
130
  @abstractmethod
130
131
  def get_all_items(
131
132
  self,
@@ -136,13 +137,13 @@ class VectorDBConnection(ABC):
136
137
  ) -> Optional[Dict[str, Any]]:
137
138
  """
138
139
  Get all items from a collection.
139
-
140
+
140
141
  Args:
141
142
  collection_name: Name of collection
142
143
  limit: Maximum number of items to return
143
144
  offset: Number of items to skip
144
145
  where: Metadata filter
145
-
146
+
146
147
  Returns:
147
148
  Dictionary with collection items:
148
149
  - ids: List of item IDs
@@ -151,7 +152,7 @@ class VectorDBConnection(ABC):
151
152
  - embeddings: List of embedding vectors
152
153
  """
153
154
  pass
154
-
155
+
155
156
  @abstractmethod
156
157
  def update_items(
157
158
  self,
@@ -163,19 +164,19 @@ class VectorDBConnection(ABC):
163
164
  ) -> bool:
164
165
  """
165
166
  Update items in a collection.
166
-
167
+
167
168
  Args:
168
169
  collection_name: Name of collection
169
170
  ids: IDs of items to update
170
171
  documents: New document texts
171
172
  metadatas: New metadata
172
173
  embeddings: New embeddings
173
-
174
+
174
175
  Returns:
175
176
  True if successful, False otherwise
176
177
  """
177
178
  pass
178
-
179
+
179
180
  @abstractmethod
180
181
  def delete_items(
181
182
  self,
@@ -185,36 +186,32 @@ class VectorDBConnection(ABC):
185
186
  ) -> bool:
186
187
  """
187
188
  Delete items from a collection.
188
-
189
+
189
190
  Args:
190
191
  collection_name: Name of collection
191
192
  ids: IDs of items to delete
192
193
  where: Metadata filter for items to delete
193
-
194
+
194
195
  Returns:
195
196
  True if successful, False otherwise
196
197
  """
197
198
  pass
198
-
199
199
 
200
200
  # Optional: Methods that may be provider-specific but useful to define
201
-
201
+
202
202
  def get_connection_info(self) -> Dict[str, Any]:
203
203
  """
204
204
  Get information about the current connection.
205
-
205
+
206
206
  Returns:
207
207
  Dictionary with connection details (provider-specific)
208
208
  """
209
- return {
210
- "provider": self.__class__.__name__,
211
- "connected": self.is_connected
212
- }
213
-
209
+ return {"provider": self.__class__.__name__, "connected": self.is_connected}
210
+
214
211
  def get_supported_filter_operators(self) -> List[Dict[str, Any]]:
215
212
  """
216
213
  Get list of filter operators supported by this provider.
217
-
214
+
218
215
  Returns:
219
216
  List of operator dictionaries with 'name' and 'server_side' keys
220
217
  """
@@ -230,20 +227,22 @@ class VectorDBConnection(ABC):
230
227
  {"name": "not in", "server_side": True},
231
228
  {"name": "contains", "server_side": False},
232
229
  ]
233
-
234
- def get_embedding_model(self, collection_name: str, connection_id: Optional[str] = None) -> Optional[str]:
230
+
231
+ def get_embedding_model(
232
+ self, collection_name: str, connection_id: Optional[str] = None
233
+ ) -> Optional[str]:
235
234
  """
236
235
  Get the embedding model used for a collection.
237
-
236
+
238
237
  Retrieves the model name from:
239
238
  1. Collection-level metadata (if supported)
240
239
  2. Vector metadata (_embedding_model field)
241
240
  3. User settings (for collections we can't modify)
242
-
241
+
243
242
  Args:
244
243
  collection_name: Name of collection
245
244
  connection_id: Optional connection ID for settings lookup
246
-
245
+
247
246
  Returns:
248
247
  Model name string (e.g., "sentence-transformers/all-MiniLM-L6-v2") or None
249
248
  """
@@ -252,23 +251,24 @@ class VectorDBConnection(ABC):
252
251
  info = self.get_collection_info(collection_name)
253
252
  if info and info.get("embedding_model"):
254
253
  return info["embedding_model"]
255
-
254
+
256
255
  # Fall back to checking a sample vector's metadata
257
256
  data = self.get_all_items(collection_name, limit=1, offset=0)
258
257
  if data and data.get("metadatas") and len(data["metadatas"]) > 0:
259
258
  metadata = data["metadatas"][0]
260
259
  if "_embedding_model" in metadata:
261
260
  return metadata["_embedding_model"]
262
-
261
+
263
262
  # Finally, check user settings (for collections we can't modify)
264
263
  if connection_id:
265
264
  from ...services.settings_service import SettingsService
265
+
266
266
  settings = SettingsService()
267
267
  model_info = settings.get_embedding_model(connection_id, collection_name)
268
268
  if model_info:
269
269
  return model_info["model"]
270
-
270
+
271
271
  return None
272
272
  except Exception as e:
273
- print(f"Failed to get embedding model: {e}")
273
+ log_error("Failed to get embedding model: %s", e)
274
274
  return None