Using Python Flask

Flask is a huge HTTP web library build on Werkzeug (a WSGI library), with a tonne of features and additional packages. In these notes, I’ll include common solutions to problems, project architectures, and useful packages.

Basic setup

A good guide is this quick-start; I elaborate here and leave out other information.

The minimal setup for flask is to initialize an app context in an app.py or a run.py.

# app.py
from flask import Flask 
app = Flask(__name__)

# register routes here
@app.route('/')
def index():
    return "Hello World"

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

Although the server can be started using a direct call python app.py, for environment variable utilization, and general ease of practice, it is more convenient to use flask run.

URL endpoints can be registered in different way, but the decorator method is a common practice.

File serving

Configuration

Configuration in Flask can be quite daunting. Fortunately, there are numerous guides, such as this one by Pythonise that clarify the process.

The default config variables are

{
    'APPLICATION_ROOT': '/',
    'DEBUG': True,
    'ENV': 'development',
    'EXPLAIN_TEMPLATE_LOADING': False,
    'JSONIFY_MIMETYPE': 'application/json',
    'JSONIFY_PRETTYPRINT_REGULAR': False,
    'JSON_AS_ASCII': True,
    'JSON_SORT_KEYS': True,
    'MAX_CONTENT_LENGTH': None,
    'MAX_COOKIE_SIZE': 4093,
    'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31),
    'PREFERRED_URL_SCHEME': 'http',
    'PRESERVE_CONTEXT_ON_EXCEPTION': None,
    'PROPAGATE_EXCEPTIONS': None,
    'SECRET_KEY': None,
    'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200),
    'SERVER_NAME': None,
    'SESSION_COOKIE_DOMAIN': None,
    'SESSION_COOKIE_HTTPONLY': True,
    'SESSION_COOKIE_NAME': 'session',
    'SESSION_COOKIE_PATH': None,
    'SESSION_COOKIE_SAMESITE': None,
    'SESSION_COOKIE_SECURE': False,
    'SESSION_REFRESH_EACH_REQUEST': True,
    'TEMPLATES_AUTO_RELOAD': None,
    'TESTING': False,
    'TRAP_BAD_REQUEST_ERRORS': None,
    'TRAP_HTTP_EXCEPTIONS': False,
    'USE_X_SENDFILE': False
}

which are accessible, extendable and modifiable with

app.config["key"] = "value"

We can configure configuration in numerous different ways:

  • environment variables

  • from dictionaries

  • from Python class objects

  • from Python .cfg files

Using config.py

The file config.py, located at the same level as app.py should be used to create configuration classes for Flask. This could involve reading from a more conventional file, taking in environment variables, or reading from a bitestream – or just holding named variables; consider this simple configuration file

# config.py

class Config:
    DATABASE_ADDR = "some address"
    FILE_DIRECTORY = "/path/to/files"

class Development(Config):
    DEBUG = True
    TESTING = True 

class Production(Config):
    FILE_DIRECTORY = "/some/other/directory"

here we have specified three configurations, which we can load into flask with

app.config.from_object("config.Development")

for e.g. development. Note that you do not need to explicitly import config as Flask with handle the import so that code in config.py does not arbitrarily execute more than once.

Link to the API for the application object.

The FLASK_ENV environment variable

The environment variable FLASK_ENV gets mapped into app.config["ENV"] and controls some additional features in the environment to use. Good practice is to use this variable in your own application configuration

if app.config["ENV"] == "development":
    app.config.from_object("config.Development")
else:
    app.config.from_object("config.Production")

Blueprints

## Application Contexts documentation api

request object

The request context

@app.before_request and @app.teardown_request decorators

current_app

current_app

HTTP Endpoints

Parsing Forms

Logging

There is a lot of information available in the docs, however for most use cases, it is sufficient to know that the Flask logger is accessible with app.logger or current_app.logger. The handler is available and modifiable

from flask.logging import default_handler
default_handler.setFormatter(formatter)

Flask CLI

The Flask CLI comes with a few built-in commands

(venv) ophelia: asteroid-flask $ flask
Usage: flask [OPTIONS] COMMAND [ARGS]...

  A general utility script for Flask applications.

  Provides commands from Flask, extensions, and the application. Loads the
  application defined in the FLASK_APP environment variable, or from a
  wsgi.py file. Setting the FLASK_ENV environment variable to 'development'
  will enable debug mode.

    $ export FLASK_APP=hello.py
    $ export FLASK_ENV=development
    $ flask run

Options:
  --version  Show the flask version
  --help     Show this message and exit.

Commands:
  routes  Show the routes for the app.
  run     Run a development server.
  shell   Run a shell in the app context.

You can, however, for your application, provide additional commands with the flask.cli module. This module is itself built on top of click.

import click
from flask import Flask

app = Flask(__name__)

@app.cli.command("new-table")
@click.argument("name")
def add_table_to_database(name):
    ...

Commands can also be added through blueprints. There is also a note about application context, which discusses how you can hoist and/or sink the context for the CLIcommands.

Flask-Restful

Response marshaling

The data returned by an endpoint or rest resource may contain additional or too few fields for what the expected response ought to contain – Flask Restful includes a convenience decorator @marshal_with() to graft the response into an expected format. Consider this example for returning user information

from flask_restful import marshal_with, fields, Resource

user_model = {
    "name": fields.String,
    "uid": fields.Int,
    "data": fields.Nested({
        "full_name": fields.String,
        "created": fields.String,
        "links": fields.List(fields.String)
    })
}

@app.route("/user/<int:id>")
class Users(Resource):

    @marshal_with(user_model)
    def get(self, _id):
        # database lookup
        user = fetch_from_database(_id)
        if user:
            return user, 200
        else:
            return {}, 400

Now, irrespective of the fields returned from the database, @marshal_with() guarantees that the fields we specified will be in the response.

In the documentation it is explained that @marshal_with() takes the optional keyword envelope="some_field", which acts to put the marshalled response into a JSON structure under some_field.

There is also marshal(), which is the non-decorator version, which returns a dictionary structure in cohesion with the model

marshal(data, model)

### Argument parsing Behaving in much the same way as the built-in argparse library, Flask Restful includes

from flask_restplus import reqparse

parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()

However here args is now a dictionary.

Another good note is to include the strict=True flag in .parse_args() so that an error is thrown if additional fields are sent.

The search order for argsparse delves all the way through query structure, request data, and any json information.

Databases

RethinkDB

See my writeup.

SQLAlchemy

A heavy handed and verbose sqlite3 approach can be seen in the documentation. A more elegant way to use SQL based databases is using SQLAlchemy.

Postgres

There is a very comprehensive guide on Medium for creating a Flask API with Postgres.

Additional

Enabling CORS

Found in this SO answer, the trick here is

from flask import Flask
from flask_cors import CORS, cross_origin

app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

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