Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-iniconfig for openSUSE:Factory checked in at 2026-10-27 18:18:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-iniconfig (Old) and /work/SRC/openSUSE:Factory/.python-iniconfig.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-iniconfig" Tue Oct 27 18:18:59 2026 rev:11 rq:1392636 version:2.3.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-iniconfig/python-iniconfig.changes 2025-12-11 18:32:15.666598211 +0100 +++ /work/SRC/openSUSE:Factory/.python-iniconfig.new.2252680/python-iniconfig.changes 2026-10-27 18:19:07.888020623 +0100 @@ -1,0 +2,11 @@ +Mon Oct 26 07:44:35 UTC 2026 - Dirk Müller <[email protected]> + +- update to 2.3.1: + * fix quadratic parse time for values with many continuation + lines (#78, regarding #77) + * strip a leading UTF-8 BOM when reading INI files, which + previously raised ``ParseError`` (#76) + * internal: ``iniconfig._parse.ParsedLine.value`` is now a list + of lines + +------------------------------------------------------------------- Old: ---- iniconfig-2.3.0-tests.tar.gz iniconfig-2.3.0.tar.gz New: ---- iniconfig-2.3.1-tests.tar.gz iniconfig-2.3.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-iniconfig.spec ++++++ --- /var/tmp/diff_new_pack.8mXUPA/_old 2026-10-27 18:19:20.689556040 +0100 +++ /var/tmp/diff_new_pack.8mXUPA/_new 2026-10-27 18:19:20.691556123 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-iniconfig # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -26,7 +26,7 @@ %endif %{?sle15_python_module_pythons} Name: python-iniconfig%{psuffix} -Version: 2.3.0 +Version: 2.3.1 Release: 0 Summary: iniconfig: brain-dead simple config-ini parsing License: MIT ++++++ iniconfig-2.3.0-tests.tar.gz -> iniconfig-2.3.1-tests.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/.pre-commit-config.yaml new/iniconfig-2.3.1/.pre-commit-config.yaml --- old/iniconfig-2.3.0/.pre-commit-config.yaml 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/.pre-commit-config.yaml 2026-10-07 00:11:15.000000000 +0200 @@ -1,18 +1,18 @@ repos: - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.11.0" + rev: "v2.30.1" hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.1 + rev: v0.16.10 hooks: - id: ruff-check args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.18.2' + rev: 'v2.4.0' hooks: - id: mypy args: [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/CHANGELOG new/iniconfig-2.3.1/CHANGELOG --- old/iniconfig-2.3.0/CHANGELOG 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/CHANGELOG 2026-10-07 00:11:15.000000000 +0200 @@ -1,3 +1,10 @@ +2.3.1 +===== + +* fix quadratic parse time for values with many continuation lines (#78, regarding #77) +* strip a leading UTF-8 BOM when reading INI files, which previously raised ``ParseError`` (#76) +* internal: ``iniconfig._parse.ParsedLine.value`` is now a list of lines + 2.3.0 ===== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/README.rst new/iniconfig-2.3.1/README.rst --- old/iniconfig-2.3.0/README.rst 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/README.rst 2026-10-07 00:11:15.000000000 +0200 @@ -13,7 +13,7 @@ If you encounter issues or have feature wishes please report them to: - https://github.com/RonnyPfannschmidt/iniconfig/issues + https://github.com/pytest-dev/iniconfig/issues Basic Example =================================== @@ -32,12 +32,12 @@ line1 line2 -then you can do: +use ``IniConfig.parse()`` to strip inline comments from values: .. code-block:: pycon >>> import iniconfig - >>> ini = iniconfig.IniConfig("example.ini") + >>> ini = iniconfig.IniConfig.parse("example.ini") >>> ini['section1']['name1'] # raises KeyError if not exists 'value1' >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) @@ -52,3 +52,7 @@ True >>> 'inexistendsection' in ini False + +The ``IniConfig()`` constructor preserves inline comments in values for backward +compatibility. Use ``IniConfig.parse(..., strip_inline_comments=False)`` to +preserve them when using the parsing method. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/pyproject.toml new/iniconfig-2.3.1/pyproject.toml --- old/iniconfig-2.3.0/pyproject.toml 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/pyproject.toml 2026-10-07 00:11:15.000000000 +0200 @@ -28,6 +28,7 @@ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: Software Development :: Libraries", "Topic :: Utilities", ] @@ -45,15 +46,12 @@ [tool.setuptools] packages = [ "iniconfig" ] package-dir = { "" = "src" } - -[tool.setuptools.package-data] -iniconfig = [ "py.typed" ] +package-data.iniconfig = [ "py.typed" ] [tool.setuptools_scm] write_to = "src/iniconfig/_version.py" [tool.ruff] - lint.extend-select = [ "B", # flake8-bugbear "I", # isort @@ -63,8 +61,8 @@ lint.isort.force-single-line = true lint.isort.known-first-party = [ "iniconfig" ] -[tool.pytest.ini_options] -testpaths = "testing" - [tool.mypy] strict = true + +[tool.pytest] +ini_options.testpaths = "testing" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig/__init__.py new/iniconfig-2.3.1/src/iniconfig/__init__.py --- old/iniconfig-2.3.0/src/iniconfig/__init__.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/src/iniconfig/__init__.py 2026-10-07 00:11:15.000000000 +0200 @@ -10,7 +10,7 @@ from typing import TypeVar from typing import overload -__all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] +__all__ = ["COMMENTCHARS", "IniConfig", "ParseError", "iscommentline"] from . import _parse from ._parse import COMMENTCHARS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig/_parse.py new/iniconfig-2.3.1/src/iniconfig/_parse.py --- old/iniconfig-2.3.0/src/iniconfig/_parse.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/src/iniconfig/_parse.py 2026-10-07 00:11:15.000000000 +0200 @@ -1,16 +1,18 @@ from collections.abc import Mapping +from typing import Final from typing import NamedTuple from .exceptions import ParseError COMMENTCHARS = "#;" +UTF8_BOM: Final = "\N{BYTE ORDER MARK}" class ParsedLine(NamedTuple): lineno: int section: str | None name: str | None - value: str | None + value: list[str] | None def parse_ini_data( @@ -34,6 +36,8 @@ - sections_data: mapping of section -> {name -> value} - sources: mapping of (section, name) -> line number """ + data = data.removeprefix(UTF8_BOM) + tokens = parse_lines( path, data.splitlines(True), @@ -56,7 +60,7 @@ if name in sections_data[section]: raise ParseError(path, lineno, f"duplicate name {name!r}") assert value is not None - sections_data[section][name] = value + sections_data[section][name] = "\n".join(value) return sections_data, sources @@ -76,7 +80,7 @@ ) # new value if name is not None and data is not None: - result.append(ParsedLine(lineno, section, name, data)) + result.append(ParsedLine(lineno, section, name, [data] if data else [])) # new section elif name is not None and data is None: if not name: @@ -85,17 +89,10 @@ result.append(ParsedLine(lineno, section, None, None)) # continuation elif name is None and data is not None: - if not result: - raise ParseError(path, lineno, "unexpected value continuation") - last = result.pop() - if last.name is None: + value = result[-1].value if result else None + if value is None: raise ParseError(path, lineno, "unexpected value continuation") - - if last.value: - last = last._replace(value=f"{last.value}\n{data}") - else: - last = last._replace(value=data) - result.append(last) + value.append(data) return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/testing/test_iniconfig.py new/iniconfig-2.3.1/testing/test_iniconfig.py --- old/iniconfig-2.3.0/testing/test_iniconfig.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/testing/test_iniconfig.py 2026-10-07 00:11:15.000000000 +0200 @@ -11,36 +11,46 @@ check_tokens: dict[str, tuple[str, list[PL]]] = { "section": ("[section]", [PL(0, "section", None, None)]), - "value": ("value = 1", [PL(0, None, "value", "1")]), + "value": ("value = 1", [PL(0, None, "value", ["1"])]), "value in section": ( "[section]\nvalue=1", - [PL(0, "section", None, None), PL(1, "section", "value", "1")], + [PL(0, "section", None, None), PL(1, "section", "value", ["1"])], ), + "empty value": ("value =", [PL(0, None, "value", [])]), "value with continuation": ( "names =\n Alice\n Bob", - [PL(0, None, "names", "Alice\nBob")], + [PL(0, None, "names", ["Alice", "Bob"])], ), "value with aligned continuation": ( "names = Alice\n Bob", - [PL(0, None, "names", "Alice\nBob")], + [PL(0, None, "names", ["Alice", "Bob"])], + ), + "continuations on several values": ( + "a = 1\n 2\n\n 3\n[s]\nb =\n x\n y\nc = z", + [ + PL(0, None, "a", ["1", "2", "3"]), + PL(4, "s", None, None), + PL(5, "s", "b", ["x", "y"]), + PL(8, "s", "c", ["z"]), + ], ), "blank line": ( "[section]\n\nvalue=1", - [PL(0, "section", None, None), PL(2, "section", "value", "1")], + [PL(0, "section", None, None), PL(2, "section", "value", ["1"])], ), "comment": ("# comment", []), - "comment on value": ("value = 1", [PL(0, None, "value", "1")]), + "comment on value": ("value = 1", [PL(0, None, "value", ["1"])]), "comment on section": ("[section] #comment", [PL(0, "section", None, None)]), "comment2": ("; comment", []), "comment2 on section": ("[section] ;comment", [PL(0, "section", None, None)]), "pseudo section syntax in value": ( "name = value []", - [PL(0, None, "name", "value []")], + [PL(0, None, "name", ["value []"])], ), - "assignment in value": ("value = x = 3", [PL(0, None, "value", "x = 3")]), - "use of colon for name-values": ("name: y", [PL(0, None, "name", "y")]), - "use of colon without space": ("value:y=5", [PL(0, None, "value", "y=5")]), - "equality gets precedence": ("value=xyz:5", [PL(0, None, "value", "xyz:5")]), + "assignment in value": ("value = x = 3", [PL(0, None, "value", ["x = 3"])]), + "use of colon for name-values": ("name: y", [PL(0, None, "name", ["y"])]), + "use of colon without space": ("value:y=5", [PL(0, None, "value", ["y=5"])]), + "equality gets precedence": ("value=xyz:5", [PL(0, None, "value", ["xyz:5"])]), } @@ -290,7 +300,7 @@ def test_api_import() -> None: - assert ALL == ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] + assert ALL == ["COMMENTCHARS", "IniConfig", "ParseError", "iscommentline"] @pytest.mark.parametrize( @@ -412,3 +422,23 @@ ) assert "key" in config["section"] assert config["section"]["key"] == "value" + + +def test_utf8_bom_file(tmp_path: Path) -> None: + """Files saved with a UTF-8 BOM (common on Windows editors) must parse.""" + path = tmp_path / "bom.ini" + path.write_bytes(b"\xef\xbb\xbf[section]\nkey = value\n") + config = IniConfig(path) + assert config["section"]["key"] == "value" + + +def test_utf8_bom_in_data_string() -> None: + config = IniConfig("x.ini", data="\ufeff[section]\nkey = value\n") + assert config["section"]["key"] == "value" + + +def test_parse_utf8_bom_file(tmp_path: Path) -> None: + path = tmp_path / "bom.ini" + path.write_bytes(b"\xef\xbb\xbf[section]\nkey = value\n") + config = IniConfig.parse(path) + assert config["section"]["key"] == "value" ++++++ iniconfig-2.3.0-tests.tar.gz -> iniconfig-2.3.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/.pre-commit-config.yaml new/iniconfig-2.3.1/.pre-commit-config.yaml --- old/iniconfig-2.3.0/.pre-commit-config.yaml 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/.pre-commit-config.yaml 2026-10-07 00:47:10.299275900 +0200 @@ -1,18 +1,18 @@ repos: - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.11.0" + rev: "v2.30.1" hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.1 + rev: v0.16.10 hooks: - id: ruff-check args: [--fix] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.18.2' + rev: 'v2.4.0' hooks: - id: mypy args: [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/CHANGELOG new/iniconfig-2.3.1/CHANGELOG --- old/iniconfig-2.3.0/CHANGELOG 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/CHANGELOG 2026-10-07 00:47:10.299275900 +0200 @@ -1,3 +1,10 @@ +2.3.1 +===== + +* fix quadratic parse time for values with many continuation lines (#78, regarding #77) +* strip a leading UTF-8 BOM when reading INI files, which previously raised ``ParseError`` (#76) +* internal: ``iniconfig._parse.ParsedLine.value`` is now a list of lines + 2.3.0 ===== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/PKG-INFO new/iniconfig-2.3.1/PKG-INFO --- old/iniconfig-2.3.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/PKG-INFO 2026-10-07 00:47:14.833928300 +0200 @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: iniconfig +Version: 2.3.1 +Summary: brain-dead simple config-ini parsing +Author-email: Ronny Pfannschmidt <[email protected]>, Holger Krekel <[email protected]> +License-Expression: MIT +Project-URL: Homepage, https://github.com/pytest-dev/iniconfig +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: license-file + +iniconfig: brain-dead simple parsing of ini files +======================================================= + +iniconfig is a small and simple INI-file parser module +having a unique set of features: + +* maintains order of sections and entries +* supports multi-line values with or without line-continuations +* supports "#" comments everywhere +* raises errors with proper line-numbers +* no bells and whistles like automatic substitutions +* iniconfig raises an Error if two sections have the same name. + +If you encounter issues or have feature wishes please report them to: + + https://github.com/pytest-dev/iniconfig/issues + +Basic Example +=================================== + +If you have an ini file like this: + +.. code-block:: ini + + # content of example.ini + [section1] # comment + name1=value1 # comment + name1b=value1,value2 # comment + + [section2] + name2= + line1 + line2 + +use ``IniConfig.parse()`` to strip inline comments from values: + +.. code-block:: pycon + + >>> import iniconfig + >>> ini = iniconfig.IniConfig.parse("example.ini") + >>> ini['section1']['name1'] # raises KeyError if not exists + 'value1' + >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) + ['value1', 'value2'] + >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) + [] + >>> [x.name for x in list(ini)] + ['section1', 'section2'] + >>> list(list(ini)[0].items()) + [('name1', 'value1'), ('name1b', 'value1,value2')] + >>> 'section1' in ini + True + >>> 'inexistendsection' in ini + False + +The ``IniConfig()`` constructor preserves inline comments in values for backward +compatibility. Use ``IniConfig.parse(..., strip_inline_comments=False)`` to +preserve them when using the parsing method. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/README.rst new/iniconfig-2.3.1/README.rst --- old/iniconfig-2.3.0/README.rst 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/README.rst 2026-10-07 00:47:10.299275900 +0200 @@ -13,7 +13,7 @@ If you encounter issues or have feature wishes please report them to: - https://github.com/RonnyPfannschmidt/iniconfig/issues + https://github.com/pytest-dev/iniconfig/issues Basic Example =================================== @@ -32,12 +32,12 @@ line1 line2 -then you can do: +use ``IniConfig.parse()`` to strip inline comments from values: .. code-block:: pycon >>> import iniconfig - >>> ini = iniconfig.IniConfig("example.ini") + >>> ini = iniconfig.IniConfig.parse("example.ini") >>> ini['section1']['name1'] # raises KeyError if not exists 'value1' >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) @@ -52,3 +52,7 @@ True >>> 'inexistendsection' in ini False + +The ``IniConfig()`` constructor preserves inline comments in values for backward +compatibility. Use ``IniConfig.parse(..., strip_inline_comments=False)`` to +preserve them when using the parsing method. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/pyproject.toml new/iniconfig-2.3.1/pyproject.toml --- old/iniconfig-2.3.0/pyproject.toml 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/pyproject.toml 2026-10-07 00:47:10.299275900 +0200 @@ -28,6 +28,7 @@ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: Software Development :: Libraries", "Topic :: Utilities", ] @@ -45,15 +46,12 @@ [tool.setuptools] packages = [ "iniconfig" ] package-dir = { "" = "src" } - -[tool.setuptools.package-data] -iniconfig = [ "py.typed" ] +package-data.iniconfig = [ "py.typed" ] [tool.setuptools_scm] write_to = "src/iniconfig/_version.py" [tool.ruff] - lint.extend-select = [ "B", # flake8-bugbear "I", # isort @@ -63,8 +61,8 @@ lint.isort.force-single-line = true lint.isort.known-first-party = [ "iniconfig" ] -[tool.pytest.ini_options] -testpaths = "testing" - [tool.mypy] strict = true + +[tool.pytest] +ini_options.testpaths = "testing" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/setup.cfg new/iniconfig-2.3.1/setup.cfg --- old/iniconfig-2.3.0/setup.cfg 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/setup.cfg 2026-10-07 00:47:14.834203700 +0200 @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig/__init__.py new/iniconfig-2.3.1/src/iniconfig/__init__.py --- old/iniconfig-2.3.0/src/iniconfig/__init__.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/src/iniconfig/__init__.py 2026-10-07 00:47:10.299815400 +0200 @@ -10,7 +10,7 @@ from typing import TypeVar from typing import overload -__all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] +__all__ = ["COMMENTCHARS", "IniConfig", "ParseError", "iscommentline"] from . import _parse from ._parse import COMMENTCHARS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig/_parse.py new/iniconfig-2.3.1/src/iniconfig/_parse.py --- old/iniconfig-2.3.0/src/iniconfig/_parse.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/src/iniconfig/_parse.py 2026-10-07 00:47:10.299902000 +0200 @@ -1,16 +1,18 @@ from collections.abc import Mapping +from typing import Final from typing import NamedTuple from .exceptions import ParseError COMMENTCHARS = "#;" +UTF8_BOM: Final = "\N{BYTE ORDER MARK}" class ParsedLine(NamedTuple): lineno: int section: str | None name: str | None - value: str | None + value: list[str] | None def parse_ini_data( @@ -34,6 +36,8 @@ - sections_data: mapping of section -> {name -> value} - sources: mapping of (section, name) -> line number """ + data = data.removeprefix(UTF8_BOM) + tokens = parse_lines( path, data.splitlines(True), @@ -56,7 +60,7 @@ if name in sections_data[section]: raise ParseError(path, lineno, f"duplicate name {name!r}") assert value is not None - sections_data[section][name] = value + sections_data[section][name] = "\n".join(value) return sections_data, sources @@ -76,7 +80,7 @@ ) # new value if name is not None and data is not None: - result.append(ParsedLine(lineno, section, name, data)) + result.append(ParsedLine(lineno, section, name, [data] if data else [])) # new section elif name is not None and data is None: if not name: @@ -85,17 +89,10 @@ result.append(ParsedLine(lineno, section, None, None)) # continuation elif name is None and data is not None: - if not result: - raise ParseError(path, lineno, "unexpected value continuation") - last = result.pop() - if last.name is None: + value = result[-1].value if result else None + if value is None: raise ParseError(path, lineno, "unexpected value continuation") - - if last.value: - last = last._replace(value=f"{last.value}\n{data}") - else: - last = last._replace(value=data) - result.append(last) + value.append(data) return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig/_version.py new/iniconfig-2.3.1/src/iniconfig/_version.py --- old/iniconfig-2.3.0/src/iniconfig/_version.py 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig/_version.py 2026-10-07 00:47:14.783777000 +0200 @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '2.3.1' +__version_tuple__ = version_tuple = (2, 3, 1) + +__commit_id__ = commit_id = 'g316fbfaf2' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/PKG-INFO new/iniconfig-2.3.1/src/iniconfig.egg-info/PKG-INFO --- old/iniconfig-2.3.0/src/iniconfig.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/PKG-INFO 2026-10-07 00:47:14.815185000 +0200 @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: iniconfig +Version: 2.3.1 +Summary: brain-dead simple config-ini parsing +Author-email: Ronny Pfannschmidt <[email protected]>, Holger Krekel <[email protected]> +License-Expression: MIT +Project-URL: Homepage, https://github.com/pytest-dev/iniconfig +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Dynamic: license-file + +iniconfig: brain-dead simple parsing of ini files +======================================================= + +iniconfig is a small and simple INI-file parser module +having a unique set of features: + +* maintains order of sections and entries +* supports multi-line values with or without line-continuations +* supports "#" comments everywhere +* raises errors with proper line-numbers +* no bells and whistles like automatic substitutions +* iniconfig raises an Error if two sections have the same name. + +If you encounter issues or have feature wishes please report them to: + + https://github.com/pytest-dev/iniconfig/issues + +Basic Example +=================================== + +If you have an ini file like this: + +.. code-block:: ini + + # content of example.ini + [section1] # comment + name1=value1 # comment + name1b=value1,value2 # comment + + [section2] + name2= + line1 + line2 + +use ``IniConfig.parse()`` to strip inline comments from values: + +.. code-block:: pycon + + >>> import iniconfig + >>> ini = iniconfig.IniConfig.parse("example.ini") + >>> ini['section1']['name1'] # raises KeyError if not exists + 'value1' + >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) + ['value1', 'value2'] + >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) + [] + >>> [x.name for x in list(ini)] + ['section1', 'section2'] + >>> list(list(ini)[0].items()) + [('name1', 'value1'), ('name1b', 'value1,value2')] + >>> 'section1' in ini + True + >>> 'inexistendsection' in ini + False + +The ``IniConfig()`` constructor preserves inline comments in values for backward +compatibility. Use ``IniConfig.parse(..., strip_inline_comments=False)`` to +preserve them when using the parsing method. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/SOURCES.txt new/iniconfig-2.3.1/src/iniconfig.egg-info/SOURCES.txt --- old/iniconfig-2.3.0/src/iniconfig.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/SOURCES.txt 2026-10-07 00:47:14.825145000 +0200 @@ -0,0 +1,23 @@ +.gitignore +.pre-commit-config.yaml +CHANGELOG +LICENSE +MANIFEST.in +README.rst +example.ini +pyproject.toml +uv.lock +.github/workflows/test.yml +src/iniconfig/__init__.py +src/iniconfig/_parse.py +src/iniconfig/_version.py +src/iniconfig/exceptions.py +src/iniconfig/py.typed +src/iniconfig.egg-info/PKG-INFO +src/iniconfig.egg-info/SOURCES.txt +src/iniconfig.egg-info/dependency_links.txt +src/iniconfig.egg-info/scm_file_list.json +src/iniconfig.egg-info/scm_version.json +src/iniconfig.egg-info/top_level.txt +testing/conftest.py +testing/test_iniconfig.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/dependency_links.txt new/iniconfig-2.3.1/src/iniconfig.egg-info/dependency_links.txt --- old/iniconfig-2.3.0/src/iniconfig.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/dependency_links.txt 2026-10-07 00:47:14.815457000 +0200 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/scm_file_list.json new/iniconfig-2.3.1/src/iniconfig.egg-info/scm_file_list.json --- old/iniconfig-2.3.0/src/iniconfig.egg-info/scm_file_list.json 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/scm_file_list.json 2026-10-07 00:47:14.812223400 +0200 @@ -0,0 +1,20 @@ +{ + "files": [ + ".github/workflows/test.yml", + ".gitignore", + ".pre-commit-config.yaml", + "CHANGELOG", + "LICENSE", + "MANIFEST.in", + "README.rst", + "example.ini", + "pyproject.toml", + "src/iniconfig/__init__.py", + "src/iniconfig/_parse.py", + "src/iniconfig/exceptions.py", + "src/iniconfig/py.typed", + "testing/conftest.py", + "testing/test_iniconfig.py", + "uv.lock" + ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/scm_version.json new/iniconfig-2.3.1/src/iniconfig.egg-info/scm_version.json --- old/iniconfig-2.3.0/src/iniconfig.egg-info/scm_version.json 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/scm_version.json 2026-10-07 00:47:14.806256500 +0200 @@ -0,0 +1,8 @@ +{ + "tag": "2.3.1", + "distance": 0, + "node": "g316fbfaf2752d89326e1e75b45cd070f5b5ace9a", + "dirty": false, + "branch": "HEAD", + "node_date": "2026-10-06" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/src/iniconfig.egg-info/top_level.txt new/iniconfig-2.3.1/src/iniconfig.egg-info/top_level.txt --- old/iniconfig-2.3.0/src/iniconfig.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/iniconfig-2.3.1/src/iniconfig.egg-info/top_level.txt 2026-10-07 00:47:14.815787300 +0200 @@ -0,0 +1 @@ +iniconfig diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/iniconfig-2.3.0/testing/test_iniconfig.py new/iniconfig-2.3.1/testing/test_iniconfig.py --- old/iniconfig-2.3.0/testing/test_iniconfig.py 2025-10-18 23:51:36.000000000 +0200 +++ new/iniconfig-2.3.1/testing/test_iniconfig.py 2026-10-07 00:47:10.300073600 +0200 @@ -11,36 +11,46 @@ check_tokens: dict[str, tuple[str, list[PL]]] = { "section": ("[section]", [PL(0, "section", None, None)]), - "value": ("value = 1", [PL(0, None, "value", "1")]), + "value": ("value = 1", [PL(0, None, "value", ["1"])]), "value in section": ( "[section]\nvalue=1", - [PL(0, "section", None, None), PL(1, "section", "value", "1")], + [PL(0, "section", None, None), PL(1, "section", "value", ["1"])], ), + "empty value": ("value =", [PL(0, None, "value", [])]), "value with continuation": ( "names =\n Alice\n Bob", - [PL(0, None, "names", "Alice\nBob")], + [PL(0, None, "names", ["Alice", "Bob"])], ), "value with aligned continuation": ( "names = Alice\n Bob", - [PL(0, None, "names", "Alice\nBob")], + [PL(0, None, "names", ["Alice", "Bob"])], + ), + "continuations on several values": ( + "a = 1\n 2\n\n 3\n[s]\nb =\n x\n y\nc = z", + [ + PL(0, None, "a", ["1", "2", "3"]), + PL(4, "s", None, None), + PL(5, "s", "b", ["x", "y"]), + PL(8, "s", "c", ["z"]), + ], ), "blank line": ( "[section]\n\nvalue=1", - [PL(0, "section", None, None), PL(2, "section", "value", "1")], + [PL(0, "section", None, None), PL(2, "section", "value", ["1"])], ), "comment": ("# comment", []), - "comment on value": ("value = 1", [PL(0, None, "value", "1")]), + "comment on value": ("value = 1", [PL(0, None, "value", ["1"])]), "comment on section": ("[section] #comment", [PL(0, "section", None, None)]), "comment2": ("; comment", []), "comment2 on section": ("[section] ;comment", [PL(0, "section", None, None)]), "pseudo section syntax in value": ( "name = value []", - [PL(0, None, "name", "value []")], + [PL(0, None, "name", ["value []"])], ), - "assignment in value": ("value = x = 3", [PL(0, None, "value", "x = 3")]), - "use of colon for name-values": ("name: y", [PL(0, None, "name", "y")]), - "use of colon without space": ("value:y=5", [PL(0, None, "value", "y=5")]), - "equality gets precedence": ("value=xyz:5", [PL(0, None, "value", "xyz:5")]), + "assignment in value": ("value = x = 3", [PL(0, None, "value", ["x = 3"])]), + "use of colon for name-values": ("name: y", [PL(0, None, "name", ["y"])]), + "use of colon without space": ("value:y=5", [PL(0, None, "value", ["y=5"])]), + "equality gets precedence": ("value=xyz:5", [PL(0, None, "value", ["xyz:5"])]), } @@ -290,7 +300,7 @@ def test_api_import() -> None: - assert ALL == ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] + assert ALL == ["COMMENTCHARS", "IniConfig", "ParseError", "iscommentline"] @pytest.mark.parametrize( @@ -412,3 +422,23 @@ ) assert "key" in config["section"] assert config["section"]["key"] == "value" + + +def test_utf8_bom_file(tmp_path: Path) -> None: + """Files saved with a UTF-8 BOM (common on Windows editors) must parse.""" + path = tmp_path / "bom.ini" + path.write_bytes(b"\xef\xbb\xbf[section]\nkey = value\n") + config = IniConfig(path) + assert config["section"]["key"] == "value" + + +def test_utf8_bom_in_data_string() -> None: + config = IniConfig("x.ini", data="\ufeff[section]\nkey = value\n") + assert config["section"]["key"] == "value" + + +def test_parse_utf8_bom_file(tmp_path: Path) -> None: + path = tmp_path / "bom.ini" + path.write_bytes(b"\xef\xbb\xbf[section]\nkey = value\n") + config = IniConfig.parse(path) + assert config["section"]["key"] == "value"
