Merge branch 'master' into figome-multiple-inheritance
This commit is contained in:
commit
0d9d3d8f92
115 changed files with 1453 additions and 740 deletions
|
|
@ -10,7 +10,7 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
__version__ = '0.11.2-dev'
|
||||
__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
|
||||
|
||||
|
|
|
|||
133
flask/app.py
133
flask/app.py
|
|
@ -14,7 +14,6 @@ from threading import Lock
|
|||
from datetime import timedelta
|
||||
from itertools import chain
|
||||
from functools import update_wrapper
|
||||
from collections import deque
|
||||
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
||||
|
|
@ -124,6 +123,9 @@ class Flask(_PackageBoundObject):
|
|||
.. 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
|
||||
|
|
@ -131,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
|
||||
|
|
@ -315,7 +324,7 @@ 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,
|
||||
})
|
||||
|
|
@ -338,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,
|
||||
|
|
@ -392,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}
|
||||
|
||||
|
|
@ -519,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
|
||||
|
|
@ -814,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.
|
||||
|
|
@ -825,25 +839,31 @@ 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)
|
||||
options.setdefault('use_debugger', self.debug)
|
||||
options.setdefault('passthrough_errors', True)
|
||||
try:
|
||||
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
|
||||
|
||||
|
|
@ -877,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 ....')
|
||||
|
|
@ -960,7 +980,7 @@ class Flask(_PackageBoundObject):
|
|||
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.
|
||||
|
|
@ -1000,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
|
||||
|
|
@ -1029,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:
|
||||
|
|
@ -1116,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)
|
||||
|
|
@ -1154,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)
|
||||
|
|
@ -1348,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.
|
||||
|
|
@ -1437,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, {})
|
||||
|
|
@ -1556,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`
|
||||
|
|
@ -1624,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):
|
||||
|
|
@ -1973,7 +2009,10 @@ class Flask(_PackageBoundObject):
|
|||
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):
|
||||
|
|
|
|||
37
flask/cli.py
37
flask/cli.py
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from threading import Lock, Thread
|
||||
from functools import update_wrapper
|
||||
|
||||
|
|
@ -86,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)
|
||||
|
|
@ -125,9 +140,9 @@ version_option = click.Option(['--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.
|
||||
"""
|
||||
|
||||
|
|
@ -356,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):
|
||||
|
|
@ -400,6 +417,13 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|||
"""
|
||||
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 = bool(debug)
|
||||
|
|
@ -423,8 +447,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
|||
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
||||
|
||||
run_simple(host, port, app, use_reloader=reload,
|
||||
use_debugger=debugger, threaded=with_threads,
|
||||
passthrough_errors=True)
|
||||
use_debugger=debugger, threaded=with_threads)
|
||||
|
||||
|
||||
@click.command('shell', short_help='Runs a shell in the app context.')
|
||||
|
|
@ -464,7 +487,7 @@ 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
|
||||
It loads the application configured (through the FLASK_APP environment
|
||||
variable) and then provides commands either provided by the application or
|
||||
Flask itself.
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
122
flask/helpers.py
122
flask/helpers.py
|
|
@ -17,6 +17,7 @@ import mimetypes
|
|||
from time import time
|
||||
from zlib import adler32
|
||||
from threading import RLock
|
||||
import unicodedata
|
||||
from werkzeug.routing import BuildError
|
||||
from functools import update_wrapper
|
||||
|
||||
|
|
@ -25,8 +26,9 @@ try:
|
|||
except ImportError:
|
||||
from urlparse import quote as url_quote
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
from werkzeug.datastructures import Headers, Range
|
||||
from werkzeug.exceptions import BadRequest, NotFound, \
|
||||
RequestedRangeNotSatisfiable
|
||||
|
||||
# this was moved in 0.7
|
||||
try:
|
||||
|
|
@ -39,7 +41,7 @@ from jinja2 import FileSystemLoader
|
|||
from .signals import message_flashed
|
||||
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
|
||||
current_app, request
|
||||
from ._compat import string_types, text_type, PY2
|
||||
from ._compat import string_types, text_type
|
||||
|
||||
|
||||
# sentinel
|
||||
|
|
@ -57,7 +59,7 @@ def get_debug_flag(default=None):
|
|||
val = os.environ.get('FLASK_DEBUG')
|
||||
if not val:
|
||||
return default
|
||||
return val not in ('0', 'false', 'no')
|
||||
return val.lower() not in ('0', 'false', 'no')
|
||||
|
||||
|
||||
def _endpoint_from_view_func(view_func):
|
||||
|
|
@ -329,6 +331,7 @@ def url_for(endpoint, **values):
|
|||
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:
|
||||
|
|
@ -437,7 +440,18 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
to ``True`` to directly emit an ``X-Sendfile`` header. This however
|
||||
requires support of the underlying webserver for ``X-Sendfile``.
|
||||
|
||||
You must explicitly provide the mimetype for the filename or file object.
|
||||
By default it will try to guess the mimetype for you, but you can
|
||||
also explicitly provide one. For extra security you probably want
|
||||
to send certain files as attachment (HTML for instance). The mimetype
|
||||
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.
|
||||
|
|
@ -458,11 +472,20 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
cache_timeout pulls its default from application config, when None.
|
||||
|
||||
.. versionchanged:: 0.12
|
||||
mimetype guessing and etag support removed for file objects.
|
||||
If no mimetype or attachment_filename is provided, application/octet-stream
|
||||
will be used.
|
||||
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`.
|
||||
|
||||
:param filename_or_fp: the filename of the file to send in `latin-1`.
|
||||
.. 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
|
||||
|
|
@ -470,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
|
||||
|
|
@ -488,42 +512,62 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
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:
|
||||
file = filename_or_fp
|
||||
filename = getattr(file, 'name', None)
|
||||
|
||||
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,
|
||||
|
|
@ -541,7 +585,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
rv.cache_control.max_age = cache_timeout
|
||||
rv.expires = int(time() + cache_timeout)
|
||||
|
||||
if add_etags and filename is not None and file is None:
|
||||
if add_etags and filename is not None:
|
||||
from warnings import warn
|
||||
|
||||
try:
|
||||
|
|
@ -557,12 +601,22 @@ 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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.jsonimpl
|
||||
~~~~~~~~~~~~~~
|
||||
flask.json
|
||||
~~~~~~~~~~
|
||||
|
||||
Implementation helpers for the JSON support in Flask.
|
||||
|
||||
|
|
@ -236,11 +236,10 @@ def jsonify(*args, **kwargs):
|
|||
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 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.
|
||||
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
|
||||
"""
|
||||
|
|
@ -248,7 +247,7 @@ 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 = (', ', ': ')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -43,11 +44,23 @@ class FlaskClient(Client):
|
|||
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
|
||||
|
|
@ -101,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)
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class MethodViewType(type):
|
|||
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
|
||||
:meth:`get` it means it will respond to ``'GET'`` requests and
|
||||
the :meth:`dispatch_request` implementation will automatically
|
||||
forward your request to that. Also :attr:`options` is set for you
|
||||
automatically::
|
||||
|
|
|
|||
|
|
@ -137,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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue