From bfdd37110c67ebeb42061fb43cf684b05291241f Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 1 Aug 2022 16:53:15 -0700 Subject: [PATCH] deprecate config attributes --- CHANGES.rst | 5 ++ docs/api.rst | 7 +- src/flask/app.py | 171 +++++++++++++++++++++++++++++++-------- src/flask/helpers.py | 2 +- src/flask/scaffold.py | 8 +- src/flask/sessions.py | 7 +- tests/test_config.py | 9 --- tests/test_templating.py | 2 - 8 files changed, 154 insertions(+), 57 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index dacee6da02..7c5786be05 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -27,6 +27,11 @@ Unreleased deprecated, removing the distinction between development and debug mode. Debug mode should be controlled directly using the ``--debug`` option or ``app.run(debug=True)``. :issue:`4714` +- Some attributes that proxied config keys on ``app`` are deprecated: + ``session_cookie_name``, ``send_file_max_age_default``, + ``use_x_sendfile``, ``propagate_exceptions``, and + ``templates_auto_reload``. Use the relevant config keys instead. + :issue:`4716` - Add new customization points to the ``Flask`` app object for many previously global behaviors. diff --git a/docs/api.rst b/docs/api.rst index 5359b37044..880720b408 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -125,10 +125,9 @@ implementation that Flask is using. .. admonition:: Notice - The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer - starting with Flask 0.8. Either catch this down yourself or use - the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the - app which converts the result to an integer automatically. + The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``. + The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a + ``timedelta``. Test Client diff --git a/src/flask/app.py b/src/flask/app.py index d4c3370ab1..2da0a9fa2c 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -99,7 +99,7 @@ def iscoroutinefunction(func: t.Any) -> bool: return inspect.iscoroutinefunction(func) -def _make_timedelta(value: t.Optional[timedelta]) -> t.Optional[timedelta]: +def _make_timedelta(value: t.Union[timedelta, int, None]) -> t.Optional[timedelta]: if value is None or isinstance(value, timedelta): return value @@ -273,11 +273,35 @@ class Flask(Scaffold): #: :data:`SECRET_KEY` configuration key. Defaults to ``None``. secret_key = ConfigAttribute("SECRET_KEY") - #: The secure cookie uses this for the name of the session cookie. - #: - #: This attribute can also be configured from the config with the - #: ``SESSION_COOKIE_NAME`` configuration key. Defaults to ``'session'`` - session_cookie_name = ConfigAttribute("SESSION_COOKIE_NAME") + @property + def session_cookie_name(self) -> str: + """The name of the cookie set by the session interface. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["SESSION_COOKIE_NAME"]`` + instead. + """ + import warnings + + warnings.warn( + "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use" + " 'SESSION_COOKIE_NAME' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["SESSION_COOKIE_NAME"] + + @session_cookie_name.setter + def session_cookie_name(self, value: str) -> None: + import warnings + + warnings.warn( + "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use" + " 'SESSION_COOKIE_NAME' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["SESSION_COOKIE_NAME"] = value #: A :class:`~datetime.timedelta` which is used to set the expiration #: date of a permanent session. The default is 31 days which makes a @@ -290,29 +314,70 @@ class Flask(Scaffold): "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta ) - #: A :class:`~datetime.timedelta` or number of seconds which is used - #: as the default ``max_age`` for :func:`send_file`. The default is - #: ``None``, which tells the browser to use conditional requests - #: instead of a timed cache. - #: - #: Configured with the :data:`SEND_FILE_MAX_AGE_DEFAULT` - #: configuration key. - #: - #: .. versionchanged:: 2.0 - #: Defaults to ``None`` instead of 12 hours. - send_file_max_age_default = ConfigAttribute( - "SEND_FILE_MAX_AGE_DEFAULT", get_converter=_make_timedelta - ) + @property + def send_file_max_age_default(self) -> t.Optional[timedelta]: + """The default value for ``max_age`` for :func:`~flask.send_file`. The default + is ``None``, which tells the browser to use conditional requests instead of a + timed cache. - #: Enable this if you want to use the X-Sendfile feature. Keep in - #: mind that the server has to support this. This only affects files - #: sent with the :func:`send_file` method. - #: - #: .. versionadded:: 0.2 - #: - #: This attribute can also be configured from the config with the - #: ``USE_X_SENDFILE`` configuration key. Defaults to ``False``. - use_x_sendfile = ConfigAttribute("USE_X_SENDFILE") + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use + ``app.config["SEND_FILE_MAX_AGE_DEFAULT"]`` instead. + + .. versionchanged:: 2.0 + Defaults to ``None`` instead of 12 hours. + """ + import warnings + + warnings.warn( + "'send_file_max_age_default' is deprecated and will be removed in Flask" + " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _make_timedelta(self.config["SEND_FILE_MAX_AGE_DEFAULT"]) + + @send_file_max_age_default.setter + def send_file_max_age_default(self, value: t.Union[int, timedelta, None]) -> None: + import warnings + + warnings.warn( + "'send_file_max_age_default' is deprecated and will be removed in Flask" + " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["SEND_FILE_MAX_AGE_DEFAULT"] = _make_timedelta(value) + + @property + def use_x_sendfile(self) -> bool: + """Enable this to use the ``X-Sendfile`` feature, assuming the server supports + it, from :func:`~flask.send_file`. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["USE_X_SENDFILE"]`` instead. + """ + import warnings + + warnings.warn( + "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use" + " 'USE_X_SENDFILE' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["USE_X_SENDFILE"] + + @use_x_sendfile.setter + def use_x_sendfile(self, value: bool) -> None: + import warnings + + warnings.warn( + "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use" + " 'USE_X_SENDFILE' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["USE_X_SENDFILE"] = value #: The JSON encoder class to use. Defaults to #: :class:`~flask.json.JSONEncoder`. @@ -624,8 +689,18 @@ def propagate_exceptions(self) -> bool: """Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration value in case it's set, otherwise a sensible default is returned. + .. deprecated:: 2.2 + Will be removed in Flask 2.3. + .. versionadded:: 0.7 """ + import warnings + + warnings.warn( + "'propagate_exceptions' is deprecated and will be removed in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) rv = self.config["PROPAGATE_EXCEPTIONS"] if rv is not None: return rv @@ -734,20 +809,37 @@ def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyS @property def templates_auto_reload(self) -> bool: """Reload templates when they are changed. Used by - :meth:`create_jinja_environment`. + :meth:`create_jinja_environment`. It is enabled by default in debug mode. - This attribute can be configured with :data:`TEMPLATES_AUTO_RELOAD`. If - not set, it will be enabled in debug mode. + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["TEMPLATES_AUTO_RELOAD"]`` + instead. .. versionadded:: 1.0 This property was added but the underlying config and behavior already existed. """ + import warnings + + warnings.warn( + "'templates_auto_reload' is deprecated and will be removed in Flask 2.3." + " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) rv = self.config["TEMPLATES_AUTO_RELOAD"] return rv if rv is not None else self.debug @templates_auto_reload.setter def templates_auto_reload(self, value: bool) -> None: + import warnings + + warnings.warn( + "'templates_auto_reload' is deprecated and will be removed in Flask 2.3." + " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) self.config["TEMPLATES_AUTO_RELOAD"] = value def create_jinja_environment(self) -> Environment: @@ -768,7 +860,12 @@ def create_jinja_environment(self) -> Environment: options["autoescape"] = self.select_jinja_autoescape if "auto_reload" not in options: - options["auto_reload"] = self.templates_auto_reload + auto_reload = self.config["TEMPLATES_AUTO_RELOAD"] + + if auto_reload is None: + auto_reload = self.debug + + options["auto_reload"] = auto_reload rv = self.jinja_environment(self, **options) rv.globals.update( @@ -898,7 +995,9 @@ def debug(self) -> bool: @debug.setter def debug(self, value: bool) -> None: self.config["DEBUG"] = value - self.jinja_env.auto_reload = self.templates_auto_reload + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value def run( self, @@ -1541,8 +1640,12 @@ def handle_exception(self, e: Exception) -> Response: """ exc_info = sys.exc_info() got_request_exception.send(self, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug - if self.propagate_exceptions: + if propagate: # Re-raise if called with an active exception, otherwise # raise the passed in exception. if exc_info[1] is e: diff --git a/src/flask/helpers.py b/src/flask/helpers.py index e3fbb1daea..15990d0e82 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -414,7 +414,7 @@ def _prepare_send_file_kwargs(**kwargs: t.Any) -> t.Dict[str, t.Any]: kwargs.update( environ=request.environ, - use_x_sendfile=current_app.use_x_sendfile, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], response_class=current_app.response_class, _root_path=current_app.root_path, # type: ignore ) diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index c009da8cf7..4d3356b880 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -5,6 +5,7 @@ import sys import typing as t from collections import defaultdict +from datetime import timedelta from functools import update_wrapper from jinja2 import FileSystemLoader @@ -302,12 +303,15 @@ def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]: .. versionadded:: 0.9 """ - value = current_app.send_file_max_age_default + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] if value is None: return None - return int(value.total_seconds()) + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value def send_static_file(self, filename: str) -> "Response": """The view function used to serve files from diff --git a/src/flask/sessions.py b/src/flask/sessions.py index f49b582e1c..02b8cf7693 100644 --- a/src/flask/sessions.py +++ b/src/flask/sessions.py @@ -177,11 +177,8 @@ def is_null_session(self, obj: object) -> bool: return isinstance(obj, self.null_session_class) def get_cookie_name(self, app: "Flask") -> str: - """Returns the name of the session cookie. - - Uses ``app.session_cookie_name`` which is set to ``SESSION_COOKIE_NAME`` - """ - return app.session_cookie_name + """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``.""" + return app.config["SESSION_COOKIE_NAME"] def get_cookie_domain(self, app: "Flask") -> t.Optional[str]: """Returns the domain that should be set for the session cookie. diff --git a/tests/test_config.py b/tests/test_config.py index cd856b2bfc..76c5d2721a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,6 @@ import json import os import textwrap -from datetime import timedelta import pytest @@ -207,14 +206,6 @@ def test_session_lifetime(): assert app.permanent_session_lifetime.seconds == 42 -def test_send_file_max_age(): - app = flask.Flask(__name__) - app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600 - assert app.send_file_max_age_default.seconds == 3600 - app.config["SEND_FILE_MAX_AGE_DEFAULT"] = timedelta(hours=2) - assert app.send_file_max_age_default.seconds == 7200 - - def test_get_namespace(): app = flask.Flask(__name__) app.config["FOO_OPTION_1"] = "foo option 1" diff --git a/tests/test_templating.py b/tests/test_templating.py index f0d7c1564a..863417c0df 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -397,11 +397,9 @@ def run_simple_mock(*args, **kwargs): monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) app.run() - assert not app.templates_auto_reload assert not app.jinja_env.auto_reload app.run(debug=True) - assert app.templates_auto_reload assert app.jinja_env.auto_reload