6. FastAPI 13.06.2024¶
6.1. Einleitung¶
FastAPI ist ein modernes, schneller und webbasiertes Framework für das Erstellen von APIs mit Python (ab Version 3.7). Es basiert dabei auf Standard Python-Typanmerkungen.
6.2. FastAPI Allgemeines¶
6.2.1. Abhängigkeiten¶
Python3.11
pipenv
6.2.2. Installation¶
pipenv install
6.2.3. Starten der Anwendung¶
pipenv run python3.11 main.py
6.2.4. OpenAPI und Swagger¶
FastAPI generiert automatisch eine OpenAPI-Dokumentation für die erstellte API. Die Dokumentation kann unter http://localhost:8000/docs erreicht werden, alternativ auch mit einer alternativen Darstellung unter http://localhost:8000/redoc.
Dadurch ist es nicht nötig, zusätzlich zur API kontinuierlich die API-Dokumentation zu pflegen, da dies automatisch passiert.
Ein paar wesentliche Metainformationen über die jeweiligen Routen müssen jedoch dennoch im jeweiligen Decorator angegeben werden:
@router.put("/api/to-do/{list_id}", status_code=200, tags=["ToDo"], summary="Insert or update a ToDoList.")
In diesem Fall wird der Status-Code 200, die Tags „ToDo“ und die Zusammenfassung „Insert or update a ToDoList“ angegeben. Die Tags werden verwendet, um die jeweiligen API-Endpoints in der Dokumentation nach Tag zu gruppieren.
Zusätzlich kann durch response_model angegeben werden, welcher Typ von Daten zurückgegeben wird:
@router.get("/api/to-do", response_model=List[ToDoListDto], tags=["ToDo"], summary="Get all ToDoLists.")
Dadurch wird in der Swagger-Dokumentation eine Beispielantwort angezeigt, die dem Typ ToDoListDto entspricht.
Diese Typinformation kann auch in einem TypeScript-Client verwendet werden, welcher die API aufruft. Es ist möglich,
diesen automatisch generieren zu lassen, indem bspw. das Tool openapi-ts verwendet wird:
https://fastapi.tiangolo.com/de/advanced/generate-clients/#openapi-client-generatoren
6.2.5. pydantic¶
Pydantic ist eine Bibliothek, die es ermöglicht, Typen in Python zu validieren. Pydantic ist in Rust geschrieben und positioniert sich als eine der schnellsten Bibliotheken für die Datenvalidierung in Python.
Beispiel:
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
Im vorliegenden Beispiel wird ein User-Model definiert, welches die Attribute id, name, signup_ts und tastes
enthält.
Angenommen es wird ein User-Objekt mit den folgenden JSON-Daten erstellt:
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
b'cheese': 7,
'cabbage': '1',
},
}
user = User(**external_data)
Wenn die Validierung fehlschlägt wird eine ValidationError-Exception geworfen, in der Details über die fehlerhaften
Felder des Objekts enthalten sind.
Eine beispielhafte Verwendung findet sich auch in unserem Source Code der Todo-App:
@router.put("/api/to-do/{list_id}/{item_id}", tags=["ToDo"], summary="Insert or update a ToDoItem.")
def put_to_do_item(list_id: str, item_id: str, item: PutToDoItemDto):
Hier wird für den Endpunkt /api/to-do/{list_id}/{item_id} ein PutToDoItemDto-Objekt erwartet. Durch die Typenangabe
in der Parameterliste wird die Validierung automatisch durchgeführt.
6.2.6. uvicorn¶
Uvicorn ist ein leichtgewichtiger, schneller und asynchroner ASGI-Server in Python. Er wird verwendet, um asynchrone Python-Webanwendungen wie FastAPI-Anwendungen zu betreiben. Uvicorn zeichnet sich durch seine einfache Einrichtung, hohe Leistung und seine Unterstützung für moderne Python-Features wie Typ-Hinweise und asynchrones Warten aus.
ASGI (Asynchronous Server Gateway Interface) ist ein Spezifikationsstandard mit Unterstützung für asynchrone Programmierung. ASGI ermöglicht eine effiziente Handhabung von asynchronen Funktionen wie WebSockets und HTTP/2. Es dient als Schnittstelle zwischen asynchronen Webframeworks und Webservern, sodass Entwickler die Vorteile der asynchronen Programmierung in Python nutzen können, um skalierbare und leistungsfähige Webdienste zu erstellen.
6.3. FastAPI in TodoApp¶
models.py
In dem File
models.pywird Pydantic verwendet, um die benötigten Models zu definieren. Im folgenden ist exemplarisch die KlasseToDoItemDtozu sehen:class ToDoItemDto(BaseModel): id: str sequenceNumber: int name: str = Field(..., min_length=1, max_length=100) description: Optional[str] = Field(None, min_length=1, max_length=1000) done: bool
sieht man, dass die Klasse das BaseModel verwendet. Nun kann man z.B. bei name wird die Funktion Field() verwendet, um zusätzliche Metadaten und Validierungsregeln für die Felder eines Modells festzulegen.name muss eine Zeichenkette mit der Mindestlänge von 1 und eine maximale Länge von 100 haben. das
...ist ein Platzhalter und gibt an, dass das Feld erforderlich ist. Im Vergleich zur description wird hier alsdefault: Noneangegeben, was bedeutet, dass dieser Parameter nicht erforderlich ist.todo_controller.py
Im
todo_controllerwerden mit Hilfe von FastAPI eine Reihe von RESTful-API-Endpunkten definiert. Die Endpunkte verwalten die Todo-Listen und die ToDo-Elemente. Zunächst muss der Router initialisiert werden. Das geschieht mitrouter = APIRouter()
Dies initialisiert eine Instanz von APIRouter, damit die API-Routen definiert werden können. Nun kann man mit Hilfe der
router-Instanz zum Beispiel eine get-Anfrage stellen, um sich alle Todo-Listen anzeigen zu lassen.@router.get("/api/to-do", response_model=List[ToDoListDto], tags=["ToDo"], summary="Get all ToDoLists.") def get_to_do_lists(): return list(todo_lists.values())
Mit
@router.getkann man den API-Endpunkt definieren. der erste Parameter der folgt sagt, unter welcher Route dieser Endpunkt zu erreichen ist.response_modelgibt an, was zuückgegeben werden. Im Falle der obigen Abfrage, bekommt man eine Liste vonTodoListDtozurück.Um einen
delete-Endpunkt zu erstellen, kann man die@router.deleteAnnotation schreiben.@router.delete("`/api/to-do/{list_id}/{item_id}", tags=["ToDo"], summary="Delete a ToDoItem.") def delete_to_do_item(list_id: str, item_id: str): if list_id not in todo_lists: raise HTTPException(status_code=404, detail="ToDoList not found") todo_list = todo_lists[list_id] for item in todo_list.items: if item.id == item_id: todo_list.items.remove(item) for other_item in todo_list.items: if other_item.sequenceNumber > item.sequenceNumber: other_item.sequenceNumber -= 1 return todo_list raise HTTPException(status_code=404, detail="ToDoItem not found")
Hier sieht man wieder den Endpunkt
/api/to-do/{list_id}/{item_id}. In der Methode findet man zweiHTTPExceptions, die einen404-Errorwerfen, wenn entweder eine Todo-Liste oder ein Todo-Item nicht gefunden wurde.database.py
In der
database.py wird eine Datenbank verwendet, die bei jedem Neustart wieder eine neues leeres Dictionary initialisiert. Man kann natürlich auch eine konsistente Datenbank verwenden. Man könnte beispielsweise die Python-Bibliotheksqlalchemy` verwenden. Das ist ein Open-Source-SQL-Toolkit und eine Object-Relationa-Mapping Bibliothek. Da wir in unserer Anwendung die Funktionsweise für FastAPI darstellen wollten, war jedoch eine simple ‚ Datenbank‘ ausreichend.todo_lists: Dict[int, ToDoListDto] = {}
main.py
app = FastAPI( title="ToDo API", version="0.0.1" ) origins = [ "http://localhost", "http://localhost:5173", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(to_do_router) if __name__ == "__main__": uvicorn.run("main:app", host="localhost", port=8000, reload=True)
In der
main.pywird nun mit Hilfe vonapp = FastAPI()eine Instanz der FastAPI-Anwendung erstellt. der Parametertitlebeschreibt den Titel der API, der in der automatisch generierten Dokumentation angezeigt wird. der Parameterversionbeschreibt die Version der API.Danach wird durch
originseine Liste von URLs definiert, von denen aus die API aufgerufen werden darf. Danach wird durchapp.add_middlewaredie CORS-Middleware zur Anwendung hinzugefügt. im Folgenden werden die einzelnen Parameter dazu erklärt:allow_origins: Erlaubt die angegebenen URLsallow_credentials: Erlaubt das Senden von Cookies und anderen Anmeldeinformationen.allow_methods: Erlaubt mit[*]alle HTTP-Methoden, also Get, Post, Put, Delete, etc.allow_headers: Erlaubt alle HTTP-Headers
Dann muss noch der Router eingebunden werden durch
app.include_router(to_do_router). Dies fügt die Routen und die Logik des ToDo-Moduls hinzu.Im letzten Schritt wird dann die Anwendung in der
if __name__ == "__main__":ausgeführt. Hier wird dann mituvicorn.run()der Uvicron-Server gestartet und die FastAPI-Anwendung ausgeführt."main:app": verweist auf dieapp-Instanzhost="localhost: führt den Server auf „localhost“ ausport=8000: zeigt, auf welchen Port der Sever zu erreichen istreload=True: aktiviert den automatischen Neustart des Servers bei Codeänderungen
6.4. Vorteile¶
Vergleichsweise schnell:
Framework |
Test 1 |
Test 2 |
|---|---|---|
FastAPI |
17 ms |
6.2 ms |
Django |
517.2 ms |
5.834 |
Flask |
507.2 |
508.9 |
Schnelle Entwicklung aufgrund der geringen Anzahl an LOC
Einfaches Testen mithilfe des TestClients on FastAPI:
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
Automatische API-Dokumentation mit Swagger
Out-of-box-Unterstützung von asynchronem Code mithilfe der
async/await-Keywords
6.5. Nachteile¶
In FastAPI ist alles an die FastAPI-App gebunden, sodass die main.py-Datei schnell überladen sein kann
Das Ökosystem von FastAPI entwickelt sich noch im Vergleich zu etablierteren Frameworks wie Django
Abhängigkeiten zu anderen Packages
Im Vergleich zu einigen anderen Frameworks bietet FastAPI weniger Projekttemplates und Konventionen