Using Python Flask
Contents
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.
Table of Contents¶
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
¶
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¶
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.
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!"