diff --git a/src/config/__init__.py b/src/config/__init__.py index c493d61..f50d33b 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -26,7 +26,7 @@ from ._version import __version__, __version_tuple__ # noqa: F401 from .configuration import Configuration from .configuration_set import ConfigurationSet -from .helpers import InterpolateEnumType, InterpolateType +from .helpers import InterpolateEnumType, InterpolateType, parse_env_line def config( @@ -586,9 +586,7 @@ def _reload( data = data.read() data = cast(str, data) result: Dict[str, Any] = dict( - (y.strip() for y in x.split("=", 1)) # type: ignore - for x in data.splitlines() - if x + parse_env_line(x) for x in data.splitlines() if x and not x.startswith("#") ) result = { @@ -613,6 +611,8 @@ def config_from_dotenv( ) -> Configuration: """Create a [Configuration][config.configuration.Configuration] instance from a .env type file. + Lines starting with a # are ignored and treated as comments. + Params: data: path to a .env type file or contents. read_from_file: whether to read from a file path or to interpret. diff --git a/src/config/helpers.py b/src/config/helpers.py index 54afcfa..2db70e7 100644 --- a/src/config/helpers.py +++ b/src/config/helpers.py @@ -212,3 +212,12 @@ def interpolate_object( return [interpolate_object(attr, x, d, method) for x in obj] else: return obj + + +def parse_env_line(line: str) -> Tuple[str, str]: + """Split an env line into variable and value.""" + try: + key, value = tuple(y.strip() for y in line.split("=", 1)) + except ValueError: + raise ValueError("Invalid line %s" % line) from None + return key.strip(), value.strip() diff --git a/tests/test_dotenv.py b/tests/test_dotenv.py index b6e7c84..d492914 100644 --- a/tests/test_dotenv.py +++ b/tests/test_dotenv.py @@ -1,3 +1,4 @@ +import pytest from config import config_from_dotenv, config_from_dict import tempfile @@ -17,6 +18,14 @@ PREFIX__KEY4__C = 3 """ +DOTENV_WITH_COMMENTS = """ +# key 1 +KEY1 = abc +# key 2 +KEY2 = def +# key 3 +KEY3 = 1.1 +""" DICT = { "key1": "abc", @@ -83,3 +92,21 @@ def test_load_dotenv(): # type: ignore cfg = config_from_dotenv(DOTENV_WITH_PREFIXES, lowercase_keys=True, prefix="PREFIX") print(cfg.as_dict()) assert cfg == config_from_dict(DICT_WITH_PREFIXES) + + +def test_load_dotenv_comments(): # type: ignore + cfg = config_from_dotenv(DOTENV_WITH_COMMENTS, lowercase_keys=True) + assert cfg == config_from_dict(dict((k, str(v)) for k, v in DICT.items())) + + +def test_load_dotenv_comments_invalid(): # type: ignore + invalid = """ +# key 1 +VALID=1 +## key2 +INVALID +""" + with pytest.raises(ValueError) as err: + config_from_dotenv(invalid, lowercase_keys=True) + assert 'Invalid line INVALID' in str(err) + diff --git a/tests/test_issues.py b/tests/test_issues.py index 4337991..3acd591 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,9 +1,11 @@ +import pytest from config import ( Configuration, ConfigurationSet, EnvConfiguration, config, config_from_dict, + config_from_dotenv, ) @@ -78,3 +80,11 @@ def test_issue_79(): # type: ignore conf.update(data) assert conf["abc.def"] == "ghi" + + +def test_issue_100(): # type: ignore + invalid = """# key 1\nVALID=1\n## key2\nINVALID\n""" + with pytest.raises(ValueError) as err: + config_from_dotenv(invalid, lowercase_keys=True) + assert 'Invalid line INVALID' in str(err) +