Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0fc8ae4

Browse files
authored
[3.12]gh-116957: configparser: Do post-process values after DuplicateOptionError (GH-116958) (GH-117013)
If you catch DuplicateOptionError / DuplicateSectionError when reading aconfig file (the intention is to skip invalid config files) and thenattempt to use the ConfigParser instance, any values it *had* readsuccessfully so far, were stored as a list instead of string! Later`get` calls would raise "AttributeError: 'list' object has no attribute'find'" from somewhere deep in the interpolation code.(cherry picked from commitb1bc375)
1 parent688623d commit0fc8ae4

File tree

3 files changed

+109
-89
lines changed

3 files changed

+109
-89
lines changed

‎Lib/configparser.py

Lines changed: 91 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -995,100 +995,102 @@ def _read(self, fp, fpname):
995995
lineno=0
996996
indent_level=0
997997
e=None# None, or an exception
998-
forlineno,lineinenumerate(fp,start=1):
999-
comment_start=sys.maxsize
1000-
# strip inline comments
1001-
inline_prefixes= {p:-1forpinself._inline_comment_prefixes}
1002-
whilecomment_start==sys.maxsizeandinline_prefixes:
1003-
next_prefixes= {}
1004-
forprefix,indexininline_prefixes.items():
1005-
index=line.find(prefix,index+1)
1006-
ifindex==-1:
1007-
continue
1008-
next_prefixes[prefix]=index
1009-
ifindex==0or (index>0andline[index-1].isspace()):
1010-
comment_start=min(comment_start,index)
1011-
inline_prefixes=next_prefixes
1012-
# strip full line comments
1013-
forprefixinself._comment_prefixes:
1014-
ifline.strip().startswith(prefix):
1015-
comment_start=0
1016-
break
1017-
ifcomment_start==sys.maxsize:
1018-
comment_start=None
1019-
value=line[:comment_start].strip()
1020-
ifnotvalue:
1021-
ifself._empty_lines_in_values:
1022-
# add empty line to the value, but only if there was no
1023-
# comment on the line
1024-
if (comment_startisNoneand
1025-
cursectisnotNoneand
1026-
optnameand
1027-
cursect[optname]isnotNone):
1028-
cursect[optname].append('')# newlines added at join
1029-
else:
1030-
# empty line marks end of value
1031-
indent_level=sys.maxsize
1032-
continue
1033-
# continuation line?
1034-
first_nonspace=self.NONSPACECRE.search(line)
1035-
cur_indent_level=first_nonspace.start()iffirst_nonspaceelse0
1036-
if (cursectisnotNoneandoptnameand
1037-
cur_indent_level>indent_level):
1038-
cursect[optname].append(value)
1039-
# a section header or option header?
1040-
else:
1041-
indent_level=cur_indent_level
1042-
# is it a section header?
1043-
mo=self.SECTCRE.match(value)
1044-
ifmo:
1045-
sectname=mo.group('header')
1046-
ifsectnameinself._sections:
1047-
ifself._strictandsectnameinelements_added:
1048-
raiseDuplicateSectionError(sectname,fpname,
1049-
lineno)
1050-
cursect=self._sections[sectname]
1051-
elements_added.add(sectname)
1052-
elifsectname==self.default_section:
1053-
cursect=self._defaults
998+
try:
999+
forlineno,lineinenumerate(fp,start=1):
1000+
comment_start=sys.maxsize
1001+
# strip inline comments
1002+
inline_prefixes= {p:-1forpinself._inline_comment_prefixes}
1003+
whilecomment_start==sys.maxsizeandinline_prefixes:
1004+
next_prefixes= {}
1005+
forprefix,indexininline_prefixes.items():
1006+
index=line.find(prefix,index+1)
1007+
ifindex==-1:
1008+
continue
1009+
next_prefixes[prefix]=index
1010+
ifindex==0or (index>0andline[index-1].isspace()):
1011+
comment_start=min(comment_start,index)
1012+
inline_prefixes=next_prefixes
1013+
# strip full line comments
1014+
forprefixinself._comment_prefixes:
1015+
ifline.strip().startswith(prefix):
1016+
comment_start=0
1017+
break
1018+
ifcomment_start==sys.maxsize:
1019+
comment_start=None
1020+
value=line[:comment_start].strip()
1021+
ifnotvalue:
1022+
ifself._empty_lines_in_values:
1023+
# add empty line to the value, but only if there was no
1024+
# comment on the line
1025+
if (comment_startisNoneand
1026+
cursectisnotNoneand
1027+
optnameand
1028+
cursect[optname]isnotNone):
1029+
cursect[optname].append('')# newlines added at join
10541030
else:
1055-
cursect=self._dict()
1056-
self._sections[sectname]=cursect
1057-
self._proxies[sectname]=SectionProxy(self,sectname)
1058-
elements_added.add(sectname)
1059-
# So sections can't start with a continuationline
1060-
optname=None
1061-
# no section header in the file?
1062-
elifcursectisNone:
1063-
raiseMissingSectionHeaderError(fpname,lineno,line)
1064-
#anoptionline?
1031+
# empty line marks end of value
1032+
indent_level=sys.maxsize
1033+
continue
1034+
# continuation line?
1035+
first_nonspace=self.NONSPACECRE.search(line)
1036+
cur_indent_level=first_nonspace.start()iffirst_nonspaceelse0
1037+
if (cursectisnotNoneandoptnameand
1038+
cur_indent_level>indent_level):
1039+
cursect[optname].append(value)
1040+
#a section header oroptionheader?
10651041
else:
1066-
mo=self._optcre.match(value)
1042+
indent_level=cur_indent_level
1043+
# is it a section header?
1044+
mo=self.SECTCRE.match(value)
10671045
ifmo:
1068-
optname,vi,optval=mo.group('option','vi','value')
1069-
ifnotoptname:
1070-
e=self._handle_error(e,fpname,lineno,line)
1071-
optname=self.optionxform(optname.rstrip())
1072-
if (self._strictand
1073-
(sectname,optname)inelements_added):
1074-
raiseDuplicateOptionError(sectname,optname,
1075-
fpname,lineno)
1076-
elements_added.add((sectname,optname))
1077-
# This check is fine because the OPTCRE cannot
1078-
# match if it would set optval to None
1079-
ifoptvalisnotNone:
1080-
optval=optval.strip()
1081-
cursect[optname]= [optval]
1046+
sectname=mo.group('header')
1047+
ifsectnameinself._sections:
1048+
ifself._strictandsectnameinelements_added:
1049+
raiseDuplicateSectionError(sectname,fpname,
1050+
lineno)
1051+
cursect=self._sections[sectname]
1052+
elements_added.add(sectname)
1053+
elifsectname==self.default_section:
1054+
cursect=self._defaults
10821055
else:
1083-
# valueless option handling
1084-
cursect[optname]=None
1056+
cursect=self._dict()
1057+
self._sections[sectname]=cursect
1058+
self._proxies[sectname]=SectionProxy(self,sectname)
1059+
elements_added.add(sectname)
1060+
# So sections can't start with a continuation line
1061+
optname=None
1062+
# no section header in the file?
1063+
elifcursectisNone:
1064+
raiseMissingSectionHeaderError(fpname,lineno,line)
1065+
# an option line?
10851066
else:
1086-
# a non-fatal parsing error occurred. set up the
1087-
# exception but keep going. the exception will be
1088-
# raised at the end of the file and will contain a
1089-
# list of all bogus lines
1090-
e=self._handle_error(e,fpname,lineno,line)
1091-
self._join_multiline_values()
1067+
mo=self._optcre.match(value)
1068+
ifmo:
1069+
optname,vi,optval=mo.group('option','vi','value')
1070+
ifnotoptname:
1071+
e=self._handle_error(e,fpname,lineno,line)
1072+
optname=self.optionxform(optname.rstrip())
1073+
if (self._strictand
1074+
(sectname,optname)inelements_added):
1075+
raiseDuplicateOptionError(sectname,optname,
1076+
fpname,lineno)
1077+
elements_added.add((sectname,optname))
1078+
# This check is fine because the OPTCRE cannot
1079+
# match if it would set optval to None
1080+
ifoptvalisnotNone:
1081+
optval=optval.strip()
1082+
cursect[optname]= [optval]
1083+
else:
1084+
# valueless option handling
1085+
cursect[optname]=None
1086+
else:
1087+
# a non-fatal parsing error occurred. set up the
1088+
# exception but keep going. the exception will be
1089+
# raised at the end of the file and will contain a
1090+
# list of all bogus lines
1091+
e=self._handle_error(e,fpname,lineno,line)
1092+
finally:
1093+
self._join_multiline_values()
10921094
# if any parsing errors occurred, raise an exception
10931095
ife:
10941096
raisee

‎Lib/test/test_configparser.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,21 @@ def test_weird_errors(self):
647647
"'opt' in section 'Bar' already exists")
648648
self.assertEqual(e.args, ("Bar","opt","<dict>",None))
649649

650+
deftest_get_after_duplicate_option_error(self):
651+
cf=self.newconfig()
652+
ini=textwrap.dedent("""\
653+
[Foo]
654+
x{equals}1
655+
y{equals}2
656+
y{equals}3
657+
""".format(equals=self.delimiters[0]))
658+
ifself.strict:
659+
withself.assertRaises(configparser.DuplicateOptionError):
660+
cf.read_string(ini)
661+
else:
662+
cf.read_string(ini)
663+
self.assertEqual(cf.get('Foo','x'),'1')
664+
650665
deftest_write(self):
651666
config_string= (
652667
"[Long Line]\n"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
configparser: Don't leave ConfigParser values in an invalid state (stored as
2+
a list instead of a str) after an earlier read raised DuplicateSectionError
3+
or DuplicateOptionError.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp