Package web :: Package web :: Module webapi
[hide private]
[frames] | no frames]

Source Code for Module web.web.webapi

  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      # 200, 201, 202 
 15      "OK", "Created", "Accepted",     
 16      "ok", "created", "accepted", 
 17       
 18      # 301, 302, 303, 304, 307 
 19      "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect",  
 20      "redirect", "found", "seeother", "notmodified", "tempredirect", 
 21   
 22      # 400, 401, 403, 404, 405, 406, 409, 410, 412 
 23      "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", 
 24      "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", 
 25   
 26      # 500 
 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   
42 -class HTTPError(Exception):
43 - def __init__(self, status, headers={}, data=""):
44 ctx.status = status 45 for k, v in headers.items(): 46 header(k, v) 47 self.data = data 48 Exception.__init__(self, status)
49
50 -def _status_code(status, data=None, classname=None, docstring=None):
51 if data is None: 52 data = status.split(" ", 1)[1] 53 classname = status.split(" ", 1)[1].replace(' ', '') # 304 Not Modified -> NotModified 54 docstring = docstring or '`%s` status' % status 55 56 def __init__(self, data=data, headers={}): 57 HTTPError.__init__(self, status, headers, data)
58 59 # trick to create class dynamically with dynamic docstring. 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
69 -class Redirect(HTTPError):
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
94 -class Found(Redirect):
95 """A `302 Found` redirect."""
96 - def __init__(self, url, absolute=False):
97 Redirect.__init__(self, url, '302 Found', absolute=absolute)
98 99 found = Found 100
101 -class SeeOther(Redirect):
102 """A `303 See Other` redirect."""
103 - def __init__(self, url, absolute=False):
104 Redirect.__init__(self, url, '303 See Other', absolute=absolute)
105 106 seeother = SeeOther 107
108 -class NotModified(HTTPError):
109 """A `304 Not Modified` status."""
110 - def __init__(self):
111 HTTPError.__init__(self, "304 Not Modified")
112 113 notmodified = NotModified 114
115 -class TempRedirect(Redirect):
116 """A `307 Temporary Redirect` redirect."""
117 - def __init__(self, url, absolute=False):
118 Redirect.__init__(self, url, '307 Temporary Redirect', absolute=absolute)
119 120 tempredirect = TempRedirect 121
122 -class BadRequest(HTTPError):
123 """`400 Bad Request` error.""" 124 message = "bad request"
125 - def __init__(self):
126 status = "400 Bad Request" 127 headers = {'Content-Type': 'text/html'} 128 HTTPError.__init__(self, status, headers, self.message)
129 130 badrequest = BadRequest 131
132 -class _NotFound(HTTPError):
133 """`404 Not Found` error.""" 134 message = "not found"
135 - def __init__(self, message=None):
136 status = '404 Not Found' 137 headers = {'Content-Type': 'text/html'} 138 HTTPError.__init__(self, status, headers, message or self.message)
139
140 -def NotFound(message=None):
141 """Returns HTTPError with '404 Not Found' error from the active application. 142 """ 143 if message: 144 return _NotFound(message) 145 elif ctx.get('app_stack'): 146 return ctx.app_stack[-1].notfound() 147 else: 148 return _NotFound()
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
158 -class NoMethod(HTTPError):
159 """A `405 Method Not Allowed` error."""
160 - def __init__(self, cls=None):
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"
178 - def __init__(self):
179 status = '410 Gone' 180 headers = {'Content-Type': 'text/html'} 181 HTTPError.__init__(self, status, headers, self.message)
182 183 gone = Gone 184
185 -class _InternalError(HTTPError):
186 """500 Internal Server Error`.""" 187 message = "internal server error" 188
189 - def __init__(self, message=None):
190 status = '500 Internal Server Error' 191 headers = {'Content-Type': 'text/html'} 192 HTTPError.__init__(self, status, headers, message or self.message)
193
194 -def InternalError(message=None):
195 """Returns HTTPError with '500 internal error' error from the active application. 196 """ 197 if message: 198 return _InternalError(message) 199 elif ctx.get('app_stack'): 200 return ctx.app_stack[-1].internalerror() 201 else: 202 return _InternalError()
203 204 internalerror = InternalError 205
206 -def header(hdr, value, unique=False):
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 # protection against HTTP response splitting attack 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
224 -def rawinput(method=None):
225 """Returns storage object with GET or POST arguments. 226 """ 227 method = method or "both" 228 from cStringIO import StringIO 229 230 def dictify(fs): 231 # hack to make web.input work with enctype='text/plain. 232 if fs.list is None: 233 fs.list = [] 234 235 return dict([(k, fs[k]) for k in fs.keys()])
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 # since wsgi.input is directly passed to cgi.FieldStorage, 244 # it can not be called multiple times. Saving the FieldStorage 245 # object in ctx to allow calling web.input multiple times. 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
270 -def input(*requireds, **defaults):
271 """ 272 Returns a `storage` object with the GET and POST arguments. 273 See `storify` for how `requireds` and `defaults` work. 274 """ 275 _method = defaults.pop('_method', 'both') 276 out = rawinput(_method) 277 try: 278 defaults.setdefault('_unicode', True) # force unicode conversion by default. 279 return storify(out, *requireds, **defaults) 280 except KeyError: 281 raise badrequest()
282
283 -def data():
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 # @@ should we limit cookies to a different path? 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
322 -def debug(*args):
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
334 -def _debugwrite(x):
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