0%

Wat een framework voor je doet · oefening

Laat een model de body controleren

In hoofdstuk 5 bewaakten twee elke schrijfactie die je server accepteerde, en ze staan nog in reading_server.py. body_error keek naar het verzoek, en book_error naar de geparste gegevens:

FunctieWeigert alsStatus
body_error(request)content-type niet application/json is415
body_error(request)de body geen is400
book_error(data)het geen JSON- is422
book_error(data)titel of auteur ontbreekt422
book_error(data)titel of auteur geen niet-lege is422

handle riep ze in die volgorde aan, en create_book en replace_book draaiden alleen als beide None teruggaven. De app doet beide taken met het model dat je in les 1 las, BookIn, en één annotatie in elke def-regel: book: BookIn.

Het model controleert eerst

Als POST /books binnenkomt, leest de body, parset die als JSON en controleert het resultaat tegen BookIn, allemaal voordat create_book draait. Je functie wordt alleen aangeroepen met een body die door de controle kwam, en ze ontvangt die body als een BookIn-object. Het object geeft zijn gecontroleerde als attributen, geschreven met een punt: book.title en book.author. Het is geen , dus book["title"] werpt op.

Op de versies die deze cursus gebruikt, weigert het model:

  • een body die geen JSON-object is, of waarin title of author ontbreekt;

  • een veld dat geen string is: 7 en true worden geweigerd, en nooit omgezet in "7" of "true";

  • een veld zonder enig teken behalve spaties, zoals "" of " ". Daar vraagt pattern=r"\S" om: één teken, waar dan ook, dat geen spatie, tab, regeleinde of ander soort witruimte is.

De rest accepteert het zoals het is. " Ærø " houdt zijn spaties, "7" is een string, en een titel van 3000 tekens heeft geen limiet om te overschrijden. Velden die het model niet noemt, zoals "id": 99, worden weggelaten voordat je functie de body ziet, dus een client kan het id van een boek niet kiezen.

De body van een POST is {"title": "Dune"}. Wanneer draait create_book?

De 422 van FastAPI lezen

Op die body antwoordde je server uit hoofdstuk 5 {"error": "author is missing"}. De app antwoordt 422 met deze body:

{"detail":[{"type":"missing","loc":["body","author"],"msg":"Field required","input":{"title":"Dune"}}]}

detail is een , met één item per probleem. In elk item noemt type de regel die niet werd gehaald (hier missing; string_type voor een getal waar een string hoort; string_pattern_mismatch voor een lege titel), zegt loc waar (["body", "author"], het veld author van de body), zegt msg het in woorden, en herhaalt input de waarde die werd gecontroleerd. Jouw fout was één zin voor een mens. Deze is data waar een clientprogramma op kan reageren, veld voor veld. Beide hebben 422.

Waar het framework anders beslist

De meeste slechte body’s krijgen de status die ze van je server kregen. Deze niet:

VerzoekHoofdstuk 5De FastAPI-app
een geldig JSON-boek met label text/plain415422
een geldig JSON-boek zonder content-type415422
JSON die niet te parsen is400422
bytes die geen UTF-8 zijn400400, {"detail":"There was an error parsing the body"}
een boek met label application/merge-patch+json415geaccepteerd, 201
PUT /books/9999, zo’n boek bestaat niet, met een kapotte body404422

FastAPI parset een body alleen als JSON wanneer de content-type JSON zegt: application/json, of een application/-type waarvan het subtype op +json eindigt, zoals application/merge-patch+json. Elk ander label, of geen label, laat de body als tekst staan, en het model weigert tekst omdat het geen object is; de 422 zegt dat het model een object verwachtte. Er is geen aparte 415. Kapotte JSON is ook een 422, met het type json_invalid. Bytes die niet als UTF-8 te decoderen zijn, zijn het enige bodyprobleem waarop FastAPI met 400 antwoordt, en de detail is daar een gewone string.

De rij met PUT gaat over volgorde. Je server zocht het id op voordat hij de body las, dus een onbekend id was een 404, wat de body ook bevatte. FastAPI controleert de body voordat het replace_book aanroept, dus een slechte body is een 422 voordat je functie iets kan opzoeken. Vanaf hoofdstuk 7 vroeg je server ook vóór beide om een token; deze app heeft geen tokens.

Twee statussen uit hoofdstuk 5 komen hier nooit voor. 411 en 413 waren framingwerk in je read_body; TestClient stuurt altijd een lengte, en de app stelt geen limiet aan de grootte.

