From b3ce3f32505a5c00d5e8902dd744ac649d6bf0ed Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:42:48 +0530 Subject: [PATCH 01/31] gh-127011: Add __str__ and __repr__ to ConfigParser --- Lib/configparser.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/configparser.py b/Lib/configparser.py index 420dce77c234e1..9ae36c8365e3d0 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1040,6 +1040,14 @@ def __iter__(self): # XXX does it break when underlying container state changed? return itertools.chain((self.default_section,), self._sections.keys()) + def __str__(self): + config_dict = {section: dict(self.items(section)) for section in self.sections()} + return str(config_dict) + + def __repr__(self): + return f"" + + def _read(self, fp, fpname): """Parse a sectioned configuration file. From d603aa8410179bf8e98d30dd193d28dd8f413001 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:52:37 +0530 Subject: [PATCH 02/31] Add test --- Lib/test/test_configparser.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index e3c5d08dd1e7d1..7adbccbdd550c2 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,6 +1080,7 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): + cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1126,6 +1127,34 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) +def test_repr_and_str_representation(self): + """Test repr and str representation of RawConfigParser.""" + config_string = """\ +[Section1] +key1 = value1 +key2 = value2 + +[Section2] +keyA = valueA +keyB = valueB +""" + cf = self.newconfig() + cf.read_string(config_string) # Use a left-aligned string to avoid ParsingError + cf.optionxform = str # Preserve key casing + + # Test `repr` representation + expected_repr = ( + "ConfigParser(default_section='DEFAULT', " + "interpolation=None)" + ) + self.assertEqual(repr(cf), expected_repr) + + # Test `str` representation + expected_str = ( + "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " + "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" + ) + self.assertEqual(str(cf), expected_str) class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') From 91db566f982a96f32880c460b1b588b59221c7d2 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:54:19 +0530 Subject: [PATCH 03/31] Remove unnecessary comments --- Lib/test/test_configparser.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 7adbccbdd550c2..67819766256305 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1139,17 +1139,14 @@ def test_repr_and_str_representation(self): keyB = valueB """ cf = self.newconfig() - cf.read_string(config_string) # Use a left-aligned string to avoid ParsingError - cf.optionxform = str # Preserve key casing + cf.read_string(config_string) + cf.optionxform = str - # Test `repr` representation expected_repr = ( "ConfigParser(default_section='DEFAULT', " "interpolation=None)" ) self.assertEqual(repr(cf), expected_repr) - - # Test `str` representation expected_str = ( "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" From 7f3633d8fba845c0cb577cad84394b5085068300 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 21:22:11 +0530 Subject: [PATCH 04/31] Fix whitespaces --- Lib/test/test_configparser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 67819766256305..1c79c9d331625c 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,7 +1080,7 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): - + cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1139,8 +1139,8 @@ def test_repr_and_str_representation(self): keyB = valueB """ cf = self.newconfig() - cf.read_string(config_string) - cf.optionxform = str + cf.read_string(config_string) + cf.optionxform = str expected_repr = ( "ConfigParser(default_section='DEFAULT', " From 5cd1d1f1d8ebaabef7673fb734ccdb06b593789b Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Wed, 20 Nov 2024 04:13:07 +0530 Subject: [PATCH 05/31] Revert unittest --- Lib/test/test_configparser.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 1c79c9d331625c..fb5e6d4c2032af 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1127,32 +1127,6 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) -def test_repr_and_str_representation(self): - """Test repr and str representation of RawConfigParser.""" - config_string = """\ -[Section1] -key1 = value1 -key2 = value2 - -[Section2] -keyA = valueA -keyB = valueB -""" - cf = self.newconfig() - cf.read_string(config_string) - cf.optionxform = str - - expected_repr = ( - "ConfigParser(default_section='DEFAULT', " - "interpolation=None)" - ) - self.assertEqual(repr(cf), expected_repr) - expected_str = ( - "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " - "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" - ) - self.assertEqual(str(cf), expected_str) - class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') From 525502546f729879606770425563cd577603b6d8 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Wed, 20 Nov 2024 04:20:42 +0530 Subject: [PATCH 06/31] Remove unnecessary changes --- Lib/test/test_configparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index fb5e6d4c2032af..e3c5d08dd1e7d1 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,7 +1080,6 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): - cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1127,6 +1126,7 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) + class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') From dc71b39975c8424290041454479d819e094db999 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Fri, 22 Nov 2024 15:56:45 +0530 Subject: [PATCH 07/31] change approach for repr --- Lib/configparser.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 9ae36c8365e3d0..ab63e672d94677 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -682,6 +682,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, if defaults: self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section + self._loaded_files = [] def defaults(self): return self._defaults @@ -750,6 +751,7 @@ def read(self, filenames, encoding=None): if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) + self._loaded_files.extend(read_ok) return read_ok def read_file(self, f, source=None): @@ -1041,12 +1043,31 @@ def __iter__(self): return itertools.chain((self.default_section,), self._sections.keys()) def __str__(self): - config_dict = {section: dict(self.items(section)) for section in self.sections()} + config_dict = { + section: dict(self.items(section)) for section in self.sections() + } return str(config_dict) def __repr__(self): - return f"" - + init_params = { + "defaults": self._defaults if self._defaults else None, + "dict_type": type(self._dict).__name__, + "allow_no_value": self._allow_no_value, + "delimiters": self._delimiters, + "strict": self._strict, + "default_section": self.default_section, + "interpolation": type(self._interpolation).__name__, + } + init_params = {k: v for k, v in init_params.items() if v is not None} + + state_summary = { + "loaded_files": self._loaded_files if hasattr(self, '_loaded_files') else "(no files loaded)", + "sections": len(self._sections), + } + + return (f"<{self.__class__.__name__}(" + f"params={init_params}, " + f"state={state_summary})>") def _read(self, fp, fpname): """Parse a sectioned configuration file. From c59d4faf009978f2045fba27c07c82d1b6ab0bed Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:34:58 +0000 Subject: [PATCH 08/31] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst diff --git a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst new file mode 100644 index 00000000000000..e2199743350007 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst @@ -0,0 +1 @@ +The __str__ and __repr__ methods have been added to the :class:configparser.RawConfigParser From d5e944fec844e2a23626727a3c84e2e09d289e9e Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 23 Nov 2024 19:12:52 +0530 Subject: [PATCH 09/31] Add test and fix parsing error of news --- Lib/configparser.py | 4 ++- Lib/test/test_configparser.py | 26 +++++++++++++++++++ ...-11-23-13-34-55.gh-issue-127011.dT88uF.rst | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index ab63e672d94677..5562a91f89c4b0 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1044,10 +1044,12 @@ def __iter__(self): def __str__(self): config_dict = { - section: dict(self.items(section)) for section in self.sections() + section: {key: value for key, value in self.items(section, raw=True)} + for section in self.sections() } return str(config_dict) + def __repr__(self): init_params = { "defaults": self._defaults if self._defaults else None, diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index e3c5d08dd1e7d1..8f0cca71bbf6d5 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -980,6 +980,28 @@ def test_set_nonstring_types(self): self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!") self.assertRaises(TypeError, cf.add_section, 123) + def test_str_and_repr(self): + self.maxDiff = None + cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) + + cf.add_section("sect") + cf.set("sect", "option1", "foo") + cf.set("sect", "option2", "bar") + + expected_str = "{'sect': {'option1': 'foo', 'option2': 'bar'}}" + self.assertEqual(str(cf), expected_str) + + dict_type = type(cf._dict).__name__ + + expected_repr = ( + f"<{cf.__class__.__name__}(" + f"params={{'dict_type': '{dict_type}', 'allow_no_value': True, " + "'delimiters': ('=',), 'strict': True, 'default_section': 'DEFAULT', " + "'interpolation': 'BasicInterpolation'}, " + "state={'loaded_files': [], 'sections': 1})>" + ) + self.assertEqual(repr(cf), expected_repr) + def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) @@ -1153,6 +1175,8 @@ def test_reading(self): self.assertEqual(cf.get("global", "hosts allow"), "127.") self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") + + class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = configparser.ExtendedInterpolation() @@ -1300,6 +1324,8 @@ def test_case_sensitivity_conflicts(self): eq(cf['random']['foo'], 'value redefined') eq(cf['random']['Foo'], 'A Better Value Redefined') + + def test_other_errors(self): cf = self.fromstring(""" [interpolation fail] diff --git a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst index e2199743350007..78d3e7ec7742b9 100644 --- a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst +++ b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst @@ -1 +1 @@ -The __str__ and __repr__ methods have been added to the :class:configparser.RawConfigParser +The ``__str__`` and ``__repr__`` methods have been added to the :class:`configparser.RawConfigParser` From 0883b5287c270abaef93ed2ef5f7b7ac22ecc4e2 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 23 Nov 2024 19:18:37 +0530 Subject: [PATCH 10/31] Remove extra whitespace --- Lib/test/test_configparser.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 8f0cca71bbf6d5..1660f6f93bd7e9 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1175,8 +1175,6 @@ def test_reading(self): self.assertEqual(cf.get("global", "hosts allow"), "127.") self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") - - class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = configparser.ExtendedInterpolation() @@ -1324,8 +1322,6 @@ def test_case_sensitivity_conflicts(self): eq(cf['random']['foo'], 'value redefined') eq(cf['random']['Foo'], 'A Better Value Redefined') - - def test_other_errors(self): cf = self.fromstring(""" [interpolation fail] From d76eec4006583bd555b63ca84e372a65486c3954 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 24 Nov 2024 01:37:11 +0530 Subject: [PATCH 11/31] Track all config source for repr --- Lib/configparser.py | 19 ++++++++++++++----- Lib/test/test_configparser.py | 24 +++++++++++++++++------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 5562a91f89c4b0..3914f584449b44 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -663,6 +663,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, full=tuple(comment_prefixes or ()), inline=tuple(inline_comment_prefixes or ()), ) + self._loaded_sources = [] self._strict = strict self._allow_no_value = allow_no_value self._empty_lines_in_values = empty_lines_in_values @@ -682,7 +683,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, if defaults: self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section - self._loaded_files = [] + def defaults(self): return self._defaults @@ -746,12 +747,13 @@ def read(self, filenames, encoding=None): try: with open(filename, encoding=encoding) as fp: self._read(fp, filename) + self._loaded_sources.append(filename) except OSError: continue if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) - self._loaded_files.extend(read_ok) + return read_ok def read_file(self, f, source=None): @@ -768,11 +770,13 @@ def read_file(self, f, source=None): except AttributeError: source = '' self._read(f, source) + self._loaded_sources.append(source) def read_string(self, string, source=''): """Read configuration from a given string.""" sfile = io.StringIO(string) self.read_file(sfile, source) + self._loaded_sources.append(source) def read_dict(self, dictionary, source=''): """Read configuration from a dictionary. @@ -804,6 +808,8 @@ def read_dict(self, dictionary, source=''): raise DuplicateOptionError(section, key, source) elements_added.add((section, key)) self.set(section, key, value) + self._loaded_sources.append(source) + def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. @@ -1061,12 +1067,15 @@ def __repr__(self): "interpolation": type(self._interpolation).__name__, } init_params = {k: v for k, v in init_params.items() if v is not None} - state_summary = { - "loaded_files": self._loaded_files if hasattr(self, '_loaded_files') else "(no files loaded)", - "sections": len(self._sections), + "loaded_sources": self._loaded_sources, + "sections_count": len(self._sections), + "sections": list(self._sections.keys())[:5], # Limit to 5 section names for readability } + if len(self._sections) > 5: + state_summary["sections_truncated"] = f"...and {len(self._sections) - 5} more" + return (f"<{self.__class__.__name__}(" f"params={init_params}, " f"state={state_summary})>") diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 1660f6f93bd7e9..2b5c4b2408bd43 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -983,12 +983,19 @@ def test_set_nonstring_types(self): def test_str_and_repr(self): self.maxDiff = None cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) - - cf.add_section("sect") - cf.set("sect", "option1", "foo") - cf.set("sect", "option2", "bar") - - expected_str = "{'sect': {'option1': 'foo', 'option2': 'bar'}}" + cf.add_section("sect1") + cf.add_section("sect2") + cf.add_section("sect3") + cf.add_section("sect4") + cf.add_section("sect5") + cf.add_section("sect6") # Added more than 5 sections to trigger truncation + cf.set("sect1", "option1", "foo") + cf.set("sect2", "option2", "bar") + + expected_str = ( + "{'sect1': {'option1': 'foo'}, 'sect2': {'option2': 'bar'}, 'sect3': {}, " + "'sect4': {}, 'sect5': {}, 'sect6': {}}" + ) self.assertEqual(str(cf), expected_str) dict_type = type(cf._dict).__name__ @@ -998,10 +1005,13 @@ def test_str_and_repr(self): f"params={{'dict_type': '{dict_type}', 'allow_no_value': True, " "'delimiters': ('=',), 'strict': True, 'default_section': 'DEFAULT', " "'interpolation': 'BasicInterpolation'}, " - "state={'loaded_files': [], 'sections': 1})>" + "state={'loaded_sources': [], 'sections_count': 6, " + "'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], " + "'sections_truncated': '...and 1 more'})>" ) self.assertEqual(repr(cf), expected_repr) + def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) From 433432b0a331ff99f70bca1e5b66e3c0957ae251 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 24 Nov 2024 01:39:32 +0530 Subject: [PATCH 12/31] Remove extra whitespaces --- Lib/configparser.py | 2 -- Lib/test/test_configparser.py | 1 - 2 files changed, 3 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 3914f584449b44..d14ded90bb2346 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -753,7 +753,6 @@ def read(self, filenames, encoding=None): if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) - return read_ok def read_file(self, f, source=None): @@ -810,7 +809,6 @@ def read_dict(self, dictionary, source=''): self.set(section, key, value) self._loaded_sources.append(source) - def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 2b5c4b2408bd43..88e92f1ae479b3 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1011,7 +1011,6 @@ def test_str_and_repr(self): ) self.assertEqual(repr(cf), expected_repr) - def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) From acd31aea699afaeac5c8f84e1e896126b31d7437 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 11:10:51 +0530 Subject: [PATCH 13/31] Change str response --- Lib/configparser.py | 2 +- Lib/test/test_configparser.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index d14ded90bb2346..45ffbf03da3cc7 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1051,7 +1051,7 @@ def __str__(self): section: {key: value for key, value in self.items(section, raw=True)} for section in self.sections() } - return str(config_dict) + return f"" def __repr__(self): diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 88e92f1ae479b3..2fa01357a39546 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -988,16 +988,18 @@ def test_str_and_repr(self): cf.add_section("sect3") cf.add_section("sect4") cf.add_section("sect5") - cf.add_section("sect6") # Added more than 5 sections to trigger truncation + cf.add_section("sect6") cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") + expected_str = ( - "{'sect1': {'option1': 'foo'}, 'sect2': {'option2': 'bar'}, 'sect3': {}, " - "'sect4': {}, 'sect5': {}, 'sect6': {}}" + "" ) self.assertEqual(str(cf), expected_str) + dict_type = type(cf._dict).__name__ expected_repr = ( From 79e4892a48a7cfea4295f778e71273a3feea1658 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 11:26:08 +0530 Subject: [PATCH 14/31] Add my name Misc/ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 08cd293eac3835..197a346389e0c1 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -476,6 +476,7 @@ Mehdi Drissi Derk Drukker John DuBois Paul Dubois +Prince Roshan Jacques Ducasse Andrei Dorian Duma Graham Dumpleton From 913c2a45d668bc7f9f03773a0c4984e589cf37c8 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 22:27:13 +0530 Subject: [PATCH 15/31] Fix alphabetical order in Misc/ACKS --- Misc/ACKS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/ACKS b/Misc/ACKS index 197a346389e0c1..1e980822804069 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -476,7 +476,6 @@ Mehdi Drissi Derk Drukker John DuBois Paul Dubois -Prince Roshan Jacques Ducasse Andrei Dorian Duma Graham Dumpleton @@ -1584,6 +1583,7 @@ Erik Rose Mark Roseman Josh Rosenberg Jim Roskind +Prince Roshan Brian Rosner Ignacio Rossi Guido van Rossum From e60ffe4f2ce5ec8f6c0ab176a37f2724eabff10f Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:42:48 +0530 Subject: [PATCH 16/31] gh-127011: Add __str__ and __repr__ to ConfigParser --- Lib/configparser.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/configparser.py b/Lib/configparser.py index 239fda60a02ca0..a3814ce965aee3 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1048,6 +1048,14 @@ def __iter__(self): # XXX does it break when underlying container state changed? return itertools.chain((self.default_section,), self._sections.keys()) + def __str__(self): + config_dict = {section: dict(self.items(section)) for section in self.sections()} + return str(config_dict) + + def __repr__(self): + return f"" + + def _read(self, fp, fpname): """Parse a sectioned configuration file. From 0ed179aba68c9003e7bb6c3817a54623e3ec4cc3 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:52:37 +0530 Subject: [PATCH 17/31] Add test --- Lib/test/test_configparser.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 23904d17d326d8..86e5ea61173f95 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,6 +1080,7 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): + cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1126,6 +1127,34 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) +def test_repr_and_str_representation(self): + """Test repr and str representation of RawConfigParser.""" + config_string = """\ +[Section1] +key1 = value1 +key2 = value2 + +[Section2] +keyA = valueA +keyB = valueB +""" + cf = self.newconfig() + cf.read_string(config_string) # Use a left-aligned string to avoid ParsingError + cf.optionxform = str # Preserve key casing + + # Test `repr` representation + expected_repr = ( + "ConfigParser(default_section='DEFAULT', " + "interpolation=None)" + ) + self.assertEqual(repr(cf), expected_repr) + + # Test `str` representation + expected_str = ( + "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " + "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" + ) + self.assertEqual(str(cf), expected_str) class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') From 726b152c147c2c0c8a5372dcfc1aef9e5611163e Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 20:54:19 +0530 Subject: [PATCH 18/31] Remove unnecessary comments --- Lib/test/test_configparser.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 86e5ea61173f95..623222a4242a70 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1139,17 +1139,14 @@ def test_repr_and_str_representation(self): keyB = valueB """ cf = self.newconfig() - cf.read_string(config_string) # Use a left-aligned string to avoid ParsingError - cf.optionxform = str # Preserve key casing + cf.read_string(config_string) + cf.optionxform = str - # Test `repr` representation expected_repr = ( "ConfigParser(default_section='DEFAULT', " "interpolation=None)" ) self.assertEqual(repr(cf), expected_repr) - - # Test `str` representation expected_str = ( "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" From 70fb9a5abd0ef06071b808e663dea8cf9290013f Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Tue, 19 Nov 2024 21:22:11 +0530 Subject: [PATCH 19/31] Fix whitespaces --- Lib/test/test_configparser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 623222a4242a70..ff523bb7d0b5cc 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,7 +1080,7 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): - + cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1139,8 +1139,8 @@ def test_repr_and_str_representation(self): keyB = valueB """ cf = self.newconfig() - cf.read_string(config_string) - cf.optionxform = str + cf.read_string(config_string) + cf.optionxform = str expected_repr = ( "ConfigParser(default_section='DEFAULT', " From 38809c963b52be2325f5f29264a6662bc7fb932e Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Wed, 20 Nov 2024 04:13:07 +0530 Subject: [PATCH 20/31] Revert unittest --- Lib/test/test_configparser.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index ff523bb7d0b5cc..01083834f2ac65 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1127,32 +1127,6 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) -def test_repr_and_str_representation(self): - """Test repr and str representation of RawConfigParser.""" - config_string = """\ -[Section1] -key1 = value1 -key2 = value2 - -[Section2] -keyA = valueA -keyB = valueB -""" - cf = self.newconfig() - cf.read_string(config_string) - cf.optionxform = str - - expected_repr = ( - "ConfigParser(default_section='DEFAULT', " - "interpolation=None)" - ) - self.assertEqual(repr(cf), expected_repr) - expected_str = ( - "{'Section1': {'key1': 'value1', 'key2': 'value2'}, " - "'Section2': {'keyA': 'valueA', 'keyB': 'valueB'}}" - ) - self.assertEqual(str(cf), expected_str) - class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') From 081c34097d40a60aedef2dff12791b8d54d96b94 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Wed, 20 Nov 2024 04:20:42 +0530 Subject: [PATCH 21/31] Remove unnecessary changes --- Lib/test/test_configparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 01083834f2ac65..23904d17d326d8 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1080,7 +1080,6 @@ class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): - cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), @@ -1127,6 +1126,7 @@ def test_defaults_keyword(self): cf = self.newconfig(defaults={"A": 5.2}) self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) + class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') From 50838d54bcf9bfcf0c3b2ac323c551386ffa0c61 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Fri, 22 Nov 2024 15:56:45 +0530 Subject: [PATCH 22/31] change approach for repr --- Lib/configparser.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index a3814ce965aee3..543d11029dee3e 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -689,6 +689,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, if defaults: self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section + self._loaded_files = [] def defaults(self): return self._defaults @@ -757,6 +758,7 @@ def read(self, filenames, encoding=None): if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) + self._loaded_files.extend(read_ok) return read_ok def read_file(self, f, source=None): @@ -1049,12 +1051,31 @@ def __iter__(self): return itertools.chain((self.default_section,), self._sections.keys()) def __str__(self): - config_dict = {section: dict(self.items(section)) for section in self.sections()} + config_dict = { + section: dict(self.items(section)) for section in self.sections() + } return str(config_dict) def __repr__(self): - return f"" - + init_params = { + "defaults": self._defaults if self._defaults else None, + "dict_type": type(self._dict).__name__, + "allow_no_value": self._allow_no_value, + "delimiters": self._delimiters, + "strict": self._strict, + "default_section": self.default_section, + "interpolation": type(self._interpolation).__name__, + } + init_params = {k: v for k, v in init_params.items() if v is not None} + + state_summary = { + "loaded_files": self._loaded_files if hasattr(self, '_loaded_files') else "(no files loaded)", + "sections": len(self._sections), + } + + return (f"<{self.__class__.__name__}(" + f"params={init_params}, " + f"state={state_summary})>") def _read(self, fp, fpname): """Parse a sectioned configuration file. From 2560eb9fa668ccdbbe6aac91b99b9d8fb3796623 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:34:58 +0000 Subject: [PATCH 23/31] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst diff --git a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst new file mode 100644 index 00000000000000..e2199743350007 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst @@ -0,0 +1 @@ +The __str__ and __repr__ methods have been added to the :class:configparser.RawConfigParser From cb53ff239435565beec7cd7add02aedad4ea34e4 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 23 Nov 2024 19:12:52 +0530 Subject: [PATCH 24/31] Add test and fix parsing error of news --- Lib/configparser.py | 4 ++- Lib/test/test_configparser.py | 26 +++++++++++++++++++ ...-11-23-13-34-55.gh-issue-127011.dT88uF.rst | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 543d11029dee3e..bd631456188c0f 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1052,10 +1052,12 @@ def __iter__(self): def __str__(self): config_dict = { - section: dict(self.items(section)) for section in self.sections() + section: {key: value for key, value in self.items(section, raw=True)} + for section in self.sections() } return str(config_dict) + def __repr__(self): init_params = { "defaults": self._defaults if self._defaults else None, diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 23904d17d326d8..d8a06522a418f8 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -980,6 +980,28 @@ def test_set_nonstring_types(self): self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!") self.assertRaises(TypeError, cf.add_section, 123) + def test_str_and_repr(self): + self.maxDiff = None + cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) + + cf.add_section("sect") + cf.set("sect", "option1", "foo") + cf.set("sect", "option2", "bar") + + expected_str = "{'sect': {'option1': 'foo', 'option2': 'bar'}}" + self.assertEqual(str(cf), expected_str) + + dict_type = type(cf._dict).__name__ + + expected_repr = ( + f"<{cf.__class__.__name__}(" + f"params={{'dict_type': '{dict_type}', 'allow_no_value': True, " + "'delimiters': ('=',), 'strict': True, 'default_section': 'DEFAULT', " + "'interpolation': 'BasicInterpolation'}, " + "state={'loaded_files': [], 'sections': 1})>" + ) + self.assertEqual(repr(cf), expected_repr) + def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) @@ -1153,6 +1175,8 @@ def test_reading(self): self.assertEqual(cf.get("global", "hosts allow"), "127.") self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") + + class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = configparser.ExtendedInterpolation() @@ -1300,6 +1324,8 @@ def test_case_sensitivity_conflicts(self): eq(cf['random']['foo'], 'value redefined') eq(cf['random']['Foo'], 'A Better Value Redefined') + + def test_other_errors(self): cf = self.fromstring(""" [interpolation fail] diff --git a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst index e2199743350007..78d3e7ec7742b9 100644 --- a/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst +++ b/Misc/NEWS.d/next/Library/2024-11-23-13-34-55.gh-issue-127011.dT88uF.rst @@ -1 +1 @@ -The __str__ and __repr__ methods have been added to the :class:configparser.RawConfigParser +The ``__str__`` and ``__repr__`` methods have been added to the :class:`configparser.RawConfigParser` From 155b8ef511f8b63a1a9801cc8aa8e2e11179c7f2 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 23 Nov 2024 19:18:37 +0530 Subject: [PATCH 25/31] Remove extra whitespace --- Lib/test/test_configparser.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index d8a06522a418f8..365de503e2e9de 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1175,8 +1175,6 @@ def test_reading(self): self.assertEqual(cf.get("global", "hosts allow"), "127.") self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") - - class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = configparser.ExtendedInterpolation() @@ -1324,8 +1322,6 @@ def test_case_sensitivity_conflicts(self): eq(cf['random']['foo'], 'value redefined') eq(cf['random']['Foo'], 'A Better Value Redefined') - - def test_other_errors(self): cf = self.fromstring(""" [interpolation fail] From 1a03c72901bc9a0ab39948c96ee5ee97954ee3a4 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 24 Nov 2024 01:37:11 +0530 Subject: [PATCH 26/31] Track all config source for repr --- Lib/configparser.py | 18 +++++++++++++----- Lib/test/test_configparser.py | 24 +++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index bd631456188c0f..42fd51b47695a5 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -689,7 +689,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, if defaults: self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section - self._loaded_files = [] + def defaults(self): return self._defaults @@ -753,12 +753,13 @@ def read(self, filenames, encoding=None): try: with open(filename, encoding=encoding) as fp: self._read(fp, filename) + self._loaded_sources.append(filename) except OSError: continue if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) - self._loaded_files.extend(read_ok) + return read_ok def read_file(self, f, source=None): @@ -775,11 +776,13 @@ def read_file(self, f, source=None): except AttributeError: source = '' self._read(f, source) + self._loaded_sources.append(source) def read_string(self, string, source=''): """Read configuration from a given string.""" sfile = io.StringIO(string) self.read_file(sfile, source) + self._loaded_sources.append(source) def read_dict(self, dictionary, source=''): """Read configuration from a dictionary. @@ -811,6 +814,8 @@ def read_dict(self, dictionary, source=''): raise DuplicateOptionError(section, key, source) elements_added.add((section, key)) self.set(section, key, value) + self._loaded_sources.append(source) + def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. @@ -1069,12 +1074,15 @@ def __repr__(self): "interpolation": type(self._interpolation).__name__, } init_params = {k: v for k, v in init_params.items() if v is not None} - state_summary = { - "loaded_files": self._loaded_files if hasattr(self, '_loaded_files') else "(no files loaded)", - "sections": len(self._sections), + "loaded_sources": self._loaded_sources, + "sections_count": len(self._sections), + "sections": list(self._sections.keys())[:5], # Limit to 5 section names for readability } + if len(self._sections) > 5: + state_summary["sections_truncated"] = f"...and {len(self._sections) - 5} more" + return (f"<{self.__class__.__name__}(" f"params={init_params}, " f"state={state_summary})>") diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 365de503e2e9de..e81a0ba0685983 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -983,12 +983,19 @@ def test_set_nonstring_types(self): def test_str_and_repr(self): self.maxDiff = None cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) - - cf.add_section("sect") - cf.set("sect", "option1", "foo") - cf.set("sect", "option2", "bar") - - expected_str = "{'sect': {'option1': 'foo', 'option2': 'bar'}}" + cf.add_section("sect1") + cf.add_section("sect2") + cf.add_section("sect3") + cf.add_section("sect4") + cf.add_section("sect5") + cf.add_section("sect6") # Added more than 5 sections to trigger truncation + cf.set("sect1", "option1", "foo") + cf.set("sect2", "option2", "bar") + + expected_str = ( + "{'sect1': {'option1': 'foo'}, 'sect2': {'option2': 'bar'}, 'sect3': {}, " + "'sect4': {}, 'sect5': {}, 'sect6': {}}" + ) self.assertEqual(str(cf), expected_str) dict_type = type(cf._dict).__name__ @@ -998,10 +1005,13 @@ def test_str_and_repr(self): f"params={{'dict_type': '{dict_type}', 'allow_no_value': True, " "'delimiters': ('=',), 'strict': True, 'default_section': 'DEFAULT', " "'interpolation': 'BasicInterpolation'}, " - "state={'loaded_files': [], 'sections': 1})>" + "state={'loaded_sources': [], 'sections_count': 6, " + "'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], " + "'sections_truncated': '...and 1 more'})>" ) self.assertEqual(repr(cf), expected_repr) + def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) From acfa96984ee88a380937983c047b474c02770276 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sun, 24 Nov 2024 01:39:32 +0530 Subject: [PATCH 27/31] Remove extra whitespaces --- Lib/configparser.py | 2 -- Lib/test/test_configparser.py | 1 - 2 files changed, 3 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 42fd51b47695a5..7082373a218a04 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -759,7 +759,6 @@ def read(self, filenames, encoding=None): if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) - return read_ok def read_file(self, f, source=None): @@ -816,7 +815,6 @@ def read_dict(self, dictionary, source=''): self.set(section, key, value) self._loaded_sources.append(source) - def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): """Get an option value for a given section. diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index e81a0ba0685983..e0fcccd97a2582 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1011,7 +1011,6 @@ def test_str_and_repr(self): ) self.assertEqual(repr(cf), expected_repr) - def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) From 8415943a960bafd4a7bf74c079470b0d767063c5 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 11:10:51 +0530 Subject: [PATCH 28/31] Change str response --- Lib/configparser.py | 2 +- Lib/test/test_configparser.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 7082373a218a04..21a9e4e8ed4ac4 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1058,7 +1058,7 @@ def __str__(self): section: {key: value for key, value in self.items(section, raw=True)} for section in self.sections() } - return str(config_dict) + return f"" def __repr__(self): diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index e0fcccd97a2582..40863d3e0f2c4f 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -988,16 +988,18 @@ def test_str_and_repr(self): cf.add_section("sect3") cf.add_section("sect4") cf.add_section("sect5") - cf.add_section("sect6") # Added more than 5 sections to trigger truncation + cf.add_section("sect6") cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") + expected_str = ( - "{'sect1': {'option1': 'foo'}, 'sect2': {'option2': 'bar'}, 'sect3': {}, " - "'sect4': {}, 'sect5': {}, 'sect6': {}}" + "" ) self.assertEqual(str(cf), expected_str) + dict_type = type(cf._dict).__name__ expected_repr = ( From ee219a6920f839df75e258c2380d6d622163a7da Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 11:26:08 +0530 Subject: [PATCH 29/31] Add my name Misc/ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 42068ec6aefbd2..e299252e944e57 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -480,6 +480,7 @@ Mehdi Drissi Derk Drukker John DuBois Paul Dubois +Prince Roshan Jacques Ducasse Andrei Dorian Duma Graham Dumpleton From 63d58e0516454945f337509a5eb6044ed1579d0a Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Mon, 25 Nov 2024 22:27:13 +0530 Subject: [PATCH 30/31] Fix alphabetical order in Misc/ACKS --- Misc/ACKS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/ACKS b/Misc/ACKS index e299252e944e57..61ddc9fcce5836 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -480,7 +480,6 @@ Mehdi Drissi Derk Drukker John DuBois Paul Dubois -Prince Roshan Jacques Ducasse Andrei Dorian Duma Graham Dumpleton @@ -1600,6 +1599,7 @@ Erik Rose Mark Roseman Josh Rosenberg Jim Roskind +Prince Roshan Brian Rosner Ignacio Rossi Guido van Rossum From 6427a0dc462dbda234ddc27f225c547d93008726 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Fri, 25 Apr 2025 23:12:48 +0530 Subject: [PATCH 31/31] fix test --- Lib/configparser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/configparser.py b/Lib/configparser.py index 21a9e4e8ed4ac4..c738de2f385b5d 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -670,6 +670,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, self._optcre = re.compile(self._OPT_TMPL.format(delim=d), re.VERBOSE) self._comments = _CommentSpec(comment_prefixes or (), inline_comment_prefixes or ()) + self._loaded_sources = [] self._strict = strict self._allow_no_value = allow_no_value self._empty_lines_in_values = empty_lines_in_values