annotate bottle.py @ 43:2e2981af7d2d

Use bottledash-specific graph template This version differs from roundup_cc's graph.html in that is not a complete HTML file but a snippet.
author Gernot Schulz <gernot@intevation.de>
date Sun, 31 Jan 2016 15:21:18 +0100
parents e1264e58154c
children
rev   line source
0
e1264e58154c initial commpit
sean
parents:
diff changeset
1 #!/usr/bin/env python
e1264e58154c initial commpit
sean
parents:
diff changeset
2 # -*- coding: utf-8 -*-
e1264e58154c initial commpit
sean
parents:
diff changeset
3 """
e1264e58154c initial commpit
sean
parents:
diff changeset
4 Bottle is a fast and simple micro-framework for small web applications. It
e1264e58154c initial commpit
sean
parents:
diff changeset
5 offers request dispatching (Routes) with url parameter support, templates,
e1264e58154c initial commpit
sean
parents:
diff changeset
6 a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
e1264e58154c initial commpit
sean
parents:
diff changeset
7 template engines - all in a single file and with no dependencies other than the
e1264e58154c initial commpit
sean
parents:
diff changeset
8 Python Standard Library.
e1264e58154c initial commpit
sean
parents:
diff changeset
9
e1264e58154c initial commpit
sean
parents:
diff changeset
10 Homepage and documentation: http://bottlepy.org/
e1264e58154c initial commpit
sean
parents:
diff changeset
11
e1264e58154c initial commpit
sean
parents:
diff changeset
12 Copyright (c) 2014, Marcel Hellkamp.
e1264e58154c initial commpit
sean
parents:
diff changeset
13 License: MIT (see LICENSE for details)
e1264e58154c initial commpit
sean
parents:
diff changeset
14 """
e1264e58154c initial commpit
sean
parents:
diff changeset
15
e1264e58154c initial commpit
sean
parents:
diff changeset
16 from __future__ import with_statement
e1264e58154c initial commpit
sean
parents:
diff changeset
17
e1264e58154c initial commpit
sean
parents:
diff changeset
18 __author__ = 'Marcel Hellkamp'
e1264e58154c initial commpit
sean
parents:
diff changeset
19 __version__ = '0.13-dev'
e1264e58154c initial commpit
sean
parents:
diff changeset
20 __license__ = 'MIT'
e1264e58154c initial commpit
sean
parents:
diff changeset
21
e1264e58154c initial commpit
sean
parents:
diff changeset
22 # The gevent and eventlet server adapters need to patch some modules before
e1264e58154c initial commpit
sean
parents:
diff changeset
23 # they are imported. This is why we parse the commandline parameters here but
e1264e58154c initial commpit
sean
parents:
diff changeset
24 # handle them later
e1264e58154c initial commpit
sean
parents:
diff changeset
25 if __name__ == '__main__':
e1264e58154c initial commpit
sean
parents:
diff changeset
26 from optparse import OptionParser
e1264e58154c initial commpit
sean
parents:
diff changeset
27 _cmd_parser = OptionParser(
e1264e58154c initial commpit
sean
parents:
diff changeset
28 usage="usage: %prog [options] package.module:app")
e1264e58154c initial commpit
sean
parents:
diff changeset
29 _opt = _cmd_parser.add_option
e1264e58154c initial commpit
sean
parents:
diff changeset
30 _opt("--version", action="store_true", help="show version number.")
e1264e58154c initial commpit
sean
parents:
diff changeset
31 _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
e1264e58154c initial commpit
sean
parents:
diff changeset
32 _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
e1264e58154c initial commpit
sean
parents:
diff changeset
33 _opt("-p", "--plugin",
e1264e58154c initial commpit
sean
parents:
diff changeset
34 action="append",
e1264e58154c initial commpit
sean
parents:
diff changeset
35 help="install additional plugin/s.")
e1264e58154c initial commpit
sean
parents:
diff changeset
36 _opt("--debug", action="store_true", help="start server in debug mode.")
e1264e58154c initial commpit
sean
parents:
diff changeset
37 _opt("--reload", action="store_true", help="auto-reload on file changes.")
e1264e58154c initial commpit
sean
parents:
diff changeset
38 _cmd_options, _cmd_args = _cmd_parser.parse_args()
e1264e58154c initial commpit
sean
parents:
diff changeset
39 if _cmd_options.server:
e1264e58154c initial commpit
sean
parents:
diff changeset
40 if _cmd_options.server.startswith('gevent'):
e1264e58154c initial commpit
sean
parents:
diff changeset
41 import gevent.monkey
e1264e58154c initial commpit
sean
parents:
diff changeset
42 gevent.monkey.patch_all()
e1264e58154c initial commpit
sean
parents:
diff changeset
43 elif _cmd_options.server.startswith('eventlet'):
e1264e58154c initial commpit
sean
parents:
diff changeset
44 import eventlet
e1264e58154c initial commpit
sean
parents:
diff changeset
45 eventlet.monkey_patch()
e1264e58154c initial commpit
sean
parents:
diff changeset
46
e1264e58154c initial commpit
sean
parents:
diff changeset
47 import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\
e1264e58154c initial commpit
sean
parents:
diff changeset
48 os, re, sys, tempfile, threading, time, warnings
e1264e58154c initial commpit
sean
parents:
diff changeset
49
e1264e58154c initial commpit
sean
parents:
diff changeset
50 from types import FunctionType
e1264e58154c initial commpit
sean
parents:
diff changeset
51 from datetime import date as datedate, datetime, timedelta
e1264e58154c initial commpit
sean
parents:
diff changeset
52 from tempfile import TemporaryFile
e1264e58154c initial commpit
sean
parents:
diff changeset
53 from traceback import format_exc, print_exc
e1264e58154c initial commpit
sean
parents:
diff changeset
54 from inspect import getargspec
e1264e58154c initial commpit
sean
parents:
diff changeset
55 from unicodedata import normalize
e1264e58154c initial commpit
sean
parents:
diff changeset
56
e1264e58154c initial commpit
sean
parents:
diff changeset
57 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
58 from simplejson import dumps as json_dumps, loads as json_lds
e1264e58154c initial commpit
sean
parents:
diff changeset
59 except ImportError: # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
60 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
61 from json import dumps as json_dumps, loads as json_lds
e1264e58154c initial commpit
sean
parents:
diff changeset
62 except ImportError:
e1264e58154c initial commpit
sean
parents:
diff changeset
63 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
64 from django.utils.simplejson import dumps as json_dumps, loads as json_lds
e1264e58154c initial commpit
sean
parents:
diff changeset
65 except ImportError:
e1264e58154c initial commpit
sean
parents:
diff changeset
66
e1264e58154c initial commpit
sean
parents:
diff changeset
67 def json_dumps(data):
e1264e58154c initial commpit
sean
parents:
diff changeset
68 raise ImportError(
e1264e58154c initial commpit
sean
parents:
diff changeset
69 "JSON support requires Python 2.6 or simplejson.")
e1264e58154c initial commpit
sean
parents:
diff changeset
70
e1264e58154c initial commpit
sean
parents:
diff changeset
71 json_lds = json_dumps
e1264e58154c initial commpit
sean
parents:
diff changeset
72
e1264e58154c initial commpit
sean
parents:
diff changeset
73 # We now try to fix 2.5/2.6/3.1/3.2 incompatibilities.
e1264e58154c initial commpit
sean
parents:
diff changeset
74 # It ain't pretty but it works... Sorry for the mess.
e1264e58154c initial commpit
sean
parents:
diff changeset
75
e1264e58154c initial commpit
sean
parents:
diff changeset
76 py = sys.version_info
e1264e58154c initial commpit
sean
parents:
diff changeset
77 py3k = py >= (3, 0, 0)
e1264e58154c initial commpit
sean
parents:
diff changeset
78 py25 = py < (2, 6, 0)
e1264e58154c initial commpit
sean
parents:
diff changeset
79 py31 = (3, 1, 0) <= py < (3, 2, 0)
e1264e58154c initial commpit
sean
parents:
diff changeset
80
e1264e58154c initial commpit
sean
parents:
diff changeset
81 # Workaround for the missing "as" keyword in py3k.
e1264e58154c initial commpit
sean
parents:
diff changeset
82 def _e():
e1264e58154c initial commpit
sean
parents:
diff changeset
83 return sys.exc_info()[1]
e1264e58154c initial commpit
sean
parents:
diff changeset
84
e1264e58154c initial commpit
sean
parents:
diff changeset
85 # Workaround for the "print is a keyword/function" Python 2/3 dilemma
e1264e58154c initial commpit
sean
parents:
diff changeset
86 # and a fallback for mod_wsgi (resticts stdout/err attribute access)
e1264e58154c initial commpit
sean
parents:
diff changeset
87 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
88 _stdout, _stderr = sys.stdout.write, sys.stderr.write
e1264e58154c initial commpit
sean
parents:
diff changeset
89 except IOError:
e1264e58154c initial commpit
sean
parents:
diff changeset
90 _stdout = lambda x: sys.stdout.write(x)
e1264e58154c initial commpit
sean
parents:
diff changeset
91 _stderr = lambda x: sys.stderr.write(x)
e1264e58154c initial commpit
sean
parents:
diff changeset
92
e1264e58154c initial commpit
sean
parents:
diff changeset
93 # Lots of stdlib and builtin differences.
e1264e58154c initial commpit
sean
parents:
diff changeset
94 if py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
95 import http.client as httplib
e1264e58154c initial commpit
sean
parents:
diff changeset
96 import _thread as thread
e1264e58154c initial commpit
sean
parents:
diff changeset
97 from urllib.parse import urljoin, SplitResult as UrlSplitResult
e1264e58154c initial commpit
sean
parents:
diff changeset
98 from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
e1264e58154c initial commpit
sean
parents:
diff changeset
99 urlunquote = functools.partial(urlunquote, encoding='latin1')
e1264e58154c initial commpit
sean
parents:
diff changeset
100 from http.cookies import SimpleCookie
e1264e58154c initial commpit
sean
parents:
diff changeset
101 from collections import MutableMapping as DictMixin
e1264e58154c initial commpit
sean
parents:
diff changeset
102 import pickle
e1264e58154c initial commpit
sean
parents:
diff changeset
103 from io import BytesIO
e1264e58154c initial commpit
sean
parents:
diff changeset
104 from configparser import ConfigParser
e1264e58154c initial commpit
sean
parents:
diff changeset
105 basestring = str
e1264e58154c initial commpit
sean
parents:
diff changeset
106 unicode = str
e1264e58154c initial commpit
sean
parents:
diff changeset
107 json_loads = lambda s: json_lds(touni(s))
e1264e58154c initial commpit
sean
parents:
diff changeset
108 callable = lambda x: hasattr(x, '__call__')
e1264e58154c initial commpit
sean
parents:
diff changeset
109 imap = map
e1264e58154c initial commpit
sean
parents:
diff changeset
110
e1264e58154c initial commpit
sean
parents:
diff changeset
111 def _raise(*a):
e1264e58154c initial commpit
sean
parents:
diff changeset
112 raise a[0](a[1]).with_traceback(a[2])
e1264e58154c initial commpit
sean
parents:
diff changeset
113 else: # 2.x
e1264e58154c initial commpit
sean
parents:
diff changeset
114 import httplib
e1264e58154c initial commpit
sean
parents:
diff changeset
115 import thread
e1264e58154c initial commpit
sean
parents:
diff changeset
116 from urlparse import urljoin, SplitResult as UrlSplitResult
e1264e58154c initial commpit
sean
parents:
diff changeset
117 from urllib import urlencode, quote as urlquote, unquote as urlunquote
e1264e58154c initial commpit
sean
parents:
diff changeset
118 from Cookie import SimpleCookie
e1264e58154c initial commpit
sean
parents:
diff changeset
119 from itertools import imap
e1264e58154c initial commpit
sean
parents:
diff changeset
120 import cPickle as pickle
e1264e58154c initial commpit
sean
parents:
diff changeset
121 from StringIO import StringIO as BytesIO
e1264e58154c initial commpit
sean
parents:
diff changeset
122 from ConfigParser import SafeConfigParser as ConfigParser
e1264e58154c initial commpit
sean
parents:
diff changeset
123 if py25:
e1264e58154c initial commpit
sean
parents:
diff changeset
124 msg = "Python 2.5 support may be dropped in future versions of Bottle."
e1264e58154c initial commpit
sean
parents:
diff changeset
125 warnings.warn(msg, DeprecationWarning)
e1264e58154c initial commpit
sean
parents:
diff changeset
126 from UserDict import DictMixin
e1264e58154c initial commpit
sean
parents:
diff changeset
127
e1264e58154c initial commpit
sean
parents:
diff changeset
128 def next(it):
e1264e58154c initial commpit
sean
parents:
diff changeset
129 return it.next()
e1264e58154c initial commpit
sean
parents:
diff changeset
130
e1264e58154c initial commpit
sean
parents:
diff changeset
131 bytes = str
e1264e58154c initial commpit
sean
parents:
diff changeset
132 else: # 2.6, 2.7
e1264e58154c initial commpit
sean
parents:
diff changeset
133 from collections import MutableMapping as DictMixin
e1264e58154c initial commpit
sean
parents:
diff changeset
134 unicode = unicode
e1264e58154c initial commpit
sean
parents:
diff changeset
135 json_loads = json_lds
e1264e58154c initial commpit
sean
parents:
diff changeset
136 eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
e1264e58154c initial commpit
sean
parents:
diff changeset
137
e1264e58154c initial commpit
sean
parents:
diff changeset
138
e1264e58154c initial commpit
sean
parents:
diff changeset
139 # Some helpers for string/byte handling
e1264e58154c initial commpit
sean
parents:
diff changeset
140 def tob(s, enc='utf8'):
e1264e58154c initial commpit
sean
parents:
diff changeset
141 return s.encode(enc) if isinstance(s, unicode) else bytes(s)
e1264e58154c initial commpit
sean
parents:
diff changeset
142
e1264e58154c initial commpit
sean
parents:
diff changeset
143
e1264e58154c initial commpit
sean
parents:
diff changeset
144 def touni(s, enc='utf8', err='strict'):
e1264e58154c initial commpit
sean
parents:
diff changeset
145 if isinstance(s, bytes):
e1264e58154c initial commpit
sean
parents:
diff changeset
146 return s.decode(enc, err)
e1264e58154c initial commpit
sean
parents:
diff changeset
147 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
148 return unicode(s or ("" if s is None else s))
e1264e58154c initial commpit
sean
parents:
diff changeset
149
e1264e58154c initial commpit
sean
parents:
diff changeset
150
e1264e58154c initial commpit
sean
parents:
diff changeset
151 tonat = touni if py3k else tob
e1264e58154c initial commpit
sean
parents:
diff changeset
152
e1264e58154c initial commpit
sean
parents:
diff changeset
153 # 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
e1264e58154c initial commpit
sean
parents:
diff changeset
154 # 3.1 needs a workaround.
e1264e58154c initial commpit
sean
parents:
diff changeset
155 if py31:
e1264e58154c initial commpit
sean
parents:
diff changeset
156 from io import TextIOWrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
157
e1264e58154c initial commpit
sean
parents:
diff changeset
158 class NCTextIOWrapper(TextIOWrapper):
e1264e58154c initial commpit
sean
parents:
diff changeset
159 def close(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
160 pass # Keep wrapped buffer open.
e1264e58154c initial commpit
sean
parents:
diff changeset
161
e1264e58154c initial commpit
sean
parents:
diff changeset
162
e1264e58154c initial commpit
sean
parents:
diff changeset
163 # A bug in functools causes it to break if the wrapper is an instance method
e1264e58154c initial commpit
sean
parents:
diff changeset
164 def update_wrapper(wrapper, wrapped, *a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
165 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
166 functools.update_wrapper(wrapper, wrapped, *a, **ka)
e1264e58154c initial commpit
sean
parents:
diff changeset
167 except AttributeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
168 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
169
e1264e58154c initial commpit
sean
parents:
diff changeset
170 # These helpers are used at module level and need to be defined first.
e1264e58154c initial commpit
sean
parents:
diff changeset
171 # And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
e1264e58154c initial commpit
sean
parents:
diff changeset
172
e1264e58154c initial commpit
sean
parents:
diff changeset
173
e1264e58154c initial commpit
sean
parents:
diff changeset
174 def depr(message, strict=False):
e1264e58154c initial commpit
sean
parents:
diff changeset
175 warnings.warn(message, DeprecationWarning, stacklevel=3)
e1264e58154c initial commpit
sean
parents:
diff changeset
176
e1264e58154c initial commpit
sean
parents:
diff changeset
177
e1264e58154c initial commpit
sean
parents:
diff changeset
178 def makelist(data): # This is just too handy
e1264e58154c initial commpit
sean
parents:
diff changeset
179 if isinstance(data, (tuple, list, set, dict)):
e1264e58154c initial commpit
sean
parents:
diff changeset
180 return list(data)
e1264e58154c initial commpit
sean
parents:
diff changeset
181 elif data:
e1264e58154c initial commpit
sean
parents:
diff changeset
182 return [data]
e1264e58154c initial commpit
sean
parents:
diff changeset
183 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
184 return []
e1264e58154c initial commpit
sean
parents:
diff changeset
185
e1264e58154c initial commpit
sean
parents:
diff changeset
186
e1264e58154c initial commpit
sean
parents:
diff changeset
187 class DictProperty(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
188 """ Property that maps to a key in a local dict-like attribute. """
e1264e58154c initial commpit
sean
parents:
diff changeset
189
e1264e58154c initial commpit
sean
parents:
diff changeset
190 def __init__(self, attr, key=None, read_only=False):
e1264e58154c initial commpit
sean
parents:
diff changeset
191 self.attr, self.key, self.read_only = attr, key, read_only
e1264e58154c initial commpit
sean
parents:
diff changeset
192
e1264e58154c initial commpit
sean
parents:
diff changeset
193 def __call__(self, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
194 functools.update_wrapper(self, func, updated=[])
e1264e58154c initial commpit
sean
parents:
diff changeset
195 self.getter, self.key = func, self.key or func.__name__
e1264e58154c initial commpit
sean
parents:
diff changeset
196 return self
e1264e58154c initial commpit
sean
parents:
diff changeset
197
e1264e58154c initial commpit
sean
parents:
diff changeset
198 def __get__(self, obj, cls):
e1264e58154c initial commpit
sean
parents:
diff changeset
199 if obj is None: return self
e1264e58154c initial commpit
sean
parents:
diff changeset
200 key, storage = self.key, getattr(obj, self.attr)
e1264e58154c initial commpit
sean
parents:
diff changeset
201 if key not in storage: storage[key] = self.getter(obj)
e1264e58154c initial commpit
sean
parents:
diff changeset
202 return storage[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
203
e1264e58154c initial commpit
sean
parents:
diff changeset
204 def __set__(self, obj, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
205 if self.read_only: raise AttributeError("Read-Only property.")
e1264e58154c initial commpit
sean
parents:
diff changeset
206 getattr(obj, self.attr)[self.key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
207
e1264e58154c initial commpit
sean
parents:
diff changeset
208 def __delete__(self, obj):
e1264e58154c initial commpit
sean
parents:
diff changeset
209 if self.read_only: raise AttributeError("Read-Only property.")
e1264e58154c initial commpit
sean
parents:
diff changeset
210 del getattr(obj, self.attr)[self.key]
e1264e58154c initial commpit
sean
parents:
diff changeset
211
e1264e58154c initial commpit
sean
parents:
diff changeset
212
e1264e58154c initial commpit
sean
parents:
diff changeset
213 class cached_property(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
214 """ A property that is only computed once per instance and then replaces
e1264e58154c initial commpit
sean
parents:
diff changeset
215 itself with an ordinary attribute. Deleting the attribute resets the
e1264e58154c initial commpit
sean
parents:
diff changeset
216 property. """
e1264e58154c initial commpit
sean
parents:
diff changeset
217
e1264e58154c initial commpit
sean
parents:
diff changeset
218 def __init__(self, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
219 self.__doc__ = getattr(func, '__doc__')
e1264e58154c initial commpit
sean
parents:
diff changeset
220 self.func = func
e1264e58154c initial commpit
sean
parents:
diff changeset
221
e1264e58154c initial commpit
sean
parents:
diff changeset
222 def __get__(self, obj, cls):
e1264e58154c initial commpit
sean
parents:
diff changeset
223 if obj is None: return self
e1264e58154c initial commpit
sean
parents:
diff changeset
224 value = obj.__dict__[self.func.__name__] = self.func(obj)
e1264e58154c initial commpit
sean
parents:
diff changeset
225 return value
e1264e58154c initial commpit
sean
parents:
diff changeset
226
e1264e58154c initial commpit
sean
parents:
diff changeset
227
e1264e58154c initial commpit
sean
parents:
diff changeset
228 class lazy_attribute(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
229 """ A property that caches itself to the class object. """
e1264e58154c initial commpit
sean
parents:
diff changeset
230
e1264e58154c initial commpit
sean
parents:
diff changeset
231 def __init__(self, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
232 functools.update_wrapper(self, func, updated=[])
e1264e58154c initial commpit
sean
parents:
diff changeset
233 self.getter = func
e1264e58154c initial commpit
sean
parents:
diff changeset
234
e1264e58154c initial commpit
sean
parents:
diff changeset
235 def __get__(self, obj, cls):
e1264e58154c initial commpit
sean
parents:
diff changeset
236 value = self.getter(cls)
e1264e58154c initial commpit
sean
parents:
diff changeset
237 setattr(cls, self.__name__, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
238 return value
e1264e58154c initial commpit
sean
parents:
diff changeset
239
e1264e58154c initial commpit
sean
parents:
diff changeset
240 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
241 # Exceptions and Events ########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
242 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
243
e1264e58154c initial commpit
sean
parents:
diff changeset
244
e1264e58154c initial commpit
sean
parents:
diff changeset
245 class BottleException(Exception):
e1264e58154c initial commpit
sean
parents:
diff changeset
246 """ A base class for exceptions used by bottle. """
e1264e58154c initial commpit
sean
parents:
diff changeset
247 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
248
e1264e58154c initial commpit
sean
parents:
diff changeset
249 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
250 # Routing ######################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
251 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
252
e1264e58154c initial commpit
sean
parents:
diff changeset
253
e1264e58154c initial commpit
sean
parents:
diff changeset
254 class RouteError(BottleException):
e1264e58154c initial commpit
sean
parents:
diff changeset
255 """ This is a base class for all routing related exceptions """
e1264e58154c initial commpit
sean
parents:
diff changeset
256
e1264e58154c initial commpit
sean
parents:
diff changeset
257
e1264e58154c initial commpit
sean
parents:
diff changeset
258 class RouteReset(BottleException):
e1264e58154c initial commpit
sean
parents:
diff changeset
259 """ If raised by a plugin or request handler, the route is reset and all
e1264e58154c initial commpit
sean
parents:
diff changeset
260 plugins are re-applied. """
e1264e58154c initial commpit
sean
parents:
diff changeset
261
e1264e58154c initial commpit
sean
parents:
diff changeset
262
e1264e58154c initial commpit
sean
parents:
diff changeset
263 class RouterUnknownModeError(RouteError):
e1264e58154c initial commpit
sean
parents:
diff changeset
264
e1264e58154c initial commpit
sean
parents:
diff changeset
265 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
266
e1264e58154c initial commpit
sean
parents:
diff changeset
267
e1264e58154c initial commpit
sean
parents:
diff changeset
268 class RouteSyntaxError(RouteError):
e1264e58154c initial commpit
sean
parents:
diff changeset
269 """ The route parser found something not supported by this router. """
e1264e58154c initial commpit
sean
parents:
diff changeset
270
e1264e58154c initial commpit
sean
parents:
diff changeset
271
e1264e58154c initial commpit
sean
parents:
diff changeset
272 class RouteBuildError(RouteError):
e1264e58154c initial commpit
sean
parents:
diff changeset
273 """ The route could not be built. """
e1264e58154c initial commpit
sean
parents:
diff changeset
274
e1264e58154c initial commpit
sean
parents:
diff changeset
275
e1264e58154c initial commpit
sean
parents:
diff changeset
276 def _re_flatten(p):
e1264e58154c initial commpit
sean
parents:
diff changeset
277 """ Turn all capturing groups in a regular expression pattern into
e1264e58154c initial commpit
sean
parents:
diff changeset
278 non-capturing groups. """
e1264e58154c initial commpit
sean
parents:
diff changeset
279 if '(' not in p:
e1264e58154c initial commpit
sean
parents:
diff changeset
280 return p
e1264e58154c initial commpit
sean
parents:
diff changeset
281 return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))', lambda m: m.group(0) if
e1264e58154c initial commpit
sean
parents:
diff changeset
282 len(m.group(1)) % 2 else m.group(1) + '(?:', p)
e1264e58154c initial commpit
sean
parents:
diff changeset
283
e1264e58154c initial commpit
sean
parents:
diff changeset
284
e1264e58154c initial commpit
sean
parents:
diff changeset
285 class Router(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
286 """ A Router is an ordered collection of route->target pairs. It is used to
e1264e58154c initial commpit
sean
parents:
diff changeset
287 efficiently match WSGI requests against a number of routes and return
e1264e58154c initial commpit
sean
parents:
diff changeset
288 the first target that satisfies the request. The target may be anything,
e1264e58154c initial commpit
sean
parents:
diff changeset
289 usually a string, ID or callable object. A route consists of a path-rule
e1264e58154c initial commpit
sean
parents:
diff changeset
290 and a HTTP method.
e1264e58154c initial commpit
sean
parents:
diff changeset
291
e1264e58154c initial commpit
sean
parents:
diff changeset
292 The path-rule is either a static path (e.g. `/contact`) or a dynamic
e1264e58154c initial commpit
sean
parents:
diff changeset
293 path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
294 and details on the matching order are described in docs:`routing`.
e1264e58154c initial commpit
sean
parents:
diff changeset
295 """
e1264e58154c initial commpit
sean
parents:
diff changeset
296
e1264e58154c initial commpit
sean
parents:
diff changeset
297 default_pattern = '[^/]+'
e1264e58154c initial commpit
sean
parents:
diff changeset
298 default_filter = 're'
e1264e58154c initial commpit
sean
parents:
diff changeset
299
e1264e58154c initial commpit
sean
parents:
diff changeset
300 #: The current CPython regexp implementation does not allow more
e1264e58154c initial commpit
sean
parents:
diff changeset
301 #: than 99 matching groups per regular expression.
e1264e58154c initial commpit
sean
parents:
diff changeset
302 _MAX_GROUPS_PER_PATTERN = 99
e1264e58154c initial commpit
sean
parents:
diff changeset
303
e1264e58154c initial commpit
sean
parents:
diff changeset
304 def __init__(self, strict=False):
e1264e58154c initial commpit
sean
parents:
diff changeset
305 self.rules = [] # All rules in order
e1264e58154c initial commpit
sean
parents:
diff changeset
306 self._groups = {} # index of regexes to find them in dyna_routes
e1264e58154c initial commpit
sean
parents:
diff changeset
307 self.builder = {} # Data structure for the url builder
e1264e58154c initial commpit
sean
parents:
diff changeset
308 self.static = {} # Search structure for static routes
e1264e58154c initial commpit
sean
parents:
diff changeset
309 self.dyna_routes = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
310 self.dyna_regexes = {} # Search structure for dynamic routes
e1264e58154c initial commpit
sean
parents:
diff changeset
311 #: If true, static routes are no longer checked first.
e1264e58154c initial commpit
sean
parents:
diff changeset
312 self.strict_order = strict
e1264e58154c initial commpit
sean
parents:
diff changeset
313 self.filters = {
e1264e58154c initial commpit
sean
parents:
diff changeset
314 're': lambda conf: (_re_flatten(conf or self.default_pattern),
e1264e58154c initial commpit
sean
parents:
diff changeset
315 None, None),
e1264e58154c initial commpit
sean
parents:
diff changeset
316 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
e1264e58154c initial commpit
sean
parents:
diff changeset
317 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
e1264e58154c initial commpit
sean
parents:
diff changeset
318 'path': lambda conf: (r'.+?', None, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
319 }
e1264e58154c initial commpit
sean
parents:
diff changeset
320
e1264e58154c initial commpit
sean
parents:
diff changeset
321 def add_filter(self, name, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
322 """ Add a filter. The provided function is called with the configuration
e1264e58154c initial commpit
sean
parents:
diff changeset
323 string as parameter and must return a (regexp, to_python, to_url) tuple.
e1264e58154c initial commpit
sean
parents:
diff changeset
324 The first element is a string, the last two are callables or None. """
e1264e58154c initial commpit
sean
parents:
diff changeset
325 self.filters[name] = func
e1264e58154c initial commpit
sean
parents:
diff changeset
326
e1264e58154c initial commpit
sean
parents:
diff changeset
327 rule_syntax = re.compile('(\\\\*)'
e1264e58154c initial commpit
sean
parents:
diff changeset
328 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'
e1264e58154c initial commpit
sean
parents:
diff changeset
329 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'
e1264e58154c initial commpit
sean
parents:
diff changeset
330 '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
e1264e58154c initial commpit
sean
parents:
diff changeset
331
e1264e58154c initial commpit
sean
parents:
diff changeset
332 def _itertokens(self, rule):
e1264e58154c initial commpit
sean
parents:
diff changeset
333 offset, prefix = 0, ''
e1264e58154c initial commpit
sean
parents:
diff changeset
334 for match in self.rule_syntax.finditer(rule):
e1264e58154c initial commpit
sean
parents:
diff changeset
335 prefix += rule[offset:match.start()]
e1264e58154c initial commpit
sean
parents:
diff changeset
336 g = match.groups()
e1264e58154c initial commpit
sean
parents:
diff changeset
337 if len(g[0]) % 2: # Escaped wildcard
e1264e58154c initial commpit
sean
parents:
diff changeset
338 prefix += match.group(0)[len(g[0]):]
e1264e58154c initial commpit
sean
parents:
diff changeset
339 offset = match.end()
e1264e58154c initial commpit
sean
parents:
diff changeset
340 continue
e1264e58154c initial commpit
sean
parents:
diff changeset
341 if prefix:
e1264e58154c initial commpit
sean
parents:
diff changeset
342 yield prefix, None, None
e1264e58154c initial commpit
sean
parents:
diff changeset
343 name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
e1264e58154c initial commpit
sean
parents:
diff changeset
344 yield name, filtr or 'default', conf or None
e1264e58154c initial commpit
sean
parents:
diff changeset
345 offset, prefix = match.end(), ''
e1264e58154c initial commpit
sean
parents:
diff changeset
346 if offset <= len(rule) or prefix:
e1264e58154c initial commpit
sean
parents:
diff changeset
347 yield prefix + rule[offset:], None, None
e1264e58154c initial commpit
sean
parents:
diff changeset
348
e1264e58154c initial commpit
sean
parents:
diff changeset
349 def add(self, rule, method, target, name=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
350 """ Add a new rule or replace the target for an existing rule. """
e1264e58154c initial commpit
sean
parents:
diff changeset
351 anons = 0 # Number of anonymous wildcards found
e1264e58154c initial commpit
sean
parents:
diff changeset
352 keys = [] # Names of keys
e1264e58154c initial commpit
sean
parents:
diff changeset
353 pattern = '' # Regular expression pattern with named groups
e1264e58154c initial commpit
sean
parents:
diff changeset
354 filters = [] # Lists of wildcard input filters
e1264e58154c initial commpit
sean
parents:
diff changeset
355 builder = [] # Data structure for the URL builder
e1264e58154c initial commpit
sean
parents:
diff changeset
356 is_static = True
e1264e58154c initial commpit
sean
parents:
diff changeset
357
e1264e58154c initial commpit
sean
parents:
diff changeset
358 for key, mode, conf in self._itertokens(rule):
e1264e58154c initial commpit
sean
parents:
diff changeset
359 if mode:
e1264e58154c initial commpit
sean
parents:
diff changeset
360 is_static = False
e1264e58154c initial commpit
sean
parents:
diff changeset
361 if mode == 'default': mode = self.default_filter
e1264e58154c initial commpit
sean
parents:
diff changeset
362 mask, in_filter, out_filter = self.filters[mode](conf)
e1264e58154c initial commpit
sean
parents:
diff changeset
363 if not key:
e1264e58154c initial commpit
sean
parents:
diff changeset
364 pattern += '(?:%s)' % mask
e1264e58154c initial commpit
sean
parents:
diff changeset
365 key = 'anon%d' % anons
e1264e58154c initial commpit
sean
parents:
diff changeset
366 anons += 1
e1264e58154c initial commpit
sean
parents:
diff changeset
367 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
368 pattern += '(?P<%s>%s)' % (key, mask)
e1264e58154c initial commpit
sean
parents:
diff changeset
369 keys.append(key)
e1264e58154c initial commpit
sean
parents:
diff changeset
370 if in_filter: filters.append((key, in_filter))
e1264e58154c initial commpit
sean
parents:
diff changeset
371 builder.append((key, out_filter or str))
e1264e58154c initial commpit
sean
parents:
diff changeset
372 elif key:
e1264e58154c initial commpit
sean
parents:
diff changeset
373 pattern += re.escape(key)
e1264e58154c initial commpit
sean
parents:
diff changeset
374 builder.append((None, key))
e1264e58154c initial commpit
sean
parents:
diff changeset
375
e1264e58154c initial commpit
sean
parents:
diff changeset
376 self.builder[rule] = builder
e1264e58154c initial commpit
sean
parents:
diff changeset
377 if name: self.builder[name] = builder
e1264e58154c initial commpit
sean
parents:
diff changeset
378
e1264e58154c initial commpit
sean
parents:
diff changeset
379 if is_static and not self.strict_order:
e1264e58154c initial commpit
sean
parents:
diff changeset
380 self.static.setdefault(method, {})
e1264e58154c initial commpit
sean
parents:
diff changeset
381 self.static[method][self.build(rule)] = (target, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
382 return
e1264e58154c initial commpit
sean
parents:
diff changeset
383
e1264e58154c initial commpit
sean
parents:
diff changeset
384 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
385 re_pattern = re.compile('^(%s)$' % pattern)
e1264e58154c initial commpit
sean
parents:
diff changeset
386 re_match = re_pattern.match
e1264e58154c initial commpit
sean
parents:
diff changeset
387 except re.error:
e1264e58154c initial commpit
sean
parents:
diff changeset
388 raise RouteSyntaxError("Could not add Route: %s (%s)" %
e1264e58154c initial commpit
sean
parents:
diff changeset
389 (rule, _e()))
e1264e58154c initial commpit
sean
parents:
diff changeset
390
e1264e58154c initial commpit
sean
parents:
diff changeset
391 if filters:
e1264e58154c initial commpit
sean
parents:
diff changeset
392
e1264e58154c initial commpit
sean
parents:
diff changeset
393 def getargs(path):
e1264e58154c initial commpit
sean
parents:
diff changeset
394 url_args = re_match(path).groupdict()
e1264e58154c initial commpit
sean
parents:
diff changeset
395 for name, wildcard_filter in filters:
e1264e58154c initial commpit
sean
parents:
diff changeset
396 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
397 url_args[name] = wildcard_filter(url_args[name])
e1264e58154c initial commpit
sean
parents:
diff changeset
398 except ValueError:
e1264e58154c initial commpit
sean
parents:
diff changeset
399 raise HTTPError(400, 'Path has wrong format.')
e1264e58154c initial commpit
sean
parents:
diff changeset
400 return url_args
e1264e58154c initial commpit
sean
parents:
diff changeset
401 elif re_pattern.groupindex:
e1264e58154c initial commpit
sean
parents:
diff changeset
402
e1264e58154c initial commpit
sean
parents:
diff changeset
403 def getargs(path):
e1264e58154c initial commpit
sean
parents:
diff changeset
404 return re_match(path).groupdict()
e1264e58154c initial commpit
sean
parents:
diff changeset
405 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
406 getargs = None
e1264e58154c initial commpit
sean
parents:
diff changeset
407
e1264e58154c initial commpit
sean
parents:
diff changeset
408 flatpat = _re_flatten(pattern)
e1264e58154c initial commpit
sean
parents:
diff changeset
409 whole_rule = (rule, flatpat, target, getargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
410
e1264e58154c initial commpit
sean
parents:
diff changeset
411 if (flatpat, method) in self._groups:
e1264e58154c initial commpit
sean
parents:
diff changeset
412 if DEBUG:
e1264e58154c initial commpit
sean
parents:
diff changeset
413 msg = 'Route <%s %s> overwrites a previously defined route'
e1264e58154c initial commpit
sean
parents:
diff changeset
414 warnings.warn(msg % (method, rule), RuntimeWarning)
e1264e58154c initial commpit
sean
parents:
diff changeset
415 self.dyna_routes[method][
e1264e58154c initial commpit
sean
parents:
diff changeset
416 self._groups[flatpat, method]] = whole_rule
e1264e58154c initial commpit
sean
parents:
diff changeset
417 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
418 self.dyna_routes.setdefault(method, []).append(whole_rule)
e1264e58154c initial commpit
sean
parents:
diff changeset
419 self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
e1264e58154c initial commpit
sean
parents:
diff changeset
420
e1264e58154c initial commpit
sean
parents:
diff changeset
421 self._compile(method)
e1264e58154c initial commpit
sean
parents:
diff changeset
422
e1264e58154c initial commpit
sean
parents:
diff changeset
423 def _compile(self, method):
e1264e58154c initial commpit
sean
parents:
diff changeset
424 all_rules = self.dyna_routes[method]
e1264e58154c initial commpit
sean
parents:
diff changeset
425 comborules = self.dyna_regexes[method] = []
e1264e58154c initial commpit
sean
parents:
diff changeset
426 maxgroups = self._MAX_GROUPS_PER_PATTERN
e1264e58154c initial commpit
sean
parents:
diff changeset
427 for x in range(0, len(all_rules), maxgroups):
e1264e58154c initial commpit
sean
parents:
diff changeset
428 some = all_rules[x:x + maxgroups]
e1264e58154c initial commpit
sean
parents:
diff changeset
429 combined = (flatpat for (_, flatpat, _, _) in some)
e1264e58154c initial commpit
sean
parents:
diff changeset
430 combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
e1264e58154c initial commpit
sean
parents:
diff changeset
431 combined = re.compile(combined).match
e1264e58154c initial commpit
sean
parents:
diff changeset
432 rules = [(target, getargs) for (_, _, target, getargs) in some]
e1264e58154c initial commpit
sean
parents:
diff changeset
433 comborules.append((combined, rules))
e1264e58154c initial commpit
sean
parents:
diff changeset
434
e1264e58154c initial commpit
sean
parents:
diff changeset
435 def build(self, _name, *anons, **query):
e1264e58154c initial commpit
sean
parents:
diff changeset
436 """ Build an URL by filling the wildcards in a rule. """
e1264e58154c initial commpit
sean
parents:
diff changeset
437 builder = self.builder.get(_name)
e1264e58154c initial commpit
sean
parents:
diff changeset
438 if not builder:
e1264e58154c initial commpit
sean
parents:
diff changeset
439 raise RouteBuildError("No route with that name.", _name)
e1264e58154c initial commpit
sean
parents:
diff changeset
440 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
441 for i, value in enumerate(anons):
e1264e58154c initial commpit
sean
parents:
diff changeset
442 query['anon%d' % i] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
443 url = ''.join([f(query.pop(n)) if n else f for (n, f) in builder])
e1264e58154c initial commpit
sean
parents:
diff changeset
444 return url if not query else url + '?' + urlencode(query)
e1264e58154c initial commpit
sean
parents:
diff changeset
445 except KeyError:
e1264e58154c initial commpit
sean
parents:
diff changeset
446 raise RouteBuildError('Missing URL argument: %r' % _e().args[0])
e1264e58154c initial commpit
sean
parents:
diff changeset
447
e1264e58154c initial commpit
sean
parents:
diff changeset
448 def match(self, environ):
e1264e58154c initial commpit
sean
parents:
diff changeset
449 """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """
e1264e58154c initial commpit
sean
parents:
diff changeset
450 verb = environ['REQUEST_METHOD'].upper()
e1264e58154c initial commpit
sean
parents:
diff changeset
451 path = environ['PATH_INFO'] or '/'
e1264e58154c initial commpit
sean
parents:
diff changeset
452
e1264e58154c initial commpit
sean
parents:
diff changeset
453 if verb == 'HEAD':
e1264e58154c initial commpit
sean
parents:
diff changeset
454 methods = ['PROXY', verb, 'GET', 'ANY']
e1264e58154c initial commpit
sean
parents:
diff changeset
455 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
456 methods = ['PROXY', verb, 'ANY']
e1264e58154c initial commpit
sean
parents:
diff changeset
457
e1264e58154c initial commpit
sean
parents:
diff changeset
458 for method in methods:
e1264e58154c initial commpit
sean
parents:
diff changeset
459 if method in self.static and path in self.static[method]:
e1264e58154c initial commpit
sean
parents:
diff changeset
460 target, getargs = self.static[method][path]
e1264e58154c initial commpit
sean
parents:
diff changeset
461 return target, getargs(path) if getargs else {}
e1264e58154c initial commpit
sean
parents:
diff changeset
462 elif method in self.dyna_regexes:
e1264e58154c initial commpit
sean
parents:
diff changeset
463 for combined, rules in self.dyna_regexes[method]:
e1264e58154c initial commpit
sean
parents:
diff changeset
464 match = combined(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
465 if match:
e1264e58154c initial commpit
sean
parents:
diff changeset
466 target, getargs = rules[match.lastindex - 1]
e1264e58154c initial commpit
sean
parents:
diff changeset
467 return target, getargs(path) if getargs else {}
e1264e58154c initial commpit
sean
parents:
diff changeset
468
e1264e58154c initial commpit
sean
parents:
diff changeset
469 # No matching route found. Collect alternative methods for 405 response
e1264e58154c initial commpit
sean
parents:
diff changeset
470 allowed = set([])
e1264e58154c initial commpit
sean
parents:
diff changeset
471 nocheck = set(methods)
e1264e58154c initial commpit
sean
parents:
diff changeset
472 for method in set(self.static) - nocheck:
e1264e58154c initial commpit
sean
parents:
diff changeset
473 if path in self.static[method]:
e1264e58154c initial commpit
sean
parents:
diff changeset
474 allowed.add(verb)
e1264e58154c initial commpit
sean
parents:
diff changeset
475 for method in set(self.dyna_regexes) - allowed - nocheck:
e1264e58154c initial commpit
sean
parents:
diff changeset
476 for combined, rules in self.dyna_regexes[method]:
e1264e58154c initial commpit
sean
parents:
diff changeset
477 match = combined(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
478 if match:
e1264e58154c initial commpit
sean
parents:
diff changeset
479 allowed.add(method)
e1264e58154c initial commpit
sean
parents:
diff changeset
480 if allowed:
e1264e58154c initial commpit
sean
parents:
diff changeset
481 allow_header = ",".join(sorted(allowed))
e1264e58154c initial commpit
sean
parents:
diff changeset
482 raise HTTPError(405, "Method not allowed.", Allow=allow_header)
e1264e58154c initial commpit
sean
parents:
diff changeset
483
e1264e58154c initial commpit
sean
parents:
diff changeset
484 # No matching route and no alternative method found. We give up
e1264e58154c initial commpit
sean
parents:
diff changeset
485 raise HTTPError(404, "Not found: " + repr(path))
e1264e58154c initial commpit
sean
parents:
diff changeset
486
e1264e58154c initial commpit
sean
parents:
diff changeset
487
e1264e58154c initial commpit
sean
parents:
diff changeset
488 class Route(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
489 """ This class wraps a route callback along with route specific metadata and
e1264e58154c initial commpit
sean
parents:
diff changeset
490 configuration and applies Plugins on demand. It is also responsible for
e1264e58154c initial commpit
sean
parents:
diff changeset
491 turing an URL path rule into a regular expression usable by the Router.
e1264e58154c initial commpit
sean
parents:
diff changeset
492 """
e1264e58154c initial commpit
sean
parents:
diff changeset
493
e1264e58154c initial commpit
sean
parents:
diff changeset
494 def __init__(self, app, rule, method, callback,
e1264e58154c initial commpit
sean
parents:
diff changeset
495 name=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
496 plugins=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
497 skiplist=None, **config):
e1264e58154c initial commpit
sean
parents:
diff changeset
498 #: The application this route is installed to.
e1264e58154c initial commpit
sean
parents:
diff changeset
499 self.app = app
e1264e58154c initial commpit
sean
parents:
diff changeset
500 #: The path-rule string (e.g. ``/wiki/<page>``).
e1264e58154c initial commpit
sean
parents:
diff changeset
501 self.rule = rule
e1264e58154c initial commpit
sean
parents:
diff changeset
502 #: The HTTP method as a string (e.g. ``GET``).
e1264e58154c initial commpit
sean
parents:
diff changeset
503 self.method = method
e1264e58154c initial commpit
sean
parents:
diff changeset
504 #: The original callback with no plugins applied. Useful for introspection.
e1264e58154c initial commpit
sean
parents:
diff changeset
505 self.callback = callback
e1264e58154c initial commpit
sean
parents:
diff changeset
506 #: The name of the route (if specified) or ``None``.
e1264e58154c initial commpit
sean
parents:
diff changeset
507 self.name = name or None
e1264e58154c initial commpit
sean
parents:
diff changeset
508 #: A list of route-specific plugins (see :meth:`Bottle.route`).
e1264e58154c initial commpit
sean
parents:
diff changeset
509 self.plugins = plugins or []
e1264e58154c initial commpit
sean
parents:
diff changeset
510 #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
e1264e58154c initial commpit
sean
parents:
diff changeset
511 self.skiplist = skiplist or []
e1264e58154c initial commpit
sean
parents:
diff changeset
512 #: Additional keyword arguments passed to the :meth:`Bottle.route`
e1264e58154c initial commpit
sean
parents:
diff changeset
513 #: decorator are stored in this dictionary. Used for route-specific
e1264e58154c initial commpit
sean
parents:
diff changeset
514 #: plugin configuration and meta-data.
e1264e58154c initial commpit
sean
parents:
diff changeset
515 self.config = ConfigDict().load_dict(config)
e1264e58154c initial commpit
sean
parents:
diff changeset
516
e1264e58154c initial commpit
sean
parents:
diff changeset
517 @cached_property
e1264e58154c initial commpit
sean
parents:
diff changeset
518 def call(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
519 """ The route callback with all plugins applied. This property is
e1264e58154c initial commpit
sean
parents:
diff changeset
520 created on demand and then cached to speed up subsequent requests."""
e1264e58154c initial commpit
sean
parents:
diff changeset
521 return self._make_callback()
e1264e58154c initial commpit
sean
parents:
diff changeset
522
e1264e58154c initial commpit
sean
parents:
diff changeset
523 def reset(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
524 """ Forget any cached values. The next time :attr:`call` is accessed,
e1264e58154c initial commpit
sean
parents:
diff changeset
525 all plugins are re-applied. """
e1264e58154c initial commpit
sean
parents:
diff changeset
526 self.__dict__.pop('call', None)
e1264e58154c initial commpit
sean
parents:
diff changeset
527
e1264e58154c initial commpit
sean
parents:
diff changeset
528 def prepare(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
529 """ Do all on-demand work immediately (useful for debugging)."""
e1264e58154c initial commpit
sean
parents:
diff changeset
530 self.call
e1264e58154c initial commpit
sean
parents:
diff changeset
531
e1264e58154c initial commpit
sean
parents:
diff changeset
532 def all_plugins(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
533 """ Yield all Plugins affecting this route. """
e1264e58154c initial commpit
sean
parents:
diff changeset
534 unique = set()
e1264e58154c initial commpit
sean
parents:
diff changeset
535 for p in reversed(self.app.plugins + self.plugins):
e1264e58154c initial commpit
sean
parents:
diff changeset
536 if True in self.skiplist: break
e1264e58154c initial commpit
sean
parents:
diff changeset
537 name = getattr(p, 'name', False)
e1264e58154c initial commpit
sean
parents:
diff changeset
538 if name and (name in self.skiplist or name in unique): continue
e1264e58154c initial commpit
sean
parents:
diff changeset
539 if p in self.skiplist or type(p) in self.skiplist: continue
e1264e58154c initial commpit
sean
parents:
diff changeset
540 if name: unique.add(name)
e1264e58154c initial commpit
sean
parents:
diff changeset
541 yield p
e1264e58154c initial commpit
sean
parents:
diff changeset
542
e1264e58154c initial commpit
sean
parents:
diff changeset
543 def _make_callback(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
544 callback = self.callback
e1264e58154c initial commpit
sean
parents:
diff changeset
545 for plugin in self.all_plugins():
e1264e58154c initial commpit
sean
parents:
diff changeset
546 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
547 if hasattr(plugin, 'apply'):
e1264e58154c initial commpit
sean
parents:
diff changeset
548 callback = plugin.apply(callback, self)
e1264e58154c initial commpit
sean
parents:
diff changeset
549 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
550 callback = plugin(callback)
e1264e58154c initial commpit
sean
parents:
diff changeset
551 except RouteReset: # Try again with changed configuration.
e1264e58154c initial commpit
sean
parents:
diff changeset
552 return self._make_callback()
e1264e58154c initial commpit
sean
parents:
diff changeset
553 if not callback is self.callback:
e1264e58154c initial commpit
sean
parents:
diff changeset
554 update_wrapper(callback, self.callback)
e1264e58154c initial commpit
sean
parents:
diff changeset
555 return callback
e1264e58154c initial commpit
sean
parents:
diff changeset
556
e1264e58154c initial commpit
sean
parents:
diff changeset
557 def get_undecorated_callback(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
558 """ Return the callback. If the callback is a decorated function, try to
e1264e58154c initial commpit
sean
parents:
diff changeset
559 recover the original function. """
e1264e58154c initial commpit
sean
parents:
diff changeset
560 func = self.callback
e1264e58154c initial commpit
sean
parents:
diff changeset
561 func = getattr(func, '__func__' if py3k else 'im_func', func)
e1264e58154c initial commpit
sean
parents:
diff changeset
562 closure_attr = '__closure__' if py3k else 'func_closure'
e1264e58154c initial commpit
sean
parents:
diff changeset
563 while hasattr(func, closure_attr) and getattr(func, closure_attr):
e1264e58154c initial commpit
sean
parents:
diff changeset
564 attributes = getattr(func, closure_attr)
e1264e58154c initial commpit
sean
parents:
diff changeset
565 func = attributes[0].cell_contents
e1264e58154c initial commpit
sean
parents:
diff changeset
566
e1264e58154c initial commpit
sean
parents:
diff changeset
567 # in case of decorators with multiple arguments
e1264e58154c initial commpit
sean
parents:
diff changeset
568 if not isinstance(func, FunctionType):
e1264e58154c initial commpit
sean
parents:
diff changeset
569 # pick first FunctionType instance from multiple arguments
e1264e58154c initial commpit
sean
parents:
diff changeset
570 func = filter(lambda x: isinstance(x, FunctionType),
e1264e58154c initial commpit
sean
parents:
diff changeset
571 map(lambda x: x.cell_contents, attributes))
e1264e58154c initial commpit
sean
parents:
diff changeset
572 func = list(func)[0] # py3 support
e1264e58154c initial commpit
sean
parents:
diff changeset
573 return func
e1264e58154c initial commpit
sean
parents:
diff changeset
574
e1264e58154c initial commpit
sean
parents:
diff changeset
575 def get_callback_args(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
576 """ Return a list of argument names the callback (most likely) accepts
e1264e58154c initial commpit
sean
parents:
diff changeset
577 as keyword arguments. If the callback is a decorated function, try
e1264e58154c initial commpit
sean
parents:
diff changeset
578 to recover the original function before inspection. """
e1264e58154c initial commpit
sean
parents:
diff changeset
579 return getargspec(self.get_undecorated_callback())[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
580
e1264e58154c initial commpit
sean
parents:
diff changeset
581 def get_config(self, key, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
582 """ Lookup a config field and return its value, first checking the
e1264e58154c initial commpit
sean
parents:
diff changeset
583 route.config, then route.app.config."""
e1264e58154c initial commpit
sean
parents:
diff changeset
584 for conf in (self.config, self.app.config):
e1264e58154c initial commpit
sean
parents:
diff changeset
585 if key in conf: return conf[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
586 return default
e1264e58154c initial commpit
sean
parents:
diff changeset
587
e1264e58154c initial commpit
sean
parents:
diff changeset
588 def __repr__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
589 cb = self.get_undecorated_callback()
e1264e58154c initial commpit
sean
parents:
diff changeset
590 return '<%s %r %r>' % (self.method, self.rule, cb)
e1264e58154c initial commpit
sean
parents:
diff changeset
591
e1264e58154c initial commpit
sean
parents:
diff changeset
592 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
593 # Application Object ###########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
594 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
595
e1264e58154c initial commpit
sean
parents:
diff changeset
596
e1264e58154c initial commpit
sean
parents:
diff changeset
597 class Bottle(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
598 """ Each Bottle object represents a single, distinct web application and
e1264e58154c initial commpit
sean
parents:
diff changeset
599 consists of routes, callbacks, plugins, resources and configuration.
e1264e58154c initial commpit
sean
parents:
diff changeset
600 Instances are callable WSGI applications.
e1264e58154c initial commpit
sean
parents:
diff changeset
601
e1264e58154c initial commpit
sean
parents:
diff changeset
602 :param catchall: If true (default), handle all exceptions. Turn off to
e1264e58154c initial commpit
sean
parents:
diff changeset
603 let debugging middleware handle exceptions.
e1264e58154c initial commpit
sean
parents:
diff changeset
604 """
e1264e58154c initial commpit
sean
parents:
diff changeset
605
e1264e58154c initial commpit
sean
parents:
diff changeset
606 def __init__(self, catchall=True, autojson=True):
e1264e58154c initial commpit
sean
parents:
diff changeset
607
e1264e58154c initial commpit
sean
parents:
diff changeset
608 #: A :class:`ConfigDict` for app specific configuration.
e1264e58154c initial commpit
sean
parents:
diff changeset
609 self.config = ConfigDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
610 self.config._on_change = functools.partial(self.trigger_hook, 'config')
e1264e58154c initial commpit
sean
parents:
diff changeset
611 self.config.meta_set('autojson', 'validate', bool)
e1264e58154c initial commpit
sean
parents:
diff changeset
612 self.config.meta_set('catchall', 'validate', bool)
e1264e58154c initial commpit
sean
parents:
diff changeset
613 self.config['catchall'] = catchall
e1264e58154c initial commpit
sean
parents:
diff changeset
614 self.config['autojson'] = autojson
e1264e58154c initial commpit
sean
parents:
diff changeset
615
e1264e58154c initial commpit
sean
parents:
diff changeset
616 #: A :class:`ResourceManager` for application files
e1264e58154c initial commpit
sean
parents:
diff changeset
617 self.resources = ResourceManager()
e1264e58154c initial commpit
sean
parents:
diff changeset
618
e1264e58154c initial commpit
sean
parents:
diff changeset
619 self.routes = [] # List of installed :class:`Route` instances.
e1264e58154c initial commpit
sean
parents:
diff changeset
620 self.router = Router() # Maps requests to :class:`Route` instances.
e1264e58154c initial commpit
sean
parents:
diff changeset
621 self.error_handler = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
622
e1264e58154c initial commpit
sean
parents:
diff changeset
623 # Core plugins
e1264e58154c initial commpit
sean
parents:
diff changeset
624 self.plugins = [] # List of installed plugins.
e1264e58154c initial commpit
sean
parents:
diff changeset
625 if self.config['autojson']:
e1264e58154c initial commpit
sean
parents:
diff changeset
626 self.install(JSONPlugin())
e1264e58154c initial commpit
sean
parents:
diff changeset
627 self.install(TemplatePlugin())
e1264e58154c initial commpit
sean
parents:
diff changeset
628
e1264e58154c initial commpit
sean
parents:
diff changeset
629 #: If true, most exceptions are caught and returned as :exc:`HTTPError`
e1264e58154c initial commpit
sean
parents:
diff changeset
630 catchall = DictProperty('config', 'catchall')
e1264e58154c initial commpit
sean
parents:
diff changeset
631
e1264e58154c initial commpit
sean
parents:
diff changeset
632 __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
e1264e58154c initial commpit
sean
parents:
diff changeset
633 __hook_reversed = 'after_request'
e1264e58154c initial commpit
sean
parents:
diff changeset
634
e1264e58154c initial commpit
sean
parents:
diff changeset
635 @cached_property
e1264e58154c initial commpit
sean
parents:
diff changeset
636 def _hooks(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
637 return dict((name, []) for name in self.__hook_names)
e1264e58154c initial commpit
sean
parents:
diff changeset
638
e1264e58154c initial commpit
sean
parents:
diff changeset
639 def add_hook(self, name, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
640 """ Attach a callback to a hook. Three hooks are currently implemented:
e1264e58154c initial commpit
sean
parents:
diff changeset
641
e1264e58154c initial commpit
sean
parents:
diff changeset
642 before_request
e1264e58154c initial commpit
sean
parents:
diff changeset
643 Executed once before each request. The request context is
e1264e58154c initial commpit
sean
parents:
diff changeset
644 available, but no routing has happened yet.
e1264e58154c initial commpit
sean
parents:
diff changeset
645 after_request
e1264e58154c initial commpit
sean
parents:
diff changeset
646 Executed once after each request regardless of its outcome.
e1264e58154c initial commpit
sean
parents:
diff changeset
647 app_reset
e1264e58154c initial commpit
sean
parents:
diff changeset
648 Called whenever :meth:`Bottle.reset` is called.
e1264e58154c initial commpit
sean
parents:
diff changeset
649 """
e1264e58154c initial commpit
sean
parents:
diff changeset
650 if name in self.__hook_reversed:
e1264e58154c initial commpit
sean
parents:
diff changeset
651 self._hooks[name].insert(0, func)
e1264e58154c initial commpit
sean
parents:
diff changeset
652 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
653 self._hooks[name].append(func)
e1264e58154c initial commpit
sean
parents:
diff changeset
654
e1264e58154c initial commpit
sean
parents:
diff changeset
655 def remove_hook(self, name, func):
e1264e58154c initial commpit
sean
parents:
diff changeset
656 """ Remove a callback from a hook. """
e1264e58154c initial commpit
sean
parents:
diff changeset
657 if name in self._hooks and func in self._hooks[name]:
e1264e58154c initial commpit
sean
parents:
diff changeset
658 self._hooks[name].remove(func)
e1264e58154c initial commpit
sean
parents:
diff changeset
659 return True
e1264e58154c initial commpit
sean
parents:
diff changeset
660
e1264e58154c initial commpit
sean
parents:
diff changeset
661 def trigger_hook(self, __name, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
662 """ Trigger a hook and return a list of results. """
e1264e58154c initial commpit
sean
parents:
diff changeset
663 return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
e1264e58154c initial commpit
sean
parents:
diff changeset
664
e1264e58154c initial commpit
sean
parents:
diff changeset
665 def hook(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
666 """ Return a decorator that attaches a callback to a hook. See
e1264e58154c initial commpit
sean
parents:
diff changeset
667 :meth:`add_hook` for details."""
e1264e58154c initial commpit
sean
parents:
diff changeset
668
e1264e58154c initial commpit
sean
parents:
diff changeset
669 def decorator(func):
e1264e58154c initial commpit
sean
parents:
diff changeset
670 self.add_hook(name, func)
e1264e58154c initial commpit
sean
parents:
diff changeset
671 return func
e1264e58154c initial commpit
sean
parents:
diff changeset
672
e1264e58154c initial commpit
sean
parents:
diff changeset
673 return decorator
e1264e58154c initial commpit
sean
parents:
diff changeset
674
e1264e58154c initial commpit
sean
parents:
diff changeset
675 def mount(self, prefix, app, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
676 """ Mount an application (:class:`Bottle` or plain WSGI) to a specific
e1264e58154c initial commpit
sean
parents:
diff changeset
677 URL prefix. Example::
e1264e58154c initial commpit
sean
parents:
diff changeset
678
e1264e58154c initial commpit
sean
parents:
diff changeset
679 root_app.mount('/admin/', admin_app)
e1264e58154c initial commpit
sean
parents:
diff changeset
680
e1264e58154c initial commpit
sean
parents:
diff changeset
681 :param prefix: path prefix or `mount-point`. If it ends in a slash,
e1264e58154c initial commpit
sean
parents:
diff changeset
682 that slash is mandatory.
e1264e58154c initial commpit
sean
parents:
diff changeset
683 :param app: an instance of :class:`Bottle` or a WSGI application.
e1264e58154c initial commpit
sean
parents:
diff changeset
684
e1264e58154c initial commpit
sean
parents:
diff changeset
685 All other parameters are passed to the underlying :meth:`route` call.
e1264e58154c initial commpit
sean
parents:
diff changeset
686 """
e1264e58154c initial commpit
sean
parents:
diff changeset
687
e1264e58154c initial commpit
sean
parents:
diff changeset
688 segments = [p for p in prefix.split('/') if p]
e1264e58154c initial commpit
sean
parents:
diff changeset
689 if not segments: raise ValueError('Empty path prefix.')
e1264e58154c initial commpit
sean
parents:
diff changeset
690 path_depth = len(segments)
e1264e58154c initial commpit
sean
parents:
diff changeset
691
e1264e58154c initial commpit
sean
parents:
diff changeset
692 def mountpoint_wrapper():
e1264e58154c initial commpit
sean
parents:
diff changeset
693 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
694 request.path_shift(path_depth)
e1264e58154c initial commpit
sean
parents:
diff changeset
695 rs = HTTPResponse([])
e1264e58154c initial commpit
sean
parents:
diff changeset
696
e1264e58154c initial commpit
sean
parents:
diff changeset
697 def start_response(status, headerlist, exc_info=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
698 if exc_info:
e1264e58154c initial commpit
sean
parents:
diff changeset
699 _raise(*exc_info)
e1264e58154c initial commpit
sean
parents:
diff changeset
700 rs.status = status
e1264e58154c initial commpit
sean
parents:
diff changeset
701 for name, value in headerlist:
e1264e58154c initial commpit
sean
parents:
diff changeset
702 rs.add_header(name, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
703 return rs.body.append
e1264e58154c initial commpit
sean
parents:
diff changeset
704
e1264e58154c initial commpit
sean
parents:
diff changeset
705 body = app(request.environ, start_response)
e1264e58154c initial commpit
sean
parents:
diff changeset
706 rs.body = itertools.chain(rs.body, body) if rs.body else body
e1264e58154c initial commpit
sean
parents:
diff changeset
707 return rs
e1264e58154c initial commpit
sean
parents:
diff changeset
708 finally:
e1264e58154c initial commpit
sean
parents:
diff changeset
709 request.path_shift(-path_depth)
e1264e58154c initial commpit
sean
parents:
diff changeset
710
e1264e58154c initial commpit
sean
parents:
diff changeset
711 options.setdefault('skip', True)
e1264e58154c initial commpit
sean
parents:
diff changeset
712 options.setdefault('method', 'PROXY')
e1264e58154c initial commpit
sean
parents:
diff changeset
713 options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
e1264e58154c initial commpit
sean
parents:
diff changeset
714 options['callback'] = mountpoint_wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
715
e1264e58154c initial commpit
sean
parents:
diff changeset
716 self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
717 if not prefix.endswith('/'):
e1264e58154c initial commpit
sean
parents:
diff changeset
718 self.route('/' + '/'.join(segments), **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
719
e1264e58154c initial commpit
sean
parents:
diff changeset
720 def merge(self, routes):
e1264e58154c initial commpit
sean
parents:
diff changeset
721 """ Merge the routes of another :class:`Bottle` application or a list of
e1264e58154c initial commpit
sean
parents:
diff changeset
722 :class:`Route` objects into this application. The routes keep their
e1264e58154c initial commpit
sean
parents:
diff changeset
723 'owner', meaning that the :data:`Route.app` attribute is not
e1264e58154c initial commpit
sean
parents:
diff changeset
724 changed. """
e1264e58154c initial commpit
sean
parents:
diff changeset
725 if isinstance(routes, Bottle):
e1264e58154c initial commpit
sean
parents:
diff changeset
726 routes = routes.routes
e1264e58154c initial commpit
sean
parents:
diff changeset
727 for route in routes:
e1264e58154c initial commpit
sean
parents:
diff changeset
728 self.add_route(route)
e1264e58154c initial commpit
sean
parents:
diff changeset
729
e1264e58154c initial commpit
sean
parents:
diff changeset
730 def install(self, plugin):
e1264e58154c initial commpit
sean
parents:
diff changeset
731 """ Add a plugin to the list of plugins and prepare it for being
e1264e58154c initial commpit
sean
parents:
diff changeset
732 applied to all routes of this application. A plugin may be a simple
e1264e58154c initial commpit
sean
parents:
diff changeset
733 decorator or an object that implements the :class:`Plugin` API.
e1264e58154c initial commpit
sean
parents:
diff changeset
734 """
e1264e58154c initial commpit
sean
parents:
diff changeset
735 if hasattr(plugin, 'setup'): plugin.setup(self)
e1264e58154c initial commpit
sean
parents:
diff changeset
736 if not callable(plugin) and not hasattr(plugin, 'apply'):
e1264e58154c initial commpit
sean
parents:
diff changeset
737 raise TypeError("Plugins must be callable or implement .apply()")
e1264e58154c initial commpit
sean
parents:
diff changeset
738 self.plugins.append(plugin)
e1264e58154c initial commpit
sean
parents:
diff changeset
739 self.reset()
e1264e58154c initial commpit
sean
parents:
diff changeset
740 return plugin
e1264e58154c initial commpit
sean
parents:
diff changeset
741
e1264e58154c initial commpit
sean
parents:
diff changeset
742 def uninstall(self, plugin):
e1264e58154c initial commpit
sean
parents:
diff changeset
743 """ Uninstall plugins. Pass an instance to remove a specific plugin, a type
e1264e58154c initial commpit
sean
parents:
diff changeset
744 object to remove all plugins that match that type, a string to remove
e1264e58154c initial commpit
sean
parents:
diff changeset
745 all plugins with a matching ``name`` attribute or ``True`` to remove all
e1264e58154c initial commpit
sean
parents:
diff changeset
746 plugins. Return the list of removed plugins. """
e1264e58154c initial commpit
sean
parents:
diff changeset
747 removed, remove = [], plugin
e1264e58154c initial commpit
sean
parents:
diff changeset
748 for i, plugin in list(enumerate(self.plugins))[::-1]:
e1264e58154c initial commpit
sean
parents:
diff changeset
749 if remove is True or remove is plugin or remove is type(plugin) \
e1264e58154c initial commpit
sean
parents:
diff changeset
750 or getattr(plugin, 'name', True) == remove:
e1264e58154c initial commpit
sean
parents:
diff changeset
751 removed.append(plugin)
e1264e58154c initial commpit
sean
parents:
diff changeset
752 del self.plugins[i]
e1264e58154c initial commpit
sean
parents:
diff changeset
753 if hasattr(plugin, 'close'): plugin.close()
e1264e58154c initial commpit
sean
parents:
diff changeset
754 if removed: self.reset()
e1264e58154c initial commpit
sean
parents:
diff changeset
755 return removed
e1264e58154c initial commpit
sean
parents:
diff changeset
756
e1264e58154c initial commpit
sean
parents:
diff changeset
757 def reset(self, route=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
758 """ Reset all routes (force plugins to be re-applied) and clear all
e1264e58154c initial commpit
sean
parents:
diff changeset
759 caches. If an ID or route object is given, only that specific route
e1264e58154c initial commpit
sean
parents:
diff changeset
760 is affected. """
e1264e58154c initial commpit
sean
parents:
diff changeset
761 if route is None: routes = self.routes
e1264e58154c initial commpit
sean
parents:
diff changeset
762 elif isinstance(route, Route): routes = [route]
e1264e58154c initial commpit
sean
parents:
diff changeset
763 else: routes = [self.routes[route]]
e1264e58154c initial commpit
sean
parents:
diff changeset
764 for route in routes:
e1264e58154c initial commpit
sean
parents:
diff changeset
765 route.reset()
e1264e58154c initial commpit
sean
parents:
diff changeset
766 if DEBUG:
e1264e58154c initial commpit
sean
parents:
diff changeset
767 for route in routes:
e1264e58154c initial commpit
sean
parents:
diff changeset
768 route.prepare()
e1264e58154c initial commpit
sean
parents:
diff changeset
769 self.trigger_hook('app_reset')
e1264e58154c initial commpit
sean
parents:
diff changeset
770
e1264e58154c initial commpit
sean
parents:
diff changeset
771 def close(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
772 """ Close the application and all installed plugins. """
e1264e58154c initial commpit
sean
parents:
diff changeset
773 for plugin in self.plugins:
e1264e58154c initial commpit
sean
parents:
diff changeset
774 if hasattr(plugin, 'close'): plugin.close()
e1264e58154c initial commpit
sean
parents:
diff changeset
775
e1264e58154c initial commpit
sean
parents:
diff changeset
776 def run(self, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
777 """ Calls :func:`run` with the same parameters. """
e1264e58154c initial commpit
sean
parents:
diff changeset
778 run(self, **kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
779
e1264e58154c initial commpit
sean
parents:
diff changeset
780 def match(self, environ):
e1264e58154c initial commpit
sean
parents:
diff changeset
781 """ Search for a matching route and return a (:class:`Route` , urlargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
782 tuple. The second value is a dictionary with parameters extracted
e1264e58154c initial commpit
sean
parents:
diff changeset
783 from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
e1264e58154c initial commpit
sean
parents:
diff changeset
784 return self.router.match(environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
785
e1264e58154c initial commpit
sean
parents:
diff changeset
786 def get_url(self, routename, **kargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
787 """ Return a string that matches a named route """
e1264e58154c initial commpit
sean
parents:
diff changeset
788 scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
e1264e58154c initial commpit
sean
parents:
diff changeset
789 location = self.router.build(routename, **kargs).lstrip('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
790 return urljoin(urljoin('/', scriptname), location)
e1264e58154c initial commpit
sean
parents:
diff changeset
791
e1264e58154c initial commpit
sean
parents:
diff changeset
792 def add_route(self, route):
e1264e58154c initial commpit
sean
parents:
diff changeset
793 """ Add a route object, but do not change the :data:`Route.app`
e1264e58154c initial commpit
sean
parents:
diff changeset
794 attribute."""
e1264e58154c initial commpit
sean
parents:
diff changeset
795 self.routes.append(route)
e1264e58154c initial commpit
sean
parents:
diff changeset
796 self.router.add(route.rule, route.method, route, name=route.name)
e1264e58154c initial commpit
sean
parents:
diff changeset
797 if DEBUG: route.prepare()
e1264e58154c initial commpit
sean
parents:
diff changeset
798
e1264e58154c initial commpit
sean
parents:
diff changeset
799 def route(self,
e1264e58154c initial commpit
sean
parents:
diff changeset
800 path=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
801 method='GET',
e1264e58154c initial commpit
sean
parents:
diff changeset
802 callback=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
803 name=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
804 apply=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
805 skip=None, **config):
e1264e58154c initial commpit
sean
parents:
diff changeset
806 """ A decorator to bind a function to a request URL. Example::
e1264e58154c initial commpit
sean
parents:
diff changeset
807
e1264e58154c initial commpit
sean
parents:
diff changeset
808 @app.route('/hello/<name>')
e1264e58154c initial commpit
sean
parents:
diff changeset
809 def hello(name):
e1264e58154c initial commpit
sean
parents:
diff changeset
810 return 'Hello %s' % name
e1264e58154c initial commpit
sean
parents:
diff changeset
811
e1264e58154c initial commpit
sean
parents:
diff changeset
812 The ``<name>`` part is a wildcard. See :class:`Router` for syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
813 details.
e1264e58154c initial commpit
sean
parents:
diff changeset
814
e1264e58154c initial commpit
sean
parents:
diff changeset
815 :param path: Request path or a list of paths to listen to. If no
e1264e58154c initial commpit
sean
parents:
diff changeset
816 path is specified, it is automatically generated from the
e1264e58154c initial commpit
sean
parents:
diff changeset
817 signature of the function.
e1264e58154c initial commpit
sean
parents:
diff changeset
818 :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
e1264e58154c initial commpit
sean
parents:
diff changeset
819 methods to listen to. (default: `GET`)
e1264e58154c initial commpit
sean
parents:
diff changeset
820 :param callback: An optional shortcut to avoid the decorator
e1264e58154c initial commpit
sean
parents:
diff changeset
821 syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
e1264e58154c initial commpit
sean
parents:
diff changeset
822 :param name: The name for this route. (default: None)
e1264e58154c initial commpit
sean
parents:
diff changeset
823 :param apply: A decorator or plugin or a list of plugins. These are
e1264e58154c initial commpit
sean
parents:
diff changeset
824 applied to the route callback in addition to installed plugins.
e1264e58154c initial commpit
sean
parents:
diff changeset
825 :param skip: A list of plugins, plugin classes or names. Matching
e1264e58154c initial commpit
sean
parents:
diff changeset
826 plugins are not installed to this route. ``True`` skips all.
e1264e58154c initial commpit
sean
parents:
diff changeset
827
e1264e58154c initial commpit
sean
parents:
diff changeset
828 Any additional keyword arguments are stored as route-specific
e1264e58154c initial commpit
sean
parents:
diff changeset
829 configuration and passed to plugins (see :meth:`Plugin.apply`).
e1264e58154c initial commpit
sean
parents:
diff changeset
830 """
e1264e58154c initial commpit
sean
parents:
diff changeset
831 if callable(path): path, callback = None, path
e1264e58154c initial commpit
sean
parents:
diff changeset
832 plugins = makelist(apply)
e1264e58154c initial commpit
sean
parents:
diff changeset
833 skiplist = makelist(skip)
e1264e58154c initial commpit
sean
parents:
diff changeset
834
e1264e58154c initial commpit
sean
parents:
diff changeset
835 def decorator(callback):
e1264e58154c initial commpit
sean
parents:
diff changeset
836 if isinstance(callback, basestring): callback = load(callback)
e1264e58154c initial commpit
sean
parents:
diff changeset
837 for rule in makelist(path) or yieldroutes(callback):
e1264e58154c initial commpit
sean
parents:
diff changeset
838 for verb in makelist(method):
e1264e58154c initial commpit
sean
parents:
diff changeset
839 verb = verb.upper()
e1264e58154c initial commpit
sean
parents:
diff changeset
840 route = Route(self, rule, verb, callback,
e1264e58154c initial commpit
sean
parents:
diff changeset
841 name=name,
e1264e58154c initial commpit
sean
parents:
diff changeset
842 plugins=plugins,
e1264e58154c initial commpit
sean
parents:
diff changeset
843 skiplist=skiplist, **config)
e1264e58154c initial commpit
sean
parents:
diff changeset
844 self.add_route(route)
e1264e58154c initial commpit
sean
parents:
diff changeset
845 return callback
e1264e58154c initial commpit
sean
parents:
diff changeset
846
e1264e58154c initial commpit
sean
parents:
diff changeset
847 return decorator(callback) if callback else decorator
e1264e58154c initial commpit
sean
parents:
diff changeset
848
e1264e58154c initial commpit
sean
parents:
diff changeset
849 def get(self, path=None, method='GET', **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
850 """ Equals :meth:`route`. """
e1264e58154c initial commpit
sean
parents:
diff changeset
851 return self.route(path, method, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
852
e1264e58154c initial commpit
sean
parents:
diff changeset
853 def post(self, path=None, method='POST', **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
854 """ Equals :meth:`route` with a ``POST`` method parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
855 return self.route(path, method, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
856
e1264e58154c initial commpit
sean
parents:
diff changeset
857 def put(self, path=None, method='PUT', **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
858 """ Equals :meth:`route` with a ``PUT`` method parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
859 return self.route(path, method, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
860
e1264e58154c initial commpit
sean
parents:
diff changeset
861 def delete(self, path=None, method='DELETE', **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
862 """ Equals :meth:`route` with a ``DELETE`` method parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
863 return self.route(path, method, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
864
e1264e58154c initial commpit
sean
parents:
diff changeset
865 def patch(self, path=None, method='PATCH', **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
866 """ Equals :meth:`route` with a ``PATCH`` method parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
867 return self.route(path, method, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
868
e1264e58154c initial commpit
sean
parents:
diff changeset
869 def error(self, code=500):
e1264e58154c initial commpit
sean
parents:
diff changeset
870 """ Decorator: Register an output handler for a HTTP error code"""
e1264e58154c initial commpit
sean
parents:
diff changeset
871
e1264e58154c initial commpit
sean
parents:
diff changeset
872 def wrapper(handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
873 self.error_handler[int(code)] = handler
e1264e58154c initial commpit
sean
parents:
diff changeset
874 return handler
e1264e58154c initial commpit
sean
parents:
diff changeset
875
e1264e58154c initial commpit
sean
parents:
diff changeset
876 return wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
877
e1264e58154c initial commpit
sean
parents:
diff changeset
878 def default_error_handler(self, res):
e1264e58154c initial commpit
sean
parents:
diff changeset
879 return tob(template(ERROR_PAGE_TEMPLATE, e=res))
e1264e58154c initial commpit
sean
parents:
diff changeset
880
e1264e58154c initial commpit
sean
parents:
diff changeset
881 def _handle(self, environ):
e1264e58154c initial commpit
sean
parents:
diff changeset
882 path = environ['bottle.raw_path'] = environ['PATH_INFO']
e1264e58154c initial commpit
sean
parents:
diff changeset
883 if py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
884 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
885 environ['PATH_INFO'] = path.encode('latin1').decode('utf8')
e1264e58154c initial commpit
sean
parents:
diff changeset
886 except UnicodeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
887 return HTTPError(400, 'Invalid path string. Expected UTF-8')
e1264e58154c initial commpit
sean
parents:
diff changeset
888
e1264e58154c initial commpit
sean
parents:
diff changeset
889 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
890 environ['bottle.app'] = self
e1264e58154c initial commpit
sean
parents:
diff changeset
891 request.bind(environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
892 response.bind()
e1264e58154c initial commpit
sean
parents:
diff changeset
893 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
894 self.trigger_hook('before_request')
e1264e58154c initial commpit
sean
parents:
diff changeset
895 route, args = self.router.match(environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
896 environ['route.handle'] = route
e1264e58154c initial commpit
sean
parents:
diff changeset
897 environ['bottle.route'] = route
e1264e58154c initial commpit
sean
parents:
diff changeset
898 environ['route.url_args'] = args
e1264e58154c initial commpit
sean
parents:
diff changeset
899 return route.call(**args)
e1264e58154c initial commpit
sean
parents:
diff changeset
900 finally:
e1264e58154c initial commpit
sean
parents:
diff changeset
901 self.trigger_hook('after_request')
e1264e58154c initial commpit
sean
parents:
diff changeset
902 except HTTPResponse:
e1264e58154c initial commpit
sean
parents:
diff changeset
903 return _e()
e1264e58154c initial commpit
sean
parents:
diff changeset
904 except RouteReset:
e1264e58154c initial commpit
sean
parents:
diff changeset
905 route.reset()
e1264e58154c initial commpit
sean
parents:
diff changeset
906 return self._handle(environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
907 except (KeyboardInterrupt, SystemExit, MemoryError):
e1264e58154c initial commpit
sean
parents:
diff changeset
908 raise
e1264e58154c initial commpit
sean
parents:
diff changeset
909 except Exception:
e1264e58154c initial commpit
sean
parents:
diff changeset
910 if not self.catchall: raise
e1264e58154c initial commpit
sean
parents:
diff changeset
911 stacktrace = format_exc()
e1264e58154c initial commpit
sean
parents:
diff changeset
912 environ['wsgi.errors'].write(stacktrace)
e1264e58154c initial commpit
sean
parents:
diff changeset
913 return HTTPError(500, "Internal Server Error", _e(), stacktrace)
e1264e58154c initial commpit
sean
parents:
diff changeset
914
e1264e58154c initial commpit
sean
parents:
diff changeset
915 def _cast(self, out, peek=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
916 """ Try to convert the parameter into something WSGI compatible and set
e1264e58154c initial commpit
sean
parents:
diff changeset
917 correct HTTP headers when possible.
e1264e58154c initial commpit
sean
parents:
diff changeset
918 Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
e1264e58154c initial commpit
sean
parents:
diff changeset
919 iterable of strings and iterable of unicodes
e1264e58154c initial commpit
sean
parents:
diff changeset
920 """
e1264e58154c initial commpit
sean
parents:
diff changeset
921
e1264e58154c initial commpit
sean
parents:
diff changeset
922 # Empty output is done here
e1264e58154c initial commpit
sean
parents:
diff changeset
923 if not out:
e1264e58154c initial commpit
sean
parents:
diff changeset
924 if 'Content-Length' not in response:
e1264e58154c initial commpit
sean
parents:
diff changeset
925 response['Content-Length'] = 0
e1264e58154c initial commpit
sean
parents:
diff changeset
926 return []
e1264e58154c initial commpit
sean
parents:
diff changeset
927 # Join lists of byte or unicode strings. Mixed lists are NOT supported
e1264e58154c initial commpit
sean
parents:
diff changeset
928 if isinstance(out, (tuple, list))\
e1264e58154c initial commpit
sean
parents:
diff changeset
929 and isinstance(out[0], (bytes, unicode)):
e1264e58154c initial commpit
sean
parents:
diff changeset
930 out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
e1264e58154c initial commpit
sean
parents:
diff changeset
931 # Encode unicode strings
e1264e58154c initial commpit
sean
parents:
diff changeset
932 if isinstance(out, unicode):
e1264e58154c initial commpit
sean
parents:
diff changeset
933 out = out.encode(response.charset)
e1264e58154c initial commpit
sean
parents:
diff changeset
934 # Byte Strings are just returned
e1264e58154c initial commpit
sean
parents:
diff changeset
935 if isinstance(out, bytes):
e1264e58154c initial commpit
sean
parents:
diff changeset
936 if 'Content-Length' not in response:
e1264e58154c initial commpit
sean
parents:
diff changeset
937 response['Content-Length'] = len(out)
e1264e58154c initial commpit
sean
parents:
diff changeset
938 return [out]
e1264e58154c initial commpit
sean
parents:
diff changeset
939 # HTTPError or HTTPException (recursive, because they may wrap anything)
e1264e58154c initial commpit
sean
parents:
diff changeset
940 # TODO: Handle these explicitly in handle() or make them iterable.
e1264e58154c initial commpit
sean
parents:
diff changeset
941 if isinstance(out, HTTPError):
e1264e58154c initial commpit
sean
parents:
diff changeset
942 out.apply(response)
e1264e58154c initial commpit
sean
parents:
diff changeset
943 out = self.error_handler.get(out.status_code,
e1264e58154c initial commpit
sean
parents:
diff changeset
944 self.default_error_handler)(out)
e1264e58154c initial commpit
sean
parents:
diff changeset
945 return self._cast(out)
e1264e58154c initial commpit
sean
parents:
diff changeset
946 if isinstance(out, HTTPResponse):
e1264e58154c initial commpit
sean
parents:
diff changeset
947 out.apply(response)
e1264e58154c initial commpit
sean
parents:
diff changeset
948 return self._cast(out.body)
e1264e58154c initial commpit
sean
parents:
diff changeset
949
e1264e58154c initial commpit
sean
parents:
diff changeset
950 # File-like objects.
e1264e58154c initial commpit
sean
parents:
diff changeset
951 if hasattr(out, 'read'):
e1264e58154c initial commpit
sean
parents:
diff changeset
952 if 'wsgi.file_wrapper' in request.environ:
e1264e58154c initial commpit
sean
parents:
diff changeset
953 return request.environ['wsgi.file_wrapper'](out)
e1264e58154c initial commpit
sean
parents:
diff changeset
954 elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
e1264e58154c initial commpit
sean
parents:
diff changeset
955 return WSGIFileWrapper(out)
e1264e58154c initial commpit
sean
parents:
diff changeset
956
e1264e58154c initial commpit
sean
parents:
diff changeset
957 # Handle Iterables. We peek into them to detect their inner type.
e1264e58154c initial commpit
sean
parents:
diff changeset
958 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
959 iout = iter(out)
e1264e58154c initial commpit
sean
parents:
diff changeset
960 first = next(iout)
e1264e58154c initial commpit
sean
parents:
diff changeset
961 while not first:
e1264e58154c initial commpit
sean
parents:
diff changeset
962 first = next(iout)
e1264e58154c initial commpit
sean
parents:
diff changeset
963 except StopIteration:
e1264e58154c initial commpit
sean
parents:
diff changeset
964 return self._cast('')
e1264e58154c initial commpit
sean
parents:
diff changeset
965 except HTTPResponse:
e1264e58154c initial commpit
sean
parents:
diff changeset
966 first = _e()
e1264e58154c initial commpit
sean
parents:
diff changeset
967 except (KeyboardInterrupt, SystemExit, MemoryError):
e1264e58154c initial commpit
sean
parents:
diff changeset
968 raise
e1264e58154c initial commpit
sean
parents:
diff changeset
969 except:
e1264e58154c initial commpit
sean
parents:
diff changeset
970 if not self.catchall: raise
e1264e58154c initial commpit
sean
parents:
diff changeset
971 first = HTTPError(500, 'Unhandled exception', _e(), format_exc())
e1264e58154c initial commpit
sean
parents:
diff changeset
972
e1264e58154c initial commpit
sean
parents:
diff changeset
973 # These are the inner types allowed in iterator or generator objects.
e1264e58154c initial commpit
sean
parents:
diff changeset
974 if isinstance(first, HTTPResponse):
e1264e58154c initial commpit
sean
parents:
diff changeset
975 return self._cast(first)
e1264e58154c initial commpit
sean
parents:
diff changeset
976 elif isinstance(first, bytes):
e1264e58154c initial commpit
sean
parents:
diff changeset
977 new_iter = itertools.chain([first], iout)
e1264e58154c initial commpit
sean
parents:
diff changeset
978 elif isinstance(first, unicode):
e1264e58154c initial commpit
sean
parents:
diff changeset
979 encoder = lambda x: x.encode(response.charset)
e1264e58154c initial commpit
sean
parents:
diff changeset
980 new_iter = imap(encoder, itertools.chain([first], iout))
e1264e58154c initial commpit
sean
parents:
diff changeset
981 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
982 msg = 'Unsupported response type: %s' % type(first)
e1264e58154c initial commpit
sean
parents:
diff changeset
983 return self._cast(HTTPError(500, msg))
e1264e58154c initial commpit
sean
parents:
diff changeset
984 if hasattr(out, 'close'):
e1264e58154c initial commpit
sean
parents:
diff changeset
985 new_iter = _closeiter(new_iter, out.close)
e1264e58154c initial commpit
sean
parents:
diff changeset
986 return new_iter
e1264e58154c initial commpit
sean
parents:
diff changeset
987
e1264e58154c initial commpit
sean
parents:
diff changeset
988 def wsgi(self, environ, start_response):
e1264e58154c initial commpit
sean
parents:
diff changeset
989 """ The bottle WSGI-interface. """
e1264e58154c initial commpit
sean
parents:
diff changeset
990 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
991 out = self._cast(self._handle(environ))
e1264e58154c initial commpit
sean
parents:
diff changeset
992 # rfc2616 section 4.3
e1264e58154c initial commpit
sean
parents:
diff changeset
993 if response._status_code in (100, 101, 204, 304)\
e1264e58154c initial commpit
sean
parents:
diff changeset
994 or environ['REQUEST_METHOD'] == 'HEAD':
e1264e58154c initial commpit
sean
parents:
diff changeset
995 if hasattr(out, 'close'): out.close()
e1264e58154c initial commpit
sean
parents:
diff changeset
996 out = []
e1264e58154c initial commpit
sean
parents:
diff changeset
997 start_response(response._status_line, response.headerlist)
e1264e58154c initial commpit
sean
parents:
diff changeset
998 return out
e1264e58154c initial commpit
sean
parents:
diff changeset
999 except (KeyboardInterrupt, SystemExit, MemoryError):
e1264e58154c initial commpit
sean
parents:
diff changeset
1000 raise
e1264e58154c initial commpit
sean
parents:
diff changeset
1001 except:
e1264e58154c initial commpit
sean
parents:
diff changeset
1002 if not self.catchall: raise
e1264e58154c initial commpit
sean
parents:
diff changeset
1003 err = '<h1>Critical error while processing request: %s</h1>' \
e1264e58154c initial commpit
sean
parents:
diff changeset
1004 % html_escape(environ.get('PATH_INFO', '/'))
e1264e58154c initial commpit
sean
parents:
diff changeset
1005 if DEBUG:
e1264e58154c initial commpit
sean
parents:
diff changeset
1006 err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
e1264e58154c initial commpit
sean
parents:
diff changeset
1007 '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
e1264e58154c initial commpit
sean
parents:
diff changeset
1008 % (html_escape(repr(_e())), html_escape(format_exc()))
e1264e58154c initial commpit
sean
parents:
diff changeset
1009 environ['wsgi.errors'].write(err)
e1264e58154c initial commpit
sean
parents:
diff changeset
1010 headers = [('Content-Type', 'text/html; charset=UTF-8')]
e1264e58154c initial commpit
sean
parents:
diff changeset
1011 start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
e1264e58154c initial commpit
sean
parents:
diff changeset
1012 return [tob(err)]
e1264e58154c initial commpit
sean
parents:
diff changeset
1013
e1264e58154c initial commpit
sean
parents:
diff changeset
1014 def __call__(self, environ, start_response):
e1264e58154c initial commpit
sean
parents:
diff changeset
1015 """ Each instance of :class:'Bottle' is a WSGI application. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1016 return self.wsgi(environ, start_response)
e1264e58154c initial commpit
sean
parents:
diff changeset
1017
e1264e58154c initial commpit
sean
parents:
diff changeset
1018 def __enter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1019 """ Use this application as default for all module-level shortcuts. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1020 default_app.push(self)
e1264e58154c initial commpit
sean
parents:
diff changeset
1021 return self
e1264e58154c initial commpit
sean
parents:
diff changeset
1022
e1264e58154c initial commpit
sean
parents:
diff changeset
1023 def __exit__(self, exc_type, exc_value, traceback):
e1264e58154c initial commpit
sean
parents:
diff changeset
1024 default_app.pop()
e1264e58154c initial commpit
sean
parents:
diff changeset
1025
e1264e58154c initial commpit
sean
parents:
diff changeset
1026 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1027 # HTTP and WSGI Tools ##########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1028 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1029
e1264e58154c initial commpit
sean
parents:
diff changeset
1030
e1264e58154c initial commpit
sean
parents:
diff changeset
1031 class BaseRequest(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1032 """ A wrapper for WSGI environment dictionaries that adds a lot of
e1264e58154c initial commpit
sean
parents:
diff changeset
1033 convenient access methods and properties. Most of them are read-only.
e1264e58154c initial commpit
sean
parents:
diff changeset
1034
e1264e58154c initial commpit
sean
parents:
diff changeset
1035 Adding new attributes to a request actually adds them to the environ
e1264e58154c initial commpit
sean
parents:
diff changeset
1036 dictionary (as 'bottle.request.ext.<name>'). This is the recommended
e1264e58154c initial commpit
sean
parents:
diff changeset
1037 way to store and access request-specific data.
e1264e58154c initial commpit
sean
parents:
diff changeset
1038 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1039
e1264e58154c initial commpit
sean
parents:
diff changeset
1040 __slots__ = ('environ', )
e1264e58154c initial commpit
sean
parents:
diff changeset
1041
e1264e58154c initial commpit
sean
parents:
diff changeset
1042 #: Maximum size of memory buffer for :attr:`body` in bytes.
e1264e58154c initial commpit
sean
parents:
diff changeset
1043 MEMFILE_MAX = 102400
e1264e58154c initial commpit
sean
parents:
diff changeset
1044
e1264e58154c initial commpit
sean
parents:
diff changeset
1045 def __init__(self, environ=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1046 """ Wrap a WSGI environ dictionary. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1047 #: The wrapped WSGI environ dictionary. This is the only real attribute.
e1264e58154c initial commpit
sean
parents:
diff changeset
1048 #: All other attributes actually are read-only properties.
e1264e58154c initial commpit
sean
parents:
diff changeset
1049 self.environ = {} if environ is None else environ
e1264e58154c initial commpit
sean
parents:
diff changeset
1050 self.environ['bottle.request'] = self
e1264e58154c initial commpit
sean
parents:
diff changeset
1051
e1264e58154c initial commpit
sean
parents:
diff changeset
1052 @DictProperty('environ', 'bottle.app', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1053 def app(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1054 """ Bottle application handling this request. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1055 raise RuntimeError('This request is not connected to an application.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1056
e1264e58154c initial commpit
sean
parents:
diff changeset
1057 @DictProperty('environ', 'bottle.route', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1058 def route(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1059 """ The bottle :class:`Route` object that matches this request. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1060 raise RuntimeError('This request is not connected to a route.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1061
e1264e58154c initial commpit
sean
parents:
diff changeset
1062 @DictProperty('environ', 'route.url_args', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1063 def url_args(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1064 """ The arguments extracted from the URL. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1065 raise RuntimeError('This request is not connected to a route.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1066
e1264e58154c initial commpit
sean
parents:
diff changeset
1067 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1068 def path(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1069 """ The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
e1264e58154c initial commpit
sean
parents:
diff changeset
1070 broken clients and avoid the "empty path" edge case). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1071 return '/' + self.environ.get('PATH_INFO', '').lstrip('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
1072
e1264e58154c initial commpit
sean
parents:
diff changeset
1073 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1074 def method(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1075 """ The ``REQUEST_METHOD`` value as an uppercase string. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1076 return self.environ.get('REQUEST_METHOD', 'GET').upper()
e1264e58154c initial commpit
sean
parents:
diff changeset
1077
e1264e58154c initial commpit
sean
parents:
diff changeset
1078 @DictProperty('environ', 'bottle.request.headers', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1079 def headers(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1080 """ A :class:`WSGIHeaderDict` that provides case-insensitive access to
e1264e58154c initial commpit
sean
parents:
diff changeset
1081 HTTP request headers. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1082 return WSGIHeaderDict(self.environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
1083
e1264e58154c initial commpit
sean
parents:
diff changeset
1084 def get_header(self, name, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1085 """ Return the value of a request header, or a given default value. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1086 return self.headers.get(name, default)
e1264e58154c initial commpit
sean
parents:
diff changeset
1087
e1264e58154c initial commpit
sean
parents:
diff changeset
1088 @DictProperty('environ', 'bottle.request.cookies', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1089 def cookies(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1090 """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
e1264e58154c initial commpit
sean
parents:
diff changeset
1091 decoded. Use :meth:`get_cookie` if you expect signed cookies. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1092 cookies = SimpleCookie(self.environ.get('HTTP_COOKIE', '')).values()
e1264e58154c initial commpit
sean
parents:
diff changeset
1093 return FormsDict((c.key, c.value) for c in cookies)
e1264e58154c initial commpit
sean
parents:
diff changeset
1094
e1264e58154c initial commpit
sean
parents:
diff changeset
1095 def get_cookie(self, key, default=None, secret=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1096 """ Return the content of a cookie. To read a `Signed Cookie`, the
e1264e58154c initial commpit
sean
parents:
diff changeset
1097 `secret` must match the one used to create the cookie (see
e1264e58154c initial commpit
sean
parents:
diff changeset
1098 :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
e1264e58154c initial commpit
sean
parents:
diff changeset
1099 cookie or wrong signature), return a default value. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1100 value = self.cookies.get(key)
e1264e58154c initial commpit
sean
parents:
diff changeset
1101 if secret and value:
e1264e58154c initial commpit
sean
parents:
diff changeset
1102 dec = cookie_decode(value, secret) # (key, value) tuple or None
e1264e58154c initial commpit
sean
parents:
diff changeset
1103 return dec[1] if dec and dec[0] == key else default
e1264e58154c initial commpit
sean
parents:
diff changeset
1104 return value or default
e1264e58154c initial commpit
sean
parents:
diff changeset
1105
e1264e58154c initial commpit
sean
parents:
diff changeset
1106 @DictProperty('environ', 'bottle.request.query', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1107 def query(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1108 """ The :attr:`query_string` parsed into a :class:`FormsDict`. These
e1264e58154c initial commpit
sean
parents:
diff changeset
1109 values are sometimes called "URL arguments" or "GET parameters", but
e1264e58154c initial commpit
sean
parents:
diff changeset
1110 not to be confused with "URL wildcards" as they are provided by the
e1264e58154c initial commpit
sean
parents:
diff changeset
1111 :class:`Router`. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1112 get = self.environ['bottle.get'] = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1113 pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
e1264e58154c initial commpit
sean
parents:
diff changeset
1114 for key, value in pairs:
e1264e58154c initial commpit
sean
parents:
diff changeset
1115 get[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1116 return get
e1264e58154c initial commpit
sean
parents:
diff changeset
1117
e1264e58154c initial commpit
sean
parents:
diff changeset
1118 @DictProperty('environ', 'bottle.request.forms', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1119 def forms(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1120 """ Form values parsed from an `url-encoded` or `multipart/form-data`
e1264e58154c initial commpit
sean
parents:
diff changeset
1121 encoded POST or PUT request body. The result is returned as a
e1264e58154c initial commpit
sean
parents:
diff changeset
1122 :class:`FormsDict`. All keys and values are strings. File uploads
e1264e58154c initial commpit
sean
parents:
diff changeset
1123 are stored separately in :attr:`files`. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1124 forms = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1125 for name, item in self.POST.allitems():
e1264e58154c initial commpit
sean
parents:
diff changeset
1126 if not isinstance(item, FileUpload):
e1264e58154c initial commpit
sean
parents:
diff changeset
1127 forms[name] = item
e1264e58154c initial commpit
sean
parents:
diff changeset
1128 return forms
e1264e58154c initial commpit
sean
parents:
diff changeset
1129
e1264e58154c initial commpit
sean
parents:
diff changeset
1130 @DictProperty('environ', 'bottle.request.params', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1131 def params(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1132 """ A :class:`FormsDict` with the combined values of :attr:`query` and
e1264e58154c initial commpit
sean
parents:
diff changeset
1133 :attr:`forms`. File uploads are stored in :attr:`files`. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1134 params = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1135 for key, value in self.query.allitems():
e1264e58154c initial commpit
sean
parents:
diff changeset
1136 params[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1137 for key, value in self.forms.allitems():
e1264e58154c initial commpit
sean
parents:
diff changeset
1138 params[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1139 return params
e1264e58154c initial commpit
sean
parents:
diff changeset
1140
e1264e58154c initial commpit
sean
parents:
diff changeset
1141 @DictProperty('environ', 'bottle.request.files', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1142 def files(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1143 """ File uploads parsed from `multipart/form-data` encoded POST or PUT
e1264e58154c initial commpit
sean
parents:
diff changeset
1144 request body. The values are instances of :class:`FileUpload`.
e1264e58154c initial commpit
sean
parents:
diff changeset
1145
e1264e58154c initial commpit
sean
parents:
diff changeset
1146 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1147 files = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1148 for name, item in self.POST.allitems():
e1264e58154c initial commpit
sean
parents:
diff changeset
1149 if isinstance(item, FileUpload):
e1264e58154c initial commpit
sean
parents:
diff changeset
1150 files[name] = item
e1264e58154c initial commpit
sean
parents:
diff changeset
1151 return files
e1264e58154c initial commpit
sean
parents:
diff changeset
1152
e1264e58154c initial commpit
sean
parents:
diff changeset
1153 @DictProperty('environ', 'bottle.request.json', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1154 def json(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1155 """ If the ``Content-Type`` header is ``application/json``, this
e1264e58154c initial commpit
sean
parents:
diff changeset
1156 property holds the parsed content of the request body. Only requests
e1264e58154c initial commpit
sean
parents:
diff changeset
1157 smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
e1264e58154c initial commpit
sean
parents:
diff changeset
1158 exhaustion. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1159 ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
1160 if ctype == 'application/json':
e1264e58154c initial commpit
sean
parents:
diff changeset
1161 b = self._get_body_string()
e1264e58154c initial commpit
sean
parents:
diff changeset
1162 if not b:
e1264e58154c initial commpit
sean
parents:
diff changeset
1163 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
1164 return json_loads(b)
e1264e58154c initial commpit
sean
parents:
diff changeset
1165 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
1166
e1264e58154c initial commpit
sean
parents:
diff changeset
1167 def _iter_body(self, read, bufsize):
e1264e58154c initial commpit
sean
parents:
diff changeset
1168 maxread = max(0, self.content_length)
e1264e58154c initial commpit
sean
parents:
diff changeset
1169 while maxread:
e1264e58154c initial commpit
sean
parents:
diff changeset
1170 part = read(min(maxread, bufsize))
e1264e58154c initial commpit
sean
parents:
diff changeset
1171 if not part: break
e1264e58154c initial commpit
sean
parents:
diff changeset
1172 yield part
e1264e58154c initial commpit
sean
parents:
diff changeset
1173 maxread -= len(part)
e1264e58154c initial commpit
sean
parents:
diff changeset
1174
e1264e58154c initial commpit
sean
parents:
diff changeset
1175 @staticmethod
e1264e58154c initial commpit
sean
parents:
diff changeset
1176 def _iter_chunked(read, bufsize):
e1264e58154c initial commpit
sean
parents:
diff changeset
1177 err = HTTPError(400, 'Error while parsing chunked transfer body.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1178 rn, sem, bs = tob('\r\n'), tob(';'), tob('')
e1264e58154c initial commpit
sean
parents:
diff changeset
1179 while True:
e1264e58154c initial commpit
sean
parents:
diff changeset
1180 header = read(1)
e1264e58154c initial commpit
sean
parents:
diff changeset
1181 while header[-2:] != rn:
e1264e58154c initial commpit
sean
parents:
diff changeset
1182 c = read(1)
e1264e58154c initial commpit
sean
parents:
diff changeset
1183 header += c
e1264e58154c initial commpit
sean
parents:
diff changeset
1184 if not c: raise err
e1264e58154c initial commpit
sean
parents:
diff changeset
1185 if len(header) > bufsize: raise err
e1264e58154c initial commpit
sean
parents:
diff changeset
1186 size, _, _ = header.partition(sem)
e1264e58154c initial commpit
sean
parents:
diff changeset
1187 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1188 maxread = int(tonat(size.strip()), 16)
e1264e58154c initial commpit
sean
parents:
diff changeset
1189 except ValueError:
e1264e58154c initial commpit
sean
parents:
diff changeset
1190 raise err
e1264e58154c initial commpit
sean
parents:
diff changeset
1191 if maxread == 0: break
e1264e58154c initial commpit
sean
parents:
diff changeset
1192 buff = bs
e1264e58154c initial commpit
sean
parents:
diff changeset
1193 while maxread > 0:
e1264e58154c initial commpit
sean
parents:
diff changeset
1194 if not buff:
e1264e58154c initial commpit
sean
parents:
diff changeset
1195 buff = read(min(maxread, bufsize))
e1264e58154c initial commpit
sean
parents:
diff changeset
1196 part, buff = buff[:maxread], buff[maxread:]
e1264e58154c initial commpit
sean
parents:
diff changeset
1197 if not part: raise err
e1264e58154c initial commpit
sean
parents:
diff changeset
1198 yield part
e1264e58154c initial commpit
sean
parents:
diff changeset
1199 maxread -= len(part)
e1264e58154c initial commpit
sean
parents:
diff changeset
1200 if read(2) != rn:
e1264e58154c initial commpit
sean
parents:
diff changeset
1201 raise err
e1264e58154c initial commpit
sean
parents:
diff changeset
1202
e1264e58154c initial commpit
sean
parents:
diff changeset
1203 @DictProperty('environ', 'bottle.request.body', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1204 def _body(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1205 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1206 read_func = self.environ['wsgi.input'].read
e1264e58154c initial commpit
sean
parents:
diff changeset
1207 except KeyError:
e1264e58154c initial commpit
sean
parents:
diff changeset
1208 self.environ['wsgi.input'] = BytesIO()
e1264e58154c initial commpit
sean
parents:
diff changeset
1209 return self.environ['wsgi.input']
e1264e58154c initial commpit
sean
parents:
diff changeset
1210 body_iter = self._iter_chunked if self.chunked else self._iter_body
e1264e58154c initial commpit
sean
parents:
diff changeset
1211 body, body_size, is_temp_file = BytesIO(), 0, False
e1264e58154c initial commpit
sean
parents:
diff changeset
1212 for part in body_iter(read_func, self.MEMFILE_MAX):
e1264e58154c initial commpit
sean
parents:
diff changeset
1213 body.write(part)
e1264e58154c initial commpit
sean
parents:
diff changeset
1214 body_size += len(part)
e1264e58154c initial commpit
sean
parents:
diff changeset
1215 if not is_temp_file and body_size > self.MEMFILE_MAX:
e1264e58154c initial commpit
sean
parents:
diff changeset
1216 body, tmp = TemporaryFile(mode='w+b'), body
e1264e58154c initial commpit
sean
parents:
diff changeset
1217 body.write(tmp.getvalue())
e1264e58154c initial commpit
sean
parents:
diff changeset
1218 del tmp
e1264e58154c initial commpit
sean
parents:
diff changeset
1219 is_temp_file = True
e1264e58154c initial commpit
sean
parents:
diff changeset
1220 self.environ['wsgi.input'] = body
e1264e58154c initial commpit
sean
parents:
diff changeset
1221 body.seek(0)
e1264e58154c initial commpit
sean
parents:
diff changeset
1222 return body
e1264e58154c initial commpit
sean
parents:
diff changeset
1223
e1264e58154c initial commpit
sean
parents:
diff changeset
1224 def _get_body_string(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1225 """ read body until content-length or MEMFILE_MAX into a string. Raise
e1264e58154c initial commpit
sean
parents:
diff changeset
1226 HTTPError(413) on requests that are to large. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1227 clen = self.content_length
e1264e58154c initial commpit
sean
parents:
diff changeset
1228 if clen > self.MEMFILE_MAX:
e1264e58154c initial commpit
sean
parents:
diff changeset
1229 raise HTTPError(413, 'Request entity too large')
e1264e58154c initial commpit
sean
parents:
diff changeset
1230 if clen < 0: clen = self.MEMFILE_MAX + 1
e1264e58154c initial commpit
sean
parents:
diff changeset
1231 data = self.body.read(clen)
e1264e58154c initial commpit
sean
parents:
diff changeset
1232 if len(data) > self.MEMFILE_MAX: # Fail fast
e1264e58154c initial commpit
sean
parents:
diff changeset
1233 raise HTTPError(413, 'Request entity too large')
e1264e58154c initial commpit
sean
parents:
diff changeset
1234 return data
e1264e58154c initial commpit
sean
parents:
diff changeset
1235
e1264e58154c initial commpit
sean
parents:
diff changeset
1236 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1237 def body(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1238 """ The HTTP request body as a seek-able file-like object. Depending on
e1264e58154c initial commpit
sean
parents:
diff changeset
1239 :attr:`MEMFILE_MAX`, this is either a temporary file or a
e1264e58154c initial commpit
sean
parents:
diff changeset
1240 :class:`io.BytesIO` instance. Accessing this property for the first
e1264e58154c initial commpit
sean
parents:
diff changeset
1241 time reads and replaces the ``wsgi.input`` environ variable.
e1264e58154c initial commpit
sean
parents:
diff changeset
1242 Subsequent accesses just do a `seek(0)` on the file object. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1243 self._body.seek(0)
e1264e58154c initial commpit
sean
parents:
diff changeset
1244 return self._body
e1264e58154c initial commpit
sean
parents:
diff changeset
1245
e1264e58154c initial commpit
sean
parents:
diff changeset
1246 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1247 def chunked(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1248 """ True if Chunked transfer encoding was. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1249 return 'chunked' in self.environ.get(
e1264e58154c initial commpit
sean
parents:
diff changeset
1250 'HTTP_TRANSFER_ENCODING', '').lower()
e1264e58154c initial commpit
sean
parents:
diff changeset
1251
e1264e58154c initial commpit
sean
parents:
diff changeset
1252 #: An alias for :attr:`query`.
e1264e58154c initial commpit
sean
parents:
diff changeset
1253 GET = query
e1264e58154c initial commpit
sean
parents:
diff changeset
1254
e1264e58154c initial commpit
sean
parents:
diff changeset
1255 @DictProperty('environ', 'bottle.request.post', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1256 def POST(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1257 """ The values of :attr:`forms` and :attr:`files` combined into a single
e1264e58154c initial commpit
sean
parents:
diff changeset
1258 :class:`FormsDict`. Values are either strings (form values) or
e1264e58154c initial commpit
sean
parents:
diff changeset
1259 instances of :class:`cgi.FieldStorage` (file uploads).
e1264e58154c initial commpit
sean
parents:
diff changeset
1260 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1261 post = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1262 # We default to application/x-www-form-urlencoded for everything that
e1264e58154c initial commpit
sean
parents:
diff changeset
1263 # is not multipart and take the fast path (also: 3.1 workaround)
e1264e58154c initial commpit
sean
parents:
diff changeset
1264 if not self.content_type.startswith('multipart/'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1265 pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
e1264e58154c initial commpit
sean
parents:
diff changeset
1266 for key, value in pairs:
e1264e58154c initial commpit
sean
parents:
diff changeset
1267 post[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1268 return post
e1264e58154c initial commpit
sean
parents:
diff changeset
1269
e1264e58154c initial commpit
sean
parents:
diff changeset
1270 safe_env = {'QUERY_STRING': ''} # Build a safe environment for cgi
e1264e58154c initial commpit
sean
parents:
diff changeset
1271 for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1272 if key in self.environ: safe_env[key] = self.environ[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
1273 args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1274 if py31:
e1264e58154c initial commpit
sean
parents:
diff changeset
1275 args['fp'] = NCTextIOWrapper(args['fp'],
e1264e58154c initial commpit
sean
parents:
diff changeset
1276 encoding='utf8',
e1264e58154c initial commpit
sean
parents:
diff changeset
1277 newline='\n')
e1264e58154c initial commpit
sean
parents:
diff changeset
1278 elif py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
1279 args['encoding'] = 'utf8'
e1264e58154c initial commpit
sean
parents:
diff changeset
1280 data = cgi.FieldStorage(**args)
e1264e58154c initial commpit
sean
parents:
diff changeset
1281 self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394
e1264e58154c initial commpit
sean
parents:
diff changeset
1282 data = data.list or []
e1264e58154c initial commpit
sean
parents:
diff changeset
1283 for item in data:
e1264e58154c initial commpit
sean
parents:
diff changeset
1284 if item.filename:
e1264e58154c initial commpit
sean
parents:
diff changeset
1285 post[item.name] = FileUpload(item.file, item.name,
e1264e58154c initial commpit
sean
parents:
diff changeset
1286 item.filename, item.headers)
e1264e58154c initial commpit
sean
parents:
diff changeset
1287 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
1288 post[item.name] = item.value
e1264e58154c initial commpit
sean
parents:
diff changeset
1289 return post
e1264e58154c initial commpit
sean
parents:
diff changeset
1290
e1264e58154c initial commpit
sean
parents:
diff changeset
1291 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1292 def url(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1293 """ The full request URI including hostname and scheme. If your app
e1264e58154c initial commpit
sean
parents:
diff changeset
1294 lives behind a reverse proxy or load balancer and you get confusing
e1264e58154c initial commpit
sean
parents:
diff changeset
1295 results, make sure that the ``X-Forwarded-Host`` header is set
e1264e58154c initial commpit
sean
parents:
diff changeset
1296 correctly. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1297 return self.urlparts.geturl()
e1264e58154c initial commpit
sean
parents:
diff changeset
1298
e1264e58154c initial commpit
sean
parents:
diff changeset
1299 @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
e1264e58154c initial commpit
sean
parents:
diff changeset
1300 def urlparts(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1301 """ The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
e1264e58154c initial commpit
sean
parents:
diff changeset
1302 The tuple contains (scheme, host, path, query_string and fragment),
e1264e58154c initial commpit
sean
parents:
diff changeset
1303 but the fragment is always empty because it is not visible to the
e1264e58154c initial commpit
sean
parents:
diff changeset
1304 server. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1305 env = self.environ
e1264e58154c initial commpit
sean
parents:
diff changeset
1306 http = env.get('HTTP_X_FORWARDED_PROTO') \
e1264e58154c initial commpit
sean
parents:
diff changeset
1307 or env.get('wsgi.url_scheme', 'http')
e1264e58154c initial commpit
sean
parents:
diff changeset
1308 host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
e1264e58154c initial commpit
sean
parents:
diff changeset
1309 if not host:
e1264e58154c initial commpit
sean
parents:
diff changeset
1310 # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
e1264e58154c initial commpit
sean
parents:
diff changeset
1311 host = env.get('SERVER_NAME', '127.0.0.1')
e1264e58154c initial commpit
sean
parents:
diff changeset
1312 port = env.get('SERVER_PORT')
e1264e58154c initial commpit
sean
parents:
diff changeset
1313 if port and port != ('80' if http == 'http' else '443'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1314 host += ':' + port
e1264e58154c initial commpit
sean
parents:
diff changeset
1315 path = urlquote(self.fullpath)
e1264e58154c initial commpit
sean
parents:
diff changeset
1316 return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
e1264e58154c initial commpit
sean
parents:
diff changeset
1317
e1264e58154c initial commpit
sean
parents:
diff changeset
1318 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1319 def fullpath(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1320 """ Request path including :attr:`script_name` (if present). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1321 return urljoin(self.script_name, self.path.lstrip('/'))
e1264e58154c initial commpit
sean
parents:
diff changeset
1322
e1264e58154c initial commpit
sean
parents:
diff changeset
1323 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1324 def query_string(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1325 """ The raw :attr:`query` part of the URL (everything in between ``?``
e1264e58154c initial commpit
sean
parents:
diff changeset
1326 and ``#``) as a string. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1327 return self.environ.get('QUERY_STRING', '')
e1264e58154c initial commpit
sean
parents:
diff changeset
1328
e1264e58154c initial commpit
sean
parents:
diff changeset
1329 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1330 def script_name(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1331 """ The initial portion of the URL's `path` that was removed by a higher
e1264e58154c initial commpit
sean
parents:
diff changeset
1332 level (server or routing middleware) before the application was
e1264e58154c initial commpit
sean
parents:
diff changeset
1333 called. This script path is returned with leading and tailing
e1264e58154c initial commpit
sean
parents:
diff changeset
1334 slashes. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1335 script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
1336 return '/' + script_name + '/' if script_name else '/'
e1264e58154c initial commpit
sean
parents:
diff changeset
1337
e1264e58154c initial commpit
sean
parents:
diff changeset
1338 def path_shift(self, shift=1):
e1264e58154c initial commpit
sean
parents:
diff changeset
1339 """ Shift path segments from :attr:`path` to :attr:`script_name` and
e1264e58154c initial commpit
sean
parents:
diff changeset
1340 vice versa.
e1264e58154c initial commpit
sean
parents:
diff changeset
1341
e1264e58154c initial commpit
sean
parents:
diff changeset
1342 :param shift: The number of path segments to shift. May be negative
e1264e58154c initial commpit
sean
parents:
diff changeset
1343 to change the shift direction. (default: 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
1344 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1345 script, path = path_shift(self.environ.get('SCRIPT_NAME', '/'), self.path, shift)
e1264e58154c initial commpit
sean
parents:
diff changeset
1346 self['SCRIPT_NAME'], self['PATH_INFO'] = script, path
e1264e58154c initial commpit
sean
parents:
diff changeset
1347
e1264e58154c initial commpit
sean
parents:
diff changeset
1348 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1349 def content_length(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1350 """ The request body length as an integer. The client is responsible to
e1264e58154c initial commpit
sean
parents:
diff changeset
1351 set this header. Otherwise, the real length of the body is unknown
e1264e58154c initial commpit
sean
parents:
diff changeset
1352 and -1 is returned. In this case, :attr:`body` will be empty. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1353 return int(self.environ.get('CONTENT_LENGTH') or -1)
e1264e58154c initial commpit
sean
parents:
diff changeset
1354
e1264e58154c initial commpit
sean
parents:
diff changeset
1355 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1356 def content_type(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1357 """ The Content-Type header as a lowercase-string (default: empty). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1358 return self.environ.get('CONTENT_TYPE', '').lower()
e1264e58154c initial commpit
sean
parents:
diff changeset
1359
e1264e58154c initial commpit
sean
parents:
diff changeset
1360 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1361 def is_xhr(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1362 """ True if the request was triggered by a XMLHttpRequest. This only
e1264e58154c initial commpit
sean
parents:
diff changeset
1363 works with JavaScript libraries that support the `X-Requested-With`
e1264e58154c initial commpit
sean
parents:
diff changeset
1364 header (most of the popular libraries do). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1365 requested_with = self.environ.get('HTTP_X_REQUESTED_WITH', '')
e1264e58154c initial commpit
sean
parents:
diff changeset
1366 return requested_with.lower() == 'xmlhttprequest'
e1264e58154c initial commpit
sean
parents:
diff changeset
1367
e1264e58154c initial commpit
sean
parents:
diff changeset
1368 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1369 def is_ajax(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1370 """ Alias for :attr:`is_xhr`. "Ajax" is not the right term. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1371 return self.is_xhr
e1264e58154c initial commpit
sean
parents:
diff changeset
1372
e1264e58154c initial commpit
sean
parents:
diff changeset
1373 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1374 def auth(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1375 """ HTTP authentication data as a (user, password) tuple. This
e1264e58154c initial commpit
sean
parents:
diff changeset
1376 implementation currently supports basic (not digest) authentication
e1264e58154c initial commpit
sean
parents:
diff changeset
1377 only. If the authentication happened at a higher level (e.g. in the
e1264e58154c initial commpit
sean
parents:
diff changeset
1378 front web-server or a middleware), the password field is None, but
e1264e58154c initial commpit
sean
parents:
diff changeset
1379 the user field is looked up from the ``REMOTE_USER`` environ
e1264e58154c initial commpit
sean
parents:
diff changeset
1380 variable. On any errors, None is returned. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1381 basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION', ''))
e1264e58154c initial commpit
sean
parents:
diff changeset
1382 if basic: return basic
e1264e58154c initial commpit
sean
parents:
diff changeset
1383 ruser = self.environ.get('REMOTE_USER')
e1264e58154c initial commpit
sean
parents:
diff changeset
1384 if ruser: return (ruser, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
1385 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
1386
e1264e58154c initial commpit
sean
parents:
diff changeset
1387 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1388 def remote_route(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1389 """ A list of all IPs that were involved in this request, starting with
e1264e58154c initial commpit
sean
parents:
diff changeset
1390 the client IP and followed by zero or more proxies. This does only
e1264e58154c initial commpit
sean
parents:
diff changeset
1391 work if all proxies support the ```X-Forwarded-For`` header. Note
e1264e58154c initial commpit
sean
parents:
diff changeset
1392 that this information can be forged by malicious clients. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1393 proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
e1264e58154c initial commpit
sean
parents:
diff changeset
1394 if proxy: return [ip.strip() for ip in proxy.split(',')]
e1264e58154c initial commpit
sean
parents:
diff changeset
1395 remote = self.environ.get('REMOTE_ADDR')
e1264e58154c initial commpit
sean
parents:
diff changeset
1396 return [remote] if remote else []
e1264e58154c initial commpit
sean
parents:
diff changeset
1397
e1264e58154c initial commpit
sean
parents:
diff changeset
1398 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1399 def remote_addr(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1400 """ The client IP as a string. Note that this information can be forged
e1264e58154c initial commpit
sean
parents:
diff changeset
1401 by malicious clients. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1402 route = self.remote_route
e1264e58154c initial commpit
sean
parents:
diff changeset
1403 return route[0] if route else None
e1264e58154c initial commpit
sean
parents:
diff changeset
1404
e1264e58154c initial commpit
sean
parents:
diff changeset
1405 def copy(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1406 """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1407 return Request(self.environ.copy())
e1264e58154c initial commpit
sean
parents:
diff changeset
1408
e1264e58154c initial commpit
sean
parents:
diff changeset
1409 def get(self, value, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1410 return self.environ.get(value, default)
e1264e58154c initial commpit
sean
parents:
diff changeset
1411
e1264e58154c initial commpit
sean
parents:
diff changeset
1412 def __getitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1413 return self.environ[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
1414
e1264e58154c initial commpit
sean
parents:
diff changeset
1415 def __delitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1416 self[key] = ""
e1264e58154c initial commpit
sean
parents:
diff changeset
1417 del (self.environ[key])
e1264e58154c initial commpit
sean
parents:
diff changeset
1418
e1264e58154c initial commpit
sean
parents:
diff changeset
1419 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1420 return iter(self.environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
1421
e1264e58154c initial commpit
sean
parents:
diff changeset
1422 def __len__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1423 return len(self.environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
1424
e1264e58154c initial commpit
sean
parents:
diff changeset
1425 def keys(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1426 return self.environ.keys()
e1264e58154c initial commpit
sean
parents:
diff changeset
1427
e1264e58154c initial commpit
sean
parents:
diff changeset
1428 def __setitem__(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1429 """ Change an environ value and clear all caches that depend on it. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1430
e1264e58154c initial commpit
sean
parents:
diff changeset
1431 if self.environ.get('bottle.request.readonly'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1432 raise KeyError('The environ dictionary is read-only.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1433
e1264e58154c initial commpit
sean
parents:
diff changeset
1434 self.environ[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1435 todelete = ()
e1264e58154c initial commpit
sean
parents:
diff changeset
1436
e1264e58154c initial commpit
sean
parents:
diff changeset
1437 if key == 'wsgi.input':
e1264e58154c initial commpit
sean
parents:
diff changeset
1438 todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
e1264e58154c initial commpit
sean
parents:
diff changeset
1439 elif key == 'QUERY_STRING':
e1264e58154c initial commpit
sean
parents:
diff changeset
1440 todelete = ('query', 'params')
e1264e58154c initial commpit
sean
parents:
diff changeset
1441 elif key.startswith('HTTP_'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1442 todelete = ('headers', 'cookies')
e1264e58154c initial commpit
sean
parents:
diff changeset
1443
e1264e58154c initial commpit
sean
parents:
diff changeset
1444 for key in todelete:
e1264e58154c initial commpit
sean
parents:
diff changeset
1445 self.environ.pop('bottle.request.' + key, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
1446
e1264e58154c initial commpit
sean
parents:
diff changeset
1447 def __repr__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1448 return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
e1264e58154c initial commpit
sean
parents:
diff changeset
1449
e1264e58154c initial commpit
sean
parents:
diff changeset
1450 def __getattr__(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
1451 """ Search in self.environ for additional user defined attributes. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1452 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1453 var = self.environ['bottle.request.ext.%s' % name]
e1264e58154c initial commpit
sean
parents:
diff changeset
1454 return var.__get__(self) if hasattr(var, '__get__') else var
e1264e58154c initial commpit
sean
parents:
diff changeset
1455 except KeyError:
e1264e58154c initial commpit
sean
parents:
diff changeset
1456 raise AttributeError('Attribute %r not defined.' % name)
e1264e58154c initial commpit
sean
parents:
diff changeset
1457
e1264e58154c initial commpit
sean
parents:
diff changeset
1458 def __setattr__(self, name, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1459 if name == 'environ': return object.__setattr__(self, name, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1460 self.environ['bottle.request.ext.%s' % name] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1461
e1264e58154c initial commpit
sean
parents:
diff changeset
1462
e1264e58154c initial commpit
sean
parents:
diff changeset
1463 def _hkey(s):
e1264e58154c initial commpit
sean
parents:
diff changeset
1464 return s.title().replace('_', '-')
e1264e58154c initial commpit
sean
parents:
diff changeset
1465
e1264e58154c initial commpit
sean
parents:
diff changeset
1466
e1264e58154c initial commpit
sean
parents:
diff changeset
1467 class HeaderProperty(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1468 def __init__(self, name, reader=None, writer=str, default=''):
e1264e58154c initial commpit
sean
parents:
diff changeset
1469 self.name, self.default = name, default
e1264e58154c initial commpit
sean
parents:
diff changeset
1470 self.reader, self.writer = reader, writer
e1264e58154c initial commpit
sean
parents:
diff changeset
1471 self.__doc__ = 'Current value of the %r header.' % name.title()
e1264e58154c initial commpit
sean
parents:
diff changeset
1472
e1264e58154c initial commpit
sean
parents:
diff changeset
1473 def __get__(self, obj, _):
e1264e58154c initial commpit
sean
parents:
diff changeset
1474 if obj is None: return self
e1264e58154c initial commpit
sean
parents:
diff changeset
1475 value = obj.headers.get(self.name, self.default)
e1264e58154c initial commpit
sean
parents:
diff changeset
1476 return self.reader(value) if self.reader else value
e1264e58154c initial commpit
sean
parents:
diff changeset
1477
e1264e58154c initial commpit
sean
parents:
diff changeset
1478 def __set__(self, obj, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1479 obj.headers[self.name] = self.writer(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1480
e1264e58154c initial commpit
sean
parents:
diff changeset
1481 def __delete__(self, obj):
e1264e58154c initial commpit
sean
parents:
diff changeset
1482 del obj.headers[self.name]
e1264e58154c initial commpit
sean
parents:
diff changeset
1483
e1264e58154c initial commpit
sean
parents:
diff changeset
1484
e1264e58154c initial commpit
sean
parents:
diff changeset
1485 class BaseResponse(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1486 """ Storage class for a response body as well as headers and cookies.
e1264e58154c initial commpit
sean
parents:
diff changeset
1487
e1264e58154c initial commpit
sean
parents:
diff changeset
1488 This class does support dict-like case-insensitive item-access to
e1264e58154c initial commpit
sean
parents:
diff changeset
1489 headers, but is NOT a dict. Most notably, iterating over a response
e1264e58154c initial commpit
sean
parents:
diff changeset
1490 yields parts of the body and not the headers.
e1264e58154c initial commpit
sean
parents:
diff changeset
1491
e1264e58154c initial commpit
sean
parents:
diff changeset
1492 :param body: The response body as one of the supported types.
e1264e58154c initial commpit
sean
parents:
diff changeset
1493 :param status: Either an HTTP status code (e.g. 200) or a status line
e1264e58154c initial commpit
sean
parents:
diff changeset
1494 including the reason phrase (e.g. '200 OK').
e1264e58154c initial commpit
sean
parents:
diff changeset
1495 :param headers: A dictionary or a list of name-value pairs.
e1264e58154c initial commpit
sean
parents:
diff changeset
1496
e1264e58154c initial commpit
sean
parents:
diff changeset
1497 Additional keyword arguments are added to the list of headers.
e1264e58154c initial commpit
sean
parents:
diff changeset
1498 Underscores in the header name are replaced with dashes.
e1264e58154c initial commpit
sean
parents:
diff changeset
1499 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1500
e1264e58154c initial commpit
sean
parents:
diff changeset
1501 default_status = 200
e1264e58154c initial commpit
sean
parents:
diff changeset
1502 default_content_type = 'text/html; charset=UTF-8'
e1264e58154c initial commpit
sean
parents:
diff changeset
1503
e1264e58154c initial commpit
sean
parents:
diff changeset
1504 # Header blacklist for specific response codes
e1264e58154c initial commpit
sean
parents:
diff changeset
1505 # (rfc2616 section 10.2.3 and 10.3.5)
e1264e58154c initial commpit
sean
parents:
diff changeset
1506 bad_headers = {
e1264e58154c initial commpit
sean
parents:
diff changeset
1507 204: set(('Content-Type', )),
e1264e58154c initial commpit
sean
parents:
diff changeset
1508 304: set(('Allow', 'Content-Encoding', 'Content-Language',
e1264e58154c initial commpit
sean
parents:
diff changeset
1509 'Content-Length', 'Content-Range', 'Content-Type',
e1264e58154c initial commpit
sean
parents:
diff changeset
1510 'Content-Md5', 'Last-Modified'))
e1264e58154c initial commpit
sean
parents:
diff changeset
1511 }
e1264e58154c initial commpit
sean
parents:
diff changeset
1512
e1264e58154c initial commpit
sean
parents:
diff changeset
1513 def __init__(self, body='', status=None, headers=None, **more_headers):
e1264e58154c initial commpit
sean
parents:
diff changeset
1514 self._cookies = None
e1264e58154c initial commpit
sean
parents:
diff changeset
1515 self._headers = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
1516 self.body = body
e1264e58154c initial commpit
sean
parents:
diff changeset
1517 self.status = status or self.default_status
e1264e58154c initial commpit
sean
parents:
diff changeset
1518 if headers:
e1264e58154c initial commpit
sean
parents:
diff changeset
1519 if isinstance(headers, dict):
e1264e58154c initial commpit
sean
parents:
diff changeset
1520 headers = headers.items()
e1264e58154c initial commpit
sean
parents:
diff changeset
1521 for name, value in headers:
e1264e58154c initial commpit
sean
parents:
diff changeset
1522 self.add_header(name, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1523 if more_headers:
e1264e58154c initial commpit
sean
parents:
diff changeset
1524 for name, value in more_headers.items():
e1264e58154c initial commpit
sean
parents:
diff changeset
1525 self.add_header(name, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1526
e1264e58154c initial commpit
sean
parents:
diff changeset
1527 def copy(self, cls=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1528 """ Returns a copy of self. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1529 cls = cls or BaseResponse
e1264e58154c initial commpit
sean
parents:
diff changeset
1530 assert issubclass(cls, BaseResponse)
e1264e58154c initial commpit
sean
parents:
diff changeset
1531 copy = cls()
e1264e58154c initial commpit
sean
parents:
diff changeset
1532 copy.status = self.status
e1264e58154c initial commpit
sean
parents:
diff changeset
1533 copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
e1264e58154c initial commpit
sean
parents:
diff changeset
1534 if self._cookies:
e1264e58154c initial commpit
sean
parents:
diff changeset
1535 copy._cookies = SimpleCookie()
e1264e58154c initial commpit
sean
parents:
diff changeset
1536 copy._cookies.load(self._cookies.output(header=''))
e1264e58154c initial commpit
sean
parents:
diff changeset
1537 return copy
e1264e58154c initial commpit
sean
parents:
diff changeset
1538
e1264e58154c initial commpit
sean
parents:
diff changeset
1539 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1540 return iter(self.body)
e1264e58154c initial commpit
sean
parents:
diff changeset
1541
e1264e58154c initial commpit
sean
parents:
diff changeset
1542 def close(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1543 if hasattr(self.body, 'close'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1544 self.body.close()
e1264e58154c initial commpit
sean
parents:
diff changeset
1545
e1264e58154c initial commpit
sean
parents:
diff changeset
1546 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1547 def status_line(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1548 """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
e1264e58154c initial commpit
sean
parents:
diff changeset
1549 return self._status_line
e1264e58154c initial commpit
sean
parents:
diff changeset
1550
e1264e58154c initial commpit
sean
parents:
diff changeset
1551 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1552 def status_code(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1553 """ The HTTP status code as an integer (e.g. 404)."""
e1264e58154c initial commpit
sean
parents:
diff changeset
1554 return self._status_code
e1264e58154c initial commpit
sean
parents:
diff changeset
1555
e1264e58154c initial commpit
sean
parents:
diff changeset
1556 def _set_status(self, status):
e1264e58154c initial commpit
sean
parents:
diff changeset
1557 if isinstance(status, int):
e1264e58154c initial commpit
sean
parents:
diff changeset
1558 code, status = status, _HTTP_STATUS_LINES.get(status)
e1264e58154c initial commpit
sean
parents:
diff changeset
1559 elif ' ' in status:
e1264e58154c initial commpit
sean
parents:
diff changeset
1560 status = status.strip()
e1264e58154c initial commpit
sean
parents:
diff changeset
1561 code = int(status.split()[0])
e1264e58154c initial commpit
sean
parents:
diff changeset
1562 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
1563 raise ValueError('String status line without a reason phrase.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1564 if not 100 <= code <= 999:
e1264e58154c initial commpit
sean
parents:
diff changeset
1565 raise ValueError('Status code out of range.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1566 self._status_code = code
e1264e58154c initial commpit
sean
parents:
diff changeset
1567 self._status_line = str(status or ('%d Unknown' % code))
e1264e58154c initial commpit
sean
parents:
diff changeset
1568
e1264e58154c initial commpit
sean
parents:
diff changeset
1569 def _get_status(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1570 return self._status_line
e1264e58154c initial commpit
sean
parents:
diff changeset
1571
e1264e58154c initial commpit
sean
parents:
diff changeset
1572 status = property(
e1264e58154c initial commpit
sean
parents:
diff changeset
1573 _get_status, _set_status, None,
e1264e58154c initial commpit
sean
parents:
diff changeset
1574 ''' A writeable property to change the HTTP response status. It accepts
e1264e58154c initial commpit
sean
parents:
diff changeset
1575 either a numeric code (100-999) or a string with a custom reason
e1264e58154c initial commpit
sean
parents:
diff changeset
1576 phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
e1264e58154c initial commpit
sean
parents:
diff changeset
1577 :data:`status_code` are updated accordingly. The return value is
e1264e58154c initial commpit
sean
parents:
diff changeset
1578 always a status string. ''')
e1264e58154c initial commpit
sean
parents:
diff changeset
1579 del _get_status, _set_status
e1264e58154c initial commpit
sean
parents:
diff changeset
1580
e1264e58154c initial commpit
sean
parents:
diff changeset
1581 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1582 def headers(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1583 """ An instance of :class:`HeaderDict`, a case-insensitive dict-like
e1264e58154c initial commpit
sean
parents:
diff changeset
1584 view on the response headers. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1585 hdict = HeaderDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
1586 hdict.dict = self._headers
e1264e58154c initial commpit
sean
parents:
diff changeset
1587 return hdict
e1264e58154c initial commpit
sean
parents:
diff changeset
1588
e1264e58154c initial commpit
sean
parents:
diff changeset
1589 def __contains__(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
1590 return _hkey(name) in self._headers
e1264e58154c initial commpit
sean
parents:
diff changeset
1591
e1264e58154c initial commpit
sean
parents:
diff changeset
1592 def __delitem__(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
1593 del self._headers[_hkey(name)]
e1264e58154c initial commpit
sean
parents:
diff changeset
1594
e1264e58154c initial commpit
sean
parents:
diff changeset
1595 def __getitem__(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
1596 return self._headers[_hkey(name)][-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
1597
e1264e58154c initial commpit
sean
parents:
diff changeset
1598 def __setitem__(self, name, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1599 self._headers[_hkey(name)] = [value if isinstance(value, unicode) else
e1264e58154c initial commpit
sean
parents:
diff changeset
1600 str(value)]
e1264e58154c initial commpit
sean
parents:
diff changeset
1601
e1264e58154c initial commpit
sean
parents:
diff changeset
1602 def get_header(self, name, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1603 """ Return the value of a previously defined header. If there is no
e1264e58154c initial commpit
sean
parents:
diff changeset
1604 header with that name, return a default value. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1605 return self._headers.get(_hkey(name), [default])[-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
1606
e1264e58154c initial commpit
sean
parents:
diff changeset
1607 def set_header(self, name, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1608 """ Create a new response header, replacing any previously defined
e1264e58154c initial commpit
sean
parents:
diff changeset
1609 headers with the same name. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1610 self._headers[_hkey(name)] = [value if isinstance(value, unicode)
e1264e58154c initial commpit
sean
parents:
diff changeset
1611 else str(value)]
e1264e58154c initial commpit
sean
parents:
diff changeset
1612
e1264e58154c initial commpit
sean
parents:
diff changeset
1613 def add_header(self, name, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1614 """ Add an additional response header, not removing duplicates. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1615 self._headers.setdefault(_hkey(name), []).append(
e1264e58154c initial commpit
sean
parents:
diff changeset
1616 value if isinstance(value, unicode) else str(value))
e1264e58154c initial commpit
sean
parents:
diff changeset
1617
e1264e58154c initial commpit
sean
parents:
diff changeset
1618 def iter_headers(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1619 """ Yield (header, value) tuples, skipping headers that are not
e1264e58154c initial commpit
sean
parents:
diff changeset
1620 allowed with the current response status code. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1621 return self.headerlist
e1264e58154c initial commpit
sean
parents:
diff changeset
1622
e1264e58154c initial commpit
sean
parents:
diff changeset
1623 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1624 def headerlist(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1625 """ WSGI conform list of (header, value) tuples. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1626 out = []
e1264e58154c initial commpit
sean
parents:
diff changeset
1627 headers = list(self._headers.items())
e1264e58154c initial commpit
sean
parents:
diff changeset
1628 if 'Content-Type' not in self._headers:
e1264e58154c initial commpit
sean
parents:
diff changeset
1629 headers.append(('Content-Type', [self.default_content_type]))
e1264e58154c initial commpit
sean
parents:
diff changeset
1630 if self._status_code in self.bad_headers:
e1264e58154c initial commpit
sean
parents:
diff changeset
1631 bad_headers = self.bad_headers[self._status_code]
e1264e58154c initial commpit
sean
parents:
diff changeset
1632 headers = [h for h in headers if h[0] not in bad_headers]
e1264e58154c initial commpit
sean
parents:
diff changeset
1633 out += [(name, val) for (name, vals) in headers for val in vals]
e1264e58154c initial commpit
sean
parents:
diff changeset
1634 if self._cookies:
e1264e58154c initial commpit
sean
parents:
diff changeset
1635 for c in self._cookies.values():
e1264e58154c initial commpit
sean
parents:
diff changeset
1636 out.append(('Set-Cookie', c.OutputString()))
e1264e58154c initial commpit
sean
parents:
diff changeset
1637 if py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
1638 return [(k, v.encode('utf8').decode('latin1')) for (k, v) in out]
e1264e58154c initial commpit
sean
parents:
diff changeset
1639 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
1640 return [(k, v.encode('utf8') if isinstance(v, unicode) else v)
e1264e58154c initial commpit
sean
parents:
diff changeset
1641 for (k, v) in out]
e1264e58154c initial commpit
sean
parents:
diff changeset
1642
e1264e58154c initial commpit
sean
parents:
diff changeset
1643 content_type = HeaderProperty('Content-Type')
e1264e58154c initial commpit
sean
parents:
diff changeset
1644 content_length = HeaderProperty('Content-Length', reader=int)
e1264e58154c initial commpit
sean
parents:
diff changeset
1645 expires = HeaderProperty(
e1264e58154c initial commpit
sean
parents:
diff changeset
1646 'Expires',
e1264e58154c initial commpit
sean
parents:
diff changeset
1647 reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
e1264e58154c initial commpit
sean
parents:
diff changeset
1648 writer=lambda x: http_date(x))
e1264e58154c initial commpit
sean
parents:
diff changeset
1649
e1264e58154c initial commpit
sean
parents:
diff changeset
1650 @property
e1264e58154c initial commpit
sean
parents:
diff changeset
1651 def charset(self, default='UTF-8'):
e1264e58154c initial commpit
sean
parents:
diff changeset
1652 """ Return the charset specified in the content-type header (default: utf8). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1653 if 'charset=' in self.content_type:
e1264e58154c initial commpit
sean
parents:
diff changeset
1654 return self.content_type.split('charset=')[-1].split(';')[0].strip()
e1264e58154c initial commpit
sean
parents:
diff changeset
1655 return default
e1264e58154c initial commpit
sean
parents:
diff changeset
1656
e1264e58154c initial commpit
sean
parents:
diff changeset
1657 def set_cookie(self, name, value, secret=None, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
1658 """ Create a new cookie or replace an old one. If the `secret` parameter is
e1264e58154c initial commpit
sean
parents:
diff changeset
1659 set, create a `Signed Cookie` (described below).
e1264e58154c initial commpit
sean
parents:
diff changeset
1660
e1264e58154c initial commpit
sean
parents:
diff changeset
1661 :param name: the name of the cookie.
e1264e58154c initial commpit
sean
parents:
diff changeset
1662 :param value: the value of the cookie.
e1264e58154c initial commpit
sean
parents:
diff changeset
1663 :param secret: a signature key required for signed cookies.
e1264e58154c initial commpit
sean
parents:
diff changeset
1664
e1264e58154c initial commpit
sean
parents:
diff changeset
1665 Additionally, this method accepts all RFC 2109 attributes that are
e1264e58154c initial commpit
sean
parents:
diff changeset
1666 supported by :class:`cookie.Morsel`, including:
e1264e58154c initial commpit
sean
parents:
diff changeset
1667
e1264e58154c initial commpit
sean
parents:
diff changeset
1668 :param max_age: maximum age in seconds. (default: None)
e1264e58154c initial commpit
sean
parents:
diff changeset
1669 :param expires: a datetime object or UNIX timestamp. (default: None)
e1264e58154c initial commpit
sean
parents:
diff changeset
1670 :param domain: the domain that is allowed to read the cookie.
e1264e58154c initial commpit
sean
parents:
diff changeset
1671 (default: current domain)
e1264e58154c initial commpit
sean
parents:
diff changeset
1672 :param path: limits the cookie to a given path (default: current path)
e1264e58154c initial commpit
sean
parents:
diff changeset
1673 :param secure: limit the cookie to HTTPS connections (default: off).
e1264e58154c initial commpit
sean
parents:
diff changeset
1674 :param httponly: prevents client-side javascript to read this cookie
e1264e58154c initial commpit
sean
parents:
diff changeset
1675 (default: off, requires Python 2.6 or newer).
e1264e58154c initial commpit
sean
parents:
diff changeset
1676
e1264e58154c initial commpit
sean
parents:
diff changeset
1677 If neither `expires` nor `max_age` is set (default), the cookie will
e1264e58154c initial commpit
sean
parents:
diff changeset
1678 expire at the end of the browser session (as soon as the browser
e1264e58154c initial commpit
sean
parents:
diff changeset
1679 window is closed).
e1264e58154c initial commpit
sean
parents:
diff changeset
1680
e1264e58154c initial commpit
sean
parents:
diff changeset
1681 Signed cookies may store any pickle-able object and are
e1264e58154c initial commpit
sean
parents:
diff changeset
1682 cryptographically signed to prevent manipulation. Keep in mind that
e1264e58154c initial commpit
sean
parents:
diff changeset
1683 cookies are limited to 4kb in most browsers.
e1264e58154c initial commpit
sean
parents:
diff changeset
1684
e1264e58154c initial commpit
sean
parents:
diff changeset
1685 Warning: Signed cookies are not encrypted (the client can still see
e1264e58154c initial commpit
sean
parents:
diff changeset
1686 the content) and not copy-protected (the client can restore an old
e1264e58154c initial commpit
sean
parents:
diff changeset
1687 cookie). The main intention is to make pickling and unpickling
e1264e58154c initial commpit
sean
parents:
diff changeset
1688 save, not to store secret information at client side.
e1264e58154c initial commpit
sean
parents:
diff changeset
1689 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1690 if not self._cookies:
e1264e58154c initial commpit
sean
parents:
diff changeset
1691 self._cookies = SimpleCookie()
e1264e58154c initial commpit
sean
parents:
diff changeset
1692
e1264e58154c initial commpit
sean
parents:
diff changeset
1693 if secret:
e1264e58154c initial commpit
sean
parents:
diff changeset
1694 value = touni(cookie_encode((name, value), secret))
e1264e58154c initial commpit
sean
parents:
diff changeset
1695 elif not isinstance(value, basestring):
e1264e58154c initial commpit
sean
parents:
diff changeset
1696 raise TypeError('Secret key missing for non-string Cookie.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1697
e1264e58154c initial commpit
sean
parents:
diff changeset
1698 if len(value) > 4096: raise ValueError('Cookie value to long.')
e1264e58154c initial commpit
sean
parents:
diff changeset
1699 self._cookies[name] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1700
e1264e58154c initial commpit
sean
parents:
diff changeset
1701 for key, value in options.items():
e1264e58154c initial commpit
sean
parents:
diff changeset
1702 if key == 'max_age':
e1264e58154c initial commpit
sean
parents:
diff changeset
1703 if isinstance(value, timedelta):
e1264e58154c initial commpit
sean
parents:
diff changeset
1704 value = value.seconds + value.days * 24 * 3600
e1264e58154c initial commpit
sean
parents:
diff changeset
1705 if key == 'expires':
e1264e58154c initial commpit
sean
parents:
diff changeset
1706 if isinstance(value, (datedate, datetime)):
e1264e58154c initial commpit
sean
parents:
diff changeset
1707 value = value.timetuple()
e1264e58154c initial commpit
sean
parents:
diff changeset
1708 elif isinstance(value, (int, float)):
e1264e58154c initial commpit
sean
parents:
diff changeset
1709 value = time.gmtime(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1710 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1711 if key in ('secure', 'httponly') and not value:
e1264e58154c initial commpit
sean
parents:
diff changeset
1712 continue
e1264e58154c initial commpit
sean
parents:
diff changeset
1713 self._cookies[name][key.replace('_', '-')] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1714
e1264e58154c initial commpit
sean
parents:
diff changeset
1715 def delete_cookie(self, key, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
1716 """ Delete a cookie. Be sure to use the same `domain` and `path`
e1264e58154c initial commpit
sean
parents:
diff changeset
1717 settings as used to create the cookie. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1718 kwargs['max_age'] = -1
e1264e58154c initial commpit
sean
parents:
diff changeset
1719 kwargs['expires'] = 0
e1264e58154c initial commpit
sean
parents:
diff changeset
1720 self.set_cookie(key, '', **kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
1721
e1264e58154c initial commpit
sean
parents:
diff changeset
1722 def __repr__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1723 out = ''
e1264e58154c initial commpit
sean
parents:
diff changeset
1724 for name, value in self.headerlist:
e1264e58154c initial commpit
sean
parents:
diff changeset
1725 out += '%s: %s\n' % (name.title(), value.strip())
e1264e58154c initial commpit
sean
parents:
diff changeset
1726 return out
e1264e58154c initial commpit
sean
parents:
diff changeset
1727
e1264e58154c initial commpit
sean
parents:
diff changeset
1728
e1264e58154c initial commpit
sean
parents:
diff changeset
1729 def _local_property():
e1264e58154c initial commpit
sean
parents:
diff changeset
1730 ls = threading.local()
e1264e58154c initial commpit
sean
parents:
diff changeset
1731
e1264e58154c initial commpit
sean
parents:
diff changeset
1732 def fget(_):
e1264e58154c initial commpit
sean
parents:
diff changeset
1733 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1734 return ls.var
e1264e58154c initial commpit
sean
parents:
diff changeset
1735 except AttributeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
1736 raise RuntimeError("Request context not initialized.")
e1264e58154c initial commpit
sean
parents:
diff changeset
1737
e1264e58154c initial commpit
sean
parents:
diff changeset
1738 def fset(_, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1739 ls.var = value
e1264e58154c initial commpit
sean
parents:
diff changeset
1740
e1264e58154c initial commpit
sean
parents:
diff changeset
1741 def fdel(_):
e1264e58154c initial commpit
sean
parents:
diff changeset
1742 del ls.var
e1264e58154c initial commpit
sean
parents:
diff changeset
1743
e1264e58154c initial commpit
sean
parents:
diff changeset
1744 return property(fget, fset, fdel, 'Thread-local property')
e1264e58154c initial commpit
sean
parents:
diff changeset
1745
e1264e58154c initial commpit
sean
parents:
diff changeset
1746
e1264e58154c initial commpit
sean
parents:
diff changeset
1747 class LocalRequest(BaseRequest):
e1264e58154c initial commpit
sean
parents:
diff changeset
1748 """ A thread-local subclass of :class:`BaseRequest` with a different
e1264e58154c initial commpit
sean
parents:
diff changeset
1749 set of attributes for each thread. There is usually only one global
e1264e58154c initial commpit
sean
parents:
diff changeset
1750 instance of this class (:data:`request`). If accessed during a
e1264e58154c initial commpit
sean
parents:
diff changeset
1751 request/response cycle, this instance always refers to the *current*
e1264e58154c initial commpit
sean
parents:
diff changeset
1752 request (even on a multithreaded server). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1753 bind = BaseRequest.__init__
e1264e58154c initial commpit
sean
parents:
diff changeset
1754 environ = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1755
e1264e58154c initial commpit
sean
parents:
diff changeset
1756
e1264e58154c initial commpit
sean
parents:
diff changeset
1757 class LocalResponse(BaseResponse):
e1264e58154c initial commpit
sean
parents:
diff changeset
1758 """ A thread-local subclass of :class:`BaseResponse` with a different
e1264e58154c initial commpit
sean
parents:
diff changeset
1759 set of attributes for each thread. There is usually only one global
e1264e58154c initial commpit
sean
parents:
diff changeset
1760 instance of this class (:data:`response`). Its attributes are used
e1264e58154c initial commpit
sean
parents:
diff changeset
1761 to build the HTTP response at the end of the request/response cycle.
e1264e58154c initial commpit
sean
parents:
diff changeset
1762 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1763 bind = BaseResponse.__init__
e1264e58154c initial commpit
sean
parents:
diff changeset
1764 _status_line = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1765 _status_code = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1766 _cookies = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1767 _headers = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1768 body = _local_property()
e1264e58154c initial commpit
sean
parents:
diff changeset
1769
e1264e58154c initial commpit
sean
parents:
diff changeset
1770
e1264e58154c initial commpit
sean
parents:
diff changeset
1771 Request = BaseRequest
e1264e58154c initial commpit
sean
parents:
diff changeset
1772 Response = BaseResponse
e1264e58154c initial commpit
sean
parents:
diff changeset
1773
e1264e58154c initial commpit
sean
parents:
diff changeset
1774
e1264e58154c initial commpit
sean
parents:
diff changeset
1775 class HTTPResponse(Response, BottleException):
e1264e58154c initial commpit
sean
parents:
diff changeset
1776 def __init__(self, body='', status=None, headers=None, **more_headers):
e1264e58154c initial commpit
sean
parents:
diff changeset
1777 super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
e1264e58154c initial commpit
sean
parents:
diff changeset
1778
e1264e58154c initial commpit
sean
parents:
diff changeset
1779 def apply(self, other):
e1264e58154c initial commpit
sean
parents:
diff changeset
1780 other._status_code = self._status_code
e1264e58154c initial commpit
sean
parents:
diff changeset
1781 other._status_line = self._status_line
e1264e58154c initial commpit
sean
parents:
diff changeset
1782 other._headers = self._headers
e1264e58154c initial commpit
sean
parents:
diff changeset
1783 other._cookies = self._cookies
e1264e58154c initial commpit
sean
parents:
diff changeset
1784 other.body = self.body
e1264e58154c initial commpit
sean
parents:
diff changeset
1785
e1264e58154c initial commpit
sean
parents:
diff changeset
1786
e1264e58154c initial commpit
sean
parents:
diff changeset
1787 class HTTPError(HTTPResponse):
e1264e58154c initial commpit
sean
parents:
diff changeset
1788 default_status = 500
e1264e58154c initial commpit
sean
parents:
diff changeset
1789
e1264e58154c initial commpit
sean
parents:
diff changeset
1790 def __init__(self,
e1264e58154c initial commpit
sean
parents:
diff changeset
1791 status=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
1792 body=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
1793 exception=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
1794 traceback=None, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
1795 self.exception = exception
e1264e58154c initial commpit
sean
parents:
diff changeset
1796 self.traceback = traceback
e1264e58154c initial commpit
sean
parents:
diff changeset
1797 super(HTTPError, self).__init__(body, status, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
1798
e1264e58154c initial commpit
sean
parents:
diff changeset
1799 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1800 # Plugins ######################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1801 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1802
e1264e58154c initial commpit
sean
parents:
diff changeset
1803
e1264e58154c initial commpit
sean
parents:
diff changeset
1804 class PluginError(BottleException):
e1264e58154c initial commpit
sean
parents:
diff changeset
1805 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
1806
e1264e58154c initial commpit
sean
parents:
diff changeset
1807
e1264e58154c initial commpit
sean
parents:
diff changeset
1808 class JSONPlugin(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1809 name = 'json'
e1264e58154c initial commpit
sean
parents:
diff changeset
1810 api = 2
e1264e58154c initial commpit
sean
parents:
diff changeset
1811
e1264e58154c initial commpit
sean
parents:
diff changeset
1812 def __init__(self, json_dumps=json_dumps):
e1264e58154c initial commpit
sean
parents:
diff changeset
1813 self.json_dumps = json_dumps
e1264e58154c initial commpit
sean
parents:
diff changeset
1814
e1264e58154c initial commpit
sean
parents:
diff changeset
1815 def apply(self, callback, _):
e1264e58154c initial commpit
sean
parents:
diff changeset
1816 dumps = self.json_dumps
e1264e58154c initial commpit
sean
parents:
diff changeset
1817 if not dumps: return callback
e1264e58154c initial commpit
sean
parents:
diff changeset
1818
e1264e58154c initial commpit
sean
parents:
diff changeset
1819 def wrapper(*a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
1820 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1821 rv = callback(*a, **ka)
e1264e58154c initial commpit
sean
parents:
diff changeset
1822 except HTTPError:
e1264e58154c initial commpit
sean
parents:
diff changeset
1823 rv = _e()
e1264e58154c initial commpit
sean
parents:
diff changeset
1824
e1264e58154c initial commpit
sean
parents:
diff changeset
1825 if isinstance(rv, dict):
e1264e58154c initial commpit
sean
parents:
diff changeset
1826 #Attempt to serialize, raises exception on failure
e1264e58154c initial commpit
sean
parents:
diff changeset
1827 json_response = dumps(rv)
e1264e58154c initial commpit
sean
parents:
diff changeset
1828 #Set content type only if serialization successful
e1264e58154c initial commpit
sean
parents:
diff changeset
1829 response.content_type = 'application/json'
e1264e58154c initial commpit
sean
parents:
diff changeset
1830 return json_response
e1264e58154c initial commpit
sean
parents:
diff changeset
1831 elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
e1264e58154c initial commpit
sean
parents:
diff changeset
1832 rv.body = dumps(rv.body)
e1264e58154c initial commpit
sean
parents:
diff changeset
1833 rv.content_type = 'application/json'
e1264e58154c initial commpit
sean
parents:
diff changeset
1834 return rv
e1264e58154c initial commpit
sean
parents:
diff changeset
1835
e1264e58154c initial commpit
sean
parents:
diff changeset
1836 return wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
1837
e1264e58154c initial commpit
sean
parents:
diff changeset
1838
e1264e58154c initial commpit
sean
parents:
diff changeset
1839 class TemplatePlugin(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1840 """ This plugin applies the :func:`view` decorator to all routes with a
e1264e58154c initial commpit
sean
parents:
diff changeset
1841 `template` config parameter. If the parameter is a tuple, the second
e1264e58154c initial commpit
sean
parents:
diff changeset
1842 element must be a dict with additional options (e.g. `template_engine`)
e1264e58154c initial commpit
sean
parents:
diff changeset
1843 or default variables for the template. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1844 name = 'template'
e1264e58154c initial commpit
sean
parents:
diff changeset
1845 api = 2
e1264e58154c initial commpit
sean
parents:
diff changeset
1846
e1264e58154c initial commpit
sean
parents:
diff changeset
1847 def apply(self, callback, route):
e1264e58154c initial commpit
sean
parents:
diff changeset
1848 conf = route.config.get('template')
e1264e58154c initial commpit
sean
parents:
diff changeset
1849 if isinstance(conf, (tuple, list)) and len(conf) == 2:
e1264e58154c initial commpit
sean
parents:
diff changeset
1850 return view(conf[0], **conf[1])(callback)
e1264e58154c initial commpit
sean
parents:
diff changeset
1851 elif isinstance(conf, str):
e1264e58154c initial commpit
sean
parents:
diff changeset
1852 return view(conf)(callback)
e1264e58154c initial commpit
sean
parents:
diff changeset
1853 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
1854 return callback
e1264e58154c initial commpit
sean
parents:
diff changeset
1855
e1264e58154c initial commpit
sean
parents:
diff changeset
1856
e1264e58154c initial commpit
sean
parents:
diff changeset
1857 #: Not a plugin, but part of the plugin API. TODO: Find a better place.
e1264e58154c initial commpit
sean
parents:
diff changeset
1858 class _ImportRedirect(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
1859 def __init__(self, name, impmask):
e1264e58154c initial commpit
sean
parents:
diff changeset
1860 """ Create a virtual package that redirects imports (see PEP 302). """
e1264e58154c initial commpit
sean
parents:
diff changeset
1861 self.name = name
e1264e58154c initial commpit
sean
parents:
diff changeset
1862 self.impmask = impmask
e1264e58154c initial commpit
sean
parents:
diff changeset
1863 self.module = sys.modules.setdefault(name, imp.new_module(name))
e1264e58154c initial commpit
sean
parents:
diff changeset
1864 self.module.__dict__.update({
e1264e58154c initial commpit
sean
parents:
diff changeset
1865 '__file__': __file__,
e1264e58154c initial commpit
sean
parents:
diff changeset
1866 '__path__': [],
e1264e58154c initial commpit
sean
parents:
diff changeset
1867 '__all__': [],
e1264e58154c initial commpit
sean
parents:
diff changeset
1868 '__loader__': self
e1264e58154c initial commpit
sean
parents:
diff changeset
1869 })
e1264e58154c initial commpit
sean
parents:
diff changeset
1870 sys.meta_path.append(self)
e1264e58154c initial commpit
sean
parents:
diff changeset
1871
e1264e58154c initial commpit
sean
parents:
diff changeset
1872 def find_module(self, fullname, path=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1873 if '.' not in fullname: return
e1264e58154c initial commpit
sean
parents:
diff changeset
1874 packname = fullname.rsplit('.', 1)[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
1875 if packname != self.name: return
e1264e58154c initial commpit
sean
parents:
diff changeset
1876 return self
e1264e58154c initial commpit
sean
parents:
diff changeset
1877
e1264e58154c initial commpit
sean
parents:
diff changeset
1878 def load_module(self, fullname):
e1264e58154c initial commpit
sean
parents:
diff changeset
1879 if fullname in sys.modules: return sys.modules[fullname]
e1264e58154c initial commpit
sean
parents:
diff changeset
1880 modname = fullname.rsplit('.', 1)[1]
e1264e58154c initial commpit
sean
parents:
diff changeset
1881 realname = self.impmask % modname
e1264e58154c initial commpit
sean
parents:
diff changeset
1882 __import__(realname)
e1264e58154c initial commpit
sean
parents:
diff changeset
1883 module = sys.modules[fullname] = sys.modules[realname]
e1264e58154c initial commpit
sean
parents:
diff changeset
1884 setattr(self.module, modname, module)
e1264e58154c initial commpit
sean
parents:
diff changeset
1885 module.__loader__ = self
e1264e58154c initial commpit
sean
parents:
diff changeset
1886 return module
e1264e58154c initial commpit
sean
parents:
diff changeset
1887
e1264e58154c initial commpit
sean
parents:
diff changeset
1888 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1889 # Common Utilities #############################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1890 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
1891
e1264e58154c initial commpit
sean
parents:
diff changeset
1892
e1264e58154c initial commpit
sean
parents:
diff changeset
1893 class MultiDict(DictMixin):
e1264e58154c initial commpit
sean
parents:
diff changeset
1894 """ This dict stores multiple values per key, but behaves exactly like a
e1264e58154c initial commpit
sean
parents:
diff changeset
1895 normal dict in that it returns only the newest value for any given key.
e1264e58154c initial commpit
sean
parents:
diff changeset
1896 There are special methods available to access the full list of values.
e1264e58154c initial commpit
sean
parents:
diff changeset
1897 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1898
e1264e58154c initial commpit
sean
parents:
diff changeset
1899 def __init__(self, *a, **k):
e1264e58154c initial commpit
sean
parents:
diff changeset
1900 self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
e1264e58154c initial commpit
sean
parents:
diff changeset
1901
e1264e58154c initial commpit
sean
parents:
diff changeset
1902 def __len__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1903 return len(self.dict)
e1264e58154c initial commpit
sean
parents:
diff changeset
1904
e1264e58154c initial commpit
sean
parents:
diff changeset
1905 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1906 return iter(self.dict)
e1264e58154c initial commpit
sean
parents:
diff changeset
1907
e1264e58154c initial commpit
sean
parents:
diff changeset
1908 def __contains__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1909 return key in self.dict
e1264e58154c initial commpit
sean
parents:
diff changeset
1910
e1264e58154c initial commpit
sean
parents:
diff changeset
1911 def __delitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1912 del self.dict[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
1913
e1264e58154c initial commpit
sean
parents:
diff changeset
1914 def __getitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1915 return self.dict[key][-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
1916
e1264e58154c initial commpit
sean
parents:
diff changeset
1917 def __setitem__(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1918 self.append(key, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1919
e1264e58154c initial commpit
sean
parents:
diff changeset
1920 def keys(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1921 return self.dict.keys()
e1264e58154c initial commpit
sean
parents:
diff changeset
1922
e1264e58154c initial commpit
sean
parents:
diff changeset
1923 if py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
1924
e1264e58154c initial commpit
sean
parents:
diff changeset
1925 def values(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1926 return (v[-1] for v in self.dict.values())
e1264e58154c initial commpit
sean
parents:
diff changeset
1927
e1264e58154c initial commpit
sean
parents:
diff changeset
1928 def items(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1929 return ((k, v[-1]) for k, v in self.dict.items())
e1264e58154c initial commpit
sean
parents:
diff changeset
1930
e1264e58154c initial commpit
sean
parents:
diff changeset
1931 def allitems(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1932 return ((k, v) for k, vl in self.dict.items() for v in vl)
e1264e58154c initial commpit
sean
parents:
diff changeset
1933
e1264e58154c initial commpit
sean
parents:
diff changeset
1934 iterkeys = keys
e1264e58154c initial commpit
sean
parents:
diff changeset
1935 itervalues = values
e1264e58154c initial commpit
sean
parents:
diff changeset
1936 iteritems = items
e1264e58154c initial commpit
sean
parents:
diff changeset
1937 iterallitems = allitems
e1264e58154c initial commpit
sean
parents:
diff changeset
1938
e1264e58154c initial commpit
sean
parents:
diff changeset
1939 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
1940
e1264e58154c initial commpit
sean
parents:
diff changeset
1941 def values(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1942 return [v[-1] for v in self.dict.values()]
e1264e58154c initial commpit
sean
parents:
diff changeset
1943
e1264e58154c initial commpit
sean
parents:
diff changeset
1944 def items(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1945 return [(k, v[-1]) for k, v in self.dict.items()]
e1264e58154c initial commpit
sean
parents:
diff changeset
1946
e1264e58154c initial commpit
sean
parents:
diff changeset
1947 def iterkeys(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1948 return self.dict.iterkeys()
e1264e58154c initial commpit
sean
parents:
diff changeset
1949
e1264e58154c initial commpit
sean
parents:
diff changeset
1950 def itervalues(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1951 return (v[-1] for v in self.dict.itervalues())
e1264e58154c initial commpit
sean
parents:
diff changeset
1952
e1264e58154c initial commpit
sean
parents:
diff changeset
1953 def iteritems(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1954 return ((k, v[-1]) for k, v in self.dict.iteritems())
e1264e58154c initial commpit
sean
parents:
diff changeset
1955
e1264e58154c initial commpit
sean
parents:
diff changeset
1956 def iterallitems(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1957 return ((k, v) for k, vl in self.dict.iteritems() for v in vl)
e1264e58154c initial commpit
sean
parents:
diff changeset
1958
e1264e58154c initial commpit
sean
parents:
diff changeset
1959 def allitems(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
1960 return [(k, v) for k, vl in self.dict.iteritems() for v in vl]
e1264e58154c initial commpit
sean
parents:
diff changeset
1961
e1264e58154c initial commpit
sean
parents:
diff changeset
1962 def get(self, key, default=None, index=-1, type=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
1963 """ Return the most recent value for a key.
e1264e58154c initial commpit
sean
parents:
diff changeset
1964
e1264e58154c initial commpit
sean
parents:
diff changeset
1965 :param default: The default value to be returned if the key is not
e1264e58154c initial commpit
sean
parents:
diff changeset
1966 present or the type conversion fails.
e1264e58154c initial commpit
sean
parents:
diff changeset
1967 :param index: An index for the list of available values.
e1264e58154c initial commpit
sean
parents:
diff changeset
1968 :param type: If defined, this callable is used to cast the value
e1264e58154c initial commpit
sean
parents:
diff changeset
1969 into a specific type. Exception are suppressed and result in
e1264e58154c initial commpit
sean
parents:
diff changeset
1970 the default value to be returned.
e1264e58154c initial commpit
sean
parents:
diff changeset
1971 """
e1264e58154c initial commpit
sean
parents:
diff changeset
1972 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
1973 val = self.dict[key][index]
e1264e58154c initial commpit
sean
parents:
diff changeset
1974 return type(val) if type else val
e1264e58154c initial commpit
sean
parents:
diff changeset
1975 except Exception:
e1264e58154c initial commpit
sean
parents:
diff changeset
1976 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
1977 return default
e1264e58154c initial commpit
sean
parents:
diff changeset
1978
e1264e58154c initial commpit
sean
parents:
diff changeset
1979 def append(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1980 """ Add a new value to the list of values for this key. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1981 self.dict.setdefault(key, []).append(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
1982
e1264e58154c initial commpit
sean
parents:
diff changeset
1983 def replace(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
1984 """ Replace the list of values with a single value. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1985 self.dict[key] = [value]
e1264e58154c initial commpit
sean
parents:
diff changeset
1986
e1264e58154c initial commpit
sean
parents:
diff changeset
1987 def getall(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
1988 """ Return a (possibly empty) list of values for a key. """
e1264e58154c initial commpit
sean
parents:
diff changeset
1989 return self.dict.get(key) or []
e1264e58154c initial commpit
sean
parents:
diff changeset
1990
e1264e58154c initial commpit
sean
parents:
diff changeset
1991 #: Aliases for WTForms to mimic other multi-dict APIs (Django)
e1264e58154c initial commpit
sean
parents:
diff changeset
1992 getone = get
e1264e58154c initial commpit
sean
parents:
diff changeset
1993 getlist = getall
e1264e58154c initial commpit
sean
parents:
diff changeset
1994
e1264e58154c initial commpit
sean
parents:
diff changeset
1995
e1264e58154c initial commpit
sean
parents:
diff changeset
1996 class FormsDict(MultiDict):
e1264e58154c initial commpit
sean
parents:
diff changeset
1997 """ This :class:`MultiDict` subclass is used to store request form data.
e1264e58154c initial commpit
sean
parents:
diff changeset
1998 Additionally to the normal dict-like item access methods (which return
e1264e58154c initial commpit
sean
parents:
diff changeset
1999 unmodified data as native strings), this container also supports
e1264e58154c initial commpit
sean
parents:
diff changeset
2000 attribute-like access to its values. Attributes are automatically de-
e1264e58154c initial commpit
sean
parents:
diff changeset
2001 or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
e1264e58154c initial commpit
sean
parents:
diff changeset
2002 attributes default to an empty string. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2003
e1264e58154c initial commpit
sean
parents:
diff changeset
2004 #: Encoding used for attribute values.
e1264e58154c initial commpit
sean
parents:
diff changeset
2005 input_encoding = 'utf8'
e1264e58154c initial commpit
sean
parents:
diff changeset
2006 #: If true (default), unicode strings are first encoded with `latin1`
e1264e58154c initial commpit
sean
parents:
diff changeset
2007 #: and then decoded to match :attr:`input_encoding`.
e1264e58154c initial commpit
sean
parents:
diff changeset
2008 recode_unicode = True
e1264e58154c initial commpit
sean
parents:
diff changeset
2009
e1264e58154c initial commpit
sean
parents:
diff changeset
2010 def _fix(self, s, encoding=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2011 if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI
e1264e58154c initial commpit
sean
parents:
diff changeset
2012 return s.encode('latin1').decode(encoding or self.input_encoding)
e1264e58154c initial commpit
sean
parents:
diff changeset
2013 elif isinstance(s, bytes): # Python 2 WSGI
e1264e58154c initial commpit
sean
parents:
diff changeset
2014 return s.decode(encoding or self.input_encoding)
e1264e58154c initial commpit
sean
parents:
diff changeset
2015 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2016 return s
e1264e58154c initial commpit
sean
parents:
diff changeset
2017
e1264e58154c initial commpit
sean
parents:
diff changeset
2018 def decode(self, encoding=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2019 """ Returns a copy with all keys and values de- or recoded to match
e1264e58154c initial commpit
sean
parents:
diff changeset
2020 :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
e1264e58154c initial commpit
sean
parents:
diff changeset
2021 unicode dictionary. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2022 copy = FormsDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
2023 enc = copy.input_encoding = encoding or self.input_encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
2024 copy.recode_unicode = False
e1264e58154c initial commpit
sean
parents:
diff changeset
2025 for key, value in self.allitems():
e1264e58154c initial commpit
sean
parents:
diff changeset
2026 copy.append(self._fix(key, enc), self._fix(value, enc))
e1264e58154c initial commpit
sean
parents:
diff changeset
2027 return copy
e1264e58154c initial commpit
sean
parents:
diff changeset
2028
e1264e58154c initial commpit
sean
parents:
diff changeset
2029 def getunicode(self, name, default=None, encoding=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2030 """ Return the value as a unicode string, or the default. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2031 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2032 return self._fix(self[name], encoding)
e1264e58154c initial commpit
sean
parents:
diff changeset
2033 except (UnicodeError, KeyError):
e1264e58154c initial commpit
sean
parents:
diff changeset
2034 return default
e1264e58154c initial commpit
sean
parents:
diff changeset
2035
e1264e58154c initial commpit
sean
parents:
diff changeset
2036 def __getattr__(self, name, default=unicode()):
e1264e58154c initial commpit
sean
parents:
diff changeset
2037 # Without this guard, pickle generates a cryptic TypeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
2038 if name.startswith('__') and name.endswith('__'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2039 return super(FormsDict, self).__getattr__(name)
e1264e58154c initial commpit
sean
parents:
diff changeset
2040 return self.getunicode(name, default=default)
e1264e58154c initial commpit
sean
parents:
diff changeset
2041
e1264e58154c initial commpit
sean
parents:
diff changeset
2042
e1264e58154c initial commpit
sean
parents:
diff changeset
2043 class HeaderDict(MultiDict):
e1264e58154c initial commpit
sean
parents:
diff changeset
2044 """ A case-insensitive version of :class:`MultiDict` that defaults to
e1264e58154c initial commpit
sean
parents:
diff changeset
2045 replace the old value instead of appending it. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2046
e1264e58154c initial commpit
sean
parents:
diff changeset
2047 def __init__(self, *a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
2048 self.dict = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
2049 if a or ka: self.update(*a, **ka)
e1264e58154c initial commpit
sean
parents:
diff changeset
2050
e1264e58154c initial commpit
sean
parents:
diff changeset
2051 def __contains__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2052 return _hkey(key) in self.dict
e1264e58154c initial commpit
sean
parents:
diff changeset
2053
e1264e58154c initial commpit
sean
parents:
diff changeset
2054 def __delitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2055 del self.dict[_hkey(key)]
e1264e58154c initial commpit
sean
parents:
diff changeset
2056
e1264e58154c initial commpit
sean
parents:
diff changeset
2057 def __getitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2058 return self.dict[_hkey(key)][-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
2059
e1264e58154c initial commpit
sean
parents:
diff changeset
2060 def __setitem__(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2061 self.dict[_hkey(key)] = [value if isinstance(value, unicode) else
e1264e58154c initial commpit
sean
parents:
diff changeset
2062 str(value)]
e1264e58154c initial commpit
sean
parents:
diff changeset
2063
e1264e58154c initial commpit
sean
parents:
diff changeset
2064 def append(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2065 self.dict.setdefault(_hkey(key), []).append(
e1264e58154c initial commpit
sean
parents:
diff changeset
2066 value if isinstance(value, unicode) else str(value))
e1264e58154c initial commpit
sean
parents:
diff changeset
2067
e1264e58154c initial commpit
sean
parents:
diff changeset
2068 def replace(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2069 self.dict[_hkey(key)] = [value if isinstance(value, unicode) else
e1264e58154c initial commpit
sean
parents:
diff changeset
2070 str(value)]
e1264e58154c initial commpit
sean
parents:
diff changeset
2071
e1264e58154c initial commpit
sean
parents:
diff changeset
2072 def getall(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2073 return self.dict.get(_hkey(key)) or []
e1264e58154c initial commpit
sean
parents:
diff changeset
2074
e1264e58154c initial commpit
sean
parents:
diff changeset
2075 def get(self, key, default=None, index=-1):
e1264e58154c initial commpit
sean
parents:
diff changeset
2076 return MultiDict.get(self, _hkey(key), default, index)
e1264e58154c initial commpit
sean
parents:
diff changeset
2077
e1264e58154c initial commpit
sean
parents:
diff changeset
2078 def filter(self, names):
e1264e58154c initial commpit
sean
parents:
diff changeset
2079 for name in [_hkey(n) for n in names]:
e1264e58154c initial commpit
sean
parents:
diff changeset
2080 if name in self.dict:
e1264e58154c initial commpit
sean
parents:
diff changeset
2081 del self.dict[name]
e1264e58154c initial commpit
sean
parents:
diff changeset
2082
e1264e58154c initial commpit
sean
parents:
diff changeset
2083
e1264e58154c initial commpit
sean
parents:
diff changeset
2084 class WSGIHeaderDict(DictMixin):
e1264e58154c initial commpit
sean
parents:
diff changeset
2085 """ This dict-like class wraps a WSGI environ dict and provides convenient
e1264e58154c initial commpit
sean
parents:
diff changeset
2086 access to HTTP_* fields. Keys and values are native strings
e1264e58154c initial commpit
sean
parents:
diff changeset
2087 (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
e1264e58154c initial commpit
sean
parents:
diff changeset
2088 environment contains non-native string values, these are de- or encoded
e1264e58154c initial commpit
sean
parents:
diff changeset
2089 using a lossless 'latin1' character set.
e1264e58154c initial commpit
sean
parents:
diff changeset
2090
e1264e58154c initial commpit
sean
parents:
diff changeset
2091 The API will remain stable even on changes to the relevant PEPs.
e1264e58154c initial commpit
sean
parents:
diff changeset
2092 Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
e1264e58154c initial commpit
sean
parents:
diff changeset
2093 that uses non-native strings.)
e1264e58154c initial commpit
sean
parents:
diff changeset
2094 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2095 #: List of keys that do not have a ``HTTP_`` prefix.
e1264e58154c initial commpit
sean
parents:
diff changeset
2096 cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
e1264e58154c initial commpit
sean
parents:
diff changeset
2097
e1264e58154c initial commpit
sean
parents:
diff changeset
2098 def __init__(self, environ):
e1264e58154c initial commpit
sean
parents:
diff changeset
2099 self.environ = environ
e1264e58154c initial commpit
sean
parents:
diff changeset
2100
e1264e58154c initial commpit
sean
parents:
diff changeset
2101 def _ekey(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2102 """ Translate header field name to CGI/WSGI environ key. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2103 key = key.replace('-', '_').upper()
e1264e58154c initial commpit
sean
parents:
diff changeset
2104 if key in self.cgikeys:
e1264e58154c initial commpit
sean
parents:
diff changeset
2105 return key
e1264e58154c initial commpit
sean
parents:
diff changeset
2106 return 'HTTP_' + key
e1264e58154c initial commpit
sean
parents:
diff changeset
2107
e1264e58154c initial commpit
sean
parents:
diff changeset
2108 def raw(self, key, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2109 """ Return the header value as is (may be bytes or unicode). """
e1264e58154c initial commpit
sean
parents:
diff changeset
2110 return self.environ.get(self._ekey(key), default)
e1264e58154c initial commpit
sean
parents:
diff changeset
2111
e1264e58154c initial commpit
sean
parents:
diff changeset
2112 def __getitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2113 val = self.environ[self._ekey(key)]
e1264e58154c initial commpit
sean
parents:
diff changeset
2114 if py3k:
e1264e58154c initial commpit
sean
parents:
diff changeset
2115 if isinstance(val, unicode):
e1264e58154c initial commpit
sean
parents:
diff changeset
2116 val = val.encode('latin1').decode('utf8')
e1264e58154c initial commpit
sean
parents:
diff changeset
2117 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2118 val = val.decode('utf8')
e1264e58154c initial commpit
sean
parents:
diff changeset
2119 return val
e1264e58154c initial commpit
sean
parents:
diff changeset
2120
e1264e58154c initial commpit
sean
parents:
diff changeset
2121 def __setitem__(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2122 raise TypeError("%s is read-only." % self.__class__)
e1264e58154c initial commpit
sean
parents:
diff changeset
2123
e1264e58154c initial commpit
sean
parents:
diff changeset
2124 def __delitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2125 raise TypeError("%s is read-only." % self.__class__)
e1264e58154c initial commpit
sean
parents:
diff changeset
2126
e1264e58154c initial commpit
sean
parents:
diff changeset
2127 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2128 for key in self.environ:
e1264e58154c initial commpit
sean
parents:
diff changeset
2129 if key[:5] == 'HTTP_':
e1264e58154c initial commpit
sean
parents:
diff changeset
2130 yield _hkey(key[5:])
e1264e58154c initial commpit
sean
parents:
diff changeset
2131 elif key in self.cgikeys:
e1264e58154c initial commpit
sean
parents:
diff changeset
2132 yield _hkey(key)
e1264e58154c initial commpit
sean
parents:
diff changeset
2133
e1264e58154c initial commpit
sean
parents:
diff changeset
2134 def keys(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2135 return [x for x in self]
e1264e58154c initial commpit
sean
parents:
diff changeset
2136
e1264e58154c initial commpit
sean
parents:
diff changeset
2137 def __len__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2138 return len(self.keys())
e1264e58154c initial commpit
sean
parents:
diff changeset
2139
e1264e58154c initial commpit
sean
parents:
diff changeset
2140 def __contains__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2141 return self._ekey(key) in self.environ
e1264e58154c initial commpit
sean
parents:
diff changeset
2142
e1264e58154c initial commpit
sean
parents:
diff changeset
2143
e1264e58154c initial commpit
sean
parents:
diff changeset
2144 class ConfigDict(dict):
e1264e58154c initial commpit
sean
parents:
diff changeset
2145 """ A dict-like configuration storage with additional support for
e1264e58154c initial commpit
sean
parents:
diff changeset
2146 namespaces, validators, meta-data, on_change listeners and more.
e1264e58154c initial commpit
sean
parents:
diff changeset
2147 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2148
e1264e58154c initial commpit
sean
parents:
diff changeset
2149 __slots__ = ('_meta', '_on_change')
e1264e58154c initial commpit
sean
parents:
diff changeset
2150
e1264e58154c initial commpit
sean
parents:
diff changeset
2151 def __init__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2152 self._meta = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
2153 self._on_change = lambda name, value: None
e1264e58154c initial commpit
sean
parents:
diff changeset
2154
e1264e58154c initial commpit
sean
parents:
diff changeset
2155 def load_config(self, filename):
e1264e58154c initial commpit
sean
parents:
diff changeset
2156 """ Load values from an ``*.ini`` style config file.
e1264e58154c initial commpit
sean
parents:
diff changeset
2157
e1264e58154c initial commpit
sean
parents:
diff changeset
2158 If the config file contains sections, their names are used as
e1264e58154c initial commpit
sean
parents:
diff changeset
2159 namespaces for the values within. The two special sections
e1264e58154c initial commpit
sean
parents:
diff changeset
2160 ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix).
e1264e58154c initial commpit
sean
parents:
diff changeset
2161 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2162 conf = ConfigParser()
e1264e58154c initial commpit
sean
parents:
diff changeset
2163 conf.read(filename)
e1264e58154c initial commpit
sean
parents:
diff changeset
2164 for section in conf.sections():
e1264e58154c initial commpit
sean
parents:
diff changeset
2165 for key, value in conf.items(section):
e1264e58154c initial commpit
sean
parents:
diff changeset
2166 if section not in ('DEFAULT', 'bottle'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2167 key = section + '.' + key
e1264e58154c initial commpit
sean
parents:
diff changeset
2168 self[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
2169 return self
e1264e58154c initial commpit
sean
parents:
diff changeset
2170
e1264e58154c initial commpit
sean
parents:
diff changeset
2171 def load_dict(self, source, namespace=''):
e1264e58154c initial commpit
sean
parents:
diff changeset
2172 """ Load values from a dictionary structure. Nesting can be used to
e1264e58154c initial commpit
sean
parents:
diff changeset
2173 represent namespaces.
e1264e58154c initial commpit
sean
parents:
diff changeset
2174
e1264e58154c initial commpit
sean
parents:
diff changeset
2175 >>> c = ConfigDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
2176 >>> c.load_dict({'some': {'namespace': {'key': 'value'} } })
e1264e58154c initial commpit
sean
parents:
diff changeset
2177 {'some.namespace.key': 'value'}
e1264e58154c initial commpit
sean
parents:
diff changeset
2178 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2179 for key, value in source.items():
e1264e58154c initial commpit
sean
parents:
diff changeset
2180 if isinstance(key, str):
e1264e58154c initial commpit
sean
parents:
diff changeset
2181 nskey = (namespace + '.' + key).strip('.')
e1264e58154c initial commpit
sean
parents:
diff changeset
2182 if isinstance(value, dict):
e1264e58154c initial commpit
sean
parents:
diff changeset
2183 self.load_dict(value, namespace=nskey)
e1264e58154c initial commpit
sean
parents:
diff changeset
2184 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2185 self[nskey] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
2186 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2187 raise TypeError('Key has type %r (not a string)' % type(key))
e1264e58154c initial commpit
sean
parents:
diff changeset
2188 return self
e1264e58154c initial commpit
sean
parents:
diff changeset
2189
e1264e58154c initial commpit
sean
parents:
diff changeset
2190 def update(self, *a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
2191 """ If the first parameter is a string, all keys are prefixed with this
e1264e58154c initial commpit
sean
parents:
diff changeset
2192 namespace. Apart from that it works just as the usual dict.update().
e1264e58154c initial commpit
sean
parents:
diff changeset
2193 Example: ``update('some.namespace', key='value')`` """
e1264e58154c initial commpit
sean
parents:
diff changeset
2194 prefix = ''
e1264e58154c initial commpit
sean
parents:
diff changeset
2195 if a and isinstance(a[0], str):
e1264e58154c initial commpit
sean
parents:
diff changeset
2196 prefix = a[0].strip('.') + '.'
e1264e58154c initial commpit
sean
parents:
diff changeset
2197 a = a[1:]
e1264e58154c initial commpit
sean
parents:
diff changeset
2198 for key, value in dict(*a, **ka).items():
e1264e58154c initial commpit
sean
parents:
diff changeset
2199 self[prefix + key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
2200
e1264e58154c initial commpit
sean
parents:
diff changeset
2201 def setdefault(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2202 if key not in self:
e1264e58154c initial commpit
sean
parents:
diff changeset
2203 self[key] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
2204 return self[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
2205
e1264e58154c initial commpit
sean
parents:
diff changeset
2206 def __setitem__(self, key, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2207 if not isinstance(key, str):
e1264e58154c initial commpit
sean
parents:
diff changeset
2208 raise TypeError('Key has type %r (not a string)' % type(key))
e1264e58154c initial commpit
sean
parents:
diff changeset
2209 value = self.meta_get(key, 'filter', lambda x: x)(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2210 if key in self and self[key] is value:
e1264e58154c initial commpit
sean
parents:
diff changeset
2211 return
e1264e58154c initial commpit
sean
parents:
diff changeset
2212 self._on_change(key, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2213 dict.__setitem__(self, key, value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2214
e1264e58154c initial commpit
sean
parents:
diff changeset
2215 def __delitem__(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2216 self._on_change(key, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
2217 dict.__delitem__(self, key)
e1264e58154c initial commpit
sean
parents:
diff changeset
2218
e1264e58154c initial commpit
sean
parents:
diff changeset
2219 def meta_get(self, key, metafield, default=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2220 """ Return the value of a meta field for a key. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2221 return self._meta.get(key, {}).get(metafield, default)
e1264e58154c initial commpit
sean
parents:
diff changeset
2222
e1264e58154c initial commpit
sean
parents:
diff changeset
2223 def meta_set(self, key, metafield, value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2224 """ Set the meta field for a key to a new value. This triggers the
e1264e58154c initial commpit
sean
parents:
diff changeset
2225 on-change handler for existing keys. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2226 self._meta.setdefault(key, {})[metafield] = value
e1264e58154c initial commpit
sean
parents:
diff changeset
2227 if key in self:
e1264e58154c initial commpit
sean
parents:
diff changeset
2228 self[key] = self[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
2229
e1264e58154c initial commpit
sean
parents:
diff changeset
2230 def meta_list(self, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2231 """ Return an iterable of meta field names defined for a key. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2232 return self._meta.get(key, {}).keys()
e1264e58154c initial commpit
sean
parents:
diff changeset
2233
e1264e58154c initial commpit
sean
parents:
diff changeset
2234
e1264e58154c initial commpit
sean
parents:
diff changeset
2235 class AppStack(list):
e1264e58154c initial commpit
sean
parents:
diff changeset
2236 """ A stack-like list. Calling it returns the head of the stack. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2237
e1264e58154c initial commpit
sean
parents:
diff changeset
2238 def __call__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2239 """ Return the current default application. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2240 return self[-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
2241
e1264e58154c initial commpit
sean
parents:
diff changeset
2242 def push(self, value=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2243 """ Add a new :class:`Bottle` instance to the stack """
e1264e58154c initial commpit
sean
parents:
diff changeset
2244 if not isinstance(value, Bottle):
e1264e58154c initial commpit
sean
parents:
diff changeset
2245 value = Bottle()
e1264e58154c initial commpit
sean
parents:
diff changeset
2246 self.append(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2247 return value
e1264e58154c initial commpit
sean
parents:
diff changeset
2248
e1264e58154c initial commpit
sean
parents:
diff changeset
2249
e1264e58154c initial commpit
sean
parents:
diff changeset
2250 class WSGIFileWrapper(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
2251 def __init__(self, fp, buffer_size=1024 * 64):
e1264e58154c initial commpit
sean
parents:
diff changeset
2252 self.fp, self.buffer_size = fp, buffer_size
e1264e58154c initial commpit
sean
parents:
diff changeset
2253 for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2254 if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
e1264e58154c initial commpit
sean
parents:
diff changeset
2255
e1264e58154c initial commpit
sean
parents:
diff changeset
2256 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2257 buff, read = self.buffer_size, self.read
e1264e58154c initial commpit
sean
parents:
diff changeset
2258 while True:
e1264e58154c initial commpit
sean
parents:
diff changeset
2259 part = read(buff)
e1264e58154c initial commpit
sean
parents:
diff changeset
2260 if not part: return
e1264e58154c initial commpit
sean
parents:
diff changeset
2261 yield part
e1264e58154c initial commpit
sean
parents:
diff changeset
2262
e1264e58154c initial commpit
sean
parents:
diff changeset
2263
e1264e58154c initial commpit
sean
parents:
diff changeset
2264 class _closeiter(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
2265 """ This only exists to be able to attach a .close method to iterators that
e1264e58154c initial commpit
sean
parents:
diff changeset
2266 do not support attribute assignment (most of itertools). """
e1264e58154c initial commpit
sean
parents:
diff changeset
2267
e1264e58154c initial commpit
sean
parents:
diff changeset
2268 def __init__(self, iterator, close=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2269 self.iterator = iterator
e1264e58154c initial commpit
sean
parents:
diff changeset
2270 self.close_callbacks = makelist(close)
e1264e58154c initial commpit
sean
parents:
diff changeset
2271
e1264e58154c initial commpit
sean
parents:
diff changeset
2272 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2273 return iter(self.iterator)
e1264e58154c initial commpit
sean
parents:
diff changeset
2274
e1264e58154c initial commpit
sean
parents:
diff changeset
2275 def close(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2276 for func in self.close_callbacks:
e1264e58154c initial commpit
sean
parents:
diff changeset
2277 func()
e1264e58154c initial commpit
sean
parents:
diff changeset
2278
e1264e58154c initial commpit
sean
parents:
diff changeset
2279
e1264e58154c initial commpit
sean
parents:
diff changeset
2280 class ResourceManager(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
2281 """ This class manages a list of search paths and helps to find and open
e1264e58154c initial commpit
sean
parents:
diff changeset
2282 application-bound resources (files).
e1264e58154c initial commpit
sean
parents:
diff changeset
2283
e1264e58154c initial commpit
sean
parents:
diff changeset
2284 :param base: default value for :meth:`add_path` calls.
e1264e58154c initial commpit
sean
parents:
diff changeset
2285 :param opener: callable used to open resources.
e1264e58154c initial commpit
sean
parents:
diff changeset
2286 :param cachemode: controls which lookups are cached. One of 'all',
e1264e58154c initial commpit
sean
parents:
diff changeset
2287 'found' or 'none'.
e1264e58154c initial commpit
sean
parents:
diff changeset
2288 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2289
e1264e58154c initial commpit
sean
parents:
diff changeset
2290 def __init__(self, base='./', opener=open, cachemode='all'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2291 self.opener = opener
e1264e58154c initial commpit
sean
parents:
diff changeset
2292 self.base = base
e1264e58154c initial commpit
sean
parents:
diff changeset
2293 self.cachemode = cachemode
e1264e58154c initial commpit
sean
parents:
diff changeset
2294
e1264e58154c initial commpit
sean
parents:
diff changeset
2295 #: A list of search paths. See :meth:`add_path` for details.
e1264e58154c initial commpit
sean
parents:
diff changeset
2296 self.path = []
e1264e58154c initial commpit
sean
parents:
diff changeset
2297 #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
e1264e58154c initial commpit
sean
parents:
diff changeset
2298 self.cache = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
2299
e1264e58154c initial commpit
sean
parents:
diff changeset
2300 def add_path(self, path, base=None, index=None, create=False):
e1264e58154c initial commpit
sean
parents:
diff changeset
2301 """ Add a new path to the list of search paths. Return False if the
e1264e58154c initial commpit
sean
parents:
diff changeset
2302 path does not exist.
e1264e58154c initial commpit
sean
parents:
diff changeset
2303
e1264e58154c initial commpit
sean
parents:
diff changeset
2304 :param path: The new search path. Relative paths are turned into
e1264e58154c initial commpit
sean
parents:
diff changeset
2305 an absolute and normalized form. If the path looks like a file
e1264e58154c initial commpit
sean
parents:
diff changeset
2306 (not ending in `/`), the filename is stripped off.
e1264e58154c initial commpit
sean
parents:
diff changeset
2307 :param base: Path used to absolutize relative search paths.
e1264e58154c initial commpit
sean
parents:
diff changeset
2308 Defaults to :attr:`base` which defaults to ``os.getcwd()``.
e1264e58154c initial commpit
sean
parents:
diff changeset
2309 :param index: Position within the list of search paths. Defaults
e1264e58154c initial commpit
sean
parents:
diff changeset
2310 to last index (appends to the list).
e1264e58154c initial commpit
sean
parents:
diff changeset
2311
e1264e58154c initial commpit
sean
parents:
diff changeset
2312 The `base` parameter makes it easy to reference files installed
e1264e58154c initial commpit
sean
parents:
diff changeset
2313 along with a python module or package::
e1264e58154c initial commpit
sean
parents:
diff changeset
2314
e1264e58154c initial commpit
sean
parents:
diff changeset
2315 res.add_path('./resources/', __file__)
e1264e58154c initial commpit
sean
parents:
diff changeset
2316 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2317 base = os.path.abspath(os.path.dirname(base or self.base))
e1264e58154c initial commpit
sean
parents:
diff changeset
2318 path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
e1264e58154c initial commpit
sean
parents:
diff changeset
2319 path += os.sep
e1264e58154c initial commpit
sean
parents:
diff changeset
2320 if path in self.path:
e1264e58154c initial commpit
sean
parents:
diff changeset
2321 self.path.remove(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
2322 if create and not os.path.isdir(path):
e1264e58154c initial commpit
sean
parents:
diff changeset
2323 os.makedirs(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
2324 if index is None:
e1264e58154c initial commpit
sean
parents:
diff changeset
2325 self.path.append(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
2326 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2327 self.path.insert(index, path)
e1264e58154c initial commpit
sean
parents:
diff changeset
2328 self.cache.clear()
e1264e58154c initial commpit
sean
parents:
diff changeset
2329 return os.path.exists(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
2330
e1264e58154c initial commpit
sean
parents:
diff changeset
2331 def __iter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2332 """ Iterate over all existing files in all registered paths. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2333 search = self.path[:]
e1264e58154c initial commpit
sean
parents:
diff changeset
2334 while search:
e1264e58154c initial commpit
sean
parents:
diff changeset
2335 path = search.pop()
e1264e58154c initial commpit
sean
parents:
diff changeset
2336 if not os.path.isdir(path): continue
e1264e58154c initial commpit
sean
parents:
diff changeset
2337 for name in os.listdir(path):
e1264e58154c initial commpit
sean
parents:
diff changeset
2338 full = os.path.join(path, name)
e1264e58154c initial commpit
sean
parents:
diff changeset
2339 if os.path.isdir(full): search.append(full)
e1264e58154c initial commpit
sean
parents:
diff changeset
2340 else: yield full
e1264e58154c initial commpit
sean
parents:
diff changeset
2341
e1264e58154c initial commpit
sean
parents:
diff changeset
2342 def lookup(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
2343 """ Search for a resource and return an absolute file path, or `None`.
e1264e58154c initial commpit
sean
parents:
diff changeset
2344
e1264e58154c initial commpit
sean
parents:
diff changeset
2345 The :attr:`path` list is searched in order. The first match is
e1264e58154c initial commpit
sean
parents:
diff changeset
2346 returend. Symlinks are followed. The result is cached to speed up
e1264e58154c initial commpit
sean
parents:
diff changeset
2347 future lookups. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2348 if name not in self.cache or DEBUG:
e1264e58154c initial commpit
sean
parents:
diff changeset
2349 for path in self.path:
e1264e58154c initial commpit
sean
parents:
diff changeset
2350 fpath = os.path.join(path, name)
e1264e58154c initial commpit
sean
parents:
diff changeset
2351 if os.path.isfile(fpath):
e1264e58154c initial commpit
sean
parents:
diff changeset
2352 if self.cachemode in ('all', 'found'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2353 self.cache[name] = fpath
e1264e58154c initial commpit
sean
parents:
diff changeset
2354 return fpath
e1264e58154c initial commpit
sean
parents:
diff changeset
2355 if self.cachemode == 'all':
e1264e58154c initial commpit
sean
parents:
diff changeset
2356 self.cache[name] = None
e1264e58154c initial commpit
sean
parents:
diff changeset
2357 return self.cache[name]
e1264e58154c initial commpit
sean
parents:
diff changeset
2358
e1264e58154c initial commpit
sean
parents:
diff changeset
2359 def open(self, name, mode='r', *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
2360 """ Find a resource and return a file object, or raise IOError. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2361 fname = self.lookup(name)
e1264e58154c initial commpit
sean
parents:
diff changeset
2362 if not fname: raise IOError("Resource %r not found." % name)
e1264e58154c initial commpit
sean
parents:
diff changeset
2363 return self.opener(fname, mode=mode, *args, **kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
2364
e1264e58154c initial commpit
sean
parents:
diff changeset
2365
e1264e58154c initial commpit
sean
parents:
diff changeset
2366 class FileUpload(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
2367 def __init__(self, fileobj, name, filename, headers=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2368 """ Wrapper for file uploads. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2369 #: Open file(-like) object (BytesIO buffer or temporary file)
e1264e58154c initial commpit
sean
parents:
diff changeset
2370 self.file = fileobj
e1264e58154c initial commpit
sean
parents:
diff changeset
2371 #: Name of the upload form field
e1264e58154c initial commpit
sean
parents:
diff changeset
2372 self.name = name
e1264e58154c initial commpit
sean
parents:
diff changeset
2373 #: Raw filename as sent by the client (may contain unsafe characters)
e1264e58154c initial commpit
sean
parents:
diff changeset
2374 self.raw_filename = filename
e1264e58154c initial commpit
sean
parents:
diff changeset
2375 #: A :class:`HeaderDict` with additional headers (e.g. content-type)
e1264e58154c initial commpit
sean
parents:
diff changeset
2376 self.headers = HeaderDict(headers) if headers else HeaderDict()
e1264e58154c initial commpit
sean
parents:
diff changeset
2377
e1264e58154c initial commpit
sean
parents:
diff changeset
2378 content_type = HeaderProperty('Content-Type')
e1264e58154c initial commpit
sean
parents:
diff changeset
2379 content_length = HeaderProperty('Content-Length', reader=int, default=-1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2380
e1264e58154c initial commpit
sean
parents:
diff changeset
2381 @cached_property
e1264e58154c initial commpit
sean
parents:
diff changeset
2382 def filename(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2383 """ Name of the file on the client file system, but normalized to ensure
e1264e58154c initial commpit
sean
parents:
diff changeset
2384 file system compatibility. An empty filename is returned as 'empty'.
e1264e58154c initial commpit
sean
parents:
diff changeset
2385
e1264e58154c initial commpit
sean
parents:
diff changeset
2386 Only ASCII letters, digits, dashes, underscores and dots are
e1264e58154c initial commpit
sean
parents:
diff changeset
2387 allowed in the final filename. Accents are removed, if possible.
e1264e58154c initial commpit
sean
parents:
diff changeset
2388 Whitespace is replaced by a single dash. Leading or tailing dots
e1264e58154c initial commpit
sean
parents:
diff changeset
2389 or dashes are removed. The filename is limited to 255 characters.
e1264e58154c initial commpit
sean
parents:
diff changeset
2390 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2391 fname = self.raw_filename
e1264e58154c initial commpit
sean
parents:
diff changeset
2392 if not isinstance(fname, unicode):
e1264e58154c initial commpit
sean
parents:
diff changeset
2393 fname = fname.decode('utf8', 'ignore')
e1264e58154c initial commpit
sean
parents:
diff changeset
2394 fname = normalize('NFKD', fname)
e1264e58154c initial commpit
sean
parents:
diff changeset
2395 fname = fname.encode('ASCII', 'ignore').decode('ASCII')
e1264e58154c initial commpit
sean
parents:
diff changeset
2396 fname = os.path.basename(fname.replace('\\', os.path.sep))
e1264e58154c initial commpit
sean
parents:
diff changeset
2397 fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
e1264e58154c initial commpit
sean
parents:
diff changeset
2398 fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
e1264e58154c initial commpit
sean
parents:
diff changeset
2399 return fname[:255] or 'empty'
e1264e58154c initial commpit
sean
parents:
diff changeset
2400
e1264e58154c initial commpit
sean
parents:
diff changeset
2401 def _copy_file(self, fp, chunk_size=2 ** 16):
e1264e58154c initial commpit
sean
parents:
diff changeset
2402 read, write, offset = self.file.read, fp.write, self.file.tell()
e1264e58154c initial commpit
sean
parents:
diff changeset
2403 while 1:
e1264e58154c initial commpit
sean
parents:
diff changeset
2404 buf = read(chunk_size)
e1264e58154c initial commpit
sean
parents:
diff changeset
2405 if not buf: break
e1264e58154c initial commpit
sean
parents:
diff changeset
2406 write(buf)
e1264e58154c initial commpit
sean
parents:
diff changeset
2407 self.file.seek(offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
2408
e1264e58154c initial commpit
sean
parents:
diff changeset
2409 def save(self, destination, overwrite=False, chunk_size=2 ** 16):
e1264e58154c initial commpit
sean
parents:
diff changeset
2410 """ Save file to disk or copy its content to an open file(-like) object.
e1264e58154c initial commpit
sean
parents:
diff changeset
2411 If *destination* is a directory, :attr:`filename` is added to the
e1264e58154c initial commpit
sean
parents:
diff changeset
2412 path. Existing files are not overwritten by default (IOError).
e1264e58154c initial commpit
sean
parents:
diff changeset
2413
e1264e58154c initial commpit
sean
parents:
diff changeset
2414 :param destination: File path, directory or file(-like) object.
e1264e58154c initial commpit
sean
parents:
diff changeset
2415 :param overwrite: If True, replace existing files. (default: False)
e1264e58154c initial commpit
sean
parents:
diff changeset
2416 :param chunk_size: Bytes to read at a time. (default: 64kb)
e1264e58154c initial commpit
sean
parents:
diff changeset
2417 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2418 if isinstance(destination, basestring): # Except file-likes here
e1264e58154c initial commpit
sean
parents:
diff changeset
2419 if os.path.isdir(destination):
e1264e58154c initial commpit
sean
parents:
diff changeset
2420 destination = os.path.join(destination, self.filename)
e1264e58154c initial commpit
sean
parents:
diff changeset
2421 if not overwrite and os.path.exists(destination):
e1264e58154c initial commpit
sean
parents:
diff changeset
2422 raise IOError('File exists.')
e1264e58154c initial commpit
sean
parents:
diff changeset
2423 with open(destination, 'wb') as fp:
e1264e58154c initial commpit
sean
parents:
diff changeset
2424 self._copy_file(fp, chunk_size)
e1264e58154c initial commpit
sean
parents:
diff changeset
2425 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2426 self._copy_file(destination, chunk_size)
e1264e58154c initial commpit
sean
parents:
diff changeset
2427
e1264e58154c initial commpit
sean
parents:
diff changeset
2428 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2429 # Application Helper ###########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2430 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2431
e1264e58154c initial commpit
sean
parents:
diff changeset
2432
e1264e58154c initial commpit
sean
parents:
diff changeset
2433 def abort(code=500, text='Unknown Error.'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2434 """ Aborts execution and causes a HTTP error. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2435 raise HTTPError(code, text)
e1264e58154c initial commpit
sean
parents:
diff changeset
2436
e1264e58154c initial commpit
sean
parents:
diff changeset
2437
e1264e58154c initial commpit
sean
parents:
diff changeset
2438 def redirect(url, code=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
2439 """ Aborts execution and causes a 303 or 302 redirect, depending on
e1264e58154c initial commpit
sean
parents:
diff changeset
2440 the HTTP protocol version. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2441 if not code:
e1264e58154c initial commpit
sean
parents:
diff changeset
2442 code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
e1264e58154c initial commpit
sean
parents:
diff changeset
2443 res = response.copy(cls=HTTPResponse)
e1264e58154c initial commpit
sean
parents:
diff changeset
2444 res.status = code
e1264e58154c initial commpit
sean
parents:
diff changeset
2445 res.body = ""
e1264e58154c initial commpit
sean
parents:
diff changeset
2446 res.set_header('Location', urljoin(request.url, url))
e1264e58154c initial commpit
sean
parents:
diff changeset
2447 raise res
e1264e58154c initial commpit
sean
parents:
diff changeset
2448
e1264e58154c initial commpit
sean
parents:
diff changeset
2449
e1264e58154c initial commpit
sean
parents:
diff changeset
2450 def _file_iter_range(fp, offset, bytes, maxread=1024 * 1024):
e1264e58154c initial commpit
sean
parents:
diff changeset
2451 """ Yield chunks from a range in a file. No chunk is bigger than maxread."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2452 fp.seek(offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
2453 while bytes > 0:
e1264e58154c initial commpit
sean
parents:
diff changeset
2454 part = fp.read(min(bytes, maxread))
e1264e58154c initial commpit
sean
parents:
diff changeset
2455 if not part: break
e1264e58154c initial commpit
sean
parents:
diff changeset
2456 bytes -= len(part)
e1264e58154c initial commpit
sean
parents:
diff changeset
2457 yield part
e1264e58154c initial commpit
sean
parents:
diff changeset
2458
e1264e58154c initial commpit
sean
parents:
diff changeset
2459
e1264e58154c initial commpit
sean
parents:
diff changeset
2460 def static_file(filename, root,
e1264e58154c initial commpit
sean
parents:
diff changeset
2461 mimetype='auto',
e1264e58154c initial commpit
sean
parents:
diff changeset
2462 download=False,
e1264e58154c initial commpit
sean
parents:
diff changeset
2463 charset='UTF-8'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2464 """ Open a file in a safe way and return :exc:`HTTPResponse` with status
e1264e58154c initial commpit
sean
parents:
diff changeset
2465 code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
e1264e58154c initial commpit
sean
parents:
diff changeset
2466 ``Content-Length`` and ``Last-Modified`` headers are set if possible.
e1264e58154c initial commpit
sean
parents:
diff changeset
2467 Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
e1264e58154c initial commpit
sean
parents:
diff changeset
2468 requests.
e1264e58154c initial commpit
sean
parents:
diff changeset
2469
e1264e58154c initial commpit
sean
parents:
diff changeset
2470 :param filename: Name or path of the file to send.
e1264e58154c initial commpit
sean
parents:
diff changeset
2471 :param root: Root path for file lookups. Should be an absolute directory
e1264e58154c initial commpit
sean
parents:
diff changeset
2472 path.
e1264e58154c initial commpit
sean
parents:
diff changeset
2473 :param mimetype: Defines the content-type header (default: guess from
e1264e58154c initial commpit
sean
parents:
diff changeset
2474 file extension)
e1264e58154c initial commpit
sean
parents:
diff changeset
2475 :param download: If True, ask the browser to open a `Save as...` dialog
e1264e58154c initial commpit
sean
parents:
diff changeset
2476 instead of opening the file with the associated program. You can
e1264e58154c initial commpit
sean
parents:
diff changeset
2477 specify a custom filename as a string. If not specified, the
e1264e58154c initial commpit
sean
parents:
diff changeset
2478 original filename is used (default: False).
e1264e58154c initial commpit
sean
parents:
diff changeset
2479 :param charset: The charset to use for files with a ``text/*``
e1264e58154c initial commpit
sean
parents:
diff changeset
2480 mime-type. (default: UTF-8)
e1264e58154c initial commpit
sean
parents:
diff changeset
2481 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2482
e1264e58154c initial commpit
sean
parents:
diff changeset
2483 root = os.path.abspath(root) + os.sep
e1264e58154c initial commpit
sean
parents:
diff changeset
2484 filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
e1264e58154c initial commpit
sean
parents:
diff changeset
2485 headers = dict()
e1264e58154c initial commpit
sean
parents:
diff changeset
2486
e1264e58154c initial commpit
sean
parents:
diff changeset
2487 if not filename.startswith(root):
e1264e58154c initial commpit
sean
parents:
diff changeset
2488 return HTTPError(403, "Access denied.")
e1264e58154c initial commpit
sean
parents:
diff changeset
2489 if not os.path.exists(filename) or not os.path.isfile(filename):
e1264e58154c initial commpit
sean
parents:
diff changeset
2490 return HTTPError(404, "File does not exist.")
e1264e58154c initial commpit
sean
parents:
diff changeset
2491 if not os.access(filename, os.R_OK):
e1264e58154c initial commpit
sean
parents:
diff changeset
2492 return HTTPError(403, "You do not have permission to access this file.")
e1264e58154c initial commpit
sean
parents:
diff changeset
2493
e1264e58154c initial commpit
sean
parents:
diff changeset
2494 if mimetype == 'auto':
e1264e58154c initial commpit
sean
parents:
diff changeset
2495 if download and download != True:
e1264e58154c initial commpit
sean
parents:
diff changeset
2496 mimetype, encoding = mimetypes.guess_type(download)
e1264e58154c initial commpit
sean
parents:
diff changeset
2497 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2498 mimetype, encoding = mimetypes.guess_type(filename)
e1264e58154c initial commpit
sean
parents:
diff changeset
2499 if encoding: headers['Content-Encoding'] = encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
2500
e1264e58154c initial commpit
sean
parents:
diff changeset
2501 if mimetype:
e1264e58154c initial commpit
sean
parents:
diff changeset
2502 if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
e1264e58154c initial commpit
sean
parents:
diff changeset
2503 mimetype += '; charset=%s' % charset
e1264e58154c initial commpit
sean
parents:
diff changeset
2504 headers['Content-Type'] = mimetype
e1264e58154c initial commpit
sean
parents:
diff changeset
2505
e1264e58154c initial commpit
sean
parents:
diff changeset
2506 if download:
e1264e58154c initial commpit
sean
parents:
diff changeset
2507 download = os.path.basename(filename if download == True else download)
e1264e58154c initial commpit
sean
parents:
diff changeset
2508 headers['Content-Disposition'] = 'attachment; filename="%s"' % download
e1264e58154c initial commpit
sean
parents:
diff changeset
2509
e1264e58154c initial commpit
sean
parents:
diff changeset
2510 stats = os.stat(filename)
e1264e58154c initial commpit
sean
parents:
diff changeset
2511 headers['Content-Length'] = clen = stats.st_size
e1264e58154c initial commpit
sean
parents:
diff changeset
2512 lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
e1264e58154c initial commpit
sean
parents:
diff changeset
2513 headers['Last-Modified'] = lm
e1264e58154c initial commpit
sean
parents:
diff changeset
2514
e1264e58154c initial commpit
sean
parents:
diff changeset
2515 ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
e1264e58154c initial commpit
sean
parents:
diff changeset
2516 if ims:
e1264e58154c initial commpit
sean
parents:
diff changeset
2517 ims = parse_date(ims.split(";")[0].strip())
e1264e58154c initial commpit
sean
parents:
diff changeset
2518 if ims is not None and ims >= int(stats.st_mtime):
e1264e58154c initial commpit
sean
parents:
diff changeset
2519 headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
e1264e58154c initial commpit
sean
parents:
diff changeset
2520 time.gmtime())
e1264e58154c initial commpit
sean
parents:
diff changeset
2521 return HTTPResponse(status=304, **headers)
e1264e58154c initial commpit
sean
parents:
diff changeset
2522
e1264e58154c initial commpit
sean
parents:
diff changeset
2523 body = '' if request.method == 'HEAD' else open(filename, 'rb')
e1264e58154c initial commpit
sean
parents:
diff changeset
2524
e1264e58154c initial commpit
sean
parents:
diff changeset
2525 headers["Accept-Ranges"] = "bytes"
e1264e58154c initial commpit
sean
parents:
diff changeset
2526 ranges = request.environ.get('HTTP_RANGE')
e1264e58154c initial commpit
sean
parents:
diff changeset
2527 if 'HTTP_RANGE' in request.environ:
e1264e58154c initial commpit
sean
parents:
diff changeset
2528 ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen))
e1264e58154c initial commpit
sean
parents:
diff changeset
2529 if not ranges:
e1264e58154c initial commpit
sean
parents:
diff changeset
2530 return HTTPError(416, "Requested Range Not Satisfiable")
e1264e58154c initial commpit
sean
parents:
diff changeset
2531 offset, end = ranges[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
2532 headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
e1264e58154c initial commpit
sean
parents:
diff changeset
2533 headers["Content-Length"] = str(end - offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
2534 if body: body = _file_iter_range(body, offset, end - offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
2535 return HTTPResponse(body, status=206, **headers)
e1264e58154c initial commpit
sean
parents:
diff changeset
2536 return HTTPResponse(body, **headers)
e1264e58154c initial commpit
sean
parents:
diff changeset
2537
e1264e58154c initial commpit
sean
parents:
diff changeset
2538 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2539 # HTTP Utilities and MISC (TODO) ###############################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2540 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2541
e1264e58154c initial commpit
sean
parents:
diff changeset
2542
e1264e58154c initial commpit
sean
parents:
diff changeset
2543 def debug(mode=True):
e1264e58154c initial commpit
sean
parents:
diff changeset
2544 """ Change the debug level.
e1264e58154c initial commpit
sean
parents:
diff changeset
2545 There is only one debug level supported at the moment."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2546 global DEBUG
e1264e58154c initial commpit
sean
parents:
diff changeset
2547 if mode: warnings.simplefilter('default')
e1264e58154c initial commpit
sean
parents:
diff changeset
2548 DEBUG = bool(mode)
e1264e58154c initial commpit
sean
parents:
diff changeset
2549
e1264e58154c initial commpit
sean
parents:
diff changeset
2550
e1264e58154c initial commpit
sean
parents:
diff changeset
2551 def http_date(value):
e1264e58154c initial commpit
sean
parents:
diff changeset
2552 if isinstance(value, (datedate, datetime)):
e1264e58154c initial commpit
sean
parents:
diff changeset
2553 value = value.utctimetuple()
e1264e58154c initial commpit
sean
parents:
diff changeset
2554 elif isinstance(value, (int, float)):
e1264e58154c initial commpit
sean
parents:
diff changeset
2555 value = time.gmtime(value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2556 if not isinstance(value, basestring):
e1264e58154c initial commpit
sean
parents:
diff changeset
2557 value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
e1264e58154c initial commpit
sean
parents:
diff changeset
2558 return value
e1264e58154c initial commpit
sean
parents:
diff changeset
2559
e1264e58154c initial commpit
sean
parents:
diff changeset
2560
e1264e58154c initial commpit
sean
parents:
diff changeset
2561 def parse_date(ims):
e1264e58154c initial commpit
sean
parents:
diff changeset
2562 """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2563 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2564 ts = email.utils.parsedate_tz(ims)
e1264e58154c initial commpit
sean
parents:
diff changeset
2565 return time.mktime(ts[:8] + (0, )) - (ts[9] or 0) - time.timezone
e1264e58154c initial commpit
sean
parents:
diff changeset
2566 except (TypeError, ValueError, IndexError, OverflowError):
e1264e58154c initial commpit
sean
parents:
diff changeset
2567 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
2568
e1264e58154c initial commpit
sean
parents:
diff changeset
2569
e1264e58154c initial commpit
sean
parents:
diff changeset
2570 def parse_auth(header):
e1264e58154c initial commpit
sean
parents:
diff changeset
2571 """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
e1264e58154c initial commpit
sean
parents:
diff changeset
2572 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2573 method, data = header.split(None, 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2574 if method.lower() == 'basic':
e1264e58154c initial commpit
sean
parents:
diff changeset
2575 user, pwd = touni(base64.b64decode(tob(data))).split(':', 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2576 return user, pwd
e1264e58154c initial commpit
sean
parents:
diff changeset
2577 except (KeyError, ValueError):
e1264e58154c initial commpit
sean
parents:
diff changeset
2578 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
2579
e1264e58154c initial commpit
sean
parents:
diff changeset
2580
e1264e58154c initial commpit
sean
parents:
diff changeset
2581 def parse_range_header(header, maxlen=0):
e1264e58154c initial commpit
sean
parents:
diff changeset
2582 """ Yield (start, end) ranges parsed from a HTTP Range header. Skip
e1264e58154c initial commpit
sean
parents:
diff changeset
2583 unsatisfiable ranges. The end index is non-inclusive."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2584 if not header or header[:6] != 'bytes=': return
e1264e58154c initial commpit
sean
parents:
diff changeset
2585 ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
e1264e58154c initial commpit
sean
parents:
diff changeset
2586 for start, end in ranges:
e1264e58154c initial commpit
sean
parents:
diff changeset
2587 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2588 if not start: # bytes=-100 -> last 100 bytes
e1264e58154c initial commpit
sean
parents:
diff changeset
2589 start, end = max(0, maxlen - int(end)), maxlen
e1264e58154c initial commpit
sean
parents:
diff changeset
2590 elif not end: # bytes=100- -> all but the first 99 bytes
e1264e58154c initial commpit
sean
parents:
diff changeset
2591 start, end = int(start), maxlen
e1264e58154c initial commpit
sean
parents:
diff changeset
2592 else: # bytes=100-200 -> bytes 100-200 (inclusive)
e1264e58154c initial commpit
sean
parents:
diff changeset
2593 start, end = int(start), min(int(end) + 1, maxlen)
e1264e58154c initial commpit
sean
parents:
diff changeset
2594 if 0 <= start < end <= maxlen:
e1264e58154c initial commpit
sean
parents:
diff changeset
2595 yield start, end
e1264e58154c initial commpit
sean
parents:
diff changeset
2596 except ValueError:
e1264e58154c initial commpit
sean
parents:
diff changeset
2597 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
2598
e1264e58154c initial commpit
sean
parents:
diff changeset
2599
e1264e58154c initial commpit
sean
parents:
diff changeset
2600 def _parse_qsl(qs):
e1264e58154c initial commpit
sean
parents:
diff changeset
2601 r = []
e1264e58154c initial commpit
sean
parents:
diff changeset
2602 for pair in qs.replace(';', '&').split('&'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2603 if not pair: continue
e1264e58154c initial commpit
sean
parents:
diff changeset
2604 nv = pair.split('=', 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2605 if len(nv) != 2: nv.append('')
e1264e58154c initial commpit
sean
parents:
diff changeset
2606 key = urlunquote(nv[0].replace('+', ' '))
e1264e58154c initial commpit
sean
parents:
diff changeset
2607 value = urlunquote(nv[1].replace('+', ' '))
e1264e58154c initial commpit
sean
parents:
diff changeset
2608 r.append((key, value))
e1264e58154c initial commpit
sean
parents:
diff changeset
2609 return r
e1264e58154c initial commpit
sean
parents:
diff changeset
2610
e1264e58154c initial commpit
sean
parents:
diff changeset
2611
e1264e58154c initial commpit
sean
parents:
diff changeset
2612 def _lscmp(a, b):
e1264e58154c initial commpit
sean
parents:
diff changeset
2613 """ Compares two strings in a cryptographically safe way:
e1264e58154c initial commpit
sean
parents:
diff changeset
2614 Runtime is not affected by length of common prefix. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2615 return not sum(0 if x == y else 1
e1264e58154c initial commpit
sean
parents:
diff changeset
2616 for x, y in zip(a, b)) and len(a) == len(b)
e1264e58154c initial commpit
sean
parents:
diff changeset
2617
e1264e58154c initial commpit
sean
parents:
diff changeset
2618
e1264e58154c initial commpit
sean
parents:
diff changeset
2619 def cookie_encode(data, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2620 """ Encode and sign a pickle-able object. Return a (byte) string """
e1264e58154c initial commpit
sean
parents:
diff changeset
2621 msg = base64.b64encode(pickle.dumps(data, -1))
e1264e58154c initial commpit
sean
parents:
diff changeset
2622 sig = base64.b64encode(hmac.new(tob(key), msg).digest())
e1264e58154c initial commpit
sean
parents:
diff changeset
2623 return tob('!') + sig + tob('?') + msg
e1264e58154c initial commpit
sean
parents:
diff changeset
2624
e1264e58154c initial commpit
sean
parents:
diff changeset
2625
e1264e58154c initial commpit
sean
parents:
diff changeset
2626 def cookie_decode(data, key):
e1264e58154c initial commpit
sean
parents:
diff changeset
2627 """ Verify and decode an encoded string. Return an object or None."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2628 data = tob(data)
e1264e58154c initial commpit
sean
parents:
diff changeset
2629 if cookie_is_encoded(data):
e1264e58154c initial commpit
sean
parents:
diff changeset
2630 sig, msg = data.split(tob('?'), 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2631 if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
e1264e58154c initial commpit
sean
parents:
diff changeset
2632 return pickle.loads(base64.b64decode(msg))
e1264e58154c initial commpit
sean
parents:
diff changeset
2633 return None
e1264e58154c initial commpit
sean
parents:
diff changeset
2634
e1264e58154c initial commpit
sean
parents:
diff changeset
2635
e1264e58154c initial commpit
sean
parents:
diff changeset
2636 def cookie_is_encoded(data):
e1264e58154c initial commpit
sean
parents:
diff changeset
2637 """ Return True if the argument looks like a encoded cookie."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2638 return bool(data.startswith(tob('!')) and tob('?') in data)
e1264e58154c initial commpit
sean
parents:
diff changeset
2639
e1264e58154c initial commpit
sean
parents:
diff changeset
2640
e1264e58154c initial commpit
sean
parents:
diff changeset
2641 def html_escape(string):
e1264e58154c initial commpit
sean
parents:
diff changeset
2642 """ Escape HTML special characters ``&<>`` and quotes ``'"``. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2643 return string.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')\
e1264e58154c initial commpit
sean
parents:
diff changeset
2644 .replace('"', '&quot;').replace("'", '&#039;')
e1264e58154c initial commpit
sean
parents:
diff changeset
2645
e1264e58154c initial commpit
sean
parents:
diff changeset
2646
e1264e58154c initial commpit
sean
parents:
diff changeset
2647 def html_quote(string):
e1264e58154c initial commpit
sean
parents:
diff changeset
2648 """ Escape and quote a string to be used as an HTTP attribute."""
e1264e58154c initial commpit
sean
parents:
diff changeset
2649 return '"%s"' % html_escape(string).replace('\n', '&#10;')\
e1264e58154c initial commpit
sean
parents:
diff changeset
2650 .replace('\r', '&#13;').replace('\t', '&#9;')
e1264e58154c initial commpit
sean
parents:
diff changeset
2651
e1264e58154c initial commpit
sean
parents:
diff changeset
2652
e1264e58154c initial commpit
sean
parents:
diff changeset
2653 def yieldroutes(func):
e1264e58154c initial commpit
sean
parents:
diff changeset
2654 """ Return a generator for routes that match the signature (name, args)
e1264e58154c initial commpit
sean
parents:
diff changeset
2655 of the func parameter. This may yield more than one route if the function
e1264e58154c initial commpit
sean
parents:
diff changeset
2656 takes optional keyword arguments. The output is best described by example::
e1264e58154c initial commpit
sean
parents:
diff changeset
2657
e1264e58154c initial commpit
sean
parents:
diff changeset
2658 a() -> '/a'
e1264e58154c initial commpit
sean
parents:
diff changeset
2659 b(x, y) -> '/b/<x>/<y>'
e1264e58154c initial commpit
sean
parents:
diff changeset
2660 c(x, y=5) -> '/c/<x>' and '/c/<x>/<y>'
e1264e58154c initial commpit
sean
parents:
diff changeset
2661 d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
e1264e58154c initial commpit
sean
parents:
diff changeset
2662 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2663 path = '/' + func.__name__.replace('__', '/').lstrip('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
2664 spec = getargspec(func)
e1264e58154c initial commpit
sean
parents:
diff changeset
2665 argc = len(spec[0]) - len(spec[3] or [])
e1264e58154c initial commpit
sean
parents:
diff changeset
2666 path += ('/<%s>' * argc) % tuple(spec[0][:argc])
e1264e58154c initial commpit
sean
parents:
diff changeset
2667 yield path
e1264e58154c initial commpit
sean
parents:
diff changeset
2668 for arg in spec[0][argc:]:
e1264e58154c initial commpit
sean
parents:
diff changeset
2669 path += '/<%s>' % arg
e1264e58154c initial commpit
sean
parents:
diff changeset
2670 yield path
e1264e58154c initial commpit
sean
parents:
diff changeset
2671
e1264e58154c initial commpit
sean
parents:
diff changeset
2672
e1264e58154c initial commpit
sean
parents:
diff changeset
2673 def path_shift(script_name, path_info, shift=1):
e1264e58154c initial commpit
sean
parents:
diff changeset
2674 """ Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
e1264e58154c initial commpit
sean
parents:
diff changeset
2675
e1264e58154c initial commpit
sean
parents:
diff changeset
2676 :return: The modified paths.
e1264e58154c initial commpit
sean
parents:
diff changeset
2677 :param script_name: The SCRIPT_NAME path.
e1264e58154c initial commpit
sean
parents:
diff changeset
2678 :param script_name: The PATH_INFO path.
e1264e58154c initial commpit
sean
parents:
diff changeset
2679 :param shift: The number of path fragments to shift. May be negative to
e1264e58154c initial commpit
sean
parents:
diff changeset
2680 change the shift direction. (default: 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
2681 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2682 if shift == 0: return script_name, path_info
e1264e58154c initial commpit
sean
parents:
diff changeset
2683 pathlist = path_info.strip('/').split('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
2684 scriptlist = script_name.strip('/').split('/')
e1264e58154c initial commpit
sean
parents:
diff changeset
2685 if pathlist and pathlist[0] == '': pathlist = []
e1264e58154c initial commpit
sean
parents:
diff changeset
2686 if scriptlist and scriptlist[0] == '': scriptlist = []
e1264e58154c initial commpit
sean
parents:
diff changeset
2687 if 0 < shift <= len(pathlist):
e1264e58154c initial commpit
sean
parents:
diff changeset
2688 moved = pathlist[:shift]
e1264e58154c initial commpit
sean
parents:
diff changeset
2689 scriptlist = scriptlist + moved
e1264e58154c initial commpit
sean
parents:
diff changeset
2690 pathlist = pathlist[shift:]
e1264e58154c initial commpit
sean
parents:
diff changeset
2691 elif 0 > shift >= -len(scriptlist):
e1264e58154c initial commpit
sean
parents:
diff changeset
2692 moved = scriptlist[shift:]
e1264e58154c initial commpit
sean
parents:
diff changeset
2693 pathlist = moved + pathlist
e1264e58154c initial commpit
sean
parents:
diff changeset
2694 scriptlist = scriptlist[:shift]
e1264e58154c initial commpit
sean
parents:
diff changeset
2695 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
2696 empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
e1264e58154c initial commpit
sean
parents:
diff changeset
2697 raise AssertionError("Cannot shift. Nothing left from %s" % empty)
e1264e58154c initial commpit
sean
parents:
diff changeset
2698 new_script_name = '/' + '/'.join(scriptlist)
e1264e58154c initial commpit
sean
parents:
diff changeset
2699 new_path_info = '/' + '/'.join(pathlist)
e1264e58154c initial commpit
sean
parents:
diff changeset
2700 if path_info.endswith('/') and pathlist: new_path_info += '/'
e1264e58154c initial commpit
sean
parents:
diff changeset
2701 return new_script_name, new_path_info
e1264e58154c initial commpit
sean
parents:
diff changeset
2702
e1264e58154c initial commpit
sean
parents:
diff changeset
2703
e1264e58154c initial commpit
sean
parents:
diff changeset
2704 def auth_basic(check, realm="private", text="Access denied"):
e1264e58154c initial commpit
sean
parents:
diff changeset
2705 """ Callback decorator to require HTTP auth (basic).
e1264e58154c initial commpit
sean
parents:
diff changeset
2706 TODO: Add route(check_auth=...) parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2707
e1264e58154c initial commpit
sean
parents:
diff changeset
2708 def decorator(func):
e1264e58154c initial commpit
sean
parents:
diff changeset
2709
e1264e58154c initial commpit
sean
parents:
diff changeset
2710 @functools.wraps(func)
e1264e58154c initial commpit
sean
parents:
diff changeset
2711 def wrapper(*a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
2712 user, password = request.auth or (None, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
2713 if user is None or not check(user, password):
e1264e58154c initial commpit
sean
parents:
diff changeset
2714 err = HTTPError(401, text)
e1264e58154c initial commpit
sean
parents:
diff changeset
2715 err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
e1264e58154c initial commpit
sean
parents:
diff changeset
2716 return err
e1264e58154c initial commpit
sean
parents:
diff changeset
2717 return func(*a, **ka)
e1264e58154c initial commpit
sean
parents:
diff changeset
2718
e1264e58154c initial commpit
sean
parents:
diff changeset
2719 return wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
2720
e1264e58154c initial commpit
sean
parents:
diff changeset
2721 return decorator
e1264e58154c initial commpit
sean
parents:
diff changeset
2722
e1264e58154c initial commpit
sean
parents:
diff changeset
2723 # Shortcuts for common Bottle methods.
e1264e58154c initial commpit
sean
parents:
diff changeset
2724 # They all refer to the current default application.
e1264e58154c initial commpit
sean
parents:
diff changeset
2725
e1264e58154c initial commpit
sean
parents:
diff changeset
2726
e1264e58154c initial commpit
sean
parents:
diff changeset
2727 def make_default_app_wrapper(name):
e1264e58154c initial commpit
sean
parents:
diff changeset
2728 """ Return a callable that relays calls to the current default app. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2729
e1264e58154c initial commpit
sean
parents:
diff changeset
2730 @functools.wraps(getattr(Bottle, name))
e1264e58154c initial commpit
sean
parents:
diff changeset
2731 def wrapper(*a, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
2732 return getattr(app(), name)(*a, **ka)
e1264e58154c initial commpit
sean
parents:
diff changeset
2733
e1264e58154c initial commpit
sean
parents:
diff changeset
2734 return wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
2735
e1264e58154c initial commpit
sean
parents:
diff changeset
2736
e1264e58154c initial commpit
sean
parents:
diff changeset
2737 route = make_default_app_wrapper('route')
e1264e58154c initial commpit
sean
parents:
diff changeset
2738 get = make_default_app_wrapper('get')
e1264e58154c initial commpit
sean
parents:
diff changeset
2739 post = make_default_app_wrapper('post')
e1264e58154c initial commpit
sean
parents:
diff changeset
2740 put = make_default_app_wrapper('put')
e1264e58154c initial commpit
sean
parents:
diff changeset
2741 delete = make_default_app_wrapper('delete')
e1264e58154c initial commpit
sean
parents:
diff changeset
2742 patch = make_default_app_wrapper('patch')
e1264e58154c initial commpit
sean
parents:
diff changeset
2743 error = make_default_app_wrapper('error')
e1264e58154c initial commpit
sean
parents:
diff changeset
2744 mount = make_default_app_wrapper('mount')
e1264e58154c initial commpit
sean
parents:
diff changeset
2745 hook = make_default_app_wrapper('hook')
e1264e58154c initial commpit
sean
parents:
diff changeset
2746 install = make_default_app_wrapper('install')
e1264e58154c initial commpit
sean
parents:
diff changeset
2747 uninstall = make_default_app_wrapper('uninstall')
e1264e58154c initial commpit
sean
parents:
diff changeset
2748 url = make_default_app_wrapper('get_url')
e1264e58154c initial commpit
sean
parents:
diff changeset
2749
e1264e58154c initial commpit
sean
parents:
diff changeset
2750 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2751 # Server Adapter ###############################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2752 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
2753
e1264e58154c initial commpit
sean
parents:
diff changeset
2754
e1264e58154c initial commpit
sean
parents:
diff changeset
2755 class ServerAdapter(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
2756 quiet = False
e1264e58154c initial commpit
sean
parents:
diff changeset
2757
e1264e58154c initial commpit
sean
parents:
diff changeset
2758 def __init__(self, host='127.0.0.1', port=8080, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
2759 self.options = options
e1264e58154c initial commpit
sean
parents:
diff changeset
2760 self.host = host
e1264e58154c initial commpit
sean
parents:
diff changeset
2761 self.port = int(port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2762
e1264e58154c initial commpit
sean
parents:
diff changeset
2763 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2764 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
2765
e1264e58154c initial commpit
sean
parents:
diff changeset
2766 def __repr__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2767 args = ', '.join(['%s=%s' % (k, repr(v))
e1264e58154c initial commpit
sean
parents:
diff changeset
2768 for k, v in self.options.items()])
e1264e58154c initial commpit
sean
parents:
diff changeset
2769 return "%s(%s)" % (self.__class__.__name__, args)
e1264e58154c initial commpit
sean
parents:
diff changeset
2770
e1264e58154c initial commpit
sean
parents:
diff changeset
2771
e1264e58154c initial commpit
sean
parents:
diff changeset
2772 class CGIServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2773 quiet = True
e1264e58154c initial commpit
sean
parents:
diff changeset
2774
e1264e58154c initial commpit
sean
parents:
diff changeset
2775 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2776 from wsgiref.handlers import CGIHandler
e1264e58154c initial commpit
sean
parents:
diff changeset
2777
e1264e58154c initial commpit
sean
parents:
diff changeset
2778 def fixed_environ(environ, start_response):
e1264e58154c initial commpit
sean
parents:
diff changeset
2779 environ.setdefault('PATH_INFO', '')
e1264e58154c initial commpit
sean
parents:
diff changeset
2780 return handler(environ, start_response)
e1264e58154c initial commpit
sean
parents:
diff changeset
2781
e1264e58154c initial commpit
sean
parents:
diff changeset
2782 CGIHandler().run(fixed_environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
2783
e1264e58154c initial commpit
sean
parents:
diff changeset
2784
e1264e58154c initial commpit
sean
parents:
diff changeset
2785 class FlupFCGIServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2786 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2787 import flup.server.fcgi
e1264e58154c initial commpit
sean
parents:
diff changeset
2788 self.options.setdefault('bindAddress', (self.host, self.port))
e1264e58154c initial commpit
sean
parents:
diff changeset
2789 flup.server.fcgi.WSGIServer(handler, **self.options).run()
e1264e58154c initial commpit
sean
parents:
diff changeset
2790
e1264e58154c initial commpit
sean
parents:
diff changeset
2791
e1264e58154c initial commpit
sean
parents:
diff changeset
2792 class WSGIRefServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2793 def run(self, app): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2794 from wsgiref.simple_server import make_server
e1264e58154c initial commpit
sean
parents:
diff changeset
2795 from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
e1264e58154c initial commpit
sean
parents:
diff changeset
2796 import socket
e1264e58154c initial commpit
sean
parents:
diff changeset
2797
e1264e58154c initial commpit
sean
parents:
diff changeset
2798 class FixedHandler(WSGIRequestHandler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2799 def address_string(self): # Prevent reverse DNS lookups please.
e1264e58154c initial commpit
sean
parents:
diff changeset
2800 return self.client_address[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
2801
e1264e58154c initial commpit
sean
parents:
diff changeset
2802 def log_request(*args, **kw):
e1264e58154c initial commpit
sean
parents:
diff changeset
2803 if not self.quiet:
e1264e58154c initial commpit
sean
parents:
diff changeset
2804 return WSGIRequestHandler.log_request(*args, **kw)
e1264e58154c initial commpit
sean
parents:
diff changeset
2805
e1264e58154c initial commpit
sean
parents:
diff changeset
2806 handler_cls = self.options.get('handler_class', FixedHandler)
e1264e58154c initial commpit
sean
parents:
diff changeset
2807 server_cls = self.options.get('server_class', WSGIServer)
e1264e58154c initial commpit
sean
parents:
diff changeset
2808
e1264e58154c initial commpit
sean
parents:
diff changeset
2809 if ':' in self.host: # Fix wsgiref for IPv6 addresses.
e1264e58154c initial commpit
sean
parents:
diff changeset
2810 if getattr(server_cls, 'address_family') == socket.AF_INET:
e1264e58154c initial commpit
sean
parents:
diff changeset
2811
e1264e58154c initial commpit
sean
parents:
diff changeset
2812 class server_cls(server_cls):
e1264e58154c initial commpit
sean
parents:
diff changeset
2813 address_family = socket.AF_INET6
e1264e58154c initial commpit
sean
parents:
diff changeset
2814
e1264e58154c initial commpit
sean
parents:
diff changeset
2815 self.srv = make_server(self.host, self.port, app, server_cls,
e1264e58154c initial commpit
sean
parents:
diff changeset
2816 handler_cls)
e1264e58154c initial commpit
sean
parents:
diff changeset
2817 self.port = self.srv.server_port # update port actual port (0 means random)
e1264e58154c initial commpit
sean
parents:
diff changeset
2818 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2819 self.srv.serve_forever()
e1264e58154c initial commpit
sean
parents:
diff changeset
2820 except KeyboardInterrupt:
e1264e58154c initial commpit
sean
parents:
diff changeset
2821 self.srv.server_close() # Prevent ResourceWarning: unclosed socket
e1264e58154c initial commpit
sean
parents:
diff changeset
2822 raise
e1264e58154c initial commpit
sean
parents:
diff changeset
2823
e1264e58154c initial commpit
sean
parents:
diff changeset
2824
e1264e58154c initial commpit
sean
parents:
diff changeset
2825 class CherryPyServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2826 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2827 from cherrypy import wsgiserver
e1264e58154c initial commpit
sean
parents:
diff changeset
2828 self.options['bind_addr'] = (self.host, self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2829 self.options['wsgi_app'] = handler
e1264e58154c initial commpit
sean
parents:
diff changeset
2830
e1264e58154c initial commpit
sean
parents:
diff changeset
2831 certfile = self.options.get('certfile')
e1264e58154c initial commpit
sean
parents:
diff changeset
2832 if certfile:
e1264e58154c initial commpit
sean
parents:
diff changeset
2833 del self.options['certfile']
e1264e58154c initial commpit
sean
parents:
diff changeset
2834 keyfile = self.options.get('keyfile')
e1264e58154c initial commpit
sean
parents:
diff changeset
2835 if keyfile:
e1264e58154c initial commpit
sean
parents:
diff changeset
2836 del self.options['keyfile']
e1264e58154c initial commpit
sean
parents:
diff changeset
2837
e1264e58154c initial commpit
sean
parents:
diff changeset
2838 server = wsgiserver.CherryPyWSGIServer(**self.options)
e1264e58154c initial commpit
sean
parents:
diff changeset
2839 if certfile:
e1264e58154c initial commpit
sean
parents:
diff changeset
2840 server.ssl_certificate = certfile
e1264e58154c initial commpit
sean
parents:
diff changeset
2841 if keyfile:
e1264e58154c initial commpit
sean
parents:
diff changeset
2842 server.ssl_private_key = keyfile
e1264e58154c initial commpit
sean
parents:
diff changeset
2843
e1264e58154c initial commpit
sean
parents:
diff changeset
2844 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
2845 server.start()
e1264e58154c initial commpit
sean
parents:
diff changeset
2846 finally:
e1264e58154c initial commpit
sean
parents:
diff changeset
2847 server.stop()
e1264e58154c initial commpit
sean
parents:
diff changeset
2848
e1264e58154c initial commpit
sean
parents:
diff changeset
2849
e1264e58154c initial commpit
sean
parents:
diff changeset
2850 class WaitressServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2851 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2852 from waitress import serve
e1264e58154c initial commpit
sean
parents:
diff changeset
2853 serve(handler, host=self.host, port=self.port, _quiet=self.quiet)
e1264e58154c initial commpit
sean
parents:
diff changeset
2854
e1264e58154c initial commpit
sean
parents:
diff changeset
2855
e1264e58154c initial commpit
sean
parents:
diff changeset
2856 class PasteServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2857 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2858 from paste import httpserver
e1264e58154c initial commpit
sean
parents:
diff changeset
2859 from paste.translogger import TransLogger
e1264e58154c initial commpit
sean
parents:
diff changeset
2860 handler = TransLogger(handler, setup_console_handler=(not self.quiet))
e1264e58154c initial commpit
sean
parents:
diff changeset
2861 httpserver.serve(handler,
e1264e58154c initial commpit
sean
parents:
diff changeset
2862 host=self.host,
e1264e58154c initial commpit
sean
parents:
diff changeset
2863 port=str(self.port), **self.options)
e1264e58154c initial commpit
sean
parents:
diff changeset
2864
e1264e58154c initial commpit
sean
parents:
diff changeset
2865
e1264e58154c initial commpit
sean
parents:
diff changeset
2866 class MeinheldServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2867 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2868 from meinheld import server
e1264e58154c initial commpit
sean
parents:
diff changeset
2869 server.listen((self.host, self.port))
e1264e58154c initial commpit
sean
parents:
diff changeset
2870 server.run(handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
2871
e1264e58154c initial commpit
sean
parents:
diff changeset
2872
e1264e58154c initial commpit
sean
parents:
diff changeset
2873 class FapwsServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2874 """ Extremely fast webserver using libev. See http://www.fapws.org/ """
e1264e58154c initial commpit
sean
parents:
diff changeset
2875
e1264e58154c initial commpit
sean
parents:
diff changeset
2876 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2877 import fapws._evwsgi as evwsgi
e1264e58154c initial commpit
sean
parents:
diff changeset
2878 from fapws import base, config
e1264e58154c initial commpit
sean
parents:
diff changeset
2879 port = self.port
e1264e58154c initial commpit
sean
parents:
diff changeset
2880 if float(config.SERVER_IDENT[-2:]) > 0.4:
e1264e58154c initial commpit
sean
parents:
diff changeset
2881 # fapws3 silently changed its API in 0.5
e1264e58154c initial commpit
sean
parents:
diff changeset
2882 port = str(port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2883 evwsgi.start(self.host, port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2884 # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
e1264e58154c initial commpit
sean
parents:
diff changeset
2885 if 'BOTTLE_CHILD' in os.environ and not self.quiet:
e1264e58154c initial commpit
sean
parents:
diff changeset
2886 _stderr("WARNING: Auto-reloading does not work with Fapws3.\n")
e1264e58154c initial commpit
sean
parents:
diff changeset
2887 _stderr(" (Fapws3 breaks python thread support)\n")
e1264e58154c initial commpit
sean
parents:
diff changeset
2888 evwsgi.set_base_module(base)
e1264e58154c initial commpit
sean
parents:
diff changeset
2889
e1264e58154c initial commpit
sean
parents:
diff changeset
2890 def app(environ, start_response):
e1264e58154c initial commpit
sean
parents:
diff changeset
2891 environ['wsgi.multiprocess'] = False
e1264e58154c initial commpit
sean
parents:
diff changeset
2892 return handler(environ, start_response)
e1264e58154c initial commpit
sean
parents:
diff changeset
2893
e1264e58154c initial commpit
sean
parents:
diff changeset
2894 evwsgi.wsgi_cb(('', app))
e1264e58154c initial commpit
sean
parents:
diff changeset
2895 evwsgi.run()
e1264e58154c initial commpit
sean
parents:
diff changeset
2896
e1264e58154c initial commpit
sean
parents:
diff changeset
2897
e1264e58154c initial commpit
sean
parents:
diff changeset
2898 class TornadoServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2899 """ The super hyped asynchronous server by facebook. Untested. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2900
e1264e58154c initial commpit
sean
parents:
diff changeset
2901 def run(self, handler): # pragma: no cover
e1264e58154c initial commpit
sean
parents:
diff changeset
2902 import tornado.wsgi, tornado.httpserver, tornado.ioloop
e1264e58154c initial commpit
sean
parents:
diff changeset
2903 container = tornado.wsgi.WSGIContainer(handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
2904 server = tornado.httpserver.HTTPServer(container)
e1264e58154c initial commpit
sean
parents:
diff changeset
2905 server.listen(port=self.port, address=self.host)
e1264e58154c initial commpit
sean
parents:
diff changeset
2906 tornado.ioloop.IOLoop.instance().start()
e1264e58154c initial commpit
sean
parents:
diff changeset
2907
e1264e58154c initial commpit
sean
parents:
diff changeset
2908
e1264e58154c initial commpit
sean
parents:
diff changeset
2909 class AppEngineServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2910 """ Adapter for Google App Engine. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2911 quiet = True
e1264e58154c initial commpit
sean
parents:
diff changeset
2912
e1264e58154c initial commpit
sean
parents:
diff changeset
2913 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2914 from google.appengine.ext.webapp import util
e1264e58154c initial commpit
sean
parents:
diff changeset
2915 # A main() function in the handler script enables 'App Caching'.
e1264e58154c initial commpit
sean
parents:
diff changeset
2916 # Lets makes sure it is there. This _really_ improves performance.
e1264e58154c initial commpit
sean
parents:
diff changeset
2917 module = sys.modules.get('__main__')
e1264e58154c initial commpit
sean
parents:
diff changeset
2918 if module and not hasattr(module, 'main'):
e1264e58154c initial commpit
sean
parents:
diff changeset
2919 module.main = lambda: util.run_wsgi_app(handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
2920 util.run_wsgi_app(handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
2921
e1264e58154c initial commpit
sean
parents:
diff changeset
2922
e1264e58154c initial commpit
sean
parents:
diff changeset
2923 class TwistedServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2924 """ Untested. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2925
e1264e58154c initial commpit
sean
parents:
diff changeset
2926 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2927 from twisted.web import server, wsgi
e1264e58154c initial commpit
sean
parents:
diff changeset
2928 from twisted.python.threadpool import ThreadPool
e1264e58154c initial commpit
sean
parents:
diff changeset
2929 from twisted.internet import reactor
e1264e58154c initial commpit
sean
parents:
diff changeset
2930 thread_pool = ThreadPool()
e1264e58154c initial commpit
sean
parents:
diff changeset
2931 thread_pool.start()
e1264e58154c initial commpit
sean
parents:
diff changeset
2932 reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
e1264e58154c initial commpit
sean
parents:
diff changeset
2933 factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
e1264e58154c initial commpit
sean
parents:
diff changeset
2934 reactor.listenTCP(self.port, factory, interface=self.host)
e1264e58154c initial commpit
sean
parents:
diff changeset
2935 if not reactor.running:
e1264e58154c initial commpit
sean
parents:
diff changeset
2936 reactor.run()
e1264e58154c initial commpit
sean
parents:
diff changeset
2937
e1264e58154c initial commpit
sean
parents:
diff changeset
2938
e1264e58154c initial commpit
sean
parents:
diff changeset
2939 class DieselServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2940 """ Untested. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2941
e1264e58154c initial commpit
sean
parents:
diff changeset
2942 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2943 from diesel.protocols.wsgi import WSGIApplication
e1264e58154c initial commpit
sean
parents:
diff changeset
2944 app = WSGIApplication(handler, port=self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2945 app.run()
e1264e58154c initial commpit
sean
parents:
diff changeset
2946
e1264e58154c initial commpit
sean
parents:
diff changeset
2947
e1264e58154c initial commpit
sean
parents:
diff changeset
2948 class GeventServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2949 """ Untested. Options:
e1264e58154c initial commpit
sean
parents:
diff changeset
2950
e1264e58154c initial commpit
sean
parents:
diff changeset
2951 * `fast` (default: False) uses libevent's http server, but has some
e1264e58154c initial commpit
sean
parents:
diff changeset
2952 issues: No streaming, no pipelining, no SSL.
e1264e58154c initial commpit
sean
parents:
diff changeset
2953 * See gevent.wsgi.WSGIServer() documentation for more options.
e1264e58154c initial commpit
sean
parents:
diff changeset
2954 """
e1264e58154c initial commpit
sean
parents:
diff changeset
2955
e1264e58154c initial commpit
sean
parents:
diff changeset
2956 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2957 from gevent import wsgi, pywsgi, local
e1264e58154c initial commpit
sean
parents:
diff changeset
2958 if not isinstance(threading.local(), local.local):
e1264e58154c initial commpit
sean
parents:
diff changeset
2959 msg = "Bottle requires gevent.monkey.patch_all() (before import)"
e1264e58154c initial commpit
sean
parents:
diff changeset
2960 raise RuntimeError(msg)
e1264e58154c initial commpit
sean
parents:
diff changeset
2961 if not self.options.pop('fast', None): wsgi = pywsgi
e1264e58154c initial commpit
sean
parents:
diff changeset
2962 self.options['log'] = None if self.quiet else 'default'
e1264e58154c initial commpit
sean
parents:
diff changeset
2963 address = (self.host, self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2964 server = wsgi.WSGIServer(address, handler, **self.options)
e1264e58154c initial commpit
sean
parents:
diff changeset
2965 if 'BOTTLE_CHILD' in os.environ:
e1264e58154c initial commpit
sean
parents:
diff changeset
2966 import signal
e1264e58154c initial commpit
sean
parents:
diff changeset
2967 signal.signal(signal.SIGINT, lambda s, f: server.stop())
e1264e58154c initial commpit
sean
parents:
diff changeset
2968 server.serve_forever()
e1264e58154c initial commpit
sean
parents:
diff changeset
2969
e1264e58154c initial commpit
sean
parents:
diff changeset
2970
e1264e58154c initial commpit
sean
parents:
diff changeset
2971 class GeventSocketIOServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2972 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2973 from socketio import server
e1264e58154c initial commpit
sean
parents:
diff changeset
2974 address = (self.host, self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
2975 server.SocketIOServer(address, handler, **self.options).serve_forever()
e1264e58154c initial commpit
sean
parents:
diff changeset
2976
e1264e58154c initial commpit
sean
parents:
diff changeset
2977
e1264e58154c initial commpit
sean
parents:
diff changeset
2978 class GunicornServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2979 """ Untested. See http://gunicorn.org/configure.html for options. """
e1264e58154c initial commpit
sean
parents:
diff changeset
2980
e1264e58154c initial commpit
sean
parents:
diff changeset
2981 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
2982 from gunicorn.app.base import Application
e1264e58154c initial commpit
sean
parents:
diff changeset
2983
e1264e58154c initial commpit
sean
parents:
diff changeset
2984 config = {'bind': "%s:%d" % (self.host, int(self.port))}
e1264e58154c initial commpit
sean
parents:
diff changeset
2985 config.update(self.options)
e1264e58154c initial commpit
sean
parents:
diff changeset
2986
e1264e58154c initial commpit
sean
parents:
diff changeset
2987 class GunicornApplication(Application):
e1264e58154c initial commpit
sean
parents:
diff changeset
2988 def init(self, parser, opts, args):
e1264e58154c initial commpit
sean
parents:
diff changeset
2989 return config
e1264e58154c initial commpit
sean
parents:
diff changeset
2990
e1264e58154c initial commpit
sean
parents:
diff changeset
2991 def load(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
2992 return handler
e1264e58154c initial commpit
sean
parents:
diff changeset
2993
e1264e58154c initial commpit
sean
parents:
diff changeset
2994 GunicornApplication().run()
e1264e58154c initial commpit
sean
parents:
diff changeset
2995
e1264e58154c initial commpit
sean
parents:
diff changeset
2996
e1264e58154c initial commpit
sean
parents:
diff changeset
2997 class EventletServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
2998 """ Untested. Options:
e1264e58154c initial commpit
sean
parents:
diff changeset
2999
e1264e58154c initial commpit
sean
parents:
diff changeset
3000 * `backlog` adjust the eventlet backlog parameter which is the maximum
e1264e58154c initial commpit
sean
parents:
diff changeset
3001 number of queued connections. Should be at least 1; the maximum
e1264e58154c initial commpit
sean
parents:
diff changeset
3002 value is system-dependent.
e1264e58154c initial commpit
sean
parents:
diff changeset
3003 * `family`: (default is 2) socket family, optional. See socket
e1264e58154c initial commpit
sean
parents:
diff changeset
3004 documentation for available families.
e1264e58154c initial commpit
sean
parents:
diff changeset
3005 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3006
e1264e58154c initial commpit
sean
parents:
diff changeset
3007 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
3008 from eventlet import wsgi, listen, patcher
e1264e58154c initial commpit
sean
parents:
diff changeset
3009 if not patcher.is_monkey_patched(os):
e1264e58154c initial commpit
sean
parents:
diff changeset
3010 msg = "Bottle requires eventlet.monkey_patch() (before import)"
e1264e58154c initial commpit
sean
parents:
diff changeset
3011 raise RuntimeError(msg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3012 socket_args = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
3013 for arg in ('backlog', 'family'):
e1264e58154c initial commpit
sean
parents:
diff changeset
3014 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3015 socket_args[arg] = self.options.pop(arg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3016 except KeyError:
e1264e58154c initial commpit
sean
parents:
diff changeset
3017 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
3018 address = (self.host, self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
3019 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3020 wsgi.server(listen(address, **socket_args), handler,
e1264e58154c initial commpit
sean
parents:
diff changeset
3021 log_output=(not self.quiet))
e1264e58154c initial commpit
sean
parents:
diff changeset
3022 except TypeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
3023 # Fallback, if we have old version of eventlet
e1264e58154c initial commpit
sean
parents:
diff changeset
3024 wsgi.server(listen(address), handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
3025
e1264e58154c initial commpit
sean
parents:
diff changeset
3026
e1264e58154c initial commpit
sean
parents:
diff changeset
3027 class RocketServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3028 """ Untested. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3029
e1264e58154c initial commpit
sean
parents:
diff changeset
3030 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
3031 from rocket import Rocket
e1264e58154c initial commpit
sean
parents:
diff changeset
3032 server = Rocket((self.host, self.port), 'wsgi', {'wsgi_app': handler})
e1264e58154c initial commpit
sean
parents:
diff changeset
3033 server.start()
e1264e58154c initial commpit
sean
parents:
diff changeset
3034
e1264e58154c initial commpit
sean
parents:
diff changeset
3035
e1264e58154c initial commpit
sean
parents:
diff changeset
3036 class BjoernServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3037 """ Fast server written in C: https://github.com/jonashaag/bjoern """
e1264e58154c initial commpit
sean
parents:
diff changeset
3038
e1264e58154c initial commpit
sean
parents:
diff changeset
3039 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
3040 from bjoern import run
e1264e58154c initial commpit
sean
parents:
diff changeset
3041 run(handler, self.host, self.port)
e1264e58154c initial commpit
sean
parents:
diff changeset
3042
e1264e58154c initial commpit
sean
parents:
diff changeset
3043
e1264e58154c initial commpit
sean
parents:
diff changeset
3044 class AiohttpServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3045 """ Untested.
e1264e58154c initial commpit
sean
parents:
diff changeset
3046 aiohttp
e1264e58154c initial commpit
sean
parents:
diff changeset
3047 https://pypi.python.org/pypi/aiohttp/
e1264e58154c initial commpit
sean
parents:
diff changeset
3048 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3049
e1264e58154c initial commpit
sean
parents:
diff changeset
3050 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
3051 import asyncio
e1264e58154c initial commpit
sean
parents:
diff changeset
3052 from aiohttp.wsgi import WSGIServerHttpProtocol
e1264e58154c initial commpit
sean
parents:
diff changeset
3053 self.loop = asyncio.new_event_loop()
e1264e58154c initial commpit
sean
parents:
diff changeset
3054 asyncio.set_event_loop(self.loop)
e1264e58154c initial commpit
sean
parents:
diff changeset
3055
e1264e58154c initial commpit
sean
parents:
diff changeset
3056 protocol_factory = lambda: WSGIServerHttpProtocol(
e1264e58154c initial commpit
sean
parents:
diff changeset
3057 handler,
e1264e58154c initial commpit
sean
parents:
diff changeset
3058 readpayload=True,
e1264e58154c initial commpit
sean
parents:
diff changeset
3059 debug=(not self.quiet))
e1264e58154c initial commpit
sean
parents:
diff changeset
3060 self.loop.run_until_complete(self.loop.create_server(protocol_factory,
e1264e58154c initial commpit
sean
parents:
diff changeset
3061 self.host,
e1264e58154c initial commpit
sean
parents:
diff changeset
3062 self.port))
e1264e58154c initial commpit
sean
parents:
diff changeset
3063
e1264e58154c initial commpit
sean
parents:
diff changeset
3064 if 'BOTTLE_CHILD' in os.environ:
e1264e58154c initial commpit
sean
parents:
diff changeset
3065 import signal
e1264e58154c initial commpit
sean
parents:
diff changeset
3066 signal.signal(signal.SIGINT, lambda s, f: self.loop.stop())
e1264e58154c initial commpit
sean
parents:
diff changeset
3067
e1264e58154c initial commpit
sean
parents:
diff changeset
3068 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3069 self.loop.run_forever()
e1264e58154c initial commpit
sean
parents:
diff changeset
3070 except KeyboardInterrupt:
e1264e58154c initial commpit
sean
parents:
diff changeset
3071 self.loop.stop()
e1264e58154c initial commpit
sean
parents:
diff changeset
3072
e1264e58154c initial commpit
sean
parents:
diff changeset
3073
e1264e58154c initial commpit
sean
parents:
diff changeset
3074 class AutoServer(ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3075 """ Untested. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3076 adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3077 WSGIRefServer]
e1264e58154c initial commpit
sean
parents:
diff changeset
3078
e1264e58154c initial commpit
sean
parents:
diff changeset
3079 def run(self, handler):
e1264e58154c initial commpit
sean
parents:
diff changeset
3080 for sa in self.adapters:
e1264e58154c initial commpit
sean
parents:
diff changeset
3081 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3082 return sa(self.host, self.port, **self.options).run(handler)
e1264e58154c initial commpit
sean
parents:
diff changeset
3083 except ImportError:
e1264e58154c initial commpit
sean
parents:
diff changeset
3084 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
3085
e1264e58154c initial commpit
sean
parents:
diff changeset
3086
e1264e58154c initial commpit
sean
parents:
diff changeset
3087 server_names = {
e1264e58154c initial commpit
sean
parents:
diff changeset
3088 'cgi': CGIServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3089 'flup': FlupFCGIServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3090 'wsgiref': WSGIRefServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3091 'waitress': WaitressServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3092 'cherrypy': CherryPyServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3093 'paste': PasteServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3094 'fapws3': FapwsServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3095 'tornado': TornadoServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3096 'gae': AppEngineServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3097 'twisted': TwistedServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3098 'diesel': DieselServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3099 'meinheld': MeinheldServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3100 'gunicorn': GunicornServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3101 'eventlet': EventletServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3102 'gevent': GeventServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3103 'geventSocketIO': GeventSocketIOServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3104 'rocket': RocketServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3105 'bjoern': BjoernServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3106 'aiohttp': AiohttpServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3107 'auto': AutoServer,
e1264e58154c initial commpit
sean
parents:
diff changeset
3108 }
e1264e58154c initial commpit
sean
parents:
diff changeset
3109
e1264e58154c initial commpit
sean
parents:
diff changeset
3110 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3111 # Application Control ##########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3112 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3113
e1264e58154c initial commpit
sean
parents:
diff changeset
3114
e1264e58154c initial commpit
sean
parents:
diff changeset
3115 def load(target, **namespace):
e1264e58154c initial commpit
sean
parents:
diff changeset
3116 """ Import a module or fetch an object from a module.
e1264e58154c initial commpit
sean
parents:
diff changeset
3117
e1264e58154c initial commpit
sean
parents:
diff changeset
3118 * ``package.module`` returns `module` as a module object.
e1264e58154c initial commpit
sean
parents:
diff changeset
3119 * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
e1264e58154c initial commpit
sean
parents:
diff changeset
3120 * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
e1264e58154c initial commpit
sean
parents:
diff changeset
3121
e1264e58154c initial commpit
sean
parents:
diff changeset
3122 The last form accepts not only function calls, but any type of
e1264e58154c initial commpit
sean
parents:
diff changeset
3123 expression. Keyword arguments passed to this function are available as
e1264e58154c initial commpit
sean
parents:
diff changeset
3124 local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
e1264e58154c initial commpit
sean
parents:
diff changeset
3125 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3126 module, target = target.split(":", 1) if ':' in target else (target, None)
e1264e58154c initial commpit
sean
parents:
diff changeset
3127 if module not in sys.modules: __import__(module)
e1264e58154c initial commpit
sean
parents:
diff changeset
3128 if not target: return sys.modules[module]
e1264e58154c initial commpit
sean
parents:
diff changeset
3129 if target.isalnum(): return getattr(sys.modules[module], target)
e1264e58154c initial commpit
sean
parents:
diff changeset
3130 package_name = module.split('.')[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
3131 namespace[package_name] = sys.modules[package_name]
e1264e58154c initial commpit
sean
parents:
diff changeset
3132 return eval('%s.%s' % (module, target), namespace)
e1264e58154c initial commpit
sean
parents:
diff changeset
3133
e1264e58154c initial commpit
sean
parents:
diff changeset
3134
e1264e58154c initial commpit
sean
parents:
diff changeset
3135 def load_app(target):
e1264e58154c initial commpit
sean
parents:
diff changeset
3136 """ Load a bottle application from a module and make sure that the import
e1264e58154c initial commpit
sean
parents:
diff changeset
3137 does not affect the current default application, but returns a separate
e1264e58154c initial commpit
sean
parents:
diff changeset
3138 application object. See :func:`load` for the target parameter. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3139 global NORUN
e1264e58154c initial commpit
sean
parents:
diff changeset
3140 NORUN, nr_old = True, NORUN
e1264e58154c initial commpit
sean
parents:
diff changeset
3141 tmp = default_app.push() # Create a new "default application"
e1264e58154c initial commpit
sean
parents:
diff changeset
3142 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3143 rv = load(target) # Import the target module
e1264e58154c initial commpit
sean
parents:
diff changeset
3144 return rv if callable(rv) else tmp
e1264e58154c initial commpit
sean
parents:
diff changeset
3145 finally:
e1264e58154c initial commpit
sean
parents:
diff changeset
3146 default_app.remove(tmp) # Remove the temporary added default application
e1264e58154c initial commpit
sean
parents:
diff changeset
3147 NORUN = nr_old
e1264e58154c initial commpit
sean
parents:
diff changeset
3148
e1264e58154c initial commpit
sean
parents:
diff changeset
3149
e1264e58154c initial commpit
sean
parents:
diff changeset
3150 _debug = debug
e1264e58154c initial commpit
sean
parents:
diff changeset
3151
e1264e58154c initial commpit
sean
parents:
diff changeset
3152
e1264e58154c initial commpit
sean
parents:
diff changeset
3153 def run(app=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3154 server='wsgiref',
e1264e58154c initial commpit
sean
parents:
diff changeset
3155 host='127.0.0.1',
e1264e58154c initial commpit
sean
parents:
diff changeset
3156 port=8080,
e1264e58154c initial commpit
sean
parents:
diff changeset
3157 interval=1,
e1264e58154c initial commpit
sean
parents:
diff changeset
3158 reloader=False,
e1264e58154c initial commpit
sean
parents:
diff changeset
3159 quiet=False,
e1264e58154c initial commpit
sean
parents:
diff changeset
3160 plugins=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3161 debug=None, **kargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3162 """ Start a server instance. This method blocks until the server terminates.
e1264e58154c initial commpit
sean
parents:
diff changeset
3163
e1264e58154c initial commpit
sean
parents:
diff changeset
3164 :param app: WSGI application or target string supported by
e1264e58154c initial commpit
sean
parents:
diff changeset
3165 :func:`load_app`. (default: :func:`default_app`)
e1264e58154c initial commpit
sean
parents:
diff changeset
3166 :param server: Server adapter to use. See :data:`server_names` keys
e1264e58154c initial commpit
sean
parents:
diff changeset
3167 for valid names or pass a :class:`ServerAdapter` subclass.
e1264e58154c initial commpit
sean
parents:
diff changeset
3168 (default: `wsgiref`)
e1264e58154c initial commpit
sean
parents:
diff changeset
3169 :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
e1264e58154c initial commpit
sean
parents:
diff changeset
3170 all interfaces including the external one. (default: 127.0.0.1)
e1264e58154c initial commpit
sean
parents:
diff changeset
3171 :param port: Server port to bind to. Values below 1024 require root
e1264e58154c initial commpit
sean
parents:
diff changeset
3172 privileges. (default: 8080)
e1264e58154c initial commpit
sean
parents:
diff changeset
3173 :param reloader: Start auto-reloading server? (default: False)
e1264e58154c initial commpit
sean
parents:
diff changeset
3174 :param interval: Auto-reloader interval in seconds (default: 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
3175 :param quiet: Suppress output to stdout and stderr? (default: False)
e1264e58154c initial commpit
sean
parents:
diff changeset
3176 :param options: Options passed to the server adapter.
e1264e58154c initial commpit
sean
parents:
diff changeset
3177 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3178 if NORUN: return
e1264e58154c initial commpit
sean
parents:
diff changeset
3179 if reloader and not os.environ.get('BOTTLE_CHILD'):
e1264e58154c initial commpit
sean
parents:
diff changeset
3180 import subprocess
e1264e58154c initial commpit
sean
parents:
diff changeset
3181 lockfile = None
e1264e58154c initial commpit
sean
parents:
diff changeset
3182 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3183 fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
e1264e58154c initial commpit
sean
parents:
diff changeset
3184 os.close(fd) # We only need this file to exist. We never write to it
e1264e58154c initial commpit
sean
parents:
diff changeset
3185 while os.path.exists(lockfile):
e1264e58154c initial commpit
sean
parents:
diff changeset
3186 args = [sys.executable] + sys.argv
e1264e58154c initial commpit
sean
parents:
diff changeset
3187 environ = os.environ.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3188 environ['BOTTLE_CHILD'] = 'true'
e1264e58154c initial commpit
sean
parents:
diff changeset
3189 environ['BOTTLE_LOCKFILE'] = lockfile
e1264e58154c initial commpit
sean
parents:
diff changeset
3190 p = subprocess.Popen(args, env=environ)
e1264e58154c initial commpit
sean
parents:
diff changeset
3191 while p.poll() is None: # Busy wait...
e1264e58154c initial commpit
sean
parents:
diff changeset
3192 os.utime(lockfile, None) # I am alive!
e1264e58154c initial commpit
sean
parents:
diff changeset
3193 time.sleep(interval)
e1264e58154c initial commpit
sean
parents:
diff changeset
3194 if p.poll() != 3:
e1264e58154c initial commpit
sean
parents:
diff changeset
3195 if os.path.exists(lockfile): os.unlink(lockfile)
e1264e58154c initial commpit
sean
parents:
diff changeset
3196 sys.exit(p.poll())
e1264e58154c initial commpit
sean
parents:
diff changeset
3197 except KeyboardInterrupt:
e1264e58154c initial commpit
sean
parents:
diff changeset
3198 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
3199 finally:
e1264e58154c initial commpit
sean
parents:
diff changeset
3200 if os.path.exists(lockfile):
e1264e58154c initial commpit
sean
parents:
diff changeset
3201 os.unlink(lockfile)
e1264e58154c initial commpit
sean
parents:
diff changeset
3202 return
e1264e58154c initial commpit
sean
parents:
diff changeset
3203
e1264e58154c initial commpit
sean
parents:
diff changeset
3204 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3205 if debug is not None: _debug(debug)
e1264e58154c initial commpit
sean
parents:
diff changeset
3206 app = app or default_app()
e1264e58154c initial commpit
sean
parents:
diff changeset
3207 if isinstance(app, basestring):
e1264e58154c initial commpit
sean
parents:
diff changeset
3208 app = load_app(app)
e1264e58154c initial commpit
sean
parents:
diff changeset
3209 if not callable(app):
e1264e58154c initial commpit
sean
parents:
diff changeset
3210 raise ValueError("Application is not callable: %r" % app)
e1264e58154c initial commpit
sean
parents:
diff changeset
3211
e1264e58154c initial commpit
sean
parents:
diff changeset
3212 for plugin in plugins or []:
e1264e58154c initial commpit
sean
parents:
diff changeset
3213 if isinstance(plugin, basestring):
e1264e58154c initial commpit
sean
parents:
diff changeset
3214 plugin = load(plugin)
e1264e58154c initial commpit
sean
parents:
diff changeset
3215 app.install(plugin)
e1264e58154c initial commpit
sean
parents:
diff changeset
3216
e1264e58154c initial commpit
sean
parents:
diff changeset
3217 if server in server_names:
e1264e58154c initial commpit
sean
parents:
diff changeset
3218 server = server_names.get(server)
e1264e58154c initial commpit
sean
parents:
diff changeset
3219 if isinstance(server, basestring):
e1264e58154c initial commpit
sean
parents:
diff changeset
3220 server = load(server)
e1264e58154c initial commpit
sean
parents:
diff changeset
3221 if isinstance(server, type):
e1264e58154c initial commpit
sean
parents:
diff changeset
3222 server = server(host=host, port=port, **kargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3223 if not isinstance(server, ServerAdapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3224 raise ValueError("Unknown or unsupported server: %r" % server)
e1264e58154c initial commpit
sean
parents:
diff changeset
3225
e1264e58154c initial commpit
sean
parents:
diff changeset
3226 server.quiet = server.quiet or quiet
e1264e58154c initial commpit
sean
parents:
diff changeset
3227 if not server.quiet:
e1264e58154c initial commpit
sean
parents:
diff changeset
3228 _stderr("Bottle v%s server starting up (using %s)...\n" %
e1264e58154c initial commpit
sean
parents:
diff changeset
3229 (__version__, repr(server)))
e1264e58154c initial commpit
sean
parents:
diff changeset
3230 _stderr("Listening on http://%s:%d/\n" %
e1264e58154c initial commpit
sean
parents:
diff changeset
3231 (server.host, server.port))
e1264e58154c initial commpit
sean
parents:
diff changeset
3232 _stderr("Hit Ctrl-C to quit.\n\n")
e1264e58154c initial commpit
sean
parents:
diff changeset
3233
e1264e58154c initial commpit
sean
parents:
diff changeset
3234 if reloader:
e1264e58154c initial commpit
sean
parents:
diff changeset
3235 lockfile = os.environ.get('BOTTLE_LOCKFILE')
e1264e58154c initial commpit
sean
parents:
diff changeset
3236 bgcheck = FileCheckerThread(lockfile, interval)
e1264e58154c initial commpit
sean
parents:
diff changeset
3237 with bgcheck:
e1264e58154c initial commpit
sean
parents:
diff changeset
3238 server.run(app)
e1264e58154c initial commpit
sean
parents:
diff changeset
3239 if bgcheck.status == 'reload':
e1264e58154c initial commpit
sean
parents:
diff changeset
3240 sys.exit(3)
e1264e58154c initial commpit
sean
parents:
diff changeset
3241 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3242 server.run(app)
e1264e58154c initial commpit
sean
parents:
diff changeset
3243 except KeyboardInterrupt:
e1264e58154c initial commpit
sean
parents:
diff changeset
3244 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
3245 except (SystemExit, MemoryError):
e1264e58154c initial commpit
sean
parents:
diff changeset
3246 raise
e1264e58154c initial commpit
sean
parents:
diff changeset
3247 except:
e1264e58154c initial commpit
sean
parents:
diff changeset
3248 if not reloader: raise
e1264e58154c initial commpit
sean
parents:
diff changeset
3249 if not getattr(server, 'quiet', quiet):
e1264e58154c initial commpit
sean
parents:
diff changeset
3250 print_exc()
e1264e58154c initial commpit
sean
parents:
diff changeset
3251 time.sleep(interval)
e1264e58154c initial commpit
sean
parents:
diff changeset
3252 sys.exit(3)
e1264e58154c initial commpit
sean
parents:
diff changeset
3253
e1264e58154c initial commpit
sean
parents:
diff changeset
3254
e1264e58154c initial commpit
sean
parents:
diff changeset
3255 class FileCheckerThread(threading.Thread):
e1264e58154c initial commpit
sean
parents:
diff changeset
3256 """ Interrupt main-thread as soon as a changed module file is detected,
e1264e58154c initial commpit
sean
parents:
diff changeset
3257 the lockfile gets deleted or gets to old. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3258
e1264e58154c initial commpit
sean
parents:
diff changeset
3259 def __init__(self, lockfile, interval):
e1264e58154c initial commpit
sean
parents:
diff changeset
3260 threading.Thread.__init__(self)
e1264e58154c initial commpit
sean
parents:
diff changeset
3261 self.daemon = True
e1264e58154c initial commpit
sean
parents:
diff changeset
3262 self.lockfile, self.interval = lockfile, interval
e1264e58154c initial commpit
sean
parents:
diff changeset
3263 #: Is one of 'reload', 'error' or 'exit'
e1264e58154c initial commpit
sean
parents:
diff changeset
3264 self.status = None
e1264e58154c initial commpit
sean
parents:
diff changeset
3265
e1264e58154c initial commpit
sean
parents:
diff changeset
3266 def run(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3267 exists = os.path.exists
e1264e58154c initial commpit
sean
parents:
diff changeset
3268 mtime = lambda p: os.stat(p).st_mtime
e1264e58154c initial commpit
sean
parents:
diff changeset
3269 files = dict()
e1264e58154c initial commpit
sean
parents:
diff changeset
3270
e1264e58154c initial commpit
sean
parents:
diff changeset
3271 for module in list(sys.modules.values()):
e1264e58154c initial commpit
sean
parents:
diff changeset
3272 path = getattr(module, '__file__', '')
e1264e58154c initial commpit
sean
parents:
diff changeset
3273 if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
e1264e58154c initial commpit
sean
parents:
diff changeset
3274 if path and exists(path): files[path] = mtime(path)
e1264e58154c initial commpit
sean
parents:
diff changeset
3275
e1264e58154c initial commpit
sean
parents:
diff changeset
3276 while not self.status:
e1264e58154c initial commpit
sean
parents:
diff changeset
3277 if not exists(self.lockfile)\
e1264e58154c initial commpit
sean
parents:
diff changeset
3278 or mtime(self.lockfile) < time.time() - self.interval - 5:
e1264e58154c initial commpit
sean
parents:
diff changeset
3279 self.status = 'error'
e1264e58154c initial commpit
sean
parents:
diff changeset
3280 thread.interrupt_main()
e1264e58154c initial commpit
sean
parents:
diff changeset
3281 for path, lmtime in list(files.items()):
e1264e58154c initial commpit
sean
parents:
diff changeset
3282 if not exists(path) or mtime(path) > lmtime:
e1264e58154c initial commpit
sean
parents:
diff changeset
3283 self.status = 'reload'
e1264e58154c initial commpit
sean
parents:
diff changeset
3284 thread.interrupt_main()
e1264e58154c initial commpit
sean
parents:
diff changeset
3285 break
e1264e58154c initial commpit
sean
parents:
diff changeset
3286 time.sleep(self.interval)
e1264e58154c initial commpit
sean
parents:
diff changeset
3287
e1264e58154c initial commpit
sean
parents:
diff changeset
3288 def __enter__(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3289 self.start()
e1264e58154c initial commpit
sean
parents:
diff changeset
3290
e1264e58154c initial commpit
sean
parents:
diff changeset
3291 def __exit__(self, exc_type, *_):
e1264e58154c initial commpit
sean
parents:
diff changeset
3292 if not self.status: self.status = 'exit' # silent exit
e1264e58154c initial commpit
sean
parents:
diff changeset
3293 self.join()
e1264e58154c initial commpit
sean
parents:
diff changeset
3294 return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
e1264e58154c initial commpit
sean
parents:
diff changeset
3295
e1264e58154c initial commpit
sean
parents:
diff changeset
3296 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3297 # Template Adapters ############################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3298 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3299
e1264e58154c initial commpit
sean
parents:
diff changeset
3300
e1264e58154c initial commpit
sean
parents:
diff changeset
3301 class TemplateError(HTTPError):
e1264e58154c initial commpit
sean
parents:
diff changeset
3302 def __init__(self, message):
e1264e58154c initial commpit
sean
parents:
diff changeset
3303 HTTPError.__init__(self, 500, message)
e1264e58154c initial commpit
sean
parents:
diff changeset
3304
e1264e58154c initial commpit
sean
parents:
diff changeset
3305
e1264e58154c initial commpit
sean
parents:
diff changeset
3306 class BaseTemplate(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
3307 """ Base class and minimal API for template adapters """
e1264e58154c initial commpit
sean
parents:
diff changeset
3308 extensions = ['tpl', 'html', 'thtml', 'stpl']
e1264e58154c initial commpit
sean
parents:
diff changeset
3309 settings = {} #used in prepare()
e1264e58154c initial commpit
sean
parents:
diff changeset
3310 defaults = {} #used in render()
e1264e58154c initial commpit
sean
parents:
diff changeset
3311
e1264e58154c initial commpit
sean
parents:
diff changeset
3312 def __init__(self,
e1264e58154c initial commpit
sean
parents:
diff changeset
3313 source=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3314 name=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3315 lookup=None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3316 encoding='utf8', **settings):
e1264e58154c initial commpit
sean
parents:
diff changeset
3317 """ Create a new template.
e1264e58154c initial commpit
sean
parents:
diff changeset
3318 If the source parameter (str or buffer) is missing, the name argument
e1264e58154c initial commpit
sean
parents:
diff changeset
3319 is used to guess a template filename. Subclasses can assume that
e1264e58154c initial commpit
sean
parents:
diff changeset
3320 self.source and/or self.filename are set. Both are strings.
e1264e58154c initial commpit
sean
parents:
diff changeset
3321 The lookup, encoding and settings parameters are stored as instance
e1264e58154c initial commpit
sean
parents:
diff changeset
3322 variables.
e1264e58154c initial commpit
sean
parents:
diff changeset
3323 The lookup parameter stores a list containing directory paths.
e1264e58154c initial commpit
sean
parents:
diff changeset
3324 The encoding parameter should be used to decode byte strings or files.
e1264e58154c initial commpit
sean
parents:
diff changeset
3325 The settings parameter contains a dict for engine-specific settings.
e1264e58154c initial commpit
sean
parents:
diff changeset
3326 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3327 self.name = name
e1264e58154c initial commpit
sean
parents:
diff changeset
3328 self.source = source.read() if hasattr(source, 'read') else source
e1264e58154c initial commpit
sean
parents:
diff changeset
3329 self.filename = source.filename if hasattr(source, 'filename') else None
e1264e58154c initial commpit
sean
parents:
diff changeset
3330 self.lookup = [os.path.abspath(x) for x in lookup] if lookup else []
e1264e58154c initial commpit
sean
parents:
diff changeset
3331 self.encoding = encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
3332 self.settings = self.settings.copy() # Copy from class variable
e1264e58154c initial commpit
sean
parents:
diff changeset
3333 self.settings.update(settings) # Apply
e1264e58154c initial commpit
sean
parents:
diff changeset
3334 if not self.source and self.name:
e1264e58154c initial commpit
sean
parents:
diff changeset
3335 self.filename = self.search(self.name, self.lookup)
e1264e58154c initial commpit
sean
parents:
diff changeset
3336 if not self.filename:
e1264e58154c initial commpit
sean
parents:
diff changeset
3337 raise TemplateError('Template %s not found.' % repr(name))
e1264e58154c initial commpit
sean
parents:
diff changeset
3338 if not self.source and not self.filename:
e1264e58154c initial commpit
sean
parents:
diff changeset
3339 raise TemplateError('No template specified.')
e1264e58154c initial commpit
sean
parents:
diff changeset
3340 self.prepare(**self.settings)
e1264e58154c initial commpit
sean
parents:
diff changeset
3341
e1264e58154c initial commpit
sean
parents:
diff changeset
3342 @classmethod
e1264e58154c initial commpit
sean
parents:
diff changeset
3343 def search(cls, name, lookup=None):
e1264e58154c initial commpit
sean
parents:
diff changeset
3344 """ Search name in all directories specified in lookup.
e1264e58154c initial commpit
sean
parents:
diff changeset
3345 First without, then with common extensions. Return first hit. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3346 if not lookup:
e1264e58154c initial commpit
sean
parents:
diff changeset
3347 depr('The template lookup path list should not be empty.',
e1264e58154c initial commpit
sean
parents:
diff changeset
3348 True) #0.12
e1264e58154c initial commpit
sean
parents:
diff changeset
3349 lookup = ['.']
e1264e58154c initial commpit
sean
parents:
diff changeset
3350
e1264e58154c initial commpit
sean
parents:
diff changeset
3351 if os.path.isabs(name) and os.path.isfile(name):
e1264e58154c initial commpit
sean
parents:
diff changeset
3352 depr('Absolute template path names are deprecated.', True) #0.12
e1264e58154c initial commpit
sean
parents:
diff changeset
3353 return os.path.abspath(name)
e1264e58154c initial commpit
sean
parents:
diff changeset
3354
e1264e58154c initial commpit
sean
parents:
diff changeset
3355 for spath in lookup:
e1264e58154c initial commpit
sean
parents:
diff changeset
3356 spath = os.path.abspath(spath) + os.sep
e1264e58154c initial commpit
sean
parents:
diff changeset
3357 fname = os.path.abspath(os.path.join(spath, name))
e1264e58154c initial commpit
sean
parents:
diff changeset
3358 if not fname.startswith(spath): continue
e1264e58154c initial commpit
sean
parents:
diff changeset
3359 if os.path.isfile(fname): return fname
e1264e58154c initial commpit
sean
parents:
diff changeset
3360 for ext in cls.extensions:
e1264e58154c initial commpit
sean
parents:
diff changeset
3361 if os.path.isfile('%s.%s' % (fname, ext)):
e1264e58154c initial commpit
sean
parents:
diff changeset
3362 return '%s.%s' % (fname, ext)
e1264e58154c initial commpit
sean
parents:
diff changeset
3363
e1264e58154c initial commpit
sean
parents:
diff changeset
3364 @classmethod
e1264e58154c initial commpit
sean
parents:
diff changeset
3365 def global_config(cls, key, *args):
e1264e58154c initial commpit
sean
parents:
diff changeset
3366 """ This reads or sets the global settings stored in class.settings. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3367 if args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3368 cls.settings = cls.settings.copy() # Make settings local to class
e1264e58154c initial commpit
sean
parents:
diff changeset
3369 cls.settings[key] = args[0]
e1264e58154c initial commpit
sean
parents:
diff changeset
3370 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3371 return cls.settings[key]
e1264e58154c initial commpit
sean
parents:
diff changeset
3372
e1264e58154c initial commpit
sean
parents:
diff changeset
3373 def prepare(self, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
3374 """ Run preparations (parsing, caching, ...).
e1264e58154c initial commpit
sean
parents:
diff changeset
3375 It should be possible to call this again to refresh a template or to
e1264e58154c initial commpit
sean
parents:
diff changeset
3376 update settings.
e1264e58154c initial commpit
sean
parents:
diff changeset
3377 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3378 raise NotImplementedError
e1264e58154c initial commpit
sean
parents:
diff changeset
3379
e1264e58154c initial commpit
sean
parents:
diff changeset
3380 def render(self, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3381 """ Render the template with the specified local variables and return
e1264e58154c initial commpit
sean
parents:
diff changeset
3382 a single byte or unicode string. If it is a byte string, the encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
3383 must match self.encoding. This method must be thread-safe!
e1264e58154c initial commpit
sean
parents:
diff changeset
3384 Local variables may be provided in dictionaries (args)
e1264e58154c initial commpit
sean
parents:
diff changeset
3385 or directly, as keywords (kwargs).
e1264e58154c initial commpit
sean
parents:
diff changeset
3386 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3387 raise NotImplementedError
e1264e58154c initial commpit
sean
parents:
diff changeset
3388
e1264e58154c initial commpit
sean
parents:
diff changeset
3389
e1264e58154c initial commpit
sean
parents:
diff changeset
3390 class MakoTemplate(BaseTemplate):
e1264e58154c initial commpit
sean
parents:
diff changeset
3391 def prepare(self, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
3392 from mako.template import Template
e1264e58154c initial commpit
sean
parents:
diff changeset
3393 from mako.lookup import TemplateLookup
e1264e58154c initial commpit
sean
parents:
diff changeset
3394 options.update({'input_encoding': self.encoding})
e1264e58154c initial commpit
sean
parents:
diff changeset
3395 options.setdefault('format_exceptions', bool(DEBUG))
e1264e58154c initial commpit
sean
parents:
diff changeset
3396 lookup = TemplateLookup(directories=self.lookup, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
3397 if self.source:
e1264e58154c initial commpit
sean
parents:
diff changeset
3398 self.tpl = Template(self.source, lookup=lookup, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
3399 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3400 self.tpl = Template(uri=self.name,
e1264e58154c initial commpit
sean
parents:
diff changeset
3401 filename=self.filename,
e1264e58154c initial commpit
sean
parents:
diff changeset
3402 lookup=lookup, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
3403
e1264e58154c initial commpit
sean
parents:
diff changeset
3404 def render(self, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3405 for dictarg in args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3406 kwargs.update(dictarg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3407 _defaults = self.defaults.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3408 _defaults.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3409 return self.tpl.render(**_defaults)
e1264e58154c initial commpit
sean
parents:
diff changeset
3410
e1264e58154c initial commpit
sean
parents:
diff changeset
3411
e1264e58154c initial commpit
sean
parents:
diff changeset
3412 class CheetahTemplate(BaseTemplate):
e1264e58154c initial commpit
sean
parents:
diff changeset
3413 def prepare(self, **options):
e1264e58154c initial commpit
sean
parents:
diff changeset
3414 from Cheetah.Template import Template
e1264e58154c initial commpit
sean
parents:
diff changeset
3415 self.context = threading.local()
e1264e58154c initial commpit
sean
parents:
diff changeset
3416 self.context.vars = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
3417 options['searchList'] = [self.context.vars]
e1264e58154c initial commpit
sean
parents:
diff changeset
3418 if self.source:
e1264e58154c initial commpit
sean
parents:
diff changeset
3419 self.tpl = Template(source=self.source, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
3420 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3421 self.tpl = Template(file=self.filename, **options)
e1264e58154c initial commpit
sean
parents:
diff changeset
3422
e1264e58154c initial commpit
sean
parents:
diff changeset
3423 def render(self, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3424 for dictarg in args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3425 kwargs.update(dictarg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3426 self.context.vars.update(self.defaults)
e1264e58154c initial commpit
sean
parents:
diff changeset
3427 self.context.vars.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3428 out = str(self.tpl)
e1264e58154c initial commpit
sean
parents:
diff changeset
3429 self.context.vars.clear()
e1264e58154c initial commpit
sean
parents:
diff changeset
3430 return out
e1264e58154c initial commpit
sean
parents:
diff changeset
3431
e1264e58154c initial commpit
sean
parents:
diff changeset
3432
e1264e58154c initial commpit
sean
parents:
diff changeset
3433 class Jinja2Template(BaseTemplate):
e1264e58154c initial commpit
sean
parents:
diff changeset
3434 def prepare(self, filters=None, tests=None, globals={}, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3435 from jinja2 import Environment, FunctionLoader
e1264e58154c initial commpit
sean
parents:
diff changeset
3436 self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3437 if filters: self.env.filters.update(filters)
e1264e58154c initial commpit
sean
parents:
diff changeset
3438 if tests: self.env.tests.update(tests)
e1264e58154c initial commpit
sean
parents:
diff changeset
3439 if globals: self.env.globals.update(globals)
e1264e58154c initial commpit
sean
parents:
diff changeset
3440 if self.source:
e1264e58154c initial commpit
sean
parents:
diff changeset
3441 self.tpl = self.env.from_string(self.source)
e1264e58154c initial commpit
sean
parents:
diff changeset
3442 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3443 self.tpl = self.env.get_template(self.filename)
e1264e58154c initial commpit
sean
parents:
diff changeset
3444
e1264e58154c initial commpit
sean
parents:
diff changeset
3445 def render(self, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3446 for dictarg in args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3447 kwargs.update(dictarg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3448 _defaults = self.defaults.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3449 _defaults.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3450 return self.tpl.render(**_defaults)
e1264e58154c initial commpit
sean
parents:
diff changeset
3451
e1264e58154c initial commpit
sean
parents:
diff changeset
3452 def loader(self, name):
e1264e58154c initial commpit
sean
parents:
diff changeset
3453 fname = self.search(name, self.lookup)
e1264e58154c initial commpit
sean
parents:
diff changeset
3454 if not fname: return
e1264e58154c initial commpit
sean
parents:
diff changeset
3455 with open(fname, "rb") as f:
e1264e58154c initial commpit
sean
parents:
diff changeset
3456 return f.read().decode(self.encoding)
e1264e58154c initial commpit
sean
parents:
diff changeset
3457
e1264e58154c initial commpit
sean
parents:
diff changeset
3458
e1264e58154c initial commpit
sean
parents:
diff changeset
3459 class SimpleTemplate(BaseTemplate):
e1264e58154c initial commpit
sean
parents:
diff changeset
3460 def prepare(self,
e1264e58154c initial commpit
sean
parents:
diff changeset
3461 escape_func=html_escape,
e1264e58154c initial commpit
sean
parents:
diff changeset
3462 noescape=False,
e1264e58154c initial commpit
sean
parents:
diff changeset
3463 syntax=None, **ka):
e1264e58154c initial commpit
sean
parents:
diff changeset
3464 self.cache = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
3465 enc = self.encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
3466 self._str = lambda x: touni(x, enc)
e1264e58154c initial commpit
sean
parents:
diff changeset
3467 self._escape = lambda x: escape_func(touni(x, enc))
e1264e58154c initial commpit
sean
parents:
diff changeset
3468 self.syntax = syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
3469 if noescape:
e1264e58154c initial commpit
sean
parents:
diff changeset
3470 self._str, self._escape = self._escape, self._str
e1264e58154c initial commpit
sean
parents:
diff changeset
3471
e1264e58154c initial commpit
sean
parents:
diff changeset
3472 @cached_property
e1264e58154c initial commpit
sean
parents:
diff changeset
3473 def co(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3474 return compile(self.code, self.filename or '<string>', 'exec')
e1264e58154c initial commpit
sean
parents:
diff changeset
3475
e1264e58154c initial commpit
sean
parents:
diff changeset
3476 @cached_property
e1264e58154c initial commpit
sean
parents:
diff changeset
3477 def code(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3478 source = self.source
e1264e58154c initial commpit
sean
parents:
diff changeset
3479 if not source:
e1264e58154c initial commpit
sean
parents:
diff changeset
3480 with open(self.filename, 'rb') as f:
e1264e58154c initial commpit
sean
parents:
diff changeset
3481 source = f.read()
e1264e58154c initial commpit
sean
parents:
diff changeset
3482 try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3483 source, encoding = touni(source), 'utf8'
e1264e58154c initial commpit
sean
parents:
diff changeset
3484 except UnicodeError:
e1264e58154c initial commpit
sean
parents:
diff changeset
3485 depr('Template encodings other than utf8 are not supported.') #0.11
e1264e58154c initial commpit
sean
parents:
diff changeset
3486 source, encoding = touni(source, 'latin1'), 'latin1'
e1264e58154c initial commpit
sean
parents:
diff changeset
3487 parser = StplParser(source, encoding=encoding, syntax=self.syntax)
e1264e58154c initial commpit
sean
parents:
diff changeset
3488 code = parser.translate()
e1264e58154c initial commpit
sean
parents:
diff changeset
3489 self.encoding = parser.encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
3490 return code
e1264e58154c initial commpit
sean
parents:
diff changeset
3491
e1264e58154c initial commpit
sean
parents:
diff changeset
3492 def _rebase(self, _env, _name=None, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3493 _env['_rebase'] = (_name, kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3494
e1264e58154c initial commpit
sean
parents:
diff changeset
3495 def _include(self, _env, _name=None, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3496 env = _env.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3497 env.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3498 if _name not in self.cache:
e1264e58154c initial commpit
sean
parents:
diff changeset
3499 self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
e1264e58154c initial commpit
sean
parents:
diff changeset
3500 return self.cache[_name].execute(env['_stdout'], env)
e1264e58154c initial commpit
sean
parents:
diff changeset
3501
e1264e58154c initial commpit
sean
parents:
diff changeset
3502 def execute(self, _stdout, kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3503 env = self.defaults.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3504 env.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3505 env.update({
e1264e58154c initial commpit
sean
parents:
diff changeset
3506 '_stdout': _stdout,
e1264e58154c initial commpit
sean
parents:
diff changeset
3507 '_printlist': _stdout.extend,
e1264e58154c initial commpit
sean
parents:
diff changeset
3508 'include': functools.partial(self._include, env),
e1264e58154c initial commpit
sean
parents:
diff changeset
3509 'rebase': functools.partial(self._rebase, env),
e1264e58154c initial commpit
sean
parents:
diff changeset
3510 '_rebase': None,
e1264e58154c initial commpit
sean
parents:
diff changeset
3511 '_str': self._str,
e1264e58154c initial commpit
sean
parents:
diff changeset
3512 '_escape': self._escape,
e1264e58154c initial commpit
sean
parents:
diff changeset
3513 'get': env.get,
e1264e58154c initial commpit
sean
parents:
diff changeset
3514 'setdefault': env.setdefault,
e1264e58154c initial commpit
sean
parents:
diff changeset
3515 'defined': env.__contains__
e1264e58154c initial commpit
sean
parents:
diff changeset
3516 })
e1264e58154c initial commpit
sean
parents:
diff changeset
3517 eval(self.co, env)
e1264e58154c initial commpit
sean
parents:
diff changeset
3518 if env.get('_rebase'):
e1264e58154c initial commpit
sean
parents:
diff changeset
3519 subtpl, rargs = env.pop('_rebase')
e1264e58154c initial commpit
sean
parents:
diff changeset
3520 rargs['base'] = ''.join(_stdout) #copy stdout
e1264e58154c initial commpit
sean
parents:
diff changeset
3521 del _stdout[:] # clear stdout
e1264e58154c initial commpit
sean
parents:
diff changeset
3522 return self._include(env, subtpl, **rargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3523 return env
e1264e58154c initial commpit
sean
parents:
diff changeset
3524
e1264e58154c initial commpit
sean
parents:
diff changeset
3525 def render(self, *args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3526 """ Render the template using keyword arguments as local variables. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3527 env = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
3528 stdout = []
e1264e58154c initial commpit
sean
parents:
diff changeset
3529 for dictarg in args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3530 env.update(dictarg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3531 env.update(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3532 self.execute(stdout, env)
e1264e58154c initial commpit
sean
parents:
diff changeset
3533 return ''.join(stdout)
e1264e58154c initial commpit
sean
parents:
diff changeset
3534
e1264e58154c initial commpit
sean
parents:
diff changeset
3535
e1264e58154c initial commpit
sean
parents:
diff changeset
3536 class StplSyntaxError(TemplateError):
e1264e58154c initial commpit
sean
parents:
diff changeset
3537
e1264e58154c initial commpit
sean
parents:
diff changeset
3538 pass
e1264e58154c initial commpit
sean
parents:
diff changeset
3539
e1264e58154c initial commpit
sean
parents:
diff changeset
3540
e1264e58154c initial commpit
sean
parents:
diff changeset
3541 class StplParser(object):
e1264e58154c initial commpit
sean
parents:
diff changeset
3542 """ Parser for stpl templates. """
e1264e58154c initial commpit
sean
parents:
diff changeset
3543 _re_cache = {} #: Cache for compiled re patterns
e1264e58154c initial commpit
sean
parents:
diff changeset
3544
e1264e58154c initial commpit
sean
parents:
diff changeset
3545 # This huge pile of voodoo magic splits python code into 8 different tokens.
e1264e58154c initial commpit
sean
parents:
diff changeset
3546 # We use the verbose (?x) regex mode to make this more manageable
e1264e58154c initial commpit
sean
parents:
diff changeset
3547
e1264e58154c initial commpit
sean
parents:
diff changeset
3548 _re_tok = _re_inl = r'''((?mx) # verbose and dot-matches-newline mode
e1264e58154c initial commpit
sean
parents:
diff changeset
3549 [urbURB]*
e1264e58154c initial commpit
sean
parents:
diff changeset
3550 (?: ''(?!')
e1264e58154c initial commpit
sean
parents:
diff changeset
3551 |""(?!")
e1264e58154c initial commpit
sean
parents:
diff changeset
3552 |'{6}
e1264e58154c initial commpit
sean
parents:
diff changeset
3553 |"{6}
e1264e58154c initial commpit
sean
parents:
diff changeset
3554 |'(?:[^\\']|\\.)+?'
e1264e58154c initial commpit
sean
parents:
diff changeset
3555 |"(?:[^\\"]|\\.)+?"
e1264e58154c initial commpit
sean
parents:
diff changeset
3556 |'{3}(?:[^\\]|\\.|\n)+?'{3}
e1264e58154c initial commpit
sean
parents:
diff changeset
3557 |"{3}(?:[^\\]|\\.|\n)+?"{3}
e1264e58154c initial commpit
sean
parents:
diff changeset
3558 )
e1264e58154c initial commpit
sean
parents:
diff changeset
3559 )'''
e1264e58154c initial commpit
sean
parents:
diff changeset
3560
e1264e58154c initial commpit
sean
parents:
diff changeset
3561 _re_inl = _re_tok.replace(r'|\n', '') # We re-use this string pattern later
e1264e58154c initial commpit
sean
parents:
diff changeset
3562
e1264e58154c initial commpit
sean
parents:
diff changeset
3563 _re_tok += r'''
e1264e58154c initial commpit
sean
parents:
diff changeset
3564 # 2: Comments (until end of line, but not the newline itself)
e1264e58154c initial commpit
sean
parents:
diff changeset
3565 |(\#.*)
e1264e58154c initial commpit
sean
parents:
diff changeset
3566
e1264e58154c initial commpit
sean
parents:
diff changeset
3567 # 3: Open and close (4) grouping tokens
e1264e58154c initial commpit
sean
parents:
diff changeset
3568 |([\[\{\(])
e1264e58154c initial commpit
sean
parents:
diff changeset
3569 |([\]\}\)])
e1264e58154c initial commpit
sean
parents:
diff changeset
3570
e1264e58154c initial commpit
sean
parents:
diff changeset
3571 # 5,6: Keywords that start or continue a python block (only start of line)
e1264e58154c initial commpit
sean
parents:
diff changeset
3572 |^([\ \t]*(?:if|for|while|with|try|def|class)\b)
e1264e58154c initial commpit
sean
parents:
diff changeset
3573 |^([\ \t]*(?:elif|else|except|finally)\b)
e1264e58154c initial commpit
sean
parents:
diff changeset
3574
e1264e58154c initial commpit
sean
parents:
diff changeset
3575 # 7: Our special 'end' keyword (but only if it stands alone)
e1264e58154c initial commpit
sean
parents:
diff changeset
3576 |((?:^|;)[\ \t]*end[\ \t]*(?=(?:%(block_close)s[\ \t]*)?\r?$|;|\#))
e1264e58154c initial commpit
sean
parents:
diff changeset
3577
e1264e58154c initial commpit
sean
parents:
diff changeset
3578 # 8: A customizable end-of-code-block template token (only end of line)
e1264e58154c initial commpit
sean
parents:
diff changeset
3579 |(%(block_close)s[\ \t]*(?=\r?$))
e1264e58154c initial commpit
sean
parents:
diff changeset
3580
e1264e58154c initial commpit
sean
parents:
diff changeset
3581 # 9: And finally, a single newline. The 10th token is 'everything else'
e1264e58154c initial commpit
sean
parents:
diff changeset
3582 |(\r?\n)
e1264e58154c initial commpit
sean
parents:
diff changeset
3583 '''
e1264e58154c initial commpit
sean
parents:
diff changeset
3584
e1264e58154c initial commpit
sean
parents:
diff changeset
3585 # Match the start tokens of code areas in a template
e1264e58154c initial commpit
sean
parents:
diff changeset
3586 _re_split = r'''(?m)^[ \t]*(\\?)((%(line_start)s)|(%(block_start)s))'''
e1264e58154c initial commpit
sean
parents:
diff changeset
3587 # Match inline statements (may contain python strings)
e1264e58154c initial commpit
sean
parents:
diff changeset
3588 _re_inl = r'''%%(inline_start)s((?:%s|[^'"\n]+?)*?)%%(inline_end)s''' % _re_inl
e1264e58154c initial commpit
sean
parents:
diff changeset
3589
e1264e58154c initial commpit
sean
parents:
diff changeset
3590 default_syntax = '<% %> % {{ }}'
e1264e58154c initial commpit
sean
parents:
diff changeset
3591
e1264e58154c initial commpit
sean
parents:
diff changeset
3592 def __init__(self, source, syntax=None, encoding='utf8'):
e1264e58154c initial commpit
sean
parents:
diff changeset
3593 self.source, self.encoding = touni(source, encoding), encoding
e1264e58154c initial commpit
sean
parents:
diff changeset
3594 self.set_syntax(syntax or self.default_syntax)
e1264e58154c initial commpit
sean
parents:
diff changeset
3595 self.code_buffer, self.text_buffer = [], []
e1264e58154c initial commpit
sean
parents:
diff changeset
3596 self.lineno, self.offset = 1, 0
e1264e58154c initial commpit
sean
parents:
diff changeset
3597 self.indent, self.indent_mod = 0, 0
e1264e58154c initial commpit
sean
parents:
diff changeset
3598 self.paren_depth = 0
e1264e58154c initial commpit
sean
parents:
diff changeset
3599
e1264e58154c initial commpit
sean
parents:
diff changeset
3600 def get_syntax(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3601 """ Tokens as a space separated string (default: <% %> % {{ }}) """
e1264e58154c initial commpit
sean
parents:
diff changeset
3602 return self._syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
3603
e1264e58154c initial commpit
sean
parents:
diff changeset
3604 def set_syntax(self, syntax):
e1264e58154c initial commpit
sean
parents:
diff changeset
3605 self._syntax = syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
3606 self._tokens = syntax.split()
e1264e58154c initial commpit
sean
parents:
diff changeset
3607 if not syntax in self._re_cache:
e1264e58154c initial commpit
sean
parents:
diff changeset
3608 names = 'block_start block_close line_start inline_start inline_end'
e1264e58154c initial commpit
sean
parents:
diff changeset
3609 etokens = map(re.escape, self._tokens)
e1264e58154c initial commpit
sean
parents:
diff changeset
3610 pattern_vars = dict(zip(names.split(), etokens))
e1264e58154c initial commpit
sean
parents:
diff changeset
3611 patterns = (self._re_split, self._re_tok, self._re_inl)
e1264e58154c initial commpit
sean
parents:
diff changeset
3612 patterns = [re.compile(p % pattern_vars) for p in patterns]
e1264e58154c initial commpit
sean
parents:
diff changeset
3613 self._re_cache[syntax] = patterns
e1264e58154c initial commpit
sean
parents:
diff changeset
3614 self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
e1264e58154c initial commpit
sean
parents:
diff changeset
3615
e1264e58154c initial commpit
sean
parents:
diff changeset
3616 syntax = property(get_syntax, set_syntax)
e1264e58154c initial commpit
sean
parents:
diff changeset
3617
e1264e58154c initial commpit
sean
parents:
diff changeset
3618 def translate(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3619 if self.offset: raise RuntimeError('Parser is a one time instance.')
e1264e58154c initial commpit
sean
parents:
diff changeset
3620 while True:
e1264e58154c initial commpit
sean
parents:
diff changeset
3621 m = self.re_split.search(self.source, pos=self.offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
3622 if m:
e1264e58154c initial commpit
sean
parents:
diff changeset
3623 text = self.source[self.offset:m.start()]
e1264e58154c initial commpit
sean
parents:
diff changeset
3624 self.text_buffer.append(text)
e1264e58154c initial commpit
sean
parents:
diff changeset
3625 self.offset = m.end()
e1264e58154c initial commpit
sean
parents:
diff changeset
3626 if m.group(1): # Escape syntax
e1264e58154c initial commpit
sean
parents:
diff changeset
3627 line, sep, _ = self.source[self.offset:].partition('\n')
e1264e58154c initial commpit
sean
parents:
diff changeset
3628 self.text_buffer.append(self.source[m.start():m.start(1)] +
e1264e58154c initial commpit
sean
parents:
diff changeset
3629 m.group(2) + line + sep)
e1264e58154c initial commpit
sean
parents:
diff changeset
3630 self.offset += len(line + sep)
e1264e58154c initial commpit
sean
parents:
diff changeset
3631 continue
e1264e58154c initial commpit
sean
parents:
diff changeset
3632 self.flush_text()
e1264e58154c initial commpit
sean
parents:
diff changeset
3633 self.offset += self.read_code(self.source[self.offset:],
e1264e58154c initial commpit
sean
parents:
diff changeset
3634 multiline=bool(m.group(4)))
e1264e58154c initial commpit
sean
parents:
diff changeset
3635 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3636 break
e1264e58154c initial commpit
sean
parents:
diff changeset
3637 self.text_buffer.append(self.source[self.offset:])
e1264e58154c initial commpit
sean
parents:
diff changeset
3638 self.flush_text()
e1264e58154c initial commpit
sean
parents:
diff changeset
3639 return ''.join(self.code_buffer)
e1264e58154c initial commpit
sean
parents:
diff changeset
3640
e1264e58154c initial commpit
sean
parents:
diff changeset
3641 def read_code(self, pysource, multiline):
e1264e58154c initial commpit
sean
parents:
diff changeset
3642 code_line, comment = '', ''
e1264e58154c initial commpit
sean
parents:
diff changeset
3643 offset = 0
e1264e58154c initial commpit
sean
parents:
diff changeset
3644 while True:
e1264e58154c initial commpit
sean
parents:
diff changeset
3645 m = self.re_tok.search(pysource, pos=offset)
e1264e58154c initial commpit
sean
parents:
diff changeset
3646 if not m:
e1264e58154c initial commpit
sean
parents:
diff changeset
3647 code_line += pysource[offset:]
e1264e58154c initial commpit
sean
parents:
diff changeset
3648 offset = len(pysource)
e1264e58154c initial commpit
sean
parents:
diff changeset
3649 self.write_code(code_line.strip(), comment)
e1264e58154c initial commpit
sean
parents:
diff changeset
3650 break
e1264e58154c initial commpit
sean
parents:
diff changeset
3651 code_line += pysource[offset:m.start()]
e1264e58154c initial commpit
sean
parents:
diff changeset
3652 offset = m.end()
e1264e58154c initial commpit
sean
parents:
diff changeset
3653 _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups()
e1264e58154c initial commpit
sean
parents:
diff changeset
3654 if self.paren_depth > 0 and (_blk1 or _blk2): # a if b else c
e1264e58154c initial commpit
sean
parents:
diff changeset
3655 code_line += _blk1 or _blk2
e1264e58154c initial commpit
sean
parents:
diff changeset
3656 continue
e1264e58154c initial commpit
sean
parents:
diff changeset
3657 if _str: # Python string
e1264e58154c initial commpit
sean
parents:
diff changeset
3658 code_line += _str
e1264e58154c initial commpit
sean
parents:
diff changeset
3659 elif _com: # Python comment (up to EOL)
e1264e58154c initial commpit
sean
parents:
diff changeset
3660 comment = _com
e1264e58154c initial commpit
sean
parents:
diff changeset
3661 if multiline and _com.strip().endswith(self._tokens[1]):
e1264e58154c initial commpit
sean
parents:
diff changeset
3662 multiline = False # Allow end-of-block in comments
e1264e58154c initial commpit
sean
parents:
diff changeset
3663 elif _po: # open parenthesis
e1264e58154c initial commpit
sean
parents:
diff changeset
3664 self.paren_depth += 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3665 code_line += _po
e1264e58154c initial commpit
sean
parents:
diff changeset
3666 elif _pc: # close parenthesis
e1264e58154c initial commpit
sean
parents:
diff changeset
3667 if self.paren_depth > 0:
e1264e58154c initial commpit
sean
parents:
diff changeset
3668 # we could check for matching parentheses here, but it's
e1264e58154c initial commpit
sean
parents:
diff changeset
3669 # easier to leave that to python - just check counts
e1264e58154c initial commpit
sean
parents:
diff changeset
3670 self.paren_depth -= 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3671 code_line += _pc
e1264e58154c initial commpit
sean
parents:
diff changeset
3672 elif _blk1: # Start-block keyword (if/for/while/def/try/...)
e1264e58154c initial commpit
sean
parents:
diff changeset
3673 code_line, self.indent_mod = _blk1, -1
e1264e58154c initial commpit
sean
parents:
diff changeset
3674 self.indent += 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3675 elif _blk2: # Continue-block keyword (else/elif/except/...)
e1264e58154c initial commpit
sean
parents:
diff changeset
3676 code_line, self.indent_mod = _blk2, -1
e1264e58154c initial commpit
sean
parents:
diff changeset
3677 elif _end: # The non-standard 'end'-keyword (ends a block)
e1264e58154c initial commpit
sean
parents:
diff changeset
3678 self.indent -= 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3679 elif _cend: # The end-code-block template token (usually '%>')
e1264e58154c initial commpit
sean
parents:
diff changeset
3680 if multiline: multiline = False
e1264e58154c initial commpit
sean
parents:
diff changeset
3681 else: code_line += _cend
e1264e58154c initial commpit
sean
parents:
diff changeset
3682 else: # \n
e1264e58154c initial commpit
sean
parents:
diff changeset
3683 self.write_code(code_line.strip(), comment)
e1264e58154c initial commpit
sean
parents:
diff changeset
3684 self.lineno += 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3685 code_line, comment, self.indent_mod = '', '', 0
e1264e58154c initial commpit
sean
parents:
diff changeset
3686 if not multiline:
e1264e58154c initial commpit
sean
parents:
diff changeset
3687 break
e1264e58154c initial commpit
sean
parents:
diff changeset
3688
e1264e58154c initial commpit
sean
parents:
diff changeset
3689 return offset
e1264e58154c initial commpit
sean
parents:
diff changeset
3690
e1264e58154c initial commpit
sean
parents:
diff changeset
3691 def flush_text(self):
e1264e58154c initial commpit
sean
parents:
diff changeset
3692 text = ''.join(self.text_buffer)
e1264e58154c initial commpit
sean
parents:
diff changeset
3693 del self.text_buffer[:]
e1264e58154c initial commpit
sean
parents:
diff changeset
3694 if not text: return
e1264e58154c initial commpit
sean
parents:
diff changeset
3695 parts, pos, nl = [], 0, '\\\n' + ' ' * self.indent
e1264e58154c initial commpit
sean
parents:
diff changeset
3696 for m in self.re_inl.finditer(text):
e1264e58154c initial commpit
sean
parents:
diff changeset
3697 prefix, pos = text[pos:m.start()], m.end()
e1264e58154c initial commpit
sean
parents:
diff changeset
3698 if prefix:
e1264e58154c initial commpit
sean
parents:
diff changeset
3699 parts.append(nl.join(map(repr, prefix.splitlines(True))))
e1264e58154c initial commpit
sean
parents:
diff changeset
3700 if prefix.endswith('\n'): parts[-1] += nl
e1264e58154c initial commpit
sean
parents:
diff changeset
3701 parts.append(self.process_inline(m.group(1).strip()))
e1264e58154c initial commpit
sean
parents:
diff changeset
3702 if pos < len(text):
e1264e58154c initial commpit
sean
parents:
diff changeset
3703 prefix = text[pos:]
e1264e58154c initial commpit
sean
parents:
diff changeset
3704 lines = prefix.splitlines(True)
e1264e58154c initial commpit
sean
parents:
diff changeset
3705 if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
e1264e58154c initial commpit
sean
parents:
diff changeset
3706 elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
e1264e58154c initial commpit
sean
parents:
diff changeset
3707 parts.append(nl.join(map(repr, lines)))
e1264e58154c initial commpit
sean
parents:
diff changeset
3708 code = '_printlist((%s,))' % ', '.join(parts)
e1264e58154c initial commpit
sean
parents:
diff changeset
3709 self.lineno += code.count('\n') + 1
e1264e58154c initial commpit
sean
parents:
diff changeset
3710 self.write_code(code)
e1264e58154c initial commpit
sean
parents:
diff changeset
3711
e1264e58154c initial commpit
sean
parents:
diff changeset
3712 @staticmethod
e1264e58154c initial commpit
sean
parents:
diff changeset
3713 def process_inline(chunk):
e1264e58154c initial commpit
sean
parents:
diff changeset
3714 if chunk[0] == '!': return '_str(%s)' % chunk[1:]
e1264e58154c initial commpit
sean
parents:
diff changeset
3715 return '_escape(%s)' % chunk
e1264e58154c initial commpit
sean
parents:
diff changeset
3716
e1264e58154c initial commpit
sean
parents:
diff changeset
3717 def write_code(self, line, comment=''):
e1264e58154c initial commpit
sean
parents:
diff changeset
3718 code = ' ' * (self.indent + self.indent_mod)
e1264e58154c initial commpit
sean
parents:
diff changeset
3719 code += line.lstrip() + comment + '\n'
e1264e58154c initial commpit
sean
parents:
diff changeset
3720 self.code_buffer.append(code)
e1264e58154c initial commpit
sean
parents:
diff changeset
3721
e1264e58154c initial commpit
sean
parents:
diff changeset
3722
e1264e58154c initial commpit
sean
parents:
diff changeset
3723 def template(*args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3724 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3725 Get a rendered template as a string iterator.
e1264e58154c initial commpit
sean
parents:
diff changeset
3726 You can use a name, a filename or a template string as first parameter.
e1264e58154c initial commpit
sean
parents:
diff changeset
3727 Template rendering arguments can be passed as dictionaries
e1264e58154c initial commpit
sean
parents:
diff changeset
3728 or directly (as keyword arguments).
e1264e58154c initial commpit
sean
parents:
diff changeset
3729 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3730 tpl = args[0] if args else None
e1264e58154c initial commpit
sean
parents:
diff changeset
3731 adapter = kwargs.pop('template_adapter', SimpleTemplate)
e1264e58154c initial commpit
sean
parents:
diff changeset
3732 lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
e1264e58154c initial commpit
sean
parents:
diff changeset
3733 tplid = (id(lookup), tpl)
e1264e58154c initial commpit
sean
parents:
diff changeset
3734 if tplid not in TEMPLATES or DEBUG:
e1264e58154c initial commpit
sean
parents:
diff changeset
3735 settings = kwargs.pop('template_settings', {})
e1264e58154c initial commpit
sean
parents:
diff changeset
3736 if isinstance(tpl, adapter):
e1264e58154c initial commpit
sean
parents:
diff changeset
3737 TEMPLATES[tplid] = tpl
e1264e58154c initial commpit
sean
parents:
diff changeset
3738 if settings: TEMPLATES[tplid].prepare(**settings)
e1264e58154c initial commpit
sean
parents:
diff changeset
3739 elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
e1264e58154c initial commpit
sean
parents:
diff changeset
3740 TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
e1264e58154c initial commpit
sean
parents:
diff changeset
3741 else:
e1264e58154c initial commpit
sean
parents:
diff changeset
3742 TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
e1264e58154c initial commpit
sean
parents:
diff changeset
3743 if not TEMPLATES[tplid]:
e1264e58154c initial commpit
sean
parents:
diff changeset
3744 abort(500, 'Template (%s) not found' % tpl)
e1264e58154c initial commpit
sean
parents:
diff changeset
3745 for dictarg in args[1:]:
e1264e58154c initial commpit
sean
parents:
diff changeset
3746 kwargs.update(dictarg)
e1264e58154c initial commpit
sean
parents:
diff changeset
3747 return TEMPLATES[tplid].render(kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3748
e1264e58154c initial commpit
sean
parents:
diff changeset
3749
e1264e58154c initial commpit
sean
parents:
diff changeset
3750 mako_template = functools.partial(template, template_adapter=MakoTemplate)
e1264e58154c initial commpit
sean
parents:
diff changeset
3751 cheetah_template = functools.partial(template,
e1264e58154c initial commpit
sean
parents:
diff changeset
3752 template_adapter=CheetahTemplate)
e1264e58154c initial commpit
sean
parents:
diff changeset
3753 jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
e1264e58154c initial commpit
sean
parents:
diff changeset
3754
e1264e58154c initial commpit
sean
parents:
diff changeset
3755
e1264e58154c initial commpit
sean
parents:
diff changeset
3756 def view(tpl_name, **defaults):
e1264e58154c initial commpit
sean
parents:
diff changeset
3757 """ Decorator: renders a template for a handler.
e1264e58154c initial commpit
sean
parents:
diff changeset
3758 The handler can control its behavior like that:
e1264e58154c initial commpit
sean
parents:
diff changeset
3759
e1264e58154c initial commpit
sean
parents:
diff changeset
3760 - return a dict of template vars to fill out the template
e1264e58154c initial commpit
sean
parents:
diff changeset
3761 - return something other than a dict and the view decorator will not
e1264e58154c initial commpit
sean
parents:
diff changeset
3762 process the template, but return the handler result as is.
e1264e58154c initial commpit
sean
parents:
diff changeset
3763 This includes returning a HTTPResponse(dict) to get,
e1264e58154c initial commpit
sean
parents:
diff changeset
3764 for instance, JSON with autojson or other castfilters.
e1264e58154c initial commpit
sean
parents:
diff changeset
3765 """
e1264e58154c initial commpit
sean
parents:
diff changeset
3766
e1264e58154c initial commpit
sean
parents:
diff changeset
3767 def decorator(func):
e1264e58154c initial commpit
sean
parents:
diff changeset
3768
e1264e58154c initial commpit
sean
parents:
diff changeset
3769 @functools.wraps(func)
e1264e58154c initial commpit
sean
parents:
diff changeset
3770 def wrapper(*args, **kwargs):
e1264e58154c initial commpit
sean
parents:
diff changeset
3771 result = func(*args, **kwargs)
e1264e58154c initial commpit
sean
parents:
diff changeset
3772 if isinstance(result, (dict, DictMixin)):
e1264e58154c initial commpit
sean
parents:
diff changeset
3773 tplvars = defaults.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3774 tplvars.update(result)
e1264e58154c initial commpit
sean
parents:
diff changeset
3775 return template(tpl_name, **tplvars)
e1264e58154c initial commpit
sean
parents:
diff changeset
3776 elif result is None:
e1264e58154c initial commpit
sean
parents:
diff changeset
3777 return template(tpl_name, defaults)
e1264e58154c initial commpit
sean
parents:
diff changeset
3778 return result
e1264e58154c initial commpit
sean
parents:
diff changeset
3779
e1264e58154c initial commpit
sean
parents:
diff changeset
3780 return wrapper
e1264e58154c initial commpit
sean
parents:
diff changeset
3781
e1264e58154c initial commpit
sean
parents:
diff changeset
3782 return decorator
e1264e58154c initial commpit
sean
parents:
diff changeset
3783
e1264e58154c initial commpit
sean
parents:
diff changeset
3784
e1264e58154c initial commpit
sean
parents:
diff changeset
3785 mako_view = functools.partial(view, template_adapter=MakoTemplate)
e1264e58154c initial commpit
sean
parents:
diff changeset
3786 cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
e1264e58154c initial commpit
sean
parents:
diff changeset
3787 jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
e1264e58154c initial commpit
sean
parents:
diff changeset
3788
e1264e58154c initial commpit
sean
parents:
diff changeset
3789 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3790 # Constants and Globals ########################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3791 ###############################################################################
e1264e58154c initial commpit
sean
parents:
diff changeset
3792
e1264e58154c initial commpit
sean
parents:
diff changeset
3793 TEMPLATE_PATH = ['./', './views/']
e1264e58154c initial commpit
sean
parents:
diff changeset
3794 TEMPLATES = {}
e1264e58154c initial commpit
sean
parents:
diff changeset
3795 DEBUG = False
e1264e58154c initial commpit
sean
parents:
diff changeset
3796 NORUN = False # If set, run() does nothing. Used by load_app()
e1264e58154c initial commpit
sean
parents:
diff changeset
3797
e1264e58154c initial commpit
sean
parents:
diff changeset
3798 #: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
e1264e58154c initial commpit
sean
parents:
diff changeset
3799 HTTP_CODES = httplib.responses.copy()
e1264e58154c initial commpit
sean
parents:
diff changeset
3800 HTTP_CODES[418] = "I'm a teapot" # RFC 2324
e1264e58154c initial commpit
sean
parents:
diff changeset
3801 HTTP_CODES[428] = "Precondition Required"
e1264e58154c initial commpit
sean
parents:
diff changeset
3802 HTTP_CODES[429] = "Too Many Requests"
e1264e58154c initial commpit
sean
parents:
diff changeset
3803 HTTP_CODES[431] = "Request Header Fields Too Large"
e1264e58154c initial commpit
sean
parents:
diff changeset
3804 HTTP_CODES[511] = "Network Authentication Required"
e1264e58154c initial commpit
sean
parents:
diff changeset
3805 _HTTP_STATUS_LINES = dict((k, '%d %s' % (k, v))
e1264e58154c initial commpit
sean
parents:
diff changeset
3806 for (k, v) in HTTP_CODES.items())
e1264e58154c initial commpit
sean
parents:
diff changeset
3807
e1264e58154c initial commpit
sean
parents:
diff changeset
3808 #: The default template used for error pages. Override with @error()
e1264e58154c initial commpit
sean
parents:
diff changeset
3809 ERROR_PAGE_TEMPLATE = """
e1264e58154c initial commpit
sean
parents:
diff changeset
3810 %%try:
e1264e58154c initial commpit
sean
parents:
diff changeset
3811 %%from %s import DEBUG, request
e1264e58154c initial commpit
sean
parents:
diff changeset
3812 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
e1264e58154c initial commpit
sean
parents:
diff changeset
3813 <html>
e1264e58154c initial commpit
sean
parents:
diff changeset
3814 <head>
e1264e58154c initial commpit
sean
parents:
diff changeset
3815 <title>Error: {{e.status}}</title>
e1264e58154c initial commpit
sean
parents:
diff changeset
3816 <style type="text/css">
e1264e58154c initial commpit
sean
parents:
diff changeset
3817 html {background-color: #eee; font-family: sans-serif;}
e1264e58154c initial commpit
sean
parents:
diff changeset
3818 body {background-color: #fff; border: 1px solid #ddd;
e1264e58154c initial commpit
sean
parents:
diff changeset
3819 padding: 15px; margin: 15px;}
e1264e58154c initial commpit
sean
parents:
diff changeset
3820 pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
e1264e58154c initial commpit
sean
parents:
diff changeset
3821 </style>
e1264e58154c initial commpit
sean
parents:
diff changeset
3822 </head>
e1264e58154c initial commpit
sean
parents:
diff changeset
3823 <body>
e1264e58154c initial commpit
sean
parents:
diff changeset
3824 <h1>Error: {{e.status}}</h1>
e1264e58154c initial commpit
sean
parents:
diff changeset
3825 <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
e1264e58154c initial commpit
sean
parents:
diff changeset
3826 caused an error:</p>
e1264e58154c initial commpit
sean
parents:
diff changeset
3827 <pre>{{e.body}}</pre>
e1264e58154c initial commpit
sean
parents:
diff changeset
3828 %%if DEBUG and e.exception:
e1264e58154c initial commpit
sean
parents:
diff changeset
3829 <h2>Exception:</h2>
e1264e58154c initial commpit
sean
parents:
diff changeset
3830 <pre>{{repr(e.exception)}}</pre>
e1264e58154c initial commpit
sean
parents:
diff changeset
3831 %%end
e1264e58154c initial commpit
sean
parents:
diff changeset
3832 %%if DEBUG and e.traceback:
e1264e58154c initial commpit
sean
parents:
diff changeset
3833 <h2>Traceback:</h2>
e1264e58154c initial commpit
sean
parents:
diff changeset
3834 <pre>{{e.traceback}}</pre>
e1264e58154c initial commpit
sean
parents:
diff changeset
3835 %%end
e1264e58154c initial commpit
sean
parents:
diff changeset
3836 </body>
e1264e58154c initial commpit
sean
parents:
diff changeset
3837 </html>
e1264e58154c initial commpit
sean
parents:
diff changeset
3838 %%except ImportError:
e1264e58154c initial commpit
sean
parents:
diff changeset
3839 <b>ImportError:</b> Could not generate the error page. Please add bottle to
e1264e58154c initial commpit
sean
parents:
diff changeset
3840 the import path.
e1264e58154c initial commpit
sean
parents:
diff changeset
3841 %%end
e1264e58154c initial commpit
sean
parents:
diff changeset
3842 """ % __name__
e1264e58154c initial commpit
sean
parents:
diff changeset
3843
e1264e58154c initial commpit
sean
parents:
diff changeset
3844 #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
e1264e58154c initial commpit
sean
parents:
diff changeset
3845 #: request callback, this instance always refers to the *current* request
e1264e58154c initial commpit
sean
parents:
diff changeset
3846 #: (even on a multithreaded server).
e1264e58154c initial commpit
sean
parents:
diff changeset
3847 request = LocalRequest()
e1264e58154c initial commpit
sean
parents:
diff changeset
3848
e1264e58154c initial commpit
sean
parents:
diff changeset
3849 #: A thread-safe instance of :class:`LocalResponse`. It is used to change the
e1264e58154c initial commpit
sean
parents:
diff changeset
3850 #: HTTP response for the *current* request.
e1264e58154c initial commpit
sean
parents:
diff changeset
3851 response = LocalResponse()
e1264e58154c initial commpit
sean
parents:
diff changeset
3852
e1264e58154c initial commpit
sean
parents:
diff changeset
3853 #: A thread-safe namespace. Not used by Bottle.
e1264e58154c initial commpit
sean
parents:
diff changeset
3854 local = threading.local()
e1264e58154c initial commpit
sean
parents:
diff changeset
3855
e1264e58154c initial commpit
sean
parents:
diff changeset
3856 # Initialize app stack (create first empty Bottle app)
e1264e58154c initial commpit
sean
parents:
diff changeset
3857 # BC: 0.6.4 and needed for run()
e1264e58154c initial commpit
sean
parents:
diff changeset
3858 app = default_app = AppStack()
e1264e58154c initial commpit
sean
parents:
diff changeset
3859 app.push()
e1264e58154c initial commpit
sean
parents:
diff changeset
3860
e1264e58154c initial commpit
sean
parents:
diff changeset
3861 #: A virtual package that redirects import statements.
e1264e58154c initial commpit
sean
parents:
diff changeset
3862 #: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
e1264e58154c initial commpit
sean
parents:
diff changeset
3863 ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else
e1264e58154c initial commpit
sean
parents:
diff changeset
3864 __name__ + ".ext", 'bottle_%s').module
e1264e58154c initial commpit
sean
parents:
diff changeset
3865
e1264e58154c initial commpit
sean
parents:
diff changeset
3866 if __name__ == '__main__':
e1264e58154c initial commpit
sean
parents:
diff changeset
3867 opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
e1264e58154c initial commpit
sean
parents:
diff changeset
3868 if opt.version:
e1264e58154c initial commpit
sean
parents:
diff changeset
3869 _stdout('Bottle %s\n' % __version__)
e1264e58154c initial commpit
sean
parents:
diff changeset
3870 sys.exit(0)
e1264e58154c initial commpit
sean
parents:
diff changeset
3871 if not args:
e1264e58154c initial commpit
sean
parents:
diff changeset
3872 parser.print_help()
e1264e58154c initial commpit
sean
parents:
diff changeset
3873 _stderr('\nError: No application entry point specified.\n')
e1264e58154c initial commpit
sean
parents:
diff changeset
3874 sys.exit(1)
e1264e58154c initial commpit
sean
parents:
diff changeset
3875
e1264e58154c initial commpit
sean
parents:
diff changeset
3876 sys.path.insert(0, '.')
e1264e58154c initial commpit
sean
parents:
diff changeset
3877 sys.modules.setdefault('bottle', sys.modules['__main__'])
e1264e58154c initial commpit
sean
parents:
diff changeset
3878
e1264e58154c initial commpit
sean
parents:
diff changeset
3879 host, port = (opt.bind or 'localhost'), 8080
e1264e58154c initial commpit
sean
parents:
diff changeset
3880 if ':' in host and host.rfind(']') < host.rfind(':'):
e1264e58154c initial commpit
sean
parents:
diff changeset
3881 host, port = host.rsplit(':', 1)
e1264e58154c initial commpit
sean
parents:
diff changeset
3882 host = host.strip('[]')
e1264e58154c initial commpit
sean
parents:
diff changeset
3883
e1264e58154c initial commpit
sean
parents:
diff changeset
3884 run(args[0],
e1264e58154c initial commpit
sean
parents:
diff changeset
3885 host=host,
e1264e58154c initial commpit
sean
parents:
diff changeset
3886 port=int(port),
e1264e58154c initial commpit
sean
parents:
diff changeset
3887 server=opt.server,
e1264e58154c initial commpit
sean
parents:
diff changeset
3888 reloader=opt.reload,
e1264e58154c initial commpit
sean
parents:
diff changeset
3889 plugins=opt.plugin,
e1264e58154c initial commpit
sean
parents:
diff changeset
3890 debug=opt.debug)
e1264e58154c initial commpit
sean
parents:
diff changeset
3891
e1264e58154c initial commpit
sean
parents:
diff changeset
3892 # THE END
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)