Flask

Im folgenden Beitrag wird das für die Programmiersprache Python entwickelte Programmiergerüst namens Flask beschrieben. Es wird der Versuche unternommen, einen Einstieg in das Flask-Gerüst bereitzustellen und Besonderheiten sowie Erweiterungen, die für den Betrieb einer komplexeren Webseite nötig sein können, durch Beispiele zu erklären.

Auch in diesem Beitrag werden zuerst alle grundlegenden Schritte geschildert, die nötig sind, um ein einfaches Flask basiertes Projekt aufzusetzen. Danach werden die komplexeren Themen wie Benutzeroberflächen, Einbindung von Web-Formularen, Datenspeicherung mittels Datenbanken, Benutzerauthentifizierung und -verwaltung, Fehlerbehandlung, Seitenunterteilung usw. behandelt.

Einrichtung

Die Grundlagen einer Programmiersprache wären nicht richtig erklärt, wenn man nicht mit dem einfachsten aller Programme anfinge: einem, dass "Hallo Welt" anzeigt. Aber grade da für eine statische Flask-Anwendung, die nur "Hallo Welt" anzeigt, inhaltlich wenig zu programmieren ist, so gibt es einige Schritte, die zur Einrichtung von Flask nötig sind. Zusammengefasst sind diese Schritte:

  1. die Installation von Python;
  2. das Aufsetzen einer virtuellen Umgebung; und
  3. die Installation von Flask in dieser virtuellen Umgebung.

Diese Schritte werden im folgenden ausführlicher beschrieben, können aber nach Bedarf übersprungen werden.

Python

Flask ist ein Python basiertes Programmiergerüst. Daher sollte eine Version von Python (vorzugsweise Version 3) installiert sein. Falls Sie Python bereits installiert haben, können Sie diesen Abschnitt überspringen und bei dem zur Installation von Flask wieder einsteigen. Andernfalls können Sie eine der zwei folgenden Installations­möglichkeiten wählen, die im folgenden kurz geschildert werden:

  1. Python von der offiziellen Homepage herunterladen und alleinstehend installieren; oder
  2. Python durch eine Versionsverwaltungs­software, z.B. Pyenv installieren.

Alleinstehende Python Version installieren

Die Installationsdatei für Python kann von http://python.org/download/ herunterladen werden. Die Installation erfolgt dann wie gewohnt. Am Ende sollten Sie über eine Befehlszeile mit dem Befehl python den auf ihrem Computer installieren Interpreter ausführen können, zB.:

% python
Python 3.9.1 (default, Mar 30 2021, 21:19:43) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Der Python-Interpreter wartet nun als interaktive Eingabeaufforderung, in der Sie Python-Anweisungen eingeben können. In späteren Abschnitten und Kapiteln werden Sie erfahren, wozu diese interaktive Eingabeaufforderung nützlich ist. Aber für den Moment dient dies nur zur Bestätigung, dass Python auf Ihrem System installiert wurde. Um die interaktive Eingabeaufforderung zu verlassen, können Sie exit() eingeben und mit der die Eingabetaste den Befehl bestätigen. Bei den Python unter Linux und macOS können Sie den Interpreter auch durch Drücken von Strg-D beenden. Unter Windows ist das Tastaturkürzel zum Beenden Strg-Z gefolgt von der Eingabetaste.

Python mittels Versionsverwaltung installieren (hauptsächlich für macOS)

Die vorstehende Installation von Python ist die einfachste. Es kann jedoch der Fall eintreten, dass Sie jedoch in der Zukunft zwischen verschiedenen Versionen von Python wechseln wollen, zB.:

Dafür entfielt sich eine Software wie Pyenv, die unterschiedliche Versionen von Python einfach installieren, löschen und zwischen ihnen wechseln, also verwalten kann. Pyenv wird am besten über die Packetverwaltungs­software Homebrew installiert. Um sowohl Homebrew als auch Pyenv unter macOS Big Sur (11.3) zu installieren, geben Sie am besten die folgenden Befehle in der Befehlszeile (bzw. dem Programm Terminal) ein:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install pyenv
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.zshrc

Nachdem die Befehlszeile neu gestartet wurde und können Sie Pyenv verwenden. Eine Version von Python wird dann mit der Befehlskombination aus pyenv install XXX installiert, wobei XXX die zu installierende Version von Python angibt. Eine Liste aller verfügbaren Versionen erhalten Sie über die Befehlskombination pyenv install -list. Beim Schreiben dieses Beitrags benutzte ich Python 3.9.1. Um dieselbe Version zu installieren, geben Sie die folgende Befehlskombination in der Befehlszeile ein:

pyenv install 3.9.1

Pyenv führt dann die Installation aus:

python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.9.1.tar.xz...
-> https://www.python.org/ftp/python/3.9.1/Python-3.9.1.tar.xz
Installing Python-3.9.1...
python-build: use readline from homebrew
python-build: use zlib from homebrew
Installed Python-3.9.1 to /Users/maxi/.pyenv/versions/3.9.1

Danach können Sie die unter Pyenv installierten Versionen von Python über den folgenden Befehl einsehen:

pyenv versions

Das Ergebnis zeigt dann an, dass neben der vom Betriebssystem mitgelieferten Version von Python auch Version 3.9.1. zur Verfügung steht:

* system (set by /Users/maxi/.pyenv/version)
  3.9.1

Diese Version können sie mit pyenv global (oder pyenv lokal) ändern:

pyenv global 3.9.1

Nun steht Ihnen Python als Version 3.9.1 zur Verfügung.

Flask

Der nächste Schritt ist die Installation von Flask. Aber bevor ich darauf eingehe, möchte ich Sie über die besten Praktiken im Zusammenhang mit der Installation von Python-Paketen informieren.

Das Repository und Python-Pakete

In Python sind Pakete wie Flask in einem öffentlichen Depot bzw. Repository verfügbar, von dem jeder sie herunterladen und installieren kann. Das offizielle Python-Paket-Repository heißt PyPI, was für Python Package Index steht. Die Installation eines Pakets aus PyPI ist sehr einfach, da Python mit einem Werkzeug namens pip ausgeliefert wird, das diese Arbeit erledigt (in Python 2.7 wird pip nicht mit Python ausgeliefert und muss separat installiert werden).

Einige bezeichnen das das Python-Pakete-Repository auch als "cheese shop", da dies die Geheimbezeichnung für das Repository war.

Um ein Paket zu installieren, verwendet man den Befehl pip wie folgt:

pip install <package-name>

Interessanterweise wird diese Art der Installation von Python-Paketen in den meisten Fällen nicht funktionieren. Falls Ihr Python-Interpreter global für alle Benutzer Ihres Rechners installiert wurde, ist es wahrscheinlich, dass Ihr normales Benutzerkonto keine Berechtigung haben wird, Änderungen daran vorzunehmen. Daher ist die einzige Möglichkeit, den obigen Befehl auszuführen, die, ihn als Administrator (zB. durch das Schlüsselwort sudo) auszuführen. Haben Sie jedoch Python lokal oder mittels Versionsverwaltung installiert, tritt diese Komplikation bei der Installation nicht auf. Aber auch ohne diese Komplikation sollten Sie wissen, was passiert, wenn Sie ein Python-Paket wie Flask installieren.

Der Befehl pip lädt ein Paket von PyPI herunter und fügt es dann Ihrer Installation von Python hinzu. Von diesem Zeitpunkt an hat jeder Python-Skript, den Sie auf Ihrem System haben, Zugriff auf dieses Paket. Stellen Sie sich vor, Sie haben eine Anwendung A mit der Version 1.0 eines Pakets P programmiert. Zum Zeitpunkt als Sie die Anwendung A programmierten, war Version 1.0 die aktuellste Version des Pakets P, ist nun aber durch Version 2.0 überholt worden. Sie möchten nun eine zweite Anwendung B unter Nutzung der aktuellsten Version des Pakets P, also Version 2.0, programmieren und ausführen. Wenn Sie aber die installierte Version 1.0 ersetzen, riskieren Sie, Ihre ältere Anwendung A unbrauchbar zu machen, da sie nicht mehr auf die bei der Entwicklung benutzte Version 1.0 des Pakets P zugreifen kann. Erkennen Sie das Problem? Idealerweise, sollte das Paket so installiert werden, dass Ihre alte Anwendung A Version 1.0 verwenden kann, und gleichzeitig Version 2.0 für die Entwicklung Ihrer neue Anwendung bereitgestellt wird.

Virtuelle Umgebung

Um das Problem der Pflege verschiedener Versionen von Paketen für verschiedene Anwendungen zu lösen, verwendet Python das Konzept der virtuellen Umgebungen. Eine virtuelle Umgebung ist eine vollständige Kopie des Python-Interpreters. Wenn Sie Pakete in einer virtuellen Umgebung installieren, ist der systemweite Python-Interpreter nicht betroffen (auch nicht der von einer Versionsverwaltung installierte Interpreter), sondern nur die Kopie in der virtuellen Umgebung. Die Lösung, um völlige Flexibilität hinsichtlich beliebiger Versionen von Paketen für jede Anwendung zu haben, ist also, für jede Anwendung eine andere virtuelle Umgebung zu benutzen. Virtuelle Umgebungen haben den zusätzlichen Vorteil, dass sie dem Benutzer gehören, der sie anlegt, und keine Administratorrechte gebraucht werden.

Im Vergleich zu virtualenv ist venv: langsamer; nicht so erweiterungsfähig; nicht in der Lage für willkürlich installierte Python-Versionen virtuelle Umgebungen zu erstellen; nicht über den pip-Befehl aktualisierbar; und hat keine umfangreiche programmatische API.

Obwohl ab Version 3.3 Python von Haus aus ein Paket namens venv umfasst, das zur Erstellung einer virtuellen Umgebung benutzt werden kann, empfiehlt es sich, das vollständige Paket namens virtualenv zu installieren. Virtualenv wird wie folgt (ggf. mit Administratorrechten) Installiert:

pip install virtualenv

Um eine virtuelle Umgebung zu erstellen, bedarf es zuerst ein Projektverzeichnis, das ich hier der Einfachheit halber tinyblog nenne.

mkdir tinyblog
cd tinyblog

In diesem Verzeichnis, wird eine virtuelle Umgebung nun wie folgt erstellt:

python -m virtualenv venv

Dieser Befehl erstellt durch das Paket virtualenv im Ordner venv eine Kopie des global genutzten Python-Interpreters (also 3.9.1, wenn Sie Python wie vorstehend beschrieben durch die Versionsverwaltung installiert hatten). Der Ordnername ist auswechselbar, es ist jedoch gängige Praxis geworden, virtuelle Umgebungen in einem Ordner namens venv zu speichern (auch wenn nicht venv als solches, sondern wie hier virtualenv benutzt wird).

Die virtuelle Umgebung wird nun wie folgt aktiviert:

source venv/bin/activate

Danach sollte das Schlüsselwort (venv) vor dem Cursor in der Befehlszeile erscheinen, wo eine neue Eingabe erwartet wird. Das Schlüsselwort ist der Name der aktivierten virtuellen Umgebung gibt an, dass die Konfiguration Ihrer Befehlszeile so geändert wurde, dass der im Ordner venv gespeicherte Python-Interpreter derjenige ist, der aufgerufen wird, wenn Sie Python-Befehle eingeben werden. Die über die Befehlszeile vorgenommenen Änderungen sind demnach alle temporär und nur für diesen aktivierten Python-Interpreter bestimmt. Das bedeutet, sie ändern nicht die auf dem System installierte globale Version von Python, sondern nur die der aktivierten virtuellen Umgebung. Es ist also in Ordnung, wenn Sie mit mehreren gleichzeitig geöffneten Befehlszeilen arbeiten, solange Sie für jede Befehlszeile eine andere virtuelle Umgebung aktiviert haben. Denn so können die Änderungen der Python-Pakete durch eine Befehlszeile nicht die Python-Pakete einer anderen Befehlszeile beeinträchtigen.

Flask Installation

Jetzt, wo Sie eine virtuelle Umgebung erstellt und aktiviert haben, können Sie (endlich) Flask installieren:

pip install flask

Um zu bestätigen, dass Flask richtig installiert wurde, können Sie das Paket im Python-Interpreter probeweise importieren:

>>> import flask
>>> _

Sollten keine Fehler angezeigt werden, können Sie sich beglückwünschen, denn Flask ist erfolgreich installiert und einsatzbereit.

Hallo Welt

Auf der Webseite von Flask gibt es eine Schnellstart Anleitung, in der eine einfache Anwendung gezeigt wird, die grade mal aus fünf Zeilen Code besteht. Anstatt nur dieses (fast schon zu einfache) Beispiel hier zu wiederholen, wird im folgenden eine etwas umfangreichere Projekt­struktur mit etwas aufwändigerem Code vorgestellt. Diese Struktur eignet sich dafür aber besser als Ausgangspunkt für die Bereitstellung der Anwendung auf einem Server und stellt daher auch den Grundstein jedes meiner Flask-Projekte dar. Die Projektstruktur im Ordner namens tinyblog sieht dann wie folgt aus:

tinyblog/
├── tinyblog
│   ├── __init__.py
├── launch.py
└── venv
    └── ...

Hauptdatei

Die Datei tinyblog/tinyblog/__init__py ist die Hauptdatei der Flask-Anwendung und enthält quasi denselben Code wie in der Schnellstart Anleitung:

import os
from flask import Flask

def create_app():
    """
    Dies ist die Funktion zur Erstellung der Flask-Anwendung bzw. `app`.
    Es wird ein geheimer Schlüssel und (falls nötig) der Instanzpfad der Anwendung erstellt,
    bevor der Einstiegspunkt zur Webseite definiert wird.
    """
    app = Flask(__name__)

    app.secret_key = os.urandom(16)

    if not os.path.exists(app.instance_path):
        os.makedirs(app.instance_path)

    @app.route('/')
    @app.route('/index/')
    def index():
        return '<h1>Hallo, Welt!</h1>'

    return app

Der geheime Schlüssel app.secret_key wird zum sicheren Signieren der Sitzungscookies verwendet und kann für alle anderen sicherheitsrelevanten Anforderungen von Erweiterungen oder Ihrer Anwendung selbst verwendet werden. Es sollte eine lange zufällige Bytefolge sein, obwohl auch Unicode akzeptiert wird.

Der Instanzpfad app.instance_path ist ein Pfad zu einem Ordner, in dem die Anwendung Dateien verwalten kann. Dieser Ordner existiert nicht von Haus aus, obwohl die Variable app.instance_path von der gesamten Anwendung eingesehen werden kann. Daher empfiehlt es sich, zu prüfen, ob der Ordner existiert (mit os.path.exists()), und versucht den Ordner vor dem Start der restlichen Flask-Anwendung zu erstellen. Sollte der Ordner nicht existieren oder die Anwendung nicht die Berechtigung haben, den Ordner zu erstellen, bricht der Start hier ab.

Mit dem @app wird hier ein Decorator für die Anwendung app definiert, wobei der @app.route() Decorator die Anzeige einer Webseite für eine bestimmte Route bzw. URL definiert. Diese sogenannte view function verknüpft demnach die nachfolgende Funktion (hier index()) mit der URL-Endung /index/ und /. Der zurückgegebene Wert stellt den Inhalt der aufgerufenen Webseite dar. Somit würde beim Aufrufen von zB. http://adresse.com/ oder http://adresse.com/index dieselbe Funktion, nämlich index() ausgeführt und <h1>Hallo, Welt!</h1> im Browser angezeigt.

Damit dies jedoch geschieht, muss die Anwendung gestartet werden. Dies geschieht mit einer Startdatei.

Startdatei

Die Datei tinyblog/launch.py ist die Startdatei mit der die Flask-Anwendung (zB. vom Server) gestartet wird. Sie kann entweder nur die Anwendung, also app nach außen sichtbar machen, sodass man die Anwendung manuell starten kann, oder doe Datei kann einen Startcode enthalten, der auch das Einbinden der virtuellen Umgebung umfasst, und somit einige Schritte automatisiert.

Manueller Start

Für den manuellen Start enthält die Datei tinyblog/launch.py folgendes:

from tinyblog import create_app
application = create_app()

Hierdurch wird die Funktion zum Erstellen der Anwendung, die app mit allen View Functions zurückgibt, importiert und aufgerufen. Das Ergebnis wird in der Variablen application gespeichert.

Zwar ist die Anwendung nun nach außen sichtbar, muss aber ausgeführt werden. Hierfür Flask jedoch wissen, durch welche Datei die Anwendung sichtbar gemacht wird. Dies geschieht, indem die Umgebungsvariable FLASK_APP gesetzt wird, indem folgendes in die Befehlszeile eingegeben wird:

(venv) % export FLASK_APP=tinyblog.py

Nun kann die Anwendung durch die folgende Eingabe in die Befehlszeile ausgeführt werden:

(venv) % flask run

Als Ergebnis sollte in etwa folgendes zu sehen sein:

 * Serving Flask app "launch.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Nachdem der Server initialisiert wurde, wartet er auf eingehende Verbindungen. Die Ausgabe von flask run zeigt an, dass der Server von Ihrem Computer über die IP-Adresse 127.0.0.1 erreichbar ist. Diese Adresse ist so geläufig, dass sie auch einen einfachen Namen hat, den Sie vielleicht schon einmal gesehen haben: localhost. Netzwerkserver warten auf Verbindungen unter einer bestimmten Portnummer. Anwendungen, die auf produktiven Webservern eingesetzt werden, befinden sich in der Regel hinter Port 443 (oder manchmal 80, falls sie eine unverschlüsselte Verbindung herstellen), aber die Einrichtung diese Ports erfordert unter Umständen Administrationsrechte. Da diese Anwendung in einer Entwicklungsumgebung läuft, verwendet Flask den frei verfügbaren Port 5000.

Öffnen Sie nun Ihren Webbrowser und geben Sie die folgende URL in das Adressfeld ein:

http://localhost:5000/

Alternativ können Sie auch diese andere URL verwenden:

http://localhost:5000/index

Sie sehen nun die Zuordnungen durch die View Function in Aktion. Die erste URL wird auf / abgebildet, während die zweite auf /index abgebildet wird. Beide Routen sind mit der einzigen View Function in der Anwendung verknüpft, sodass sie die gleiche Ausgabe erzeugen, nämlich die Zeichenkette, die die Funktion index() zurückgibt. Wenn Sie eine andere URL eingeben, erhalten Sie einen Fehler, da nur diese beiden URLs von der Anwendung erkannt werden.

Die einfachste aller Anwendungen: Hallo, Welt!
Die einfachste aller Anwendungen: Hallo, Welt!

Wie Sie bereits gesehen haben, dient dieser Server nur Testzwecken und sollte nicht in der Produktionsumgebung eingesetzt werden. Wie der Server richtig in einer Produktionsumgebung gestartet werden kann, hängt von der Art und Weise ab, wie der Server verwaltet werden soll (zB. mittels Apache oder nginx) und wird daher in einem späteren Kapitel ausführlich erklärt. Den Entwicklungsserver halten Sie jedenfalls durch CTRL+C an.

