Skip to content

Commit 2f41582

Browse files
committed
Found this bug that was duplicating the generated envlist
also stopped using `/tmp` on tests (using tempfile instead)
1 parent 912ce08 commit 2f41582

9 files changed

Lines changed: 218 additions & 40 deletions

File tree

docs/merging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ settings.SCRIPTS == ['install.sh', 'dev.sh', 'test.sh', 'deploy.sh', 'run.sh']
192192
```
193193

194194
> Note that `deploy.sh` is set 3 times but it is not repeated in the final settings.
195-
195+
> **also note** that it avoids duplication but overrides the order of the elements.
196196

197197
## Local configuration files and merging to existing data
198198

dynaconf/base.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -826,9 +826,15 @@ def set(
826826

827827
if getattr(value, "_dynaconf_reset", False): # pragma: no cover
828828
# just in case someone use a `@reset` in a first level var.
829-
# NOTE: @reset/Reset is deprecated in v3.0.0
830829
value = value.unwrap()
831830

831+
if getattr(value, "_dynaconf_merge_unique", False):
832+
# just in case someone use a `@merge_unique` in a first level var
833+
if existing:
834+
value = object_merge(existing, value.unwrap(), unique=True)
835+
else:
836+
value = value.unwrap()
837+
832838
if getattr(value, "_dynaconf_merge", False):
833839
# just in case someone use a `@merge` in a first level var
834840
if existing:
@@ -842,6 +848,7 @@ def set(
842848
value = object_merge(existing, value)
843849
else:
844850
# `dynaconf_merge` may be used within the key structure
851+
# Or merge_enabled is set to True
845852
value = self._merge_before_set(existing, value)
846853

847854
if isinstance(value, dict):
@@ -918,10 +925,9 @@ def _merge_before_set(self, existing, value):
918925
local_merge = (
919926
"dynaconf_merge" in value or "dynaconf_merge_unique" in value
920927
)
921-
default_env = self.DEFAULT_ENV_FOR_DYNACONF == "DEFAULT"
922928
if global_merge or local_merge:
923929
value = list(value)
924-
unique = True if default_env else False
930+
unique = False
925931
if local_merge:
926932
try:
927933
value.remove("dynaconf_merge")
@@ -974,8 +980,8 @@ def execute_loaders(
974980

975981
loaders = self.loaders
976982

977-
for loader in loaders:
978-
loader.load(self, env, silent=silent, key=key)
983+
for core_loader in loaders:
984+
core_loader.load(self, env, silent=silent, key=key)
979985

980986
self.load_includes(env, silent=silent, key=key)
981987
execute_hooks("post", self, env, silent=silent, key=key)

dynaconf/loaders/base.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,9 @@ def _load_all_envs(self, source_data, silent=True, key=None):
131131
if not data:
132132
continue
133133

134-
if env != self.obj.get("DEFAULT_ENV_FOR_DYNACONF").lower():
135-
identifier = f"{self.identifier}_{env}"
136-
else:
137-
identifier = self.identifier
138134
self._set_data_to_obj(
139135
data,
140-
identifier,
136+
f"{self.identifier}_{env}",
141137
file_merge,
142138
key,
143139
)

dynaconf/utils/__init__.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -305,27 +305,27 @@ def build_env_list(
305305
[str] -- A list of string names of the envs to load.
306306
"""
307307
# add the [default] env
308-
env_list = [obj.get("DEFAULT_ENV_FOR_DYNACONF")]
308+
env_list = [(obj.get("DEFAULT_ENV_FOR_DYNACONF") or "default").lower()]
309309

310310
# compatibility with older versions that still uses [dynaconf] as
311311
# [default] env
312-
global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "DYNACONF"
312+
global_env = (obj.get("ENVVAR_PREFIX_FOR_DYNACONF") or "dynaconf").lower()
313313
if global_env not in env_list:
314314
env_list.append(global_env)
315315

316316
# add the current env
317-
if obj.current_env and obj.current_env not in env_list:
318-
env_list.append(obj.current_env)
317+
current_env = obj.current_env
318+
if current_env and current_env.lower() not in env_list:
319+
env_list.append(current_env.lower())
319320

320321
# add a manually set env
321-
if env and env not in env_list:
322-
env_list.append(env)
322+
if env and env.lower() not in env_list:
323+
env_list.append(env.lower())
323324

324325
# add the [global] env
325-
env_list.append("GLOBAL")
326+
env_list.append("global")
326327

327-
# loaders are responsible to change to lower/upper cases
328-
return [env.lower() for env in env_list]
328+
return env_list
329329

330330

331331
def upperfy(key: str) -> str:

dynaconf/utils/parse_conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ class Merge(MetaValue):
8080
_dynaconf_merge = True
8181

8282
def __init__(self, value, box_settings, unique=False):
83+
if unique:
84+
self._dynaconf_merge_unique = True
85+
8386
self.box_settings = box_settings
8487

8588
self.value = parse_conf_data(

example/compat.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import os
2+
import tempfile
3+
14
from dynaconf import LazySettings
25
from dynaconf.utils import RENAMED_VARS
36

@@ -16,12 +19,19 @@
1619
assert getattr(settings, new) == getattr(settings, old)
1720

1821

22+
tempdir = tempfile.mkdtemp()
23+
temptoml = tempfile.mktemp(".toml", dir=tempdir)
24+
temptomlfilename = os.path.basename(temptoml)
25+
temppy = tempfile.mktemp(".py", dir=tempdir)
26+
temppyfilename = os.path.basename(temppy)
27+
28+
1929
# 0 given a full old-style configured setting
2030
settings = LazySettings(
2131
environments=True,
2232
DYNACONF_NAMESPACE="FOO",
23-
DYNACONF_SETTINGS_MODULE="/tmp/foo.toml",
24-
PROJECT_ROOT="/tmp/",
33+
DYNACONF_SETTINGS_MODULE=str(temptoml),
34+
PROJECT_ROOT=str(tempdir),
2535
DYNACONF_SILENT_ERRORS=False,
2636
DYNACONF_ALWAYS_FRESH_VARS=["baz", "zaz", "caz"],
2737
BASE_NAMESPACE_FOR_DYNACONF="original",
@@ -51,17 +61,17 @@
5161
settings = LazySettings(
5262
environments=True,
5363
DYNACONF_NAMESPACE="FOO",
54-
DYNACONF_SETTINGS_MODULE="foo.py",
55-
PROJECT_ROOT="/tmp",
64+
DYNACONF_SETTINGS_MODULE=temppyfilename,
65+
PROJECT_ROOT=str(tempdir),
5666
DYNACONF_SILENT_ERRORS=True,
5767
DYNACONF_ALWAYS_FRESH_VARS=["BAR"],
5868
GLOBAL_ENV_FOR_DYNACONF="BLARG",
5969
)
6070

6171

6272
assert settings.ENV_FOR_DYNACONF == "FOO"
63-
assert settings.SETTINGS_FILE_FOR_DYNACONF == "foo.py"
64-
assert settings.ROOT_PATH_FOR_DYNACONF == "/tmp"
73+
assert settings.SETTINGS_FILE_FOR_DYNACONF == temppyfilename
74+
assert settings.ROOT_PATH_FOR_DYNACONF == str(tempdir)
6575
assert settings.SILENT_ERRORS_FOR_DYNACONF is True
6676
assert settings.FRESH_VARS_FOR_DYNACONF == ["BAR"]
6777
assert settings.ENVVAR_PREFIX_FOR_DYNACONF == "BLARG"
@@ -76,16 +86,16 @@
7686
settings = LazySettings(
7787
environments=True,
7888
NAMESPACE="FOO",
79-
SETTINGS_MODULE="foo.py",
80-
PROJECT_ROOT="/tmp",
89+
SETTINGS_MODULE=temppyfilename,
90+
PROJECT_ROOT=str(tempdir),
8191
DYNACONF_SILENT_ERRORS=True,
8292
DYNACONF_ALWAYS_FRESH_VARS=["BAR"],
8393
)
8494

8595

8696
assert settings.ENV_FOR_DYNACONF == "FOO"
87-
assert settings.SETTINGS_FILE_FOR_DYNACONF == "foo.py"
88-
assert settings.ROOT_PATH_FOR_DYNACONF == "/tmp"
97+
assert settings.SETTINGS_FILE_FOR_DYNACONF == temppyfilename
98+
assert settings.ROOT_PATH_FOR_DYNACONF == str(tempdir)
8999
assert settings.SILENT_ERRORS_FOR_DYNACONF is True
90100
assert settings.FRESH_VARS_FOR_DYNACONF == ["BAR"]
91101

tests/test_base.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dynaconf import Dynaconf
77
from dynaconf import LazySettings
88
from dynaconf.loaders import toml_loader
9+
from dynaconf.loaders import yaml_loader
910
from dynaconf.strategies.filtering import PrefixFilter
1011
from dynaconf.utils.parse_conf import true_values
1112
from dynaconf.vendor_src.box.box_list import BoxList
@@ -1030,22 +1031,16 @@ def test_wrap_existing_settings_clone():
10301031
assert settings.FOO == "bar"
10311032

10321033

1033-
@pytest.fixture()
1034-
def resource():
1035-
settings_test = {
1034+
def test_list_entries_from_yaml_should_not_duplicate_when_merged(tmpdir):
1035+
data = {
10361036
"default": {
10371037
"SOME_KEY": "value",
10381038
"SOME_LIST": ["item_1", "item_2", "item_3"],
10391039
},
10401040
"other": {"SOME_KEY": "new_value", "SOME_LIST": ["item_4", "item_5"]},
10411041
}
1042+
yaml_loader.write(str(tmpdir.join("test_settings.yaml")), data)
10421043

1043-
settings_yaml = yaml.dump(settings_test)
1044-
with open("test_settings.yaml", "w") as file:
1045-
file.write(settings_yaml)
1046-
1047-
1048-
def test_list_entries_from_yaml_should_not_duplicate_when_merged(resource):
10491044
settings = Dynaconf(
10501045
settings_files="test_settings.yaml",
10511046
environments=True,

tests/test_utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from dynaconf import default_settings
99
from dynaconf.loaders.json_loader import DynaconfEncoder
10+
from dynaconf.utils import build_env_list
1011
from dynaconf.utils import ensure_a_list
1112
from dynaconf.utils import extract_json_objects
1213
from dynaconf.utils import isnamedtupleinstance
@@ -169,7 +170,6 @@ def test_meta_values(settings):
169170
reset = parse_conf_data(
170171
"@reset [1, 2]", tomlfy=True, box_settings=settings
171172
)
172-
# @reset is DEPRECATED in v3.0.0 but kept for backwards compatibility
173173
assert reset.value == [1, 2]
174174
assert reset._dynaconf_reset is True
175175
assert "Reset([1, 2])" in repr(reset)
@@ -373,3 +373,17 @@ def test_extract_json():
373373
assert list(extract_json_objects('foo bar {"a": 1}')) == [{"a": 1}]
374374
assert list(extract_json_objects("foo bar {'a': 2{")) == []
375375
assert list(extract_json_objects('{{{"x": {}}}}')) == [{"x": {}}]
376+
377+
378+
def test_env_list():
379+
class Obj(dict):
380+
@property
381+
def current_env(self):
382+
return "other"
383+
384+
assert build_env_list(Obj(), env="OTHER") == [
385+
"default",
386+
"dynaconf",
387+
"other",
388+
"global",
389+
]

0 commit comments

Comments
 (0)