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
requestsoder ä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:
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:
/booksfür die Sammlung-
/books/{id}für einzelne Ressourcen -
Verschachtelte Ressourcen:
-
z.B.
/users/{userId}/ordersfür Bestellungen eines Nutzers -
Filter und Paginierung:
- Query-Parameter wie
?page=2&limit=20oder?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 anfordernPOST: Daten senden (z.B. Formulare, JSON)PUT,PATCH: Ressourcen aktualisierenDELETE: 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:
-
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;
TypedDictoderpydantichelfen 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.
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 dieStrEnum-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
OpenAIClientleicht 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:
mainist das Python-Modul (Dateiname ohne.py)appist die FastAPI-Instanz im Modul--reloadaktiviert 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:
Der Server läuft nun auf http://127.0.0.1:8000.
Konfigurationsoptionen¶
Uvicorn bietet zahlreiche CLI-Parameter, z.B.:
--host: Standard ist127.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
--reloadnur in der Entwicklung, da es Performance kostet - Nutzen Sie
--workersfü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 deffü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
Fieldfü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:
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
summaryunddescriptionParametern 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
requestszur 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:
requestsist synchron, FastAPI unterstützt asynchrone Endpunkte. Für produktive Systeme empfiehlt sichhttpxmit 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.