From 55261c84055f02d0fe1cef4da190ee59ff3fee40 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:56:52 +0200 Subject: [PATCH] FIX: do not convert to `pyproject.toml` in Python 3.6 --- pyproject.toml | 109 +---------------------- setup.cfg | 93 +++++++++++++++++++ src/repoma/check_dev_files/deprecated.py | 22 +++++ src/repoma/check_dev_files/ruff.py | 48 +++++++++- src/repoma/check_dev_files/setup_cfg.py | 98 +++++++++++++++----- src/repoma/utilities/project_info.py | 2 +- tests/utilities/test_cfg.py | 27 +++++- tests/utilities/test_setup_cfg.py | 15 ++++ 8 files changed, 279 insertions(+), 135 deletions(-) create mode 100644 setup.cfg create mode 100644 tests/utilities/test_setup_cfg.py diff --git a/pyproject.toml b/pyproject.toml index 3a207f85..09a5aac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,115 +1,10 @@ [build-system] -build-backend = "setuptools.build_meta" requires = [ - "setuptools>=58.0", + "setuptools>=36.2.1", # environment markers "setuptools_scm", + "wheel", ] -[project] -authors = [ - {name = "Common Partial Wave Analysis", email = "compwa-admin@ep1.rub.de"}, -] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python", - "Typing :: Typed", -] -dependencies = [ - "PyYAML", - "attrs >=20.1.0", # https://www.attrs.org/en/stable/changelog.html#id82 - "html2text", - "ini2toml", - "nbformat", - "pip-tools", - "pre-commit", - "ruamel.yaml", # better YAML dumping - "tomlkit", -] -description = "Pre-commit hooks that ensure that ComPWA repositories have a similar developer set-up" -dynamic = ["version"] -license = {text = "BSD 3-Clause License"} -maintainers = [{email = "compwa-admin@ep1.rub.de"}] -name = "repo-maintenance" -requires-python = ">=3.6" - -[project.optional-dependencies] -dev = [ - "labels", - "repo-maintenance[sty]", - "repo-maintenance[test]", - "tox >=1.9", # for skip_install, use_develop -] -format = [ - "black", -] -lint = [ - "radon", - "repo-maintenance[mypy]", - 'ruff; python_version >="3.7.0"', -] -mypy = [ - "mypy", - "types-PyYAML", - "types-setuptools", - "types-toml", -] -sty = [ - "pre-commit >=1.4.0", - "repo-maintenance[format]", - "repo-maintenance[lint]", - "repo-maintenance[test]", # for pytest type hints -] -test = [ - "pytest", - "pytest-cov", - "pytest-xdist", -] - -[project.readme] -content-type = "text/markdown" -file = "README.md" - -[project.scripts] -check-dev-files = "repoma.check_dev_files:main" -colab-toc-visible = "repoma.colab_toc_visible:main" -fix-nbformat-version = "repoma.fix_nbformat_version:main" -format-setup-cfg = "repoma.format_setup_cfg:main" -pin-nb-requirements = "repoma.pin_nb_requirements:main" -repoma-self-check = "repoma.self_check:main" -set-nb-cells = "repoma.set_nb_cells:main" - -[project.urls] -Source = "https://github.com/ComPWA/repo-maintenance" -Tracker = "https://github.com/ComPWA/repo-maintenance/issues" - -[tool.setuptools] -include-package-data = false -license-files = ["LICENSE"] -package-dir = {"" = "src"} - -[tool.setuptools.package-data] -repoma = [ - ".github/*", - ".github/**/*", - ".template/*", - ".template/.*", - "py.typed", -] - -[tool.setuptools.packages.find] -namespaces = false -where = ["src"] - [tool.setuptools_scm] write_to = "src/repoma/version.py" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..39be1cf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,93 @@ +[metadata] +name = repo-maintenance +author = Common Partial Wave Analysis +author_email = compwa-admin@ep1.rub.de +maintainer_email = compwa-admin@ep1.rub.de +description = Pre-commit hooks that ensure that ComPWA repositories have a similar developer set-up +long_description = file: README.md +long_description_content_type = text/markdown +project_urls = + Tracker = https://github.com/ComPWA/repo-maintenance/issues + Source = https://github.com/ComPWA/repo-maintenance +license = BSD 3-Clause License +license_files = LICENSE +classifiers = + Development Status :: 3 - Alpha + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Natural Language :: English + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Typing :: Typed + +[options] +python_requires = >=3.6 +setup_requires = + setuptools_scm +install_requires = + attrs >=20.1.0 # https://www.attrs.org/en/stable/changelog.html#id82 + html2text + ini2toml + nbformat + pip-tools + pre-commit + PyYAML + ruamel.yaml # better YAML dumping + tomlkit +packages = find: +package_dir = + =src + +[options.extras_require] +test = + pytest + pytest-cov + pytest-xdist +format = + black +mypy = + mypy + types-PyYAML + types-setuptools + types-toml +lint = + %(mypy)s + radon + ruff; python_version >="3.7.0" +sty = + %(format)s + %(lint)s + %(test)s # for pytest type hints + pre-commit >=1.4.0 +dev = + %(sty)s + %(test)s + labels + tox >=1.9 # for skip_install, use_develop + +[options.entry_points] +console_scripts = + check-dev-files = repoma.check_dev_files:main + colab-toc-visible = repoma.colab_toc_visible:main + fix-nbformat-version = repoma.fix_nbformat_version:main + format-setup-cfg = repoma.format_setup_cfg:main + pin-nb-requirements = repoma.pin_nb_requirements:main + repoma-self-check = repoma.self_check:main + set-nb-cells = repoma.set_nb_cells:main + +[options.packages.find] +where = src + +[options.package_data] +repoma = + .github/* + .github/**/* + .template/* + .template/.* + py.typed diff --git a/src/repoma/check_dev_files/deprecated.py b/src/repoma/check_dev_files/deprecated.py index ecd24070..ce809bff 100644 --- a/src/repoma/check_dev_files/deprecated.py +++ b/src/repoma/check_dev_files/deprecated.py @@ -7,6 +7,7 @@ from repoma.utilities import CONFIG_PATH from repoma.utilities.executor import Executor from repoma.utilities.precommit import remove_precommit_hook +from repoma.utilities.project_info import open_setup_cfg from repoma.utilities.pyproject import load_pyproject, write_pyproject from repoma.utilities.readme import remove_badge from repoma.utilities.vscode import ( @@ -149,6 +150,27 @@ def __remove_file(path: str) -> None: def __uninstall(package: str) -> None: + __uninstall_from_setup_cfg(package) + __uninstall_from_pyproject_toml(package) + + +def __uninstall_from_setup_cfg(package: str) -> None: + if not os.path.exists(CONFIG_PATH.setup_cfg): + return + cfg = open_setup_cfg() + section = "options.extras_require" + if not cfg.has_section(section): + return + for option in cfg[section]: + if not cfg.has_option(section, option): + continue + if package not in cfg.get(section, option): + continue + msg = f'Please remove {package} from the "{section}" section of setup.cfg' + raise PrecommitError(msg) + + +def __uninstall_from_pyproject_toml(package: str) -> None: if not os.path.exists(CONFIG_PATH.pyproject): return pyproject = load_pyproject() diff --git a/src/repoma/check_dev_files/ruff.py b/src/repoma/check_dev_files/ruff.py index b137fe8f..5cf5648e 100644 --- a/src/repoma/check_dev_files/ruff.py +++ b/src/repoma/check_dev_files/ruff.py @@ -2,11 +2,16 @@ import os from copy import deepcopy +from textwrap import dedent from typing import List, Set from ruamel.yaml.comments import CommentedMap from tomlkit.items import Array, Table +from repoma.check_dev_files.setup_cfg import ( + has_pyproject_build_system, + has_setup_cfg_build_system, +) from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH, natural_sorting from repoma.utilities.executor import Executor @@ -17,6 +22,7 @@ from repoma.utilities.project_info import ( get_project_info, get_supported_python_versions, + open_setup_cfg, ) from repoma.utilities.pyproject import ( complies_with_subset, @@ -36,18 +42,56 @@ def main() -> None: add_badge, "[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)", ) + executor(_check_setup_cfg) executor(_update_nbqa_settings) executor(_update_ruff_settings) executor(_update_ruff_per_file_ignores) executor(_update_ruff_pydocstyle_settings) executor(_update_precommit_hook) executor(_update_precommit_nbqa_hook) - executor(_update_setup_cfg) + executor(_update_pyproject) executor(_update_vscode_settings) executor.finalize() -def _update_setup_cfg() -> None: +def _check_setup_cfg() -> None: + if not has_setup_cfg_build_system(): + return + cfg = open_setup_cfg() + extras_require = "options.extras_require" + if not cfg.has_section(extras_require): + msg = f"Please list ruff under a section [{extras_require}] in setup.cfg" + raise PrecommitError(msg) + msg = f"""\ + Section [{extras_require}] in setup.cfg should look like this: + + [{extras_require}] + ... + lint = + ruff + ... + sty = + ... + %(lint)s + ... + dev = + ... + %(sty)s + ... + """ + msg = dedent(msg).strip() + for section in ("dev", "lint", "sty"): + if cfg.has_option(extras_require, section): + continue + raise PrecommitError(msg) + lint_section = cfg.get(extras_require, "lint") + if not any("ruff" in line for line in lint_section.split("\n")): + raise PrecommitError(msg) + + +def _update_pyproject() -> None: + if not has_pyproject_build_system(): + return pyproject = load_pyproject() project_info = get_project_info(pyproject) package = project_info.name diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 89119900..5510f5b8 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -6,7 +6,7 @@ import textwrap from collections import defaultdict from configparser import RawConfigParser -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Tuple, Union import tomlkit from ini2toml.api import Translator @@ -15,10 +15,18 @@ from tomlkit.items import Array, Table from repoma.errors import PrecommitError +from repoma.format_setup_cfg import write_formatted_setup_cfg from repoma.utilities import CONFIG_PATH +from repoma.utilities.cfg import copy_config from repoma.utilities.executor import Executor from repoma.utilities.precommit import remove_precommit_hook -from repoma.utilities.project_info import get_pypi_name, get_supported_python_versions +from repoma.utilities.project_info import ( + ProjectInfo, + get_project_info, + get_pypi_name, + get_supported_python_versions, + open_setup_cfg, +) from repoma.utilities.pyproject import ( get_sub_table, load_pyproject, @@ -40,6 +48,8 @@ def main(ignore_author: bool) -> None: def _convert_to_pyproject() -> None: + if "3.6" in get_supported_python_versions(): + return setup_cfg = CONFIG_PATH.setup_cfg with open(setup_cfg) as stream: original_contents = stream.read() @@ -50,8 +60,6 @@ def _convert_to_pyproject() -> None: extras_require = _get_recursive_optional_dependencies() if extras_require: _update_optional_dependencies(pyproject, extras_require) - if "3.6" in get_supported_python_versions(pyproject): - __downgrade_setuptools(pyproject) write_pyproject(pyproject) os.remove(setup_cfg) if os.path.exists("setup.py"): @@ -61,25 +69,6 @@ def _convert_to_pyproject() -> None: raise PrecommitError(msg) -def __downgrade_setuptools(pyproject: TOMLDocument) -> None: - if "3.6" not in get_supported_python_versions(pyproject): - return - build_system: Optional[Table] = pyproject.get("build-system") - if build_system is None: - return - requirements: Optional[Array] = build_system.get("requires") - if requirements is None: - return - for idx, package in enumerate(requirements): - if ">" not in package: - continue - package, *_ = package.split(">") - if package.strip() == "setuptools": - requirements[idx] = "setuptools>=58.0" - break - build_system["requires"] = requirements - - def _get_recursive_optional_dependencies() -> Dict[str, List[Tuple[str, str]]]: if not CONFIG_PATH.setup_cfg.exists(): return {} @@ -144,7 +133,12 @@ def _update_container( def _check_required_options() -> None: + if not has_pyproject_build_system(): + return pyproject = load_pyproject() + project_info = get_project_info() + if project_info.is_empty(): + return required_options = { "project": [ "name", @@ -174,6 +168,15 @@ def _check_required_options() -> None: def _update_author_data() -> None: + __update_author_data_in_pyproject() + __update_author_data_in_setup_cfg() + + +def __update_author_data_in_pyproject() -> None: + if not CONFIG_PATH.pyproject.exists(): + return + if not has_pyproject_build_system(): + return pyproject = load_pyproject() author_info = dict( name="Common Partial Wave Analysis", @@ -189,9 +192,30 @@ def _update_author_data() -> None: raise PrecommitError(msg) +def __update_author_data_in_setup_cfg() -> None: + if not CONFIG_PATH.setup_cfg.exists(): + return + old_cfg = open_setup_cfg() + new_cfg = copy_config(old_cfg) + new_cfg.set("metadata", "author", "Common Partial Wave Analysis") + new_cfg.set("metadata", "author_email", "Common Partial Wave Analysis") + new_cfg.set("metadata", "author_email", "compwa-admin@ep1.rub.de") + if new_cfg != old_cfg: + write_formatted_setup_cfg(new_cfg) + msg = f"Updated author info in ./{CONFIG_PATH.setup_cfg}" + raise PrecommitError(msg) + + def _fix_long_description() -> None: if not os.path.exists("README.md"): return + __fix_long_description_in_pyproject() + __fix_long_description_in_setup_cfg() + + +def __fix_long_description_in_pyproject() -> None: + if not has_pyproject_build_system(): + return cfg = load_pyproject() project = get_sub_table(cfg, "project", create=True) existing_readme = project.get("readme") @@ -205,3 +229,31 @@ def _fix_long_description() -> None: write_pyproject(cfg) msg = f"Updated long_description in ./{CONFIG_PATH.setup_cfg}" raise PrecommitError(msg) + + +def __fix_long_description_in_setup_cfg() -> None: + if not has_setup_cfg_build_system(): + return + old_cfg = open_setup_cfg() + new_cfg = copy_config(old_cfg) + new_cfg.set("metadata", "long_description", "file: README.md") + new_cfg.set("metadata", "long_description_content_type", "text/markdown") + if new_cfg != old_cfg: + write_formatted_setup_cfg(new_cfg) + msg = f"Updated long_description in ./{CONFIG_PATH.setup_cfg}" + raise PrecommitError(msg) + + +def has_pyproject_build_system() -> bool: + if not CONFIG_PATH.pyproject.exists(): + return False + pyproject = load_pyproject() + project_info = ProjectInfo.from_pyproject_toml(pyproject) + return not project_info.is_empty() + + +def has_setup_cfg_build_system() -> bool: + if not CONFIG_PATH.setup_cfg.exists(): + return False + cfg = open_setup_cfg() + return cfg.has_section("metadata") diff --git a/src/repoma/utilities/project_info.py b/src/repoma/utilities/project_info.py index 9b671923..3f10e980 100644 --- a/src/repoma/utilities/project_info.py +++ b/src/repoma/utilities/project_info.py @@ -111,7 +111,7 @@ def get_supported_python_versions( """Extract supported Python versions from package classifiers. >>> get_supported_python_versions() - ['3.10', '3.11', '3.6', '3.7', '3.8', '3.9'] + ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] """ project_info = get_project_info(pyproject) if project_info.supported_python_versions is None: diff --git a/tests/utilities/test_cfg.py b/tests/utilities/test_cfg.py index d1d2a08a..a25fb812 100644 --- a/tests/utilities/test_cfg.py +++ b/tests/utilities/test_cfg.py @@ -1,11 +1,19 @@ import io +from pathlib import Path from textwrap import dedent import pytest from repoma.errors import PrecommitError -from repoma.utilities.cfg import format_config, open_config -from repoma.utilities.project_info import get_repo_url +from repoma.utilities.cfg import copy_config, format_config, open_config +from repoma.utilities.project_info import get_repo_url, open_setup_cfg + + +def test_copy_config(): + cfg = open_setup_cfg() + cfg_copy = copy_config(cfg) + assert cfg_copy is not cfg + assert cfg_copy == cfg @pytest.mark.parametrize( @@ -92,6 +100,21 @@ def test_open_config_exception(): open_config(path) +def test_open_config_from_path(): + path_str = "setup.cfg" + cfg_from_str = open_config(path_str) + assert cfg_from_str.sections() == [ + "metadata", + "options", + "options.extras_require", + "options.entry_points", + "options.packages.find", + "options.package_data", + ] + cfg_from_path = open_config(Path(path_str)) + assert cfg_from_path == cfg_from_str + + def test_open_config_from_stream(): content = dedent("""\ [section1] diff --git a/tests/utilities/test_setup_cfg.py b/tests/utilities/test_setup_cfg.py new file mode 100644 index 00000000..d0267aec --- /dev/null +++ b/tests/utilities/test_setup_cfg.py @@ -0,0 +1,15 @@ +from repoma.utilities.project_info import open_setup_cfg + + +def test_open_setup_cfg(): + cfg = open_setup_cfg() + sections = cfg.sections() + assert sections == [ + "metadata", + "options", + "options.extras_require", + "options.entry_points", + "options.packages.find", + "options.package_data", + ] + assert cfg.get("metadata", "name") == "repo-maintenance"