View on GitHub

NEH Institute materials

July 2017

Home | Admin | Week 1 | Week 2 | Week 3 | Misc

Introduction to Flask

Flask http://flask.pocoo.org/ is a microframework for Python. There are no batteries included, but it has a bunch of available plugins you can use, e.g.Flask plugins. By using the Flask microframwork you will be able to get close to all individual steps in the publication process and glue it all together keeping the overview.

Flask installation

It is a really simple installation. All you have to do if it is not already installed is to run:

 $ pip install flask

To test that it works you can run the following code (also available in the repo as flask_index.py) in your terminal command line:

from flask import Flask
app = Flask(__name__)
@app.route("/")
def get_my_index():
    return "This is index for / (ROOT) in flask_index.py"
if __name__ == '__main__':
    app.run(port=5000, debug=True)

When running this Flask app from the commandline it will output something like this:

 $ python flask_index.py 
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger pin code: nnn-nnn-nnn

And when making a request to you app through the url specified http://localhost:5000/ it will continue to output the access information:

127.0.0.1 - - [23/Jul/2017 19:02:15] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Jul/2017 19:02:16] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [23/Jul/2017 19:02:17] "GET /favicon.ico HTTP/1.1" 404 -

So with this small amount of code you have actually brought up a simple web server (eventhough only answering requests for /).

Templates

Then Jinja templating engine is available in Python. There is a convention to put the templates in a subdirectory called templates. You need to include it in your python header:

from flask import render_template

If we put a file named index.html into the templates directory with the following contents:

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>template index.html</title>
  </head>
  <body>
    <h1>index.html</h1>
    <p>This is index.html in response to a request for / (ROOT) in flask_index_template.py</p>
  </body>
</html>

it could be used like:

from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route("/")
def get_my_index():
    return render_template("index.html")
if __name__ == '__main__':
    app.run(port=5000, debug=True)

Dynamic data with templates

The render_template() function can take more arguments than only the template name.

 render_template("index_dyn.html", title="template index.html", header="index.html", paragraph="...") 

This is used in the template index_dyn.html:

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title></title>
  </head>
  <body>
    <h1></h1>
    <p></p>
  </body>
</html>

See flask_index_template_dyn.py and templates/index_dyn.html in the repo.

Using a Python dictionary in the template

Instead of passing in all single variables to the render_template function you can use a Python dictionary. You access the keys in the template by putting the dictionary variable name and the key together with a period, e.g. dict.title See flask_index_template_dyn_dict.py and templates/index_dyn.html in the repo.

Error handling

When creating applications it is important to manage forseen errors as well as unforseen errors and exceptions. In the previous Python sessions of the institute we have not been doing this much since we were focusing on other parts of the coding. But this week it will be needed for publishing your edition.

Default values

To make your application more robust you should use default values to avoid errors. We are also introducing request parameters. In this case we use the default request method GET to pass our parameter header and its value to the server for retrieval. By convention GET should only be used for retrieval and not update the state in the server application. This way you can dynamically pass parameters from the client e.g. the web browser or any HTTP capable library. Some of the more used methods are POST, PUT, and DELETE.

from flask import Flask
from flask import render_template
from flask import request

defaults = { 'title': 'dynamic request header index.html' ,
             'header': 'Default "header" is used. Give request parameter header with a value to change it.',
             'paragraph': 'This is index.html with dynamic contents in response to a request for / (ROOT) in flask_request.py'
}

app = Flask(__name__)
@app.route("/")
def get_my_index():
    header = get_request_value_with_fallback('header')
    values = {'title': defaults['title'] ,
        'header': header,
        'paragraph': defaults['paragraph'] 
}
    return render_template("index_dyn_dict.html", dict=values)

def get_request_value_with_fallback(key):
    if request.args.get(key):
        return request.args.get(key)
    return defaults[key]

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

So by passing the request parameter we can now set the header to any string value. In this case My header:

 http://localhost:5000/?header=My%20header

See flask_request.py.

Avoid dividing by zero

Python have Exceptions. They can be handled. Sometimes catching them withing try blocks might be enough, but not always. In this case we can handle ZeroDivisionError to say something about if we can continue or not. If we do not handle it but let Python throw the error it would break the application execution and potentially cause inconsistency or at least giving you unhappy users.

(x,y) = (5,0)
try:
   z = x/y
except ZeroDivisionError:
   print "divide by zero. We need to recover. Maybe ask the user for a better value."
# Depending on if we can recover or not: do what is needed to nicely exit or take receovery actions

See error_handling_divide_by_zero.py.

If you find it unrecoverable you might want to modify the exception and retrow it with raise:

# -*- coding: utf-8 -*-

d = '''This division by zero was not expected — this error is not correctable by you.
    It is an application error. If you contact the project at ... 
    we can fix it so that others do not need to experience this in the future.'''
(x,y) = (5,0)
try:
   z = x/y
except ZeroDivisionError as e:
   e.args += (d,) # tuple so cannot directly concatenate stringvalue 'd' 
   raise

See error_handling_divide_by_zero_raise.py.

Check your values

If you are doing API calls you also need to make sure that you handle connection errors and recovery from that e.g. retries. Bur first of all you need to handle the exceptions on connection. in this case the exception is namespaced e.g. it is from the urllib2 library.

import json
import urllib.error
import urllib.parse
import urllib.request

urlbase = "http://localhost:8091/exist/rest/db/karp/karp-stats.xql?op=feat-stats&do-feat-values=true&use-current=true&json=true&resurs={}"

def get_data(resource):
    query = urllib.parse.quote(resource)
    url = urlbase.format(query)
    request = urllib.request.Request(url)
    with urllib.request.urlopen(request) as response: 
        data = response.read().decode('utf-8')
    parsed = json.loads(data)
    info = None
    if parsed.get('resource'):
        info = {'description': parsed['resource'][0]['description'],
                   'name': parsed['resource'][0]['name']
                   }
    return info

try: 
   get_data("dalin")
except urllib.error.URLError:
   print("Connection refused. Please check the URL and port")

See error_handling_url_not_found.py.

Exercise: There are of course many other errors and exceptions you would like handle in this still rather simple script. Which ones could you come up with?

Validation

Every app running in a web browser should have validation of values from forms validated both on client and server side.