From ffc6e30a5dde2b1436a8ceea2fc821c99455c529 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Sun, 3 Dec 2023 10:41:50 +0100 Subject: [PATCH] MAINT: upgrade Python syntax --- docs/conf.py | 5 +- src/repoma/check_dev_files/__init__.py | 8 ++- src/repoma/check_dev_files/citation.py | 13 ++-- src/repoma/check_dev_files/cspell.py | 12 ++-- src/repoma/check_dev_files/github_labels.py | 5 +- .../check_dev_files/github_workflows.py | 39 +++++----- src/repoma/check_dev_files/precommit.py | 18 ++--- src/repoma/check_dev_files/prettier.py | 6 +- src/repoma/check_dev_files/pyright.py | 6 +- src/repoma/check_dev_files/pytest.py | 5 +- src/repoma/check_dev_files/release_drafter.py | 8 ++- src/repoma/check_dev_files/ruff.py | 18 +++-- src/repoma/check_dev_files/setup_cfg.py | 18 ++--- src/repoma/check_dev_files/toml.py | 12 ++-- src/repoma/colab_toc_visible.py | 6 +- src/repoma/fix_nbformat_version.py | 6 +- src/repoma/format_setup_cfg.py | 18 +++-- src/repoma/pin_nb_requirements.py | 12 ++-- src/repoma/self_check.py | 5 +- src/repoma/set_nb_cells.py | 6 +- src/repoma/utilities/__init__.py | 16 +++-- src/repoma/utilities/cfg.py | 26 +++---- src/repoma/utilities/executor.py | 6 +- src/repoma/utilities/precommit.py | 71 ++++++++++--------- src/repoma/utilities/project_info.py | 33 +++++---- src/repoma/utilities/pyproject.py | 32 +++++---- src/repoma/utilities/vscode.py | 20 +++--- src/repoma/utilities/yaml.py | 12 ++-- tests/utilities/test_pyproject.py | 5 +- 29 files changed, 256 insertions(+), 191 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ebd7e0e1..aab54513 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,12 +4,13 @@ documentation: https://www.sphinx-doc.org/en/master/usage/configuration.html """ +from __future__ import annotations + import contextlib import os import shutil import subprocess import sys -from typing import Optional import requests @@ -46,7 +47,7 @@ def generate_api(package: str) -> None: ) -def get_html_logo_path() -> Optional[str]: +def get_html_logo_path() -> str | None: logo_path = "_static/logo.svg" os.makedirs(os.path.dirname(logo_path), exist_ok=True) with contextlib.suppress(requests.exceptions.ConnectionError): diff --git a/src/repoma/check_dev_files/__init__.py b/src/repoma/check_dev_files/__init__.py index 25f3748b..27bcb7e0 100644 --- a/src/repoma/check_dev_files/__init__.py +++ b/src/repoma/check_dev_files/__init__.py @@ -1,8 +1,10 @@ """A collection of scripts that check the file structure of a repository.""" +from __future__ import annotations + import sys from argparse import ArgumentParser -from typing import List, Optional, Sequence +from typing import Sequence from repoma.check_dev_files.deprecated import remove_deprecated_tools from repoma.utilities.executor import Executor @@ -33,7 +35,7 @@ ) -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = _create_argparse() args = parser.parse_args(argv) is_python_repo = not args.no_python @@ -243,7 +245,7 @@ def _create_argparse() -> ArgumentParser: return parser -def _to_list(arg: str) -> List[str]: +def _to_list(arg: str) -> list[str]: """Create a comma-separated list from a string argument. >>> _to_list('a c , test,b') diff --git a/src/repoma/check_dev_files/citation.py b/src/repoma/check_dev_files/citation.py index ec93b7c9..174bc870 100644 --- a/src/repoma/check_dev_files/citation.py +++ b/src/repoma/check_dev_files/citation.py @@ -1,9 +1,10 @@ """Check citation files.""" +from __future__ import annotations + import json import os from textwrap import dedent -from typing import Dict, List, Optional, Set from html2text import HTML2Text from ruamel.yaml import YAML @@ -112,8 +113,8 @@ def _write_citation_cff(citation_cff: CommentedMap) -> None: yaml.dump(citation_cff, stream) -def _get_authors(zenodo: dict) -> Optional[List[Dict[str, str]]]: - creators: Optional[List[Dict[str, str]]] = zenodo.get("creators") +def _get_authors(zenodo: dict) -> list[dict[str, str]] | None: + creators: list[dict[str, str]] | None = zenodo.get("creators") if creators is None: return None return [__convert_author(item) for item in creators] @@ -132,10 +133,10 @@ def __convert_author(creator: dict) -> dict: "family-names": family_name.strip(), "given-names": given_names.strip(), } - affiliation: Optional[str] = creator.get("affiliation") + affiliation: str | None = creator.get("affiliation") if affiliation is not None: author_info["affiliation"] = affiliation - orcid: Optional[str] = creator.get("orcid") + orcid: str | None = creator.get("orcid") if orcid is not None: author_info["orcid"] = f"https://orcid.org/{orcid}" return author_info @@ -160,7 +161,7 @@ def check_citation_keys() -> None: if not citation_cff: msg = f"{CONFIG_PATH.citation} is empty" raise PrecommitError(msg) - existing: Set[str] = set(citation_cff) + existing: set[str] = set(citation_cff) missing_keys = expected - existing if missing_keys: sorted_keys = sorted(missing_keys) diff --git a/src/repoma/check_dev_files/cspell.py b/src/repoma/check_dev_files/cspell.py index fb630d44..dbdb7035 100644 --- a/src/repoma/check_dev_files/cspell.py +++ b/src/repoma/check_dev_files/cspell.py @@ -4,11 +4,12 @@ `_. """ +from __future__ import annotations + import json import os from glob import glob -from pathlib import Path -from typing import Any, Iterable, List, Sequence, Union +from typing import TYPE_CHECKING, Any, Iterable, Sequence from ruamel.yaml.comments import CommentedMap @@ -23,6 +24,9 @@ from repoma.utilities.readme import add_badge, remove_badge from repoma.utilities.vscode import sort_case_insensitive +if TYPE_CHECKING: + from pathlib import Path + __VSCODE_EXTENSION_NAME = "streetsidesoftware.code-spell-checker" # cspell:ignore pelling @@ -202,7 +206,7 @@ def __express_list_of_sections(sections: Sequence[str]) -> str: return sentence -def __get_config(path: Union[str, Path]) -> dict: +def __get_config(path: str | Path) -> dict: with open(path) as stream: return json.load(stream) @@ -213,7 +217,7 @@ def __write_config(config: dict) -> None: stream.write("\n") -def __sort_section(content: Iterable[Any], section_name: str) -> List[str]: +def __sort_section(content: Iterable[Any], section_name: str) -> list[str]: """Sort a list section. >>> __sort_section({"one", "Two"}, section_name="words") diff --git a/src/repoma/check_dev_files/github_labels.py b/src/repoma/check_dev_files/github_labels.py index aca1afd8..5eb8a215 100644 --- a/src/repoma/check_dev_files/github_labels.py +++ b/src/repoma/check_dev_files/github_labels.py @@ -4,9 +4,10 @@ https://github.com/ComPWA/repo-maintenance. """ +from __future__ import annotations + import os import pathlib -from typing import List from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH @@ -48,7 +49,7 @@ def _check_has_labels_requirement(path: pathlib.Path) -> bool: return False -def _get_requirement_files() -> List[pathlib.Path]: +def _get_requirement_files() -> list[pathlib.Path]: return [ *pathlib.Path(".").glob("**/requirements*.in"), *pathlib.Path(".").glob("**/requirements*.txt"), diff --git a/src/repoma/check_dev_files/github_workflows.py b/src/repoma/check_dev_files/github_workflows.py index 9e30a330..bac5caa1 100644 --- a/src/repoma/check_dev_files/github_workflows.py +++ b/src/repoma/check_dev_files/github_workflows.py @@ -1,13 +1,12 @@ """Check :file:`.github/workflows` folder content.""" +from __future__ import annotations + import os import re import shutil -from pathlib import Path -from typing import List, Tuple +from typing import TYPE_CHECKING -from ruamel.yaml.comments import CommentedMap -from ruamel.yaml.main import YAML from ruamel.yaml.scalarstring import DoubleQuotedScalarString from repoma.errors import PrecommitError @@ -22,16 +21,22 @@ ) from repoma.utilities.yaml import create_prettier_round_trip_yaml +if TYPE_CHECKING: + from pathlib import Path + + from ruamel.yaml.comments import CommentedMap + from ruamel.yaml.main import YAML + def main( allow_deprecated: bool, - doc_apt_packages: List[str], + doc_apt_packages: list[str], no_macos: bool, no_pypi: bool, no_version_branches: bool, single_threaded: bool, - skip_tests: List[str], - test_extras: List[str], + skip_tests: list[str], + test_extras: list[str], ) -> None: executor = Executor() executor(_update_cd_workflow, no_pypi, no_version_branches) @@ -86,11 +91,11 @@ def _update_pr_linting() -> None: def _update_ci_workflow( allow_deprecated: bool, - doc_apt_packages: List[str], + doc_apt_packages: list[str], no_macos: bool, single_threaded: bool, - skip_tests: List[str], - test_extras: List[str], + skip_tests: list[str], + test_extras: list[str], ) -> None: def update() -> None: yaml, expected_data = _get_ci_workflow( @@ -128,12 +133,12 @@ def update() -> None: def _get_ci_workflow( path: Path, - doc_apt_packages: List[str], + doc_apt_packages: list[str], no_macos: bool, single_threaded: bool, - skip_tests: List[str], - test_extras: List[str], -) -> Tuple[YAML, dict]: + skip_tests: list[str], + test_extras: list[str], +) -> tuple[YAML, dict]: yaml = create_prettier_round_trip_yaml() config = yaml.load(path) __update_doc_section(config, doc_apt_packages) @@ -142,7 +147,7 @@ def _get_ci_workflow( return yaml, config -def __update_doc_section(config: CommentedMap, apt_packages: List[str]) -> None: +def __update_doc_section(config: CommentedMap, apt_packages: list[str]) -> None: if not os.path.exists("docs/"): del config["jobs"]["doc"] else: @@ -167,8 +172,8 @@ def __update_pytest_section( config: CommentedMap, no_macos: bool, single_threaded: bool, - skip_tests: List[str], - test_extras: List[str], + skip_tests: list[str], + test_extras: list[str], ) -> None: test_dir = "tests" if not os.path.exists(test_dir): diff --git a/src/repoma/check_dev_files/precommit.py b/src/repoma/check_dev_files/precommit.py index 31a452e1..13f6dcab 100644 --- a/src/repoma/check_dev_files/precommit.py +++ b/src/repoma/check_dev_files/precommit.py @@ -1,7 +1,9 @@ """Check content of :code:`.pre-commit-config.yaml` and related files.""" +from __future__ import annotations + from pathlib import Path -from typing import Iterable, List, Optional, Set, Tuple +from typing import Iterable from ruamel.yaml.comments import CommentedMap, CommentedSeq from ruamel.yaml.scalarstring import DoubleQuotedScalarString @@ -25,10 +27,10 @@ def main() -> None: def _sort_hooks() -> None: yaml = create_prettier_round_trip_yaml() contents: CommentedMap = yaml.load(CONFIG_PATH.precommit) - repos: Optional[CommentedSeq] = contents.get("repos") + repos: CommentedSeq | None = contents.get("repos") if repos is None: return - sorted_repos: List[CommentedMap] = sorted(repos, key=__repo_def_sorting) + sorted_repos: list[CommentedMap] = sorted(repos, key=__repo_def_sorting) contents["repos"] = sorted_repos if sorted_repos != repos: yaml.dump(contents, CONFIG_PATH.precommit) @@ -36,7 +38,7 @@ def _sort_hooks() -> None: raise PrecommitError(msg) -def __repo_def_sorting(repo_def: CommentedMap) -> Tuple[int, str]: +def __repo_def_sorting(repo_def: CommentedMap) -> tuple[int, str]: if repo_def["repo"] == "meta": return (0, "meta") hooks: CommentedSeq = repo_def["hooks"] @@ -80,7 +82,7 @@ def __update_precommit_ci_skip(expected_skips: Iterable[str]) -> None: raise PrecommitError(msg) -def __get_precommit_ci_skips(config: PrecommitConfig) -> Set[str]: +def __get_precommit_ci_skips(config: PrecommitConfig) -> set[str]: if config.ci is None: msg = "Pre-commit config does not contain a ci section" raise ValueError(msg) @@ -89,11 +91,11 @@ def __get_precommit_ci_skips(config: PrecommitConfig) -> Set[str]: return set(config.ci.skip) -def get_local_hooks(config: PrecommitConfig) -> List[str]: +def get_local_hooks(config: PrecommitConfig) -> list[str]: return [h.id for r in config.repos for h in r.hooks if r.repo == "local"] -def get_non_functional_hooks(config: PrecommitConfig) -> List[str]: +def get_non_functional_hooks(config: PrecommitConfig) -> list[str]: return [ hook.id for repo in config.repos @@ -131,7 +133,7 @@ def _update_conda_environment(precommit_config: PrecommitConfig) -> None: raise PrecommitError(msg) -def __get_skipped_hooks(config: PrecommitConfig) -> Set[str]: +def __get_skipped_hooks(config: PrecommitConfig) -> set[str]: skipped_hooks = { "check-jsonschema", "pyright", diff --git a/src/repoma/check_dev_files/prettier.py b/src/repoma/check_dev_files/prettier.py index f2988228..84ff4465 100644 --- a/src/repoma/check_dev_files/prettier.py +++ b/src/repoma/check_dev_files/prettier.py @@ -1,7 +1,9 @@ """Check the configuration for `Prettier `_.""" +from __future__ import annotations + import os -from typing import Iterable, List +from typing import Iterable from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH, REPOMA_DIR, vscode @@ -125,7 +127,7 @@ def __insert_expected_paths() -> None: raise PrecommitError(msg) -def __get_existing_lines() -> List[str]: +def __get_existing_lines() -> list[str]: if not os.path.exists(CONFIG_PATH.prettier_ignore): return [""] with open(CONFIG_PATH.prettier_ignore) as f: diff --git a/src/repoma/check_dev_files/pyright.py b/src/repoma/check_dev_files/pyright.py index fbb840ea..d2513e9d 100644 --- a/src/repoma/check_dev_files/pyright.py +++ b/src/repoma/check_dev_files/pyright.py @@ -1,8 +1,10 @@ """Check and update :code:`mypy` settings.""" +from __future__ import annotations + import json import os -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH @@ -46,7 +48,7 @@ def _merge_config_into_pyproject() -> None: def _update_settings() -> None: pyproject = load_pyproject() - pyright_settings: Optional[Table] = pyproject.get("tool", {}).get("pyright") + pyright_settings: Table | None = pyproject.get("tool", {}).get("pyright") if pyright_settings is None: return minimal_settings = { diff --git a/src/repoma/check_dev_files/pytest.py b/src/repoma/check_dev_files/pytest.py index cca6fa5d..32eb3a2e 100644 --- a/src/repoma/check_dev_files/pytest.py +++ b/src/repoma/check_dev_files/pytest.py @@ -1,7 +1,8 @@ """Check and update :code:`pytest` settings.""" +from __future__ import annotations + import os -from typing import Dict import tomlkit from ini2toml.api import Translator @@ -30,7 +31,7 @@ def _merge_coverage_into_pyproject() -> None: section_name = "coverage:run" if not pytest_ini.has_section(section_name): return - coverage_config: Dict = dict(pytest_ini[section_name]) + coverage_config: dict = dict(pytest_ini[section_name]) for key, value in coverage_config.items(): if value in {"False", "True"}: coverage_config[key] = bool(value) diff --git a/src/repoma/check_dev_files/release_drafter.py b/src/repoma/check_dev_files/release_drafter.py index 4e97a490..d2571c7f 100644 --- a/src/repoma/check_dev_files/release_drafter.py +++ b/src/repoma/check_dev_files/release_drafter.py @@ -1,7 +1,9 @@ """Update Release Drafter Action.""" +from __future__ import annotations + import os -from typing import Any, Dict +from typing import Any from repoma.errors import PrecommitError from repoma.utilities import CONFIG_PATH, REPOMA_DIR, update_file @@ -28,7 +30,7 @@ def _update_draft(repo_name: str, repo_title: str) -> None: raise PrecommitError(msg) -def _get_expected_config(repo_name: str, repo_title: str) -> Dict[str, Any]: +def _get_expected_config(repo_name: str, repo_title: str) -> dict[str, Any]: yaml = create_prettier_round_trip_yaml() config = yaml.load(REPOMA_DIR / CONFIG_PATH.release_drafter_config) key = "name-template" @@ -41,6 +43,6 @@ def _get_expected_config(repo_name: str, repo_title: str) -> Dict[str, Any]: return config -def _get_existing_config() -> Dict[str, Any]: +def _get_existing_config() -> dict[str, Any]: yaml = create_prettier_round_trip_yaml() return yaml.load(CONFIG_PATH.release_drafter_config) diff --git a/src/repoma/check_dev_files/ruff.py b/src/repoma/check_dev_files/ruff.py index 7073417a..af3427d5 100644 --- a/src/repoma/check_dev_files/ruff.py +++ b/src/repoma/check_dev_files/ruff.py @@ -1,13 +1,13 @@ """Check `Ruff `_ configuration.""" +from __future__ import annotations + import os from textwrap import dedent -from typing import Iterable, List, Set +from typing import TYPE_CHECKING, Iterable from ruamel.yaml import YAML from ruamel.yaml.comments import CommentedMap -from tomlkit.items import Array, Table -from tomlkit.toml_document import TOMLDocument from repoma.check_dev_files.setup_cfg import ( has_pyproject_build_system, @@ -41,6 +41,10 @@ set_setting, ) +if TYPE_CHECKING: + from tomlkit.items import Array, Table + from tomlkit.toml_document import TOMLDocument + def main(has_notebooks: bool) -> None: executor = Executor() @@ -335,11 +339,11 @@ def __merge(*listings: Iterable[str], enforce_multiline: bool = False) -> Array: return to_toml_array(sorted(merged), enforce_multiline) -def __get_existing_nbqa_ignores(pyproject: TOMLDocument) -> Set[str]: +def __get_existing_nbqa_ignores(pyproject: TOMLDocument) -> set[str]: nbqa_table = get_sub_table(pyproject, "tool.nbqa.addopts", create=True) if not nbqa_table: return set() - ruff_rules: List[str] = nbqa_table.get("ruff", []) + ruff_rules: list[str] = nbqa_table.get("ruff", []) return { r.replace("--extend-ignore=", "") for r in ruff_rules @@ -386,14 +390,14 @@ def __get_selected_ruff_rules() -> Array: def __get_task_tags(ruff_settings: Table) -> Array: - existing: Set[str] = set(ruff_settings.get("task-tags", set())) + existing: set[str] = set(ruff_settings.get("task-tags", set())) expected = { "cspell", } return to_toml_array(sorted(existing | expected)) -def __get_src_directories() -> List[str]: +def __get_src_directories() -> list[str]: expected_directories = ( "src", "tests", diff --git a/src/repoma/check_dev_files/setup_cfg.py b/src/repoma/check_dev_files/setup_cfg.py index c558d3b8..1cdeea14 100644 --- a/src/repoma/check_dev_files/setup_cfg.py +++ b/src/repoma/check_dev_files/setup_cfg.py @@ -1,6 +1,9 @@ """Apply a certain set of standards to the :file:`setup.cfg`.""" # pyright: reportUnknownLambdaType=false + +from __future__ import annotations + import dataclasses import os import re @@ -8,7 +11,6 @@ from collections import defaultdict from configparser import RawConfigParser from copy import deepcopy -from typing import Dict, List, Tuple, Union import tomlkit from ini2toml.api import Translator @@ -70,7 +72,7 @@ def _convert_to_pyproject() -> None: raise PrecommitError(msg) -def _get_recursive_optional_dependencies() -> Dict[str, List[Tuple[str, str]]]: +def _get_recursive_optional_dependencies() -> dict[str, list[tuple[str, str]]]: if not CONFIG_PATH.setup_cfg.exists(): return {} cfg = RawConfigParser() @@ -85,7 +87,7 @@ def _get_recursive_optional_dependencies() -> Dict[str, List[Tuple[str, str]]]: def _update_optional_dependencies( - pyproject: TOMLDocument, extras_require: Dict[str, List[Tuple[str, str]]] + pyproject: TOMLDocument, extras_require: dict[str, list[tuple[str, str]]] ) -> None: package_name = get_pypi_name(pyproject) optional_dependencies = tomlkit.table() @@ -103,8 +105,8 @@ def _update_optional_dependencies( project["optional-dependencies"] = optional_dependencies -def __extract_package_list(raw_content: str) -> List[Tuple[str, str]]: - def split_comment(line: str) -> Tuple[str, str]: +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(), "" @@ -120,9 +122,7 @@ def __add_comment(array: Array, idx: int, comment: str) -> None: # disgusting h array._value[idx].comment = toml_comment -def _update_container( - old: Union[Container, Table], new: Union[Container, Table] -) -> None: +def _update_container(old: Container | Table, new: Container | Table) -> None: for key, value in new.items(): if isinstance(value, (Container, Table)): if key in old: @@ -255,7 +255,7 @@ def _remove_empty_tables() -> None: raise PrecommitError(msg) -def __recursive_remove_empty_tables(table: Union[Container, Table]) -> bool: +def __recursive_remove_empty_tables(table: Container | Table) -> bool: updated = False items = list(table.items()) for key, value in items: diff --git a/src/repoma/check_dev_files/toml.py b/src/repoma/check_dev_files/toml.py index de482c14..34b1ac0f 100644 --- a/src/repoma/check_dev_files/toml.py +++ b/src/repoma/check_dev_files/toml.py @@ -1,10 +1,11 @@ """Configuration for working with TOML files.""" +from __future__ import annotations + import os import shutil from glob import glob -from pathlib import Path -from typing import List, Union +from typing import TYPE_CHECKING import tomlkit from ruamel.yaml import YAML @@ -21,10 +22,13 @@ write_pyproject, ) +if TYPE_CHECKING: + from pathlib import Path + __INCORRECT_TAPLO_CONFIG_PATHS = [ "taplo.toml", ] -__TRIGGER_FILES: List[Union[Path, str]] = [ +__TRIGGER_FILES: list[Path | str] = [ "pyproject.toml", CONFIG_PATH.taplo, *__INCORRECT_TAPLO_CONFIG_PATHS, @@ -104,7 +108,7 @@ def _update_taplo_config() -> None: raise PrecommitError(msg) with open(template_path) as f: expected = tomlkit.load(f) - excludes: List[str] = [p for p in expected["exclude"] if glob(p, recursive=True)] # type: ignore[union-attr] + excludes: list[str] = [p for p in expected["exclude"] if glob(p, recursive=True)] # type: ignore[union-attr] if excludes: excludes = sorted(excludes, key=str.lower) expected["exclude"] = to_toml_array(excludes, enforce_multiline=True) diff --git a/src/repoma/colab_toc_visible.py b/src/repoma/colab_toc_visible.py index f48d831a..e44d4797 100644 --- a/src/repoma/colab_toc_visible.py +++ b/src/repoma/colab_toc_visible.py @@ -4,9 +4,11 @@ for more information. """ +from __future__ import annotations + import argparse import sys -from typing import Optional, Sequence +from typing import Sequence import nbformat @@ -16,7 +18,7 @@ from .utilities.executor import Executor -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(__doc__) parser.add_argument( "filenames", diff --git a/src/repoma/fix_nbformat_version.py b/src/repoma/fix_nbformat_version.py index 26559479..fa122b35 100644 --- a/src/repoma/fix_nbformat_version.py +++ b/src/repoma/fix_nbformat_version.py @@ -4,10 +4,12 @@ diffs. The solution is to set the version to v4 and removes those cell ids. """ +from __future__ import annotations + import argparse import sys from textwrap import dedent -from typing import Optional, Sequence +from typing import Sequence import nbformat @@ -23,7 +25,7 @@ ] -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(__doc__) parser.add_argument("filenames", nargs="*", help="Filenames to fix.") args = parser.parse_args(argv) diff --git a/src/repoma/format_setup_cfg.py b/src/repoma/format_setup_cfg.py index 84625f98..8b82f7dc 100644 --- a/src/repoma/format_setup_cfg.py +++ b/src/repoma/format_setup_cfg.py @@ -1,17 +1,21 @@ """Format :code:`setup.cfg` if available.""" +from __future__ import annotations + import argparse -import io import re import sys -from configparser import ConfigParser -from pathlib import Path -from typing import Optional, Sequence, Union +from typing import TYPE_CHECKING, Sequence from repoma.utilities import CONFIG_PATH from repoma.utilities.cfg import format_config from repoma.utilities.project_info import open_setup_cfg +if TYPE_CHECKING: + import io + from configparser import ConfigParser + from pathlib import Path + def format_setup_cfg() -> None: cfg = open_setup_cfg() @@ -28,8 +32,8 @@ def write_formatted_setup_cfg(cfg: ConfigParser) -> None: def _format_setup_cfg( - input: Union[Path, io.TextIOBase, str], # noqa: A002 - output: Union[Path, io.TextIOBase, str], + input: Path | (io.TextIOBase | str), # noqa: A002 + output: Path | (io.TextIOBase | str), ) -> None: def format_version_constraints(content: str) -> str: content = re.sub(r"(>=?|<=?|==)\s+", r"\1", content) @@ -45,7 +49,7 @@ def format_version_constraints(content: str) -> str: ) -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(__doc__) parser.add_argument("filenames", nargs="*", help="Filenames to check.") args = parser.parse_args(argv) diff --git a/src/repoma/pin_nb_requirements.py b/src/repoma/pin_nb_requirements.py index f639b604..6d5430cd 100644 --- a/src/repoma/pin_nb_requirements.py +++ b/src/repoma/pin_nb_requirements.py @@ -6,12 +6,14 @@ comply with the expected formatting. """ +from __future__ import annotations + import argparse import re import sys from functools import lru_cache from textwrap import dedent -from typing import List, Optional, Sequence +from typing import Sequence import nbformat from nbformat import NotebookNode @@ -63,7 +65,7 @@ def __to_oneline(source: str) -> str: @lru_cache(maxsize=1) -def extract_pip_requirements(source: str) -> Optional[List[str]]: +def extract_pip_requirements(source: str) -> list[str] | None: r"""Check if the source in a cell is a pip install statement. >>> extract_pip_requirements("Not a pip install statement") @@ -99,7 +101,7 @@ def extract_pip_requirements(source: str) -> Optional[List[str]]: return [p for p in packages if p] -def _check_pip_requirements(filename: str, requirements: List[str]) -> None: +def _check_pip_requirements(filename: str, requirements: list[str]) -> None: if len(requirements) == 0: msg = f'At least one dependency required in install cell of "{filename}"' raise PrecommitError(msg) @@ -170,12 +172,12 @@ def _update_metadata(filename: str, metadata: dict, notebook: NotebookNode) -> N raise PrecommitError(msg) -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(__doc__) parser.add_argument("filenames", nargs="*", help="Filenames to check.") args = parser.parse_args(argv) - errors: List[PrecommitError] = [] + errors: list[PrecommitError] = [] for filename in args.filenames: try: check_pinned_requirements(filename) diff --git a/src/repoma/self_check.py b/src/repoma/self_check.py index ebc85126..ac4cd7df 100644 --- a/src/repoma/self_check.py +++ b/src/repoma/self_check.py @@ -1,9 +1,10 @@ """Checks to be performed locally on the ComPWA/repo-maintenance repository.""" +from __future__ import annotations + from functools import lru_cache from io import StringIO from textwrap import dedent, indent -from typing import Dict import yaml @@ -48,7 +49,7 @@ def _to_dict(hook: Hook) -> dict: @lru_cache(maxsize=None) -def _load_precommit_hook_definitions() -> Dict[str, Hook]: +def _load_precommit_hook_definitions() -> dict[str, Hook]: with open(__HOOK_DEFINITION_FILE) as f: hook_definitions = yaml.load(f, Loader=yaml.SafeLoader) hooks = [fromdict(h, Hook) for h in hook_definitions] diff --git a/src/repoma/set_nb_cells.py b/src/repoma/set_nb_cells.py index 30d4e3fe..0033feca 100644 --- a/src/repoma/set_nb_cells.py +++ b/src/repoma/set_nb_cells.py @@ -18,11 +18,13 @@ """ +from __future__ import annotations + import argparse import sys from functools import lru_cache from textwrap import dedent -from typing import Optional, Sequence +from typing import Sequence import nbformat @@ -50,7 +52,7 @@ } -def main(argv: Optional[Sequence[str]] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(__doc__) parser.add_argument("filenames", nargs="*", help="Filenames to check.") parser.add_argument( diff --git a/src/repoma/utilities/__init__.py b/src/repoma/utilities/__init__.py index c3290567..bbbb3031 100644 --- a/src/repoma/utilities/__init__.py +++ b/src/repoma/utilities/__init__.py @@ -1,5 +1,7 @@ """Collection of helper functions that are shared by all sub-hooks.""" +from __future__ import annotations + import hashlib import io import os @@ -7,7 +9,7 @@ import shutil from pathlib import Path from shutil import copyfile -from typing import List, NamedTuple, Union +from typing import NamedTuple import repoma from repoma.errors import PrecommitError @@ -42,7 +44,7 @@ class _ConfigFilePaths(NamedTuple): REPOMA_DIR = Path(repoma.__file__).parent.absolute() -def hash_file(path: Union[Path, str]) -> str: +def hash_file(path: Path | str) -> str: # https://stackoverflow.com/a/22058673 buffer_size = 65_536 sha256 = hashlib.sha256() @@ -55,7 +57,7 @@ def hash_file(path: Union[Path, str]) -> str: return sha256.hexdigest() -def read(input: Union[Path, io.TextIOBase, str]) -> str: # noqa: A002 +def read(input: Path | (io.TextIOBase | str)) -> str: # noqa: A002 if isinstance(input, (Path, str)): with open(input) as input_stream: return input_stream.read() @@ -65,7 +67,7 @@ def read(input: Union[Path, io.TextIOBase, str]) -> str: # noqa: A002 raise TypeError(msg) -def write(content: str, target: Union[Path, io.TextIOBase, str]) -> None: +def write(content: str, target: Path | (io.TextIOBase | str)) -> None: if isinstance(target, str): target = Path(target) if isinstance(target, Path): @@ -79,7 +81,7 @@ def write(content: str, target: Union[Path, io.TextIOBase, str]) -> None: raise TypeError(msg) -def remove_configs(paths: List[str]) -> None: +def remove_configs(paths: list[str]) -> None: executor = Executor() for path in paths: executor(__remove_file, path) @@ -120,7 +122,7 @@ def rename_file(old: str, new: str) -> None: raise PrecommitError(msg) -def natural_sorting(text: str) -> List[Union[float, str]]: +def natural_sorting(text: str) -> list[float | str]: # https://stackoverflow.com/a/5967539/13219025 return [ __attempt_number_cast(c) @@ -148,7 +150,7 @@ def update_file(relative_path: Path, in_template_folder: bool = False) -> None: raise PrecommitError(msg) -def __attempt_number_cast(text: str) -> Union[float, str]: +def __attempt_number_cast(text: str) -> float | str: try: return float(text) except ValueError: diff --git a/src/repoma/utilities/cfg.py b/src/repoma/utilities/cfg.py index 1d1ac9ba..ff7ec208 100644 --- a/src/repoma/utilities/cfg.py +++ b/src/repoma/utilities/cfg.py @@ -1,11 +1,13 @@ """Helper functions for formatting :file:`.cfg` files.""" +from __future__ import annotations + import io import re from configparser import ConfigParser from copy import deepcopy from pathlib import Path -from typing import Callable, Iterable, List, Optional, Tuple, Union +from typing import Callable, Iterable from repoma.errors import PrecommitError @@ -13,9 +15,9 @@ def extract_config_section( - extract_from: Union[Path, str], - extract_to: Union[Path, str], - sections: List[str], + extract_from: Path | str, + extract_to: Path | str, + sections: list[str], ) -> None: cfg = open_config(extract_from) if any(map(cfg.has_section, sections)): @@ -30,8 +32,8 @@ def extract_config_section( def __split_config( - cfg: ConfigParser, extracted_sections: List[str] -) -> Tuple[ConfigParser, ConfigParser]: + cfg: ConfigParser, extracted_sections: list[str] +) -> tuple[ConfigParser, ConfigParser]: old_config = deepcopy(cfg) extracted_config = deepcopy(cfg) for section in cfg.sections(): @@ -42,16 +44,16 @@ def __split_config( return old_config, extracted_config -def __write_config(cfg: ConfigParser, output_path: Union[Path, str]) -> None: +def __write_config(cfg: ConfigParser, output_path: Path | str) -> None: with open(output_path, "w") as stream: cfg.write(stream) format_config(input=output_path, output=output_path) def format_config( - input: Union[Path, io.TextIOBase, str], # noqa: A002 - output: Union[Path, io.TextIOBase, str], - additional_rules: Optional[Iterable[Callable[[str], str]]] = None, + input: Path | (io.TextIOBase | str), # noqa: A002 + output: Path | (io.TextIOBase | str), + additional_rules: Iterable[Callable[[str], str]] | None = None, ) -> None: content = read(input) indent_size = 4 @@ -73,7 +75,7 @@ def format_config( write(content, target=output) -def open_config(definition: Union[Path, io.TextIOBase, str]) -> ConfigParser: +def open_config(definition: Path | (io.TextIOBase | str)) -> ConfigParser: cfg = ConfigParser() if isinstance(definition, io.TextIOBase): text = definition.read() @@ -96,7 +98,7 @@ def open_config(definition: Union[Path, io.TextIOBase, str]) -> ConfigParser: return cfg -def write_config(cfg: ConfigParser, output: Union[Path, io.TextIOBase, str]) -> None: +def write_config(cfg: ConfigParser, output: Path | (io.TextIOBase | str)) -> None: if isinstance(output, io.TextIOBase): cfg.write(output) elif isinstance(output, (Path, str)): diff --git a/src/repoma/utilities/executor.py b/src/repoma/utilities/executor.py index 3e835489..7a668de2 100644 --- a/src/repoma/utilities/executor.py +++ b/src/repoma/utilities/executor.py @@ -1,6 +1,8 @@ """Collect `.PrecommitError` instances from several executed functions.""" -from typing import Any, Callable, List +from __future__ import annotations + +from typing import Any, Callable import attr @@ -11,7 +13,7 @@ class Executor: """Execute functions and collect any `.PrecommitError` exceptions.""" - error_messages: List[str] = attr.ib(factory=list, init=False) + error_messages: list[str] = attr.ib(factory=list, init=False) def __call__(self, function: Callable, *args: Any, **kwargs: Any) -> None: try: diff --git a/src/repoma/utilities/precommit.py b/src/repoma/utilities/precommit.py index 5157130e..09bae52b 100644 --- a/src/repoma/utilities/precommit.py +++ b/src/repoma/utilities/precommit.py @@ -1,18 +1,17 @@ """Helper functions for modifying :file:`.pre-commit.config.yaml`.""" +from __future__ import annotations + import os.path import re import socket from functools import lru_cache -from pathlib import Path -from typing import Any, List, Optional, Tuple, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar import attrs import yaml from attrs import define, field from pre_commit.commands.autoupdate import autoupdate as precommit_autoupdate -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap, CommentedSeq from ruamel.yaml.scalarstring import PlainScalarString from repoma.errors import PrecommitError @@ -20,10 +19,16 @@ from . import CONFIG_PATH from .yaml import create_prettier_round_trip_yaml +if TYPE_CHECKING: + from pathlib import Path + + from ruamel.yaml import YAML + from ruamel.yaml.comments import CommentedMap, CommentedSeq + def load_round_trip_precommit_config( path: Path = CONFIG_PATH.precommit, -) -> Tuple[CommentedMap, YAML]: +) -> tuple[CommentedMap, YAML]: yaml_parser = create_prettier_round_trip_yaml() config = yaml_parser.load(path) return config, yaml_parser @@ -31,7 +36,7 @@ def load_round_trip_precommit_config( def find_repo( config: CommentedMap, search_pattern: str -) -> Optional[Tuple[int, CommentedMap]]: +) -> tuple[int, CommentedMap] | None: """Find pre-commit hook definition and its index in pre-commit config.""" repos: CommentedSeq = config.get("repos", []) for i, repo in enumerate(repos): @@ -41,7 +46,7 @@ def find_repo( return None -def remove_precommit_hook(hook_id: str, repo_url: Optional[str] = None) -> None: +def remove_precommit_hook(hook_id: str, repo_url: str | None = None) -> None: config, yaml_parser = load_round_trip_precommit_config() repo_and_hook_idx = __find_repo_and_hook_idx(config, hook_id, repo_url) if repo_and_hook_idx is None: @@ -59,8 +64,8 @@ def remove_precommit_hook(hook_id: str, repo_url: Optional[str] = None) -> None: def __find_repo_and_hook_idx( - config: CommentedMap, hook_id: str, repo_url: Optional[str] = None -) -> Optional[Tuple[int, int]]: + config: CommentedMap, hook_id: str, repo_url: str | None = None +) -> tuple[int, int] | None: repos: CommentedSeq = config.get("repos", []) for repo_idx, repo in enumerate(repos): if repo_url is not None and repo.get("repo") != repo_url: @@ -170,7 +175,7 @@ def update_precommit_hook(repo_url: str, expected_hook: CommentedMap) -> None: raise PrecommitError(msg) -def __find_hook_idx(hooks: CommentedSeq, hook_id: str) -> Optional[int]: +def __find_hook_idx(hooks: CommentedSeq, hook_id: str) -> int | None: msg = "" for i, hook in enumerate(hooks): msg += " " + hook["id"] @@ -194,7 +199,7 @@ class PrecommitCi: autofix_prs: bool = True autoupdate_commit_msg: str = "[pre-commit.ci] pre-commit autoupdate" autoupdate_schedule: str = "weekly" - skip: Optional[List[str]] = None + skip: list[str] | None = None submodules: bool = False @@ -203,20 +208,20 @@ class Hook: """https://pre-commit.com/#pre-commit-configyaml---hooks.""" id: str # noqa: A003 - name: Optional[str] = None - description: Optional[str] = None - entry: Optional[str] = None - alias: Optional[str] = None - additional_dependencies: List[str] = field(factory=list) - args: List[str] = field(factory=list) - files: Optional[str] = None - exclude: Optional[str] = None - types: Optional[List[str]] = None + name: str | None = None + description: str | None = None + entry: str | None = None + alias: str | None = None + additional_dependencies: list[str] = field(factory=list) + args: list[str] = field(factory=list) + files: str | None = None + exclude: str | None = None + types: list[str] | None = None require_serial: bool = False - language: Optional[str] = None - always_run: Optional[bool] = None - pass_filenames: Optional[bool] = None - types_or: Optional[List[str]] = None + language: str | None = None + always_run: bool | None = None + pass_filenames: bool | None = None + types_or: list[str] | None = None @define @@ -224,10 +229,10 @@ class Repo: """https://pre-commit.com/#pre-commit-configyaml---repos.""" repo: str - hooks: List[Hook] - rev: Optional[str] = None + hooks: list[Hook] + rev: str | None = None - def get_hook_index(self, hook_id: str) -> Optional[int]: + def get_hook_index(self, hook_id: str) -> int | None: for i, hook in enumerate(self.hooks): if hook.id == hook_id: return i @@ -238,14 +243,14 @@ def get_hook_index(self, hook_id: str) -> Optional[int]: class PrecommitConfig: """https://pre-commit.com/#pre-commit-configyaml---top-level.""" - repos: List[Repo] - ci: Optional[PrecommitCi] = None + repos: list[Repo] + ci: PrecommitCi | None = None files: str = "" exclude: str = "^$" fail_fast: bool = False @classmethod - def load(cls, path: Union[Path, str] = CONFIG_PATH.precommit) -> "PrecommitConfig": + def load(cls, path: Path | str = CONFIG_PATH.precommit) -> PrecommitConfig: if not os.path.exists(path): msg = f"This repository contains no {path}" raise PrecommitError(msg) @@ -253,14 +258,14 @@ def load(cls, path: Union[Path, str] = CONFIG_PATH.precommit) -> "PrecommitConfi definition = yaml.safe_load(stream) return fromdict(definition, PrecommitConfig) - def find_repo(self, search_pattern: str) -> Optional[Repo]: + def find_repo(self, search_pattern: str) -> Repo | None: for repo in self.repos: url = repo.repo if re.search(search_pattern, url): return repo return None - def get_repo_index(self, search_pattern: str) -> Optional[int]: + def get_repo_index(self, search_pattern: str) -> int | None: for i, repo in enumerate(self.repos): url = repo.repo if re.search(search_pattern, url): @@ -279,7 +284,7 @@ def asdict(inst: Any) -> dict: ) -def fromdict(definition: dict, typ: Type[T]) -> T: +def fromdict(definition: dict, typ: type[T]) -> T: if typ in {Hook, PrecommitCi}: return typ(**definition) # type: ignore[return-value] if typ is Repo: diff --git a/src/repoma/utilities/project_info.py b/src/repoma/utilities/project_info.py index 47cd0b11..5e4e37fe 100644 --- a/src/repoma/utilities/project_info.py +++ b/src/repoma/utilities/project_info.py @@ -1,13 +1,13 @@ """Helper functions for reading from and writing to :file:`setup.cfg`.""" +from __future__ import annotations + import os import sys -from configparser import ConfigParser from textwrap import dedent -from typing import Dict, List, Optional +from typing import TYPE_CHECKING from attrs import field, frozen -from tomlkit import TOMLDocument from repoma.errors import PrecommitError from repoma.utilities.pyproject import get_sub_table, load_pyproject @@ -15,6 +15,11 @@ from . import CONFIG_PATH from .cfg import open_config +if TYPE_CHECKING: + from configparser import ConfigParser + + from tomlkit import TOMLDocument + if sys.version_info >= (3, 8): from typing import Literal else: @@ -26,9 +31,9 @@ @frozen class ProjectInfo: - name: Optional[str] = None - supported_python_versions: Optional[List[PythonVersion]] = None - urls: Dict[str, str] = field(factory=dict) + name: str | None = None + supported_python_versions: list[PythonVersion] | None = None + urls: dict[str, str] = field(factory=dict) def is_empty(self) -> bool: return ( @@ -38,7 +43,7 @@ def is_empty(self) -> bool: ) @staticmethod - def from_pyproject_toml(pyproject: TOMLDocument) -> "ProjectInfo": + def from_pyproject_toml(pyproject: TOMLDocument) -> ProjectInfo: if "project" not in pyproject: return ProjectInfo() project = get_sub_table(pyproject, "project") @@ -51,7 +56,7 @@ def from_pyproject_toml(pyproject: TOMLDocument) -> "ProjectInfo": ) @staticmethod - def from_setup_cfg(cfg: ConfigParser) -> "ProjectInfo": + def from_setup_cfg(cfg: ConfigParser) -> ProjectInfo: if not cfg.has_section("metadata"): return ProjectInfo() metadata = dict(cfg.items("metadata")) @@ -73,7 +78,7 @@ def from_setup_cfg(cfg: ConfigParser) -> "ProjectInfo": ) -def get_project_info(pyproject: Optional[TOMLDocument] = None) -> ProjectInfo: +def get_project_info(pyproject: TOMLDocument | None = None) -> ProjectInfo: if pyproject is not None or os.path.exists(CONFIG_PATH.pyproject): if pyproject is None: pyproject = load_pyproject() @@ -89,7 +94,7 @@ def get_project_info(pyproject: Optional[TOMLDocument] = None) -> ProjectInfo: raise PrecommitError(msg) -def _extract_python_versions(classifiers: List[str]) -> Optional[List[PythonVersion]]: +def _extract_python_versions(classifiers: list[str]) -> list[PythonVersion] | None: identifier = "Programming Language :: Python :: 3." version_classifiers = [s for s in classifiers if s.startswith(identifier)] if not version_classifiers: @@ -98,7 +103,7 @@ def _extract_python_versions(classifiers: List[str]) -> Optional[List[PythonVers return [s.replace(prefix, "") for s in version_classifiers] # type: ignore[misc] -def get_pypi_name(pyproject: Optional[TOMLDocument] = None) -> str: +def get_pypi_name(pyproject: TOMLDocument | None = None) -> str: """Extract package name for PyPI from :file:`setup.cfg`. >>> get_pypi_name() @@ -115,8 +120,8 @@ def get_pypi_name(pyproject: Optional[TOMLDocument] = None) -> str: def get_supported_python_versions( - pyproject: Optional[TOMLDocument] = None, -) -> List[PythonVersion]: + pyproject: TOMLDocument | None = None, +) -> list[PythonVersion]: """Extract supported Python versions from package classifiers. >>> get_supported_python_versions() @@ -129,7 +134,7 @@ def get_supported_python_versions( return project_info.supported_python_versions -def get_repo_url(pyproject: Optional[TOMLDocument] = None) -> str: +def get_repo_url(pyproject: TOMLDocument | None = None) -> str: project_info = get_project_info(pyproject) if not project_info.urls: msg = dedent(""" diff --git a/src/repoma/utilities/pyproject.py b/src/repoma/utilities/pyproject.py index 1a3431ea..7abad9e7 100644 --- a/src/repoma/utilities/pyproject.py +++ b/src/repoma/utilities/pyproject.py @@ -1,13 +1,13 @@ """Tools for loading, inspecting, and updating :code:`pyproject.toml`.""" +from __future__ import annotations + import io from collections import abc from pathlib import Path -from typing import IO, Any, Iterable, List, Optional, Sequence, Set, Union +from typing import IO, TYPE_CHECKING, Any, Iterable, Sequence import tomlkit -from tomlkit.container import Container -from tomlkit.items import Array, Table from tomlkit.toml_document import TOMLDocument from repoma.errors import PrecommitError @@ -15,12 +15,16 @@ from repoma.utilities.executor import Executor from repoma.utilities.precommit import find_repo, load_round_trip_precommit_config +if TYPE_CHECKING: + from tomlkit.container import Container + from tomlkit.items import Array, Table + def add_dependency( # noqa: C901, PLR0912 package: str, - optional_key: Optional[Union[str, Sequence[str]]] = None, - source: Union[IO, Path, TOMLDocument, str] = CONFIG_PATH.pyproject, - target: Optional[Union[IO, Path, str]] = None, + optional_key: str | Sequence[str] | None = None, + source: IO | (Path | (TOMLDocument | str)) = CONFIG_PATH.pyproject, + target: IO | (Path | str) | None = None, ) -> None: if isinstance(source, TOMLDocument): pyproject = source @@ -33,7 +37,7 @@ def add_dependency( # noqa: C901, PLR0912 target = source if optional_key is None: project = get_sub_table(pyproject, "project", create=True) - existing_dependencies: Set[str] = set(project.get("dependencies", [])) + existing_dependencies: set[str] = set(project.get("dependencies", [])) if package in existing_dependencies: return existing_dependencies.add(package) @@ -73,7 +77,7 @@ def add_dependency( # noqa: C901, PLR0912 raise PrecommitError(msg) -def _sort_taplo(items: Iterable[str]) -> List[str]: +def _sort_taplo(items: Iterable[str]) -> list[str]: return sorted(items, key=lambda s: ('"' in s, s)) @@ -81,9 +85,7 @@ def complies_with_subset(settings: dict, minimal_settings: dict) -> bool: return all(settings.get(key) == value for key, value in minimal_settings.items()) -def load_pyproject( - source: Union[IO, Path, str] = CONFIG_PATH.pyproject -) -> TOMLDocument: +def load_pyproject(source: IO | (Path | str) = CONFIG_PATH.pyproject) -> TOMLDocument: if isinstance(source, io.IOBase): source.seek(0) return tomlkit.load(source) @@ -97,8 +99,8 @@ def load_pyproject( def get_package_name( - source: Union[IO, Path, TOMLDocument, str] = CONFIG_PATH.pyproject -) -> Optional[str]: + source: IO | (Path | (TOMLDocument | str)) = CONFIG_PATH.pyproject, +) -> str | None: if isinstance(source, TOMLDocument): pyproject = source else: @@ -111,7 +113,7 @@ def get_package_name( def get_package_name_safe( - source: Union[IO, Path, TOMLDocument, str] = CONFIG_PATH.pyproject + source: IO | (Path | (TOMLDocument | str)) = CONFIG_PATH.pyproject, ) -> str: package_name = get_package_name(source) if package_name is None: @@ -138,7 +140,7 @@ def get_sub_table(config: Container, dotted_header: str, create: bool = False) - def write_pyproject( - config: TOMLDocument, target: Union[IO, Path, str] = CONFIG_PATH.pyproject + config: TOMLDocument, target: IO | (Path | str) = CONFIG_PATH.pyproject ) -> None: if isinstance(target, io.IOBase): target.seek(0) diff --git a/src/repoma/utilities/vscode.py b/src/repoma/utilities/vscode.py index fd0f073f..4a9bf8da 100644 --- a/src/repoma/utilities/vscode.py +++ b/src/repoma/utilities/vscode.py @@ -1,32 +1,30 @@ """Helper functions for modifying a VSCode configuration.""" +from __future__ import annotations + import collections import json -import sys from collections import abc from copy import deepcopy -from pathlib import Path -from typing import Dict, Iterable, List, TypeVar, Union, overload +from typing import TYPE_CHECKING, Iterable, OrderedDict, TypeVar, overload from repoma.errors import PrecommitError from repoma.utilities.executor import Executor from . import CONFIG_PATH -if sys.version_info < (3, 7): - from typing_extensions import OrderedDict -else: - from typing import OrderedDict +if TYPE_CHECKING: + from pathlib import Path -def remove_setting(key: Union[str, dict]) -> None: +def remove_setting(key: str | dict) -> None: old = __load_config(CONFIG_PATH.vscode_settings, create=True) new = deepcopy(old) _recursive_remove_setting(key, new) _update_settings(old, new) -def _recursive_remove_setting(nested_keys: Union[str, dict], settings: dict) -> None: +def _recursive_remove_setting(nested_keys: str | dict, settings: dict) -> None: if isinstance(nested_keys, str) and nested_keys in settings: settings.pop(nested_keys) elif isinstance(nested_keys, dict): @@ -127,11 +125,11 @@ def __dump_config(config: dict, path: Path) -> None: @overload -def sort_case_insensitive(dct: Dict[K, V]) -> OrderedDict[K, V]: ... # type: ignore[misc] +def sort_case_insensitive(dct: dict[K, V]) -> OrderedDict[K, V]: ... # type: ignore[misc] @overload def sort_case_insensitive(dct: str) -> str: ... # type: ignore[misc] @overload -def sort_case_insensitive(dct: Iterable[K]) -> List[K]: ... # type: ignore[misc] +def sort_case_insensitive(dct: Iterable[K]) -> list[K]: ... # type: ignore[misc] @overload def sort_case_insensitive(dct: K) -> K: ... def sort_case_insensitive(dct): # type: ignore[no-untyped-def] diff --git a/src/repoma/utilities/yaml.py b/src/repoma/utilities/yaml.py index 8a42584a..0ffec72f 100644 --- a/src/repoma/utilities/yaml.py +++ b/src/repoma/utilities/yaml.py @@ -1,17 +1,21 @@ """Helper functions for reading and writing to YAML files.""" -from pathlib import Path -from typing import Optional, Union +from __future__ import annotations + +from typing import TYPE_CHECKING import yaml from ruamel.yaml import YAML +if TYPE_CHECKING: + from pathlib import Path + class _IncreasedYamlIndent(yaml.Dumper): def increase_indent(self, flow: bool = False, indentless: bool = False) -> None: return super().increase_indent(flow, indentless=False) - def write_line_break(self, data: Optional[str] = None) -> None: + def write_line_break(self, data: str | None = None) -> None: """See https://stackoverflow.com/a/44284819.""" super().write_line_break(data) if len(self.indents) == 1: @@ -27,7 +31,7 @@ def create_prettier_round_trip_yaml() -> YAML: return yaml_parser -def write_yaml(definition: dict, output_path: Union[Path, str]) -> None: +def write_yaml(definition: dict, output_path: Path | str) -> None: """Write a `dict` to disk with standardized YAML formatting.""" with open(output_path, "w") as stream: yaml.dump( diff --git a/tests/utilities/test_pyproject.py b/tests/utilities/test_pyproject.py index 48656af0..0f35dbb9 100644 --- a/tests/utilities/test_pyproject.py +++ b/tests/utilities/test_pyproject.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import io from pathlib import Path from textwrap import dedent, indent -from typing import Optional import pytest from tomlkit.items import Table @@ -136,7 +137,7 @@ def test_get_package_name_safe(): @pytest.mark.parametrize("path", [None, REPOMA_DIR / "pyproject.toml"]) -def test_load_pyproject(path: Optional[Path]): +def test_load_pyproject(path: Path | None): if path is None: pyproject = load_pyproject() else: