Zum Inhalt

FastApi

Das Framework FastApi zecihnet sich durch eine herrvorragende Performance und die Unterstützung der EndPoint-Entwicklung aus. FastAPI baut auf dem Starlette-Webserver auf und enthält Funktionen, die das Erstellen von Webanwendungen erleichtern, z. B. automatische Datenvalidierung, Fehlerbehandlung und interaktive API-Dokumente.

Zahlreiche Erweiterungen wie:

  • Pydantic
  • SqlAlchemy
  • SqlModel
  • MongoEngine, Beanie
  • ...

sind verfügbar.

Homepage

Installation

>_
pip install fastapi

Zusätzlich sollte der ASGI-Server uvicorn installiert werden:

>_
pip install uvicorn

Entwicklung einer App

Erstes Beispiel

Der nachfolgende Code definiert die Funktion async def root():, die mit dem Dekorator @app.get("/") ausgezeichnet ist:

  • Methode GET
  • Endpoint-Pfad ROOT

first.py

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}
Download: File

Um das Skript auszuführen, muss es mit fastapi aufgerufen werden:

>_
py -m fastapi dev first.py 
Ausgabe
INFO     Using path first.py
INFO     Resolved absolute path p:\py\first.py
INFO     Searching for package file structure from directories with __init__.py files
INFO     Importing from p:\py

╭─ Python module file ─╮
│                      │
│  🐍 first.py         │
│                      │
╰──────────────────────╯

INFO     Importing module first
INFO     Found importable FastAPI app

╭─ Importable FastAPI app ─╮
│                          │
│  from first import app   │
│                          │
╰──────────────────────────╯

INFO     Using import string first:app

╭────────── FastAPI CLI - Development mode ───────────╮
│                                                     │
│  Serving at: http://127.0.0.1:8000                  │
│                                                     │
│  API docs: http://127.0.0.1:8000/docs               │
│                                                     │
│  Running in development mode, for production use:   
│                                                     │
│  fastapi run                                        │
│                                                     │
╰─────────────────────────────────────────────────────╯

INFO:     Will watch for changes in these directories: ['P:\\docs\\_frameworks\\_fastapi\\py']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [27872] using WatchFiles
INFO:     Started server process [2020]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Die App kann aber auch explizit mit dem uvicorn-Werbserver gestartet werden:

>_
uvicorn first:app --host 0.0.0.0 --port 80 
Dabei werden die IP-Adressen des Rechners mit dem Port 80 verbunden.

Ausgabe
INFO:     Started server process [30840]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)

Dieser Aufruf lässt sich auch mit VS Code in einer launch.json` konfigurieren:

launch.json
{
    "name": "Python-Debugger: FastAPI",
            "type": "debugpy",
            "request": "launch",
            "module": "uvicorn",
            "args": [
                "first:app",
                "--reload"
            ],
            "jinja": true
        }

uvicorn

Damit das Skript beim Start automatisch den Webserver startet, muss es wie folgt erweitert werden:

first_uvi.py

from fastapi import FastAPI
import uvicorn

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)
Download: File

Ausgabe
INFO:     Started server process [35812]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)

Browser-Client

Diese erste App stellt den GET-Endpoint \ zur Verfügung. Ein Aufruf im Browser stößt diesen Request an und die App liefert durch Aufruf der Funktion root() den Response aus:

first_rew_res

Request und Response im Webbrowser

docs

Die FastApi-App kann mit dem Endpoint /docs die komplette API-Schnittstelle, die der Spezifikation von "openapi" genügt zeigen:

first_get1

Docs-Seite der Api-Schnitstellen

first_first2

Docs-Seite der Api-Schnittstellen - Ausgeführter Request

Jinja2-Engine

Wenn gerenderte Html-Seiten über den Endpoint ausgeliefert werden sollen, dann bietet sich der Einsatz der Jinja2-Engine an. Die Grundlagen sind in Kapitel beschrieben.

Das folgende Skript realsiert die gleiche Aufgabe wie das Skript mit dem Flask-Framework:

site_html_1.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Webseite mit HTML-Responses
# ------------------------------
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
    name = ['Rosalia','Otto']
    return templates.TemplateResponse(
        request=request, name="index.html.jinja", 
        context={
            "title=":'Welcome', 
            "members":name
        }
    )    

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)
Download: File

Formular-Handling

Für die POST-Methode wird - anders als in Flask einen separate Funktion definiert, die mit dem Decorator @app.post ausgezeichnet wird. Hier wird analog zum Formular-Beispiel mit Flask gezeigt:

site_html_form.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Webseite mit HTML-Responses
# Formular
# ------------------------------
from fastapi import FastAPI, Request
from fastapi import Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

from typing import Annotated
import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/login/", response_class=HTMLResponse)
async def g_login(request: Request):
    return templates.TemplateResponse(
        request=request, name="login.html.jinja", 
        context={
            "title=":'Login-Seite'
        }
    )    

@app.post("/login/")        
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
    return {"username": username}


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)
Download: File

Hier wird in der POST-Methode jedes Formularelement zusätzlich mit "Annotated" (1) verbunden.

  1. Python-Annotationen (auch bekannt als Typ-Hinweise oder Type Hints) sind eine Möglichkeit, den Datentyp von Variablen und Funktionsparametern in Python anzugeben.

Formular-Handling mit Pydantic

Um die Übergabe der Daten zu zusätzlichen Testroutinen zu versehen und die eigentliche Daten-Rückgabe aus dem Formular zu vereinfachen, kann die Datenklasse pydantic.Basemodel eingesetzt werden (siehe Pydantic)

In einem ersten Schritt wird das Datenmodel, das später im Formular bearbeitet werden soll, definiert:

Datenmodell
class User(BaseModel):
    username: str # (1)
    password : str

  1. lässt sich mit Field(meta={"key1":"val1","key2":"val2", ... }) beliebig erweitern (siehe TIP)

Die POST-Methode kann nun direkt das Datenobjekt User(Basemodel) übernehmen:

Formdata
@app.post("/login/")
async def login(data: Annotated[User, Form()]):
    print(data.model_json_schema()) #(1)
    return data   

  1. An dieser Stelle kann das gesamte Modell-Schema als json-Objekt ausgegeben werden.

Das gesamte Skript:

site_html_formdata.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Webseite mit HTML-Responses
# Formular
# ------------------------------
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

from typing import Annotated
import uvicorn

app = FastAPI()
templates = Jinja2Templates(directory="templates")

class User(BaseModel):
    username: str
    password : str


@app.get("/login/", response_class=HTMLResponse)
async def g_login(request: Request):
    return templates.TemplateResponse(
        request=request, name="login.html.jinja", 
        context={
            "title=":'Login-Seite'
        }
    )    

@app.post("/login/")
async def login(data: Annotated[User, Form()]):
    print(data.model_json_schema())
    return data        

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)
Download: File

Tip

In einem nächsten Schritt könnte mit Hilfe von Jinja2-Makros auf der Basis des json-Modells das Formular automatisch aus dem Template generiert werden!! Dazu erweitert man im pydantic-Modell jedes Element mit Hilfe von pydantic.Field().

pydantic-Modelle lassen sich auch dynamisch erzeugen!