vector-inspector 0.3.6__tar.gz → 0.3.8__tar.gz

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 (82) hide show
  1. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/PKG-INFO +27 -5
  2. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/README.md +26 -4
  3. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/pyproject.toml +1 -1
  4. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/chroma_connection.py +4 -1
  5. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/pgvector_connection.py +108 -93
  6. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/pinecone_connection.py +4 -0
  7. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/qdrant_connection.py +13 -0
  8. vector_inspector-0.3.8/src/vector_inspector/core/provider_factory.py +97 -0
  9. vector_inspector-0.3.8/src/vector_inspector/services/update_service.py +73 -0
  10. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/splash_window.py +14 -2
  11. vector_inspector-0.3.8/src/vector_inspector/ui/components/update_details_dialog.py +47 -0
  12. vector_inspector-0.3.8/src/vector_inspector/ui/controllers/__init__.py +1 -0
  13. vector_inspector-0.3.8/src/vector_inspector/ui/controllers/connection_controller.py +177 -0
  14. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/main_window.py +148 -323
  15. vector_inspector-0.3.8/src/vector_inspector/ui/main_window_shell.py +106 -0
  16. vector_inspector-0.3.8/src/vector_inspector/ui/services/__init__.py +1 -0
  17. vector_inspector-0.3.8/src/vector_inspector/ui/services/dialog_service.py +113 -0
  18. vector_inspector-0.3.8/src/vector_inspector/ui/tabs.py +64 -0
  19. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/metadata_view.py +72 -0
  20. vector_inspector-0.3.8/src/vector_inspector/utils/version.py +12 -0
  21. vector_inspector-0.3.8/tests/test_chroma_connection.py +105 -0
  22. vector_inspector-0.3.8/tests/test_pgvector_connection.py +91 -0
  23. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_pinecone_connection.py +114 -98
  24. vector_inspector-0.3.8/tests/test_qdrant_connection.py +128 -0
  25. vector_inspector-0.3.6/src/vector_inspector/utils/version.py +0 -5
  26. vector_inspector-0.3.6/tests/test_connections.py +0 -71
  27. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/__init__.py +0 -0
  28. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/__main__.py +0 -0
  29. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/config/__init__.py +0 -0
  30. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/config/known_embedding_models.json +0 -0
  31. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/__init__.py +0 -0
  32. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/cache_manager.py +0 -0
  33. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connection_manager.py +0 -0
  34. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/__init__.py +0 -0
  35. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/base_connection.py +0 -0
  36. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/qdrant_helpers/__init__.py +0 -0
  37. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +0 -0
  38. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +0 -0
  39. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/connections/template_connection.py +0 -0
  40. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_providers/__init__.py +0 -0
  41. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_providers/base_provider.py +0 -0
  42. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_providers/clip_provider.py +0 -0
  43. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_providers/provider_factory.py +0 -0
  44. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_providers/sentence_transformer_provider.py +0 -0
  45. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/embedding_utils.py +0 -0
  46. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/logging.py +0 -0
  47. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/core/model_registry.py +0 -0
  48. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/main.py +0 -0
  49. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/__init__.py +0 -0
  50. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/backup_helpers.py +0 -0
  51. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/backup_restore_service.py +0 -0
  52. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/credential_service.py +0 -0
  53. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/filter_service.py +0 -0
  54. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/import_export_service.py +0 -0
  55. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/profile_service.py +0 -0
  56. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/settings_service.py +0 -0
  57. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/services/visualization_service.py +0 -0
  58. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/__init__.py +0 -0
  59. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/__init__.py +0 -0
  60. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/backup_restore_dialog.py +0 -0
  61. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/connection_manager_panel.py +0 -0
  62. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/filter_builder.py +0 -0
  63. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/item_dialog.py +0 -0
  64. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/loading_dialog.py +0 -0
  65. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/components/profile_manager_panel.py +0 -0
  66. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/dialogs/__init__.py +0 -0
  67. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/dialogs/cross_db_migration.py +0 -0
  68. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/dialogs/embedding_config_dialog.py +0 -0
  69. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/dialogs/provider_type_dialog.py +0 -0
  70. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/__init__.py +0 -0
  71. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/collection_browser.py +0 -0
  72. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/connection_view.py +0 -0
  73. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/info_panel.py +0 -0
  74. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/search_view.py +0 -0
  75. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/ui/views/visualization_view.py +0 -0
  76. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/utils/__init__.py +0 -0
  77. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/src/vector_inspector/utils/lazy_imports.py +0 -0
  78. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_backup_helpers.py +0 -0
  79. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_backup_restore_service.py +0 -0
  80. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_filter_service.py +0 -0
  81. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_runner.py +0 -0
  82. {vector_inspector-0.3.6 → vector_inspector-0.3.8}/tests/test_settings_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vector-inspector
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: A comprehensive desktop application for visualizing, querying, and managing vector database data
5
5
  Author-Email: Anthony Dawson <anthonypdawson+github@gmail.com>
