Configuration provider

Configuration provider provides configuration options to the other providers.

importboto3
fromdependency_injectorimport containers, providers
classContainer(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:

fromdependency_injectorimport containers, providers
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.from_ini("./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

Alternatively, you can provide a path to the INI file over the configuration provider argument. In that case, the container will call config.from_ini() automatically:

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(ini_files=["./config.ini"])

if __name__ == "__main__":
 container = Container() # Config is loaded from ./config.ini

Configuration.from_ini() method supports environment variables interpolation.

[section]
option1=${ENV_VAR}
option2=${ENV_VAR}/path
option3=${ENV_VAR:default}

See also: Using environment variables in configuration files.

Loading from a YAML file

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

fromdependency_injectorimport containers, providers
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.from_yaml("./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"

Alternatively, you can provide a path to the YAML file over the configuration provider argument. In that case, the container will call config.from_yaml() automatically:

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(yaml_files=["./config.yml"])

if __name__ == "__main__":
 container = Container() # Config is loaded from ./config.yml

Configuration.from_yaml() method supports environment variables interpolation.

section:
option1:${ENV_VAR}
option2:${ENV_VAR}/path
option3:${ENV_VAR:default}

See also: Using environment variables in configuration files.

Configuration.from_yaml() method uses custom version of yaml.SafeLoader. To use another loader use loader argument:

importyaml
container.config.from_yaml("config.yml", loader=yaml.UnsafeLoader)

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 JSON file

Configuration provider can load configuration from a json file using the Configuration.from_json() method:

fromdependency_injectorimport containers, providers
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.from_json("./config.json")

 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.json is:

{
"aws":{
"access_key_id":"KEY",
"secret_access_key":"SECRET"
}
}

Alternatively, you can provide a path to a json file over the configuration provider argument. In that case, the container will call config.from_json() automatically:

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(json_files=["./config.json"])

if __name__ == "__main__":
 container = Container() # Config is loaded from ./config.json

Configuration.from_json() method supports environment variables interpolation.

{
"section":{
"option1":"${ENV_VAR}",
"option2":"${ENV_VAR}/path",
"option3":"${ENV_VAR:default}"
}
}

See also: Using environment variables in configuration files.

Loading from a Pydantic settings

Configuration provider can load configuration from a pydantic_settings.BaseSettings object using the Configuration.from_pydantic() method:

importos
fromdependency_injectorimport containers, providers
frompydantic_settingsimport BaseSettings, SettingsConfigDict
# Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
classAwsSettings(BaseSettings):
 model_config = SettingsConfigDict(env_prefix="aws_")
 access_key_id: str
 secret_access_key: str
classSettings(BaseSettings):
 aws: AwsSettings = AwsSettings()
 optional: str = "default_value"
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.from_pydantic(Settings())

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

To get the data from pydantic settings Configuration provider calls its model_dump() method. If you need to pass an argument to this call, use .from_pydantic() keyword arguments.

container.config.from_pydantic(Settings(), exclude={"optional"})

Alternatively, you can provide a pydantic_settings.BaseSettings object over the configuration provider argument. In that case, the container will call config.from_pydantic() automatically:

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(pydantic_settings=[Settings()])

if __name__ == "__main__":
 container = Container() # Config is loaded from Settings()

Note

Dependency Injector doesn’t install pydantic-settings by default.

You can install the Dependency Injector with an extra dependency:

pip install dependency-injector[pydantic2]

or install pydantic-settings directly:

pip install pydantic-settings

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

Note

For backward-compatibility, Pydantic v1 is still supported. Passing pydantic.BaseSettings instances will work just as fine as pydantic_settings.BaseSettings.

Loading from a dictionary

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

fromdependency_injectorimport containers, providers
classContainer(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:

importos
fromdependency_injectorimport containers, providers
classContainer(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"

You can use as_ argument for the type casting of an environment variable value:

# API_KEY=secret
container.config.api_key.from_env("API_KEY", as_=str, required=True)
assert container.config.api_key() == "secret"
# SAMPLING_RATIO=0.5
container.config.sampling.from_env("SAMPLING_RATIO", as_=float, required=True)
assert container.config.sampling() == 0.5
# TIMEOUT undefined, default is used
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
assert container.config.timeout() == 5

Loading a value

Configuration provider can load configuration value using the Configuration.from_value() method:

fromdatetimeimport date
fromdependency_injectorimport containers, providers
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.option1.from_value(date(2022, 3, 13))
 container.config.option2.from_value(date(2022, 3, 14))

 assert container.config() == {
 "option1": date(2022, 3, 13),
 "option2": date(2022, 3, 14),
 }
 assert container.config.option1() == date(2022, 3, 13)
 assert container.config.option2() == date(2022, 3, 14)

Loading from the multiple sources

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

fromdependency_injectorimport containers, providers
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 container = Container()
 container.config.from_yaml("./config.yml")
 container.config.from_yaml("./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"

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.

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.

[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().

importos
fromdependency_injectorimport containers, providers
classContainer(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"

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:

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

See also: Strict mode and required options.

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:

section:
option:${ENV_NAME}

Configuration file after interpolation where ENV_NAME is undefined:

section:
option:

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

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

If you want to disable environment variables interpolation, pass envs_required=None:

templates.yml
template_string:'Hello,${name}!'
>>> container.config.from_yaml("templates.yml", envs_required=None)
>>> container.config.template_string()
'Hello, ${name}!'

Mandatory and optional sources

By default, methods .from_yaml() and .from_ini() ignore errors if configuration file does not exist. You can use this to specify optional configuration files.

If configuration file is mandatory, use required argument. Configuration provider will raise an error if required file does not exist.

You can also use required argument when loading configuration from dictionaries and environment variables.

Mandatory YAML file:

container.config.from_yaml("config.yaml", required=True)

Mandatory INI file:

container.config.from_ini("config.ini", required=True)

Mandatory dictionary:

container.config.from_dict(config_dict, required=True)

Mandatory environment variable:

container.config.api_key.from_env("API_KEY", required=True)

See also: Strict mode and required options.

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.

importos
fromdependency_injectorimport containers, providers
classApiClient:
 def__init__(self, api_key: str, timeout: int):
 self.api_key = api_key
 self.timeout = timeout
classContainer(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.

importos
importdecimal
fromdependency_injectorimport containers, providers
classCalculator:
 def__init__(self, pi: decimal.Decimal):
 self.pi = pi
classContainer(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.

Strict mode and required options

You can use configuration provider in strict mode. In strict mode configuration provider raises an error on access to any undefined option.

fromdependency_injectorimport containers, providers, errors
classApiClient:
 def__init__(self, api_key: str, timeout: int):
 self.api_key = api_key
 self.timeout = timeout
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(strict=True)

 api_client_factory = providers.Factory(
 ApiClient,
 api_key=config.api.key,
 timeout=config.api.timeout.as_int(),
 )
if __name__ == "__main__":
 container = Container()
 try:
 api_client = container.api_client_factory()
 except errors.Error:
 # raises error: Undefined configuration option "config.api.key"
 ...

Methods .from_*() in strict mode raise an exception if configuration file does not exist or configuration data is undefined:

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(strict=True)
if __name__ == "__main__":
 container = Container()
 try:
 container.config.from_yaml("does-not_exist.yml") # raise exception
 except FileNotFoundError:
 ...
 try:
 container.config.from_ini("does-not_exist.ini") # raise exception
 except FileNotFoundError:
 ...
 try:
 container.config.from_pydantic(EmptySettings()) # raise exception
 except ValueError:
 ...
 try:
 container.config.from_env("UNDEFINED_ENV_VAR") # raise exception
 except ValueError:
 ...
 try:
 container.config.from_dict({}) # raise exception
 except ValueError:
 ...

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

section:
option:${UNDEFINED}
try:
 container.config.from_yaml("undefined_env.yml") # raise exception
except ValueError:
 ...

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

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration(strict=True)
if __name__ == "__main__":
 container = Container()
 container.config.from_yaml("config.yml")
 container.config.from_yaml("config.local.yml", required=False)

You can also use .required() option modifier when making an injection. It does not require to switch configuration provider to strict mode.

classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
 api_client_factory = providers.Factory(
 ApiClient,
 api_key=config.api.key.required(),
 timeout=config.api.timeout.required().as_int(),
 )

Note

Modifier .required() should be specified before type modifier .as_*().

Aliases

You can use Configuration provider with a context manager to create aliases.

fromdependency_injectorimport containers, providers
fromenvironsimport Env
classContainer(containers.DeclarativeContainer):
 config = providers.Configuration()
if __name__ == "__main__":
 env = Env()
 container = Container()
 with container.config.some_plugin_name as plugin:
 plugin.some_interval_ms.override(
 env.int(
 "SOME_INTERVAL_MS",
 default=30000,
 ),
 )
 with plugin.kafka as kafka:
 kafka.bootstrap_servers.override(
 env.list(
 "KAFKA_BOOTSTRAP_SERVERS",
 default=["kafka1", "kafka2"],
 ),
 )
 kafka.security_protocol.override(
 env.str(
 "KAFKA_SECURITY_PROTOCOL",
 default="SASL_SSL",
 ),
 )

Note

Library environs is a 3rd party library. You need to install it separately:

pip install environs

Documentation is available on GitHub: https://github.com/sloria/environs

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

importdataclasses
fromdependency_injectorimport containers, providers
@dataclasses.dataclass
classFoo:
 option1: object
 option2: object
classContainer(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