Flask

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda

De moment, açò no és un manual ni tutorial de flask. És una col·lecció d'aclaracions sobre el Manual Oficial per a projectes propis o en classe.

Instal·lació

En ubuntu 20.04 ja venen instal·lats quasi tots els paquets necessaris inicialment. Cal instal·lar python3-venv.

La configuració d'una aplicació feta en Flask és molt diversa. Aquestes són algunes de les opcions:

  • Executar Flask sobre el fitxer de l'aplicació i accedir al port que obri. Flask té el seu propi WSGI fet en Werkzeug, així que funcionarà. No obstant, no el recomanen perquè no està optimitzat per a entrar en producció. Tant per rendiment (Un sol fil d'execució, difícultat amb varis accessos simultanis) com per la seguretat (HTTPS i altres)
  • Utilitzar un WSGI com uWSGI o Gunicorn. Aquests sí estan preparats per a entrar en producció. Saben distribuir la càrrega i solucionen problemes de rendiment.
  • Utilitzar Nginx/Apache amb un mòdul per WSGI i cridar a Flask: Aquests actuen com a proxy invers, millorant considerablement el rendiment i la seguretat (HTTPS). Amés, permeten tindre contingut estàtic amb millor rendiment.
  • Utilitzar Nginx/Apache, connectat a uWSGI o Gunicorn, que executa Flask: És l'opció més complexa per configurar, però la ideal per a un entorn de producció complex. La capa enmig del WSGI simplifica les cridades a l'aplicació i millora el rendiment.Tutorial

Una vegada decidit quina configuració anem a triar, cal preguntar-se si volem un entorn virtual de Python per a la nostra aplicació. Això independitza l'aplicació, el Python i les biblioteques que utilitzem de la resta del sistema. En entorns en els que es van a implementar varies aplicacions Python és recomanable.

python3.6 -m venv myprojectenv
source myprojectenv/bin/activate
pip install gunicorn flask


Configuració mínima

En aquest exemple, anem a fer una configuració mínima funcional. Es farà amb Nginx <-> Flask per les següents raons:

  • Es tracta d'un exemple real en el que una Raspberry Pi ha de fer de servidor en una xarxa local.
  • Al no donar servei a Internet i tindre pocs clients simultanis no és necessari preocupar-se tant del rendiment o seguretat.
  • Necessitem en Nginx per a donar accés a fitxers estàtics, ja que la web que proporcionarà serà principalment una SPA. Aquesta aplicació accedirà a l'aplicació feta en Flask, la qual proporcionarà una API REST.
  • L'aplicació necessitarà una base de dades. Per simplicitat la farem en SQLite.
  • Es decideix fer-la en Python perquè necessitem accedir als pins GPIO i amb Python és senzill.

En el sistema operatiu de la Raspberry (basat en Debian) farem les següents instal·lacions i configuracions:

 $ mkdir projecte
 $ cd projecte
 ~/projecte $ python3 -m venv venv
 ~/projecte $ . venv/bin/activate
 (venv) ~/projecte $ pip install flask

Fins a aquest moment, tenim una directori per al projecte, un entorn virtual i dins hem instal·lat Flask.

Ara anem a crear un servei en systemd per arrancar l'entorn virtual en arrancar el sistema. Per tant, en: /etc/systemd/system/projecte.service

[Unit]
Description=Flask myproject
After=network.target

[Service]
User=pi
Group=www-data
WorkingDirectory=/home/pi/projecte
Environment="PATH=/home/pi/projecte/venv/bin"
ExecStart=/home/pi/projecte/venv/bin/flask run --host=0.0.0.0

[Install]
WantedBy=multi-user.target

I podem gestionar el servei en:

 $ systemtctl [start,stop,restart] projecte.service 

I si volem que arranque a l'inici:

 $ systemtctl enable projecte.service 


Això farà que arranque en el port 5000 al qual tenim que redireccionar en Nginx. Per a fer això, anem a afegir una ruta per a l'app en Nginx a la que apliquem un proxy invers i la ruta normal quedarà per a les dades estàtiques:

         location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

	location /projecte/ {
	       include proxy_params;
               proxy_pass http://127.0.0.1:5000$request_uri;
	}

Connexió amb la base de dades

Ens podem connectar amb qualsevol SGBD, el més apropiat per a projectes importants en Python és PostgreSQL. MySQL i MariaDB també poden funcionar. No obstant, per a projectes menuts no cal coses molt complexes que donen més feina a la Raspberry. En SQLite serà suficient.

Suposant que ja tenim el fitxer SQlite, ací veiem la creació de taules per a una aplicació de control de reg:

from flask import Flask, jsonify, request
import sqlite3

DATABASE_NAME = "programas.db"


def get_db():
    conn = sqlite3.connect(DATABASE_NAME)
    return conn


def create_tables():
    tables = [
        """CREATE TABLE IF NOT EXISTS programas(
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                identificador TEXT NOT NULL,
                sector INTEGER NOT NULL,
	        inicio TEXT NOT NULL,
	        tiempo INTEGER NOT NULL,
                abonado INTEGER NOT NULL,
                lunes INTEGER NOT NULL DEFAULT 0,
                martes INTEGER NOT NULL DEFAULT 0,
                miercoles INTEGER NOT NULL DEFAULT 0,
                jueves INTEGER NOT NULL DEFAULT 0,
                viernes INTEGER NOT NULL DEFAULT 0,
                sabado INTEGER NOT NULL DEFAULT 0,
                domingo INTEGER NOT NULL DEFAULT 0
            )
            """,
            """
            CREATE TABLE IF NOT EXISTS settings(
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                minutos_abonado INTEGER NOT NULL
            )
            """
    ]
    db = get_db()
    cursor = db.cursor()
    for table in tables:
        cursor.execute(table)

A continuació cal fer funcions que creen programes o traguen la llista de programes:

def insert_programa(p):
    db = get_db()
    cursor = db.cursor()
    statement = "INSERT INTO programas(identificador, sector,inicio,tiempo,abonado) VALUES (?, ?, ?, ?, ?)"
    cursor.execute(statement, [p.identificador, p.sector, p.inicio, p.tiempo, p.abonado])
    db.commit()
    return True
def get_programas():
    db = get_db()
    cursor = db.cursor()
    query = "SELECT * FROM programas"
    cursor.execute(query)
    records = cursor.fetchall()
    nombres_columnas = [c[0] for c in cursor.description]
    records = list(map(lambda r: dict(zip(nombres_columnas,r)),records))
    ids = list(map(lambda r: r['identificador'],records))
    dict_records = {'Programas':  dict(zip(ids,records))}
    #print(ids,records,dict_records)
    return dict_records

A falta de fer la resta de funcions, observem com la primera inserta un nou programa i la segona el treu. El problema és que els treu com un array d'arrays. El que fem després és treure el nom de les columnes i amb zip() i dict() transformar l'array de cada fila en un diccionari i traguent els identificadors, podem fer un diccionari de diccionaris, que serà més útil per al client web.

Per últim, cal fer les funcions per a acceptar peticions del client web i insertar o actualitzar:

@app.route('/programas', methods=["GET"])
def get_p():
    programas = get_programas()
    print(programas)
    return jsonify(programas)
@app.route("/programa", methods=["POST"])
def insert_p():
    programa_details = request.get_json()
    result = insert_programa(programa_details)
    return jsonify(result)

https://parzibyte.me/blog/2020/11/10/api-rest-python-flask-sqlite3/

Hola Mon

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hola Mon</p>"

Com es veu, importem la classe Flask i creem un objecte app al que li passem el __name__ que és el nom del mòdul. El més interessant és el decorador @app.route() que indica la funció que s'executarà per a respondre a la ruta indicada. El que retorna és un HTML.

Per a executar-la necessitem declarar una variable amb export per a que flask conega el nom de l'aplicació:

 $ export FLASK_APP=hello
 $ flask run --host=0.0.0.0

Li posem el --host=0.0.0.0 per a que es puga accedir de fora de la Raspbery per fer proves.

Si volem veure els possibles errors:

 $ export FLASK_ENV=development

Gràcies al WSGI de Werkzeug, podem passar paràmetres en la ruta, per exemple:

from flask import Flask
from markupsafe import escape

app = Flask(__name__)

@app.route("/<name>")
def hello(name):
    return f"Hello, {escape(name)}!"

Això serà molt útil per a fer el API REST.