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 2025-12-11 18:31:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-iniconfig (Old)
and /work/SRC/openSUSE:Factory/.python-iniconfig.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-iniconfig"
Thu Dec 11 18:31:58 2025 rev:10 rq:1321919 version:2.3.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-iniconfig/python-iniconfig.changes
2025-06-10 08:58:30.182585626 +0200
+++
/work/SRC/openSUSE:Factory/.python-iniconfig.new.1939/python-iniconfig.changes
2025-12-11 18:32:15.666598211 +0100
@@ -1,0 +2,36 @@
+Wed Dec 10 09:30:44 UTC 2025 - Matej Cepl <[email protected]>
+
+- Clean the SPEC file.
+
+-------------------------------------------------------------------
+Thu Dec 4 10:02:05 UTC 2025 - John Paul Adrian Glaubitz
<[email protected]>
+
+- Update to 2.3.0
+ * add IniConfig.parse() classmethod with strip_inline_comments parameter
(fixes #55)
+ - by default (strip_inline_comments=True), inline comments are properly
stripped from values
+ - set strip_inline_comments=False to preserve old behavior if needed
+ * IniConfig() constructor maintains backward compatibility (does not strip
inline comments)
+ * users should migrate to IniConfig.parse() for correct comment handling
+ * add strip_section_whitespace parameter to IniConfig.parse() (regarding #4)
+ - opt-in parameter to strip Unicode whitespace from section names
+ - when True, strips Unicode whitespace (U+00A0, U+2000, U+3000, etc.) from
section names
+ - when False (default), preserves existing behavior for backward
compatibility
+ * clarify Unicode whitespace handling (regarding #4)
+ - since iniconfig 2.0.0 (Python 3 only), all strings are Unicode by default
+ - Python 3's str.strip() has handled Unicode whitespace since Python 3.0
(2008)
+ - iniconfig automatically benefits from this in all supported versions
(Python >= 3.10)
+ - key names and values have Unicode whitespace properly stripped using
Python's built-in methods
+- from version 2.2.0
+ * drop Python 3.8 and 3.9 support (now requires Python >= 3.10)
+ * add Python 3.14 classifier
+ * migrate from hatchling to setuptools 77 with setuptools_scm
+ * adopt PEP 639 license specifiers and PEP 740 build attestations
+ * migrate from black + pyupgrade to ruff
+ * migrate CI to uv and unified test workflow
+ * automate GitHub releases and PyPI publishing via Trusted Publishing
+ * include tests in sdist
+ * modernize code for Python 3.10+ (remove __future__ annotations,
TYPE_CHECKING guards)
+ * rename _ParsedLine to ParsedLine
+- Update BuildRequires from pyproject.toml
+
+-------------------------------------------------------------------
Old:
----
iniconfig-2.1.0-tests.tar.gz
iniconfig-2.1.0.tar.gz
New:
----
iniconfig-2.3.0-tests.tar.gz
iniconfig-2.3.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-iniconfig.spec ++++++
--- /var/tmp/diff_new_pack.w5psLa/_old 2025-12-11 18:32:16.358627302 +0100
+++ /var/tmp/diff_new_pack.w5psLa/_new 2025-12-11 18:32:16.362627470 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-iniconfig
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 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
@@ -16,7 +16,6 @@
#
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%global flavor @BUILD_FLAVOR@%{nil}
%if "%{flavor}" == "test"
%define psuffix -%{flavor}
@@ -27,7 +26,7 @@
%endif
%{?sle15_python_module_pythons}
Name: python-iniconfig%{psuffix}
-Version: 2.1.0
+Version: 2.3.0
Release: 0
Summary: iniconfig: brain-dead simple config-ini parsing
License: MIT
@@ -36,9 +35,9 @@
Source:
https://files.pythonhosted.org/packages/source/i/iniconfig/iniconfig-%{version}.tar.gz
Source1:
https://github.com/pytest-dev/iniconfig/archive/refs/tags/v%{version}.tar.gz#/iniconfig-%{version}-tests.tar.gz
BuildRequires: %{python_module base >= 3.8}
-BuildRequires: %{python_module hatch_vcs}
-BuildRequires: %{python_module hatchling}
BuildRequires: %{python_module pip}
+BuildRequires: %{python_module setuptools >= 77}
+BuildRequires: %{python_module setuptools_scm}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
BuildArch: noarch
@@ -61,7 +60,7 @@
* iniconfig raises an Error if two sections have the same name.
%prep
-%setup -q -n iniconfig-%{version} -a1
+%autosetup -p1 -n iniconfig-%{version} -a1
%if !%{with test}
%build
++++++ iniconfig-2.1.0-tests.tar.gz -> iniconfig-2.3.0-tests.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/deploy.yml
new/iniconfig-2.3.0/.github/workflows/deploy.yml
--- old/iniconfig-2.1.0/.github/workflows/deploy.yml 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/deploy.yml 1970-01-01
01:00:00.000000000 +0100
@@ -1,63 +0,0 @@
-name: Deploy
-
-on:
- push:
- branches:
- - master
- - "*deploy*"
- release:
- types:
- - published
-
-jobs:
- build:
- if: github.repository == 'pytest-dev/iniconfig'
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Cache
- uses: actions/cache@v4
- with:
- path: ~/.cache/pip
- key: deploy-${{ hashFiles('**/pyproject.toml') }}
- restore-keys: |
- deploy-
-
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.x"
-
- - name: Install build + twine
- run: python -m pip install build twine setuptools_scm
-
- - name: git describe output
- run: git describe --tags
-
- - id: scm_version
- run: |
- VERSION=$(python -m setuptools_scm --strip-dev)
- echo SETUPTOOLS_SCM_PRETEND_VERSION=$VERSION >> $GITHUB_ENV
-
- - name: Build package
- run: python -m build
-
- - name: twine check
- run: twine check dist/*
-
- - name: Publish package to PyPI
- if: github.event.action == 'published'
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.pypi_password }}
-
- - name: Publish package to TestPyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.test_pypi_password }}
- repository_url: https://test.pypi.org/legacy/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/main.yml
new/iniconfig-2.3.0/.github/workflows/main.yml
--- old/iniconfig-2.1.0/.github/workflows/main.yml 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/main.yml 1970-01-01
01:00:00.000000000 +0100
@@ -1,35 +0,0 @@
-name: build
-
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- build:
-
- runs-on: ${{ matrix.os }}
-
- strategy:
- fail-fast: false
- matrix:
- python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
- os: [ubuntu-latest, windows-latest]
-
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python }}
- allow-prereleases: true
- - name: Install hatch
- run: python -m pip install --upgrade pip hatch hatch-vcs
- - name: Run tests
- run: hatch run +py=${{ matrix.python }} test:default --color=yes
-
- pre-commit:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
- with:
- python-version: 3.x
- - uses: pre-commit/[email protected]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/test.yml
new/iniconfig-2.3.0/.github/workflows/test.yml
--- old/iniconfig-2.1.0/.github/workflows/test.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/test.yml 2025-10-18
23:51:36.000000000 +0200
@@ -0,0 +1,144 @@
+name: test
+
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ build-and-inspect:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ outputs:
+ python-versions: ${{
steps.baipp.outputs.supported_python_classifiers_json_array }}
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Build and inspect package
+ id: baipp
+ uses: hynek/build-and-inspect-python-package@v2
+ with:
+ attest-build-provenance-github: ${{ github.event_name !=
'pull_request' || github.event.pull_request.head.repo.full_name ==
github.repository }}
+ env:
+ SETUPTOOLS_SCM_OVERRIDES_FOR_INICONFIG: ${{ github.ref ==
'refs/heads/main' && 'local_scheme="no-local-version"' || '' }}
+
+ test:
+ needs: build-and-inspect
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ shell: bash
+
+ strategy:
+ fail-fast: false
+ matrix:
+ python: ${{ fromJSON(needs.build-and-inspect.outputs.python-versions)
}}
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Checkout test files
+ uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ testing
+ uv.lock
+ sparse-checkout-cone-mode: false
+
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Verify artifacts exist
+ run: |
+ ls -la dist/
+ test -f dist/*.whl || (echo "No wheel found!" && exit 1)
+ test -f dist/*.tar.gz || (echo "No sdist found!" && exit 1)
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+
+ - name: Set up Python ${{ matrix.python }}
+ run: uv python install ${{ matrix.python }}
+
+ - name: Run tests with built wheel
+ run: uv run --with dist/*.whl --with pytest pytest testing --color=yes
+
+ create-release:
+ name: Create GitHub Release
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+ needs: [test, build-and-inspect]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Extract version from tag
+ id: version
+ run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: dist/*
+ fail_on_unmatched_files: true
+ generate_release_notes: true
+ name: Version ${{ steps.version.outputs.VERSION }}
+
+ publish-to-pypi:
+ name: Publish to PyPI
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+ needs: [test, build-and-inspect, create-release]
+ runs-on: ubuntu-latest
+ environment: pypi-upload
+ permissions:
+ id-token: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Publish package to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+
+ publish-to-test-pypi:
+ name: Publish to TestPyPI
+ if: |
+ github.repository == 'pytest-dev/iniconfig' &&
+ (github.event_name == 'push' && (
+ github.ref == 'refs/heads/main' ||
+ contains(github.ref, 'deploy')
+ ))
+ needs: [test, build-and-inspect]
+ runs-on: ubuntu-latest
+ environment: test-pypi-upload
+ permissions:
+ id-token: write
+ attestations: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Publish package to TestPyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.pre-commit-config.yaml
new/iniconfig-2.3.0/.pre-commit-config.yaml
--- old/iniconfig-2.1.0/.pre-commit-config.yaml 2025-03-19 18:28:27.000000000
+0100
+++ new/iniconfig-2.3.0/.pre-commit-config.yaml 2025-10-18 23:51:36.000000000
+0200
@@ -1,24 +1,21 @@
repos:
-- repo: https://github.com/asottile/pyupgrade
- rev: v3.3.1
- hooks:
- - id: pyupgrade
- args: [--py38-plus]
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: "0.4.1"
+ rev: "v2.11.0"
hooks:
- id: pyproject-fmt
-- repo: https://github.com/psf/black
- rev: 22.12.0
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.14.1
hooks:
- - id: black
- language_version: python3
-- repo: https://github.com/pre-commit/mirrors-mypy
- rev: 'v0.991'
- hooks:
- - id: mypy
- args: []
- additional_dependencies:
- - "pytest==7.2.0"
- - "tomli"
\ No newline at end of file
+ - id: ruff-check
+ args: [--fix]
+ - id: ruff-format
+
+- repo: https://github.com/pre-commit/mirrors-mypy
+ rev: 'v1.18.2'
+ hooks:
+ - id: mypy
+ args: []
+ additional_dependencies:
+ - "pytest==7.2.0"
+ - "tomli"
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/CHANGELOG
new/iniconfig-2.3.0/CHANGELOG
--- old/iniconfig-2.1.0/CHANGELOG 2025-03-19 18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/CHANGELOG 2025-10-18 23:51:36.000000000 +0200
@@ -1,3 +1,35 @@
+2.3.0
+=====
+
+* add IniConfig.parse() classmethod with strip_inline_comments parameter
(fixes #55)
+ - by default (strip_inline_comments=True), inline comments are properly
stripped from values
+ - set strip_inline_comments=False to preserve old behavior if needed
+* IniConfig() constructor maintains backward compatibility (does not strip
inline comments)
+* users should migrate to IniConfig.parse() for correct comment handling
+* add strip_section_whitespace parameter to IniConfig.parse() (regarding #4)
+ - opt-in parameter to strip Unicode whitespace from section names
+ - when True, strips Unicode whitespace (U+00A0, U+2000, U+3000, etc.) from
section names
+ - when False (default), preserves existing behavior for backward
compatibility
+* clarify Unicode whitespace handling (regarding #4)
+ - since iniconfig 2.0.0 (Python 3 only), all strings are Unicode by default
+ - Python 3's str.strip() has handled Unicode whitespace since Python 3.0
(2008)
+ - iniconfig automatically benefits from this in all supported versions
(Python >= 3.10)
+ - key names and values have Unicode whitespace properly stripped using
Python's built-in methods
+
+2.2.0
+=====
+
+* drop Python 3.8 and 3.9 support (now requires Python >= 3.10)
+* add Python 3.14 classifier
+* migrate from hatchling to setuptools 77 with setuptools_scm
+* adopt PEP 639 license specifiers and PEP 740 build attestations
+* migrate from black + pyupgrade to ruff
+* migrate CI to uv and unified test workflow
+* automate GitHub releases and PyPI publishing via Trusted Publishing
+* include tests in sdist
+* modernize code for Python 3.10+ (remove __future__ annotations,
TYPE_CHECKING guards)
+* rename _ParsedLine to ParsedLine
+
2.1.0
=====
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/pyproject.toml
new/iniconfig-2.3.0/pyproject.toml
--- old/iniconfig-2.1.0/pyproject.toml 2025-03-19 18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/pyproject.toml 2025-10-18 23:51:36.000000000 +0200
@@ -1,8 +1,8 @@
[build-system]
-build-backend = "hatchling.build"
+build-backend = "setuptools.build_meta"
requires = [
- "hatch-vcs",
- "hatchling>=1.26",
+ "setuptools>=77",
+ "setuptools-scm>=8",
]
[project]
@@ -10,62 +10,61 @@
description = "brain-dead simple config-ini parsing"
readme = "README.rst"
license = "MIT"
+license-files = [ "LICENSE" ]
authors = [
- { name = "Ronny Pfannschmidt", email = "[email protected]" },
- { name = "Holger Krekel", email = "[email protected]" },
+ { name = "Ronny Pfannschmidt", email = "[email protected]" },
+ { name = "Holger Krekel", email = "[email protected]" },
+]
+requires-python = ">=3.10"
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Utilities",
]
-requires-python = ">=3.8"
dynamic = [
"version",
]
-classifiers = [
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: MIT License",
- "Operating System :: MacOS :: MacOS X",
- "Operating System :: Microsoft :: Windows",
- "Operating System :: POSIX",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Topic :: Software Development :: Libraries",
- "Topic :: Utilities",
-]
-[project.urls]
-Homepage = "https://github.com/pytest-dev/iniconfig"
-
-
-[tool.hatch.version]
-source = "vcs"
-
-[tool.hatch.build.hooks.vcs]
-version-file = "src/iniconfig/_version.py"
-
-[tool.hatch.build.targets.sdist]
-include = [
- "/src",
-]
-
-[tool.hatch.envs.test]
-dependencies = [
- "pytest"
+urls.Homepage = "https://github.com/pytest-dev/iniconfig"
+
+[dependency-groups]
+dev = [
+ "pytest>=8.4.2",
+ "pytest-xdist>=3.8",
]
-[tool.hatch.envs.test.scripts]
-default = "pytest {args}"
-[[tool.hatch.envs.test.matrix]]
-python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+[tool.setuptools]
+packages = [ "iniconfig" ]
+package-dir = { "" = "src" }
+
+[tool.setuptools.package-data]
+iniconfig = [ "py.typed" ]
[tool.setuptools_scm]
+write_to = "src/iniconfig/_version.py"
-[tool.mypy]
-strict = true
+[tool.ruff]
+lint.extend-select = [
+ "B", # flake8-bugbear
+ "I", # isort
+ "PT", # flake8-pytest-style
+ "UP", # pyupgrade
+]
+lint.isort.force-single-line = true
+lint.isort.known-first-party = [ "iniconfig" ]
[tool.pytest.ini_options]
testpaths = "testing"
+
+[tool.mypy]
+strict = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/__init__.py
new/iniconfig-2.3.0/src/iniconfig/__init__.py
--- old/iniconfig-2.1.0/src/iniconfig/__init__.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig/__init__.py 2025-10-18
23:51:36.000000000 +0200
@@ -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 @@
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 @@
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 @@
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,39 +89,93 @@
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)
+
+ # 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:
+ 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(
+ self.path, data, strip_inline_comments=False
+ )
+
+ # 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(self.path, encoding=encoding) as fp:
+ with open(fspath, encoding=encoding) as fp:
data = fp.read()
- tokens = _parse.parse_lines(self.path, data.splitlines(True))
+ sections_data, sources = _parse.parse_ini_data(
+ fspath,
+ data,
+ strip_inline_comments=strip_inline_comments,
+ strip_section_whitespace=strip_section_whitespace,
+ )
- self._sources = {}
- sections_data: dict[str, dict[str, str]]
- self.sections = sections_data = {}
-
- 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
+ # 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))
@@ -148,8 +186,7 @@
self,
section: str,
name: str,
- ) -> str | None:
- ...
+ ) -> str | None: ...
@overload
def get(
@@ -157,8 +194,7 @@
section: str,
name: str,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
def get(
@@ -167,14 +203,12 @@
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(
@@ -183,8 +217,7 @@
name: str,
default: _D,
convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
+ ) -> _T | _D: ...
def get( # type: ignore
self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/_parse.py
new/iniconfig-2.3.0/src/iniconfig/_parse.py
--- old/iniconfig-2.1.0/src/iniconfig/_parse.py 2025-03-19 18:28:27.000000000
+0100
+++ new/iniconfig-2.3.0/src/iniconfig/_parse.py 2025-10-18 23:51:36.000000000
+0200
@@ -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 @@
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 @@
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 @@
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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/exceptions.py
new/iniconfig-2.3.0/src/iniconfig/exceptions.py
--- old/iniconfig-2.1.0/src/iniconfig/exceptions.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig/exceptions.py 2025-10-18
23:51:36.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/testing/test_iniconfig.py
new/iniconfig-2.3.0/testing/test_iniconfig.py
--- old/iniconfig-2.1.0/testing/test_iniconfig.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/testing/test_iniconfig.py 2025-10-18
23:51:36.000000000 +0200
@@ -1,11 +1,13 @@
-from __future__ import annotations
-import pytest
-from iniconfig import IniConfig, ParseError, __all__ as ALL
-from iniconfig._parse import _ParsedLine as PL
-from iniconfig import iscommentline
-from textwrap import dedent
from pathlib import Path
+from textwrap import dedent
+
+import pytest
+from iniconfig import IniConfig
+from iniconfig import ParseError
+from iniconfig import __all__ as ALL
+from iniconfig import iscommentline
+from iniconfig._parse import ParsedLine as PL
check_tokens: dict[str, tuple[str, list[PL]]] = {
"section": ("[section]", [PL(0, "section", None, None)]),
@@ -44,7 +46,6 @@
@pytest.fixture(params=sorted(check_tokens))
def input_expected(request: pytest.FixtureRequest) -> tuple[str, list[PL]]:
-
return check_tokens[request.param]
@@ -124,7 +125,7 @@
config = IniConfig(str(path), "[diff]")
assert list(config.sections) == ["diff"]
with pytest.raises(TypeError):
- IniConfig(data=path.read_text()) # type: ignore
+ IniConfig(data=path.read_text()) # type: ignore[call-arg]
def test_iniconfig_section_first() -> None:
@@ -214,12 +215,12 @@
"""
),
)
- l = list(config)
- assert len(l) == 2
- assert l[0].name == "section1"
- assert l[0]["value"] == "1"
- assert l[1].name == "section2"
- assert l[1]["value"] == "2"
+ sections = list(config)
+ assert len(sections) == 2
+ assert sections[0].name == "section1"
+ assert sections[0]["value"] == "1"
+ assert sections[1].name == "section2"
+ assert sections[1]["value"] == "2"
def test_config_contains() -> None:
@@ -251,8 +252,8 @@
b = 2
""",
)
- l = list(config)
- secnames = [x.name for x in l]
+ sections_list = list(config)
+ secnames = [x.name for x in sections_list]
assert secnames == ["section2", "section"]
assert list(config["section2"]) == ["value", "value2"]
assert list(config["section"]) == ["a", "b"]
@@ -303,3 +304,111 @@
)
def test_iscommentline_true(line: str) -> None:
assert iscommentline(line)
+
+
+def test_parse_strips_inline_comments() -> None:
+ """Test that IniConfig.parse() strips inline comments from values by
default."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ name3 = value3# no space before comment
+ list = a, b, c # some items
+ """
+ ),
+ )
+ assert config["section1"]["name1"] == "value1"
+ assert config["section1"]["name2"] == "value2"
+ assert config["section1"]["name3"] == "value3"
+ assert config["section1"]["list"] == "a, b, c"
+
+
+def test_parse_strips_inline_comments_from_continuations() -> None:
+ """Test that inline comments are stripped from continuation lines."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section]
+ names =
+ Alice # first person
+ Bob ; second person
+ Charlie
+ """
+ ),
+ )
+ assert config["section"]["names"] == "Alice\nBob\nCharlie"
+
+
+def test_parse_preserves_inline_comments_when_disabled() -> None:
+ """Test that IniConfig.parse(strip_inline_comments=False) preserves
comments."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ list = a, b, c # some items
+ """
+ ),
+ strip_inline_comments=False,
+ )
+ assert config["section1"]["name1"] == "value1 # this is a comment"
+ assert config["section1"]["name2"] == "value2 ; this is also a comment"
+ assert config["section1"]["list"] == "a, b, c # some items"
+
+
+def test_constructor_preserves_inline_comments_for_backward_compatibility() ->
None:
+ """Test that IniConfig() constructor preserves old behavior (no
stripping)."""
+ config = IniConfig(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ """
+ ),
+ )
+ assert config["section1"]["name1"] == "value1 # this is a comment"
+ assert config["section1"]["name2"] == "value2 ; this is also a comment"
+
+
+def test_unicode_whitespace_stripped() -> None:
+ """Test that Unicode whitespace is stripped (issue #4)."""
+ config = IniConfig(
+ "test.ini",
+ data="[section]\n"
+ + "name1 = \u00a0value1\u00a0\n" # NO-BREAK SPACE
+ + "name2 = \u2000value2\u2000\n" # EN QUAD
+ + "name3 = \u3000value3\u3000\n", # IDEOGRAPHIC SPACE
+ )
+ assert config["section"]["name1"] == "value1"
+ assert config["section"]["name2"] == "value2"
+ assert config["section"]["name3"] == "value3"
+
+
+def test_unicode_whitespace_in_section_names_with_opt_in() -> None:
+ """Test that Unicode whitespace can be stripped from section names with
opt-in (issue #4)."""
+ config = IniConfig.parse(
+ "test.ini",
+ data="[section\u00a0]\n" # NO-BREAK SPACE at end
+ + "key = value\n",
+ strip_section_whitespace=True,
+ )
+ assert "section" in config
+ assert config["section"]["key"] == "value"
+
+
+def test_unicode_whitespace_in_key_names() -> None:
+ """Test that Unicode whitespace is stripped from key names (issue #4)."""
+ config = IniConfig(
+ "test.ini",
+ data="[section]\n" + "key\u00a0 = value\n", # NO-BREAK SPACE after key
+ )
+ assert "key" in config["section"]
+ assert config["section"]["key"] == "value"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/uv.lock new/iniconfig-2.3.0/uv.lock
--- old/iniconfig-2.1.0/uv.lock 1970-01-01 01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/uv.lock 2025-10-18 23:51:36.000000000 +0200
@@ -0,0 +1,167 @@
+version = 1
+revision = 3
+requires-python = ">=3.10"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz",
hash =
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size
= 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
hash =
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size
= 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz",
hash =
"sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size
= 29749, upload-time = "2025-05-10T17:42:51.123Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl",
hash =
"sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size
= 16674, upload-time = "2025-05-10T17:42:49.33Z" },
+]
+
+[[package]]
+name = "execnet"
+version = "2.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz",
hash =
"sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size
= 166524, upload-time = "2024-04-08T09:04:19.245Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl",
hash =
"sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size
= 40612, upload-time = "2024-04-08T09:04:17.414Z" },
+]
+
+[[package]]
+name = "iniconfig"
+source = { editable = "." }
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-xdist" },
+]
+
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=8.4.2" },
+ { name = "pytest-xdist", specifier = ">=3.8.0" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz",
hash =
"sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size
= 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl",
hash =
"sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size
= 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz",
hash =
"sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size
= 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl",
hash =
"sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size
= 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz",
hash =
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size
= 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl",
hash =
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size
= 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz",
hash =
"sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size
= 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl",
hash =
"sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size
= 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-xdist"
+version = "3.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "execnet" },
+ { name = "pytest" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz",
hash =
"sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size
= 88069, upload-time = "2025-07-01T13:30:59.346Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl",
hash =
"sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size
= 46396, upload-time = "2025-07-01T13:30:56.632Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz",
hash =
"sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size
= 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl",
hash =
"sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size
= 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url =
"https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl",
hash =
"sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size
= 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url =
"https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size
= 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url =
"https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size
= 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url =
"https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl",
hash =
"sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size
= 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url =
"https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl",
hash =
"sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size
= 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url =
"https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl",
hash =
"sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size
= 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url =
"https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl",
hash =
"sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size
= 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url =
"https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl",
hash =
"sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size
= 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url =
"https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl",
hash =
"sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size
= 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url =
"https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size
= 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url =
"https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size
= 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url =
"https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl",
hash =
"sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size
= 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url =
"https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl",
hash =
"sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size
= 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url =
"https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl",
hash =
"sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size
= 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url =
"https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl",
hash =
"sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size
= 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url =
"https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl",
hash =
"sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size
= 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url =
"https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl",
hash =
"sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size
= 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url =
"https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size
= 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url =
"https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size
= 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url =
"https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl",
hash =
"sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size
= 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url =
"https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl",
hash =
"sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size
= 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url =
"https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl",
hash =
"sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size
= 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url =
"https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl",
hash =
"sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size
= 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url =
"https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl",
hash =
"sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size
= 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url =
"https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl",
hash =
"sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size
= 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url =
"https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size
= 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url =
"https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size
= 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url =
"https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl",
hash =
"sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size
= 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url =
"https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl",
hash =
"sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size
= 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url =
"https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl",
hash =
"sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size
= 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url =
"https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl",
hash =
"sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size
= 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url =
"https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl",
hash =
"sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size
= 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url =
"https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl",
hash =
"sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size
= 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url =
"https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size
= 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url =
"https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size
= 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url =
"https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl",
hash =
"sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size
= 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url =
"https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl",
hash =
"sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size
= 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url =
"https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl",
hash =
"sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size
= 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url =
"https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl",
hash =
"sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size
= 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url =
"https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl",
hash =
"sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size
= 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz",
hash =
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size
= 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",
hash =
"sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size
= 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
++++++ iniconfig-2.1.0-tests.tar.gz -> iniconfig-2.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/deploy.yml
new/iniconfig-2.3.0/.github/workflows/deploy.yml
--- old/iniconfig-2.1.0/.github/workflows/deploy.yml 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/deploy.yml 1970-01-01
01:00:00.000000000 +0100
@@ -1,63 +0,0 @@
-name: Deploy
-
-on:
- push:
- branches:
- - master
- - "*deploy*"
- release:
- types:
- - published
-
-jobs:
- build:
- if: github.repository == 'pytest-dev/iniconfig'
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Cache
- uses: actions/cache@v4
- with:
- path: ~/.cache/pip
- key: deploy-${{ hashFiles('**/pyproject.toml') }}
- restore-keys: |
- deploy-
-
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.x"
-
- - name: Install build + twine
- run: python -m pip install build twine setuptools_scm
-
- - name: git describe output
- run: git describe --tags
-
- - id: scm_version
- run: |
- VERSION=$(python -m setuptools_scm --strip-dev)
- echo SETUPTOOLS_SCM_PRETEND_VERSION=$VERSION >> $GITHUB_ENV
-
- - name: Build package
- run: python -m build
-
- - name: twine check
- run: twine check dist/*
-
- - name: Publish package to PyPI
- if: github.event.action == 'published'
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.pypi_password }}
-
- - name: Publish package to TestPyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.test_pypi_password }}
- repository_url: https://test.pypi.org/legacy/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/main.yml
new/iniconfig-2.3.0/.github/workflows/main.yml
--- old/iniconfig-2.1.0/.github/workflows/main.yml 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/main.yml 1970-01-01
01:00:00.000000000 +0100
@@ -1,35 +0,0 @@
-name: build
-
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- build:
-
- runs-on: ${{ matrix.os }}
-
- strategy:
- fail-fast: false
- matrix:
- python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
- os: [ubuntu-latest, windows-latest]
-
- steps:
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python }}
- allow-prereleases: true
- - name: Install hatch
- run: python -m pip install --upgrade pip hatch hatch-vcs
- - name: Run tests
- run: hatch run +py=${{ matrix.python }} test:default --color=yes
-
- pre-commit:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5
- with:
- python-version: 3.x
- - uses: pre-commit/[email protected]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.github/workflows/test.yml
new/iniconfig-2.3.0/.github/workflows/test.yml
--- old/iniconfig-2.1.0/.github/workflows/test.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/.github/workflows/test.yml 2025-10-18
23:53:59.000000000 +0200
@@ -0,0 +1,144 @@
+name: test
+
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ build-and-inspect:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ attestations: write
+ contents: read
+ outputs:
+ python-versions: ${{
steps.baipp.outputs.supported_python_classifiers_json_array }}
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Build and inspect package
+ id: baipp
+ uses: hynek/build-and-inspect-python-package@v2
+ with:
+ attest-build-provenance-github: ${{ github.event_name !=
'pull_request' || github.event.pull_request.head.repo.full_name ==
github.repository }}
+ env:
+ SETUPTOOLS_SCM_OVERRIDES_FOR_INICONFIG: ${{ github.ref ==
'refs/heads/main' && 'local_scheme="no-local-version"' || '' }}
+
+ test:
+ needs: build-and-inspect
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ shell: bash
+
+ strategy:
+ fail-fast: false
+ matrix:
+ python: ${{ fromJSON(needs.build-and-inspect.outputs.python-versions)
}}
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Checkout test files
+ uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ testing
+ uv.lock
+ sparse-checkout-cone-mode: false
+
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Verify artifacts exist
+ run: |
+ ls -la dist/
+ test -f dist/*.whl || (echo "No wheel found!" && exit 1)
+ test -f dist/*.tar.gz || (echo "No sdist found!" && exit 1)
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+
+ - name: Set up Python ${{ matrix.python }}
+ run: uv python install ${{ matrix.python }}
+
+ - name: Run tests with built wheel
+ run: uv run --with dist/*.whl --with pytest pytest testing --color=yes
+
+ create-release:
+ name: Create GitHub Release
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+ needs: [test, build-and-inspect]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Extract version from tag
+ id: version
+ run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: dist/*
+ fail_on_unmatched_files: true
+ generate_release_notes: true
+ name: Version ${{ steps.version.outputs.VERSION }}
+
+ publish-to-pypi:
+ name: Publish to PyPI
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+ needs: [test, build-and-inspect, create-release]
+ runs-on: ubuntu-latest
+ environment: pypi-upload
+ permissions:
+ id-token: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Publish package to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+
+ publish-to-test-pypi:
+ name: Publish to TestPyPI
+ if: |
+ github.repository == 'pytest-dev/iniconfig' &&
+ (github.event_name == 'push' && (
+ github.ref == 'refs/heads/main' ||
+ contains(github.ref, 'deploy')
+ ))
+ needs: [test, build-and-inspect]
+ runs-on: ubuntu-latest
+ environment: test-pypi-upload
+ permissions:
+ id-token: write
+ attestations: write
+
+ steps:
+ - name: Download built packages
+ uses: actions/download-artifact@v4
+ with:
+ name: Packages
+ path: dist
+
+ - name: Publish package to TestPyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/.pre-commit-config.yaml
new/iniconfig-2.3.0/.pre-commit-config.yaml
--- old/iniconfig-2.1.0/.pre-commit-config.yaml 2025-03-19 18:28:27.000000000
+0100
+++ new/iniconfig-2.3.0/.pre-commit-config.yaml 2025-10-18 23:53:59.000000000
+0200
@@ -1,24 +1,21 @@
repos:
-- repo: https://github.com/asottile/pyupgrade
- rev: v3.3.1
- hooks:
- - id: pyupgrade
- args: [--py38-plus]
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: "0.4.1"
+ rev: "v2.11.0"
hooks:
- id: pyproject-fmt
-- repo: https://github.com/psf/black
- rev: 22.12.0
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.14.1
hooks:
- - id: black
- language_version: python3
-- repo: https://github.com/pre-commit/mirrors-mypy
- rev: 'v0.991'
- hooks:
- - id: mypy
- args: []
- additional_dependencies:
- - "pytest==7.2.0"
- - "tomli"
\ No newline at end of file
+ - id: ruff-check
+ args: [--fix]
+ - id: ruff-format
+
+- repo: https://github.com/pre-commit/mirrors-mypy
+ rev: 'v1.18.2'
+ hooks:
+ - id: mypy
+ args: []
+ additional_dependencies:
+ - "pytest==7.2.0"
+ - "tomli"
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/CHANGELOG
new/iniconfig-2.3.0/CHANGELOG
--- old/iniconfig-2.1.0/CHANGELOG 2025-03-19 18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/CHANGELOG 2025-10-18 23:53:59.000000000 +0200
@@ -1,3 +1,35 @@
+2.3.0
+=====
+
+* add IniConfig.parse() classmethod with strip_inline_comments parameter
(fixes #55)
+ - by default (strip_inline_comments=True), inline comments are properly
stripped from values
+ - set strip_inline_comments=False to preserve old behavior if needed
+* IniConfig() constructor maintains backward compatibility (does not strip
inline comments)
+* users should migrate to IniConfig.parse() for correct comment handling
+* add strip_section_whitespace parameter to IniConfig.parse() (regarding #4)
+ - opt-in parameter to strip Unicode whitespace from section names
+ - when True, strips Unicode whitespace (U+00A0, U+2000, U+3000, etc.) from
section names
+ - when False (default), preserves existing behavior for backward
compatibility
+* clarify Unicode whitespace handling (regarding #4)
+ - since iniconfig 2.0.0 (Python 3 only), all strings are Unicode by default
+ - Python 3's str.strip() has handled Unicode whitespace since Python 3.0
(2008)
+ - iniconfig automatically benefits from this in all supported versions
(Python >= 3.10)
+ - key names and values have Unicode whitespace properly stripped using
Python's built-in methods
+
+2.2.0
+=====
+
+* drop Python 3.8 and 3.9 support (now requires Python >= 3.10)
+* add Python 3.14 classifier
+* migrate from hatchling to setuptools 77 with setuptools_scm
+* adopt PEP 639 license specifiers and PEP 740 build attestations
+* migrate from black + pyupgrade to ruff
+* migrate CI to uv and unified test workflow
+* automate GitHub releases and PyPI publishing via Trusted Publishing
+* include tests in sdist
+* modernize code for Python 3.10+ (remove __future__ annotations,
TYPE_CHECKING guards)
+* rename _ParsedLine to ParsedLine
+
2.1.0
=====
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/PKG-INFO new/iniconfig-2.3.0/PKG-INFO
--- old/iniconfig-2.1.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/PKG-INFO 2025-10-18 23:54:02.445129200 +0200
@@ -0,0 +1,79 @@
+Metadata-Version: 2.4
+Name: iniconfig
+Version: 2.3.0
+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: 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/RonnyPfannschmidt/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
+
+then you can do:
+
+.. code-block:: pycon
+
+ >>> import iniconfig
+ >>> ini = iniconfig.IniConfig("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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/pyproject.toml
new/iniconfig-2.3.0/pyproject.toml
--- old/iniconfig-2.1.0/pyproject.toml 2025-03-19 18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/pyproject.toml 2025-10-18 23:53:59.000000000 +0200
@@ -1,8 +1,8 @@
[build-system]
-build-backend = "hatchling.build"
+build-backend = "setuptools.build_meta"
requires = [
- "hatch-vcs",
- "hatchling>=1.26",
+ "setuptools>=77",
+ "setuptools-scm>=8",
]
[project]
@@ -10,62 +10,61 @@
description = "brain-dead simple config-ini parsing"
readme = "README.rst"
license = "MIT"
+license-files = [ "LICENSE" ]
authors = [
- { name = "Ronny Pfannschmidt", email = "[email protected]" },
- { name = "Holger Krekel", email = "[email protected]" },
+ { name = "Ronny Pfannschmidt", email = "[email protected]" },
+ { name = "Holger Krekel", email = "[email protected]" },
+]
+requires-python = ">=3.10"
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Utilities",
]
-requires-python = ">=3.8"
dynamic = [
"version",
]
-classifiers = [
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: MIT License",
- "Operating System :: MacOS :: MacOS X",
- "Operating System :: Microsoft :: Windows",
- "Operating System :: POSIX",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Topic :: Software Development :: Libraries",
- "Topic :: Utilities",
-]
-[project.urls]
-Homepage = "https://github.com/pytest-dev/iniconfig"
-
-
-[tool.hatch.version]
-source = "vcs"
-
-[tool.hatch.build.hooks.vcs]
-version-file = "src/iniconfig/_version.py"
-
-[tool.hatch.build.targets.sdist]
-include = [
- "/src",
-]
-
-[tool.hatch.envs.test]
-dependencies = [
- "pytest"
+urls.Homepage = "https://github.com/pytest-dev/iniconfig"
+
+[dependency-groups]
+dev = [
+ "pytest>=8.4.2",
+ "pytest-xdist>=3.8",
]
-[tool.hatch.envs.test.scripts]
-default = "pytest {args}"
-[[tool.hatch.envs.test.matrix]]
-python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+[tool.setuptools]
+packages = [ "iniconfig" ]
+package-dir = { "" = "src" }
+
+[tool.setuptools.package-data]
+iniconfig = [ "py.typed" ]
[tool.setuptools_scm]
+write_to = "src/iniconfig/_version.py"
-[tool.mypy]
-strict = true
+[tool.ruff]
+lint.extend-select = [
+ "B", # flake8-bugbear
+ "I", # isort
+ "PT", # flake8-pytest-style
+ "UP", # pyupgrade
+]
+lint.isort.force-single-line = true
+lint.isort.known-first-party = [ "iniconfig" ]
[tool.pytest.ini_options]
testpaths = "testing"
+
+[tool.mypy]
+strict = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/setup.cfg
new/iniconfig-2.3.0/setup.cfg
--- old/iniconfig-2.1.0/setup.cfg 1970-01-01 01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/setup.cfg 2025-10-18 23:54:02.445381000 +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.1.0/src/iniconfig/__init__.py
new/iniconfig-2.3.0/src/iniconfig/__init__.py
--- old/iniconfig-2.1.0/src/iniconfig/__init__.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig/__init__.py 2025-10-18
23:53:59.000000000 +0200
@@ -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 @@
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 @@
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 @@
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,39 +89,93 @@
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)
+
+ # 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:
+ 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(
+ self.path, data, strip_inline_comments=False
+ )
+
+ # 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(self.path, encoding=encoding) as fp:
+ with open(fspath, encoding=encoding) as fp:
data = fp.read()
- tokens = _parse.parse_lines(self.path, data.splitlines(True))
+ sections_data, sources = _parse.parse_ini_data(
+ fspath,
+ data,
+ strip_inline_comments=strip_inline_comments,
+ strip_section_whitespace=strip_section_whitespace,
+ )
- self._sources = {}
- sections_data: dict[str, dict[str, str]]
- self.sections = sections_data = {}
-
- 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
+ # 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))
@@ -148,8 +186,7 @@
self,
section: str,
name: str,
- ) -> str | None:
- ...
+ ) -> str | None: ...
@overload
def get(
@@ -157,8 +194,7 @@
section: str,
name: str,
convert: Callable[[str], _T],
- ) -> _T | None:
- ...
+ ) -> _T | None: ...
@overload
def get(
@@ -167,14 +203,12 @@
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(
@@ -183,8 +217,7 @@
name: str,
default: _D,
convert: Callable[[str], _T],
- ) -> _T | _D:
- ...
+ ) -> _T | _D: ...
def get( # type: ignore
self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/_parse.py
new/iniconfig-2.3.0/src/iniconfig/_parse.py
--- old/iniconfig-2.1.0/src/iniconfig/_parse.py 2025-03-19 18:28:27.000000000
+0100
+++ new/iniconfig-2.3.0/src/iniconfig/_parse.py 2025-10-18 23:53:59.000000000
+0200
@@ -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 @@
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 @@
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 @@
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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/_version.py
new/iniconfig-2.3.0/src/iniconfig/_version.py
--- old/iniconfig-2.1.0/src/iniconfig/_version.py 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig/_version.py 2025-10-18
23:54:02.000000000 +0200
@@ -0,0 +1,34 @@
+# file generated by setuptools-scm
+# don't change, don't track in version control
+
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import Tuple
+ 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.3.0'
+__version_tuple__ = version_tuple = (2, 3, 0)
+
+__commit_id__ = commit_id = 'g7faed13ae'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig/exceptions.py
new/iniconfig-2.3.0/src/iniconfig/exceptions.py
--- old/iniconfig-2.1.0/src/iniconfig/exceptions.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig/exceptions.py 2025-10-18
23:53:59.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig.egg-info/PKG-INFO
new/iniconfig-2.3.0/src/iniconfig.egg-info/PKG-INFO
--- old/iniconfig-2.1.0/src/iniconfig.egg-info/PKG-INFO 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig.egg-info/PKG-INFO 2025-10-18
23:54:02.000000000 +0200
@@ -0,0 +1,79 @@
+Metadata-Version: 2.4
+Name: iniconfig
+Version: 2.3.0
+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: 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/RonnyPfannschmidt/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
+
+then you can do:
+
+.. code-block:: pycon
+
+ >>> import iniconfig
+ >>> ini = iniconfig.IniConfig("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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig.egg-info/SOURCES.txt
new/iniconfig-2.3.0/src/iniconfig.egg-info/SOURCES.txt
--- old/iniconfig-2.1.0/src/iniconfig.egg-info/SOURCES.txt 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig.egg-info/SOURCES.txt 2025-10-18
23:54:02.000000000 +0200
@@ -0,0 +1,21 @@
+.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/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.1.0/src/iniconfig.egg-info/dependency_links.txt
new/iniconfig-2.3.0/src/iniconfig.egg-info/dependency_links.txt
--- old/iniconfig-2.1.0/src/iniconfig.egg-info/dependency_links.txt
1970-01-01 01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig.egg-info/dependency_links.txt
2025-10-18 23:54:02.000000000 +0200
@@ -0,0 +1 @@
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/src/iniconfig.egg-info/top_level.txt
new/iniconfig-2.3.0/src/iniconfig.egg-info/top_level.txt
--- old/iniconfig-2.1.0/src/iniconfig.egg-info/top_level.txt 1970-01-01
01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/src/iniconfig.egg-info/top_level.txt 2025-10-18
23:54:02.000000000 +0200
@@ -0,0 +1 @@
+iniconfig
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/testing/test_iniconfig.py
new/iniconfig-2.3.0/testing/test_iniconfig.py
--- old/iniconfig-2.1.0/testing/test_iniconfig.py 2025-03-19
18:28:27.000000000 +0100
+++ new/iniconfig-2.3.0/testing/test_iniconfig.py 2025-10-18
23:53:59.000000000 +0200
@@ -1,11 +1,13 @@
-from __future__ import annotations
-import pytest
-from iniconfig import IniConfig, ParseError, __all__ as ALL
-from iniconfig._parse import _ParsedLine as PL
-from iniconfig import iscommentline
-from textwrap import dedent
from pathlib import Path
+from textwrap import dedent
+
+import pytest
+from iniconfig import IniConfig
+from iniconfig import ParseError
+from iniconfig import __all__ as ALL
+from iniconfig import iscommentline
+from iniconfig._parse import ParsedLine as PL
check_tokens: dict[str, tuple[str, list[PL]]] = {
"section": ("[section]", [PL(0, "section", None, None)]),
@@ -44,7 +46,6 @@
@pytest.fixture(params=sorted(check_tokens))
def input_expected(request: pytest.FixtureRequest) -> tuple[str, list[PL]]:
-
return check_tokens[request.param]
@@ -124,7 +125,7 @@
config = IniConfig(str(path), "[diff]")
assert list(config.sections) == ["diff"]
with pytest.raises(TypeError):
- IniConfig(data=path.read_text()) # type: ignore
+ IniConfig(data=path.read_text()) # type: ignore[call-arg]
def test_iniconfig_section_first() -> None:
@@ -214,12 +215,12 @@
"""
),
)
- l = list(config)
- assert len(l) == 2
- assert l[0].name == "section1"
- assert l[0]["value"] == "1"
- assert l[1].name == "section2"
- assert l[1]["value"] == "2"
+ sections = list(config)
+ assert len(sections) == 2
+ assert sections[0].name == "section1"
+ assert sections[0]["value"] == "1"
+ assert sections[1].name == "section2"
+ assert sections[1]["value"] == "2"
def test_config_contains() -> None:
@@ -251,8 +252,8 @@
b = 2
""",
)
- l = list(config)
- secnames = [x.name for x in l]
+ sections_list = list(config)
+ secnames = [x.name for x in sections_list]
assert secnames == ["section2", "section"]
assert list(config["section2"]) == ["value", "value2"]
assert list(config["section"]) == ["a", "b"]
@@ -303,3 +304,111 @@
)
def test_iscommentline_true(line: str) -> None:
assert iscommentline(line)
+
+
+def test_parse_strips_inline_comments() -> None:
+ """Test that IniConfig.parse() strips inline comments from values by
default."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ name3 = value3# no space before comment
+ list = a, b, c # some items
+ """
+ ),
+ )
+ assert config["section1"]["name1"] == "value1"
+ assert config["section1"]["name2"] == "value2"
+ assert config["section1"]["name3"] == "value3"
+ assert config["section1"]["list"] == "a, b, c"
+
+
+def test_parse_strips_inline_comments_from_continuations() -> None:
+ """Test that inline comments are stripped from continuation lines."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section]
+ names =
+ Alice # first person
+ Bob ; second person
+ Charlie
+ """
+ ),
+ )
+ assert config["section"]["names"] == "Alice\nBob\nCharlie"
+
+
+def test_parse_preserves_inline_comments_when_disabled() -> None:
+ """Test that IniConfig.parse(strip_inline_comments=False) preserves
comments."""
+ config = IniConfig.parse(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ list = a, b, c # some items
+ """
+ ),
+ strip_inline_comments=False,
+ )
+ assert config["section1"]["name1"] == "value1 # this is a comment"
+ assert config["section1"]["name2"] == "value2 ; this is also a comment"
+ assert config["section1"]["list"] == "a, b, c # some items"
+
+
+def test_constructor_preserves_inline_comments_for_backward_compatibility() ->
None:
+ """Test that IniConfig() constructor preserves old behavior (no
stripping)."""
+ config = IniConfig(
+ "test.ini",
+ data=dedent(
+ """
+ [section1]
+ name1 = value1 # this is a comment
+ name2 = value2 ; this is also a comment
+ """
+ ),
+ )
+ assert config["section1"]["name1"] == "value1 # this is a comment"
+ assert config["section1"]["name2"] == "value2 ; this is also a comment"
+
+
+def test_unicode_whitespace_stripped() -> None:
+ """Test that Unicode whitespace is stripped (issue #4)."""
+ config = IniConfig(
+ "test.ini",
+ data="[section]\n"
+ + "name1 = \u00a0value1\u00a0\n" # NO-BREAK SPACE
+ + "name2 = \u2000value2\u2000\n" # EN QUAD
+ + "name3 = \u3000value3\u3000\n", # IDEOGRAPHIC SPACE
+ )
+ assert config["section"]["name1"] == "value1"
+ assert config["section"]["name2"] == "value2"
+ assert config["section"]["name3"] == "value3"
+
+
+def test_unicode_whitespace_in_section_names_with_opt_in() -> None:
+ """Test that Unicode whitespace can be stripped from section names with
opt-in (issue #4)."""
+ config = IniConfig.parse(
+ "test.ini",
+ data="[section\u00a0]\n" # NO-BREAK SPACE at end
+ + "key = value\n",
+ strip_section_whitespace=True,
+ )
+ assert "section" in config
+ assert config["section"]["key"] == "value"
+
+
+def test_unicode_whitespace_in_key_names() -> None:
+ """Test that Unicode whitespace is stripped from key names (issue #4)."""
+ config = IniConfig(
+ "test.ini",
+ data="[section]\n" + "key\u00a0 = value\n", # NO-BREAK SPACE after key
+ )
+ assert "key" in config["section"]
+ assert config["section"]["key"] == "value"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/iniconfig-2.1.0/uv.lock new/iniconfig-2.3.0/uv.lock
--- old/iniconfig-2.1.0/uv.lock 1970-01-01 01:00:00.000000000 +0100
+++ new/iniconfig-2.3.0/uv.lock 2025-10-18 23:53:59.000000000 +0200
@@ -0,0 +1,167 @@
+version = 1
+revision = 3
+requires-python = ">=3.10"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz",
hash =
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size
= 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
hash =
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size
= 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz",
hash =
"sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size
= 29749, upload-time = "2025-05-10T17:42:51.123Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl",
hash =
"sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size
= 16674, upload-time = "2025-05-10T17:42:49.33Z" },
+]
+
+[[package]]
+name = "execnet"
+version = "2.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz",
hash =
"sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size
= 166524, upload-time = "2024-04-08T09:04:19.245Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl",
hash =
"sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size
= 40612, upload-time = "2024-04-08T09:04:17.414Z" },
+]
+
+[[package]]
+name = "iniconfig"
+source = { editable = "." }
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-xdist" },
+]
+
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=8.4.2" },
+ { name = "pytest-xdist", specifier = ">=3.8.0" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz",
hash =
"sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size
= 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl",
hash =
"sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size
= 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz",
hash =
"sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size
= 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl",
hash =
"sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size
= 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz",
hash =
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size
= 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl",
hash =
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size
= 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz",
hash =
"sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size
= 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl",
hash =
"sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size
= 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-xdist"
+version = "3.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "execnet" },
+ { name = "pytest" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz",
hash =
"sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size
= 88069, upload-time = "2025-07-01T13:30:59.346Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl",
hash =
"sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size
= 46396, upload-time = "2025-07-01T13:30:56.632Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz",
hash =
"sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size
= 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl",
hash =
"sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size
= 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url =
"https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl",
hash =
"sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size
= 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url =
"https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size
= 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url =
"https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size
= 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url =
"https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl",
hash =
"sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size
= 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url =
"https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl",
hash =
"sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size
= 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url =
"https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl",
hash =
"sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size
= 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url =
"https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl",
hash =
"sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size
= 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url =
"https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl",
hash =
"sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size
= 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url =
"https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl",
hash =
"sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size
= 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url =
"https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size
= 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url =
"https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size
= 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url =
"https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl",
hash =
"sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size
= 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url =
"https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl",
hash =
"sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size
= 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url =
"https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl",
hash =
"sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size
= 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url =
"https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl",
hash =
"sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size
= 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url =
"https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl",
hash =
"sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size
= 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url =
"https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl",
hash =
"sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size
= 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url =
"https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size
= 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url =
"https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size
= 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url =
"https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl",
hash =
"sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size
= 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url =
"https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl",
hash =
"sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size
= 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url =
"https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl",
hash =
"sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size
= 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url =
"https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl",
hash =
"sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size
= 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url =
"https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl",
hash =
"sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size
= 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url =
"https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl",
hash =
"sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size
= 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url =
"https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size
= 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url =
"https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size
= 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url =
"https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl",
hash =
"sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size
= 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url =
"https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl",
hash =
"sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size
= 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url =
"https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl",
hash =
"sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size
= 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url =
"https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl",
hash =
"sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size
= 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url =
"https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl",
hash =
"sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size
= 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url =
"https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl",
hash =
"sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size
= 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url =
"https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl",
hash =
"sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size
= 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url =
"https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
hash =
"sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size
= 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url =
"https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl",
hash =
"sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size
= 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url =
"https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl",
hash =
"sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size
= 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url =
"https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl",
hash =
"sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size
= 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url =
"https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl",
hash =
"sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size
= 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url =
"https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl",
hash =
"sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size
= 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz",
hash =
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size
= 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",
hash =
"sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size
= 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]