Da Umgebungsvariablen nicht über eine Terminalsitzung hinaus gespeichert werden, ist es vielleicht lästig, die Umgebungsvariable FLASK_APP immer von neuem setzen zu müssen, wenn ein neues Terminalfenster geöffnet wird. Ab Version 1.0 erlaubt Flask die Registrierung von Umgebungsvariablen, die automatisch importiert werden sollen, wenn Sie den Flask-Befehl ausführen. Um diese Option zu nutzen, müssen Sie das Paket python-dotenv installieren:

(venv) % pip install python-dotenv

Dann können Sie den Namen und den Wert der Umgebungsvariablen einfach in eine Datei namens .flaskenv im obersten Verzeichnis der Projektstruktur speichern:

FLASK_APP=tinyblog.py

Dies ist jedoch optional, falls Sie es vorziehen, die Umgebungsvariable manuell zu setzen. Das ist auch das völlig in Ordnung, solange Sie immer daran denken.

Automatischer Start

Alternativ zur vorstehenden Möglichkeit, können Sie die Datei tinyblog/launch.py erweitern, sodass sie wie folgt aussieht:

import sys, os

__dir__ = '/'.join(os.path.abspath(__file__).split('/')[0:-1])
sys.path.insert(0, __dir__)

activate_this = os.path.join(__dir__, 'venv', 'bin', 'activate_this.py')
with open(activate_this) as file_:
    exec(file_.read(), dict(__file__=activate_this))

from tinyblog import create_app
application = create_app()

if __name__ == '__main__':
    application.run(
        host=('0.0.0.0' if '-global' in sys.argv else '127.0.0.1'),
        debug=('-debug' in sys.argv)
        )

Dieser Code macht die Variable application genau so sichtbar, wie der vorstehende Code. Jedoch fügt er zudem den eigenen Pfad zu der Umgebungsvariablen PATH hinzu und aktiviert die virtuelle Umgebung automatisch. Das bedeutet, dass sie die virtuelle Umgebung vorerst durch die folgende Eingabe in die Befehlszeile deaktivieren können:

(venv) % deactivate

sodass das Schlüsselwort (venv) verschwindet. Nun können Sie mit einem beliebigen Python-Interpreter folgenden die Anwendung wie folgt starten:

% python launch.py

Dann wird mit dem Python-Interpreter, der launch.py ausführt, die virtuelle Umgebung geladen und die Flask-Anwendung erstellt. Danach, da somit die Bedingung if __name__ == '__main__': erfüllt, also True ist, wird die Methode run() der Instanz namens application ausgeführt. Im Ergebnis wird der Server wie oben, nur mit weniger Schritten, gestartet.

Zudem können Sie bei dieser Variante, den Server zu starten, auch die Argumente -global und -debug an launch.py übergeben.

Mit -global wird die IP-Adresse des Servers von 127.0.0.1 bzw. localhost auf 0.0.0.0 gesetzt, sodass der Server auch dritten Geräten erreicht werden kann. Sie müssen dafür nur die IP-Adresse ihres Computers herausfinden (zB. 192.168.0.4) und können dann wie die Webseite Aufrufen:

http://192.168.0.4:5000

Mit -debug wird der Server so gestartet, dass er automatisch Änderungen im Code erkennt und als Reaktion darauf, den Server neu startet. Diese Einstellung empfiehlt sich besonders beim Entwickeln, da man den Server somit nicht manuell neu starten muss, um die Änderung im Code zu testen.

Zusammenfassung

Gratulation, Sie haben nun Ihren ersten Flask basierten Webserver erstellt und können auf eine Webseite zugreifen. Auf diesem Grundgerüst wird in den folgenden Kapiteln die Funktionalität des Webservers erweitert. Merken sollten Sie sich aber, dass: