Source Code(1): Flask and Flask-RESTful

It's easy to creat an api or restful api with Flask and Flask-RESTful extension. But what's happening under the hood when executing the code?

In this blog, we will dive into the source code of Flask framework and its extension Flask-RESTful, and understand the code deeply.

Flask API

We can create an api with Flask quickly and easily.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from flask import Flask, jsonify, request, render_template

# ***************
# Creat the App
# ***************

app = Flask(__name__)


# *********************************
# Creat various endpoints
# send request and return response
# *********************************

@app.route('/')
def homepage():
return render_template('index.html')


@app.route('/store', methods=['POST'])
def create_store():
request_data = request.get_json()
new_store = {
'name': request_data['name'],
'items': []
}
stores.append(new_store)
return jsonify(new_store)


@app.route('/store/<string:store_name>')
def get_store(store_name):
for store in stores:
if store['name'] == store_name:
return jsonify(store)
return jsonify({'message': 'store not found'})


# *************
# Run the App
# *************

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

We use decorator app.route() to create endpoints. It's like a magic —— we provide an URL rule and/or some methods to the decorator, then we can call the corresponding function by typing URL in our browser or Postman.

But it's not a magic. There is a function called route in Flask class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app.py

class Flask(_PackageBoundObject):
....
....

def route(self, rule, **options):
"""
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
"""
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f

return decorator
1
2
3
4
5
# endpoint

@app.route('/')
def index():
return "Hello World"

The app.route('/') will call this higher-order function route, where parameter self is app and rule is '/', then the function decorator will be returned.

When applying decorator to our user-defined function, such as index, we can see this function will add the previous given url rule, i.e. '/' , to the app. So when we type the url '/', the Flask app will help us trigger the corresponding function index, and returns Hello World.

Pluggable Views

Introduction

A view function, or view in short, is just a Python function that takes a web request and returns a web response.

In the last section, we use decorators to register view functions, and use them to deal with requests. However, this way is not generic and not flexible if we want to apply other models or templates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# view function is not generic and flexible

@app.route('/')
@app.route('/users')
def show_users(page):
users = User.query.all()
return render_template('users.html', users=users)


@app.route('/stores')
def show_products(page):
products = Product.query.all()
return render_template('products.html', products=products)

This is where pluggable view comes in.

Flask 0.7 introduces pluggable views inspired by the generic view from Django. The pluggable views are based on classes instead of functions. We can use customized class view to deal with requests.

See an example. With the following code, we can type a specific url to let Flask app trigger the corresponding function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from flask.views import View


"""
# Alternative way to use view function.
class View:
...

def dispatch_request(self) -> ResponseReturnValue:
raise NotImplementedError()

...


"""

# customized-view class: inherit from View class
class ListView(View):
methods = ['GET', 'POST']

def get_template_name(self):
raise NotImplementedError()

def render_template(self, context):
return render_template(self.get_template_name(), **context)

# the subclass should implement dispatch_request method
def dispatch_request(self):
if request.method == 'GET':
context = {'objects': self.get_objects()}
return self.render_template(context)
elif request.method == 'POST':
pass


class UserView(ListView):
def get_template_name(self):
return 'users.html'

def get_objects(self):
return User.query.all()


class ProductView(ListView):
def get_template_name(self):
return 'products.html'

def get_objects(self):
return Product.query.all()


# add url rule into the app (endpoint is optional)
app.add_url_rule(rule='/users', endpoint='show_users', view_func=UserView.as_view('show_users'))

In this example, we define some pluggable views instead of view functions, then use add_url_rule() method to register a rule for routing. You should notice that, although we also use add_url_rule() here, there is a slight difference between this example and the previous example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Comparasion

# view function
class Flask(_PackageBoundObject):
....
....

def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options) # this line!
return f

return decorator


# pluggable view
app.add_url_rule(rule='/users', endpoint='show_users', view_func=UserView.as_view('show_users'))

