To explore the concept of a WSGI application that serves more than one resource more in-depth, we’ll build a simple application.
Our application will serve a small database of python books.
Begin by creating a new project to work in:
heffalump:tests cewing$ mkproject bookapp
New python executable in bookapp/bin/python
Installing setuptools, pip...done.
Creating /Users/cewing/projects/bookapp
Setting project for bookapp to /Users/cewing/projects/bookapp
[bookapp]
heffalump:bookapp cewing$
Our application will need some data. I’ve got a simple database that we can use all set up.
The database (with a very simple api) can be found here. Copy that code and paste it into a file in your bookapp project folder. Keep the same name (bookdb.py).
Once you have that in place, you’re ready to get started on the application.
A good place to start when thinking about a new app is to consider what it does.
For our application we will want it to provide the following:
When viewing our first wsgi app, do we see the name of the wsgi application script anywhere in the URL?
In our wsgi application script, how many applications did we actually have?
How are we going to serve different types of information out of a single application?
We have to write an app that will map our incoming request path to some code that can handle that request.
This process is called dispatch. There are many possible approaches
Let’s begin by designing this piece of it.
Create a new file called bookapp.py in your bookapp project folder. We’ll do our work here.
The wsgi environment gives us access to PATH_INFO, which maps to the URI the user requested when they loaded the page.
We can design the URLs that our app will use to assist us in routing.
Let’s declare that any request for / will map to the list page
We can also say that the URL for a book will look like this:
http://localhost:8080/book/<identifier>
We need to design a function that will take the incoming path information from the wsgi environment and map it to something that will actually build our response for us.
Conceptually, this is much like the map_uri function you built for your HTTP server.
The difference is that the thing it finds will be a Python function, not a filesystem file or folder.
Add a new function called resolve_path in our application file.
import re
from bookdb import BookDB
DB = BookDB()
def resolve_path(path):
urls = [(r'^$', books),
(r'^book/(id[\d]+)$', book)]
matchpath = path.lstrip('/')
for regexp, func in urls:
match = re.match(regexp, matchpath)
if match is None:
continue
args = match.groups([])
return func, args
# we get here if no url matches
raise NameError
Because this code references symbols (book and books) that do not exist, we need to make some dummy functions to stand in for them:
def book(book_id):
return "<h1>a book with id %s</h1>" % book_id
def books():
return "<h1>a list of books</h1>"
These function are not a WSGI application. They are pieces that the application we write will use to make things happen.
Let’s add our actual application next:
def application(environ, start_response):
headers = [("Content-type", "text/html")]
try:
path = environ.get('PATH_INFO', None)
if path is None:
raise NameError
func, args = resolve_path(path)
body = func(*args)
status = "200 OK"
except NameError:
status = "404 Not Found"
body = "<h1>Not Found</h1>"
except Exception:
status = "500 Internal Server Error"
body = "<h1>Internal Server Error</h1>"
finally:
headers.append(('Content-length', str(len(body))))
start_response(status, headers)
return [body]
Finally, you’ll need to add a __main__ block to run your application:
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, application)
srv.serve_forever()
Once you’ve got your script settled, run it:
$ python bookapp.py
Then point your browser at http://localhost:8080/
Did that all work as you would have expected?
The basics of our app are already in place. Let’s move on next to build the functions that will generate our individual pages.
The function books should return an html list of book titles where each title is a link to the detail page for that book
Look at the bookdb.py file and ad the api for the books
def books():
all_books = DB.titles()
body = ['<h1>My Bookshelf</h1>', '<ul>']
item_template = '<li><a href="/book/{id}">{title}</a></li>'
for book in all_books:
body.append(item_template.format(**book))
body.append('</ul>')
return '\n'.join(body)
To see the effect of this function, quit your application and restart it:
$ python bookapp.py
Then reload the root of your application:
http://localhost:8080/
You should see a nice list of the books in the database. Do you?
Click on a link to view the detail page. Does it load without error?
The next step of course is to polish up those detail pages.
In this last case, what’s the right HTTP response code to send?
def book(book_id):
page = """
<h1>{title}</h1>
<table>
<tr><th>Author</th><td>{author}</td></tr>
<tr><th>Publisher</th><td>{publisher}</td></tr>
<tr><th>ISBN</th><td>{isbn}</td></tr>
</table>
<a href="/">Back to the list</a>
"""
book = DB.title_info(book_id)
if book is None:
raise NameError
return page.format(**book)
Quit and restart your script one more time
Then poke around at your application and see the good you’ve made
And your application is portable and sharable
It should run equally well under any wsgi server
Next steps for an app like this might be: