Skip to content

12. APIs und moderne Python-Anwendungen

REST APIs konsumieren, eigene Webservices entwickeln und moderne Python-Anwendungen bereitstellen.

Moderne Software besteht heute selten aus isolierten Programmen. Anwendungen kommunizieren über APIs, tauschen Daten mit externen Diensten aus und stellen eigene Schnittstellen für andere Systeme bereit.

Python bietet für diese Aufgaben ein hervorragendes Ökosystem. Mit requests lassen sich externe APIs komfortabel ansprechen. Frameworks wie FastAPI ermöglichen die Entwicklung moderner, leistungsfähiger Webservices mit vergleichsweise wenig Code.

Darüber hinaus haben KI-Dienste wie die OpenAI API gezeigt, wie einfach sich leistungsfähige Cloud-Dienste in eigene Anwendungen integrieren lassen.

In diesem Kapitel lernen Sie, wie APIs funktionieren, wie externe Dienste verwendet werden und wie eigene REST-Schnittstellen mit FastAPI entwickelt werden.

Was sind APIs?

APIs als Schnittstellen moderner Software

APIs (Application Programming Interfaces) sind standardisierte Schnittstellen, die es ermöglichen, dass verschiedene Softwarekomponenten, Systeme oder Dienste miteinander kommunizieren. In der Praxis sind APIs häufig Web-APIs, die über das HTTP-Protokoll angesprochen werden.

Requests, Responses und Statuscodes

Eine API-Kommunikation erfolgt typischerweise über Requests (Anfragen) und Responses (Antworten). Ein Client sendet eine HTTP-Anfrage an einen Server, der darauf mit einem HTTP-Statuscode und einer Antwort (Payload) reagiert.

  • Request: Enthält eine HTTP-Methode (GET, POST, PUT, DELETE etc.), URL, Header und optional einen Body.
  • Response: Enthält einen Statuscode, Header und optional einen Body.

Die Statuscodes sind dreistellige Zahlen, die den Erfolg oder Fehler der Anfrage signalisieren:

  • 2xx: Erfolg (z. B. 200 OK, 201 Created)
  • 4xx: Clientfehler (z. B. 404 Not Found, 400 Bad Request)
  • 5xx: Serverfehler (z. B. 500 Internal Server Error)

Diese Trennung ist in HTTP festgelegt und wird von allen Web-APIs eingehalten.

HTTP-Methoden und ihre Bedeutung

Die HTTP-Methoden folgen einer semantischen Bedeutung, die RESTful APIs konsequent nutzen:

  • GET: Daten abrufen, keine Seiteneffekte
  • POST: Neue Ressourcen erzeugen oder Aktionen auslösen
  • PUT: Ressourcen vollständig ersetzen
  • PATCH: Ressourcen teilweise aktualisieren
  • DELETE: Ressourcen löschen

API-Kommunikation als verteiltes System

Die Kommunikation über HTTP ist inhärent asynchron und fehleranfällig. Daher sind robuste Fehlerbehandlung und Timeout-Management essenziell. Anders als bei lokalen Methodenaufrufen müssen Entwickler hier Netzwerkfehler, Latenzen und inkonsistente Zustände berücksichtigen.

Beispiel: Einfacher HTTP-Request mit Python

from http.client import HTTPResponse
from typing import Optional
import urllib.request

url = "https://api.example.com/resources/42"

try:
    with urllib.request.urlopen(url, timeout=5) as response:  # HTTPResponse
        status: int = response.status
        body: bytes = response.read()
        print(f"Status: {status}")
        print(f"Body: {body.decode('utf-8')}")
except urllib.error.HTTPError as e:
    print(f"HTTP-Fehler: {e.code} - {e.reason}")
except urllib.error.URLError as e:
    print(f"Netzwerkfehler: {e.reason}")

Dieses Beispiel zeigt die Grundstruktur einer HTTP-Anfrage in Python. Anders als in Java mit HttpClient oder C# mit HttpClient ist die Standardbibliothek in Python minimalistisch, was den Einsatz von Drittbibliotheken wie requests nahelegt, um den Umgang mit APIs zu vereinfachen.

Architekturübersicht einer API-Kommunikation

sequenceDiagram
    participant Client
    participant API_Server

    Client->>API_Server: HTTP Request (GET /resource/42)
    API_Server-->>Client: HTTP Response (200 OK + JSON-Daten)

    Note over Client,API_Server: Statuscodes signalisieren Erfolg oder Fehler

Zusammenfassung

  • APIs sind standardisierte Schnittstellen, häufig über HTTP realisiert
  • Requests bestehen aus Methode, URL, Header und Body; Responses aus Statuscode, Header und Body
  • HTTP-Statuscodes geben Auskunft über Erfolg oder Fehler
  • HTTP-Methoden haben eine semantische Bedeutung, die RESTful APIs konsequent nutzen
  • API-Kommunikation ist zustandslos, verteilt und erfordert robuste Fehlerbehandlung
  • Python bietet einfache HTTP-Tools, aber für professionelle API-Nutzung empfiehlt sich requests oder ähnliche Bibliotheken

Diese Grundlagen sind essenziell, um moderne Python-Anwendungen mit APIs zu entwickeln und deren Design und Verhalten zu verstehen.

REST APIs verstehen

REST-Architekturprinzipien

REST (Representational State Transfer) ist kein Protokoll, sondern ein Architekturstil, der auf den Prinzipien des Webs aufbaut. RESTful APIs nutzen HTTP als Transport und definieren klare Konventionen für Ressourcen, Zustände und Operationen.

Die wichtigsten Prinzipien sind:

  • Ressourcenorientierung: Jede Entität wird als Ressource mit einer eindeutigen URI adressiert.
  • Zustandslosigkeit: Der Server speichert keinen Client-Zustand zwischen den Anfragen.
  • Repräsentationen: Ressourcen werden in verschiedenen Formaten (meist JSON) übertragen.
  • Standardisierte HTTP-Methoden: GET, POST, PUT, PATCH, DELETE steuern die Operationen.
  • Selbstbeschreibende Nachrichten: Jede Nachricht enthält alle notwendigen Informationen.
  • Hypermedia als Engine of Application State (HATEOAS): Clients navigieren durch Ressourcen über Hyperlinks (in der Praxis oft vernachlässigt).

Ressourcen und URIs

REST APIs modellieren die Domäne über Ressourcen, die durch URIs eindeutig identifiziert werden. Im Vergleich zu RPC- oder SOAP-basierten APIs liegt der Fokus auf Substantiven statt auf Verben.

Beispiel für eine Ressourcen-URI:

GET /api/users/42

Hier wird die Ressource "User" mit der ID 42 angefragt.

HTTP-Methoden und ihre Bedeutung

Methode Zweck Idempotent Beispiel
GET Daten abrufen Ja GET /api/products/123
POST Neue Ressource erstellen Nein POST /api/products mit JSON-Body
PUT Ressource vollständig ersetzen Ja PUT /api/products/123 mit JSON-Body
PATCH Ressource teilweise ändern Nein PATCH /api/products/123
DELETE Ressource löschen Ja DELETE /api/products/123

Idempotenz bedeutet, dass mehrere identische Anfragen denselben Effekt haben wie eine einzelne.

Statuscodes als Kommunikationsmittel

HTTP-Statuscodes sind ein zentrales Kommunikationsmittel, um den Erfolg oder Fehler einer Anfrage zu signalisieren. REST APIs nutzen sie konsequent:

  • 2xx: Erfolg (z.B. 200 OK, 201 Created)
  • 4xx: Client-Fehler (z.B. 400 Bad Request, 404 Not Found, 409 Conflict)
  • 5xx: Server-Fehler (z.B. 500 Internal Server Error)

Beispiel: RESTful API-Design für eine Buchverwaltung

classDiagram

    class Book {
        +id: int
        +title: str
        +author: str
        +year: int
    }

    class API {
        +get_books()
        +get_book_by_id()
        +create_book()
        +update_book()
        +patch_book()
        +delete_book()
    }

    API --> Book : manages

Typische REST-API-Designs

  • Sammlungen und Einzelelemente:
  • /books für die Sammlung
  • /books/{id} für einzelne Ressourcen

  • Verschachtelte Ressourcen:

  • z.B. /users/{userId}/orders für Bestellungen eines Nutzers

  • Filter und Paginierung:

  • Query-Parameter wie ?page=2&limit=20 oder ?author=Smith

Warum REST in Python anders gedacht wird

Python-Entwickler schätzen die Einfachheit und Direktheit von REST. Python-Frameworks wie FastAPI oder Flask fördern deklarative Routen und automatische Serialisierung, was den Fokus auf Ressourcenmodellierung erleichtert.

Python nutzt häufig Typannotationen und Pydantic-Modelle, um Datenvalidierung und Dokumentation eng zu verknüpfen.

Zusammenfassung

REST APIs sind ressourcenorientierte, zustandslose HTTP-Schnittstellen, die durch klare Konventionen und Statuscodes eine robuste und skalierbare Kommunikation ermöglichen. Für erfahrene Entwickler bedeutet der Umstieg, von methodenorientierten zu ressourcenorientierten Denkweisen zu wechseln und HTTP als universelles Protokoll konsequent zu nutzen.

Externe APIs mit requests nutzen

HTTP-Anfragen mit requests senden

Das requests-Modul ist das De-facto-Standardpaket für HTTP-Interaktionen in Python. Es abstrahiert die Komplexität von Sockets und HTTP-Protokolldetails auf eine sehr lesbare, imperative API.

import requests
from requests import Response

response: Response = requests.get('https://api.example.com/data')
print(response.status_code)  # HTTP-Statuscode
print(response.headers['Content-Type'])  # Header auslesen
print(response.text)  # Rohtext der Antwort

Die Methode requests.get() ist eine einfache, synchrone Funktion, die eine HTTP-GET-Anfrage sendet und ein Response-Objekt zurückgibt. Dieses Objekt kapselt alle relevanten Informationen der Antwort.

Wichtige HTTP-Methoden

  • GET: Daten anfordern
  • POST: Daten senden (z.B. Formulare, JSON)
  • PUT, PATCH: Ressourcen aktualisieren
  • DELETE: Ressourcen löschen

Diese Methoden spiegeln das REST-Prinzip wider, das wir im nächsten Kapitel vertiefen.

Parameter, Header und Payload

requests erlaubt es, Query-Parameter, Header und Payload klar zu trennen. Das erleichtert die Lesbarkeit und Wartbarkeit:

params = {'search': 'python', 'page': 2}
headers = {'Authorization': 'Bearer TOKEN123'}

response = requests.get('https://api.example.com/items', params=params, headers=headers)

Für POST-Anfragen ist es üblich, JSON-Daten zu senden:

json_data = {'name': 'Alice', 'role': 'developer'}
response = requests.post('https://api.example.com/users', json=json_data)

Hier übernimmt requests die Serialisierung ins JSON-Format und setzt den passenden Header Content-Type: application/json automatisch.

Response-Handling und Fehlerbehandlung

Das Response-Objekt bietet neben status_code auch die Methode raise_for_status(), die bei HTTP-Fehlern eine Ausnahme wirft. Dies ist idiomatisch und erleichtert robusten Code:

try:
    response = requests.get('https://api.example.com/resource')
    response.raise_for_status()  # HTTPError bei 4xx/5xx
    data = response.json()  # JSON-Daten parsen
except requests.HTTPError as e:
    print(f'HTTP-Fehler: {e.response.status_code}')
except requests.RequestException as e:
    print(f'Netzwerkfehler: {e}')

Best Practices

  • Verwenden Sie timeout-Parameter, um blockierende Anfragen zu vermeiden:
response = requests.get('https://api.example.com', timeout=5.0)  # 5 Sekunden Timeout
  • Nutzen Sie Sessions (requests.Session()), um Verbindungen wiederzuverwenden und Performance zu verbessern.

  • Verwenden Sie response.json() nur, wenn Sie sicher sind, dass die Antwort JSON enthält.

