mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
Fix blocking I/O operations in async route handlers
Wrapped blocking file I/O and CPU-bound operations with asyncio.to_thread() to prevent blocking the event loop: - about.py: stats() and cross_references_index() now compute in thread pool (extensive JSON loading and iteration) - commentary.py: commentary_index() file I/O in thread pool - misc.py: OG image fallback read_bytes() in thread pool These routes perform heavy file I/O (reading 66+ JSON files, iterating 31k verses) which would block all other requests if run in the async context directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""About routes - stats, cross-references index, and about page."""
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
@@ -22,12 +23,11 @@ def init_templates(t: Jinja2Templates):
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes
|
||||
# Helper Functions (run in thread pool)
|
||||
# =============================================================================
|
||||
|
||||
@router.get("/about/stats", response_class=HTMLResponse)
|
||||
async def stats(request: Request):
|
||||
"""Hidden statistics page - comprehensive site metrics"""
|
||||
def _compute_stats() -> dict:
|
||||
"""Compute all statistics - runs in thread pool to avoid blocking."""
|
||||
data_dir = Path(__file__).parent.parent / "data"
|
||||
|
||||
# Bible statistics
|
||||
@@ -148,7 +148,7 @@ async def stats(request: Request):
|
||||
total_hebrew_entries = 0
|
||||
total_greek_entries = 0
|
||||
|
||||
stats_data = {
|
||||
return {
|
||||
'bible': {
|
||||
'total_verses': total_verses,
|
||||
'total_books': total_books,
|
||||
@@ -207,27 +207,9 @@ async def stats(request: Request):
|
||||
}
|
||||
}
|
||||
|
||||
books = bible.get_books()
|
||||
breadcrumbs = [
|
||||
{"text": "Home", "url": "/"},
|
||||
{"text": "About", "url": "/about"},
|
||||
{"text": "Statistics", "url": None}
|
||||
]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"stats.html",
|
||||
{
|
||||
"request": request,
|
||||
"books": books,
|
||||
"stats": stats_data,
|
||||
"breadcrumbs": breadcrumbs,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/about/cross-references", response_class=HTMLResponse)
|
||||
async def cross_references_index(request: Request):
|
||||
"""Cross-references index - list all verses with cross-references"""
|
||||
def _compute_crossref_index() -> tuple:
|
||||
"""Compute cross-reference index - runs in thread pool."""
|
||||
data_dir = Path(__file__).parent.parent / "data" / "cross_references"
|
||||
|
||||
# Build index of all verses with cross-references, grouped by book
|
||||
@@ -273,6 +255,43 @@ async def cross_references_index(request: Request):
|
||||
for verses in chapters.values()
|
||||
)
|
||||
|
||||
return crossref_index, total_books, total_verses, total_refs
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes
|
||||
# =============================================================================
|
||||
|
||||
@router.get("/about/stats", response_class=HTMLResponse)
|
||||
async def stats(request: Request):
|
||||
"""Hidden statistics page - comprehensive site metrics"""
|
||||
# Run heavy computation in thread pool
|
||||
stats_data = await asyncio.to_thread(_compute_stats)
|
||||
|
||||
books = bible.get_books()
|
||||
breadcrumbs = [
|
||||
{"text": "Home", "url": "/"},
|
||||
{"text": "About", "url": "/about"},
|
||||
{"text": "Statistics", "url": None}
|
||||
]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"stats.html",
|
||||
{
|
||||
"request": request,
|
||||
"books": books,
|
||||
"stats": stats_data,
|
||||
"breadcrumbs": breadcrumbs,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/about/cross-references", response_class=HTMLResponse)
|
||||
async def cross_references_index(request: Request):
|
||||
"""Cross-references index - list all verses with cross-references"""
|
||||
# Run heavy I/O in thread pool
|
||||
crossref_index, total_books, total_verses, total_refs = await asyncio.to_thread(_compute_crossref_index)
|
||||
|
||||
books = bible.get_books()
|
||||
breadcrumbs = [
|
||||
{"text": "Home", "url": "/"},
|
||||
|
||||
@@ -5,8 +5,10 @@ This module contains the commentary generation system including:
|
||||
- Helper functions for generating theological commentary
|
||||
- Book summaries, chapter overviews, and verse analysis
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
@@ -20,10 +22,8 @@ router = APIRouter(tags=["Commentary"])
|
||||
templates = None
|
||||
|
||||
|
||||
@router.get("/about/commentary", response_class=HTMLResponse)
|
||||
async def commentary_index(request: Request):
|
||||
"""Commentary index - list all verses with commentary"""
|
||||
from collections import defaultdict
|
||||
def _compute_commentary_index() -> tuple:
|
||||
"""Compute commentary index - runs in thread pool."""
|
||||
from ..utils.books import OT_BOOKS, NT_BOOKS
|
||||
|
||||
data_dir = Path(__file__).parent.parent / "data" / "verse_commentary"
|
||||
@@ -58,6 +58,15 @@ async def commentary_index(request: Request):
|
||||
for verses in chapters.values()
|
||||
)
|
||||
|
||||
return commentary_index, total_books, total_verses
|
||||
|
||||
|
||||
@router.get("/about/commentary", response_class=HTMLResponse)
|
||||
async def commentary_index(request: Request):
|
||||
"""Commentary index - list all verses with commentary"""
|
||||
# Run heavy I/O in thread pool
|
||||
commentary_idx, total_books, total_verses = await asyncio.to_thread(_compute_commentary_index)
|
||||
|
||||
breadcrumbs = [
|
||||
{"text": "Home", "url": "/"},
|
||||
{"text": "About", "url": "/about"},
|
||||
@@ -73,7 +82,7 @@ async def commentary_index(request: Request):
|
||||
{
|
||||
"request": request,
|
||||
"books": books,
|
||||
"commentary_index": commentary_index,
|
||||
"commentary_index": commentary_idx,
|
||||
"total_books": total_books,
|
||||
"total_verses": total_verses,
|
||||
"breadcrumbs": breadcrumbs,
|
||||
|
||||
@@ -413,7 +413,8 @@ async def og_image_verse(
|
||||
# Return default image if verse not found
|
||||
from pathlib import Path as PathLib
|
||||
default_path = PathLib(__file__).parent.parent / "static" / "og-image.png"
|
||||
return Response(content=default_path.read_bytes(), media_type="image/png")
|
||||
content = await asyncio.to_thread(default_path.read_bytes)
|
||||
return Response(content=content, media_type="image/png")
|
||||
|
||||
title = f"{book} {chapter}:{verse}"
|
||||
cache_key = f"verse:{book}:{chapter}:{verse}"
|
||||
|
||||
Reference in New Issue
Block a user