| 1 | = Trac and mod_python 2.7 = |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | Here's the [http://www.modpython.org/ mod_python] patch. |
| 8 | |
| 9 | {{{ |
| 10 | 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 |
| 11 | --- mod_python-2.7.11.orig/lib/python/mod_python/util.py 2000-12-13 23:45:48.000000000 +0000 |
| 12 | +++ mod_python-2.7.11/lib/python/mod_python/util.py 2005-09-16 12:58:07.000000000 +0100 |
| 13 | @@ -47,6 +47,9 @@ |
| 14 | import string |
| 15 | import StringIO |
| 16 | |
| 17 | +from types import * |
| 18 | +from exceptions import * |
| 19 | + |
| 20 | parse_qs = apache.parse_qs |
| 21 | parse_qsl = apache.parse_qsl |
| 22 | |
| 23 | @@ -95,6 +98,14 @@ |
| 24 | def __del__(self): |
| 25 | self.file.close() |
| 26 | |
| 27 | +class StringField(str): |
| 28 | + """ This class is basically a string with |
| 29 | + a value attribute for compatibility with std lib cgi.py |
| 30 | + """ |
| 31 | + |
| 32 | + def __init__(self, str=""): |
| 33 | + str.__init__(self, str) |
| 34 | + self.value = self.__str__() |
| 35 | |
| 36 | class FieldStorage: |
| 37 | |
| 38 | @@ -239,10 +250,10 @@ |
| 39 | found = [] |
| 40 | for item in self.list: |
| 41 | if item.name == key: |
| 42 | - if isinstance(item.file, StringIO.StringIO): |
| 43 | - found.append(item.value) |
| 44 | - else: |
| 45 | - found.append(item) |
| 46 | + if isinstance(item.file, FileType): |
| 47 | + found.append(item) |
| 48 | + else: |
| 49 | + found.append(StringField(item.value)) |
| 50 | if not found: |
| 51 | raise KeyError, key |
| 52 | if len(found) == 1: |
| 53 | @@ -250,6 +261,12 @@ |
| 54 | else: |
| 55 | return found |
| 56 | |
| 57 | + def get(self, key, default): |
| 58 | + try: |
| 59 | + return self.__getitem__(key) |
| 60 | + except KeyError: |
| 61 | + return default |
| 62 | + |
| 63 | def keys(self): |
| 64 | """Dictionary style keys() method.""" |
| 65 | if self.list is None: |
| 66 | @@ -271,23 +288,121 @@ |
| 67 | """Dictionary style len(x) support.""" |
| 68 | return len(self.keys()) |
| 69 | |
| 70 | - |
| 71 | + def getfirst(self, key, default=None): |
| 72 | + """ return the first value received """ |
| 73 | + for item in self.list: |
| 74 | + if item.name == key: |
| 75 | + if isinstance(item.file, FileType): |
| 76 | + return item |
| 77 | + else: |
| 78 | + return StringField(item.value) |
| 79 | + return default |
| 80 | + |
| 81 | + def getlist(self, key): |
| 82 | + """ return a list of received values """ |
| 83 | + if self.list is None: |
| 84 | + raise TypeError, "not indexable" |
| 85 | + found = [] |
| 86 | + for item in self.list: |
| 87 | + if item.name == key: |
| 88 | + if isinstance(item.file, FileType): |
| 89 | + found.append(item) |
| 90 | + else: |
| 91 | + found.append(StringField(item.value)) |
| 92 | + return found |
| 93 | + |
| 94 | def parse_header(line): |
| 95 | """Parse a Content-type like header. |
| 96 | |
| 97 | Return the main content-type and a dictionary of options. |
| 98 | |
| 99 | """ |
| 100 | - plist = map(string.strip, string.splitfields(line, ';')) |
| 101 | - key = string.lower(plist[0]) |
| 102 | + |
| 103 | + plist = map(lambda a: a.strip(), line.split(';')) |
| 104 | + key = plist[0].lower() |
| 105 | del plist[0] |
| 106 | pdict = {} |
| 107 | for p in plist: |
| 108 | - i = string.find(p, '=') |
| 109 | + i = p.find('=') |
| 110 | if i >= 0: |
| 111 | - name = string.lower(string.strip(p[:i])) |
| 112 | - value = string.strip(p[i+1:]) |
| 113 | + name = p[:i].strip().lower() |
| 114 | + value = p[i+1:].strip() |
| 115 | if len(value) >= 2 and value[0] == value[-1] == '"': |
| 116 | value = value[1:-1] |
| 117 | pdict[name] = value |
| 118 | return key, pdict |
| 119 | + |
| 120 | +def apply_fs_data(object, fs, **args): |
| 121 | + """ |
| 122 | + Apply FieldStorage data to an object - the object must be |
| 123 | + callable. Examine the args, and match then with fs data, |
| 124 | + then call the object, return the result. |
| 125 | + """ |
| 126 | + |
| 127 | + # add form data to args |
| 128 | + for field in fs.list: |
| 129 | + if field.filename: |
| 130 | + val = field |
| 131 | + else: |
| 132 | + val = field.value |
| 133 | + args.setdefault(field.name, []).append(val) |
| 134 | + |
| 135 | + # replace lists with single values |
| 136 | + for arg in args: |
| 137 | + if ((type(args[arg]) is ListType) and |
| 138 | + (len(args[arg]) == 1)): |
| 139 | + args[arg] = args[arg][0] |
| 140 | + |
| 141 | + # we need to weed out unexpected keyword arguments |
| 142 | + # and for that we need to get a list of them. There |
| 143 | + # are a few options for callable objects here: |
| 144 | + |
| 145 | + if type(object) is InstanceType: |
| 146 | + # instances are callable when they have __call__() |
| 147 | + object = object.__call__ |
| 148 | + |
| 149 | + expected = [] |
| 150 | + if hasattr(object, "func_code"): |
| 151 | + # function |
| 152 | + fc = object.func_code |
| 153 | + expected = fc.co_varnames[0:fc.co_argcount] |
| 154 | + elif hasattr(object, 'im_func'): |
| 155 | + # method |
| 156 | + fc = object.im_func.func_code |
| 157 | + expected = fc.co_varnames[1:fc.co_argcount] |
| 158 | + elif type(object) is ClassType: |
| 159 | + # class |
| 160 | + fc = object.__init__.im_func.func_code |
| 161 | + expected = fc.co_varnames[1:fc.co_argcount] |
| 162 | + |
| 163 | + # remove unexpected args unless co_flags & 0x08, |
| 164 | + # meaning function accepts **kw syntax |
| 165 | + if not (fc.co_flags & 0x08): |
| 166 | + for name in args.keys(): |
| 167 | + if name not in expected: |
| 168 | + del args[name] |
| 169 | + |
| 170 | + return object(**args) |
| 171 | + |
| 172 | +def redirect(req, location, permanent=0, text=None): |
| 173 | + """ |
| 174 | + A convenience function to provide redirection |
| 175 | + """ |
| 176 | + |
| 177 | + if req.sent_bodyct: |
| 178 | + raise IOError, "Cannot redirect after headers have already been sent." |
| 179 | + |
| 180 | + req.err_headers_out["Location"] = location |
| 181 | + if permanent: |
| 182 | + req.status = apache.HTTP_MOVED_PERMANENTLY |
| 183 | + else: |
| 184 | + req.status = apache.HTTP_MOVED_TEMPORARILY |
| 185 | + |
| 186 | + if text is None: |
| 187 | + req.write('<p>The document has moved' |
| 188 | + ' <a href="%s">here</a></p>\n' |
| 189 | + % location) |
| 190 | + else: |
| 191 | + req.write(text) |
| 192 | + |
| 193 | + raise apache.SERVER_RETURN, apache.OK |
| 194 | diff -r -u mod_python-2.7.11.orig/src/include/mod_python.h mod_python-2.7.11/src/include/mod_python.h |
| 195 | --- mod_python-2.7.11.orig/src/include/mod_python.h 2003-12-08 04:36:10.000000000 +0000 |
| 196 | +++ mod_python-2.7.11/src/include/mod_python.h 2005-09-16 01:01:47.000000000 +0100 |
| 197 | @@ -67,7 +67,7 @@ |
| 198 | #include "http_protocol.h" |
| 199 | #include "util_script.h" |
| 200 | #include "http_log.h" |
| 201 | - |
| 202 | +#include "multithread.h" |
| 203 | |
| 204 | /* Python headers */ |
| 205 | /* this gets rid of some comile warnings */ |
| 206 | diff -r -u mod_python-2.7.11.orig/src/mod_python.c mod_python-2.7.11/src/mod_python.c |
| 207 | --- mod_python-2.7.11.orig/src/mod_python.c 2001-05-28 21:00:41.000000000 +0100 |
| 208 | +++ mod_python-2.7.11/src/mod_python.c 2005-09-16 01:38:05.000000000 +0100 |
| 209 | @@ -264,6 +264,9 @@ |
| 210 | server.register_cleanup() */ |
| 211 | pool *child_init_pool = NULL; |
| 212 | |
| 213 | + /* Fudge for OS X */ |
| 214 | + static int initialized = 0; |
| 215 | + |
| 216 | /* mod_python version */ |
| 217 | ap_add_version_component(VERSION_COMPONENT); |
| 218 | |
| 219 | @@ -272,8 +275,9 @@ |
| 220 | ap_add_version_component(buff); |
| 221 | |
| 222 | /* initialize global Python interpreter if necessary */ |
| 223 | - if (! Py_IsInitialized()) |
| 224 | + if (!initialized || !Py_IsInitialized()) |
| 225 | { |
| 226 | + initialized = 1; |
| 227 | |
| 228 | /* initialze the interpreter */ |
| 229 | Py_Initialize(); |
| 230 | diff -r -u mod_python-2.7.11.orig/src/requestobject.c mod_python-2.7.11/src/requestobject.c |
| 231 | --- mod_python-2.7.11.orig/src/requestobject.c 2001-05-23 03:49:43.000000000 +0100 |
| 232 | +++ mod_python-2.7.11/src/requestobject.c 2005-09-16 02:18:33.000000000 +0100 |
| 233 | @@ -849,6 +849,7 @@ |
| 234 | {"filename", T_STRING, OFF(filename), }, |
| 235 | {"path_info", T_STRING, OFF(path_info), RO}, |
| 236 | {"args", T_STRING, OFF(args), RO}, |
| 237 | + {"user", T_STRING, }, |
| 238 | /* XXX - test an array header */ |
| 239 | /* XXX finfo */ |
| 240 | /* XXX parsed_uri */ |
| 241 | @@ -1015,6 +1016,15 @@ |
| 242 | else if (strcmp(name, "hstack") == 0) { |
| 243 | return PyString_FromString(self->hstack); |
| 244 | } |
| 245 | + else if (strcmp(name, "user") == 0) { |
| 246 | + if (!self->request_rec->connection |
| 247 | + || !self->request_rec->connection->user) { |
| 248 | + Py_INCREF(Py_None); |
| 249 | + return Py_None; |
| 250 | + } |
| 251 | + |
| 252 | + return PyString_FromString(self->request_rec->connection->user); |
| 253 | + } |
| 254 | else if (strcmp(name, "_content_type_set") == 0) { |
| 255 | return PyInt_FromLong(self->content_type_set); |
| 256 | } |
| 257 | @@ -1058,6 +1068,16 @@ |
| 258 | ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); |
| 259 | return 0; |
| 260 | } |
| 261 | + else if (strcmp(name, "user") == 0) { |
| 262 | + if (!self->request_rec->connection) { |
| 263 | + PyErr_SetString(PyExc_AttributeError, "no connection"); |
| 264 | + return -1; |
| 265 | + } |
| 266 | + |
| 267 | + self->request_rec->connection->user = |
| 268 | + ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); |
| 269 | + return 0; |
| 270 | + } |
| 271 | else if (strcmp(name, "hstack") == 0) { |
| 272 | self->hstack = ap_pstrdup(self->request_rec->pool, PyString_AsString(value)); |
| 273 | return 0; |
| 274 | }}} |
| 275 | |
| 276 | You also need the following patch for Trac's ModPythonHandler.py: |
| 277 | |
| 278 | {{{ |
| 279 | diff -r -u trac-0.8.4.orig/trac/ModPythonHandler.py trac-0.8.4/trac/ModPythonHandler.py |
| 280 | --- trac-0.8.4.orig/trac/ModPythonHandler.py 2005-06-17 19:22:32.000000000 +0100 |
| 281 | +++ trac-0.8.4/trac/ModPythonHandler.py 2005-09-16 16:54:13.000000000 +0100 |
| 282 | @@ -99,7 +99,7 @@ |
| 283 | self.req.write(data) |
| 284 | |
| 285 | def get_header(self, name): |
| 286 | - return self.req.headers_in.get(name) |
| 287 | + return self.req.headers_in[name] |
| 288 | |
| 289 | def send_response(self, code): |
| 290 | self.req.status = code |
| 291 | @@ -108,11 +108,10 @@ |
| 292 | if name.lower() == 'content-type': |
| 293 | self.req.content_type = value |
| 294 | else: |
| 295 | - self.req.headers_out.add(name, str(value)) |
| 296 | + self.req.headers_out[name] = str(value) |
| 297 | |
| 298 | def end_headers(self): |
| 299 | - pass |
| 300 | - |
| 301 | + self.req.send_http_header() |
| 302 | |
| 303 | class TracFieldStorage(util.FieldStorage): |
| 304 | """ |
| 305 | }}} |
| 306 | |