Files: - STACK.md - FEATURES.md - ARCHITECTURE.md - PITFALLS.md - SUMMARY.md Key findings: - Stack: FastAPI 0.135.1 + React 19 + Vite 7 + Tailwind v4, single-container deploy - Architecture: FastAPI serves React SPA via catch-all, file-based storage (Docker volume), LLMService with retry/backoff - Critical pitfall: All 9 pitfalls map to Phase 1 — Italian prompts, Canva field constants, UTF-8 BOM, root_path config, per-item bulk isolation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
416 lines
17 KiB
Markdown
416 lines
17 KiB
Markdown
# Stack Research
|
|
|
|
**Domain:** Python content automation backend + React SPA frontend (bulk social media content generation)
|
|
**Researched:** 2026-03-07
|
|
**Confidence:** HIGH (all versions verified against PyPI and npm registries as of March 2026)
|
|
|
|
---
|
|
|
|
## Recommended Stack
|
|
|
|
### Core Technologies
|
|
|
|
| Technology | Version | Purpose | Why Recommended |
|
|
|------------|---------|---------|-----------------|
|
|
| Python | 3.12+ | Runtime | LTS, full async support, matches Docker slim images |
|
|
| FastAPI | 0.135.1 | API framework + static file serving | Native async, Pydantic v2 integration, StaticFiles for SPA, OpenAPI auto-docs. Current standard for Python REST APIs. |
|
|
| Uvicorn | 0.41.0 | ASGI server | Production-grade async server for FastAPI. "Standard" install includes uvicorn + extras. |
|
|
| Pydantic | 2.12.5 | Data validation + settings | FastAPI depends on it. v2 is 5-50x faster than v1. Required for request/response schemas. |
|
|
| anthropic (SDK) | 0.84.0 | Claude API client | Official Anthropic Python SDK. Supports sync/async, streaming via `client.messages.stream()`. |
|
|
| React | 19.2.4 | Frontend SPA | Latest stable. New concurrent features, improved hooks. Ecosystem standard. |
|
|
| Vite | 7.x (latest stable) | Frontend build tool | Faster than Webpack/CRA. Native ES modules in dev. First-party React plugin. Single-command build for production. |
|
|
| Tailwind CSS | 4.2.1 | Utility CSS | v4 is major rewrite: 5x faster builds, no tailwind.config.js required, native @tailwindcss/vite plugin. |
|
|
|
|
### Supporting Libraries
|
|
|
|
#### Python Backend
|
|
|
|
| Library | Version | Purpose | When to Use |
|
|
|---------|---------|---------|-------------|
|
|
| python-dotenv | 1.2.2 | Environment variable loading | Load `.env` in local dev. In Docker, use compose `environment:` directly. |
|
|
| python-multipart | 0.0.22 | Form/file upload parsing | Required by FastAPI for `UploadFile` and `Form` parameters. |
|
|
| httpx | 0.28.1 | Async HTTP client | Calling Unsplash API. Native async, HTTP/2 support, replaces requests for async code. |
|
|
| aiofiles | 24.x | Async file I/O | Reading/writing prompt files and generated CSVs without blocking the event loop. |
|
|
|
|
**CSV generation:** Use Python's built-in `csv` module (stdlib). No external dependency needed. This is the correct choice for structured CSV output to Canva. Pandas/Polars are overkill — the project generates structured rows, not analyzes datasets.
|
|
|
|
**Prompt file management:** Use Python's built-in `pathlib.Path` + `json`/`yaml` stdlib. No external library needed for file-based prompt storage.
|
|
|
|
#### React Frontend
|
|
|
|
| Library | Version | Purpose | When to Use |
|
|
|---------|---------|---------|-------------|
|
|
| @tailwindcss/vite | 4.x | Tailwind v4 Vite plugin | Required for Tailwind v4 integration. Replaces PostCSS config. |
|
|
| shadcn/ui | latest CLI | UI component collection | Copy-owned components built on Radix UI + Tailwind. Use for forms, tables, modals, progress indicators. Not an npm dep — CLI copies source. |
|
|
| @radix-ui/react-* | latest | Accessible primitives | Pulled in automatically by shadcn/ui. Headless, accessible. |
|
|
| react-router-dom | 7.x | Client-side routing | SPA navigation between generation wizard, history, settings pages. |
|
|
| @tanstack/react-query | 5.x | Server state + async data | Handles API call lifecycle (loading, error, cache). Essential for polling long-running generation jobs. |
|
|
| lucide-react | latest | Icons | shadcn/ui default icon set. Consistent with component library. |
|
|
|
|
### Development Tools
|
|
|
|
| Tool | Purpose | Notes |
|
|
|------|---------|-------|
|
|
| Docker | Container runtime | Multi-stage build: Node stage builds React, Python stage serves everything |
|
|
| docker compose | Local dev + VPS deploy | Single `docker-compose.yml` for both environments with env overrides |
|
|
| Vite dev proxy | Dev CORS bypass | `vite.config.ts` proxy `/api` to `localhost:8000` during development |
|
|
| TypeScript | Frontend type safety | Enabled by default in Vite React template. Catches API contract errors at compile time. |
|
|
| ESLint + prettier | Code quality | Vite template includes eslint. Add prettier for formatting. |
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
### Backend
|
|
|
|
```bash
|
|
# Create and activate venv
|
|
python -m venv .venv
|
|
source .venv/bin/activate # Linux/Mac
|
|
# .venv\Scripts\activate # Windows
|
|
|
|
# Core
|
|
pip install fastapi[standard]==0.135.1
|
|
pip install anthropic==0.84.0
|
|
pip install httpx==0.28.1
|
|
pip install python-dotenv==1.2.2
|
|
pip install aiofiles
|
|
|
|
# python-multipart is included in fastapi[standard]
|
|
# pydantic is included in fastapi[standard]
|
|
# uvicorn is included in fastapi[standard]
|
|
|
|
# Generate requirements.txt
|
|
pip freeze > requirements.txt
|
|
```
|
|
|
|
**Note:** `fastapi[standard]` installs FastAPI + uvicorn[standard] + python-multipart + pydantic + email-validator. This is the recommended install per FastAPI docs as of v0.100+. Do NOT install `fastapi-slim` — it was deprecated.
|
|
|
|
### Frontend
|
|
|
|
```bash
|
|
# Create Vite + React + TypeScript project
|
|
npm create vite@latest frontend -- --template react-ts
|
|
cd frontend
|
|
|
|
# Install Tailwind v4 with Vite plugin
|
|
npm install tailwindcss @tailwindcss/vite
|
|
|
|
# Install routing and data fetching
|
|
npm install react-router-dom @tanstack/react-query
|
|
|
|
# Install shadcn/ui (interactive CLI)
|
|
npx shadcn@latest init
|
|
|
|
# Add components as needed
|
|
npx shadcn@latest add button input table progress select
|
|
|
|
# Dev dependencies
|
|
npm install -D typescript @types/react @types/react-dom
|
|
```
|
|
|
|
### Tailwind v4 Configuration
|
|
|
|
In `vite.config.ts`:
|
|
```typescript
|
|
import { defineConfig } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
react(),
|
|
tailwindcss(),
|
|
],
|
|
})
|
|
```
|
|
|
|
In `src/index.css` (no `tailwind.config.js` needed in v4):
|
|
```css
|
|
@import "tailwindcss";
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture: Single-Container Deploy
|
|
|
|
FastAPI serves both the API and the built React SPA. This eliminates CORS configuration and simplifies Docker deployment on the VPS.
|
|
|
|
**Pattern:**
|
|
```python
|
|
# main.py
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
import os
|
|
|
|
app = FastAPI()
|
|
|
|
# API routes first
|
|
app.include_router(api_router, prefix="/api")
|
|
|
|
# SPA catch-all: serve index.html for any non-API route
|
|
@app.get("/{full_path:path}")
|
|
async def serve_spa(full_path: str):
|
|
static_dir = "static"
|
|
file_path = os.path.join(static_dir, full_path)
|
|
if os.path.isfile(file_path):
|
|
return FileResponse(file_path)
|
|
return FileResponse(os.path.join(static_dir, "index.html"))
|
|
|
|
# Mount static files (CSS, JS, assets)
|
|
app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")
|
|
```
|
|
|
|
**Multi-stage Dockerfile:**
|
|
```dockerfile
|
|
# Stage 1: Build React frontend
|
|
FROM node:22-slim AS frontend-builder
|
|
WORKDIR /app/frontend
|
|
COPY frontend/package*.json ./
|
|
RUN npm ci
|
|
COPY frontend/ .
|
|
RUN npm run build
|
|
|
|
# Stage 2: Python runtime
|
|
FROM python:3.12-slim
|
|
WORKDIR /app
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
COPY backend/ .
|
|
# Copy built frontend into FastAPI static directory
|
|
COPY --from=frontend-builder /app/frontend/dist ./static
|
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]
|
|
```
|
|
|
|
---
|
|
|
|
## Claude API Integration
|
|
|
|
**Recommended model:** `claude-sonnet-4-5` for content generation (best cost/quality ratio for structured text output at volume).
|
|
|
|
**Sync pattern** (for bulk generation — simpler, no streaming needed for batch jobs):
|
|
```python
|
|
import anthropic
|
|
|
|
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env
|
|
|
|
response = client.messages.create(
|
|
model="claude-sonnet-4-5",
|
|
max_tokens=2000,
|
|
messages=[
|
|
{"role": "user", "content": prompt}
|
|
]
|
|
)
|
|
content = response.content[0].text
|
|
```
|
|
|
|
**Streaming pattern** (for real-time UI feedback during generation):
|
|
```python
|
|
with client.messages.stream(
|
|
model="claude-sonnet-4-5",
|
|
max_tokens=2000,
|
|
messages=[{"role": "user", "content": prompt}]
|
|
) as stream:
|
|
for text in stream.text_stream:
|
|
yield text # SSE to frontend
|
|
```
|
|
|
|
**Async pattern** (for FastAPI async endpoints):
|
|
```python
|
|
async_client = anthropic.AsyncAnthropic()
|
|
|
|
async def generate_content(prompt: str) -> str:
|
|
response = await async_client.messages.create(
|
|
model="claude-sonnet-4-5",
|
|
max_tokens=2000,
|
|
messages=[{"role": "user", "content": prompt}]
|
|
)
|
|
return response.content[0].text
|
|
```
|
|
|
|
---
|
|
|
|
## Unsplash API Integration
|
|
|
|
**Rate limits:**
|
|
- Demo (free) tier: **50 requests/hour**
|
|
- Production (after manual approval): 5,000 requests/hour
|
|
|
|
**Implication for bulk generation:** With 50 req/hour limit and bulk carousel generation, implement:
|
|
1. Local image cache (save URLs/download images locally after first fetch)
|
|
2. Deduplication (don't re-fetch same search term if cached)
|
|
3. Fallback to cached results when limit approached
|
|
|
|
**Python integration via httpx** (no dedicated library needed — Unsplash REST API is simple):
|
|
```python
|
|
import httpx
|
|
|
|
async def search_unsplash(query: str, count: int = 1) -> list[dict]:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(
|
|
"https://api.unsplash.com/search/photos",
|
|
params={"query": query, "per_page": count},
|
|
headers={"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"}
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()["results"]
|
|
```
|
|
|
|
**Why not python-unsplash library:** `python-unsplash` (v1.2.5, last updated Nov 2023) and `pyunsplash` (v2020) are both poorly maintained. The Unsplash REST API is simple enough that `httpx` direct calls are cleaner, more maintainable, and don't add a stale dependency.
|
|
|
|
---
|
|
|
|
## CSV Generation for Canva Bulk Create
|
|
|
|
Use Python's stdlib `csv` module — no additional dependency:
|
|
|
|
```python
|
|
import csv
|
|
import io
|
|
|
|
def generate_canva_csv(rows: list[dict], fields: list[str]) -> str:
|
|
"""Generate CSV string formatted for Canva Bulk Create."""
|
|
output = io.StringIO()
|
|
writer = csv.DictWriter(output, fieldnames=fields)
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
return output.getvalue()
|
|
```
|
|
|
|
**FastAPI endpoint for CSV download:**
|
|
```python
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
@app.get("/api/export/{job_id}/csv")
|
|
async def export_csv(job_id: str):
|
|
csv_content = get_job_csv(job_id) # retrieve generated CSV
|
|
return StreamingResponse(
|
|
io.StringIO(csv_content),
|
|
media_type="text/csv",
|
|
headers={"Content-Disposition": f"attachment; filename=carousels_{job_id}.csv"}
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## File-Based Prompt Management
|
|
|
|
No database. Prompt templates stored as files:
|
|
|
|
```
|
|
backend/
|
|
prompts/
|
|
frameworks/
|
|
persuasion_nurturing.txt
|
|
schwartz_awareness.txt
|
|
niches/
|
|
tech_saas.txt
|
|
finance.txt
|
|
system_prompt.txt
|
|
```
|
|
|
|
**Access pattern:**
|
|
```python
|
|
from pathlib import Path
|
|
|
|
PROMPTS_DIR = Path(__file__).parent / "prompts"
|
|
|
|
def load_prompt(category: str, name: str) -> str:
|
|
prompt_path = PROMPTS_DIR / category / f"{name}.txt"
|
|
return prompt_path.read_text(encoding="utf-8")
|
|
|
|
def list_prompts(category: str) -> list[str]:
|
|
return [p.stem for p in (PROMPTS_DIR / category).glob("*.txt")]
|
|
```
|
|
|
|
**Advantages over DB:** Zero setup, version-controlled with git, easy to edit, diff-friendly. Appropriate for this scale (tens of prompt files, not millions of records).
|
|
|
|
---
|
|
|
|
## Alternatives Considered
|
|
|
|
| Recommended | Alternative | When to Use Alternative |
|
|
|-------------|-------------|-------------------------|
|
|
| FastAPI | Django REST Framework | When you need Django ORM, admin panel, or large team with Django expertise |
|
|
| FastAPI | Flask | Flask is simpler but lacks native async, auto OpenAPI docs, and Pydantic integration |
|
|
| React + Vite | Next.js | When you need SSR, SEO, or ISR. Overkill for an internal B2B tool served on a subpath. |
|
|
| Tailwind v4 | Tailwind v3 | Only if targeting environments where v4's new CSS syntax causes issues. v4 is stable as of 2025. |
|
|
| shadcn/ui | MUI / Chakra UI | When you need a full pre-styled design system without customization. shadcn is better for owned components. |
|
|
| stdlib csv | pandas | When doing data analysis, not just writing structured rows. pandas adds ~30MB to container. |
|
|
| httpx direct | python-unsplash | Only if Unsplash API complexity grows significantly (pagination, upload, etc.) |
|
|
| Single container | Separate nginx + Python | When frontend and backend scale independently, or need different update cadences. Overkill for this project. |
|
|
|
|
---
|
|
|
|
## What NOT to Use
|
|
|
|
| Avoid | Why | Use Instead |
|
|
|-------|-----|-------------|
|
|
| `fastapi-slim` | Deprecated as of recent FastAPI versions. Missing extras. | `fastapi[standard]` |
|
|
| `create-react-app` | Unmaintained since 2023. No Vite, slow builds, outdated webpack. | `npm create vite@latest` |
|
|
| `requests` library | Sync-only. Blocks FastAPI async event loop when called from async endpoints. | `httpx` with `AsyncClient` |
|
|
| `openai` library for Claude | Wrong SDK. Anthropic has official `anthropic` SDK with better type safety and streaming helpers. | `anthropic` SDK |
|
|
| Tailwind v3 PostCSS config | v3 config is still valid but v4 drops `tailwind.config.js` requirement and is significantly faster. | Tailwind v4 + `@tailwindcss/vite` |
|
|
| `python-unsplash` / `pyunsplash` | Both stale (2020-2023), no async support, thin wrappers adding maintenance risk. | `httpx` direct calls |
|
|
| `uvicorn` without `[standard]` | Base uvicorn lacks websocket support and other extensions. | `uvicorn[standard]` (included in `fastapi[standard]`) |
|
|
| Separate nginx container | Adds operational complexity without benefit for single-service subpath deploy. | FastAPI serves static files directly. |
|
|
|
|
---
|
|
|
|
## Stack Patterns by Variant
|
|
|
|
**For development (local):**
|
|
- Run FastAPI with `uvicorn main:app --reload` on port 8000
|
|
- Run Vite dev server on port 5173 with proxy: `vite.config.ts` → `proxy: { '/api': 'http://localhost:8000' }`
|
|
- No Docker needed locally
|
|
|
|
**For VPS subpath deployment (`lab.mlhub.it/postgenerator/`):**
|
|
- React must be built with `base: '/postgenerator/'` in `vite.config.ts`
|
|
- FastAPI must handle requests prefixed with `/postgenerator/` (via reverse proxy or app root_path)
|
|
- Set `root_path="/postgenerator"` in uvicorn or via `--root-path /postgenerator` flag
|
|
- lab-router nginx handles the subpath routing
|
|
|
|
**For bulk generation (many carousels in one job):**
|
|
- Use FastAPI `BackgroundTasks` for async job processing
|
|
- Store job state in a JSON file (file-based, no DB)
|
|
- Frontend polls `/api/jobs/{id}/status` with react-query
|
|
- Streaming SSE optional for real-time token display
|
|
|
|
---
|
|
|
|
## Version Compatibility
|
|
|
|
| Package | Compatible With | Notes |
|
|
|---------|-----------------|-------|
|
|
| fastapi==0.135.1 | pydantic>=2.0, starlette>=0.46.0 | v0.100+ requires pydantic v2. pydantic v1 not supported. |
|
|
| anthropic==0.84.0 | Python>=3.8 | Fully supports async via `AsyncAnthropic()`. |
|
|
| tailwindcss==4.2.1 | @tailwindcss/vite==4.x | v4 requires the Vite plugin, NOT PostCSS plugin (different from v3). |
|
|
| react==19.2.4 | react-router-dom>=7, @tanstack/react-query>=5 | React 19 has breaking changes from 18. Verify shadcn/ui component compatibility. |
|
|
| shadcn/ui | react>=18, tailwindcss>=3 | shadcn/ui officially supports Tailwind v4 since early 2025. Use `npx shadcn@latest init`. |
|
|
| python-multipart==0.0.22 | Python>=3.10 | Required for FastAPI file uploads. Requires py3.10+. |
|
|
|
|
---
|
|
|
|
## Sources
|
|
|
|
- [PyPI - fastapi](https://pypi.org/project/fastapi/) — v0.135.1 verified March 1, 2026 (HIGH confidence)
|
|
- [PyPI - anthropic](https://pypi.org/project/anthropic/) — v0.84.0 verified February 25, 2026 (HIGH confidence)
|
|
- [PyPI - uvicorn](https://pypi.org/project/uvicorn/) — v0.41.0 verified February 16, 2026 (HIGH confidence)
|
|
- [PyPI - pydantic](https://pypi.org/project/pydantic/) — v2.12.5 verified (HIGH confidence)
|
|
- [PyPI - python-dotenv](https://pypi.org/project/python-dotenv/) — v1.2.2 verified March 1, 2026 (HIGH confidence)
|
|
- [PyPI - httpx](https://pypi.org/project/httpx/) — v0.28.1 verified December 6, 2024 (HIGH confidence)
|
|
- [PyPI - python-multipart](https://pypi.org/project/python-multipart/) — v0.0.22 verified January 25, 2026 (HIGH confidence)
|
|
- [npm - react](https://www.npmjs.com/package/react) — v19.2.4 (HIGH confidence, per WebSearch)
|
|
- [npm - tailwindcss](https://www.npmjs.com/package/tailwindcss) — v4.2.1 verified ~February 2026 (HIGH confidence)
|
|
- [npm - vite](https://vite.dev/releases) — v7.x confirmed stable 2026 (MEDIUM confidence — WebSearch shows 7.3.1, v8 beta in progress)
|
|
- [Anthropic Streaming Docs](https://platform.claude.com/docs/en/api/messages-streaming) — Streaming patterns verified directly (HIGH confidence)
|
|
- [Unsplash API Docs](https://unsplash.com/documentation) — Rate limits 50 req/hr demo, 5000 prod (HIGH confidence)
|
|
- [FastAPI Static Files Docs](https://fastapi.tiangolo.com/tutorial/static-files/) — SPA serving pattern (HIGH confidence)
|
|
- [Tailwind CSS v4 Announcement](https://tailwindcss.com/blog/tailwindcss-v4) — v4 features and migration (HIGH confidence)
|
|
|
|
---
|
|
|
|
*Stack research for: PostGenerator — Instagram carousel automation (Python + FastAPI + React + Claude API)*
|
|
*Researched: 2026-03-07*
|