1# -*- coding: utf-8 -*- 2""" 3 webapp2 4 ======= 5 6 Taking Google App Engine's webapp to the next level! 7 8 :copyright: 2011 by tipfy.org. 9 :license: Apache Sotware License, see LICENSE for details. 10""" 11from __future__ import with_statement 12 13import cgi 14import inspect 15import logging 16import os 17import re 18import sys 19import threading 20import traceback 21import urllib 22import urlparse 23from wsgiref import handlers 24 25import webob 26from webob import exc 27 28_webapp = _webapp_util = _local = None 29 30try: # pragma: no cover 31 # WebOb < 1.0 (App Engine Python 2.5). 32 from webob.statusreasons import status_reasons 33 from webob.headerdict import HeaderDict as BaseResponseHeaders 34except ImportError: # pragma: no cover 35 # WebOb >= 1.0. 36 from webob.util import status_reasons 37 from webob.headers import ResponseHeaders as BaseResponseHeaders 38 39# google.appengine.ext.webapp imports webapp2 in the 40# App Engine Python 2.7 runtime. 41if os.environ.get('APPENGINE_RUNTIME') != 'python27': # pragma: no cover 42 try: 43 from google.appengine.ext import webapp as _webapp 44 except ImportError: # pragma: no cover 45 # Running webapp2 outside of GAE. 46 pass 47 48try: # pragma: no cover 49 # Thread-local variables container. 50 from webapp2_extras import local 51 _local = local.Local() 52except ImportError: # pragma: no cover 53 logging.warning("webapp2_extras.local is not available " 54 "so webapp2 won't be thread-safe!") 55 56 57__version_info__ = (2, 5, 1) 58__version__ = '.'.join(str(n) for n in __version_info__) 59 60#: Base HTTP exception, set here as public interface. 61HTTPException = exc.HTTPException 62 63#: Regex for route definitions. 64_route_re = re.compile(r""" 65 \< # The exact character "<" 66 ([a-zA-Z_]\w*)? # The optional variable name 67 (?:\:([^\>]*))? # The optional :regex part 68 \> # The exact character ">" 69 """, re.VERBOSE) 70#: Regex extract charset from environ. 71_charset_re = re.compile(r';\s*charset=([^;]*)', re.I) 72 73#: To show exceptions in debug mode. 74_debug_template = """<html> 75 <head> 76 <title>Internal Server Error</title> 77 <style> 78 body { 79 padding: 20px; 80 font-family: arial, sans-serif; 81 font-size: 14px; 82 } 83 pre { 84 background: #F2F2F2; 85 padding: 10px; 86 } 87 </style> 88 </head> 89 <body> 90 <h1>Internal Server Error</h1> 91 <p>The server has either erred or is incapable of performing 92 the requested operation.</p> 93 <pre>%s</pre> 94 </body> 95</html>""" 96 97# Set same default messages from webapp plus missing ones. 98_webapp_status_reasons = { 99 203: 'Non-Authoritative Information', 100 302: 'Moved Temporarily', 101 306: 'Unused', 102 408: 'Request Time-out', 103 414: 'Request-URI Too Large', 104 504: 'Gateway Time-out', 105 505: 'HTTP Version not supported', 106} 107status_reasons.update(_webapp_status_reasons) 108for code, message in _webapp_status_reasons.iteritems(): 109 cls = exc.status_map.get(code) 110 if cls: 111 cls.title = message 112 113 114class Request(webob.Request): 115 """Abstraction for an HTTP request. 116 117 Most extra methods and attributes are ported from webapp. Check the 118 `WebOb documentation <WebOb>`_ for the ones not listed here. 119 """ 120 121 #: A reference to the active :class:`WSGIApplication` instance. 122 app = None 123 #: A reference to the active :class:`Response` instance. 124 response = None 125 #: A reference to the matched :class:`Route`. 126 route = None 127 #: The matched route positional arguments. 128 route_args = None 129 #: The matched route keyword arguments. 130 route_kwargs = None 131 #: A dictionary to register objects used during the request lifetime. 132 registry = None 133 # Attributes from webapp. 134 request_body_tempfile_limit = 0 135 uri = property(lambda self: self.url) 136 query = property(lambda self: self.query_string) 137 138 def __init__(self, environ, *args, **kwargs): 139 """Constructs a Request object from a WSGI environment. 140 141 :param environ: 142 A WSGI-compliant environment dictionary. 143 """ 144 if kwargs.get('charset') is None and not hasattr(webob, '__version__'): 145 # webob 0.9 didn't have a __version__ attribute and also defaulted 146 # to None rather than UTF-8 if no charset was provided. Providing a 147 # default charset is required for backwards compatibility. 148 match = _charset_re.search(environ.get('CONTENT_TYPE', '')) 149 if match: 150 charset = match.group(1).lower().strip().strip('"').strip() 151 else: 152 charset = 'utf-8' 153 kwargs['charset'] = charset 154 155 super(Request, self).__init__(environ, *args, **kwargs) 156 self.registry = {} 157 158 def get(self, argument_name, default_value='', allow_multiple=False): 159 """Returns the query or POST argument with the given name. 160 161 We parse the query string and POST payload lazily, so this will be a 162 slower operation on the first call. 163 164 :param argument_name: 165 The name of the query or POST argument. 166 :param default_value: 167 The value to return if the given argument is not present. 168 :param allow_multiple: 169 Return a list of values with the given name (deprecated). 170 :returns: 171 If allow_multiple is False (which it is by default), we return 172 the first value with the given name given in the request. If it 173 is True, we always return a list. 174 """ 175 param_value = self.get_all(argument_name) 176 if allow_multiple: 177 logging.warning('allow_multiple is a deprecated param. ' 178 'Please use the Request.get_all() method instead.') 179 180 if len(param_value) > 0: 181 if allow_multiple: 182 return param_value 183 184 return param_value[0] 185 else: 186 if allow_multiple and not default_value: 187 return [] 188 189 return default_value 190 191 def get_all(self, argument_name, default_value=None): 192 """Returns a list of query or POST arguments with the given name. 193 194 We parse the query string and POST payload lazily, so this will be a 195 slower operation on the first call. 196 197 :param argument_name: 198 The name of the query or POST argument. 199 :param default_value: 200 The value to return if the given argument is not present, 201 None may not be used as a default, if it is then an empty 202 list will be returned instead. 203 :returns: 204 A (possibly empty) list of values. 205 """ 206 if self.charset: 207 argument_name = argument_name.encode(self.charset) 208 209 if default_value is None: 210 default_value = [] 211 212 param_value = self.params.getall(argument_name) 213 214 if param_value is None or len(param_value) == 0: 215 return default_value 216 217 for i in xrange(len(param_value)): 218 if isinstance(param_value[i], cgi.FieldStorage): 219 param_value[i] = param_value[i].value 220 221 return param_value 222 223 def arguments(self): 224 """Returns a list of the arguments provided in the query and/or POST. 225 226 The return value is a list of strings. 227 """ 228 return list(set(self.params.keys())) 229 230 def get_range(self, name, min_value=None, max_value=None, default=0): 231 """Parses the given int argument, limiting it to the given range. 232 233 :param name: 234 The name of the argument. 235 :param min_value: 236 The minimum int value of the argument (if any). 237 :param max_value: 238 The maximum int value of the argument (if any). 239 :param default: 240 The default value of the argument if it is not given. 241 :returns: 242 An int within the given range for the argument. 243 """ 244 value = self.get(name, default) 245 if value is None: 246 return value 247 248 try: 249 value = int(value) 250 except ValueError: 251 value = default 252 if value is not None: 253 if max_value is not None: 254 value = min(value, max_value) 255 256 if min_value is not None: 257 value = max(value, min_value) 258 259 return value 260 261 @classmethod 262 def blank(cls, path, environ=None, base_url=None, 263 headers=None, **kwargs): # pragma: no cover 264 """Adds parameters compatible with WebOb >= 1.0: POST and **kwargs.""" 265 try: 266 return super(Request, cls).blank(path, environ=environ, 267 base_url=base_url, 268 headers=headers, **kwargs) 269 except TypeError: 270 if not kwargs: 271 raise 272 273 data = kwargs.pop('POST', None) 274 if data is not None: 275 from cStringIO import StringIO 276 environ = environ or {} 277 environ['REQUEST_METHOD'] = 'POST' 278 if hasattr(data, 'items'): 279 data = data.items() 280 if not isinstance(data, str): 281 data = urllib.urlencode(data) 282 environ['wsgi.input'] = StringIO(data) 283 environ['webob.is_body_seekable'] = True 284 environ['CONTENT_LENGTH'] = str(len(data)) 285 environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' 286 287 base = super(Request, cls).blank(path, environ=environ, 288 base_url=base_url, headers=headers) 289 if kwargs: 290 obj = cls(base.environ, **kwargs) 291 obj.headers.update(base.headers) 292 return obj 293 else: 294 return base 295 296 297class ResponseHeaders(BaseResponseHeaders): 298 """Implements methods from ``wsgiref.headers.Headers``, used by webapp.""" 299 300 get_all = BaseResponseHeaders.getall 301 302 def add_header(self, _name, _value, **_params): 303 """Extended header setting. 304 305 _name is the header field to add. keyword arguments can be used to set 306 additional parameters for the header field, with underscores converted 307 to dashes. Normally the parameter will be added as key="value" unless 308 value is None, in which case only the key will be added. 309 310 Example:: 311 312 h.add_header('content-disposition', 'attachment', 313 filename='bud.gif') 314 315 Note that unlike the corresponding 'email.message' method, this does 316 *not* handle '(charset, language, value)' tuples: all values must be 317 strings or None. 318 """ 319 parts = [] 320 if _value is not None: 321 parts.append(_value) 322 323 for k, v in _params.items(): 324 k = k.replace('_', '-') 325 if v is not None and len(v) > 0: 326 v = v.replace('\\', '\\\\').replace('"', r'\"') 327 parts.append('%s="%s"' % (k, v)) 328 else: 329 parts.append(k) 330 331 self.add(_name, '; '.join(parts)) 332 333 def __str__(self): 334 """Returns the formatted headers ready for HTTP transmission.""" 335 return '\r\n'.join(['%s: %s' % v for v in self.items()] + ['', '']) 336 337 338class Response(webob.Response): 339 """Abstraction for an HTTP response. 340 341 Most extra methods and attributes are ported from webapp. Check the 342 `WebOb documentation <WebOb>`_ for the ones not listed here. 343 344 Differences from webapp.Response: 345 346 - ``out`` is not a ``StringIO.StringIO`` instance. Instead it is the 347 response itself, as it has the method ``write()``. 348 - As in WebOb, ``status`` is the code plus message, e.g., '200 OK', while 349 in webapp it is the integer code. The status code as an integer is 350 available in ``status_int``, and the status message is available in 351 ``status_message``. 352 - ``response.headers`` raises an exception when a key that doesn't exist 353 is accessed or deleted, differently from ``wsgiref.headers.Headers``. 354 """ 355 356 #: Default charset as in webapp. 357 default_charset = 'utf-8' 358 359 def __init__(self, *args, **kwargs): 360 """Constructs a response with the default settings.""" 361 super(Response, self).__init__(*args, **kwargs) 362 self.headers['Cache-Control'] = 'no-cache' 363 364 @property 365 def out(self): 366 """A reference to the Response instance itself, for compatibility with 367 webapp only: webapp uses `Response.out.write()`, so we point `out` to 368 `self` and it will use `Response.write()`. 369 """ 370 return self 371 372 def write(self, text): 373 """Appends a text to the response body.""" 374 # webapp uses StringIO as Response.out, so we need to convert anything 375 # that is not str or unicode to string to keep same behavior. 376 if not isinstance(text, basestring): 377 text = unicode(text) 378 379 if isinstance(text, unicode) and not self.charset: 380 self.charset = self.default_charset 381 382 super(Response, self).write(text) 383 384 def _set_status(self, value): 385 """The status string, including code and message.""" 386 message = None 387 # Accept long because urlfetch in App Engine returns codes as longs. 388 if isinstance(value, (int, long)): 389 code = int(value) 390 else: 391 if isinstance(value, unicode): 392 # Status messages have to be ASCII safe, so this is OK. 393 value = str(value) 394 395 if not isinstance(value, str): 396 raise TypeError( 397 'You must set status to a string or integer (not %s)' % 398 type(value)) 399 400 parts = value.split(' ', 1) 401 code = int(parts[0]) 402 if len(parts) == 2: 403 message = parts[1] 404 405 message = message or Response.http_status_message(code) 406 self._status = '%d %s' % (code, message) 407 408 def _get_status(self): 409 return self._status 410 411 status = property(_get_status, _set_status, doc=_set_status.__doc__) 412 413 def set_status(self, code, message=None): 414 """Sets the HTTP status code of this response. 415 416 :param code: 417 The HTTP status string to use 418 :param message: 419 A status string. If none is given, uses the default from the 420 HTTP/1.1 specification. 421 """ 422 if message: 423 self.status = '%d %s' % (code, message) 424 else: 425 self.status = code 426 427 def _get_status_message(self): 428 """The response status message, as a string.""" 429 return self.status.split(' ', 1)[1] 430 431 def _set_status_message(self, message): 432 self.status = '%d %s' % (self.status_int, message) 433 434 status_message = property(_get_status_message, _set_status_message, 435 doc=_get_status_message.__doc__) 436 437 def _get_headers(self): 438 """The headers as a dictionary-like object.""" 439 if self._headers is None: 440 self._headers = ResponseHeaders.view_list(self.headerlist) 441 442 return self._headers 443 444 def _set_headers(self, value): 445 if hasattr(value, 'items'): 446 value = value.items() 447 elif not isinstance(value, list): 448 raise TypeError('Response headers must be a list or dictionary.') 449 450 self.headerlist = value 451 self._headers = None 452 453 headers = property(_get_headers, _set_headers, doc=_get_headers.__doc__) 454 455 def has_error(self): 456 """Indicates whether the response was an error response.""" 457 return self.status_int >= 400 458 459 def clear(self): 460 """Clears all data written to the output stream so that it is empty.""" 461 self.body = '' 462 463 def wsgi_write(self, start_response): 464 """Writes this response using using the given WSGI function. 465 466 This is only here for compatibility with ``webapp.WSGIApplication``. 467 468 :param start_response: 469 The WSGI-compatible start_response function. 470 """ 471 if (self.headers.get('Cache-Control') == 'no-cache' and 472 not self.headers.get('Expires')): 473 self.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' 474 self.headers['Content-Length'] = str(len(self.body)) 475 476 write = start_response(self.status, self.headerlist) 477 write(self.body) 478 479 @staticmethod 480 def http_status_message(code): 481 """Returns the default HTTP status message for the given code. 482 483 :param code: 484 The HTTP code for which we want a message. 485 """ 486 message = status_reasons.get(code) 487 if not message: 488 raise KeyError('Invalid HTTP status code: %d' % code) 489 490 return message 491 492 493class RequestHandler(object): 494 """Base HTTP request handler. 495 496 Implements most of ``webapp.RequestHandler`` interface. 497 """ 498 499 #: A :class:`Request` instance. 500 request = None 501 #: A :class:`Response` instance. 502 response = None 503 #: A :class:`WSGIApplication` instance. 504 app = None 505 506 def __init__(self, request=None, response=None): 507 """Initializes this request handler with the given WSGI application, 508 Request and Response. 509 510 When instantiated by ``webapp.WSGIApplication``, request and response 511 are not set on instantiation. Instead, initialize() is called right 512 after the handler is created to set them. 513 514 Also in webapp dispatching is done by the WSGI app, while webapp2 515 does it here to allow more flexibility in extended classes: handlers 516 can wrap :meth:`dispatch` to check for conditions before executing the 517 requested method and/or post-process the response. 518 519 .. note:: 520 Parameters are optional only to support webapp's constructor which 521 doesn't take any arguments. Consider them as required. 522 523 :param request: 524 A :class:`Request` instance. 525 :param response: 526 A :class:`Response` instance. 527 """ 528 self.initialize(request, response) 529 530 def initialize(self, request, response): 531 """Initializes this request handler with the given WSGI application, 532 Request and Response. 533 534 :param request: 535 A :class:`Request` instance. 536 :param response: 537 A :class:`Response` instance. 538 """ 539 self.request = request 540 self.response = response 541 self.app = WSGIApplication.active_instance 542 543 def dispatch(self): 544 """Dispatches the request. 545 546 This will first check if there's a handler_method defined in the 547 matched route, and if not it'll use the method correspondent to the 548 request method (``get()``, ``post()`` etc). 549 """ 550 request = self.request 551 method_name = request.route.handler_method 552 if not method_name: 553 method_name = _normalize_handler_method(request.method) 554 555 method = getattr(self, method_name, None) 556 if method is None: 557 # 405 Method Not Allowed. 558 # The response MUST include an Allow header containing a 559 # list of valid methods for the requested resource. 560 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6 561 valid = ', '.join(_get_handler_methods(self)) 562 self.abort(405, headers=[('Allow', valid)]) 563 564 # The handler only receives *args if no named variables are set. 565 args, kwargs = request.route_args, request.route_kwargs 566 if kwargs: 567 args = () 568 569 try: 570 return method(*args, **kwargs) 571 except Exception, e: 572 return self.handle_exception(e, self.app.debug) 573 574 def error(self, code): 575 """Clears the response and sets the given HTTP status code. 576 577 This doesn't stop code execution; for this, use :meth:`abort`. 578 579 :param code: 580 HTTP status error code (e.g., 501). 581 """ 582 self.response.status = code 583 self.response.clear() 584 585 def abort(self, code, *args, **kwargs): 586 """Raises an :class:`HTTPException`. 587 588 This stops code execution, leaving the HTTP exception to be handled 589 by an exception handler. 590 591 :param code: 592 HTTP status code (e.g., 404). 593 :param args: 594 Positional arguments to be passed to the exception class. 595 :param kwargs: 596 Keyword arguments to be passed to the exception class. 597 """ 598 abort(code, *args, **kwargs) 599 600 def redirect(self, uri, permanent=False, abort=False, code=None, 601 body=None): 602 """Issues an HTTP redirect to the given relative URI. 603 604 The arguments are described in :func:`redirect`. 605 """ 606 return redirect(uri, permanent=permanent, abort=abort, code=code, 607 body=body, request=self.request, 608 response=self.response) 609 610 def redirect_to(self, _name, _permanent=False, _abort=False, _code=None, 611 _body=None, *args, **kwargs): 612 """Convenience method mixing :meth:`redirect` and :meth:`uri_for`. 613 614 The arguments are described in :func:`redirect` and :func:`uri_for`. 615 """ 616 uri = self.uri_for(_name, *args, **kwargs) 617 return self.redirect(uri, permanent=_permanent, abort=_abort, 618 code=_code, body=_body) 619 620 def uri_for(self, _name, *args, **kwargs): 621 """Returns a URI for a named :class:`Route`. 622 623 .. seealso:: :meth:`Router.build`. 624 """ 625 return self.app.router.build(self.request, _name, args, kwargs) 626 # Alias. 627 url_for = uri_for 628 629 def handle_exception(self, exception, debug): 630 """Called if this handler throws an exception during execution. 631 632 The default behavior is to re-raise the exception to be handled by 633 :meth:`WSGIApplication.handle_exception`. 634 635 :param exception: 636 The exception that was thrown. 637 :param debug_mode: 638 True if the web application is running in debug mode. 639 """ 640 raise 641 642 643class RedirectHandler(RequestHandler): 644 """Redirects to the given URI for all GET requests. 645 646 This is intended to be used when defining URI routes. You must provide at 647 least the keyword argument *url* in the route default values. Example:: 648 649 def get_redirect_url(handler, *args, **kwargs): 650 return handler.uri_for('new-route-name') 651 652 app = WSGIApplication([ 653 Route('/old-url', RedirectHandler, defaults={'_uri': '/new-url'}), 654 Route('/other-old-url', RedirectHandler, defaults={ 655 '_uri': get_redirect_url}), 656 ]) 657 658 Based on idea from `Tornado`_. 659 """ 660 661 def get(self, *args, **kwargs): 662 """Performs a redirect. 663 664 Two keyword arguments can be passed through the URI route: 665 666 - **_uri**: A URI string or a callable that returns a URI. The callable 667 is called passing ``(handler, *args, **kwargs)`` as arguments. 668 - **_code**: The redirect status code. Default is 301 (permanent 669 redirect). 670 """ 671 uri = kwargs.pop('_uri', '/') 672 permanent = kwargs.pop('_permanent', True) 673 code = kwargs.pop('_code', None) 674 675 func = getattr(uri, '__call__', None) 676 if func: 677 uri = func(self, *args, **kwargs) 678 679 self.redirect(uri, permanent=permanent, code=code) 680 681 682class cached_property(object): 683 """A decorator that converts a function into a lazy property. 684 685 The function wrapped is called the first time to retrieve the result 686 and then that calculated result is used the next time you access 687 the value:: 688 689 class Foo(object): 690 691 @cached_property 692 def foo(self): 693 # calculate something important here 694 return 42 695 696 The class has to have a `__dict__` in order for this property to 697 work. 698 699 .. note:: Implementation detail: this property is implemented as non-data 700 descriptor. non-data descriptors are only invoked if there is 701 no entry with the same name in the instance's __dict__. 702 this allows us to completely get rid of the access function call 703 overhead. If one choses to invoke __get__ by hand the property 704 will still work as expected because the lookup logic is replicated 705 in __get__ for manual invocation. 706 707 This class was ported from `Werkzeug`_ and `Flask`_. 708 """ 709 710 _default_value = object() 711 712 def __init__(self, func, name=None, doc=None): 713 self.__name__ = name or func.__name__ 714 self.__module__ = func.__module__ 715 self.__doc__ = doc or func.__doc__ 716 self.func = func 717 self.lock = threading.RLock() 718 719 def __get__(self, obj, type=None): 720 if obj is None: 721 return self 722 723 with self.lock: 724 value = obj.__dict__.get(self.__name__, self._default_value) 725 if value is self._default_value: 726 value = self.func(obj) 727 obj.__dict__[self.__name__] = value 728 729 return value 730 731 732class BaseRoute(object): 733 """Interface for URI routes.""" 734 735 #: The regex template. 736 template = None 737 #: Route name, used to build URIs. 738 name = None 739 #: True if this route is only used for URI generation and never matches. 740 build_only = False 741 #: The handler or string in dotted notation to be lazily imported. 742 handler = None 743 #: The custom handler method, if handler is a class. 744 handler_method = None 745 #: The handler, imported and ready for dispatching. 746 handler_adapter = None 747 748 def __init__(self, template, handler=None, name=None, build_only=False): 749 """Initializes this route. 750 751 :param template: 752 A regex to be matched. 753 :param handler: 754 A callable or string in dotted notation to be lazily imported, 755 e.g., ``'my.module.MyHandler'`` or ``'my.module.my_function'``. 756 :param name: 757 The name of this route, used to build URIs based on it. 758 :param build_only: 759 If True, this route never matches and is used only to build URIs. 760 """ 761 if build_only and name is None: 762 raise ValueError( 763 "Route %r is build_only but doesn't have a name." % self) 764 765 self.template = template 766 self.handler = handler 767 self.name = name 768 self.build_only = build_only 769 770 def match(self, request): 771 """Matches all routes against a request object. 772 773 The first one that matches is returned. 774 775 :param request: 776 A :class:`Request` instance. 777 :returns: 778 A tuple ``(route, args, kwargs)`` if a route matched, or None. 779 """ 780 raise NotImplementedError() 781 782 def build(self, request, args, kwargs): 783 """Returns a URI for this route. 784 785 :param request: 786 The current :class:`Request` object. 787 :param args: 788 Tuple of positional arguments to build the URI. 789 :param kwargs: 790 Dictionary of keyword arguments to build the URI. 791 :returns: 792 An absolute or relative URI. 793 """ 794 raise NotImplementedError() 795 796 def get_routes(self): 797 """Generator to get all routes from a route. 798 799 :yields: 800 This route or all nested routes that it contains. 801 """ 802 yield self 803 804 def get_match_routes(self): 805 """Generator to get all routes that can be matched from a route. 806 807 Match routes must implement :meth:`match`. 808 809 :yields: 810 This route or all nested routes that can be matched. 811 """ 812 if not self.build_only: 813 yield self 814 815 def get_build_routes(self): 816 """Generator to get all routes that can be built from a route. 817 818 Build routes must implement :meth:`build`. 819 820 :yields: 821 A tuple ``(name, route)`` for all nested routes that can be built. 822 """ 823 if self.name is not None: 824 yield self.name, self 825 826 827class SimpleRoute(BaseRoute): 828 """A route that is compatible with webapp's routing mechanism. 829 830 URI building is not implemented as webapp has rudimentar support for it, 831 and this is the most unknown webapp feature anyway. 832 """ 833 834 @cached_property 835 def regex(self): 836 """Lazy regex compiler.""" 837 if not self.template.startswith('^'): 838 self.template = '^' + self.template 839 840 if not self.template.endswith('$'): 841 self.template += '$' 842 843 return re.compile(self.template) 844 845 def match(self, request): 846 """Matches this route against the current request. 847 848 .. seealso:: :meth:`BaseRoute.match`. 849 """ 850 match = self.regex.match(urllib.unquote(request.path)) 851 if match: 852 return self, match.groups(), {} 853 854 def __repr__(self): 855 return '<SimpleRoute(%r, %r)>' % (self.template, self.handler) 856 857 858class Route(BaseRoute): 859 """A route definition that maps a URI path to a handler. 860 861 The initial concept was based on `Another Do-It-Yourself Framework`_, by 862 Ian Bicking. 863 """ 864 865 #: Default parameters values. 866 defaults = None 867 #: Sequence of allowed HTTP methods. If not set, all methods are allowed. 868 methods = None 869 #: Sequence of allowed URI schemes. If not set, all schemes are allowed. 870 schemes = None 871 # Lazy properties extracted from the route template. 872 regex = None 873 reverse_template = None 874 variables = None 875 args_count = 0 876 kwargs_count = 0 877 878 def __init__(self, template, handler=None, name=None, defaults=None, 879 build_only=False, handler_method=None, methods=None, 880 schemes=None): 881 """Initializes this route. 882 883 :param template: 884 A route template to match against the request path. A template 885 can have variables enclosed by ``<>`` that define a name, a 886 regular expression or both. Examples: 887 888 ================= ================================== 889 Format Example 890 ================= ================================== 891 ``<name>`` ``'/blog/<year>/<month>'`` 892 ``<:regex>`` ``'/blog/<:\d{4}>/<:\d{2}>'`` 893 ``<name:regex>`` ``'/blog/<year:\d{4}>/<month:\d{2}>'`` 894 ================= ================================== 895 896 The same template can mix parts with name, regular expression or 897 both. 898 899 If the name is set, the value of the matched regular expression 900 is passed as keyword argument to the handler. Otherwise it is 901 passed as positional argument. 902 903 If only the name is set, it will match anything except a slash. 904 So these routes are equivalent:: 905 906 Route('/<user_id>/settings', handler=SettingsHandler, 907 name='user-settings') 908 Route('/<user_id:[^/]+>/settings', handler=SettingsHandler, 909 name='user-settings') 910 911 .. note:: 912 The handler only receives ``*args`` if no named variables are 913 set. Otherwise, the handler only receives ``**kwargs``. This 914 allows you to set regular expressions that are not captured: 915 just mix named and unnamed variables and the handler will 916 only receive the named ones. 917 918 :param handler: 919 A callable or string in dotted notation to be lazily imported, 920 e.g., ``'my.module.MyHandler'`` or ``'my.module.my_function'``. 921 It is possible to define a method if the callable is a class, 922 separating it by a colon: ``'my.module.MyHandler:my_method'``. 923 This is a shortcut and has the same effect as defining the 924 `handler_method` parameter. 925 :param name: 926 The name of this route, used to build URIs based on it. 927 :param defaults: 928 Default or extra keywords to be returned by this route. Values 929 also present in the route variables are used to build the URI 930 when they are missing. 931 :param build_only: 932 If True, this route never matches and is used only to build URIs. 933 :param handler_method: 934 The name of a custom handler method to be called, in case `handler` 935 is a class. If not defined, the default behavior is to call the 936 handler method correspondent to the HTTP request method in lower 937 case (e.g., `get()`, `post()` etc). 938 :param methods: 939 A sequence of HTTP methods. If set, the route will only match if 940 the request method is allowed. 941 :param schemes: 942 A sequence of URI schemes, e.g., ``['http']`` or ``['https']``. 943 If set, the route will only match requests with these schemes. 944 """ 945 super(Route, self).__init__(template, handler=handler, name=name, 946 build_only=build_only) 947 self.defaults = defaults or {} 948 self.methods = methods 949 self.schemes = schemes 950 if isinstance(handler, basestring) and ':' in handler: 951 if handler_method: 952 raise ValueError( 953 "If handler_method is defined in a Route, handler " 954 "can't have a colon (got %r)." % handler) 955 else: 956 self.handler, self.handler_method = handler.rsplit(':', 1) 957 else: 958 self.handler_method = handler_method 959 960 @cached_property 961 def regex(self): 962 """Lazy route template parser.""" 963 regex, self.reverse_template, self.args_count, self.kwargs_count, \ 964 self.variables = _parse_route_template(self.template, 965 default_sufix='[^/]+') 966 return regex 967 968 def match(self, request): 969 """Matches this route against the current request. 970 971 :raises: 972 ``exc.HTTPMethodNotAllowed`` if the route defines :attr:`methods` 973 and the request method isn't allowed. 974 975 .. seealso:: :meth:`BaseRoute.match`. 976 """ 977 match = self.regex.match(urllib.unquote(request.path)) 978 if not match or self.schemes and request.scheme not in self.schemes: 979 return None 980 981 if self.methods and request.method not in self.methods: 982 # This will be caught by the router, so routes with different 983 # methods can be tried. 984 raise exc.HTTPMethodNotAllowed() 985 986 args, kwargs = _get_route_variables(match, self.defaults.copy()) 987 return self, args, kwargs 988 989 def build(self, request, args, kwargs): 990 """Returns a URI for this route. 991 992 .. seealso:: :meth:`Router.build`. 993 """ 994 scheme = kwargs.pop('_scheme', None) 995 netloc = kwargs.pop('_netloc', None) 996 anchor = kwargs.pop('_fragment', None) 997 full = kwargs.pop('_full', False) and not scheme and not netloc 998 999 if full or scheme or netloc: 1000 netloc = netloc or request.host 1001 scheme = scheme or request.scheme 1002 1003 path, query = self._build(args, kwargs) 1004 return _urlunsplit(scheme, netloc, path, query, anchor) 1005 1006 def _build(self, args, kwargs): 1007 """Returns the URI path for this route. 1008 1009 :returns: 1010 A tuple ``(path, kwargs)`` with the built URI path and extra 1011 keywords to be used as URI query arguments. 1012 """ 1013 # Access self.regex just to set the lazy properties. 1014 regex = self.regex 1015 variables = self.variables 1016 if self.args_count: 1017 for index, value in enumerate(args): 1018 key = '__%d__' % index 1019 if key in variables: 1020 kwargs[key] = value 1021 1022 values = {} 1023 for name, regex in variables.iteritems(): 1024 value = kwargs.pop(name, self.defaults.get(name)) 1025 if value is None: 1026 raise KeyError('Missing argument "%s" to build URI.' % \ 1027 name.strip('_')) 1028 1029 if not isinstance(value, basestring): 1030 value = str(value) 1031 1032 if not regex.match(value): 1033 raise ValueError('URI buiding error: Value "%s" is not ' 1034 'supported for argument "%s".' % (value, name.strip('_'))) 1035 1036 values[name] = value 1037 1038 return (self.reverse_template % values, kwargs) 1039 1040 def __repr__(self): 1041 return '<Route(%r, %r, name=%r, defaults=%r, build_only=%r)>' % \ 1042 (self.template, self.handler, self.name, self.defaults, 1043 self.build_only) 1044 1045 1046class BaseHandlerAdapter(object): 1047 """A basic adapter to dispatch a handler. 1048 1049 This is used when the handler is a simple function: it just calls the 1050 handler and returns the resulted response. 1051 """ 1052 1053 #: The handler to be dispatched. 1054 handler = None 1055 1056 def __init__(self, handler): 1057 self.handler = handler 1058 1059 def __call__(self, request, response): 1060 # The handler only receives *args if no named variables are set. 1061 args, kwargs = request.route_args, request.route_kwargs 1062 if kwargs: 1063 args = () 1064 1065 return self.handler(request, *args, **kwargs) 1066 1067 1068class WebappHandlerAdapter(BaseHandlerAdapter): 1069 """An adapter to dispatch a ``webapp.RequestHandler``. 1070 1071 Like in webapp, the handler is constructed, then ``initialize()`` is 1072 called, then the method corresponding to the HTTP request method is called. 1073 """ 1074 1075 def __call__(self, request, response): 1076 handler = self.handler() 1077 handler.initialize(request, response) 1078 method_name = _normalize_handler_method(request.method) 1079 method = getattr(handler, method_name, None) 1080 if not method: 1081 abort(501) 1082 1083 # The handler only receives *args if no named variables are set. 1084 args, kwargs = request.route_args, request.route_kwargs 1085 if kwargs: 1086 args = () 1087 1088 try: 1089 method(*args, **kwargs) 1090 except Exception, e: 1091 handler.handle_exception(e, request.app.debug) 1092 1093 1094class Webapp2HandlerAdapter(BaseHandlerAdapter): 1095 """An adapter to dispatch a ``webapp2.RequestHandler``. 1096 1097 The handler is constructed then ``dispatch()`` is called. 1098 """ 1099 1100 def __call__(self, request, response): 1101 handler = self.handler(request, response) 1102 return handler.dispatch() 1103 1104 1105class Router(object): 1106 """A URI router used to match, dispatch and build URIs.""" 1107 1108 #: Class used when the route is set as a tuple. 1109 route_class = SimpleRoute 1110 #: All routes that can be matched. 1111 match_routes = None 1112 #: All routes that can be built. 1113 build_routes = None 1114 #: Handler classes imported lazily. 1115 handlers = None 1116 1117 def __init__(self, routes=None): 1118 """Initializes the router. 1119 1120 :param routes: 1121 A sequence of :class:`Route` instances or, for simple routes, 1122 tuples ``(regex, handler)``. 1123 """ 1124 self.match_routes = [] 1125 self.build_routes = {} 1126 self.handlers = {} 1127 if routes: 1128 for route in routes: 1129 self.add(route) 1130 1131 def add(self, route): 1132 """Adds a route to this router. 1133 1134 :param route: 1135 A :class:`Route` instance or, for simple routes, a tuple 1136 ``(regex, handler)``. 1137 """ 1138 if isinstance(route, tuple): 1139 # Exceptional case: simple routes defined as a tuple. 1140 route = self.route_class(*route) 1141 1142 for r in route.get_match_routes(): 1143 self.match_routes.append(r) 1144 1145 for name, r in route.get_build_routes(): 1146 self.build_routes[name] = r 1147 1148 def set_matcher(self, func): 1149 """Sets the function called to match URIs. 1150 1151 :param func: 1152 A function that receives ``(router, request)`` and returns 1153 a tuple ``(route, args, kwargs)`` if any route matches, or 1154 raise ``exc.HTTPNotFound`` if no route matched or 1155 ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP 1156 method was not allowed. 1157 """ 1158 # Functions are descriptors, so bind it to this instance with __get__. 1159 self.match = func.__get__(self, self.__class__) 1160 1161 def set_builder(self, func): 1162 """Sets the function called to build URIs. 1163 1164 :param func: 1165 A function that receives ``(router, request, name, args, kwargs)`` 1166 and returns a URI. 1167 """ 1168 self.build = func.__get__(self, self.__class__) 1169 1170 def set_dispatcher(self, func): 1171 """Sets the function called to dispatch the handler. 1172 1173 :param func: 1174 A function that receives ``(router, request, response)`` 1175 and returns the value returned by the dispatched handler. 1176 """ 1177 self.dispatch = func.__get__(self, self.__class__) 1178 1179 def set_adapter(self, func): 1180 """Sets the function that adapts loaded handlers for dispatching. 1181 1182 :param func: 1183 A function that receives ``(router, handler)`` and returns a 1184 handler callable. 1185 """ 1186 self.adapt = func.__get__(self, self.__class__) 1187 1188 def default_matcher(self, request): 1189 """Matches all routes against a request object. 1190 1191 The first one that matches is returned. 1192 1193 :param request: 1194 A :class:`Request` instance. 1195 :returns: 1196 A tuple ``(route, args, kwargs)`` if a route matched, or None. 1197 :raises: 1198 ``exc.HTTPNotFound`` if no route matched or 1199 ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP 1200 method was not allowed. 1201 """ 1202 method_not_allowed = False 1203 for route in self.match_routes: 1204 try: 1205 match = route.match(request) 1206 if match: 1207 return match 1208 except exc.HTTPMethodNotAllowed: 1209 method_not_allowed = True 1210 1211 if method_not_allowed: 1212 raise exc.HTTPMethodNotAllowed() 1213 1214 raise exc.HTTPNotFound() 1215 1216 def default_builder(self, request, name, args, kwargs): 1217 """Returns a URI for a named :class:`Route`. 1218 1219 :param request: 1220 The current :class:`Request` object. 1221 :param name: 1222 The route name. 1223 :param args: 1224 Tuple of positional arguments to build the URI. All positional 1225 variables defined in the route must be passed and must conform 1226 to the format set in the route. Extra arguments are ignored. 1227 :param kwargs: 1228 Dictionary of keyword arguments to build the URI. All variables 1229 not set in the route default values must be passed and must 1230 conform to the format set in the route. Extra keywords are 1231 appended as a query string. 1232 1233 A few keywords have special meaning: 1234 1235 - **_full**: If True, builds an absolute URI. 1236 - **_scheme**: URI scheme, e.g., `http` or `https`. If defined, 1237 an absolute URI is always returned. 1238 - **_netloc**: Network location, e.g., `www.google.com`. If 1239 defined, an absolute URI is always returned. 1240 - **_fragment**: If set, appends a fragment (or "anchor") to the 1241 generated URI. 1242 :returns: 1243 An absolute or relative URI. 1244 """ 1245 route = self.build_routes.get(name) 1246 if route is None: 1247 raise KeyError('Route named %r is not defined.' % name) 1248 1249 return route.build(request, args, kwargs) 1250 1251 def default_dispatcher(self, request, response): 1252 """Dispatches a handler. 1253 1254 :param request: 1255 A :class:`Request` instance. 1256 :param response: 1257 A :class:`Response` instance. 1258 :raises: 1259 ``exc.HTTPNotFound`` if no route matched or 1260 ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP 1261 method was not allowed. 1262 :returns: 1263 The returned value from the handler. 1264 """ 1265 route, args, kwargs = rv = self.match(request) 1266 request.route, request.route_args, request.route_kwargs = rv 1267 1268 if route.handler_adapter is None: 1269 handler = route.handler 1270 if isinstance(handler, basestring): 1271 if handler not in self.handlers: 1272 self.handlers[handler] = handler = import_string(handler) 1273 else: 1274 handler = self.handlers[handler] 1275 1276 route.handler_adapter = self.adapt(handler) 1277 1278 return route.handler_adapter(request, response) 1279 1280 def default_adapter(self, handler): 1281 """Adapts a handler for dispatching. 1282 1283 Because handlers use or implement different dispatching mechanisms, 1284 they can be wrapped to use a unified API for dispatching. 1285 This way webapp2 can support, for example, a :class:`RequestHandler` 1286 class and function views or, for compatibility purposes, a 1287 ``webapp.RequestHandler`` class. The adapters follow the same router 1288 dispatching API but dispatch each handler type differently. 1289 1290 :param handler: 1291 A handler callable. 1292 :returns: 1293 A wrapped handler callable. 1294 """ 1295 if inspect.isclass(handler): 1296 if _webapp and issubclass(handler, _webapp.RequestHandler): 1297 # Compatible with webapp.RequestHandler. 1298 adapter = WebappHandlerAdapter 1299 else: 1300 # Default, compatible with webapp2.RequestHandler. 1301 adapter = Webapp2HandlerAdapter 1302 else: 1303 # A "view" function. 1304 adapter = BaseHandlerAdapter 1305 1306 return adapter(handler) 1307 1308 def __repr__(self): 1309 routes = self.match_routes + [v for k, v in \ 1310 self.build_routes.iteritems() if v not in self.match_routes] 1311 1312 return '<Router(%r)>' % routes 1313 1314 # Default matcher, builder, dispatcher and adapter. 1315 match = default_matcher 1316 build = default_builder 1317 dispatch = default_dispatcher 1318 adapt = default_adapter 1319 1320 1321class Config(dict): 1322 """A simple configuration dictionary for the :class:`WSGIApplication`.""" 1323 1324 #: Loaded configurations. 1325 loaded = None 1326 1327 def __init__(self, defaults=None): 1328 dict.__init__(self, defaults or ()) 1329 self.loaded = [] 1330 1331 def load_config(self, key, default_values=None, user_values=None, 1332 required_keys=None): 1333 """Returns a configuration for a given key. 1334 1335 This can be used by objects that define a default configuration. It 1336 will update the app configuration with the default values the first 1337 time it is requested, and mark the key as loaded. 1338 1339 :param key: 1340 A configuration key. 1341 :param default_values: 1342 Default values defined by a module or class. 1343 :param user_values: 1344 User values, used when an object can be initialized with 1345 configuration. This overrides the app configuration. 1346 :param required_keys: 1347 Keys that can not be None. 1348 :raises: 1349 Exception, when a required key is not set or is None. 1350 """ 1351 if key in self.loaded: 1352 config = self[key] 1353 else: 1354 config = dict(default_values or ()) 1355 if key in self: 1356 config.update(self[key]) 1357 1358 self[key] = config 1359 self.loaded.append(key) 1360 if required_keys and not user_values: 1361 self._validate_required(key, config, required_keys) 1362 1363 if user_values: 1364 config = config.copy() 1365 config.update(user_values) 1366 if required_keys: 1367 self._validate_required(key, config, required_keys) 1368 1369 return config 1370 1371 def _validate_required(self, key, config, required_keys): 1372 missing = [k for k in required_keys if config.get(k) is None] 1373 if missing: 1374 raise Exception( 1375 'Missing configuration keys for %r: %r.' % (key, missing)) 1376 1377 1378class RequestContext(object): 1379 """Context for a single request. 1380 1381 The context is responsible for setting and cleaning global variables for 1382 a request. 1383 """ 1384 1385 #: A :class:`WSGIApplication` instance. 1386 app = None 1387 #: WSGI environment dictionary. 1388 environ = None 1389 1390 def __init__(self, app, environ): 1391 """Initializes the request context. 1392 1393 :param app: 1394 An :class:`WSGIApplication` instance. 1395 :param environ: 1396 A WSGI environment dictionary. 1397 """ 1398 self.app = app 1399 self.environ = environ 1400 1401 def __enter__(self): 1402 """Enters the request context. 1403 1404 :returns: 1405 A tuple ``(request, response)``. 1406 """ 1407 # Build request and response. 1408 request = self.app.request_class(self.environ) 1409 response = self.app.response_class() 1410 # Make active app and response available through the request object. 1411 request.app = self.app 1412 request.response = response 1413 # Register global variables. 1414 self.app.set_globals(app=self.app, request=request) 1415 return request, response 1416 1417 def __exit__(self, exc_type, exc_value, traceback): 1418 """Exits the request context. 1419 1420 This release the context locals except if an exception is caught 1421 in debug mode. In this case they are kept to be inspected. 1422 """ 1423 if exc_type is None or not self.app.debug: 1424 # Unregister global variables. 1425 self.app.clear_globals() 1426 1427 1428class WSGIApplication(object): 1429 """A WSGI-compliant application.""" 1430 1431 #: Allowed request methods. 1432 allowed_methods = frozenset(('GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 1433 'DELETE', 'TRACE')) 1434 #: Class used for the request object. 1435 request_class = Request 1436 #: Class used for the response object. 1437 response_class = Response 1438 #: Class used for the router object. 1439 router_class = Router 1440 #: Class used for the request context object. 1441 request_context_class = RequestContext 1442 #: Class used for the configuration object. 1443 config_class = Config 1444 #: A general purpose flag to indicate development mode: if True, uncaught 1445 #: exceptions are raised instead of using ``HTTPInternalServerError``. 1446 debug = False 1447 #: A :class:`Router` instance with all URIs registered for the application. 1448 router = None 1449 #: A :class:`Config` instance with the application configuration. 1450 config = None 1451 #: A dictionary to register objects used during the app lifetime. 1452 registry = None 1453 #: A dictionary mapping HTTP error codes to callables to handle those 1454 #: HTTP exceptions. See :meth:`handle_exception`. 1455 error_handlers = None 1456 #: Active :class:`WSGIApplication` instance. See :meth:`set_globals`. 1457 app = None 1458 #: Active :class:`Request` instance. See :meth:`set_globals`. 1459 request = None 1460 #: Same as :attr:`app`, for webapp compatibility. See :meth:`set_globals`. 1461 active_instance = None 1462 1463 def __init__(self, routes=None, debug=False, config=None): 1464 """Initializes the WSGI application. 1465 1466 :param routes: 1467 A sequence of :class:`Route` instances or, for simple routes, 1468 tuples ``(regex, handler)``. 1469 :param debug: 1470 True to enable debug mode, False otherwise. 1471 :param config: 1472 A configuration dictionary for the application. 1473 """ 1474 self.debug = debug 1475 self.registry = {} 1476 self.error_handlers = {} 1477 self.set_globals(app=self) 1478 self.config = self.config_class(config) 1479 self.router = self.router_class(routes) 1480 1481 def set_globals(self, app=None, request=None): 1482 """Registers the global variables for app and request. 1483 1484 If :mod:`webapp2_extras.local` is available the app and request 1485 class attributes are assigned to a proxy object that returns them 1486 using thread-local, making the application thread-safe. This can also 1487 be used in environments that don't support threading. 1488 1489 If :mod:`webapp2_extras.local` is not available app and request will 1490 be assigned directly as class attributes. This should only be used in 1491 non-threaded environments (e.g., App Engine Python 2.5). 1492 1493 :param app: 1494 A :class:`WSGIApplication` instance. 1495 :param request: 1496 A :class:`Request` instance. 1497 """ 1498 if _local is not None: # pragma: no cover 1499 _local.app = app 1500 _local.request = request 1501 else: # pragma: no cover 1502 WSGIApplication.app = WSGIApplication.active_instance = app 1503 WSGIApplication.request = request 1504 1505 def clear_globals(self): 1506 """Clears global variables. See :meth:`set_globals`.""" 1507 if _local is not None: # pragma: no cover 1508 _local.__release_local__() 1509 else: # pragma: no cover 1510 WSGIApplication.app = WSGIApplication.active_instance = None 1511 WSGIApplication.request = None 1512 1513 def __call__(self, environ, start_response): 1514 """Called by WSGI when a request comes in. 1515 1516 :param environ: 1517 A WSGI environment. 1518 :param start_response: 1519 A callable accepting a status code, a list of headers and an 1520 optional exception context to start the response. 1521 :returns: 1522 An iterable with the response to return to the client. 1523 """ 1524 with self.request_context_class(self, environ) as (request, response): 1525 try: 1526 if request.method not in self.allowed_methods: 1527 # 501 Not Implemented. 1528 raise exc.HTTPNotImplemented() 1529 1530 rv = self.router.dispatch(request, response) 1531 if rv is not None: 1532 response = rv 1533 except Exception, e: 1534 try: 1535 # Try to handle it with a custom error handler. 1536 rv = self.handle_exception(request, response, e) 1537 if rv is not None: 1538 response = rv 1539 except HTTPException, e: 1540 # Use the HTTP exception as response. 1541 response = e 1542 except Exception, e: 1543 # Error wasn't handled so we have nothing else to do. 1544 response = self._internal_error(e) 1545 1546 try: 1547 return response(environ, start_response) 1548 except Exception, e: 1549 return self._internal_error(e)(environ, start_response) 1550 1551 def _internal_error(self, exception): 1552 """Last resource error for :meth:`__call__`.""" 1553 logging.exception(exception) 1554 if self.debug: 1555 lines = ''.join(traceback.format_exception(*sys.exc_info())) 1556 html = _debug_template % (cgi.escape(lines, quote=True)) 1557 return Response(body=html, status=500) 1558 1559 return exc.HTTPInternalServerError() 1560 1561 def handle_exception(self, request, response, e): 1562 """Handles a uncaught exception occurred in :meth:`__call__`. 1563 1564 Uncaught exceptions can be handled by error handlers registered in 1565 :attr:`error_handlers`. This is a dictionary that maps HTTP status 1566 codes to callables that will handle the corresponding error code. 1567 If the exception is not an ``HTTPException``, the status code 500 1568 is used. 1569 1570 The error handlers receive (request, response, exception) and can be 1571 a callable or a string in dotted notation to be lazily imported. 1572 1573 If no error handler is found, the exception is re-raised. 1574 1575 Based on idea from `Flask`_. 1576 1577 :param request: 1578 A :class:`Request` instance. 1579 :param response: 1580 A :class:`Response` instance. 1581 :param e: 1582 The uncaught exception. 1583 :returns: 1584 The returned value from the error handler. 1585 """ 1586 if isinstance(e, HTTPException): 1587 code = e.code 1588 else: 1589 code = 500 1590 1591 handler = self.error_handlers.get(code) 1592 if handler: 1593 if isinstance(handler, basestring): 1594 self.error_handlers[code] = handler = import_string(handler) 1595 1596 return handler(request, response, e) 1597 else: 1598 # Re-raise it to be caught by the WSGI app. 1599 raise 1600 1601 def run(self, bare=False): 1602 """Runs this WSGI-compliant application in a CGI environment. 1603 1604 This uses functions provided by ``google.appengine.ext.webapp.util``, 1605 if available: ``run_bare_wsgi_app`` and ``run_wsgi_app``. 1606 1607 Otherwise, it uses ``wsgiref.handlers.CGIHandler().run()``. 1608 1609 :param bare: 1610 If True, doesn't add registered WSGI middleware: use 1611 ``run_bare_wsgi_app`` instead of ``run_wsgi_app``. 1612 """ 1613 if _webapp_util: 1614 if bare: 1615 _webapp_util.run_bare_wsgi_app(self) 1616 else: 1617 _webapp_util.run_wsgi_app(self) 1618 else: # pragma: no cover 1619 handlers.CGIHandler().run(self) 1620 1621 def get_response(self, *args, **kwargs): 1622 """Creates a request and returns a response for this app. 1623 1624 This is a convenience for unit testing purposes. It receives 1625 parameters to build a request and calls the application, returning 1626 the resulting response:: 1627 1628 class HelloHandler(webapp2.RequestHandler): 1629 def get(self): 1630 self.response.write('Hello, world!') 1631 1632 app = webapp2.WSGIapplication([('/', HelloHandler)]) 1633 1634 # Test the app, passing parameters to build a request. 1635 response = app.get_response('/') 1636 assert response.status_int == 200 1637 assert response.body == 'Hello, world!' 1638 1639 :param args: 1640 Positional arguments to be passed to ``Request.blank()``. 1641 :param kwargs: 1642 Keyword arguments to be passed to ``Request.blank()``. 1643 :returns: 1644 A :class:`Response` object. 1645 """ 1646 return self.request_class.blank(*args, **kwargs).get_response(self) 1647 1648 1649_import_string_error = """\ 1650import_string() failed for %r. Possible reasons are: 1651 1652- missing __init__.py in a package; 1653- package or module path not included in sys.path; 1654- duplicated package or module name taking precedence in sys.path; 1655- missing module, class, function or variable; 1656 1657Original exception: 1658 1659%s: %s 1660 1661Debugged import: 1662 1663%s""" 1664 1665 1666class ImportStringError(Exception): 1667 """Provides information about a failed :func:`import_string` attempt.""" 1668 1669 #: String in dotted notation that failed to be imported. 1670 import_name = None 1671 #: Wrapped exception. 1672 exception = None 1673 1674 def __init__(self, import_name, exception): 1675 self.import_name = import_name 1676 self.exception = exception 1677 msg = _import_string_error 1678 name = '' 1679 tracked = [] 1680 for part in import_name.split('.'): 1681 name += (name and '.') + part 1682 imported = import_string(name, silent=True) 1683 if imported: 1684 tracked.append((name, imported.__file__)) 1685 else: 1686 track = ['- %r found in %r.' % rv for rv in tracked] 1687 track.append('- %r not found.' % name) 1688 msg = msg % (import_name, exception.__class__.__name__, 1689 str(exception), '\n'.join(track)) 1690 break 1691 1692 Exception.__init__(self, msg) 1693 1694 1695_get_app_error = 'WSGIApplication global variable is not set.' 1696_get_request_error = 'Request global variable is not set.' 1697 1698 1699def get_app(): 1700 """Returns the active app instance. 1701 1702 :returns: 1703 A :class:`WSGIApplication` instance. 1704 """ 1705 if _local: 1706 assert getattr(_local, 'app', None) is not None, _get_app_error 1707 else: 1708 assert WSGIApplication.app is not None, _get_app_error 1709 1710 return WSGIApplication.app 1711 1712 1713def get_request(): 1714 """Returns the active request instance. 1715 1716 :returns: 1717 A :class:`Request` instance. 1718 """ 1719 if _local: 1720 assert getattr(_local, 'request', None) is not None, _get_request_error 1721 else: 1722 assert WSGIApplication.request is not None, _get_request_error 1723 1724 return WSGIApplication.request 1725 1726 1727def uri_for(_name, _request=None, *args, **kwargs): 1728 """A standalone uri_for version that can be passed to templates. 1729 1730 .. seealso:: :meth:`Router.build`. 1731 """ 1732 request = _request or get_request() 1733 return request.app.router.build(request, _name, args, kwargs) 1734 1735 1736def redirect(uri, permanent=False, abort=False, code=None, body=None, 1737 request=None, response=None): 1738 """Issues an HTTP redirect to the given relative URI. 1739 1740 This won't stop code execution unless **abort** is True. A common 1741 practice is to return when calling this method:: 1742 1743 return redirect('/some-path') 1744 1745 :param uri: 1746 A relative or absolute URI (e.g., ``'../flowers.html'``). 1747 :param permanent: 1748 If True, uses a 301 redirect instead of a 302 redirect. 1749 :param abort: 1750 If True, raises an exception to perform the redirect. 1751 :param code: 1752 The redirect status code. Supported codes are 301, 302, 303, 305, 1753 and 307. 300 is not supported because it's not a real redirect 1754 and 304 because it's the answer for a request with defined 1755 ``If-Modified-Since`` headers. 1756 :param body: 1757 Response body, if any. 1758 :param request: 1759 Optional request object. If not set, uses :func:`get_request`. 1760 :param response: 1761 Optional response object. If not set, a new response is created. 1762 :returns: 1763 A :class:`Response` instance. 1764 """ 1765 if uri.startswith(('.', '/')): 1766 request = request or get_request() 1767 uri = str(urlparse.urljoin(request.url, uri)) 1768 1769 if code is None: 1770 if permanent: 1771 code = 301 1772 else: 1773 code = 302 1774 1775 assert code in (301, 302, 303, 305, 307), \ 1776 'Invalid redirect status code.' 1777 1778 if abort: 1779 _abort(code, headers=[('Location', uri)]) 1780 1781 if response is None: 1782 request = request or get_request() 1783 response = request.app.response_class() 1784 else: 1785 response.clear() 1786 1787 response.headers['Location'] = uri 1788 response.status = code 1789 if body is not None: 1790 response.write(body) 1791 1792 return response 1793 1794 1795def redirect_to(_name, _permanent=False, _abort=False, _code=None, 1796 _body=None, _request=None, _response=None, *args, **kwargs): 1797 """Convenience function mixing :func:`redirect` and :func:`uri_for`. 1798 1799 Issues an HTTP redirect to a named URI built using :func:`uri_for`. 1800 1801 :param _name: 1802 The route name to redirect to. 1803 :param args: 1804 Positional arguments to build the URI. 1805 :param kwargs: 1806 Keyword arguments to build the URI. 1807 :returns: 1808 A :class:`Response` instance. 1809 1810 The other arguments are described in :func:`redirect`. 1811 """ 1812 uri = uri_for(_name, _request=_request, *args, **kwargs) 1813 return redirect(uri, permanent=_permanent, abort=_abort, code=_code, 1814 body=_body, request=_request, response=_response) 1815 1816 1817def abort(code, *args, **kwargs): 1818 """Raises an ``HTTPException``. 1819 1820 :param code: 1821 An integer that represents a valid HTTP status code. 1822 :param args: 1823 Positional arguments to instantiate the exception. 1824 :param kwargs: 1825 Keyword arguments to instantiate the exception. 1826 """ 1827 cls = exc.status_map.get(code) 1828 if not cls: 1829 raise KeyError('No exception is defined for code %r.' % code) 1830 1831 raise cls(*args, **kwargs) 1832 1833 1834def import_string(import_name, silent=False): 1835 """Imports an object based on a string in dotted notation. 1836 1837 Simplified version of the function with same name from `Werkzeug`_. 1838 1839 :param import_name: 1840 String in dotted notation of the object to be imported. 1841 :param silent: 1842 If True, import or attribute errors are ignored and None is returned 1843 instead of raising an exception. 1844 :returns: 1845 The imported object. 1846 """ 1847 import_name = _to_utf8(import_name) 1848 try: 1849 if '.' in import_name: 1850 module, obj = import_name.rsplit('.', 1) 1851 return getattr(__import__(module, None, None, [obj]), obj) 1852 else: 1853 return __import__(import_name) 1854 except (ImportError, AttributeError), e: 1855 if not silent: 1856 raise ImportStringError(import_name, e), None, sys.exc_info()[2] 1857 1858 1859def _urlunsplit(scheme=None, netloc=None, path=None, query=None, 1860 fragment=None): 1861 """Like ``urlparse.urlunsplit``, but will escape values and urlencode and 1862 sort query arguments. 1863 1864 :param scheme: 1865 URI scheme, e.g., `http` or `https`. 1866 :param netloc: 1867 Network location, e.g., `localhost:8080` or `www.google.com`. 1868 :param path: 1869 URI path. 1870 :param query: 1871 URI query as an escaped string, or a dictionary or list of key-values 1872 tuples to build a query. 1873 :param fragment: 1874 Fragment identifier, also known as "anchor". 1875 :returns: 1876 An assembled absolute or relative URI. 1877 """ 1878 if not scheme or not netloc: 1879 scheme = None 1880 netloc = None 1881 1882 if path: 1883 path = urllib.quote(_to_utf8(path)) 1884 1885 if query and not isinstance(query, basestring): 1886 if isinstance(query, dict): 1887 query = query.iteritems() 1888 1889 # Sort args: commonly needed to build signatures for services. 1890 query = urllib.urlencode(sorted(query)) 1891 1892 if fragment: 1893 fragment = urllib.quote(_to_utf8(fragment)) 1894 1895 return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) 1896 1897 1898def _get_handler_methods(handler): 1899 """Returns a list of HTTP methods supported by a handler. 1900 1901 :param handler: 1902 A :class:`RequestHandler` instance. 1903 :returns: 1904 A list of HTTP methods supported by the handler. 1905 """ 1906 methods = [] 1907 for method in get_app().allowed_methods: 1908 if getattr(handler, _normalize_handler_method(method), None): 1909 methods.append(method) 1910 1911 return methods 1912 1913 1914def _normalize_handler_method(method): 1915 """Transforms an HTTP method into a valid Python identifier.""" 1916 return method.lower().replace('-', '_') 1917 1918 1919def _to_utf8(value): 1920 """Encodes a unicode value to UTF-8 if not yet encoded.""" 1921 if isinstance(value, str): 1922 return value 1923 1924 return value.encode('utf-8') 1925 1926 1927def _parse_route_template(template, default_sufix=''): 1928 """Lazy route template parser.""" 1929 variables = {} 1930 reverse_template = pattern = '' 1931 args_count = last = 0 1932 for match in _route_re.finditer(template): 1933 part = template[last:match.start()] 1934 name = match.group(1) 1935 expr = match.group(2) or default_sufix 1936 last = match.end() 1937 1938 if not name: 1939 name = '__%d__' % args_count 1940 args_count += 1 1941 1942 pattern += '%s(?P<%s>%s)' % (re.escape(part), name, expr) 1943 reverse_template += '%s%%(%s)s' % (part, name) 1944 variables[name] = re.compile('^%s$' % expr) 1945 1946 part = template[last:] 1947 kwargs_count = len(variables) - args_count 1948 reverse_template += part 1949 regex = re.compile('^%s%s$' % (pattern, re.escape(part))) 1950 return regex, reverse_template, args_count, kwargs_count, variables 1951 1952 1953def _get_route_variables(match, default_kwargs=None): 1954 """Returns (args, kwargs) for a route match.""" 1955 kwargs = default_kwargs or {} 1956 kwargs.update(match.groupdict()) 1957 if kwargs: 1958 args = tuple(value[1] for value in sorted( 1959 (int(key[2:-2]), kwargs.pop(key)) for key in kwargs.keys() \ 1960 if key.startswith('__') and key.endswith('__'))) 1961 else: 1962 args = () 1963 1964 return args, kwargs 1965 1966 1967def _set_thread_safe_app(): 1968 """Assigns WSGIApplication globals to a proxy pointing to thread-local.""" 1969 if _local is not None: # pragma: no cover 1970 WSGIApplication.app = WSGIApplication.active_instance = _local('app') 1971 WSGIApplication.request = _local('request') 1972 1973 1974Request.ResponseClass = Response 1975Response.RequestClass = Request 1976# Alias. 1977_abort = abort 1978# Thread-safety support. 1979_set_thread_safe_app() 1980 1981# Defer importing google.appengine.ext.webapp.util until every public symbol 1982# has been defined since google.appengine.ext.webapp in App Engine Python 2.7 1983# runtime imports this module to provide its public interface. 1984try: 1985 from google.appengine.ext.webapp import util as _webapp_util 1986except ImportError: # pragma: no cover 1987 pass 1988