Framework Integrations

This guide provides comprehensive integration examples for popular Python web frameworks, serverless platforms, and deployment scenarios. Each section includes production-ready code examples and best practices.

Django Integration

View-Based Conversion

Basic file upload and conversion view:

# views.py
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from all2md import to_markdown
from all2md.options import PdfOptions, HtmlOptions
from all2md.exceptions import All2MdError
import logging

logger = logging.getLogger(__name__)

@csrf_exempt  # Use proper CSRF in production
@require_http_methods(["POST"])
def convert_document(request):
    """Convert uploaded document to Markdown."""
    if 'document' not in request.FILES:
        return JsonResponse({'error': 'No file provided'}, status=400)

    uploaded_file = request.FILES['document']
    file_ext = uploaded_file.name.lower().split('.')[-1]

    # Validate file size (10MB limit)
    max_size = 10 * 1024 * 1024
    if uploaded_file.size > max_size:
        return JsonResponse({
            'error': f'File too large. Max size: {max_size} bytes'
        }, status=400)

    try:
        # Configure security options based on file type
        if file_ext == 'pdf':
            options = PdfOptions(
                attachment_mode='skip',
                pages=[1, 2, 3, 4, 5]  # Limit to first 5 pages
            )
        elif file_ext in ['html', 'htm']:
            options = HtmlOptions(
                strip_dangerous_elements=True,
                attachment_mode='skip',
                network=None  # Use env var ALL2MD_DISABLE_NETWORK
            )
        else:
            options = None

        # Convert document
        markdown = to_markdown(
            uploaded_file.temporary_file_path(),
            parser_options=options
        )

        # Return markdown
        return HttpResponse(markdown, content_type='text/markdown')

    except All2MdError as e:
        logger.error(f"Conversion failed: {e}")
        return JsonResponse({'error': str(e)}, status=500)

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('api/convert/', views.convert_document, name='convert_document'),
]

Class-Based Views

Using Django’s class-based views:

# views.py
from django.views import View
from django.http import HttpResponse, JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from all2md import to_markdown
import tempfile
from pathlib import Path

@method_decorator(csrf_exempt, name='dispatch')
class DocumentConversionView(View):
    """Handle document conversion requests."""

    ALLOWED_FORMATS = ['pdf', 'docx', 'pptx', 'html', 'txt']
    MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB

    def post(self, request):
        """Handle POST request with file upload."""
        # Validate request
        if 'file' not in request.FILES:
            return JsonResponse({'error': 'No file provided'}, status=400)

        uploaded_file = request.FILES['file']

        # Validate file type
        file_ext = Path(uploaded_file.name).suffix.lower().lstrip('.')
        if file_ext not in self.ALLOWED_FORMATS:
            return JsonResponse({
                'error': f'Unsupported format. Allowed: {self.ALLOWED_FORMATS}'
            }, status=400)

        # Validate file size
        if uploaded_file.size > self.MAX_FILE_SIZE:
            return JsonResponse({
                'error': f'File too large. Max: {self.MAX_FILE_SIZE} bytes'
            }, status=413)

        # Convert document
        try:
            markdown = self._convert_file(uploaded_file, file_ext)
            return HttpResponse(
                markdown,
                content_type='text/markdown',
                headers={'Content-Disposition': f'attachment; filename="{uploaded_file.name}.md"'}
            )
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)

    def _convert_file(self, uploaded_file, file_ext: str) -> str:
        """Convert uploaded file to markdown."""
        # Write to temporary file
        with tempfile.NamedTemporaryFile(suffix=f'.{file_ext}', delete=False) as tmp:
            for chunk in uploaded_file.chunks():
                tmp.write(chunk)
            tmp_path = tmp.name

        try:
            markdown = to_markdown(tmp_path, attachment_mode='skip')
            return markdown
        finally:
            # Clean up temp file
            Path(tmp_path).unlink(missing_ok=True)

Django Admin Integration

Add document conversion to Django admin:

# models.py
from django.db import models

