Jun 302012
 

If you’ve Googled around for a quick and simple framework for putting together web services in Python, odds are you’ve stumbled on the Flask framework. In fact, quick and simple is pretty much Flask’s claim to fame.

1 of the big advantages of Flask is that it does pretty much everything right out of the box. Flask’s quickstart shows a simple “Hello, World!” app in 7 lines of code. That includes mapping a URL, serving the application, building the response object, the whole 9 yards. For the more technical, nuts and bolts types, Flask is built on top of Werkzeug and Jinja2, although I never really delved into any of those libraries other than scouring documentation when trying to figure out how to do stuff (which typically ended in me realizing that Flask had the exact functionality I needed already built-in).

Installation

Flask is on pypi.python.org, so calling easy_install Flask should work for you. The Flask website also has instructions for creating a virtual environment for your web services and installing via pip if you prefer that route. Whatever your option, you’re a handful of command-line statements at most from having Flask installed and ready to use on your system.

Writing your web service

Want to map a URL to a method? No need for a mapping table or any special object to do that, Flask has a decorator that does that for you.

from flask import app, Flask
@app.route("/your/route/goes/here")
def yourMethod():
    # Your code goes here.
    pass

Maybe you need to specify a specific HTTP method:

    
@app.route("/your/route/goes/here", methods=["POST"])
def yourPostMethod():
    # This automatically generates an error page if 
    # accessed any way other than POST.
    pass

I know what you’re saying. “Sure moron, code examples are easy when all you’re doing is defining methods and then passing.” Luckily for you, oh cynical ones, actually implementing methods is pretty easy too. For example, take the following (very) simple web application below (and pay attention – I’ll be referencing throughout this section):

import simplejson as json
from flask import Flask, current_app, make_response

app = Flask(__name__)

@app.route("/")
def index(username="stranger"):
    return "Hello, " + username + "!"

@app.route("/path/to/url", methods=["GET"])
def urlMethod():
    return make_response("With Flask it's easy to define a URL mapping for " +
            "a method.", 200)

@app.route("/formprocessing", methods=["POST"])
def processForm():
    return "I can only get here via POST!"

@app.route("/error")
def error():
    return make_response("Here's a server-generated error.", 401)

@app.route("/return/json", methods=["GET"])
def returnjson():
    jsonData = {"Key 1": "Value 1",
                "Key 2": "Value 2",
                "Key 3": "Value 3"}

    jsonData = json.dumps(jsonData)
    return current_app.response_class(jsonData, mimetype="application/json")

if __name__ == "__main__":
    app.run()

You could take the above code, copy it into a Python file, run it, and have a fully functioning web service. You can easily see how to get your web application to access the different methods. Let’s take a look at how it works.

Note the fact that the name in the angle brackets matches the parameter name in the index() function. That’s not a coincidence, that’s how you pass parameters to Flask webservices. If you don’t specify the HTTP methods that the application method accepts, it defaults to GET and HEAD. In the example code above, just typing in http://<your server name and port here>/formprocessing will give you an error page since you didn’t call it via POST. Use any URL other than the ones mapped in the code above, and you’ll get a stock 404 page.

You don’t just have to return a string. In the returnjson() method, you can see where the application makes a simple dictionary and converts it to JSON before sending it to the client, with the proper mimetype.

If you need to return an error, you can build a response message and set an appropriate HTTP error code, as seen in the error() method in the test class.

By the way, it’s easy to use your application with other WSGI middleware, just wrap the app variable with your middleware of choice. You can use the wsgi_app property from Flask to wrap your application around other middleware if you’re so inclined. In my instance, I wrapped my application in a custom little security middleware, and then I wrapped that in some Beaker session middleware, but Flask can handle sessions for you if you don’t want to use third party libraries.

Some words of caution

With any RESTful application, take care in naming your URL routes. I’ve forgotten this a few times and had my application try calling the wrong data because while the parameters I was passing were completely different to me, the application didn’t have the advantage of intuiting the meanings of the strings I was passing back and forth.

By the way, you can type your parameters so they’re not all just strings (it didn’t help in my app, since I needed everything in strings, but if you check the Flask documentation, you can type numeric values and avoid trying to convert the data from a string on the server).

Serving with Paste

While the Flask framework is capable of serving your application all on its own, the decision was made for this particular project to use Paste to serve the application instead. To get this to work, I had to make a few adjustments to how I structured my code. First and foremost, I moved pretty much all of my app code (as in, “everything but the variables intended to be constants”) into a class. Sure this is a bit extreme just to make the application run, but I have my reasons (that I’ll get into in a bit). While we’re at it, add a little method, call it something like getApp(), that returns your app variable.

Got your application code wrapped up in a class? Great! Now we’re going to write a simple little factory script that has but 1 method, make_app(global_args, **local_args). Those parameters will be coming from your application’s configuration file. In this make_app() method, setting up the application logging (if records of what your application is doing is your sort of thing), and initialize your application class. Now simply return your ApplicationClass.getApp() method (the one I told you to make last paragraph, weren’t you paying attention?).

Now, onto the Paste configuration file! For a simple setup where you’d only be running 1 application, it’d look something like this:

[DEFAULT]
# Your global configurations go here

[app:main]
paste.app_factory = apppackage.simple_little_factory_script.:make_app
# Other application-specific configurations go here