Ablaufdiagramm einer typischen API-Anfrage mit requests

sequenceDiagram
    participant Client
    participant requests
    participant Server

    Client->>requests: requests.get(url, params, headers)
    requests->>Server: HTTP GET Anfrage
    Server-->>requests: HTTP Response (Status, Header, Body)
    requests-->>Client: Response Objekt
    Client->>Client: response.raise_for_status()
    Client->>Client: response.json() oder response.text

Dieser einfache, aber flexible Workflow macht requests zur ersten Wahl für externe API-Interaktionen in Python.

JSON und API-Antworten

JSON als Standardformat für API-Antworten

JSON (JavaScript Object Notation) ist das de-facto-Standardformat für strukturierte Daten in Web-APIs. Im Gegensatz zu XML oder proprietären Formaten ist JSON leichtgewichtig, gut lesbar und direkt in Python mit dem json-Modul oder Bibliotheken wie requests einfach zu verarbeiten.

JSON-Antworten mit requests verarbeiten

Die requests-Bibliothek bietet die Methode .json(), um eine HTTP-Antwort direkt als Python-Objekt zu dekodieren. Dabei wird intern response.content mit json.loads geparst.

import requests


class UserNotFoundError(Exception):
    """Benutzer wurde nicht gefunden."""


class UserApiError(Exception):
    """Allgemeiner Fehler beim Zugriff auf die API."""


def fetch_user(user_id: int) -> dict:
    url = f"https://api.example.com/users/{user_id}"

    try:
        response = requests.get(url, timeout=5)

        if response.status_code == 404:
            raise UserNotFoundError(
                f"Benutzer mit ID {user_id} wurde nicht gefunden."
            )

        response.raise_for_status()

        return response.json()

    except requests.RequestException as e:
        raise UserApiError(
            "Fehler beim Zugriff auf die Benutzer-API."
        ) from e

Die Funktion kapselt die technischen Details der verwendeten HTTP-Bibliothek und wirft stattdessen eigene Exceptions. Dadurch muss der aufrufende Code keine Kenntnisse über requests besitzen.

Fachliche Fehler wie ein nicht vorhandener Benutzer (UserNotFoundError) können von technischen Problemen (UserApiError) klar getrennt behandelt werden.

Der Aufrufer:

try:
    user = fetch_user(42)
    print(f"Name: {user['name']}")

except UserNotFoundError as e:
    print(e)

except UserApiError as e:
    print(e)

Umgang mit ungültigen oder unerwarteten JSON-Daten

Pydantic-Modelle auf Basis von BaseModel kombinieren Typdefinition und Datenvalidierung in einer einzigen Klasse. Eingabedaten werden automatisch geprüft, bei Bedarf in passende Typen konvertiert und bei ungültigen Werten mit aussagekräftigen Fehlermeldungen abgelehnt.

Dadurch eignen sich Pydantic-Modelle besonders für die Verarbeitung von API-Anfragen, Konfigurationsdaten und anderen externen Datenquellen.

Ein einfaches Beispiel zur Validierung:

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    id: int
    name: str
    email: EmailStr | None = None

raw_data = fetch_user(42)
user = User.model_validate(raw_data)
if user:
    print(f"User: {user.name} (ID: {user.id})")
else:
    print("Ungültige Benutzerdaten")

Netzwerkfehler und Timeouts robust behandeln

In produktiven Umgebungen sind Timeouts und Netzwerkfehler unvermeidlich. requests erlaubt es, Timeouts explizit zu setzen. Python-Entwickler bevorzugen explizite Timeout-Parameter, um blockierende Aufrufe zu vermeiden.

# 5 Sekunden connect-timeout, 27 Sekunden read timeout
response = requests.get(url, timeout=(5, 27))

Diese Trennung von Verbindungs- und Lese-Timeout ist ein Detail, das in Python-APIs oft übersehen wird, aber gerade bei APIs mit variabler Antwortzeit wichtig ist.

Zusammenfassung

  • JSON ist in Python dank nativer Datentypen und requests-Integration besonders einfach zu handhaben.
  • Fehlerbehandlung erfolgt gezielt über spezifische Exceptions, nicht über generische Catch-All-Mechanismen.
  • Validierung der Datenstruktur ist essenziell, um Laufzeitfehler zu vermeiden; TypedDict oder pydantic helfen dabei.
  • Timeouts sollten explizit gesetzt werden, um blockierende Netzwerkaufrufe zu vermeiden.

Die OpenAI API verwenden

Die OpenAI API aus Python ansprechen

Die OpenAI API ermöglicht den Zugriff auf leistungsfähige Sprachmodelle, die sich für vielfältige Aufgaben wie Textgenerierung, Übersetzung, Zusammenfassung oder Chatbots eignen. Für erfahrene Entwickler ist entscheidend, die API effizient, robust und idiomatisch in Python zu nutzen.

Einrichtung und Authentifizierung

Die Kommunikation mit der OpenAI API erfolgt über HTTPS-Requests, typischerweise mit dem openai-Python-Paket, das offizielle SDK. Dieses abstrahiert HTTP-Aufrufe und bietet eine klare, objektorientierte Schnittstelle.

pip install openai
import openai

openai.api_key = "dein_api_schluessel"

Der API-Key wird als globale Variable gesetzt, was in Python üblich ist. Meist wird der Key als Umgebungsvariable abgelegt.

Einfache Textgenerierung

Ein typischer Aufruf zur Textgenerierung mit einem GPT-Modell sieht so aus:

response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Erkläre Python-Dekoratoren."}]
)

print(response.choices[0].message.content)

Die messages sind eine Liste von Dictionaries, die den Dialogverlauf repräsentieren. Dieses Design ist flexibel und unterstützt kontextreiche Interaktionen. Die Antwort wird in response.choices geliefert, da die API mehrere Antwortvarianten generieren kann.

Ablaufdiagramm: Anfrage an die OpenAI API