6
6
  License: MIT
@@ -31,9 +31,31 @@ Requires-Dist: pgvector>=0.4.2
31
31
  Description-Content-Type: text/markdown
32
32
 
33
33
  # Latest updates
34
- - Added Postgres (pgvector extension) support as a new vector database connection option.
35
- - Fixed issue with embedding regeneration on add/edit
36
- - Added option for editing items without regenerating embeddings
34
+
35
+ ## Vector Inspector 2026.01 Release Notes
36
+
37
+ ### Major Refactor and Studio-Ready Architecture
38
+ - Refactored main window into modular components:
39
+ - InspectorShell: reusable UI shell (splitter, tabs, layout)
40
+ - ProviderFactory: centralized connection creation
41
+ - DialogService: dialog management
42
+ - ConnectionController: connection lifecycle and threading
43
+ - InspectorTabs: pluggable tab registry
44
+ - MainWindow now inherits from InspectorShell and is fully reusable as a widget
45
+ - Bootstrap logic is separated from UI logic—Studio can host Inspector as a component
46
+ - Tab system is now pluggable: Studio and Inspector can add, remove, or override tabs via TabDefinition
47
+ - All Inspector UI logic is self-contained; Studio can extend without modifying Inspector code
48
+
49
+ ### Data Browser Improvements
50
+ - Added a checkbox: Generate embeddings on edit (default: checked)
51
+ - When unchecked, editing a row skips embedding regeneration
52
+ - Setting is persisted per user
53
+
54
+ ### Developer and Architecture Notes
55
+ - All modules pass syntax checks and are ready for Studio integration
56
+ - No breaking changes for existing Inspector users
57
+ - Inspector is now a true UI module, not just an application
58
+
37
59
  ---
38
60
 
39
61
  # Vector Inspector
@@ -41,7 +63,7 @@ Description-Content-Type: text/markdown
41
63
  > **Disclaimer:** This tool is currently under active development and is **not production ready**. Not all features have been thoroughly tested and code is released frequently. Use with caution in critical or production environments.
42
64
 
43
65
  [![CI](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml)
44
- [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml)
66
+ [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml)
45
67
 
46
68
  [![PyPI Version](https://img.shields.io/pypi/v/vector-inspector.svg?cacheSeconds=300)](https://pypi.org/project/vector-inspector/)
47
69
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/vector-inspector?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/vector-inspector)
@@ -1,7 +1,29 @@
1
1
  # Latest updates
2
- - Added Postgres (pgvector extension) support as a new vector database connection option.
3
- - Fixed issue with embedding regeneration on add/edit
4
- - Added option for editing items without regenerating embeddings
2
+
3
+ ## Vector Inspector 2026.01 Release Notes
4
+
5
+ ### Major Refactor and Studio-Ready Architecture
6
+ - Refactored main window into modular components:
7
+ - InspectorShell: reusable UI shell (splitter, tabs, layout)
8
+ - ProviderFactory: centralized connection creation
9
+ - DialogService: dialog management
10
+ - ConnectionController: connection lifecycle and threading
11
+ - InspectorTabs: pluggable tab registry
12
+ - MainWindow now inherits from InspectorShell and is fully reusable as a widget
13
+ - Bootstrap logic is separated from UI logic—Studio can host Inspector as a component
14
+ - Tab system is now pluggable: Studio and Inspector can add, remove, or override tabs via TabDefinition
15
+ - All Inspector UI logic is self-contained; Studio can extend without modifying Inspector code
16
+
17
+ ### Data Browser Improvements
18
+ - Added a checkbox: Generate embeddings on edit (default: checked)
19
+ - When unchecked, editing a row skips embedding regeneration
20
+ - Setting is persisted per user
21
+
22
+ ### Developer and Architecture Notes
23
+ - All modules pass syntax checks and are ready for Studio integration
24
+ - No breaking changes for existing Inspector users
25
+ - Inspector is now a true UI module, not just an application
26
+
5
27
  ---
6
28
 
7
29
  # Vector Inspector
@@ -9,7 +31,7 @@
9
31
  > **Disclaimer:** This tool is currently under active development and is **not production ready**. Not all features have been thoroughly tested and code is released frequently. Use with caution in critical or production environments.
10
32
 
11
33
  [![CI](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml)
12
- [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml)
34
+ [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml)
13
35
 
14
36
  [![PyPI Version](https://img.shields.io/pypi/v/vector-inspector.svg?cacheSeconds=300)](https://pypi.org/project/vector-inspector/)
15
37
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/vector-inspector?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/vector-inspector)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vector-inspector"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  description = "A comprehensive desktop application for visualizing, querying, and managing vector database data"
5
5
  authors = [
6
6
  { name = "Anthony Dawson", email = "anthonypdawson+github@gmail.com" },
@@ -489,7 +489,7 @@ class ChromaDBConnection(VectorDBConnection):
489
489
  name: Collection name
490
490
 
491
491
  Returns:
492
- True if successful, False otherwise
492
+ True if successful or collection does not exist, False otherwise
493
493
  """
494
494
  if not self._client:
495
495
  return False
@@ -500,6 +500,9 @@ class ChromaDBConnection(VectorDBConnection):
500
500
  self._current_collection = None
501
501
  return True
502
502
  except Exception as e:
503
+ # If the exception is about the collection not existing, treat as success (idempotent)
504
+ if "does not exist" in str(e).lower():
505
+ return True
503
506
  log_error("Failed to delete collection: %s", e)
504
507
  return False
505
508
 
@@ -1,6 +1,6 @@
1
1
  """PgVector/PostgreSQL connection manager."""
2
2
 
3
- from typing import Optional, List, Dict, Any
3
+ from typing import Any
4
4
  import json
5
5
  import psycopg2
6
6
  from psycopg2 import sql
@@ -36,7 +36,7 @@ class PgVectorConnection(VectorDBConnection):
36
36
  self.database = database
37
37
  self.user = user
38
38
  self.password = password
39
- self._client: Optional[psycopg2.extensions.connection] = None
39
+ self._client = None
40
40
  # Track how many embeddings were regenerated by the last update operation
41
41
  self._last_regenerated_count: int = 0
42
42
 
@@ -94,7 +94,7 @@ class PgVectorConnection(VectorDBConnection):
94
94
  """Check if connected to PostgreSQL."""
95
95
  return self._client is not None
96
96
 
97
- def list_collections(self) -> List[str]:
97
+ def list_collections(self) -> list[str]:
98
98
  """
99
99
  Get list of all vector tables (collections).
100
100
 
@@ -117,7 +117,7 @@ class PgVectorConnection(VectorDBConnection):
117
117
  log_error("Failed to list collections: %s", e)
118
118
  return []
119
119
 
120
- def list_databases(self) -> List[str]:
120
+ def list_databases(self) -> list[str]:
121
121
  """
122
122
  List available databases on the server (non-template databases).
123
123
 
@@ -155,7 +155,7 @@ class PgVectorConnection(VectorDBConnection):
155
155
  except Exception:
156
156
  pass
157
157
 
158
- def get_collection_info(self, name: str) -> Optional[Dict[str, Any]]:
158
+ def get_collection_info(self, name: str) -> dict[str, Any] | None:
159
159
  """
160
160
  Get collection metadata and statistics.
161
161
 
@@ -291,10 +291,10 @@ class PgVectorConnection(VectorDBConnection):
291
291
  def add_items(
292
292
  self,
293
293
  collection_name: str,
294
- documents: List[str],
295
- metadatas: Optional[List[Dict[str, Any]]] = None,
296
- ids: Optional[List[str]] = None,
297
- embeddings: Optional[List[List[float]]] = None,
294
+ documents: list[str],
295
+ metadatas: list[dict[str, Any]] | None = None,
296
+ ids: list[str] | None = None,
297
+ embeddings: list[list[float]] | None = None,
298
298
  ) -> bool:
299
299
  """
300
300
  Add items to a collection.
@@ -312,66 +312,16 @@ class PgVectorConnection(VectorDBConnection):
312
312
  if not self._client:
313
313
  return False
314
314
 
315
- # If embeddings weren't provided, try to compute them using configured/default model
315
+ # If embeddings weren't provided, try to compute them using the helper method
316
316
  if not embeddings:
317
317
  try:
318
- from vector_inspector.services.settings_service import SettingsService
319
- from vector_inspector.core.embedding_utils import (
320
- load_embedding_model,
321
- get_embedding_model_for_dimension,
322
- DEFAULT_MODEL,
323
- encode_text,
324
- )
325
-
326
- model_name = None
327
- model_type = None
328
-
329
- # 1) settings
330
- settings = SettingsService()
331
- model_info = settings.get_embedding_model(self.database, collection_name)
332
- if model_info:
333
- model_name = model_info.get("model")
334
- model_type = model_info.get("type", "sentence-transformer")
335
-
336
- # 2) collection metadata
337
- coll_info = None
338
- if not model_name:
339
- coll_info = self.get_collection_info(collection_name)
340
- if coll_info and coll_info.get("embedding_model"):
341
- model_name = coll_info.get("embedding_model")
342
- model_type = coll_info.get("embedding_model_type", "stored")
343
-
344
- # 3) dimension-based fallback
345
- loaded_model = None
346
- if not model_name:
347
- # Try to get vector dimension
348
- dim = None
349
- if not coll_info:
350
- coll_info = self.get_collection_info(collection_name)
351
- if coll_info and coll_info.get("vector_dimension"):
352
- try:
353
- dim = int(coll_info.get("vector_dimension"))
354
- except Exception:
355
- dim = None
356
- if dim:
357
- loaded_model, model_name, model_type = get_embedding_model_for_dimension(
358
- dim
359
- )
360
- else:
361
- model_name, model_type = DEFAULT_MODEL
362
-
363
- # Load model
364
- if not loaded_model:
365
- loaded_model = load_embedding_model(model_name, model_type)
366
-
367
- # Compute embeddings for all documents
368
- if model_type != "clip":
369
- embeddings = loaded_model.encode(documents, show_progress_bar=False).tolist()
370
- else:
371
- embeddings = [encode_text(d, loaded_model, model_type) for d in documents]
318
+ embeddings = self.compute_embeddings_for_documents(collection_name, documents)
372
319
  except Exception as e:
373
320
  log_error("Failed to compute embeddings on add: %s", e)
374
321
  return False
322
+ if embeddings is None:
323
+ return False
324
+
375
325
  try:
376
326
  import uuid
377
327
 
@@ -428,7 +378,7 @@ class PgVectorConnection(VectorDBConnection):
428
378
  self._client.rollback()
429
379
  return False
430
380
 
431
- def get_items(self, name: str, ids: List[str]) -> Dict[str, Any]:
381
+ def get_items(self, name: str, ids: list[str]) -> dict[str, Any]:
432
382
  """
433
383
  Retrieve items by IDs.
434
384
 
@@ -548,12 +498,12 @@ class PgVectorConnection(VectorDBConnection):
548
498
  def query_collection(
549
499
  self,
550
500
  collection_name: str,
551
- query_texts: Optional[List[str]] = None,
552
- query_embeddings: Optional[List[List[float]]] = None,
501
+ query_texts: list[str] | None = None,
502
+ query_embeddings: list[list[float]] | None = None,
553
503
  n_results: int = 10,
554
- where: Optional[Dict[str, Any]] = None,
555
- where_document: Optional[Dict[str, Any]] = None,
556
- ) -> Optional[Dict[str, Any]]:
504
+ where: dict[str, Any] | None = None,
505
+ where_document: dict[str, Any] | None = None,
506
+ ) -> dict[str, Any] | None:
557
507
  """
558
508
  Query a collection for similar vectors.
559
509
 
@@ -601,6 +551,7 @@ class PgVectorConnection(VectorDBConnection):
601
551
  # 3) dimension-based fallback
602
552
  loaded_model = None
603
553
  if not model_name:
554
+ # Try to get vector dimension
604
555
  dim = None
605
556
  coll_info = self.get_collection_info(collection_name)
606
557
  if coll_info and coll_info.get("vector_dimension"):
@@ -613,6 +564,7 @@ class PgVectorConnection(VectorDBConnection):
613
564
  dim
614
565
  )
615
566
  else:
567
+ # Use default model
616
568
  model_name, model_type = DEFAULT_MODEL
617
569
 
618
570
  if not loaded_model:
@@ -636,11 +588,11 @@ class PgVectorConnection(VectorDBConnection):
636
588
  # so callers receive the top-N results per query (matching SearchView expectations).
637
589
  with self._client.cursor() as cur:
638
590
  # Prepare containers for per-query results
639
- per_ids: List[List[str]] = []
640
- per_docs: List[List[str]] = []
641
- per_metas: List[List[Dict[str, Any]]] = []
642
- per_embeds: List[List[List[float]]] = []
643
- per_dists: List[List[float]] = []
591
+ per_ids = []
592
+ per_docs = []
593
+ per_metas = []
594
+ per_embeds = []
595
+ per_dists = []
644
596
 
645
597
  for emb in query_embeddings:
646
598
  # Build base query for this single embedding
@@ -675,11 +627,11 @@ class PgVectorConnection(VectorDBConnection):
675
627
  colnames = [desc[0] for desc in cur.description]
676
628
 
677
629
  # Build per-query result lists
678
- ids_q: List[str] = []
679
- docs_q: List[str] = []
680
- metas_q: List[Dict[str, Any]] = []
681
- embeds_q: List[List[float]] = []
682
- dists_q: List[float] = []
630
+ ids_q = []
631
+ docs_q = []
632
+ metas_q = []
633
+ embeds_q = []
634
+ dists_q = []
683
635
 
684
636
  for row in rows:
685
637
  row_dict = dict(zip(colnames, row))
@@ -731,10 +683,10 @@ class PgVectorConnection(VectorDBConnection):
731
683
  def get_all_items(
732
684
  self,
733
685
  collection_name: str,
734
- limit: Optional[int] = None,
735
- offset: Optional[int] = None,
736
- where: Optional[Dict[str, Any]] = None,
737
- ) -> Optional[Dict[str, Any]]:
686
+ limit: int | None = None,
687
+ offset: int | None = None,
688
+ where: dict[str, Any] | None = None,
689
+ ) -> dict[str, Any] | None:
738
690
  """
739
691
  Get all items from a collection.
740
692
 
@@ -835,10 +787,10 @@ class PgVectorConnection(VectorDBConnection):
835
787
  def update_items(
836
788
  self,
837
789
  collection_name: str,
838
- ids: List[str],
839
- documents: Optional[List[str]] = None,
840
- metadatas: Optional[List[Dict[str, Any]]] = None,
841
- embeddings: Optional[List[List[float]]] = None,
790
+ ids: list[str],
791
+ documents: list[str] | None = None,
792
+ metadatas: list[dict[str, Any]] | None = None,
793
+ embeddings: list[list[float]] | None = None,
842
794
  ) -> bool:
843
795
  """
844
796
  Update items in a collection.
@@ -1002,8 +954,8 @@ class PgVectorConnection(VectorDBConnection):
1002
954
  def delete_items(
1003
955
  self,
1004
956
  collection_name: str,
1005
- ids: Optional[List[str]] = None,
1006
- where: Optional[Dict[str, Any]] = None,
957
+ ids: list[str] | None = None,
958
+ where: dict[str, Any] | None = None,
1007
959
  ) -> bool:
1008
960
  """
1009
961
  Delete items from a collection.
@@ -1034,7 +986,7 @@ class PgVectorConnection(VectorDBConnection):
1034
986
  self._client.rollback()
1035
987
  return False
1036
988
 
1037
- def get_connection_info(self) -> Dict[str, Any]:
989
+ def get_connection_info(self) -> dict[str, Any]:
1038
990
  """
1039
991
  Get information about the current connection.
1040
992
 
@@ -1050,7 +1002,7 @@ class PgVectorConnection(VectorDBConnection):
1050
1002
  "connected": self.is_connected,
1051
1003
  }
1052
1004
 
1053
- def _get_table_schema(self, table_name: str) -> Dict[str, str]:
1005
+ def _get_table_schema(self, table_name: str) -> dict[str, str]:
1054
1006
  """
1055
1007
  Get the schema (column names and types) for a table.
1056
1008
 
@@ -1081,7 +1033,7 @@ class PgVectorConnection(VectorDBConnection):
1081
1033
  log_error("Failed to get table schema: %s", e)
1082
1034
  return {}
1083
1035
 
1084
- def _parse_vector(self, vector_str: Any) -> List[float]:
1036
+ def _parse_vector(self, vector_str: Any) -> list[float]:
1085
1037
  """
1086
1038
  Parse pgvector string format to Python list.
1087
1039
 
@@ -1098,3 +1050,66 @@ class PgVectorConnection(VectorDBConnection):
1098
1050
  vector_str = vector_str.strip("[]")
1099
1051
  return [float(x) for x in vector_str.split(",")]
1100
1052
  return []
1053
+
1054
+ def compute_embeddings_for_documents(
1055
+ self, collection_name: str, documents: list[str]
1056
+ ) -> list[list[float]] | None:
1057
+ """
1058
+ Compute embeddings for a list of documents using the configured/default model for the collection.
1059
+ Returns a list of embeddings, or None on failure.
1060
+ """
1061
+ try:
1062
+ from vector_inspector.services.settings_service import SettingsService
1063
+ from vector_inspector.core.embedding_utils import (
1064
+ load_embedding_model,
1065
+ get_embedding_model_for_dimension,
1066
+ DEFAULT_MODEL,
1067
+ encode_text,
1068
+ )
1069
+
1070
+ model_name = None
1071
+ model_type = None
1072
+
1073
+ # 1) settings
1074
+ settings = SettingsService()
1075
+ model_info = settings.get_embedding_model(self.database, collection_name)
1076
+ if model_info:
1077
+ model_name = model_info.get("model")
1078
+ model_type = model_info.get("type", "sentence-transformer")
1079
+
1080
+ # 2) collection metadata
1081
+ if not model_name:
1082
+ coll_info = self.get_collection_info(collection_name)
1083
+ if coll_info and coll_info.get("embedding_model"):
1084
+ model_name = coll_info.get("embedding_model")
1085
+ model_type = coll_info.get("embedding_model_type", "stored")
1086
+
1087
+ # 3) dimension-based fallback
1088
+ loaded_model = None
1089
+ if not model_name:
1090
+ # Try to get vector dimension
1091
+ dim = None
1092
+ coll_info = self.get_collection_info(collection_name)
1093
+ if coll_info and coll_info.get("vector_dimension"):
1094
+ try:
1095
+ dim = int(coll_info.get("vector_dimension"))
1096
+ except Exception:
1097
+ dim = None
1098
+ if dim:
1099
+ loaded_model, model_name, model_type = get_embedding_model_for_dimension(dim)
1100
+ else:
1101
+ model_name, model_type = DEFAULT_MODEL
1102
+
1103
+ # Load model
1104
+ if not loaded_model:
1105
+ loaded_model = load_embedding_model(model_name, model_type)
1106
+
1107
+ # Compute embeddings for all documents
1108
+ if model_type != "clip":
1109
+ embeddings = loaded_model.encode(documents, show_progress_bar=False).tolist()
1110
+ else:
1111
+ embeddings = [encode_text(d, loaded_model, model_type) for d in documents]
1112
+ return embeddings
1113
+ except Exception as e:
1114
+ log_error("Failed to compute embeddings: %s", e)
1115
+ return None
@@ -239,6 +239,10 @@ class PineconeConnection(VectorDBConnection):
239
239
  log_error("Embeddings are required for Pinecone and computing them failed: %s", e)
240
240
  return False
241
241
 
242
+ if not embeddings:
243
+ log_error("Embeddings are required for Pinecone but none were provided or computed")
244
+ return False
245
+
242
246
  index = self._get_index(collection_name)
243
247
  if not index:
244
248
  return False
@@ -512,6 +512,19 @@ class QdrantConnection(VectorDBConnection):
512
512
  """
513
513
  if not self._client:
514
514
  return False
515
+ # Reject empty document lists early
516
+ if not documents:
517
+ return False
518
+
519
+ # If embeddings provided, ensure counts match
520
+ if embeddings is not None and len(embeddings) != len(documents):
521
+ log_error(
522
+ "Embeddings length (%d) does not match documents length (%d) for collection %s",
523
+ len(embeddings),
524
+ len(documents),
525
+ collection_name,
526
+ )
527
+ return False
515
528
 
516
529
  # If embeddings not provided, compute using model resolution helper
517
530
  if not embeddings and documents:
@@ -0,0 +1,97 @@
1
+ """Factory for creating vector database connections from provider configs."""
2
+
3
+ from typing import Dict, Any
4
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
5
+ from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
6
+ from vector_inspector.core.connections.qdrant_connection import QdrantConnection
7
+ from vector_inspector.core.connections.pinecone_connection import PineconeConnection
8
+ from vector_inspector.core.connections.pgvector_connection import PgVectorConnection
9
+
10
+
11
+ class ProviderFactory:
12
+ """Factory for creating database connections from configuration."""
13
+
14
+ @staticmethod
15
+ def create(
16
+ provider: str, config: Dict[str, Any], credentials: Dict[str, Any] = None
17
+ ) -> VectorDBConnection:
18
+ """Create a connection object for the specified provider.
19
+
20
+ Args:
21
+ provider: Provider type (chromadb, qdrant, pinecone, pgvector)
22
+ config: Provider-specific configuration
23
+ credentials: Optional credentials (API keys, passwords, etc.)
24
+
25
+ Returns:
26
+ VectorDBConnection instance
27
+
28
+ Raises:
29
+ ValueError: If provider is unsupported or configuration is invalid
30
+ """
31
+ credentials = credentials or {}
32
+
33
+ if provider == "chromadb":
34
+ return ProviderFactory._create_chroma(config, credentials)
35
+ elif provider == "qdrant":
36
+ return ProviderFactory._create_qdrant(config, credentials)
37
+ elif provider == "pinecone":
38
+ return ProviderFactory._create_pinecone(config, credentials)
39
+ elif provider == "pgvector":
40
+ return ProviderFactory._create_pgvector(config, credentials)
41
+ else:
42
+ raise ValueError(f"Unsupported provider: {provider}")
43
+
44
+ @staticmethod
45
+ def _create_chroma(config: Dict[str, Any], credentials: Dict[str, Any]) -> ChromaDBConnection:
46
+ """Create a ChromaDB connection."""
47
+ conn_type = config.get("type")
48
+
49
+ if conn_type == "persistent":
50
+ return ChromaDBConnection(path=config.get("path"))
51
+ elif conn_type == "http":
52
+ return ChromaDBConnection(host=config.get("host"), port=config.get("port"))
53
+ else: # ephemeral
54
+ return ChromaDBConnection()
55
+
56
+ @staticmethod
57
+ def _create_qdrant(config: Dict[str, Any], credentials: Dict[str, Any]) -> QdrantConnection:
58
+ """Create a Qdrant connection."""
59
+ conn_type = config.get("type")
60
+ api_key = credentials.get("api_key")
61
+
62
+ if conn_type == "persistent":
63
+ return QdrantConnection(path=config.get("path"))
64
+ elif conn_type == "http":
65
+ return QdrantConnection(
66
+ host=config.get("host"), port=config.get("port"), api_key=api_key
67
+ )
68
+ else: # ephemeral
69
+ return QdrantConnection()
70
+
71
+ @staticmethod
72
+ def _create_pinecone(config: Dict[str, Any], credentials: Dict[str, Any]) -> PineconeConnection:
73
+ """Create a Pinecone connection."""
74
+ api_key = credentials.get("api_key")
75
+ if not api_key:
76
+ raise ValueError("Pinecone requires an API key")
77
+
78
+ return PineconeConnection(api_key=api_key)
79
+
80
+ @staticmethod
81
+ def _create_pgvector(config: Dict[str, Any], credentials: Dict[str, Any]) -> PgVectorConnection:
82
+ """Create a PgVector/Postgres connection."""
83
+ conn_type = config.get("type")
84
+
85
+ if conn_type == "http":
86
+ host = config.get("host", "localhost")
87
+ port = int(config.get("port", 5432))
88
+ database = config.get("database")
89
+ user = config.get("user")
90
+ # Prefer password from credentials
91
+ password = credentials.get("password")
92
+
93
+ return PgVectorConnection(
94
+ host=host, port=port, database=database, user=user, password=password
95
+ )
96
+
97
+ raise ValueError("Unsupported connection type for PgVector profile")
@@ -0,0 +1,73 @@
1
+ import os
2
+ import json
3
+ import time
4
+ import requests
5
+ from typing import Optional, Dict
6
+
7
+ GITHUB_API_URL = "https://api.github.com/repos/anthonypdawson/vector-inspector/releases/latest"
8
+ CACHE_FILE = os.path.expanduser("~/.vector_inspector_update_cache.json")
9
+ CACHE_TTL = 24 * 60 * 60 # 1 day in seconds
10
+
11
+
12
+ class UpdateService:
13
+ @staticmethod
14
+ def get_latest_release(force_refresh: bool = False) -> Optional[Dict]:
15
+ """
16
+ Fetch the latest release info from GitHub, with caching and rate limit handling.
17
+ Returns None on error or if rate limited.
18
+ """
19
+ now = int(time.time())
20
+ # Check cache for rate limit state or valid release
21
+ if os.path.exists(CACHE_FILE):
22
+ try:
23
+ with open(CACHE_FILE, "r", encoding="utf-8") as f:
24
+ cache = json.load(f)
25
+ # If rate limited, respect the reset time
26
+ if cache.get("rate_limited_until", 0) > now:
27
+ return None
28
+ if not force_refresh and now - cache.get("timestamp", 0) < CACHE_TTL:
29
+ return cache.get("release")
30
+ except Exception:
31
+ pass
32
+ try:
33
+ resp = requests.get(GITHUB_API_URL, timeout=5)
34
+ if resp.status_code == 200:
35
+ release = resp.json()
36
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
37
+ json.dump({"timestamp": now, "release": release}, f)
38
+ return release
39
+ elif resp.status_code == 403:
40
+ # Check for rate limit headers
41
+ reset = resp.headers.get("X-RateLimit-Reset")
42
+ if reset:
43
+ rate_limited_until = int(reset)
44
+ else:
45
+ # Default to 1 hour if no header
46
+ rate_limited_until = now + 3600
47
+ with open(CACHE_FILE, "w", encoding="utf-8") as f:
48
+ json.dump({"rate_limited_until": rate_limited_until}, f)
49
+ return None
50
+ except Exception:
51
+ pass
52
+ return None
53
+
54
+ @staticmethod
55
+ def compare_versions(current_version: str, latest_version: str) -> bool:
56
+ """
57
+ Returns True if latest_version is newer than current_version.
58
+ """
59
+
60
+ def parse(v):
61
+ return [int(x) for x in v.strip("v").split(".") if x.isdigit()]
62
+
63
+ return parse(latest_version) > parse(current_version)
64
+
65
+ @staticmethod
66
+ def get_update_instructions() -> Dict[str, str]:
67
+ """
68
+ Returns update instructions for both PyPI and GitHub.
69
+ """
70
+ return {
71
+ "pip": "pip install --upgrade vector-inspector",
72
+ "github": "https://github.com/anthonypdawson/vector-inspector/releases/latest",
73
+ }