├── tests ├── __init__.py ├── victim │ ├── pytest.ini │ ├── mypy.ini │ └── setup.cfg ├── test_cli.py └── __snapshots__ │ └── test_cli.ambr ├── pyproject_migrator ├── __about__.py ├── __init__.py ├── __main__.py ├── cli.py ├── result.py └── convert.py ├── .gitignore ├── .pre-commit-config.yaml ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── LICENSE.txt ├── pyproject.toml └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyproject_migrator/__about__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1.1" 2 | -------------------------------------------------------------------------------- /pyproject_migrator/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger("pyproject-migrator") 4 | -------------------------------------------------------------------------------- /pyproject_migrator/__main__.py: -------------------------------------------------------------------------------- 1 | from pyproject_migrator.cli import cli 2 | 3 | if __name__ == "__main__": # pragma: no cover 4 | cli() 5 | -------------------------------------------------------------------------------- /tests/victim/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = 3 | ignore:.*imp module is deprecated.*:DeprecationWarning 4 | env = 5 | AWS_ACCESS_KEY_ID=foobar_key 6 | AWS_SECRET_ACCESS_KEY=foobar_secret 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info/ 3 | *.log 4 | *.manifest 5 | *.mo 6 | *.pot 7 | *.py[cod] 8 | *.so 9 | *.spec 10 | *cache 11 | .coverage 12 | .idea/ 13 | .tox/ 14 | __pycache__/ 15 | build/ 16 | dist/ 17 | eggs/ 18 | env/ 19 | htmlcov/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | pip-delete-this-directory.txt 24 | pip-log.txt 25 | sdist/ 26 | venv* 27 | -------------------------------------------------------------------------------- /tests/victim/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version: 3.5 3 | check_untyped_defs = False 4 | no_implicit_optional = True 5 | warn_unused_configs = True 6 | warn_redundant_casts = False 7 | warn_unused_ignores = False 8 | warn_no_return = True 9 | warn_return_any = True 10 | warn_unreachable = True 11 | implicit_reexport = False 12 | strict_equality = True 13 | show_error_context = True 14 | show_column_numbers = True 15 | show_error_codes = True 16 | pretty = True 17 | 18 | [mypy-colors] 19 | ignore_missing_imports = True 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: debug-statements 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.8.6 10 | hooks: 11 | - id: ruff 12 | args: 13 | - --fix 14 | - id: ruff-format 15 | - repo: https://github.com/abravalheri/validate-pyproject 16 | rev: v0.20.2 17 | hooks: 18 | - id: validate-pyproject 19 | -------------------------------------------------------------------------------- /tests/victim/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = victim 3 | 4 | [options] 5 | include_package_data = True 6 | python_requires = >=3.6 7 | 8 | [flake8] 9 | ignore = E741 10 | max-line-length = 119 11 | max-complexity = 10 12 | ban-relative-imports = all 13 | 14 | [isort] 15 | profile = black 16 | multi_line_output = 3 17 | 18 | [tool:pytest] 19 | DJANGO_SETTINGS_MODULE = my_test_settings.settings 20 | norecursedirs = bower_components node_modules foo bar 21 | doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ALLOW_UNICODE 22 | filterwarnings = 23 | error 24 | default:Exception ignored.*FileIO.* 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Keep GitHub Actions up to date with GitHub's Dependabot... 2 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 3 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | groups: 9 | github-actions: 10 | patterns: 11 | - "*" # Group all Actions updates into a single larger pull request 12 | schedule: 13 | interval: weekly 14 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from pyproject_migrator.cli import cli 4 | 5 | import tomlkit 6 | 7 | victim_path = Path(__file__).parent / "victim" 8 | 9 | 10 | def test_cli(monkeypatch, capsys, caplog, snapshot): 11 | caplog.set_level(logging.INFO) 12 | monkeypatch.setattr("sys.argv", ["tool", str(victim_path)]) 13 | cli() 14 | # Snip out pathnames for snapshot comparison... 15 | messages = sorted(m.partition(": ")[-1] for m in caplog.messages) 16 | assert messages == snapshot(name="log") 17 | assert "setuptools sections ['metadata', 'options']" in caplog.text 18 | assert "flake8-related sections ['flake8']" in caplog.text 19 | parsed = tomlkit.loads(capsys.readouterr()[0]) 20 | assert parsed == snapshot(name="result") 21 | -------------------------------------------------------------------------------- /pyproject_migrator/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import sys 4 | from pathlib import Path 5 | 6 | import tomlkit 7 | 8 | from pyproject_migrator.convert import CONFIG_FILES, process_file 9 | from pyproject_migrator.result import Result 10 | 11 | 12 | def parse_args(): 13 | ap = argparse.ArgumentParser() 14 | ap.add_argument("src", nargs="+", help="Source files/directories") 15 | return ap.parse_args() 16 | 17 | 18 | def cli() -> None: 19 | logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") 20 | args = parse_args() 21 | res = Result() 22 | for src in args.src: 23 | pth = Path(src) 24 | if pth.is_dir(): 25 | for config_file_name in sorted(CONFIG_FILES): 26 | config_file = pth / config_file_name 27 | if config_file.exists(): 28 | process_file(res, config_file) 29 | elif pth.is_file(): 30 | process_file(res, pth) 31 | tomlkit.dump(res.dest, sys.stdout) 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Aarni Koskela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | tags: 10 | - v* 11 | jobs: 12 | test: 13 | strategy: 14 | matrix: 15 | python-version: ["3.9", "3.13"] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | cache: pip 23 | cache-dependency-path: pyproject.toml 24 | - run: pip install hatch 25 | - run: hatch build 26 | - if: matrix.python-version == 3.9 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: dist 30 | path: dist/ 31 | - run: hatch run cov 32 | - uses: codecov/codecov-action@v5 33 | lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: pre-commit/action@v3.0.1 38 | publish: 39 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 40 | needs: 41 | - test 42 | name: Upload release to PyPI 43 | runs-on: ubuntu-latest 44 | environment: 45 | name: release 46 | url: https://pypi.org/p/pyproject-migrator/ 47 | permissions: 48 | id-token: write 49 | steps: 50 | - uses: actions/download-artifact@v4 51 | with: 52 | name: dist 53 | path: dist/ 54 | - name: Publish package distributions to PyPI 55 | uses: pypa/gh-action-pypi-publish@release/v1 56 | with: 57 | verbose: true 58 | print-hash: true 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "pyproject-migrator" 7 | description = 'Convert setup.cfg, etc. to pyproject.toml files' 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | license = { text = "MIT" } 11 | keywords = [] 12 | authors = [ 13 | { name = "Aarni Koskela", email = "akx@iki.fi" }, 14 | ] 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Programming Language :: Python :: Implementation :: CPython", 24 | "Programming Language :: Python :: Implementation :: PyPy", 25 | ] 26 | dependencies = [ 27 | "tomlkit~=0.11.0", 28 | ] 29 | dynamic = ["version"] 30 | 31 | [project.scripts] 32 | pyproject-migrator = "pyproject_migrator.cli:cli" 33 | 34 | [project.urls] 35 | Documentation = "https://github.com/akx/pyproject-migrator#readme" 36 | Issues = "https://github.com/akx/pyproject-migrator/issues" 37 | Source = "https://github.com/akx/pyproject-migrator" 38 | 39 | [tool.hatch.version] 40 | path = "pyproject_migrator/__about__.py" 41 | 42 | [tool.hatch.envs.default] 43 | dependencies = [ 44 | "pytest", 45 | "pytest-cov", 46 | "syrupy", 47 | ] 48 | [tool.hatch.envs.default.scripts] 49 | cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=pyproject_migrator --cov=tests {args}" 50 | no-cov = "cov --no-cov {args}" 51 | 52 | [[tool.hatch.envs.test.matrix]] 53 | python = ["37", "38", "39", "310", "311"] 54 | 55 | [tool.coverage.run] 56 | branch = true 57 | parallel = true 58 | omit = [ 59 | "pyproject_migrator/__about__.py", 60 | ] 61 | 62 | [tool.coverage.report] 63 | exclude_lines = [ 64 | "no cov", 65 | "if __name__ == .__main__.:", 66 | "if TYPE_CHECKING:", 67 | ] 68 | 69 | [tool.ruff] 70 | ignore = [ 71 | "E501", 72 | ] 73 | -------------------------------------------------------------------------------- /pyproject_migrator/result.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | from pathlib import Path 3 | 4 | from pyproject_migrator import log 5 | 6 | 7 | @dataclasses.dataclass 8 | class Result: 9 | dest: dict = dataclasses.field(default_factory=dict) 10 | warnings: list[str] = dataclasses.field(default_factory=list) 11 | 12 | def warn(self, msg: str, path: Path) -> None: 13 | msg = f"{path}: {msg}" 14 | self.warnings.append(msg) 15 | log.warning(msg) 16 | 17 | def assign(self, key: str, value: object) -> None: 18 | dest, final_key = self._find_dest(key) 19 | if final_key in dest: 20 | raise ValueError(f"Key {key} already exists") 21 | dest[final_key] = value 22 | 23 | def append(self, key: str, value: object) -> None: 24 | dest, final_key = self._find_dest(key) 25 | dest.setdefault(final_key, []).append(value) 26 | 27 | def merge(self, t_key: str, vals: dict, *, path: Path) -> None: 28 | dest, final_key = self._find_dest(t_key) 29 | dest = dest.setdefault(final_key, {}) 30 | for key, value in vals.items(): 31 | if isinstance(value, list): 32 | if key in dest: 33 | self.warn( 34 | f"Appending values in {t_key}->{key}; make sure the order is correct", 35 | path, 36 | ) 37 | dest.setdefault(key, []).extend(value) 38 | elif isinstance(value, dict): 39 | dest.setdefault(key, {}).update(value) 40 | else: 41 | if key in dest: 42 | self.warn( 43 | f"Overwriting {t_key}->{key} (previously {dest[key]!r}) with {value!r}", 44 | path, 45 | ) 46 | dest[key] = value 47 | 48 | def _find_dest(self, key: str) -> tuple[dict, str]: 49 | dest = self.dest 50 | bits = key.split(".") 51 | final_key = bits.pop() 52 | for bit in bits: 53 | dest = dest.setdefault(bit, {}) 54 | return dest, final_key 55 | -------------------------------------------------------------------------------- /tests/__snapshots__/test_cli.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_cli[log] 3 | list([ 4 | 'Appending values in tool.pytest.ini_options->filterwarnings; make sure the order is correct', 5 | "flake8-related sections ['flake8']; porting depends on which toolset to use. If you're transitioning to ruff, see https://pypi.org/project/flake8-to-ruff/", 6 | "setuptools sections ['metadata', 'options']; porting depends on which toolset to use. For example, hatch (https://pypi.org/project/hatch/) can port metadata to pyproject.toml.", 7 | ]) 8 | # --- 9 | # name: test_cli[result] 10 | TOMLDocument({ 11 | 'tool': Table({ 12 | 'isort': Table({ 13 | 'multi_line_output': 3, 14 | 'profile': 'black', 15 | }), 16 | 'mypy': Table({ 17 | 'check_untyped_defs': False, 18 | 'implicit_reexport': False, 19 | 'no_implicit_optional': True, 20 | 'overrides': AoT([ 21 | Table({ 22 | 'ignore_missing_imports': True, 23 | 'module': 'colors', 24 | }), 25 | ]), 26 | 'pretty': True, 27 | 'python_version': '3.5', 28 | 'show_column_numbers': True, 29 | 'show_error_codes': True, 30 | 'show_error_context': True, 31 | 'strict_equality': True, 32 | 'warn_no_return': True, 33 | 'warn_redundant_casts': False, 34 | 'warn_return_any': True, 35 | 'warn_unreachable': True, 36 | 'warn_unused_configs': True, 37 | 'warn_unused_ignores': False, 38 | }), 39 | 'pytest': Table({ 40 | 'ini_options': Table({ 41 | 'DJANGO_SETTINGS_MODULE': 'my_test_settings.settings', 42 | 'doctest_optionflags': Array([ 43 | 'NORMALIZE_WHITESPACE', 44 | 'IGNORE_EXCEPTION_DETAIL', 45 | 'ALLOW_UNICODE', 46 | ]), 47 | 'env': Array([ 48 | 'AWS_ACCESS_KEY_ID=foobar_key', 49 | 'AWS_SECRET_ACCESS_KEY=foobar_secret', 50 | ]), 51 | 'filterwarnings': Array([ 52 | 'ignore:.*imp module is deprecated.*:DeprecationWarning', 53 | 'error', 54 | 'default:Exception ignored.*FileIO.*', 55 | ]), 56 | 'norecursedirs': Array([ 57 | 'bower_components', 58 | 'node_modules', 59 | 'foo', 60 | 'bar', 61 | ]), 62 | }), 63 | }), 64 | }), 65 | }) 66 | # --- 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyproject-migrator 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/pyproject-migrator.svg)](https://pypi.org/project/pyproject-migrator) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyproject-migrator.svg)](https://pypi.org/project/pyproject-migrator) 5 | 6 | ----- 7 | 8 | ## What is this? 9 | 10 | This tool helps with converting `setup.cfg` (and other configuration files such as `mypy.ini`) 11 | to a single `pyproject.toml` file. 12 | 13 | 14 | ## Installation 15 | 16 | You can install the project from Pip with 17 | 18 | ```console 19 | pip install pyproject-migrator 20 | ``` 21 | 22 | but it may be more useful to use [`pipx`][pipx] to run it as a tool; if you prefer to do that, just substitute 23 | `pipx run pyproject-migrator` for `pyproject-migrator` in the examples below. 24 | 25 | ## Usage 26 | 27 | The tool can be run against a number of files or directories, but these files are considered to be part of 28 | the same project. The tool will then attempt to merge these into a single `pyproject.toml` file fragment. 29 | 30 | ```console 31 | $ pyproject-migrator setup.cfg mypy.ini 32 | ``` 33 | or 34 | ```console 35 | $ pyproject-migrator . 36 | ``` 37 | 38 | The tool will output a chunk of TOML you can copy-paste (or `>>` redirect) into your `pyproject.toml` file. 39 | 40 | It may also output a number of warnings about configuration that could not be converted. 41 | 42 | Some of these are because the tool does not yet support the option, but others are because there is no 43 | direct equivalent in the TOML format. In these cases, you will need to manually convert the option. 44 | 45 | ## Supported configuration 46 | 47 | The tool currently supports the following configuration: 48 | 49 | * codespell (in setup.cfg) 50 | * coverage (in setup.cfg) 51 | * isort (in setup.cfg) 52 | * mypy (in setup.cfg and mypy.ini) 53 | * pylint (in setup.cfg) 54 | * pytest (in setup.cfg and pytest.ini) 55 | 56 | Explicitly unsupported is 57 | 58 | * flake8 (because it [currently explicitly does not support pyproject.toml][flake8-234]) 59 | * setuptools (because there are a number of approaches to take to map it into pyproject.toml) 60 | * Sphinx's `build_sphinx` section 61 | * tox (because there is no other TOML mapping than splatting INI config in there, ew) 62 | 63 | Other tools that emit "Unknown section" currently include, 64 | but are not limited to (based on the setup.cfgs I had at hand): 65 | 66 | * babel (extract_messages, extractors, mappings) (see https://github.com/python-babel/babel/issues/777) 67 | * bumpversion 68 | * nosetests 69 | * pbr 70 | * prequ 71 | * pyscaffold 72 | * versioneer 73 | * vpip 74 | * zest.releaser 75 | 76 | ## License 77 | 78 | `pyproject-migrator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 79 | 80 | [pipx]: https://pypa.github.io/pipx/ 81 | [flake8-234]: https://github.com/PyCQA/flake8/issues/234 82 | -------------------------------------------------------------------------------- /pyproject_migrator/convert.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import re 3 | from pathlib import Path 4 | 5 | from pyproject_migrator.result import Result 6 | 7 | PYTEST_WS_SPLIT_KEYS = {"addopts", "norecursedirs", "doctest_optionflags"} 8 | 9 | CONFIG_FILES = { 10 | "mypy.ini", 11 | "pytest.ini", 12 | "setup.cfg", 13 | } 14 | 15 | 16 | def massage_value(value, key, *, ws_split_keys): 17 | if key in ws_split_keys: 18 | return [v for v in value.split() if v] 19 | if isinstance(value, str): 20 | if "\n" in value: 21 | return [v for v in value.splitlines() if v] 22 | if value.lower() == "true": 23 | return True 24 | if value.lower() == "false": 25 | return False 26 | if value.isnumeric(): 27 | return int(value) 28 | return value 29 | 30 | 31 | def translate_config(config: dict, *, ws_split_keys=()) -> dict: 32 | return { 33 | key: massage_value(value, key, ws_split_keys=ws_split_keys) 34 | for key, value in config.items() 35 | } 36 | 37 | 38 | FLAKE8_SECTION_RE = re.compile(r"flake8.*|pep8|py(code|doc)style|pep257") 39 | TOX_SECTION_RE = re.compile(r"testenv.*|tox.*") 40 | SETUPTOOLS_SECTION_RE = re.compile( 41 | r"[bs]dist.*|" 42 | r"aliases|" 43 | r"build_ext|" 44 | r"egg_info|" 45 | r"entry_points|" 46 | r"extras|" 47 | r"files|" 48 | r"install|" 49 | r"metadata|" 50 | r"options.*|" 51 | r"paths|" 52 | r"upload_docs|" 53 | r"wheel" 54 | ) 55 | 56 | 57 | def process_config_file(res: Result, pth: Path): 58 | config = configparser.ConfigParser() 59 | config.optionxform = str 60 | config.read(pth) 61 | setuptools_sections = {} 62 | flake8_sections = {} 63 | tox_sections = {} 64 | 65 | for section in config.sections(): 66 | if section == "mypy": 67 | res.assign("tool.mypy", translate_config(config[section])) 68 | continue 69 | 70 | if section.startswith("mypy-"): 71 | res.append( 72 | "tool.mypy.overrides", 73 | { 74 | "module": section[5:], 75 | **translate_config(config[section]), 76 | }, 77 | ) 78 | continue 79 | 80 | if section.endswith("pytest"): 81 | translated = translate_config( 82 | config[section], 83 | ws_split_keys=PYTEST_WS_SPLIT_KEYS, 84 | ) 85 | res.merge("tool.pytest.ini_options", translated, path=pth) 86 | continue 87 | if section.endswith("isort"): 88 | translated = translate_config( 89 | config[section], 90 | ws_split_keys={"skip"}, 91 | ) 92 | res.assign("tool.isort", translated) 93 | continue 94 | 95 | if section.startswith("coverage"): 96 | res.assign( 97 | f"tool.{section.replace(':', '.')}", translate_config(config[section]) 98 | ) 99 | continue 100 | 101 | if section.startswith("pylint"): 102 | res.assign( 103 | f"tool.{section.replace(':', '.')}", translate_config(config[section]) 104 | ) 105 | continue 106 | 107 | if section == "codespell": 108 | res.assign("tool.codespell", translate_config(config[section])) 109 | continue 110 | 111 | if section == "build_sphinx": 112 | res.warn( 113 | "The build_sphinx section is not supported, " 114 | "and support will be removed with Sphinx 7 anyway. " 115 | "See https://www.sphinx-doc.org/en/master/usage/advanced/setuptools.html", 116 | pth, 117 | ) 118 | continue 119 | 120 | if TOX_SECTION_RE.match(section): 121 | tox_sections[section] = config[section] 122 | continue 123 | 124 | if FLAKE8_SECTION_RE.match(section): 125 | flake8_sections[section] = config[section] 126 | continue 127 | 128 | if SETUPTOOLS_SECTION_RE.match(section): 129 | setuptools_sections[section] = config[section] 130 | continue 131 | 132 | res.warn(f"unknown section {section}", path=pth) 133 | 134 | if setuptools_sections: 135 | # TODO: we could allow setting a target toolset and port these. 136 | res.warn( 137 | f"setuptools sections {sorted(setuptools_sections.keys())}; porting depends on which toolset to use. " 138 | "For example, hatch (https://pypi.org/project/hatch/) can port metadata to pyproject.toml.", 139 | path=pth, 140 | ) 141 | 142 | if flake8_sections: 143 | res.warn( 144 | f"flake8-related sections {sorted(flake8_sections.keys())}; porting depends on which toolset to use. " 145 | "If you're transitioning to ruff, see https://pypi.org/project/flake8-to-ruff/", 146 | path=pth, 147 | ) 148 | 149 | if tox_sections: 150 | res.warn( 151 | f"tox-related sections {sorted(tox_sections.keys())}; tox does not currently support pyproject.toml " 152 | f"except by way of `legacy_tox_in`; see https://tox.wiki/en/4.3.3/config.html#pyproject-toml", 153 | path=pth, 154 | ) 155 | 156 | 157 | def process_file(res: Result, pth: Path): 158 | if pth.name in CONFIG_FILES: 159 | process_config_file(res, pth) 160 | return 161 | raise NotImplementedError(f"Can't process {pth}") 162 | --------------------------------------------------------------------------------