Roundup Issue Tracker - WSGI

Roundup is a simple-to-use and -install and highly configurable issue-tracking system with command-line, web and e-mail interfaces. It can be used to track bugs, features, user feedback, sales opportunities, milestones.

Its requires start_response to return a write callable. According to Python WSGI spec, it is compliant, but it could/should have just return an iterable.

The start_response callable must return a write(body_data) callable that takes one positional parameter: a bytestring to be written as part of the HTTP response body. (Note: the write() callable is provided only to support certain existing frameworks' imperative output APIs; it should not be used by new applications or frameworks if it can be avoided.

  1. .

New WSGI applications and frameworks should not use the write() callable if it is possible to avoid doing so. The write() callable is strictly a hack to support imperative streaming APIs. In general, applications should produce their output via their returned iterable, as this makes it possible for web servers to interleave other tasks in the same Python thread, potentially providing better throughput for the server as a whole.

Also, because there are servers that does not return a write callable, so I have a hack to allow the to return a list: request.__data if there is no write callable. (Change/addition is highlighted/bold)

 1 def __call__(self, environ, start_response):
2 """Initialize with apache.Request object"""
3 self.environ = environ
4 request = RequestDispatcher(self.home, self.debug, self.timing)
5 request.__start_response = start_response
7 request.wfile = Writer(request)
8 request.__wfile = None
9 request.__data = None
11 if environ ['REQUEST_METHOD'] == 'OPTIONS':
12 code = 501
13 message, explain = BaseHTTPRequestHandler.responses[code]
14 request.start_response([('Content-Type', 'text/html'),
15 ('Connection', 'close')], code)
16 request.wfile.write(DEFAULT_ERROR_MESSAGE % locals())
17 return request.__data or []
19 tracker =, not self.debug)
21 # need to strip the leading '/'
22 environ["PATH_INFO"] = environ["PATH_INFO"][1:]
23 if request.timing:
24 environ["CGI_SHOW_TIMING"] = request.timing
26 form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ)
28 client = tracker.Client(tracker, request, environ, form,
29 request.translator)
30 try:
31 client.main()
32 except roundup.cgi.client.NotFound:
33 request.start_response([('Content-Type', 'text/html')], 404)
34 request.wfile.write('Not found: %s'%client.path)
36 # all body data has been written using wfile
37 return request.__data or []
39 def start_response(self, headers, response_code):
40 """Set HTTP response code"""
41 message, explain = BaseHTTPRequestHandler.responses[response_code]
42 self.__wfile = self.__start_response('%d %s'%(response_code,
43 message), headers)
45 if (self.__wfile is None) and (self.__data is None):
46 self.__data = []
47 self.__wfile = self.__data.append


blog comments powered by Disqus