ConfigUpdater 3.2__tar.gz → 4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. configupdater-4.0/.gitignore +59 -0
  2. {ConfigUpdater-3.2 → configupdater-4.0}/AUTHORS.rst +6 -1
  3. configupdater-4.0/CHANGELOG.rst +183 -0
  4. {ConfigUpdater-3.2 → configupdater-4.0}/CONTRIBUTING.rst +20 -20
  5. configupdater-4.0/PKG-INFO +349 -0
  6. configupdater-4.0/README.md +320 -0
  7. configupdater-4.0/docs/conf.py +59 -0
  8. {ConfigUpdater-3.2 → configupdater-4.0}/docs/index.rst +5 -1
  9. configupdater-4.0/docs/usage.rst +388 -0
  10. configupdater-4.0/pyproject.toml +216 -0
  11. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/__init__.py +3 -9
  12. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/block.py +57 -48
  13. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/builder.py +33 -14
  14. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/configupdater.py +54 -39
  15. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/container.py +18 -21
  16. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/document.py +69 -60
  17. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/option.py +59 -59
  18. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/parser.py +96 -85
  19. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/section.py +109 -71
  20. {ConfigUpdater-3.2 → configupdater-4.0}/tests/conftest.py +4 -7
  21. {ConfigUpdater-3.2 → configupdater-4.0}/tests/test_configupdater.py +245 -35
  22. configupdater-4.0/tests/test_option.py +171 -0
  23. configupdater-4.0/tests/test_optionxform.py +270 -0
  24. configupdater-4.0/tests/test_parser.py +54 -0
  25. configupdater-4.0/tests/test_readme.py +26 -0
  26. {ConfigUpdater-3.2 → configupdater-4.0}/tests/test_section.py +67 -0
  27. configupdater-4.0/tools/release_notes.py +61 -0
  28. {ConfigUpdater-3.2 → configupdater-4.0}/tools/stdlib_diff.py +14 -9
  29. ConfigUpdater-3.2/.cirrus.yml +0 -185
  30. ConfigUpdater-3.2/.coveragerc +0 -32
  31. ConfigUpdater-3.2/.github/FUNDING.yml +0 -12
  32. ConfigUpdater-3.2/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  33. ConfigUpdater-3.2/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  34. ConfigUpdater-3.2/.github/workflows/publish-package.yml +0 -32
  35. ConfigUpdater-3.2/.gitignore +0 -53
  36. ConfigUpdater-3.2/.isort.cfg +0 -3
  37. ConfigUpdater-3.2/.pre-commit-config.yaml +0 -51
  38. ConfigUpdater-3.2/.readthedocs.yml +0 -26
  39. ConfigUpdater-3.2/CHANGELOG.rst +0 -118
  40. ConfigUpdater-3.2/PKG-INFO +0 -282
  41. ConfigUpdater-3.2/README.rst +0 -248
  42. ConfigUpdater-3.2/docs/Makefile +0 -29
  43. ConfigUpdater-3.2/docs/conf.py +0 -260
  44. ConfigUpdater-3.2/docs/requirements.txt +0 -3
  45. ConfigUpdater-3.2/docs/usage.rst +0 -179
  46. ConfigUpdater-3.2/hatch.toml +0 -0
  47. ConfigUpdater-3.2/pyproject.toml +0 -8
  48. ConfigUpdater-3.2/setup.cfg +0 -109
  49. ConfigUpdater-3.2/setup.py +0 -21
  50. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/PKG-INFO +0 -282
  51. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/SOURCES.txt +0 -65
  52. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/dependency_links.txt +0 -1
  53. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/not-zip-safe +0 -1
  54. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/requires.txt +0 -11
  55. ConfigUpdater-3.2/src/ConfigUpdater.egg-info/top_level.txt +0 -1
  56. ConfigUpdater-3.2/tests/test_option.py +0 -53
  57. ConfigUpdater-3.2/tests/test_optionxform.py +0 -100
  58. ConfigUpdater-3.2/tests/test_parser.py +0 -16
  59. ConfigUpdater-3.2/tox.ini +0 -112
  60. {ConfigUpdater-3.2 → configupdater-4.0}/LICENSE.txt +0 -0
  61. {ConfigUpdater-3.2 → configupdater-4.0}/docs/_static/.gitignore +0 -0
  62. {ConfigUpdater-3.2 → configupdater-4.0}/docs/api.rst +0 -0
  63. {ConfigUpdater-3.2 → configupdater-4.0}/docs/authors.rst +0 -0
  64. {ConfigUpdater-3.2 → configupdater-4.0}/docs/changelog.rst +0 -0
  65. {ConfigUpdater-3.2 → configupdater-4.0}/docs/contributing.rst +0 -0
  66. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/README.md +0 -0
  67. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/banner-1280x645.png +0 -0
  68. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/banner-640x323.png +0 -0
  69. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/configupdater.svg +0 -0
  70. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/configupdater_plain.svg +0 -0
  71. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/favicon.ico +0 -0
  72. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/icon-92x128.png +0 -0
  73. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/icon.svg +0 -0
  74. {ConfigUpdater-3.2 → configupdater-4.0}/docs/gfx/sidebar-254x128.png +0 -0
  75. {ConfigUpdater-3.2 → configupdater-4.0}/docs/license.rst +0 -0
  76. {ConfigUpdater-3.2 → configupdater-4.0}/src/configupdater/py.typed +0 -0
  77. {ConfigUpdater-3.2 → configupdater-4.0}/tests/test_setup.cfg +0 -0
  78. {ConfigUpdater-3.2 → configupdater-4.0}/tools/__init__.py +0 -0
@@ -0,0 +1,59 @@
1
+ # Byte-compiled files and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ .cache/
6
+ .mypy_cache/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+
10
+ # Virtual environments
11
+ .venv*/
12
+ .conda*/
13
+ .tox/
14
+ .python-version
15
+
16
+ # Build artifacts
17
+ build/
18
+ dist/
19
+ *.egg-info/
20
+ *.egg
21
+ .eggs/
22
+
23
+ # Coverage and test reports
24
+ .coverage
25
+ .coverage.*
26
+ htmlcov/
27
+ coverage.xml
28
+ coverage.lcov
29
+ junit*.xml
30
+
31
+ # Docs
32
+ docs/_build/
33
+ docs/api/
34
+ docs/_rst/
35
+ docs/html/
36
+ site/
37
+
38
+ # Editors and OS files
39
+ .idea/
40
+ .vscode/
41
+ .project
42
+ .pydevproject
43
+ .settings/
44
+ .ropeproject
45
+ tags
46
+ *~
47
+ .*.swp
48
+ .DS_Store
49
+ */.ipynb_checkpoints/
50
+
51
+ # Claude Code (local only)
52
+ .claude/worktrees/
53
+ .claude/settings.local.json
54
+
55
+ # Temporary files
56
+ *.orig
57
+ *.bak
58
+ *.log
59
+ *.pot
@@ -3,5 +3,10 @@ Contributors
3
3
  ============
4
4
 
5
5
  * Florian Wilhelm <florian.wilhelm@gmail.com>
6
- * Christian Theune <ct@flyingcircus.io>
7
6
  * Anderson Bravalheri <andersonbravalheri@gmail.com>
