Flask - A list of useful “HOW TO’s”

This article presents a shortlist with useful hints and features that might help developers code faster a Flask project.

Flask - A list of useful “HOW TO’s”

This article presents a shortlist with useful hints and features that might help developers code faster a Flask project. For newcomers, Flask is a lightweight WSGI web application framework designed to get started quickly and easily, with the ability to scale up to complex applications.

  • #1 - How to code a super simple App in Flask
  • #2 - How to structure your project
  • #3 - How to enable DEBUG
  • #4 - How to set up SQLAlchemy
  • #5 - How to use the Flask CLI
  • #6 - How to migrate a Database
  • #7 - How to define and use a form
  • #8 - How to implement authentication - Flask-Login
  • #9 - How to read POST data
  • #10 - How to redirect in Flask
  • #11 - Logging in Flask
  • #12 - How to return JSON in Flask
  • #13 - How to enable CORS

Ok, let's get back to the real content - the first one is pretty basic and explains how to code a minimal Flask app in less than 1 minute (you need to type fast and have Python3 installed).


#1 - Flask, a minimal app

Open a terminal and install Flask using PIP:

$ pip install Flask

Use your preferred editor to create a file called hello.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return f'Hello from Flask!'

Once the file is saved, let's start the app:

$ env FLASK_APP=hello.py flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/

By visiting the app in the browser we should see "Hello from Flask" message.

Flask App Tutorial

#2 - How to structure your project

Being such a lightweight framework, Flask comes with great flexibility regarding the codebase structure of a project. The one suggested in this article has been used in many Flask starters generated by the AppSeed platform but we are pretty sure there are many other good configurations published on Github or private blogs.

Flask Project Structure
< PROJECT ROOT >
   |
   |-- app/__init__.py
   |-- app/
   |    |-- static/
   |    |    |-- <css, JS, images>         # CSS files, Javascripts files
   |    |
   |    |-- templates/
   |    |    |
   |    |    |-- includes/                 # Page chunks, components
   |    |    |    |
   |    |    |    |-- navigation.html      # Top bar
   |    |    |    |-- sidebar.html         # Left sidebar
   |    |    |    |-- scripts.html         # JS scripts common to all pages
   |    |    |    |-- footer.html          # The common footer
   |    |    |
   |    |    |-- layouts/                  # App Layouts (the master pages)
   |    |    |    |
   |    |    |    |-- base.html            # Used by common pages like index, UI
   |    |    |    |-- base-fullscreen.html # Used by auth pages (login, register)
   |    |    |
   |    |    |-- accounts/                 # Auth Pages (login, register)
   |    |    |    |
   |    |    |    |-- login.html           # Use layout `base-fullscreen.html`
   |    |    |    |-- register.html        # Use layout `base-fullscreen.html`  
   |    |    |
   |    |  index.html                      # The default page
   |    |  page-404.html                   # Error 404 page (page not found)
   |    |  page-500.html                   # Error 500 page (server error)
   |    |    *.html                        # All other pages provided by the UI Kit
   |
   |-- requirements.txt
   |
   |-- run.py
   |
   |-- ************************************************************************

The relevant files:

  • run.py - the entry point used to start the application
  • requirements.txt - a file that specify all dependencies (for now is just Flask)
  • app - the application folder where we will add our code
  • app/__init__.py - This file is required to let us use the app as a Python Package
  • app/static - this folder will contain design assets: JS, css and images.
  • templates - the directory with pages, layouts, and components used by Flask to generate some nice pages for us
Regarding this codebase structure, here is a short list with open-source projects built with a similar files structure:
Datta Able - Open-source Flask starter.
Datta Able - Open-source Flask starter.

#3 - How to enable DEBUG

Sometimes during our development, we need to investigate errors in deep by checking the values of the variables, freeze the application execution and inspect the context or visualize a filtered products list in the user shopping cart. We can achieve this in two ways:

  • using logs and inject print() statements all over the place
  • using a Debugger and gain full control over the app without writing non-functional code in all controllers and helpers.

