Lerne Coding
Entwicklung einer REST-API mit Python & dem FastAPI-Modul
27.02.2022

Eine Rest-API schnell programmieren mit Python FastAPI

Inhaltsverzeichnis
[[TABLE OF CONTENTS]]
access_timeGeschätzte Lesezeit ca. Minuten

Die Möglichkeiten Anwendungsarchitekturen aufzubauen werden immer vielfältiger. Und häufiger braucht man auch schon für kleinere Anwendungen eine API, um seine Daten zu verwalten. Django ist in Python ein wirkliches schweres Gewicht, bietet viele Optionen, kann als API verwendet werden, dafür ist es aber auch für vieles mehr gedacht. Den Django liefert nicht nur einen Router, sondern auch ein Admin Interface und eine eigene Template-Language, das sind nur zwei Beispiele – es gibt auch ein Extra Django Rest Framework, sicherlich interessant, wenn du bereits mit Django Erfahrungen gemacht hast. Es ist natürlich noch deutlich mehr möglich, aber grade für einfache REST API die keine weiteren Besonderheiten hat bietet sich es an auf eine schlanke Basis zu setzen. Dort ist mir in den letzten beiden Jahren das FastAPI Module immer wieder aufgefallen.

Logo der FastAPI
Logo der FastAPI

Bevor wir zu den Funktionen der FastAPI kommen, noch ein kurzer Hinweis, FastAPI basiert auf Starlette, ein anderes Python-Modul, diese Funktionen sind natürlich auch in der FastAPI vorhanden, die Interessanteste liste ich ebenfalls auf.

Funktionen des FastAPI Python Modul

  • OpenAPI Specification
  • Automatisches Data Model Dokumentation als JSON Schema
  • Automatische Dokumentation (Swagger UI / ReDoc)
  • Validierungs-Möglichkeiten für sehr viele Datentypen.
  • Authentification via HTTP Basic, OAuth2, JSON Web Token (JWT), API Key (im Header, Query oder als Cookie)
  • Websocket Support (Starlette)
  • Cors, Gzip, Static Files, Streaming Response (Starlette)
  • Session & Cookie Support (Starlette)
  • Integration von Pydantic für eine bessere Type Validierung
  • Support von Middlewares

Die FastAPI hat damit auf jeden Fall die wichtigsten Basics bereits integriert. Die meisten APIs, die man so programmieren will, wird man mit dem FastAPI Modul für Python umsetzen können.

Die offizielle Dokumentation findest du unter: https://fastapi.tiangolo.com/

Installation der FastAPI

Die Installation der FastAPI gestaltet sich nicht aufwendiger als die Installation von jedem anderen Python Modul. Auch die FastAPI ist über PIP zu installieren. Neben der FastAPI ist noch ein Serverprogramm (ASGI) notwendig. Zum Beispiel Uvicorn, Hypercorn oder zum Beispiel Daphne. Uvicorn ist der Standard Server, der in allen Beispielen in der aktuellen Dokumentation verwendet wird. Aber auch jede andere Server Software, die sich an den ASGI Standard hält, kann verwendet werden. Zum Beispiel hat Hypercorn den Vorteil das HTTP/2 Protokoll zu unterstützen, das die Performance je nach Anwendungsfall deutlich verbessern kann.

Diese Befehle müssen ausgeführt werden um FastAPI und Uvicorn zu installieren:

pip install fastapi
pip install "uvicorn[standard]"

Eine genaue Erklärung zu PIP findest du in meinem Artikel "Wie du PIP Installieren und Verwenden kannst?"

Nachdem die Installation abgeschlossen ist, kannst du auch schon mit der Programmierung loslegen. Als Konzeptidee, für die nachfolgenden Beispiele verwende ich den Gedanken eine To-do-API → Einträge anlegen, löschen, bestätigen und lesen. Abschließend findet ihr ein vollständiges und funktionierendes Beispiel einer To-do-API.

Hallo-Welt API erstellen

Eine Hallo-Welt Anwendung via FastAPI ist ziemlich einfach aufgebaut, als erstes muss man das FastAPI Paket importieren. Die Hauptfunktion abrufen und anschließend den entsprechenden Decorator je nach Request Typ verwenden. Als Response kann direkt eine Python Dictionary angegeben werden, dieses wird bei einer Anfrage an die API automatisch als JSON String ausgegeben.

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def index():
    return {"message": "Hallo Welt!"}

Uvicorn Server starten

Um nun die API zu starten, reicht es nicht wie bei anderen Python Programmen python main.py ins Terminal einzugeben, sondern es muss zuvor ein ASGI Server gestartet werden.

Für das Debuggen habe ich mich für den Standard Uvicorn Server entschieden. Folgender Befehl startet uns die API und der Reload Parameter ist dazu da, nicht nach jeder Änderung an der API den Server stoppen und neu starten zu müssen, dieses wird für euch automatisch übernommen.

uvicorn main:app --reload

Durch diese Variante könnt ihr die API sehr gut testen und eure Änderungen an dieser schnell und bequem durchführen.

Wie kann man Parameter an die API übergeben?

Starten wir nun mit einer der wichtigsten Funktionen, die eine API braucht, und zwar das Übergeben von Parametern um Werte abzufragen oder spezifische Werte zum Beispiel zu löschen.

Es gibt verschiede Möglichkeiten Werte an eine API zu übermitteln. Eine Option ist einen Parameter an die URL als Pfad anzuhängen. Dabei wird folgende URL Syntax verwendet:  http://host/api/items/{id}. Eine zweite Option wäre es über einen sogenannten Query Parameter oder auch, umgangssprachlich als GET Parameter bezeichnet, anzuhängen: http://host/api/items?id={id} . Das hat den Vorteil, dass der Parameter in der URL direkt zu finden ist, der die Lesbarkeit erhöht.

Pfad als Parameter-Wert verwenden

Um nun ein Parameter im Pfad zu verwenden, gibt man diesen im Decorator mit geschweiften Klammern an, dort drin schreibt man den Namen des Parameters. Zusätzlich kann eine Typ Hint bei der Definition der Funktion angegeben werden. Das Besondere ist, dass diese beachtet werden, klassische sind in Python eigentlich nur Hinweise die keine Wirkung auf den Programmablauf nehmen.

@app.get("/todo/{item_id}")
async def read_item(item_id: int):
    return {"id": item_id,"todo": "FastAPI Artikel schreiben","done": False}

Neben der Request Methode get können hier auch post, put, delete, options und head verwendet werden.

Query Parameter verwenden

Alternative zu den Pfad Parametern sind die sogenannten Query Parameter, damit können wir ebenfalls Parameter angeben. Technisch gesehen ist dies ein Unterschied gegenüber den Query Parametern. Persönlich würde ich empfehlen, dass der Hauptparameter als Pfad Parameter angegeben wird. Und weitere Option als Query Parameter.

Der Query Parameter wird genau wie der Pfad Parameter im Funktionskopf angegeben, allerdings wird im Decorator keine Parameter angegeben.

@app.get("/todo/")
async def read_item_second(id: int):
    return {"id": item_id,"todo": "FastAPI Artikel schreiben"}

HTTP-Headers als Parameter verwenden

Als dritte Alternative kann man auch Parameter in den HTTP Headern setzen, dies ist schon eher eine nicht so geläufige Variante. Eher für das Abfragen von User Agent und andere Meta-Daten wird diese Variante verwendet. Gerne wird der HTTP Header aber auch für einen API-Token verwendet.

@app.get("/todo/")
async def read_todo_by_header_id(item_id: int = Header(None)):
    return {"id": item_id,"todo": "FastAPI Artikel schreiben"}

Verwenden von Daten-Typen

Durch die Integration von Pydantic hat die FastAPI den Vorteil, dass Type Hints für eine stärkere Typisierung als für Python üblich sorgen.

Die Syntax unterliegt hier keinem Unterschied zu den Python typischen Type Hints, mit dem Unterschied, dass sie eben beachtet werden.

API Response – Welche Möglichkeiten gibt es?

Standardmäßig gibt die API alles als JSON Response zurück, natürlich können auch viele andere Response Typen verwendet werden, diese müssen aber explizit angegeben werden. Es gibt verschiedene Response Klassen, die verwendet werden können.

  • HTMLResponse → Rückgabe von HTML
  • PlainTextResponse → Rückgabe von Plain Text
  • JSONResponse (Standard) → Rückgabe von JSON
  • ORJSONResponse → Rückgabe von JSON mit besserer Performance
  • RedirectResponse → Rückgabe von einem Redirect (falls diese als Response Klasse verwendet wird, kann einfach eine URL als String zurückgegeben werden für ein Redirect.
  • StreamResponse → Für Video & Audio Dateien um diese zu streamen.
  • FileResponse → Für Dateien wie Bilder.
  • Response → Globaler Response der Mime Type kann selber definiert werden.

Die meisten Response-Arten sind ziemlich selbst erklärend vom Namen. Manche sind aber auch nicht direkt so verständlich.

ORJSONResponse, ist eine Response Möglichkeit die besonders schnell ist, aber auch JSON zurückgibt, wie der Standard Fall, mit dem Unterschied das ORJSON in der Performance gegenüber dem Standard JSON Module deutlich besser abschneidet.

Die Response Klasse wird im Decorator angegeben. So kannst du viele andere Varianten verwenden. Manche kannst du auch direkt im Return angeben, um so bei einer API Route verschiedene Response Arten zurück zu bekommen.

from fastapi.responses import ORJSONResponse

...

@app.get("/", tags=["Read"],response_class=ORJSONResponse)
async def list_item():
    return load_data()

Als Alternative gibt es auch die Response Klasse, diese kann direkt im Return angegeben werden, um so eigene Datentypen zurückzugeben, zum Beispiel für einen Plain Response oder auch für eine XML API, falls diese gewünscht ist.

return Response(content=data, media_type="plain/txt")

Was kann man mit einer Middleware anstellen?

Zusätzlich hat die FastAPI die Möglichkeit, Middlewares einzurichten. Dies ist eine mächtige Möglichkeit um den Response im Nachhinein noch zu verändern oder auch zusätzliche Informationen bereitzustellen, wie zum Beispiel wie lange die Python Funktion gebraucht hat.

Als Beispiel habe ich einmal eine Middleware geschrieben die einen Noindex Header hinzufügt, dieser sorgt dafür, das Google nicht versucht die abgerufene Seite zu indexieren. Um eine Middleware zu definieren, ist eine Decorator notwendig.

@app.middleware("http")
async def add_noindex(request: Request, call_next):
    response = await call_next(request)
    response.headers["x-robots-tag"] = 'noindex, nofollow'

    return response

Die automatisierte Dokumentation (Redoc & Swagger UI)

Eine besonders angenehme Funktion der FastAPI ist, dass Redoc und Swagger UI bereits integriert ist und so sehr schnell Dokumentationen erstellt werden können. Für meine To-do-Anwendung, die weiter unten gezeigt ist, gibt es direkt eine passende Dokumentation, natürlich kann diese auch mit weiteren Informationen noch verfeinert werden. Zum Beispiel mit dem Keyword “tags” bei den Decorator können die Request gruppiert werden. In dem Beispiel zu sehen “Read” und “Change” als Gruppe.

Natürlich braucht man in der Regel auch keine zwei API Dokumentationen gleichzeitig. Wenn man im Aufruf der Methode FastAPI den Parameter redoc_url=None mit None definiert ist diese deaktiviert, das Gleiche gilt für docs_url für Swagger UI. Als Alternative kann dort eine String angegeben werden, der einen Alternativen-Ort der Dokumentation bestimmt.

Static Files Verzeichnis einrichten

Manchmal ist es nützlich ein Verzeichnis für Bilddaten zu haben, wo diese abgelegt werden können und auch ganz normal via HTTP Request abgerufen werden können. Diese muss bei dem Modul FastAPI extra eingerichtet werden.

Die Route nach außen hin heißt /files und das Verzeichnis auf Dateiebene wurde als Assets bezeichnet. Wichtig: den Ordner muss man selber anlegen und wird standardmäßig nicht vom Module übernommen.

from fastapi.staticfiles import StaticFiles

app = FastAPI(...

app.mount("/files", StaticFiles(directory="assets"), name="assets")

Kleines Todo API - Beispiel

Abschließend noch der ganze Quelle-Code für die To-do-API – zu beachten ist, dass es jetzt noch kein User Key oder ähnliches gibt um das ganze zu begrenzen. Es geht nur allgemein darum, die Möglichkeiten einer API mit FastAPI zu zeigen und nicht die perfekte To-do-API mit guter Speicherverwaltung etc. Für das Speichern der To-dos wird auch eine Pickel-Datei ohne großes Fehlerhandling verwendet, für die Produktion ist dieser Code definitiv nicht geeignet. Es soll nur einen Eindruck geben.

Diese To-do-App hat folgende Routen/Möglichkeiten:

  • Anzeigen aller To-dos
  • Anzeigen einer To-do
  • Ein To-do als erledigt oder offen markieren
  • Einen To-do Punkt löschen
  • Einen To-do Punkt anlegen

Tipp: Nutze den nachfolgenden Code als Basis für eine komplexere To-do-App, zum Beispiel gibt es zurzeit keine Validierung, ob die ID schon vorhanden ist beim Abrufen. Sicherlich kann man auch die Endpunkte an sich noch verbessern. Bau doch mit diesem Ansatz eine erste API für eine To-do-App, die via Webseite, Interface in Python und zum Beispiel als Android oder iOS App funktioniert.

import pickle
import uuid
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
import os

app = FastAPI(title="Todo System",description="Beispeil vom HelloCoding.de Blog für eine API mit FastAPI.",version="1",contact={"name":"Felix Schürmeyer"})

def load_data():
    if(os.path.isfile('todos.pickle')):
        with open('todos.pickle', 'rb') as handle:
            return pickle.load(handle)

    return []

class Todo(BaseModel):
    name: str
    description: str
    done: bool | None = None
    id: str | None = None

# Todos Anzeigen
@app.get("/", tags=["Read"])
async def list_item():
    return load_data()

# Spezifische Todo Erhalten
@app.get("/todo/{item_id}",tags=["Read"])
async def read_item(item_id: str):
    data = load_data()

    for item in data:
        if str(item.id) == str(item_id):
            return item

    raise HTTPException(status_code=404, detail="Item not found")

# Todo Punkt Status setzen
@app.get("/done/{item_id}", tags=["Change"])
async def done_item(item_id: str,done: bool):
    data = load_data()

    for index,item in enumerate(data):
        if str(item.id) == str(item_id):
            data[index].done = done;

            pickle.dump(data, open('todos.pickle', 'wb'))

            return data

    raise HTTPException(status_code=404, detail="Item not found")

# Todo Punkt löschen
@app.delete("/todo/{item_id}", tags=["Change"])
async def delete_item(item_id: str):
    data = load_data()

    for index,item in enumerate(data):
        if str(item.id) == str(item_id):
            del data[index]

            pickle.dump(data, open('todos.pickle', 'wb'))

            return data

    raise HTTPException(status_code=404, detail="Item not found")

# Todo Punkt anlegen
@app.post("/todo", tags=["Change"])
async def set_item(item: Todo):
    item.id = uuid.uuid1()
    item.done = False

    data = load_data()

    data.append(item)

    pickle.dump(data, open('todos.pickle', 'wb'))

    return data

Zusammenfassung

FastAPI ist ein Python Modul mit sehr vielen Funktionen das gerade für APIs eine sehr schöne Variante ist, die in Python zu entwerfen. In diesem Artikel habe ich euch ein kleinen Einblick gegeben, wie das ganze aussehen könnte. Jetzt kennst du alle Basics, um mit Python eine API zu erstellen. Wie wäre es mit einer API für einen Key Value Storage oder eine API für eine Cross Plattform Anwendung. Egal was du nun mit FastAPI entwickeln willst, ich wünsche dir viel Spaß dabei.

Bildquelle - Vielen Dank an die Ersteller:innen für dieses Bild
Kommentare zum Artikel

Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Entwicklung einer REST-API mit Python & dem FastAPI-Modul"!

Kommentar schreiben

Vom Autor Empfohlen
close