In my post about Grayvatar, I warned everybody away from actually using it for anything as it was a complete toy. The major reason I cited was that it did not make any attempt to respect cache headers. Enter Grayvatar 2.0, which is slightly less of a toy as it, at least, makes a half-hearted attempt to respect cache headers.
The key addition was httplib2, which maintains it's own cache, so that we're not hitting Gravatar's servers for every single request we receive. The other big change was to copy cache headers from the client to the server and vice-versa. This means that we'll pass a long the cache-control: max-age=300 that we get from Gravatar or the cache-control: max-age=0 we get from the client. All of this adds about 20 lines on top of the existing implementation. You still shouldn't use it for anything, it's still a toy, but at least it's slightly less naive one.
import StringIO import tempfile import urllib, urllib2 import cherrypy import httplib2 import Image from paver.easy import path URL = "http://www.gravatar.com/avatar/%s?%s" cache_headers = ("cache-control",) class Grayvatar(object): def __init__(self): tmp_dir = tempfile.mkdtemp(".cache") self.http = http = httplib2.Http(tmp_dir) @cherrypy.expose def default(self, *bits, **kwargs): if bits: cherrypy.response.headers["Content-Type"] = "image/png" url = URL % (bits, urllib.urlencode(kwargs)) headers, img = self.get_img(url) for header in cache_headers: if header in headers: cherrypy.response.headers[header] = headers[header] return img_as_str(img.convert("L")) pwd = path(__file__).abspath().parent return (pwd / "how_it_works.html").text() def get_img(self, url): headers, body = self.http.request(url, headers = project(cherrypy.request.headers, cache_headers) ) return headers, Image.open(StringIO.StringIO(body)) def project(d, keys): return dict((k, d[k]) for k in keys if k in d) def img_as_str(img): sock = StringIO.StringIO() img.save(sock, "png") return sock.getvalue() if __name__ == "__main__": cherrypy.quickstart(Grayvatar())