= Trac and mod_python 2.7 = Trac was written to use Apache 2, but if you want to use Apache 1.3, you couldn't use [http://www.modpython.org/ mod_python] because [http://www.modpython.org/ mod_python] 3 only runs on Apache 2. Since this wasn't an option in my case, I patched [http://www.modpython.org/ mod_python] 2.7.11 and also made a couple of small changes to Trac to make it work. There are two patches, one for [http://www.modpython.org/ mod_python] 2.7.11, and one for Trac 0.8.4. They have only been tested on Mac OS X, with these specific versions, so your mileage may vary. Here's the [http://www.modpython.org/ mod_python] patch. {{{ diff -r -u mod_python-2.7.11.orig/lib/python/mod_python/util.py mod_python-2.7.11/lib/python/mod_python/util.py --- mod_python-2.7.11.orig/lib/python/mod_python/util.py 2000-12-13 23:45:48.000000000 +0000 +++ mod_python-2.7.11/lib/python/mod_python/util.py 2005-09-16 12:58:07.000000000 +0100 @@ -47,6 +47,9 @@ import string import StringIO +from types import * +from exceptions import * + parse_qs = apache.parse_qs parse_qsl = apache.parse_qsl @@ -95,6 +98,14 @@ def __del__(self): self.file.close() +class StringField(str): + """ This class is basically a string with + a value attribute for compatibility with std lib cgi.py + """ + + def __init__(self, str=""): + str.__init__(self, str) + self.value = self.__str__() class FieldStorage: @@ -239,10 +250,10 @@ found = [] for item in self.list: if item.name == key: - if isinstance(item.file, StringIO.StringIO): - found.append(item.value) - else: - found.append(item) + if isinstance(item.file, FileType): + found.append(item) + else: + found.append(StringField(item.value)) if not found: raise KeyError, key if len(found) == 1: @@ -250,6 +261,12 @@ else: return found + def get(self, key, default): + try: + return self.__getitem__(key) + except KeyError: + return default + def keys(self): """Dictionary style keys() method.""" if self.list is None: @@ -271,23 +288,121 @@ """Dictionary style len(x) support.""" return len(self.keys()) - + def getfirst(self, key, default=None): + """ return the first value received """ + for item in self.list: + if item.name == key: + if isinstance(item.file, FileType): + return item + else: + return StringField(item.value) + return default + + def getlist(self, key): + """ return a list of received values """ + if self.list is None: + raise TypeError, "not indexable" + found = [] + for item in self.list: + if item.name == key: + if isinstance(item.file, FileType): + found.append(item) + else: + found.append(StringField(item.value)) + return found + def parse_header(line): """Parse a Content-type like header. Return the main content-type and a dictionary of options. """ - plist = map(string.strip, string.splitfields(line, ';')) - key = string.lower(plist[0]) + + plist = map(lambda a: a.strip(), line.split(';')) + key = plist[0].lower() del plist[0] pdict = {} for p in plist: - i = string.find(p, '=') + i = p.find('=') if i >= 0: - name = string.lower(string.strip(p[:i])) - value = string.strip(p[i+1:]) + name = p[:i].strip().lower() + value = p[i+1:].strip() if len(value) >= 2 and value[0] == value[-1] == '"': value = value[1:-1] pdict[name] = value return key, pdict + +def apply_fs_data(object, fs, **args): + """ + Apply FieldStorage data to an object - the object must be + callable. Examine the args, and match then with fs data, + then call the object, return the result. + """ + + # add form data to args + for field in fs.list: + if field.filename: + val = field + else: + val = field.value + args.setdefault(field.name, []).append(val) + + # replace lists with single values + for arg in args: + if ((type(args[arg]) is ListType) and + (len(args[arg]) == 1)): + args[arg] = args[arg][0] + + # we need to weed out unexpected keyword arguments + # and for that we need to get a list of them. There + # are a few options for callable objects here: + + if type(object) is InstanceType: + # instances are callable when they have __call__() + object = object.__call__ + + expected = [] + if hasattr(object, "func_code"): + # function + fc = object.func_code + expected = fc.co_varnames[0:fc.co_argcount] + elif hasattr(object, 'im_func'): + # method + fc = object.im_func.func_code + expected = fc.co_varnames[1:fc.co_argcount] + elif type(object) is ClassType: + # class + fc = object.__init__.im_func.func_code + expected = fc.co_varnames[1:fc.co_argcount] + + # remove unexpected args unless co_flags & 0x08, + # meaning function accepts **kw syntax + if not (fc.co_flags & 0x08): + for name in args.keys(): + if name not in expected: + del args[name] + + return object(**args) + +def redirect(req, location, permanent=0, text=None): + """ + A convenience function to provide redirection + """ + + if req.sent_bodyct: + raise IOError, "Cannot redirect after headers have already been sent." + + req.err_headers_out["Location"] = location + if permanent: + req.status = apache.HTTP_MOVED_PERMANENTLY + else: + req.status = apache.HTTP_MOVED_TEMPORARILY + + if text is None: + req.write('

The document has moved' + ' here

\n' + % location) + else: + req.write(text) + + raise apache.SERVER_RETURN, apache.OK diff -r -u mod_python-2.7.11.orig/src/include/mod_python.h mod_python-2.7.11/src/include/mod_python.h --- mod_python-2.7.11.orig/src/include/mod_python.h 2003-12-08 04:36:10.000000000 +0000 +++ mod_python-2.7.11/src/include/mod_python.h 2005-09-16 01:01:47.000000000 +0100 @@ -67,7 +67,7 @@ #include "http_protocol.h" #include "util_script.h" #include "http_log.h" - +#include "multithread.h" /* Python headers */ /* this gets rid of some comile warnings */ diff -r -u mod_python-2.7.11.orig/src/mod_python.c mod_python-2.7.11/src/mod_python.c --- mod_python-2.7.11.orig/src/mod_python.c 2001-05-28 21:00:41.000000000 +0100 +++ mod_python-2.7.11/src/mod_python.c 2005-09-16 01:38:05.000000000 +0100 @@ -264,6 +264,9 @@ server.register_cleanup() */ pool *child_init_pool = NULL; + /* Fudge for OS X */ + static int initialized = 0; + /* mod_python version */ ap_add_version_component(VERSION_COMPONENT); @@ -272,8 +275,9 @@ ap_add_version_component(buff); /* initialize global Python interpreter if necessary */ - if (! Py_IsInitialized()) + if (!initialized || !Py_IsInitialized()) { + initialized = 1; /* initialze the interpreter */ Py_Initialize(); diff -r -u mod_python-2.7.11.orig/src/requestobject.c mod_python-2.7.11/src/requestobject.c --- mod_python-2.7.11.orig/src/requestobject.c 2001-05-23 03:49:43.000000000 +0100 +++ mod_python-2.7.11/src/requestobject.c 2005-09-16 02:18:33.000000000 +0100 @@ -849,6 +849,7 @@ {"filename", T_STRING, OFF(filename), }, {"path_info", T_STRING, OFF(path_info), RO}, {"args", T_STRING, OFF(args), RO}, + {"user", T_STRING, }, /* XXX - test an array header */ /* XXX finfo */ /* XXX parsed_uri */ @@ -1015,6 +1016,15 @@ else if (strcmp(name, "hstack") == 0) { return PyString_FromString(self->hstack); } + else if (strcmp(name, "user") == 0) { + if (!self->request_rec->connection + || !self->request_rec->connection->user) { + Py_INCREF(Py_None); + return Py_None; + } + + return PyString_FromString(self->request_rec->connection->user); + } else if (strcmp(name, "_content_type_set") == 0) { return PyInt_FromLong(self->content_type_set); } @@ -1058,6 +1068,16 @@ ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); return 0; } + else if (strcmp(name, "user") == 0) { + if (!self->request_rec->connection) { + PyErr_SetString(PyExc_AttributeError, "no connection"); + return -1; + } + + self->request_rec->connection->user = + ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); + return 0; + } else if (strcmp(name, "hstack") == 0) { self->hstack = ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); return 0; }}} You also need the following patch for Trac's ModPythonHandler.py: {{{ diff -r -u trac-0.8.4.orig/trac/ModPythonHandler.py trac-0.8.4/trac/ModPythonHandler.py --- trac-0.8.4.orig/trac/ModPythonHandler.py 2005-06-17 19:22:32.000000000 +0100 +++ trac-0.8.4/trac/ModPythonHandler.py 2005-09-16 16:54:13.000000000 +0100 @@ -99,7 +99,7 @@ self.req.write(data) def get_header(self, name): - return self.req.headers_in.get(name) + return self.req.headers_in[name] def send_response(self, code): self.req.status = code @@ -108,11 +108,10 @@ if name.lower() == 'content-type': self.req.content_type = value else: - self.req.headers_out.add(name, str(value)) + self.req.headers_out[name] = str(value) def end_headers(self): - pass - + self.req.send_http_header() class TracFieldStorage(util.FieldStorage): """ }}}