Skip to content

463 Config environment variables interpolation required and nones #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 24, 2021
Merged
13 changes: 13 additions & 0 deletions docs/main/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_

Development version
-------------------
- Add option ``envs_required`` for configuration provider ``.from_yaml()`` and ``.from_ini()``
methods. With ``envs_required=True`` methods ``.from_yaml()`` and ``.from_ini()`` raise
an exception when encounter an undefined environment variable in the configuration file.
By default this option is set to false for preserving previous behavior ``envs_required=False``.
- Add raising of an exception in configuration provider strict mode when provider encounters
an undefined environment variable in the configuration file.
- Update configuration provider environment variables interpolation to replace
undefined environment variables with an empty value.
- Update configuration provider to perform environment variables interpolation before passing
configuration file content to the parser.

4.33.0
------
- Add support of default value for environment variable in INI and YAML
Expand Down
107 changes: 93 additions & 14 deletions docs/providers/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ Configuration provider

.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection,
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable,Default,Load,Read,Get
Option,Ini,Json,Yaml,Pydantic,Dict,Environment Variable Interpolation,
Environment Variable Substitution,Environment Variable in Config,
Environment Variable in YAML file,Environment Variable in INI file,Default,Load,Read
:description: Configuration provides configuration options to the other providers. This page
demonstrates how to use Configuration provider to inject the dependencies, load
a configuration from an ini or yaml file, a dictionary, an environment variable,
or a pydantic settings object.
or a pydantic settings object. This page also describes how to substitute (interpolate)
environment variables in YAML and INI configuration files.

.. currentmodule:: dependency_injector.providers

Expand Down Expand Up @@ -42,12 +45,7 @@ where ``examples/providers/configuration/config.ini`` is:
.. literalinclude:: ../../examples/providers/configuration/config.ini
:language: ini

:py:meth:`Configuration.from_ini` method supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value from ``ENV_NAME`` environment
variable.

You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.
:py:meth:`Configuration.from_ini` method supports environment variables interpolation.

.. code-block:: ini

Expand All @@ -56,6 +54,8 @@ variable ``ENV_NAME`` is undefined, configuration provider will substitute value
option2 = {$ENV_VAR}/path
option3 = {$ENV_VAR:default}

See also: :ref:`configuration-envs-interpolation`.

Loading from a YAML file
------------------------

Expand All @@ -72,12 +72,7 @@ where ``examples/providers/configuration/config.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.yml
:language: ini

:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use
``${ENV_NAME}`` format in the configuration file to substitute value from ``ENV_NAME`` environment
variable.

You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.
:py:meth:`Configuration.from_yaml` method supports environment variables interpolation.

.. code-block:: ini

Expand All @@ -86,6 +81,8 @@ variable ``ENV_NAME`` is undefined, configuration provider will substitute value
option2: {$ENV_VAR}/path
option3: {$ENV_VAR:default}

See also: :ref:`configuration-envs-interpolation`.

:py:meth:`Configuration.from_yaml` method uses custom version of ``yaml.SafeLoader``.
To use another loader use ``loader`` argument:

Expand Down Expand Up @@ -191,6 +188,73 @@ where ``examples/providers/configuration/config.local.yml`` is:
.. literalinclude:: ../../examples/providers/configuration/config.local.yml
:language: ini

.. _configuration-envs-interpolation:

Using environment variables in configuration files
--------------------------------------------------

``Configuration`` provider supports environment variables interpolation in configuration files.
Use ``${ENV_NAME}`` in the configuration file to substitute value from environment
variable ``ENV_NAME``.

.. code-block:: ini

section:
option: ${ENV_NAME}

You can also specify a default value using ``${ENV_NAME:default}`` format. If environment
variable ``ENV_NAME`` is undefined, configuration provider will substitute value ``default``.

.. code-block:: ini

[section]
option = {$ENV_NAME:default}

If you'd like to specify a default value for environment variable inside of the application you can use
``os.environ.setdefault()``.

.. literalinclude:: ../../examples/providers/configuration/configuration_env_interpolation_os_default.py
:language: python
:lines: 3-
:emphasize-lines: 12

If environment variable is undefined and doesn't have a default, ``Configuration`` provider
will replace it with an empty value. This is a default behavior. To raise an error on
undefined environment variable that doesn't have a default value, pass argument
``envs_required=True`` to a configuration reading method:

.. code-block:: python

container.config.from_yaml('config.yml', envs_required=True)

See also: :ref:`configuration-strict-mode`.

.. note::
``Configuration`` provider makes environment variables interpolation before parsing. This preserves
original parser behavior. For instance, undefined environment variable in YAML configuration file
will be replaced with an empty value and then YAML parser will load the file.

Original configuration file:

.. code-block:: ini

section:
option: ${ENV_NAME}

Configuration file after interpolation where ``ENV_NAME`` is undefined:

.. code-block:: ini

section:
option:

Configuration provider after parsing interpolated YAML file contains ``None`` in
option ``section.option``:

.. code-block:: python

assert container.config.section.option() is None

Mandatory and optional sources
------------------------------

Expand Down Expand Up @@ -310,6 +374,21 @@ configuration data is undefined:
except ValueError:
...

Environment variables interpolation in strict mode raises an exception when encounters
an undefined environment variable without a default value.

.. code-block:: ini

section:
option: {$UNDEFINED}

.. code-block:: python

try:
container.config.from_yaml('undefined_env.yml') # raise exception
except ValueError:
...

You can override ``.from_*()`` methods behaviour in strict mode using ``required`` argument:

.. code-block:: python
Expand Down
2 changes: 2 additions & 0 deletions examples/providers/configuration/config-with-env-var.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
section:
option: ${ENV_VAR}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""`Configuration` provider values loading example."""

import os

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

config = providers.Configuration()


if __name__ == '__main__':
os.environ.setdefault('ENV_VAR', 'default value')

container = Container()
container.config.from_yaml('config-with-env-var.yml')

assert container.config.section.option() == 'default value'
Loading