From bcc623c7c7bb86d56c772f1ed8ffa40019a7e713 Mon Sep 17 00:00:00 2001 From: hakarrr Date: Sat, 24 Oct 2026 21:51:33 +0530 Subject: [PATCH 1/2] Raise KeyError for missing INI sections Preserve RepositoryIni missing-key behavior when a valid file has no settings section. --- decouple.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/decouple.py b/decouple.py index 9873fc9..8198b26 100644 --- a/decouple.py +++ b/decouple.py @@ -11,10 +11,10 @@ if PYVERSION >= (3, 0, 0): - from configparser import ConfigParser, NoOptionError + from configparser import ConfigParser, NoOptionError, NoSectionError text_type = str else: - from ConfigParser import SafeConfigParser as ConfigParser, NoOptionError + from ConfigParser import SafeConfigParser as ConfigParser, NoOptionError, NoSectionError text_type = unicode if PYVERSION >= (3, 2, 0): @@ -136,7 +136,7 @@ def __contains__(self, key): def __getitem__(self, key): try: return self.parser.get(self.SECTION, key) - except NoOptionError: + except (NoOptionError, NoSectionError): raise KeyError(key) From c400e4b948407f38a6be77d45dc0d2e2c1c91af1 Mon Sep 17 00:00:00 2001 From: hakarrr Date: Sat, 24 Oct 2026 21:51:50 +0530 Subject: [PATCH 2/2] Cover absent INI sections and repository fallback Test valid empty and unrelated-section files, missing-key arguments, fallback lookup and preserved interpolation errors. --- tests/test_ini_missing_section.py | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_ini_missing_section.py diff --git a/tests/test_ini_missing_section.py b/tests/test_ini_missing_section.py new file mode 100644 index 0000000..593d34e --- /dev/null +++ b/tests/test_ini_missing_section.py @@ -0,0 +1,47 @@ +# coding: utf-8 +from io import StringIO + +import pytest +from mock import patch + +from decouple import RepositoryIni + +try: + from configparser import InterpolationMissingOptionError +except ImportError: + from ConfigParser import InterpolationMissingOptionError + + +@pytest.mark.parametrize('contents', ['', '[other]\nkey=value\n']) +def test_ini_without_settings_raises_keyerror(contents): + with patch('decouple.open', return_value=StringIO(contents), create=True): + repository = RepositoryIni('settings.ini') + assert 'missing' not in repository + with pytest.raises(KeyError) as error: + repository['missing'] + assert error.value.args == ('missing',) + + +@pytest.mark.parametrize('contents', ['', '[other]\nkey=value\n']) +def test_ini_without_settings_allows_repository_fallback(contents): + with patch('decouple.open', return_value=StringIO(contents), create=True): + repository = RepositoryIni('settings.ini') + + def lookup(key): + for source in (repository, {'fallback': 'value'}): + try: + return source[key] + except KeyError: + pass + raise KeyError(key) + + assert lookup('fallback') == 'value' + + +def test_ini_preserves_interpolation_errors(): + contents = '[settings]\nkey=%(missing)s\n' + with patch('decouple.open', return_value=StringIO(contents), create=True): + repository = RepositoryIni('settings.ini') + assert 'key' in repository + with pytest.raises(InterpolationMissingOptionError): + repository['key']