Advanced Flask
Following our brief introduction to the Flask framework, we continue here with a look at more complex endpoints and data retrieval functions for our REST API. After going through this module, students should be able to:
Identify valid and invalid Flask route return types
Convert unsupported types (e.g.
list) to valid Flask route return typesExtract Content-Type and other headers from Flask route responses
Add query parameters to GET requests, and extract their values inside Flask routes
Defining the URLs of Our API
The first basic goal of our API is to provide an interface to a dataset. Since the URLs in a REST API are defined by the “nouns” or collections of the application domain, we can use a noun that represents our data.
For example, suppose we have the following dataset that represents the number of students earning an undergraduate degree for a given year:
def get_data():
return [ {'id': 0, 'year': 1990, 'degrees': 5818},
{'id': 1, 'year': 1991, 'degrees': 5725},
{'id': 2, 'year': 1992, 'degrees': 6005},
{'id': 3, 'year': 1993, 'degrees': 6123},
{'id': 4, 'year': 1994, 'degrees': 6096} ]
In this case, one collection described by the data is “degrees”. So, let’s
define a route, /degrees, that by default returns all of the data points.
EXERCISE 1
Create a new file, degrees_api.py to hold a Flask application then do the
following:
Import the Flask class and instantiate a Flask application object
Add code so that the Flask server is started with this file is executed directly by the Python interpreter
Copy the
get_data()method above into the application scriptAdd a route (
/degrees) which responds to the HTTPGETrequest and returns the complete list of data returned byget_data()
In a separate Terminal use curl to test out your new route. Does it work as
expected?
Tip
Refer back to the Intro to Flask material if you need help remembering the boiler-plate code.
Responses in Flask
If you tried to return the list object directly in your route function definition, you got an error when you tried to request it with curl. Something like:
TypeError: The function did not return a valid response
Flask allows you three options for creating responses:
Return a string (
str) objectReturn a dictionary (
dict) objectReturn a tuple (
tuple) objectReturn a
flask.Responseobject
Some notes:
Option 1 is good for text or html such as when returning a web page or text file
Option 2 is good for returning rich information in JSON-esque format
Option 3 is good for returning a list of data using a special type of Python list - a
tuple- which is ordered and unchangeableOption 4 gives you the most flexibility, as it allows you to customize the headers and other aspects of the response.
For our REST API, we will want to return JSON-formatted data. We will use a
special Flask method to convert our list to JSON - flask.jsonify. (More on
this later.)
Tip
Refer back to the Working with JSON material for a primer on the JSON format and relevant JSON-handling methods.
EXERCISE 2
Serialize the list returned by the get_data() method above into a
JSON-formatted string using the Python json library. Verify that the type
returned is a string.
Next, Deserialize the string returned in part a) by using the json library
to decode it. Verify that the result equals the original list.
Returning JSON (and Other Kinds of Data)
You probably are thinking at this point we can fix our solution to Exercise 1
by using the json library (which function?). Let’s try that and see what
happens:
EXERCISE 3
Update your code for Exercise 1 to use the json library to return a properly
formatted JSON string.
Then, with your API server running in one window, open a Python3 interactive session in another window and:
Make a
GETrequest to your/degreesURL and capture the response in a variable, sayrVerify that
r.status_codeis what you expect (what do you expect it to be?)Verify that
r.contentis what you expectTry to use
r.json()to decode the response - does it work?Compare that with the response from the Bitbucket API to the URL
https://api.bitbucket.org/2.0/repositories
The issue you may be encountering has to do with the Content-Type header
being returned by the degrees API.
HTTP Content Type Headers
Requests and responses have headers which describe additional metadata about
them. Headers are key: value pairs (much like dictionary entries). The key
is called the header name and the value is the header value.
There are many pre-defined headers for common metadata such as specifying the
size of the message (Content-Length), the domain the server is listening on
(Host), and the type of content included in the message (Content-Type).
Media Type (or Mime Type)
The allowed values for the Content-Type header are the defined
media types (formerly, mime types). The main thing you want to know
about media types are that they:
Consist of a type and subtype
The most common types are application, text, audio, image, and multipart
The most common values (type and subtype) are application/json, application/xml, text/html, audio/mpeg, image/png, and multipart/form-data
Content Types in Flask
The Flask library has the following built-in conventions you want to keep in mind:
When returning a string as part of a route function in Flask, a
Content-Typeof text/html is returnedTo convert a Python object to a JSON-formatted string and set the content type properly, use the
flask.jsonify()function.
For example, the following code will convert the list to a JSON string and return a content type of aplication/json:
return flask.jsonify(['a', 'b', 'c'])
EXERCISE 4
Use the flask.jsonify() method to update your code from Exercise 1. Then:
Validate that your
/degreesendpoint works as expected by using therequestslibrary to make an API request and check that the.json()method works as expected on the response.Use the
.headers()method on the response to verify theContent-Typeis what you expect.
Query Parameters
The HTTP spec allows for parameters to be added to the URL in form of
key=value pairs. Query parameters come after a ? character and are
separated by & characters; for example, the following request:
GET https://api.example.com/degrees?limit=3&offset=2
Passes two query parameters: limit=3 and offset=2.
In REST architectures, query parameters are often used to allow clients to provide additional, optional arguments to the request.
Common uses of query parameters in RESTful APIs include:
Pagination: specifying a specific page of results from a collection
Search terms: filtering the objects within a collection by additional search attributes
Other parameters that might apply to most if not all collections such as an ordering attribute (
ascendingvsdescending)
Extracting Query Parameters in Flask
Flask makes the query parameters available on the request.args object, which
is a “dictionary-like” object. To work with the query parameters supplied on a
request, you must import the Flask request method (this is different from the
Python3 requests library), and use an imbedded method to extract the passed
query parameter into a variable:
from flask import Flask, request
@app.route('/degrees', methods=['GET'])
def degrees():
start = request.args.get('start')
The start variable will be the value of the start parameter, if one is
passed, or it will be None otherwise:
GET https://api.example.com/degrees?start=2
Note
request.args.get() will always return a string, regardless of the
type of data being passed in.
EXERCISE 5
Add support for a limit parameter to the code you wrote for Exercise 4. The
limit parameter should be optional. When passed with an integer value, the
API should return no more than limit data points.