fix: clear ruff so the pre-push gate passes

105 findings to zero. Most were mechanical — 67 unused imports, and assorted
f-strings without placeholders. Three groups needed a decision.

The 15 F821 "undefined name" were forward references, not runtime errors. Each
annotation is quoted — `-> "WikiService"`, `Optional["IngestionService"]` — with
the real import inside the function body to break an import cycle. A quoted
annotation is never evaluated, so the code ran; the names were simply
unresolvable to any checker. They now have a TYPE_CHECKING block, which costs
nothing at import time and keeps the cycle broken.

The 6 E402 split two ways. `import secrets`, `Security`, `Request` and
`HTTPBearer` in dependencies.py had drifted below several hundred lines of
factory functions for no reason — stdlib and fastapi, no cycle to avoid — and
moved up. The other three are deliberate and now say so: the VectorService and
GraphService aliases import back into dependencies.py, and main.py's routers
expect a configured app, so both must stay put.

Bare `except:` narrowed to `except Exception:` in three places, which stops them
swallowing KeyboardInterrupt and SystemExit.

The 5 unused locals were all genuinely dead. One is worth naming rather than
fixing: qdrant_client.delete()'s return value was bound and never read, so a
failed delete is indistinguishable from a successful one — the assignment is
gone, but nothing checks the status either way and that has not changed here.
`timing = {}` in _retrieve_parallel looked like it might mean the reported
per-leg timings were always zero; traced, and they come from output["timing"],
so the local was only vestigial.

426 passed, 29 skipped, unchanged. The app imports and the service aliases still
resolve, which is the check that mattered after moving imports in
dependencies.py.

