mitmario.dev

Eine JSON-Schnittstelle

Python Sandbox 4 Min Lesezeit 4 BeispieleLektion 6 von 7

Ein Programm, das wissen will, was im September für Lebensmittel draufging, müsste die Zahl aus dem HTML des Haushaltsbuchs herausklauben. Bisher schickt der Server nur Seiten für Menschen, HTML für den Browser. Viel einfacher ist es, dem Programm gleich Daten zu schicken.

In Abschnitt 17 hat dein Programm einen Dienst gefragt und Daten als JSON bekommen. Jetzt geht es um die andere Seite, einen Server, an dem du jede Zeile selbst geschrieben hast.

Ein Dictionary wird JSON

Ein Dictionary wird JSON
from flask import Flask

from haushalt import AUSGABEN, KATEGORIEN

app = Flask(__name__)


@app.get("/api/kategorien")
def kategorien():
    return {"kategorien": KATEGORIEN}


@app.get("/api/ausgabe/<int:nummer>")
def ausgabe(nummer):
    return AUSGABEN[nummer - 1]

Die Route gibt ein Dictionary zurück. Das ist schon alles. Flask macht daraus JSON und schickt es mit der Kopfzeile Content-Type: application/json, wie der Wetterdienst. Im Reiter Browser steht nur der Text, mehr ist damit dort nicht anzufangen. Mit dem ersten Befehl in der Befehlsleiste siehst du die Antwort samt Kopfzeilen.

Eine Liste geht genauso, /api/kategorien gibt eine Liste in einem Dictionary zurück. Damit ist dein Haushaltsbuch eine Schnittstelle wie die aus Abschnitt 17.

Am JSON fallen zwei Dinge auf. Die Schlüssel stehen alphabetisch, betrag vor empfaenger, obwohl sie im Dictionary andersherum stehen. Flask sortiert sie, damit dieselben Daten immer gleich aussehen. Und aus „Bäckerei“ ist B\u00e4ckerei geworden, wie bei json.dumps ohne ensure_ascii=False in Lektion 12.5.

Beides ist kein Fehler. Wenn ein Programm die Antwort mit json() liest, bekommt es wieder ein Dictionary mit „Bäckerei“ darin, und auf die Reihenfolge kommt es in einem Dictionary sowieso nicht an.

Der Betrag steht in Cent da, 840, wie in der Datenbank aus Abschnitt 16. Viele Schnittstellen im echten Leben machen das auch so, etwa die von Zahlungsdiensten, damit kein Programm am anderen Ende mit Kommazahlen rechnen muss. Was die Zahl bedeutet, steht dann in der Beschreibung der Schnittstelle.

Unter /api/ausgabe/14 wird die Nummer ungeprüft verwendet, und es gibt einen Absturz mit 500, genau wie vorher bei der Seite für eine einzelne Ausgabe.

Der Status gehört dazu

Der Status gehört dazu
from flask import Flask

from haushalt import AUSGABEN

app = Flask(__name__)


@app.get("/api/ausgabe/<int:nummer>")
def ausgabe(nummer):
    if nummer < 1 or nummer > len(AUSGABEN):
        return {"fehler": f"Keine Ausgabe mit der Nummer {nummer}"}, 404
    return AUSGABEN[nummer - 1]

Bei einer Seite für Menschen war abort(404) die ehrliche Antwort. Eine Schnittstelle antwortet aber auch im Fehlerfall mit JSON. Sonst bekommt das Programm am anderen Ende eine HTML-Seite, die es nicht lesen kann.

return {"fehler": f"Keine Ausgabe mit der Nummer {nummer}"}, 404 gibt beides zurück, die Daten und den Statuscode, so wie render_template(…), 400 beim Formular.

Mit return {"fehler": …} allein käme die Meldung mit 200 an, also „alles in Ordnung“. Ein Programm, das sich am Status orientiert, würde danach im Dictionary nach einem Betrag greifen, den es nicht gibt. Wer eine Schnittstelle baut, setzt den Status deshalb bewusst. 404, wenn es etwas nicht gibt, 400, wenn die Anfrage nicht stimmt, und 200, wenn alles passt. Das ist der Normalfall und muss nicht dastehen.

Die andere Seite

Die andere Seite
# Ein zweites Programm, das deine Anwendung fragt, so wie die Programme in
# Abschnitt 17 den Wetterdienst gefragt haben. Es läuft im Terminal, während
# die Anwendung läuft.
import requests

ADRESSE = "http://127.0.0.1:3000"

antwort = requests.get(ADRESSE + "/api/ausgabe/3", timeout=2)
print(antwort.status_code, antwort.headers["Content-Type"])
print(antwort.json())

antwort = requests.get(ADRESSE + "/api/ausgabe/14", timeout=2)
print(antwort.status_code, antwort.json())

Hier schließt sich der Kreis zu Abschnitt 17. In der Befehlsleiste steht python frage.py. Wenn die Anwendung noch nicht läuft, wird sie vorher gestartet, wie bei Sandbox starten.

frage.py ist ein zweites Programm, gebaut wie die aus Abschnitt 17, und es fragt deine Anwendung über requests, während sie läuft. Für die dritte Ausgabe kommen der Status 200, der Typ application/json und ein Dictionary mit „Bäckerei Krume“ zurück, geschrieben mit ä. Für die vierzehnte kommen 404 und die Meldung. Was in Abschnitt 17 der Wetterdienst war, ist jetzt deine eigene Anwendung.

Zu Hause brauchst du dafür zwei Terminals. In einem läuft der Server und wartet, im anderen startest du python frage.py. Ohne den Server endet es mit dem ConnectionError aus Lektion 17.1, weil an dem Port dann niemand zuhört. Wenn du Flask zu Hause ohne --port startest, ist es Port 5000, und in frage.py muss dann auch 5000 stehen.

Eine Menge ist kein Dictionary

Eine Menge ist kein Dictionary
from flask import Flask

app = Flask(__name__)


@app.get("/api/kategorien")
def kategorien():
    return {"Lebensmittel", "Wohnen", "Freizeit"}

Die geschweiften Klammern sehen aus wie ein Dictionary. Es fehlen aber die Doppelpunkte, und damit ist es eine Menge. Damit kann Flask nichts anfangen. Im Browser steht ein Traceback, der so endet:

TypeError: The view function did not return a valid response. The return type must be a string, dict, list, tuple with headers or status, Response instance, or WSGI callable, but it was a set.

Übersetzt heißt das: Die Funktion hat keine gültige Antwort zurückgegeben. Erlaubt sind Text, ein Dictionary, eine Liste, ein Tupel mit Status oder Kopfzeilen und zwei Dinge, die hier nicht vorkommen, es war aber eine Menge (set). Richtig wäre eine Liste in eckigen Klammern oder ein Dictionary mit Schlüssel und Wert.

Was die Aufgabe will

Gefragt sind die Summen je Kategorie, diesmal als Daten. Eine Adresse liefert alle Kategorien, eine zweite eine einzelne. Wenn es die Kategorie nicht gibt, kommt eine ehrliche 404 als JSON.

Zum Mitnehmen

Wenn eine Route ein Dictionary oder eine Liste zurückgibt, macht Flask JSON daraus und schickt es mit dem Typ application/json. Mit return {"fehler": "…"}, 404 kommt der Statuscode bewusst dazu, damit ein Programm am anderen Ende weiß, woran es ist. Dass Flask die Schlüssel sortiert und Umlaute als \u00e4 schreibt, macht nichts, requests liest beides wieder richtig.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.