diff --git a/CHANGES.rst b/CHANGES.rst index 8111d3ef..78951681 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -49,7 +49,8 @@ Unreleased implementations in ``werkzeug.utils``. :pr:`3828` - Some ``send_file`` parameters have been renamed, the old names are 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 ``as_attachment=False`` by using ``Content-Disposition: inline``. :pr:`3828` diff --git a/docs/api.rst b/docs/api.rst index e5f68b8d..8ca1e42c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -27,9 +27,9 @@ Incoming Request Data --------------------- .. autoclass:: Request - :members: - :inherited-members: - :exclude-members: json_module + :members: + :inherited-members: + :exclude-members: json_module .. attribute:: request @@ -48,20 +48,9 @@ Response Objects ---------------- .. autoclass:: flask.Response - :members: set_cookie, max_cookie_size, data, mimetype, is_json, get_json - - .. attribute:: headers - - 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. - + :members: + :inherited-members: + :exclude-members: json_module Sessions -------- diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index d1b35699..764dfd20 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -249,25 +249,11 @@ be passed an instance of ``InternalServerError``, not the original unhandled error. 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 - - @app.errorhandler(InternalServerError) - def handle_500(e): - 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. +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 diff --git a/docs/testing.rst b/docs/testing.rst index 942a4e4d..2fedc600 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -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 application with the given path. The return value will be a :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. In this case, we ensure that ``'No entries here so far'`` is part of the output. diff --git a/docs/tutorial/tests.rst b/docs/tutorial/tests.rst index 079aeaa1..d8f2a931 100644 --- a/docs/tutorial/tests.rst +++ b/docs/tutorial/tests.rst @@ -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 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, -use :meth:`get_data(as_text=True) ` +use :meth:`get_data(as_text=True) ` instead. ``pytest.mark.parametrize`` tells Pytest to run the same test function diff --git a/src/flask/__main__.py b/src/flask/__main__.py index 33b6c380..4e28416e 100644 --- a/src/flask/__main__.py +++ b/src/flask/__main__.py @@ -1,3 +1,3 @@ from .cli import main -main(as_module=True) +main() diff --git a/src/flask/app.py b/src/flask/app.py index 34ca3700..e58ee983 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -16,7 +16,7 @@ from werkzeug.routing import Map from werkzeug.routing import RequestRedirect from werkzeug.routing import RoutingException from werkzeug.routing import Rule -from werkzeug.wrappers import BaseResponse +from werkzeug.wrappers import Response as BaseResponse from . import cli from . import json @@ -278,7 +278,7 @@ class Flask(Scaffold): #: This is a ``dict`` instead of an ``ImmutableDict`` to allow #: easier configuration. #: - jinja_options = {"extensions": ["jinja2.ext.autoescape", "jinja2.ext.with_"]} + jinja_options = {} #: Default configuration parameters. default_config = ImmutableDict( @@ -1386,17 +1386,10 @@ class Flask(Scaffold): .. versionadded:: 0.7 """ - if isinstance(e, BadRequestKeyError): - if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]: - 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, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True if isinstance(e, HTTPException) and not self.trap_http_exception(e): return self.handle_http_exception(e) @@ -1454,10 +1447,7 @@ class Flask(Scaffold): raise e self.log_exception(exc_info) - server_error = InternalServerError() - # 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 + server_error = InternalServerError(original_exception=e) handler = self._find_error_handler(server_error) if handler is not None: diff --git a/src/flask/cli.py b/src/flask/cli.py index 713d2b59..48c73763 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -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 - 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__": - main(as_module=True) + main() diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 325ac981..85277407 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -439,6 +439,8 @@ def get_flashed_messages(with_categories=False, category_filter=()): def _prepare_send_file_kwargs( download_name=None, attachment_filename=None, + etag=None, + add_etags=None, max_age=None, cache_timeout=None, **kwargs, @@ -461,12 +463,22 @@ def _prepare_send_file_kwargs( ) 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: max_age = current_app.get_send_file_max_age kwargs.update( environ=request.environ, download_name=download_name, + etag=etag, max_age=max_age, use_x_sendfile=current_app.use_x_sendfile, response_class=current_app.response_class, @@ -482,7 +494,8 @@ def send_file( download_name=None, attachment_filename=None, conditional=True, - add_etags=True, + etag=True, + add_etags=None, last_modified=None, max_age=None, cache_timeout=None, @@ -518,8 +531,8 @@ def send_file( the file. Defaults to the passed file name. :param conditional: Enable conditional and range responses based on request headers. Requires passing a file path and ``environ``. - :param add_etags: Calculate an ETag for the file. Requires passing a - file path. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. :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 file path. @@ -537,6 +550,10 @@ def send_file( ``conditional`` is enabled and ``max_age`` is not set by 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 Passing a file-like object that inherits from :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather @@ -593,6 +610,7 @@ def send_file( download_name=download_name, attachment_filename=attachment_filename, conditional=conditional, + etag=etag, add_etags=add_etags, last_modified=last_modified, max_age=max_age, diff --git a/src/flask/testing.py b/src/flask/testing.py index a316bf42..247e6605 100644 --- a/src/flask/testing.py +++ b/src/flask/testing.py @@ -5,7 +5,7 @@ import werkzeug.test from click.testing import CliRunner from werkzeug.test import Client from werkzeug.urls import url_parse -from werkzeug.wrappers import BaseRequest +from werkzeug.wrappers import Request as BaseRequest from . import _request_ctx_stack from .cli import ScriptInfo diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index 76a12bd0..1d8f17d7 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -1,23 +1,12 @@ from werkzeug.exceptions import BadRequest from werkzeug.wrappers import Request as RequestBase from werkzeug.wrappers import Response as ResponseBase -from werkzeug.wrappers.json import JSONMixin as _JSONMixin from . import json from .globals import current_app -class JSONMixin(_JSONMixin): - 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): +class Request(RequestBase): """The request object used by default in Flask. Remembers the matched endpoint and view arguments. @@ -30,6 +19,8 @@ class Request(RequestBase, JSONMixin): specific ones. """ + json_module = json + #: The internal URL rule that matched the request. This can be #: useful to inspect which methods are allowed for the URL from #: a before/after handler (``request.url_rule.methods``) etc. @@ -89,8 +80,14 @@ class Request(RequestBase, JSONMixin): 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 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 @@ -110,14 +107,13 @@ class Response(JSONMixin, ResponseBase): default_mimetype = "text/html" - def _get_data_for_json(self, cache): - return self.get_data() + json_module = json @property def max_cookie_size(self): """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. """ if current_app: