Created by Ian Connolly for DUCSS / Slides available on GitHub
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
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"
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)
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
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
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
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
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 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...
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)
{
"id": 1,
"name": "Foo",
"price": 123,
"tags": [ "Bar", "Eek" ],
"stock": {
"warehouse": 300,
"retail": 20
}
}
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 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
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
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
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
@app.route('/', methods=['POST', 'PUT'])
def index():
user = User.create(request.get_json())
return "Created user id={}".format(user.id), 201
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
Basically...
Small Flask apps are ideal for building services