mirror of https://github.com/mitsuhiko/flask.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
19 KiB
523 lines
19 KiB
# -*- coding: utf-8 -*- |
|
""" |
|
flask |
|
~~~~~ |
|
|
|
A microframework based on Werkzeug. It's extensively documented |
|
and follows best practice patterns. |
|
|
|
:copyright: (c) 2010 by Armin Ronacher. |
|
:license: BSD, see LICENSE for more details. |
|
""" |
|
import os |
|
import sys |
|
import pkg_resources |
|
from threading import local |
|
from contextlib import contextmanager |
|
from jinja2 import Environment, PackageLoader |
|
from werkzeug import Request, Response, LocalStack, LocalProxy, \ |
|
create_environ, cached_property |
|
from werkzeug.routing import Map, Rule |
|
from werkzeug.exceptions import HTTPException, InternalServerError |
|
from werkzeug.contrib.securecookie import SecureCookie |
|
|
|
# utilities we import from Werkzeug and Jinja2 that are unused |
|
# in the module but are exported as public interface. |
|
from werkzeug import abort, redirect |
|
from jinja2 import Markup, escape |
|
|
|
|
|
class FlaskRequest(Request): |
|
"""The request object used by default in flask. Remembers the |
|
matched endpoint and view arguments. |
|
""" |
|
|
|
def __init__(self, environ): |
|
Request.__init__(self, environ) |
|
self.endpoint = None |
|
self.view_args = None |
|
|
|
|
|
class FlaskResponse(Response): |
|
"""The response object that is used by default in flask. Works like the |
|
response object from Werkzeug but is set to have a HTML mimetype by |
|
default. |
|
""" |
|
default_mimetype = 'text/html' |
|
|
|
|
|
class _RequestGlobals(object): |
|
pass |
|
|
|
|
|
class _RequestContext(object): |
|
"""The request context contains all request relevant information. It is |
|
created at the beginning of the request and pushed to the |
|
`_request_ctx_stack` and removed at the end of it. It will create the |
|
URL adapter and request object for the WSGI environment provided. |
|
""" |
|
|
|
def __init__(self, app, environ): |
|
self.app = app |
|
self.url_adapter = app.url_map.bind_to_environ(environ) |
|
self.request = app.request_class(environ) |
|
self.session = app.open_session(self.request) |
|
self.g = _RequestGlobals() |
|
self.flashes = None |
|
|
|
|
|
def url_for(endpoint, **values): |
|
"""Generates a URL to the given endpoint with the method provided. |
|
|
|
:param endpoint: the endpoint of the URL (name of the function) |
|
:param values: the variable arguments of the URL rule |
|
""" |
|
return _request_ctx_stack.top.url_adapter.build(endpoint, values) |
|
|
|
|
|
def flash(message): |
|
"""Flashes a message to the next request. In order to remove the |
|
flashed message from the session and to display it to the user, |
|
the template has to call :func:`get_flashed_messages`. |
|
""" |
|
session['_flashes'] = (session.get('_flashes', [])) + [message] |
|
|
|
|
|
def get_flashed_messages(): |
|
"""Pulls all flashed messages from the session and returns them. |
|
Further calls in the same request to the function will return |
|
the same messages. |
|
""" |
|
flashes = _request_ctx_stack.top.flashes |
|
if flashes is None: |
|
_request_ctx_stack.top.flashes = flashes = \ |
|
session.pop('_flashes', []) |
|
return flashes |
|
|
|
|
|
def render_template(template_name, **context): |
|
"""Renders a template from the template folder with the given |
|
context. |
|
""" |
|
current_app.update_template_context(context) |
|
return current_app.jinja_env.get_template(template_name).render(context) |
|
|
|
|
|
def render_template_string(source, **context): |
|
"""Renders a template from the given template source string |
|
with the given context. |
|
""" |
|
current_app.update_template_context(context) |
|
return current_app.jinja_env.from_string(source).render(context) |
|
|
|
|
|
class Flask(object): |
|
"""The flask object implements a WSGI application and acts as the central |
|
object. It is passed the name of the module or package of the |
|
application. Once it is created it will act as a central registry for |
|
the view functions, the URL rules, template configuration and much more. |
|
|
|
The name of the package is used to resolve resources from inside the |
|
package or the folder the module is contained in depending on if the |
|
package parameter resolves to an actual python package (a folder with |
|
an `__init__.py` file inside) or a standard module (just a `.py` file). |
|
|
|
For more information about resource loading, see :func:`open_resource`. |
|
|
|
Usually you create a :class:`Flask` instance in your main module or |
|
in the `__init__.py` file of your package like this:: |
|
|
|
from flask import Flask |
|
app = Flask(__name__) |
|
""" |
|
|
|
#: the class that is used for request objects |
|
request_class = FlaskRequest |
|
|
|
#: the class that is used for response objects |
|
response_class = FlaskResponse |
|
|
|
#: path for the static files. If you don't want to use static files |
|
#: you can set this value to `None` in which case no URL rule is added |
|
#: and the development server will no longer serve any static files. |
|
static_path = '/static' |
|
|
|
#: if a secret key is set, cryptographic components can use this to |
|
#: sign cookies and other things. Set this to a complex random value |
|
#: when you want to use the secure cookie for instance. |
|
secret_key = None |
|
|
|
#: The secure cookie uses this for the name of the session cookie |
|
session_cookie_name = 'session' |
|
|
|
#: options that are passed directly to the Jinja2 environment |
|
jinja_options = dict( |
|
autoescape=True, |
|
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] |
|
) |
|
|
|
def __init__(self, package_name): |
|
#: the debug flag. Set this to `True` to enable debugging of |
|
#: the application. In debug mode the debugger will kick in |
|
#: when an unhandled exception ocurrs and the integrated server |
|
#: will automatically reload the application if changes in the |
|
#: code are detected. |
|
self.debug = False |
|
|
|
#: the name of the package or module. Do not change this once |
|
#: it was set by the constructor. |
|
self.package_name = package_name |
|
|
|
#: a dictionary of all view functions registered. The keys will |
|
#: be function names which are also used to generate URLs and |
|
#: the values are the function objects themselves. |
|
#: to register a view function, use the :meth:`route` decorator. |
|
self.view_functions = {} |
|
|
|
#: a dictionary of all registered error handlers. The key is |
|
#: be the error code as integer, the value the function that |
|
#: should handle that error. |
|
#: To register a error handler, use the :meth:`errorhandler` |
|
#: decorator. |
|
self.error_handlers = {} |
|
|
|
#: a list of functions that should be called at the beginning |
|
#: of the request before request dispatching kicks in. This |
|
#: can for example be used to open database connections or |
|
#: getting hold of the currently logged in user. |
|
#: To register a function here, use the :meth:`request_init` |
|
#: decorator. |
|
self.request_init_funcs = [] |
|
|
|
#: a list of functions that are called at the end of the |
|
#: request. Tha function is passed the current response |
|
#: object and modify it in place or replace it. |
|
#: To register a function here use the :meth:`request_shtdown` |
|
#: decorator. |
|
self.request_shutdown_funcs = [] |
|
self.url_map = Map() |
|
|
|
if self.static_path is not None: |
|
self.url_map.add(Rule(self.static_path + '/<filename>', |
|
build_only=True, endpoint='static')) |
|
|
|
#: the Jinja2 environment. It is created from the |
|
#: :attr:`jinja_options` and the loader that is returned |
|
#: by the :meth:`create_jinja_loader` function. |
|
self.jinja_env = Environment(loader=self.create_jinja_loader(), |
|
**self.jinja_options) |
|
self.jinja_env.globals.update( |
|
url_for=url_for, |
|
get_flashed_messages=get_flashed_messages |
|
) |
|
|
|
def create_jinja_loader(self): |
|
"""Creates the Jinja loader. By default just a package loader for |
|
the configured package is returned that looks up templates in the |
|
`templates` folder. To add other loaders it's possible to |
|
override this method. |
|
""" |
|
return PackageLoader(self.package_name) |
|
|
|
def update_template_context(self, context): |
|
"""Update the template context with some commonly used variables. |
|
This injects request, session and g into the template context. |
|
""" |
|
reqctx = _request_ctx_stack.top |
|
context['request'] = reqctx.request |
|
context['session'] = reqctx.session |
|
context['g'] = reqctx.g |
|
|
|
def run(self, host='localhost', port=5000, **options): |
|
"""Runs the application on a local development server. If the |
|
:attr:`debug` flag is set the server will automatically reload |
|
for code changes and show a debugger in case an exception happened. |
|
""" |
|
from werkzeug import run_simple |
|
if 'debug' in options: |
|
self.debug = options.pop('debug') |
|
if self.static_path is not None: |
|
options['static_files'] = { |
|
self.static_path: (self.package_name, 'static') |
|
} |
|
options.setdefault('use_reloader', self.debug) |
|
options.setdefault('use_debugger', self.debug) |
|
return run_simple(host, port, self, **options) |
|
|
|
@cached_property |
|
def test(self): |
|
"""A test client for this application""" |
|
from werkzeug import Client |
|
return Client(self, self.response_class, use_cookies=True) |
|
|
|
def open_resource(self, resource): |
|
"""Opens a resource from the application's resource folder. To see |
|
how this works, consider the following folder structure:: |
|
|
|
/myapplication.py |
|
/schemal.sql |
|
/static |
|
/style.css |
|
/template |
|
/layout.html |
|
/index.html |
|
|
|
If you want to open the `schema.sql` file you would do the |
|
following:: |
|
|
|
with app.open_resource('schema.sql') as f: |
|
contents = f.read() |
|
do_something_with(contents) |
|
""" |
|
return pkg_resources.resource_stream(self.package_name, resource) |
|
|
|
def open_session(self, request): |
|
"""Creates or opens a new session. Default implementation stores all |
|
session data in a signed cookie. This requires that the |
|
:attr:`secret_key` is set. |
|
""" |
|
key = self.secret_key |
|
if key is not None: |
|
return SecureCookie.load_cookie(request, self.session_cookie_name, |
|
secret_key=key) |
|
|
|
def save_session(self, session, response): |
|
"""Saves the session if it needs updates. For the default |
|
implementation, check :meth:`open_session`. |
|
""" |
|
if session is not None: |
|
session.save_cookie(response, self.session_cookie_name) |
|
|
|
def add_url_rule(self, rule, endpoint, **options): |
|
"""Connects a URL rule. Works exactly like the :meth:`route` |
|
decorator but does not register the view function for the endpoint. |
|
|
|
Basically this example:: |
|
|
|
@app.route('/') |
|
def index(): |
|
pass |
|
|
|
Is equivalent to the following:: |
|
|
|
def index(): |
|
pass |
|
app.add_url_rule('index', '/') |
|
app.view_functions['index'] = index |
|
""" |
|
options['endpoint'] = endpoint |
|
options.setdefault('methods', ('GET',)) |
|
self.url_map.add(Rule(rule, **options)) |
|
|
|
def route(self, rule, **options): |
|
"""A decorator that is used to register a view function for a |
|
given URL rule. Example:: |
|
|
|
@app.route('/') |
|
def index(): |
|
return 'Hello World' |
|
|
|
Variables parts in the route can be specified with angular |
|
brackets (``/user/<username>``). By default a variable part |
|
in the URL accepts any string without a slash however a differnt |
|
converter can be specified as well by using ``<converter:name>``. |
|
|
|
Variable parts are passed to the view function as keyword |
|
arguments. |
|
|
|
The following converters are possible: |
|
|
|
=========== =========================================== |
|
`int` accepts integers |
|
`float` like `int` but for floating point values |
|
`path` like the default but also accepts slashes |
|
=========== =========================================== |
|
|
|
Here some examples:: |
|
|
|
@app.route('/') |
|
def index(): |
|
pass |
|
|
|
@app.route('/<username>') |
|
def show_user(username): |
|
pass |
|
|
|
@app.route('/post/<int:post_id>') |
|
def show_post(post_id): |
|
pass |
|
|
|
An important detail to keep in mind is how Flask deals with trailing |
|
slashes. The idea is to keep each URL unique so the following rules |
|
apply: |
|
|
|
1. If a rule ends with a slash and is requested without a slash |
|
by the user, the user is automatically redirected to the same |
|
page with a trailing slash attached. |
|
2. If a rule does not end with a trailing slash and the user request |
|
the page with a trailing slash, a 404 not found is raised. |
|
|
|
This is consistent with how web servers deal with static files. This |
|
also makes it possible to use relative link targets safely. |
|
|
|
The :meth:`route` decorator accepts a couple of other arguments |
|
as well: |
|
|
|
:param methods: a list of methods this rule should be limited |
|
to (``GET``, ``POST`` etc.). By default a rule |
|
just listens for ``GET`` (and implicitly ``HEAD``). |
|
:param subdomain: specifies the rule for the subdoain in case |
|
subdomain matching is in use. |
|
:param strict_slashes: can be used to disable the strict slashes |
|
setting for this rule. See above. |
|
""" |
|
def decorator(f): |
|
self.add_url_rule(rule, f.__name__, **options) |
|
self.view_functions[f.__name__] = f |
|
return f |
|
return decorator |
|
|
|
def errorhandler(self, code): |
|
"""A decorator that is used to register a function give a given |
|
error code. Example:: |
|
|
|
@app.errorhandler(404) |
|
def page_not_found(): |
|
return 'This page does not exist', 404 |
|
|
|
You can also register a function as error handler without using |
|
the :meth:`errorhandler` decorator. The following example is |
|
equivalent to the one above:: |
|
|
|
def page_not_found(): |
|
return 'This page does not exist', 404 |
|
app.error_handlers[404] = page_not_found |
|
""" |
|
def decorator(f): |
|
self.error_handlers[code] = f |
|
return f |
|
return decorator |
|
|
|
def request_init(self, f): |
|
"""Registers a function to run before each request.""" |
|
self.request_init_funcs.append(f) |
|
return f |
|
|
|
def request_shutdown(self, f): |
|
"""Register a function to be run after each request.""" |
|
self.request_shutdown_funcs.append(f) |
|
return f |
|
|
|
def match_request(self): |
|
"""Matches the current request against the URL map and also |
|
stores the endpoint and view arguments on the request object |
|
is successful, otherwise the exception is stored. |
|
""" |
|
rv = _request_ctx_stack.top.url_adapter.match() |
|
request.endpoint, request.view_args = rv |
|
return rv |
|
|
|
def dispatch_request(self): |
|
"""Does the request dispatching. Matches the URL and returns the |
|
return value of the view or error handler. This does not have to |
|
be a response object. In order to convert the return value to a |
|
proper response object, call :func:`make_response`. |
|
""" |
|
try: |
|
endpoint, values = self.match_request() |
|
return self.view_functions[endpoint](**values) |
|
except HTTPException, e: |
|
handler = self.error_handlers.get(e.code) |
|
if handler is None: |
|
return e |
|
return handler(e) |
|
except Exception, e: |
|
handler = self.error_handlers.get(500) |
|
if self.debug or handler is None: |
|
raise |
|
return handler(e) |
|
|
|
def make_response(self, rv): |
|
"""Converts the return value from a view function to a real |
|
response object that is an instance of :attr:`response_class`. |
|
""" |
|
if isinstance(rv, self.response_class): |
|
return rv |
|
if isinstance(rv, basestring): |
|
return self.response_class(rv) |
|
if isinstance(rv, tuple): |
|
return self.response_class(*rv) |
|
return self.response_class.force_type(rv, request.environ) |
|
|
|
def preprocess_request(self): |
|
"""Called before the actual request dispatching and will |
|
call every as :func:`request_init` decorated function. |
|
If any of these function returns a value it's handled as |
|
if it was the return value from the view and further |
|
request handling is stopped. |
|
""" |
|
for func in self.request_init_funcs: |
|
rv = func() |
|
if rv is not None: |
|
return rv |
|
|
|
def process_response(self, response): |
|
"""Can be overridden in order to modify the response object |
|
before it's sent to the WSGI server. |
|
""" |
|
session = _request_ctx_stack.top.session |
|
if session is not None: |
|
self.save_session(session, response) |
|
for handler in self.request_shutdown_funcs: |
|
response = handler(response) |
|
return response |
|
|
|
def wsgi_app(self, environ, start_response): |
|
"""The actual WSGI application. This is not implemented in |
|
`__call__` so that middlewares can be applied: |
|
|
|
app.wsgi_app = MyMiddleware(app.wsgi_app) |
|
""" |
|
with self.request_context(environ): |
|
rv = self.preprocess_request() |
|
if rv is None: |
|
rv = self.dispatch_request() |
|
response = self.make_response(rv) |
|
response = self.process_response(response) |
|
return response(environ, start_response) |
|
|
|
@contextmanager |
|
def request_context(self, environ): |
|
"""Creates a request context from the given environment and binds |
|
it to the current context. This must be used in combination with |
|
the `with` statement because the request is only bound to the |
|
current context for the duration of the `with` block. |
|
|
|
Example usage:: |
|
|
|
with app.request_context(environ): |
|
do_something_with(request) |
|
""" |
|
_request_ctx_stack.push(_RequestContext(self, environ)) |
|
try: |
|
yield |
|
finally: |
|
_request_ctx_stack.pop() |
|
|
|
def test_request_context(self, *args, **kwargs): |
|
"""Creates a WSGI environment from the given values (see |
|
:func:`werkzeug.create_environ` for more information). |
|
""" |
|
return self.request_context(create_environ(*args, **kwargs)) |
|
|
|
def __call__(self, environ, start_response): |
|
"""Shortcut for :attr:`wsgi_app`""" |
|
return self.wsgi_app(environ, start_response) |
|
|
|
|
|
# context locals |
|
_request_ctx_stack = LocalStack() |
|
current_app = LocalProxy(lambda: _request_ctx_stack.top.app) |
|
request = LocalProxy(lambda: _request_ctx_stack.top.request) |
|
session = LocalProxy(lambda: _request_ctx_stack.top.session) |
|
g = LocalProxy(lambda: _request_ctx_stack.top.g)
|
|
|