Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/pip/example/django_pytest_pure/…
Browse files Browse the repository at this point in the history
…django-2.2.26
  • Loading branch information
rochacbruno committed Jan 29, 2022
2 parents 0012de5 + bab39d2 commit a5c522d
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 17 deletions.
34 changes: 34 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Contributors

Shout out to our top contributors!

- [rochacbruno](https://api.github.com/users/rochacbruno)
- [douglas](https://api.github.com/users/douglas)
- [dependabot-preview[bot]](https://api.github.com/users/dependabot-preview%5Bbot%5D)
- [jperras](https://api.github.com/users/jperras)
- [janw](https://api.github.com/users/janw)
- [hilam](https://api.github.com/users/hilam)
- [VaultVulp](https://api.github.com/users/VaultVulp)
- [gpkc](https://api.github.com/users/gpkc)
- [ilitotor](https://api.github.com/users/ilitotor)
- [kedark3](https://api.github.com/users/kedark3)
- [sirex](https://api.github.com/users/sirex)
- [dgarcia360](https://api.github.com/users/dgarcia360)
- [rsnyman](https://api.github.com/users/rsnyman)
- [andressadotpy](https://api.github.com/users/andressadotpy)
- [Bernardoow](https://api.github.com/users/Bernardoow)
- [caneco](https://api.github.com/users/caneco)
- [dmsimard](https://api.github.com/users/dmsimard)
- [Sytten](https://api.github.com/users/Sytten)
- [endersonmenezes](https://api.github.com/users/endersonmenezes)
- [FrankBattaglia](https://api.github.com/users/FrankBattaglia)
- [pheanex](https://api.github.com/users/pheanex)
- [chobeat](https://api.github.com/users/chobeat)
- [tanalam2411](https://api.github.com/users/tanalam2411)
- [mspinelli](https://api.github.com/users/mspinelli)
- [cassiobotaro](https://api.github.com/users/cassiobotaro)
- [mirekdlugosz](https://api.github.com/users/mirekdlugosz)
- [adevore](https://api.github.com/users/adevore)
- [AmbientLighter](https://api.github.com/users/AmbientLighter)
- [ap--](https://api.github.com/users/ap--)
- [posquit0](https://api.github.com/users/posquit0)
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ test_examples:
cd example/project_root/;pwd;rm -rf /tmp/dynaconf_project_root_test/settings.py;mkdir -p /tmp/dynaconf_project_root_test/;echo "MESSAGE = 'Hello from tmp'" > /tmp/dynaconf_project_root_test/settings.py;python app.py;rm -rf /tmp/dynaconf_project_root_test/
cd example/settings_file/;pwd;rm -rf /tmp/settings_file_test/settings.py;mkdir -p /tmp/settings_file_test/;echo "MESSAGE = 'Hello from tmp'" > /tmp/settings_file_test/settings.py;python app.py;rm -rf /tmp/settings_file_test/
cd example/configure/;pwd;rm -rf /tmp/configure_test/settings.py;mkdir -p /tmp/configure_test/;echo "MESSAGE = 'Hello from tmp'" > /tmp/configure_test/settings.py;python app.py;rm -rf /tmp/configure_test/
cd example/-m_case/;pwd;python -m module

@echo '############### Calling from outer folder ###############'
python example/common/program.py
Expand Down Expand Up @@ -208,7 +209,7 @@ publish:

clean:
@find ./ -name '*.pyc' -exec rm -f {} \;
@find ./ -name '__pycache__' -exec rm -rf {} \;
@find ./ -name '__pycache__' -prune -exec rm -rf {} \;
@find ./ -name 'Thumbs.db' -exec rm -f {} \;
@find ./ -name '*~' -exec rm -f {} \;
rm -rf .cache
Expand Down
4 changes: 4 additions & 0 deletions docs/settings_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ from dynaconf import Dynaconf
settings = Dynaconf(settings_files=["settings.toml", "/etc/program/foo.yaml"])
```

!!! info
To use `python -m module`, where the module uses dynaconf you will need to
specify your `settings.toml` path, for example, like this: `settings_file="module/config/settings.toml"`.

### settings.toml

In the above example, dynaconf will try to load `settings.toml` from the same
Expand Down
59 changes: 48 additions & 11 deletions dynaconf/utils/parse_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,12 @@ class Lazy:

_dynaconf_lazy_format = True

def __init__(self, value=empty, formatter=Formatters.python_formatter):
def __init__(
self, value=empty, formatter=Formatters.python_formatter, casting=None
):
self.value = value
self.formatter = formatter
self.casting = casting

@property
def context(self):
Expand All @@ -179,7 +182,10 @@ def __call__(self, settings, validator_object=None):
"""LazyValue triggers format lazily."""
self.settings = settings
self.context["_validator_object"] = validator_object
return self.formatter(self.value, **self.context)
result = self.formatter(self.value, **self.context)
if self.casting is not None:
result = self.casting(result)
return result

def __str__(self):
"""Gives string representation for the object."""
Expand All @@ -193,6 +199,11 @@ def _dynaconf_encode(self):
"""Encodes this object values to be serializable to json"""
return f"@{self.formatter} {self.value}"

def set_casting(self, casting):
"""Set the casting and return the instance."""
self.casting = casting
return self


def try_to_encode(value, callback=str):
"""Tries to encode a value by verifying existence of `_dynaconf_encode`"""
Expand All @@ -215,11 +226,25 @@ def evaluate(settings, *args, **kwargs):


converters = {
"@str": str,
"@int": int,
"@float": float,
"@bool": lambda value: str(value).lower() in true_values,
"@json": json.loads,
"@str": lambda value: value.set_casting(str)
if isinstance(value, Lazy)
else str(value),
"@int": lambda value: value.set_casting(int)
if isinstance(value, Lazy)
else int(value),
"@float": lambda value: value.set_casting(float)
if isinstance(value, Lazy)
else float(value),
"@bool": lambda value: value.set_casting(
lambda x: str(x).lower() in true_values
)
if isinstance(value, Lazy)
else str(value).lower() in true_values,
"@json": lambda value: value.set_casting(
lambda x: json.loads(x.replace("'", '"'))
)
if isinstance(value, Lazy)
else json.loads(value),
"@format": lambda value: Lazy(value),
"@jinja": lambda value: Lazy(value, formatter=Formatters.jinja_formatter),
# Meta Values to trigger pre assignment actions
Expand Down Expand Up @@ -279,10 +304,22 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
and isinstance(data, str)
and data.startswith(tuple(converters.keys()))
):
parts = data.partition(" ")
converter_key = parts[0]
value = parts[-1]
value = get_converter(converter_key, value, box_settings)
# Check combination token is used
comb_token = re.match(
r"^@(str|int|float|bool|json) @(jinja|format)", data
)
if comb_token:
tokens = comb_token.group(0)
converter_key_list = tokens.split(" ")
value = data.replace(tokens, "").strip()
else:
parts = data.partition(" ")
converter_key_list = [parts[0]]
value = parts[-1]

# Parse the converters iteratively
for converter_key in converter_key_list[::-1]:
value = get_converter(converter_key, value, box_settings)
else:
value = parse_with_toml(data) if tomlfy else data

Expand Down
Empty file.
12 changes: 12 additions & 0 deletions example/-m_case/module/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from dynaconf import Dynaconf

settings = Dynaconf(
settings_file="module/config/settings.toml",
environments=True,
)


print(settings.WORKS) # noqa


assert "-m_case" == getattr(settings, "WORKS")
2 changes: 2 additions & 0 deletions example/-m_case/module/config/settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default]
works = '-m_case'
2 changes: 1 addition & 1 deletion example/format/.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
USERNAME='robert_plant'
FORMAT_USERNAME='robert_plant'
4 changes: 2 additions & 2 deletions example/format/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from dynaconf import settings

assert settings.get("USERNAME") is None
assert settings.get("FORMAT_USERNAME") is None
assert settings.DATABASE_NAME == "my_database.db"
assert os.environ["USERNAME"] == "robert_plant"
assert os.environ["FORMAT_USERNAME"] == "robert_plant"

DB_PATH = "/home/robert_plant/databases/my_database.db"
assert settings.DATABASE_PATH == DB_PATH, settings.DATABASE_PATH
Expand Down
4 changes: 2 additions & 2 deletions example/format/settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
database_name = 'my_database.db'

[development]
database_path = '@format /home/{env[USERNAME]}/databases/{this[database_name]}'
database_path_jinja = '@jinja /home/{{env.USERNAME}}/{{this.current_env | lower}}/{{this["database_name"] }}'
database_path = '@format /home/{env[FORMAT_USERNAME]}/databases/{this[database_name]}'
database_path_jinja = '@jinja /home/{{env.FORMAT_USERNAME}}/{{this.current_env | lower}}/{{this["database_name"] }}'
77 changes: 77 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,83 @@ def test_find_file(tmpdir):
) == os.path.join(str(tmpdir), ".env")


def test_casting_str(settings):
res = parse_conf_data("@str 7")
assert isinstance(res, str) and res == "7"

settings.set("value", 7)
res = parse_conf_data("@str @jinja {{ this.value }}")(settings)
assert isinstance(res, str) and res == "7"

res = parse_conf_data("@str @format {this.value}")(settings)
assert isinstance(res, str) and res == "7"


def test_casting_int(settings):
res = parse_conf_data("@int 2")
assert isinstance(res, int) and res == 2

settings.set("value", 2)
res = parse_conf_data("@int @jinja {{ this.value }}")(settings)
assert isinstance(res, int) and res == 2

res = parse_conf_data("@int @format {this.value}")(settings)
assert isinstance(res, int) and res == 2


def test_casting_float(settings):
res = parse_conf_data("@float 0.3")
assert isinstance(res, float) and abs(res - 0.3) < 1e-6

settings.set("value", 0.3)
res = parse_conf_data("@float @jinja {{ this.value }}")(settings)
assert isinstance(res, float) and abs(res - 0.3) < 1e-6

res = parse_conf_data("@float @format {this.value}")(settings)
assert isinstance(res, float) and abs(res - 0.3) < 1e-6


def test_casting_bool(settings):
res = parse_conf_data("@bool true")
assert isinstance(res, bool) and res is True

settings.set("value", "true")
res = parse_conf_data("@bool @jinja {{ this.value }}")(settings)
assert isinstance(res, bool) and res is True

settings.set("value", "false")
res = parse_conf_data("@bool @format {this.value}")(settings)
assert isinstance(res, bool) and res is False


def test_casting_json(settings):
res = parse_conf_data("""@json {"FOO": "bar"}""")
assert isinstance(res, dict)
assert "FOO" in res and "bar" in res.values()

# Test how single quotes cases are handled.
# When jinja uses `attr` to render a json string,
# it may covnert double quotes to single quotes.
settings.set("value", "{'FOO': 'bar'}")
res = parse_conf_data("@json @jinja {{ this.value }}")(settings)
assert isinstance(res, dict)
assert "FOO" in res and "bar" in res.values()

res = parse_conf_data("@json @format {this.value}")(settings)
assert isinstance(res, dict)
assert "FOO" in res and "bar" in res.values()

# Test jinja rendering a dict
settings.set("value", "OPTION1")
settings.set("OPTION1", {"bar": 1})
settings.set("OPTION2", {"bar": 2})
res = parse_conf_data("@jinja {{ this|attr(this.value) }}")(settings)
assert isinstance(res, str)
res = parse_conf_data("@json @jinja {{ this|attr(this.value) }}")(settings)
assert isinstance(res, dict)
assert "bar" in res and res["bar"] == 1


def test_disable_cast(monkeypatch):
# this casts for int
assert parse_conf_data("@int 42", box_settings={}) == 42
Expand Down

0 comments on commit a5c522d

Please sign in to comment.