Skip to content

Commit

Permalink
Allow loading of environment variables into the config
Browse files Browse the repository at this point in the history
This new method will pick out any environment variables with a certain
prefix and place them into the config named without the prefix. This
makes it easy to use environment variables to configure the app as is
now more popular than when Flask started.

The prefix should ensure that the environment isn't polluted and the
config isn't polluted by environment variables.
  • Loading branch information
pgjones committed Mar 8, 2022
1 parent b655a9d commit 295eff6
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -45,6 +45,8 @@ Unreleased
- When using lazy loading (the default with the debugger), the Click
context from the ``flask run`` command remains available in the
loader thread. :issue:`4460`
- Allow loading of prefixed environment variables into the Config.
:pr:`4479`


Version 2.0.3
Expand Down
46 changes: 23 additions & 23 deletions docs/config.rst
Expand Up @@ -515,9 +515,11 @@ Or from a JSON file:
Configuring from Environment Variables
--------------------------------------

In addition to pointing to configuration files using environment variables, you
may find it useful (or necessary) to control your configuration values directly
from the environment.
In addition to pointing to configuration files using environment
variables, you may find it useful (or necessary) to control your
configuration values directly from the environment. Flask can be
instructed to load all environment variables starting with a specific
prefix into the config using :meth:`~flask.Config.from_prefixed_env`.

Environment variables can be set in the shell before starting the server:

Expand All @@ -527,54 +529,52 @@ Environment variables can be set in the shell before starting the server:

.. code-block:: text
$ export SECRET_KEY="5f352379324c22463451387a0aec5d2f"
$ export MAIL_ENABLED=false
$ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
$ export FLASK_MAIL_ENABLED=false
$ flask run
* Running on http://127.0.0.1:5000/
.. group-tab:: Fish

.. code-block:: text
$ set -x SECRET_KEY "5f352379324c22463451387a0aec5d2f"
$ set -x MAIL_ENABLED false
$ set -x FLASK_SECRET_KEY "5f352379324c22463451387a0aec5d2f"
$ set -x FLASK_MAIL_ENABLED false
$ flask run
* Running on http://127.0.0.1:5000/
.. group-tab:: CMD

.. code-block:: text
> set SECRET_KEY="5f352379324c22463451387a0aec5d2f"
> set MAIL_ENABLED=false
> set FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
> set FLASK_MAIL_ENABLED=false
> flask run
* Running on http://127.0.0.1:5000/
.. group-tab:: Powershell

.. code-block:: text
> $env:SECRET_KEY = "5f352379324c22463451387a0aec5d2f"
> $env:MAIL_ENABLED = "false"
> $env:FLASK_SECRET_KEY = "5f352379324c22463451387a0aec5d2f"
> $env:FLASK_MAIL_ENABLED = "false"
> flask run
* Running on http://127.0.0.1:5000/
While this approach is straightforward to use, it is important to remember that
environment variables are strings -- they are not automatically deserialized
into Python types.
The variables can then be loaded and accessed via the config with a
key equal to the environment variable name without the prefix i.e.

Here is an example of a configuration file that uses environment variables::

import os

_mail_enabled = os.environ.get("MAIL_ENABLED", default="true")
MAIL_ENABLED = _mail_enabled.lower() in {"1", "t", "true"}
.. code-block:: python
SECRET_KEY = os.environ.get("SECRET_KEY")
app.config.from_prefixed_env()
app.config["SECRET_KEY"] # Is "5f352379324c22463451387a0aec5d2f"
if not SECRET_KEY:
raise ValueError("No SECRET_KEY set for Flask application")
The prefix is ``FLASK_`` by default, however it is an configurable to
:meth:`~flask.Config.from_prefixed_env`.

While this approach is straightforward to use, it is important to
remember that environment variables are strings -- they are not
automatically deserialized into Python types.

Notice that any value besides an empty string will be interpreted as a boolean
``True`` value in Python, which requires care if an environment explicitly sets
Expand Down
25 changes: 25 additions & 0 deletions src/flask/config.py
Expand Up @@ -97,6 +97,31 @@ def from_envvar(self, variable_name: str, silent: bool = False) -> bool:
)
return self.from_pyfile(rv, silent=silent)

def from_prefixed_env(self, prefix: str = "FLASK_") -> bool:
"""Updates the config from environment variables with the prefix.
Calling this method will result in every environment variable
starting with **prefix** being placed into the configuration
without the **prefix**. The prefix is configurable as an
argument. Note that this method updates the existing config.
For example if there is an environment variable
``FLASK_SECRET_KEY`` with value ``secretly`` and the prefix is
``FLASK_`` the config will contain the key ``SECRET_KEY`` with
the value ``secretly`` after calling this method.
:return: Always returns ``True``.
.. versionadded:: 2.1.0
"""
mapping = {
key[len(prefix) :]: value # Use removeprefix with Python 3.9
for key, value in os.environ.items()
if key.startswith(prefix)
}
return self.from_mapping(mapping)

def from_pyfile(self, filename: str, silent: bool = False) -> bool:
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
Expand Down
18 changes: 18 additions & 0 deletions tests/test_config.py
Expand Up @@ -38,6 +38,24 @@ def test_config_from_file():
common_object_test(app)


def test_config_from_prefixed_env(monkeypatch):
app = flask.Flask(__name__)
monkeypatch.setenv("FLASK_A", "A value")
monkeypatch.setenv("NOT_FLASK_A", "Another value")
app.config.from_prefixed_env()
assert app.config["A"] == "A value"
assert "Another value" not in app.config.items()


def test_config_from_custom_prefixed_env(monkeypatch):
app = flask.Flask(__name__)
monkeypatch.setenv("FLASK_A", "A value")
monkeypatch.setenv("NOT_FLASK_A", "Another value")
app.config.from_prefixed_env("NOT_FLASK_")
assert app.config["A"] == "Another value"
assert "A value" not in app.config.items()


def test_config_from_mapping():
app = flask.Flask(__name__)
app.config.from_mapping({"SECRET_KEY": "config", "TEST_KEY": "foo"})
Expand Down

0 comments on commit 295eff6

Please sign in to comment.