Reading the Flask Documentation / Introduction to Flask

Why Learn Flask? It is much easier to implement some machine learning / artificial intelligence tasks using Python rather than JavaScript or some other backend language. There are some tools that are built in and for Python, and so it is best to use Python to implement them. I plan on adding a Python systemd service to my server to implement some machine learning / AI functionality to this site. I also may use nbconvert for Jupyter Notebook to HTML.

Flask is a Python-based web framework that allows developers to build web applications quickly and easily.

References

Installation

Create an Environment:

# Linux
mkdir myproject
cd myproject
python3 -m venv .venv

# Windows
mkdir myproject
cd myproject
py -3 -m venv .venv

Active the Environment:

# Linux
. .venv/bin/activate

# Windows
.venv\Scripts\activate

Install Flask:

pip install Flask

Quickstart

A Minimal Application

The following contains a small Flask application. Make sure to not save files as flask.py as this would conflict with flask itself. Save the file below as hello.py.

# Import the Flask class
from flask import Flask

# Create an instance of the class. The first argument is the name
# of the application's module or package. __name__ is a convenient
# shortcut for this that is appropriate for most use cases. This 
# is needed so that Flask knows where to look for resources such
# as templates and static files.
app = Flask(__name__)

# Use thr route() decorator to tell Flask what URL should trigger our
# function
@app.route("/")
def hello_world():
    """
    The function returns a message that we want to display in the user's browser
    """
    return "<p>Hello, World!</p>"

To run the application, use the flask command or python -m flask. You need to tell Flask where the application is with the --app option. (Note: If the file is named app.py or wsgi.py, you don;t have to use --app.)

$ flask --app hello run
* Serving Flask app 'hello'
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

The flask run command can do more than just start the development server. By enabling debug mode, the server can automatically reload if the code changes, and will show interactive debugger in the browser if an error occurs during a request. Do not run debugger in a production environment. To enable debug mode, use the --debug option.

$ flask --app hello run --debug
* Serving Flask app 'hello'
* Debug mode: on
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: nnn-nnn-nnn

HTML Escaping

When returning HTML (the default response type in Flask), any user-provided values must be escaped to protect from injection attacks. HTML templates rendered with Jinja will do this automatically. escape() can be used to do this manually. Note: The <name> in the route captures a value from the URL and passes it to the view function.

from markupsafe import escape

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

Routing

Use the route() decorator to bind a function to a URL.

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

Variable Rules

You can add variable sections to a URL by marking sections with <variable_name>. Your function then receives the <variable_name> as a keyword argument. Optionally, you can use a converter to specify the type of an argument like <converter:variable_name>.

from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return f'User {escape(username)}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return f'Post {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return f'Subpath {escape(subpath)}'
Converter Types
string (default) accepts any text without a slash
int accepts positive integers
float accepts positive floating point values
path like string but also accepts slashes
uuid accepts UUID strings

Unique URLs / Redirection Behavior

# If you access the URL without a trailing slash (`/projects`), 
# Flask redirects you to the canonical URL with the trailing slash (`/projects/`).
@app.route('/projects/')
def projects():
    return 'The project page'
# The canonical URL for the about endpoint does not have a trailing slash. 
# It’s similar to the pathname of a file. Accessing the URL with a trailing slash (/about/) 
# produces a 404 “Not Found” error. This helps keep URLs unique for these resources, 
# which helps search engines avoid indexing the same page twice.
@app.route('/about')
def about():
    return 'The about page'

HTTP Methods

Web applications use different HTTP methods when accessing URLs. By default, a route only answres GET requests. (If GET is present, Flask automatically handles HEAD method and OPTIONS requests according to the HTTP RFC) You can use the methods argument of the route() decorator to handle different HTTP methods.

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

# You can also separate views for different methods into different functions.
@app.get('/login')
def login_get():
    return show_the_login_form()

@app.post('/login')
def login_post():
    return do_the_login()

Rendering Templates

Jinja2

Flask configures the Jinja2 template engine for you automatically. Templates can be used to generate any type of text file. To render a template, you can use the render_template() method. All you have to do is provide the name of the template and the variables you want to pass to the template engine as keyword arguments. Flask will look for templates in the templates folder. So if your application is a module, this folder is next to that module, if it's a package, it's actually inside your package. Inside templates you also have access to the config, request, session, and g objects as well as the url_for() and get_flashed_messages() functions. Templates are especially useful if inheritance is used. If you want to know how that works, see Template Inheritance. Basically template inheritance makes it possible to keep certain elements on each page (like header, navigation, and footer).

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', person=name)

Accessing Request Data

In Flask applications the data a client sends to the server is provided by the global request object.

Context Locals

Certain objects in Flask are global objects, but not of the usual kind. These objects are actually proxies to objects that are local to a specific context. Imagine the context being the handling thread. A request comes on and the web server decides to spawn a new thread (or something else, the underlying object is capable of dealing with concurrency systems other than threads). When Flask starts its internal request handling it figures out that the the current thread is the active context and binds the current application and the WSGI environments to that context (thread). It does that in an intelligent way so that one application can invoke abother application without breaking.

The Request Object

You need to import the request object from the flask module:

from flask import request

The current request method is available by using the method attribute. To access form data (data transmitted in a POST or PUT request), you can use the form attribute:

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)

