Anfrage-Bodys und eine JSON-API · Abschlussprojekt
Abschlussprojekt: Projekt: Eine beschreibbare Leseliste
Deine Leseliste legt jetzt Bücher an, liest, ersetzt und löscht sie, lehnt jede fehlerhafte Anfrage mit ihrem eigenen Status ab und hat Tests, die sie an ihre Methodenversprechen binden. Eine Zeile von SERVICE_CONTRACT.md stimmt noch nicht: GET /books?limit=N, die ersten N Bücher. Dieses Projekt fügt sie hinzu und prüft dann den ganzen Dienst gegen den Vertrag.
Was der Vertrag verlangt
Öffne SERVICE_CONTRACT.md und lies ihn noch einmal als Checkliste. Jede Zeile außer ?limit= sollte bereits gelten:
GETundHEADauf/booksund/books/{id};POST /booksmit 201 undlocation;PUT /books/{id}mit 200;DELETE /books/{id}mit 204, ohne Body und ohnecontent-length.405 mit
allow: GET, HEAD, POSTauf der Sammlung undallow: GET, HEAD, PUT, DELETEauf einem Element, und 404 überall sonst.Die Fehlerreihenfolge: Route und
, Rahmung (411, 400, 413), die Buch-ID, Medientyp (415), Syntax (400), Form und (422), wobei bei keinem Fehler etwas gespeichert oder hochgezählt wird. read_bodyprüft die Rahmung, bevorhandleläuft, also bekommt einPOST /nowhereohne Länge trotzdem 411.
Starte das vollständige Szenario des Hilfsprogramms, um zu sehen, wo du stehst:
python pl_http.py crud
Es fragt nach GET /books?limit=1, legt Buch 3 an, ersetzt es, löscht es und fragt nach GET /books?limit=0. Heute antworten beide GETs mit 200 und allen Büchern, content-length: 132 für die zwei Beispielbücher, weil handle die Query ignoriert. Die drei Schreibzugriffe dazwischen sollten bereits HTTP/1.0 201 Created, HTTP/1.0 200 OK und HTTP/1.0 204 No Content zeigen.
Ein Query-Parameter
In Kapitel 2 hast du Querys mit urlencode gebaut und urlsplit zerlegt. Eine Query auf dem Server zu lesen ist das Umgekehrte: urlsplit(target).query ist der Text nach dem ?, und parse_qs macht daraus ein parse_qs dekodiert außerdem %-Escapes und macht aus + ein Leerzeichen, wie du es bei Formularen gesehen hast:
Das gibt {'limit': ['2'], 'sort': ['title']} aus, dann {'limit': ['1', '2']}, dann {}. parse_qs ist oben in reading_server.py bereits importiert.
Die Regeln für limit folgen denen, die read_body auf eine Länge anwendet. Kein limit heißt keine Begrenzung. Ein limit muss genau einmal vorkommen und aus ASCII-Ziffern bestehen, geprüft mit isascii() und isdigit() vor int, damit two, -1, 1.5 und ² abgelehnt werden, statt deinen Server abstürzen zu lassen. Und es muss zwischen 1 und 100 liegen: limit=0 fragt nach nichts, und ein Client, der zehntausend Bücher in einer Antwort will, sollte sie nicht bekommen. Jedes Limit, das eine Regel verletzt, bekommt 400 mit einem sort=title, werden ignoriert.
query_limit(target) macht aus diesen Regeln einen einzigen Wert für handle: 0 für keine Begrenzung, die Zahl für ein gültiges Limit und None für ein ungültiges. 0 funktioniert als „keine Begrenzung“, weil ein gültiges Limit nie 0 ist.
Eine echte API kombiniert ein Limit meist mit einer Möglichkeit, nach der nächsten Seite zu fragen; solche Entwürfe gehen über diesen Kurs hinaus. Dieser hört bei den ersten N auf.
Meilensteine
Schreib
query_limit(target)mit den Regeln von oben.Ruf in
handleim Zweig fürGETundHEADauf/booksquery_limit(request["target"])auf. Wenn esNonezurückgibt, antworte mitjson_error(400, ...). Andernfalls bau die Liste wie bisher, nach ID aufsteigend, und hör auf, sobald sielimitBücher enthält, wennlimitnicht 0 ist. DieHEAD-Regel am Ende vonhandleleert weiterhin den Body, also trägtHEAD /books?limit=1die Länge, dieGET /books?limit=1senden würde.Führ
python pl_http.py crudnoch einmal aus. Die erste Antwort ist jetztHTTP/1.0 200 OKmitcontent-length: 73, ein Buch, und die letzte istHTTP/1.0 400 Bad Request.Führ
python -m pytest -qaus. Deine Tests aus Lektion 6 sollten weiterhin bestehen. Du kannst eigene Tests für?limit=hinzufügen, auf dieselbe Weise geschrieben, zum Beispiel:
def test_a_limit_lists_the_first_books():
response = reading_server.handle(make_request("GET", "/books?limit=1"), reading_server.sample_state())
data = json.loads(response["body"].decode("utf-8"))
assert len(data["books"]) == 1
python -m pytest -q
Was geprüft wird
Check my work prüft noch einmal das ganze Kapitel, mit Büchern, die es dir nicht gezeigt hat. Es ruft read_body, query_limit und handle direkt auf, mit guten und schlechten Limits, jeder Art von Schreibzugriff und einer fehlerhaften Anfrage pro Ursache, und vergleicht den Zustand vor und nach jedem Fehler. Es baut deinen Server mit make_server, ohne einen POST, ein DELETE und ein POST /nowhere ohne Länge als Bytes durch die Handler-Klasse und liest die Bytes, die zurückkommen. Es führt serve mit einem Ersatz-Server aus, und es führt deine Tests einmal gegen sein eigenes korrektes handle aus.
Drück dann Check my work. Dein Dienst hält jetzt seinen ganzen Vertrag. Manche Clients fragen allerdings noch unter Pfaden aus einer älteren Version der API nach Büchern, /v0/books; in Kapitel 6 sagt dein Server ihnen, wohin die Bücher gegangen sind, und dein Client lernt, zu folgen.
Aufgabe
Stell die beschreibbare Leseliste fertig: Füge GET /books?limit=N hinzu und sorg dafür, dass jede Zeile von SERVICE_CONTRACT.md gilt.
In reading_server.py:
Schreib
query_limit(target). Lies die Query mitparse_qs(urlsplit(target).query). Gib 0 zurück, wenn es keinlimitgibt. GibNonezurück, wennlimitmehr als einmal vorkommt, nicht aus ASCII-Ziffern besteht oder nicht zwischen 1 und 100 liegt. Andernfalls gib das Limit als int zurück.In
handle, beiGETundHEADauf/books: Wennquery_limitNonezurückgibt, antworte mit 400 überjson_error; andernfallsdie ersten limitBücher nach ID aufsteigend auf, oder jedes Buch, wenn das Limit 0 ist. Andere Query-werden ignoriert.
Alles aus den Lektionen 1 bis 6 muss weiterhin funktionieren: read_body, das von make_server an den Adapter übergeben wird, body_error, die drei Schreibzugriffe mit 201 und location, 200 und 204 ohne Body oder content-length, Status nach Ursache ohne Änderung am Zustand bei einem Fehler, die allow-tests/test_server.py.
Im python pl_http.py crud eine vollständige Abfolge gegen deinen Server aus, und python -m pytest -q führt deine Tests aus.
Drück dann Check my work. Mit Büchern, IDs und Limits, die du noch nicht gesehen hast, ruft es query_limit, read_body und handle direkt auf, für gültige und ungültige Limits, HEAD mit einem Limit, jeden Schreibzugriff und eine fehlerhafte Anfrage pro Ursache, und vergleicht den Zustand vor und nach jedem Fehler. Es baut deinen Server mit make_server, ohne einen POST, ein DELETE und ein POST /nowhere ohne Länge als Bytes durch die Handler-Klasse. Es führt serve mit einem Ersatz-Server aus. Zuletzt führt es deine Tests einmal mit seinem eigenen korrekten handle aus; sie müssen alle bestehen. Nichts aus deinem Terminal wird bewertet.