Merge pull request #3894 from pallets/update-werkzeug

Update for recent changes in Pallets libraries
This commit is contained in:
David Lord 2021-02-01 19:20:20 -08:00 committed by GitHub
commit 9f7ac04aaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 59 additions and 79 deletions

View file

@ -49,7 +49,8 @@ Unreleased
implementations in ``werkzeug.utils``. :pr:`3828` implementations in ``werkzeug.utils``. :pr:`3828`
- Some ``send_file`` parameters have been renamed, the old names are - Some ``send_file`` parameters have been renamed, the old names are
deprecated. ``attachment_filename`` is renamed to ``download_name``. deprecated. ``attachment_filename`` is renamed to ``download_name``.
``cache_timeout`` is renamed to ``max_age``. :pr:`3828` ``cache_timeout`` is renamed to ``max_age``. ``add_etags`` is
renamed to ``etag``. :pr:`3828, 3883`
- ``send_file`` passes ``download_name`` even if - ``send_file`` passes ``download_name`` even if
``as_attachment=False`` by using ``Content-Disposition: inline``. ``as_attachment=False`` by using ``Content-Disposition: inline``.
:pr:`3828` :pr:`3828`

View file

@ -27,9 +27,9 @@ Incoming Request Data
--------------------- ---------------------
.. autoclass:: Request .. autoclass:: Request
:members: :members:
:inherited-members: :inherited-members:
:exclude-members: json_module :exclude-members: json_module
.. attribute:: request .. attribute:: request
@ -48,20 +48,9 @@ Response Objects
---------------- ----------------
.. autoclass:: flask.Response .. autoclass:: flask.Response
:members: set_cookie, max_cookie_size, data, mimetype, is_json, get_json :members:
:inherited-members:
.. attribute:: headers :exclude-members: json_module
A :class:`~werkzeug.datastructures.Headers` object representing the response headers.
.. attribute:: status
A string with a response status.
.. attribute:: status_code
The response status as integer.
Sessions Sessions
-------- --------

View file

@ -249,25 +249,11 @@ be passed an instance of ``InternalServerError``, not the original
unhandled error. unhandled error.
The original error is available as ``e.original_exception``. The original error is available as ``e.original_exception``.
Until Werkzeug 1.0.0, this attribute will only exist during unhandled
errors, use ``getattr`` to get access it for compatibility.
.. code-block:: python An error handler for "500 Internal Server Error" will be passed uncaught
exceptions in addition to explicit 500 errors. In debug mode, a handler
@app.errorhandler(InternalServerError) for "500 Internal Server Error" will not be used. Instead, the
def handle_500(e): interactive debugger will be shown.
original = getattr(e, "original_exception", None)
if original is None:
# direct 500 error, such as abort(500)
return render_template("500.html"), 500
# wrapped unhandled error
return render_template("500_unhandled.html", e=original), 500
An error handler for "500 Internal Server Error" will be passed uncaught exceptions in
addition to explicit 500 errors. In debug mode, a handler for "500 Internal Server Error" will not be used.
Instead, the interactive debugger will be shown.
Custom Error Pages Custom Error Pages

View file

@ -119,7 +119,7 @@ Notice that our test functions begin with the word `test`; this allows
By using ``client.get`` we can send an HTTP ``GET`` request to the By using ``client.get`` we can send an HTTP ``GET`` request to the
application with the given path. The return value will be a application with the given path. The return value will be a
:class:`~flask.Flask.response_class` object. We can now use the :class:`~flask.Flask.response_class` object. We can now use the
:attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect :attr:`~werkzeug.wrappers.Response.data` attribute to inspect
the return value (as string) from the application. the return value (as string) from the application.
In this case, we ensure that ``'No entries here so far'`` In this case, we ensure that ``'No entries here so far'``
is part of the output. is part of the output.

View file

