feat: enhance database transactions, add Makefiles, and improve Docker setup

- Refactored database batch operations to ensure transaction atomicity and simplify nested structure.
- Added `Makefile` for `knowledge-base` and `llm-gateway` modules to streamline development workflows.
- Simplified `Dockerfile` for `llm-gateway` by removing multi-stage builds and optimizing dependencies.
- Improved code readability in `collection_manager` and `failover` modules with refined logic.
- Minor fixes in `test_server` and Redis health check handling for better diagnostics.
This commit is contained in:
2026-01-05 00:49:19 +01:00
parent db12937495
commit 4154dd5268
8 changed files with 259 additions and 119 deletions

View File

@@ -0,0 +1,79 @@
.PHONY: help install install-dev lint lint-fix format type-check test test-cov validate clean run
# Default target
help:
@echo "Knowledge Base MCP Server - Development Commands"
@echo ""
@echo "Setup:"
@echo " make install - Install production dependencies"
@echo " make install-dev - Install development dependencies"
@echo ""
@echo "Quality Checks:"
@echo " make lint - Run Ruff linter"
@echo " make lint-fix - Run Ruff linter with auto-fix"
@echo " make format - Format code with Ruff"
@echo " make type-check - Run mypy type checker"
@echo ""
@echo "Testing:"
@echo " make test - Run pytest"
@echo " make test-cov - Run pytest with coverage"
@echo ""
@echo "All-in-one:"
@echo " make validate - Run lint, type-check, and tests"
@echo ""
@echo "Running:"
@echo " make run - Run the server locally"
@echo ""
@echo "Cleanup:"
@echo " make clean - Remove cache and build artifacts"
# Setup
install:
@echo "Installing production dependencies..."
@uv pip install -e .
install-dev:
@echo "Installing development dependencies..."
@uv pip install -e ".[dev]"
# Quality checks
lint:
@echo "Running Ruff linter..."
@uv run ruff check .
lint-fix:
@echo "Running Ruff linter with auto-fix..."
@uv run ruff check --fix .
format:
@echo "Formatting code..."
@uv run ruff format .
type-check:
@echo "Running mypy..."
@uv run mypy . --ignore-missing-imports
# Testing
test:
@echo "Running tests..."
@uv run pytest tests/ -v
test-cov:
@echo "Running tests with coverage..."
@uv run pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=html
# All-in-one validation
validate: lint type-check test
@echo "All validations passed!"
# Running
run:
@echo "Starting Knowledge Base server..."
@uv run python server.py
# Cleanup
clean:
@echo "Cleaning up..."
@rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
@find . -type f -name "*.pyc" -delete 2>/dev/null || true

View File

