Below is the file 'web.py' from this revision. You can also download the file.
#!/usr/bin/env python """web.py: makes web apps (http://webpy.org)""" __version__ = "0.133" __license__ = "Affero General Public License, Version 1" __author__ = "Aaron Swartz <me@aaronsw.com>" from __future__ import generators # long term todo: # - new form system # - new templating system # - unit tests? # todo: # - get rid of upvars # - move documentation into docstrings # - provide an option to use .write() # - add ip:port support # - allow people to do $self.id from inside a reparam # - add sqlite support # - make storage a subclass of dictionary # - convert datetimes, floats in WebSafe # - locks around memoize # - fix memoize to use cacheify style techniques # - merge curval query with the insert # - figure out how to handle squid, etc. for web.ctx.ip import os, os.path, sys, time, types, traceback import cgi, re, urllib, urlparse, Cookie, pprint from threading import currentThread from tokenize import tokenprog iters = (list, tuple) if hasattr(__builtins__, 'set'): iters += (set,) try: from sets import Set; iters += (Set,) except ImportError: pass try: import datetime, itertools except ImportError: pass try: from Cheetah.Compiler import Compiler from Cheetah.Filters import Filter _hasTemplating = True except ImportError: _hasTemplating = False try: from DBUtils.PooledDB import PooledDB _hasPooling = True except ImportError: _hasPooling = False # hack for compatibility with Python 2.3: if not hasattr(traceback, 'format_exc'): from cStringIO import StringIO def format_exc(limit=None): s = StringIO() traceback.print_exc(limit, s) return s.getvalue() traceback.format_exc = format_exc ## general utils def _strips(direction, text, remove): if direction == 'l': if text.startswith(remove): return text[len(remove):] elif direction == 'r': if text.endswith(remove): return text[:-len(remove)] else: raise "WrongDirection", "Needs to be r or l." return text def rstrips(text, remove): """removes the string `remove` from the right of `text`""" return _strips('r', text, remove) def lstrips(text, remove): """removes the string `remove` from the right of `text`""" return _strips('l', text, remove) def strips(a, b): """removes the string `remove` from the both sides of `text`""" return rstrips(lstrips(a,b),b) def autoassign(self, locals): """ Automatically assigns local variables to `self`. Generally used in `__init__` methods, as in: def __init__(self, foo, bar, baz=1): autoassign(self, locals()) """ #locals = sys._getframe(1).f_locals #self = locals['self'] for (k, v) in locals.iteritems(): if k == 'self': continue setattr(self, k, v) class Storage(dict): """ A Storage object is like a dictionary except `obj.foo` can be used instead of `obj['foo']`. Create one by doing `storage({'a':1})`. """ def __getattr__(self, k): if self.has_key(k): return self[k] raise AttributeError, repr(k) def __setattr__(self, k, v): self[k] = v def __repr__(self): return '<Storage '+dict.__repr__(self)+'>' storage = Storage def storify(f, *requireds, **defaults): """ Creates a `storage` object from dictionary d, raising `IndexError` if d doesn't have all of the keys in `requireds` and using the default values for keys found in `defaults`. For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of `storage({'a':1, 'b':2, 'c':3})`. """ stor = Storage() for k in requireds + tuple(f.keys()): v = f[k] if isinstance(v, list): v = v[-1] if hasattr(v, 'value'): v = v.value setattr(stor, k, v) for (k,v) in defaults.iteritems(): result = v if hasattr(stor, k): result = stor[k] if v == () and not isinstance(result, tuple): result = (result,) setattr(stor, k, result) return stor class memoize: """ "Memoizes" a function, caching its return values for each input. """ def __init__(self, func): self.func = func; self.cache = {} def __call__(self, *a, **k): key = (a, tuple(k.items())) if key not in self.cache: self.cache[key] = self.func(*a, **k) return self.cache[key] re_compile = memoize(re.compile) #@@ threadsafe? re_compile.__doc__ = """ A memoized version of re.compile. """ class _re_subm_proxy: def __init__(self): self.match = None def __call__(self, match): self.match = match; return '' def re_subm(pat, repl, string): """Like re.sub, but returns the replacement _and_ the match object.""" r = re_compile(pat) proxy = _re_subm_proxy() r.sub(proxy.__call__, string) return r.sub(repl, string), proxy.match def group(seq, size): """ Returns an iterator over a series of lists of length size from iterable. For example, `list(group([1,2,3,4], 2))` returns `[[1,2],[3,4]]`. """ if not hasattr(seq, 'next'): seq = iter(seq) while True: yield [seq.next() for i in xrange(size)] class iterbetter: """ Returns an object that can be used as an iterator but can also be used via __getitem__ (although it cannot go backwards -- that is, you cannot request `iterbetter[0]` after requesting `iterbetter[1]`). """ def __init__(self, iterator): self.i, self.c = iterator, 0 def __iter__(self): while 1: yield self.i.next(); self.c += 1 def __getitem__(self, i): #todo: slices if i > self.c: raise KeyError, "already passed "+str(i) try: while i < self.c: self.i.next(); self.c += 1 # now self.c == i self.c += 1; return self.i.next() except StopIteration: raise KeyError, repr(i) def dictreverse(d): """Takes a dictionary like `{1:2, 3:4}` and returns `{2:1, 4:3}`.""" return dict([(v,k) for k,v in d.iteritems()]) def dictfind(dictionary, element): """ Returns a key whose value in `dictionary` is `element` or, if none exists, None. """ for (k,v) in dictionary.iteritems(): if element is v: return k def dictincr(dictionary, element): """ Increments `element` in `dictionary`, setting it to one if it doesn't exist. """ dictionary.setdefault(element, 0) dictionary[element] += 1 return dictionary[element] def dictadd(a, b): """Returns a dictionary consisting of the keys in `a` and `b`.""" result = {} result.update(a) result.update(b) return result sumdicts = dictadd # deprecated def listget(l, n, default=None): """Returns `l[n]` if it exists, `default` otherwise.""" if len(l)-1 < n: return default return l[n] def upvars(n=2): """Guido van Rossum doesn't want you to use this function.""" return dictadd( sys._getframe(n).f_globals, sys._getframe(n).f_locals) class capturestdout: """ Captures everything func prints to stdout and returns it instead. **WARNING:** Not threadsafe! """ def __init__(self, func): self.func = func def __call__(self, *args, **kw): from cStringIO import StringIO # Not threadsafe! out = StringIO() oldstdout = sys.stdout sys.stdout = out try: self.func(*args, **kw) finally: sys.stdout = oldstdout return out.getvalue() class profile: """ Profiles `func` and returns a tuple containing its output and a string with human-readable profiling information. """ def __init__(self, func): self.func = func def __call__(self, *args, **kw): import hotshot, hotshot.stats, tempfile, time temp = tempfile.NamedTemporaryFile() prof = hotshot.Profile(temp.name) stime = time.time() result = prof.runcall(self.func, *args) stime = time.time() - stime prof.close() stats = hotshot.stats.load(temp.name) stats.strip_dirs() stats.sort_stats('time', 'calls') x = '\n\ntook '+ str(stime) + ' seconds\n' x += capturestdout(stats.print_stats)(40) x += capturestdout(stats.print_callers)() return result, x def tryall(context, prefix=None): """ Tries a series of functions and prints their results. `context` is a dictionary mapping names to values; the value will only be tried if it's callable. For example, you might have a file `test/stuff.py` with a series of functions testing various things in it. At the bottom, have a line: if __name__ == "__main__": tryall(globals()) Then you can run `python test/stuff.py` and get the results of all the tests. """ context = context.copy() # vars() would update results = {} for (k, v) in context.iteritems(): if not hasattr(v, '__call__'): continue if prefix and not k.startswith(prefix): continue print k+':', try: r = v() dictincr(results, r) print r except: print 'ERROR' dictincr(results, 'ERROR') print ' '+'\n '.join(traceback.format_exc().split('\n')) print '-'*40 print 'results:' for (k, v) in results.iteritems(): print ' '*2, str(k)+':', v class threadeddict: """ Takes a dictionary that maps threads to objects. When a thread tries to get or set an attribute or item of the threadeddict, it passes it on to the object for that thread in dictionary. """ def __init__(self, d): self.__dict__['_threadeddict__d'] = d def __getattr__(self, a): return getattr(self.__d[currentThread()], a) def __getitem__(self, i): return self.__d[currentThread()][i] def __setattr__(self, a, v): return setattr(self.__d[currentThread()], a, v) def __setitem__(self, i, v): self.__d[currentThread()][i] = v def __hash__(self): return hash(self.__d[currentThread()]) ## url utils def prefixurl(base=''): """ Sorry, this function is really difficult to explain. Maybe some other time. """ url = context.path.lstrip('/') for i in xrange(url.count('/')): base += '../' if not base: base = './' return base urlquote = urllib.quote ## formatting try: from markdown import markdown # http://webpy.org/markdown.py except ImportError: pass r_url = re_compile('(?<!\()(http://(\S+))') def safemarkdown(text): """ Converts text to HTML following the rules of Markdown, but blocking any outside HTML input, so that only the things supported by Markdown can be used. Also converts raw URLs to links. (requires [markdown.py](http://webpy.org/markdown.py)) """ if text: text = text.replace('<', '<') # TODO: automatically get page title? text = r_url.sub(r'<\1>', text) text = markdown(text) return text ## db api def _interpolate(format): """ Takes a format string and returns a list of 2-tuples of the form (boolean, string) where boolean says whether string should be evaled or not. from http://lfw.org/python/Itpl.py (public domain, Ka-Ping Yee) """ def matchorfail(text, pos): match = tokenprog.match(text, pos) if match is None: raise ItplError(text, pos) return match, match.end() namechars = "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; chunks = [] pos = 0 while 1: dollar = format.find("$", pos) if dollar < 0: break nextchar = format[dollar+1] if nextchar == "{": chunks.append((0, format[pos:dollar])) pos, level = dollar+2, 1 while level: match, pos = matchorfail(format, pos) tstart, tend = match.regs[3] token = format[tstart:tend] if token == "{": level = level+1 elif token == "}": level = level-1 chunks.append((1, format[dollar+2:pos-1])) elif nextchar in namechars: chunks.append((0, format[pos:dollar])) match, pos = matchorfail(format, dollar+1) while pos < len(format): if format[pos] == "." and \ pos+1 < len(format) and format[pos+1] in namechars: match, pos = matchorfail(format, pos+1) elif format[pos] in "([": pos, level = pos+1, 1 while level: match, pos = matchorfail(format, pos) tstart, tend = match.regs[3] token = format[tstart:tend] if token[0] in "([": level = level+1 elif token[0] in ")]": level = level-1 else: break chunks.append((1, format[dollar+1:pos])) else: chunks.append((0, format[pos:dollar+1])) pos = dollar + 1 + (nextchar == "$") if pos < len(format): chunks.append((0, format[pos:])) return chunks def sqlors(left, lst): """ `left is a SQL clause like `tablename.arg = ` and `lst` is a list of values. Returns a reparam-style pair featuring the SQL that ORs together the clause for each item in the lst. For example: web.sqlors('foo =', [1,2,3]) would result in: foo = 1 OR foo = 2 OR foo = 3 contributed by Steven Huffman <http://spez.name> """ if isinstance(lst, iters) and len(lst) == 1: lst = lst[0] if isinstance(lst, iters): return '(' + left + (' OR ' + left).join([aparam() for x in lst]) + ")", lst elif not list: return "", [] else: return left + aparam(), [lst,] class UnknownParamstyle(Exception): pass def aparam(): """Use in a SQL string to make a spot for a db value.""" p = ctx.db_module.paramstyle if p == 'qmark': return '?' elif p == 'numeric': return ':1' elif p in ['format', 'pyformat']: return '%s' raise UnknownParamstyle, p def reparam(s, d): """ Takes a string and a dictionary and interpolates the string using values from the dictionary. Returns a 2-tuple containing the a string with `aparam()`s in it and a list of the matching values. You can pass this sort of thing as a clause in any db function. Otherwise, you can pass a dictionary to the keyword argument `vars` and the function will call reparam for you. """ vals = [] result = [] for live, chunk in _interpolate(s): if live: result.append(aparam()) vals.append(eval(chunk, d)) else: result.append(chunk) return ''.join(result), vals class UnknownDB(Exception): pass def connect(dbn, **kw): """ Connects to the specified database. db currently must be "postgres" or "mysql". If DBUtils is installed, connection pooling will be used. """ if dbn == "postgres": try: import psycopg2 as db except ImportError: try: import psycopg as db except ImportError: import pgdb as db kw['password'] = kw['pw'] del kw['pw'] kw['database'] = kw['db'] del kw['db'] elif dbn == "mysql": import MySQLdb as db kw['passwd'] = kw['pw'] del kw['pw'] db.paramstyle = 'pyformat' # it's both, like psycopg else: raise UnknownDB, dbn ctx.db_name = dbn ctx.db_module = db ctx.db_transaction = False if _hasPooling: if 'db' not in globals(): globals()['db'] = PooledDB(dbapi=db, **kw) ctx.db = globals()['db'].connection() else: ctx.db = db.connect(**kw) ctx.dbq_count = 0 if globals().get('db_printing'): def db_execute(cur, q, d=None): ctx.dbq_count += 1 try: outq = q % tuple(d) except: outq = q print>>debug, str(ctx.dbq_count)+':', outq a = time.time() out = cur.execute(q, d) b = time.time() print>>debug, '(%s)' % round(b-a, 2) return out ctx.db_execute = db_execute else: ctx.db_execute = lambda cur, q, d=None: cur.execute(q, d) return ctx.db def transact(): """Start a transaction.""" # commit everything up to now, so we don't rollback it later ctx.db.commit() ctx.db_transaction = True def commit(): """Commits a transaction.""" ctx.db.commit() ctx.db_transaction = False def rollback(): """Rolls back a transaction.""" ctx.db.rollback() ctx.db_transaction = False def query(q, vars=None, processed=False): """ Execute SQL query `q` using dictionary `vars` to interpolate it. If `processed=True`, `vars` is a `reparam`-style list to use instead of interpolating. """ if vars is None: vars = {} d = ctx.db.cursor() if not processed: q, vars = reparam(q, vars) ctx.db_execute(d, q, vars) if d.description: names = [x[0] for x in d.description] def iterwrapper(): x = d.fetchone() while x: yield Storage(dict(zip(names, x))) x = d.fetchone() out = iterbetter(iterwrapper()) out.__len__ = lambda: int(d.rowcount) out.list = lambda: [Storage(dict(zip(names, x))) for x in d.fetchall()] else: out = d.rowcount if not ctx.db_transaction: ctx.db.commit() return out def sqllist(l): """ If a list, converts it to a comma-separated string. Otherwise, returns the string. """ if isinstance(l, str): return l else: return ', '.join(l) def select(tables, vars=None, what='*', where=None, order=None, group=None, limit=None, offset=None): """ Selects `what` from `tables` with clauses `where`, `order`, `group`, `limit`, and `offset. Uses vars to interpolate. Otherwise, each clause can take a reparam-style list. """ if vars is None: vars = {} values = [] qout = "SELECT "+what+" FROM "+sqllist(tables) for (sql, val) in ( ('WHERE', where), ('GROUP BY', group), ('ORDER BY', order), ('LIMIT', limit), ('OFFSET', offset)): if isinstance(val, (int, long)): if sql == 'WHERE': nquery, nvalue = 'id = '+aparam(), [val] else: nquery, nvalue = str(val), () elif isinstance(val, (list, tuple)) and len(val) == 2: nquery, nvalue = val elif val: nquery, nvalue = reparam(val, vars) else: continue qout += " "+sql+" " + nquery values.extend(nvalue) return query(qout, values, processed=True) def insert(tablename, seqname=None, **values): """ Inserts `values` into `tablename`. Returns current sequence ID. Set `seqname` to the ID if it's not the default, or to `False` if there isn't one. """ d = ctx.db.cursor() if values: q, v = "INSERT INTO %s (%s) VALUES (%s)" % ( tablename, ", ".join(values.keys()), ', '.join([aparam() for x in values]) ), values.values() else: q, v = "INSERT INTO %s DEFAULT VALUES" % tablename, None if seqname is False: pass elif ctx.db_name == "postgres": if seqname is None: seqname = tablename + "_id_seq" q += "; SELECT currval('%s')" % seqname elif ctx.db_name == "mysql": q += "; SELECT last_insert_id()" elif ctx.db_name == "sqlite": # not really the same... q += "; SELECT last_insert_rowid()" ctx.db_execute(d, q, v) try: out = d.fetchone()[0] except: out = None if not ctx.db_transaction: ctx.db.commit() return out def update(tables, where, vars=None, **values): """ Update `tables` with clause `where` (interpolated using `vars`) and setting `values`. """ if vars is None: vars = {} if isinstance(where, (int, long)): vars = [where] where = "id = "+aparam() elif isinstance(where, (list, tuple)) and len(where) == 2: where, vars = where else: where, vars = reparam(where, vars) d = ctx.db.cursor() ctx.db_execute(d, "UPDATE %s SET %s WHERE %s" % ( sqllist(tables), ', '.join([k+'='+aparam() for k in values.keys()]), where), values.values()+vars) if not ctx.db_transaction: ctx.db.commit() return d.rowcount def delete(table, where, using=None, vars=None): """ Deletes from `table` with clauses `where` and `using`. """ if vars is None: vars = {} d = ctx.db.cursor() if isinstance(where, (int, long)): vars = [where] where = "id = "+aparam() elif isinstance(where, (list, tuple)) and len(where) == 2: where, vars = val else: where, vars = reparam(where, vars) q = 'DELETE FROM %s WHERE %s' % (table, where) if using: q += ' USING '+sqllist(using) ctx.db_execute(d, q, vars) if not ctx.db_transaction: ctx.db.commit() return d.rowcount ## request handlers def handle(mapping, fvars=None): """ Call the appropriate function based on the url to function mapping in `mapping`. If no module for the function is specified, look up the function in `fvars`. If `fvars` is empty, using the caller's context. `mapping` should be a tuple of paired regular expressions with function name substitutions. `handle` will import modules as necessary. """ for url, ofno in group(mapping, 2): if isinstance(ofno, tuple): ofn, fna = ofno[0], list(ofno[1:]) else: ofn, fna = ofno, [] fn, result = re_subm('^'+url+'$', ofn, context.path) if result: # it's a match if fn.split(' ', 1)[0] == "redirect": url = fn.split(' ', 1)[1] if context.method == "GET": x = context.environ.get('QUERY_STRING', '') if x: url += '?'+x return redirect(url) elif '.' in fn: x = fn.split('.') mod, cls = '.'.join(x[:-1]), x[-1] mod = __import__(mod, globals(), locals(), [""]) cls = getattr(mod, cls) else: cls = fn mod = fvars or upvars() if isinstance(mod, types.ModuleType): mod = vars(mod) try: cls = mod[cls] except KeyError: return notfound() meth = context.method if meth == "HEAD": if not hasattr(cls, meth): meth = "GET" if not hasattr(cls, meth): return nomethod(cls) tocall = getattr(cls(), meth) args = list(result.groups()) for d in re.findall(r'\\(\d+)', ofn): args.pop(int(d)-1) return tocall(*([urllib.unquote(x) for x in args]+fna)) return notfound() def autodelegate(prefix=''): """ Returns a method that takes one argument and calls the method named prefix+arg, calling `notfound()` if there isn't one. Example: urls = ('/prefs/(.*)', 'prefs') class prefs: GET = autodelegate('GET_') def GET_password(self): pass def GET_privacy(self): pass `GET_password` would get called for `/prefs/password` while `GET_privacy` for `GET_privacy` gets called for `/prefs/privacy`. """ def internal(self, arg): func = prefix+arg if hasattr(self, func): return getattr(self, func)() else: return notfound() return internal ## http defaults def expires(delta): """ Outputs an `Expires` header for `delta` from now. `delta` is a `timedelta` object or a number of seconds. """ try: datetime except NameError: raise Exception, "requires Python 2.3 or later" if isinstance(delta, (int, long)): delta = datetime.timedelta(seconds=delta) o = datetime.datetime.utcnow() + delta header('Expires', o.strftime("%a, %d %b %Y %T GMT")) def lastmodified(d): """Outputs a `Last-Modified` header for `datetime`.""" header('Last-Modified', d.strftime("%a, %d %b %Y %T GMT")) """ By default, these all return simple error messages that send very short messages (like "bad request") to the user. They can and should be overridden to return nicer ones. """ def redirect(url, status='301 Moved Permanently'): """ Returns a `status` redirect to the new URL. `url` is joined with the base URL so that things like `redirect("about") will work properly. """ newloc = urlparse.urljoin(context.home + context.path, url) context.status = status header('Content-Type', 'text/html') header('Location', newloc) # seems to add a three-second delay for some reason: # output('<a href="'+ newloc + '">moved permanently</a>') def found(url): """A `302 Found` redirect.""" return redirect(url, '302 Found') def seeother(url): """A `303 See Other` redirect.""" return redirect(url, '303 See Other') def tempredirect(url): """A `307 Temporary Redirect` redirect.""" return redirect(url, '307 Temporary Redirect') def badrequest(): """Return a `400 Bad Request` error.""" context.status = '400 Bad Request' header('Content-Type', 'text/html') return output('bad request') def notfound(): """Returns a `404 Not Found` error.""" context.status = '404 Not Found' header('Content-Type', 'text/html') return output('not found') def nomethod(cls): """Returns a `405 Method Not Allowed` error for `cls`.""" context.status = '405 Method Not Allowed' header('Content-Type', 'text/html') header("Allow", ', '.join([x for x in ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'] if hasattr(cls, x)])) return output('method not allowed') def gone(): """Returns a `410 Gone` error.""" context.status = '410 Gone' header('Content-Type', 'text/html') return output("gone") def internalerror(): """Returns a `500 Internal Server` error.""" context.status = "500 Internal Server Error" context.headers = [('Content-Type', 'text/html')] context.output = "internal server error" # adapted from Django <djangoproject.com> # Copyright (c) 2005, the Lawrence Journal-World # Used under the modified BSD license: # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 DJANGO_500_PAGE = """#import inspect <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="robots" content="NONE,NOARCHIVE" /> <title>$exception_type at $context.path</title> <style type="text/css"> html * { padding:0; margin:0; } body * { padding:10px 20px; } body * * { padding:0; } body { font:small sans-serif; } body>div { border-bottom:1px solid #ddd; } h1 { font-weight:normal; } h2 { margin-bottom:.8em; } h2 span { font-size:80%; color:#666; font-weight:normal; } h3 { margin:1em 0 .5em 0; } h4 { margin:0 0 .5em 0; font-weight: normal; } table { border:1px solid #ccc; border-collapse: collapse; background:white; } tbody td, tbody th { vertical-align:top; padding:2px 3px; } thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; } tbody th { text-align:right; color:#666; padding-right:.5em; } table.vars { margin:5px 0 2px 40px; } table.vars td, table.req td { font-family:monospace; } table td.code { width:100%;} table td.code div { overflow:hidden; } table.source th { color:#666; } table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } ul.traceback { list-style-type:none; } ul.traceback li.frame { margin-bottom:1em; } div.context { margin: 10px 0; } div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; } div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; } div.context ol.context-line li { color:black; background-color:#ccc; } div.context ol.context-line li span { float: right; } div.commands { margin-left: 40px; } div.commands a { color:black; text-decoration:none; } #summary { background: #ffc; } #summary h2 { font-weight: normal; color: #666; } #explanation { background:#eee; } #template, #template-not-exist { background:#f6f6f6; } #template-not-exist ul { margin: 0 0 0 20px; } #traceback { background:#eee; } #requestinfo { background:#f6f6f6; padding-left:120px; } #summary table { border:none; background:transparent; } #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; } #requestinfo h3 { margin-bottom:-1em; } .error { background: #ffc; } .specific { color:#cc3300; font-weight:bold; } </style> <script type="text/javascript"> //<!-- function getElementsByClassName(oElm, strTagName, strClassName){ // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com var arrElements = (strTagName == "*" && document.all)? document.all : oElm.getElementsByTagName(strTagName); var arrReturnElements = new Array(); strClassName = strClassName.replace(/\-/g, "\\-"); var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)"); var oElement; for(var i=0; i<arrElements.length; i++){ oElement = arrElements[i]; if(oRegExp.test(oElement.className)){ arrReturnElements.push(oElement); } } return (arrReturnElements) } function hideAll(elems) { for (var e = 0; e < elems.length; e++) { elems[e].style.display = 'none'; } } window.onload = function() { hideAll(getElementsByClassName(document, 'table', 'vars')); hideAll(getElementsByClassName(document, 'ol', 'pre-context')); hideAll(getElementsByClassName(document, 'ol', 'post-context')); } function toggle() { for (var i = 0; i < arguments.length; i++) { var e = document.getElementById(arguments[i]); if (e) { e.style.display = e.style.display == 'none' ? 'block' : 'none'; } } return false; } function varToggle(link, id) { toggle('v' + id); var s = link.getElementsByTagName('span')[0]; var uarr = String.fromCharCode(0x25b6); var darr = String.fromCharCode(0x25bc); s.innerHTML = s.innerHTML == uarr ? darr : uarr; return false; } //--> </script> </head> <body> <div id="summary"> <h1>$exception_type at $context.path</h1> <h2>$exception_value</h2> <table><tr> <th>Python</th> <td>$lastframe.filename in $lastframe.function, line $lastframe.lineno</td> </tr><tr> <th>Web</th> <td>$context.method $context.home$context.path</td> </tr></table> </div> <div id="traceback"> <h2>Traceback <span>(innermost first)</span></h2> <ul class="traceback"> #for frame in $frames <li class="frame"> <code>$frame.filename</code> in <code>$frame.function</code> #if $frame.context_line <div class="context" id="c$frame.id"> #if $frame.pre_context <ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">#for line in $frame.pre_context#<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>#end for#</ol> #end if <ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol> #if $frame.post_context <ol start='$(frame.lineno+1)' class="post-context" id="post$frame.id">#for line in $frame.post_context#<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>#end for#</ol> #end if </div> #end if #if $frame.vars <div class="commands"> <a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>## $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame)) </div> <table class="vars" id="v$frame.id"> <thead> <tr> <th>Variable</th> <th>Value</th> </tr> </thead> <tbody> #set frameitems = $frame.vars #silent frameitems.sort(lambda x,y: cmp(x[0], y[0])) #for (key, val) in frameitems <tr> <td>$key</td> <td class="code"><div>$prettify(val)</div></td> </tr> #end for </tbody> </table> #end if </li> #end for </ul> </div> <div id="requestinfo"> #if $context_.output or $context_.headers <h2>Response so far</h2> <h3>HEADERS</h3> #if $context.headers <p class="req"><code> #for (k, v) in $context_.headers $k: $v<br /> #end for </code></p> #else <p>No headers.</p> #end if <h3>BODY</h3> <p class="req" style="padding-bottom: 2em"><code> $context_.output </code></p> #end if <h2>Request information</h2> <h3>INPUT</h3> #if $input_ <table class="req"> <thead> <tr> <th>Variable</th> <th>Value</th> </tr> </thead> <tbody> #set myitems = $input_.items() #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) #for (key, val) in myitems <tr> <td>$key</td> <td class="code"><div>$val</div></td> </tr> #end for </tbody> </table> #else <p>No input data.</p> #end if <h3 id="cookie-info">COOKIES</h3> #if $cookies_ <table class="req"> <thead> <tr> <th>Variable</th> <th>Value</th> </tr> </thead> <tbody> #for (key, val) in $cookies_.items() <tr> <td>$key</td> <td class="code"><div>$val</div></td> </tr> #end for </tbody> </table> #else <p>No cookie data</p> #end if <h3 id="meta-info">META</h3> <table class="req"> <thead> <tr> <th>Variable</th> <th>Value</th> </tr> </thead> <tbody> #set myitems = $context_.items() #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) #for (key, val) in $myitems #if not $key.startswith('_') and $key not in ['env', 'output', 'headers', 'environ', 'status', 'db_execute'] <tr> <td>$key</td> <td class="code"><div>$prettify($val)</div></td> </tr> #end if #end for </tbody> </table> <h3 id="meta-info">ENVIRONMENT</h3> <table class="req"> <thead> <tr> <th>Variable</th> <th>Value</th> </tr> </thead> <tbody> #set myitems = $context_.environ.items() #silent myitems.sort(lambda x,y: cmp(x[0], y[0])) #for (key, val) in $myitems <tr> <td>$key</td> <td class="code"><div>$prettify($val)</div></td> </tr> #end for </tbody> </table> </div> <div id="explanation"> <p> You're seeing this error because you have <code>web.internalerror</code> set to <code>web.debugerror</code>. Change that if you want a different one. </p> </div> </body> </html>""" def djangoerror(): def _get_lines_from_file(filename, lineno, context_lines): """ Returns context_lines before and after lineno from file. Returns (pre_context_lineno, pre_context, context_line, post_context). """ try: source = open(filename).readlines() lower_bound = max(0, lineno - context_lines) upper_bound = lineno + context_lines pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] context_line = source[lineno].strip('\n') post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] return lower_bound, pre_context, context_line, post_context except (OSError, IOError): return None, [], None, [] exception_type, exception_value, tb = sys.exc_info() frames = [] while tb is not None: filename = tb.tb_frame.f_code.co_filename function = tb.tb_frame.f_code.co_name lineno = tb.tb_lineno - 1 pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) frames.append({ 'tb': tb, 'filename': filename, 'function': function, 'lineno': lineno, 'vars': tb.tb_frame.f_locals.items(), 'id': id(tb), 'pre_context': pre_context, 'context_line': context_line, 'post_context': post_context, 'pre_context_lineno': pre_context_lineno, }) tb = tb.tb_next lastframe = frames[-1] frames.reverse() urljoin = urlparse.urljoin input_ = input() cookies_ = cookies() context_ = context def prettify(x): try: out = pprint.pformat(x) except Exception, e: out = '[could not display: <'+e.__class__.__name__+': '+str(e)+'>]' return out return render(DJANGO_500_PAGE, asTemplate=True, isString=True) def debugerror(): """ A replacement for `internalerror` that presents a nice page with lots of debug information for the programmer. (Based on the beautiful 500 page from [Django](http://djangoproject.com/), designed by [Wilson Miner](http://wilsonminer.com/).) Requires [Cheetah](http://cheetahtemplate.org/). """ # need to do django first, so it can get the old stuff if _hasTemplating: out = str(djangoerror()) else: # Cheetah isn