Skip to content

Commit d76eec4

Browse files
committed
Track all config source for repr
1 parent 0883b52 commit d76eec4

File tree

2 files changed

+31
-12
lines changed

2 files changed

+31
-12
lines changed

Lib/configparser.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ def __init__(self, defaults=None, dict_type=_default_dict,
663663
full=tuple(comment_prefixes or ()),
664664
inline=tuple(inline_comment_prefixes or ()),
665665
)
666+
self._loaded_sources = []
666667
self._strict = strict
667668
self._allow_no_value = allow_no_value
668669
self._empty_lines_in_values = empty_lines_in_values
@@ -682,7 +683,7 @@ def __init__(self, defaults=None, dict_type=_default_dict,
682683
if defaults:
683684
self._read_defaults(defaults)
684685
self._allow_unnamed_section = allow_unnamed_section
685-
self._loaded_files = []
686+
686687

687688
def defaults(self):
688689
return self._defaults
@@ -746,12 +747,13 @@ def read(self, filenames, encoding=None):
746747
try:
747748
with open(filename, encoding=encoding) as fp:
748749
self._read(fp, filename)
750+
self._loaded_sources.append(filename)
749751
except OSError:
750752
continue
751753
if isinstance(filename, os.PathLike):
752754
filename = os.fspath(filename)
753755
read_ok.append(filename)
754-
self._loaded_files.extend(read_ok)
756+
755757
return read_ok
756758

757759
def read_file(self, f, source=None):
@@ -768,11 +770,13 @@ def read_file(self, f, source=None):
768770
except AttributeError:
769771
source = '<???>'
770772
self._read(f, source)
773+
self._loaded_sources.append(source)
771774

772775
def read_string(self, string, source='<string>'):
773776
"""Read configuration from a given string."""
774777
sfile = io.StringIO(string)
775778
self.read_file(sfile, source)
779+
self._loaded_sources.append(source)
776780

777781
def read_dict(self, dictionary, source='<dict>'):
778782
"""Read configuration from a dictionary.
@@ -804,6 +808,8 @@ def read_dict(self, dictionary, source='<dict>'):
804808
raise DuplicateOptionError(section, key, source)
805809
elements_added.add((section, key))
806810
self.set(section, key, value)
811+
self._loaded_sources.append(source)
812+
807813

808814
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
809815
"""Get an option value for a given section.
@@ -1061,12 +1067,15 @@ def __repr__(self):
10611067
"interpolation": type(self._interpolation).__name__,
10621068
}
10631069
init_params = {k: v for k, v in init_params.items() if v is not None}
1064-
10651070
state_summary = {
1066-
"loaded_files": self._loaded_files if hasattr(self, '_loaded_files') else "(no files loaded)",
1067-
"sections": len(self._sections),
1071+
"loaded_sources": self._loaded_sources,
1072+
"sections_count": len(self._sections),
1073+
"sections": list(self._sections.keys())[:5], # Limit to 5 section names for readability
10681074
}
10691075

1076+
if len(self._sections) > 5:
1077+
state_summary["sections_truncated"] = f"...and {len(self._sections) - 5} more"
1078+
10701079
return (f"<{self.__class__.__name__}("
10711080
f"params={init_params}, "
10721081
f"state={state_summary})>")

Lib/test/test_configparser.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -983,12 +983,19 @@ def test_set_nonstring_types(self):
983983
def test_str_and_repr(self):
984984
self.maxDiff = None
985985
cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True)
986-
987-
cf.add_section("sect")
988-
cf.set("sect", "option1", "foo")
989-
cf.set("sect", "option2", "bar")
990-
991-
expected_str = "{'sect': {'option1': 'foo', 'option2': 'bar'}}"
986+
cf.add_section("sect1")
987+
cf.add_section("sect2")
988+
cf.add_section("sect3")
989+
cf.add_section("sect4")
990+
cf.add_section("sect5")
991+
cf.add_section("sect6") # Added more than 5 sections to trigger truncation
992+
cf.set("sect1", "option1", "foo")
993+
cf.set("sect2", "option2", "bar")
994+
995+
expected_str = (
996+
"{'sect1': {'option1': 'foo'}, 'sect2': {'option2': 'bar'}, 'sect3': {}, "
997+
"'sect4': {}, 'sect5': {}, 'sect6': {}}"
998+
)
992999
self.assertEqual(str(cf), expected_str)
9931000

9941001
dict_type = type(cf._dict).__name__
@@ -998,10 +1005,13 @@ def test_str_and_repr(self):
9981005
f"params={{'dict_type': '{dict_type}', 'allow_no_value': True, "
9991006
"'delimiters': ('=',), 'strict': True, 'default_section': 'DEFAULT', "
10001007
"'interpolation': 'BasicInterpolation'}, "
1001-
"state={'loaded_files': [], 'sections': 1})>"
1008+
"state={'loaded_sources': [], 'sections_count': 6, "
1009+
"'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], "
1010+
"'sections_truncated': '...and 1 more'})>"
10021011
)
10031012
self.assertEqual(repr(cf), expected_repr)
10041013

1014+
10051015
def test_add_section_default(self):
10061016
cf = self.newconfig()
10071017
self.assertRaises(ValueError, cf.add_section, self.default_section)

0 commit comments

Comments
 (0)