sequenceDiagram
    participant Client as Python-Client
    participant OpenAI as OpenAI API

    Client->>OpenAI: POST /v1/chat/completions
    Note right of OpenAI: Validierung, Verarbeitung
    OpenAI-->>Client: JSON-Antwort mit Text

Umgang mit API-Antworten und Fehlern

Die API kann Netzwerkfehler, Rate-Limits oder ungültige Eingaben zurückmelden. Python-Entwickler nutzen Exceptions und Kontextmanager, um solche Fälle idiomatisch zu behandeln:

from openai.error import OpenAIError

try:
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "Fasse den folgenden Text zusammen: ..."}]
    )
except OpenAIError as e:
    # Loggen, Retry-Mechanismen oder Benutzerfeedback
    print(f"OpenAI API Fehler: {e}")
else:
    print(response.choices[0].message.content)

Best Practices

  • API-Schlüssel sicher verwalten: Nutze Umgebungsvariablen oder Secret-Management, niemals Hardcoding.
  • Asynchrone Nutzung: Für skalierbare Anwendungen unterstützt das SDK auch asynchrone Aufrufe mit asyncio.
  • Response-Parsing: Greife nur auf die benötigten Felder zu, um die API-Änderungen leichter abzufangen.

Beispiel: Einfache Chat-Funktion in einer Funktion

def chat_with_openai(prompt: str) -> str:
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

if __name__ == "__main__":
    user_input = "Gib mir bitte ein Rezept für Spiegeleier mit Senf."
    print(chat_with_openai(user_input))

Diese Funktion kapselt die API-Interaktion klar, was Wartbarkeit und Testbarkeit verbessert. Die Python-Denkweise legt Wert auf einfache, lesbare Funktionen, die sich gut komponieren lassen.


Die OpenAI API in Python zu nutzen bedeutet, die Vorteile der Sprache für einfache, klare und robuste API-Interaktionen zu nutzen. Das offizielle SDK abstrahiert HTTP-Details, während Python-typische Fehlerbehandlung und idiomatische Datenstrukturen die Integration erleichtern. Im nächsten Abschnitt werden wir darauf aufbauen und eine kleine KI-Anwendung mit der OpenAI API entwickeln.

Ein einfacher KI-Assistent

Architektur eines einfachen KI-Assistenten

Ein KI-Assistent, der auf der OpenAI API basiert, besteht im Kern aus drei Komponenten:

  • Client-Interface: Nimmt Benutzereingaben entgegen und zeigt Antworten an.
  • API-Client: Kommuniziert mit der OpenAI API, sendet Anfragen und empfängt Antworten.
  • Verarbeitungsschicht: Bereitet Eingaben auf, verarbeitet Antworten und steuert den Dialog.

Diese Trennung fördert Testbarkeit und Erweiterbarkeit, ähnlich wie in etablierten Architekturmustern (z.B. MVC).

classDiagram
    class ClientInterface {
        +send_user_input(text: str) -> None
        +display_response(text: str) -> None
    }
    class OpenAIClient {
        -api_key: str
        +send_message(messages: list[dict]) -> dict
    }
    class Assistant {
        -client: OpenAIClient
        +chat(messages: list[dict]) -> str
    }

    ClientInterface --> Assistant : nutzt
    Assistant --> OpenAIClient : nutzt

Minimaler Beispielcode

Im Folgenden zeigen wir eine einfache, aber praxisnahe Implementierung:

from enum import StringEnum
from typing import List, Dict
import openai

class Role(StrEnum):
  USER = "user"
  SYSTEM = "system"
  ASSISTANT = "assistant"

class OpenAIClient:
    def __init__(self, api_key: str) -> None:
        self.api_key = api_key
        openai.api_key = api_key

    def send_message(self, messages: List[Dict[str, str]]) -> Dict:
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages,
            temperature=0.7
        )
        return response

class Assistant:
    def __init__(self, client: OpenAIClient) -> None:
        self.client = client

    def chat(self, messages: List[Dict[str, str]]) -> str:
        response = self.client.send_message(messages)
        # Extrahiere die Antwort des Modells
        return response.choices[0].message.content

if __name__ == "__main__":
    import os

    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        raise RuntimeError("OPENAI_API_KEY environment variable not set")

    client = OpenAIClient(api_key)
    assistant = Assistant(client)

    conversation = [
        {"role": Role.SYSTEM, "content": "Du bist ein hilfreicher Assistent."},
        {"role": Role.USER, "content": "Erkläre den Unterschied zwischen Python und Java."}
    ]

    answer = assistant.chat(conversation)
    print(f"KI-Assistent: {answer}")

Erläuterungen und Python-spezifische Aspekte

  • Flexible Datenstrukturen: Die Nachrichten sind Listen von Dictionaries mit klar definierten Rollen (system, user, assistant). Python erlaubt hier eine sehr natürliche und dynamische Modellierung, ohne komplexe DTOs oder Klassenhierarchien. Hier im Beispiel wurde die StrEnum-Klasse für Enumerationen genutzt.

  • Umgang mit Umgebungsvariablen: Die API-Schlüsselverwaltung erfolgt idiomatisch über os.getenv, was die Sicherheit und Portabilität verbessert.

  • Modularität: Durch die klare Trennung von API-Client und Assistant wird der Code testbar und wartbar. So kann z.B. der OpenAIClient leicht gegen einen Mock ausgetauscht werden.

Best Practices

  • Verwende Umgebungsvariablen oder sichere Konfigurationsmechanismen für API-Schlüssel.
  • Kapsle API-Aufrufe in eigene Klassen, um Wiederverwendbarkeit und Testbarkeit zu erhöhen.

Häufige Fehler

  • Direktes Einbetten von API-Schlüsseln im Code: Vermeide das, um Sicherheitsrisiken zu minimieren.
  • Unbehandelte API-Fehler: Die OpenAI API kann Fehler zurückgeben (Rate Limits, Netzwerkprobleme). Fehlerbehandlung ist im produktiven Code essenziell.

Dieser einfache KI-Assistent bildet die Grundlage für komplexere Anwendungen, die wir in späteren Abschnitten mit FastAPI und weiteren Features erweitern werden.

Einführung in FastAPI

Architektur und Designprinzipien von FastAPI

FastAPI ist ein modernes, asynchrones Webframework für Python, das speziell für den Aufbau von APIs optimiert ist. Es basiert auf Starlette (für das Web-Framework und ASGI-Server-Kompatibilität) und Pydantic (für Datenvalidierung und -serialisierung). Die Architektur ist modular und nutzt Python-Typannotationen intensiv, um automatische Validierung, Dokumentation und Entwicklererfahrung zu ermöglichen.

Minimaler FastAPI-Server: Erster Endpunkt

Das folgende Beispiel zeigt den minimalen Aufbau einer FastAPI-Anwendung mit einem einfachen GET-Endpunkt:

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello() -> dict:
    return {"message": "Hello, FastAPI!"}

Hier definiert der @app.get-Dekorator einen HTTP-GET-Endpunkt unter /hello. Die Funktion hello ist asynchron (async), was FastAPI erlaubt, performant mit I/O-gebundenen Operationen umzugehen. Die Rückgabe ist ein Dictionary, das automatisch als JSON serialisiert wird.

Ablauf eines HTTP-Requests in FastAPI

sequenceDiagram
    participant Client
    participant FastAPIApp
    participant Starlette
    participant ASGIServer

    Client->>ASGIServer: HTTP GET /hello
    ASGIServer->>FastAPIApp: Request-Objekt
    FastAPIApp->>FastAPIApp: Route Matching
    FastAPIApp->>FastAPIApp: Request-Validierung (Pydantic)
    FastAPIApp->>FastAPIApp: Handler-Funktion (async)
    FastAPIApp->>FastAPIApp: Response-Erstellung (JSON)
    FastAPIApp->>ASGIServer: Response-Objekt
    ASGIServer->>Client: HTTP Response

FastAPI arbeitet als ASGI-Anwendung (Asynchronous Server Gateway Interface), was eine asynchrone Verarbeitung von Requests ermöglicht. Der ASGI-Server (z.B. Uvicorn) empfängt HTTP-Anfragen und leitet sie an FastAPI weiter. FastAPI matcht die Route, validiert Eingaben mit Pydantic, führt den Handler aus und serialisiert die Antwort.

Warum asynchron?

FastAPI arbeitet von Grund auf asynchron. Das erlaubt eine hohe Skalierbarkeit bei I/O-lastigen Anwendungen, wie API-Servern, die Datenbanken oder externe Services anfragen. Python-Entwickler sollten sich an das async/await-Paradigma gewöhnen, da es die Performance deutlich verbessert.

Erweiterung: Pfad- und Query-Parameter

FastAPI unterstützt die einfache Definition von Pfad- und Query-Parametern mit Typannotationen:

from fastapi import Query

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = Query(None, max_length=50)) -> dict:
    return {"item_id": item_id, "q": q}

Hier wird item_id als Pfadparameter erwartet, automatisch in einen int konvertiert und validiert. Der Parameter q ist ein optionaler Query-Parameter. Durch den Typ str | None darf entweder ein String oder None übergeben werden. None bedeutet, dass der Parameter nicht gesetzt wurde. Mit Query(None, max_length=50) wird zusätzlich festgelegt, dass der Standardwert None ist und ein übergebener String maximal 50 Zeichen lang sein darf.

Die Funktion gibt ein Dictionary (dict) zurück, das von FastAPI automatisch in eine JSON-Antwort für den Client umgewandelt wird.

Zusammenfassung

FastAPI verbindet moderne Python-Idiome mit einer leistungsfähigen Architektur, die asynchrones Web-Programming und automatische Validierung ermöglicht. Die Integration von Pydantic und Starlette als Bausteine macht FastAPI zu einem idealen Werkzeug für moderne API-Entwicklung in Python.

FastAPI mit Uvicorn betreiben

FastAPI ist ein modernes, asynchrones Webframework, das auf Starlette und Pydantic aufbaut. Um eine FastAPI-Anwendung lokal zu betreiben, ist Uvicorn der empfohlene ASGI-Server. Uvicorn ist leichtgewichtig, performant und unterstützt asynchrone Python-Features nativ.

Uvicorn starten

Nach der Implementierung der FastAPI-App (z.B. in main.py) starten Sie Uvicorn über die Kommandozeile:

pip install uvicorn

uvicorn main:app --reload
  • main ist das Python-Modul (Dateiname ohne .py)
  • app ist die FastAPI-Instanz im Modul
  • --reload aktiviert den automatischen Neustart bei Codeänderungen, ideal für die Entwicklung

Warum Uvicorn?

Im Gegensatz zu klassischen WSGI-Servern (z.B. Gunicorn mit Flask) unterstützt Uvicorn das ASGI-Protokoll, welches Asynchronität und WebSockets ermöglicht. Das ist essenziell für moderne APIs, die hohe Parallelität und niedrige Latenz erfordern.

Beispiel: Minimaler Start

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root() -> dict[str, str]:
    return {"message": "Hello, FastAPI with Uvicorn!"}

Dann starten:

uvicorn main:app --reload

Der Server läuft nun auf http://127.0.0.1:8000.

Konfigurationsoptionen

Uvicorn bietet zahlreiche CLI-Parameter, z.B.:

  • --host: Standard ist 127.0.0.1, für externe Zugriffe z.B. 0.0.0.0
  • --port: Standard 8000
  • --workers: Anzahl paralleler Worker-Prozesse (für Multi-Core-Nutzung)

Für produktive Umgebungen empfiehlt sich eine Kombination aus mehreren Uvicorn-Workern hinter einem Reverse-Proxy (z.B. Nginx).

Ablaufdiagramm: Startprozess mit Uvicorn

