Configuration provider

Configuration provider provides configuration options to the other providers.

import boto3
from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()

    s3_client_factory = providers.Factory(
        boto3.client,
        's3',
        aws_access_key_id=config.aws.access_key_id,
        aws_secret_access_key=config.aws.secret_access_key,
    )


if __name__ == '__main__':
    container = Container()
    container.config.from_dict(
        {
            'aws': {
                 'access_key_id': 'KEY',
                 'secret_access_key': 'SECRET',
             },
        },
    )
    s3_client = container.s3_client_factory()

It implements the principle “use first, define later”.

Loading from an INI file

Configuration provider can load configuration from an ini file using the Configuration.from_ini() method:

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()


if __name__ == '__main__':
    container = Container()

    container.config.from_ini('examples/providers/configuration/config.ini')

    assert container.config() == {
        'aws': {
            'access_key_id': 'KEY',
            'secret_access_key': 'SECRET',
        },
    }
    assert container.config.aws() == {
        'access_key_id': 'KEY',
        'secret_access_key': 'SECRET',
    }
    assert container.config.aws.access_key_id() == 'KEY'
    assert container.config.aws.secret_access_key() == 'SECRET'

where examples/providers/configuration/config.ini is:

[aws]
access_key_id = KEY
secret_access_key = SECRET

Configuration.from_ini() method supports environment variables interpolation. Use ${ENV_NAME} format in the configuration file to substitute value of the environment variable ENV_NAME.

Loading from a YAML file

Configuration provider can load configuration from a yaml file using the Configuration.from_yaml() method:

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()


if __name__ == '__main__':
    container = Container()

    container.config.from_yaml('examples/providers/configuration/config.yml')

    assert container.config() == {
        'aws': {
            'access_key_id': 'KEY',
            'secret_access_key': 'SECRET',
        },
    }
    assert container.config.aws() == {
        'access_key_id': 'KEY',
        'secret_access_key': 'SECRET',
    }
    assert container.config.aws.access_key_id() == 'KEY'
    assert container.config.aws.secret_access_key() == 'SECRET'

where examples/providers/configuration/config.yml is:

aws:
  access_key_id: "KEY"
  secret_access_key: "SECRET"

Configuration.from_yaml() method supports environment variables interpolation. Use ${ENV_NAME} format in the configuration file to substitute value of the environment variable ENV_NAME.

Note

Loading of a yaml configuration requires PyYAML package.

You can install the Dependency Injector with an extra dependency:

pip install dependency-injector[yaml]

or install PyYAML directly:

pip install pyyaml

Don’t forget to mirror the changes in the requirements file.

Loading from a dictionary

Configuration provider can load configuration from a Python dict using the Configuration.from_dict() method:

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()


if __name__ == '__main__':
    container = Container()

    container.config.from_dict(
        {
            'aws': {
                 'access_key_id': 'KEY',
                 'secret_access_key': 'SECRET',
             },
        },
    )

    assert container.config() == {
        'aws': {
            'access_key_id': 'KEY',
            'secret_access_key': 'SECRET',
        },
    }
    assert container.config.aws() == {
        'access_key_id': 'KEY',
        'secret_access_key': 'SECRET',
    }
    assert container.config.aws.access_key_id() == 'KEY'
    assert container.config.aws.secret_access_key() == 'SECRET'

Loading from an environment variable

Configuration provider can load configuration from an environment variable using the Configuration.from_env() method:

import os

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()


if __name__ == '__main__':
    container = Container()

    # Emulate environment variables
    os.environ['AWS_ACCESS_KEY_ID'] = 'KEY'
    os.environ['AWS_SECRET_ACCESS_KEY'] = 'SECRET'

    container.config.aws.access_key_id.from_env('AWS_ACCESS_KEY_ID')
    container.config.aws.secret_access_key.from_env('AWS_SECRET_ACCESS_KEY')
    container.config.optional.from_env('UNDEFINED', 'default_value')

    assert container.config.aws.access_key_id() == 'KEY'
    assert container.config.aws.secret_access_key() == 'SECRET'
    assert container.config.optional() == 'default_value'

Loading from the multiple sources

Configuration provider can load configuration from the multiple sources. Loaded configuration is merged recursively over the existing configuration.

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()


if __name__ == '__main__':
    container = Container()

    container.config.from_yaml('examples/providers/configuration/config.yml')
    container.config.from_yaml('examples/providers/configuration/config.local.yml')

    assert container.config() == {
        'aws': {
            'access_key_id': 'LOCAL-KEY',
            'secret_access_key': 'LOCAL-SECRET',
        },
    }
    assert container.config.aws() == {
        'access_key_id': 'LOCAL-KEY',
        'secret_access_key': 'LOCAL-SECRET',
    }
    assert container.config.aws.access_key_id() == 'LOCAL-KEY'
    assert container.config.aws.secret_access_key() == 'LOCAL-SECRET'

where examples/providers/configuration/config.local.yml is:

aws:
  access_key_id: "LOCAL-KEY"
  secret_access_key: "LOCAL-SECRET"

Specifying the value type

You can specify the type of the injected configuration value explicitly.

This helps when you read the value from an ini file or an environment variable and need to convert it into an int or a float.

import os

from dependency_injector import containers, providers


class ApiClient:
    def __init__(self, api_key: str, timeout: int):
        self.api_key = api_key
        self.timeout = timeout


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()

    api_client_factory = providers.Factory(
        ApiClient,
        api_key=config.api.key,
        timeout=config.api.timeout.as_int(),
    )


if __name__ == '__main__':
    container = Container()

    # Emulate environment variables
    os.environ['API_KEY'] = 'secret'
    os.environ['API_TIMEOUT'] = '5'

    container.config.api.key.from_env('API_KEY')
    container.config.api.timeout.from_env('API_TIMEOUT')

    api_client = container.api_client_factory()

    assert api_client.api_key == 'secret'
    assert api_client.timeout == 5

Configuration provider has next helper methods:

  • .as_int()
  • .as_float()
  • .as_(callback, *args, **kwargs)

The last method .as_(callback, *args, **kwargs) helps to implement other conversions.

import os
import decimal

from dependency_injector import containers, providers


class Calculator:
    def __init__(self, pi: decimal.Decimal):
        self.pi = pi


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()

    calculator_factory = providers.Factory(
        Calculator,
        pi=config.pi.as_(decimal.Decimal),
    )


if __name__ == '__main__':
    container = Container()

    # Emulate environment variables
    os.environ['PI'] = '3.1415926535897932384626433832'

    container.config.pi.from_env('PI')

    calculator = container.calculator_factory()

    assert calculator.pi == decimal.Decimal('3.1415926535897932384626433832')

With the .as_(callback, *args, **kwargs) you can specify a function that will be called before the injection. The value from the config will be passed as a first argument. The returned value will be injected. Parameters *args and **kwargs are handled as any other injections.

Injecting invariants

You can inject invariant configuration options based on the value of the other configuration option.

To use that you should provide the switch-value as an item of the configuration option that contains sections config.options[config.switch]:

  • When the value of the config.switch is A, the config.options.A is injected
  • When the value of the config.switch is B, the config.options.B is injected
import dataclasses

from dependency_injector import containers, providers


@dataclasses.dataclass
class Foo:
    option1: object
    option2: object


class Container(containers.DeclarativeContainer):

    config = providers.Configuration(default={
        'target': 'A',
        'items': {
            'A': {
                'option1': 60,
                'option2': 80,
            },
            'B': {
                'option1': 10,
                'option2': 20,
            },
        },
    })

    foo_factory = providers.Factory(
        Foo,
        option1=config.items[config.target].option1,
        option2=config.items[config.target].option2,
    )


if __name__ == '__main__':
    container = Container()

    container.config.target.from_env('TARGET')
    foo = container.foo_factory()
    print(foo.option1, foo.option2)

    # $ TARGET=A python configuration_itemselector.py
    # 60 80
    # $ TARGET=B python configuration_itemselector.py
    # 10 20