summaryrefslogtreecommitdiffstats
path: root/contrib/python/iniconfig
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-11-03 13:19:34 +0300
committerrobot-piglet <[email protected]>2025-11-03 13:30:30 +0300
commitd560383ad38f2b482e41032dec7607891c3f8ffe (patch)
tree8980d2e3091cf4d7586773e179aae0391a7a5800 /contrib/python/iniconfig
parent73672db4e0da266a2d32be8e83983f1b556eb68d (diff)
Intermediate changes
commit_hash:3eaa0363a0a20d6baf96d02762d2656dc18cf1b1
Diffstat (limited to 'contrib/python/iniconfig')
-rw-r--r--contrib/python/iniconfig/.dist-info/METADATA14
-rw-r--r--contrib/python/iniconfig/iniconfig/__init__.py177
-rw-r--r--contrib/python/iniconfig/iniconfig/_parse.py109
-rw-r--r--contrib/python/iniconfig/iniconfig/_version.py19
-rw-r--r--contrib/python/iniconfig/iniconfig/exceptions.py6
-rw-r--r--contrib/python/iniconfig/patches/01-arcadia.patch32
-rw-r--r--contrib/python/iniconfig/ya.make2
7 files changed, 240 insertions, 119 deletions
diff --git a/contrib/python/iniconfig/.dist-info/METADATA b/contrib/python/iniconfig/.dist-info/METADATA
index 3a8ef46a3b2..fc3c00df97b 100644
--- a/contrib/python/iniconfig/.dist-info/METADATA
+++ b/contrib/python/iniconfig/.dist-info/METADATA
@@ -1,29 +1,27 @@
Metadata-Version: 2.4
Name: iniconfig
-Version: 2.1.0
+Version: 2.3.0
Summary: brain-dead simple config-ini parsing
-Project-URL: Homepage, https://github.com/pytest-dev/iniconfig
Author-email: Ronny Pfannschmidt <[email protected]>, Holger Krekel <[email protected]>
License-Expression: MIT
-License-File: LICENSE
+Project-URL: Homepage, https://github.com/pytest-dev/iniconfig
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
-Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
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: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
-Requires-Python: >=3.8
+Requires-Python: >=3.10
Description-Content-Type: text/x-rst
+License-File: LICENSE
+Dynamic: license-file
iniconfig: brain-dead simple parsing of ini files
=======================================================
diff --git a/contrib/python/iniconfig/iniconfig/__init__.py b/contrib/python/iniconfig/iniconfig/__init__.py
index ed6499bc6cb..3098daf7e89 100644
--- a/contrib/python/iniconfig/iniconfig/__init__.py
+++ b/contrib/python/iniconfig/iniconfig/__init__.py
@@ -1,42 +1,31 @@
-""" brain-dead simple parser for ini-style files.
+"""brain-dead simple parser for ini-style files.
(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
"""
-from __future__ import annotations
-from typing import (
- Callable,
- Iterator,
- Mapping,
- Optional,
- Tuple,
- TypeVar,
- Union,
- TYPE_CHECKING,
- NoReturn,
- NamedTuple,
- overload,
- cast,
-)
import os
-
-if TYPE_CHECKING:
- from typing import Final
+from collections.abc import Callable
+from collections.abc import Iterator
+from collections.abc import Mapping
+from typing import Final
+from typing import TypeVar
+from typing import overload
__all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"]
-from .exceptions import ParseError
from . import _parse
-from ._parse import COMMENTCHARS, iscommentline
+from ._parse import COMMENTCHARS
+from ._parse import iscommentline
+from .exceptions import ParseError
_D = TypeVar("_D")
_T = TypeVar("_T")
class SectionWrapper:
- config: Final[IniConfig]
+ config: Final["IniConfig"]
name: Final[str]
- def __init__(self, config: IniConfig, name: str) -> None:
+ def __init__(self, config: "IniConfig", name: str) -> None:
self.config = config
self.name = name
@@ -44,16 +33,14 @@ class SectionWrapper:
return self.config.lineof(self.name, name)
@overload
- def get(self, key: str) -> str | None:
- ...
+ def get(self, key: str) -> str | None: ...
@overload
def get(
self,
key: str,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
def get(
@@ -61,12 +48,10 @@ class SectionWrapper:
key: str,
default: None,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
- def get(self, key: str, default: _D, convert: None = None) -> str | _D:
- ...
+ def get(self, key: str, default: _D, convert: None = None) -> str | _D: ...
@overload
def get(
@@ -74,8 +59,7 @@ class SectionWrapper:
key: str,
default: _D,
convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
+ ) -> _T | _D: ...
# TODO: investigate possible mypy bug wrt matching the passed over data
def get( # type: ignore [misc]
@@ -105,47 +89,101 @@ class SectionWrapper:
class IniConfig:
path: Final[str]
sections: Final[Mapping[str, Mapping[str, str]]]
+ _sources: Final[Mapping[tuple[str, str | None], int]]
def __init__(
self,
path: str | os.PathLike[str],
data: str | None = None,
encoding: str = "utf-8",
+ *,
+ _sections: Mapping[str, Mapping[str, str]] | None = None,
+ _sources: Mapping[tuple[str, str | None], int] | None = None,
) -> None:
self.path = os.fspath(path)
- if data is None:
- if os.path.basename(self.path).startswith('pkg:'):
- import pkgutil
- basename = os.path.basename(self.path)
- _, package, resource = basename.split(':')
- content = pkgutil.get_data(package, resource)
- data = content.decode('utf-8')
- else:
- with open(self.path, encoding=encoding) as fp:
- data = fp.read()
+ # Determine sections and sources
+ if _sections is not None and _sources is not None:
+ # Use provided pre-parsed data (called from parse())
+ sections_data = _sections
+ sources = _sources
+ else:
+ # Parse the data (backward compatible path)
+ if data is None:
+ if os.path.basename(self.path).startswith('pkg:'):
+ import pkgutil
- tokens = _parse.parse_lines(self.path, data.splitlines(True))
+ basename = os.path.basename(self.path)
+ _, package, resource = basename.split(':')
+ content = pkgutil.get_data(package, resource)
+ data = content.decode('utf-8')
+ else:
+ with open(self.path, encoding=encoding) as fp:
+ data = fp.read()
- self._sources = {}
- sections_data: dict[str, dict[str, str]]
- self.sections = sections_data = {}
+ # Use old behavior (no stripping) for backward compatibility
+ sections_data, sources = _parse.parse_ini_data(
+ self.path, data, strip_inline_comments=False
+ )
- for lineno, section, name, value in tokens:
- if section is None:
- raise ParseError(self.path, lineno, "no section header defined")
- self._sources[section, name] = lineno
- if name is None:
- if section in self.sections:
- raise ParseError(
- self.path, lineno, f"duplicate section {section!r}"
- )
- sections_data[section] = {}
- else:
- if name in self.sections[section]:
- raise ParseError(self.path, lineno, f"duplicate name {name!r}")
- assert value is not None
- sections_data[section][name] = value
+ # Assign once to Final attributes
+ self._sources = sources
+ self.sections = sections_data
+
+ @classmethod
+ def parse(
+ cls,
+ path: str | os.PathLike[str],
+ data: str | None = None,
+ encoding: str = "utf-8",
+ *,
+ strip_inline_comments: bool = True,
+ strip_section_whitespace: bool = False,
+ ) -> "IniConfig":
+ """Parse an INI file.
+
+ Args:
+ path: Path to the INI file (used for error messages)
+ data: Optional INI content as string. If None, reads from path.
+ encoding: Encoding to use when reading the file (default: utf-8)
+ strip_inline_comments: Whether to strip inline comments from values
+ (default: True). When True, comments starting with # or ; are
+ removed from values, matching the behavior for section comments.
+ strip_section_whitespace: Whether to strip whitespace from section and key names
+ (default: False). When True, strips Unicode whitespace from section and key names,
+ addressing issue #4. When False, preserves existing behavior for backward compatibility.
+
+ Returns:
+ IniConfig instance with parsed configuration
+
+ Example:
+ # With comment stripping (default):
+ config = IniConfig.parse("setup.cfg")
+ # value = "foo" instead of "foo # comment"
+
+ # Without comment stripping (old behavior):
+ config = IniConfig.parse("setup.cfg", strip_inline_comments=False)
+ # value = "foo # comment"
+
+ # With section name stripping (opt-in for issue #4):
+ config = IniConfig.parse("setup.cfg", strip_section_whitespace=True)
+ # section names and keys have Unicode whitespace stripped
+ """
+ fspath = os.fspath(path)
+
+ if data is None:
+ with open(fspath, encoding=encoding) as fp:
+ data = fp.read()
+
+ sections_data, sources = _parse.parse_ini_data(
+ fspath,
+ data,
+ strip_inline_comments=strip_inline_comments,
+ strip_section_whitespace=strip_section_whitespace,
+ )
+
+ # Call constructor with pre-parsed sections and sources
+ return cls(path=fspath, _sections=sections_data, _sources=sources)
def lineof(self, section: str, name: str | None = None) -> int | None:
lineno = self._sources.get((section, name))
@@ -156,8 +194,7 @@ class IniConfig:
self,
section: str,
name: str,
- ) -> str | None:
- ...
+ ) -> str | None: ...
@overload
def get(
@@ -165,8 +202,7 @@ class IniConfig:
section: str,
name: str,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
def get(
@@ -175,14 +211,12 @@ class IniConfig:
name: str,
default: None,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
def get(
self, section: str, name: str, default: _D, convert: None = None
- ) -> str | _D:
- ...
+ ) -> str | _D: ...
@overload
def get(
@@ -191,8 +225,7 @@ class IniConfig:
name: str,
default: _D,
convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
+ ) -> _T | _D: ...
def get( # type: ignore
self,
diff --git a/contrib/python/iniconfig/iniconfig/_parse.py b/contrib/python/iniconfig/iniconfig/_parse.py
index 2d03437bb1c..57b9b44e4cc 100644
--- a/contrib/python/iniconfig/iniconfig/_parse.py
+++ b/contrib/python/iniconfig/iniconfig/_parse.py
@@ -1,33 +1,88 @@
-from __future__ import annotations
-from .exceptions import ParseError
-
+from collections.abc import Mapping
from typing import NamedTuple
+from .exceptions import ParseError
COMMENTCHARS = "#;"
-class _ParsedLine(NamedTuple):
+class ParsedLine(NamedTuple):
lineno: int
section: str | None
name: str | None
value: str | None
-def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]:
- result: list[_ParsedLine] = []
+def parse_ini_data(
+ path: str,
+ data: str,
+ *,
+ strip_inline_comments: bool,
+ strip_section_whitespace: bool = False,
+) -> tuple[Mapping[str, Mapping[str, str]], Mapping[tuple[str, str | None], int]]:
+ """Parse INI data and return sections and sources mappings.
+
+ Args:
+ path: Path for error messages
+ data: INI content as string
+ strip_inline_comments: Whether to strip inline comments from values
+ strip_section_whitespace: Whether to strip whitespace from section and key names
+ (default: False). When True, addresses issue #4 by stripping Unicode whitespace.
+
+ Returns:
+ Tuple of (sections_data, sources) where:
+ - sections_data: mapping of section -> {name -> value}
+ - sources: mapping of (section, name) -> line number
+ """
+ tokens = parse_lines(
+ path,
+ data.splitlines(True),
+ strip_inline_comments=strip_inline_comments,
+ strip_section_whitespace=strip_section_whitespace,
+ )
+
+ sources: dict[tuple[str, str | None], int] = {}
+ sections_data: dict[str, dict[str, str]] = {}
+
+ for lineno, section, name, value in tokens:
+ if section is None:
+ raise ParseError(path, lineno, "no section header defined")
+ sources[section, name] = lineno
+ if name is None:
+ if section in sections_data:
+ raise ParseError(path, lineno, f"duplicate section {section!r}")
+ sections_data[section] = {}
+ else:
+ 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
+
+ return sections_data, sources
+
+
+def parse_lines(
+ path: str,
+ line_iter: list[str],
+ *,
+ strip_inline_comments: bool = False,
+ strip_section_whitespace: bool = False,
+) -> list[ParsedLine]:
+ result: list[ParsedLine] = []
section = None
for lineno, line in enumerate(line_iter):
- name, data = _parseline(path, line, lineno)
+ name, data = _parseline(
+ path, line, lineno, strip_inline_comments, strip_section_whitespace
+ )
# 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))
# new section
elif name is not None and data is None:
if not name:
raise ParseError(path, lineno, "empty section name")
section = name
- result.append(_ParsedLine(lineno, section, None, None))
+ result.append(ParsedLine(lineno, section, None, None))
# continuation
elif name is None and data is not None:
if not result:
@@ -44,7 +99,13 @@ def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]:
return result
-def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | None]:
+def _parseline(
+ path: str,
+ line: str,
+ lineno: int,
+ strip_inline_comments: bool,
+ strip_section_whitespace: bool,
+) -> tuple[str | None, str | None]:
# blank lines
if iscommentline(line):
line = ""
@@ -58,7 +119,11 @@ def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | Non
for c in COMMENTCHARS:
line = line.split(c)[0].rstrip()
if line[-1] == "]":
- return line[1:-1], None
+ section_name = line[1:-1]
+ # Optionally strip whitespace from section name (issue #4)
+ if strip_section_whitespace:
+ section_name = section_name.strip()
+ return section_name, None
return None, realline.strip()
# value
elif not line[0].isspace():
@@ -70,11 +135,27 @@ def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | Non
try:
name, value = line.split(":", 1)
except ValueError:
- raise ParseError(path, lineno, "unexpected line: %r" % line)
- return name.strip(), value.strip()
+ raise ParseError(path, lineno, f"unexpected line: {line!r}") from None
+
+ # Strip key name (always for backward compatibility, optionally with unicode awareness)
+ key_name = name.strip()
+
+ # Strip value
+ value = value.strip()
+ # Strip inline comments from values if requested (issue #55)
+ if strip_inline_comments:
+ for c in COMMENTCHARS:
+ value = value.split(c)[0].rstrip()
+
+ return key_name, value
# continuation
else:
- return None, line.strip()
+ line = line.strip()
+ # Strip inline comments from continuations if requested (issue #55)
+ if strip_inline_comments:
+ for c in COMMENTCHARS:
+ line = line.split(c)[0].rstrip()
+ return None, line
def iscommentline(line: str) -> bool:
diff --git a/contrib/python/iniconfig/iniconfig/_version.py b/contrib/python/iniconfig/iniconfig/_version.py
index e058e2c6573..b982b024d86 100644
--- a/contrib/python/iniconfig/iniconfig/_version.py
+++ b/contrib/python/iniconfig/iniconfig/_version.py
@@ -1,7 +1,14 @@
# file generated by setuptools-scm
# don't change, don't track in version control
-__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
TYPE_CHECKING = False
if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
from typing import Union
VERSION_TUPLE = Tuple[Union[int, str], ...]
+ COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
+ COMMIT_ID = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
+commit_id: COMMIT_ID
+__commit_id__: COMMIT_ID
-__version__ = version = '2.1.0'
-__version_tuple__ = version_tuple = (2, 1, 0)
+__version__ = version = '2.3.0'
+__version_tuple__ = version_tuple = (2, 3, 0)
+
+__commit_id__ = commit_id = None
diff --git a/contrib/python/iniconfig/iniconfig/exceptions.py b/contrib/python/iniconfig/iniconfig/exceptions.py
index 8c4dc9a8b00..d078bc65950 100644
--- a/contrib/python/iniconfig/iniconfig/exceptions.py
+++ b/contrib/python/iniconfig/iniconfig/exceptions.py
@@ -1,8 +1,4 @@
-from __future__ import annotations
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
- from typing import Final
+from typing import Final
class ParseError(Exception):
diff --git a/contrib/python/iniconfig/patches/01-arcadia.patch b/contrib/python/iniconfig/patches/01-arcadia.patch
index d583d659ca6..22c5dcf61ec 100644
--- a/contrib/python/iniconfig/patches/01-arcadia.patch
+++ b/contrib/python/iniconfig/patches/01-arcadia.patch
@@ -1,21 +1,21 @@
--- contrib/python/iniconfig/iniconfig/__init__.py (index)
+++ contrib/python/iniconfig/iniconfig/__init__.py (working tree)
@@ -114,8 +114,16 @@ class IniConfig:
- ) -> None:
- self.path = os.fspath(path)
- if data is None:
-- with open(self.path, encoding=encoding) as fp:
-- data = fp.read()
-+ if os.path.basename(self.path).startswith('pkg:'):
-+ import pkgutil
+ else:
+ # Parse the data (backward compatible path)
+ if data is None:
+- with open(self.path, encoding=encoding) as fp:
+- data = fp.read()
++ if os.path.basename(self.path).startswith('pkg:'):
++ import pkgutil
+
-+ basename = os.path.basename(self.path)
-+ _, package, resource = basename.split(':')
-+ content = pkgutil.get_data(package, resource)
-+ data = content.decode('utf-8')
-+ else:
-+ with open(self.path, encoding=encoding) as fp:
-+ data = fp.read()
-
- tokens = _parse.parse_lines(self.path, data.splitlines(True))
++ basename = os.path.basename(self.path)
++ _, package, resource = basename.split(':')
++ content = pkgutil.get_data(package, resource)
++ data = content.decode('utf-8')
++ else:
++ with open(self.path, encoding=encoding) as fp:
++ data = fp.read()
+ # Use old behavior (no stripping) for backward compatibility
+ sections_data, sources = _parse.parse_ini_data(
diff --git a/contrib/python/iniconfig/ya.make b/contrib/python/iniconfig/ya.make
index 20492d75c6b..e644dce094f 100644
--- a/contrib/python/iniconfig/ya.make
+++ b/contrib/python/iniconfig/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.1.0)
+VERSION(2.3.0)
LICENSE(MIT)