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.
In this repository58 other files it gives its agents.
- ad-hoc-script-verificationSkill
- architecture-diagramSkill
- blocked-page-recoverySkill
- cadquery-enclosure-designSkill
- canonical-thumbnailSkill
- circuit-designSkill
- competitor-news-monitorSkill
- component-alternativesSkill
- debugging-hermes-tui-commandsSkill
- docker-container-update-checkSkill
- document-to-action-itemsSkill
- docxSkill
- email-inbox-triageSkill
- embedded-porting-patternsSkill
- ephemeral-file-sharingSkill
- excalidrawSkill
- flash-game-researchSkill
- flutter-android-buildSkill
- flutter-content-renderingSkill
- github-code-reviewSkill
- githubSkill
- goatcounter-analyticsSkill
- google-oauth-file-pollSkill
- grounded-citationsSkill
- hermes-agent-skill-authoringSkill
- hermes-gateway-slash-commandSkill
- hermes-gateway-supervisionSkill
- jellyseerr-watch-suggestionsSkill
- maintained-fork-publishingSkill
- meeting-action-itemsSkill
- native-mcpSkill
- obsidian-mapsSkill
- pdf-flattenSkill
- pdfSkill
- pdf-surgical-editsSkill
- planSkill
- product-price-monitorSkill
- psp-developmentSkill
- psp-homebrew-testingSkill
- psp-prx-plugin-maintenanceSkill
- python-debugpySkill
- reddit-readingSkill
- retro-fansiteSkill
- reverse-proxy-websocket-debuggingSkill
- rss-feedsSkill
- simplify-codeSkill
- sketchSkill
- skit-thumbnailSkill
- spikeSkill
- subagent-driven-developmentSkill
- test-driven-developmentSkill
- usb-device-enumerationSkill
- weekly-review-planningSkill
- wiki-media-asset-pipelineSkill
- windows-c-build-toolchainSkill
- windows-crash-forensicsSkill
- writing-plansSkill
- yt-dlp-anti-botSkill
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
- Very specific: line 30
Fix: Read the file bytes manually and return a raw Response:
- Very specific: line 49
Use Content-Disposition: inline (not attachment) for iframe embedding.
- Very specific: line 56
When building a search UI with Fuse.js over a local index that starts empty (e.g.
- Very specific: line 62
If Fuse.js returns 0 results AND query.length >= 2:
- Very specific: line 70
Use AbortController to cancel stale backend requests when user keeps typing
- Very specific: line 72
After backend finds a result, the index has grown — re-init Fuse.js
- Very specific: line 73
Don't re-fetch the index on every input focus.
- Very specific: line 151
Read sidecar .json metadata files (same name, next to the cached file)
- Distinctive: line 152
If no sidecar, extract metadata from filename/path
- Very specific: line 154
Write data/index.json — flat array of entries
- Very specific: line 157
Sidecar JSON pattern: Every cached file gets a .json sibling with {source_url, downloaded_at, file_size_kb, ...}.
≈ “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.
Files
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:
- Load index from
GET /api/indexon page init → build Fuse.js instance - On input (debounced 150ms): search Fuse.js locally
- 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
- Fire
- Cancel pending backend search on each new keystroke
Key implementation details:
- Use
AbortControllerto cancel stale backend requests when user keeps typing - Track a
pendingBackendSearchreference; 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
_indexLoadedflag - 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 pointRoute patterns:
GET /→FileResponseforindex.htmlGET /static/{path}→StaticFilesmount for CSS/JSGET /api/*→ JSON API endpointsGET /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 hostThis 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:
- Walk
data/cache/**/*.extrecursively - Read sidecar
.jsonmetadata files (same name, next to the cached file) - If no sidecar, extract metadata from filename/path
- Auto-generate tags from vendor/category prefixes
- Write
data/index.json— flat array of entries - 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.
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
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.