class Document(models.Model):
    """Document model with conversion support."""
    title = models.CharField(max_length=200)
    file = models.FileField(upload_to='documents/')
    markdown_content = models.TextField(blank=True)
    converted_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def convert_to_markdown(self):
        """Convert document file to markdown."""
        from all2md import to_markdown
        from django.utils import timezone

        self.markdown_content = to_markdown(
            self.file.path,
            attachment_mode='skip'
        )
        self.converted_at = timezone.now()
        self.save()

# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Document

@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
    list_display = ['title', 'created_at', 'converted_at', 'convert_action']
    readonly_fields = ['markdown_content', 'converted_at']

    def convert_action(self, obj):
        """Display convert button."""
        if obj.pk:
            return format_html(
                '<a class="button" href="/admin/convert-doc/{}/"> Convert</a>',
                obj.pk
            )
        return '-'

    convert_action.short_description = 'Convert'

    def get_urls(self):
        from django.urls import path
        urls = super().get_urls()
        custom_urls = [
            path('convert-doc/<int:doc_id>/',
                 self.admin_site.admin_view(self.convert_document),
                 name='convert-document'),
        ]
        return custom_urls + urls

    def convert_document(self, request, doc_id):
        from django.shortcuts import redirect
        from django.contrib import messages

        doc = Document.objects.get(pk=doc_id)
        try:
            doc.convert_to_markdown()
            messages.success(request, f'Document "{doc.title}" converted successfully')
        except Exception as e:
            messages.error(request, f'Conversion failed: {e}')

        return redirect('admin:myapp_document_change', doc_id)

Background Task with Celery

Process documents asynchronously:

# tasks.py
from celery import shared_task
from .models import Document
from all2md import to_markdown
from all2md.exceptions import All2MdError
import logging

logger = logging.getLogger(__name__)

@shared_task(bind=True, max_retries=3)
def convert_document_task(self, document_id: int):
    """Convert document to markdown asynchronously."""
    try:
        document = Document.objects.get(pk=document_id)

        # Convert document
        markdown = to_markdown(
            document.file.path,
            attachment_mode='skip'
        )

        # Save result
        document.markdown_content = markdown
        from django.utils import timezone
        document.converted_at = timezone.now()
        document.save()

        logger.info(f"Converted document {document_id}")
        return f"Success: {document.title}"

    except Document.DoesNotExist:
        logger.error(f"Document {document_id} not found")
        return f"Error: Document not found"

    except All2MdError as e:
        logger.error(f"Conversion failed for document {document_id}: {e}")
        # Retry with exponential backoff
        raise self.retry(exc=e, countdown=60 * (2 ** self.request.retries))

# views.py - Trigger background task
from .tasks import convert_document_task

def upload_document(request):
    """Upload document and trigger conversion."""
    if request.method == 'POST':
        document = Document.objects.create(
            title=request.POST['title'],
            file=request.FILES['file']
        )
        # Trigger async conversion
        convert_document_task.delay(document.id)

        return JsonResponse({
            'message': 'Document uploaded, conversion started',
            'document_id': document.id
        })

FastAPI Integration

Basic File Upload

Simple FastAPI endpoint for document conversion:

# main.py
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import PlainTextResponse
from all2md import to_markdown
from all2md.exceptions import All2MdError
import tempfile
from pathlib import Path

app = FastAPI(title="Document Converter API")

@app.post("/convert", response_class=PlainTextResponse)
async def convert_document(
    file: UploadFile = File(...),
    max_pages: int = 10
):
    """Convert uploaded document to Markdown.

    Args:
        file: Document file to convert
        max_pages: Maximum pages to process (PDF only)

    Returns:
        Markdown content as plain text
    """
    # Validate file type
    allowed_types = {
        'application/pdf': 'pdf',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
        'text/html': 'html'
    }

    if file.content_type not in allowed_types:
        raise HTTPException(
            status_code=400,
            detail=f"Unsupported file type: {file.content_type}"
        )

    # Save to temporary file
    suffix = f".{allowed_types[file.content_type]}"
    with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
        content = await file.read()
        tmp.write(content)
        tmp_path = tmp.name

    try:
        # Convert document
        markdown = to_markdown(
            tmp_path,
            pages=list(range(1, max_pages + 1)) if suffix == '.pdf' else None,
            attachment_mode='skip'
        )
        return markdown

    except All2MdError as e:
        raise HTTPException(status_code=500, detail=str(e))

    finally:
        # Cleanup
        Path(tmp_path).unlink(missing_ok=True)

Async with Background Tasks

Use FastAPI’s background tasks for processing:

from fastapi import FastAPI, UploadFile, BackgroundTasks
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from all2md import to_markdown
import uuid
from pathlib import Path
from typing import Dict

app = FastAPI()

# In-memory storage (use Redis/database in production)
conversion_status: Dict[str, dict] = {}

class ConversionResponse(BaseModel):
    job_id: str
    status: str
    message: str

class ConversionResult(BaseModel):
    job_id: str
    status: str
    markdown: str | None = None
    error: str | None = None

def process_document(job_id: str, file_path: str):
    """Background task to process document."""
    try:
        markdown = to_markdown(file_path, attachment_mode='skip')
        conversion_status[job_id] = {
            'status': 'completed',
            'markdown': markdown
        }
    except Exception as e:
        conversion_status[job_id] = {
            'status': 'failed',
            'error': str(e)
        }
    finally:
        # Cleanup temp file
        Path(file_path).unlink(missing_ok=True)

@app.post("/convert/async", response_model=ConversionResponse)
async def convert_async(
    file: UploadFile,
    background_tasks: BackgroundTasks
):
    """Start async document conversion."""
    # Generate job ID
    job_id = str(uuid.uuid4())

    # Save uploaded file
    upload_dir = Path("/tmp/uploads")
    upload_dir.mkdir(exist_ok=True)
    file_path = upload_dir / f"{job_id}_{file.filename}"

    with open(file_path, 'wb') as f:
        content = await file.read()
        f.write(content)

    # Mark as processing
    conversion_status[job_id] = {'status': 'processing'}

    # Add background task
    background_tasks.add_task(process_document, job_id, str(file_path))

    return ConversionResponse(
        job_id=job_id,
        status='processing',
        message='Conversion started'
    )

@app.get("/convert/status/{job_id}", response_model=ConversionResult)
async def get_conversion_status(job_id: str):
    """Get conversion status and result."""
    if job_id not in conversion_status:
        raise HTTPException(status_code=404, detail="Job not found")

    status_data = conversion_status[job_id]
    return ConversionResult(
        job_id=job_id,
        status=status_data['status'],
        markdown=status_data.get('markdown'),
        error=status_data.get('error')
    )

Streaming Response

Stream large document conversions:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from all2md import to_markdown
from all2md.options import PdfOptions
import io

app = FastAPI()

@app.get("/convert/stream/{filename}")
async def stream_conversion(filename: str):
    """Stream document conversion in chunks."""

    def generate_chunks():
        """Generate markdown in chunks."""
        # Process document in page chunks
        total_pages = 100  # Get from metadata
        chunk_size = 10

        for start in range(1, total_pages + 1, chunk_size):
            end = min(start + chunk_size - 1, total_pages)
            pages = list(range(start, end + 1))

            options = PdfOptions(
                pages=pages,
                attachment_mode='skip'
            )

            chunk_markdown = to_markdown(filename, parser_options=options)
            yield chunk_markdown.encode('utf-8')

    return StreamingResponse(
        generate_chunks(),
        media_type='text/markdown',
        headers={'Content-Disposition': f'attachment; filename="{filename}.md"'}
    )

Flask Integration

Basic Flask App

Simple Flask application with document conversion:

# app.py
from flask import Flask, request, Response, jsonify
from werkzeug.utils import secure_filename
from all2md import to_markdown
from all2md.exceptions import All2MdError
import tempfile
from pathlib import Path

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024  # 10MB limit

ALLOWED_EXTENSIONS = {'pdf', 'docx', 'pptx', 'html', 'txt'}

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/convert', methods=['POST'])
def convert():
    """Convert uploaded document to Markdown."""
    if 'file' not in request.files:
        return jsonify({'error': 'No file provided'}), 400

    file = request.files['file']

    if file.filename == '':
        return jsonify({'error': 'No file selected'}), 400

    if not allowed_file(file.filename):
        return jsonify({'error': 'File type not allowed'}), 400

    tmp_path = None
    try:
        # Save the upload to a temp file so the format is detected by extension
        suffix = Path(secure_filename(file.filename)).suffix
        with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
            file.save(tmp.name)
            tmp_path = tmp.name

        markdown = to_markdown(tmp_path, attachment_mode='skip')
        download_name = Path(secure_filename(file.filename)).stem

        return Response(
            markdown,
            mimetype='text/markdown',
            headers={'Content-Disposition': f'attachment; filename="{download_name}.md"'}
        )

    except All2MdError as e:
        return jsonify({'error': str(e)}), 500

    finally:
        if tmp_path:
            Path(tmp_path).unlink(missing_ok=True)

if __name__ == '__main__':
    app.run(debug=True)

Flask Blueprint

Organize as a Flask blueprint:

# converter/blueprint.py
from flask import Blueprint, request, jsonify, send_file
from all2md import to_markdown
from pathlib import Path
import tempfile

converter_bp = Blueprint('converter', __name__, url_prefix='/api/converter')

@converter_bp.route('/convert', methods=['POST'])
def convert_document():
    """Convert document endpoint."""
    file = request.files.get('file')
    if not file:
        return jsonify({'error': 'No file provided'}), 400

    # Process file
    with tempfile.NamedTemporaryFile(suffix=Path(file.filename).suffix) as tmp:
        file.save(tmp.name)
        markdown = to_markdown(tmp.name, attachment_mode='skip')

    return jsonify({'markdown': markdown})

@converter_bp.route('/formats', methods=['GET'])
def supported_formats():
    """List supported formats."""
    from all2md.dependencies import get_all_dependencies
    return jsonify({'formats': sorted(get_all_dependencies().keys())})

# app.py
from flask import Flask
from converter.blueprint import converter_bp

app = Flask(__name__)
app.register_blueprint(converter_bp)

Error Handling

Comprehensive error handling:

from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException, RequestEntityTooLarge
from all2md.exceptions import All2MdError
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@app.errorhandler(RequestEntityTooLarge)
def handle_file_too_large(e):
    """Handle file size limit exceeded."""
    logger.warning(f"File too large: {e}")
    return jsonify({
        'error': 'File too large',
        'max_size': app.config['MAX_CONTENT_LENGTH']
    }), 413

@app.errorhandler(All2MdError)
def handle_conversion_error(e):
    """Handle all2md conversion errors."""
    logger.error(f"Conversion error: {e}")
    return jsonify({'error': f'Conversion failed: {str(e)}'}), 500

@app.errorhandler(Exception)
def handle_unexpected_error(e):
    """Handle unexpected errors."""
    logger.error(f"Unexpected error: {e}", exc_info=True)
    return jsonify({'error': 'Internal server error'}), 500

AWS Lambda

Basic Lambda Function

Deploy all2md as AWS Lambda function:

# lambda_function.py
import json
import base64
import tempfile
from pathlib import Path
from all2md import to_markdown
from all2md.options import PdfOptions

def lambda_handler(event, context):
    """AWS Lambda handler for document conversion.

    Event format:
    {
        "file_content": "base64-encoded file content",
        "filename": "document.pdf",
        "options": {
            "max_pages": 10,
            "attachment_mode": "skip"
        }
    }
    """
    try:
        # Extract parameters
        file_content_b64 = event.get('file_content')
        filename = event.get('filename', 'document.pdf')
        options_dict = event.get('options', {})

        if not file_content_b64:
            return {
                'statusCode': 400,
                'body': json.dumps({'error': 'No file_content provided'})
            }

        # Decode file content
        file_content = base64.b64decode(file_content_b64)

        # Write to /tmp (Lambda's writable directory)
        tmp_path = Path('/tmp') / filename
        tmp_path.write_bytes(file_content)

        # Prepare conversion options
        max_pages = options_dict.get('max_pages', 50)
        options = PdfOptions(
            pages=list(range(1, max_pages + 1)),
            attachment_mode=options_dict.get('attachment_mode', 'skip')
        )

        # Convert document
        markdown = to_markdown(str(tmp_path), parser_options=options)

        # Cleanup
        tmp_path.unlink()

        return {
            'statusCode': 200,
            'headers': {'Content-Type': 'text/markdown'},
            'body': markdown
        }

    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

Lambda Deployment Package

Create deployment package with dependencies:

# Create deployment package
mkdir lambda-package
cd lambda-package

# Install all2md and dependencies
pip install 'all2md[pdf,docx]' -t .

# Add your lambda function
cp ../lambda_function.py .

# Create zip
zip -r lambda-deployment.zip .

Lambda with S3 Integration

Process documents from S3:

import boto3
import json
from all2md import to_markdown
from pathlib import Path

s3_client = boto3.client('s3')

def lambda_handler(event, context):
    """Process S3 upload and convert to markdown."""
    # Get S3 object info from event
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = record['s3']['object']['key']

        # Download file from S3
        download_path = f'/tmp/{Path(key).name}'
        s3_client.download_file(bucket, key, download_path)

        try:
            # Convert to markdown
            markdown = to_markdown(download_path, attachment_mode='skip')

            # Upload markdown back to S3
            output_key = f"{Path(key).stem}.md"
            s3_client.put_object(
                Bucket=bucket,
                Key=f"converted/{output_key}",
                Body=markdown.encode('utf-8'),
                ContentType='text/markdown'
            )

            return {
                'statusCode': 200,
                'body': json.dumps({
                    'input': key,
                    'output': f"converted/{output_key}"
                })
            }

        finally:
            Path(download_path).unlink(missing_ok=True)

Docker Deployment

Basic Dockerfile

Containerize all2md application:

# Dockerfile
FROM python:3.12-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create upload directory
RUN mkdir -p /app/uploads /app/output

# Expose port
EXPOSE 8000

# Run application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# requirements.txt
all2md[all]
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6

Docker Compose

Multi-container setup with Redis queue:

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - ALL2MD_DISABLE_NETWORK=true
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./uploads:/app/uploads
      - ./output:/app/output
    depends_on:
      - redis
      - worker

  worker:
    build: .
    command: celery -A tasks worker --loglevel=info
    environment:
      - ALL2MD_DISABLE_NETWORK=true
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./uploads:/app/uploads
      - ./output:/app/output
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Production Best Practices

Security Configuration

Production-ready security settings:

# config.py
import os
from all2md.options import HtmlOptions, PdfOptions
from all2md.options.common import NetworkFetchOptions, LocalFileAccessOptions

class ProductionConfig:
    """Production configuration for all2md."""

    # File limits
    MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB
    MAX_PAGES = 50

    # Security settings
    HTML_OPTIONS = HtmlOptions(
        strip_dangerous_elements=True,
        attachment_mode='skip',
        network=NetworkFetchOptions(
            allow_remote_fetch=False  # Block SSRF
        ),
        local_files=LocalFileAccessOptions(
            allow_local_files=False  # Block local file access
        )
    )

    PDF_OPTIONS = PdfOptions(
        attachment_mode='skip',
        pages=list(range(1, MAX_PAGES + 1))
    )

    # Environment variables
    @staticmethod
    def configure_environment():
        """Set security environment variables."""
        os.environ['ALL2MD_DISABLE_NETWORK'] = '1'
        os.environ['ALL2MD_ATTACHMENT_MODE'] = 'skip'

Rate Limiting

Implement rate limiting (Flask example):

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    app=app,
    key_func=get_remote_address,
    default_limits=["100 per hour"]
)

@app.route('/convert', methods=['POST'])
@limiter.limit("10 per minute")  # Stricter limit for conversion
def convert():
    # ... conversion logic ...
    pass

Health Checks

Implement health check endpoints:

from fastapi import FastAPI
from all2md import to_markdown
import tempfile

app = FastAPI()

@app.get("/health")
async def health_check():
    """Basic health check."""
    return {"status": "ok"}

@app.get("/health/ready")
async def readiness_check():
    """Readiness check with conversion test."""
    try:
        # Test conversion
        with tempfile.NamedTemporaryFile(suffix='.txt', mode='w') as tmp:
            tmp.write("# Test")
            tmp.flush()
            markdown = to_markdown(tmp.name)

        return {"status": "ready"}
    except Exception as e:
        return {"status": "not_ready", "error": str(e)}, 503

See Also