Tutorial on the use of @app.route in Python's Flask framework

  • 2020-04-02 14:47:37
  • OfStack

In my last article, I built a framework that mimics the behavior of the first example of "@app.route ('/')" on Flask's website.

If you missed the article "" it's not magic, please click (link: http://fanyi.jobbole.com/5958/).

In this article, we're going to make it a little harder to add variable parameters to our urls, and at the end of this article, we'll support the behavior that the following code snippet expects.


 
app = Flask(__name__)
 
@app.route("/hello/<username>")
def hello_user(username):
  return "Hello {}!".format(username)

The following path instance (path) :
/ hello/ains
Will match the path above, giving us the output as
Hello ains!

Express our path in regular form.

Now that we will allow our URL to change dynamically, we will no longer be able to directly compare the path that was previously registered using "@app.route()" with the path instance.

What will we replace it with? We need to use regular expressions so that we can match paths as a pattern instead of a fixed string.

I'm not going to in this article to discuss the details of the regular expression, but if you need a review of data, can click on this link: http://www.regular-expressions.info/quickstart.html).

So, our first step is to convert our path to a regular expression pattern so that we can match when we enter a path instance. We will also use this regular expression to extract the variables we are interested in.

So what does a regular expression that matches the path "/hello/" look like?

Well, a simple regular expression like "^/hello/(.+)$" would be a good place to start. Let's see how it works with the code:


 
import re
 
route_regex = re.compile(r"^/hello/(.+)$")
match = route_regex.match("/hello/ains")
 
print match.groups()

Will output:
(' ains')

That's fine, but ideally we want to maintain the first set of links we've matched and identify "username" from the path "/hello/".

Named capture group

Fortunately, regular expressions also support named capture groups, allowing us to assign a name to the match group, which we can retrieve after reading our match.

We can give the first example of a capture group that identifies "username" by using the following notation.
 


/hello/(<?P<username>.+)"

We can then use the groupdict() method on our regular expression to treat all the capture groups as a dictionary, with the group names corresponding to the matching values.

Then we give the following code:
 


route_regex = re.compile(r'^/hello/(?P<username>.+)$')
match = route_regex.match("/hello/ains")
 
print match.groupdict()

The following dictionaries will be output for us:
{' username ':' ains'}

Now that we have the format of the regular expressions we need and the knowledge of how to use them to match the URLs we are entering, what remains is to write a method that converts the paths we declare into their equivalent regular expression pattern.

To do this we will use another regular expression (which will be all regular expressions next), and in order for the variables in our path to be converted to a regular representation pattern, we will convert "" to" (? P. +) ".

That sounds too simple! We'll be able to implement it with a single line of new code.


 
def build_route_pattern(route):
  route_regex = re.sub(r'(<w+>)', r'(?P1.+)', route)
  return re.compile("^{}$".format(route_regex))
 
print build_route_pattern('/hello/<username>')

Here we have a regular expression representing all the patterns that occur (a string contained in Angle brackets), equivalent to its regular expression named group.

The first parameter of re. Sub we put our pattern in parentheses to assign it to the first matching group. In our second argument, we can use the contents of the first match group by writing 1 (2 will be the contents of the second match group, and so on... .).

So finally, input mode
 


/hello/<username>

Will give us the regular expression:
 


^/hello/(?P<username>.+)$

new

Let's take a quick look at the simple NotFlask class we wrote last time.
 


class NotFlask():
  def __init__(self):
    self.routes = {}
 
  def route(self, route_str):
    def decorator(f):
      self.routes[route_str] = f
      return f
 
    return decorator
 
  def serve(self, path):
    view_function = self.routes.get(path)
    if view_function:
      return view_function()
    else:
      raise ValueError('Route "{}"" has not been registered'.format(path))
 
app = NotFlask()
 
@app.route("/")
def hello():
  return "Hello World!"

Now that we have a new improved method to match the path of the input, we are going to remove the native dictionary that we used in the previous implementation.

Let's start by modifying our function to make it easier to add paths so that we can save our paths using (pattern, view_function) pairs of lists instead of dictionaries.

This means that when a programmer decorates a function with @app.route(), we're going to try to compile their path into a regular expression and then store it as a decorator in our new path list.

Let's look at the implementation code:


 
class NotFlask():
  def __init__(self):
    self.routes = []
 
  # Here's our build_route_pattern we made earlier
  @staticmethod
  def build_route_pattern(route):
    route_regex = re.sub(r'(<w+>)', r'(?P1.+)', route)
    return re.compile("^{}$".format(route_regex))
 
  def route(self, route_str):
    def decorator(f):
      # Instead of inserting into a dictionary,
      # We'll append the tuple to our route list
      route_pattern = self.build_route_pattern(route_str)
      self.routes.append((route_pattern, f))
 
      return f
 
    return decorator

We are also going to need a get_route_match method, give it a path instance, will try and find a matching view_function, or return None if None can be found.

However, if a match is found, one thing we need to return in addition to view_function is that we include the dictionary that previously captured the match group, which we need to pass the correct arguments to the view function.

Okay, so our get_route_match looks something like this:


 
def get_route_match(path):
  for route_pattern, view_function in self.routes:
    m = route_pattern.match(path)
    if m:
      return m.groupdict(), view_function
 
  return None

Now that we are almost done, the final step will be to figure out how to call view_function with the correct arguments from the regular expression matching group dictionary.

Several methods that call a function

Let's review the different methods that call a python function.

Like this:


 
def hello_user(username):
  return "Hello {}!".format(username)

The simplest (and perhaps most familiar) approach is to use regular arguments, where the order of the arguments matches the order of the functions we defined.


>>> hello_user("ains")
Hello ains!

Another way to call a function is to use keyword arguments. Keyword parameters can be specified in any order and are suitable for functions with many optional parameters.


>>> hello_user(username="ains")
Hello ains!

The last way to call a function in Python is to use a keyword argument dictionary, where the keyword corresponds to the argument name. We tell Python to unpack a dictionary and treat it as a keyword parameter of the function by using two asterisks of "**". The following code snippet is exactly the same as the one above, and now we use the dictionary parameter, which we can create dynamically at runtime.


>>> kwargs = {"username": "ains"}
>>> hello_user(**kwargs)
Hello ains!

Okay, remember the groupdict() method above? The same guy who returns {" username ":" ains "} after the regular expression completes the match? So now that we know about kwargs, we can easily pass a dictionary match to our view_function and complete the NotFlask!

So let's cram all of this into our final class.
 


class NotFlask():
  def __init__(self):
    self.routes = []
 
  @staticmethod
  def build_route_pattern(route):
    route_regex = re.sub(r'(<w+>)', r'(?P1.+)', route)
    return re.compile("^{}$".format(route_regex))
 
  def route(self, route_str):
    def decorator(f):
      route_pattern = self.build_route_pattern(route_str)
      self.routes.append((route_pattern, f))
 
      return f
 
    return decorator
 
  def get_route_match(self, path):
    for route_pattern, view_function in self.routes:
      m = route_pattern.match(path)
      if m:
        return m.groupdict(), view_function
 
    return None
 
  def serve(self, path):
    route_match = self.get_route_match(path)
    if route_match:
      kwargs, view_function = route_match
      return view_function(**kwargs)
    else:
      raise ValueError('Route "{}"" has not been registered'.format(path))

Next, it's time to see the magic. Here's the code snippet:


 
app = NotFlask()
 
@app.route("/hello/")
def hello_user(username):
return "Hello {}!".format(username)
 
print app.serve("/hello/ains")

We will get the output:

Hello ains!


Related articles: