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.
356 lines
12 KiB
356 lines
12 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 jinja2 import Environment, PackageLoader |
|
from werkzeug import Request, Response, LocalStack, LocalProxy |
|
from werkzeug.routing import Map, Rule |
|
from werkzeug.exceptions import HTTPException, InternalServerError |
|
from werkzeug.contrib.securecookie import SecureCookie |
|
|
|
# try to import the json helpers |
|
try: |
|
from simplejson import loads as load_json, dumps as dump_json |
|
except ImportError: |
|
try: |
|
from json import loads as load_json, dumps as dump_json |
|
except ImportError: |
|
pass |
|
|
|
# utilities we import from Werkzeug and Jinja2 that are unused |
|
# in the module but are exported as public interface. |
|
from werkzeug import abort, redirect, secure_filename, cached_property, \ |
|
html, import_string, generate_password_hash, check_password_hash |
|
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 jsonified(**values): |
|
"""Returns a json response""" |
|
return current_app.response_class(dump_json(values), |
|
mimetype='application/json') |
|
|
|
|
|
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. |
|
""" |
|
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. |
|
""" |
|
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 |
|
and optionally a configuration. When it's created it sets up the |
|
template engine and provides ways to register view functions. |
|
""" |
|
|
|
#: 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): |
|
self.debug = False |
|
self.package_name = package_name |
|
self.view_functions = {} |
|
self.error_handlers = {} |
|
self.request_init_funcs = [] |
|
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')) |
|
|
|
self.jinja_env = Environment(loader=self.create_jinja_loader(), |
|
**self.jinja_options) |
|
self.jinja_env.globals.update( |
|
url_for=url_for, |
|
request=request, |
|
session=session, |
|
g=g, |
|
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 run(self, host='localhost', port=5000, **options): |
|
"""Runs the application on a local development server""" |
|
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""" |
|
return pkg_resources.resource_stream(self.package_name, resource) |
|
|
|
def open_session(self, request): |
|
"""Creates or opens a new session. Default implementation requires |
|
that `securecookie.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.""" |
|
if session is not None: |
|
session.save_cookie(response, self.session_cookie_name) |
|
|
|
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' |
|
""" |
|
def decorator(f): |
|
if 'endpoint' not in options: |
|
options['endpoint'] = f.__name__ |
|
self.url_map.add(Rule(rule, **options)) |
|
self.view_functions[options['endpoint']] = 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 |
|
""" |
|
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) |
|
""" |
|
_request_ctx_stack.push(_RequestContext(self, environ)) |
|
try: |
|
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) |
|
finally: |
|
_request_ctx_stack.pop() |
|
|
|
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)
|
|
|