# app.py from fastapi import FastAPI, HTTPException, Request from fastapi.responses import StreamingResponse import qrcode import io from functools import lru_cache app = FastAPI() # --------------------------------------------------------------------------- # QR‑code generation helper (cached) # --------------------------------------------------------------------------- @lru_cache(maxsize=512) def _qr_png(text: str) -> bytes: """Return PNG bytes for *text*. * Uses `qrcode.make`, which is faster than constructing a `QRCode` object manually. * Memoised with `lru_cache` so repeated requests for the same text are served from memory (~10× faster, avoids QR code re‑creation and pillow image encoding). """ img = qrcode.make(text) # Fast 1‑liner QR generation buf = io.BytesIO() img.save(buf, format="PNG") return buf.getvalue() # --------------------------------------------------------------------------- # API endpoint # --------------------------------------------------------------------------- @app.post("/echo") async def echo(request: Request): """Generate a QR‑code for the given JSON body. Accepted payloads: • Plain JSON string → "Hello" • Object with `text` → {"text": "Hello"} Any other JSON types are stringified via `str(payload)`. """ # --------------------------------------------------------------------- # 1. Extract JSON body # --------------------------------------------------------------------- try: payload = await request.json() except Exception: raise HTTPException(status_code=400, detail="Expected a JSON body") if payload in (None, "", []): raise HTTPException(status_code=400, detail="Payload cannot be empty") # Normalise to string text = payload["text"] if isinstance(payload, dict) and "text" in payload else str(payload) # --------------------------------------------------------------------- # 2. Build / fetch QR code PNG (cached) # --------------------------------------------------------------------- png_bytes = _qr_png(text) # --------------------------------------------------------------------- # 3. Return as stream (new BytesIO per request so multiple clients can read) # --------------------------------------------------------------------- return StreamingResponse(io.BytesIO(png_bytes), media_type="image/png")