[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = %(port_number)s

For a setup where you’d possibly be serving multiple Flask applications on the same server, it’d be something more like:

[DEFAULT]
# Your global configurations go here

[composite:main]
use = egg:Paste#urlmap
/path/to/app1 = app1
/path/to/app2 = app2
# You get the idea from here...

[app:app1]
paste.app_factory = app1package.simple_little_factory_script1:make_app
# Other app-specific configurations go here

[app:app2]
paste.app_factory = app2package.simple_little_factory_script2:make_app
# Other app-specific configurations go here

# And so on and so forth...

[server:main]
use = egg:Paste#http
host = 0.0.0.0
port = %(port_number)s

About that application class…

Now, about me putting everything into my application class. That was so I could store my configuration parameters for use later, particularly in the mapped methods. I did this because I wanted my methods mapped to URLs to have access to configuration data. You know, the stuff I passed to my factory class? I had that factory pass them on to the class wrapping my application when I instantiated it, and stored it in global variables.

Well, as it turns out, the downside to putting all of the application code is that all of your variables (like “app”, as in “that handy little variable you use in the decorators”) are no longer global variables. Sure, you can fix that by just adding a “self” reference in front of the variables. But did you notice something missing from all of that handy little example code I posted above? Oh yeah, it’s the word “self”! “self.app.route” isn’t a viable option for mapping routes, so how are we going to do our route mapping now? Well, intrepid blog reader (and if you’ve gotten this far, you’re intrepid), we’re going to do it the old-fashioned way. Flask provides an app.add_url_rule() method that does the mappings for you, it’s just not as clean and simple and fancy-looking as the decorators used above.

If we were to wrap up the test app above into a class, we’d end up with a class that looks something like this:

import simplejson as json
from flask import Flask, current_app, make_response

class TestApp():

    def __init__(self, global_args, **local_args):
        self.app = Flask(__name__)

        self.app.add_url_rule("/", "index", self.index)
        self.app.add_url_rule("/", "index", self.index)
        self.app.add_url_rule("/path/to/url", "urlMethod", self.urlMethod, 
                methods=["GET"])
        self.app.add_url_rule("/formprocessing", "processForm",
                self.processForm, methods=["POST"])
        self.app.add_url_rule("/error", "error", self.error)
        self.app.add_url_rule("/return/json", returnjson, self.returnjson,
                methods=["GET"])

        self.global_args = global_args
        self.local_args = local_args

    def index(self, username="stranger"):
        return "Hello, " + username + "!"

    def urlMethod(self):
        return make_response("With Flask it's easy to define a URL mapping " +
                "for a method.", 200)

    def processForm(self):
        return "I can only get here via POST!"

    def error(self):
        return make_response("Here's a server-generated error.", 401)

    def returnjson(self):
        jsonData = {"Key 1": "Value 1",
                    "Key 2": "Value 2",
                    "Key 3": "Value 3"}

        jsonData = json.dumps(jsonData)
        return current_app.response_class(jsonData, mimetype="application/json")

    if __name__ == "__main__":
        app.run()

What I lost in nifty little routing decorators, I gained in the ability to reference class data in my mapped methods. In my real application, I actually used the global and local arguments I assigned in the __init__() method above along with other class variables, so it was the way to go for me. If you can get around the lack of self in your application without committing obscene acts of programming contortion, I don’t recommend this route (doubly so if you’re not serving using something like Paste).

Another reason to move everything (including the app variable) into the class is for unit testing (you’ll need the self.app reference to run nosetests). Again, if this doesn’t apply to you, I wouldn’t bother.

Parsing your configuration options

At 1 point during the development of this application, I tried to parse configurations manually using ConfigObject. This created an issue when I tried to run it on my server with nginx sitting in front of it. For reasons I will never know or understand, nginx wouldn’t forward the request onto Paste when I was doing this. After changing the code to store my configuration options in class variables, this problem vanished. Again, I don’t know how or why, but don’t say I never warned you.

TL;DR

For another good guide to the basics of just using Flask (without so much talk of using classes or other software libraries), check out this article.

Other references

For a just-the-nuts-and-bolts of specific issues, here are the StackOverflow questions (some more intelligent than others) I wound up creating over the course of this project:

Why does the login method of Flask use ‘GET’? (This stems from reading over the demo code on the Flask website, and is not one of the brighter things I’ve ever put forth on the Internet)

Unit testing a Flask application class (Also not a bright point in terms looking smart on the world wide web, but this is what ultimately led to me moving the app variable inside my class, and changing the @app.route decorators for the self.app.add_url_rule() calls)

nginx not forwarding upstream (This was me trying to debug the nginx issue briefly mentioned above. I’d have discussed it more in the hopes of posting something useful and educational, but like I said, I still don’t know what happened)

Deploying a Flask application using Paste (I was new to Paste, so I was having trouble with the whole Paste configuration thing. If your application doesn’t fit the template on their website closely, then the best I can offer is the hope you have other applications whose configurations you can use as other references for the purpose of aping them)

As a bonus, here’s a more detailed explanation of the whole using self reference with mapped methods:

How to pass class’s self through a flask.Blueprint.route decorator?

Bottom line

Overall, as a basic, quick, get-it-up-and-going-we-have-to-ship-it-quick RESTful web service framework, Flask does a good job, especially if you’re new to this sort of thing and want to have to contend with as few third-party libraries or frameworks as possible. It behaves well with third-party libraries, and keeps setup-related work out of your hair so you can put your time and effort into business logic. However, you could probably get a lot more control over the overall architecture by using a different framework or collection of frameworks. If you really want or need to put your logic into a class, you can very quickly start to lose the advantage of using Flask, and may want to start looking towards other frameworks.

 Posted by at 7:20 PM