0%

Was ein Framework für dich tut · Abschlussprojekt

Abschlussprojekt: Projekt: Die Leseliste in FastAPI

Deine App beantwortet GET auf beiden Pfaden, schreibt Bücher aus einem geprüften Modell und führt ihre Tests im selben Prozess aus. Zwei Verhaltensweisen deines Servers aus Kapitel 5 fehlen noch: DELETE /books/{id} und die Query limit bei GET /books. Dieses Projekt fügt beide hinzu, mit zwei Tests, die bemerken, wenn eine davon kaputtgeht. Jede @app-Zeile und BookIn stehen schon in reading_app.py; du schreibst Funktionskörper und Tests.

Was die App tun muss

  • DELETE /books/{id} entfernt das Buch und antwortet mit 204 ohne Body. Eine unbekannte ID bekommt eine 404, wie überall sonst in der App.

  • GET /books?limit=N antwortet mit den ersten N Büchern in der Reihenfolge der IDs, für N von 1 bis 100. Ohne limit listet es jedes Buch auf, und eine Grenze, die größer als die Anzahl der Bücher ist, listet ebenfalls jedes Buch auf.

204 mit nichts

Die Zeile über delete_book lautet @app.delete("/books/{book_id}", status_code=204). Eine 204 sagt, dass die Anfrage gelungen ist und es nichts zurückzusenden gibt. In Kapitel 5 hast du diese Antwort von Hand gebaut, ohne Body und ohne content-length. Hier sendet sie: Was delete_book auch zurückgibt, die 204 geht ohne Body und ohne content-length hinaus. Mit diesen Versionen trägt sie trotzdem content-type: application/json, eine Beschriftung, die nichts zu beschriften hat, was harmlos ist. Die Aufgabe der ist also die Entscheidung. Gibt es so ein Buch? Wenn nicht, löse dasselbe HTTPException aus wie get_book. Wenn ja, entferne es mit .pop(), wie es das delete_book deines Servers getan hat.

delete_book endet mit return {"deleted": 7} unter status_code=204. Was wird gesendet?

Ein Query-Parameter, den FastAPI prüft

Lies jetzt den Rest der def-Zeile von list_books:

def list_books(limit: int | None = Query(default=None, ge=1, le=100)):

Lies sie so, wie du book_id: int gelesen hast. FastAPI nimmt limit aus dem Query-, macht einen int daraus und prüft, dass er mindestens 1 (ge, greater than or equal, größer oder gleich) und höchstens 100 (le, less than or equal, kleiner oder gleich) ist, alles bevor deine Funktion läuft. int | None mit default=None bedeutet, dass eine Anfrage ihn weglassen darf, und dann ist limit gleich None. Deine Funktion trifft also nur auf None oder eine ganze Zahl von 1 bis 100 und braucht keine eigenen Prüfungen.

Den Rest beantwortet FastAPI selbst, und nicht immer so wie dein Server:

  • ?limit=0, ?limit=101 und ?limit=two bekommen 422. Dein Server hat mit 400 geantwortet.

  • ?limit= ohne etwas dahinter bekommt 422. Dein Server hat einen leeren als keine Grenze gelesen und jedes Buch aufgelistet.

  • ?limit=1&limit=2 bekommt 200 mit zwei Büchern: FastAPI behält den letzten Wert eines wiederholten Parameters. Dein Server hat die Wiederholung mit 400 abgelehnt.

GET /books?limit=1&limit=2. Kapitel 5 hat mit 400 geantwortet. Was antwortet die App?

Meilensteine

  1. delete_book, etwa vier Zeilen: die 404 für eine unbekannte ID, dann .pop().

  2. limit in list_books, etwa drei geänderte Zeilen: Füge weiter Bücher hinzu, solange limit gleich None ist oder die noch kürzer als limit ist.

  3. Die zwei Tests von Lektion 5 in tests/test_app.py, jeder mit fresh_client(): test_delete_unknown_book_is_404 sichert zu, dass DELETE /books/99 mit 404 antwortet, und test_limit_returns_the_first_books sichert zu, dass GET /books?limit=1 mit genau einem Buch antwortet, Buch 1. Ein dritter Test, der ein Buch löscht und dann eine 404 bekommt, wenn er nach diesem Buch fragt, lohnt sich.

Führe die Tests im Tab Shell aus:

python -m pytest -q

Jeder Test sollte bestehen, und die Zusammenfassungszeile endet mit 1 warning, dem Hinweis von anyio aus Lektion 2. Wenn du magst, führe noch einmal python pl_http.py uvicorn aus: Seine drei Antworten sind die, die Lektion 4 gezeigt hat.

Check my work sendet Anfragen über TestClient mit Büchern, die du noch nicht gesehen hast, wiederholt die Prüfungen der Lektionen 1 und 3 und prüft DELETE, unbekannte IDs bei GET, PUT und DELETE und limit. Dann führt es deine Tests dreimal aus, wie in Lektion 2: gegen deine eigene App, bei der alle bestehen müssen, und gegen zwei Versionen, die aus deinen Funktionen mit je einem Fehler gebaut sind, ein delete_book, das für eine unbekannte ID mit 204 antwortet, und ein list_books, das limit ignoriert. Gegen jede muss einer deiner Tests fehlschlagen.

Die App neben deinem Server

Die App stimmt jetzt bei jedem Verhalten, das ihre Funktionen entscheiden, mit deinem Server aus der Standardbibliothek überein: bei den , die sie wählen, bei location, bei den gespeicherten Büchern, bei der 404 für eine unbekannte ID und beim Ausschnitt durch limit. Wo sich die beiden noch unterscheiden, hat FastAPI entschieden, mit den Versionen, die dieser Kurs benutzt:

AnfrageDein ServerDie App
HEAD /books200, ohne Body405, allow: GET
DELETE /books405, allow: GET, HEAD, POST405, allow: GET
GET /books/abc404422
GET /books/301 auf /books307 auf eine absolute
ein Buch, als text/plain gekennzeichnet oder ohne content-type415422
, das sich nicht parsen lässt400422
?limit=0400422
?limit=1&limit=2400200, zwei Bücher

Diese Antworten zurückzuändern würde Code erfordern, der die eigenen Fehler von FastAPI abfängt, und den verlangt dieser Kurs nicht von dir. Deine Tests halten stattdessen die fest, die dir wichtig sind, und sie sagen dir, wenn eine neue Version von FastAPI eine davon ändert.

Klicke auf Check my work. Ein Vertrag hat jetzt zwei Implementierungen, und Kapitel 12 kehrt zu der zurück, die du von Hand gebaut hast: Du machst sie fertig, machst ihren requests-Client fertig und beweist, dass die beiden übereinstimmen.

Aufgabe

Mach die Leseliste in fertig: Schreibe delete_book, lass list_books limit beachten und schreibe die zwei Tests von Lektion 5.

In reading_app.py:

  1. delete_book(book_id) löst HTTPException(status_code=404, detail="no such book") aus, wenn STATE["books"] kein Buch mit dieser ID hat. Sonst entfernt es das Buch mit .pop(). Das mitgelieferte status_code=204 macht die Antwort zu einer 204 ohne Body.

  2. list_books(limit) gibt {"books": [...]} mit den ersten limit Büchern in der Reihenfolge der IDs zurück, oder mit jedem Buch, wenn limit gleich None ist. FastAPI hat schon geprüft, dass limit gleich None oder eine ganze Zahl von 1 bis 100 ist.

Behalte get_book, create_book und replace_book so, wie die Lektionen 1 und 3 sie hinterlassen haben, und jede @app-Zeile, jede def-Zeile und BookIn so, wie sie gekommen sind.

Ersetze in tests/test_app.py die beiden Platzhalter, wobei jeder seine Anfrage über fresh_client() sendet:

  1. test_delete_unknown_book_is_404: DELETE /books/99 antwortet mit 404.

  2. test_limit_returns_the_first_books: GET /books?limit=1 antwortet mit genau einem Buch, Buch 1.

Halte die Regeln aus Lektion 2 ein: import reading_app, kein from reading_app import ..., kein TestClient(...) und kein gespeichertes reading_app.app oben in der Datei und kein Import von socket, http, urllib, uvicorn, requests oder httpx. Du darfst weitere Tests hinzufügen.

Im führt python -m pytest -q jeden Test aus; alle sollten bestehen. python pl_http.py uvicorn ist zum Zuschauen da.

Klicke dann auf Check my work. Es füllt STATE über reset_state mit Büchern, die du noch nicht gesehen hast, und sendet mit TestClient Anfragen an die App in diesem Prozess: die Lesezugriffe aus Lektion 1, die Schreibzugriffe aus Lektion 3 mit , die das Modell akzeptiert, und solchen, die es ablehnt, DELETE eines Buchs, das existiert, und eines, das nicht existiert, GET und PUT einer unbekannten ID und limit ab 1 aufwärts. Außerdem ruft es deine direkt auf. Die eigenen Antworten von FastAPI, etwa 422 für ?limit=0 und 405 für HEAD /books, werden als die von FastAPI geprüft. Dann führt es deine Tests dreimal aus: gegen deine eigene App, bei der mindestens fünf bestehen müssen und keiner fehlschlagen darf, und gegen zwei Versionen der App, die aus deinen Funktionen mit je einem Fehler gebaut sind, ein delete_book, das für eine unbekannte ID mit 204 antwortet, und ein list_books, das limit ignoriert. Gegen jede muss mindestens ein Test fehlschlagen. Ein Lauf, der länger als 3 Sekunden dauert, wird angehalten und gemeldet. Nichts aus deinem Terminal wird bewertet.