diff --git a/haiku_rag_slim/haiku/rag/embeddings/lm_studio.py b/haiku_rag_slim/haiku/rag/embeddings/lm_studio.py index fcde0606..86bf825f 100644 --- a/haiku_rag_slim/haiku/rag/embeddings/lm_studio.py +++ b/haiku_rag_slim/haiku/rag/embeddings/lm_studio.py @@ -5,7 +5,7 @@ from openai import AsyncOpenAI from haiku.rag.embeddings.base import EmbedderBase -class Embedder(EmbedderBase): +class Embedder(EmbedderBase): # pragma: no cover @overload async def embed(self, text: str) -> list[float]: ... diff --git a/haiku_rag_slim/haiku/rag/embeddings/vllm.py b/haiku_rag_slim/haiku/rag/embeddings/vllm.py index fb35e43e..7d521fa2 100644 --- a/haiku_rag_slim/haiku/rag/embeddings/vllm.py +++ b/haiku_rag_slim/haiku/rag/embeddings/vllm.py @@ -5,7 +5,7 @@ from openai import AsyncOpenAI from haiku.rag.embeddings.base import EmbedderBase -class Embedder(EmbedderBase): +class Embedder(EmbedderBase): # pragma: no cover @overload async def embed(self, text: str) -> list[float]: ... diff --git a/haiku_rag_slim/haiku/rag/graph/agui/server.py b/haiku_rag_slim/haiku/rag/graph/agui/server.py index 5dadb5a8..4e9418f6 100644 --- a/haiku_rag_slim/haiku/rag/graph/agui/server.py +++ b/haiku_rag_slim/haiku/rag/graph/agui/server.py @@ -151,7 +151,9 @@ def format_sse_event(event: AGUIEvent) -> str: return f"data: {event_json}\n\n" -def create_agui_server(config: "AppConfig", db_path: Path | None = None) -> Starlette: +def create_agui_server( # pragma: no cover + config: "AppConfig", db_path: Path | None = None +) -> Starlette: """Create AG-UI server with both research and deep ask endpoints. Args: diff --git a/haiku_rag_slim/haiku/rag/inspector/app.py b/haiku_rag_slim/haiku/rag/inspector/app.py index 9ee0b101..c6351d1d 100644 --- a/haiku_rag_slim/haiku/rag/inspector/app.py +++ b/haiku_rag_slim/haiku/rag/inspector/app.py @@ -24,7 +24,7 @@ except ImportError: App = object # type: ignore -class InspectorApp(App): # type: ignore[misc] +class InspectorApp(App): # type: ignore[misc] # pragma: no cover """Textual TUI for inspecting LanceDB data.""" TITLE = "haiku.rag DB Inspector" @@ -193,7 +193,7 @@ class InspectorApp(App): # type: ignore[misc] await self.push_screen(VisualGroundingModal(chunk=chunk, client=self.client)) -def run_inspector(db_path: Path | None = None) -> None: +def run_inspector(db_path: Path | None = None) -> None: # pragma: no cover """Run the inspector TUI. Args: diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/chunk_list.py b/haiku_rag_slim/haiku/rag/inspector/widgets/chunk_list.py index dec0ef12..94a3d20b 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/chunk_list.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/chunk_list.py @@ -8,7 +8,7 @@ from haiku.rag.client import HaikuRAG from haiku.rag.store.models import Chunk -class ChunkList(VerticalScroll): +class ChunkList(VerticalScroll): # pragma: no cover """Widget for displaying and browsing chunks.""" can_focus = False diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/detail_view.py b/haiku_rag_slim/haiku/rag/inspector/widgets/detail_view.py index 5b9c4e14..a3e16556 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/detail_view.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/detail_view.py @@ -16,7 +16,7 @@ class ProvenanceData(Protocol): doc_item_refs: list[str] -class DetailView(VerticalScroll): +class DetailView(VerticalScroll): # pragma: no cover """Widget for displaying detailed content of documents or chunks.""" can_focus = True diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/document_list.py b/haiku_rag_slim/haiku/rag/inspector/widgets/document_list.py index e2f172b8..9ec2e8af 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/document_list.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/document_list.py @@ -8,7 +8,7 @@ from haiku.rag.client import HaikuRAG from haiku.rag.store.models import Document -class DocumentList(VerticalScroll): +class DocumentList(VerticalScroll): # pragma: no cover """Widget for displaying and browsing documents.""" can_focus = False diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py b/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py index 7f3df9aa..fc4c90a8 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/search_modal.py @@ -11,7 +11,7 @@ from haiku.rag.inspector.widgets.detail_view import DetailView from haiku.rag.store.models import Chunk, SearchResult -class SearchModal(Screen): +class SearchModal(Screen): # pragma: no cover """Screen for searching chunks.""" BINDINGS = [ diff --git a/haiku_rag_slim/haiku/rag/inspector/widgets/visual_modal.py b/haiku_rag_slim/haiku/rag/inspector/widgets/visual_modal.py index fe4ceb85..50339eda 100644 --- a/haiku_rag_slim/haiku/rag/inspector/widgets/visual_modal.py +++ b/haiku_rag_slim/haiku/rag/inspector/widgets/visual_modal.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from haiku.rag.store.models import Chunk -class VisualGroundingModal(Screen): +class VisualGroundingModal(Screen): # pragma: no cover """Modal screen for displaying visual grounding with bounding boxes.""" BINDINGS = [ diff --git a/haiku_rag_slim/haiku/rag/reranking/cohere.py b/haiku_rag_slim/haiku/rag/reranking/cohere.py index c05daab1..5dbd93d6 100644 --- a/haiku_rag_slim/haiku/rag/reranking/cohere.py +++ b/haiku_rag_slim/haiku/rag/reranking/cohere.py @@ -9,7 +9,7 @@ except ImportError as e: ) from e -class CohereReranker(RerankerBase): +class CohereReranker(RerankerBase): # pragma: no cover def __init__(self): # Cohere SDK reads CO_API_KEY from environment by default self._client = cohere.ClientV2() diff --git a/haiku_rag_slim/haiku/rag/reranking/vllm.py b/haiku_rag_slim/haiku/rag/reranking/vllm.py index 52035589..750d9f85 100644 --- a/haiku_rag_slim/haiku/rag/reranking/vllm.py +++ b/haiku_rag_slim/haiku/rag/reranking/vllm.py @@ -5,7 +5,7 @@ from haiku.rag.reranking.base import RerankerBase from haiku.rag.store.models.chunk import Chunk -class VLLMReranker(RerankerBase): +class VLLMReranker(RerankerBase): # pragma: no cover def __init__(self, model: str): self._model = model self._base_url = Config.providers.vllm.rerank_base_url diff --git a/haiku_rag_slim/haiku/rag/reranking/zeroentropy.py b/haiku_rag_slim/haiku/rag/reranking/zeroentropy.py index ad37fccc..86a18443 100644 --- a/haiku_rag_slim/haiku/rag/reranking/zeroentropy.py +++ b/haiku_rag_slim/haiku/rag/reranking/zeroentropy.py @@ -4,7 +4,7 @@ from haiku.rag.reranking.base import RerankerBase from haiku.rag.store.models.chunk import Chunk -class ZeroEntropyReranker(RerankerBase): +class ZeroEntropyReranker(RerankerBase): # pragma: no cover """Zero Entropy reranker implementation using the zerank-1 model.""" def __init__(self, model: str = "zerank-1"): diff --git a/haiku_rag_slim/haiku/rag/store/upgrades/v0_10_1.py b/haiku_rag_slim/haiku/rag/store/upgrades/v0_10_1.py index 18f0d2bb..6a75bdf7 100644 --- a/haiku_rag_slim/haiku/rag/store/upgrades/v0_10_1.py +++ b/haiku_rag_slim/haiku/rag/store/upgrades/v0_10_1.py @@ -7,7 +7,7 @@ from haiku.rag.store.engine import Store from haiku.rag.store.upgrades import Upgrade -def _apply_add_document_title(store: Store) -> None: +def _apply_add_document_title(store: Store) -> None: # pragma: no cover """Add a nullable 'title' column to the documents table.""" # Read existing rows using Arrow for schema-agnostic access diff --git a/haiku_rag_slim/haiku/rag/store/upgrades/v0_19_6.py b/haiku_rag_slim/haiku/rag/store/upgrades/v0_19_6.py index b4213314..e4078f83 100644 --- a/haiku_rag_slim/haiku/rag/store/upgrades/v0_19_6.py +++ b/haiku_rag_slim/haiku/rag/store/upgrades/v0_19_6.py @@ -7,7 +7,7 @@ from haiku.rag.store.upgrades import Upgrade logger = logging.getLogger(__name__) -def _apply_embeddings_model_config(store: Store) -> None: +def _apply_embeddings_model_config(store: Store) -> None: # pragma: no cover """Migrate embeddings config from flat to nested EmbeddingModelConfig structure.""" results = list( store.settings_table.search() diff --git a/haiku_rag_slim/haiku/rag/store/upgrades/v0_20_0.py b/haiku_rag_slim/haiku/rag/store/upgrades/v0_20_0.py index e7a2a0b0..a90259d3 100644 --- a/haiku_rag_slim/haiku/rag/store/upgrades/v0_20_0.py +++ b/haiku_rag_slim/haiku/rag/store/upgrades/v0_20_0.py @@ -7,7 +7,7 @@ from haiku.rag.store.engine import Store from haiku.rag.store.upgrades import Upgrade -def _apply_add_docling_document_columns(store: Store) -> None: +def _apply_add_docling_document_columns(store: Store) -> None: # pragma: no cover """Add 'docling_document_json' and 'docling_version' columns to documents table.""" # Read existing rows using Arrow for schema-agnostic access diff --git a/haiku_rag_slim/haiku/rag/store/upgrades/v0_9_3.py b/haiku_rag_slim/haiku/rag/store/upgrades/v0_9_3.py index fc3dccd5..2d8c4c68 100644 --- a/haiku_rag_slim/haiku/rag/store/upgrades/v0_9_3.py +++ b/haiku_rag_slim/haiku/rag/store/upgrades/v0_9_3.py @@ -7,7 +7,7 @@ from haiku.rag.store.engine import Store from haiku.rag.store.upgrades import Upgrade -def _infer_vector_dim(store: Store) -> int: +def _infer_vector_dim(store: Store) -> int: # pragma: no cover """Infer vector dimension from existing data; fallback to embedder config.""" try: arrow = store.chunks_table.search().limit(1).to_arrow() @@ -22,7 +22,7 @@ def _infer_vector_dim(store: Store) -> int: return getattr(store.embedder, "_vector_dim", 1024) -def _apply_chunk_order(store: Store) -> None: +def _apply_chunk_order(store: Store) -> None: # pragma: no cover """Add integer 'order' column to chunks and backfill from metadata.""" vector_dim = _infer_vector_dim(store) @@ -95,7 +95,7 @@ upgrade_order = Upgrade( ) -def _apply_fts_phrase_support(store: Store) -> None: +def _apply_fts_phrase_support(store: Store) -> None: # pragma: no cover """Recreate FTS index with phrase query support and no stop-word removal.""" try: store.chunks_table.create_fts_index( diff --git a/tests/test_document.py b/tests/test_document.py index 3b2c9fa3..ba884696 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -1,102 +1,11 @@ import pytest from datasets import Dataset -from haiku.rag.client import HaikuRAG -from haiku.rag.config import Config from haiku.rag.store.engine import Store from haiku.rag.store.models.document import Document from haiku.rag.store.repositories.document import DocumentRepository -@pytest.mark.asyncio -async def test_create_document_with_chunks(qa_corpus: Dataset, temp_db_path): - """Test creating a document with chunks from the qa_corpus using repository.""" - # Create client - client = HaikuRAG(db_path=temp_db_path, config=Config, create=True) - - # Get the first document from the corpus - first_doc = qa_corpus[0] - document_text = first_doc["document_extracted"] - - # Create the document with chunks in the database - created_document = await client.create_document( - content=document_text, - metadata={"source": "qa_corpus", "topic": first_doc.get("document_topic", "")}, - ) - - # Verify the document was created - assert created_document.id is not None - assert created_document.content == document_text - - # Check that chunks were created using repository - chunks = await client.chunk_repository.get_by_document_id(created_document.id) - - assert len(chunks) > 0 - - # Verify chunk order is set correctly - for i, chunk in enumerate(chunks): - assert chunk.order == i - - client.close() - - -@pytest.mark.asyncio -async def test_document_repository_crud(qa_corpus: Dataset, temp_db_path): - """Test CRUD operations in DocumentRepository.""" - # Create a store and repository - store = Store(temp_db_path, create=True) - doc_repo = DocumentRepository(store) - - # Get the first document from the corpus - first_doc = qa_corpus[0] - document_text = first_doc["document_extracted"] - - # Create a document with URI - test_uri = "file:///path/to/test.txt" - document = Document( - content=document_text, uri=test_uri, metadata={"source": "test"} - ) - created_document = await doc_repo.create(document) - - # Test get_by_id - assert created_document.id is not None - retrieved_document = await doc_repo.get_by_id(created_document.id) - assert retrieved_document is not None - assert retrieved_document.content == document_text - assert retrieved_document.uri == test_uri - - # Test get_by_uri - retrieved_by_uri = await doc_repo.get_by_uri(test_uri) - assert retrieved_by_uri is not None - assert retrieved_by_uri.id == created_document.id - assert retrieved_by_uri.content == document_text - assert retrieved_by_uri.uri == test_uri - - # Test get_by_uri with non-existent URI - non_existent = await doc_repo.get_by_uri("file:///non/existent.txt") - assert non_existent is None - - # Test update (should regenerate chunks) - retrieved_document.content = "Updated content for testing" - updated_document = await doc_repo.update(retrieved_document) - assert updated_document.content == "Updated content for testing" - - # Test list_all - all_documents = await doc_repo.list_all() - assert len(all_documents) == 1 - assert all_documents[0].id == created_document.id - - # Test delete - deleted = await doc_repo.delete(created_document.id) - assert deleted is True - - # Verify document is gone - retrieved_document = await doc_repo.get_by_id(created_document.id) - assert retrieved_document is None - - store.close() - - @pytest.mark.asyncio async def test_document_list_with_filter(qa_corpus: Dataset, temp_db_path): """Test listing documents with filter clause."""