Python flask

From wikinotes

Flask allows you to call on python to generate websites, or host a REST api.

Documentation

Official Docs http://flask.pocoo.org/docs/0.12/
Testing Flask Apps http://flask.pocoo.org/docs/0.12/testing/
Background Jobs with progress using Flask/Celery (polling) https://blog.miguelgrinberg.com/post/using-celery-with-flask
Celery progress without polling https://stackoverflow.com/questions/31059888/monitor-a-celery-task-state-without-polling

Plugins

Python flask-restful like argparse for API routes (ex: curl 'http://api/user' -d 'name=will' -d 'age=30+')
Python flask-restless sqlalchemy-syntax in API route queries (ex: curl 'http://api/user?q{"filters": [{"name": "age", "op": ">", "val": 30}]}' )
Python flask-sqlalchemy sqlalchemy integration within flask, (authentication, connection-pooling, etc).

Serving Content

Dynamic Websites

A website is just a REST API that handles a http GET request, and replies with html as a payload.

Knowing this, you can use flask to either host very dynamic web contents, a rest api, or just about anything else.

URL

ex: http://website.com/hello

import flask
app = flask.Flask(__name__)

@app.route('/hello')
def hello():
    return '<html><header>My Webpage</header></html>

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

Then run your website (using a quick n' dirty webserver):

python myflaskapp.py
firefox http://127.0.0.1:5000/hello

URL with params

Ex: http://website.com/user/wpittman

You can also parametrize your website generation using the URL:

@app.route('/user/<string:username>')
def username(username):
    return 'hello, {}'.format(username)

https://flask.palletsprojects.com/en/1.1.x/quickstart/#variable-rules for the other types you can use.

REST API

HTTP MethodTypes
Method Allows Caching? Description
GET retrieve existing data
POST create new data
PUT modify existing data
DELETE delete data
PATCH
HEAD
TRACE
CONNECT

Sample Data

import flask
app = flask.Flask(__name__)

user_data = {
    'wpittman': {'first': 'will', 'last': 'pittman'},
    'cnelder':  {'first': 'chris', 'last': 'nelder'},
}

GET requests

# GET request
@app.route('/user/info/<string:username>', methods=['GET'])
def user_info(username):
    if username not in user_data:
        flask.abort(404) # file not found
    return flask.jsonify(user_data[username])
curl -X GET  127.0.0.1:5000/user/info/wpittman
{
    "first": "will",
    "last": "pittman"
}

POST requests

# POST request
@app.route('/user/create', methods=['POST'])
def user_create():
    r = flask.request  # global var (one per-worker-thread), the payload

    if not r.json:
        flask.abort(400)  # bad request
    
    user_data[r.json['username']] = {
        'first': r.json['first'],
        'last':  r.json['last'],
    }
    return flask.jsonify(user_data[r.json['username']])
curl -X POST \
    -d '{"username": "dvader", "first": "darth", "last": "vader"}' \
    -H 'Content-Type: application/json' \
    127.0.0.1:5000/user/create

Handling Multiple RequestTypes

You can also handle multiple request types from one base url:

@app.route('/user', methods=['GET', 'POST'])
def user():
    r = flask.request
    if r.method == 'GET':
        # handle GET
    elif r.method == 'POST':
        # handle POST
    return flask.jsonify({'result': True})


Responses

default responses

Success

# flask/werkzeug does not expose it's error templates
reply = flask.make_response('success', 204)  # HTTP-204: no content

Errors

flask.abort(200)  # success
flask.abort(404)  # file not found

# these refer to werkzeug classes
werkzeug.exceptions.BadGateway()

jsonify

reply = flask.jsonify({'a': 1})    # default status 200
reply.status_code = 202            # change http status

custom

reply = flask.Response('user already exists', status=409)
flask.abort(reply)

wrapping requests response

Forwarding/Redirecting

TODO:

wrapping requests responses

TODO:

redirects within flask, and status codes

TODO:

url_for() and how it works

TODO:

mention my issues with reverse proxy at sub-location, and workarounds


Raising/Handling Errors

serverside

Unhandled Exceptions

Unhandled exceptions are handled by default, and return 500 - server error to the client.

Abort using default HTTP status-code page

This can be in any part of the flask app. You do not need to carry a returnvalue throughout the call stack.

flask.abort(400)

Abort using custom reply

# note that python's stdlib HTTPError cannot access payload.
# 'requests' 3rd party lib can work.
flask.abort(
    flask.Response(
        json.dumps({'success': False}).encode('utf-8'),
        status=404,
        headers={'Content-Type': 'application/json; charset=utf-8'},
    )
)

clientside

python stdlib

I would not use the stdlib unless you absolutely have to:

  • Python stdlib will raise an exception, but will not give you the payload of the response. This is frustrating and hard to debug.
  • Python stdlib also only implements GET/POST. You can override the HTTP method, but it will not work universally.
from six.moves.urllib import requests, error

try:
    request = requests.Request(
        url='your.url.com',
        headers={'Content-Type': 'application/json; charset=utf-8'},
        data=json.dumps({'a': 'b'}).encode('utf-8'),
        )
    response = request.urlopen(request)
except(error.HTTPError) as e:
    print(e.reason, e.code)

requests 3rd party library

url = 'http://website.com/user'
headers = {'Content-Type': 'application/json; charset=utf-8'}
data = {
    'id': 1234,
    'columns': ['firstname', 'lastname', 'middlename'],
}
json_data = json.dumps(data)

# query toolsets
response = requests.get(url=url, headers=headers, data=json_data)

if response.status_code != 200:
    msg = (
        'An Error occurred during load.\n'
        'http status: {}\n'
        'text: {}\n'
    ).format(response.status_code, response.text)
    raise RuntimeError(msg)

return response.json()

Logging

If your flask app is being managed by a different WSGI server, you'll probably want your loggers to use flask's loghandler.

import logging
from flask.logging import default_handler

logging.root.addHandler(default_handler)

Hosting In Production

WSGI Server

In order to host a flask website, you'll need to run it using a WSGI server.

See python gunicorn .

SSL

See certbot.

Troubleshooting

See python flask: troubleshooting.

Resources

rest api https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
custom login https://pythonspot.com/login-authentication-with-flask/