Using Werkzeug with Codenvy

 

Werkzeug tutorial with Codenvy IDE

What is Werkzeug

Werkzeug is a WSGI utility library for Python. It’s widely used and BSD licensed. Werkzeug started as a simple collection of various utilities for WSGI applications and has become one of the most advanced WSGI utility modules. It includes a powerful debugger, fully featured request and response objects, HTTP utilities to handle entity tags, cache control headers, HTTP dates, cookie handling, file uploads, a powerful URL routing system and a bunch of community contributed addon modules.

It does Unicode and doesn’t enforce a specific template engine, database adapter or anything else. It doesn’t even enforce a specific way of handling requests and leaves all that up to the developer.

Lets start a python project

We will create a simple werkzeug application that greets the world with different ways by using multiple url’s and handlers that pull data from different sources.

First of all create a new python project using the Codenvy IDE. Select from menu ‘Project’, New->Create Project. new_project Name your new application accordingly, select Python as the project type, and leave PaaS as None. new_project-2 Next select the ‘simple python project’ and click finish.

The sample ‘Simple python application’ is based on bottle, but as Werkzeug and bottle functionality overlap, replace “bottle” with “werkzeug” in the requirements.txt file to make the python environment to install Werkzeug.

Initial configuration and debugging

Clear out everything in the wsgi.py file and replace them with:

#wsgi.py
from hello import Hello
from werkzeug.debug import DebuggedApplication

def application(environ, start_response):
    app = Hello()
    app = DebuggedApplication(app, evalex=True)
    return app(environ, start_response)

As you see we are importing the Hello class from the hello package. The Hello class is build with Werkzeug and calling the instance directly behaves like a WSGI application. Additionally we wrap the Hello app with Werkzeug runtime debugger, a handy feature as we currently don’t have access to the console output.

The Controller

Lets start writing hello.py. Create a new file named hello.py

new_file

Open the file and add all the import statements:

#hello.py
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.utils import redirect

We see a lot of new stuff coming from Werkzeug, so let me explain a bit. Request and Response are 2 classes representing the request coming from the client, e.g. request headers, request type, client ip, etc. The Response class on the other hand represents the responses we return to the client, for example the response code (200 404 500 etc), any set cookies, text etc. Using these 2 objects we can easily get info about the request and casually manipulate our responses to those requests.

redirect is an utility function that returns a specially crafted Response with the redirection code of 301 or 302, useful for redirecting clients to login pages, moved resources etc.

One other important piece of software that is relevant to all web applications is routing. Werkzeug provides helpers to route requests to specific handlers. This accomplished via the Map and Rule classes. And is one of the first things we will declare in our class constructor.

#hello.py

class Hello(object):

    def __init__(self):
        self.url_map = Map([
            Rule('/', endpoint='root'),
            Rule('/hello', endpoint='hello'),
            Rule('/helloargs', endpoint='hello_args'),
            Rule('/hello/<name>', endpoint='hello_dynamic'),
            Rule('/hellodb/<user_id>', endpoint='hello_datastore')
        ])

So we are connecting url structures with specific endpoints or else handlers. See that some rules have dynamic part inside their url such as “name” or “user_id”, these define arguments that are going to be passed to our handlers responsible for the request in hand.

Routing Explained

This is all nice. But how do we apply these rules to the request at hand? This is the next function in our Hello class. The dispatcher:

#hello.py - Hello class

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        try:
            endpoint, values = adapter.match()
            return getattr(self, 'on_' + endpoint)(request, **values)
        except HTTPException, e:
            return e

By defining __call__ we allow of calling the object direcctly as a function. It acts as a proxy to the wsgi_app function.

wsgi_app is a simple wsgi ‘function’ that bootstraps our werkzeug application. First it creates a Request object by passing to it the environ and after that it feeds it to the dispatcher, where all the magic happens.

The dispatch_request function is essentially our router. What it does is by applying the routing map we build in the constructor to the request at hand, it learns the endpoint responsible for handling the request.

The endpoint variable is just a string we specified when we build the map, but we can exploit the dynamic nature of python to call a Hello class method that follows a specific structure in its name. For example the ‘/’ Rule has an endpoint of ‘root’, when a request for ‘/’ arrives dispatch_request will try to call the on_root method of the class and return it as a result for wsgi_app. So now we only need to define a set of class methods such as:

The Handlers

#hello.py - Hello class

    def on_root(self, request):
        if request.method == 'POST': # or 'UPDATE' or etc...
            raise MethodNotAllowed()
        return Response(tmpl.root, mimetype='text/html')

    def on_hello(self, request):
        return Response(tmpl.hello_static, mimetype='text/html')

    def on_hello_args(self, request):
        return Response(tmpl.hello_dynamic % request.args.get('name', 'WorldDefault'), mimetype='text/html')

    def on_hello_dynamic(self, request, **keys):
        return Response(tmpl.hello_dynamic % keys.get('name', 'None'), mimetype='text/html')

    def on_hello_datastore(self, request, **keys):
        try:
            uid = int(keys.get('user_id'))
            user = self.database[uid]
            ret = tmpl.hello_dynamic % user['name']
            status = 200
        except:
            ret = 'Wrong user id, or user does not exist'
            status = 404
        return Response(ret, mimetype='text/html', status=status)

We can see that all of these functions use a “tmpl” statement we have not explained. This is our template holder which we define in a different file named templates.py.

The Templates

#templates.py
root = """
          <html>
            <body>
              <h1>This is a home page</h1>
              <a href='/hello'>A static hello world page</a><br/>
              <a href='/helloargs?name=ArgName'>A dynamic hello world page that gets name from argument</a><br/>
              <a href='/hello/AnotherName'>A dynamic hello world page that gets name from url</a><br/>
              <a href='/hellodb/1'>A dynamic hello world page that gets name from datastore</a>
            </body>
          </html>
        """
hello_static = """<html>
                      <body>
                        <h1>Hello World</h1>
                      </body>
                  </html>
               """

hello_dynamic = """<html>
                      <body>
                        <h1>Hello %s</h1>
                      </body>
                   </html>
                """

These are simple python strings that we use to return in response to a request. They are static strings except “hello_dynamic” that has a substitution (%s) variable defined. See on_hello_dynamic and on_hello_datastore for examples of how this template string is used.

The Datastore

In on_hello_datastore we also use “self.database”. This is a simple python dictionary holding some structured data and is simulating in our example a theoretical external NOSQL storage system.
So lets create another file named datastore.py containing our hypothetical database.

#datastore.py
kvstore = {  1: {'name':     'John Dow',
                 'username': 'jo',
                 'password': '12345',
                 'email':    'myemail@email.com'},

             2: {'name':     'Foo Bar',
                 'username': 'foo',
                 'password': 'Bar',
                 'email':    'foo@bar.com'},

             3: {'name':     'Josua Bar',
                 'username': 'Josua',
                 'password': 'josua',
                 'email':    'josua@bar.com'}
           }

And add the relevant import statement for datastore.py and templates.py on the top of wsgi.py:

from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
from werkzeug.utils import redirect

# Add these two lines:
import templates as tmpl
from datastore import kvstore

And then in the Hello constructor connect the kvstore variable with self.database:

    def __init__(self):

        self.url_map = Map([
            Rule('/', endpoint='root'),
            Rule('/hello', endpoint='hello'),
            Rule('/helloargs', endpoint='hello_args'),
            Rule('/hello/<name>', endpoint='hello_dynamic'),
            Rule('/hellodb/<user_id>', endpoint='hello_datastore')
        ])
        # Add this line:
        self.database = kvstore

Now our werkzeug project is ready to launch.

  • /hello returns a static “Hello World” string
  • /helloargs returns a “Hello -arg-” with arg getting pulled from the HTTP url arguments
  • /hello/-name- Gets the name from the second part of the url as we defined it in the Rule
  • /hellodb/-user_id- gets a user_id from the url, and uses this to pull data from the database.

Run the project and find out for yourself how amazingly easy Werkzeug is.