July 18, 2018
Hot Topics:

Building a Photo Gallery with Python and WSGI

  • March 17, 2008
  • 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

Comment and Contribute


(Maximum characters: 1200). You have characters left.



Enterprise Development Update

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

By submitting your information, you agree that developer.com may send you developer offers via email, phone and text message, as well as email offers about other products and services that developer believes may be of interest to you. developer will process your information in accordance with the Quinstreet Privacy Policy.


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