diff --git a/decouple.py b/decouple.py index 9873fc9..3dab28d 100644 --- a/decouple.py +++ b/decouple.py @@ -71,7 +71,7 @@ def _cast_boolean(self, value): Helper to convert config values to boolean as ConfigParser do. """ value = str(value) - return bool(value) if value == '' else bool(strtobool(value)) + return False if value == '' else bool(strtobool(value)) @staticmethod def _cast_do_nothing(value): @@ -98,6 +98,12 @@ def get(self, option, default=undefined, cast=undefined): elif cast is bool: cast = self._cast_boolean + if value is None or value == '': + if not isinstance(default, Undefined): + value = default + elif cast == self._cast_boolean: + return False + return cast(value) def __call__(self, *args, **kwargs): @@ -274,7 +280,7 @@ def __init__(self, cast=text_type, delimiter=',', strip=string.whitespace, post_ def __call__(self, value): """The actual transformation""" - if value is None: + if value is None or value == '': return self.post_process() transform = lambda s: self.cast(s.strip(self.strip)) diff --git a/tests/test_empty_values.py b/tests/test_empty_values.py new file mode 100644 index 0000000..fc4c753 --- /dev/null +++ b/tests/test_empty_values.py @@ -0,0 +1,75 @@ +# coding: utf-8 +import os +import sys +from mock import patch +import pytest +from decouple import Config, RepositoryEnv, UndefinedValueError + +# Useful for very coarse version differentiation. +PY3 = sys.version_info[0] == 3 + +if PY3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +ENVFILE = ''' +# Empty values +DB_PORT= +SECRET= +DEBUG= +LIST_VALUES= + +# Non-empty values for comparison +DB_HOST=localhost +SECRET_KEY=abc123 +''' + +@pytest.fixture(scope='module') +def config(): + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + return Config(RepositoryEnv('.env')) + + +def test_empty_value_with_default_int(): + """Test that an empty DB_PORT with default and int cast works correctly.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + config = Config(RepositoryEnv('.env')) + # DB_PORT= (empty) should use the default value 5432 + assert 5432 == config('DB_PORT', default=5432, cast=int) + + +def test_empty_value_with_default_none(): + """Test that an empty SECRET with default=None works correctly.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + config = Config(RepositoryEnv('.env')) + # SECRET= (empty) should use the default value None + assert None is config('SECRET', default=None) + + +def test_empty_value_with_default_bool(): + """Test that an empty DEBUG with default and bool cast works correctly.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + config = Config(RepositoryEnv('.env')) + # DEBUG= (empty) should use the default value True + assert True is config('DEBUG', default=True, cast=bool) + # Empty value without default should be False when cast to bool + assert False is config('DEBUG', cast=bool) + + +def test_empty_value_with_csv_cast(): + """Test that an empty LIST_VALUES with Csv cast works correctly.""" + # Create a fresh config for this test to avoid fixture caching issues + from decouple import Csv + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + config = Config(RepositoryEnv('.env')) + # LIST_VALUES= (empty) should return an empty list with Csv cast + # For empty values, we need to manually apply the Csv cast + empty_value = config('LIST_VALUES') + assert [] == Csv()(empty_value) + # With default values + assert ['default'] == config('LIST_VALUES', default='default', cast=Csv()) diff --git a/tests/test_empty_values_autoconfig.py b/tests/test_empty_values_autoconfig.py new file mode 100644 index 0000000..3b3e8ea --- /dev/null +++ b/tests/test_empty_values_autoconfig.py @@ -0,0 +1,89 @@ +# coding: utf-8 +import os +import sys +from mock import patch, MagicMock +import pytest +from decouple import AutoConfig, UndefinedValueError + +# Useful for very coarse version differentiation. +PY3 = sys.version_info[0] == 3 + +if PY3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +ENVFILE = ''' +# Empty values +DB_PORT= +SECRET= +DEBUG= +LIST_VALUES= + +# Non-empty values for comparison +DB_HOST=localhost +SECRET_KEY=abc123 +''' + + +def test_autoconfig_empty_value_with_default_int(): + """Test that an empty DB_PORT with default and int cast works correctly with AutoConfig.""" + config = AutoConfig() + + # Mock the _find_file method to return a fake path + fake_path = os.path.join('fake', 'path', '.env') + config._find_file = MagicMock(return_value=fake_path) + + # Mock open to return our test env content + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + # DB_PORT= (empty) should use the default value 5432 + assert 5432 == config('DB_PORT', default=5432, cast=int) + + +def test_autoconfig_empty_value_with_default_none(): + """Test that an empty SECRET with default=None works correctly with AutoConfig.""" + config = AutoConfig() + + # Mock the _find_file method to return a fake path + fake_path = os.path.join('fake', 'path', '.env') + config._find_file = MagicMock(return_value=fake_path) + + # Mock open to return our test env content + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + # SECRET= (empty) should use the default value None + assert None is config('SECRET', default=None) + + +def test_autoconfig_empty_value_with_default_bool(): + """Test that an empty DEBUG with default and bool cast works correctly with AutoConfig.""" + config = AutoConfig() + + # Mock the _find_file method to return a fake path + fake_path = os.path.join('fake', 'path', '.env') + config._find_file = MagicMock(return_value=fake_path) + + # Mock open to return our test env content + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + # DEBUG= (empty) should use the default value True + assert True is config('DEBUG', default=True, cast=bool) + # Empty value without default should be False when cast to bool + assert False is config('DEBUG', cast=bool) + + +def test_autoconfig_empty_value_with_csv_cast(): + """Test that an empty LIST_VALUES with Csv cast works correctly with AutoConfig.""" + from decouple import Csv + + config = AutoConfig() + + # Mock the _find_file method to return a fake path + fake_path = os.path.join('fake', 'path', '.env') + config._find_file = MagicMock(return_value=fake_path) + + # Mock open to return our test env content + with patch('decouple.open', return_value=StringIO(ENVFILE), create=True): + # LIST_VALUES= (empty) should return an empty list with Csv cast + assert [] == config('LIST_VALUES', cast=Csv()) + # With default values + assert ['default'] == config('LIST_VALUES', default='default', cast=Csv()) diff --git a/tests/test_empty_values_ini.py b/tests/test_empty_values_ini.py new file mode 100644 index 0000000..964a138 --- /dev/null +++ b/tests/test_empty_values_ini.py @@ -0,0 +1,74 @@ +# coding: utf-8 +import os +import sys +from mock import patch +import pytest +from decouple import Config, RepositoryIni, UndefinedValueError + +# Useful for very coarse version differentiation. +PY3 = sys.version_info[0] == 3 + +if PY3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +INIFILE = ''' +[settings] +# Empty values +DB_PORT= +SECRET= +DEBUG= +LIST_VALUES= + +# Non-empty values for comparison +DB_HOST=localhost +SECRET_KEY=abc123 +''' + +@pytest.fixture(scope='module') +def config(): + with patch('decouple.open', return_value=StringIO(INIFILE), create=True): + return Config(RepositoryIni('settings.ini')) + + +def test_ini_empty_value_with_default_int(): + """Test that an empty DB_PORT with default and int cast works correctly in INI files.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(INIFILE), create=True): + config = Config(RepositoryIni('settings.ini')) + # DB_PORT= (empty) should use the default value 5432 + assert 5432 == config('DB_PORT', default=5432, cast=int) + + +def test_ini_empty_value_with_default_none(): + """Test that an empty SECRET with default=None works correctly in INI files.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(INIFILE), create=True): + config = Config(RepositoryIni('settings.ini')) + # SECRET= (empty) should use the default value None + assert None is config('SECRET', default=None) + + +def test_ini_empty_value_with_default_bool(): + """Test that an empty DEBUG with default and bool cast works correctly in INI files.""" + # Create a fresh config for this test to avoid fixture caching issues + with patch('decouple.open', return_value=StringIO(INIFILE), create=True): + config = Config(RepositoryIni('settings.ini')) + # DEBUG= (empty) should use the default value True + assert True is config('DEBUG', default=True, cast=bool) + # Empty value without default should be False when cast to bool + assert False is config('DEBUG', cast=bool) + + +def test_ini_empty_value_with_csv_cast(): + """Test that an empty LIST_VALUES with Csv cast works correctly in INI files.""" + # Create a fresh config for this test to avoid fixture caching issues + from decouple import Csv + with patch('decouple.open', return_value=StringIO(INIFILE), create=True): + config = Config(RepositoryIni('settings.ini')) + # LIST_VALUES= (empty) should return an empty list with Csv cast + assert [] == config('LIST_VALUES', cast=Csv()) + # With default values + assert ['default'] == config('LIST_VALUES', default='default', cast=Csv()) diff --git a/tests/test_env.py b/tests/test_env.py index a91c95c..afb3d85 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -110,7 +110,7 @@ def test_env_default_none(config): def test_env_empty(config): - assert '' == config('KeyEmpty', default=None) + assert None is config('KeyEmpty', default=None) assert '' == config('KeyEmpty') diff --git a/tests/test_ini.py b/tests/test_ini.py index f610078..15557aa 100644 --- a/tests/test_ini.py +++ b/tests/test_ini.py @@ -100,7 +100,8 @@ def test_ini_default_invalid_bool(config): def test_ini_empty(config): - assert '' == config('KeyEmpty', default=None) + assert None is config('KeyEmpty', default=None) + assert '' == config('KeyEmpty') def test_ini_support_space(config):