From 4d10deabe479ea6753f191ef5836d87918dcf3c7 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Fri, 25 Apr 2025 23:56:25 +0530 Subject: [PATCH 01/14] gh-127011: Add __str__ and __repr__ to ConfigParser --- Lib/configparser.py | 39 +++++++++++++++++++++++++++++++++++ Lib/test/test_configparser.py | 33 +++++++++++++++++++++++++++++ Misc/ACKS | 1 + 3 files changed, 73 insertions(+) diff --git a/Lib/configparser.py b/Lib/configparser.py index 239fda60a02ca0..951eb7eea419bc 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 @@ -690,6 +691,7 @@ def __init__(self, defaults=None, dict_type=_default_dict, self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section + def defaults(self): return self._defaults @@ -752,6 +754,7 @@ 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): @@ -773,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. @@ -809,6 +814,7 @@ 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. @@ -1048,6 +1054,39 @@ 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 f"" + + + 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_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})>") + + def _read(self, fp, fpname): """Parse a sectioned configuration file. diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 23904d17d326d8..40863d3e0f2c4f 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -980,6 +980,39 @@ 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("sect1") + cf.add_section("sect2") + cf.add_section("sect3") + cf.add_section("sect4") + cf.add_section("sect5") + cf.add_section("sect6") + cf.set("sect1", "option1", "foo") + cf.set("sect2", "option2", "bar") + + + expected_str = ( + "" + ) + 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_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) diff --git a/Misc/ACKS b/Misc/ACKS index 42068ec6aefbd2..61ddc9fcce5836 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1599,6 +1599,7 @@ Erik Rose Mark Roseman Josh Rosenberg Jim Roskind +Prince Roshan Brian Rosner Ignacio Rossi Guido van Rossum From bf75e89ae3be21774d4cc51a1cbdeddf803555e7 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:29:14 +0000 Subject: [PATCH 02/14] =?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/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst new file mode 100644 index 00000000000000..78d3e7ec7742b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst @@ -0,0 +1 @@ +The ``__str__`` and ``__repr__`` methods have been added to the :class:`configparser.RawConfigParser` From d3811af7bd20aaa3126ed0aac641aa547979c885 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:56:17 +0530 Subject: [PATCH 03/14] Update Lib/configparser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/configparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 951eb7eea419bc..9f14ea9d29760c 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1056,7 +1056,7 @@ def __iter__(self): def __str__(self): config_dict = { - section: {key: value for key, value in self.items(section, raw=True)} + section: dict(self.items(section, raw=True)) for section in self.sections() } return f"" From e83d3976a79b42644149f3cf2b1254ff80739e1d Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:56:34 +0530 Subject: [PATCH 04/14] Update Lib/test/test_configparser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_configparser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 40863d3e0f2c4f..da832ba1bb7fce 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -992,7 +992,6 @@ def test_str_and_repr(self): cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") - expected_str = ( "" From 39f4dd2ccb0092eb5fc265cd9f9f5f2804ce4dc8 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:58:15 +0530 Subject: [PATCH 05/14] Update Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- .../Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst index 78d3e7ec7742b9..fad03b0805b03c 100644 --- a/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst +++ b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst @@ -1 +1,2 @@ -The ``__str__`` and ``__repr__`` methods have been added to the :class:`configparser.RawConfigParser` +Implement :meth:`~object.__str__` and :meth:`~object.__repr__` +for :class:`configparser.RawConfigParser` objects From 560bd999fccb8748b649ec1285e60e782f2b1dbf Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:58:23 +0530 Subject: [PATCH 06/14] Update Lib/configparser.py Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/configparser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 9f14ea9d29760c..904950b6cb30c8 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -691,7 +691,6 @@ def __init__(self, defaults=None, dict_type=_default_dict, self._read_defaults(defaults) self._allow_unnamed_section = allow_unnamed_section - def defaults(self): return self._defaults From 3ae9d4702b75207ba05b5ec21621dc111e898f88 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:58:30 +0530 Subject: [PATCH 07/14] Update Lib/configparser.py Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/configparser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 904950b6cb30c8..9f23cce840a4f9 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1060,7 +1060,6 @@ def __str__(self): } return f"" - def __repr__(self): init_params = { "defaults": self._defaults if self._defaults else None, From 140f28a54cdc7b2c171dabc1f022293c1d70a84d Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:58:36 +0530 Subject: [PATCH 08/14] Update Lib/configparser.py Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/configparser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 9f23cce840a4f9..da9784e77dbe02 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1084,7 +1084,6 @@ def __repr__(self): f"params={init_params}, " f"state={state_summary})>") - def _read(self, fp, fpname): """Parse a sectioned configuration file. From 907a10ec1abfcb37e2103d0303500217ebdc9c91 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 00:58:44 +0530 Subject: [PATCH 09/14] Update Lib/test/test_configparser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_configparser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index da832ba1bb7fce..6986b0eac1ce8b 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -998,7 +998,6 @@ def test_str_and_repr(self): ) self.assertEqual(str(cf), expected_str) - dict_type = type(cf._dict).__name__ expected_repr = ( From df4bf21ec5bb36ae2f3a6ec8e582a83d6d25ce01 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 01:10:04 +0530 Subject: [PATCH 10/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/configparser.py | 9 +++++---- .../2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index da9784e77dbe02..d716fbf9960048 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -1071,14 +1071,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} + sections_count = len(self._sections) state_summary = { "loaded_sources": self._loaded_sources, - "sections_count": len(self._sections), - "sections": list(self._sections.keys())[:5], # Limit to 5 section names for readability + "sections_count": sections_count, + "sections": list(self._sections)[:5], # limit to 5 section names for readability } - if len(self._sections) > 5: - state_summary["sections_truncated"] = f"...and {len(self._sections) - 5} more" + if sections_count > 5: + state_summary["sections_truncated"] = f"...and {sections_count - 5} more" return (f"<{self.__class__.__name__}(" f"params={init_params}, " diff --git a/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst index fad03b0805b03c..f98b5f36d59585 100644 --- a/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst +++ b/Misc/NEWS.d/next/Library/2025-04-25-18-29-10.gh-issue-127011.Ipem5z.rst @@ -1,2 +1,2 @@ Implement :meth:`~object.__str__` and :meth:`~object.__repr__` -for :class:`configparser.RawConfigParser` objects +for :class:`configparser.RawConfigParser` objects. From 2333ff4c09916e36a8a8f95f3849035b4661c857 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 01:12:22 +0530 Subject: [PATCH 11/14] resolve comments --- Lib/configparser.py | 3 +-- Lib/test/test_configparser.py | 17 +++++++++++++++-- Misc/ACKS | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index d716fbf9960048..887069c627f1ed 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -753,12 +753,12 @@ 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_sources.extend(read_ok) return read_ok def read_file(self, f, source=None): @@ -781,7 +781,6 @@ 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. diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 6986b0eac1ce8b..dfd3250ab5fa97 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -980,7 +980,7 @@ 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): + def test_str(self): self.maxDiff = None cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) cf.add_section("sect1") @@ -998,6 +998,19 @@ def test_str_and_repr(self): ) self.assertEqual(str(cf), expected_str) + def test_repr(self): + self.maxDiff = None + cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) + cf.add_section("sect1") + cf.add_section("sect2") + cf.add_section("sect3") + cf.add_section("sect4") + cf.add_section("sect5") + cf.add_section("sect6") + cf.set("sect1", "option1", "foo") + cf.set("sect2", "option2", "bar") + cf.read_string("") + dict_type = type(cf._dict).__name__ expected_repr = ( @@ -1005,7 +1018,7 @@ 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_sources': [], 'sections_count': 6, " + "state={'loaded_sources': [''], 'sections_count': 6, " "'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], " "'sections_truncated': '...and 1 more'})>" ) diff --git a/Misc/ACKS b/Misc/ACKS index 61ddc9fcce5836..9fef76f6d06bf8 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1598,8 +1598,8 @@ Joel Rosdahl Erik Rose Mark Roseman Josh Rosenberg -Jim Roskind Prince Roshan +Jim Roskind Brian Rosner Ignacio Rossi Guido van Rossum From ae883aaba1cd542a12dbc96bea4a70d69deedef9 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 02:02:20 +0530 Subject: [PATCH 12/14] resolve comments --- Lib/configparser.py | 2 +- Lib/test/test_configparser.py | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Lib/configparser.py b/Lib/configparser.py index 887069c627f1ed..e41e082d45f92a 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -758,7 +758,7 @@ def read(self, filenames, encoding=None): if isinstance(filename, os.PathLike): filename = os.fspath(filename) read_ok.append(filename) - self._loaded_sources.extend(read_ok) + self._loaded_sources.append(read_ok) return read_ok def read_file(self, f, source=None): diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index dfd3250ab5fa97..6f1b382730e4b5 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -985,16 +985,11 @@ def test_str(self): cf = self.config_class(allow_no_value=True, delimiters=('=',), strict=True) cf.add_section("sect1") cf.add_section("sect2") - cf.add_section("sect3") - cf.add_section("sect4") - cf.add_section("sect5") - cf.add_section("sect6") cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") expected_str = ( - "" + "" ) self.assertEqual(str(cf), expected_str) @@ -1012,17 +1007,23 @@ def test_repr(self): cf.read_string("") 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_sources': [''], 'sections_count': 6, " - "'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], " - "'sections_truncated': '...and 1 more'})>" - ) - self.assertEqual(repr(cf), expected_repr) + classname = cf.__class__.__name__ + params = { + 'dict_type': dict_type, + 'allow_no_value': True, + 'delimiters': ('=',), + 'strict': True, + 'default_section': 'DEFAULT', + 'interpolation': 'BasicInterpolation', + } + state = { + 'loaded_sources': [''], + 'sections_count': 6, + 'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], + 'sections_truncated': '...and 1 more', + } + expected = f"<{classname}({params=}, {state=})>" + self.assertEqual(repr(cf), expected) def test_add_section_default(self): cf = self.newconfig() From 860e4a1148d769493d39db0c1ccbe4a775545e0e Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 14:19:10 +0530 Subject: [PATCH 13/14] resolve comments --- Lib/test/test_configparser.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 6f1b382730e4b5..6d87e5e1e97a77 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1004,10 +1004,9 @@ def test_repr(self): cf.add_section("sect6") cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") - cf.read_string("") + cf.read_string("") # To trigger the loading of sources dict_type = type(cf._dict).__name__ - classname = cf.__class__.__name__ params = { 'dict_type': dict_type, 'allow_no_value': True, @@ -1022,7 +1021,7 @@ def test_repr(self): 'sections': ['sect1', 'sect2', 'sect3', 'sect4', 'sect5'], 'sections_truncated': '...and 1 more', } - expected = f"<{classname}({params=}, {state=})>" + expected = f"<{type(cf).__name__}({params=}, {state=})>" self.assertEqual(repr(cf), expected) def test_add_section_default(self): From da57a9db5d7eacb4b6b28b1bf6c321abbed18f25 Mon Sep 17 00:00:00 2001 From: Prince Roshan Date: Sat, 26 Apr 2025 15:24:51 +0530 Subject: [PATCH 14/14] Update Lib/test/test_configparser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- 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 6d87e5e1e97a77..cfd9b59fe0ab4d 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1004,7 +1004,7 @@ def test_repr(self): cf.add_section("sect6") cf.set("sect1", "option1", "foo") cf.set("sect2", "option2", "bar") - cf.read_string("") # To trigger the loading of sources + cf.read_string("") # to trigger the loading of sources dict_type = type(cf._dict).__name__ params = {