Flask is a very popular Python web frame that allows you to create web UI's using the Python programming language.
I want to run Flask on my RPi. To do this, the following minimal preconditions must be met:
Python3 must be installed
venv must be installed: this is automatically available with the most recent versions of Python (versions 3.4 and above)
Always use a virtual environment to avoid messing up the overall Python installation. A virtual environment will install all the stuff locally in your project and will not clutter or mess up the rest of your global Python installation.
Flask must be installed
To verify which Python version you're running, run the command python --version on a command line and see for the resulting version. Currently, I'm running Python version 3.9.2 on my RPi where I want to develop a Flask web application.
There might be more Python modules needed, depending on the type of Flask application you want to make. But that will become clear when you're building a specific Flask application that needs extra modules. An example is the WTF-forms module, needed to create web forms.
Flask is totally new for me. I want to use it to create a (local) website running on my RPi to provide input from a user or a technician to make a connection between a push button on a switch plate to a relay or impuls switch (a.k.a. teleruptor) in my home automation system. This way, even a noob user should be able to set up and modify the new or existing connections at will.
To learn Flask, I hunted for useful web sites on the internet and found a few of which the following from Miguel Grinberg and Abdelhadi Dyouri are the most useful I found to date:
Next to this, the most useful YouTube series I found is this one from Corey Schaefer:
https://www.youtube.com/watch?v=MwZwr5Tvyxo&list=PL-osiE80TeTs4UjLw5MM6OjgkjFeUxCYH
Note that this is already quite old (still uses Flask 1.0 while the newest version of Flask is 3.0 as of this writing) but the general mechanisms are still very much in place!
Next to this, the following Flask-learning websites are also worth a visit:
https://www.hackster.io/mjrobot/from-data-to-graph-a-web-journey-with-flask-and-sqlite-4dba35
https://randomnerdtutorials.com/raspberry-pi-web-server-using-flask-to-control-gpios/
https://www.geeksforgeeks.org/uploading-and-reading-a-csv-file-in-flask/
https://towardsdatascience.com/python-webserver-with-flask-and-raspberry-pi-398423cc6f5d
https://www.geeksforgeeks.org/connect-flask-to-a-database-with-flask-sqlalchemy/
Traditionally, a "Hello, World!" application is the way to introduce a new language or whatever other thing (thank you very much Brian Kernighan for this simple yet world-famous one-liner application!!!).
To start, create a directory where you want to test your first Flask application. Then inside that directory run the command python -m venv venv. This will create a local Python environment to isolate it from your global Python environment (avoid messing it up!).
After running the command you should still activate it. To activate the virtual environment run the command . venv/bin/activate. If that's successful you should see (venv) in front of your command prompt like so:
(venv) pi@rpi3Bimmcff ~/mystuff/flask/gfg$
From now on, whatever packages you install with the pip install or python -m pip install command they will be installed locally and not globally.
Note 1:
You can call your virtual environment whatever you want. To do this, change the second parameter in the call to create the virtual environment.
Example:
python -m venv myenv
The command prompt will then have (myenv) at the start of the command prompt line.
Note 2:
To deactivate the virtual environment simply type deactivate on the command line and you're done!
The most simple Flask application can be found on the Flask website itself:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
That's it, nothing more nothing less.
If you want to run this application then run the following commands (
flask --app hello run
* Serving Flask app 'hello'
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
This will be simplified in the future but for now that should do it.
Go to the browser and type in the address given above (note: 127.0.0.1 is same as localhost).
To know in which virtual environment Python is running, you can run the command pip -V. After some time you will get something like this in the command window:
(venv) pi@rpi3Bimmcff ~/mystuff/flask/sandbox$ pip -V
pip 20.3.4 from /home/pi/mystuff/flask/sandbox/venv/lib/python3.9/site-packages/pip (python 3.9)
This means Python is running in that particular virtual environment (here: the one of the sandbox directory).
Since I don't have a GUI running on all of my RPi's I have to make sure I can connect to the Flask server from a browser running on another PC on my network.
By default, the Flask server is running on IP address 127.0.0.1 or localhost (alias for the IP address). This way, you cannot connect from another PC to the Flask server running on - in my case - the RPi.
To allow this connection from a web browser, add the following parameter to the app.run() command, given in the First Flask application section:
app.run(host="0.0.0.0")
This tells your operating system to listen on all public IPs.
If you now start the Flask server on the RPi you can connect to it from another PC using the IP address of the RPi.
Example: http:/192.168.25.20:5000, where 192.168.25.20 is the IP address of the RPi (fictive example given).
By default, the Flask server is not updating the modified content when reloading web pages. You have to stop the server and restart it again.
Especially when starting a new project, this is very annoying. Each modification in a file or files needs a restart of the Flask server.
To avoid this, you can activate the debug mode. When active, the debug mode will have the following features:
the server will automatically reload if code changes
the server will show an interactive debugger in the browser if an error occurs during a request
To activate the debug mode, add debug=True in the app.run() command like so:
app.run(debug=True)
This way when you start the Flask server you will see the following info on the command line:
(venv) pi@rpi3Bimmcff ~/mystuff/flask/gfg$ python app.py
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.1.40:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 657-166-356
From now onwards, if the content has changed, a simple refresh of the page will take in all the modifications done.
Note that you can give multiple parameters with the run() method. Example: app.run(host="0.0.0.0", debug=True). Separate the different parts with a comma.
To get even more information while testing the new Flask web site you can install a module called flask_debugtoolbar. To install it in the isolated virtual environment run the following command:
python -m pip flask_debugtoolbar
After some time, the module will be installed into the virtual environment.
To use the feature in your Flask applications add the following extra items to your Python script:
from flask_debugtoolbar import DebuToolbarExtension
app.config['SECRET_KEY'] = '<your secret key>'
toolbar = DebugToolbarExtension(app)
A complete script can look like this:
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'ToyotaStarlet'
toolbar = DebugToolbarExtension(app)
@app.route('/')
def check():
return '<html><body>Flask is just extremely well working</body></html>'
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
When you run this a logo will be visible on the right side of the web page. If you click the logo a side bar will open where you can verify lots of parameters of the running Flask application.
Example where the HTTP Headers are checked for their content:
If you installed the extra debugger toolbar then one of the options is to activate the profiler. Due to the performance overhead, profiling is disabled by default.
To activate the profiler, open the debugger toolbar at the right side of the screen and then click on the red check mark to activate the feature. The checkmark will become green instead indicating the profiler is now activated.
IMPORTANT!
If you want to use the profiler then one important line of code must be added to your Flask main application:
app.config['DEBUG_TB_PROFILER_ENABLED'] = True
Not doing this and still activating the profiler will result in the following obscure (well, not that obscure, see further) error if you launch your Flask application:
AttributeError: 'ProfilerDebugPanel' object has no attribute 'dump_filename'
Looking to the stack trace, the error comes from the file profiler.py, part of the flask_debugtoolbar Python extension:
File "/home/pi/mystuff/flask/gfg/venv/lib/python3.9/site-packages/flask_debugtoolbar/panels/profiler.py", line 96, in process_response
if self.dump_filename:
AttributeError: 'ProfilerDebugPanel' object has no attribute 'dump_filename'
A complete stack trace image is added at the end of this section.
Now, if you're a bit proactive, you should look to the source code of the file profiler.py. Then you will understand why there's such an error submitted if you want to activate the profiler.
If the profiler.py file is open, search where the variable dump_filename is used and created. You will see the variable is given a value in the __init__ method of the class ProfilerDebugPanel like so:
def __init__(self, jinja_env, context={}):
DebugPanel.__init__(self, jinja_env, context=context)
if current_app.config.get('DEBUG_TB_PROFILER_ENABLED'):
self.is_active = True
self.dump_filename = current_app.config.get(
"DEBUG_TB_PROFILER_DUMP_FILENAME"
)
You also see that the assignment is conditional: the environment variable DEBUG_TB_PROFILER_ENABLED must be set to True, else the if construction is not executed.
If the environment variable is not set then in line 96 of the file profiler.py the if condition will fail (with the AttributeError as a consequence) since dump_filename simply doesn't exist!
So, this is the reason why you must define the configuration variable DEBUG_TB_PROFILER_ENABLED to True in your Flask application!
Below is a screenshot of the full error stack trace:
When the profiler is activated and you run your Flask application you see the following information when selecting the item Profiler from the Flask debug toolbar. Few remarks:
Not all numbers are clear yet to me, but you see all methods called when running the Flask application
There's more info available than shown in the below image: try it yourself and see for the results
When working with databases (which I mostly do) then the following standard setup procedure should be followed:
Create a new directory for your Flask application
Run the command python -m venv venv (or whatever name you want to give to the Python virtual environment)
Activate the virtual environment with . venv/bin/activate (make sure to use the virtual environment name you've given)
Install the following minimal packages:
Flask:
pip install Flask
Flask-SQLAlchemy:
pip install Flask-SQLAlchemy
Flask-Migrate:
pip install Flask-Migrate
You can also do this in one go: pip install Flask Flask-SQLAlchemy Flask-Migrate
This should give you a pretty basic starting base for your Flask application.
There's also a possibility to create a file, mostly named requirements.txt, which contains all packages to be installed, one per line.
The line format is <package>==<version_number>. Example: jinja==3.1.4 .
If you have already a virtual environment available then you can "peek" to see what packages and their versions are already installed. This can be done with the command pip freeze and you can output the content to the file requirements.txt like so:
pip freeze > requirements.txt
If you acquire the file through pip freeze or you make the file yourself manually, installing the packages listed inside the file must be done with the following command:
pip install -r requirements.txt
As you can see, you can name the text file whatever you want but again, mostly requirements.txt is used.
If you do changes to the database (e.g. adding/deleting columns, renaming tables/columns,...) this can be quite cumbersome and error prone if you have to do it by hand.
Luckily, Flask has an extension called flask_migrate who does take the workload away from the user.
To use the migrate facilities, import the extension: from flask_migrate import Migrate.
Once the extension is imported you have to connect the application and the database to the Migrate module like so:
migrate = Migrate(app, db), where app is the application and db is the database.
Once this is done you can ask for the migrate help if you change the database structure.
Suppose you rename a column in the database. Those are the steps to follow to implement that change using Migrate:
Make sure the above is established (connect application and database to the Migrate constructor)
Apply the change in the class that represents the database: rename the column the name you want
Open a terminal and make sure the Python virtual environment is active. If not, run the command . venv/bin/activate.
If it's the first time you do the migration, run flask db init. This will create an extra direcory within the project called migrations. This directory will be used for future purposes like upgrading or downgrading the database and more.
If it's not the first time you do the migration, run flask db migrate. The script will detect the database changes and create a new migration file in the migrations directory with a very cryptic number. Don't be bothered by this, you can just open such file with a regular text editor.
If all goes well, the last step to do is to run the database upgrade. This can be done by running the command flask db upgrade.
After this is done, open the database and see if your changes are pushed through.
An example of such migration script is given below. What I did, as a test, was changing a table name from data to geertsdata just to see if all went well.
"""empty message
Revision ID: 52a468f9be7d
Revises: 9b5e304c99ed
Create Date: 2024-10-26 11:02:03.341653
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '52a468f9be7d'
down_revision = '9b5e304c99ed'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('geertsdata',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('device', sa.String(length=100), nullable=True),
sa.Column('iec', sa.String(length=100), nullable=True),
sa.Column('connection', sa.String(length=100), nullable=True),
sa.Column('location', sa.String(length=100), nullable=True),
sa.Column('room', sa.String(length=100), nullable=True),
sa.Column('floor', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('data')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('data',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('floor', sa.INTEGER(), nullable=True),
sa.Column('device', sa.VARCHAR(length=100), nullable=True),
sa.Column('iec', sa.VARCHAR(length=100), nullable=True),
sa.Column('connection', sa.VARCHAR(length=100), nullable=True),
sa.Column('location', sa.VARCHAR(length=100), nullable=True),
sa.Column('room', sa.VARCHAR(length=100), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('geertsdata')
# ### end Alembic commands ###
I've marked a few things in red:
upgrade
geertsdata
downgrade
data
What it means is this:
If I do the upgrade, then my table data will be renamed in geertsdata.
If I do the downgrade, then my table geertsdata will be renamed in data.
So, as you can see, you can go both directions: forward and backward. That's why this migration module is keeping track of the migration history by means of different files, every file representing one step in the history of the database.
There's more than one way to migrate a database. Next to the method described in the section above there's also a migration application you can create by means of using extensively the extra methods that are available in the Migrations module.
TBC.
Open a terminal in the Python virtual environment you're working
Run flask shell: this will open a Python-alike shell
Run the command from app import db
The above assumes you have already a basic Flask framework with 'app' as application variable and 'db' as database variable
Run the command db.create_all(): this will create the database and all the defined tables in the subdirectory instance.
To add items from the Flask shell, do the following (given as an example, assuming the class Child is available in the app and has a field name next to the field id, which is the primary key):
from app import Child
one = Child(name='Child 1)
two = Child(name='Child 2)
three = Child(name='Child 3)
db.session.add(one)
db.session.add(two)
db.session.add(three)
db.session.commit
To exit the Flask shell type exit()
If you now open the database you should see the items added to it.