# FastAPI 13.06.2024 ## 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. ## FastAPI Allgemeines ### Abhängigkeiten - Python3.11 - pipenv ### Installation - `pipenv install` ### Starten der Anwendung - `pipenv run python3.11 main.py` ### 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: ```python @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: ```python @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 ### 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: ```python 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: ```python 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: ```python @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. ### 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. - [Uvicorn](https://www.uvicorn.org/) - [ASGI-Doku](https://asgi.readthedocs.io/en/latest/) ## FastAPI in TodoApp 1. **models.py** In dem File `models.py` wird Pydantic verwendet, um die benötigten Models zu definieren. Im folgenden ist exemplarisch die Klasse `ToDoItemDto` zu sehen: ```python 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 als `default: None` angegeben, was bedeutet, dass dieser Parameter nicht erforderlich ist. 2. **todo_controller.py** Im `todo_controller` werden 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 mit ```python router = 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. ```python @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.get` kann man den API-Endpunkt definieren. der erste Parameter der folgt sagt, unter welcher Route dieser Endpunkt zu erreichen ist. `response_model` gibt an, was zuückgegeben werden. Im Falle der obigen Abfrage, bekommt man eine Liste von `TodoListDto` zurück. Um einen `delete`-Endpunkt zu erstellen, kann man die `@router.delete` Annotation schreiben. ```python @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 zwei `HTTPExceptions`, die einen `404-Error`werfen, wenn entweder eine Todo-Liste oder ein Todo-Item nicht gefunden wurde. 3. **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-Bibliothek `sqlalchemy` 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. ```python todo_lists: Dict[int, ToDoListDto] = {} ``` 4. **main.py** ```python 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.py` wird nun mit Hilfe von `app = FastAPI()` eine Instanz der FastAPI-Anwendung erstellt. der Parameter `title` beschreibt den Titel der API, der in der automatisch generierten Dokumentation angezeigt wird. der Parameter `version` beschreibt die Version der API. Danach wird durch `origins` eine Liste von URLs definiert, von denen aus die API aufgerufen werden darf. Danach wird durch `app.add_middleware` die CORS-Middleware zur Anwendung hinzugefügt. im Folgenden werden die einzelnen Parameter dazu erklärt: - `allow_origins`: Erlaubt die angegebenen URLs - `allow_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 mit `uvicorn.run()` der Uvicron-Server gestartet und die FastAPI-Anwendung ausgeführt. - `"main:app"`: verweist auf die `app`-Instanz - `host="localhost`: führt den Server auf "localhost" aus - `port=8000`: zeigt, auf welchen Port der Sever zu erreichen ist - `reload=True`: aktiviert den automatischen Neustart des Servers bei Codeänderungen ## 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: ```python 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 ## 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 ## Quellen - [FastAPI](https://fastapi.tiangolo.com/) - [Pydantic](https://docs.pydantic.dev/latest/) - [Uvicorn](https://www.uvicorn.org/) - [ASGI-Doku](https://asgi.readthedocs.io/en/latest/) - [Datascientest - FastAPI](https://datascientest.com/de/fastapi) - [dev.to - fastapi](https://dev.to/fuadrafid/fastapi-the-good-the-bad-and-the-ugly-20ob)