Merge branch 'master' into AvivC-improve-docs-in-Flask.preprocess_request

This commit is contained in:
David Lord 2017-04-25 09:43:18 -07:00
commit bc7dae3d5a
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
156 changed files with 3327 additions and 2059 deletions

View file

@ -10,7 +10,7 @@
:license: BSD, see LICENSE for more details.
"""
__version__ = '0.11.dev0'
__version__ = '0.13-dev'
# utilities we import from Werkzeug and Jinja2 that are unused
# in the module but are exported as public interface.
@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \
# it.
from . import json
# This was the only thing that flask used to export at one point and it had
# This was the only thing that Flask used to export at one point and it had
# a more generic name.
jsonify = json.jsonify

View file

@ -65,17 +65,25 @@ def with_metaclass(meta, *bases):
# Certain versions of pypy have a bug where clearing the exception stack
# breaks the __exit__ function in a very peculiar way. This is currently
# true for pypy 2.2.1 for instance. The second level of exception blocks
# is necessary because pypy seems to forget to check if an exception
# happened until the next bytecode instruction?
# breaks the __exit__ function in a very peculiar way. The second level of
# exception blocks is necessary because pypy seems to forget to check if an
# exception happened until the next bytecode instruction?
#
# Relevant PyPy bugfix commit:
# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301
# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later
# versions.
#
# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug.
BROKEN_PYPY_CTXMGR_EXIT = False
if hasattr(sys, 'pypy_version_info'):
class _Mgr(object):
def __enter__(self):
return self
def __exit__(self, *args):
sys.exc_clear()
if hasattr(sys, 'exc_clear'):
# Python 3 (PyPy3) doesn't have exc_clear
sys.exc_clear()
try:
try:
with _Mgr():

View file

@ -10,30 +10,30 @@
"""
import os
import sys
from threading import Lock
from datetime import timedelta
from itertools import chain
from functools import update_wrapper
from collections import deque
from itertools import chain
from threading import Lock
from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest, default_exceptions
from werkzeug.datastructures import ImmutableDict, Headers
from werkzeug.exceptions import BadRequest, HTTPException, \
InternalServerError, MethodNotAllowed, default_exceptions
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _endpoint_from_view_func, find_package
from . import json, cli
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import RequestContext, AppContext, _AppCtxGlobals
from .globals import _request_ctx_stack, request, session, g
from . import cli, json
from ._compat import integer_types, reraise, string_types, text_type
from .config import Config, ConfigAttribute
from .ctx import AppContext, RequestContext, _AppCtxGlobals
from .globals import _request_ctx_stack, g, request, session
from .helpers import _PackageBoundObject, \
_endpoint_from_view_func, find_package, get_debug_flag, \
get_flashed_messages, locked_cached_property, url_for
from .sessions import SecureCookieSessionInterface
from .signals import appcontext_tearing_down, got_request_exception, \
request_finished, request_started, request_tearing_down
from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \
request_tearing_down, appcontext_tearing_down
from ._compat import reraise, string_types, text_type, integer_types
_default_template_ctx_processor
from .wrappers import Request, Response
# a lock used for logger initialization
_logger_lock = Lock()
@ -120,9 +120,12 @@ class Flask(_PackageBoundObject):
The `instance_path` and `instance_relative_config` parameters were
added.
.. versionadded:: 1.0
.. versionadded:: 0.11
The `root_path` parameter was added.
.. versionadded:: 0.13
The `host_matching` and `static_host` parameters were added.
:param import_name: the name of the application package
:param static_url_path: can be used to specify a different path for the
static files on the web. Defaults to the name
@ -130,6 +133,13 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application.
folder in the root path of the application. Defaults
to None.
:param host_matching: sets the app's ``url_map.host_matching`` to the given
given value. Defaults to False.
:param static_host: the host to use when adding the static route. Defaults
to None. Required when using ``host_matching=True``
with a ``static_folder`` configured.
:param template_folder: the folder that contains the templates that should
be used by the application. Defaults to
``'templates'`` folder in the root path of the
@ -159,7 +169,7 @@ class Flask(_PackageBoundObject):
#: The class that is used for the Jinja environment.
#:
#: .. versionadded:: 1.0
#: .. versionadded:: 0.11
jinja_environment = Environment
#: The class that is used for the :data:`~flask.g` instance.
@ -198,7 +208,7 @@ class Flask(_PackageBoundObject):
#: 1. Default values for certain config options.
#: 2. Access to config values through attributes in addition to keys.
#:
#: .. versionadded:: 1.0
#: .. versionadded:: 0.11
config_class = Config
#: The debug flag. Set this to ``True`` to enable debugging of the
@ -289,7 +299,7 @@ class Flask(_PackageBoundObject):
#: Default configuration parameters.
default_config = ImmutableDict({
'DEBUG': False,
'DEBUG': get_debug_flag(default=False),
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
@ -314,7 +324,8 @@ class Flask(_PackageBoundObject):
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_PRETTYPRINT_REGULAR': False,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
})
@ -336,7 +347,8 @@ class Flask(_PackageBoundObject):
session_interface = SecureCookieSessionInterface()
def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
static_folder='static', static_host=None,
host_matching=False, template_folder='templates',
instance_path=None, instance_relative_config=False,
root_path=None):
_PackageBoundObject.__init__(self, import_name,
@ -390,7 +402,7 @@ class Flask(_PackageBoundObject):
#: is the class for the instance check and the second the error handler
#: function.
#:
#: To register a error handler, use the :meth:`errorhandler`
#: To register an error handler, use the :meth:`errorhandler`
#: decorator.
self.error_handler_spec = {None: self._error_handlers}
@ -421,9 +433,8 @@ class Flask(_PackageBoundObject):
#: A dictionary with lists of functions that should be called after
#: each request. The key of the dictionary is the name of the blueprint
#: this function is active for, ``None`` for all requests. 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:`after_request` decorator.
#: example be used to close database connections. To register a function
#: here, use the :meth:`after_request` decorator.
self.after_request_funcs = {}
#: A dictionary with lists of functions that are called after
@ -481,7 +492,7 @@ class Flask(_PackageBoundObject):
#: A list of shell context processor functions that should be run
#: when a shell context is created.
#:
#: .. versionadded:: 1.0
#: .. versionadded:: 0.11
self.shell_context_processors = []
#: all the attached blueprints in a dictionary by name. Blueprints
@ -518,26 +529,29 @@ class Flask(_PackageBoundObject):
#: def to_python(self, value):
#: return value.split(',')
#: def to_url(self, values):
#: return ','.join(BaseConverter.to_url(value)
#: return ','.join(super(ListConverter, self).to_url(value)
#: for value in values)
#:
#: app = Flask(__name__)
#: app.url_map.converters['list'] = ListConverter
self.url_map = Map()
self.url_map.host_matching = host_matching
# tracks internally if the application already handled at least one
# request.
self._got_first_request = False
self._before_request_lock = Lock()
# register the static folder for the application. Do that even
# if the folder does not exist. First of all it might be created
# while the server is running (usually happens during development)
# but also because google appengine stores static files somewhere
# else when mapped with the .yml file.
# Add a static route using the provided static_url_path, static_host,
# and static_folder if there is a configured static_folder.
# Note we do this without checking if static_folder exists.
# For one, it might be created while the server is running (e.g. during
# development). Also, Google App Engine stores static files somewhere
if self.has_static_folder:
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
self.add_url_rule(self.static_url_path + '/<path:filename>',
endpoint='static',
endpoint='static', host=static_host,
view_func=self.send_static_file)
#: The click command line context for this application. Commands
@ -546,7 +560,7 @@ class Flask(_PackageBoundObject):
#: provided by Flask itself and can be overridden.
#:
#: This is an instance of a :class:`click.Group` object.
self.cli = cli.AppGroup(self)
self.cli = cli.AppGroup(self.name)
def _get_error_handlers(self):
from warnings import warn
@ -683,7 +697,7 @@ class Flask(_PackageBoundObject):
this function to customize the behavior.
.. versionadded:: 0.5
.. versionchanged:: 1.0
.. versionchanged:: 0.11
``Environment.auto_reload`` set in accordance with
``TEMPLATES_AUTO_RELOAD`` configuration option.
"""
@ -772,7 +786,7 @@ class Flask(_PackageBoundObject):
application. This runs all the registered shell context
processors.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
rv = {'app': self, 'g': g}
for processor in self.shell_context_processors:
@ -780,8 +794,13 @@ class Flask(_PackageBoundObject):
return rv
def run(self, host=None, port=None, debug=None, **options):
"""Runs the application on a local development server. If the
:attr:`debug` flag is set the server will automatically reload
"""Runs the application on a local development server.
Do not use ``run()`` in a production setting. It is not intended to
meet security and performance requirements for a production server.
Instead, see :ref:`deployment` for WSGI server recommendations.
If the :attr:`debug` flag is set the server will automatically reload
for code changes and show a debugger in case an exception happened.
If you want to run the application in debug mode, but disable the
@ -791,8 +810,7 @@ class Flask(_PackageBoundObject):
It is not recommended to use this function for development with
automatic reloading as this is badly supported. Instead you should
be using the :command:`flask` command line script's ``runserver``
support.
be using the :command:`flask` command line script's ``run`` support.
.. admonition:: Keep in Mind
@ -809,7 +827,8 @@ class Flask(_PackageBoundObject):
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
have the server available externally as well. Defaults to
``'127.0.0.1'``.
``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config
variable if present.
:param port: the port of the webserver. Defaults to ``5000`` or the
port defined in the ``SERVER_NAME`` config variable if
present.
@ -820,15 +839,22 @@ class Flask(_PackageBoundObject):
:func:`werkzeug.serving.run_simple` for more
information.
"""
# Change this into a no-op if the server is invoked from the
# command line. Have a look at cli.py for more information.
if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1':
from .debughelpers import explain_ignored_app_run
explain_ignored_app_run()
return
from werkzeug.serving import run_simple
if host is None:
host = '127.0.0.1'
if port is None:
server_name = self.config['SERVER_NAME']
if server_name and ':' in server_name:
port = int(server_name.rsplit(':', 1)[1])
else:
port = 5000
_host = '127.0.0.1'
_port = 5000
server_name = self.config.get("SERVER_NAME")
sn_host, sn_port = None, None
if server_name:
sn_host, _, sn_port = server_name.partition(':')
host = host or sn_host or _host
port = int(port or sn_port or _port)
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
@ -837,7 +863,7 @@ class Flask(_PackageBoundObject):
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# resetted normally. This makes it possible to restart the server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
@ -871,9 +897,9 @@ class Flask(_PackageBoundObject):
from flask.testing import FlaskClient
class CustomClient(FlaskClient):
def __init__(self, authentication=None, *args, **kwargs):
FlaskClient.__init__(*args, **kwargs)
self._authentication = authentication
def __init__(self, *args, **kwargs):
self._authentication = kwargs.pop("authentication")
super(CustomClient,self).__init__( *args, **kwargs)
app.test_client_class = CustomClient
client = app.test_client(authentication='Basic ....')
@ -888,7 +914,7 @@ class Flask(_PackageBoundObject):
to override the client to be used by setting the
:attr:`test_client_class` attribute.
.. versionchanged:: 1.0
.. versionchanged:: 0.11
Added `**kwargs` to support passing additional keyword arguments to
the constructor of :attr:`test_client_class`.
"""
@ -949,12 +975,12 @@ class Flask(_PackageBoundObject):
def iter_blueprints(self):
"""Iterates over all blueprints by the order they were registered.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
return iter(self._blueprint_order)
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the
endpoint.
@ -994,6 +1020,10 @@ class Flask(_PackageBoundObject):
endpoint
:param view_func: the function to call when serving a request to the
provided endpoint
:param provide_automatic_options: controls whether the ``OPTIONS``
method should be added automatically. This can also be controlled
by setting the ``view_func.provide_automatic_options = False``
before adding the rule.
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
@ -1023,8 +1053,9 @@ class Flask(_PackageBoundObject):
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
if provide_automatic_options is None:
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
if provide_automatic_options is None:
if 'OPTIONS' not in methods:
@ -1110,7 +1141,7 @@ class Flask(_PackageBoundObject):
@setupmethod
def errorhandler(self, code_or_exception):
"""A decorator that is used to register a function give a given
"""A decorator that is used to register a function given an
error code. Example::
@app.errorhandler(404)
@ -1148,7 +1179,8 @@ class Flask(_PackageBoundObject):
that do not necessarily have to be a subclass of the
:class:`~werkzeug.exceptions.HTTPException` class.
:param code: the code as integer for the handler
:param code_or_exception: the code as integer for the handler, or
an arbitrary exception
"""
def decorator(f):
self._register_error_handler(None, code_or_exception, f)
@ -1342,7 +1374,7 @@ class Flask(_PackageBoundObject):
will have to surround the execution of these code by try/except
statements and log occurring errors.
When a teardown function was called because of a exception it will
When a teardown function was called because of an exception it will
be passed an error object.
The return values of teardown functions are ignored.
@ -1398,7 +1430,7 @@ class Flask(_PackageBoundObject):
def shell_context_processor(self, f):
"""Registers a shell context processor function.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
self.shell_context_processors.append(f)
return f
@ -1431,24 +1463,13 @@ class Flask(_PackageBoundObject):
def find_handler(handler_map):
if not handler_map:
return
queue = deque(exc_class.__mro__)
# Protect from geniuses who might create circular references in
# __mro__
done = set()
while queue:
cls = queue.popleft()
if cls in done:
continue
done.add(cls)
for cls in exc_class.__mro__:
handler = handler_map.get(cls)
if handler is not None:
# cache for next time exc_class is raised
handler_map[exc_class] = handler
return handler
queue.extend(cls.__mro__)
# try blueprint handlers
handler = find_handler(self.error_handler_spec
.get(request.blueprint, {})
@ -1550,7 +1571,7 @@ class Flask(_PackageBoundObject):
self.log_exception((exc_type, exc_value, tb))
if handler is None:
return InternalServerError()
return handler(e)
return self.finalize_request(handler(e), from_error_handler=True)
def log_exception(self, exc_info):
"""Logs an exception. This is called by :meth:`handle_exception`
@ -1618,9 +1639,30 @@ class Flask(_PackageBoundObject):
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
def finalize_request(self, rv, from_error_handler=False):
"""Given the return value from a view function this finalizes
the request by converting it into a response and invoking the
postprocessing functions. This is invoked for both normal
request dispatching as well as error handlers.
Because this means that it might be called as a result of a
failure a special safe mode is available which can be enabled
with the `from_error_handler` flag. If enabled, failures in
response processing will be logged and otherwise ignored.
:internal:
"""
response = self.make_response(rv)
response = self.process_response(response)
request_finished.send(self, response=response)
try:
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while handling an error')
return response
def try_trigger_before_first_request_functions(self):
@ -1673,62 +1715,106 @@ class Flask(_PackageBoundObject):
return False
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`.
"""Convert the return value from a view function to an instance of
:attr:`response_class`.
The following types are allowed for `rv`:
:param rv: the return value from the view function. The view function
must return a response. Returning ``None``, or the view ending
without returning, is not allowed. The following types are allowed
for ``view_rv``:
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
======================= ===========================================
:attr:`response_class` the object is returned unchanged
:class:`str` a response object is created with the
string as body
:class:`unicode` a response object is created with the
string encoded to utf-8 as body
a WSGI function the function is called as WSGI application
and buffered as response object
:class:`tuple` A tuple in the form ``(response, status,
headers)`` or ``(response, headers)``
where `response` is any of the
types defined here, `status` is a string
or an integer and `headers` is a list or
a dictionary with header values.
======================= ===========================================
:param rv: the return value from the view function
``str`` (``unicode`` in Python 2)
A response object is created with the string encoded to UTF-8
as the body.
``bytes`` (``str`` in Python 2)
A response object is created with the bytes as the body.
``tuple``
Either ``(body, status, headers)``, ``(body, status)``, or
``(body, headers)``, where ``body`` is any of the other types
allowed here, ``status`` is a string or an integer, and
``headers`` is a dictionary or a list of ``(key, value)``
tuples. If ``body`` is a :attr:`response_class` instance,
``status`` overwrites the exiting value and ``headers`` are
extended.
:attr:`response_class`
The object is returned unchanged.
other :class:`~werkzeug.wrappers.Response` class
The object is coerced to :attr:`response_class`.
:func:`callable`
The function is called as a WSGI application. The result is
used to create a response object.
.. versionchanged:: 0.9
Previously a tuple was interpreted as the arguments for the
response object.
"""
status_or_headers = headers = None
if isinstance(rv, tuple):
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
status = headers = None
# unpack tuple returns
if isinstance(rv, (tuple, list)):
len_rv = len(rv)
# a 3-tuple is unpacked directly
if len_rv == 3:
rv, status, headers = rv
# decide if a 2-tuple has status or headers
elif len_rv == 2:
if isinstance(rv[1], (Headers, dict, tuple, list)):
rv, headers = rv
else:
rv, status = rv
# other sized tuples are not allowed
else:
raise TypeError(
'The view function did not return a valid response tuple.'
' The tuple must have the form (body, status, headers),'
' (body, status), or (body, headers).'
)
# the body must not be None
if rv is None:
raise ValueError('View function did not return a response')
if isinstance(status_or_headers, (dict, list)):
headers, status_or_headers = status_or_headers, None
raise TypeError(
'The view function did not return a valid response. The'
' function either returned None or ended without a return'
' statement.'
)
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
# When we create a response object directly, we let the constructor
# set the headers and status. We do this because there can be
# some extra logic involved when creating these objects with
# specific values (like default content type selection).
if isinstance(rv, (text_type, bytes, bytearray)):
rv = self.response_class(rv, headers=headers,
status=status_or_headers)
headers = status_or_headers = None
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
rv = self.response_class(rv, status=status, headers=headers)
status = headers = None
else:
rv = self.response_class.force_type(rv, request.environ)
# evaluate a WSGI callable, or coerce a different response
# class to the correct type
try:
rv = self.response_class.force_type(rv, request.environ)
except TypeError as e:
new_error = TypeError(
'{e}\nThe view function did not return a valid'
' response. The return type must be a string, tuple,'
' Response instance, or WSGI callable, but it was a'
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
)
reraise(TypeError, new_error, sys.exc_info()[2])
if status_or_headers is not None:
if isinstance(status_or_headers, string_types):
rv.status = status_or_headers
# prefer the status if it was provided
if status is not None:
if isinstance(status, (text_type, bytes, bytearray)):
rv.status = status
else:
rv.status_code = status_or_headers
rv.status_code = status
# extend existing headers with provided headers
if headers:
rv.headers.extend(headers)
@ -1962,14 +2048,17 @@ class Flask(_PackageBoundObject):
exception context to start the response
"""
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):

View file

@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
warn_on_modifications = False
_got_registered_once = False
#: Blueprint local JSON decoder class to use.
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
json_encoder = None
#: Blueprint local JSON decoder class to use.
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
json_decoder = None
def __init__(self, name, import_name, static_folder=None,
static_url_path=None, template_folder=None,
url_prefix=None, subdomain=None, url_defaults=None,
@ -407,7 +414,7 @@ class Blueprint(_PackageBoundObject):
application-wide function of the :class:`~flask.Flask` object but
for error handlers limited to this blueprint.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
self.record_once(lambda s: s.app._register_error_handler(
self.name, code_or_exception, f))

View file

@ -11,13 +11,15 @@
import os
import sys
import traceback
from threading import Lock, Thread
from functools import update_wrapper
import click
from ._compat import iteritems, reraise
from .helpers import get_debug_flag
from . import __version__
class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded."""
@ -54,10 +56,10 @@ def prepare_exec_for_file(filename):
module = []
# Chop off file extensions or package markers
if filename.endswith('.py'):
filename = filename[:-3]
elif os.path.split(filename)[1] == '__init__.py':
if os.path.split(filename)[1] == '__init__.py':
filename = os.path.dirname(filename)
elif filename.endswith('.py'):
filename = filename[:-3]
else:
raise NoAppException('The file provided (%s) does exist but is not a '
'valid Python file. This means that it cannot '
@ -85,7 +87,21 @@ def locate_app(app_id):
module = app_id
app_obj = None
__import__(module)
try:
__import__(module)
except ImportError:
# Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[-1].tb_next:
stack_trace = traceback.format_exc()
raise NoAppException('There was an error trying to import'
' the app (%s):\n%s' % (module, stack_trace))
else:
raise NoAppException('The file/path provided (%s) does not appear'
' to exist. Please verify the path is '
'correct. If app is not on PYTHONPATH, '
'ensure the extension is .py' % module)
mod = sys.modules[module]
if app_obj is None:
app = find_best_app(mod)
@ -98,10 +114,35 @@ def locate_app(app_id):
return app
def find_default_import_path():
app = os.environ.get('FLASK_APP')
if app is None:
return
if os.path.isfile(app):
return prepare_exec_for_file(app)
return app
def get_version(ctx, param, value):
if not value or ctx.resilient_parsing:
return
message = 'Flask %(version)s\nPython %(python_version)s'
click.echo(message % {
'version': __version__,
'python_version': sys.version,
}, color=ctx.color)
ctx.exit()
version_option = click.Option(['--version'],
help='Show the flask version',
expose_value=False,
callback=get_version,
is_flag=True, is_eager=True)
class DispatchingApp(object):
"""Special application that dispatches to a flask application which
"""Special application that dispatches to a Flask application which
is imported by name in a background thread. If an error happens
it is is recorded and shows as part of the WSGI handling which in case
it is recorded and shown as part of the WSGI handling which in case
of the Werkzeug debugger means that it shows up in the browser.
"""
@ -155,15 +196,22 @@ class DispatchingApp(object):
class ScriptInfo(object):
"""Help object to deal with Flask applications. This is usually not
necessary to interface with as it's used internally in the dispatching
to click.
to click. In future versions of Flask this object will most likely play
a bigger role. Typically it's created automatically by the
:class:`FlaskGroup` but you can also manually create it and pass it
onwards as click object.
"""
def __init__(self, app_import_path=None, debug=None, create_app=None):
#: The application import path
def __init__(self, app_import_path=None, create_app=None):
if create_app is None:
if app_import_path is None:
app_import_path = find_default_import_path()
self.app_import_path = app_import_path
else:
app_import_path = None
#: Optionally the import path for the Flask application.
self.app_import_path = app_import_path
#: The debug flag. If this is not None, the application will
#: automatically have it's debug flag overridden with this value.
self.debug = debug
#: Optionally a function that is passed the script info to create
#: the instance of the application.
self.create_app = create_app
@ -183,13 +231,16 @@ class ScriptInfo(object):
if self.create_app is not None:
rv = self.create_app(self)
else:
if self.app_import_path is None:
raise NoAppException('Could not locate Flask application. '
'You did not provide FLASK_APP or the '
'--app parameter.')
if not self.app_import_path:
raise NoAppException(
'Could not locate Flask application. You did not provide '
'the FLASK_APP environment variable.\n\nFor more '
'information see '
'http://flask.pocoo.org/docs/latest/quickstart/')
rv = locate_app(self.app_import_path)
if self.debug is not None:
rv.debug = self.debug
debug = get_debug_flag()
if debug is not None:
rv.debug = debug
self._loaded_app = rv
return rv
@ -210,29 +261,6 @@ def with_appcontext(f):
return update_wrapper(decorator, f)
def set_debug_value(ctx, param, value):
ctx.ensure_object(ScriptInfo).debug = value
def set_app_value(ctx, param, value):
if value is not None:
if os.path.isfile(value):
value = prepare_exec_for_file(value)
elif '.' not in sys.path:
sys.path.insert(0, '.')
ctx.ensure_object(ScriptInfo).app_import_path = value
debug_option = click.Option(['--debug/--no-debug'],
help='Enable or disable debug mode.',
default=None, callback=set_debug_value)
app_option = click.Option(['-a', '--app'],
help='The application to run',
callback=set_app_value, is_eager=True)
class AppGroup(click.Group):
"""This works similar to a regular click :class:`~click.Group` but it
changes the behavior of the :meth:`command` decorator so that it
@ -273,23 +301,17 @@ class FlaskGroup(AppGroup):
:param add_default_commands: if this is True then the default run and
shell commands wil be added.
:param add_app_option: adds the default :option:`--app` option. This gets
automatically disabled if a `create_app`
callback is defined.
:param add_debug_option: adds the default :option:`--debug` option.
:param add_version_option: adds the ``--version`` option.
:param create_app: an optional callback that is passed the script info
and returns the loaded app.
"""
def __init__(self, add_default_commands=True, add_app_option=None,
add_debug_option=True, create_app=None, **extra):
def __init__(self, add_default_commands=True, create_app=None,
add_version_option=True, **extra):
params = list(extra.pop('params', None) or ())
if add_app_option is None:
add_app_option = create_app is None
if add_app_option:
params.append(app_option)
if add_debug_option:
params.append(debug_option)
if add_version_option:
params.append(version_option)
AppGroup.__init__(self, params=params, **extra)
self.create_app = create_app
@ -298,7 +320,24 @@ class FlaskGroup(AppGroup):
self.add_command(run_command)
self.add_command(shell_command)
self._loaded_plugin_commands = False
def _load_plugin_commands(self):
if self._loaded_plugin_commands:
return
try:
import pkg_resources
except ImportError:
self._loaded_plugin_commands = True
return
for ep in pkg_resources.iter_entry_points('flask.commands'):
self.add_command(ep.load(), ep.name)
self._loaded_plugin_commands = True
def get_command(self, ctx, name):
self._load_plugin_commands()
# We load built-in commands first as these should always be the
# same no matter what the app does. If the app does want to
# override this it needs to make a custom instance of this group
@ -319,6 +358,8 @@ class FlaskGroup(AppGroup):
pass
def list_commands(self, ctx):
self._load_plugin_commands()
# The commands available is the list of both the application (if
# available) plus the builtin commands.
rv = set(click.Group.list_commands(self, ctx))
@ -330,7 +371,9 @@ class FlaskGroup(AppGroup):
# want the help page to break if the app does not exist.
# If someone attempts to use the command we try to create
# the app again and this will give us the error.
pass
# However, we will not do so silently because that would confuse
# users.
traceback.print_exc()
return sorted(rv)
def main(self, *args, **kwargs):
@ -342,33 +385,6 @@ class FlaskGroup(AppGroup):
return AppGroup.main(self, *args, **kwargs)
def script_info_option(*args, **kwargs):
"""This decorator works exactly like :func:`click.option` but is eager
by default and stores the value in the :attr:`ScriptInfo.data`. This
is useful to further customize an application factory in very complex
situations.
:param script_info_key: this is a mandatory keyword argument which
defines under which data key the value should
be stored.
"""
try:
key = kwargs.pop('script_info_key')
except LookupError:
raise TypeError('script_info_key not provided.')
real_callback = kwargs.get('callback')
def callback(ctx, param, value):
if real_callback is not None:
value = real_callback(ctx, value)
ctx.ensure_object(ScriptInfo).data[key] = value
return value
kwargs['callback'] = callback
kwargs.setdefault('is_eager', True)
return click.option(*args, **kwargs)
@click.command('run', short_help='Runs a development server.')
@click.option('--host', '-h', default='127.0.0.1',
help='The interface to bind to.')
@ -400,16 +416,25 @@ def run_command(info, host, port, reload, debugger, eager_loading,
Flask is enabled and disabled otherwise.
"""
from werkzeug.serving import run_simple
# Set a global flag that indicates that we were invoked from the
# command line interface provided server command. This is detected
# by Flask.run to make the call into a no-op. This is necessary to
# avoid ugly errors when the script that is loaded here also attempts
# to start a server.
os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1'
debug = get_debug_flag()
if reload is None:
reload = info.debug
reload = bool(debug)
if debugger is None:
debugger = info.debug
debugger = bool(debug)
if eager_loading is None:
eager_loading = not reload
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
# Extra startup messages. This depends a but on Werkzeug internals to
# Extra startup messages. This depends a bit on Werkzeug internals to
# not double execute when the reloader kicks in.
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
# If we have an import path we can print it out now which can help
@ -418,8 +443,8 @@ def run_command(info, host, port, reload, debugger, eager_loading,
# we won't print anything.
if info.app_import_path is not None:
print(' * Serving Flask app "%s"' % info.app_import_path)
if info.debug is not None:
print(' * Forcing debug %s' % (info.debug and 'on' or 'off'))
if debug is not None:
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
run_simple(host, port, app, use_reloader=reload,
use_debugger=debugger, threaded=with_threads)
@ -462,16 +487,22 @@ def shell_command():
cli = FlaskGroup(help="""\
This shell command acts as general utility script for Flask applications.
It loads the application configured (either through the FLASK_APP environment
variable or the --app parameter) and then provides commands either provided
by the application or Flask itself.
It loads the application configured (through the FLASK_APP environment
variable) and then provides commands either provided by the application or
Flask itself.
The most useful commands are the "run" and "shell" command.
Example usage:
flask --app=hello --debug run
""")
\b
%(prefix)s%(cmd)s FLASK_APP=hello.py
%(prefix)s%(cmd)s FLASK_DEBUG=1
%(prefix)sflask run
""" % {
'cmd': os.name == 'posix' and 'export' or 'set',
'prefix': os.name == 'posix' and '$ ' or '',
})
def main(as_module=False):

View file

@ -126,7 +126,7 @@ class Config(dict):
d = types.ModuleType('config')
d.__file__ = filename
try:
with open(filename) as config_file:
with open(filename, mode='rb') as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
@ -143,10 +143,12 @@ class Config(dict):
- a string: in this case the object with that name will be imported
- an actual object reference: that object is used directly
Objects are usually either modules or classes.
Objects are usually either modules or classes. :meth:`from_object`
loads only the uppercase attributes of the module/class. A ``dict``
object will not work with :meth:`from_object` because the keys of a
``dict`` are not attributes of the ``dict`` class.
Just the uppercase variables in that object are stored in the config.
Example usage::
Example of module-based configuration::
app.config.from_object('yourapplication.default_config')
from yourapplication import default_config
@ -157,6 +159,9 @@ class Config(dict):
with :meth:`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide.
See :ref:`config-dev-prod` for an example of class-based configuration
using :meth:`from_object`.
:param obj: an import name or object
"""
if isinstance(obj, string_types):
@ -176,7 +181,7 @@ class Config(dict):
:param silent: set to ``True`` if you want silent failure for missing
files.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
filename = os.path.join(self.root_path, filename)
@ -194,7 +199,7 @@ class Config(dict):
"""Updates the config like :meth:`update` ignoring items with non-upper
keys.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
mappings = []
if len(mapping) == 1:
@ -222,7 +227,7 @@ class Config(dict):
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
image_store_config = app.config.get_namespace('IMAGE_STORE_')
The resulting dictionary `image_store` would look like::
The resulting dictionary `image_store_config` would look like::
{
'type': 'fs',
@ -239,7 +244,7 @@ class Config(dict):
:param trim_namespace: a flag indicating if the keys of the resulting
dictionary should not include the namespace
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
rv = {}
for k, v in iteritems(self):

View file

@ -36,7 +36,7 @@ class _AppCtxGlobals(object):
return self.__dict__.pop(name, default)
def setdefault(self, name, default=None):
self.__dict__.setdefault(name, default)
return self.__dict__.setdefault(name, default)
def __contains__(self, item):
return item in self.__dict__
@ -181,12 +181,14 @@ class AppContext(object):
def pop(self, exc=_sentinel):
"""Pops the app context."""
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop()
try:
self._refcnt -= 1
if self._refcnt <= 0:
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
finally:
rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self)
appcontext_popped.send(self.app)
@ -341,38 +343,40 @@ class RequestContext(object):
"""
app_ctx = self._implicit_app_ctx_stack.pop()
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
try:
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
# If this interpreter supports clearing the exception information
# we do that now. This will only go into effect on Python 2.x,
# on 3.x it disappears automatically at the end of the exception
# stack.
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
# If this interpreter supports clearing the exception information
# we do that now. This will only go into effect on Python 2.x,
# on 3.x it disappears automatically at the end of the exception
# stack.
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
request_close = getattr(self.request, 'close', None)
if request_close is not None:
request_close()
clear_request = True
request_close = getattr(self.request, 'close', None)
if request_close is not None:
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop()
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ['werkzeug.request'] = None
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ['werkzeug.request'] = None
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
assert rv is self, 'Popped wrong request context. ' \
'(%r instead of %r)' % (rv, self)
def auto_pop(self, exc):
if self.request.environ.get('flask._preserve_context') or \

View file

@ -8,6 +8,9 @@
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
from warnings import warn
from ._compat import implements_to_string, text_type
from .app import Flask
from .blueprints import Blueprint
@ -153,3 +156,12 @@ def explain_template_loading_attempts(app, template, attempts):
info.append(' See http://flask.pocoo.org/docs/blueprints/#templates')
app.logger.info('\n'.join(info))
def explain_ignored_app_run():
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
warn(Warning('Silently ignoring app.run() because the '
'application is run from the flask command line '
'executable. Consider putting app.run() behind an '
'if __name__ == "__main__" guard to silence this '
'warning.'), stacklevel=3)

View file

@ -21,9 +21,16 @@
"""
import sys
import os
import warnings
from ._compat import reraise
class ExtDeprecationWarning(DeprecationWarning):
pass
warnings.simplefilter('always', ExtDeprecationWarning)
class ExtensionImporter(object):
"""This importer redirects imports from this submodule to other locations.
This makes it possible to transition from the old flaskext.name to the
@ -49,13 +56,21 @@ class ExtensionImporter(object):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
def find_module(self, fullname, path=None):
if fullname.startswith(self.prefix):
if fullname.startswith(self.prefix) and \
fullname != 'flask.ext.ExtDeprecationWarning':
return self
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
warnings.warn(
"Importing flask.ext.{x} is deprecated, use flask_{x} instead."
.format(x=modname), ExtDeprecationWarning, stacklevel=2
)
for path in self.module_choices:
realname = path % modname
try:
@ -83,6 +98,14 @@ class ExtensionImporter(object):
module = sys.modules[fullname] = sys.modules[realname]
if '.' not in modname:
setattr(sys.modules[self.wrapper_module], modname, module)
if realname.startswith('flaskext.'):
warnings.warn(
"Detected extension named flaskext.{x}, please rename it "
"to flask_{x}. The old form is deprecated."
.format(x=modname), ExtDeprecationWarning
)
return module
raise ImportError('No module named %s' % fullname)

View file

@ -14,10 +14,10 @@ import sys
import pkgutil
import posixpath
import mimetypes
from datetime import timedelta
from time import time
from zlib import adler32
from threading import RLock
import unicodedata
from werkzeug.routing import BuildError
from functools import update_wrapper
@ -26,8 +26,9 @@ try:
except ImportError:
from urlparse import quote as url_quote
from werkzeug.datastructures import Headers
from werkzeug.exceptions import NotFound
from werkzeug.datastructures import Headers, Range
from werkzeug.exceptions import BadRequest, NotFound, \
RequestedRangeNotSatisfiable
# this was moved in 0.7
try:
@ -54,6 +55,13 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
if sep not in (None, '/'))
def get_debug_flag(default=None):
val = os.environ.get('FLASK_DEBUG')
if not val:
return default
return val.lower() not in ('0', 'false', 'no')
def _endpoint_from_view_func(view_func):
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
@ -101,7 +109,7 @@ def stream_with_context(generator_or_function):
gen = iter(generator_or_function)
except TypeError:
def decorator(*args, **kwargs):
gen = generator_or_function()
gen = generator_or_function(*args, **kwargs)
return stream_with_context(gen)
return update_wrapper(decorator, generator_or_function)
@ -300,20 +308,30 @@ def url_for(endpoint, **values):
scheme = values.pop('_scheme', None)
appctx.app.inject_url_defaults(endpoint, values)
# This is not the best way to deal with this but currently the
# underlying Werkzeug router does not support overriding the scheme on
# a per build call basis.
old_scheme = None
if scheme is not None:
if not external:
raise ValueError('When specifying _scheme, _external must be True')
old_scheme = url_adapter.url_scheme
url_adapter.url_scheme = scheme
try:
rv = url_adapter.build(endpoint, values, method=method,
force_external=external)
try:
rv = url_adapter.build(endpoint, values, method=method,
force_external=external)
finally:
if old_scheme is not None:
url_adapter.url_scheme = old_scheme
except BuildError as error:
# We need to inject the values again so that the app callback can
# deal with that sort of stuff.
values['_external'] = external
values['_anchor'] = anchor
values['_method'] = method
values['_scheme'] = scheme
return appctx.app.handle_url_build_error(error, endpoint, values)
if anchor is not None:
@ -414,7 +432,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]):
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
attachment_filename=None, add_etags=True,
cache_timeout=None, conditional=False):
cache_timeout=None, conditional=False, last_modified=None):
"""Sends the contents of a file to the client. This will use the
most efficient method available and configured. By default it will
try to use the WSGI server's file_wrapper support. Alternatively
@ -428,6 +446,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
guessing requires a `filename` or an `attachment_filename` to be
provided.
ETags will also be attached automatically if a `filename` is provided. You
can turn this off by setting `add_etags=False`.
If `conditional=True` and `filename` is provided, this method will try to
upgrade the response stream to support range requests. This will allow
the request to be answered with partial content response.
Please never pass filenames to this function from user sources;
you should use :func:`send_from_directory` instead.
@ -446,7 +471,21 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
.. versionchanged:: 0.9
cache_timeout pulls its default from application config, when None.
:param filename_or_fp: the filename of the file to send in `latin-1`.
.. versionchanged:: 0.12
The filename is no longer automatically inferred from file objects. If
you want to use automatic mimetype and etag support, pass a filepath via
`filename_or_fp` or `attachment_filename`.
.. versionchanged:: 0.12
The `attachment_filename` is preferred over `filename` for MIME-type
detection.
.. versionchanged:: 0.13
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
:param filename_or_fp: the filename of the file to send.
This is relative to the :attr:`~Flask.root_path`
if a relative path is specified.
Alternatively a file object might be provided in
@ -454,8 +493,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
back to the traditional method. Make sure that the
file pointer is positioned at the start of data to
send before calling :func:`send_file`.
:param mimetype: the mimetype of the file if provided, otherwise
auto detection happens.
:param mimetype: the mimetype of the file if provided. If a file path is
given, auto detection happens as fallback, otherwise an
error will be raised.
:param as_attachment: set to ``True`` if you want to send this file with
a ``Content-Disposition: attachment`` header.
:param attachment_filename: the filename for the attachment if it
@ -467,69 +507,76 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
(default), this value is set by
:meth:`~Flask.get_send_file_max_age` of
:data:`~flask.current_app`.
:param last_modified: set the ``Last-Modified`` header to this value,
a :class:`~datetime.datetime` or timestamp.
If a file was passed, this overrides its mtime.
"""
mtime = None
fsize = None
if isinstance(filename_or_fp, string_types):
filename = filename_or_fp
file = None
else:
from warnings import warn
file = filename_or_fp
filename = getattr(file, 'name', None)
# XXX: this behavior is now deprecated because it was unreliable.
# removed in Flask 1.0
if not attachment_filename and not mimetype \
and isinstance(filename, string_types):
warn(DeprecationWarning('The filename support for file objects '
'passed to send_file is now deprecated. Pass an '
'attach_filename if you want mimetypes to be guessed.'),
stacklevel=2)
if add_etags:
warn(DeprecationWarning('In future flask releases etags will no '
'longer be generated for file objects passed to the send_file '
'function because this behavior was unreliable. Pass '
'filenames instead if possible, otherwise attach an etag '
'yourself based on another value'), stacklevel=2)
if filename is not None:
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
if mimetype is None and (filename or attachment_filename):
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
file = None
if attachment_filename is None:
attachment_filename = os.path.basename(filename)
else:
file = filename_or_fp
filename = None
if mimetype is None:
mimetype = 'application/octet-stream'
if attachment_filename is not None:
mimetype = mimetypes.guess_type(attachment_filename)[0] \
or 'application/octet-stream'
if mimetype is None:
raise ValueError(
'Unable to infer MIME-type because no filename is available. '
'Please set either `attachment_filename`, pass a filepath to '
'`filename_or_fp` or set your own MIME-type via `mimetype`.'
)
headers = Headers()
if as_attachment:
if attachment_filename is None:
if filename is None:
raise TypeError('filename unavailable, required for '
'sending as attachment')
attachment_filename = os.path.basename(filename)
headers.add('Content-Disposition', 'attachment',
filename=attachment_filename)
raise TypeError('filename unavailable, required for '
'sending as attachment')
try:
attachment_filename = attachment_filename.encode('latin-1')
except UnicodeEncodeError:
filenames = {
'filename': unicodedata.normalize(
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
}
else:
filenames = {'filename': attachment_filename}
headers.add('Content-Disposition', 'attachment', **filenames)
if current_app.use_x_sendfile and filename:
if file is not None:
file.close()
headers['X-Sendfile'] = filename
headers['Content-Length'] = os.path.getsize(filename)
fsize = os.path.getsize(filename)
headers['Content-Length'] = fsize
data = None
else:
if file is None:
file = open(filename, 'rb')
mtime = os.path.getmtime(filename)
headers['Content-Length'] = os.path.getsize(filename)
fsize = os.path.getsize(filename)
headers['Content-Length'] = fsize
data = wrap_file(request.environ, file)
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
direct_passthrough=True)
# if we know the file modification date, we can store it as
# the time of the last modification.
if mtime is not None:
rv.last_modified = int(mtime)
if last_modified is not None:
rv.last_modified = last_modified
elif mtime is not None:
rv.last_modified = mtime
rv.cache_control.public = True
if cache_timeout is None:
@ -539,8 +586,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
rv.expires = int(time() + cache_timeout)
if add_etags and filename is not None:
from warnings import warn
try:
rv.set_etag('flask-%s-%s-%s' % (
rv.set_etag('%s-%s-%s' % (
os.path.getmtime(filename),
os.path.getsize(filename),
adler32(
@ -552,17 +601,28 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
warn('Access %s failed, maybe it does not exist, so ignore etags in '
'headers' % filename, stacklevel=2)
if conditional:
if conditional:
if callable(getattr(Range, 'to_content_range_header', None)):
# Werkzeug supports Range Requests
# Remove this test when support for Werkzeug <0.12 is dropped
try:
rv = rv.make_conditional(request, accept_ranges=True,
complete_length=fsize)
except RequestedRangeNotSatisfiable:
file.close()
raise
else:
rv = rv.make_conditional(request)
# make sure we don't send x-sendfile for servers that
# ignore the 304 status code for x-sendfile.
if rv.status_code == 304:
rv.headers.pop('x-sendfile', None)
# make sure we don't send x-sendfile for servers that
# ignore the 304 status code for x-sendfile.
if rv.status_code == 304:
rv.headers.pop('x-sendfile', None)
return rv
def safe_join(directory, filename):
"""Safely join `directory` and `filename`.
def safe_join(directory, *pathnames):
"""Safely join `directory` and zero or more untrusted `pathnames`
components.
Example usage::
@ -572,20 +632,23 @@ def safe_join(directory, filename):
with open(filename, 'rb') as fd:
content = fd.read() # Read and process the file content...
:param directory: the base directory.
:param filename: the untrusted filename relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path
would fall out of `directory`.
:param directory: the trusted base directory.
:param pathnames: the untrusted pathnames relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
paths fall out of its boundaries.
"""
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
for filename in pathnames:
if filename != '':
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound()
return os.path.join(directory, filename)
directory = os.path.join(directory, filename)
return directory
def send_from_directory(directory, filename, **options):
@ -618,8 +681,11 @@ def send_from_directory(directory, filename, **options):
filename = safe_join(directory, filename)
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
if not os.path.isfile(filename):
raise NotFound()
try:
if not os.path.isfile(filename):
raise NotFound()
except (TypeError, ValueError):
raise BadRequest()
options.setdefault('conditional', True)
return send_file(filename, **options)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
flask.jsonimpl
~~~~~~~~~~~~~~
flask.json
~~~~~~~~~~
Implementation helpers for the JSON support in Flask.
@ -13,16 +13,14 @@ import uuid
from datetime import date
from .globals import current_app, request
from ._compat import text_type, PY2
from .ctx import has_request_context
from werkzeug.http import http_date
from jinja2 import Markup
# Use the same json implementation as itsdangerous on which we
# depend anyways.
try:
from itsdangerous import simplejson as _json
except ImportError:
from itsdangerous import json as _json
from itsdangerous import json as _json
# Figure out if simplejson escapes slashes. This behavior was changed
@ -94,9 +92,16 @@ class JSONDecoder(_json.JSONDecoder):
def _dump_arg_defaults(kwargs):
"""Inject default arguments for dump functions."""
if current_app:
kwargs.setdefault('cls', current_app.json_encoder)
bp = current_app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault(
'cls',
bp.json_encoder if bp and bp.json_encoder
else current_app.json_encoder
)
if not current_app.config['JSON_AS_ASCII']:
kwargs.setdefault('ensure_ascii', False)
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
else:
kwargs.setdefault('sort_keys', True)
@ -106,7 +111,12 @@ def _dump_arg_defaults(kwargs):
def _load_arg_defaults(kwargs):
"""Inject default arguments for load functions."""
if current_app:
kwargs.setdefault('cls', current_app.json_decoder)
bp = current_app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault(
'cls',
bp.json_decoder if bp and bp.json_decoder
else current_app.json_decoder
)
else:
kwargs.setdefault('cls', JSONDecoder)
@ -195,14 +205,26 @@ def htmlsafe_dumps(obj, **kwargs):
def htmlsafe_dump(obj, fp, **kwargs):
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
fp.write(unicode(htmlsafe_dumps(obj, **kwargs)))
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
def jsonify(*args, **kwargs):
"""Creates a :class:`~flask.Response` with the JSON representation of
the given arguments with an :mimetype:`application/json` mimetype. The
arguments to this function are the same as to the :class:`dict`
constructor.
"""This function wraps :func:`dumps` to add a few enhancements that make
life easier. It turns the JSON output into a :class:`~flask.Response`
object with the :mimetype:`application/json` mimetype. For convenience, it
also converts multiple arguments into an array or multiple keyword arguments
into a dict. This means that both ``jsonify(1,2,3)`` and
``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
For clarity, the JSON serialization behavior has the following differences
from :func:`dumps`:
1. Single argument: Passed straight through to :func:`dumps`.
2. Multiple arguments: Converted to an array before being passed to
:func:`dumps`.
3. Multiple keyword arguments: Converted to a dict before being passed to
:func:`dumps`.
4. Both args and kwargs: Behavior undefined and will throw an exception.
Example usage::
@ -222,14 +244,15 @@ def jsonify(*args, **kwargs):
"id": 42
}
For security reasons only objects are supported toplevel. For more
information about this, have a look at :ref:`json-security`.
This function's response will be pretty printed if it was not requested
with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless
the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false.
Compressed (not pretty) formatting currently means no indents and no
spaces after separators.
.. versionchanged:: 0.11
Added support for serializing top-level arrays. This introduces a
security risk in ancient browsers. See :ref:`json-security` for details.
This function's response will be pretty printed if the
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
Flask app is running in debug mode. Compressed (not pretty) formatting
currently means no indents and no spaces after separators.
.. versionadded:: 0.2
"""
@ -237,18 +260,21 @@ def jsonify(*args, **kwargs):
indent = None
separators = (',', ':')
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] \
and not request.is_xhr:
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
indent = 2
separators = (', ', ': ')
# Note that we add '\n' to end of response
# (see https://github.com/mitsuhiko/flask/pull/1262)
rv = current_app.response_class(
(dumps(dict(*args, **kwargs), indent=indent, separators=separators),
'\n'),
mimetype='application/json')
return rv
if args and kwargs:
raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
elif len(args) == 1: # single args are passed directly to dumps()
data = args[0]
else:
data = args or kwargs
return current_app.response_class(
(dumps(data, indent=indent, separators=separators), '\n'),
mimetype=current_app.config['JSONIFY_MIMETYPE']
)
def tojson_filter(obj, **kwargs):

View file

@ -87,4 +87,8 @@ def create_logger(app):
logger.__class__ = DebugLogger
logger.addHandler(debug_handler)
logger.addHandler(prod_handler)
# Disable propagation by default
logger.propagate = False
return logger

View file

@ -84,21 +84,25 @@ class TaggedJSONSerializer(object):
def dumps(self, value):
return json.dumps(_tag(value), separators=(',', ':'))
LOADS_MAP = {
' t': tuple,
' u': uuid.UUID,
' b': b64decode,
' m': Markup,
' d': parse_date,
}
def loads(self, value):
def object_hook(obj):
if len(obj) != 1:
return obj
the_key, the_value = next(iteritems(obj))
if the_key == ' t':
return tuple(the_value)
elif the_key == ' u':
return uuid.UUID(the_value)
elif the_key == ' b':
return b64decode(the_value)
elif the_key == ' m':
return Markup(the_value)
elif the_key == ' d':
return parse_date(the_value)
# Check the key for a corresponding function
return_function = self.LOADS_MAP.get(the_key)
if return_function:
# Pass the value to the function
return return_function(the_value)
# Didn't find a function for this object
return obj
return json.loads(value, object_hook=object_hook)
@ -168,7 +172,7 @@ class SessionInterface(object):
null_session_class = NullSession
#: A flag that indicates if the session interface is pickle based.
#: This can be used by flask extensions to make a decision in regards
#: This can be used by Flask extensions to make a decision in regards
#: to how to deal with the session object.
#:
#: .. versionadded:: 0.10
@ -263,7 +267,7 @@ class SessionInterface(object):
This check is usually skipped if sessions get deleted.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
if session.modified:
return True

View file

@ -37,7 +37,7 @@ except ImportError:
temporarily_connected_to = connected_to = _fail
del _fail
# The namespace for code signals. If you are not flask code, do
# The namespace for code signals. If you are not Flask code, do
# not put signals in here. Create your own namespace instead.
_signals = Namespace()

View file

@ -52,27 +52,36 @@ class DispatchingJinjaLoader(BaseLoader):
self.app = app
def get_source(self, environment, template):
explain = self.app.config['EXPLAIN_TEMPLATE_LOADING']
if self.app.config['EXPLAIN_TEMPLATE_LOADING']:
return self._get_source_explained(environment, template)
return self._get_source_fast(environment, template)
def _get_source_explained(self, environment, template):
attempts = []
tmplrv = None
trv = None
for srcobj, loader in self._iter_loaders(template):
try:
rv = loader.get_source(environment, template)
if tmplrv is None:
tmplrv = rv
if not explain:
break
if trv is None:
trv = rv
except TemplateNotFound:
rv = None
attempts.append((loader, srcobj, rv))
if explain:
from .debughelpers import explain_template_loading_attempts
explain_template_loading_attempts(self.app, template, attempts)
from .debughelpers import explain_template_loading_attempts
explain_template_loading_attempts(self.app, template, attempts)
if tmplrv is not None:
return tmplrv
if trv is not None:
return trv
raise TemplateNotFound(template)
def _get_source_fast(self, environment, template):
for srcobj, loader in self._iter_loaders(template):
try:
return loader.get_source(environment, template)
except TemplateNotFound:
continue
raise TemplateNotFound(template)
def _iter_loaders(self, template):

View file

@ -10,6 +10,7 @@
:license: BSD, see LICENSE for more details.
"""
import werkzeug
from contextlib import contextmanager
from werkzeug.test import Client, EnvironBuilder
from flask import _request_ctx_stack
@ -39,19 +40,32 @@ def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has some knowledge about
how Flask works to defer the cleanup of the request context stack to the
end of a with body when used in a with statement. For general information
about how to use this class refer to :class:`werkzeug.test.Client`.
end of a ``with`` body when used in a ``with`` statement. For general
information about how to use this class refer to
:class:`werkzeug.test.Client`.
.. versionchanged:: 0.12
`app.test_client()` includes preset default environment, which can be
set after instantiation of the `app.test_client()` object in
`client.environ_base`.
Basic usage is outlined in the :ref:`testing` chapter.
"""
preserve_context = False
def __init__(self, *args, **kwargs):
super(FlaskClient, self).__init__(*args, **kwargs)
self.environ_base = {
"REMOTE_ADDR": "127.0.0.1",
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__
}
@contextmanager
def session_transaction(self, *args, **kwargs):
"""When used in combination with a with statement this opens a
"""When used in combination with a ``with`` statement this opens a
session transaction. This can be used to modify the session that
the test client uses. Once the with block is left the session is
the test client uses. Once the ``with`` block is left the session is
stored back.
::
@ -100,6 +114,7 @@ class FlaskClient(Client):
def open(self, *args, **kwargs):
kwargs.setdefault('environ_overrides', {}) \
['flask._preserve_context'] = self.preserve_context
kwargs.setdefault('environ_base', self.environ_base)
as_tuple = kwargs.pop('as_tuple', False)
buffered = kwargs.pop('buffered', False)

View file

@ -48,7 +48,7 @@ class View(object):
generated view function!
"""
#: A for which methods this pluggable view can handle.
#: A list of methods this view can handle.
methods = None
#: The canonical way to decorate class-based views is to decorate the
@ -103,33 +103,34 @@ class View(object):
class MethodViewType(type):
"""Metaclass for :class:`MethodView` that determines what methods the view
defines.
"""
def __init__(cls, name, bases, d):
super(MethodViewType, cls).__init__(name, bases, d)
def __new__(cls, name, bases, d):
rv = type.__new__(cls, name, bases, d)
if 'methods' not in d:
methods = set(rv.methods or [])
for key in d:
if key in http_method_funcs:
methods = set()
for key in http_method_funcs:
if hasattr(cls, key):
methods.add(key.upper())
# If we have no method at all in there we don't want to
# add a method list. (This is for instance the case for
# the base class or another subclass of a base method view
# that does not introduce new methods).
# If we have no method at all in there we don't want to add a
# method list. This is for instance the case for the base class
# or another subclass of a base method view that does not introduce
# new methods.
if methods:
rv.methods = sorted(methods)
return rv
cls.methods = methods
class MethodView(with_metaclass(MethodViewType, View)):
"""Like a regular class-based view but that dispatches requests to
particular methods. For instance if you implement a method called
:meth:`get` it means you will response to ``'GET'`` requests and
the :meth:`dispatch_request` implementation will automatically
forward your request to that. Also :attr:`options` is set for you
automatically::
"""A class-based view that dispatches request methods to the corresponding
class methods. For example, if you implement a ``get`` method, it will be
used to handle ``GET`` requests. ::
class CounterAPI(MethodView):
def get(self):
return session.get('counter', 0)
@ -139,11 +140,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
"""
def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None)
assert meth is not None, 'Unimplemented method %r' % request.method
return meth(*args, **kwargs)

View file

@ -113,7 +113,7 @@ class Request(RequestBase):
is considered to include JSON data if the mimetype is
:mimetype:`application/json` or :mimetype:`application/*+json`.
.. versionadded:: 1.0
.. versionadded:: 0.11
"""
mt = self.mimetype
if mt == 'application/json':
@ -123,11 +123,12 @@ class Request(RequestBase):
return False
def get_json(self, force=False, silent=False, cache=True):
"""Parses the incoming JSON request data and returns it. If
parsing fails the :meth:`on_json_loading_failed` method on the
request object will be invoked. By default this function will
only load the json data if the mimetype is :mimetype:`application/json`
but this can be overridden by the `force` parameter.
"""Parses the incoming JSON request data and returns it. By default
this function will return ``None`` if the mimetype is not
:mimetype:`application/json` but this can be overridden by the
``force`` parameter. If parsing fails the
:meth:`on_json_loading_failed` method on the request object will be
invoked.
:param force: if set to ``True`` the mimetype is ignored.
:param silent: if set to ``True`` this method will fail silently
@ -136,7 +137,8 @@ class Request(RequestBase):
on the request.
"""
rv = getattr(self, '_cached_json', _missing)
if rv is not _missing:
# We return cached JSON only when the cache is enabled.
if cache and rv is not _missing:
return rv
if not (force or self.is_json):