Although we use pluggable view in the latter case, we still need to convert it into view function when register URL rule for app. The as_view class method will convert that pluggable view class into an actual view function. The string you passed is the name of the endpoint that view will then have. Whenever a request is dispatched, a new instance of the class is created based on url rule. Then, the instance's dispatch_request() method will be called.

MethodView

For RESTful API, it is helpful to execute different function for each HTTP method. This is where flask.views.MethodView comes in.

MethodView is also a class-based pluggable view that dispatches request methods to the corresponding class methods. It has already implemented dispatch_request method, so you don't have to implement it by yourself.

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask.views import MethodView

class UserAPI(MethodView):
# each http method maps to a method with the same name
def get(self):
users = User.query.all()
pass

def post(self):
user = User.from_form_data(request.form)
pass

app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

Summary

Each URL rule is corresponding to a view. The view is callable and can deal with request and return a response. When a Flask api receives a request, it has two ways to deal with it. One is by using "FUNCTION-BASED VIEW" directly, while another way is using "CLASS-BASED VIEW".

  1. function-based view
  • use "route(url_rule, methods)" decorator to register view function.
  • the route decorator will add url-rule to the app by using "add_url_rule(rule, endpoint, view_func)" function
  • this way is suitable for simple requests
  1. class-based view
  • customized-class view must inherit "View" class or "MethodView" class, and must override dispatch_request method (MethodView has already override the dispatch_request method)
  • use "add_url_ruler(rule, endpoint, view_func)" to add url rule to the app
  • use class method "as_view()" to convert a class into an actual view function
  • whenever a request is dispatched, create a new instance based on url rule, then call its dispatch_request method

Flask-RESTful API

Now, let's use Flask and its extension flask-restful to build a RESTful API.

Resource is the core when designing a RESTful API. We make resources as the center to design different url rules, and define different HTTP methods for resource to manipulate it.

To do this,

1️⃣ first we should define the resources that our api can work with.

2️⃣ Then, define different methods that the resource should support.

3️⃣ Finally, add the defined resources to our api.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
""" restful api demo """

from flask import Flask
from flask_restful import Resource, Api


app = Flask(__name__)
api = Api(app)


# each resource should be a class (oop)
class Student(Resource):
# use http method as the method name; will handle GET requests
def get(self, name):
return {'student': name}


# api works with resource: add a resource to the api
api.add_resource(Student, '/student/<string:name>')


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

Let's see the source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Api(object):
......

def __init__(self, app=None, ...):
....
if app is not None:
self.app = app
self.init_app(app)

def add_resource(self, resource, *urls, **kwargs):
if self.app is not None:
self._register_view(self.app, resource, *urls, **kwargs)
else:
self.resources.append((resource, urls, kwargs)

def _register_view(self, app, resource, *urls, **kwargs):
...
resource_func = self.output(resource.as_view(endpoint, *resource_class_args,
**resource_class_kwargs))
...
for url in urls:
...
app.add_url_rule(rule, view_func=resource_func, **kwargs)

def output(self, resource):
""" Wraps a resource as a flask view function. """
pass


class Resource(MethodView):
...

class MethodView(View):
...
  • Api is the main entry point for the application. You need to initialize it with a Flask Application.
  • Resource represents an abstract RESTful resource. Concrete resources should extend from this class and expose methods for each supported HTTP method. If a resource is invoked with an unsupported HTTP method, the API will return a response with status 405 Method Not Allowed. Otherwise, the appropriate method is called, and all arguments from the url rule which is used when adding resource to an api instance will be passed to the method.

We use a Flask instance app to initialize Api class and get an instance api, so api.app = app . When executing api.add_resource(resource, urls) (Line 19 in restful demo) , since api.app is not None, so a pluggable view will be registered by calling _register_view method. Here, the resource is the Student class. Finally, the url rule will be added to the app.

Until now, I think you should understand every line of our code when building a simple Flask API or Flask RESTful API. If you have question or suggestion, please leave me a message.

Thank you so much, and see you later. 💝

Reference