@ -302,7 +302,7 @@ URL when the register view redirects to the login view.
:attr:`~Response.data` contains the body of the response as bytes. If :attr:`~Response.data` contains the body of the response as bytes. If
you expect a certain value to render on the page, check that it's in you expect a certain value to render on the page, check that it's in
``data``. Bytes must be compared to bytes. If you want to compare text, ``data``. Bytes must be compared to bytes. If you want to compare text,
use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>` use :meth:`get_data(as_text=True) <werkzeug.wrappers.Response.get_data>`
instead. instead.
``pytest.mark.parametrize`` tells Pytest to run the same test function ``pytest.mark.parametrize`` tells Pytest to run the same test function

View file

@ -1,3 +1,3 @@
from .cli import main from .cli import main
main(as_module=True) main()

View file

@ -16,7 +16,7 @@ from werkzeug.routing import Map
from werkzeug.routing import RequestRedirect from werkzeug.routing import RequestRedirect
from werkzeug.routing import RoutingException from werkzeug.routing import RoutingException
from werkzeug.routing import Rule from werkzeug.routing import Rule
from werkzeug.wrappers import BaseResponse from werkzeug.wrappers import Response as BaseResponse
from . import cli from . import cli
from . import json from . import json
@ -278,7 +278,7 @@ class Flask(Scaffold):
#: This is a ``dict`` instead of an ``ImmutableDict`` to allow #: This is a ``dict`` instead of an ``ImmutableDict`` to allow
#: easier configuration. #: easier configuration.
#: #:
jinja_options = {"extensions": ["jinja2.ext.autoescape", "jinja2.ext.with_"]} jinja_options = {}
#: Default configuration parameters. #: Default configuration parameters.
default_config = ImmutableDict( default_config = ImmutableDict(
@ -1386,17 +1386,10 @@ class Flask(Scaffold):
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
if isinstance(e, BadRequestKeyError): if isinstance(e, BadRequestKeyError) and (
if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]: self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]
e.show_exception = True ):
e.show_exception = True
# Werkzeug < 0.15 doesn't add the KeyError to the 400
# message, add it in manually.
# TODO: clean up once Werkzeug >= 0.15.5 is required
if e.args[0] not in e.get_description():
e.description = f"KeyError: {e.args[0]!r}"
elif not hasattr(BadRequestKeyError, "show_exception"):
e.args = ()
if isinstance(e, HTTPException) and not self.trap_http_exception(e): if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e) return self.handle_http_exception(e)
@ -1454,10 +1447,7 @@ class Flask(Scaffold):
raise e raise e
self.log_exception(exc_info) self.log_exception(exc_info)
server_error = InternalServerError() server_error = InternalServerError(original_exception=e)
# TODO: pass as param when Werkzeug>=1.0.0 is required
# TODO: also remove note about this from docstring and docs
server_error.original_exception = e
handler = self._find_error_handler(server_error) handler = self._find_error_handler(server_error)
if handler is not None: if handler is not None:

View file

@ -953,10 +953,10 @@ debug mode.
) )
def main(as_module=False): def main():
# TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed
cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None) cli.main(args=sys.argv[1:])
if __name__ == "__main__": if __name__ == "__main__":
main(as_module=True) main()

View file

@ -439,6 +439,8 @@ def get_flashed_messages(with_categories=False, category_filter=()):
def _prepare_send_file_kwargs( def _prepare_send_file_kwargs(
download_name=None, download_name=None,
attachment_filename=None, attachment_filename=None,
etag=None,
add_etags=None,
max_age=None, max_age=None,
cache_timeout=None, cache_timeout=None,
**kwargs, **kwargs,
@ -461,12 +463,22 @@ def _prepare_send_file_kwargs(
) )
max_age = cache_timeout max_age = cache_timeout
if add_etags is not None:
warnings.warn(
"The 'add_etags' parameter has been renamed to 'etag'. The old name will be"
" removed in Flask 2.1.",
DeprecationWarning,
stacklevel=3,
)
etag = add_etags
if max_age is None: if max_age is None:
max_age = current_app.get_send_file_max_age max_age = current_app.get_send_file_max_age
kwargs.update( kwargs.update(
environ=request.environ, environ=request.environ,
download_name=download_name, download_name=download_name,
etag=etag,
max_age=max_age, max_age=max_age,
use_x_sendfile=current_app.use_x_sendfile, use_x_sendfile=current_app.use_x_sendfile,
response_class=current_app.response_class, response_class=current_app.response_class,
@ -482,7 +494,8 @@ def send_file(
download_name=None, download_name=None,
attachment_filename=None, attachment_filename=None,
conditional=True, conditional=True,
add_etags=True, etag=True,
add_etags=None,
last_modified=None, last_modified=None,
max_age=None, max_age=None,
cache_timeout=None, cache_timeout=None,
@ -518,8 +531,8 @@ def send_file(
the file. Defaults to the passed file name. the file. Defaults to the passed file name.
:param conditional: Enable conditional and range responses based on :param conditional: Enable conditional and range responses based on
request headers. Requires passing a file path and ``environ``. request headers. Requires passing a file path and ``environ``.
:param add_etags: Calculate an ETag for the file. Requires passing a :param etag: Calculate an ETag for the file, which requires passing
file path. a file path. Can also be a string to use instead.
:param last_modified: The last modified time to send for the file, :param last_modified: The last modified time to send for the file,
in seconds. If not provided, it will try to detect it from the in seconds. If not provided, it will try to detect it from the
file path. file path.
@ -537,6 +550,10 @@ def send_file(
``conditional`` is enabled and ``max_age`` is not set by ``conditional`` is enabled and ``max_age`` is not set by
default. default.
.. versionchanged:: 2.0.0
``etag`` replaces the ``add_etags`` parameter. It can be a
string to use instead of generating one.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Passing a file-like object that inherits from Passing a file-like object that inherits from
:class:`~io.TextIOBase` will raise a :exc:`ValueError` rather :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather
@ -593,6 +610,7 @@ def send_file(
download_name=download_name, download_name=download_name,
attachment_filename=attachment_filename, attachment_filename=attachment_filename,
conditional=conditional, conditional=conditional,
etag=etag,
add_etags=add_etags, add_etags=add_etags,
last_modified=last_modified, last_modified=last_modified,
max_age=max_age, max_age=max_age,

View file

@ -5,7 +5,7 @@ import werkzeug.test
from click.testing import CliRunner from click.testing import CliRunner
from werkzeug.test import Client from werkzeug.test import Client
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
from werkzeug.wrappers import BaseRequest from werkzeug.wrappers import Request as BaseRequest
from . import _request_ctx_stack from . import _request_ctx_stack
from .cli import ScriptInfo from .cli import ScriptInfo

View file

@ -1,23 +1,12 @@
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Request as RequestBase
from werkzeug.wrappers import Response as ResponseBase from werkzeug.wrappers import Response as ResponseBase
from werkzeug.wrappers.json import JSONMixin as _JSONMixin
from . import json from . import json
from .globals import current_app from .globals import current_app
class JSONMixin(_JSONMixin): class Request(RequestBase):
json_module = json
def on_json_loading_failed(self, e):
if current_app and current_app.debug:
raise BadRequest(f"Failed to decode JSON object: {e}")
raise BadRequest()
class Request(RequestBase, JSONMixin):
"""The request object used by default in Flask. Remembers the """The request object used by default in Flask. Remembers the
matched endpoint and view arguments. matched endpoint and view arguments.
@ -30,6 +19,8 @@ class Request(RequestBase, JSONMixin):
specific ones. specific ones.
""" """
json_module = json
#: The internal URL rule that matched the request. This can be #: The internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from #: useful to inspect which methods are allowed for the URL from
#: a before/after handler (``request.url_rule.methods``) etc. #: a before/after handler (``request.url_rule.methods``) etc.
@ -89,8 +80,14 @@ class Request(RequestBase, JSONMixin):
attach_enctype_error_multidict(self) attach_enctype_error_multidict(self)
def on_json_loading_failed(self, e):
if current_app and current_app.debug:
raise BadRequest(f"Failed to decode JSON object: {e}")
class Response(JSONMixin, ResponseBase): raise BadRequest()
class Response(ResponseBase):
"""The response object that is used by default in Flask. Works like the """The response object that is used by default in Flask. Works like the
response object from Werkzeug but is set to have an HTML mimetype by response object from Werkzeug but is set to have an HTML mimetype by
default. Quite often you don't have to create this object yourself because default. Quite often you don't have to create this object yourself because
@ -110,14 +107,13 @@ class Response(JSONMixin, ResponseBase):
default_mimetype = "text/html" default_mimetype = "text/html"
def _get_data_for_json(self, cache): json_module = json
return self.get_data()
@property @property
def max_cookie_size(self): def max_cookie_size(self):
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key. """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
See :attr:`~werkzeug.wrappers.BaseResponse.max_cookie_size` in See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
Werkzeug's docs. Werkzeug's docs.
""" """
if current_app: if current_app: