Last active
July 9, 2025 16:43
-
-
Save rajaravivarma-r/afc81344873791cb52f3037e43e8d23a to your computer and use it in GitHub Desktop.
Guestbook FAST API equivalent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| # Scroll to the bottom for benchmarking results. | |
| import html | |
| import os | |
| import aiosqlite | |
| from datetime import datetime | |
| from typing import List, Optional | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, Form, Request, HTTPException, Depends | |
| from fastapi.responses import HTMLResponse, RedirectResponse | |
| from pydantic import BaseModel, Field | |
| # Database configuration | |
| DB_PATH = "/tmp/guestbook-py.db" | |
| # HTML templates | |
| TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Guestbook</title> | |
| <style> | |
| body { font-family: sans-serif } | |
| form { margin-bottom: 2em } | |
| textarea { width: 100%; height: 6em } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Guestbook</h2> | |
| <form method="post" action="/"> | |
| <label>Name:<br><input name="name" required></label><br> | |
| <label>Message:<br><textarea name="message" required></textarea></label><br> | |
| <button type="submit">Sign</button> | |
| </form> | |
| {{ entries|safe }} | |
| </body> | |
| </html> | |
| """ | |
| ERROR_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Error</title> | |
| <style>body { font-family: sans-serif }</style> | |
| </head> | |
| <body> | |
| <h2>Error</h2> | |
| <p>An error occurred while processing your request.</p> | |
| <p><a href="/">Back to Guestbook</a></p> | |
| </body> | |
| </html> | |
| """ | |
| # Database Models | |
| class GuestbookEntry(BaseModel): | |
| id: Optional[int] = None | |
| name: str = Field(..., max_length=100) | |
| message: str = Field(..., max_length=1000) | |
| created: Optional[datetime] = None | |
| # Lifespan context manager for database setup and teardown | |
| @asynccontextmanager | |
| async def lifespan(app: FastAPI): | |
| # Database initialization | |
| async with aiosqlite.connect(DB_PATH) as db: | |
| # Create table if it doesn't exist | |
| await db.execute( | |
| """CREATE TABLE IF NOT EXISTS guestbook( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name TEXT NOT NULL, | |
| message TEXT NOT NULL, | |
| created DATETIME DEFAULT CURRENT_TIMESTAMP | |
| );""" | |
| ) | |
| # Create indexes | |
| await db.executescript( | |
| """CREATE INDEX IF NOT EXISTS idx_guestbook_created ON guestbook(created); | |
| CREATE INDEX IF NOT EXISTS idx_guestbook_name ON guestbook(name); | |
| CREATE INDEX IF NOT EXISTS idx_guestbook_message ON guestbook(message);""" | |
| ) | |
| await db.commit() | |
| yield # Application runs here | |
| # Cleanup (if needed) | |
| # Nothing specific to clean up for this app | |
| # Initialize FastAPI app with lifespan | |
| app = FastAPI(title="Guestbook App", lifespan=lifespan) | |
| # Database connection dependency | |
| async def get_db(): | |
| """Create and return an async database connection""" | |
| db = await aiosqlite.connect(DB_PATH) | |
| db.row_factory = aiosqlite.Row | |
| try: | |
| yield db | |
| finally: | |
| await db.close() | |
| def format_entries(rows): | |
| """Format the database rows into HTML entries""" | |
| parts = [] | |
| for row in rows: | |
| entry_html = f"""<div> | |
| <strong>{html.escape(row['name'])}</strong> | |
| <em>{html.escape(row['created'])}</em> | |
| <p>{html.escape(row['message']).replace('\n', '<br>')}</p> | |
| </div><hr>""" | |
| parts.append(entry_html) | |
| return "".join(parts) | |
| @app.get("/", response_class=HTMLResponse) | |
| async def get_guestbook(db: aiosqlite.Connection = Depends(get_db)): | |
| """Display the guestbook entries""" | |
| try: | |
| async with db.execute( | |
| "SELECT name, message, created FROM guestbook ORDER BY created DESC LIMIT 100" | |
| ) as cursor: | |
| rows = await cursor.fetchall() | |
| entries = format_entries(rows) | |
| return TEMPLATE.replace("{{ entries|safe }}", entries) | |
| except Exception as e: | |
| return HTMLResponse(content=ERROR_TEMPLATE, status_code=500) | |
| @app.post("/") | |
| async def post_guestbook_entry( | |
| name: str = Form(...), | |
| message: str = Form(...), | |
| db: aiosqlite.Connection = Depends(get_db), | |
| ): | |
| """Add a new entry to the guestbook""" | |
| try: | |
| # Validate and limit input lengths | |
| name = name.strip()[:100] | |
| message = message.strip()[:1000] | |
| if not name or not message: | |
| raise HTTPException(status_code=400, detail="Name and message are required") | |
| await db.execute( | |
| "INSERT INTO guestbook(name, message) VALUES(?, ?)", (name, message) | |
| ) | |
| await db.commit() | |
| # Redirect back to the main page | |
| return RedirectResponse(url="/", status_code=303) | |
| except Exception as e: | |
| await db.rollback() | |
| return HTMLResponse(content=ERROR_TEMPLATE, status_code=500) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| "guestbook:app", host="0.0.0.0", port=8000, workers=2, log_level="critical" | |
| ) | |
| # Read | |
| # ``` | |
| # Statistics Avg Stdev Max | |
| # Reqs/sec 2019.54 1021.75 10578.27 | |
| # Latency 123.45ms 173.88ms 1.95s | |
| # HTTP codes: | |
| # 1xx - 0, 2xx - 30488, 3xx - 0, 4xx - 0, 5xx - 0 | |
| # others - 0 | |
| # Throughput: 30.29MB/s | |
| # ``` | |
| # Write (shown in the graph of the OP) | |
| # ``` | |
| # Statistics Avg Stdev Max | |
| # Reqs/sec 931.72 340.79 3654.80 | |
| # Latency 267.53ms 443.02ms 2.02s | |
| # HTTP codes: | |
| # 1xx - 0, 2xx - 0, 3xx - 13441, 4xx - 0, 5xx - 215 | |
| # others - 572 | |
| # Errors: | |
| # timeout - 572 | |
| # Throughput: 270.54KB/s | |
| # ``` | |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With postgres backend (asyncpg)
Write (workers=2, client_connections=100)
Read (workers=8, client_connections=250)