From eb4fd809e3fe67e1a4b3b4babfb9220e9c400a7a Mon Sep 17 00:00:00 2001 From: Bruno Rocha Date: Mon, 5 Sep 2022 19:56:49 +0100 Subject: [PATCH] Parse negative numbers from envvar Fix #799 and Fix #585 --- Makefile | 1 + dynaconf/utils/parse_conf.py | 8 -------- dynaconf/validator.py | 17 +++++++++++++++++ example/issues/799_negative_numbers/app.py | 11 +++++++++++ .../issues/799_negative_numbers/settings.toml | 1 + tests/test_env_loader.py | 14 ++++++++++++++ tests/test_validators.py | 11 +++++++++++ 7 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 example/issues/799_negative_numbers/app.py create mode 100644 example/issues/799_negative_numbers/settings.toml diff --git a/Makefile b/Makefile index 7e2859c3c..3bac297e3 100644 --- a/Makefile +++ b/Makefile @@ -133,6 +133,7 @@ test_examples: cd example/issues/741_envvars_ignored;pwd;sh recreate.sh cd example/issues/705_flask_dynaconf_init;pwd;make test;make clean cd example/issues/794_includes;pwd;python app.py + cd example/issues/799_negative_numbers;pwd;DYNACONF_NUM="-1" python app.py test_vault: # @cd example/vault;pwd;python write.py diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py index c6da55736..2fcf723f6 100644 --- a/dynaconf/utils/parse_conf.py +++ b/dynaconf/utils/parse_conf.py @@ -356,7 +356,6 @@ def parse_conf_data(data, tomlfy=False, box_settings=None): box_settings = box_settings or {} if isinstance(data, (tuple, list)): - # recursively parse each sequence item return [ parse_conf_data(item, tomlfy=tomlfy, box_settings=box_settings) @@ -372,13 +371,6 @@ def parse_conf_data(data, tomlfy=False, box_settings=None): ) return _parsed - if ( - isinstance(data, str) - and data.startswith(("+", "-")) - and data[1:].isdigit() - ): - return data - # return parsed string value return _parse_conf_data(data, tomlfy=tomlfy, box_settings=box_settings) diff --git a/dynaconf/validator.py b/dynaconf/validator.py index f3c581d46..60dbfded8 100644 --- a/dynaconf/validator.py +++ b/dynaconf/validator.py @@ -135,6 +135,9 @@ def __init__( self.envs: Sequence[str] | None = None self.apply_default_on_none = apply_default_on_none + # See #585 + self.is_type_of = operations.get("is_type_of") + if isinstance(env, str): self.envs = [env] elif isinstance(env, (list, tuple)): @@ -243,6 +246,20 @@ def _validate_items( else: default_value = empty + # THIS IS A FIX FOR #585 in contrast with #799 + # toml considers signed strings "+-1" as integers + # however existing users are passing strings + # to default on validator (see #585) + # The solution we added on #667 introduced a new problem + # This fix here makes it to work for both cases. + if ( + isinstance(default_value, str) + and default_value.startswith(("+", "-")) + and self.is_type_of is str + ): + # avoid TOML from parsing "+-1" as integer + default_value = f"'{default_value}'" + value = self.cast( settings.setdefault( name, diff --git a/example/issues/799_negative_numbers/app.py b/example/issues/799_negative_numbers/app.py new file mode 100644 index 000000000..f240b46bd --- /dev/null +++ b/example/issues/799_negative_numbers/app.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from dynaconf import Dynaconf + +settings = Dynaconf(settings_files=["settings.toml"]) + +print(settings.num) +print(type(settings.num)) + +assert settings.num == -1, settings.num +assert isinstance(settings.num, int) diff --git a/example/issues/799_negative_numbers/settings.toml b/example/issues/799_negative_numbers/settings.toml new file mode 100644 index 000000000..e32e0e065 --- /dev/null +++ b/example/issues/799_negative_numbers/settings.toml @@ -0,0 +1 @@ +num = -1 diff --git a/tests/test_env_loader.py b/tests/test_env_loader.py index 2d99ea41d..2e85a0053 100644 --- a/tests/test_env_loader.py +++ b/tests/test_env_loader.py @@ -206,6 +206,20 @@ def test_backwards_compat_using_env_argument(): assert settings.VALUE == "BLARG as prefix" +def test_load_signed_integer(): + environ["799_SIGNED_NEG_INT"] = "-1" + environ["799_SIGNED_POS_INT"] = "+1" + load_from_env( + identifier="env_global", + key=None, + prefix="799", + obj=settings, + silent=True, + ) + assert settings.SIGNED_NEG_INT == -1 + assert settings.SIGNED_POS_INT == 1 + + def test_env_is_not_str_raises(): with pytest.raises(TypeError): load_from_env(settings, prefix=int) diff --git a/tests/test_validators.py b/tests/test_validators.py index bf67a92e9..71eaa1fbd 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -739,6 +739,17 @@ def test_toml_should_not_change_validator_type_with_is_type_set(): assert settings.test == "+172800" +def test_toml_should_not_change_validator_type_with_is_type_not_set_int(): + settings = Dynaconf( + validators=[Validator("TEST", default="+172800")] + # The ways to force a string is + # passing is_type_of=str + # or default="@str +172800" or default="'+172800'" + ) + + assert settings.test == +172800 + + def test_toml_should_not_change_validator_type_using_at_sign(): settings = Dynaconf( validators=[Validator("TEST", is_type_of=str, default="@str +172800")]