Flask comes with a Built-in Debugger that allows us to inspect in deep application runtime execution. Flask automatically detects and enable the debugger by scanning the application environment:

$ export FLASK_ENV=development
$ flask run

When running from Python code, the app object accept the debug variable as an argument:

app.run(debug=True)

At this point, app should run in DEBUG more. Another useful option is to use the debugger toolbar plugin.

Adding an external Debugger Toolbar

This plugin provides useful runtime information like HTTP Headers, configuration, rendered templates, and SqlAlchemy queries executed during requests.

To enable these features is pretty easy. First, we need to install the plugin (via PIP):

$ pip install flask-debugtoolbar

The next step is to use the extension over our Flask app:

from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension

app = Flask(__name__)

# the toolbar is only enabled in debug mode:
app.debug = True

# set a 'SECRET_KEY' to enable the Flask session cookies
app.config['SECRET_KEY'] = '<replace with a secret key>'

toolbar = DebugToolbarExtension(app)

If your product uses an app-factory pattern to construct the Flask application, please use this code snippet:

...
# Initialize the Debug Toolbar
toolbar = DebugToolbarExtension()

# Make sure the app has DEBUG enable 
app = create_app()

# Inject the toolbar object in the Flask app object
toolbar.init_app(app)
...
For more information related to Debugging in Flask, please access

#4 - How to set up Flask-SQLAlchemy

Flask-SQLAlchemy is a wrapper over the popular SQLAlchemy Python toolkit that empowers developers to manage a database using an object-oriented approach.

The setup required by Flask-SqlAlchemy is minimal:

$ pip3 install flask_sqlalchemy

Once the package is installed, we can use it in the source code:


from flask import Flask

# Import SQLAlchemy
from flask_sqlalchemy import SQLAlchemy 

app = Flask(__name__)

# Define the database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'

# Inject SQLAlchemy magic 
db = SQLAlchemy(app)

# Sample model handled by SqlAlchemy  
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

To effectively create a User table in the database, we need to use the Flask CLI (see the next section)


#5 - How to use the Flask CLI

To use the Flask CLI, the FLASK_APP environment variable should point to the application bootstrap script. Once we have this variable present in the working environment, we can launch the Flask console and interact with our application in a different way:

$ export FLASK_APP=app # adjust to mach your entry point
$ flask shell

Using the CLI we can create the table defined in the previous section:

$ flask shell
>>> from app import db # 
>>> db.create_all()    # All tables are created

Using the CLI we can select and update users (or any other defined tables) as well:

$ flask shell
>>> from app import User
>>> User.query.all()
[<User u'admin'>, <User u'guest'>]
For more resources, please access:

#6 - How to migrate a Database

A migration means our database has changed:

  • New table added
  • Existing table is updated with a new column
  • The type of an existing column has changed. For instance we have a column that store 100 characters and we need to extend it to 500.

To safely update the database, we need to "migrate" to the new structure. The Flask plugin that saves us from doing the dirty work is Flask Migrate.

$ pip install Flask-Migrate

To effectively work, Flask-Migrate must be injected in the Flask object:

...
app = Flask(__name__)
...
migrate = Migrate(app, db) # <- The magic line

If we execute the migration for the first time, we should run:

$ # This will create the migrations folder
$ flask db init

Let's update the User model with a new field: role:

# Sample model handled by SqlAlchemy  
class User(db.Model):
    id       = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email    = db.Column(db.String(120), unique=True, nullable=False)
    
    # The new field 
    role     = db.Column(db.Integer, primary_key=False)

To update the database, two steps are required: generate the SQL according to our change and apply it on the database.

Generate the SQL

$ flask db migrate

Update the Database - using the SQL generated in the previous step

$ flask db upgrade

At this point, we can inspect the table to visualize the presence of the role column and use it in our code.

Resources

