by Niraj Zade

Flask done quick

get going with flask



Note

This is currently a simple info dump. Organization into progressive steps is pending.

The navy uses Django, pirates use flask

A basic complete flask application

form flask import Flask
app = Flask(__name__)

@app.route('/')
def index |:
    return '<h1>Hello World</h1>'

Cli commands

Run a server:

# linux, mac    
$ export FLASK_APP=hello.py
$ flask run

# windows
$ set FLASK_APP=hello.py
$ flask run

Debug mode

# run before `flask run`
$ export FLASK_DEBUG=1

Get app Urls*

$ python
>>> from hello import app
>>> app.url_map

Configuration

app.config is a general place to store configuration variables used by flask, extensions and the app itself.

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

Routes

Static

@app.route('/')
def index |:
    return '<h1>Hello World</h1>'

Dynamic

@app.route('/user/<name>')
def user(name):
return '<h1>Hello, {}!</h1>'.format(name)

Request-response cycles

Flask has 2 contexts: Aplication context and request context

var context
current_app app app instance of current active app
g app temp var used while handling current request. reset with each request
request request request object
session request @user session, a dict to store values rememebred between requests

Request objects:

Attribute/Method Description
form dictionary with all the form fields submitted with the request
args dictionary with all the arguments passed in the query string of the URL
values dictionary that combines the values in form and args
cookies dictionary with all the cookies included in the request
headers dictionary with all the HTTP headers included in the request
files dictionary with all the file uploads included with the request
get_data
get_json
blueprint name of the Flask blueprint that is handling the request. Blueprints are introduced in Chapter 7
endpoint name of the Flask endpoint that is handling the request. Flask uses the name of the view function as the endpoint name for a route
method HTTP request method, such as GET or POST
scheme URL scheme ( http or https )
is_secure
host host defined in the request, including the port number if given by the client
path path portion of the URL
query_string query string portion of the URL, as a raw binary value
full_path path and query string portions of the URL
url complete URL requested by the client
base_url same as url , but without the query string component
remote_addr the IP address of the client
environ the raw WSGI environment dictionary for the request

Request Hooks:

Hook when
before_request function to run before each request
before_first_request
after_request run only if no unhandled exceptions occured
teardown_request run even if unhandled exceptions occured

Responses

return status code:

return '<h1>html</h1>', 200

response object:

from flask import make_response
...
    response = make_response('<h1>This doc is a cookie</h1>')
    response.set_cookie('answer','42')
    return 
...
Attribute Method
status_code numeric HTTP status code
headers dictionary-like object with all the headers that will be sent with the response
set_cookie
delete_cookie
content_length length of the response body
content_type media type of the response body
set_data
get_data

Redirect response:

Can be created with a 3 valued return, or with a response object, or with redirect | helper function provided by flask

from flask import redirect
...
    return redirect('http://www.example.com')
...

Abort:

Does not return control back to fuction; raises an exception.

@app.route('/user/<id>')
def get_user(id):
user = load_user(id)
if not user:
    abort(404)
return '<h1>Hello, {}</h1>'.format(user.name)

Custom error pages

@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404

Jinja templating

Rendering:

from flask import render_template
...
    @app.route('/user/<name')
    def user(name):
    return render_template('user.html', name=name)
...

Variables:

<!-- Plain variable -->
{{ variable_name }}
<!-- Variable with filter -->
{{ name|capitalize }}

filters are:

1 2 3 4
abs float lower round
attr forceescape map safe
trim batch format max
select truncate capitalize groupby
min selectattr unique center
indent pprint slice upper
default int random sort
urlencode dictsort join reject
string urlize escape last
rejectattr striptags wordcount filesizeformat
length replace sum wordwrap
first list reverse title
xmlattr

Control structures:

<!-- if else -->
{% if users %}
    ...
{% else %}
    ...
{% endif %}

<!-- for -->
{% for element in elements %}
    ...
{% endfor %}

<!-- macros -->
{% macro render_comment(comment) %}
    <li>{{ comment }}</li>
{% endmacro %}
<!-- macros can be imported form other files -->
{% import 'macros.html' as macros %}

