Home » Beitrag verschlagwortet mit 'Flask'
Schlagwort-Archive: Flask
Docker Containers
References:
Dockerfile reference
docker build: command options and examples
docker run: command options and examples
Ablauf (Schnellversion)
docker pull postgres:latest | Docker image ‚postgres:latest‘ aus dem DockerHub holen |
oder: Selbst erstellen: | |
docker build -t test . | Docker image erstellen gemäss dem File Dockerfile, welches sich im current Directory befindet |
docker image ls | Docker Images auflisten (die Docker kennt) |
docker run — name myContainer7 – p 80:8080 test | Docker Image (als Container instantieren) laufen lassen |
docker ps | Laufende Container abfragen (–> ID auslesen) |
docker ps -a | Instantierte Container abfragen (–> ID auslesen) |
docker stop <containerId> | Container stoppen (bleibt instantiert) |
docker container rm <containerId> | Container löschen |
docker image rm <imageId> | Image löschen (aus Docker löschen) |
docker image help | Hilfefunktion für Docker Images aufrufen. |
Begriffe
Docker File: –> Ein File (Name wahrscheinlich immer ‚Dockerfile‚) welches innerhalb des SW Roots liegt und die die Erstellung des Docker Images definiert.
Docker Image –> Code, der falls instanziert, zu einem Docker Container wird.
DockerHub –> Repository für Docker Images.
Docker Registry
Einen Container beziehen und laufen lassen
Ein Docker Image aus dem DockerHub holen. (Im Beispiel ist es eine PostgresSQL DB als Docker image.)
docker pull postgres:latest
Kontrolle ob das Image nun lokal verfügbar ist:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres latest 317a302c7480 9 days ago 374MB
Das Image laufen lassen:
docker run --name psql -e POSTGRES_PASSWORD=password! -p 5433:5432 -d postgres:latest
Parameter:
-name : Sollte eigentlich der Name des Containers sein
-e : Environment. Hier werden dem Containers Environment-Parameter mitgegeben.
-p : <outerPort>:<InnerPort> bezeichnet die den Port, der von aussen angesprochen werden kann und den Port der damit innerhalb des Containers angesprochen wird.
-d : Das Docker-Image, das gestartet wird.
Die DB benutzen:
$ psql -U postgres -p 5433
Passwort für Benutzer postgres:
psql (13.3, Server 14.0 (Debian 14.0-1.pgdg110+1))
postgres=# \l
postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres +
| | | | | postgres=CTc/postgres
postgres=#
Ein Docker Image erstellen und ausführen
Unser Beispiel stellt einen Simplen Python/Flask Webserver als Docker-Image zur Verfügung.
1. Der Simple Python/Flask Webserver:
from flask import Flask
APP = Flask(__name__)
@APP.route('/')
def hello_world():
return 'Hello, World from Flask!\n'
if __name__ == '__main__':
APP.run(host='0.0.0.0', port=8080, debug=True)
Das Dockerfile darf keine File-Extension haben und liegt typischerweise im Root-Directory des SW-Projekts. (Hier im selben directory wie das Python file.)
FROM python:3.7.2-slim
COPY . /app
WORKDIR /app
RUN pip install --upgrade pip
RUN pip install flask
ENTRYPOINT ["python", "app.py"]
Die einzelnen Zeilen bedeuten:
FROM python:3.7.2-slim | Dieses Dockerfile erbt Eigenschaften vom python:3.7.2-slim image. |
COPY . /app | Kopier content des current directory ins app directory |
WORKDIR /app | Definiere ‚/app‘ als das work directory |
RUN pip install –upgrade pip | Führe ‚pip install –upgrade pip‘ aus (ich nehme mal an, dass dies auf dem Build-Server läuft und nicht auf dem Runtime server.) |
ENTRYPOINT [„python“, „app.py“] | Wird das Docker-Image (der Docker-Container) gestartet soll ‚python‘ mit parameter ‚app.py‘ aufgerufen werden. |
Kompilieren des Dockerfiles zum Dockerimage
docker build -t test .
-t test : Das Image soll den Titel ‚test‘ erhalten
‚.‘ : Sucht nach ‚Dockerfile‘ im current directory.
Kontrolle ob das Image erstellt wurde
docker image ls
Das Image laufen lassen
docker run --name myContainer -p 80:8080 test
Der Container wird den namen ‚myContainer‘ tragen und unter dem Port 80 in der Aussenwelt ansprechbar sein.
docker ps | Laufende Docker Images abfragen |
Kontrolle ob das Flask Webserver Image läuft:
curl http://127.0.0.1:80/
Oder im Browser: http://127.0.0.1:80/
Docker-Container/Image aufräumen
docker ps | Laufende Containers auflisten (z.B. Id) |
docker ps -a | In Docker existierende Container auflisten. |
docker stop <containerId> | |
docker container rm <containerId> | Einen Container von Docker entfernen. |
docker image ls | Images listen (die Docker kennt) |
docker image rm <imageId> | Image entfernen (aus der Liste der Images, die Docker kennt) |
Flask Application Programming Setup (mit PostgresSQL)
Was muss gemacht werden, um eine Flask Beispiel-Applikation im eingenen Workspace zum Laufen zu kriegen?
PostgesSQL DB: Sind die Entitäten vorhanden
Starten der DB: pg_ctl -D „C:\Program Files\PostgreSQL\13\data“ start
In die DB einloggen: psql <db_namen> <user_namen>, z.B. psql todoDB postgres
Dann innerhalb PSQL Shell:
\l : Um DBs zu listen
\dt: Um Tabellen zu listen
\? : Um andere Kommanos kennen zu lernen
Die Flask App zum Laufen kriegen
Im Windows Dos Prompt:
set FLASK_APP=<relativer_pfad_zum_app.py_file>
set FLASK_DEBUG=true damit der server dann automatisch die Codeänderungen rein lädt (life Editing)
flask run
Libraries würde man mit dem PIP (Package Manager laden).
In Flask: JavaScript als File auslagern
Symptom: Dieser seltsame Fehler:
sqlalchemy.exc.DataError: (psycopg2.errors.InvalidTextRepresentation) FEHLER: ungültige Eingabesyntax für Typ integer: »app.js«
LINE 3: WHERE todolists.id = ‚app.js‘
Lösung: Korrektes Referenzieren des Javascript-Files in einer Flask-Applikation:
In Flask apps stehen JavaScript und CSS files standardmässig in einem static
genannten directory neben dem templates
Directory, das die HTML templates enthält.
Im HTML-File wird ein File (z.B. Namens app.js) dann so referenziert:<script src="{{url_for('static', filename='app.js')}}"></script>
Achtung: Das Scripts-Tag muss am Ende des Bodies stehen.
Flask DB Migrations
Links
- Flask-Migrate
- Alembic (used by Flask-Migrate under the hood)
Flask-Migrate ist die Library um DB-Modell-Änderungen, die mit SQLAlchemy ausgedrückt sind zu kontrollieren. Es benutzt dazu die Library Alembic.
Schritte
Aufsetzen Flask Migrate Tool
Steps:
pip3 install Flask-Migrate
Projekt bezüglich Migrations-Handling aufsetzen
flask db init
Das setzt verschiedene Migrations-Script-Folders und Tools im SW-Projekt bereit.
Output:
C:\tmp\Python_Workspace1\task-app>flask db init
Creating directory C:\tmp\Python_Workspace1\task-app\migrations ... done
Creating directory C:\tmp\Python_Workspace1\task-app\migrations\versions ... done
Generating C:\tmp\Python_Workspace1\task-app\migrations\alembic.ini ... done
Generating C:\tmp\Python_Workspace1\task-app\migrations\env.py ... done
Generating C:\tmp\Python_Workspace1\task-app\migrations\README ... done
Generating C:\tmp\Python_Workspace1\task-app\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\tmp\\Python_Workspace1\\task-app\\migrations\\alembic.ini' before proceeding.
Funktioniert das Kommando nicht, weil die DB noch nicht gestartet ist, dann etwa folgendes absetzen:
pg_ctl -D "C:\Program Files\PostgreSQL\13\data" start
DB-Migrationen durchführen
flask db migrate
Erkennt die Anforderungen an das DB-Modell aus den Python-Scripts (aus den SQLAlchemy Model-Objekt-Definitionen) heraus und erstellt eine Migration (DDL Statements um das erforderliche DB-Model zu erstellen).
Ich gehe schwer davon aus, das dazu FLASK_APP auf das Python-Script mit den Entitätsdefinitionen zeigen muss.
Bsp. einer SQLAlchemy Model-Objekt-Definition im *.py:
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(), nullable=False)
Falls das erzeugte Script das ganze DB-Modell von Grund auf neu erzeugen soll, dann darf die DB diese Entitäten nicht schon enthalten. Das kann man zum Bsp. erreichen durch:
dropdb --username=postgres todoDB
createdb --username=postgres todoDB
!Stelle sicher, dass das Pyton-Script, nicht schon selbst die DB-Objekte erstellt (Z.B. Kommando db.create_all() darf nicht auch schon im Python-Code enthalten sein).
Durchführung:
C:\tmp\Python_Workspace1\task-app>flask db migrate
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'todos'
Generating C:\tmp\Python_Workspace1\task-app\migrations\versions\70ee48c313e4_.py ... done
C:\tmp\Python_Workspace1\task-app>
Ein Migrations-File wurde erstellt: task-app/migrations/versions/70ee48c313e4_.py
:
"""empty message
Revision ID: 70ee48c313e4
Revises:
Create Date: 2021-07-22 15:15:43.733091
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '70ee48c313e4'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('todos',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('description', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('todos')
# ### end Alembic commands ###
Migration auf die DB spielen:
C:\tmp\Python_Workspace1\task-app>flask db upgrade
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 70ee48c313e4, empty message
Migration rückgängig machen wäre:
flask db downgrade
Prüfung, ob DB-Migrationen auf DB angekommen sind:
C:\tmp\Python_Workspace1\task-app>psql --username=postgres todoDB
Passwort für Benutzer postgres:
todoDB=# \dt
Liste der Relationen
Schema | Name | Typ | Eigent³mer
--------+-----------------+---------+------------
public | alembic_version | Tabelle | postgres
public | todos | Tabelle | postgres
(2 Zeilen)
todoDB=#
Beachte die alembic_version
Tabelle! Beinhaltet die Migrationsinfo. Prinzipiell nicht durch den Programmierer zu verändern.
Wenn nun Änderungen am DB-Modell innerhalb des Python-Scripts gemacht werden (z.B. wird hier das boolean Feld ‚completed‘ neu hinzu gefügt),
class Todo(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(), nullable=False)
completed = db.Column(db.Boolean(), nullable=False, default=False)
…, dann wird mit flask db migrate
ein neues Update-Script erstellt und mit flask db update
dieser Update auf die DB gespielt.
Beheben der Problematik des Hinzufügens von NOT-NULL Feldern:
Das Migrations-Script folgendermassen anpassen:
from alembic import op
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('todos', sa.Column('completed', sa.Boolean(), nullable=True))
op.execute('update todos set completed=False where completed is null')
op.alter_column('todos', 'completed', nullable=False)
# ### end Alembic commands ###
Flask WebApp Request handling
Request-Typen und ihr Handling
Request-Typ | Handling (Python) |
---|---|
URL parameter: /foo?field1=value | value1 = request.args.get(field1) |
Form input | un = reqest.form.get(‚username‘) pw = request.form.get(‚password‘) |
application/json | data_string = request.data data_dictionary = json.loads(data_string) |
Erste Flask Python Webapp mit DB zugriff über sqlAlchemy
Referenzen
Overall Example
Python HTTP-Server (Flask) Applikation, die vom Browser aufgerufen mit ‚Hello <name>‘ antwortet, wobei der Name von der Personen-Tabelle der DB gelesen wird.
Flask wird dabei benutzt um den HTTP Server zur Verfügung zu stellen und darin die Web-App auf definierter URL anzubieten.
SQLAlchemy (in der Flask-Version) bietet die APIs um DB Entitäten als Klassen (z.B. ‚Person‘) formulieren zu können diese als Objekte auf die relationale DB zu mappen (ORM provider).
flask-postgres-sql-hello-app.p
y:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) #Create a flask app with the name of this runnable python file (stored in the env var __name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://myDB:pw@localhost:5432/testDB'
db = SQLAlchemy(app)
class Person(db.Model):
__tablename__ = 'persons'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), nullable=False)
#create all defined entities:
db.create_all()
#Insert an object to the DB:
person1 = Person(name = 'Marc')
db.session.add(person1)
db.session.commit()
person = Person.query.first()
#Handle web request on the web root:
@app.route('/')
def index():
return 'Hello ' + person.name
# Wird dieser Code auskommentiert, dann kann diese HTTP-Server-App
# durch 'python dieses-file-py' gestartet werden.
#if __name__ == '__main__':
# app.run()
Define an Entity
class Person(db.Model):
__tablename__ = 'persons'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), unique=False, nullable=False)
ahv = db.Column(db.String(), unique=True, nullable=False)
How to run a Flask HTTP Server
Das obige HTTP-Server-Script flask-postgres-sql-hello-app.p
y wird gestartet mit:
> flask run
nachdem die Umgebungsvariablen FLASK_APP=pfad/flask-postgres-sql-hello-app.py
gesetzt ist (Windows).
In Linux wird anscheinend so aufgerufen: >FLASK_APP=pfad/flask-postgres-sql-hello-app.py flask run
Debug-Mode: (Autorerun des HTTP-Server-Scripts)
Env. Variable setzen: FLASK_DEBUG=true
Remark flask --reload
probably does the same as set FLASK_DEBUG=TRUE
: Reloads the app as soon as code has changed.
Alternative Start-Methode:
Sieh code auch oben:
# Wird dieser Code auskommentiert, dann kann diese HTTP-Server-App
# durch 'python dieses-file-py' gestartet werden.
#if __name__ == '__main__':
# app.run()
Soll die Server-App von aussen Aufrufbar sein:
>flask run --host=0.0.0.0
oder
if __name__ == '__main__':
app.run(host="0.0.0.0")
Use Python Interactive Console to Import script and query script defined Entities
C:\tmp\Python_Workspace1\src>python
>>> from flask_postgres_sql_hello_app import db,Person
>>> Person.query.all()
[<Person ID: 5, name: Marc>
, <Person ID: 9, name: Marc>
, <Person ID: 10, name: Marc>
Funktioniert nur mit PY-Files deren Namen nach Konvention ist (kein ‚-‚).
Object Lifecycle in SQLAlchemy
The Lifecycle is: –object instantiated–> transient— query add executed–> pending –qery select/filter OR commit exec–> flushed –commit ex.–> committed
Flushed heisst: Im internen (in Memory) ORM-Object-Model-Cache sind die Änderungen bereits geschrieben.
!! ORM Selects (z.B. Person.query.all()
) erhält bereits alle Objekte, die oberhalb im Code mit session.query.add(person)
hinzugefügt/verändert/gelöscht wurden. –> ORM select-artige Queries löschen Flush aus!
Der SQL Select (direkt auf der DB) zeigt diese aber erst nach einem Commit!