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("./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:
class Container(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:
from dependency_injector import containers, providers
class Container(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:
class Container(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:
import yaml
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:
from dependency_injector import containers, providers
class Container(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:
class Container(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 object using the
Configuration.from_pydantic()
method:
import os
from dependency_injector import containers, providers
from pydantic import BaseSettings, Field
# Emulate environment variables
os.environ["AWS_ACCESS_KEY_ID"] = "KEY"
os.environ["AWS_SECRET_ACCESS_KEY"] = "SECRET"
class AwsSettings(BaseSettings):
access_key_id: str = Field(env="aws_access_key_id")
secret_access_key: str = Field(env="aws_secret_access_key")
class Settings(BaseSettings):
aws: AwsSettings = AwsSettings()
optional: str = Field(default="default_value")
class Container(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 Settings.dict()
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 object over the configuration provider argument. In that case,
the container will call config.from_pydantic()
automatically:
class Container(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
by default.
You can install the Dependency Injector
with an extra dependency:
pip install dependency-injector[pydantic]
or install pydantic
directly:
pip install pydantic
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"
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:
from datetime import date
from dependency_injector import containers, providers
class Container(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.
from dependency_injector import containers, providers
class Container(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()
.
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"
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
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
.
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.
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.
from dependency_injector import containers, providers, errors
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(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:
class Container(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:
class Container(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.
class Container(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.
from dependency_injector import containers, providers
from environs import Env
class Container(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
isA
, theconfig.options.A
is injectedWhen the value of the
config.switch
isB
, theconfig.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