1 """
2 Web API (wrapper around WSGI)
3 (from web.py)
4 """
5
6 __all__ = [
7 "config",
8 "header", "debug",
9 "input", "data",
10 "setcookie", "cookies",
11 "ctx",
12 "HTTPError",
13
14
15 "OK", "Created", "Accepted",
16 "ok", "created", "accepted",
17
18
19 "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect",
20 "redirect", "found", "seeother", "notmodified", "tempredirect",
21
22
23 "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed",
24 "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed",
25
26
27 "InternalError",
28 "internalerror",
29 ]
30
31 import sys, cgi, Cookie, pprint, urlparse, urllib
32 from utils import storage, storify, threadeddict, dictadd, intget, utf8
33
34 config = storage()
35 config.__doc__ = """
36 A configuration object for various aspects of web.py.
37
38 `debug`
39 : when True, enables reloading, disabled template caching and sets internalerror to debugerror.
40 """
41
43 - def __init__(self, status, headers={}, data=""):
49
50 -def _status_code(status, data=None, classname=None, docstring=None):
58
59
60 return type(classname, (HTTPError, object), {
61 '__doc__': docstring,
62 '__init__': __init__
63 })
64
65 ok = OK = _status_code("200 OK", data="")
66 created = Created = _status_code("201 Created")
67 accepted = Accepted = _status_code("202 Accepted")
68
70 """A `301 Moved Permanently` redirect."""
71 - def __init__(self, url, status='301 Moved Permanently', absolute=False):
72 """
73 Returns a `status` redirect to the new URL.
74 `url` is joined with the base URL so that things like
75 `redirect("about") will work properly.
76 """
77 newloc = urlparse.urljoin(ctx.path, url)
78
79 if newloc.startswith('/'):
80 if absolute:
81 home = ctx.realhome
82 else:
83 home = ctx.home
84 newloc = home + newloc
85
86 headers = {
87 'Content-Type': 'text/html',
88 'Location': newloc
89 }
90 HTTPError.__init__(self, status, headers, "")
91
92 redirect = Redirect
93
95 """A `302 Found` redirect."""
96 - def __init__(self, url, absolute=False):
98
99 found = Found
100
102 """A `303 See Other` redirect."""
103 - def __init__(self, url, absolute=False):
105
106 seeother = SeeOther
107
109 """A `304 Not Modified` status."""
112
113 notmodified = NotModified
114
116 """A `307 Temporary Redirect` redirect."""
117 - def __init__(self, url, absolute=False):
119
120 tempredirect = TempRedirect
121
123 """`400 Bad Request` error."""
124 message = "bad request"
129
130 badrequest = BadRequest
131
133 """`404 Not Found` error."""
134 message = "not found"
139
149
150 notfound = NotFound
151
152 unauthorized = Unauthorized = _status_code("401 Unauthorized")
153 forbidden = Forbidden = _status_code("403 Forbidden")
154 notacceptable = NotAcceptable = _status_code("406 Not Acceptable")
155 conflict = Conflict = _status_code("409 Conflict")
156 preconditionfailed = PreconditionFailed = _status_code("412 Precondition Failed")
157
159 """A `405 Method Not Allowed` error."""
161 status = '405 Method Not Allowed'
162 headers = {}
163 headers['Content-Type'] = 'text/html'
164
165 methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
166 if cls:
167 methods = [method for method in methods if hasattr(cls, method)]
168
169 headers['Allow'] = ', '.join(methods)
170 data = None
171 HTTPError.__init__(self, status, headers, data)
172
173 nomethod = NoMethod
174
175 -class Gone(HTTPError):
176 """`410 Gone` error."""
177 message = "gone"
182
183 gone = Gone
184
186 """500 Internal Server Error`."""
187 message = "internal server error"
188
193
203
204 internalerror = InternalError
205
207 """
208 Adds the header `hdr: value` with the response.
209
210 If `unique` is True and a header with that name already exists,
211 it doesn't add a new one.
212 """
213 hdr, value = utf8(hdr), utf8(value)
214
215 if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
216 raise ValueError, 'invalid characters in header'
217
218 if unique is True:
219 for h, v in ctx.headers:
220 if h.lower() == hdr.lower(): return
221
222 ctx.headers.append((hdr, value))
223
236
237 e = ctx.env.copy()
238 a = b = {}
239
240 if method.lower() in ['both', 'post', 'put']:
241 if e['REQUEST_METHOD'] in ['POST', 'PUT']:
242 if e.get('CONTENT_TYPE', '').lower().startswith('multipart/'):
243
244
245
246 a = ctx.get('_fieldstorage')
247 if not a:
248 fp = e['wsgi.input']
249 a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
250 ctx._fieldstorage = a
251 else:
252 fp = StringIO(data())
253 a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
254 a = dictify(a)
255
256 if method.lower() in ['both', 'get']:
257 e['REQUEST_METHOD'] = 'GET'
258 b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1))
259
260 def process_fieldstorage(fs):
261 if isinstance(fs, list):
262 return [process_fieldstorage(x) for x in fs]
263 elif fs.filename is None:
264 return fs.value
265 else:
266 return fs
267
268 return storage([(k, process_fieldstorage(v)) for k, v in dictadd(b, a).items()])
269
282
284 """Returns the data sent with the request."""
285 if 'data' not in ctx:
286 cl = intget(ctx.env.get('CONTENT_LENGTH'), 0)
287 ctx.data = ctx.env['wsgi.input'].read(cl)
288 return ctx.data
289
290 -def setcookie(name, value, expires="", domain=None, secure=False):
291 """Sets a cookie."""
292 if expires < 0:
293 expires = -1000000000
294 kargs = {'expires': expires, 'path':'/'}
295 if domain:
296 kargs['domain'] = domain
297 if secure:
298 kargs['secure'] = secure
299
300 cookie = Cookie.SimpleCookie()
301 cookie[name] = urllib.quote(utf8(value))
302 for key, val in kargs.iteritems():
303 cookie[name][key] = val
304 header('Set-Cookie', cookie.items()[0][1].OutputString())
305
306 -def cookies(*requireds, **defaults):
307 """
308 Returns a `storage` object with all the cookies in it.
309 See `storify` for how `requireds` and `defaults` work.
310 """
311 cookie = Cookie.SimpleCookie()
312 cookie.load(ctx.env.get('HTTP_COOKIE', ''))
313 try:
314 d = storify(cookie, *requireds, **defaults)
315 for k, v in d.items():
316 d[k] = v and urllib.unquote(v)
317 return d
318 except KeyError:
319 badrequest()
320 raise StopIteration
321
323 """
324 Prints a prettyprinted version of `args` to stderr.
325 """
326 try:
327 out = ctx.environ['wsgi.errors']
328 except:
329 out = sys.stderr
330 for arg in args:
331 print >> out, pprint.pformat(arg)
332 return ''
333
335 try:
336 out = ctx.environ['wsgi.errors']
337 except:
338 out = sys.stderr
339 out.write(x)
340 debug.write = _debugwrite
341
342 ctx = context = threadeddict()
343
344 ctx.__doc__ = """
345 A `storage` object containing various information about the request:
346
347 `environ` (aka `env`)
348 : A dictionary containing the standard WSGI environment variables.
349
350 `host`
351 : The domain (`Host` header) requested by the user.
352
353 `home`
354 : The base path for the application.
355
356 `ip`
357 : The IP address of the requester.
358
359 `method`
360 : The HTTP method used.
361
362 `path`
363 : The path request.
364
365 `query`
366 : If there are no query arguments, the empty string. Otherwise, a `?` followed
367 by the query string.
368
369 `fullpath`
370 : The full path requested, including query arguments (`== path + query`).
371
372 ### Response Data
373
374 `status` (default: "200 OK")
375 : The status code to be used in the response.
376
377 `headers`
378 : A list of 2-tuples to be used in the response.
379
380 `output`
381 : A string to be used as the response.
382 """
383