Merging
Overview¶
Dynaconf provides global and local tools to control if the value of conflicting settings (which have the same key) will be merged or will override one another.
By default, nothing is merged. Also, only container types can be merged (list
and dict
). Non-container types, such as str
and int
, will always override the previous value (If you want to merge them, wrap them in a list
or dict
).
You can globally turn on the merging strategy by setting merge_enabled option to True
(via instance settings or envvars).
Merging existing data structures¶
If your settings have existing variables of types list
or dict
and you want to merge
instead of override
then
the dynaconf_merge
and dynaconf_merge_unique
stanzas can mark that variable as a candidate for merging.
For dict value:
Your main settings file (e.g settings.toml
) has an existing DATABASE
dict setting on [default]
env.
Now you want to contribute to the same DATABASE
key by adding new keys, so you can use dynaconf_merge
at the end of your dict:
In specific [envs]
[default]
database = {host="server.com", user="default"}
[development]
database = {user="dev_user", dynaconf_merge=true}
[production]
database = {user="prod_user", dynaconf_merge=true}
also allowed the alternative short format
[default]
database = {host="server.com", user="default"}
[development.database]
dynaconf_merge = {user="dev_user"}
[production.database]
dynaconf_merge = {user="prod_user"}
In an environment variable:
Using @merge
mark
# Toml formatted envvar
export DYNACONF_DATABASE='@merge {password=1234}'
or @merge
mark short format
# Toml formatted envvar
export DYNACONF_DATABASE='@merge password=1234'
It is also possible to use nested dunder
traversal like:
export DYNACONF_DATABASE__password=1234
export DYNACONF_DATABASE__user=admin
export DYNACONF_DATABASE__ARGS__timeout=30
export DYNACONF_DATABASE__ARGS__retries=5
Each __
is parsed as a level traversing thought dict keys. read more in environment variables
So the above will result in
DATABASE = {'password': 1234, 'user': 'admin', 'ARGS': {'timeout': 30, 'retries': 5}}
IMPORTANT lower case keys are respected only on *nix systems. Unfortunately, Windows environment variables are case insensitive and Python reads it as all upper cases, which means that if you are running on Windows the dictionary can have only upper case keys.
You can also export a toml dictionary.
# Toml formatted envvar
export DYNACONF_DATABASE='{password=1234, dynaconf_merge=true}'
Or in an additional file (e.g settings.yaml, .secrets.yaml, etc
) by using dynaconf_merge
token:
default:
database:
password: 1234
dynaconf_merge: true
or
default:
database:
dynaconf_merge:
password: 1234
The dynaconf_merge
token will mark that object to be merged with existing values (of course dynaconf_merge
key will not be added to the final settings it is just a mark)
The end result will be on [development]
env:
settings.DATABASE == {'host': 'server.com', 'user': 'dev_user', 'password': 1234}
The same can be applied to lists:
settings.toml
[default]
plugins = ["core"]
[development]
plugins = ["debug_toolbar", "dynaconf_merge"]
or
[default]
plugins = ["core"]
[development.plugins]
dynaconf_merge = ["debug_toolbar"]
And in environment variable
using @merge
token
export DYNACONF_PLUGINS='@merge ["ci_plugin"]'
or short version
export DYNACONF_PLUGINS='@merge ci_plugin'
comma separated values also supported:
export DYNACONF_PLUGINS='@merge ci_plugin,other_plugin'
or explicitly
export DYNACONF_PLUGINS='["ci_plugin", "dynaconf_merge"]'
Then the end result on [development]
is:
settings.PLUGINS == ["ci_plugin", "debug_toolbar", "core"]
If your value is a dictionary:
export DYNACONF_DATA="@merge {foo='bar'}"
# or the short
export DYNACONF_DATA="@merge foo=bar"
Avoiding duplications on lists¶
The dynaconf_merge_unique
is the token for when you want to avoid duplications in a list.
Example:
[default]
scripts = ['install.sh', 'deploy.sh']
[development]
scripts = ['dev.sh', 'test.sh', 'deploy.sh', 'dynaconf_merge_unique']
export DYNACONF_SCRIPTS='["deploy.sh", "run.sh", "dynaconf_merge_unique"]'
The end result for [development]
will be:
settings.SCRIPTS == ['install.sh', 'dev.sh', 'test.sh', 'deploy.sh', 'run.sh']
Note that
deploy.sh
is set 3 times but it is not repeated in the final settings. also note that it avoids duplication but overrides the order of the elements.
Local configuration files and merging to existing data¶
New in 2.2.0
This feature is useful for maintaining a shared set of config files for a team, while still allowing for local configuration.
Any file matched by the glob *.local.*
will be read at the end of file loading order. So it is possible to have local settings files that are for example not committed to the version controlled repository. (e:g add **/*.local*
to your .gitignore
)
So if you have settings.toml
Dynaconf will load it and after all will also try to load a file named settings.local.toml
if it does exist. And the same applies to all the other supported extensions settings.local.{py,json,yaml,toml,ini,cfg}
Example:
# settings.toml # <-- 1st loaded
[default]
colors = ["green", "blue"]
parameters = {enabled=true, number=42}
# .secrets.toml # <-- 2nd loaded (overrides previous existing vars)
[default]
password = 1234
# settings.local.toml # <-- 3rd loaded (overrides previous existing vars)
[default]
colors = ["pink"]
parameters = {enabled=false}
password = 9999
So with the above eg, the values will be:
settings = Dynaconf(settings_files=["settings.toml", ".secrets.toml"])
settings.COLORS == ["pink"]
settings.PARAMETERS == {"enabled": False}
settings.PASSWORD == 9999
For each loaded file dynaconf will override
previous existing keys so if you want to append
new values to existing variables you can use 3 strategies.
Mark the local file to be entirely merged¶
New in 2.2.0
# settings.local.toml
dynaconf_merge = true
[default]
colors = ["pink"]
parameters = {enabled=false}
By adding dynaconf_merge
to the top root of the file, the entire file will be marked for merge.
And then the values will be updated into existing data structures.
settings.COLORS == ["pink", "green", "blue"]
settings.PARAMETERS == {"enabled": False, "number": 42}
settings.PASSWORD == 9999
You can also mark a single env
like [development]
to be merged.
# settings.local.toml
[development]
dynaconf_merge = true
colors = ["pink"]
parameters = {enabled=false}
dynaconf merge token¶
# settings.local.toml
[default]
colors = ["pink", "dynaconf_merge"]
parameters = {enabled=false, dynaconf_merge=true}
By adding dynaconf_merge
to a list
or dict
, those will be marked as merge candidates.
And then the values will be updated into existing data structures.
settings.COLORS == ["pink", "green", "blue"]
settings.PARAMETERS == {"enabled": False, "number": 42}
settings.PASSWORD == 9999
New in 2.2.0
And it also works having dynaconf_merge
as dict keys holding the value to be merged.
# settings.local.toml
[default.colors]
dynaconf_merge = ["pink"] # <-- value ["pink"] will be merged in to existing colors
[default.parameters]
dynaconf_merge = {enabled=false}
Dunder merging for nested structures¶
For nested structures, the recommendation is to use dunder merging because it is easier to read and also has no limitations in terms of nesting levels.
# settings.local.yaml
[default]
parameters__enabled = false
The use of __
to denote nested level will ensure the key is merged with existing values read more in merging existing values.
Nested keys in dictionaries via environment variables.¶
New in 2.1.0
This is useful for
Django
settings.
Let's say you have a configuration like this:
settings.py
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'module.foo.engine',
'ARGS': {'timeout': 30}
}
}
And now you want to change the values of ENGINE
to other.module
, via environment variables you can use the format ${ENVVAR_PREFIX}_${VARIABLE}__${NESTED_ITEM}__${NESTED_ITEM}
Each __
(dunder, a.k.a double underline) denotes access to nested elements in a dictionary.
So
DATABASES['default']['ENGINE'] = 'other.module'
Can be expressed as environment variables:
export DYNACONF_DATABASES__default__ENGINE=other.module
NOTE: if you are using Django extension then the prefix will be
DJANGO_
instead ofDYNACONF_
and the same if you are usingFLASK_
or a custom prefix if you have customized theENVVAR_PREFIX
.
This will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module',
'ARGS': {'timeout': 30}
}
}
Warning
lower case keys are respected only on nix systems. Unfortunately, Windows environment variables are case insensitive and Python reads it as all upper cases, which means that if you are running on Windows the dictionary can have only upper case keys.
Also* only first level keys are case insensitive, which means DYNACONF_FOO__BAR=1
is different than DYNACONF_FOO__bar=1
.
In other words, except by the first level key, you must follow strictly the case of the variable key.
Now if you want to add a new item to ARGS
key:
export DYNACONF_DATABASES__default__ARGS__retries=10
This will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module',
'ARGS': {'timeout': 30, 'retries': 10}
}
}
and you can also pass a toml
like dictionary to be merged with existing ARGS
key using @merge
token.
new in 3.0.0
# as a toml (recommended)
export DYNACONF_DATABASES__default__ARGS='@merge {timeout=50, size=1}'
# OR as a json
export DYNACONF_DATABASES__default__ARGS='@merge {"timeout": 50, "size": 1}'
# OR as plain key pair
export DYNACONF_DATABASES__default__ARGS='@merge timeout=50,size=1'
will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module',
'ARGS': {'retries': 10, 'timeout': 50, 'size': 1}
}
}
Now if you want to clean an existing nested attribute you can just assign the new value.
# As a TOML empty dictionary `"{}"`
export DYNACONF_DATABASES__default__ARGS='{}'
This will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module',
'ARGS': {}
}
}
# As a TOML dictionary (recommended)
export DYNACONF_DATABASES__default__ARGS='{timeout=90}'
This will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module',
'ARGS': {'timeout': 90}
}
}
And if in any case you need to completely remove that key from the dictionary:
export DYNACONF_DATABASES__default__ARGS='@del'
This will result in
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'other.module'
}
}
Using the dynaconf_merge
mark on configuration files.¶
New in 2.0.0
To merge exported variables there is the dynaconf_merge tokens. Example:
Your main settings file (e.g settings.toml
) has an existing DATABASE
dict setting on [default]
env.
Now you want to contribute to the same DATABASE
key by adding new keys, so you can use dynaconf_merge
at the end of your dict:
In specific [envs]
[default]
database = {host="server.com", user="default"}
[development]
database = {user="dev_user", dynaconf_merge=true}
or
New in 2.2.0
[default]
database = {host="server.com", user="default"}
[development.database]
dynaconf_merge = {user="dev_user"}
In an environment variable use @merge
token:
New in 2.2.0
# Toml formatted envvar
export DYNACONF_DATABASE='@merge {password=1234}'
or dunder
(recommended)
# Toml formatted envvar
export DYNACONF_DATABASE__PASSWORD=1234
The end result will be on [development]
env:
settings.DATABASE == {'host': 'server.com', 'user': 'dev_user', 'password': 1234}
BEWARE: Using
MERGE_ENABLED_FOR_DYNACONF
can lead to unexpected results because you do not have granular control of what is being merged or overwritten so the recommendation is to use other options.
Known caveats¶
The dynaconf_merge and @merge functionalities work only for the first level keys, it will not merge subdicts or nested lists (yet).
More examples¶
Take a look at the example folder to see some examples of use with different file formats and features.