Introduction to Flask

Created by Ian Connolly for DUCSS / Slides available on GitHub

So what's this Flask thing?

  • Flask is a microframework for developing web applications in Python
  • Built on top of Werkzeug
  • Uses Jinja2 for templating
  • Makes building for the web beautiful and easy

So what's this Flask thing? pt.2

  • Micro means the core is simple and extensible
  • It does not mean that it lacks functionality
  • Doesn't make decisions for you regarding databases, ORMs, validation...
  • And any decision it does make is easily changed

Hello, world!


from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

Routing!

URLs should be beautiful

Our web application should do different things depending on which URL the client hits

Flask's router allows us to map URLs to functions


@app.route('/')
def index():
    return 'You hit the index!'

@app.route('/hello')
def hello():
    return 'Hello to you too!'

@app.route('/fetch')
def fetch():
    return "Stop trying to make fetch happen, it's not going to happen"

Routes & Variables

Often you want to return something slightly depending on input

Think of a profile page, generally the same - but the data is different for each user


@app.route('/profile/<int:id>')
def profile(id):
    return 'Hey, user number: {}'.format(id)

A quick aside... HTTP

HTTP is the Hypertext Transfer Protocol

It's the protocol that the web is built on

HTTP has things called methods

It's the bit in the client's request that indicates what they want done

The most common are GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS

A server can do different things at a URL depending on the HTTP method

It can do whatever it wants, but best practice says we follow the proper HTTP semantics

Routes & Methods

Flask lets us specify what methods our route is defined for


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

The request object lets us get at the data the client sent to us

Routes & Methods p2.

That last example works, but it's not very Pythonic

What if we have a load of allowed methods? We'll have a lot of if/elses

It kind of makes our controller a controller of controllers (Yo dawg)

Let's fix it!


@app.route('/login', methods=['POST'])
def login_user():
    # login logic

@app.route('/login', methods=['GET'])
def get_login_form():
    # form logic

Show me a web page already!

A lot of web applications are built for web browsers

Web pages are built with HTML (So's this presentation funnily enough)

But how web pages look changes depending on data. My profile page looks different to yours

Flask lets us pull the data out of our page and we're left with a template

Templates

Flask uses a library called Jinja2 for templating

You can change that fairly easily if you want


<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello World!</h1>
{% endif %}

But how do we get rid of the logic and fill it with data?


from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

The Programmatic Web (APIs)

The Web isn't just for web browsers

HTTP is very popular for APIs too

API is an Application Programming Interface

It's how outside systems can communicate with ours

We can use URLs and HTTP methods to build APIs for devices and programs

But we need a data interchange format so we can format our data for machine consumption...

JSON

JavaScript Object Notation

Lightweight, human-readable, key->value data format

Looks like a JavaScript object, but it's just text

Maps very well to a Python dictionary

Taking over from XML (Praise the good lord)

JSON pt.2


{
    "id": 1,
    "name": "Foo",
    "price": 123,
    "tags": [ "Bar", "Eek" ],
    "stock": {
        "warehouse": 300,
        "retail": 20
    }
}

Consuming an API

http-bin is an API built by Python legend Kenneth Reitz (in Flask!)

It's useful for returning information about HTTP requests

This information is great for debugging our APIs

We'll use cURL to make a request to http-bin and ask it to return our user-agent


curl http://httpbin.org/user-agent

{"user-agent": "curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3"}

Flask, HTTP APIs and JSON

Flask is perfect for building HTTP APIs

It makes no assumptions about where we get our data, only about when we return it

Now to return some JSON from our application


from flask import jsonify

@app.route('/hello/<name>', methods=['GET'])
def hello(name):
    to_return = {"your_name_is": name}
    return jsonify(to_return)

And that's all we need to do to transform a dictionary into JSON

Talking but not listening

Being able to return JSON is all well and good

But our API needs to be able to receive data too

Remember we did this earlier?


if request.method == 'POST':
        do_the_login()

Let's talk about the request object

The Flask Request Object


from flask import request

Contains all the information about the current HTTP request we're responding to


@app.route('/', methods['GET', 'POST'])
def index():
    request.method # 'GET' or 'POST'

We've seen this one already


# http://ourapplication.com/hello?name=taylorswift
@app.route('/hello')
def hello():
    request.args["name"] # taylorswift

A Dict-like object containing the URL arguments


@app.route('/')
def index():
    username = request.cookies.get('username') # Get the cookies

Whew, okay, that was all fairly self-explanatory

That's nice, Ian. But where's my JSON?

You get one guess...


@app.route('/', methods=['POST', 'PUT'])
def index():
    json = request.json # Yep, that was easy

But don't do that

A lot of things can go wrong when parsing JSON

Luckily, @mitsuhiko has done all the hard work for us


@app.route('/', methods=['POST', 'PUT'])
def index():
    json = request.get_json() # only if the request's mimetype is application/json
    json = request.get_json(force=True) # attempt to parse even if the client didn't set the correct mimetype

Response codes


@app.route('/', methods=['POST', 'PUT'])
def index():
    user = User.create(request.get_json())
    return "Created user id={}".format(user.id), 201

Flask response object


from flask import Response

The reverse of the request object

Let's us set cookies, headers, response codes etc. explicitly

We just return one from our controller to respond


@app.route('/', methods=['GET'])
def index():
    response = Response()
    response.set_cookie(key="key", value="value")
    return repsonse

And now for something completely differently

Service Oriented Architecture

Basically...

  • The UNIX philosophy applied to systems
  • Split your application into small, discrete parts
  • Have them communicate with clear APIs (JSON!)
  • Allows you to develop, test, deploy, scale and replace modularly

Small Flask apps are ideal for building services

What next?

  • Get data from a database (SQLAlchemy, flask-sqlalchemy)
  • Validate forms (WTForms, flask-wtforms)
  • And so on... Batteries not included
  • But lets you use the best batteries for the job

Questions?