{"id":18868453,"url":"https://github.com/pyscaffold/configupdater","last_synced_at":"2025-04-08T10:25:18.774Z","repository":{"id":32910841,"uuid":"137461722","full_name":"pyscaffold/configupdater","owner":"pyscaffold","description":"🌴 Parser like ConfigParser but for updating configuration files","archived":false,"fork":false,"pushed_at":"2025-03-24T16:54:38.000Z","size":529,"stargazers_count":70,"open_issues_count":8,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-01T09:27:02.961Z","etag":null,"topics":["configuration-files","python","updating"],"latest_commit_sha":null,"homepage":"https://configupdater.readthedocs.io/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyscaffold.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.rst","contributing":"CONTRIBUTING.rst","funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.rst","dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"FlorianWilhelm","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":"https://www.paypal.me/FlorianWilhelm"}},"created_at":"2018-06-15T08:27:36.000Z","updated_at":"2025-03-26T04:34:16.000Z","dependencies_parsed_at":"2023-02-16T10:15:45.066Z","dependency_job_id":"30c5a647-4d9f-4e44-9d53-39b002a92e78","html_url":"https://github.com/pyscaffold/configupdater","commit_stats":{"total_commits":324,"total_committers":12,"mean_commits":27.0,"dds":0.6419753086419753,"last_synced_commit":"0da52c6e90b55f85eecd2efa2c37af02cd6160a3"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyscaffold%2Fconfigupdater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyscaffold%2Fconfigupdater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyscaffold%2Fconfigupdater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyscaffold%2Fconfigupdater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyscaffold","download_url":"https://codeload.github.com/pyscaffold/configupdater/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247821833,"owners_count":21001742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration-files","python","updating"],"created_at":"2024-11-08T05:13:41.109Z","updated_at":"2025-04-08T10:25:18.727Z","avatar_url":"https://github.com/pyscaffold.png","language":"Python","funding_links":["https://github.com/sponsors/FlorianWilhelm","https://www.paypal.me/FlorianWilhelm"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/pyscaffold/configupdater/master/docs/gfx/banner-640x323.png\" alt=\"Configupdater logo\" width=\"500\" role=\"img\"\u003e\n\u003c/div\u003e\n\u003cbr/\u003e\n\n|         |                                    |\n|---------|------------------------------------|\n| CI/CD   | [![Tests][Tests-image]][Tests-link] [![Coverage][Coverage-image]][Coverage-link] [![Publish Package][Publish-image]][Publish-link] [![GitHub Sponsors][sponsor-image]][sponsor-link]  |\n| 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] |\n| Details | [![Setuptools project][setuptools-image]][setuptools-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] |\n\nThe sole purpose of [ConfigUpdater] is to easily\nupdate an INI config file with no changes to the original file except\nthe intended ones. This means comments, the ordering of sections and\nkey/value-pairs as wells as their cases are kept as in the original\nfile. Thus ConfigUpdater provides complementary functionality to\nPython\\'s [ConfigParser], which is primarily meant for reading config\nfiles and writing *new* ones.\n\n# Features\n\nThe key differences to [ConfigParser] are:\n\n- minimal invasive changes in the update configuration file,\n- proper handling of comments,\n- only a single config file can be updated at a time,\n- the original case of sections and keys are kept,\n- control over the position of a new section/key\n\nFollowing features are **deliberately not** implemented:\n\n- interpolation of values,\n- propagation of parameters from the default section,\n- conversions of values,\n- passing key/value-pairs with `default` argument,\n- non-strict mode allowing duplicate sections and keys.\n\n# Usage\n\nFirst install the package with either:\n\n```console\npip install configupdater\n```\n\nor:\n\n```console\nconda install -c conda-forge configupdater\n```\n\nNow we can simply do:\n\n```python\nfrom configupdater import ConfigUpdater\n\nupdater = ConfigUpdater()\nupdater.read(\"setup.cfg\")\n```\n\nwhich would read the file `setup.cfg` that is found in many projects.\n\nTo change the value of an existing key we can simply do:\n\n```python\nupdater[\"metadata\"][\"author\"].value = \"Alan Turing\"\n```\n\nAt any point we can print the current state of the configuration file\nwith:\n\n```python\nprint(updater)\n```\n\nTo update the read-in file just call `updater.update_file()` or\n`updater.write(open('filename','w'))` to write the changed configuration\nfile to another destination. Before actually writing, ConfigUpdater will\nautomatically check that the updated configuration file is still valid\nby parsing it with the help of ConfigParser.\n\nMany of ConfigParser\\'s methods still exists and it\\'s best to look them\nup in the [module reference]. Let\\'s look at some examples.\n\n## Adding and removing options\n\nLet\\'s say we have the following configuration in a string:\n\n```python\ncfg = \"\"\"\n[metadata]\nauthor = Ada Lovelace\nsummary = The Analytical Engine\n\"\"\"\n```\n\nWe can add an *license* option, i.e. a key/value pair, in the same way\nwe would do with ConfigParser:\n\n```python\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\nupdater[\"metadata\"][\"license\"] = \"MIT\"\n```\n\nA simple `print(updater)` will give show you that the new option was\nappended to the end:\n\n```ini\n[metadata]\nauthor = Ada Lovelace\nsummary = The Analytical Engine\nlicense = MIT\n```\n\nSince the license is really important to us let\\'s say we want to add it\nbefore the `summary` and even add a short comment before it:\n\n```python\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\n(\n    updater[\"metadata\"][\"summary\"]\n    .add_before.comment(\"Ada would have loved MIT\")\n    .option(\"license\", \"MIT\")\n)\n```\n\nwhich would result in:\n\n```ini\n[metadata]\nauthor = Ada Lovelace\n# Ada would have loved MIT\nlicense = MIT\nsummary = Analytical Engine calculating the Bernoulli numbers\n```\n\nUsing `add_after` would give the same result and looks like:\n\n```python\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\n(\n    updater[\"metadata\"][\"author\"]\n    .add_after.comment(\"Ada would have loved MIT\")\n    .option(\"license\", \"MIT\")\n)\n```\n\nLet\\'s say we want to rename [summary]{.title-ref} to the more common\n\\`description\\`:\n\n```python\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\nupdater[\"metadata\"][\"summary\"].key = \"description\"\n```\n\nIf we wanted no summary at all, we could just do\n`del updater[\"metadata\"][\"summary\"]`.\n\n## Adding and removing sections\n\nAdding and remove sections just works like adding and removing options\nbut on a higher level. Sticking to our *Ada Lovelace* example, let\\'s\nsay we want to add a section `options` just before `metadata` with a\ncomment and two new lines to separate it from `metadata`:\n\n```python\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\n(\n    updater[\"metadata\"]\n    .add_before.section(\"options\")\n    .comment(\"Some specific project options\")\n    .space(2)\n)\n```\n\nAs expected, this results in:\n\n```ini\n[options]\n# Some specific project options\n\n\n[metadata]\nauthor = Ada Lovelace\nsummary = The Analytical Engine\n```\n\nWe could now fill the new section with options like we learnt before. If\nwe wanted to rename an existing section we could do this with the help\nof the `name` attribute:\n\n```python\nupdater[\"metadata\"].name = \"MetaData\"\n```\n\nSometimes it might be useful to inject a new section not in a\nprogrammatic way but more declarative. Let\\'s assume we have thus\ndefined our new section in a multi-line string:\n\n```python\nsphinx_sect_str = \"\"\"\n[build_sphinx]\nsource_dir = docs\nbuild_dir = docs/_build\n\"\"\"\n```\n\nWith the help of two ConfigUpdater objects we can easily inject this\nsection into our example:\n\n```python\nsphinx = ConfigUpdater()\nsphinx.read_string(sphinx_sect_str)\nsphinx_sect = sphinx[\"build_sphinx\"]\n\nupdater = ConfigUpdater()\nupdater.read_string(cfg)\n\n(updater[\"metadata\"].add_after.space().section(sphinx_sect.detach()))\n```\n\nThe `detach` method will remove the `build_sphinx` section from the\nfirst object and add it to the second object. This results in:\n\n```ini\n[metadata]\nauthor = Ada Lovelace\nsummary = The Analytical Engine\n\n[build_sphinx]\nsource_dir = docs\nbuild_dir = docs/_build\n```\n\nAlternatively, if you want to preserve `build_sphinx` in both\n`ConfigUpdater` objects (i.e., prevent it from being removed from the\nfirst while still adding a copy to the second), you call also rely on\nstdlib\\'s `copy.deepcopy` function instead of `detach`:\n\n```python\nfrom copy import deepcopy\n\n(updater[\"metadata\"].add_after.space().section(deepcopy(sphinx_sect)))\n```\n\nThis technique can be used for all objects inside ConfigUpdater:\nsections, options, comments and blank spaces.\n\nShallow copies are discouraged in the context of ConfigUpdater because\neach configuration block keeps a reference to its container to allow\neasy document editing. When doing editions (such as adding or changing\noptions and comments) based on a shallow copy, the results can be\nunreliable and unexpected.\n\nFor more examples on how the API of ConfigUpdater works it\\'s best to\ntake a look into the [unit tests] and read the references.\n\n# Notes\n\nConfigUpdater was mainly developed for [PyScaffold].\n\n[ConfigUpdater]: https://configupdater.readthedocs.io/\n[ConfigParser]: https://docs.python.org/3/library/configparser.html\n[PyScaffold]: https://pyscaffold.org/\n[module reference]: https://configupdater.readthedocs.io/en/latest/api.html#configupdater.configupdater.ConfigUpdater\n[unit tests]: https://github.com/pyscaffold/configupdater/blob/main/tests/test_configupdater.py\n\n[Tests-image]: https://api.cirrus-ci.com/github/pyscaffold/configupdater.svg?branch=main\n[Tests-link]: https://cirrus-ci.com/github/pyscaffold/configupdater\n[Coverage-image]: https://img.shields.io/coveralls/github/pyscaffold/configupdater/main.svg\n[Coverage-link]: https://coveralls.io/r/pyscaffold/configupdater\n[Publish-image]: https://github.com/pyscaffold/configupdater/actions/workflows/publish-package.yml/badge.svg\n[Publish-link]: https://github.com/pyscaffold/configupdater/actions/workflows/publish-package.yml\n[PyPI_ver-image]:https://img.shields.io/pypi/v/configupdater.svg?logo=pypi\u0026label=PyPI\u0026logoColor=gold\n[PyPI_ver-link]: https://pypi.org/project/configupdater/\n[Conda-image]: https://img.shields.io/conda/vn/conda-forge/configupdater.svg?logo=Anaconda\u0026label=Conda-Forge\u0026logoColor=44A833\n[Conda-link]: https://anaconda.org/conda-forge/configupdater/\n[PyPI_down-image]: https://img.shields.io/pypi/dm/configupdater.svg?color=blue\u0026label=Downloads\u0026logo=pypi\u0026logoColor=gold\n[PyPI_down-link]: https://pepy.tech/project/configupdater\n[PyPI_py-image]: https://img.shields.io/pypi/pyversions/configupdater.svg?logo=python\u0026label=Python\u0026logoColor=gold\n[PyPI_py-link]: https://pypi.org/project/configupdater/\n[setuptools-image]: https://img.shields.io/badge/-setuptools-E5B62F?logo=python\n[setuptools-link]: https://github.com/pypa/setuptools\n[ruff-image]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\n[ruff-link]: https://github.com/charliermarsh/ruff\n[mypy-image]: https://img.shields.io/badge/Types-mypy-blue.svg\n[mypy-link]: https://mypy-lang.org/\n[pytest-image]: https://img.shields.io/static/v1?label=‎\u0026message=Pytest\u0026logo=Pytest\u0026color=0A9EDC\u0026logoColor=white\n[pytest-link]:  https://docs.pytest.org/\n[rtd-image]: https://readthedocs.org/projects/pyscaffold/badge/?version=latest\n[rtd-link]: https://configupdater.readthedocs.io/\n[precommit-image]: https://img.shields.io/static/v1?label=‎\u0026message=pre-commit\u0026logo=pre-commit\u0026color=76877c\n[precommit-link]: https://pre-commit.com/\n[MIT-image]: https://img.shields.io/badge/License-MIT-9400d3.svg\n[MIT-link]: LICENSE.txt\n[sponsor-image]: https://img.shields.io/static/v1?label=Sponsor\u0026message=%E2%9D%A4\u0026logo=GitHub\u0026color=ff69b4\n[sponsor-link]: https://github.com/sponsors/FlorianWilhelm\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyscaffold%2Fconfigupdater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyscaffold%2Fconfigupdater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyscaffold%2Fconfigupdater/lists"}