February 28, 2021
Hot Topics:

Building a Photo Gallery with Python and WSGI

  • By Chris McAvoy
  • Send Email »
  • More Articles »

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
            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:
            size = cgi.parse_qs(self.query_string)
            size = size['thumbnail'][0]
            i.thumbnail((int(size), int(size)))
      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')
            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

Chris McAvoy is a developer for PSC Group LLC in Chicago Illinois. He specializes in Python and Ruby web applications. He also keeps a blog at http://weblog.lonelylion.com.

Page 2 of 2

This article was originally published on March 17, 2008

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date