Building a Photo Gallery with Python and WSGI, Page 2
So, walk through this very simple class.
class fsPicture(object): def __init__(self, root, template=base_template): self.template = template self.root = root def split_path_from_item(self, item): """removes the root directory from the path. This lets us use the result as a web path.""" return "/" + item.replace(self.root, '') def directory_listing(self, directory, path): """returns html for a directory listing""" files = "" directories = "" for item in glob(directory + "/*"): web_path = self.split_path_from_item(item) if os.path.isdir(item): directories += """<div class="directory"> <a href="%s" class="directory">%s </a></div>""" % (web_path, web_path) elif os.path.isfile(item) and item.lower().endswith('.jpg'): files += """<div class="image"> <img src="%s?thumbnail=200"><br/> <a href="%s">%s</a> </div>""" % (web_path, web_path, web_path) html = "" if directories: html += """<h2>Directories</h2> <div id="directories">%s</div>""" % directories if files: html += """<h2>Pictures</h2> <div id="pictures">%s</div>""" % files return html def picture(self, image, path): """returns raw binary image. If query string of "thumbnail" is passed to the app the image is resized to a maximum of the argument. For instance: /some_image.jpg?thumbnail=100""" i = Image.open(image) if self.query_string: try: size = cgi.parse_qs(self.query_string) size = size['thumbnail'] i.thumbnail((int(size), int(size))) except: pass s = StringIO() i.save(s, 'JPEG') return s.getvalue() def find_object(self, path): """finds the directory or picture referenced, returns the response and the mimetype""" item = os.path.join(self.root, *path.split('/')) if os.path.isdir(item): return ([self.template % self.directory_listing(item, path),], 'text/html') elif os.path.isfile(item) and item.lower().endswith('.jpg'): return ([self.picture(item, path),], 'image/jpeg') else: return ([self.template % 'not found'], 'text/html') def __call__(self, environ, start_response): """the entry point to the application""" self.query_string = environ.get('QUERY_STRING', False) response, mimetype = self.find_object(environ['PATH_INFO']) start_response('200 OK', [('content-type',mimetype)]) return response
__call__ is the main entry point for the WSGI server to call. Like a WSGI callable function, it takes 'environ' and 'start_response' as arguments, along with the object-required 'self' argument. It sets the object property 'query_string' to the environ 'QUERY_STRING' value, which is everything in the URL after the ?. You'll use it later to determine whether you should resize a photograph. You get the response, which could be a binary JPEG, or HTML, as well as the mimetype from the call to the object method find_object, passing it the environ variable 'PATH_INFO', which is everything in the URL after the domain, and before the ?.
find_object's job is to translate the 'PATH_INFO' into an object on the filesystem. That translation really begins in the object's __init__ method, where the root property is set. Root, in this context, is the base directory on the filesystem where you want to find all your jpegs. It also takes a template as an argument, or uses the base template (really just a big fat string) declared above. When find_object is called, it combines the web path with the root property (again, the directory on the filesystem that holds all your jpegs) and then determines whether the referenced file is a directory or a jpeg.
If the file is a directory, it calls directory_listing, passing it the directory on the filesystem, as well as the web path, and gets back a nice chunk of HTML that lists the contents of that directory. If the file is a jpeg, it passes the call off to picture, which returns the mimetype of "image/jpeg" as well as the raw jpeg encoded binary data from the jpeg. If the user passed in the query_string "thumbnail" variable, the image is resized on the fly to fit the constraints passed in the query string. This is how you get nice little thumbnails of each photo inside a directory.
In reality, for anything other than a trivial web application, you're probably better off building on top of an existing web framework, like Django or Pylons, both of which can be served as WSGI applications themselves. That said, building an application with WSGI, rather than a high-level framework, gives you a sense of what's happening under the covers in your Python web framework of choice. This article focused on building web applications with WSGI, but didn't touch on WSGI middleware, which allows you to insert chunks of code before or after the server request is processed by the application. If you're really committed to building a non-trivial application in WSGI, you might want to check out Paste, a series of libraries by Ian Bicking that wrap up a lot of common web patterns in a clean API, and WebOb, also by Ian, a minimalist framework built on top of Paste. Ian even writes a similar file-serving application as a demonstration of WebOb's use.
About the Author