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.
357 lines
12 KiB
357 lines
12 KiB
15 years ago
|
# -*- 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)
|