0%

Wat een framework voor je doet · eindproject

Eindproject: Project: de leeslijst in FastAPI

Je app beantwoordt GET op beide paden, schrijft boeken vanuit een gecontroleerd model, en draait zijn tests in hetzelfde proces. Twee gedragingen van je server uit hoofdstuk 5 ontbreken nog: DELETE /books/{id}, en de query limit op GET /books. Dit project voegt beide toe, met twee tests die merken wanneer een van beide stukgaat. Elke @app-regel en BookIn staan al in reading_app.py; jij schrijft body’s van en tests.

Wat de app moet doen

  • DELETE /books/{id} verwijdert het boek en antwoordt 204 zonder body. Een onbekend id krijgt 404, zoals overal in de app.

  • GET /books?limit=N antwoordt met de eerste N boeken op volgorde van id, voor N van 1 tot en met 100. Zonder limit toont het elk boek, en een limiet die groter is dan het aantal boeken toont ook elk boek.

204 met niets

De regel boven delete_book is @app.delete("/books/{book_id}", status_code=204). Een 204 zegt dat het verzoek is geslaagd en dat er niets terug te sturen is. In hoofdstuk 5 bouwde je dat antwoord met de hand, zonder body en zonder content-length. Hier stuurt het: wat delete_book ook teruggeeft, de 204 gaat de deur uit zonder body en zonder content-length. Op deze versies heeft het nog steeds content-type: application/json, een label zonder iets om te labelen, wat onschuldig is. De taak van de functie is dus de beslissing. Bestaat zo’n boek? Zo niet, werp dan dezelfde HTTPException op als get_book. Zo ja, verwijder het met .pop(), zoals de delete_book van je server deed.

delete_book eindigt met return {"deleted": 7} onder status_code=204. Wat wordt er gestuurd?

Een queryparameter die FastAPI controleert

Lees nu de rest van de def-regel van list_books:

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

Lees die zoals je book_id: int las. FastAPI haalt limit uit de querystring, maakt er een int van, en controleert dat het minstens 1 is (ge, greater than or equal, groter dan of gelijk aan) en hooguit 100 (le, less than or equal, kleiner dan of gelijk aan), allemaal voordat je functie draait. int | None met default=None betekent dat een verzoek het mag weglaten, en dan is limit None. Je functie krijgt dus alleen ooit None of een geheel getal van 1 tot en met 100, en heeft geen eigen controles nodig.

De rest beantwoordt FastAPI zelf, en niet altijd zoals je server deed:

  • ?limit=0, ?limit=101 en ?limit=two krijgen 422. Je server antwoordde 400.

  • ?limit= zonder iets erachter krijgt 422. Je server las een lege als geen limiet en toonde elk boek.

  • ?limit=1&limit=2 krijgt 200 met twee boeken: FastAPI houdt de laatste waarde van een herhaalde . Je server weigerde de herhaling met 400.

GET /books?limit=1&limit=2. Hoofdstuk 5 antwoordde 400. Wat antwoordt de app?

Mijlpalen

  1. delete_book, ongeveer vier regels: de 404 voor een onbekend id, daarna .pop().

  2. limit in list_books, ongeveer drie gewijzigde regels: blijf boeken toevoegen zolang limit None is of de nog korter is dan limit.

  3. De twee tests van les 5 in tests/test_app.py, elk met fresh_client(): test_delete_unknown_book_is_404 controleert dat DELETE /books/99 404 antwoordt, en test_limit_returns_the_first_books controleert dat GET /books?limit=1 antwoordt met precies één boek, boek 1. Een derde test die een boek verwijdert en daarna 404 krijgt als hij om dat boek vraagt, is de moeite waard om toe te voegen.

Draai de tests in het tabblad Shell:

python -m pytest -q

Elke test hoort te slagen, en de samenvattingsregel eindigt met 1 warning, de anyio-melding uit les 2. Als je wilt, draai je python pl_http.py uvicorn opnieuw: de drie antwoorden zijn de antwoorden die les 4 liet zien.

