Flask Framework: Deep Dive Into Routing

Chaitanya Srivastav
4 min readAug 6, 2022

Every web framework has its own implementation of routing. This article will provide the insights about what happens on the background in Flask Framework when it comes to routing.

Topics to be covered:

  1. Routing through Werkzeug
  2. Flask Routing

Pre-Requisite: Flask Framework: WSGI Explained

Routing through Werkzeug

Werkzeug is Python library for WSGI. Flask uses this library internally to connect to web servers. It has support for routing. In order to understand how Flask works with routing, its important to discuss routing through werkzeug.

This article assumes pre-requisites are completed.

Lets consider an example which will use routing with werkzeug.

import os
from werkzeug.wrappers import Request, Response
from http.client import HTTPException
from werkzeug.routing import Map, Rule
from werkzeug.middleware.shared_data import SharedDataMiddleware
class FrameWork(object):
def __init__(self, name):
self.name = name
self.url_map = Map([Rule('/', endpoint='index'),
Rule('/status', methods=["POST"], endpoint='status')])
def index(self, **values):
return Response('Hello Index!')
def status(self, **values):
return Response('Hello Status!')
def create_url_adapter(self, request):
return self.url_map.bind_to_environ(request.environ)
def dispatch_request(self, endpoint, values):
return getattr(self, endpoint)(**values)
def wsgi_app(self, environ, start_response):
request = Request(environ)
adapter = self.create_url_adapter(request)
try:
rule, values = adapter.match(return_rule=True)
response = self.dispatch_request(rule.endpoint, values)
return response(environ, start_response)
except HTTPException as e:
return e
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def create_app(name="World", with_static=True):
a = FrameWork(name)
if with_static:
a.wsgi_app = SharedDataMiddleware(a.wsgi_app, {
'/static': os.path.join(os.path.dirname(__file__), 'static')
})
return a
if __name__ == '__main__':
from werkzeug.serving import run_simple
a = create_app()
run_simple('127.0.0.1', 5000, a, use_debugger=True, use_reloader=True)

Don’t be overwhelmed by the above code, its only for us to understand what happens behind the scenes.

Lets focus on the following two parts of the code first.

... // rest of the code
def __init__(self, name):
self.name = name
self.url_map = Map([Rule('/', endpoint='index'),
Rule('/status', methods=["POST"], endpoint='status')])
... // rest of the code
def create_url_adapter(self, request):
return self.url_map.bind_to_environ(request.environ)
...// rest of the code

On the Framework class constructor, map of url rules have been defined and then these rules are bound to current environment by create_url_adapter(self, request) function and it returns back URLAdapter object which can be used to match the incoming request.

self.url_map is a Map object with bunch of Rule objects. Each rule has a pattern it will try to match the URL against and an “endpoint”. The endpoint is typically a string and can be used to uniquely identify the URL.

One can read more about Map and Rule objects at Map and Rule parts of the documentation.

Now lets look at wsgi_app function which is responsible for dispatching request and sending response back to the client.

def wsgi_app(self, environ, start_response):
request = Request(environ)
adapter = self.create_url_adapter(request)
try:
rule, values = adapter.match(return_rule=True)
response = self.dispatch_request(rule.endpoint, values)
return response(environ, start_response)
except HTTPException as e:
return e

This function creates an adapter for url rules defined at self.url_map at line 2.
adapter = self.create_url_adapter(request)
Then based on current request, we should try to match the appropriate rule object and all parameters to be passed.
adapter.match(return_rule=True) does just that.

If the request URL is “127.0.0.1:5000/”, then the endpoint will be “index” and values will be empty.
If the request URL is “127.0.0.1:5000/status” and method is POST, then the endpoint will be “status” and values will be empty.

Next we try to find the endpoint. This step can be customised in any way. In our case we look for endpoint on the Framework class itself. If endpoint is found then that function is executed and response is formed and sent back to client. Two endpoints we have defined in our example are:

def index(self, **values):
return Response('Hello Index!')
def status(self, **values):
return Response('Hello Status!')

Flask internally uses same library and so it also uses the same concept internally. Lets have a look at that now.

Flask Routing

Lets consider the sample Flask app below:

from flask import Flaskapp = Flask(__name__)@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
print(app.url_map)
print(app.view_functions)

Here we know “app” is the WSGI application object. “app” is also an object of Flask class. Every object of Flask class has a property called url_map which holds Map and Rules of all the routes.

Flask object also has a route() function which acts as a decorator and creates this url_map entry. There are other such methods which can be used as decorators and they in turn creates the entry inside url_map property of the object. Such methods are app.get, app.post etc.

The last two lines of the above code will make it all very clear. Once we run this Flask app we will get the following printed in terminal.

Map([
<Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>,
<Rule '/' (HEAD, GET, OPTIONS) -> hello_world>
]) // url_map
{
'static': <function Flask.__init__.<locals>.<lambda> at 0x103ae6160>,
'hello_world': <function hello_world at 0x103ae6280>
} // view_functions

“static” rule is added by default from Flask. If endpoint is not passed as a parameter to route() then endpoint will be same as the function name.

Flask class also defines wsgi_app function like we have done in our example above. In this function Flask creates a RequestContext object and it has “request” property which holds the information about the incoming request. This “request” is an object of Request class.

This “request” object have “url_rule” and “view_args” property. These property will hold the appropriate Rule object and all parameters to be passed.

Then finally endpoint inside Rule object is resolved. The function to be executed is found inside app.view_functions map which looks like the following in our case.

{
'static': <function Flask.__init__.<locals>.<lambda> at 0x103ae6160>,
'hello_world': <function hello_world at 0x103ae6280>
}

This map holds endpoint as key and view function as value. In our example, it will execute “hello_world()” function we defined, as the endpoint is “hello_world”. It then forms the response and send back to client.

This is in a nutshell the way Flask works with routing.

Thanks for Reading.
Any questions/suggestions regarding this topic are welcome in comments. :)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response