The gate still prints "not gated here yet: test (T-56)" — lint is green, tests
remain unwired, and that is left visible rather than silently absent.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 17:04:58 +02:00
co-authored by Claude
parent 4ad6598129
commit a687b770ef
36 changed files with 95 additions and 99 deletions
-1
View File
@@ -8,7 +8,6 @@ Source selection is driven by user preferences in the settings database.
import asyncio
import logging
from datetime import datetime, timezone
from typing import Optional
from .base import NewsProvider, NewsItem, NewsFeed
from .nos import NOSProvider
+1 -1
View File
@@ -8,7 +8,7 @@ Provides async Neo4j operations with:
- Automatic retry on transient failures
"""
from neo4j import AsyncGraphDatabase, AsyncDriver, AsyncSession, READ_ACCESS
from neo4j import AsyncGraphDatabase, AsyncDriver, READ_ACCESS
from typing import Optional, List, Dict, Any
import logging
+1 -1
View File
@@ -529,7 +529,7 @@ class QdrantClientWrapper:
query_filter = Filter(must=conditions)
# Delete points
result = await self.client.delete(
await self.client.delete(
collection_name=collection_name,
points_selector=query_filter
)
+24 -10
View File
@@ -9,12 +9,17 @@ Provides FastAPI dependencies for service clients with:
"""
from functools import lru_cache
from typing import Annotated
from typing import TYPE_CHECKING, Annotated
from fastapi import Depends, HTTPException, Query
import logging
import redis.asyncio as aioredis
import secrets
from fastapi import Request, Security
from fastapi.security import HTTPBearer
from src.config import Settings, get_settings
from src.clients.neo4j_client import Neo4jClient
from src.clients.qdrant_client import QdrantClientWrapper
@@ -764,11 +769,6 @@ def get_volatile_cache_service() -> "VolatileCacheService":
)
# Authentication
import secrets
from fastapi import Security, HTTPException, Request
from fastapi.security import HTTPBearer
security = HTTPBearer()
@@ -833,10 +833,24 @@ async def verify_browser_request(
raise HTTPException(status_code=401, detail="Unauthenticated")
# Service type aliases for FastAPI endpoint dependencies
# These are defined after the factory functions
from src.services.vector_service import VectorService
from src.services.graph_service import GraphService
# Service type aliases for FastAPI endpoint dependencies.
# Deliberately imported here rather than at the top: these modules import back
# into this one, so a module-level import would cycle. The factory functions
# above must exist before they are pulled in.
from src.services.vector_service import VectorService # noqa: E402
from src.services.graph_service import GraphService # noqa: E402
# Imported for annotations only. The real imports live inside the functions
# that use them, to break an import cycle; a quoted annotation is never
# evaluated at runtime, so the names were unresolvable to any checker. This
# block costs nothing at import time and makes them resolvable again.
if TYPE_CHECKING:
from src.services.consolidation_service import ConsolidationService
from src.services.hybrid_rag_service import HybridRAGService
from src.services.ingestion_service import IngestionService
from src.services.rag_search_service import RAGSearchService
from src.services.volatile_service import VolatileCacheService
from src.services.wiki_service import WikiService
VectorServiceDep = Annotated[VectorService, Depends(get_vector_service)]
GraphServiceDep = Annotated[GraphService, Depends(get_graph_service)]
+3 -2
View File
@@ -55,8 +55,9 @@ app.add_middleware(
allow_headers=["*"],
)
# Register routers
from src.routers import (
# Register routers. Imported after the app and middleware exist, because the
# routers import dependencies that expect a configured app.
from src.routers import ( # noqa: E402
wiki, tools, graph, vector, hybrid_rag, consolidation,
ingestion, entity_linking, webhooks, rag_search, content,
maintenance, volatile, documents
+1 -1
View File
@@ -5,7 +5,7 @@ Used by the consolidation endpoint to process SearchQuery nodes
and consolidate knowledge into wiki pages.
"""
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from typing import List, Optional
class ConsolidationRequest(BaseModel):
-1
View File
@@ -6,7 +6,6 @@ Provides models for knowledge graph nodes, relationships, and queries.
from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
from datetime import datetime
from src.core.multi_tenancy import RequiredUser
+1 -1
View File
@@ -2,7 +2,7 @@
Pydantic models for Document Ingestion system.
"""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from typing import Optional, List
from datetime import datetime
from src.core.multi_tenancy import RequiredUser
-1
View File
@@ -9,7 +9,6 @@ Models for:
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List, Dict, Any
from datetime import datetime
from src.core.multi_tenancy import RequiredUser
+1 -1
View File
@@ -140,7 +140,7 @@ async def capture_webhook(request: Request):
# Try to parse as JSON
try:
capture["body_json"] = json.loads(body)
except:
except Exception:
capture["body_json"] = None
# Write to file
+1 -1
View File
@@ -9,7 +9,7 @@ Creates both:
import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import List, Dict, Any, Optional, Tuple
from typing import List, Dict, Any, Tuple
import re
from src.config import get_settings
+1 -1
View File
@@ -10,7 +10,7 @@ import logging
from src.models.graph import (
CypherQueryRequest, CypherQueryResponse,
UpdateFromPageRequest, GraphUpdateSummary,
GraphUpdateSummary,
NodeListResponse, GraphNodeDetail,
MindMapResponse
)
+2 -6
View File
@@ -5,19 +5,15 @@ Endpoints for semantic search and vector operations.
"""
from fastapi import APIRouter, HTTPException, Depends, Query
from typing import Optional
import logging
from src.models.vector import (
SearchRequest, SearchResponse,
VectorUpdateRequest, VectorUpdateSummary,
VectorUpdateSummary,
CollectionListResponse,
DeletePageChunksRequest, DeletePageChunksResponse
DeletePageChunksResponse
)
from src.services.vector_service import VectorService
from src.clients.qdrant_client import QdrantClientWrapper
from src.clients.wikijs_client import WikiJSClient
from src.clients.ollama_client import OllamaClient
from src.core.dependencies import (
QdrantDep, WikiJSDep, OllamaDep, verify_api_key, RequiredUserQuery
)
-1
View File
@@ -11,7 +11,6 @@ import logging
from src.models.volatile import (
VolatileRecordCreate,
VolatileRecordResponse,
VolatileListResponse,
VolatileScheduledResponse,
VolatileStatsResponse,
VolatileDeleteResponse,
+4 -6
View File
@@ -5,14 +5,12 @@ Receives webhook events from Wiki.js for page CRUD operations
and processes them identically to AI-generated content.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from fastapi import APIRouter, Depends, BackgroundTasks
from pydantic import BaseModel
from typing import Optional, Literal
from src.core.dependencies import (
get_ingestion_service,
get_wiki_service,
get_graph_service,
verify_api_key
)
from src.services.ingestion_service import IngestionService
@@ -373,7 +371,7 @@ async def process_page_rename(
logger.error(f"Failed to apply entity linking: {e}")
elif path_changed:
logger.info(f"Path changed only (move), no re-processing needed")
logger.info("Path changed only (move), no re-processing needed")
logger.info(f"Rename processing complete for page {page_id}")
@@ -470,9 +468,9 @@ async def cleanup_deleted_page(
logger.error(f"Failed to delete orphaned entity {entity_name}: {e}")
# STEP 5: Clean up broken SearchQuery relationships
cleanup_search_query = f"""
cleanup_search_query = """
MATCH (sq:SearchQuery)-[r:FOUND]->(d:Document)
WHERE NOT EXISTS {{(d)}}
WHERE NOT EXISTS {(d)}
DELETE r
RETURN count(r) as cleaned_count
"""
-4
View File
@@ -18,10 +18,6 @@ from src.models.wiki import (
from src.services.wiki_service import WikiService
from src.services.graph_service import GraphService
from src.services.vector_service import VectorService
from src.clients.wikijs_client import WikiJSClient
from src.clients.neo4j_client import Neo4jClient
from src.clients.qdrant_client import QdrantClientWrapper
from src.clients.ollama_client import OllamaClient
from src.core.dependencies import (
WikiJSDep, Neo4jDep, QdrantDep, OllamaDep,
verify_api_key, get_settings, get_hybrid_rag_service, get_ingestion_service,
+11 -2
View File
@@ -15,14 +15,13 @@ import logging
import json
import time
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Any, Optional
from typing import TYPE_CHECKING, List, Dict, Any, Optional
from src.clients.neo4j_client import Neo4jClient
from src.clients.ollama_client import OllamaClient
from src.clients.wikijs_client import WikiJSClient
from src.services.wiki_page_writer import WikiPageWriter
from src.models.consolidation import (
SearchQueryInfo,
ConsolidationResult,
ConsolidationResponse,
MemoryRouteClassification,
@@ -30,6 +29,16 @@ from src.models.consolidation import (
)
from src.config import Settings
# Imported for annotations only. The real imports live inside the functions
# that use them, to break an import cycle; a quoted annotation is never
# evaluated at runtime, so the names were unresolvable to any checker. This
# block costs nothing at import time and makes them resolvable again.
if TYPE_CHECKING:
from src.clients.scheduler_client import SchedulerClient
from src.clients.settings_client import SettingsClient
from src.services.ingestion_service import IngestionService
from src.services.volatile_service import VolatileCacheService
logger = logging.getLogger(__name__)
+12 -6
View File
@@ -7,10 +7,19 @@ Provides bidirectional entity linking functionality that can be used by:
- Any other service that creates wiki pages
"""
import logging
from typing import Dict, Any, Optional
from typing import TYPE_CHECKING, Dict, Optional
from src.core.multi_tenancy import get_neo4j_user_base_label
# Imported for annotations only. The real imports live inside the functions
# that use them, to break an import cycle; a quoted annotation is never
# evaluated at runtime, so the names were unresolvable to any checker. This
# block costs nothing at import time and makes them resolvable again.
if TYPE_CHECKING:
from src.clients.neo4j_client import Neo4jClient
from src.services.ingestion_service import IngestionService
from src.services.wiki_service import WikiService
logger = logging.getLogger(__name__)
@@ -49,12 +58,9 @@ async def apply_bidirectional_entity_linking(
"""
from src.routers.entity_linking import (
link_entities_in_page,
EntityLinkingRequest,
get_entities_with_paths,
add_entity_links_to_content
EntityLinkingRequest
)
from src.core.dependencies import get_graph_service, get_wiki_service, get_ingestion_service
from src.models.wiki import WikiPageUpdate
from src.core.dependencies import get_graph_service, get_ingestion_service
forward_links = 0
backward_links = 0
+2 -2
View File
@@ -879,7 +879,7 @@ Feel free to expand it with more details!
{
"name": r["name"],
# Get the entity type label (not the user label)
"type": [l for l in r["labels"] if l not in [user_base_label, "Document"]][0]
"type": [line for line in r["labels"] if line not in [user_base_label, "Document"]][0]
if r["labels"] else "Entity",
"path": r.get("path") # Include path if it exists (for entity stub pages)
}
@@ -1524,7 +1524,7 @@ Feel free to expand it with more details!
for r in results:
labels = r.get("labels", [])
entity_type = next(
(l for l in labels if l != user_base_label),
(line for line in labels if line != user_base_label),
"Unknown"
)
orphans.append({
+1 -3
View File
@@ -26,7 +26,7 @@ from src.clients.ollama_client import OllamaClient
from src.clients.content_extractor import ContentExtractor
from src.config import Settings
from src.models.hybrid_rag import (
HybridRAGConfig, HybridRAGRequest, HybridRAGResponse,
HybridRAGConfig, HybridRAGResponse,
HybridRAGResult, TimingBreakdown, KeywordExtraction,
RelatedDossier
)
@@ -123,7 +123,6 @@ class HybridRAGService:
timing["query_enhancement_ms"] = (time.time() - phase0_start) * 1000
# Phase 1: Parallel Retrieval
phase1_start = time.time()
raw_results = await self._retrieve_parallel(query, user, config, keywords_data)
timing["vector_ms"] = raw_results.get("timing", {}).get("vector_ms", 0)
timing["graph_ms"] = raw_results.get("timing", {}).get("graph_ms", 0)
@@ -357,7 +356,6 @@ JSON:"""
"source_status" ('ok', 'failed', or 'disabled')
"""
tasks = {}
timing = {}
# Vector search
if config.enable_vector:
-3
View File
@@ -14,16 +14,13 @@ This service is called by:
import logging
import asyncio
from typing import List, Optional
from datetime import datetime
import time
from src.services.vector_service import VectorService
from src.services.graph_service import GraphService
from src.clients.wikijs_client import WikiJSClient
from src.models.ingestion import (
IngestionRequest,
IngestionResult,
BatchIngestionRequest,
BatchIngestionResult
)
-1
View File
@@ -21,7 +21,6 @@ from src.clients.content_extractor import ContentExtractor
from src.config import Settings
from src.models.rag_search import (
SearchType,
RAGSearchRequest,
RAGSearchResult,
RAGSearchResponse,
)
+2 -3
View File
@@ -6,9 +6,8 @@ Handles semantic search, document chunking, and embeddings.
import re
import time
import hashlib
import uuid
from typing import List, Dict, Any, Optional
from typing import List, Dict, Any
import logging
from src.clients.qdrant_client import QdrantClientWrapper
@@ -17,7 +16,7 @@ from src.clients.ollama_client import OllamaClient
from src.core.multi_tenancy import get_qdrant_collection_name, is_path_in_user_namespace
from src.models.vector import (
SearchResult, SearchResponse, VectorUpdateSummary,
DocumentChunk, CollectionInfo, CollectionListResponse
CollectionInfo, CollectionListResponse
)
logger = logging.getLogger(__name__)
-6
View File
@@ -14,12 +14,6 @@ from src.apis import (
OpenMeteoProvider,
AggregatedNewsProvider,
AlphaVantageProvider,
CurrentWeather,
WeatherForecast,
SunTimes,
AirQuality,
NewsFeed,
StockQuote,
)
from src.services.volatile_service import VolatileCacheService
from src.models.volatile import VolatileRecordResponse, VolatileNamespace
-1
View File
@@ -14,7 +14,6 @@ from datetime import datetime
from src.config import get_settings
from src.core.dependencies import get_ingestion_service
from src.services.consolidation_service import ConsolidationService
logger = logging.getLogger(__name__)
+9 -1
View File
@@ -8,7 +8,7 @@ Handles business logic for wiki operations with:
- Search functionality
"""
from typing import List, Optional, Dict, Any
from typing import TYPE_CHECKING, List, Optional, Dict, Any
import logging
from src.clients.wikijs_client import WikiJSClient
@@ -19,6 +19,14 @@ from src.models.wiki import (
DossierInfo, DossierList
)
# Imported for annotations only. The real imports live inside the functions
# that use them, to break an import cycle; a quoted annotation is never
# evaluated at runtime, so the names were unresolvable to any checker. This
# block costs nothing at import time and makes them resolvable again.
if TYPE_CHECKING:
from src.services.hybrid_rag_service import HybridRAGService
from src.services.wiki_page_writer import WikiPageWriter
logger = logging.getLogger(__name__)
+2 -5
View File
@@ -12,9 +12,7 @@ Run with: pytest tests/test_consolidation.py -v -s
"""
import pytest
import pytest_asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from typing import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock
from datetime import datetime
import json
@@ -22,8 +20,7 @@ from src.services.consolidation_service import ConsolidationService
from src.models.consolidation import (
ConsolidationRequest,
ConsolidationResponse,
ConsolidationResult,
SearchQueryInfo
ConsolidationResult
)
# Test constants
+1 -1
View File
@@ -480,4 +480,4 @@ async def test_cleanup_entity_linking_test_data(neo4j_client):
"""
await neo4j_client.execute_query(cleanup_query)
print(f"\n✓ Cleaned up entity linking test data")
print("\n✓ Cleaned up entity linking test data")
+1 -3
View File
@@ -10,9 +10,7 @@ Run with: pytest tests/test_graph_service.py -v -s
"""
import pytest
import pytest_asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from typing import AsyncGenerator
from unittest.mock import AsyncMock
from src.services.graph_service import GraphService
+7 -7
View File
@@ -29,7 +29,7 @@ from src.clients.content_extractor import ContentExtractor
from src.services.hybrid_rag_service import HybridRAGService
from src.services.vector_service import VectorService
from src.services.graph_service import GraphService
from src.models.hybrid_rag import HybridRAGConfig, HybridRAGRequest
from src.models.hybrid_rag import HybridRAGConfig
from src.config import get_settings
# Test user to isolate test data
@@ -176,7 +176,7 @@ We deploy microservices using Helm charts and manage them with kubectl.
# Cleanup
try:
await wiki_client.delete_page(page["id"])
except:
except Exception:
pass
except Exception as e:
pytest.skip(f"Could not create test page: {e}")
@@ -531,7 +531,7 @@ class TestPhase6_Persistence:
result = await neo4j_client.execute_query(query, {"search_id": search_id})
assert len(result) == 1
assert result[0]["query"] == "test query"
assert result[0]["processed"] == False
assert not result[0]["processed"]
# Cleanup
cleanup_query = f"""
@@ -614,7 +614,7 @@ class TestHybridRAG_EndToEnd:
assert len(response.context) > 0
# Log results for inspection
print(f"\n=== HybridRAG E2E Test Results ===")
print("\n=== HybridRAG E2E Test Results ===")
print(f"Query: {response.query}")
print(f"Total Results: {response.total_results}")
print(f"Source Counts: {response.source_counts}")
@@ -623,7 +623,7 @@ class TestHybridRAG_EndToEnd:
print(f"Search ID: {response.search_id}")
if response.results:
print(f"\nTop Result:")
print("\nTop Result:")
top = response.results[0]
print(f" Title: {top.title}")
print(f" Source: {top.source_type}")
@@ -674,7 +674,7 @@ class TestHybridRAG_EndToEnd:
config = HybridRAGConfig()
start = time.time()
response = await hybrid_rag_service.search(
await hybrid_rag_service.search(
query="kubernetes orchestration",
user=TEST_USER,
config=config
@@ -722,7 +722,7 @@ async def test_cleanup_test_data(neo4j_client, qdrant_client):
collection_name = get_qdrant_collection_name(TEST_USER)
try:
await qdrant_client.delete_collection(collection_name)
except:
except Exception:
pass
print(f"\n✓ Cleaned up test data for user: {TEST_USER}")
+2 -6
View File
@@ -11,16 +11,12 @@ Run with: pytest tests/test_ingestion.py -v -s
"""
import pytest
import pytest_asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from typing import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock
from src.services.ingestion_service import IngestionService
from src.models.ingestion import (
IngestionRequest,
IngestionResult,
BatchIngestionRequest,
BatchIngestionResult
BatchIngestionRequest
)
+2 -4
View File
@@ -8,7 +8,7 @@ Tests cleanup of:
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
from src.routers.maintenance import (
cleanup_vectors,
cleanup_graph,
@@ -18,9 +18,7 @@ from src.routers.maintenance import (
CleanupResult,
VectorCleanupResponse,
GraphCleanupResponse,
FullCleanupResponse,
HealthCheckResponse,
ReindexResponse
HealthCheckResponse
)
+1 -1
View File
@@ -1,7 +1,7 @@
"""Tests for RAG search service and endpoints."""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, MagicMock
from src.models.rag_search import (
SearchType,
-2
View File
@@ -538,8 +538,6 @@ class TestSmartCreateEndpoint:
@pytest.mark.asyncio
async def test_endpoint_returns_201(self, mock_clients):
"""Test that successful creation returns 201 status."""
from fastapi.testclient import TestClient
from unittest.mock import patch
# This test would require more setup with FastAPI TestClient
# For now, we test the model validation
+1 -1
View File
@@ -539,7 +539,7 @@ class TestVolatileCleanupEndpoint:
@pytest.mark.asyncio
async def test_cleanup_volatile(self):
"""Test volatile cleanup endpoint."""
from src.routers.maintenance import cleanup_volatile, VolatileCleanupResponse
from src.routers.maintenance import cleanup_volatile
mock_qdrant = AsyncMock()
mock_qdrant.get_volatile_collections = AsyncMock(return_value=[
+1 -2
View File
@@ -10,7 +10,6 @@ Tests the PostgreSQL NOTIFY/LISTEN change detection system including:
"""
import pytest
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from datetime import datetime, timedelta
@@ -238,7 +237,7 @@ class TestWikiChangeListener:
async def test_process_page_delete_calls_cleanup(self, listener):
"""Test that DELETE events call cleanup_deleted_page."""
with patch('src.routers.webhooks.cleanup_deleted_page', new_callable=AsyncMock) as mock_cleanup, \
patch('src.services.wiki_change_listener.get_ingestion_service') as mock_service:
patch('src.services.wiki_change_listener.get_ingestion_service'):
await listener._process_page_change(
page_id=123,