#7 - How to define and use a form

Flask assists us a lot regarding forms via a dedicated library: flask_wtf/FlaskForm. We can easily define, secure, and validate forms using this package.

Here is the definition of a Login form:

from flask_wtf          import FlaskForm
from wtforms            import StringField, PasswordField

class LoginForm(FlaskForm):
	username    = StringField  (u'Username'  , validators=[DataRequired()])
	password    = PasswordField(u'Password'  , validators=[DataRequired()])

As mentioned, FlaskForm comes with pre-defined types and validators for password, mandatory fields check, email and much more.

The view (aka the page) that use it:

<form role="form" method="post" action="">
    {{ form.username(placeholder="Username") }}
    {{ form.password(placeholder="Password",type="password") }}
</form>

To secure the form using a CRSF token we need a single line:

<form role="form" method="post" action="">
    
    {{ form.hidden_tag() }} <!-- CSRF Protection -->
    
    {{ form.username(placeholder="Username") }}
    {{ form.password(placeholder="Password",type="password") }}
</form>

For newcomers, a simplified CSRF mechanism is presented below:

  • The server generates the CRSF Token
  • The form is built and sent to the client browser along with the token
  • The user fills and submits the form
  • The server receives a new POST request and checks the CSRF token authenticity
  • If the token is not validated request is rejected with a 403 (not authorized) status
More forms resources

#8 - How to implement authentication - Flask-Login

Flask-Login provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering users’ sessions.

Let's highlight the code to integrate Flask-Login in a real application.

Update your user model to inherit the UserMixin class. This is necessary because Flask expects a minimum set of handlers to be implemented by the user model: is_authenticated, is_active, get_id. To skip over this work, UserMixin class saves the day for us.

from app         import db
from flask_login import UserMixin

# User model uses UserMixin as parent class 
class User(UserMixin, db.Model):

    id       = db.Column(db.Integer,     primary_key=True)
    user     = db.Column(db.String(64),  unique = True)
    email    = db.Column(db.String(120), unique = True)
    password = db.Column(db.String(500))

The next step is to define user_loader a method used by Login-Manager to identify the current authenticated user information: id, email, and all other fields provided by the User class.

...
# provide login manager with load_user callback
@lm.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
...

Once we have the User table updated in the database, we can successfully login.

The login view (aka login page)

...
<form method="post" action="" class="mt-4">
    {{ form.hidden_tag() }}
    {{ form.username(class="form-control") }}
    {{ form.password(class="form-control", type="password") }}
    
    <button type="submit" name="login">Sign in</button>
</form> 
    

Once the user fills in the user and the password, all information is sent to the server via a POST request.

Login  - The controller code

# Authenticate user
@app.route('/login.html', methods=['GET', 'POST'])
def login():
    
    # Declare the login form
    form = LoginForm(request.form)
	
    # The information returned to the user
    msg = "Your cool!"

    # Apply all validations defined in the form
    if form.validate_on_submit():

        # recover form information
        username = request.form.get('username', '', type=str)
        password = request.form.get('password', '', type=str) 

        # recover the user from database using username (unique information)
        user = User.query.filter_by(user=username).first()

		# If the user is found, validate the password
        if user:
            
            # BC = BCrypt library 
            if bc.check_password_hash(user.password, password):
                # The user is now authenticated
            	login_user(user)
            else:
                msg = "Wrong password. Please try again."
        else:
            msg = "Unknown user"

    return render_template( 'accounts/login.html', form=form, msg=msg )

The controller flow explained:

  • Local variable form is initialized using the request form (sent by the user)
  • If the form is valid (user and password are present) the code extracts the values
  • Select the user from the database using the name
  • Check the password by comparing the database value with the one provided by the user and hashed by the BCrypt library.
  • If all is good, the user session is created via "login_user()" handler
  • Error cases are flagged with a message to inform the user what was wrong.
Flask authentication resources:
Flask Pixel Lite - Free Flask Starter.
Flask Pixel Lite - Free Flask Starter.

