Issue35838
Created on 2019-01-27 22:27 by Phil Kang, last changed 2022-04-11 14:59 by admin. This issue is now closed.
| Pull Requests | |||
|---|---|---|---|
| URL | Status | Linked | Edit |
| PR 11760 | closed | xtreak, 2019-02-05 10:06 | |
| PR 11760 | closed | xtreak, 2019-02-05 10:06 | |
| PR 11760 | closed | xtreak, 2019-02-05 10:06 | |
| PR 12656 | merged | methane, 2019-04-02 07:57 | |
| PR 12657 | merged | miss-islington, 2019-04-02 09:09 | |
| Messages (7) | |||
|---|---|---|---|
| msg334439 - (view) | Author: Phil Kang (Phil Kang) | Date: 2019-01-27 22:27 | |
ConfigParser calls ConfigParser.optionxform twice per each key when assigning a dictionary to a section.
The following code:
ini = configparser.ConfigParser()
ini.optionxform = lambda x: '(' + x + ')'
# Bugged
ini['section A'] = {'key 1': 'value 1', 'key 2': 'value 2'}
# Not bugged
ini.add_section('section B')
ini['section B']['key 3'] = 'value 3'
ini['section B']['key 4'] = 'value 4'
inifile = io.StringIO()
ini.write(inifile)
print(inifile.getvalue())
...results in an INI file that looks like:
[section A]
((key 1)) = value 1
((key 2)) = value 2
[section B]
(key 3) = value 3
(key 4) = value 4
Here, optionxform has been called twice on key 1 and key 2, resulting in the double parentheses.
This also breaks conventional mapping access on the ConfigParser:
print(ini['section A']['key 1']) # Raises KeyError('key 1')
print(ini['section A']['(key 1)']) # OK
# Raises ValueError: too many values to unpack (expected 2)
for key, value in ini['section A']:
print(key + ', ' + value)
|
|||
| msg334452 - (view) | Author: Inada Naoki (methane) * ![]() |
Date: 2019-01-28 08:56 | |
I think it's easy to solve this particular case. But there may be some other cases. optionxform must be idempotent? If so, this is document issue. |
|||
| msg334470 - (view) | Author: Karthikeyan Singaravelan (xtreak) * ![]() |
Date: 2019-01-28 14:27 | |
This seems to be a bug with read_dict which is used internally when a dictionary is directly assigned. In read_dict optionxform is called with key [0] to check for duplicate and the transformed value is again passed to self.set which also calls optionxform [1] causing optionxform to be applied twice. A possible fix would be to assign the transformed key to a temporary variable to check for duplicate and then pass the original key to self.set ? My patch gives correct value and no tests fail on master. I can make a PR with test for this if my analysis is correct.
This fixes the below since the key is stored correctly now.
print(ini['section A']['key 1']) # OK
print(ini['section A']['(key 1)']) # Raises KeyError
I think for iterating over the section items [2] need to be used and the reported code can be written as below
for key, value in ini.items('section A'):
print(key + ', ' + value)
[0] https://github.com/python/cpython/blob/ea446409cd5f1364beafd5e5255da6799993f285/Lib/configparser.py#L748
[1] https://github.com/python/cpython/blob/ea446409cd5f1364beafd5e5255da6799993f285/Lib/configparser.py#L903
[2] https://docs.python.org/3.8/library/configparser.html#configparser.ConfigParser.items
# sample reproducer
import io
import configparser
ini = configparser.ConfigParser()
ini.optionxform = lambda x: '(' + x + ')'
ini.read_dict({'section A': {'key 1': 'value 1'}})
inifile = io.StringIO()
ini.write(inifile)
print(inifile.getvalue())
$ ./python.exe ../backups/bpo35838_1.py
[section A]
((key 1)) = value 1
# Possible patch
$ git diff -w | cat
diff --git a/Lib/configparser.py b/Lib/configparser.py
index 79a991084b..1389f4ac08 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -745,13 +745,13 @@ class RawConfigParser(MutableMapping):
raise
elements_added.add(section)
for key, value in keys.items():
- key = self.optionxform(str(key))
+ option_key = self.optionxform(str(key))
if value is not None:
value = str(value)
- if self._strict and (section, key) in elements_added:
- raise DuplicateOptionError(section, key, source)
- elements_added.add((section, key))
- self.set(section, key, value)
+ if self._strict and (section, option_key) in elements_added:
+ raise DuplicateOptionError(section, option_key, source)
+ elements_added.add((section, option_key))
+ self.set(section, str(key), value)
def readfp(self, fp, filename=None):
"""Deprecated, use read_file instead."""
$ ./python.exe ../backups/bpo35838_1.py
[section A]
(key 1) = value 1
|
|||
| msg337282 - (view) | Author: Inada Naoki (methane) * ![]() |
Date: 2019-03-06 06:30 | |
It seems twice call of `optionxform` is not avoidable when read-and-write workflow.
I'm not against about fixing readdict.
But I don't think configparser supports non-idempotent optionxform.
>>> import configparser
>>> cfg = configparser.ConfigParser()
>>> cfg.optionxform = lambda s: "#"+s
>>> cfg.add_section("sec")
>>> cfg.set("sec", "foo", "1")
>>> cfg["sec2"] = cfg["sec"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/inada-n/work/python/cpython/Lib/configparser.py", line 974, in __setitem__
self.read_dict({key: value})
File "/Users/inada-n/work/python/cpython/Lib/configparser.py", line 747, in read_dict
for key, value in keys.items():
File "/Users/inada-n/work/python/cpython/Lib/_collections_abc.py", line 744, in __iter__
yield (key, self._mapping[key])
File "/Users/inada-n/work/python/cpython/Lib/configparser.py", line 1254, in __getitem__
raise KeyError(key)
KeyError: '#foo'
|
|||
| msg337375 - (view) | Author: Inada Naoki (methane) * ![]() |
Date: 2019-03-07 09:30 | |
I sent a mail to python-dev ML. https://mail.python.org/pipermail/python-dev/2019-March/156613.html |
|||
| msg339321 - (view) | Author: Inada Naoki (methane) * ![]() |
Date: 2019-04-02 09:08 | |
New changeset 04694a306b8f4ab54ef5fc4ba673c26fa53b0ac1 by Inada Naoki in branch 'master': bpo-35838: document optionxform must be idempotent (GH-12656) https://github.com/python/cpython/commit/04694a306b8f4ab54ef5fc4ba673c26fa53b0ac1 |
|||
| msg339323 - (view) | Author: miss-islington (miss-islington) | Date: 2019-04-02 09:29 | |
New changeset 9a838c593f6ada69a37025d7ded8ac822816a74c by Miss Islington (bot) in branch '3.7': bpo-35838: document optionxform must be idempotent (GH-12656) https://github.com/python/cpython/commit/9a838c593f6ada69a37025d7ded8ac822816a74c |
|||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022-04-11 14:59:10 | admin | set | github: 80019 |
| 2019-04-02 09:31:52 | methane | set | keywords:
patch, patch, patch status: open -> closed stage: patch review -> resolved resolution: fixed title: ConfigParser calls optionxform twice when assigning dict -> ConfigParser: document optionxform must be idempotent |
| 2019-04-02 09:29:19 | miss-islington | set | nosy:
+ miss-islington messages: + msg339323 |
| 2019-04-02 09:09:03 | miss-islington | set | pull_requests: + pull_request12586 |
| 2019-04-02 09:08:51 | methane | set | messages: + msg339321 |
| 2019-04-02 07:57:58 | methane | set | pull_requests: + pull_request12585 |
| 2019-03-07 09:30:52 | methane | set | keywords:
patch, patch, patch messages: + msg337375 |
| 2019-03-06 06:30:12 | methane | set | keywords:
patch, patch, patch messages: + msg337282 |
| 2019-02-05 10:06:27 | xtreak | set | keywords:
+ patch stage: patch review pull_requests: + pull_request11709 |
| 2019-02-05 10:06:21 | xtreak | set | keywords:
+ patch stage: (no value) pull_requests: + pull_request11708 |
| 2019-02-05 10:06:14 | xtreak | set | keywords:
+ patch stage: (no value) pull_requests: + pull_request11707 |
| 2019-01-28 14:27:28 | xtreak | set | nosy:
+ xtreak messages:
+ msg334470 |
| 2019-01-28 08:56:57 | methane | set | nosy:
+ methane messages: + msg334452 |
| 2019-01-27 22:41:11 | xtreak | set | nosy:
+ lukasz.langa |
| 2019-01-27 22:27:01 | Phil Kang | create | |