7
+ * Christian Theune <ct@flyingcircus.io>
8
+ * codeofwxz (GitHub)
9
+ * mika <211269698+mikamikasuki@users.noreply.github.com>
10
+ * Sinkleberg
11
+ * blouirts123 (GitHub)
12
+ * bysiber (GitHub)
@@ -0,0 +1,183 @@
1
+ =========
2
+ Changelog
3
+ =========
4
+
5
+ Version 4.0
6
+ ===========
7
+
8
+ - **Breaking:** Drop support for Python < 3.11, the minimum supported version is now
9
+ Python 3.11, PR #150
10
+ - Add support for Python 3.15, PR #160
11
+ - Simplify type hints using Python 3.11 features (``typing.Self``, ``X | Y`` unions)
12
+ and remove compatibility code for older Python versions, PR #150
13
+ - Add ``add_before_skip_comments`` to ``Section`` for inserting blocks before the
14
+ comments that directly precede a section, issue #117
15
+ - Fix ``add_section`` gluing the new section header to the last line when the file
16
+ does not end with a newline, issue #126
17
+ - Fix ``None`` being written as delimiter when assigning a value to an option that was
18
+ parsed without a value (``allow_no_value``), issue #141
19
+ - Port the option regexes of Python 3.15's ``configparser`` (CPython gh-146333). The
20
+ old ones backtracked quadratically, so a long line with many spaces could stall
21
+ the parser for minutes, PR #161
22
+ - Fix navigating, detaching or inserting at the wrong position when a container holds
23
+ several equal comment or blank-line blocks, issue #140
24
+ - ``del section[key]`` (and thus ``section.pop(key)``) now applies ``optionxform``
25
+ like ``section[key]`` does, PR #148
26
+ - Document that a custom ``optionxform`` must be idempotent, PR #152
27
+ - Build with hatchling and hatch-vcs, with all metadata in ``pyproject.toml``. The
28
+ wheel is now tagged ``py3-none-any`` (instead of ``py2.py3-none-any``), and the
29
+ license is declared as a PEP 639 expression. The ``testing`` extra is removed;
30
+ development dependencies are dependency groups, e.g. ``uv sync``, PR #156
31
+ - Complete the type annotations of the public API, now checked with mypy in strict
32
+ mode, and fix the ``NoneValueDisallowed`` warning under ``python -OO``, PR #158
33
+ - Fix ``NotAttachedError``, ``AlreadyAttachedError`` and ``AssignMultilineValueError``
34
+ failing with an ``AttributeError`` under ``python -OO``, PR #163
35
+ - Correct the ``NoneValueDisallowed`` message. It claimed that an option without a
36
+ value is written with an empty value, but the option is omitted since version 3.0.1,
37
+ PR #163
38
+ - ``add_before.option`` and ``add_after.option`` pass ``delimiter`` and
39
+ ``space_around_delimiters`` on to the new option instead of silently ignoring all
40
+ keyword arguments. Other keyword arguments now raise a ``TypeError``, PR #163
41
+ - **Breaking:** An explicit ``space_around_delimiters`` of an ``Option`` (also given
42
+ to ``add_before.option``, ``add_after.option`` or ``Section.create_option``) now
43
+ takes precedence over the syntax options of the ``ConfigUpdater``, so ``False``
44
+ writes ``key=value`` even with the default ``space_around_delimiters=True``.
45
+ ``Option`` defaults to ``space_around_delimiters=None``, i.e. it follows the
46
+ ``ConfigUpdater`` it belongs to: ``Option("key", "value")`` now writes
47
+ ``key=value`` into a ``ConfigUpdater(space_around_delimiters=False)`` instead of
48
+ ``key = value``. Options created with ``section[key] = value`` or ``set`` keep
49
+ following the ``ConfigUpdater`` they are copied to, PR #166
50
+ - **Breaking:** ``key in section``, ``section.has_option(key)`` and
51
+ ``section.get(key)`` now apply ``optionxform`` to ``key``, like ``section[key]``
52
+ and ConfigParser do. With the default ``str.lower``, ``"KEY" in section`` is now
53
+ ``True`` and ``section.get("KEY")`` returns the option instead of ``None``, PR #153
54
+ - **Breaking:** ``Section.set`` and ``ConfigUpdater.set`` keep the given spelling
55
+ when they create a new option, like ``section[key] = value`` does. For example,
56
+ ``updater.set("metadata", "Name", "x")`` now writes ``Name = x`` instead of
57
+ ``name = x``, PR #153
58
+ - ``ConfigUpdater.set``, ``get``, ``has_option`` and ``remove_option`` apply
59
+ ``optionxform`` only once instead of transforming already transformed keys again,
60
+ PR #153
61
+ - Fix wrong code examples in the usage guide and the README, which are now tested,
62
+ PR #164
63
+ - Fix ``update_file`` writing the file with the platform's default encoding instead
64
+ of the encoding it was read with. ``read`` and ``read_file`` remember the encoding
65
+ (``read_file`` takes it from ``f.encoding``), PR #165
66
+ - ``ConfigUpdater.read`` and ``Parser.read`` call ``io.text_encoding`` like
67
+ ConfigParser, so with ``-X warn_default_encoding`` the ``EncodingWarning`` points
68
+ to the caller (PEP 597), PR #165
69
+
70
+ Version 3.2
71
+ ===========
72
+
73
+ - Option ``prepend_newline`` in ``set_values`` to optionally avoid new lines, issue #104
74
+ - Fix square brackets not parsed in section names, issue #122
75
+
76
+ Version 3.1.1
77
+ =============
78
+
79
+ - Preserve indentation of section when there are comments, issue #92
80
+
81
+ Version 3.1
82
+ ===========
83
+
84
+ - Prevent modifying multi-line values directly with ``value``, issue #87
85
+ - Added ``append`` method to ``Option`` for editing multi-line values
86
+ - Added ``as_list`` method to ``Option`` to handle multi-line values more easily
87
+
88
+ Version 3.0.1
89
+ =============
90
+
91
+ - Fix error when parsing unindented comments in multi-line values, issue #73
92
+ - Fix invalid string produced when ``allow_no_value = False``, issue #68
93
+
94
+ Version 3.0
95
+ ===========
96
+
97
+ - Added type hinting, issue #16
98
+ - Fix parsing error of indented comment lines, issue #25
99
+ - Allow handling of raw section comment, issue #25
100
+ - More unit testing of optionxform, issue #55
101
+ - Allowing sections/options to be copied from one document to the other, issue #47
102
+ - New logo, issue #29
103
+ - Whole API was rechecked by @abravalheri and changed for consistency, issue #19
104
+
105
+
106
+ Version 2.0
107
+ ===========
108
+
109
+ - Changes in parser, i.e. comments in multi-line option values are kept
110
+ - Issue #14 is fixed
111
+ - Parameter ``empty_lines_in_values`` is now activated by default and can be changed
112
+ - Renamed ``sections_blocks`` to ``section_blocks`` for consistency
113
+ - Renamed ``last_item`` to ``last_block`` for consistency
114
+ - Added ``first_block``
115
+ - Reworked some internal parts of the inheritance hierarchy
116
+ - Added ``remove`` to remove the current block
117
+ - Added ``next_block`` and ``previous_block`` for easier navigation in section
118
+
119
+ Version 1.1.3
120
+ =============
121
+
122
+ - Added fallback option to ConfigUpdater.get reflecting ConfigParser
123
+
124
+ Version 1.1.2
125
+ =============
126
+
127
+ - Fix wrongly modifying input in Option.set_value #11
128
+
129
+ Version 1.1.1
130
+ =============
131
+
132
+ - Fix iterating over the items() view of a section breaks #8
133
+
134
+ Version 1.1
135
+ ===========
136
+
137
+ - Validate format on write by default (can be deactivated)
138
+ - Fixed issue #7 with mixed-case options
139
+ - Fixed issue #7 with add_before/add_after problem
140
+ - Fixed issue #7 with wrong duplicate mixed-case entries
141
+ - Fixed issue #7 with duplicate options after add_after/before
142
+
143
+ Version 1.0.1
144
+ =============
145
+
146
+ - More sane error message if ``read_file`` is misused
147
+ - Also run unittests with Windows
148
+
149
+ Version 1.0
150
+ ===========
151
+
152
+ - Fix: Use \n instead of os.linesep where appropriate
153
+
154
+ Version 0.3.2
155
+ =============
156
+
157
+ - Added Github and documentation link into the project's metadata
158
+
159
+ Version 0.3.1
160
+ =============
161
+
162
+ - Require Python >= 3.4 with ``python_requires``
163
+
164
+ Version 0.3
165
+ ===========
166
+
167
+ - Added a ``insert_at`` function at section level
168
+ - Some internal code simplifications
169
+
170
+ Version 0.2
171
+ ===========
172
+
173
+ - Added a ``to_dict()`` function
174
+
175
+ Version 0.1.1
176
+ =============
177
+
178
+ - Allow for flexible comment character in ``comment(...)``
179
+
180
+ Version 0.1
181
+ ===========
182
+
183
+ - First release
@@ -22,34 +22,30 @@ additional considerations and avoids unnecessary work.
22
22
  Create an environment
23
23
  ---------------------
24
24
 
25
- Before you start coding we recommend to install Miniconda_ which allows
26
- to setup a dedicated development environment named ``configupdater`` with::
27
-
28
- conda create -n configupdater python=3 virtualenv pytest pytest-cov
29
-
30
- Then activate the environment ``configupdater`` with::
31
-
32
- source activate configupdater
25
+ ConfigUpdater requires Python 3.11 or newer. The development environment is
26
+ managed with uv_, so install it first. uv also downloads a suitable Python
27
+ version if you don't have one.
33
28
 
34
29
  Clone the repository
35
30
  --------------------
36
31
 
37
- #. `Create a Gitub account`_ if you do not already have one.
32
+ #. `Create a Github account`_ if you do not already have one.
38
33
  #. Fork the `project repository`_: click on the *Fork* button near the top of the
39
34
  page. This creates a copy of the code under your account on the GitHub server.
40
35
  #. Clone this copy to your local disk::
41
36
 
42
37
  git clone git@github.com:YourLogin/configupdater.git
43
38
 
44
- #. Run ``python setup.py develop`` to install ``configupdater`` into your environment.
39
+ #. Create a virtual environment in ``.venv`` with ``configupdater`` and all
40
+ development tools (pytest, mypy, tox, pre-commit)::
45
41
 
46
- #. Install ``pre-commit``::
42
+ uv sync
47
43
 
48
- pip install pre-commit
49
- pre-commit install
44
+ #. Install the ``pre-commit`` hooks::
50
45
 
51
- PyScaffold project comes with a lot of hooks configured to
52
- automatically help the developer to check the code being written.
46
+ uv run pre-commit install
47
+
48
+ The hooks automatically check the code you are writing on every commit.
53
49
 
54
50
  #. Create a branch to hold your changes::
55
51
 
@@ -68,12 +64,16 @@ Clone the repository
68
64
 
69
65
  #. Please check that your changes don't break any unit tests with::
70
66
 
71
- python setup.py test
67
+ uv run tox
68
+
69
+ or, for a quick run without coverage, ``uv run pytest``.
72
70
 
73
71
  Don't forget to also add unit tests in case your contribution
74
72
  adds an additional feature and is not just a bugfix.
75
73
 
76
- #. Use `flake8`_ to check your code style.
74
+ #. The ``pre-commit`` hooks check your code style with `ruff`_ on every commit.
75
+ You can also run them on all files with ``uv run tox -e lint``, and check the
76
+ type hints with ``uv run tox -e typecheck``.
77
77
  #. Add yourself to the list of contributors in ``AUTHORS.rst``.
78
78
  #. Go to the web page of your ConfigUpdater fork, and click
79
79
  "Create pull request" to send your changes to the maintainers for review.
@@ -83,8 +83,8 @@ Clone the repository
83
83
  .. _PyPI: https://pypi.python.org/
84
84
  .. _project repository: https://github.com/pyscaffold/configupdater/
85
85
  .. _Git: https://git-scm.com/
86
- .. _Miniconda: https://docs.conda.io/en/latest/miniconda.html
86
+ .. _uv: https://docs.astral.sh/uv/getting-started/installation/
87
87
  .. _issue tracker: https://github.com/pyscaffold/configupdater/issues
88
- .. _Create a Gitub account: https://github.com/join
88
+ .. _Create a Github account: https://github.com/join
89
89
  .. _creating a PR: https://help.github.com/articles/creating-a-pull-request/
90
- .. _flake8: https://flake8.pycqa.org/
90
+ .. _ruff: https://docs.astral.sh/ruff/
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.5
2
+ Name: ConfigUpdater
3
+ Version: 4.0
4
+ Summary: Parser like ConfigParser but for updating configuration files
5
+ Project-URL: Homepage, https://github.com/pyscaffold/configupdater
6
+ Project-URL: Documentation, https://configupdater.readthedocs.io/
7
+ Project-URL: Source, https://github.com/pyscaffold/configupdater/
8
+ Project-URL: Issues, https://github.com/pyscaffold/configupdater/issues
9
+ Project-URL: Changelog, https://configupdater.readthedocs.io/en/latest/changelog.html
10
+ Project-URL: Download, https://pypi.org/project/configupdater/#files
11
+ Project-URL: Conda-Forge, https://anaconda.org/conda-forge/configupdater
12
+ Author-email: Florian Wilhelm <florian.wilhelm@gmail.com>, Anderson Bravalheri <andersonbravalheri@gmail.com>
13
+ License-Expression: MIT
14
+ License-File: LICENSE.txt
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+
30
+ <div align="center">
31
+ <img src="https://raw.githubusercontent.com/pyscaffold/configupdater/master/docs/gfx/banner-640x323.png" alt="Configupdater logo" width="500" role="img">
32
+ </div>
33
+ <br/>
34
+
35
+ | | |
36
+ |---------|------------------------------------|
37
+ | CI/CD | [![Tests][Tests-image]][Tests-link] [![Coverage][Coverage-image]][Coverage-link] [![Publish Package][Publish-image]][Publish-link] [![GitHub Sponsors][sponsor-image]][sponsor-link] |
38
+ | Package | [![PyPI - Version][PyPI_ver-image]][PyPI_ver-link] [![Conda - Version][Conda-image]][Conda-link] [![PyPI - Downloads][PyPI_down-image]][PyPI_down-link] [![PyPI - Python Version][PyPI_py-image]][PyPI_py-link] |
39
+ | Details | [![uv][uv-image]][uv-link] [![Linting - Ruff][ruff-image]][ruff-link] [![test - pytest][pytest-image]][pytest-link] [![Pre-Commit][precommit-image]][precommit-link] [![Types - Mypy][mypy-image]][mypy-link] [![License - MIT][MIT-image]][MIT-link] [![Docs - RTD][rtd-image]][rtd-link] |
40
+
41
+ The sole purpose of [ConfigUpdater] is to easily
42
+ update an INI config file with no changes to the original file except
43
+ the intended ones. This means comments, the ordering of sections and
44
+ key/value-pairs as wells as their cases are kept as in the original
45
+ file. Thus ConfigUpdater provides complementary functionality to
46
+ Python\'s [ConfigParser], which is primarily meant for reading config
47
+ files and writing *new* ones.
48
+
49
+ # Features
50
+
51
+ The key differences to [ConfigParser] are:
52
+
53
+ - minimal invasive changes in the update configuration file,
54
+ - proper handling of comments,
55
+ - only a single config file can be updated at a time,
56
+ - the original case of sections and keys are kept,
57
+ - control over the position of a new section/key
58
+
59
+ Following features are **deliberately not** implemented:
60
+
61
+ - interpolation of values,
62
+ - propagation of parameters from the default section,
63
+ - conversions of values,
64
+ - passing key/value-pairs with `default` argument,
65
+ - non-strict mode allowing duplicate sections and keys.
66
+
67
+ # Usage
68
+
69
+ First install the package with either:
70
+
71
+ ```console
72
+ pip install configupdater
73
+ ```
74
+
75
+ or add it to a project managed with [uv]:
76
+
77
+ ```console
78
+ uv add configupdater
79
+ ```
80
+
81
+ or:
82
+
83
+ ```console
84
+ conda install -c conda-forge configupdater
85
+ ```
86
+
87
+ Now we can simply do:
88
+
89
+ ```python
90
+ from configupdater import ConfigUpdater
91
+
92
+ updater = ConfigUpdater()
93
+ updater.read("setup.cfg")
94
+ ```
95
+
96
+ which would read the file `setup.cfg` that is found in many projects.
97
+
98
+ To change the value of an existing key we can simply do:
99
+
100
+ ```python
101
+ updater["metadata"]["author"].value = "Alan Turing"
102
+ ```
103
+
104
+ At any point we can print the current state of the configuration file
105
+ with:
106
+
107
+ ```python
108
+ print(updater)
109
+ ```
110
+
111
+ To update the read-in file, with the encoding it was read with, just call
112
+ `updater.update_file()` or `updater.write(open('filename','w'))` to write
113
+ the changed configuration file to another destination. Before actually
114
+ writing, ConfigUpdater will automatically check that the updated
115
+ configuration file is still valid by parsing it with the help of ConfigParser.
116
+
117
+ Many of ConfigParser\'s methods still exists and it\'s best to look them
118
+ up in the [module reference]. Let\'s look at some examples.
119
+
120
+ ## Adding and removing options
121
+
122
+ Let\'s say we have the following configuration in a string:
123
+
124
+ ```python
125
+ cfg = """
126
+ [metadata]
127
+ author = Ada Lovelace
128
+ summary = The Analytical Engine
129
+ """
130
+ ```
131
+
132
+ We can add an *license* option, i.e. a key/value pair, in the same way
133
+ we would do with ConfigParser:
134
+
135
+ ```python
136
+ updater = ConfigUpdater()
137
+ updater.read_string(cfg)
138
+ updater["metadata"]["license"] = "MIT"
139
+ ```
140
+
141
+ A simple `print(updater)` will give show you that the new option was
142
+ appended to the end:
143
+
144
+ ```ini
145
+ [metadata]
146
+ author = Ada Lovelace
147
+ summary = The Analytical Engine
148
+ license = MIT
149
+ ```
150
+
151
+ Since the license is really important to us let\'s say we want to add it
152
+ before the `summary` and even add a short comment before it:
153
+
154
+ ```python
155
+ updater = ConfigUpdater()
156
+ updater.read_string(cfg)
157
+ (
158
+ updater["metadata"]["summary"]
159
+ .add_before.comment("Ada would have loved MIT")
160
+ .option("license", "MIT")
161
+ )
162
+ ```
163
+
164
+ which would result in:
165
+
166
+ ```ini
167
+ [metadata]
168
+ author = Ada Lovelace
169
+ # Ada would have loved MIT
170
+ license = MIT
171
+ summary = The Analytical Engine
172
+ ```
173
+
174
+ Using `add_after` would give the same result and looks like:
175
+
176
+ ```python
177
+ updater = ConfigUpdater()
178
+ updater.read_string(cfg)
179
+ (
180
+ updater["metadata"]["author"]
181
+ .add_after.comment("Ada would have loved MIT")
182
+ .option("license", "MIT")
183
+ )
184
+ ```
185
+
186
+ Let\'s say we want to rename `summary` to the more common
187
+ `description`:
188
+
189
+ ```python
190
+ updater = ConfigUpdater()
191
+ updater.read_string(cfg)
192
+ updater["metadata"]["summary"].key = "description"
193
+ ```
194
+
195
+ If we wanted no summary at all, we could just do
196
+ `del updater["metadata"]["summary"]`.
197
+
198
+ ## Adding and removing sections
199
+
200
+ Adding and remove sections just works like adding and removing options
201
+ but on a higher level. Sticking to our *Ada Lovelace* example, let\'s
202
+ say we want to add a section `options` just before `metadata` with a
203
+ comment and two new lines to separate it from `metadata`:
204
+
205
+ ```python
206
+ updater = ConfigUpdater()
207
+ updater.read_string(cfg)
208
+ (
209
+ updater["metadata"]
210
+ .add_before.comment("Some specific project options")
211
+ .section("options")
212
+ .space(2)
213
+ )
214
+ ```
215
+
216
+ As expected, this results in:
217
+
218
+ ```ini
219
+ # Some specific project options
220
+ [options]
221
+
222
+
223
+ [metadata]
224
+ author = Ada Lovelace
225
+ summary = The Analytical Engine
226
+ ```
227
+
228
+ We could now fill the new section with options like we learnt before. If
229
+ we wanted to rename an existing section we could do this with the help
230
+ of the `name` attribute:
231
+
232
+ ```python
233
+ updater["metadata"].name = "MetaData"
234
+ ```
235
+
236
+ Sometimes it might be useful to inject a new section not in a
237
+ programmatic way but more declarative. Let\'s assume we have thus
238
+ defined our new section in a multi-line string:
239
+
240
+ ```python
241
+ sphinx_sect_str = """
242
+ [build_sphinx]
243
+ source_dir = docs
244
+ build_dir = docs/_build
245
+ """
246
+ ```
247
+
248
+ With the help of two ConfigUpdater objects we can easily inject this
249
+ section into our example:
250
+
251
+ ```python
252
+ sphinx = ConfigUpdater()
253
+ sphinx.read_string(sphinx_sect_str)
254
+ sphinx_sect = sphinx["build_sphinx"]
255
+
256
+ updater = ConfigUpdater()
257
+ updater.read_string(cfg)
258
+
259
+ (updater["metadata"].add_after.space().section(sphinx_sect.detach()))
260
+ ```
261
+
262
+ The `detach` method will remove the `build_sphinx` section from the
263
+ first object and add it to the second object. This results in:
264
+
265
+ ```ini
266
+ [metadata]
267
+ author = Ada Lovelace
268
+ summary = The Analytical Engine
269
+
270
+ [build_sphinx]
271
+ source_dir = docs
272
+ build_dir = docs/_build
273
+ ```
274
+
275
+ Alternatively, if you want to preserve `build_sphinx` in both
276
+ `ConfigUpdater` objects (i.e., prevent it from being removed from the
277
+ first while still adding a copy to the second), you call also rely on
278
+ stdlib\'s `copy.deepcopy` function instead of `detach`:
279
+
280
+ ```python
281
+ from copy import deepcopy
282
+
283
+ sphinx = ConfigUpdater()
284
+ sphinx.read_string(sphinx_sect_str)
285
+ sphinx_sect = sphinx["build_sphinx"]
286
+
287
+ updater = ConfigUpdater()
288
+ updater.read_string(cfg)
289
+
290
+ (updater["metadata"].add_after.space().section(deepcopy(sphinx_sect)))
291
+ ```
292
+
293
+ This technique can be used for all objects inside ConfigUpdater:
294
+ sections, options, comments and blank spaces.
295
+
296
+ Shallow copies are discouraged in the context of ConfigUpdater because
297
+ each configuration block keeps a reference to its container to allow
298
+ easy document editing. When doing editions (such as adding or changing
299
+ options and comments) based on a shallow copy, the results can be
300
+ unreliable and unexpected.
301
+
302
+ For more examples on how the API of ConfigUpdater works it\'s best to
303
+ take a look into the [unit tests] and read the references.
304
+
305
+ # Notes
306
+
307
+ ConfigUpdater started out as a helper for [PyScaffold], which still uses
308
+ it to update the `setup.cfg` of existing projects. Since then it has
309
+ become a useful tool in its own right for everyone who needs to change
310
+ configuration files programmatically without losing the comments and
311
+ formatting that humans put into them.
312
+
313
+ [ConfigUpdater]: https://configupdater.readthedocs.io/
314
+ [ConfigParser]: https://docs.python.org/3/library/configparser.html
315
+ [PyScaffold]: https://pyscaffold.org/
316
+ [uv]: https://docs.astral.sh/uv/
317
+ [module reference]: https://configupdater.readthedocs.io/en/latest/api.html#configupdater.configupdater.ConfigUpdater
318
+ [unit tests]: https://github.com/pyscaffold/configupdater/blob/main/tests/test_configupdater.py
319
+
320
+ [Tests-image]: https://github.com/pyscaffold/configupdater/actions/workflows/ci.yml/badge.svg
321
+ [Tests-link]: https://github.com/pyscaffold/configupdater/actions/workflows/ci.yml
322
+ [Coverage-image]: https://img.shields.io/coveralls/github/pyscaffold/configupdater/main.svg
323
+ [Coverage-link]: https://coveralls.io/r/pyscaffold/configupdater
324
+ [Publish-image]: https://github.com/pyscaffold/configupdater/actions/workflows/publish-package.yml/badge.svg
325
+ [Publish-link]: https://github.com/pyscaffold/configupdater/actions/workflows/publish-package.yml
326
+ [PyPI_ver-image]:https://img.shields.io/pypi/v/configupdater.svg?logo=pypi&label=PyPI&logoColor=gold
327
+ [PyPI_ver-link]: https://pypi.org/project/configupdater/
328
+ [Conda-image]: https://img.shields.io/conda/vn/conda-forge/configupdater.svg?logo=Anaconda&label=Conda-Forge&logoColor=44A833
329
+ [Conda-link]: https://anaconda.org/conda-forge/configupdater/
330
+ [PyPI_down-image]: https://img.shields.io/pypi/dm/configupdater.svg?color=blue&label=Downloads&logo=pypi&logoColor=gold
331
+ [PyPI_down-link]: https://clickpy.clickhouse.com/dashboard/configupdater
332
+ [PyPI_py-image]: https://img.shields.io/pypi/pyversions/configupdater.svg?logo=python&label=Python&logoColor=gold
333
+ [PyPI_py-link]: https://pypi.org/project/configupdater/
334
+ [uv-image]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json
335
+ [uv-link]: https://github.com/astral-sh/uv
336
+ [ruff-image]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
337
+ [ruff-link]: https://github.com/charliermarsh/ruff
338
+ [mypy-image]: https://img.shields.io/badge/Types-mypy-blue.svg
339
+ [mypy-link]: https://mypy-lang.org/
340
+ [pytest-image]: https://img.shields.io/static/v1?label=‎&message=Pytest&logo=Pytest&color=0A9EDC&logoColor=white
341
+ [pytest-link]: https://docs.pytest.org/
342
+ [rtd-image]: https://readthedocs.org/projects/configupdater/badge/?version=latest
343
+ [rtd-link]: https://configupdater.readthedocs.io/
344
+ [precommit-image]: https://img.shields.io/static/v1?label=‎&message=pre-commit&logo=pre-commit&color=76877c
345
+ [precommit-link]: https://pre-commit.com/
346
+ [MIT-image]: https://img.shields.io/badge/License-MIT-9400d3.svg
347
+ [MIT-link]: LICENSE.txt
348
+ [sponsor-image]: https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=ff69b4
349
+ [sponsor-link]: https://github.com/sponsors/FlorianWilhelm