| # -*- coding: utf-8 -*- |
| """ |
| webapp2 |
| ======= |
| |
| Taking Google App Engine's webapp to the next level! |
| |
| :copyright: 2011 by tipfy.org. |
| :license: Apache Sotware License, see LICENSE for details. |
| """ |
| from __future__ import with_statement |
| |
| import cgi |
| import inspect |
| import logging |
| import os |
| import re |
| import sys |
| import threading |
| import traceback |
| import urllib |
| import urlparse |
| from wsgiref import handlers |
| |
| import webob |
| from webob import exc |
| |
| _webapp = _webapp_util = _local = None |
| |
| try: # pragma: no cover |
| # WebOb < 1.0 (App Engine Python 2.5). |
| from webob.statusreasons import status_reasons |
| from webob.headerdict import HeaderDict as BaseResponseHeaders |
| except ImportError: # pragma: no cover |
| # WebOb >= 1.0. |
| from webob.util import status_reasons |
| from webob.headers import ResponseHeaders as BaseResponseHeaders |
| |
| # google.appengine.ext.webapp imports webapp2 in the |
| # App Engine Python 2.7 runtime. |
| if os.environ.get('APPENGINE_RUNTIME') != 'python27': # pragma: no cover |
| try: |
| from google.appengine.ext import webapp as _webapp |
| except ImportError: # pragma: no cover |
| # Running webapp2 outside of GAE. |
| pass |
| |
| try: # pragma: no cover |
| # Thread-local variables container. |
| from webapp2_extras import local |
| _local = local.Local() |
| except ImportError: # pragma: no cover |
| logging.warning("webapp2_extras.local is not available " |
| "so webapp2 won't be thread-safe!") |
| |
| |
| __version_info__ = (2, 5, 1) |
| __version__ = '.'.join(str(n) for n in __version_info__) |
| |
| #: Base HTTP exception, set here as public interface. |
| HTTPException = exc.HTTPException |
| |
| #: Regex for route definitions. |
| _route_re = re.compile(r""" |
| \< # The exact character "<" |
| ([a-zA-Z_]\w*)? # The optional variable name |
| (?:\:([^\>]*))? # The optional :regex part |
| \> # The exact character ">" |
| """, re.VERBOSE) |
| #: Regex extract charset from environ. |
| _charset_re = re.compile(r';\s*charset=([^;]*)', re.I) |
| |
| #: To show exceptions in debug mode. |
| _debug_template = """<html> |
| <head> |
| <title>Internal Server Error</title> |
| <style> |
| body { |
| padding: 20px; |
| font-family: arial, sans-serif; |
| font-size: 14px; |
| } |
| pre { |
| background: #F2F2F2; |
| padding: 10px; |
| } |
| </style> |
| </head> |
| <body> |
| <h1>Internal Server Error</h1> |
| <p>The server has either erred or is incapable of performing |
| the requested operation.</p> |
| <pre>%s</pre> |
| </body> |
| </html>""" |
| |
| # Set same default messages from webapp plus missing ones. |
| _webapp_status_reasons = { |
| 203: 'Non-Authoritative Information', |
| 302: 'Moved Temporarily', |
| 306: 'Unused', |
| 408: 'Request Time-out', |
| 414: 'Request-URI Too Large', |
| 504: 'Gateway Time-out', |
| 505: 'HTTP Version not supported', |
| } |
| status_reasons.update(_webapp_status_reasons) |
| for code, message in _webapp_status_reasons.iteritems(): |
| cls = exc.status_map.get(code) |
| if cls: |
| cls.title = message |
| |
| |
| class Request(webob.Request): |
| """Abstraction for an HTTP request. |
| |
| Most extra methods and attributes are ported from webapp. Check the |
| `WebOb documentation <WebOb>`_ for the ones not listed here. |
| """ |
| |
| #: A reference to the active :class:`WSGIApplication` instance. |
| app = None |
| #: A reference to the active :class:`Response` instance. |
| response = None |
| #: A reference to the matched :class:`Route`. |
| route = None |
| #: The matched route positional arguments. |
| route_args = None |
| #: The matched route keyword arguments. |
| route_kwargs = None |
| #: A dictionary to register objects used during the request lifetime. |
| registry = None |
| # Attributes from webapp. |
| request_body_tempfile_limit = 0 |
| uri = property(lambda self: self.url) |
| query = property(lambda self: self.query_string) |
| |
| def __init__(self, environ, *args, **kwargs): |
| """Constructs a Request object from a WSGI environment. |
| |
| :param environ: |
| A WSGI-compliant environment dictionary. |
| """ |
| if kwargs.get('charset') is None and not hasattr(webob, '__version__'): |
| # webob 0.9 didn't have a __version__ attribute and also defaulted |
| # to None rather than UTF-8 if no charset was provided. Providing a |
| # default charset is required for backwards compatibility. |
| match = _charset_re.search(environ.get('CONTENT_TYPE', '')) |
| if match: |
| charset = match.group(1).lower().strip().strip('"').strip() |
| else: |
| charset = 'utf-8' |
| kwargs['charset'] = charset |
| |
| super(Request, self).__init__(environ, *args, **kwargs) |
| self.registry = {} |
| |
| def get(self, argument_name, default_value='', allow_multiple=False): |
| """Returns the query or POST argument with the given name. |
| |
| We parse the query string and POST payload lazily, so this will be a |
| slower operation on the first call. |
| |
| :param argument_name: |
| The name of the query or POST argument. |
| :param default_value: |
| The value to return if the given argument is not present. |
| :param allow_multiple: |
| Return a list of values with the given name (deprecated). |
| :returns: |
| If allow_multiple is False (which it is by default), we return |
| the first value with the given name given in the request. If it |
| is True, we always return a list. |
| """ |
| param_value = self.get_all(argument_name) |
| if allow_multiple: |
| logging.warning('allow_multiple is a deprecated param. ' |
| 'Please use the Request.get_all() method instead.') |
| |
| if len(param_value) > 0: |
| if allow_multiple: |
| return param_value |
| |
| return param_value[0] |
| else: |
| if allow_multiple and not default_value: |
| return [] |
| |
| return default_value |
| |
| def get_all(self, argument_name, default_value=None): |
| """Returns a list of query or POST arguments with the given name. |
| |
| We parse the query string and POST payload lazily, so this will be a |
| slower operation on the first call. |
| |
| :param argument_name: |
| The name of the query or POST argument. |
| :param default_value: |
| The value to return if the given argument is not present, |
| None may not be used as a default, if it is then an empty |
| list will be returned instead. |
| :returns: |
| A (possibly empty) list of values. |
| """ |
| if self.charset: |
| argument_name = argument_name.encode(self.charset) |
| |
| if default_value is None: |
| default_value = [] |
| |
| param_value = self.params.getall(argument_name) |
| |
| if param_value is None or len(param_value) == 0: |
| return default_value |
| |
| for i in xrange(len(param_value)): |
| if isinstance(param_value[i], cgi.FieldStorage): |
| param_value[i] = param_value[i].value |
| |
| return param_value |
| |
| def arguments(self): |
| """Returns a list of the arguments provided in the query and/or POST. |
| |
| The return value is a list of strings. |
| """ |
| return list(set(self.params.keys())) |
| |
| def get_range(self, name, min_value=None, max_value=None, default=0): |
| """Parses the given int argument, limiting it to the given range. |
| |
| :param name: |
| The name of the argument. |
| :param min_value: |
| The minimum int value of the argument (if any). |
| :param max_value: |
| The maximum int value of the argument (if any). |
| :param default: |
| The default value of the argument if it is not given. |
| :returns: |
| An int within the given range for the argument. |
| """ |
| value = self.get(name, default) |
| if value is None: |
| return value |
| |
| try: |
| value = int(value) |
| except ValueError: |
| value = default |
| if value is not None: |
| if max_value is not None: |
| value = min(value, max_value) |
| |
| if min_value is not None: |
| value = max(value, min_value) |
| |
| return value |
| |
| @classmethod |
| def blank(cls, path, environ=None, base_url=None, |
| headers=None, **kwargs): # pragma: no cover |
| """Adds parameters compatible with WebOb >= 1.0: POST and **kwargs.""" |
| try: |
| return super(Request, cls).blank(path, environ=environ, |
| base_url=base_url, |
| headers=headers, **kwargs) |
| except TypeError: |
| if not kwargs: |
| raise |
| |
| data = kwargs.pop('POST', None) |
| if data is not None: |
| from cStringIO import StringIO |
| environ = environ or {} |
| environ['REQUEST_METHOD'] = 'POST' |
| if hasattr(data, 'items'): |
| data = data.items() |
| if not isinstance(data, str): |
| data = urllib.urlencode(data) |
| environ['wsgi.input'] = StringIO(data) |
| environ['webob.is_body_seekable'] = True |
| environ['CONTENT_LENGTH'] = str(len(data)) |
| environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' |
| |
| base = super(Request, cls).blank(path, environ=environ, |
| base_url=base_url, headers=headers) |
| if kwargs: |
| obj = cls(base.environ, **kwargs) |
| obj.headers.update(base.headers) |
| return obj |
| else: |
| return base |
| |
| |
| class ResponseHeaders(BaseResponseHeaders): |
| """Implements methods from ``wsgiref.headers.Headers``, used by webapp.""" |
| |
| get_all = BaseResponseHeaders.getall |
| |
| def add_header(self, _name, _value, **_params): |
| """Extended header setting. |
| |
| _name is the header field to add. keyword arguments can be used to set |
| additional parameters for the header field, with underscores converted |
| to dashes. Normally the parameter will be added as key="value" unless |
| value is None, in which case only the key will be added. |
| |
| Example:: |
| |
| h.add_header('content-disposition', 'attachment', |
| filename='bud.gif') |
| |
| Note that unlike the corresponding 'email.message' method, this does |
| *not* handle '(charset, language, value)' tuples: all values must be |
| strings or None. |
| """ |
| parts = [] |
| if _value is not None: |
| parts.append(_value) |
| |
| for k, v in _params.items(): |
| k = k.replace('_', '-') |
| if v is not None and len(v) > 0: |
| v = v.replace('\\', '\\\\').replace('"', r'\"') |
| parts.append('%s="%s"' % (k, v)) |
| else: |
| parts.append(k) |
| |
| self.add(_name, '; '.join(parts)) |
| |
| def __str__(self): |
| """Returns the formatted headers ready for HTTP transmission.""" |
| return '\r\n'.join(['%s: %s' % v for v in self.items()] + ['', '']) |
| |
| |
| class Response(webob.Response): |
| """Abstraction for an HTTP response. |
| |
| Most extra methods and attributes are ported from webapp. Check the |
| `WebOb documentation <WebOb>`_ for the ones not listed here. |
| |
| Differences from webapp.Response: |
| |
| - ``out`` is not a ``StringIO.StringIO`` instance. Instead it is the |
| response itself, as it has the method ``write()``. |
| - As in WebOb, ``status`` is the code plus message, e.g., '200 OK', while |
| in webapp it is the integer code. The status code as an integer is |
| available in ``status_int``, and the status message is available in |
| ``status_message``. |
| - ``response.headers`` raises an exception when a key that doesn't exist |
| is accessed or deleted, differently from ``wsgiref.headers.Headers``. |
| """ |
| |
| #: Default charset as in webapp. |
| default_charset = 'utf-8' |
| |
| def __init__(self, *args, **kwargs): |
| """Constructs a response with the default settings.""" |
| super(Response, self).__init__(*args, **kwargs) |
| self.headers['Cache-Control'] = 'no-cache' |
| |
| @property |
| def out(self): |
| """A reference to the Response instance itself, for compatibility with |
| webapp only: webapp uses `Response.out.write()`, so we point `out` to |
| `self` and it will use `Response.write()`. |
| """ |
| return self |
| |
| def write(self, text): |
| """Appends a text to the response body.""" |
| # webapp uses StringIO as Response.out, so we need to convert anything |
| # that is not str or unicode to string to keep same behavior. |
| if not isinstance(text, basestring): |
| text = unicode(text) |
| |
| if isinstance(text, unicode) and not self.charset: |
| self.charset = self.default_charset |
| |
| super(Response, self).write(text) |
| |
| def _set_status(self, value): |
| """The status string, including code and message.""" |
| message = None |
| # Accept long because urlfetch in App Engine returns codes as longs. |
| if isinstance(value, (int, long)): |
| code = int(value) |
| else: |
| if isinstance(value, unicode): |
| # Status messages have to be ASCII safe, so this is OK. |
| value = str(value) |
| |
| if not isinstance(value, str): |
| raise TypeError( |
| 'You must set status to a string or integer (not %s)' % |
| type(value)) |
| |
| parts = value.split(' ', 1) |
| code = int(parts[0]) |
| if len(parts) == 2: |
| message = parts[1] |
| |
| message = message or Response.http_status_message(code) |
| self._status = '%d %s' % (code, message) |
| |
| def _get_status(self): |
| return self._status |
| |
| status = property(_get_status, _set_status, doc=_set_status.__doc__) |
| |
| def set_status(self, code, message=None): |
| """Sets the HTTP status code of this response. |
| |
| :param code: |
| The HTTP status string to use |
| :param message: |
| A status string. If none is given, uses the default from the |
| HTTP/1.1 specification. |
| """ |
| if message: |
| self.status = '%d %s' % (code, message) |
| else: |
| self.status = code |
| |
| def _get_status_message(self): |
| """The response status message, as a string.""" |
| return self.status.split(' ', 1)[1] |
| |
| def _set_status_message(self, message): |
| self.status = '%d %s' % (self.status_int, message) |
| |
| status_message = property(_get_status_message, _set_status_message, |
| doc=_get_status_message.__doc__) |
| |
| def _get_headers(self): |
| """The headers as a dictionary-like object.""" |
| if self._headers is None: |
| self._headers = ResponseHeaders.view_list(self.headerlist) |
| |
| return self._headers |
| |
| def _set_headers(self, value): |
| if hasattr(value, 'items'): |
| value = value.items() |
| elif not isinstance(value, list): |
| raise TypeError('Response headers must be a list or dictionary.') |
| |
| self.headerlist = value |
| self._headers = None |
| |
| headers = property(_get_headers, _set_headers, doc=_get_headers.__doc__) |
| |
| def has_error(self): |
| """Indicates whether the response was an error response.""" |
| return self.status_int >= 400 |
| |
| def clear(self): |
| """Clears all data written to the output stream so that it is empty.""" |
| self.body = '' |
| |
| def wsgi_write(self, start_response): |
| """Writes this response using using the given WSGI function. |
| |
| This is only here for compatibility with ``webapp.WSGIApplication``. |
| |
| :param start_response: |
| The WSGI-compatible start_response function. |
| """ |
| if (self.headers.get('Cache-Control') == 'no-cache' and |
| not self.headers.get('Expires')): |
| self.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' |
| self.headers['Content-Length'] = str(len(self.body)) |
| |
| write = start_response(self.status, self.headerlist) |
| write(self.body) |
| |
| @staticmethod |
| def http_status_message(code): |
| """Returns the default HTTP status message for the given code. |
| |
| :param code: |
| The HTTP code for which we want a message. |
| """ |
| message = status_reasons.get(code) |
| if not message: |
| raise KeyError('Invalid HTTP status code: %d' % code) |
| |
| return message |
| |
| |
| class RequestHandler(object): |
| """Base HTTP request handler. |
| |
| Implements most of ``webapp.RequestHandler`` interface. |
| """ |
| |
| #: A :class:`Request` instance. |
| request = None |
| #: A :class:`Response` instance. |
| response = None |
| #: A :class:`WSGIApplication` instance. |
| app = None |
| |
| def __init__(self, request=None, response=None): |
| """Initializes this request handler with the given WSGI application, |
| Request and Response. |
| |
| When instantiated by ``webapp.WSGIApplication``, request and response |
| are not set on instantiation. Instead, initialize() is called right |
| after the handler is created to set them. |
| |
| Also in webapp dispatching is done by the WSGI app, while webapp2 |
| does it here to allow more flexibility in extended classes: handlers |
| can wrap :meth:`dispatch` to check for conditions before executing the |
| requested method and/or post-process the response. |
| |
| .. note:: |
| Parameters are optional only to support webapp's constructor which |
| doesn't take any arguments. Consider them as required. |
| |
| :param request: |
| A :class:`Request` instance. |
| :param response: |
| A :class:`Response` instance. |
| """ |
| self.initialize(request, response) |
| |
| def initialize(self, request, response): |
| """Initializes this request handler with the given WSGI application, |
| Request and Response. |
| |
| :param request: |
| A :class:`Request` instance. |
| :param response: |
| A :class:`Response` instance. |
| """ |
| self.request = request |
| self.response = response |
| self.app = WSGIApplication.active_instance |
| |
| def dispatch(self): |
| """Dispatches the request. |
| |
| This will first check if there's a handler_method defined in the |
| matched route, and if not it'll use the method correspondent to the |
| request method (``get()``, ``post()`` etc). |
| """ |
| request = self.request |
| method_name = request.route.handler_method |
| if not method_name: |
| method_name = _normalize_handler_method(request.method) |
| |
| method = getattr(self, method_name, None) |
| if method is None: |
| # 405 Method Not Allowed. |
| # The response MUST include an Allow header containing a |
| # list of valid methods for the requested resource. |
| # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6 |
| valid = ', '.join(_get_handler_methods(self)) |
| self.abort(405, headers=[('Allow', valid)]) |
| |
| # The handler only receives *args if no named variables are set. |
| args, kwargs = request.route_args, request.route_kwargs |
| if kwargs: |
| args = () |
| |
| try: |
| return method(*args, **kwargs) |
| except Exception, e: |
| return self.handle_exception(e, self.app.debug) |
| |
| def error(self, code): |
| """Clears the response and sets the given HTTP status code. |
| |
| This doesn't stop code execution; for this, use :meth:`abort`. |
| |
| :param code: |
| HTTP status error code (e.g., 501). |
| """ |
| self.response.status = code |
| self.response.clear() |
| |
| def abort(self, code, *args, **kwargs): |
| """Raises an :class:`HTTPException`. |
| |
| This stops code execution, leaving the HTTP exception to be handled |
| by an exception handler. |
| |
| :param code: |
| HTTP status code (e.g., 404). |
| :param args: |
| Positional arguments to be passed to the exception class. |
| :param kwargs: |
| Keyword arguments to be passed to the exception class. |
| """ |
| abort(code, *args, **kwargs) |
| |
| def redirect(self, uri, permanent=False, abort=False, code=None, |
| body=None): |
| """Issues an HTTP redirect to the given relative URI. |
| |
| The arguments are described in :func:`redirect`. |
| """ |
| return redirect(uri, permanent=permanent, abort=abort, code=code, |
| body=body, request=self.request, |
| response=self.response) |
| |
| def redirect_to(self, _name, _permanent=False, _abort=False, _code=None, |
| _body=None, *args, **kwargs): |
| """Convenience method mixing :meth:`redirect` and :meth:`uri_for`. |
| |
| The arguments are described in :func:`redirect` and :func:`uri_for`. |
| """ |
| uri = self.uri_for(_name, *args, **kwargs) |
| return self.redirect(uri, permanent=_permanent, abort=_abort, |
| code=_code, body=_body) |
| |
| def uri_for(self, _name, *args, **kwargs): |
| """Returns a URI for a named :class:`Route`. |
| |
| .. seealso:: :meth:`Router.build`. |
| """ |
| return self.app.router.build(self.request, _name, args, kwargs) |
| # Alias. |
| url_for = uri_for |
| |
| def handle_exception(self, exception, debug): |
| """Called if this handler throws an exception during execution. |
| |
| The default behavior is to re-raise the exception to be handled by |
| :meth:`WSGIApplication.handle_exception`. |
| |
| :param exception: |
| The exception that was thrown. |
| :param debug_mode: |
| True if the web application is running in debug mode. |
| """ |
| raise |
| |
| |
| class RedirectHandler(RequestHandler): |
| """Redirects to the given URI for all GET requests. |
| |
| This is intended to be used when defining URI routes. You must provide at |
| least the keyword argument *url* in the route default values. Example:: |
| |
| def get_redirect_url(handler, *args, **kwargs): |
| return handler.uri_for('new-route-name') |
| |
| app = WSGIApplication([ |
| Route('/old-url', RedirectHandler, defaults={'_uri': '/new-url'}), |
| Route('/other-old-url', RedirectHandler, defaults={ |
| '_uri': get_redirect_url}), |
| ]) |
| |
| Based on idea from `Tornado`_. |
| """ |
| |
| def get(self, *args, **kwargs): |
| """Performs a redirect. |
| |
| Two keyword arguments can be passed through the URI route: |
| |
| - **_uri**: A URI string or a callable that returns a URI. The callable |
| is called passing ``(handler, *args, **kwargs)`` as arguments. |
| - **_code**: The redirect status code. Default is 301 (permanent |
| redirect). |
| """ |
| uri = kwargs.pop('_uri', '/') |
| permanent = kwargs.pop('_permanent', True) |
| code = kwargs.pop('_code', None) |
| |
| func = getattr(uri, '__call__', None) |
| if func: |
| uri = func(self, *args, **kwargs) |
| |
| self.redirect(uri, permanent=permanent, code=code) |
| |
| |
| class cached_property(object): |
| """A decorator that converts a function into a lazy property. |
| |
| The function wrapped is called the first time to retrieve the result |
| and then that calculated result is used the next time you access |
| the value:: |
| |
| class Foo(object): |
| |
| @cached_property |
| def foo(self): |
| # calculate something important here |
| return 42 |
| |
| The class has to have a `__dict__` in order for this property to |
| work. |
| |
| .. note:: Implementation detail: this property is implemented as non-data |
| descriptor. non-data descriptors are only invoked if there is |
| no entry with the same name in the instance's __dict__. |
| this allows us to completely get rid of the access function call |
| overhead. If one choses to invoke __get__ by hand the property |
| will still work as expected because the lookup logic is replicated |
| in __get__ for manual invocation. |
| |
| This class was ported from `Werkzeug`_ and `Flask`_. |
| """ |
| |
| _default_value = object() |
| |
| def __init__(self, func, name=None, doc=None): |
| self.__name__ = name or func.__name__ |
| self.__module__ = func.__module__ |
| self.__doc__ = doc or func.__doc__ |
| self.func = func |
| self.lock = threading.RLock() |
| |
| def __get__(self, obj, type=None): |
| if obj is None: |
| return self |
| |
| with self.lock: |
| value = obj.__dict__.get(self.__name__, self._default_value) |
| if value is self._default_value: |
| value = self.func(obj) |
| obj.__dict__[self.__name__] = value |
| |
| return value |
| |
| |
| class BaseRoute(object): |
| """Interface for URI routes.""" |
| |
| #: The regex template. |
| template = None |
| #: Route name, used to build URIs. |
| name = None |
| #: True if this route is only used for URI generation and never matches. |
| build_only = False |
| #: The handler or string in dotted notation to be lazily imported. |
| handler = None |
| #: The custom handler method, if handler is a class. |
| handler_method = None |
| #: The handler, imported and ready for dispatching. |
| handler_adapter = None |
| |
| def __init__(self, template, handler=None, name=None, build_only=False): |
| """Initializes this route. |
| |
| :param template: |
| A regex to be matched. |
| :param handler: |
| A callable or string in dotted notation to be lazily imported, |
| e.g., ``'my.module.MyHandler'`` or ``'my.module.my_function'``. |
| :param name: |
| The name of this route, used to build URIs based on it. |
| :param build_only: |
| If True, this route never matches and is used only to build URIs. |
| """ |
| if build_only and name is None: |
| raise ValueError( |
| "Route %r is build_only but doesn't have a name." % self) |
| |
| self.template = template |
| self.handler = handler |
| self.name = name |
| self.build_only = build_only |
| |
| def match(self, request): |
| """Matches all routes against a request object. |
| |
| The first one that matches is returned. |
| |
| :param request: |
| A :class:`Request` instance. |
| :returns: |
| A tuple ``(route, args, kwargs)`` if a route matched, or None. |
| """ |
| raise NotImplementedError() |
| |
| def build(self, request, args, kwargs): |
| """Returns a URI for this route. |
| |
| :param request: |
| The current :class:`Request` object. |
| :param args: |
| Tuple of positional arguments to build the URI. |
| :param kwargs: |
| Dictionary of keyword arguments to build the URI. |
| :returns: |
| An absolute or relative URI. |
| """ |
| raise NotImplementedError() |
| |
| def get_routes(self): |
| """Generator to get all routes from a route. |
| |
| :yields: |
| This route or all nested routes that it contains. |
| """ |
| yield self |
| |
| def get_match_routes(self): |
| """Generator to get all routes that can be matched from a route. |
| |
| Match routes must implement :meth:`match`. |
| |
| :yields: |
| This route or all nested routes that can be matched. |
| """ |
| if not self.build_only: |
| yield self |
| |
| def get_build_routes(self): |
| """Generator to get all routes that can be built from a route. |
| |
| Build routes must implement :meth:`build`. |
| |
| :yields: |
| A tuple ``(name, route)`` for all nested routes that can be built. |
| """ |
| if self.name is not None: |
| yield self.name, self |
| |
| |
| class SimpleRoute(BaseRoute): |
| """A route that is compatible with webapp's routing mechanism. |
| |
| URI building is not implemented as webapp has rudimentar support for it, |
| and this is the most unknown webapp feature anyway. |
| """ |
| |
| @cached_property |
| def regex(self): |
| """Lazy regex compiler.""" |
| if not self.template.startswith('^'): |
| self.template = '^' + self.template |
| |
| if not self.template.endswith('$'): |
| self.template += '$' |
| |
| return re.compile(self.template) |
| |
| def match(self, request): |
| """Matches this route against the current request. |
| |
| .. seealso:: :meth:`BaseRoute.match`. |
| """ |
| match = self.regex.match(urllib.unquote(request.path)) |
| if match: |
| return self, match.groups(), {} |
| |
| def __repr__(self): |
| return '<SimpleRoute(%r, %r)>' % (self.template, self.handler) |
| |
| |
| class Route(BaseRoute): |
| """A route definition that maps a URI path to a handler. |
| |
| The initial concept was based on `Another Do-It-Yourself Framework`_, by |
| Ian Bicking. |
| """ |
| |
| #: Default parameters values. |
| defaults = None |
| #: Sequence of allowed HTTP methods. If not set, all methods are allowed. |
| methods = None |
| #: Sequence of allowed URI schemes. If not set, all schemes are allowed. |
| schemes = None |
| # Lazy properties extracted from the route template. |
| regex = None |
| reverse_template = None |
| variables = None |
| args_count = 0 |
| kwargs_count = 0 |
| |
| def __init__(self, template, handler=None, name=None, defaults=None, |
| build_only=False, handler_method=None, methods=None, |
| schemes=None): |
| """Initializes this route. |
| |
| :param template: |
| A route template to match against the request path. A template |
| can have variables enclosed by ``<>`` that define a name, a |
| regular expression or both. Examples: |
| |
| ================= ================================== |
| Format Example |
| ================= ================================== |
| ``<name>`` ``'/blog/<year>/<month>'`` |
| ``<:regex>`` ``'/blog/<:\d{4}>/<:\d{2}>'`` |
| ``<name:regex>`` ``'/blog/<year:\d{4}>/<month:\d{2}>'`` |
| ================= ================================== |
| |
| The same template can mix parts with name, regular expression or |
| both. |
| |
| If the name is set, the value of the matched regular expression |
| is passed as keyword argument to the handler. Otherwise it is |
| passed as positional argument. |
| |
| If only the name is set, it will match anything except a slash. |
| So these routes are equivalent:: |
| |
| Route('/<user_id>/settings', handler=SettingsHandler, |
| name='user-settings') |
| Route('/<user_id:[^/]+>/settings', handler=SettingsHandler, |
| name='user-settings') |
| |
| .. note:: |
| The handler only receives ``*args`` if no named variables are |
| set. Otherwise, the handler only receives ``**kwargs``. This |
| allows you to set regular expressions that are not captured: |
| just mix named and unnamed variables and the handler will |
| only receive the named ones. |
| |
| :param handler: |
| A callable or string in dotted notation to be lazily imported, |
| e.g., ``'my.module.MyHandler'`` or ``'my.module.my_function'``. |
| It is possible to define a method if the callable is a class, |
| separating it by a colon: ``'my.module.MyHandler:my_method'``. |
| This is a shortcut and has the same effect as defining the |
| `handler_method` parameter. |
| :param name: |
| The name of this route, used to build URIs based on it. |
| :param defaults: |
| Default or extra keywords to be returned by this route. Values |
| also present in the route variables are used to build the URI |
| when they are missing. |
| :param build_only: |
| If True, this route never matches and is used only to build URIs. |
| :param handler_method: |
| The name of a custom handler method to be called, in case `handler` |
| is a class. If not defined, the default behavior is to call the |
| handler method correspondent to the HTTP request method in lower |
| case (e.g., `get()`, `post()` etc). |
| :param methods: |
| A sequence of HTTP methods. If set, the route will only match if |
| the request method is allowed. |
| :param schemes: |
| A sequence of URI schemes, e.g., ``['http']`` or ``['https']``. |
| If set, the route will only match requests with these schemes. |
| """ |
| super(Route, self).__init__(template, handler=handler, name=name, |
| build_only=build_only) |
| self.defaults = defaults or {} |
| self.methods = methods |
| self.schemes = schemes |
| if isinstance(handler, basestring) and ':' in handler: |
| if handler_method: |
| raise ValueError( |
| "If handler_method is defined in a Route, handler " |
| "can't have a colon (got %r)." % handler) |
| else: |
| self.handler, self.handler_method = handler.rsplit(':', 1) |
| else: |
| self.handler_method = handler_method |
| |
| @cached_property |
| def regex(self): |
| """Lazy route template parser.""" |
| regex, self.reverse_template, self.args_count, self.kwargs_count, \ |
| self.variables = _parse_route_template(self.template, |
| default_sufix='[^/]+') |
| return regex |
| |
| def match(self, request): |
| """Matches this route against the current request. |
| |
| :raises: |
| ``exc.HTTPMethodNotAllowed`` if the route defines :attr:`methods` |
| and the request method isn't allowed. |
| |
| .. seealso:: :meth:`BaseRoute.match`. |
| """ |
| match = self.regex.match(urllib.unquote(request.path)) |
| if not match or self.schemes and request.scheme not in self.schemes: |
| return None |
| |
| if self.methods and request.method not in self.methods: |
| # This will be caught by the router, so routes with different |
| # methods can be tried. |
| raise exc.HTTPMethodNotAllowed() |
| |
| args, kwargs = _get_route_variables(match, self.defaults.copy()) |
| return self, args, kwargs |
| |
| def build(self, request, args, kwargs): |
| """Returns a URI for this route. |
| |
| .. seealso:: :meth:`Router.build`. |
| """ |
| scheme = kwargs.pop('_scheme', None) |
| netloc = kwargs.pop('_netloc', None) |
| anchor = kwargs.pop('_fragment', None) |
| full = kwargs.pop('_full', False) and not scheme and not netloc |
| |
| if full or scheme or netloc: |
| netloc = netloc or request.host |
| scheme = scheme or request.scheme |
| |
| path, query = self._build(args, kwargs) |
| return _urlunsplit(scheme, netloc, path, query, anchor) |
| |
| def _build(self, args, kwargs): |
| """Returns the URI path for this route. |
| |
| :returns: |
| A tuple ``(path, kwargs)`` with the built URI path and extra |
| keywords to be used as URI query arguments. |
| """ |
| # Access self.regex just to set the lazy properties. |
| regex = self.regex |
| variables = self.variables |
| if self.args_count: |
| for index, value in enumerate(args): |
| key = '__%d__' % index |
| if key in variables: |
| kwargs[key] = value |
| |
| values = {} |
| for name, regex in variables.iteritems(): |
| value = kwargs.pop(name, self.defaults.get(name)) |
| if value is None: |
| raise KeyError('Missing argument "%s" to build URI.' % \ |
| name.strip('_')) |
| |
| if not isinstance(value, basestring): |
| value = str(value) |
| |
| if not regex.match(value): |
| raise ValueError('URI buiding error: Value "%s" is not ' |
| 'supported for argument "%s".' % (value, name.strip('_'))) |
| |
| values[name] = value |
| |
| return (self.reverse_template % values, kwargs) |
| |
| def __repr__(self): |
| return '<Route(%r, %r, name=%r, defaults=%r, build_only=%r)>' % \ |
| (self.template, self.handler, self.name, self.defaults, |
| self.build_only) |
| |
| |
| class BaseHandlerAdapter(object): |
| """A basic adapter to dispatch a handler. |
| |
| This is used when the handler is a simple function: it just calls the |
| handler and returns the resulted response. |
| """ |
| |
| #: The handler to be dispatched. |
| handler = None |
| |
| def __init__(self, handler): |
| self.handler = handler |
| |
| def __call__(self, request, response): |
| # The handler only receives *args if no named variables are set. |
| args, kwargs = request.route_args, request.route_kwargs |
| if kwargs: |
| args = () |
| |
| return self.handler(request, *args, **kwargs) |
| |
| |
| class WebappHandlerAdapter(BaseHandlerAdapter): |
| """An adapter to dispatch a ``webapp.RequestHandler``. |
| |
| Like in webapp, the handler is constructed, then ``initialize()`` is |
| called, then the method corresponding to the HTTP request method is called. |
| """ |
| |
| def __call__(self, request, response): |
| handler = self.handler() |
| handler.initialize(request, response) |
| method_name = _normalize_handler_method(request.method) |
| method = getattr(handler, method_name, None) |
| if not method: |
| abort(501) |
| |
| # The handler only receives *args if no named variables are set. |
| args, kwargs = request.route_args, request.route_kwargs |
| if kwargs: |
| args = () |
| |
| try: |
| method(*args, **kwargs) |
| except Exception, e: |
| handler.handle_exception(e, request.app.debug) |
| |
| |
| class Webapp2HandlerAdapter(BaseHandlerAdapter): |
| """An adapter to dispatch a ``webapp2.RequestHandler``. |
| |
| The handler is constructed then ``dispatch()`` is called. |
| """ |
| |
| def __call__(self, request, response): |
| handler = self.handler(request, response) |
| return handler.dispatch() |
| |
| |
| class Router(object): |
| """A URI router used to match, dispatch and build URIs.""" |
| |
| #: Class used when the route is set as a tuple. |
| route_class = SimpleRoute |
| #: All routes that can be matched. |
| match_routes = None |
| #: All routes that can be built. |
| build_routes = None |
| #: Handler classes imported lazily. |
| handlers = None |
| |
| def __init__(self, routes=None): |
| """Initializes the router. |
| |
| :param routes: |
| A sequence of :class:`Route` instances or, for simple routes, |
| tuples ``(regex, handler)``. |
| """ |
| self.match_routes = [] |
| self.build_routes = {} |
| self.handlers = {} |
| if routes: |
| for route in routes: |
| self.add(route) |
| |
| def add(self, route): |
| """Adds a route to this router. |
| |
| :param route: |
| A :class:`Route` instance or, for simple routes, a tuple |
| ``(regex, handler)``. |
| """ |
| if isinstance(route, tuple): |
| # Exceptional case: simple routes defined as a tuple. |
| route = self.route_class(*route) |
| |
| for r in route.get_match_routes(): |
| self.match_routes.append(r) |
| |
| for name, r in route.get_build_routes(): |
| self.build_routes[name] = r |
| |
| def set_matcher(self, func): |
| """Sets the function called to match URIs. |
| |
| :param func: |
| A function that receives ``(router, request)`` and returns |
| a tuple ``(route, args, kwargs)`` if any route matches, or |
| raise ``exc.HTTPNotFound`` if no route matched or |
| ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP |
| method was not allowed. |
| """ |
| # Functions are descriptors, so bind it to this instance with __get__. |
| self.match = func.__get__(self, self.__class__) |
| |
| def set_builder(self, func): |
| """Sets the function called to build URIs. |
| |
| :param func: |
| A function that receives ``(router, request, name, args, kwargs)`` |
| and returns a URI. |
| """ |
| self.build = func.__get__(self, self.__class__) |
| |
| def set_dispatcher(self, func): |
| """Sets the function called to dispatch the handler. |
| |
| :param func: |
| A function that receives ``(router, request, response)`` |
| and returns the value returned by the dispatched handler. |
| """ |
| self.dispatch = func.__get__(self, self.__class__) |
| |
| def set_adapter(self, func): |
| """Sets the function that adapts loaded handlers for dispatching. |
| |
| :param func: |
| A function that receives ``(router, handler)`` and returns a |
| handler callable. |
| """ |
| self.adapt = func.__get__(self, self.__class__) |
| |
| def default_matcher(self, request): |
| """Matches all routes against a request object. |
| |
| The first one that matches is returned. |
| |
| :param request: |
| A :class:`Request` instance. |
| :returns: |
| A tuple ``(route, args, kwargs)`` if a route matched, or None. |
| :raises: |
| ``exc.HTTPNotFound`` if no route matched or |
| ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP |
| method was not allowed. |
| """ |
| method_not_allowed = False |
| for route in self.match_routes: |
| try: |
| match = route.match(request) |
| if match: |
| return match |
| except exc.HTTPMethodNotAllowed: |
| method_not_allowed = True |
| |
| if method_not_allowed: |
| raise exc.HTTPMethodNotAllowed() |
| |
| raise exc.HTTPNotFound() |
| |
| def default_builder(self, request, name, args, kwargs): |
| """Returns a URI for a named :class:`Route`. |
| |
| :param request: |
| The current :class:`Request` object. |
| :param name: |
| The route name. |
| :param args: |
| Tuple of positional arguments to build the URI. All positional |
| variables defined in the route must be passed and must conform |
| to the format set in the route. Extra arguments are ignored. |
| :param kwargs: |
| Dictionary of keyword arguments to build the URI. All variables |
| not set in the route default values must be passed and must |
| conform to the format set in the route. Extra keywords are |
| appended as a query string. |
| |
| A few keywords have special meaning: |
| |
| - **_full**: If True, builds an absolute URI. |
| - **_scheme**: URI scheme, e.g., `http` or `https`. If defined, |
| an absolute URI is always returned. |
| - **_netloc**: Network location, e.g., `www.google.com`. If |
| defined, an absolute URI is always returned. |
| - **_fragment**: If set, appends a fragment (or "anchor") to the |
| generated URI. |
| :returns: |
| An absolute or relative URI. |
| """ |
| route = self.build_routes.get(name) |
| if route is None: |
| raise KeyError('Route named %r is not defined.' % name) |
| |
| return route.build(request, args, kwargs) |
| |
| def default_dispatcher(self, request, response): |
| """Dispatches a handler. |
| |
| :param request: |
| A :class:`Request` instance. |
| :param response: |
| A :class:`Response` instance. |
| :raises: |
| ``exc.HTTPNotFound`` if no route matched or |
| ``exc.HTTPMethodNotAllowed`` if a route matched but the HTTP |
| method was not allowed. |
| :returns: |
| The returned value from the handler. |
| """ |
| route, args, kwargs = rv = self.match(request) |
| request.route, request.route_args, request.route_kwargs = rv |
| |
| if route.handler_adapter is None: |
| handler = route.handler |
| if isinstance(handler, basestring): |
| if handler not in self.handlers: |
| self.handlers[handler] = handler = import_string(handler) |
| else: |
| handler = self.handlers[handler] |
| |
| route.handler_adapter = self.adapt(handler) |
| |
| return route.handler_adapter(request, response) |
| |
| def default_adapter(self, handler): |
| """Adapts a handler for dispatching. |
| |
| Because handlers use or implement different dispatching mechanisms, |
| they can be wrapped to use a unified API for dispatching. |
| This way webapp2 can support, for example, a :class:`RequestHandler` |
| class and function views or, for compatibility purposes, a |
| ``webapp.RequestHandler`` class. The adapters follow the same router |
| dispatching API but dispatch each handler type differently. |
| |
| :param handler: |
| A handler callable. |
| :returns: |
| A wrapped handler callable. |
| """ |
| if inspect.isclass(handler): |
| if _webapp and issubclass(handler, _webapp.RequestHandler): |
| # Compatible with webapp.RequestHandler. |
| adapter = WebappHandlerAdapter |
| else: |
| # Default, compatible with webapp2.RequestHandler. |
| adapter = Webapp2HandlerAdapter |
| else: |
| # A "view" function. |
| adapter = BaseHandlerAdapter |
| |
| return adapter(handler) |
| |
| def __repr__(self): |
| routes = self.match_routes + [v for k, v in \ |
| self.build_routes.iteritems() if v not in self.match_routes] |
| |
| return '<Router(%r)>' % routes |
| |
| # Default matcher, builder, dispatcher and adapter. |
| match = default_matcher |
| build = default_builder |
| dispatch = default_dispatcher |
| adapt = default_adapter |
| |
| |
| class Config(dict): |
| """A simple configuration dictionary for the :class:`WSGIApplication`.""" |
| |
| #: Loaded configurations. |
| loaded = None |
| |
| def __init__(self, defaults=None): |
| dict.__init__(self, defaults or ()) |
| self.loaded = [] |
| |
| def load_config(self, key, default_values=None, user_values=None, |
| required_keys=None): |
| """Returns a configuration for a given key. |
| |
| This can be used by objects that define a default configuration. It |
| will update the app configuration with the default values the first |
| time it is requested, and mark the key as loaded. |
| |
| :param key: |
| A configuration key. |
| :param default_values: |
| Default values defined by a module or class. |
| :param user_values: |
| User values, used when an object can be initialized with |
| configuration. This overrides the app configuration. |
| :param required_keys: |
| Keys that can not be None. |
| :raises: |
| Exception, when a required key is not set or is None. |
| """ |
| if key in self.loaded: |
| config = self[key] |
| else: |
| config = dict(default_values or ()) |
| if key in self: |
| config.update(self[key]) |
| |
| self[key] = config |
| self.loaded.append(key) |
| if required_keys and not user_values: |
| self._validate_required(key, config, required_keys) |
| |
| if user_values: |
| config = config.copy() |
| config.update(user_values) |
| if required_keys: |
| self._validate_required(key, config, required_keys) |
| |
| return config |
| |
| def _validate_required(self, key, config, required_keys): |
| missing = [k for k in required_keys if config.get(k) is None] |
| if missing: |
| raise Exception( |
| 'Missing configuration keys for %r: %r.' % (key, missing)) |
| |
| |
| class RequestContext(object): |
| """Context for a single request. |
| |
| The context is responsible for setting and cleaning global variables for |
| a request. |
| """ |
| |
| #: A :class:`WSGIApplication` instance. |
| app = None |
| #: WSGI environment dictionary. |
| environ = None |
| |
| def __init__(self, app, environ): |
| """Initializes the request context. |
| |
| :param app: |
| An :class:`WSGIApplication` instance. |
| :param environ: |
| A WSGI environment dictionary. |
| """ |
| self.app = app |
| self.environ = environ |
| |
| def __enter__(self): |
| """Enters the request context. |
| |
| :returns: |
| A tuple ``(request, response)``. |
| """ |
| # Build request and response. |
| request = self.app.request_class(self.environ) |
| response = self.app.response_class() |
| # Make active app and response available through the request object. |
| request.app = self.app |
| request.response = response |
| # Register global variables. |
| self.app.set_globals(app=self.app, request=request) |
| return request, response |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| """Exits the request context. |
| |
| This release the context locals except if an exception is caught |
| in debug mode. In this case they are kept to be inspected. |
| """ |
| if exc_type is None or not self.app.debug: |
| # Unregister global variables. |
| self.app.clear_globals() |
| |
| |
| class WSGIApplication(object): |
| """A WSGI-compliant application.""" |
| |
| #: Allowed request methods. |
| allowed_methods = frozenset(('GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', |
| 'DELETE', 'TRACE')) |
| #: Class used for the request object. |
| request_class = Request |
| #: Class used for the response object. |
| response_class = Response |
| #: Class used for the router object. |
| router_class = Router |
| #: Class used for the request context object. |
| request_context_class = RequestContext |
| #: Class used for the configuration object. |
| config_class = Config |
| #: A general purpose flag to indicate development mode: if True, uncaught |
| #: exceptions are raised instead of using ``HTTPInternalServerError``. |
| debug = False |
| #: A :class:`Router` instance with all URIs registered for the application. |
| router = None |
| #: A :class:`Config` instance with the application configuration. |
| config = None |
| #: A dictionary to register objects used during the app lifetime. |
| registry = None |
| #: A dictionary mapping HTTP error codes to callables to handle those |
| #: HTTP exceptions. See :meth:`handle_exception`. |
| error_handlers = None |
| #: Active :class:`WSGIApplication` instance. See :meth:`set_globals`. |
| app = None |
| #: Active :class:`Request` instance. See :meth:`set_globals`. |
| request = None |
| #: Same as :attr:`app`, for webapp compatibility. See :meth:`set_globals`. |
| active_instance = None |
| |
| def __init__(self, routes=None, debug=False, config=None): |
| """Initializes the WSGI application. |
| |
| :param routes: |
| A sequence of :class:`Route` instances or, for simple routes, |
| tuples ``(regex, handler)``. |
| :param debug: |
| True to enable debug mode, False otherwise. |
| :param config: |
| A configuration dictionary for the application. |
| """ |
| self.debug = debug |
| self.registry = {} |
| self.error_handlers = {} |
| self.set_globals(app=self) |
| self.config = self.config_class(config) |
| self.router = self.router_class(routes) |
| |
| def set_globals(self, app=None, request=None): |
| """Registers the global variables for app and request. |
| |
| If :mod:`webapp2_extras.local` is available the app and request |
| class attributes are assigned to a proxy object that returns them |
| using thread-local, making the application thread-safe. This can also |
| be used in environments that don't support threading. |
| |
| If :mod:`webapp2_extras.local` is not available app and request will |
| be assigned directly as class attributes. This should only be used in |
| non-threaded environments (e.g., App Engine Python 2.5). |
| |
| :param app: |
| A :class:`WSGIApplication` instance. |
| :param request: |
| A :class:`Request` instance. |
| """ |
| if _local is not None: # pragma: no cover |
| _local.app = app |
| _local.request = request |
| else: # pragma: no cover |
| WSGIApplication.app = WSGIApplication.active_instance = app |
| WSGIApplication.request = request |
| |
| def clear_globals(self): |
| """Clears global variables. See :meth:`set_globals`.""" |
| if _local is not None: # pragma: no cover |
| _local.__release_local__() |
| else: # pragma: no cover |
| WSGIApplication.app = WSGIApplication.active_instance = None |
| WSGIApplication.request = None |
| |
| def __call__(self, environ, start_response): |
| """Called by WSGI when a request comes in. |
| |
| :param environ: |
| A WSGI environment. |
| :param start_response: |
| A callable accepting a status code, a list of headers and an |
| optional exception context to start the response. |
| :returns: |
| An iterable with the response to return to the client. |
| """ |
| with self.request_context_class(self, environ) as (request, response): |
| try: |
| if request.method not in self.allowed_methods: |
| # 501 Not Implemented. |
| raise exc.HTTPNotImplemented() |
| |
| rv = self.router.dispatch(request, response) |
| if rv is not None: |
| response = rv |
| except Exception, e: |
| try: |
| # Try to handle it with a custom error handler. |
| rv = self.handle_exception(request, response, e) |
| if rv is not None: |
| response = rv |
| except HTTPException, e: |
| # Use the HTTP exception as response. |
| response = e |
| except Exception, e: |
| # Error wasn't handled so we have nothing else to do. |
| response = self._internal_error(e) |
| |
| try: |
| return response(environ, start_response) |
| except Exception, e: |
| return self._internal_error(e)(environ, start_response) |
| |
| def _internal_error(self, exception): |
| """Last resource error for :meth:`__call__`.""" |
| logging.exception(exception) |
| if self.debug: |
| lines = ''.join(traceback.format_exception(*sys.exc_info())) |
| html = _debug_template % (cgi.escape(lines, quote=True)) |
| return Response(body=html, status=500) |
| |
| return exc.HTTPInternalServerError() |
| |
| def handle_exception(self, request, response, e): |
| """Handles a uncaught exception occurred in :meth:`__call__`. |
| |
| Uncaught exceptions can be handled by error handlers registered in |
| :attr:`error_handlers`. This is a dictionary that maps HTTP status |
| codes to callables that will handle the corresponding error code. |
| If the exception is not an ``HTTPException``, the status code 500 |
| is used. |
| |
| The error handlers receive (request, response, exception) and can be |
| a callable or a string in dotted notation to be lazily imported. |
| |
| If no error handler is found, the exception is re-raised. |
| |
| Based on idea from `Flask`_. |
| |
| :param request: |
| A :class:`Request` instance. |
| :param response: |
| A :class:`Response` instance. |
| :param e: |
| The uncaught exception. |
| :returns: |
| The returned value from the error handler. |
| """ |
| if isinstance(e, HTTPException): |
| code = e.code |
| else: |
| code = 500 |
| |
| handler = self.error_handlers.get(code) |
| if handler: |
| if isinstance(handler, basestring): |
| self.error_handlers[code] = handler = import_string(handler) |
| |
| return handler(request, response, e) |
| else: |
| # Re-raise it to be caught by the WSGI app. |
| raise |
| |
| def run(self, bare=False): |
| """Runs this WSGI-compliant application in a CGI environment. |
| |
| This uses functions provided by ``google.appengine.ext.webapp.util``, |
| if available: ``run_bare_wsgi_app`` and ``run_wsgi_app``. |
| |
| Otherwise, it uses ``wsgiref.handlers.CGIHandler().run()``. |
| |
| :param bare: |
| If True, doesn't add registered WSGI middleware: use |
| ``run_bare_wsgi_app`` instead of ``run_wsgi_app``. |
| """ |
| if _webapp_util: |
| if bare: |
| _webapp_util.run_bare_wsgi_app(self) |
| else: |
| _webapp_util.run_wsgi_app(self) |
| else: # pragma: no cover |
| handlers.CGIHandler().run(self) |
| |
| def get_response(self, *args, **kwargs): |
| """Creates a request and returns a response for this app. |
| |
| This is a convenience for unit testing purposes. It receives |
| parameters to build a request and calls the application, returning |
| the resulting response:: |
| |
| class HelloHandler(webapp2.RequestHandler): |
| def get(self): |
| self.response.write('Hello, world!') |
| |
| app = webapp2.WSGIapplication([('/', HelloHandler)]) |
| |
| # Test the app, passing parameters to build a request. |
| response = app.get_response('/') |
| assert response.status_int == 200 |
| assert response.body == 'Hello, world!' |
| |
| :param args: |
| Positional arguments to be passed to ``Request.blank()``. |
| :param kwargs: |
| Keyword arguments to be passed to ``Request.blank()``. |
| :returns: |
| A :class:`Response` object. |
| """ |
| return self.request_class.blank(*args, **kwargs).get_response(self) |
| |
| |
| _import_string_error = """\ |
| import_string() failed for %r. Possible reasons are: |
| |
| - missing __init__.py in a package; |
| - package or module path not included in sys.path; |
| - duplicated package or module name taking precedence in sys.path; |
| - missing module, class, function or variable; |
| |
| Original exception: |
| |
| %s: %s |
| |
| Debugged import: |
| |
| %s""" |
| |
| |
| class ImportStringError(Exception): |
| """Provides information about a failed :func:`import_string` attempt.""" |
| |
| #: String in dotted notation that failed to be imported. |
| import_name = None |
| #: Wrapped exception. |
| exception = None |
| |
| def __init__(self, import_name, exception): |
| self.import_name = import_name |
| self.exception = exception |
| msg = _import_string_error |
| name = '' |
| tracked = [] |
| for part in import_name.split('.'): |
| name += (name and '.') + part |
| imported = import_string(name, silent=True) |
| if imported: |
| tracked.append((name, imported.__file__)) |
| else: |
| track = ['- %r found in %r.' % rv for rv in tracked] |
| track.append('- %r not found.' % name) |
| msg = msg % (import_name, exception.__class__.__name__, |
| str(exception), '\n'.join(track)) |
| break |
| |
| Exception.__init__(self, msg) |
| |
| |
| _get_app_error = 'WSGIApplication global variable is not set.' |
| _get_request_error = 'Request global variable is not set.' |
| |
| |
| def get_app(): |
| """Returns the active app instance. |
| |
| :returns: |
| A :class:`WSGIApplication` instance. |
| """ |
| if _local: |
| assert getattr(_local, 'app', None) is not None, _get_app_error |
| else: |
| assert WSGIApplication.app is not None, _get_app_error |
| |
| return WSGIApplication.app |
| |
| |
| def get_request(): |
| """Returns the active request instance. |
| |
| :returns: |
| A :class:`Request` instance. |
| """ |
| if _local: |
| assert getattr(_local, 'request', None) is not None, _get_request_error |
| else: |
| assert WSGIApplication.request is not None, _get_request_error |
| |
| return WSGIApplication.request |
| |
| |
| def uri_for(_name, _request=None, *args, **kwargs): |
| """A standalone uri_for version that can be passed to templates. |
| |
| .. seealso:: :meth:`Router.build`. |
| """ |
| request = _request or get_request() |
| return request.app.router.build(request, _name, args, kwargs) |
| |
| |
| def redirect(uri, permanent=False, abort=False, code=None, body=None, |
| request=None, response=None): |
| """Issues an HTTP redirect to the given relative URI. |
| |
| This won't stop code execution unless **abort** is True. A common |
| practice is to return when calling this method:: |
| |
| return redirect('/some-path') |
| |
| :param uri: |
| A relative or absolute URI (e.g., ``'../flowers.html'``). |
| :param permanent: |
| If True, uses a 301 redirect instead of a 302 redirect. |
| :param abort: |
| If True, raises an exception to perform the redirect. |
| :param code: |
| The redirect status code. Supported codes are 301, 302, 303, 305, |
| and 307. 300 is not supported because it's not a real redirect |
| and 304 because it's the answer for a request with defined |
| ``If-Modified-Since`` headers. |
| :param body: |
| Response body, if any. |
| :param request: |
| Optional request object. If not set, uses :func:`get_request`. |
| :param response: |
| Optional response object. If not set, a new response is created. |
| :returns: |
| A :class:`Response` instance. |
| """ |
| if uri.startswith(('.', '/')): |
| request = request or get_request() |
| uri = str(urlparse.urljoin(request.url, uri)) |
| |
| if code is None: |
| if permanent: |
| code = 301 |
| else: |
| code = 302 |
| |
| assert code in (301, 302, 303, 305, 307), \ |
| 'Invalid redirect status code.' |
| |
| if abort: |
| _abort(code, headers=[('Location', uri)]) |
| |
| if response is None: |
| request = request or get_request() |
| response = request.app.response_class() |
| else: |
| response.clear() |
| |
| response.headers['Location'] = uri |
| response.status = code |
| if body is not None: |
| response.write(body) |
| |
| return response |
| |
| |
| def redirect_to(_name, _permanent=False, _abort=False, _code=None, |
| _body=None, _request=None, _response=None, *args, **kwargs): |
| """Convenience function mixing :func:`redirect` and :func:`uri_for`. |
| |
| Issues an HTTP redirect to a named URI built using :func:`uri_for`. |
| |
| :param _name: |
| The route name to redirect to. |
| :param args: |
| Positional arguments to build the URI. |
| :param kwargs: |
| Keyword arguments to build the URI. |
| :returns: |
| A :class:`Response` instance. |
| |
| The other arguments are described in :func:`redirect`. |
| """ |
| uri = uri_for(_name, _request=_request, *args, **kwargs) |
| return redirect(uri, permanent=_permanent, abort=_abort, code=_code, |
| body=_body, request=_request, response=_response) |
| |
| |
| def abort(code, *args, **kwargs): |
| """Raises an ``HTTPException``. |
| |
| :param code: |
| An integer that represents a valid HTTP status code. |
| :param args: |
| Positional arguments to instantiate the exception. |
| :param kwargs: |
| Keyword arguments to instantiate the exception. |
| """ |
| cls = exc.status_map.get(code) |
| if not cls: |
| raise KeyError('No exception is defined for code %r.' % code) |
| |
| raise cls(*args, **kwargs) |
| |
| |
| def import_string(import_name, silent=False): |
| """Imports an object based on a string in dotted notation. |
| |
| Simplified version of the function with same name from `Werkzeug`_. |
| |
| :param import_name: |
| String in dotted notation of the object to be imported. |
| :param silent: |
| If True, import or attribute errors are ignored and None is returned |
| instead of raising an exception. |
| :returns: |
| The imported object. |
| """ |
| import_name = _to_utf8(import_name) |
| try: |
| if '.' in import_name: |
| module, obj = import_name.rsplit('.', 1) |
| return getattr(__import__(module, None, None, [obj]), obj) |
| else: |
| return __import__(import_name) |
| except (ImportError, AttributeError), e: |
| if not silent: |
| raise ImportStringError(import_name, e), None, sys.exc_info()[2] |
| |
| |
| def _urlunsplit(scheme=None, netloc=None, path=None, query=None, |
| fragment=None): |
| """Like ``urlparse.urlunsplit``, but will escape values and urlencode and |
| sort query arguments. |
| |
| :param scheme: |
| URI scheme, e.g., `http` or `https`. |
| :param netloc: |
| Network location, e.g., `localhost:8080` or `www.google.com`. |
| :param path: |
| URI path. |
| :param query: |
| URI query as an escaped string, or a dictionary or list of key-values |
| tuples to build a query. |
| :param fragment: |
| Fragment identifier, also known as "anchor". |
| :returns: |
| An assembled absolute or relative URI. |
| """ |
| if not scheme or not netloc: |
| scheme = None |
| netloc = None |
| |
| if path: |
| path = urllib.quote(_to_utf8(path)) |
| |
| if query and not isinstance(query, basestring): |
| if isinstance(query, dict): |
| query = query.iteritems() |
| |
| # Sort args: commonly needed to build signatures for services. |
| query = urllib.urlencode(sorted(query)) |
| |
| if fragment: |
| fragment = urllib.quote(_to_utf8(fragment)) |
| |
| return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) |
| |
| |
| def _get_handler_methods(handler): |
| """Returns a list of HTTP methods supported by a handler. |
| |
| :param handler: |
| A :class:`RequestHandler` instance. |
| :returns: |
| A list of HTTP methods supported by the handler. |
| """ |
| methods = [] |
| for method in get_app().allowed_methods: |
| if getattr(handler, _normalize_handler_method(method), None): |
| methods.append(method) |
| |
| return methods |
| |
| |
| def _normalize_handler_method(method): |
| """Transforms an HTTP method into a valid Python identifier.""" |
| return method.lower().replace('-', '_') |
| |
| |
| def _to_utf8(value): |
| """Encodes a unicode value to UTF-8 if not yet encoded.""" |
| if isinstance(value, str): |
| return value |
| |
| return value.encode('utf-8') |
| |
| |
| def _parse_route_template(template, default_sufix=''): |
| """Lazy route template parser.""" |
| variables = {} |
| reverse_template = pattern = '' |
| args_count = last = 0 |
| for match in _route_re.finditer(template): |
| part = template[last:match.start()] |
| name = match.group(1) |
| expr = match.group(2) or default_sufix |
| last = match.end() |
| |
| if not name: |
| name = '__%d__' % args_count |
| args_count += 1 |
| |
| pattern += '%s(?P<%s>%s)' % (re.escape(part), name, expr) |
| reverse_template += '%s%%(%s)s' % (part, name) |
| variables[name] = re.compile('^%s$' % expr) |
| |
| part = template[last:] |
| kwargs_count = len(variables) - args_count |
| reverse_template += part |
| regex = re.compile('^%s%s$' % (pattern, re.escape(part))) |
| return regex, reverse_template, args_count, kwargs_count, variables |
| |
| |
| def _get_route_variables(match, default_kwargs=None): |
| """Returns (args, kwargs) for a route match.""" |
| kwargs = default_kwargs or {} |
| kwargs.update(match.groupdict()) |
| if kwargs: |
| args = tuple(value[1] for value in sorted( |
| (int(key[2:-2]), kwargs.pop(key)) for key in kwargs.keys() \ |
| if key.startswith('__') and key.endswith('__'))) |
| else: |
| args = () |
| |
| return args, kwargs |
| |
| |
| def _set_thread_safe_app(): |
| """Assigns WSGIApplication globals to a proxy pointing to thread-local.""" |
| if _local is not None: # pragma: no cover |
| WSGIApplication.app = WSGIApplication.active_instance = _local('app') |
| WSGIApplication.request = _local('request') |
| |
| |
| Request.ResponseClass = Response |
| Response.RequestClass = Request |
| # Alias. |
| _abort = abort |
| # Thread-safety support. |
| _set_thread_safe_app() |
| |
| # Defer importing google.appengine.ext.webapp.util until every public symbol |
| # has been defined since google.appengine.ext.webapp in App Engine Python 2.7 |
| # runtime imports this module to provide its public interface. |
| try: |
| from google.appengine.ext.webapp import util as _webapp_util |
| except ImportError: # pragma: no cover |
| pass |