Een geldig JSON-boek heeft het label text/plain. Hoofdstuk 5 antwoordde 415. Wat antwoordt de app?

PUT /books/9999, waarbij geen boek 9999 bestaat, heeft een kapotte body. Hoofdstuk 5 antwoordde 404. Wat antwoordt de app?

201 en location

De regel boven create_book is @app.post("/books", status_code=201): elke waarde die de functie teruggeeft, gaat de deur uit met status 201. De def-regel vraagt ook om response: Response. FastAPI geeft een responseobject mee, en elke header die je erop zet, wordt naar het antwoord gekopieerd:

response.headers["location"] = f"/books/{book_id}"

Geef het opgeslagen boek terug, en FastAPI stuurt het als JSON-body van de 201, met jouw location. replace_book heeft geen van beide nodig: een vervangen boek is een gewone 200.

Je opdracht

Schrijf de body’s van create_book en replace_book in reading_app.py:

  1. create_book(book, response) neemt het id uit STATE["next_id"] en telt één op bij STATE["next_id"], slaat {"id": ..., "title": book.title, "author": book.author} onder dat id op in STATE["books"], zet location op /books/ plus het id, en geeft het opgeslagen boek terug.

  2. replace_book(book_id, book) werpt HTTPException(status_code=404, detail="no such book") op voor een id dat STATE["books"] niet bevat. Anders slaat het het boek op onder book_id, het id uit de , en geeft het terug.

Schrijf geen eigen controles op title of author: het model heeft dat al gedaan, en een regel van jou die iets weigert wat het model accepteert, zoals een titel met spaties eromheen, zou de app laten verschillen van zijn eigen model. Om één verzoek te proberen in het tabblad Shell, met de importwaarschuwing van de testclient uitgezet:

python -W ignore -c "from fastapi.testclient import TestClient; import reading_app; r = TestClient(reading_app.app).post('/books', json={'title': 'Piranesi', 'author': 'Susanna Clarke'}); print(r.status_code, r.headers['location'], r.json())"

Het drukt 201 /books/3 {'id': 3, 'title': 'Piranesi', 'author': 'Susanna Clarke'} af. Druk daarna op Check my work. Tot nu toe is geen enkele byte van de antwoorden van je app over een gegaan; les 4 draait hem onder een echte server voor drie verzoeken.

Opdracht

Schrijf de body’s van create_book en replace_book in reading_app.py, onder de regels @app.post en @app.put die er al staan.

  1. create_book(book, response) neemt het nieuwe id uit STATE["next_id"] en telt één op bij STATE["next_id"]. Het slaat {"id": ..., "title": book.title, "author": book.author} onder dat id op in STATE["books"], zet response.headers["location"] op /books/ gevolgd door het id, en geeft het opgeslagen boek terug. De meegeleverde status_code=201 maakt van het antwoord een 201.

  2. replace_book(book_id, book) werpt HTTPException(status_code=404, detail="no such book") op als STATE["books"] geen boek met dat id heeft. Anders slaat het {"id": book_id, "title": book.title, "author": book.author} op onder book_id, het id uit de , en geeft het terug.

book is een BookIn die al heeft gecontroleerd: lees book.title en book.author, en voeg er geen eigen controles op toe. Laat elke @app-regel, elke def-regel en BookIn zoals ze meekwamen.

In de draait python -m pytest -q nog steeds je tests, en het commando van één regel python -W ignore -c ... uit de les stuurt één POST.

Druk daarna op Check my work. Het vult STATE via reset_state met boeken die je niet hebt gezien en een next_id van 41, en stuurt daarna POST- en PUT-verzoeken de app in, in hetzelfde proces, met TestClient, met titels en auteurs die je niet hebt gezien. Een nieuw boek moet 201 krijgen, een location die zijn id noemt, en het opgeslagen boek in de body, en next_id moet opschuiven; een PUT moet het id uit de URL houden; een PUT naar een onbekend id moet 404 krijgen. Body’s die het model weigert, moeten FastAPI’s 422 krijgen, of zijn 400 voor bytes die geen zijn, en niets veranderen; die het model accepteert, zoals een titel met spaties eromheen, moeten worden opgeslagen zoals ze zijn. Het roept ook je twee rechtstreeks aan met BookIn-. Niets uit je terminal wordt beoordeeld.