Python Lernhandbuch

Kapitel 14: Webapplikation mit FastAPI und Jinja2 · Abschnitt 4 von 5

Die Webapp (main.py)

python
from contextlib import asynccontextmanager

from fastapi import FastAPI, Form, HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

from db import pool


@asynccontextmanager
async def lifespan(app: FastAPI):
    pool.open()        # beim Start der App
    yield
    pool.close()       # beim Beenden


app = FastAPI(lifespan=lifespan)
templates = Jinja2Templates(directory='templates')


# ---------- HTML-Seiten ----------

@app.get('/', response_class=HTMLResponse)
def kundenliste(request: Request, q: str = ''):
    with pool.connection() as conn:
        kunden = conn.execute(
            'SELECT id, name, stadt, umsatz FROM kunden WHERE name ILIKE %s ORDER BY name',
            (f'%{q}%',),
        ).fetchall()
    return templates.TemplateResponse(request, 'kunden.html', {'kunden': kunden, 'q': q})


@app.post('/kunden')
def kunde_anlegen(name: str = Form(...), stadt: str = Form('')):
    with pool.connection() as conn:
        conn.execute(
            'INSERT INTO kunden (name, stadt) VALUES (%s, %s)',
            (name, stadt or None),
        )
    return RedirectResponse('/', status_code=303)     # zurück zur Liste


# ---------- JSON-API ----------

class KundeNeu(BaseModel):
    name: str
    stadt: str | None = None


@app.get('/api/kunden')
def api_liste():
    with pool.connection() as conn:
        return conn.execute('SELECT id, name, stadt, umsatz FROM kunden ORDER BY id').fetchall()


@app.get('/api/kunden/{kunden_id}')
def api_einzeln(kunden_id: int):
    with pool.connection() as conn:
        kunde = conn.execute(
            'SELECT id, name, stadt, umsatz FROM kunden WHERE id = %s', (kunden_id,)
        ).fetchone()
    if kunde is None:
        raise HTTPException(status_code=404, detail='Kunde nicht gefunden')
    return kunde


@app.post('/api/kunden', status_code=201)
def api_anlegen(kunde: KundeNeu):
    with pool.connection() as conn:
        return conn.execute(
            'INSERT INTO kunden (name, stadt) VALUES (%s, %s) RETURNING id, name, stadt',
            (kunde.name, kunde.stadt),
        ).fetchone()

Starten mit DATABASE_URL=postgresql://lernen:geheim@localhost:5432/lerndb uv run fastapi dev main.py. Die Tabelle kunden hast du in Kapitel 12 angelegt.

Was passiert:

  • lifespan öffnet beim Start den Verbindungspool und schließt ihn beim Beenden. Alles vor yield läuft beim Start, alles danach beim Ende.
  • Die Routen sind normale Funktionen (def, nicht async def). FastAPI führt sie in einem Thread-Pool aus, daher blockiert der Datenbankzugriff mit psycopg nichts anderes. So bleibst du ohne async einfach.
  • templates.TemplateResponse(request, 'kunden.html', {...}) füllt das HTML-Template mit dem Dictionary und gibt die Seite zurück.
  • Nach einem POST leitest du mit Status 303 zurück auf eine GET-Seite weiter (Post-Redirect-Get). Sonst würde ein Neuladen der Seite das Formular erneut abschicken.
  • KundeNeu(BaseModel) ist ein Pydantic-Modell. FastAPI liest den JSON-Body, prüft Typen und Pflichtfelder und liefert bei Fehlern automatisch 422.
  • raise HTTPException(status_code=404, ...) bricht ab und antwortet mit dem Statuscode.

Tipp: Bei Python-Beispielen kannst du auf ▶ Ausprobieren klicken, den Code verändern und direkt ausführen.

← Zurück