From 2a94e7a920746fecf78a4b268796bfbe9d37653e Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:10:55 +0200 Subject: [PATCH 01/13] MAINT: add `toml-sort` pre-commit hook --- .pre-commit-config.yaml | 8 +++++ .taplo.toml | 2 ++ pyproject.toml | 40 ++++++++++++++++--------- src/repoma/check_dev_files/toml.py | 47 ++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2958c902..49319ad3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -125,3 +125,11 @@ repos: rev: v0.8.0 hooks: - id: taplo + + - repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 + hooks: + - id: toml-sort + args: + - --in-place + exclude: (?x)^(labels.toml|labels-physics.toml)$ diff --git a/.taplo.toml b/.taplo.toml index f5caabee..8a2b7a5d 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -6,7 +6,9 @@ exclude = [ ] [formatting] +align_comments = false align_entries = false +allowed_blank_lines = 1 array_auto_collapse = false array_auto_expand = true array_trailing_comma = true diff --git a/pyproject.toml b/pyproject.toml index d10a2071..65ec7b00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,6 @@ module = ["nbformat.*"] ignore_missing_imports = true module = ["pre_commit.commands.autoupdate.*"] - [tool.pyright] exclude = [ "**/.git", @@ -101,7 +100,6 @@ reportUnusedImport = true reportUnusedVariable = true typeCheckingMode = "strict" - [tool.pytest.ini_options] addopts = """ --color=yes @@ -153,20 +151,20 @@ extend-select = [ "YTT", ] ignore = [ - "D101", # class docstring - "D102", # method docstring - "D103", # function docstring - "D105", # magic method docstring - "D107", # init docstring - "D203", # conflicts with D211 - "D213", # multi-line docstring should start at the second line - "D407", # missing dashed underline after section - "D416", # section name does not have to end with a colon - "E501", # handled by black formatting + "D101", # class docstring + "D102", # method docstring + "D103", # function docstring + "D105", # magic method docstring + "D107", # init docstring + "D203", # conflicts with D211 + "D213", # multi-line docstring should start at the second line + "D407", # missing dashed underline after section + "D416", # section name does not have to end with a colon + "E501", # handled by black formatting "PLR0913", # sympy functions "PLW2901", # often used for xreplace - "S301", # allow pickle - "SIM108", # if-else-block-instead-of-if-exp + "S301", # allow pickle + "SIM108", # if-else-block-instead-of-if-exp ] show-fixes = true src = [ @@ -192,3 +190,17 @@ known-first-party = ["repoma"] [tool.ruff.pydocstyle] convention = "google" + +[tool.tomlsort] +all = false +ignore_case = true +in_place = true +sort_first = [ + "build-system", + "project", + "tool.setuptools", + "tool.setuptools_scm", +] +sort_table_keys = true +spaces_indent_inline_array = 4 +trailing_comma_inline_array = true diff --git a/src/repoma/check_dev_files/toml.py b/src/repoma/check_dev_files/toml.py index f39924df..24dc9c62 100644 --- a/src/repoma/check_dev_files/toml.py +++ b/src/repoma/check_dev_files/toml.py @@ -11,6 +11,12 @@ from repoma.utilities import CONFIG_PATH, REPOMA_DIR, vscode from repoma.utilities.executor import Executor from repoma.utilities.precommit import update_single_hook_precommit_repo +from repoma.utilities.pyproject import ( + get_sub_table, + load_pyproject, + to_toml_array, + write_pyproject, +) __INCORRECT_TAPLO_CONFIG_PATHS = [ "taplo.toml", @@ -29,10 +35,51 @@ def main() -> None: executor(_rename_taplo_config) executor(_update_taplo_config) executor(_update_precommit_repo) + executor(_update_tomlsort_config) + executor(_update_tomlsort_hook) executor(_update_vscode_extensions) executor.finalize() +def _update_tomlsort_config() -> None: + # cspell:ignore tomlsort + pyproject = load_pyproject() + sort_first = [ + "build-system", + "project", + "tool.setuptools", + "tool.setuptools_scm", + ] + expected_config = dict( # noqa: C408 + all=False, + ignore_case=True, + in_place=True, + sort_first=to_toml_array(sort_first), + sort_table_keys=True, + spaces_indent_inline_array=4, + trailing_comma_inline_array=True, + ) + tool_table = get_sub_table(pyproject, "tool", create=True) + if tool_table.get("tomlsort") == expected_config: + return + tool_table["tomlsort"] = expected_config + write_pyproject(pyproject) + msg = "Updated toml-sort configuration" + raise PrecommitError(msg) + + +def _update_tomlsort_hook() -> None: + expected_hook = CommentedMap( + repo="https://github.com/pappasam/toml-sort", + hooks=[CommentedMap(id="toml-sort", args=["--in-place"])], + ) + excludes = ["labels.toml", "labels-physics.toml"] + excludes = [f for f in excludes if os.path.exists(f)] + if excludes: + expected_hook["hooks"][0]["exclude"] = "(?x)^(" + "|".join(excludes) + ")$" + update_single_hook_precommit_repo(expected_hook) + + def _rename_taplo_config() -> None: for path in __INCORRECT_TAPLO_CONFIG_PATHS: if not os.path.exists(path): From a0d50b6337875e5d8ed59b1f2f1e6362a7102f81 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:16:53 +0200 Subject: [PATCH 02/13] BREAK: rename `setup_cfg` to `project_info` --- src/repoma/check_dev_files/black.py | 2 +- src/repoma/check_dev_files/deprecated.py | 2 +- src/repoma/check_dev_files/github_workflows.py | 2 +- src/repoma/check_dev_files/gitpod.py | 2 +- src/repoma/check_dev_files/pyupgrade.py | 2 +- src/repoma/check_dev_files/ruff.py | 2 +- src/repoma/check_dev_files/setup_cfg.py | 2 +- src/repoma/format_setup_cfg.py | 2 +- src/repoma/set_nb_cells.py | 2 +- src/repoma/utilities/{setup_cfg.py => project_info.py} | 0 tests/utilities/test_cfg.py | 2 +- tests/utilities/test_setup_cfg.py | 2 +- 12 files changed, 11 insertions(+), 11 deletions(-) rename src/repoma/utilities/{setup_cfg.py => project_info.py} (100%) diff --git a/src/repoma/check_dev_files/black.py b/src/repoma/check_dev_files/black.py index 8cefe245..02c57683 100644 --- a/src/repoma/check_dev_files/black.py +++ b/src/repoma/check_dev_files/black.py @@ -9,6 +9,7 @@ remove_precommit_hook, update_single_hook_precommit_repo, ) +from repoma.utilities.project_info import get_supported_python_versions from repoma.utilities.pyproject import ( complies_with_subset, get_sub_table, @@ -16,7 +17,6 @@ to_toml_array, write_pyproject, ) -from repoma.utilities.setup_cfg import get_supported_python_versions from repoma.utilities.vscode import ( add_extension_recommendation, set_setting, diff --git a/src/repoma/check_dev_files/deprecated.py b/src/repoma/check_dev_files/deprecated.py index de89c2a3..e46201ba 100644 --- a/src/repoma/check_dev_files/deprecated.py +++ b/src/repoma/check_dev_files/deprecated.py @@ -7,9 +7,9 @@ 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.setup_cfg import open_setup_cfg from repoma.utilities.vscode import ( remove_extension_recommendation, remove_settings, diff --git a/src/repoma/check_dev_files/github_workflows.py b/src/repoma/check_dev_files/github_workflows.py index 4462d22f..85486c8b 100644 --- a/src/repoma/check_dev_files/github_workflows.py +++ b/src/repoma/check_dev_files/github_workflows.py @@ -12,7 +12,7 @@ from repoma.utilities import CONFIG_PATH, REPOMA_DIR, write from repoma.utilities.executor import Executor from repoma.utilities.precommit import PrecommitConfig -from repoma.utilities.setup_cfg import get_pypi_name +from repoma.utilities.project_info import get_pypi_name from repoma.utilities.vscode import ( add_extension_recommendation, remove_extension_recommendation, diff --git a/src/repoma/check_dev_files/gitpod.py b/src/repoma/check_dev_files/gitpod.py index 3253ca7e..a50006c5 100644 --- a/src/repoma/check_dev_files/gitpod.py +++ b/src/repoma/check_dev_files/gitpod.py @@ -7,8 +7,8 @@ from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH, REPOMA_DIR +from repoma.utilities.project_info import get_repo_url from repoma.utilities.readme import add_badge -from repoma.utilities.setup_cfg import get_repo_url from repoma.utilities.yaml import write_yaml __CONSTRAINTS_FILE = ".constraints/py3.8.txt" diff --git a/src/repoma/check_dev_files/pyupgrade.py b/src/repoma/check_dev_files/pyupgrade.py index c87ced67..0499d269 100644 --- a/src/repoma/check_dev_files/pyupgrade.py +++ b/src/repoma/check_dev_files/pyupgrade.py @@ -8,7 +8,7 @@ update_precommit_hook, update_single_hook_precommit_repo, ) -from repoma.utilities.setup_cfg import get_supported_python_versions +from repoma.utilities.project_info import get_supported_python_versions def main() -> None: diff --git a/src/repoma/check_dev_files/ruff.py b/src/repoma/check_dev_files/ruff.py index 5198e864..254a2da1 100644 --- a/src/repoma/check_dev_files/ruff.py +++ b/src/repoma/check_dev_files/ruff.py @@ -14,6 +14,7 @@ update_precommit_hook, update_single_hook_precommit_repo, ) +from repoma.utilities.project_info import get_supported_python_versions, open_setup_cfg from repoma.utilities.pyproject import ( complies_with_subset, get_sub_table, @@ -23,7 +24,6 @@ write_pyproject, ) from repoma.utilities.readme import add_badge -from repoma.utilities.setup_cfg import get_supported_python_versions, open_setup_cfg from repoma.utilities.vscode import add_extension_recommendation, set_setting diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 733a93f5..59c470e1 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -9,7 +9,7 @@ from repoma.utilities import CONFIG_PATH from repoma.utilities.cfg import copy_config from repoma.utilities.executor import Executor -from repoma.utilities.setup_cfg import open_setup_cfg +from repoma.utilities.project_info import open_setup_cfg def main(ignore_author: bool) -> None: diff --git a/src/repoma/format_setup_cfg.py b/src/repoma/format_setup_cfg.py index 10fd2a41..84625f98 100644 --- a/src/repoma/format_setup_cfg.py +++ b/src/repoma/format_setup_cfg.py @@ -10,7 +10,7 @@ from repoma.utilities import CONFIG_PATH from repoma.utilities.cfg import format_config -from repoma.utilities.setup_cfg import open_setup_cfg +from repoma.utilities.project_info import open_setup_cfg def format_setup_cfg() -> None: diff --git a/src/repoma/set_nb_cells.py b/src/repoma/set_nb_cells.py index 83013b42..1ee83e44 100644 --- a/src/repoma/set_nb_cells.py +++ b/src/repoma/set_nb_cells.py @@ -25,7 +25,7 @@ import nbformat -from repoma.utilities.setup_cfg import open_setup_cfg +from repoma.utilities.project_info import open_setup_cfg __SETUP_CFG = open_setup_cfg() __PACKAGE_NAME = __SETUP_CFG["metadata"]["name"] diff --git a/src/repoma/utilities/setup_cfg.py b/src/repoma/utilities/project_info.py similarity index 100% rename from src/repoma/utilities/setup_cfg.py rename to src/repoma/utilities/project_info.py diff --git a/tests/utilities/test_cfg.py b/tests/utilities/test_cfg.py index c8d6c775..a25fb812 100644 --- a/tests/utilities/test_cfg.py +++ b/tests/utilities/test_cfg.py @@ -6,7 +6,7 @@ from repoma.errors import PrecommitError from repoma.utilities.cfg import copy_config, format_config, open_config -from repoma.utilities.setup_cfg import get_repo_url, open_setup_cfg +from repoma.utilities.project_info import get_repo_url, open_setup_cfg def test_copy_config(): diff --git a/tests/utilities/test_setup_cfg.py b/tests/utilities/test_setup_cfg.py index 1e05a47f..d0267aec 100644 --- a/tests/utilities/test_setup_cfg.py +++ b/tests/utilities/test_setup_cfg.py @@ -1,4 +1,4 @@ -from repoma.utilities.setup_cfg import open_setup_cfg +from repoma.utilities.project_info import open_setup_cfg def test_open_setup_cfg(): From 5570fd7af3b8012cd6b07843d2e5535d4afb5e8c Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:18:01 +0200 Subject: [PATCH 03/13] DX: allow constructing `dict` with `dict` kwargs --- pyproject.toml | 1 + src/repoma/check_dev_files/toml.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 65ec7b00..09a5aac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,6 +151,7 @@ extend-select = [ "YTT", ] ignore = [ + "C408", # Unnecessary dict call - rewrite as a literal "D101", # class docstring "D102", # method docstring "D103", # function docstring diff --git a/src/repoma/check_dev_files/toml.py b/src/repoma/check_dev_files/toml.py index 24dc9c62..eae33258 100644 --- a/src/repoma/check_dev_files/toml.py +++ b/src/repoma/check_dev_files/toml.py @@ -50,7 +50,7 @@ def _update_tomlsort_config() -> None: "tool.setuptools", "tool.setuptools_scm", ] - expected_config = dict( # noqa: C408 + expected_config = dict( all=False, ignore_case=True, in_place=True, From a1de3481f89c8b8c2fc74fd219d61b76ce0c3d71 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:22:47 +0200 Subject: [PATCH 04/13] FIX: write `pyright` instead of `black` --- src/repoma/check_dev_files/pyright.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repoma/check_dev_files/pyright.py b/src/repoma/check_dev_files/pyright.py index dc9d6045..cbe0f077 100644 --- a/src/repoma/check_dev_files/pyright.py +++ b/src/repoma/check_dev_files/pyright.py @@ -48,5 +48,5 @@ def _update_settings() -> None: if not complies_with_subset(settings, minimal_settings): settings.update(minimal_settings) write_pyproject(pyproject) - msg = f"Updated black configuration in {CONFIG_PATH.pyproject}" + msg = f"Updated pyright configuration in {CONFIG_PATH.pyproject}" raise PrecommitError(msg) From fc5b85d0a2aa09e2513bb09e2cec727fad1eeaa4 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:29:58 +0200 Subject: [PATCH 05/13] ENH: accept both `pyproject.toml` and `setup.cfg` in `project_info` module --- src/repoma/set_nb_cells.py | 21 ++-- src/repoma/utilities/project_info.py | 159 ++++++++++++++++++--------- 2 files changed, 122 insertions(+), 58 deletions(-) diff --git a/src/repoma/set_nb_cells.py b/src/repoma/set_nb_cells.py index 1ee83e44..bb91982a 100644 --- a/src/repoma/set_nb_cells.py +++ b/src/repoma/set_nb_cells.py @@ -20,15 +20,13 @@ import argparse import sys +from functools import lru_cache from textwrap import dedent from typing import Optional, Sequence import nbformat -from repoma.utilities.project_info import open_setup_cfg - -__SETUP_CFG = open_setup_cfg() -__PACKAGE_NAME = __SETUP_CFG["metadata"]["name"] +from repoma.utilities.project_info import get_pypi_name __CONFIG_CELL_CONTENT = """ %config InlineBackend.figure_formats = ['svg'] @@ -45,10 +43,6 @@ "tags": ["remove-cell"], } -__INSTALL_CELL_CONTENT = f""" -# WARNING: advised to install a specific version, e.g. {__PACKAGE_NAME}==0.1.2 -%pip install -q {__PACKAGE_NAME} -""" __INSTALL_CELL_METADATA: dict = { **__CONFIG_CELL_METADATA, "tags": ["remove-cell", "skip-execution"], @@ -95,7 +89,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: for filename in args.filenames: cell_id = 0 if args.add_install_cell: - cell_content = __INSTALL_CELL_CONTENT.strip("\n") + cell_content = __get_install_cell().strip("\n") if args.extras_require: extras = args.extras_require.strip() cell_content += f"[{extras}]" @@ -121,6 +115,15 @@ def main(argv: Optional[Sequence[str]] = None) -> int: return 0 +@lru_cache(maxsize=1) +def __get_install_cell() -> str: + package_name = get_pypi_name() + return dedent(f""" + # WARNING: advised to install a specific version, e.g. {package_name}==0.1.2 + %pip install -q {package_name} + """) + + def _update_cell( filename: str, new_content: str, diff --git a/src/repoma/utilities/project_info.py b/src/repoma/utilities/project_info.py index 3b5ebdf7..3f10e980 100644 --- a/src/repoma/utilities/project_info.py +++ b/src/repoma/utilities/project_info.py @@ -1,80 +1,141 @@ """Helper functions for reading from and writing to :file:`setup.cfg`.""" +import os from configparser import ConfigParser -from typing import List +from textwrap import dedent +from typing import Dict, List, Optional + +from attrs import field, frozen +from tomlkit import TOMLDocument from repoma.errors import PrecommitError +from repoma.utilities.pyproject import get_sub_table, load_pyproject from . import CONFIG_PATH from .cfg import open_config -def get_pypi_name() -> str: +@frozen +class ProjectInfo: + name: Optional[str] = None + supported_python_versions: Optional[List[str]] = None + urls: Dict[str, str] = field(factory=dict) + + def is_empty(self) -> bool: + return ( + self.name is None + and self.supported_python_versions is None + and not self.urls + ) + + @staticmethod + def from_pyproject_toml(pyproject: TOMLDocument) -> "ProjectInfo": + if "project" not in pyproject: + return ProjectInfo() + project = get_sub_table(pyproject, "project") + return ProjectInfo( + name=project.get("name"), + supported_python_versions=_extract_python_versions( + project.get("classifiers", []) + ), + urls=project.get("urls", {}), + ) + + @staticmethod + def from_setup_cfg(cfg: ConfigParser) -> "ProjectInfo": + if not cfg.has_section("metadata"): + return ProjectInfo() + metadata = dict(cfg.items("metadata")) + project_urls_raw: str = metadata.get("project_urls", "\n") + project_url_lines = project_urls_raw.split("\n") + project_url_lines = list(filter(lambda line: line.strip(), project_url_lines)) + project_urls = {} + for line in project_url_lines: + url_type, url, *_ = tuple(line.split("=")) + url_type = url_type.strip() + url = url.strip() + project_urls[url_type] = url + return ProjectInfo( + name=metadata.get("name"), + supported_python_versions=_extract_python_versions( + metadata.get("classifiers", "").split("\n") + ), + urls=project_urls, + ) + + +def get_project_info(pyproject: Optional[TOMLDocument] = None) -> ProjectInfo: + if pyproject is not None or os.path.exists(CONFIG_PATH.pyproject): + if pyproject is None: + pyproject = load_pyproject() + project_info = ProjectInfo.from_pyproject_toml(pyproject) + if not project_info.is_empty(): + return project_info + if os.path.exists(CONFIG_PATH.setup_cfg): + cfg = open_config(CONFIG_PATH.setup_cfg) + project_info = ProjectInfo.from_setup_cfg(cfg) + if not project_info.is_empty(): + return project_info + msg = f"No valid {CONFIG_PATH.setup_cfg} or {CONFIG_PATH.pyproject} found" + raise PrecommitError(msg) + + +def _extract_python_versions(classifiers: List[str]) -> Optional[List[str]]: + identifier = "Programming Language :: Python :: 3." + version_classifiers = [s for s in classifiers if s.startswith(identifier)] + if not version_classifiers: + return None + prefix = identifier[:-2] + return [s.replace(prefix, "") for s in version_classifiers] + + +def get_pypi_name(pyproject: Optional[TOMLDocument] = None) -> str: """Extract package name for PyPI from `setup.cfg`. >>> get_pypi_name() 'repo-maintenance' """ - cfg = open_setup_cfg() - if not cfg.has_option("metadata", "name"): - msg = f"No package name defined in {CONFIG_PATH.setup_cfg}" + project_info = get_project_info(pyproject) + if project_info.name is None: + msg = ( + f"No package name defined in {CONFIG_PATH.setup_cfg} or" + f" {CONFIG_PATH.pyproject}" + ) raise PrecommitError(msg) - return cfg.get("metadata", "name") + return project_info.name -def get_supported_python_versions() -> List[str]: +def get_supported_python_versions( + pyproject: Optional[TOMLDocument] = None, +) -> List[str]: """Extract supported Python versions from package classifiers. >>> get_supported_python_versions() ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] """ - cfg = open_setup_cfg() - if not cfg.has_option("metadata", "classifiers"): - msg = ( - "This package does not have Python version classifiers. See" - " https://pypi.org/classifiers." - ) - raise PrecommitError(msg) - raw = cfg.get("metadata", "classifiers") - lines = [s.strip() for s in raw.split("\n")] - identifier = "Programming Language :: Python :: 3." - classifiers = list(filter(lambda s: s.startswith(identifier), lines)) - if not classifiers: - msg = f'setup.cfg does not have any classifiers of the form "{identifier}*"' + project_info = get_project_info(pyproject) + if project_info.supported_python_versions is None: + msg = "Could not determine Python version classifiers of this package" raise PrecommitError(msg) - prefix = identifier[:-2] - return [s.replace(prefix, "") for s in classifiers] + return project_info.supported_python_versions + +def get_repo_url(pyproject: Optional[TOMLDocument] = None) -> str: + project_info = get_project_info(pyproject) + if not project_info.urls: + msg = dedent(""" + pyproject.toml or setup.cfg does not contain project URLSs. Should be + something like: -def get_repo_url() -> str: - cfg = open_setup_cfg() - if not cfg.has_section("metadata"): - msg = "setup.cfg does not contain a metadata section" + [project.urls]" + Documentation = "https://ampform.rtfd.io" + Source = "https://github.com/ComPWA/ampform" + Tracker = "https://github.com/ComPWA/ampform/issues" + """) raise PrecommitError(msg) - project_urls_def = cfg["metadata"].get("project_urls", None) - if project_urls_def is None: - error_message = ( - "Section metadata in setup.cfg does not contain project_urls." - " Should be something like:\n\n" - "[metadata]\n" - "...\n" - "project_urls =\n" - " Tracker = https://github.com/ComPWA/ampform/issues\n" - " Source = https://github.com/ComPWA/ampform\n" - " ...\n" - ) - raise PrecommitError(error_message) - project_url_lines = project_urls_def.split("\n") - project_url_lines = list(filter(lambda line: line.strip(), project_url_lines)) - project_urls = {} - for line in project_url_lines: - url_type, url, *_ = tuple(line.split("=")) - url_type = url_type.strip() - url = url.strip() - project_urls[url_type] = url - source_url = project_urls.get("Source") + source_url = project_info.urls.get("Source") if source_url is None: - msg = 'metadata.project_urls in setup.cfg does not contain "Source" URL' + msg = '[project.urls] in pyproject.toml does not contain a "Source" URL' raise PrecommitError(msg) return source_url From b4fb62bfbb85388e201a56e2670505d6b1fa0a31 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:32:15 +0200 Subject: [PATCH 06/13] BEHAVIOR: mege `setup.cfg` into `pyproject.toml` --- .pre-commit-config.yaml | 7 -- .taplo.toml | 1 + pyproject.toml | 128 ++++++++++++++++++++++- setup.cfg | 93 ---------------- src/repoma/check_dev_files/deprecated.py | 39 ++++--- src/repoma/check_dev_files/ruff.py | 79 ++++++++------ src/repoma/check_dev_files/setup_cfg.py | 99 +++++++++++++----- src/repoma/utilities/project_info.py | 2 +- tests/utilities/test_cfg.py | 27 +---- tests/utilities/test_setup_cfg.py | 15 --- 10 files changed, 273 insertions(+), 217 deletions(-) delete mode 100644 setup.cfg delete mode 100644 tests/utilities/test_setup_cfg.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49319ad3..aa6cb7ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ ci: autoupdate_schedule: quarterly skip: - check-dev-files - - format-setup-cfg - mypy - pyright - repoma-self-check @@ -49,12 +48,6 @@ repos: - --repo-name=repo-maintenance - --repo-title=ComPWA Repository Maintenance - - id: format-setup-cfg - name: Format setup.cfg - entry: format-setup-cfg - language: python - files: ^setup.cfg$ - - id: repoma-self-check name: repoma-self-check entry: repoma-self-check diff --git a/.taplo.toml b/.taplo.toml index 8a2b7a5d..424b7469 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -13,6 +13,7 @@ array_auto_collapse = false array_auto_expand = true array_trailing_comma = true column_width = 88 +compact_inline_tables = true indent_string = " " reorder_arrays = true reorder_keys = true diff --git a/pyproject.toml b/pyproject.toml index 09a5aac4..91e43a7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,134 @@ [build-system] +build-backend = "setuptools.build_meta" requires = [ - "setuptools>=36.2.1", # environment markers + "setuptools>=61.2", "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 = [ + "black", + "labels", + "mypy", + "pre-commit >=1.4.0", + "pytest", + "pytest-cov", + "pytest-xdist", + "radon", + "repo-maintenance[sty]", + "tox >=1.9", + "types-PyYAML", + "types-setuptools", + "types-toml", + 'ruff; python_version >="3.7.0"', +] +format = ["black"] +lint = [ + "mypy", + "radon", + "types-PyYAML", + "types-setuptools", + "types-toml", + 'ruff; python_version >="3.7.0"', +] +mypy = [ + "mypy", + "types-PyYAML", + "types-setuptools", + "types-toml", +] +sty = [ + "black", + "mypy", + "pre-commit >=1.4.0", + "pytest", + "pytest-cov", + "pytest-xdist", + "radon", + "repo-maintenance[lint]", + "types-PyYAML", + "types-setuptools", + "types-toml", + 'ruff; python_version >="3.7.0"', +] +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 deleted file mode 100644 index 39be1cf1..00000000 --- a/setup.cfg +++ /dev/null @@ -1,93 +0,0 @@ -[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 e46201ba..ecd24070 100644 --- a/src/repoma/check_dev_files/deprecated.py +++ b/src/repoma/check_dev_files/deprecated.py @@ -7,7 +7,6 @@ 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 ( @@ -35,8 +34,8 @@ def _remove_flake8() -> None: executor = Executor() executor(__remove_configs, [".flake8"]) executor(__remove_nbqa_option, "flake8") - executor(__uninstall, "flake8", check_options=["lint", "sty"]) - executor(__uninstall, "pep8-naming", check_options=["lint", "sty"]) + executor(__uninstall, "flake8") + executor(__uninstall, "pep8-naming") executor(remove_extension_recommendation, "ms-python.flake8", unwanted=True) executor(remove_precommit_hook, "autoflake") # cspell:ignore autoflake executor(remove_precommit_hook, "flake8") @@ -115,7 +114,7 @@ def _remove_pydocstyle() -> None: "tests/.pydocstyle", ], ) - executor(__uninstall, "pydocstyle", check_options=["lint", "sty"]) + executor(__uninstall, "pydocstyle") executor(remove_precommit_hook, "pydocstyle") executor.finalize() @@ -123,7 +122,7 @@ def _remove_pydocstyle() -> None: def _remove_pylint() -> None: executor = Executor() executor(__remove_configs, [".pylintrc"]) # cspell:ignore pylintrc - executor(__uninstall, "pylint", check_options=["lint", "sty"]) + executor(__uninstall, "pylint") executor(remove_extension_recommendation, "ms-python.pylint", unwanted=True) executor(remove_precommit_hook, "pylint") executor(remove_precommit_hook, "nbqa-pylint") @@ -149,19 +148,27 @@ def __remove_file(path: str) -> None: raise PrecommitError(msg) -def __uninstall(package: str, check_options: List[str]) -> None: - if not os.path.exists(CONFIG_PATH.setup_cfg): +def __uninstall(package: str) -> None: + if not os.path.exists(CONFIG_PATH.pyproject): return - cfg = open_setup_cfg() - section = "options.extras_require" - if not cfg.has_section(section): + pyproject = load_pyproject() + project = pyproject.get("project") + if project is None: return - for option in check_options: - 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' + updated = False + dependencies = project.get("dependencies") + if dependencies is not None and package in dependencies: + dependencies.remove(package) + updated = True + optional_dependencies = project.get("optional-dependencies") + if optional_dependencies is not None: + for values in optional_dependencies.values(): + if package in values: + values.remove(package) + updated = True + if updated: + write_pyproject(pyproject) + msg = f"Removed {package} from {CONFIG_PATH.pyproject}" raise PrecommitError(msg) diff --git a/src/repoma/check_dev_files/ruff.py b/src/repoma/check_dev_files/ruff.py index 254a2da1..b137fe8f 100644 --- a/src/repoma/check_dev_files/ruff.py +++ b/src/repoma/check_dev_files/ruff.py @@ -1,7 +1,7 @@ """Check `Ruff `_ configuration.""" import os -from textwrap import dedent +from copy import deepcopy from typing import List, Set from ruamel.yaml.comments import CommentedMap @@ -14,7 +14,10 @@ update_precommit_hook, update_single_hook_precommit_repo, ) -from repoma.utilities.project_info import get_supported_python_versions, open_setup_cfg +from repoma.utilities.project_info import ( + get_project_info, + get_supported_python_versions, +) from repoma.utilities.pyproject import ( complies_with_subset, get_sub_table, @@ -33,50 +36,62 @@ 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_vscode_settings) executor.finalize() -def _check_setup_cfg() -> None: - 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 +def _update_setup_cfg() -> None: + pyproject = load_pyproject() + project_info = get_project_info(pyproject) + package = project_info.name + if package is None: + msg = ( + "Please specify a [project.name] for the package in" + f" [{CONFIG_PATH.pyproject}]" + ) raise PrecommitError(msg) - lint_section = cfg.get(extras_require, "lint") - if not any("ruff" in line for line in lint_section.split("\n")): + project = get_sub_table(pyproject, "project", create=True) + old_dependencies = project.get("optional-dependencies") + new_dependencies = deepcopy(old_dependencies) + python_versions = project_info.supported_python_versions + if python_versions is not None and "3.6" in python_versions: + ruff = 'ruff; python_version >="3.7.0"' + else: + ruff = "ruff" + if new_dependencies is None: + new_dependencies = dict( + dev=[f"{package}[sty]"], + lint=[ruff], + sty=[f"{package}[lint]"], + ) + else: + __add_package(new_dependencies, "dev", f"{package}[sty]") + __add_package(new_dependencies, "lint", ruff) + __add_package(new_dependencies, "sty", f"{package}[lint]") + if old_dependencies != new_dependencies: + project["optional-dependencies"] = new_dependencies + write_pyproject(pyproject) + msg = f"Updated [project.optional-dependencies] in {CONFIG_PATH.pyproject}" raise PrecommitError(msg) +def __add_package(optional_dependencies: Table, key: str, package: str) -> None: + section = optional_dependencies.get(key) + if section is None: + optional_dependencies[key] = [package] + elif package not in section: + optional_dependencies[key] = to_toml_array( + sorted({package, *section}, key=lambda s: ('"' in s, s)) # Taplo sorting + ) + + def _update_nbqa_settings() -> None: # cspell:ignore addopts ruff_rules = [ diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 59c470e1..85e17b2d 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -3,17 +3,27 @@ import os import textwrap from collections import defaultdict +from typing import Union + +import tomlkit +from ini2toml.api import Translator +from tomlkit.container import Container +from tomlkit.items import 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.project_info import open_setup_cfg +from repoma.utilities.pyproject import ( + get_sub_table, + load_pyproject, + write_pyproject, +) def main(ignore_author: bool) -> None: - if not CONFIG_PATH.setup_cfg.exists(): + if CONFIG_PATH.setup_cfg.exists(): + _convert_to_pyproject() + if not CONFIG_PATH.pyproject.exists(): return executor = Executor() executor(_check_required_options) @@ -23,21 +33,48 @@ def main(ignore_author: bool) -> None: executor.finalize() +def _convert_to_pyproject() -> None: + setup_cfg = CONFIG_PATH.setup_cfg + with open(setup_cfg) as stream: + original_contents = stream.read() + toml_str = Translator().translate(original_contents, profile_name=str(setup_cfg)) + converted_cfg = tomlkit.parse(toml_str) + pyproject = load_pyproject() + _update_container(pyproject, converted_cfg) + write_pyproject(pyproject) + os.remove(setup_cfg) + msg = f"Converted {setup_cfg} configuration to {CONFIG_PATH.pyproject}" + raise PrecommitError(msg) + + +def _update_container( + old: Union[Container, Table], new: Union[Container, Table] +) -> None: + for key, value in new.items(): + if isinstance(value, (Container, Table)): + if key in old: + _update_container(old[key], value) # type: ignore[arg-type] + else: + old[key] = value + else: + old[key] = value + + def _check_required_options() -> None: - cfg = open_setup_cfg() + pyproject = load_pyproject() required_options = { - "metadata": [ + "project": [ "name", "description", "license", "classifiers", + "requires-python", ], - "options": ["python_requires"], } missing_options = defaultdict(list) for section, options in required_options.items(): for option in options: - if cfg.has_option(section, option): + if option in get_sub_table(pyproject, section, create=True): continue missing_options[section].append(option) if missing_options: @@ -48,30 +85,40 @@ def _check_required_options() -> None: summary += f"{option} = ...\n" summary += "...\n" raise PrecommitError( - f"./{CONFIG_PATH.setup_cfg} is missing the following options:\n" + f"{CONFIG_PATH.pyproject} is missing the following options:\n" + textwrap.indent(summary, prefix=" ") ) def _update_author_data() -> None: - 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}" + pyproject = load_pyproject() + author_info = dict( + name="Common Partial Wave Analysis", + email="compwa-admin@ep1.rub.de", + ) + authors = tomlkit.array().multiline(True) + authors.append(author_info) + project = get_sub_table(pyproject, "project", create=True) + if project.get("authors") != authors: + pyproject["project"]["authors"] = authors # type: ignore[index] + write_pyproject(pyproject) + msg = f"Updated author info in {CONFIG_PATH.pyproject}" raise PrecommitError(msg) def _fix_long_description() -> None: - if os.path.exists("README.md"): - 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) + if not os.path.exists("README.md"): + return + cfg = load_pyproject() + project = get_sub_table(cfg, "project", create=True) + existing_readme = project.get("readme") + expected_readme = { + "content-type": "text/markdown", + "file": "README.md", + } + if existing_readme == expected_readme: + return + project["readme "] = expected_readme + write_pyproject(cfg) + msg = f"Updated long_description in ./{CONFIG_PATH.setup_cfg}" + raise PrecommitError(msg) diff --git a/src/repoma/utilities/project_info.py b/src/repoma/utilities/project_info.py index 3f10e980..9b671923 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.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + ['3.10', '3.11', '3.6', '3.7', '3.8', '3.9'] """ 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 a25fb812..d1d2a08a 100644 --- a/tests/utilities/test_cfg.py +++ b/tests/utilities/test_cfg.py @@ -1,19 +1,11 @@ import io -from pathlib import Path from textwrap import dedent import pytest from repoma.errors import PrecommitError -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 +from repoma.utilities.cfg import format_config, open_config +from repoma.utilities.project_info import get_repo_url @pytest.mark.parametrize( @@ -100,21 +92,6 @@ 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 deleted file mode 100644 index d0267aec..00000000 --- a/tests/utilities/test_setup_cfg.py +++ /dev/null @@ -1,15 +0,0 @@ -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" From 03f90be43472eb6c2e281e613f35d14e7b698871 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:20:37 +0200 Subject: [PATCH 07/13] FIX: correctly import substitution syntax --- pyproject.toml | 35 ++++---------- src/repoma/check_dev_files/setup_cfg.py | 64 +++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 91e43a7f..7fe5002a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,28 +44,17 @@ requires-python = ">=3.6" [project.optional-dependencies] dev = [ - "black", "labels", - "mypy", - "pre-commit >=1.4.0", - "pytest", - "pytest-cov", - "pytest-xdist", - "radon", "repo-maintenance[sty]", - "tox >=1.9", - "types-PyYAML", - "types-setuptools", - "types-toml", - 'ruff; python_version >="3.7.0"', + "repo-maintenance[test]", + "tox >=1.9", # for skip_install, use_develop +] +format = [ + "black", ] -format = ["black"] lint = [ - "mypy", "radon", - "types-PyYAML", - "types-setuptools", - "types-toml", + "repo-maintenance[mypy]", 'ruff; python_version >="3.7.0"', ] mypy = [ @@ -75,18 +64,10 @@ mypy = [ "types-toml", ] sty = [ - "black", - "mypy", "pre-commit >=1.4.0", - "pytest", - "pytest-cov", - "pytest-xdist", - "radon", + "repo-maintenance[format]", "repo-maintenance[lint]", - "types-PyYAML", - "types-setuptools", - "types-toml", - 'ruff; python_version >="3.7.0"', + "repo-maintenance[test]", # for pytest type hints ] test = [ "pytest", diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 85e17b2d..a6700d18 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -1,18 +1,23 @@ """Apply a certain set of standards to the :file:`setup.cfg`.""" - +# pyright: reportUnknownLambdaType=false +import dataclasses import os +import re import textwrap from collections import defaultdict -from typing import Union +from configparser import RawConfigParser +from typing import Dict, List, Tuple, Union import tomlkit from ini2toml.api import Translator +from tomlkit import TOMLDocument from tomlkit.container import Container -from tomlkit.items import Table +from tomlkit.items import Array, Table from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH from repoma.utilities.executor import Executor +from repoma.utilities.project_info import get_pypi_name from repoma.utilities.pyproject import ( get_sub_table, load_pyproject, @@ -41,12 +46,65 @@ def _convert_to_pyproject() -> None: converted_cfg = tomlkit.parse(toml_str) pyproject = load_pyproject() _update_container(pyproject, converted_cfg) + extras_require = _get_recursive_optional_dependencies() + if extras_require: + _update_optional_dependencies(pyproject, extras_require) write_pyproject(pyproject) os.remove(setup_cfg) msg = f"Converted {setup_cfg} configuration to {CONFIG_PATH.pyproject}" raise PrecommitError(msg) +def _get_recursive_optional_dependencies() -> Dict[str, List[Tuple[str, str]]]: + if not CONFIG_PATH.setup_cfg.exists(): + return {} + cfg = RawConfigParser() + cfg.read(CONFIG_PATH.setup_cfg) + section_name = "options.extras_require" + if section_name not in cfg.sections(): + return {} + return { + option: __extract_package_list(cfg.get(section_name, option, raw=True)) + for option in cfg.options(section_name) + } + + +def _update_optional_dependencies( + pyproject: TOMLDocument, extras_require: Dict[str, List[Tuple[str, str]]] +) -> None: + package_name = get_pypi_name(pyproject) + optional_dependencies = tomlkit.table() + for key, packages in extras_require.items(): + package_array = tomlkit.array() + for package, comment in packages: + package = re.sub(r"%\(([^\)]+)\)s", rf"{package_name}[\1]", package) + toml_str = tomlkit.string(package, escape=False, literal='"' in package) + package_array.append(toml_str) + if comment: + __add_comment(package_array, -1, comment) + package_array.multiline(True) + optional_dependencies[key] = package_array + project = get_sub_table(pyproject, "project", create=True) + project["optional-dependencies"] = optional_dependencies + + +def __extract_package_list(raw_content: str) -> List[Tuple[str, str]]: + def split_comment(line: str) -> Tuple[str, str]: + if "#" in line: + return tuple(s.strip() for s in line.split("#", maxsplit=1)) # type: ignore[return-value] + return line.strip(), "" + + raw_content = raw_content.strip() + return [split_comment(s) for s in raw_content.split("\n")] + + +def __add_comment(array: Array, idx: int, comment: str) -> None: # disgusting hack + toml_comment = tomlkit.comment(comment) + toml_comment.indent(1) + toml_comment._trivia = dataclasses.replace(toml_comment._trivia, trail="") + array._value[idx].comment = toml_comment + + def _update_container( old: Union[Container, Table], new: Union[Container, Table] ) -> None: From aa53c37f57cdf7e709cea399f8367072b0e4f1fd Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:03:04 +0200 Subject: [PATCH 08/13] FIX: remove `format-setup-cfg` hook --- src/repoma/check_dev_files/setup_cfg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index a6700d18..75f380f7 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -17,6 +17,7 @@ from repoma.errors import PrecommitError 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 get_pypi_name from repoma.utilities.pyproject import ( get_sub_table, @@ -51,6 +52,7 @@ def _convert_to_pyproject() -> None: _update_optional_dependencies(pyproject, extras_require) write_pyproject(pyproject) os.remove(setup_cfg) + remove_precommit_hook("format-setup-cfg") msg = f"Converted {setup_cfg} configuration to {CONFIG_PATH.pyproject}" raise PrecommitError(msg) From cd028577bd40efb59c9d5402eca0e56a7360befa Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:18:14 +0200 Subject: [PATCH 09/13] MAINT: remove `setup.py` as well --- src/repoma/check_dev_files/setup_cfg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 75f380f7..26e5fb23 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -52,6 +52,8 @@ def _convert_to_pyproject() -> None: _update_optional_dependencies(pyproject, extras_require) write_pyproject(pyproject) os.remove(setup_cfg) + if os.path.exists("setup.py"): + os.remove("setup.py") remove_precommit_hook("format-setup-cfg") msg = f"Converted {setup_cfg} configuration to {CONFIG_PATH.pyproject}" raise PrecommitError(msg) From 25ffb58f6db55f45c504743e7ba961006c890acd Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:48:20 +0200 Subject: [PATCH 10/13] DX: rename Conda environment to `repo-maintenance` --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 45db2944..3061e189 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: repoma +name: repo-maintenance channels: - defaults dependencies: From 362fd291e1cf04cbbf9e3adc7c986450a156a8c2 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:50:11 +0200 Subject: [PATCH 11/13] FIX: downgrade `setuptools` for lower versions of Python --- pyproject.toml | 2 +- src/repoma/check_dev_files/setup_cfg.py | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7fe5002a..3a207f85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] build-backend = "setuptools.build_meta" requires = [ - "setuptools>=61.2", + "setuptools>=58.0", "setuptools_scm", ] diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index 26e5fb23..89119900 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, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import tomlkit from ini2toml.api import Translator @@ -18,7 +18,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 get_pypi_name +from repoma.utilities.project_info import get_pypi_name, get_supported_python_versions from repoma.utilities.pyproject import ( get_sub_table, load_pyproject, @@ -50,6 +50,8 @@ 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"): @@ -59,6 +61,25 @@ 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 {} 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 12/13] 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" From 703db48a68a815ec2028e385de608a2c3c73c998 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 3 Oct 2023 21:02:47 +0200 Subject: [PATCH 13/13] FIX: install `dataclasses` in Python 3.6 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 39be1cf1..3ec67a1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ setup_requires = setuptools_scm install_requires = attrs >=20.1.0 # https://www.attrs.org/en/stable/changelog.html#id82 + dataclasses; python_version<="3.7.0" html2text ini2toml nbformat