agentleFS
abduznikabduznik/abduznik-skills/skills/python-webapp-patterns/SKILL.md

python-webapp-patterns

Skill2 starsChanged 2 months agoMITView on GitHub

Build Python web apps with FastAPI or Flask — static file serving, search-index frontends, PDF/binary serving, and Windows-specific pitfalls. Covers the local-index-first search pattern, binary response serving, and asyncio quirks.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 1 is distinctive, and 10 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Fix: Read the file bytes manually and return a raw Response:

    line 30
  2. Very specific:

    Use Content-Disposition: inline (not attachment) for iframe embedding.

    line 49
  3. Very specific:

    When building a search UI with Fuse.js over a local index that starts empty (e.g.

    line 56
  4. Very specific:

    If Fuse.js returns 0 results AND query.length >= 2:

    line 62
  5. Very specific:

    Use AbortController to cancel stale backend requests when user keeps typing

    line 70
  6. Very specific:

    After backend finds a result, the index has grown — re-init Fuse.js

    line 72
  7. Very specific:

    Don't re-fetch the index on every input focus.

    line 73
  8. Very specific:

    Read sidecar .json metadata files (same name, next to the cached file)

    line 151
  9. Distinctive:

    If no sidecar, extract metadata from filename/path

    line 152
  10. Very specific:

    Write data/index.json — flat array of entries

    line 154
  11. Very specific:

    Sidecar JSON pattern: Every cached file gets a .json sibling with {source_url, downloaded_at, file_size_kb, ...}.

    line 157

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Python Web App Patterns

Reusable patterns for building self-hosted Python web apps with FastAPI (or Flask) that serve a vanilla JS frontend and handle binary content like PDFs.

1. Serving binary files (PDFs) on Windows

Pitfall: FileResponse(path=str(path_obj)) can silently fail on Windows when serving PDFs for iframe embedding. The browser receives the response but the iframe shows blank or "Failed to load PDF". Root cause: Windows path encoding quirks in Starlette's FileResponse implementation.

Fix: Read the file bytes manually and return a raw Response:

from pathlib import Path
from fastapi.responses import Response

def serve_pdf(pdf_path: Path, filename: str) -> Response:
    content = pdf_path.read_bytes()
    return Response(
        content=content,
        media_type="application/pdf",
        headers={
            "Content-Disposition": f'inline; filename="{filename}.pdf"',
            "Content-Length": str(len(content)),
            "Cache-Control": "public, max-age=3600",
        },
    )

Use Content-Disposition: inline (not attachment) for iframe embedding. Set Content-Length explicitly — some browsers need it for PDF rendering.

FileResponse is still fine for HTML/text (e.g. serving index.html).

2. Local-index-first search with backend fallback

When building a search UI with Fuse.js over a local index that starts empty (e.g. a datasheet library that grows over time), always wire a backend fallback:

Pattern:

  1. Load index from GET /api/index on page init → build Fuse.js instance
  2. On input (debounced 150ms): search Fuse.js locally
  3. If Fuse.js returns 0 results AND query.length >= 2:
    • Fire GET /search?q={query} to the backend (with AbortController)
    • Backend searches remote sources, caches result, returns JSON
    • On success: re-fetch /api/index, rebuild Fuse.js, re-run local search
    • Show toast with result metadata
  4. Cancel pending backend search on each new keystroke

Key implementation details:

  • Use AbortController to cancel stale backend requests when user keeps typing
  • Track a pendingBackendSearch reference; abort it before starting a new one
  • After backend finds a result, the index has grown — re-init Fuse.js
  • Don't re-fetch the index on every input focus; track _indexLoaded flag
  • Suppress "Loaded 0 datasheets" toast on cold start (show only when count > 0)
let pendingBackendSearch = null;

async function backendSearch(query) {
    if (pendingBackendSearch) pendingBackendSearch.abort();
    const controller = new AbortController();
    pendingBackendSearch = controller;

    try {
        const resp = await fetch(`/search?q=${encodeURIComponent(query)}`,
            { signal: controller.signal });
        if (!resp.ok) return;
        const result = await resp.json();
        if (result.status === 'found' || result.status === 'cached') {
            state.index = await apiGet('/api/index');
            rebuildFuse();  // re-init with new data
            performSearch(query);  // re-run local search
            showToast(`Found: ${result.part}`, 'success');
        }
    } catch (err) {
        if (err.name !== 'AbortError') console.warn('Backend search failed:', err);
    } finally {
        if (pendingBackendSearch === controller) pendingBackendSearch = null;
    }
}

3. FastAPI app structure for self-hosted tools

Recommended layout for a single-page app with API backend:

project/
├── backend/
│   ├── proxy.py          # FastAPI app + routes
│   ├── finder.py         # Business logic
│   ├── models.py         # Pydantic models
│   └── sources/          # Plugin architecture
├── frontend/
│   ├── index.html
│   ├── style.css
│   └── app.js
├── data/
│   └── (runtime data, gitignored)
├── requirements.txt
└── run.py                # Entry point

Route patterns:

  • GET / → FileResponse for index.html
  • GET /static/{path} → StaticFiles mount for CSS/JS
  • GET /api/* → JSON API endpoints
  • GET /pdf/{id} → Binary content serving (use the Response pattern above)

CORS: For local dev, allow_origins=["*"] is fine. For production behind a reverse proxy on the same origin, CORS isn't needed.

4. Asyncio on Windows — ConnectionResetError

ConnectionResetError: [WinError 10054] An existing connection was forcibly
closed by the remote host

This appears in asyncio callback error handlers on Windows when a client (browser) closes a connection before the response is fully flushed. It is harmless — the response was already sent. The error is a known Windows ProactorEventLoop quirk. Ignore it; do not add error handling for it.

5. Index builder pattern

For tools that cache files locally (PDFs, images, documents), build a search index by scanning the cache directory:

  1. Walk data/cache/**/*.ext recursively
  2. Read sidecar .json metadata files (same name, next to the cached file)
  3. If no sidecar, extract metadata from filename/path
  4. Auto-generate tags from vendor/category prefixes
  5. Write data/index.json — flat array of entries
  6. Rebuild on startup and after every batch operation

Sidecar JSON pattern: Every cached file gets a .json sibling with {source_url, downloaded_at, file_size_kb, ...}. This survives file moves and provides provenance without parsing filenames.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.