Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions decouple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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))
Expand Down
75 changes: 75 additions & 0 deletions tests/test_empty_values.py
Original file line number Diff line number Diff line change
@@ -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())
89 changes: 89 additions & 0 deletions tests/test_empty_values_autoconfig.py
Original file line number Diff line number Diff line change
@@ -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())
74 changes: 74 additions & 0 deletions tests/test_empty_values_ini.py
Original file line number Diff line number Diff line change
@@ -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())
2 changes: 1 addition & 1 deletion tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')


Expand Down
3 changes: 2 additions & 1 deletion tests/test_ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down