Check my work stuurt verzoeken via TestClient met boeken die je niet hebt gezien, herhaalt de controles van les 1 en 3, en controleert DELETE, onbekende id’s bij GET, PUT en DELETE, en limit. Daarna draait het je tests drie keer, zoals in les 2: tegen je eigen app, waar ze allemaal moeten slagen, en tegen twee versies die uit je functies zijn gebouwd met elk één fout, een delete_book die 204 antwoordt voor een onbekend id en een list_books die limit negeert. Tegen elk ervan moet een van je tests falen.

De app naast je server

De app komt nu overeen met je server uit de standaardbibliotheek bij elk gedrag waarover zijn functies beslissen: de statussen die ze kiezen, location, de opgeslagen boeken, 404 voor een onbekend id, en het stuk dat limit afsnijdt. Waar de twee nog verschillen, besliste FastAPI, op de versies die deze cursus gebruikt:

VerzoekJe serverDe app
HEAD /books200, geen body405, allow: GET
DELETE /books405, allow: GET, HEAD, POST405, allow: GET
GET /books/abc404422
GET /books/301 naar /books307 naar een absolute
een boek met label text/plain, of zonder content-type415422
die niet te parsen is400422
?limit=0400422
?limit=1&limit=2400200, twee boeken

Die antwoorden terugdraaien zou code vragen die de eigen fouten van FastAPI afvangt, en die vraagt deze cursus je niet te schrijven. Je tests leggen in plaats daarvan de antwoorden vast die voor jou belangrijk zijn, en ze vertellen het je als een nieuwe versie van FastAPI er een verandert.

Druk op Check my work. Eén contract heeft nu twee implementaties, en hoofdstuk 12 keert terug naar de implementatie die je met de hand bouwde: je maakt hem af, maakt de requests-client ervan af, en bewijst dat de twee het eens zijn.

Opdracht

Maak de leeslijst in af: schrijf delete_book, laat list_books rekening houden met limit, en schrijf de twee tests van les 5.

In reading_app.py:

  1. delete_book(book_id) werpt HTTPException(status_code=404, detail="no such book") op als STATE["books"] geen boek met dat id heeft. Anders verwijdert het het boek met .pop(). De meegeleverde status_code=204 maakt van het antwoord een 204 zonder body.

  2. list_books(limit) geeft {"books": [...]} terug met de eerste limit boeken op volgorde van id, of elk boek als limit None is. FastAPI heeft al gecontroleerd dat limit None is of een geheel getal van 1 tot en met 100.

Laat get_book, create_book en replace_book zoals les 1 en 3 ze achterlieten, en elke @app-regel, def-regel en BookIn zoals ze meekwamen.

Vervang in tests/test_app.py de twee plaatshouders, die elk hun verzoek via fresh_client() sturen:

  1. test_delete_unknown_book_is_404: DELETE /books/99 antwoordt 404.

  2. test_limit_returns_the_first_books: GET /books?limit=1 antwoordt met precies één boek, boek 1.

Houd je aan de regels van les 2: import reading_app, geen from reading_app import ..., geen TestClient(...) of bewaarde reading_app.app bovenaan het bestand, en geen import van socket, http, urllib, uvicorn, requests of httpx. Je mag meer tests toevoegen.

In de draait python -m pytest -q elke test; ze horen allemaal te slagen. python pl_http.py uvicorn is er om naar te kijken.

Druk daarna op Check my work. Het vult STATE via reset_state met boeken die je niet hebt gezien en stuurt verzoeken de app in, in hetzelfde proces, met TestClient: de leesverzoeken van les 1, de schrijfverzoeken van les 3 met die het model accepteert en waarden die het weigert, DELETE van een boek dat bestaat en van een boek dat niet bestaat, GET en PUT van een onbekend id, en limit vanaf 1. Het roept ook je rechtstreeks aan. FastAPI’s eigen antwoorden, zoals 422 voor ?limit=0 en 405 voor HEAD /books, worden als die van FastAPI gecontroleerd. Daarna draait het je tests drie keer: tegen je eigen app, waar er minstens vijf moeten slagen en geen enkele mag falen, en tegen twee versies van de app die uit je functies zijn gebouwd met elk één fout, een delete_book die 204 antwoordt voor een onbekend id en een list_books die limit negeert. Tegen elk ervan moet minstens één test falen. Een run die langer duurt dan 3 seconden wordt gestopt en gemeld. Niets uit je terminal wordt beoordeeld.