feat: refine source_markdown migration logic to only convert documents with blocknote_document data, ensuring lazy population for others

This commit is contained in:
Anish Sarkar 2026-02-17 11:34:28 +05:30
parent 8b497da130
commit e1087937e6
2 changed files with 33 additions and 1429 deletions

View file

@ -4,14 +4,12 @@ Revision ID: 101
Revises: 100 Revises: 100
Create Date: 2026-02-17 Create Date: 2026-02-17
Adds source_markdown column and populates it for existing documents Adds source_markdown column and converts only documents that have
using a pure-Python BlockNote JSON Markdown converter. No external blocknote_document data. Uses a pure-Python BlockNote JSON Markdown
dependencies (no Node.js, no Celery, no HTTP calls). converter. No external dependencies (no Node.js, no Celery, no HTTP calls).
Fallback chain per document: Documents without blocknote_document keep source_markdown = NULL and
1. blocknote_document exists convert to markdown with Python converter get populated lazily by the editor route when a user first opens them.
2. blocknote_document missing/fails reconstruct from chunks
3. Neither exists skip (log warning)
""" """
from __future__ import annotations from __future__ import annotations
@ -48,103 +46,67 @@ def upgrade() -> None:
sa.Column("source_markdown", sa.Text(), nullable=True), sa.Column("source_markdown", sa.Text(), nullable=True),
) )
# 2. Populate source_markdown for existing documents (inline, synchronous) # 2. Convert only documents that have blocknote_document data
_populate_source_markdown(conn) _populate_source_markdown(conn)
def _populate_source_markdown(conn) -> None: def _populate_source_markdown(conn) -> None:
"""Populate source_markdown for all documents where it is NULL. """Populate source_markdown only for documents that have blocknote_document."""
Fallback chain:
1. blocknote_document pure-Python converter source_markdown
2. chunks (ordered by id) joined text source_markdown
3. Neither skip with warning
"""
# Import the pure-Python converter (no external deps)
from app.utils.blocknote_to_markdown import blocknote_to_markdown from app.utils.blocknote_to_markdown import blocknote_to_markdown
# Find documents that need migration # Only fetch documents that have blocknote_document content
result = conn.execute( result = conn.execute(
sa.text(""" sa.text("""
SELECT id, title, blocknote_document SELECT id, title, blocknote_document
FROM documents FROM documents
WHERE source_markdown IS NULL WHERE source_markdown IS NULL
AND blocknote_document IS NOT NULL
""") """)
) )
rows = result.fetchall() rows = result.fetchall()
total = len(rows) total = len(rows)
if total == 0: if total == 0:
print("✓ No documents need source_markdown migration") print("✓ No documents with blocknote_document need migration")
return return
print(f" Migrating {total} documents to source_markdown...") print(f" Migrating {total} documents (with blocknote_document) to source_markdown...")
migrated = 0 migrated = 0
from_blocknote = 0 failed = 0
from_chunks = 0
skipped = 0
for row in rows: for row in rows:
doc_id = row[0] doc_id = row[0]
doc_title = row[1] doc_title = row[1]
blocknote_doc = row[2] blocknote_doc = row[2]
markdown = None try:
if isinstance(blocknote_doc, str):
blocknote_doc = json.loads(blocknote_doc)
markdown = blocknote_to_markdown(blocknote_doc)
# --- Fallback 1: Convert blocknote_document with pure Python --- if markdown:
if blocknote_doc: conn.execute(
try: sa.text("""
# blocknote_doc may be a JSON string or already parsed UPDATE documents SET source_markdown = :md WHERE id = :doc_id
if isinstance(blocknote_doc, str): """),
blocknote_doc = json.loads(blocknote_doc) {"md": markdown, "doc_id": doc_id},
markdown = blocknote_to_markdown(blocknote_doc)
if markdown:
from_blocknote += 1
except Exception as e:
logger.warning(
f" Doc {doc_id} ({doc_title}): blocknote conversion failed ({e}), "
f"falling back to chunks"
) )
migrated += 1
# --- Fallback 2: Reconstruct from chunks --- else:
if not markdown: logger.warning(
chunk_result = conn.execute( f" Doc {doc_id} ({doc_title}): blocknote conversion produced empty result"
sa.text(""" )
SELECT content FROM chunks failed += 1
WHERE document_id = :doc_id except Exception as e:
ORDER BY id
"""),
{"doc_id": doc_id},
)
chunk_rows = chunk_result.fetchall()
if chunk_rows:
chunk_texts = [r[0] for r in chunk_rows if r[0]]
if chunk_texts:
markdown = "\n\n".join(chunk_texts)
from_chunks += 1
# --- Fallback 3: Nothing to migrate from ---
if not markdown or not markdown.strip():
logger.warning( logger.warning(
f" Doc {doc_id} ({doc_title}): no blocknote_document or chunks — skipped" f" Doc {doc_id} ({doc_title}): blocknote conversion failed ({e})"
) )
skipped += 1 failed += 1
continue
# Write source_markdown
conn.execute(
sa.text("""
UPDATE documents SET source_markdown = :md WHERE id = :doc_id
"""),
{"md": markdown, "doc_id": doc_id},
)
migrated += 1
print( print(
f"✓ source_markdown migration complete: {migrated} migrated " f"✓ source_markdown migration complete: {migrated} migrated, "
f"({from_blocknote} from blocknote, {from_chunks} from chunks), " f"{failed} failed out of {total} total"
f"{skipped} skipped out of {total} total"
) )

File diff suppressed because it is too large Load diff