sequenceDiagram
    participant Dev as Entwickler
    participant Shell as Kommandozeile
    participant Uvicorn
    participant FastAPI

    Dev->>Shell: uvicorn main:app --reload
    Shell->>Uvicorn: Start ASGI-Server
    Uvicorn->>FastAPI: Import main.app
    Uvicorn->>Dev: Server läuft auf http://127.0.0.1:8000
    Dev->>Uvicorn: HTTP Request
    Uvicorn->>FastAPI: Anfrage verarbeiten
    FastAPI-->>Uvicorn: Antwort
    Uvicorn-->>Dev: HTTP Response

Best Practices

  • Verwenden Sie --reload nur in der Entwicklung, da es Performance kostet
  • Nutzen Sie --workers für CPU-intensive Anwendungen, beachten Sie aber, dass FastAPI selbst asynchron ist und oft mit einem Worker auskommt
  • Für Debugging und Hot-Reload ist Uvicorn ideal, in Produktion empfiehlt sich ein Prozessmanager (z.B. systemd) oder Container-Orchestrierung

Vergleich zu Java/C

In Java oder C# sind Webserver oft als eigenständige Container (Tomcat, Kestrel) implementiert. Python trennt klar zwischen Framework (FastAPI) und Server (Uvicorn).

Routing und Endpunkte

Grundlagen des Routings in FastAPI

Routing ist das zentrale Konzept, um HTTP-Anfragen auf spezifische Funktionen (Endpunkte) zuzuordnen. FastAPI nutzt dabei Python-Dekoratoren, um URLs und HTTP-Methoden präzise zu definieren.

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

Hier definiert @app.get einen GET-Endpunkt auf /items/{item_id}, wobei item_id als Pfadparameter automatisch typisiert wird. Die Funktion ist asynchron, was FastAPI für hohe Performance empfiehlt.

HTTP-Methoden und Pfadparameter

FastAPI unterstützt alle gängigen HTTP-Methoden via Dekoratoren: @app.get, @app.post, @app.put, @app.delete etc. Pfadparameter sind Teil der URL und werden durch geschweifte Klammern definiert. FastAPI wandelt sie automatisch in die deklarierte Typen um und validiert sie.

@app.post("/users/{user_id}/items")
async def create_user_item(user_id: int, item: dict):
    return {"user_id": user_id, "item": item}

Query-Parameter und optionale Parameter

Query-Parameter werden als Funktionsargumente mit Defaultwerten definiert, was eine elegante und intuitive Schnittstelle ermöglicht.

@app.get("/search")
async def search(q: str | None = None, limit: int = 10):
    return {"query": q, "limit": limit}

Routing-Mechanismus intern

sequenceDiagram
    participant Client
    participant FastAPI
    participant Endpoint

    Client->>FastAPI: HTTP GET /items/42
    FastAPI->>Endpoint: Aufruf read_item(item_id=42)
    Endpoint-->>FastAPI: JSON Response
    FastAPI-->>Client: HTTP 200 + JSON

Best Practices für Routing

  • Klarheit vor Kürze: Pfad- und Query-Parameter sollten semantisch klar benannt sein.
  • Explizite HTTP-Methoden: Vermeide Mehrdeutigkeiten, indem du für jede Methode eigene Dekoratoren nutzt.
  • Asynchronität nutzen: Verwende async def für I/O-lastige Endpunkte, um Skalierbarkeit zu verbessern.
  • Typannotationen konsequent einsetzen: Sie sind essenziell für automatische Validierung und Dokumentation.

Häufige Fehler und Fallstricke

  • Pfad- und Query-Parameter verwechseln: Pfadparameter sind zwingend, Query-Parameter optional. FastAPI unterscheidet strikt.
  • Synchroner Code in async Endpunkten: Kann zu Performanceeinbußen führen.
  • Fehlende Typannotationen: Verhindert automatische Validierung und Dokumentation.

Fazit

Routing in FastAPI ist bewusst minimalistisch und Python-idiomatisch gestaltet. Es nutzt die Stärken von Python-Typen und async/await, um deklarative, performante und leicht wartbare API-Endpunkte zu definieren.

Pydantic in FastAPI

Pydantic als Kernstück der Datenvalidierung in FastAPI

FastAPI nutzt Pydantic, um Request- und Response-Daten automatisch zu validieren und zu serialisieren. Pydantic-Modelle definieren dabei die Form und Typen der Daten, ähnlich zu DTOs (Data Transfer Objects) in Java oder C#. Anders als dort sind Pydantic-Modelle jedoch leichtgewichtig, unveränderlich (optional) und erlauben eine deklarative Validierung mit Python-Typannotationen.

Diese enge Integration ermöglicht es FastAPI, Fehler frühzeitig zu erkennen und automatisch HTTP-Fehlermeldungen (422 Unprocessable Entity) zu generieren, ohne dass explizite Validierungslogik nötig ist.

Definition von Request- und Response-Modellen

Pydantic-Modelle erben von BaseModel und definieren Felder mit Typannotationen. Optional können Standardwerte, Validierungsregeln oder Aliase angegeben werden.

from pydantic import BaseModel, ConfigDict, Field
from typing import Optional

class UserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=50)
    email: EmailStr
    full_name: str | None = None


class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    full_name: str | None = None

    model_config = ConfigDict(from_attributes=True)

Field ermöglicht die Definition zusätzlicher Validierungsregeln und Metadaten für ein Feld, beispielsweise Mindest- oder Maximallängen. Für E-Mail-Adressen kann der spezielle Typ EmailStr verwendet werden, der automatisch das Format überprüft.

In Pydantic v2 ersetzt ConfigDict(from_attributes=True) das frühere orm_mode = True und erlaubt das direkte Auslesen von Daten aus Objektattributen, beispielsweise aus SQLAlchemy-Modellen.

Über ConfigDict können außerdem weitere Modelleinstellungen vorgenommen werden, etwa das Verbieten zusätzlicher Felder (extra="forbid"), das Einfrieren von Modellen (frozen=True) oder die automatische Validierung bei Attributänderungen (validate_assignment=True).

Verwendung in FastAPI-Endpunkten

FastAPI bindet Pydantic-Modelle direkt an Endpunkte. Request-Bodies werden automatisch geparst und validiert, Response-Modelle steuern die Ausgabe und erzeugen OpenAPI-Dokumentation.

from fastapi import FastAPI

app = FastAPI()

@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate) -> UserResponse:
    # Simulierte Speicherung und ID-Zuweisung
    created_user = user.dict()
    created_user["id"] = 123
    return created_user

Hier übernimmt FastAPI das Parsen von JSON in ein UserCreate-Objekt. Bei Validierungsfehlern antwortet der Server automatisch mit einem Fehler 422. Die Antwort wird gemäß UserResponse serialisiert.

Validierung und Fehlerbehandlung

Pydantic validiert nicht nur Typen, sondern auch komplexe Regeln. Bei Verstößen werden detaillierte Fehler mit Pfad, Typ und Nachricht erzeugt, die FastAPI in HTTP-Antworten umwandelt.

from pydantic import BaseModel, EmailStr, field_validator

class UserCreate(BaseModel):
    username: str
    email: EmailStr

    @field_validator("username")
    @classmethod
    def username_must_not_contain_spaces(cls, value: str) -> str:
        if " " in value:
            raise ValueError("Username must not contain spaces")
        return value

    @field_validator("email")
    @classmethod
    def email_must_be_company_email(cls, value: EmailStr) -> EmailStr:
        if not str(value).endswith("@example.com"):
            raise ValueError("Only company email addresses are allowed")
        return value

Mit @field_validator können beliebig viele benutzerdefinierte Validierungsregeln definiert werden. Im Beispiel wird geprüft, ob der Benutzername keine Leerzeichen enthält und ob die E-Mail-Adresse zu einer bestimmten Domain gehört. Wird eine Regel verletzt, erzeugt Pydantic automatisch eine aussagekräftige Fehlermeldung.

Pydantic verwendet Python-Typannotationen nicht nur zur Dokumentation, sondern auch für automatisches Parsing und Validierung von Daten zur Laufzeit.

Pydantic-Modell in FastAPI

classDiagram
    class UserCreate {
        +str username
        +str email
        +Optional[str] full_name
        +__init__(username: str, email: str, full_name: Optional[str] = None)
        +validator email_must_contain_at()
    }

    class UserResponse {
        +int id
        +str username
        +str email
        +Optional[str] full_name
        +__init__(id: int, username: str, email: str, full_name: Optional[str] = None)
    }

    UserCreate <|-- BaseModel
    UserResponse <|-- BaseModel

Best Practices

  • Definiere separate Modelle für Eingabe (Request) und Ausgabe (Response), um klare Schnittstellen zu gewährleisten.
  • Nutze Field für zusätzliche Validierung und Dokumentation.
  • Verwende Pydantic-Validatoren für komplexe Validierungslogik.
  • Vertraue auf FastAPI’s automatische Fehlerbehandlung, um saubere APIs zu gewährleisten.

Zusammenfassung

Pydantic ist das Rückgrat der Datenvalidierung in FastAPI und ermöglicht eine deklarative, typbasierte Definition von API-Schnittstellen. Die enge Verzahnung mit Python-Typen und FastAPI-Mechanismen reduziert Boilerplate, erhöht die Wartbarkeit und sorgt für eine robuste API-Validierung, die in anderen Sprachen oft umständlicher implementiert wird.

Swagger UI und OpenAPI

Automatische API-Dokumentation mit FastAPI

FastAPI integriert OpenAPI (früher Swagger Specification) nativ, um REST-APIs automatisch zu dokumentieren. Diese Dokumentation ist nicht nur eine statische Referenz, sondern eine interaktive Oberfläche, die es erlaubt, Endpunkte direkt im Browser zu testen.

FastAPI generiert die OpenAPI-Spezifikation auf Basis der Typannotationen und Pydantic-Modelle. Dadurch entsteht eine präzise, stets aktuelle API-Beschreibung ohne zusätzlichen Aufwand.

Zugriff auf die OpenAPI-Spezifikation

Die OpenAPI-Spezifikation ist standardmäßig unter /openapi.json verfügbar:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

Ruft man http://localhost:8000/openapi.json auf, erhält man die vollständige JSON-Spezifikation, die alle Endpunkte, Parameter und Modelle beschreibt.

Swagger UI: Interaktive API-Dokumentation

Unter /docs stellt FastAPI eine interaktive Swagger UI bereit. Diese Oberfläche erlaubt es, HTTP-Requests direkt aus dem Browser abzusetzen, Parameter zu ändern und die Antworten zu inspizieren. Das ist besonders nützlich für schnelle Tests und für die Zusammenarbeit mit Frontend-Teams oder API-Konsumenten.

Redoc: Alternative Dokumentationsoberfläche

FastAPI bietet zusätzlich unter /redoc eine alternative, optisch ansprechende Dokumentation mit Redoc. Diese ist besonders geeignet für umfangreiche APIs, da sie eine bessere Strukturierung und Navigation ermöglicht.

Beispiel: Erweiterte API mit Pydantic-Modellen

from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
    id: int
    name: str
    description: str | None = None

app = FastAPI()

@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Item:
    # In einer echten Anwendung würde hier die Persistenz erfolgen
    return item

ein Aufruf mit Curl könnte so aussehen:

curl -X POST "http://localhost:8000/items/" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 1,
    "name": "Python Book",
    "description": "A book for developers"
  }'

Antwort:

{
  "id": 1,
  "name": "Python Book",
  "description": "A book for developers"
}

In Swagger UI erscheinen nun automatisch die Eingabefelder für id, name und description. Die Validierung erfolgt serverseitig anhand der Pydantic-Modelle, was die Konsistenz zwischen Dokumentation und Implementierung garantiert.

Warum OpenAPI und Swagger UI so wichtig sind

  • Synchronität: Die Dokumentation ist immer aktuell, da sie aus dem Code generiert wird.
  • Interaktivität: Entwickler können API-Endpunkte ohne zusätzliche Tools testen.
  • Standardisierung: OpenAPI ist ein branchenweit akzeptierter Standard, der Interoperabilität fördert.
  • Automatisierung: Tools wie Code-Generatoren oder API-Gateways können die Spezifikation nutzen.

Best Practices

  • Definieren Sie klare Pydantic-Modelle für Requests und Responses, um die Dokumentation präzise zu halten.
  • Nutzen Sie die interaktive Swagger UI, um Ihre API während der Entwicklung zu testen.
  • Ergänzen Sie Endpunkte mit aussagekräftigen summary und description Parametern für bessere Lesbarkeit.

Zusammenfassung

FastAPI macht es mit OpenAPI und Swagger UI möglich, APIs automatisch und interaktiv zu dokumentieren. Das reduziert den Pflegeaufwand, erhöht die Entwicklerproduktivität und verbessert die Zusammenarbeit zwischen Backend- und Frontend-Teams erheblich.

classDiagram
    class FastAPI {
        +openapi.json: JSON
        +/docs: SwaggerUI
        +/redoc: Redoc
    }
    class PydanticModel {
        +field: type
        +validation
    }
    FastAPI --> PydanticModel : nutzt zur Generierung
    FastAPI --> SwaggerUI : stellt bereit
    FastAPI --> Redoc : stellt bereit

Praxisprojekt: Eine moderne FastAPI-Anwendung

Architekturübersicht und Komponenten

Im vorliegenden Praxisprojekt entwickeln wir eine moderne FastAPI-Anwendung, die folgende Kernkomponenten integriert:

  • HTTP-Client-Requests mit requests zur Kommunikation mit externen APIs (z.B. OpenAI API)
  • Datenvalidierung und Serialisierung mit Pydantic
  • Asynchrone FastAPI-Endpunkte zur parallelen Verarbeitung
  • Automatische API-Dokumentation via OpenAPI/Swagger UI

Diese Kombination spiegelt typische Anforderungen moderner Python-Webservices wider, bei denen externe KI-Services eingebunden werden.

classDiagram
    class FastAPIApp {
        +app: FastAPI
        +run()
    }
    class OpenAIClient {
        +api_key: str
        +send_prompt(prompt: str) -> dict
    }
    class RequestModel {
        +prompt: str
    }
    class ResponseModel {
        +response_text: str
    }

    FastAPIApp --> OpenAIClient : nutzt
    FastAPIApp --> RequestModel : validiert
    FastAPIApp --> ResponseModel : serialisiert

Schritt 1: Pydantic-Modelle definieren

Pydantic erleichtert die Validierung und Typisierung von HTTP-Anfragen und -Antworten. Im Gegensatz zu Java-DTOs oder C#-Data-Transfer-Objects ist Pydantic dynamisch und nutzt Python-Typannotationen, um Validierung und Serialisierung automatisch zu übernehmen.

from pydantic import BaseModel

class RequestModel(BaseModel):
    prompt: str

class ResponseModel(BaseModel):
    response_text: str

Schritt 2: OpenAI API Client kapseln

Die Kommunikation mit der OpenAI API erfolgt synchron über requests. Die Kapselung in eine eigene Klasse fördert Testbarkeit und Separation of Concerns.

from openai import OpenAI

class OpenAIClient:
    def __init__(self, api_key: str) -> None:
        self.client = OpenAI(api_key=api_key)

    def send_prompt(self, prompt: str) -> str:
        response = self.client.responses.create(
            model="gpt-4.1",
            input=[
                {
                    "role": "user",
                    "content": prompt,
                }
            ],
        )

        return response.output_text

Schritt 3: FastAPI-Endpunkt implementieren

FastAPI nutzt Pydantic-Modelle automatisch zur Validierung und Serialisierung. Die Integration des OpenAI-Clients erfolgt im Endpunkt, der die Anfrage verarbeitet und die Antwort zurückgibt.

from fastapi import FastAPI, HTTPException

app = FastAPI()

client = OpenAIClient(api_key="YOUR_API_KEY")


@app.post("/chat", response_model=ResponseModel)
async def chat_endpoint(request: RequestModel) -> ResponseModel:
    try:
        text = client.send_prompt(request.prompt)
        return ResponseModel(response_text=text)

    except Exception:
        raise HTTPException(
            status_code=500,
            detail="Interner Serverfehler"
        )

Besonderheiten und Best Practices

  • Synchron vs. Asynchron: requests ist synchron, FastAPI unterstützt asynchrone Endpunkte. Für produktive Systeme empfiehlt sich httpx mit async-Support, um Blockierungen zu vermeiden.
  • Fehlerbehandlung: HTTP-Fehler der API werden explizit abgefangen und als HTTP-Exceptions weitergereicht.
  • Konfiguration: API-Schlüssel und Endpunkte sollten über Umgebungsvariablen oder Konfigurationsmanagement eingebunden werden, nicht hardcodiert.
  • Dokumentation: FastAPI generiert automatisch OpenAPI-Spezifikationen und stellt Swagger UI bereit, was die API-Interaktion erleichtert.

Ablaufdiagramm der Anfrageverarbeitung

sequenceDiagram
    participant Client
    participant FastAPI
    participant OpenAI

    Client->>FastAPI: POST /chat mit JSON {"prompt": "..."}
    FastAPI->>RequestModel: Validierung
    FastAPI->>OpenAIClient: send_prompt(prompt)
    OpenAIClient->>OpenAI: HTTP POST mit Prompt
    OpenAI-->>OpenAIClient: JSON-Antwort
    OpenAIClient-->>FastAPI: Antwort zurück
    FastAPI->>ResponseModel: Serialisierung
    FastAPI-->>Client: JSON mit Antworttext

Diese Architektur demonstriert, wie moderne Python-Webanwendungen externe KI-Services nahtlos integrieren, dabei Python-typische Idiome wie Pydantic-Modelle und FastAPI-Dependency-Injection nutzen und gleichzeitig robuste Fehlerbehandlung und klare Trennung der Verantwortlichkeiten realisieren.