From ef95998d79eed3b8350d8db825e3d98f25c179f2 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 1 Aug 2022 15:11:21 -0700 Subject: [PATCH] deprecate FLASK_ENV --- CHANGES.rst | 10 ++++-- src/flask/app.py | 73 ++++++++++++++++++++++++++++--------------- src/flask/cli.py | 62 ++++++++++-------------------------- src/flask/helpers.py | 31 ++++++++++++++---- tests/test_helpers.py | 15 --------- 5 files changed, 95 insertions(+), 96 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f8ebc72993..dacee6da02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,6 +23,10 @@ Unreleased ``g`` instead using a unique prefix, like ``g._extension_name_attr``. +- The ``FLASK_ENV`` environment variable and ``app.env`` attribute are + 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` - Add new customization points to the ``Flask`` app object for many previously global behaviors. @@ -60,9 +64,9 @@ Unreleased instance on every request. :issue:`2520`. - A ``flask.cli.FlaskGroup`` Click group can be nested as a sub-command in a custom CLI. :issue:`3263` -- Add ``--app``, ``--env``, and ``--debug`` options to the ``flask`` - CLI, instead of requiring that they are set through environment - variables. :issue:`2836` +- Add ``--app`` and ``--debug`` options to the ``flask`` CLI, instead + of requiring that they are set through environment variables. + :issue:`2836` - Add ``--env-file`` option to the ``flask`` CLI. This allows specifying a dotenv file to load in addition to ``.env`` and ``.flaskenv``. :issue:`3108` diff --git a/src/flask/app.py b/src/flask/app.py index 856c0d9d19..d4c3370ab1 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -45,7 +45,6 @@ from .globals import session from .helpers import _split_blueprint_path from .helpers import get_debug_flag -from .helpers import get_env from .helpers import get_flashed_messages from .helpers import get_load_dotenv from .helpers import locked_cached_property @@ -691,7 +690,7 @@ def make_config(self, instance_relative: bool = False) -> Config: if instance_relative: root_path = self.instance_path defaults = dict(self.default_config) - defaults["ENV"] = get_env() + defaults["ENV"] = os.environ.get("FLASK_ENV") or "development" defaults["DEBUG"] = get_debug_flag() return self.config_class(root_path, defaults) @@ -849,31 +848,50 @@ def make_shell_context(self) -> dict: rv.update(processor()) return rv - #: What environment the app is running in. Flask and extensions may - #: enable behaviors based on the environment, such as enabling debug - #: mode. This maps to the :data:`ENV` config key. This is set by the - #: :envvar:`FLASK_ENV` environment variable and may not behave as - #: expected if set in code. - #: - #: **Do not enable development when deploying in production.** - #: - #: Default: ``'production'`` - env = ConfigAttribute("ENV") + @property + def env(self) -> str: + """What environment the app is running in. This maps to the :data:`ENV` config + key. + + **Do not enable development when deploying in production.** + + Default: ``'production'`` + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. + """ + import warnings + + warnings.warn( + "'app.env' is deprecated and will be removed in Flask 2.3." + " Use 'app.debug' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["ENV"] + + @env.setter + def env(self, value: str) -> None: + import warnings + + warnings.warn( + "'app.env' is deprecated and will be removed in Flask 2.3." + " Use 'app.debug' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["ENV"] = value @property def debug(self) -> bool: - """Whether debug mode is enabled. When using ``flask run`` to start - the development server, an interactive debugger will be shown for - unhandled exceptions, and the server will be reloaded when code - changes. This maps to the :data:`DEBUG` config key. This is - enabled when :attr:`env` is ``'development'`` and is overridden - by the ``FLASK_DEBUG`` environment variable. It may not behave as - expected if set in code. + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. **Do not enable debug mode when deploying in production.** - Default: ``True`` if :attr:`env` is ``'development'``, or - ``False`` otherwise. + Default: ``False`` """ return self.config["DEBUG"] @@ -937,9 +955,7 @@ def run( If installed, python-dotenv will be used to load environment variables from :file:`.env` and :file:`.flaskenv` files. - If set, the :envvar:`FLASK_ENV` and :envvar:`FLASK_DEBUG` - environment variables will override :attr:`env` and - :attr:`debug`. + The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`. Threaded mode is enabled by default. @@ -966,7 +982,12 @@ def run( # if set, let env vars override previous values if "FLASK_ENV" in os.environ: - self.env = get_env() + print( + "'FLASK_ENV' is deprecated and will not be used in" + " Flask 2.3. Use 'FLASK_DEBUG' instead.", + file=sys.stderr, + ) + self.config["ENV"] = os.environ.get("FLASK_ENV") or "production" self.debug = get_debug_flag() elif "FLASK_DEBUG" in os.environ: self.debug = get_debug_flag() @@ -998,7 +1019,7 @@ def run( options.setdefault("use_debugger", self.debug) options.setdefault("threaded", True) - cli.show_server_banner(self.env, self.debug, self.name) + cli.show_server_banner(self.debug, self.name) from werkzeug.serving import run_simple diff --git a/src/flask/cli.py b/src/flask/cli.py index 634cb7e2a2..c12311f66f 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -13,12 +13,12 @@ import click from click.core import ParameterSource +from werkzeug import run_simple from werkzeug.serving import is_running_from_reloader from werkzeug.utils import import_string from .globals import current_app from .helpers import get_debug_flag -from .helpers import get_env from .helpers import get_load_dotenv if t.TYPE_CHECKING: @@ -418,29 +418,6 @@ def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str ) -def _set_env(ctx: click.Context, param: click.Option, value: str | None) -> str | None: - if value is None: - return None - - # Set with env var instead of ScriptInfo.load so that it can be - # accessed early during a factory function. - os.environ["FLASK_ENV"] = value - return value - - -_env_option = click.Option( - ["-E", "--env"], - metavar="NAME", - help=( - "The execution environment name to set in 'app.env'. Defaults to" - " 'production'. 'development' will enable 'app.debug' and start the" - " debugger and reloader when running the server." - ), - expose_value=False, - callback=_set_env, -) - - def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: # If the flag isn't provided, it will default to False. Don't use # that, let debug be set by env in that case. @@ -460,7 +437,7 @@ def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | N _debug_option = click.Option( ["--debug/--no-debug"], - help="Set 'app.debug' separately from '--env'.", + help="Set debug mode.", expose_value=False, callback=_set_debug, ) @@ -516,12 +493,10 @@ class FlaskGroup(AppGroup): :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` files to set environment variables. Will also change the working directory to the directory containing the first file found. - :param set_debug_flag: Set the app's debug flag based on the active - environment + :param set_debug_flag: Set the app's debug flag. .. versionchanged:: 2.2 - Added the ``-A/--app``, ``-E/--env``, ``--debug/--no-debug``, - and ``-e/--env-file`` options. + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. .. versionchanged:: 2.2 An app context is pushed when running ``app.cli`` commands, so @@ -546,7 +521,7 @@ def __init__( # callback. This allows users to make a custom group callback # without losing the behavior. --env-file must come first so # that it is eagerly evaluated before --app. - params.extend((_env_file_option, _app_option, _env_option, _debug_option)) + params.extend((_env_file_option, _app_option, _debug_option)) if add_version_option: params.append(version_option) @@ -737,26 +712,23 @@ def load_dotenv(path: str | os.PathLike | None = None) -> bool: return loaded # True if at least one file was located and loaded. -def show_server_banner(env, debug, app_import_path): +def show_server_banner(debug, app_import_path): """Show extra startup messages the first time the server is run, ignoring the reloader. """ if is_running_from_reloader(): return + click.secho( + "WARNING: This is a development server. Do not use it in a production" + " deployment. Use a production WSGI server instead.", + fg="red", + bold=True, + ) + if app_import_path is not None: click.echo(f" * Serving Flask app '{app_import_path}'") - click.echo(f" * Environment: {env}") - - if env == "production": - click.secho( - " WARNING: This is a development server. Do not use it in" - " a production deployment.\n Use a production WSGI server" - " instead.", - fg="red", - ) - if debug is not None: click.echo(f" * Debug mode: {'on' if debug else 'off'}") @@ -925,8 +897,8 @@ def run_command( This server is for development purposes only. It does not provide the stability, security, or performance of production WSGI servers. - The reloader and debugger are enabled by default with the - '--env development' or '--debug' options. + The reloader and debugger are enabled by default with the '--debug' + option. """ try: app = info.load_app() @@ -953,9 +925,7 @@ def app(environ, start_response): if debugger is None: debugger = debug - show_server_banner(get_env(), debug, info.app_import_path) - - from werkzeug.serving import run_simple + show_server_banner(debug, info.app_import_path) run_simple( host, diff --git a/src/flask/helpers.py b/src/flask/helpers.py index fb054466b3..e3fbb1daea 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -29,22 +29,41 @@ def get_env() -> str: """Get the environment the app is running in, indicated by the :envvar:`FLASK_ENV` environment variable. The default is ``'production'``. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. """ + import warnings + + warnings.warn( + "'FLASK_ENV' and 'get_env' are deprecated and will be removed" + " in Flask 2.3. Use 'FLASK_DEBUG' instead.", + DeprecationWarning, + stacklevel=2, + ) return os.environ.get("FLASK_ENV") or "production" def get_debug_flag() -> bool: - """Get whether debug mode should be enabled for the app, indicated - by the :envvar:`FLASK_DEBUG` environment variable. The default is - ``True`` if :func:`.get_env` returns ``'development'``, or ``False`` - otherwise. + """Get whether debug mode should be enabled for the app, indicated by the + :envvar:`FLASK_DEBUG` environment variable. The default is ``False``. """ val = os.environ.get("FLASK_DEBUG") if not val: - return get_env() == "development" + env = os.environ.get("FLASK_ENV") + + if env is not None: + print( + "'FLASK_ENV' is deprecated and will not be used in" + " Flask 2.3. Use 'FLASK_DEBUG' instead.", + file=sys.stderr, + ) + return env == "development" + + return False - return val.lower() not in ("0", "false", "no") + return val.lower() not in {"0", "false", "no"} def get_load_dotenv(default: bool = True) -> bool: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index cc2daaf77e..0e6a8856f3 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -6,7 +6,6 @@ import flask from flask.helpers import get_debug_flag -from flask.helpers import get_env class FakePath: @@ -322,20 +321,6 @@ def test_get_debug_flag( assert get_debug_flag() == expected_flag assert get_debug_flag() == expected_default_flag - @pytest.mark.parametrize( - "env, ref_env, debug", - [ - ("", "production", False), - ("production", "production", False), - ("development", "development", True), - ("other", "other", False), - ], - ) - def test_get_env(self, monkeypatch, env, ref_env, debug): - monkeypatch.setenv("FLASK_ENV", env) - assert get_debug_flag() == debug - assert get_env() == ref_env - def test_make_response(self): app = flask.Flask(__name__) with app.test_request_context():