Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b3ce3f3
gh-127011: Add __str__ and __repr__ to ConfigParser
Agent-Hellboy Nov 19, 2024
d603aa8
Add test
Agent-Hellboy Nov 19, 2024
91db566
Remove unnecessary comments
Agent-Hellboy Nov 19, 2024
7f3633d
Fix whitespaces
Agent-Hellboy Nov 19, 2024
5cd1d1f
Revert unittest
Agent-Hellboy Nov 19, 2024
5255025
Remove unnecessary changes
Agent-Hellboy Nov 19, 2024
dc71b39
change approach for repr
Agent-Hellboy Nov 22, 2024
c59d4fa
📜🤖 Added by blurb_it.
blurb-it[bot] Nov 23, 2024
d5e944f
Add test and fix parsing error of news
Agent-Hellboy Nov 23, 2024
0883b52
Remove extra whitespace
Agent-Hellboy Nov 23, 2024
d76eec4
Track all config source for repr
Agent-Hellboy Nov 23, 2024
433432b
Remove extra whitespaces
Agent-Hellboy Nov 23, 2024
acd31ae
Change str response
Agent-Hellboy Nov 25, 2024
43b7ff3
Merge branch 'main' into fix-issue-127011
Agent-Hellboy Nov 25, 2024
79e4892
Add my name Misc/ACKS
Agent-Hellboy Nov 25, 2024
913c2a4
Fix alphabetical order in Misc/ACKS
Agent-Hellboy Nov 25, 2024
e60ffe4
gh-127011: Add __str__ and __repr__ to ConfigParser
Agent-Hellboy Nov 19, 2024
0ed179a
Add test
Agent-Hellboy Nov 19, 2024
726b152
Remove unnecessary comments
Agent-Hellboy Nov 19, 2024
70fb9a5
Fix whitespaces
Agent-Hellboy Nov 19, 2024
38809c9
Revert unittest
Agent-Hellboy Nov 19, 2024
081c340
Remove unnecessary changes
Agent-Hellboy Nov 19, 2024
50838d5
change approach for repr
Agent-Hellboy Nov 22, 2024
2560eb9
📜🤖 Added by blurb_it.
blurb-it[bot] Nov 23, 2024
cb53ff2
Add test and fix parsing error of news
Agent-Hellboy Nov 23, 2024
155b8ef
Remove extra whitespace
Agent-Hellboy Nov 23, 2024
1a03c72
Track all config source for repr
Agent-Hellboy Nov 23, 2024
acfa969
Remove extra whitespaces
Agent-Hellboy Nov 23, 2024
8415943
Change str response
Agent-Hellboy Nov 25, 2024
ee219a6
Add my name Misc/ACKS
Agent-Hellboy Nov 25, 2024
63d58e0
Fix alphabetical order in Misc/ACKS
Agent-Hellboy Nov 25, 2024
9b107e2
fix merge conflicts
Agent-Hellboy Apr 25, 2025
50fdb2b
Merge branch 'main' into fix-issue-127011
Agent-Hellboy Apr 25, 2025
6427a0d
fix test
Agent-Hellboy Apr 25, 2025
d547497
Merge branch 'fix-issue-127011' of github.com:Agent-Hellboy/cpython i…
Agent-Hellboy Apr 25, 2025
3817877
Merge branch 'python:main' into fix-issue-127011
Agent-Hellboy Apr 25, 2025
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
31 changes: 31 additions & 0 deletions Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -1040,6 +1042,35 @@ 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: {key: value for key, value in self.items(section, raw=True)}
for section in self.sections()
}
return str(config_dict)
Copy link
Contributor

@brianschubert brianschubert Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this implementation leaves too much risk for confusion. With this, you can have str(config) render as "{'a': {'b': XYZ}}", but have config["a"]["b"] evaluate to something other than XYZ (or even raise an exception). That seems rather undesirable, especially for something that's intended to be used for debugging/logging.

Personally, I don't see the benefit in defining __str__. I think it would be better to let uses pick the dict-like representation they want and the tradeoffs to go with. For example, users that want an exception-safe dict representation without interpolations can get one with ConfigParser._sections, and users that a want dict representation that matches the runtime indexing behavior (but which may raise exceptions) can use {k: dict(v) for k, v in config.items()}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @brianschubert
Thank you for the feedback. Could you please share an example to help clarify your concerns?

Based on your input, I plan to make it fallback to repr for string representations and add a new API, to_dict, which could be useful for scenarios like dumping the configuration into JSON and several other use cases.

I’m waiting for others to agree with you before proceeding.

Copy link
Member

@ZeroIntensity ZeroIntensity Nov 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave a new API to another PR, let's just focus on __str__ and __repr__ here. Regarding what __str__ should do, I suggested in the issue that it display <ConfigParser: {json representation ...}> to mitigate confusion with dictionaries. I think that (or a variation of it) is the best approach here.

IMO, recommending _sections is a terrible idea for debugging, and we definitely should not write off a feature because it exists. Namely:

  • It's undocumented, so users have no idea what it will do.
  • Futhermore to being undocumented, it's not exposed by typeshed.
  • And most importantly, it's a private implementation detail. Users aren't supposed to rely on it, because we're allowed to change (or remove) it at any time in patch releases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And most importantly, it's a private implementation detail. Users aren't supposed to rely on it, because we're allowed to change (or remove) it at any time in patch releases.

exactly.

Leave a new API to another PR, let's just focus on __str__ and __repr__ here. Regarding what __str__ should do, I suggested in the issue that it display <ConfigParser: {json representation ...}>

Done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ZeroIntensity , should I create an issue for adding to_dict ?



def __repr__(self):
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.

Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``__str__`` and ``__repr__`` methods have been added to the :class:`configparser.RawConfigParser`
Loading