@@ -328,7 +328,7 @@ class CollectionManager:
"source_path": chunk.source_path or source_path, "source_path": chunk.source_path or source_path,
"start_line": chunk.start_line, "start_line": chunk.start_line,
"end_line": chunk.end_line, "end_line": chunk.end_line,
"file_type": (chunk.file_type or file_type).value if (chunk.file_type or file_type) else None, "file_type": effective_file_type.value if (effective_file_type := chunk.file_type or file_type) else None,
} }
embeddings_data.append(( embeddings_data.append((
chunk.content, chunk.content,

View File

@@ -284,9 +284,8 @@ class DatabaseManager:
) )
try: try:
async with self.acquire() as conn: async with self.acquire() as conn, conn.transaction():
# Wrap in transaction for all-or-nothing batch semantics # Wrap in transaction for all-or-nothing batch semantics
async with conn.transaction():
for project_id, collection, content, embedding, chunk_type, metadata in embeddings: for project_id, collection, content, embedding, chunk_type, metadata in embeddings:
content_hash = self.compute_content_hash(content) content_hash = self.compute_content_hash(content)
source_path = metadata.get("source_path") source_path = metadata.get("source_path")
@@ -566,9 +565,8 @@ class DatabaseManager:
) )
try: try:
async with self.acquire() as conn: async with self.acquire() as conn, conn.transaction():
# Use transaction for atomic replace # Use transaction for atomic replace
async with conn.transaction():
# First, delete existing embeddings for this source # First, delete existing embeddings for this source
delete_result = await conn.execute( delete_result = await conn.execute(
""" """

View File

@@ -193,7 +193,7 @@ async def health_check() -> dict[str, Any]:
# Check Redis cache (non-critical - degraded without it) # Check Redis cache (non-critical - degraded without it)
try: try:
if _embeddings and _embeddings._redis: if _embeddings and _embeddings._redis:
await _embeddings._redis.ping() await _embeddings._redis.ping() # type: ignore[misc]
status["dependencies"]["redis"] = "connected" status["dependencies"]["redis"] = "connected"
else: else:
status["dependencies"]["redis"] = "not initialized" status["dependencies"]["redis"] = "not initialized"

View File

@@ -1,8 +1,7 @@
"""Tests for server module and MCP tools.""" """Tests for server module and MCP tools."""
import json
from datetime import UTC, datetime from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient

View File

@@ -1,39 +1,25 @@
# Syndarix LLM Gateway MCP Server # Syndarix LLM Gateway MCP Server
# Multi-stage build for minimal image size FROM python:3.12-slim
# Build stage WORKDIR /app
FROM python:3.12-slim AS builder
# Install system dependencies (needed for tiktoken regex compilation)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install uv for fast package management # Install uv for fast package management
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app # Copy project files
# Copy dependency files
COPY pyproject.toml ./ COPY pyproject.toml ./
COPY *.py ./
# Create virtual environment and install dependencies # Install dependencies to system Python
RUN uv venv /app/.venv RUN uv pip install --system --no-cache .
ENV PATH="/app/.venv/bin:$PATH"
RUN uv pip install -e .
# Runtime stage
FROM python:3.12-slim AS runtime
# Create non-root user for security # Create non-root user for security
RUN groupadd --gid 1000 appgroup && \ RUN useradd --create-home --shell /bin/bash appuser
useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
# Copy application code
COPY --chown=appuser:appgroup . .
# Switch to non-root user
USER appuser USER appuser
# Environment variables # Environment variables
@@ -47,7 +33,7 @@ EXPOSE 8001
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8001/health').raise_for_status()" || exit 1 CMD python -c "import httpx; httpx.get('http://localhost:8001/health').raise_for_status()"
# Run the server # Run the server
CMD ["python", "server.py"] CMD ["python", "server.py"]

View File

@@ -0,0 +1,79 @@
.PHONY: help install install-dev lint lint-fix format type-check test test-cov validate clean run
# Default target
help:
@echo "LLM Gateway MCP Server - Development Commands"
@echo ""
@echo "Setup:"
@echo " make install - Install production dependencies"
@echo " make install-dev - Install development dependencies"
@echo ""
@echo "Quality Checks:"
@echo " make lint - Run Ruff linter"
@echo " make lint-fix - Run Ruff linter with auto-fix"
@echo " make format - Format code with Ruff"
@echo " make type-check - Run mypy type checker"
@echo ""
@echo "Testing:"
@echo " make test - Run pytest"
@echo " make test-cov - Run pytest with coverage"
@echo ""
@echo "All-in-one:"
@echo " make validate - Run lint, type-check, and tests"
@echo ""
@echo "Running:"
@echo " make run - Run the server locally"
@echo ""
@echo "Cleanup:"
@echo " make clean - Remove cache and build artifacts"
# Setup
install:
@echo "Installing production dependencies..."
@uv pip install -e .
install-dev:
@echo "Installing development dependencies..."
@uv pip install -e ".[dev]"
# Quality checks
lint:
@echo "Running Ruff linter..."
@uv run ruff check .
lint-fix:
@echo "Running Ruff linter with auto-fix..."
@uv run ruff check --fix .
format:
@echo "Formatting code..."
@uv run ruff format .
type-check:
@echo "Running mypy..."
@uv run mypy . --ignore-missing-imports
# Testing
test:
@echo "Running tests..."
@uv run pytest tests/ -v
test-cov:
@echo "Running tests with coverage..."
@uv run pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=html
# All-in-one validation
validate: lint type-check test
@echo "All validations passed!"
# Running
run:
@echo "Starting LLM Gateway server..."
@uv run python server.py
# Cleanup
clean:
@echo "Cleaning up..."
@rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
@find . -type f -name "*.pyc" -delete 2>/dev/null || true

View File

@@ -110,9 +110,8 @@ class CircuitBreaker:
""" """
if self._state == CircuitState.OPEN: if self._state == CircuitState.OPEN:
time_in_open = time.time() - self._stats.state_changed_at time_in_open = time.time() - self._stats.state_changed_at
if time_in_open >= self.recovery_timeout: # Double-check state after time calculation (for thread safety)
# Only transition if still in OPEN state (double-check) if time_in_open >= self.recovery_timeout and self._state == CircuitState.OPEN:
if self._state == CircuitState.OPEN:
self._transition_to(CircuitState.HALF_OPEN) self._transition_to(CircuitState.HALF_OPEN)
logger.info( logger.info(
f"Circuit {self.name} transitioned to HALF_OPEN " f"Circuit {self.name} transitioned to HALF_OPEN "