<!-- blocks -->
{% extends "base.html" %}
{% block title %}   Index   {% endblock %}
{% block head %}
    ...
{% endblock %}
{% block body %}
    ...
{% endblock %}

Bootstrap integration

Shell:

# installation
$ pip install bootstrap

Python:

from flask_bootstrap import Bootstrap
...
bootstrap = Bootstrap(app)

Html:

<!-- in html -->
{% extends "bootstrap/base.html" %}
<!-- use bootstrap classes below -->
<div class="container">
    ...
</div>
block name description
doc The entire HTML document
html_attribs Attributes inside the html tag
html The contents of the html tag
head The contents of the head tag
title The contents of the title tag
metas The list of meta tags
styles CSS definitions
body_attribs Attributes inside the body tag
body The contents of the body tag
navbar User-defined navigation bar
content User-defined page content
scripts JavaScript declarations at the bottom of the document

Urls:

Use flask's url_for() helper method

url_for('user',name='john',_external=True)
returns: http://localhost:5000/user/john
url_for('user', name='john', page=2, version=1)
returns return /user/john?page=2&version=1

Static files

Flask automatically supports routes for static files, defined as /static/filename

{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"
type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"
type="image/x-icon">
{% endblock %}

Temporal localization

Installation:

$ pip install flask-moment

Usage:

from flask import flask_moment
moment = Moment(app)

Flask-Moment implements:

format() , fromNow() , fromTime() , calendar() , valueOf() , and unix()

More info

Note

flask-moment assumes timestamps handled by applications are in UTC

Example:

from datetime import datetime
@app.route('/')
def index():
    return render_template('index.html',
    current_time=datetime.utcnow())

<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>

Forms

$ pip install flask-wtf

Configuration:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'

Note

flask-wtf requires a secret key to be configured. It's used to protect against CSRF attacks.

Example:

from flask-wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')
WTForms html field description
BooleanField checkbox with True and False values
DateField datetime.date value in a given format
DateTimeField datetime.datetime value in a given format
DecimalField accepts a decimal.Decimal value
FileField file upload field
MultipleFileField multiple file upload field
HiddenField hidden text field
FieldList list of fields of a given type
FloatField floating-point value
FormField form embedded as a field in a container form
IntegerField integer value
PasswordField password text field
RadioField list of radio buttons
SelectField drop-down list of choices
SelectMultipleField drop-down list of choices with multiple selection
SubmitField form submission button
StringField text field
TextAreaField multiple-line text field
WTForms validator description
DataRequired validates that the field contains data after type conversion
Email validates an email address
EqualTo compares the values of two fields; useful when requesting a password to be entered twice for confirmation
InputRequired validates that the field contains data before type conversion
IPAddress validates an IPv4 network address
Length validates the length of the string entered
MacAddress validates a MAC address
NumberRange validates that the value entered is within a numeric range
Optional allows empty input in the field, skipping additional validators
Regexp validates the input against a regular expression
URL validates a URL
UUID validates a UUID
AnyOf validates that the input is one of a list of possible values
NoneOf validates that the input is none of a list of possible values

Html exmaple:

<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(id='my-text-field') }}
{{ form.submit() }}
</form>

Can also use bootstrap extension as ahigh level helper to easily create forms

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}

Render the form, and recieve data entered by the user

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    form = NameForm()
    if form.validate_on_submit():
    name = form.name.data
    form.name.data = ''
    return render_template('index.html', form=form, name=name)

Note

When methods is not given, the view function is registered to handle GET requests only.

User sessions

An encrypted client cookie is used to maintain sessions

from flask import Flask, render_template, session, redirect, url_for
@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        session['name'] = form.name.data
        return redirect(url_for('index'))
    return render_template('index.html', form=form, name=session.get('name'))

Flashing changes:

Use the flash() method

Example:

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        old_name = session.get('name')
    if old_name is not None and old_name != form.name.data:
        flash('Looks like you have changed your name!')
        session['name'] = form.name.data
        return redirect(url_for('index'))
    return render_template('index.html',
        form = form, name = session.get('name'))

<!-- html -->
{% block content %}
    <div class="container">
        {% for message in get_flashed_messages() %}
            <div class="alert alert-warning">
                <button type="button" class="close" data-dismiss="alert">&times;</button>
                {{ message }}
            </div>
        {% endfor %}
        {% block page_content %}{% endblock %}
    </div>
{% endblock %}