A KeyError is raised if the key does not exist in the form attribute. To access parameters submitted in the URL (?key=value), you can use the args attribute:

searchword = request.args.get('key', '')

It is recommended to access URL parameters with the get or by catching the KeyError.

File Uploads

You can handle uploaded files with Flask easily. Uploaded files are stored in memory or at a temporary location on the filesystem. You can access those files by looking at the files attribute on the request object. Each uploaded file is stored in that dictionary. It haves just like a standard Python file object, but it also has a save() method that allows you store that file on the filesystem of the server.

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

Cookies

To access cookies, you can use the cookies attribute. To set cookies you can use the set_cookie method of response objects. The cookie attribute of request objects is a dictionary with all the cookies the client transmits. If you want to use sessions, do not use the cookies directly but instead use the Sessions in Flask that add some security on top of cookies for you:

from flask import request
# Reading Cookies
@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

from flask import make_response
# Setting cookies
@app.route('/home')
def home():
    # make_response function allows you to modify response
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

Redirects and Errors

To redirect a user to another endpoint, use the redirect() function; to abort a request early with an error code, use the abort() function:

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

By default, a black and white error page is shown for each error code. If you want to customize the error page, you can use the errorhandler() decorator:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    # The 404 after the render_template() call tells flask that the status code of the page should 
    # be 404 which means not found. By default, 200 is assumed
    return render_template('page_not_found.html'), 404

About Responses

The return value from a view function is automatically converted into a response object for you. If the return value is a string, it's converted into a response object with the string as response body, a 100 OK status code and a text/html mimetype. If the return value is a dict or list, jsonify() is called to produce a response.

If an iterator or generator returning strings or bytes is returned from the function, a streaming response is returned to the user. If you want to get hold of the resulting response object inside the view you can use the make_response() function.

Sessions

In addition to the request object there is also a second object called session which allows you to store information specific to a user from one request to the next. If you want to handle sessions on the server-side, there are several Flask extensions that support this,

from flask import session

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}'
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

Message Flashing

Flask provides a really simple way to give feedback to a user with the flashing system. The flashing system basically makes it possible to record a message at the end of a request and access it on the next (and only the next) request. This is usually combined with a layout template to expose the message. To flasj a message use the flash() method, to get a hold of the messages you can use the get_flashed_messages() which is also available in the templates.

Logging

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

Hooking in WSGI Middleware

To add WSGI middleware to your Flask application, wrap the application's wsgi_app attribute. The example below applies Werkzeug's ProxyFix middleware for running behind Nginx:

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

Tutorial

Application Setup

