mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 23:00:16 +00:00
Implement Phase 2 performance optimizations
1. Replace regex markdown with mistune library - Add mistune>=3.0.2 dependency - Replace custom regex patterns with proper markdown parser - Better performance and more robust parsing - Supports full markdown syntax (bold, italic, strikethrough, etc.) 2. Cache story counts - Cache get_story_count() and get_category_count() - Expected: 10-20x faster story index page loads - Added cache invalidation to refresh_stories() 3. Fix O(n) pattern in helpers.py - Replace manual chapter filtering with bible.get_chapters_for_book() - Uses existing @lru_cache for instant lookups Combined expected improvement: 10-20% on story pages, faster markdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+20
-14
@@ -189,27 +189,33 @@ templates = Jinja2Templates(directory=str(templates_dir))
|
||||
# Register custom Jinja2 filters
|
||||
templates.env.filters['slugify'] = create_slug
|
||||
|
||||
# Initialize mistune for markdown rendering
|
||||
import mistune
|
||||
|
||||
# Create mistune instance for full markdown (with paragraphs)
|
||||
_markdown = mistune.create_markdown(escape=False, hard_wrap=False)
|
||||
|
||||
# Create inline renderer for markdown without paragraph wrapping
|
||||
_inline_markdown = mistune.create_markdown(
|
||||
renderer=mistune.HTMLRenderer(escape=False),
|
||||
plugins=['strikethrough']
|
||||
)
|
||||
|
||||
def markdown_inline(text):
|
||||
"""Convert inline markdown to HTML (bold and italic only, no paragraph wrapping)."""
|
||||
"""Convert inline markdown to HTML (bold, italic, etc. - no paragraph wrapping)."""
|
||||
if not text:
|
||||
return text
|
||||
# Convert **bold** to <strong>bold</strong> (must be done before italic)
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
||||
# Convert *italic* to <em>italic</em>
|
||||
text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text)
|
||||
return text
|
||||
# Render and strip any outer <p> tags that mistune might add
|
||||
html = _inline_markdown(text).strip()
|
||||
if html.startswith('<p>') and html.endswith('</p>'):
|
||||
html = html[3:-4]
|
||||
return html
|
||||
|
||||
def markdown_to_html(text):
|
||||
"""Convert simple markdown to HTML (bold, italic, and paragraphs)."""
|
||||
"""Convert markdown to HTML (bold, italic, paragraphs, etc.)."""
|
||||
if not text:
|
||||
return text
|
||||
# First apply inline markdown
|
||||
text = markdown_inline(text)
|
||||
# Convert paragraphs (split on double newlines)
|
||||
paragraphs = text.split('\n\n')
|
||||
# Always wrap in <p> tags
|
||||
text = ''.join(f'<p>{p.strip()}</p>' for p in paragraphs if p.strip())
|
||||
return text
|
||||
return _markdown(text).strip()
|
||||
|
||||
templates.env.filters['md'] = markdown_to_html
|
||||
templates.env.filters['mdi'] = markdown_inline
|
||||
|
||||
+18
-5
@@ -86,14 +86,25 @@ def get_category_by_slug(category_slug: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
# Cache story counts
|
||||
_CACHED_STORY_COUNT = None
|
||||
_CACHED_CATEGORY_COUNT = None
|
||||
|
||||
|
||||
def get_story_count() -> int:
|
||||
"""Get total number of stories."""
|
||||
return len(get_all_stories_flat())
|
||||
"""Get total number of stories (cached)."""
|
||||
global _CACHED_STORY_COUNT
|
||||
if _CACHED_STORY_COUNT is None:
|
||||
_CACHED_STORY_COUNT = len(get_all_stories_flat())
|
||||
return _CACHED_STORY_COUNT
|
||||
|
||||
|
||||
def get_category_count() -> int:
|
||||
"""Get total number of categories."""
|
||||
return len(load_all_stories())
|
||||
"""Get total number of categories (cached)."""
|
||||
global _CACHED_CATEGORY_COUNT
|
||||
if _CACHED_CATEGORY_COUNT is None:
|
||||
_CACHED_CATEGORY_COUNT = len(load_all_stories())
|
||||
return _CACHED_CATEGORY_COUNT
|
||||
|
||||
|
||||
# Pre-load categories on module import for faster access
|
||||
@@ -110,5 +121,7 @@ def get_categories() -> list[dict]:
|
||||
|
||||
def refresh_stories():
|
||||
"""Refresh the cached stories (useful after adding new files)."""
|
||||
global STORY_CATEGORIES
|
||||
global STORY_CATEGORIES, _CACHED_STORY_COUNT, _CACHED_CATEGORY_COUNT
|
||||
STORY_CATEGORIES = load_all_stories()
|
||||
_CACHED_STORY_COUNT = None
|
||||
_CACHED_CATEGORY_COUNT = None
|
||||
|
||||
@@ -208,7 +208,7 @@ def get_chapter_popularity_score(book: str, chapter: int) -> int:
|
||||
if book in HIGH_READERSHIP_BOOKS:
|
||||
default_score += 1
|
||||
|
||||
total_chapters = len([ch for bk, ch in bible.iter_chapters() if bk == book])
|
||||
total_chapters = len(bible.get_chapters_for_book(book))
|
||||
if total_chapters <= 5:
|
||||
default_score += 1
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ dependencies = [
|
||||
"biblepy>=0.1.3",
|
||||
"fastapi[standard]>=0.115.12",
|
||||
"ged4py>=0.5.2",
|
||||
"mistune>=3.0.2",
|
||||
"parse>=1.20.2",
|
||||
"python-gedcom>=1.0.0",
|
||||
"requests>=2.32.3",
|
||||
|
||||
@@ -483,6 +483,7 @@ dependencies = [
|
||||
{ name = "biblepy" },
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "ged4py" },
|
||||
{ name = "mistune" },
|
||||
{ name = "parse" },
|
||||
{ name = "python-gedcom" },
|
||||
{ name = "requests" },
|
||||
@@ -506,6 +507,7 @@ requires-dist = [
|
||||
{ name = "biblepy", specifier = ">=0.1.3" },
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
||||
{ name = "ged4py", specifier = ">=0.5.2" },
|
||||
{ name = "mistune", specifier = ">=3.0.2" },
|
||||
{ name = "parse", specifier = ">=1.20.2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.5" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
||||
@@ -568,6 +570,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mistune"
|
||||
version = "3.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
|
||||
Reference in New Issue
Block a user