Ga naar hoofdinhoud

Eén route voor veel pagina's: path-parameters

Hier bouw je op verder: Parameters, F-strings, Dictionaries en Door een dictionary lopen

Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.

Elke pagina van je site heeft nu een eigen route, zoals / en /about. Stel dat je een site over dieren maakt, met een pagina per dier. Dan zou het zo beginnen:

@app.get("/kat", response_class=HTMLResponse)
async def kat():
return "<h1>Over dieren</h1><p>Een kat spint als hij tevreden is.</p>"

@app.get("/hond", response_class=HTMLResponse)
async def hond():
return "<h1>Over dieren</h1><p>Een hond kwispelt als hij blij is.</p>"

@app.get("/vis", response_class=HTMLResponse)
async def vis():
return "<h1>Over dieren</h1><p>Een vis ademt met kieuwen.</p>"

Deze drie hoef je niet over te nemen. Ze laten zien waar het knelt: drie functies die bijna hetzelfde doen. Bij vijftig dieren schrijf je vijftig functies, en wil je een link terug naar Home, dan pas je er vijftig aan. Alleen het stuk achter de / en de zin verschillen.

Eén route met een plekhouder​

Zet dit endpoint onder je andere endpoints:

@app.get("/dier/{naam}")
async def dier(naam: str):
return {"je_vroeg_om": naam}

Ga naar http://127.0.0.1:8000/dier/kat. Je krijgt {"je_vroeg_om":"kat"}. Op /dier/olifant krijg je {"je_vroeg_om":"olifant"}.

{naam} tussen accolades is een plekhouder in het pad: een path-parameter. Wat er in de URL op die plek staat, komt binnen als de parameter met dezelfde naam. Met naam: str zeg je dat het tekst is; wat zo'n type doet, zie je in de volgende les. Eén route vangt zo elk adres dat begint met /dier/.

De tekst opzoeken​

Wat per dier verschilt, zet je in een dictionary, met de naam uit de URL als sleutel. Vervang je endpoint door deze versie, met de dictionary erboven:

DIEREN = {
"kat": "Een kat spint als hij tevreden is.",
"hond": "Een hond kwispelt als hij blij is.",
"vis": "Een vis ademt met kieuwen.",
}

@app.get("/dier/{naam}", response_class=HTMLResponse)
async def dier(naam: str):
tekst = DIEREN.get(naam, "Dit dier staat nog niet op de site.")
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Over dieren</h1>
<p>{tekst}</p>
<a href="/">Terug naar Home</a>
</body>
</html>
"""
  1. Regel 1-5:

    De dictionary DIEREN staat buiten de functie, bovenaan bij je andere code of vlak boven het endpoint. Een dier erbij is één regel erbij.

  2. Regel 9:

    DIEREN.get(naam, …) geeft de zin bij naam. Staat naam niet in de dictionary, dan krijg je de tekst na de komma.

  3. Regel 10-19:

    De f voor de string zet de waarde van tekst op de plek van {tekst}, net als bij print(f"…").

Zet in de HTML de zin uit je eigen dictionary, en niet naam zelf. Wat in de URL staat, typt de bezoeker, en dat kan ook HTML zijn. Waarom dat gevaarlijk is, zie je later in de route Veiligheid, bij HTML van een bezoeker: tekst uit de URL.

Testen​

  1. Ga naar /dier/kat, /dier/hond en /dier/vis. Elk dier krijgt zijn eigen zin, uit één functie.
  2. Ga naar /dier/olifant. Je ziet "Dit dier staat nog niet op de site."
  3. Ga naar /dier/Kat, met een hoofdletter. Ook dat dier kent je site niet: een sleutel telt elke letter mee.

Hoe je zelf een 404 stuurt voor iets dat er niet is, leer je later, in Een 404 sturen: als het item niet bestaat. Daarmee kun je ook /dier/olifant een nette 404 geven.

Controlevraag
Je server draait met de main.py van deze les, en je gaat met Netwerk open naar /dier/olifant. Welke statuscode staat er?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Er gaat iets mis​

Schrijf je DIEREN[naam] in plaats van DIEREN.get(naam, …), dan werkt /dier/kat wel. Maar /dier/olifant geeft Internal Server Error, met de statuscode 500: er ging iets mis in jouw code. In je terminal staat:

KeyError: 'olifant'

Met vierkante haken vraag je een sleutel op die er moet zijn. get geeft bij een onbekende sleutel de tekst na de komma, en je server blijft heel.

Zo ziet je main.py er nu uit

Dit is je hele main.py na deze les, zonder wat je in de opdrachten maakte.

from fastapi import FastAPI
from fastapi.responses import FileResponse, HTMLResponse

app = FastAPI()

@app.get("/")
async def home():
return FileResponse("static/pages/home.html")

@app.get("/info")
async def info():
return {"naam": "Jouw Naam", "leeftijd": 16}

@app.get("/pagina", response_class=HTMLResponse)
async def pagina():
return """
<!DOCTYPE html>
<html>
<head>
<title>Mijn Pagina</title>
</head>
<body>
<h1>Hallo!</h1>
<p>Dit is mijn eerste HTML pagina.</p>
</body>
</html>
"""

@app.get("/styled", response_class=HTMLResponse)
async def styled():
return """
<!DOCTYPE html>
<html>
<head>
<title>Met CSS</title>
<style>
h1 { color: blue; }
p { color: gray; }
</style>
</head>
<body>
<h1>Gestylede pagina</h1>
<p>Deze tekst is grijs.</p>
</body>
</html>
"""

@app.get("/about")
async def about():
return FileResponse("static/pages/about.html")

DIEREN = {
"kat": "Een kat spint als hij tevreden is.",
"hond": "Een hond kwispelt als hij blij is.",
"vis": "Een vis ademt met kieuwen.",
}

@app.get("/dier/{naam}", response_class=HTMLResponse)
async def dier(naam: str):
tekst = DIEREN.get(naam, "Dit dier staat nog niet op de site.")
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Over dieren</h1>
<p>{tekst}</p>
<a href="/">Terug naar Home</a>
</body>
</html>
"""

Opdrachten​

Opdracht 1: Predict - Welke waarde komt binnen?​

@app.get("/groet/{naam}")
async def groet(naam: str):
return {"bericht": f"Hallo {naam}"}
Voorspel
Op /groet/Sarah groet de server Sarah. Wat krijg je op /groet/naam?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdracht 2: Run​

Maak de dictionary DIEREN en het endpoint /dier/{naam}. Zet er een vierde dier bij, zonder een nieuwe functie. Krijgt het op /dier/<jouw dier> meteen een eigen pagina?

Opdracht 3: Investigate - Andere naam​

Verander de parameter in je functie van naam naar dier_naam, maar laat {naam} in het pad staan:

@app.get("/dier/{naam}", response_class=HTMLResponse)
async def dier(dier_naam: str):

Verander DIEREN.get(naam, …) ook in DIEREN.get(dier_naam, …). Wat zie je op /dier/kat?

Antwoord

Je browser toont een weigering met "loc":["query","dier_naam"] en "msg":"Field required", en in je terminal staat de statuscode 422.

FastAPI koppelt op naam, niet op volgorde. Het ziet {naam} in het pad en zoekt een parameter die zo heet; die is er niet. Tegelijk vraagt je functie om dier_naam, en dat staat nergens in het pad. Daarom vraagt FastAPI erom na een vraagteken in de URL: /dier/kat?dier_naam=hond toont de zin over de hond. Dat is niet wat je bedoelde. Zet beide namen weer gelijk.

Opdracht 4: Investigate - Zonder naam​

Wat krijg je op /dier, zonder / en zonder naam erachter? En op /dier/kat/hond?

Antwoord

Allebei {"detail":"Not Found"}, een 404. De route /dier/{naam} past alleen op een adres met precies één stuk na /dier/. Bij /dier ontbreekt dat stuk, en bij /dier/kat/hond zijn het er twee. Geen enkel endpoint past, dus FastAPI stuurt zelf een 404.

Opdracht 5: Make - Een eigen verzameling​

Maak een route /vak/{naam} met een dictionary van drie schoolvakken, met bij elk vak één zin. Een vak dat er niet in staat, krijgt "Dit vak ken ik niet."

Tip

Het is dezelfde vorm als /dier/{naam}: een dictionary, get met een tekst na de komma, en een f-string met de zin erin.

Antwoord
VAKKEN = {
"wiskunde": "Bij wiskunde reken je met getallen en vormen.",
"biologie": "Bij biologie leer je hoe levende dingen werken.",
"informatica": "Bij informatica leer je programmeren.",
}

@app.get("/vak/{naam}", response_class=HTMLResponse)
async def vak(naam: str):
tekst = VAKKEN.get(naam, "Dit vak ken ik niet.")
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Mijn vakken</h1>
<p>{tekst}</p>
</body>
</html>
"""

Maak een pagina /dieren met een link naar de pagina van elk dier in DIEREN. Zet er een dier bij in de dictionary: staat het daarna ook in de lijst, zonder dat je de pagina aanpast?

Tip

Loop met for naam in DIEREN: langs de sleutels, en plak per dier een regel HTML aan een string vast, zoals <li><a href="/dier/kat">kat</a></li>.

Antwoord
@app.get("/dieren", response_class=HTMLResponse)
async def dieren():
links = ""
for naam in DIEREN:
links += f'<li><a href="/dier/{naam}">{naam}</a></li>'
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Alle dieren</h1>
<ul>{links}</ul>
</body>
</html>
"""

Hier mag naam wel in de HTML: het komt uit je eigen dictionary, niet uit de URL. Een dier erbij in DIEREN geeft meteen een link erbij, en die link komt uit bij dezelfde route /dier/{naam}.