A Flask application is an instance of the Flask class. Everything about the application, such as configuration and URLs, will be registered with this class. The most straightforward way to create a Flask application is is to create a global Flask instance directly at the top of your code. Instead of creating a Flask application globally, you will create it inside a function. This function is known as the application factory. Any configuration, registration, and other setup the application needs will happen inside the function, then the application will be returned.

import os

from flask import Flask


def create_app(test_config=None):
    """
    This is the application factory function
    """
    # create and configure the app
    """
    __name__ is the name of the current Python module. The app needs to know where it's located to set up some paths
    """
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev', # Used nu Flask extensions to keep data safe. Should be random value
        DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), # Where the database file will be saved
    )

    if test_config is None:
        # load the instance config, if it exists, when not testing
        app.config.from_pyfile('config.py', silent=True)
    else:
        # load the test config if passed in
        app.config.from_mapping(test_config)

    # ensure the instance folder exists
    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    # a simple page that says hello
    @app.route('/hello')
    def hello():
        return 'Hello, World!'

    return app

Connect to the Database

The first thing to do when working with most Python database libraries is to create a connection to it. Any queries and operations are performed using the connection, which is closed after the work is finished. In web applications, this connection is typically tied to the request. It is created at some point when handling a request, and closed before the response is sent.

g is a special object that is unique for each request. It is used to store data that might be accessed by multiple functions during the request. The connection is stored and reused instead of creating a new connection if get_db is called a second time in the same request.

current_app is another special object that points to the Flask application handling the request.

import sqlite3

import click
from flask import current_app, g


def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(
            current_app.config['DATABASE'],
            detect_types=sqlite3.PARSE_DECLTYPES
        )
        g.db.row_factory = sqlite3.Row

    return g.db


def close_db(e=None):
    """
    close_db checks if a connection was created by checking if g.db was set. If the connection
    exists, it is closed. Further down you will tell  your application about the close_db function 
    in the application factory so that it is called after each request. 
    """
    db = g.pop('db', None)

    if db is not None:
        db.close()

app.teardown_appcontext() tells Flask to call that function when cleaning up after returning the response. app.cli.add_command() adds a new command that can be called with the flask command.

def init_app(app):
    app.teardown_appcontext(close_db)
    # A function that creates a toy DB for demonstration purposes
    app.cli.add_command(init_db_command)

Blueprints and Views

A view function is the code you write to respond to requests to your application. Flask uses patterns to match the incoming request URL to the view that should handle it. The view returns data that Flask turns into an outgoing response. Flask can also go the other direction and generate a URL to a view based on its name and arguments.

A Blueprint is a way to organize a group of related views and other code. Rather than registering views and other code directly with an application, they are registered with a blueprint. Then the blueprint is registered with the application when it is available in the factory function.

import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db
# This creates a Blueprint named auth. Like the application object, the 
# blueprint needs to know where it's defined, so __name__ is passed as the 
# second argument. The url_prefix will be prepended to all the URLs associated
# with the blueprint.
bp = Blueprint('auth', __name__, url_prefix='/auth')
def create_app():
    app = ...
    # existing code omitted

    # Register the blueprint
    from . import auth
    app.register_blueprint(auth.bp)

    return app

Make the Project Installable

Making the project installable means that you can build a wheel file and install that in another environment, just like you installed Flask in your project's environment. This makes deploying your project the same as installing any other library, so you're using all the standard Python tools to manage everything.

Describe the Project

The pyproject.toml file describes the project and how to build it.

[project]
name = "flaskr"
version = "1.0.0"
description = "The basic blog app built in the Flask tutorial."
dependencies = [
    "flask",
]

[build-system]
requires = ["flit_core<4"]
build-backend = "flit_core.buildapi"

Install the Project

Use pip to install your project in the virtual environment.

$ pip install -e .

The command above tells pip to find the pyproject.toml in the current directory and install the project in editable or development mode. Editable mode means that as you make changes to your local code, you’ll only need to re-install if you change the metadata about the project, such as its dependencies.

You can observer that the project is now installed with pip list.