#9 - How to read POST data

For newcomers, POST and GET are different methods used to submit information to a server. During a GET request, the variables are appended to the page URL:

GET/login.html?user=Biden&pass=MAGA2020 

POST requests use the request body to bundle the information submitted by the user.

In both cases, we can easily read POST and GET parameters with Flask via the request object. Here is a list of request objects that we can refer to extract the information:

  • request.args: the key/value pairs in the URL query string
  • request.form: the key/value pairs in the body, from an HTML post form
  • request.json: parsed JSON data

A real sample - used to handle registration flow

@app.route('/register.html', methods=['GET', 'POST'])
def register():
    
    # declare the Registration Form
    form = RegisterForm(request.form)
	
    # request is GET - just display the form
    if request.method == 'GET': 

        return render_template( 'accounts/register.html', form=form, msg=msg )

    # Here we have a POST request
    if form.validate_on_submit():

        # READ information from a POST request
        username = request.form.get('username', '', type=str)
        password = request.form.get('password', '', type=str) 
        email    = request.form.get('email'   , '', type=str) 

As mentioned above, the composite request.form  provides the form fields in key/value pairs bundled in the request body.

More information on this topic:

#10 - How to redirect in Flask

This is a simple one.  Flask exposes a "redirect" handler to keep us lazy:

import os
from flask import Flask,redirect

app = Flask(__name__)

@app.route('/')
def not_me():
    
    # redirect to another page
    return redirect("/register")

    # redirect to external page
    return redirect("https://google.com")

    # redirect with HTTP code
    return redirect("https://google.com", code=301)
Resources:

#11 - Logging using standard Python library

Flask uses the native Python logging system (no configuration required). To log a message during a request, we need to call the logger interface and set the message we want to be visible in logs:

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.DEBUG)

@app.route('/')
def hello():
    app.logger.info('Logging is up!')
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

By running the app, we should see the logging message in the console:

* Serving Flask app "app.py"
* Environment: development
INFO:werkzeug: * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
INFO:flask.app:Logging is up
INFO:werkzeug:127.0.0.1 - - [01/Jun/2019 17:03:09] "GET / HTTP/1.1" 200 -

Logs might be useful when we want to trace runtime errors  in production or save other relevant events in the logs for the application owner.

The above sample will push the log to the console but we can easily push messages to a file:

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(filename='app.log', level=logging.DEBUG)

@app.route('/')
def hello():
    app.logger.info('Logging is up!')
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

After a first request, a new file named "app.log" should be created with our log message as content.

Flask logging resources

#12 - How to return JSON

Flask comes with native support for JSON management via jsonify interface. Let's take a look at this self-descriptive sample:

from flask import jsonify

@app.route('/_get_current_user')
def get_json():
    return jsonify(
        message="Hello",
        from="from JSON"
    )

The output should be as below:

{
    "message": "Hello",
    "from": "from JSON"
}

Something useful: Let's return a registered user using JSON format:

from flask import jsonify

@app.route('/user_json')
def get_json():
    
    user = User.query.filter_by(username='testme').first()
    
    # Using the user object (without __dict__) will generate a runtime error 
    # TypeError: 'user' is not JSON serializable
    return jsonify( user.__dict__ )

The response should contain all fields defined in the User table:

{
    "id": "1",
    "username": "testme",
    "email":"test@google.com"
}
More resources:

#13 - How to enable CORS in Flask

This feature is useful when our Flask backend is consumed by decoupled entities like a mobile UI interface, external systems that push or request the information provided by the Flask backend.

As usual, Flask has a module for this flask-cors.

$ Install the module via PIP
$ pip install -U flask-cors

Let's take a look at the sample provided in the official docs:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"

We can also isolate CORS for specific paths like /api/*:

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.route("/api/v1/users")
def list_users():
  return "user example"
CORS Resources:

Thank you!  For more resources, please access