diff --git a/docs/.DS_Store b/docs/.DS_Store deleted file mode 100644 index 940e9429..00000000 Binary files a/docs/.DS_Store and /dev/null differ diff --git a/docs/cli.md b/docs/cli.md index 033faa5a..416c4d4f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -173,6 +173,8 @@ Reduce disk usage by optimizing and pruning old table versions across all tables haiku-rag vacuum ``` +**Automatic Cleanup:** Vacuum runs automatically in the background after document operations. By default, it removes versions older than 60 seconds (`VACUUM_RETENTION_SECONDS`), preserving recent versions for concurrent connections. Manual vacuum can be useful for cleanup after bulk operations or to free disk space immediately. + ### Rebuild Database Rebuild the database by deleting all chunks & embeddings and re-indexing all documents. This is useful diff --git a/docs/configuration.md b/docs/configuration.md index 9094052a..631899ae 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -232,6 +232,12 @@ CHUNK_SIZE=256 # When expanded chunks overlap or are adjacent, they are automatically merged # into single chunks with continuous content to eliminate duplication CONTEXT_CHUNK_RADIUS=0 + +# Vacuum retention threshold (seconds) for automatic cleanup +# When documents are added/updated, old table versions older than this are removed +# Default: 60 seconds (safe for concurrent connections) +# Set to 0 for aggressive cleanup (removes all old versions immediately) +VACUUM_RETENTION_SECONDS=60 ``` #### Markdown Preprocessor diff --git a/docs/img/.DS_Store b/docs/img/.DS_Store deleted file mode 100644 index 5008ddfc..00000000 Binary files a/docs/img/.DS_Store and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml index c213f88f..73a2ac1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,10 +23,10 @@ classifiers = [ ] dependencies = [ - "docling>=2.52.0", + "docling>=2.55.1", "fastmcp>=2.12.3", "httpx>=0.28.1", - "lancedb>=0.25.0", + "lancedb>=0.25.1", "pydantic>=2.11.9", "pydantic-ai>=1.0.8", "pydantic-graph>=1.0.8", @@ -62,7 +62,7 @@ dev = [ "mkdocs-material>=9.6.14", "pydantic-evals>=1.0.8", "pre-commit>=4.2.0", - "pyright>=1.1.405", + "pyright>=1.1.406", "pytest>=8.4.2", "pytest-asyncio>=1.2.0", "pytest-cov>=7.0.0", diff --git a/src/evaluations/benchmark.py b/src/evaluations/benchmark.py index 4eb31835..055efda5 100644 --- a/src/evaluations/benchmark.py +++ b/src/evaluations/benchmark.py @@ -61,7 +61,6 @@ async def populate_db(spec: DatasetSpec) -> None: metadata=payload.metadata, ) progress.advance(task) - rag.store.vacuum() def _is_relevant_match(retrieved_uri: str | None, sample: RetrievalSample) -> bool: diff --git a/src/haiku/rag/client.py b/src/haiku/rag/client.py index 1c4377ed..e4107b86 100644 --- a/src/haiku/rag/client.py +++ b/src/haiku/rag/client.py @@ -46,6 +46,9 @@ class HaikuRAG: async def __aexit__(self, exc_type, exc_val, exc_tb): # noqa: ARG002 """Async context manager exit.""" + # Wait for any pending vacuum to complete before closing + async with self.store._vacuum_lock: + pass self.close() return False @@ -617,13 +620,13 @@ class HaikuRAG: # Final maintenance: centralized vacuum to curb disk usage try: - self.store.vacuum() + await self.store.vacuum() except Exception: pass async def vacuum(self) -> None: """Optimize and clean up old versions across all tables.""" - self.store.vacuum() + await self.store.vacuum() def close(self): """Close the underlying store connection.""" diff --git a/src/haiku/rag/config.py b/src/haiku/rag/config.py index 98a8b93d..40fcc8cd 100644 --- a/src/haiku/rag/config.py +++ b/src/haiku/rag/config.py @@ -57,6 +57,11 @@ class AppConfig(BaseModel): # and error out when the database does not already exist. DISABLE_DB_AUTOCREATE: bool = False + # Vacuum retention threshold in seconds. Only versions older than this + # threshold will be removed during vacuum operations. Default is 60 seconds + # to allow concurrent connections to safely use recent versions. + VACUUM_RETENTION_SECONDS: int = 60 + @field_validator("MONITOR_DIRECTORIES", mode="before") @classmethod def parse_monitor_directories(cls, v): diff --git a/src/haiku/rag/migration.py b/src/haiku/rag/migration.py index a0e8fce6..da2d21e6 100644 --- a/src/haiku/rag/migration.py +++ b/src/haiku/rag/migration.py @@ -27,7 +27,7 @@ class SQLiteToLanceDBMigrator: self.lancedb_path = lancedb_path self.console = Console() - def migrate(self) -> bool: + async def migrate(self) -> bool: """Perform the migration.""" try: self.console.print( @@ -94,7 +94,7 @@ class SQLiteToLanceDBMigrator: # Optimize and cleanup using centralized vacuum self.console.print("[cyan]Optimizing LanceDB...[/cyan]") try: - lance_store.vacuum() + await lance_store.vacuum() self.console.print("[green]✅ Optimization completed[/green]") except Exception as e: self.console.print( @@ -313,4 +313,4 @@ async def migrate_sqlite_to_lancedb( lancedb_path = sqlite_path.parent / (sqlite_path.stem + ".lancedb") migrator = SQLiteToLanceDBMigrator(sqlite_path, lancedb_path) - return migrator.migrate() + return await migrator.migrate() diff --git a/src/haiku/rag/store/engine.py b/src/haiku/rag/store/engine.py index 4ef55106..558f8c0d 100644 --- a/src/haiku/rag/store/engine.py +++ b/src/haiku/rag/store/engine.py @@ -1,3 +1,4 @@ +import asyncio import json import logging from datetime import timedelta @@ -51,6 +52,7 @@ class Store: def __init__(self, db_path: Path, skip_validation: bool = False): self.db_path: Path = db_path self.embedder = get_embedder() + self._vacuum_lock = asyncio.Lock() # Create the ChunkRecord model with the correct vector dimension self.ChunkRecord = create_chunk_model(self.embedder._vector_dim) @@ -78,14 +80,40 @@ class Store: if not skip_validation: self._validate_configuration() - def vacuum(self) -> None: - """Optimize and clean up old versions across all tables to reduce disk usage.""" + async def vacuum(self, retention_seconds: int | None = None) -> None: + """Optimize and clean up old versions across all tables to reduce disk usage. + + Args: + retention_seconds: Retention threshold in seconds. Only versions older + than this will be removed. If None, uses Config.VACUUM_RETENTION_SECONDS. + + Note: + If vacuum is already running, this method returns immediately without blocking. + Use asyncio.create_task(store.vacuum()) for non-blocking background execution. + """ if self._has_cloud_config() and str(Config.LANCEDB_URI).startswith("db://"): return - # Perform maintenance per table using optimize() with cleanup_older_than 0 - for table in [self.documents_table, self.chunks_table, self.settings_table]: - table.optimize(cleanup_older_than=timedelta(0)) + # Skip if already running (non-blocking) + if self._vacuum_lock.locked(): + return + + async with self._vacuum_lock: + try: + # Evaluate config at runtime to allow dynamic changes + if retention_seconds is None: + retention_seconds = Config.VACUUM_RETENTION_SECONDS + # Perform maintenance per table using optimize() with configurable retention + retention = timedelta(seconds=retention_seconds) + for table in [ + self.documents_table, + self.chunks_table, + self.settings_table, + ]: + table.optimize(cleanup_older_than=retention) + except (RuntimeError, OSError) as e: + # Handle resource errors gracefully + logger.debug(f"Vacuum skipped due to resource constraints: {e}") def _connect_to_lancedb(self, db_path: Path): """Establish connection to LanceDB (local, cloud, or object storage).""" diff --git a/src/haiku/rag/store/repositories/chunk.py b/src/haiku/rag/store/repositories/chunk.py index fe3bd540..c8fac655 100644 --- a/src/haiku/rag/store/repositories/chunk.py +++ b/src/haiku/rag/store/repositories/chunk.py @@ -1,4 +1,3 @@ -import asyncio import inspect import json import logging @@ -23,7 +22,6 @@ class ChunkRepository: def __init__(self, store: Store) -> None: self.store = store self.embedder = get_embedder() - self._optimize_lock = asyncio.Lock() def _ensure_fts_index(self) -> None: """Ensure FTS index exists on the content column.""" @@ -35,21 +33,6 @@ class ChunkRepository: # Log the error but don't fail - FTS might already exist logger.debug(f"FTS index creation skipped: {e}") - async def _optimize(self) -> None: - """Optimize the chunks table to refresh indexes.""" - # Skip optimization for LanceDB Cloud as it handles this automatically - if Config.LANCEDB_URI and Config.LANCEDB_URI.startswith("db://"): - return - - async with self._optimize_lock: - try: - self.store.chunks_table.optimize() - except (RuntimeError, OSError) as e: - # Handle "too many open files" and other resource errors gracefully - logger.debug( - f"Table optimization skipped due to resource constraints: {e}" - ) - async def create(self, entity: Chunk) -> Chunk: """Create a chunk in the database.""" assert entity.document_id, "Chunk must have a document_id to be created" @@ -77,11 +60,6 @@ class ChunkRepository: self.store.chunks_table.add([chunk_record]) entity.id = chunk_id - - # Try to optimize if not currently locked (non-blocking) - if not self._optimize_lock.locked(): - asyncio.create_task(self._optimize()) - return entity async def get_by_id(self, entity_id: str) -> Chunk | None: @@ -125,10 +103,6 @@ class ChunkRepository: "vector": embedding, }, ) - # Try to optimize if not currently locked (non-blocking) - if not self._optimize_lock.locked(): - asyncio.create_task(self._optimize()) - return entity async def delete(self, entity_id: str) -> bool: @@ -227,8 +201,6 @@ class ChunkRepository: if chunk_records: self.store.chunks_table.add(chunk_records) - # Force optimization once at the end for bulk operations - await self._optimize() return created_chunks async def delete_all(self) -> None: diff --git a/src/haiku/rag/store/repositories/document.py b/src/haiku/rag/store/repositories/document.py index ffc66a1b..ef951d6c 100644 --- a/src/haiku/rag/store/repositories/document.py +++ b/src/haiku/rag/store/repositories/document.py @@ -1,3 +1,4 @@ +import asyncio import json from datetime import datetime from typing import TYPE_CHECKING @@ -200,6 +201,9 @@ class DocumentRepository: chunk.order = order await self.chunk_repository.create(chunk) + # Vacuum old versions in background (non-blocking) + asyncio.create_task(self.store.vacuum()) + return created_doc except Exception: # Roll back to the captured versions and re-raise @@ -230,6 +234,9 @@ class DocumentRepository: updated_doc.id, docling_document ) + # Vacuum old versions in background (non-blocking) + asyncio.create_task(self.store.vacuum()) + return updated_doc except Exception: # Roll back to the captured versions and re-raise diff --git a/tests/test_lancedb_connection.py b/tests/test_lancedb_connection.py index ae821fe9..c61eae0d 100644 --- a/tests/test_lancedb_connection.py +++ b/tests/test_lancedb_connection.py @@ -4,43 +4,24 @@ import pytest from haiku.rag.config import Config from haiku.rag.store.engine import Store -from haiku.rag.store.models.chunk import Chunk -from haiku.rag.store.models.document import Document -from haiku.rag.store.repositories.chunk import ChunkRepository -from haiku.rag.store.repositories.document import DocumentRepository @pytest.mark.asyncio async def test_lancedb_cloud_skips_optimization(temp_db_path): - """Test that optimization is skipped when using LanceDB Cloud (db:// URI).""" + """Test that vacuum is skipped when using LanceDB Cloud (db:// URI).""" # Create a store store = Store(temp_db_path) - chunk_repo = ChunkRepository(store) - doc_repo = DocumentRepository(store) - # Create a document - document = Document(content="Test document content", metadata={}) - created_document = await doc_repo.create(document) - document_id = created_document.id - - # Mock LANCEDB_URI to simulate LanceDB Cloud usage - with patch.object(Config, "LANCEDB_URI", "db://test-database"): + # Mock all cloud config to simulate LanceDB Cloud usage + with ( + patch.object(Config, "LANCEDB_URI", "db://test-database"), + patch.object(Config, "LANCEDB_API_KEY", "test-api-key"), + patch.object(Config, "LANCEDB_REGION", "us-east-1"), + ): # Mock the optimize method to track if it's called with patch.object(store.chunks_table, "optimize") as mock_optimize: - # Create a chunk - this should trigger optimization logic - chunk = Chunk( - document_id=document_id, - content="Test chunk content", - metadata={"test": "value"}, - ) - - created_chunk = await chunk_repo.create(chunk) - assert created_chunk.id is not None - - # Wait a moment to ensure any async optimization would complete - import asyncio - - await asyncio.sleep(0.1) + # Call vacuum - this should skip optimization for LanceDB Cloud + await store.vacuum() # The optimize method should NOT have been called for LanceDB Cloud mock_optimize.assert_not_called() @@ -50,35 +31,16 @@ async def test_lancedb_cloud_skips_optimization(temp_db_path): @pytest.mark.asyncio async def test_local_storage_calls_optimization(temp_db_path): - """Test that optimization is called for local storage.""" + """Test that vacuum calls optimization for local storage.""" # Create a store store = Store(temp_db_path) - chunk_repo = ChunkRepository(store) - doc_repo = DocumentRepository(store) - - # Create a document - document = Document(content="Test document content", metadata={}) - created_document = await doc_repo.create(document) - document_id = created_document.id # Ensure LANCEDB_URI is empty (local storage) with patch.object(Config, "LANCEDB_URI", ""): # Mock the optimize method to track if it's called with patch.object(store.chunks_table, "optimize") as mock_optimize: - # Create a chunk - this should trigger optimization logic - chunk = Chunk( - document_id=document_id, - content="Test chunk content", - metadata={"test": "value"}, - ) - - created_chunk = await chunk_repo.create(chunk) - assert created_chunk.id is not None - - # Wait a moment to ensure async optimization completes - import asyncio - - await asyncio.sleep(0.1) + # Call vacuum - this should optimize all tables for local storage + await store.vacuum() # The optimize method SHOULD have been called for local storage mock_optimize.assert_called() diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 3d68c7d8..874696cd 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -122,3 +122,106 @@ def test_existing_database_runs_upgrades(monkeypatch, temp_db_path): Store(temp_db_path) assert called["value"] + + +@pytest.mark.asyncio +async def test_vacuum_with_retention_threshold(temp_db_path): + store = Store(temp_db_path) + repo = DocumentRepository(store) + + # Stub embeddings to avoid network + dim = repo.chunk_repository.embedder._vector_dim + + async def fake_embed(x): # type: ignore[no-redef] + if isinstance(x, list): + return [[0.0] * dim for _ in x] + return [0.0] * dim + + repo.chunk_repository.embedder.embed = fake_embed # type: ignore[assignment] + + # Create first document + doc1 = Document(content="First document") + dl_doc1 = text_to_docling_document("First document", name="doc1.md") + await repo._create_with_docling(doc1, dl_doc1) + + # Create second document + doc2 = Document(content="Second document") + dl_doc2 = text_to_docling_document("Second document", name="doc2.md") + await repo._create_with_docling(doc2, dl_doc2) + + # Get initial version counts (should have multiple versions from creates) + initial_doc_versions = len(list(store.documents_table.list_versions())) + initial_chunk_versions = len(list(store.chunks_table.list_versions())) + + assert initial_doc_versions > 1, "Should have multiple document table versions" + assert initial_chunk_versions > 1, "Should have multiple chunk table versions" + + # Vacuum with default threshold (60 seconds) - should keep recent versions + # Note: vacuum may create new versions even when not cleaning up old ones + await store.vacuum() + + after_default_doc_versions = len(list(store.documents_table.list_versions())) + after_default_chunk_versions = len(list(store.chunks_table.list_versions())) + + # After vacuum with retention, version count should stay the same or increase + # (optimize may create new versions) but not decrease + assert after_default_doc_versions >= initial_doc_versions, ( + "Default vacuum should not remove recent versions" + ) + assert after_default_chunk_versions >= initial_chunk_versions, ( + "Default vacuum should not remove recent versions" + ) + + # Vacuum with 0 threshold - should significantly reduce versions + await store.vacuum(retention_seconds=0) + + after_zero_doc_versions = len(list(store.documents_table.list_versions())) + after_zero_chunk_versions = len(list(store.chunks_table.list_versions())) + + # After aggressive vacuum, should have minimal versions (1-2) + # Note: optimize operation may create a version after cleanup + assert after_zero_doc_versions <= 2, ( + f"Should have minimal document versions after vacuum(0), got {after_zero_doc_versions}" + ) + assert after_zero_chunk_versions <= 2, ( + f"Should have minimal chunk versions after vacuum(0), got {after_zero_chunk_versions}" + ) + + # And it should be significantly fewer than before + assert after_zero_doc_versions < initial_doc_versions, ( + "Should have fewer versions after vacuum(0)" + ) + assert after_zero_chunk_versions < initial_chunk_versions, ( + "Should have fewer versions after vacuum(0)" + ) + + +@pytest.mark.asyncio +async def test_vacuum_completes_before_context_exit(temp_db_path, monkeypatch): + """Test that background vacuum completes when context manager exits.""" + from haiku.rag.client import HaikuRAG + from haiku.rag.config import Config + from haiku.rag.utils import text_to_docling_document + + # Set aggressive vacuum retention for this test + monkeypatch.setattr(Config, "VACUUM_RETENTION_SECONDS", 0) + + async with HaikuRAG(db_path=temp_db_path) as client: + # Create multiple documents - each creation triggers automatic vacuum with retention=0 + # This aggressively cleans up old versions between operations + for i in range(3): + doc = Document(content=f"Test document {i}") + dl_doc = text_to_docling_document(f"Test document {i}", name=f"test{i}.md") + await client.document_repository._create_with_docling(doc, dl_doc) + + # After context exit, automatic vacuum should have kept versions minimal + store = Store(temp_db_path) + final_versions = len(list(store.documents_table.list_versions())) + + # With retention_seconds=0, vacuum aggressively cleans up between operations + # Should have very few versions remaining (1-2) + assert final_versions <= 2, ( + f"Aggressive vacuum should keep minimal versions, got {final_versions}" + ) + assert final_versions >= 1, "Should have at least one version remaining" + store.close() diff --git a/uv.lock b/uv.lock index 0b67cc01..87d26a15 100644 --- a/uv.lock +++ b/uv.lock @@ -645,7 +645,7 @@ wheels = [ [[package]] name = "docling" -version = "2.52.0" +version = "2.55.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accelerate" }, @@ -676,14 +676,14 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/40/e6b25533f99eb48dae6f21b226ee7ebe6c9796d42ecf4ecfce525414f5fa/docling-2.52.0.tar.gz", hash = "sha256:e6c6b4db5ed583e899528f76a9de71679c75f25a0585afcc7fc3ccff6c791d41", size = 196658, upload-time = "2025-09-11T16:12:40.951Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/8c/baa24f0d64a36a87c66eef91dcf169ac346776739c4fb8065e59c31b1291/docling-2.55.1.tar.gz", hash = "sha256:e60a5612b2b993efd8a0b5464aff1b9868e3cab5c2e239c863709e6b780f3c57", size = 212483, upload-time = "2025-10-03T10:27:46.907Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/6a/ff3e65868f409438f2957ad4601852db51d7f6e5eb866825fa306177edc7/docling-2.52.0-py3-none-any.whl", hash = "sha256:85927fde42cd6b597c314c7a73241a7d28c817f8666b3c00c045c2905a769d8b", size = 224855, upload-time = "2025-09-11T16:12:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/2a2801cb909981b57326da2a9736cd11514d0393dc37771e200615b8b44f/docling-2.55.1-py3-none-any.whl", hash = "sha256:895aba282c6cca9ca1f6b9ff57c2002e4f581f722c608aa671d68382d4d61e07", size = 239394, upload-time = "2025-10-03T10:27:45.157Z" }, ] [[package]] name = "docling-core" -version = "2.48.1" +version = "2.48.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonref" }, @@ -697,9 +697,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/0c/dce7f80e99e56570d143885fc40536107e8a39ef4de2888959e055b39607/docling_core-2.48.1.tar.gz", hash = "sha256:48cb77575dfd020a51413957e96b165e45f6d1027c641710fddb389dcb9b189c", size = 161311, upload-time = "2025-09-11T12:33:22.46Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/d8/f0c8034f87d6151eb955e56975b9f2374a54d57af2b56b1682d7c8ff5c71/docling_core-2.48.4.tar.gz", hash = "sha256:d87ce3021cdae3d073ce7572a2396b69be3cde82ebf9a74d4bad1e1cdfdfd524", size = 161377, upload-time = "2025-10-01T09:10:08.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/fe/1b96120c9d94c97016716ccf46ad2708a2e76157e52dfcca4101db70fc21/docling_core-2.48.1-py3-none-any.whl", hash = "sha256:a3985999ac2067e15e589ef0f11ccde264deacaea403c0f94049242f10a6189a", size = 164330, upload-time = "2025-09-11T12:33:20.935Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/06e5f9d3083f830de8bef86f91acda994965f88d8b945ce3b257ea83e780/docling_core-2.48.4-py3-none-any.whl", hash = "sha256:367675c1165d0934ae498fa57ca2d27ef0468aad74dc44a5ab061f5d87882ea1", size = 164374, upload-time = "2025-10-01T09:10:06.034Z" }, ] [package.optional-dependencies] @@ -1153,10 +1153,10 @@ dev = [ [package.metadata] requires-dist = [ - { name = "docling", specifier = ">=2.52.0" }, + { name = "docling", specifier = ">=2.55.1" }, { name = "fastmcp", specifier = ">=2.12.3" }, { name = "httpx", specifier = ">=0.28.1" }, - { name = "lancedb", specifier = ">=0.25.0" }, + { name = "lancedb", specifier = ">=0.25.1" }, { name = "mxbai-rerank", marker = "extra == 'mxbai'", specifier = ">=0.1.6" }, { name = "pydantic", specifier = ">=2.11.9" }, { name = "pydantic-ai", specifier = ">=1.0.8" }, @@ -1178,7 +1178,7 @@ dev = [ { name = "mkdocs-material", specifier = ">=9.6.14" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pydantic-evals", specifier = ">=1.0.8" }, - { name = "pyright", specifier = ">=1.1.405" }, + { name = "pyright", specifier = ">=1.1.406" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, @@ -1516,7 +1516,7 @@ wheels = [ [[package]] name = "lancedb" -version = "0.25.0" +version = "0.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecation" }, @@ -1529,13 +1529,13 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/e7/10953deea89b06ae5bc568169d5ae888ff6df314decb92b9b3e453f53f0b/lancedb-0.25.0-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:ae2e80b7b3be3fa4d92fc8d500f47549dd1f8d28ca5092f1c898b92d0cfd4393", size = 34171227, upload-time = "2025-09-04T11:05:31.327Z" }, - { url = "https://files.pythonhosted.org/packages/55/7f/2874a3709f1b8c487e707e171c9004a9240af3af0fd7a247b9187bb6e0f7/lancedb-0.25.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a9d67ea9edffa596c6f190151fdd535da8e355a4fd1979c1dc19d540a5665916", size = 31552856, upload-time = "2025-09-04T09:46:50.788Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e9/faab70ad918576ed3bb7cb936474137ac265ac3026d3e16e30cd4d3daac2/lancedb-0.25.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8fe20079ed86b1ab75c65dcfc920a9646c835e9c40ef825cadd148c11b0001e", size = 32487962, upload-time = "2025-09-04T08:51:35.358Z" }, - { url = "https://files.pythonhosted.org/packages/ce/40/5471bc8115f287040b5afdf9d7a20c4685ec16cddb4a7da79e7c1f63914e/lancedb-0.25.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37bc402d85c83e454d9f2e79480b31acc5904bb159a4fc715032c7560494157", size = 35726794, upload-time = "2025-09-04T08:57:30.554Z" }, - { url = "https://files.pythonhosted.org/packages/47/5e/aa3d9d2c7a834a9aa539b2b1c731ab860f7e32e2c87b9086ad233ecb13cd/lancedb-0.25.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9bbc20bd1e64be359ca11c90428c00b0062d26b0291bddf32ab5471a3525c76", size = 32492508, upload-time = "2025-09-04T08:53:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/fa/37/75f4e3ed7fa00a2cd5d321e8bf13441cdb61a83fbbcd0fa0f1a7241affe1/lancedb-0.25.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1306be9c08e208a5bcb5188275f47f962c2eda96369fad5949a3ddaf592afc6d", size = 35776383, upload-time = "2025-09-04T08:57:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/b5/af/eb217ea1daab5c28ce4c764d2f672f4e3a5bcd3d4faf7921a8ee28c6cb5b/lancedb-0.25.0-cp39-abi3-win_amd64.whl", hash = "sha256:f66283e5d63c99c2bfbd4eaa134d9a5c5b0145eb26a972648214f8ba87777e24", size = 37826272, upload-time = "2025-09-04T09:15:23.729Z" }, + { url = "https://files.pythonhosted.org/packages/ad/2b/ed9870288506d8ca61cddf7b1dbb03c68f95b8797feb49467b33ef185477/lancedb-0.25.1-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:ec0a1cab435a5307054b84ffb798a4d828253f23698848788bfe31930e343c6c", size = 34985432, upload-time = "2025-09-23T23:15:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/58/75/320f9142918b646b4b6d0277676c2466d2e0ce2a22aca320d0113b3ef035/lancedb-0.25.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:69e1f8343f6a4ff6985ea13f5c5cdf6d07435d04f8279c4fc6e623a34ceadda0", size = 31993179, upload-time = "2025-09-23T22:20:23.039Z" }, + { url = "https://files.pythonhosted.org/packages/fd/44/d223cb64c9feb78dfa3857690d743e961f76e065935c8c4304cb64659882/lancedb-0.25.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9432134155474e73907fc5e1f8a4310433b9234a0c5f964c21b4c39aca50dde6", size = 32872519, upload-time = "2025-09-23T22:29:03.5Z" }, + { url = "https://files.pythonhosted.org/packages/61/a6/e6d88d8076fa8c40b7b6f96a37f21c75ce3518ccbf64a351d26ae983461a/lancedb-0.25.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955c6e1aa4e249be7456ea7f7c42ba119be5a5c2c51f4d78efeb6c4f3cc2dbdf", size = 36325984, upload-time = "2025-09-23T22:31:46.118Z" }, + { url = "https://files.pythonhosted.org/packages/97/84/14d4f0c3a98a324fcb401161e25fb1699c69ba1cd2928983fb283bd8b04f/lancedb-0.25.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d584bdfb96372c03a209bb8f010eb7358135e4adddb903ae1385450af39e1187", size = 32883704, upload-time = "2025-09-23T22:27:41.393Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/3e8ae8bf9880b2fed10122cef5e535bd67f0df0a874cc3122220d47ca255/lancedb-0.25.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c495da53d3dfa105364f202710d0bb2f031fe54a077b9c2ac9d098d02bd20bb2", size = 36369514, upload-time = "2025-09-23T22:30:53.605Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fb/dce4757f257cb4e11e13b71ce502dc5d1caf51f1e5cccfdae85bf23960a0/lancedb-0.25.1-cp39-abi3-win_amd64.whl", hash = "sha256:2c6effc10c8263ea84261f49d5ff1957c18814ed7e3eaa5094d71b1aa0573871", size = 38390878, upload-time = "2025-09-23T22:55:24.687Z" }, ] [[package]] @@ -3212,15 +3212,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b [[package]] name = "pyright" -version = "1.1.405" +version = "1.1.406" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, ] [[package]]