Databases

$ pip install flask-sqlalchemy
DB engine URL
MySQL mysql://username:[email protected]/database
Postgres postgresql://username:[email protected]/database
SQLite (Linux, macOS) sqlite:////absolute/path/to/database

Db configuration:

import os
from flask_sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
    'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

Defining models:

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

    def __repr__(self):
    return '<Role %r>' % self.name

Common SQLAlchemy column types:

SQLAlchemy type python type desc
integer int regular integer, typically 32 bits
smallinteger int short-range integer, typically 16 bits
biginteger int or long unlimited precision integer
float float floating-point number
numeric decimal.decimal fixed-point number
string str variable-length string
text str variable-length string, optimized for large or unbounded length
unicode unicode variable-length unicode string
unicodetext unicode variable-length unicode string, optimized for large or unbounded length
boolean bool boolean value
date datetime.date date value
time datetime.time time value
datetime datetime.datetime date and time value
interval datetime.timedelta time interval
enum str list of string values
pickletype any python object automatic pickle serialization
largebinary str binary blob

Common SQLAlchemy column options

Option name Description
primary_key if set to true, the column is the table’s primary key
unique if set to true, do not allow duplicate values for this column
index if set to true, create an index for this column, so that queries are more efficient
nullable if set to true, allow empty values for this column. if set to false , the column will not allow null values
default define a default value for the column

Relationships:

class Role(db.Model):
    # ...
    users = db.relationship('User', backref='role')

class User(db.Model):
    # ...
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

Common SQLAlchemy relation options

option description
backref Add a back reference in the other model in the relationship
primaryjoin Specify the join condition between the two models explicitly. This is necessary only for ambiguous relationships
lazy Specify how the related items are to be loaded. Possible values are select (items are loaded on demand the first time they are accessed), immediate (items are loaded when the source object is loaded), joined (items are loaded immediately, but as a join), subquery (items are loaded immediately, but as a subquery), noload (items are never loaded), and dynamic (instead of loading the items, the query that can load them is given)
uselist If set to False , use a scalar instead of a list
order_by Specify the ordering used for the items in the relationship
secondary Specify the name of the association table to use in many-to-many relationships
secondaryjoin Specify the secondary join condition for many-to-many relationships when SQLAlchemy cannot determine it on its own

Database operations

$ flask shell
>>> from hello import db
>>> db.create_all()

>>> db.drop_all()
>>> db.create_all()

Inserting rows:

>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)

Changes are managed through db.session. To prepare objects to be written to database, they must be added to the session

>>> db.session.add(admin_role)
>>> db.session.add(mod_role)
>>> db.session.add(user_role)
>>> db.session.add(user_john)
>>> db.session.add(user_susan)
>>> db.session.add(user_david)

or

>>> db.session.add_all([admin_role, mod_role, user_role,
    ...
    user_john, user_susan, user_david])

Commit:

>>> db.session.commit()

Note

Database session are also called transactions. They are not related to flask sessions.

Modify row:

Rename Admin role to Administrator

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

Delete row:

>>> db.session.delete(mod_role)
>>> db.session.commit()
>>> db.session.commit()

Query row:

>>> User.query.all()
[<User 'john'>, <User 'susan'>, <User 'david'>]
>>> User.query.filter_by(role=user_role).all()
[<User 'susan'>, <User 'david'>]

To inspect query generated by SQLAlchemy:

>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username,
users.role_id AS users_role_id \nFROM users \nWHERE :param_1 = users.role_id'

Common query filters

filter Description
filter() returns a new query that adds an additional filter to the original query
filter_by() returns a new query that adds an additional equality filter to the original query
limit() returns a new query that limits the number of results of the original query to the given number
offset() returns a new query that applies an offset into the list of results of the original query
order_by() returns a new query that sorts the results of the original query according to the given criteria
group_by() returns a new query that groups the results of the original query according to the given criteria

Common query executors

executor desc
all() Returns all the results of a query as a list
first() Returns the first result of a query, or None if there are no results
first_or_404() Returns the first result of a query, or aborts the request and sends a 404 error as the response if there are no results
get() Returns the row that matches the given primary key, or None if no matching row is found
get_or_404() Returns the row that matches the given primary key or, if the key is not found, aborts the request and sends a 404 error as the response
count() Returns the result count of the query
paginate() Returns a Pagination object that contains the specified range of results

Dynamic database relationships:

class Role(db.Model):
    # ...
    users = db.relationship('User', backref='role', lazy='dynamic')
    # ...

Why?

With the relationship configured in this way, user_role.users returns a query that hasn’t executed yet, so filters can be added to it:

>>> user_role.users.order_by(User.username).all()
[<User 'david'>, <User 'susan'>]
>>> user_role.users.count()
2

Database Use in View Functions

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = U12'known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html',
        form=form, name=session.get('name'),
        known=session.get('known', False))

Html example: using known argument to customize greeting

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
    {% if not known %}
    <p>Pleased to meet you!</p>
    {% else %}
    <p>Happy to see you again!</p>
    {% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

Database Migrations with Flask-Migrate

$ pip install flask-migrate

from flask_migrate import Migrate
# ...
migrate = Migrate(app, db)

Add support for database migrations with the init subcommand:

$ flask db init
    Creating directory /home/flask/flasky/migrations...done
    Creating directory /home/flask/flasky/migrations/versions...done
    Generating /home/flask/flasky/migrations/alembic.ini...done
    Generating /home/flask/flasky/migrations/env.py...done
    Generating /home/flask/flasky/migrations/env.pyc...done
    Generating /home/flask/flasky/migrations/README...done
    Generating /home/flask/flasky/migrations/script.py.mako...done
    Please edit configuration/connection/logging settings in
    '/home/flask/flasky/migrations/alembic.ini' before proceeding.

This command creates a migrations directory, where all the migration scripts will be stored.

Important

The files in a database migration repository must always be added to version control along with the rest of the application.

Caution

Automatic migrations are not always accurate and can miss some details that are ambiguous. For example, if a column is renamed, an automatically generated migration may show that the column in question was deleted and a new column was added with the new name. Leaving the migration as is will cause the data in this column to be lost! For this reason, migration scripts generated automati‐ cally should always be reviewed and manually corrected if they have any inaccuracies.

  1. Make the necessary changes to the model classes.
  2. Create an automatic migration script with the flask db migrate command.
  3. Review the generated script and adjust it so that it accurately represents the changes that were made to the models.
  4. Add the migration script to source control.
  5. Apply the migration to the database with the flask db upgrade command.

Create migraitons script

$ flask db migrate -m "initial migration"
    INFO [alembic.migration] Context impl SQLiteImpl.
    INFO [alembic.migration] Will assume non-transactional DDL.
    INFO [alembic.autogenerate] Detected added table 'roles'
    INFO [alembic.autogenerate] Detected added table 'users'
    INFO [alembic.autogenerate.compare] Detected added index
    'ix_users_username' on '['username']'
    Generating /home/flask/flasky/migrations/versions/1bc
    594146bb5_initial_migration.py...done

Upgrade:

$ flask db upgrade

Note

If you have been working with the application in its previous stages, you already have a database file that was created with the db.create_all() function earlier. In this state, the flask db upgrade will fail because it will try to create database tables that already exist. A simple way to address this problem is to delete your data.sqlite database file and then run flask db upgrade to generate a new database through the migration framework. Another option is to skip the flask db upgrade and instead mark the existing database as upgraded using the flask db stamp com‐ mand.

Adding more migrations:

  1. Make the necessary changes in the database models.
  2. Generate a migration with the flask db migrate command.
  3. Review the generated migration script and correct it if it has any inaccuracies.
  4. Apply the changes to the database with the flask db upgrade command.

To expand the last migration script:

  1. Remove the last migration from the database with the flask db downgrade com‐ mand (note that this may cause some data to be lost).
  2. Delete the last migration script, which is now orphaned.
  3. Generate a new database migration with the flask db migrate command, which will now include the changes in the migration script you just removed, plus any other changes you’ve made to the models.
  4. Review and apply the migration script as described previously.

ToDo: Large application structure

Contents