From 38c1a33cf0c27f8d67169ad08382cd44ee82887a Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:19:25 -0400 Subject: [PATCH 01/32] refactor(provider): split providers into separate files for maintainability closes #812 --- commitizen/providers.py | 319 -------------------- commitizen/providers/__init__.py | 28 ++ commitizen/providers/base_provider.py | 91 ++++++ commitizen/providers/cargo_provider.py | 31 ++ commitizen/providers/commitizen_provider.py | 16 + commitizen/providers/composer_provider.py | 13 + commitizen/providers/npm_provider.py | 83 +++++ commitizen/providers/pep621_provider.py | 12 + commitizen/providers/poetry_provider.py | 20 ++ commitizen/providers/scm_provider.py | 72 +++++ pyproject.toml | 14 +- 11 files changed, 373 insertions(+), 326 deletions(-) delete mode 100644 commitizen/providers.py create mode 100644 commitizen/providers/__init__.py create mode 100644 commitizen/providers/base_provider.py create mode 100644 commitizen/providers/cargo_provider.py create mode 100644 commitizen/providers/commitizen_provider.py create mode 100644 commitizen/providers/composer_provider.py create mode 100644 commitizen/providers/npm_provider.py create mode 100644 commitizen/providers/pep621_provider.py create mode 100644 commitizen/providers/poetry_provider.py create mode 100644 commitizen/providers/scm_provider.py diff --git a/commitizen/providers.py b/commitizen/providers.py deleted file mode 100644 index df353bb365..0000000000 --- a/commitizen/providers.py +++ /dev/null @@ -1,319 +0,0 @@ -from __future__ import annotations - -import json -import re -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, Callable, ClassVar, cast - -import importlib_metadata as metadata -import tomlkit - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.git import get_tags -from commitizen.version_schemes import get_version_scheme - -PROVIDER_ENTRYPOINT = "commitizen.provider" -DEFAULT_PROVIDER = "commitizen" - - -class VersionProvider(ABC): - """ - Abstract base class for version providers. - - Each version provider should inherit and implement this class. - """ - - config: BaseConfig - - def __init__(self, config: BaseConfig): - self.config = config - - @abstractmethod - def get_version(self) -> str: - """ - Get the current version - """ - - @abstractmethod - def set_version(self, version: str): - """ - Set the new current version - """ - - -class CommitizenProvider(VersionProvider): - """ - Default version provider: Fetch and set version in commitizen config. - """ - - def get_version(self) -> str: - return self.config.settings["version"] # type: ignore - - def set_version(self, version: str): - self.config.set_key("version", version) - - -class FileProvider(VersionProvider): - """ - Base class for file-based version providers - """ - - filename: ClassVar[str] - - @property - def file(self) -> Path: - return Path() / self.filename - - -class TomlProvider(FileProvider): - """ - Base class for TOML-based version providers - """ - - def get_version(self) -> str: - document = tomlkit.parse(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = tomlkit.parse(self.file.read_text()) - self.set(document, version) - self.file.write_text(tomlkit.dumps(document)) - - def get(self, document: tomlkit.TOMLDocument) -> str: - return document["project"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - document["project"]["version"] = version # type: ignore - - -class Pep621Provider(TomlProvider): - """ - PEP-621 version management - """ - - filename = "pyproject.toml" - - -class PoetryProvider(TomlProvider): - """ - Poetry version management - """ - - filename = "pyproject.toml" - - def get(self, pyproject: tomlkit.TOMLDocument) -> str: - return pyproject["tool"]["poetry"]["version"] # type: ignore - - def set(self, pyproject: tomlkit.TOMLDocument, version: str): - pyproject["tool"]["poetry"]["version"] = version # type: ignore - - -class CargoProvider(TomlProvider): - """ - Cargo version management - - With support for `workspaces` - """ - - filename = "Cargo.toml" - - def get(self, document: tomlkit.TOMLDocument) -> str: - try: - return document["package"]["version"] # type: ignore - except tomlkit.exceptions.NonExistentKey: - ... - return document["workspace"]["package"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - try: - document["workspace"]["package"]["version"] = version # type: ignore - return - except tomlkit.exceptions.NonExistentKey: - ... - document["package"]["version"] = version # type: ignore - - -class JsonProvider(FileProvider): - """ - Base class for JSON-based version providers - """ - - indent: ClassVar[int] = 2 - - def get_version(self) -> str: - document = json.loads(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = json.loads(self.file.read_text()) - self.set(document, version) - self.file.write_text(json.dumps(document, indent=self.indent) + "\n") - - def get(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set(self, document: dict[str, Any], version: str): - document["version"] = version - - -class NpmProvider(JsonProvider): - """ - npm package.json and package-lock.json version management - """ - - indent: ClassVar[int] = 2 - package_filename = "package.json" - lock_filename = "package-lock.json" - shrinkwrap_filename = "npm-shrinkwrap.json" - - @property - def package_file(self) -> Path: - return Path() / self.package_filename - - @property - def lock_file(self) -> Path: - return Path() / self.lock_filename - - @property - def shrinkwrap_file(self) -> Path: - return Path() / self.shrinkwrap_filename - - def get_version(self) -> str: - """ - Get the current version from package.json - """ - package_document = json.loads(self.package_file.read_text()) - return self.get_package_version(package_document) - - def set_version(self, version: str) -> None: - package_document = self.set_package_version( - json.loads(self.package_file.read_text()), version - ) - self.package_file.write_text( - json.dumps(package_document, indent=self.indent) + "\n" - ) - if self.lock_file.exists(): - lock_document = self.set_lock_version( - json.loads(self.lock_file.read_text()), version - ) - self.lock_file.write_text( - json.dumps(lock_document, indent=self.indent) + "\n" - ) - if self.shrinkwrap_file.exists(): - shrinkwrap_document = self.set_shrinkwrap_version( - json.loads(self.shrinkwrap_file.read_text()), version - ) - self.shrinkwrap_file.write_text( - json.dumps(shrinkwrap_document, indent=self.indent) + "\n" - ) - - def get_package_version(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set_package_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - return document - - def set_lock_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - def set_shrinkwrap_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - -class ComposerProvider(JsonProvider): - """ - Composer version management - """ - - filename = "composer.json" - indent = 4 - - -class ScmProvider(VersionProvider): - """ - A provider fetching the current/last version from the repository history - - The version is fetched using `git describe` and is never set. - - It is meant for `setuptools-scm` or any package manager `*-scm` provider. - """ - - TAG_FORMAT_REGEXS = { - "$version": r"(?P.+)", - "$major": r"(?P\d+)", - "$minor": r"(?P\d+)", - "$patch": r"(?P\d+)", - "$prerelease": r"(?P\w+\d+)?", - "$devrelease": r"(?P\.dev\d+)?", - } - - def _tag_format_matcher(self) -> Callable[[str], str | None]: - version_scheme = get_version_scheme(self.config) - pattern = self.config.settings["tag_format"] - if pattern == "$version": - pattern = version_scheme.parser.pattern - for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): - pattern = pattern.replace(var, tag_pattern) - - regex = re.compile(f"^{pattern}$", re.VERBOSE) - - def matcher(tag: str) -> str | None: - match = regex.match(tag) - if not match: - return None - groups = match.groupdict() - if "version" in groups: - return groups["version"] - elif "major" in groups: - return "".join( - ( - groups["major"], - f".{groups['minor']}" if groups.get("minor") else "", - f".{groups['patch']}" if groups.get("patch") else "", - groups["prerelease"] if groups.get("prerelease") else "", - groups["devrelease"] if groups.get("devrelease") else "", - ) - ) - elif pattern == version_scheme.parser.pattern: - return str(version_scheme(tag)) - return None - - return matcher - - def get_version(self) -> str: - matcher = self._tag_format_matcher() - return next( - (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" - ) - - def set_version(self, version: str): - # Not necessary - pass - - -def get_provider(config: BaseConfig) -> VersionProvider: - """ - Get the version provider as defined in the configuration - - :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. - """ - provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER - try: - (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) - except ValueError: - raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') - provider_cls = ep.load() - return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py new file mode 100644 index 0000000000..16129af04b --- /dev/null +++ b/commitizen/providers/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import cast + +import importlib_metadata as metadata + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown + +from .base_provider import VersionProvider + +PROVIDER_ENTRYPOINT = "commitizen.provider" +DEFAULT_PROVIDER = "commitizen" + + +def get_provider(config: BaseConfig) -> VersionProvider: + """ + Get the version provider as defined in the configuration + + :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. + """ + provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER + try: + (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) + except ValueError: + raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') + provider_cls = ep.load() + return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/base_provider.py b/commitizen/providers/base_provider.py new file mode 100644 index 0000000000..34048318e2 --- /dev/null +++ b/commitizen/providers/base_provider.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, ClassVar + +import tomlkit + +from commitizen.config.base_config import BaseConfig + + +class VersionProvider(ABC): + """ + Abstract base class for version providers. + + Each version provider should inherit and implement this class. + """ + + config: BaseConfig + + def __init__(self, config: BaseConfig): + self.config = config + + @abstractmethod + def get_version(self) -> str: + """ + Get the current version + """ + + @abstractmethod + def set_version(self, version: str): + """ + Set the new current version + """ + + +class FileProvider(VersionProvider): + """ + Base class for file-based version providers + """ + + filename: ClassVar[str] + + @property + def file(self) -> Path: + return Path() / self.filename + + +class JsonProvider(FileProvider): + """ + Base class for JSON-based version providers + """ + + indent: ClassVar[int] = 2 + + def get_version(self) -> str: + document = json.loads(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = json.loads(self.file.read_text()) + self.set(document, version) + self.file.write_text(json.dumps(document, indent=self.indent) + "\n") + + def get(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set(self, document: dict[str, Any], version: str): + document["version"] = version + + +class TomlProvider(FileProvider): + """ + Base class for TOML-based version providers + """ + + def get_version(self) -> str: + document = tomlkit.parse(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = tomlkit.parse(self.file.read_text()) + self.set(document, version) + self.file.write_text(tomlkit.dumps(document)) + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["project"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["project"]["version"] = version # type: ignore diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py new file mode 100644 index 0000000000..f64c003edd --- /dev/null +++ b/commitizen/providers/cargo_provider.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class CargoProvider(TomlProvider): + """ + Cargo version management + + With support for `workspaces` + """ + + filename = "Cargo.toml" + + def get(self, document: tomlkit.TOMLDocument) -> str: + try: + return document["package"]["version"] # type: ignore + except tomlkit.exceptions.NonExistentKey: + ... + return document["workspace"]["package"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + try: + document["workspace"]["package"]["version"] = version # type: ignore + return + except tomlkit.exceptions.NonExistentKey: + ... + document["package"]["version"] = version # type: ignore diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py new file mode 100644 index 0000000000..fd0cdb3ebf --- /dev/null +++ b/commitizen/providers/commitizen_provider.py @@ -0,0 +1,16 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import VersionProvider + + +class CommitizenProvider(VersionProvider): + """ + Default version provider: Fetch and set version in commitizen config. + """ + + def get_version(self) -> str: + return self.config.settings["version"] # type: ignore + + def set_version(self, version: str): + self.config.set_key("version", version) diff --git a/commitizen/providers/composer_provider.py b/commitizen/providers/composer_provider.py new file mode 100644 index 0000000000..ef36af5a72 --- /dev/null +++ b/commitizen/providers/composer_provider.py @@ -0,0 +1,13 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import JsonProvider + + +class ComposerProvider(JsonProvider): + """ + Composer version management + """ + + filename = "composer.json" + indent = 4 diff --git a/commitizen/providers/npm_provider.py b/commitizen/providers/npm_provider.py new file mode 100644 index 0000000000..f625c3c6c3 --- /dev/null +++ b/commitizen/providers/npm_provider.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, ClassVar + + +from commitizen.providers.base_provider import VersionProvider + + +class NpmProvider(VersionProvider): + """ + npm package.json and package-lock.json version management + """ + + indent: ClassVar[int] = 2 + package_filename = "package.json" + lock_filename = "package-lock.json" + shrinkwrap_filename = "npm-shrinkwrap.json" + + @property + def package_file(self) -> Path: + return Path() / self.package_filename + + @property + def lock_file(self) -> Path: + return Path() / self.lock_filename + + @property + def shrinkwrap_file(self) -> Path: + return Path() / self.shrinkwrap_filename + + def get_version(self) -> str: + """ + Get the current version from package.json + """ + package_document = json.loads(self.package_file.read_text()) + return self.get_package_version(package_document) + + def set_version(self, version: str) -> None: + package_document = self.set_package_version( + json.loads(self.package_file.read_text()), version + ) + self.package_file.write_text( + json.dumps(package_document, indent=self.indent) + "\n" + ) + if self.lock_file.exists(): + lock_document = self.set_lock_version( + json.loads(self.lock_file.read_text()), version + ) + self.lock_file.write_text( + json.dumps(lock_document, indent=self.indent) + "\n" + ) + if self.shrinkwrap_file.exists(): + shrinkwrap_document = self.set_shrinkwrap_version( + json.loads(self.shrinkwrap_file.read_text()), version + ) + self.shrinkwrap_file.write_text( + json.dumps(shrinkwrap_document, indent=self.indent) + "\n" + ) + + def get_package_version(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set_package_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + return document + + def set_lock_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document + + def set_shrinkwrap_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document diff --git a/commitizen/providers/pep621_provider.py b/commitizen/providers/pep621_provider.py new file mode 100644 index 0000000000..b6d32f1a63 --- /dev/null +++ b/commitizen/providers/pep621_provider.py @@ -0,0 +1,12 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import TomlProvider + + +class Pep621Provider(TomlProvider): + """ + PEP-621 version management + """ + + filename = "pyproject.toml" diff --git a/commitizen/providers/poetry_provider.py b/commitizen/providers/poetry_provider.py new file mode 100644 index 0000000000..d301131115 --- /dev/null +++ b/commitizen/providers/poetry_provider.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class PoetryProvider(TomlProvider): + """ + Poetry version management + """ + + filename = "pyproject.toml" + + def get(self, pyproject: tomlkit.TOMLDocument) -> str: + return pyproject["tool"]["poetry"]["version"] # type: ignore + + def set(self, pyproject: tomlkit.TOMLDocument, version: str): + pyproject["tool"]["poetry"]["version"] = version # type: ignore diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py new file mode 100644 index 0000000000..bc9dda4b8a --- /dev/null +++ b/commitizen/providers/scm_provider.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import re +from typing import Callable, cast + + +from commitizen.git import get_tags +from commitizen.version_schemes import get_version_scheme + +from commitizen.providers.base_provider import VersionProvider + + +class ScmProvider(VersionProvider): + """ + A provider fetching the current/last version from the repository history + + The version is fetched using `git describe` and is never set. + + It is meant for `setuptools-scm` or any package manager `*-scm` provider. + """ + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + } + + def _tag_format_matcher(self) -> Callable[[str], str | None]: + version_scheme = get_version_scheme(self.config) + pattern = self.config.settings["tag_format"] + if pattern == "$version": + pattern = version_scheme.parser.pattern + for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): + pattern = pattern.replace(var, tag_pattern) + + regex = re.compile(f"^{pattern}$", re.VERBOSE) + + def matcher(tag: str) -> str | None: + match = regex.match(tag) + if not match: + return None + groups = match.groupdict() + if "version" in groups: + return groups["version"] + elif "major" in groups: + return "".join( + ( + groups["major"], + f".{groups['minor']}" if groups.get("minor") else "", + f".{groups['patch']}" if groups.get("patch") else "", + groups["prerelease"] if groups.get("prerelease") else "", + groups["devrelease"] if groups.get("devrelease") else "", + ) + ) + elif pattern == version_scheme.parser.pattern: + return str(version_scheme(tag)) + return None + + return matcher + + def get_version(self) -> str: + matcher = self._tag_format_matcher() + return next( + (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" + ) + + def set_version(self, version: str): + # Not necessary + pass diff --git a/pyproject.toml b/pyproject.toml index 511d963ba7..8259ce1342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,13 +84,13 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers:CargoProvider" -commitizen = "commitizen.providers:CommitizenProvider" -composer = "commitizen.providers:ComposerProvider" -npm = "commitizen.providers:NpmProvider" -pep621 = "commitizen.providers:Pep621Provider" -poetry = "commitizen.providers:PoetryProvider" -scm = "commitizen.providers:ScmProvider" +cargo = "commitizen.providers.cargo_provider:CargoProvider" +commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" +composer = "commitizen.providers.composer_provider:ComposerProvider" +npm = "commitizen.providers.npm_provider:NpmProvider" +pep621 = "commitizen.providers.pep621_provider:Pep621Provider" +poetry = "commitizen.providers.poetry_provider:PoetryProvider" +scm = "commitizen.providers.scm_provider:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" From b46328edb15ef4f57acde742b298414e4faae7b5 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:20:49 -0400 Subject: [PATCH 02/32] refactor(tests): split provider tests into separate files for maintainability closes #812 --- tests/providers/test_base_provider.py | 35 +++ tests/providers/test_cargo_provider.py | 75 +++++ tests/providers/test_commitizen_provider.py | 33 ++ tests/providers/test_composer_provider.py | 62 ++++ tests/providers/test_npm_provider.py | 112 +++++++ tests/providers/test_pep621_provider.py | 58 ++++ tests/providers/test_poetry_provider.py | 58 ++++ tests/providers/test_scm_provider.py | 68 +++++ tests/test_version_providers.py | 316 -------------------- 9 files changed, 501 insertions(+), 316 deletions(-) create mode 100644 tests/providers/test_base_provider.py create mode 100644 tests/providers/test_cargo_provider.py create mode 100644 tests/providers/test_commitizen_provider.py create mode 100644 tests/providers/test_composer_provider.py create mode 100644 tests/providers/test_npm_provider.py create mode 100644 tests/providers/test_pep621_provider.py create mode 100644 tests/providers/test_poetry_provider.py create mode 100644 tests/providers/test_scm_provider.py delete mode 100644 tests/test_version_providers.py diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py new file mode 100644 index 0000000000..fb5a3c3b39 --- /dev/null +++ b/tests/providers/test_base_provider.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.providers import get_provider +from commitizen.providers.commitizen_provider import CommitizenProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_default_version_provider_is_commitizen_config(config: BaseConfig): + provider = get_provider(config) + + assert isinstance(provider, CommitizenProvider) + + +def test_raise_for_unknown_provider(config: BaseConfig): + config.settings["version_provider"] = "unknown" + with pytest.raises(VersionProviderUnknown): + get_provider(config) diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py new file mode 100644 index 0000000000..d6b0573828 --- /dev/null +++ b/tests/providers/test_cargo_provider.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.cargo_provider import CargoProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +CARGO_TOML = """\ +[package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_EXPECTED = """\ +[package] +name = "whatever" +version = "42.1" +""" + +CARGO_WORKSPACE_TOML = """\ +[workspace.package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_WORKSPACE_EXPECTED = """\ +[workspace.package] +name = "whatever" +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ( + (CARGO_TOML, CARGO_EXPECTED), + (CARGO_WORKSPACE_TOML, CARGO_WORKSPACE_EXPECTED), + ), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py new file mode 100644 index 0000000000..8cca153c2f --- /dev/null +++ b/tests/providers/test_commitizen_provider.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers.commitizen_provider import CommitizenProvider + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): + config.settings["version"] = "42" + mock = mocker.patch.object(config, "set_key") + + provider = CommitizenProvider(config) + assert provider.get_version() == "42" + + provider.set_version("43.1") + mock.assert_called_once_with("version", "43.1") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py new file mode 100644 index 0000000000..11d295152e --- /dev/null +++ b/tests/providers/test_composer_provider.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.composer_provider import ComposerProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +COMPOSER_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +COMPOSER_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + + +@pytest.mark.parametrize( + "content, expected", + ((COMPOSER_JSON, COMPOSER_EXPECTED),), +) +def test_composer_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = ComposerProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "composer" + + provider = get_provider(config) + assert isinstance(provider, ComposerProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py new file mode 100644 index 0000000000..3b458a93de --- /dev/null +++ b/tests/providers/test_npm_provider.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.npm_provider import NpmProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +NPM_PACKAGE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +NPM_PACKAGE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + +NPM_LOCKFILE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "0.1.0" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + +NPM_LOCKFILE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "42.1" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + + +@pytest.mark.parametrize( + "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +@pytest.mark.parametrize( + "pkg_lock_content, pkg_lock_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +def test_npm_provider( + config: BaseConfig, + chdir: Path, + pkg_lock_content: str, + pkg_lock_expected: str, + pkg_shrinkwrap_content: str, + pkg_shrinkwrap_expected: str, +): + pkg = chdir / NpmProvider.package_filename + pkg.write_text(dedent(NPM_PACKAGE_JSON)) + if pkg_lock_content: + pkg_lock = chdir / NpmProvider.lock_filename + pkg_lock.write_text(dedent(pkg_lock_content)) + if pkg_shrinkwrap_content: + pkg_shrinkwrap = chdir / NpmProvider.shrinkwrap_filename + pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) + config.settings["version_provider"] = "npm" + + provider = get_provider(config) + assert isinstance(provider, NpmProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) + if pkg_lock_content: + assert pkg_lock.read_text() == dedent(pkg_lock_expected) + if pkg_shrinkwrap_content: + assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py new file mode 100644 index 0000000000..6288b47093 --- /dev/null +++ b/tests/providers/test_pep621_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.pep621_provider import Pep621Provider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +PEP621_TOML = """\ +[project] +version = "0.1.0" +""" + +PEP621_EXPECTED = """\ +[project] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((PEP621_TOML, PEP621_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = Pep621Provider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "pep621" + + provider = get_provider(config) + assert isinstance(provider, Pep621Provider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py new file mode 100644 index 0000000000..6a38d06075 --- /dev/null +++ b/tests/providers/test_poetry_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.poetry_provider import PoetryProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +POETRY_TOML = """\ +[tool.poetry] +version = "0.1.0" +""" + +POETRY_EXPECTED = """\ +[tool.poetry] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((POETRY_TOML, POETRY_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = PoetryProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "poetry" + + provider = get_provider(config) + assert isinstance(provider, PoetryProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py new file mode 100644 index 0000000000..bf8e28fd2a --- /dev/null +++ b/tests/providers/test_scm_provider.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.scm_provider import ScmProvider +from tests.utils import create_file_and_commit, create_tag + +if TYPE_CHECKING: + pass + + +@pytest.mark.parametrize( + "tag_format,tag,expected_version", + ( + # If tag_format is $version (the default), version_scheme.parser is used. + # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. + ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), + ("$version", "0.1.0", "0.1.0"), + ("$version", "v0.1.0", "0.1.0"), + ("$version", "v-0.1.0", "0.0.0"), + # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are + # much more lenient but require a v prefix. + ("v$version", "v0.1.0", "0.1.0"), + ("v$version", "no-match-because-no-v-prefix", "0.0.0"), + ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), + ("version-$version", "version-0.1.0", "0.1.0"), + ("version-$version", "version-0.1", "0.1"), + ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), + ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), + ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), + ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), + ), +) +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider( + config: BaseConfig, tag_format: str, tag: str, expected_version: str +): + create_file_and_commit("test: fake commit") + create_tag(tag) + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + + config.settings["version_provider"] = "scm" + config.settings["tag_format"] = tag_format + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + actual_version = provider.get_version() + assert actual_version == expected_version + + # Should not fail on set_version() + provider.set_version("43.1") + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" diff --git a/tests/test_version_providers.py b/tests/test_version_providers.py deleted file mode 100644 index 1cf0e736d8..0000000000 --- a/tests/test_version_providers.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from textwrap import dedent -from typing import TYPE_CHECKING, Iterator - -import pytest - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.providers import ( - CargoProvider, - CommitizenProvider, - ComposerProvider, - NpmProvider, - Pep621Provider, - PoetryProvider, - ScmProvider, - VersionProvider, - get_provider, -) -from tests.utils import create_file_and_commit, create_tag - -if TYPE_CHECKING: - from pytest_mock import MockerFixture - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - -def test_default_version_provider_is_commitizen_config(config: BaseConfig): - provider = get_provider(config) - - assert isinstance(provider, CommitizenProvider) - - -def test_raise_for_unknown_provider(config: BaseConfig): - config.settings["version_provider"] = "unknown" - with pytest.raises(VersionProviderUnknown): - get_provider(config) - - -def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): - config.settings["version"] = "42" - mock = mocker.patch.object(config, "set_key") - - provider = CommitizenProvider(config) - assert provider.get_version() == "42" - - provider.set_version("43.1") - mock.assert_called_once_with("version", "43.1") - - -FILE_PROVIDERS = [ - ( - "pep621", - "pyproject.toml", - Pep621Provider, - """\ - [project] - version = "0.1.0" - """, - """\ - [project] - version = "42.1" - """, - ), - ( - "poetry", - "pyproject.toml", - PoetryProvider, - """\ - [tool.poetry] - version = "0.1.0" - """, - """\ - [tool.poetry] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [workspace.package] - version = "0.1.0" - """, - """\ - [workspace.package] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [package] - version = "0.1.0" - """, - """\ - [package] - version = "42.1" - """, - ), - ( - "npm", - "package.json", - NpmProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), - ( - "composer", - "composer.json", - ComposerProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), -] - - -@pytest.mark.parametrize( - "id,filename,cls,content,expected", - FILE_PROVIDERS, -) -def test_file_providers( - config: BaseConfig, - chdir: Path, - id: str, - filename: str, - cls: type[VersionProvider], - content: str, - expected: str, -): - file = chdir / filename - file.write_text(dedent(content)) - config.settings["version_provider"] = id - - provider = get_provider(config) - assert isinstance(provider, cls) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert file.read_text() == dedent(expected) - - -@pytest.mark.parametrize( - "tag_format,tag,expected_version", - ( - # If tag_format is $version (the default), version_scheme.parser is used. - # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. - ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), - ("$version", "0.1.0", "0.1.0"), - ("$version", "v0.1.0", "0.1.0"), - ("$version", "v-0.1.0", "0.0.0"), - # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are - # much more lenient but require a v prefix. - ("v$version", "v0.1.0", "0.1.0"), - ("v$version", "no-match-because-no-v-prefix", "0.0.0"), - ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), - ("version-$version", "version-0.1.0", "0.1.0"), - ("version-$version", "version-0.1", "0.1"), - ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), - ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), - ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), - ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), - ), -) -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider( - config: BaseConfig, tag_format: str, tag: str, expected_version: str -): - create_file_and_commit("test: fake commit") - create_tag(tag) - create_file_and_commit("test: fake commit") - create_tag("should-not-match") - - config.settings["version_provider"] = "scm" - config.settings["tag_format"] = tag_format - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - actual_version = provider.get_version() - assert actual_version == expected_version - - # Should not fail on set_version() - provider.set_version("43.1") - - -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): - config.settings["version_provider"] = "scm" - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - assert provider.get_version() == "0.0.0" - - -NPM_PACKAGE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0" -} -""" - -NPM_PACKAGE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1" -} -""" - -NPM_LOCKFILE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "0.1.0" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - -NPM_LOCKFILE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "42.1" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - - -@pytest.mark.parametrize( - "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -@pytest.mark.parametrize( - "pkg_lock_content, pkg_lock_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -def test_npm_provider( - config: BaseConfig, - chdir: Path, - pkg_lock_content: str, - pkg_lock_expected: str, - pkg_shrinkwrap_content: str, - pkg_shrinkwrap_expected: str, -): - pkg = chdir / "package.json" - pkg.write_text(dedent(NPM_PACKAGE_JSON)) - if pkg_lock_content: - pkg_lock = chdir / "package-lock.json" - pkg_lock.write_text(dedent(pkg_lock_content)) - if pkg_shrinkwrap_content: - pkg_shrinkwrap = chdir / "npm-shrinkwrap.json" - pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) - config.settings["version_provider"] = "npm" - - provider = get_provider(config) - assert isinstance(provider, NpmProvider) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) - if pkg_lock_content: - assert pkg_lock.read_text() == dedent(pkg_lock_expected) - if pkg_shrinkwrap_content: - assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) From 4ea8c62572112b2d71fb4a11f188f710d23e9205 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:30:46 -0400 Subject: [PATCH 03/32] refactor(provider): fully qualify import of commitizen.providers.base_provider --- commitizen/providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 16129af04b..37e330582e 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -7,7 +7,7 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionProviderUnknown -from .base_provider import VersionProvider +from commitizen.providers.base_provider import VersionProvider PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" From 7ac3a98559611718a79f6d70ea3bd76d48d77482 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:37:15 -0400 Subject: [PATCH 04/32] refactor(provider): move chdir to providers/conftest.py --- tests/providers/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/providers/conftest.py diff --git a/tests/providers/conftest.py b/tests/providers/conftest.py new file mode 100644 index 0000000000..b4432ca524 --- /dev/null +++ b/tests/providers/conftest.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Iterator + +import pytest + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) From 68b56667967ffa83f40e01dec8a8568a1603ec61 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:38:14 -0400 Subject: [PATCH 05/32] refactor(provider): remove unnecessary TYPE_CHECKING from provider tests --- tests/providers/test_base_provider.py | 5 +---- tests/providers/test_cargo_provider.py | 14 -------------- tests/providers/test_commitizen_provider.py | 13 +------------ tests/providers/test_composer_provider.py | 14 -------------- tests/providers/test_npm_provider.py | 13 ------------- tests/providers/test_pep621_provider.py | 14 -------------- tests/providers/test_poetry_provider.py | 14 -------------- tests/providers/test_scm_provider.py | 4 ---- 8 files changed, 2 insertions(+), 89 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index fb5a3c3b39..d5557a3577 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import Iterator import pytest @@ -11,9 +11,6 @@ from commitizen.providers import get_provider from commitizen.providers.commitizen_provider import CommitizenProvider -if TYPE_CHECKING: - pass - @pytest.fixture def chdir(tmp_path: Path) -> Iterator[Path]: diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index d6b0573828..cde868deb5 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.cargo_provider import CargoProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - CARGO_TOML = """\ [package] name = "whatever" diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py index 8cca153c2f..887adf3d12 100644 --- a/tests/providers/test_commitizen_provider.py +++ b/tests/providers/test_commitizen_provider.py @@ -1,10 +1,7 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING -import pytest from commitizen.config.base_config import BaseConfig from commitizen.providers.commitizen_provider import CommitizenProvider @@ -14,14 +11,6 @@ from pytest_mock import MockerFixture -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): config.settings["version"] = "42" mock = mocker.patch.object(config, "set_key") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py index 11d295152e..ce72ae4703 100644 --- a/tests/providers/test_composer_provider.py +++ b/tests/providers/test_composer_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.composer_provider import ComposerProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - COMPOSER_JSON = """\ { "name": "whatever", diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py index 3b458a93de..2e5ceb42d5 100644 --- a/tests/providers/test_npm_provider.py +++ b/tests/providers/test_npm_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -11,17 +9,6 @@ from commitizen.providers import get_provider from commitizen.providers.npm_provider import NpmProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - NPM_PACKAGE_JSON = """\ { diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py index 6288b47093..9e82213294 100644 --- a/tests/providers/test_pep621_provider.py +++ b/tests/providers/test_pep621_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.pep621_provider import Pep621Provider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - PEP621_TOML = """\ [project] version = "0.1.0" diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py index 6a38d06075..9e327db6ad 100644 --- a/tests/providers/test_poetry_provider.py +++ b/tests/providers/test_poetry_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.poetry_provider import PoetryProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - POETRY_TOML = """\ [tool.poetry] version = "0.1.0" diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py index bf8e28fd2a..a0bfc46474 100644 --- a/tests/providers/test_scm_provider.py +++ b/tests/providers/test_scm_provider.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING import pytest @@ -9,9 +8,6 @@ from commitizen.providers.scm_provider import ScmProvider from tests.utils import create_file_and_commit, create_tag -if TYPE_CHECKING: - pass - @pytest.mark.parametrize( "tag_format,tag,expected_version", From 55147374d9b5a802a8a6e201afc7903fbc96ace4 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 12:00:11 -0400 Subject: [PATCH 06/32] refactor(provider): import all providers to providers/__init__.py to preserve plugin config --- commitizen/providers/__init__.py | 7 +++ pyproject.toml | 84 +++++++++++++++----------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 37e330582e..6f777096cf 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,6 +8,13 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider +from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 +from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 +from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 +from commitizen.providers.npm_provider import NpmProvider # noqa: F401 +from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 +from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 +from commitizen.providers.scm_provider import ScmProvider # noqa: F401 PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" diff --git a/pyproject.toml b/pyproject.toml index 6524d4c510..3ad76fa7b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.7.0" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen" + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7"} +importlib_metadata = { version = ">=4.13,<7" } [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -84,46 +84,46 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers.cargo_provider:CargoProvider" -commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" -composer = "commitizen.providers.composer_provider:ComposerProvider" -npm = "commitizen.providers.npm_provider:NpmProvider" -pep621 = "commitizen.providers.pep621_provider:Pep621Provider" -poetry = "commitizen.providers.poetry_provider:PoetryProvider" -scm = "commitizen.providers.scm_provider:ScmProvider" +cargo = "commitizen.providers:CargoProvider" +commitizen = "commitizen.providers:CommitizenProvider" +composer = "commitizen.providers:ComposerProvider" +npm = "commitizen.providers:NpmProvider" +pep621 = "commitizen.providers:Pep621Provider" +poetry = "commitizen.providers:PoetryProvider" +scm = "commitizen.providers:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] - [tool.coverage.report] - show_missing = true - exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', - ] - omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*' - ] +[tool.coverage.report] +show_missing = true +exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', +] +omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', +] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,11 +134,7 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = [ - "E501", - "D1", - "D415", -] +ignore = ["E501", "D1", "D415"] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -156,5 +152,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From a996237b026d46b453f48f4ccb00083361dac35c Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:19:25 -0400 Subject: [PATCH 07/32] refactor(provider): split providers into separate files for maintainability closes #812 --- commitizen/providers.py | 319 -------------------- commitizen/providers/__init__.py | 28 ++ commitizen/providers/base_provider.py | 91 ++++++ commitizen/providers/cargo_provider.py | 31 ++ commitizen/providers/commitizen_provider.py | 16 + commitizen/providers/composer_provider.py | 13 + commitizen/providers/npm_provider.py | 83 +++++ commitizen/providers/pep621_provider.py | 12 + commitizen/providers/poetry_provider.py | 20 ++ commitizen/providers/scm_provider.py | 72 +++++ pyproject.toml | 14 +- 11 files changed, 373 insertions(+), 326 deletions(-) delete mode 100644 commitizen/providers.py create mode 100644 commitizen/providers/__init__.py create mode 100644 commitizen/providers/base_provider.py create mode 100644 commitizen/providers/cargo_provider.py create mode 100644 commitizen/providers/commitizen_provider.py create mode 100644 commitizen/providers/composer_provider.py create mode 100644 commitizen/providers/npm_provider.py create mode 100644 commitizen/providers/pep621_provider.py create mode 100644 commitizen/providers/poetry_provider.py create mode 100644 commitizen/providers/scm_provider.py diff --git a/commitizen/providers.py b/commitizen/providers.py deleted file mode 100644 index df353bb365..0000000000 --- a/commitizen/providers.py +++ /dev/null @@ -1,319 +0,0 @@ -from __future__ import annotations - -import json -import re -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, Callable, ClassVar, cast - -import importlib_metadata as metadata -import tomlkit - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.git import get_tags -from commitizen.version_schemes import get_version_scheme - -PROVIDER_ENTRYPOINT = "commitizen.provider" -DEFAULT_PROVIDER = "commitizen" - - -class VersionProvider(ABC): - """ - Abstract base class for version providers. - - Each version provider should inherit and implement this class. - """ - - config: BaseConfig - - def __init__(self, config: BaseConfig): - self.config = config - - @abstractmethod - def get_version(self) -> str: - """ - Get the current version - """ - - @abstractmethod - def set_version(self, version: str): - """ - Set the new current version - """ - - -class CommitizenProvider(VersionProvider): - """ - Default version provider: Fetch and set version in commitizen config. - """ - - def get_version(self) -> str: - return self.config.settings["version"] # type: ignore - - def set_version(self, version: str): - self.config.set_key("version", version) - - -class FileProvider(VersionProvider): - """ - Base class for file-based version providers - """ - - filename: ClassVar[str] - - @property - def file(self) -> Path: - return Path() / self.filename - - -class TomlProvider(FileProvider): - """ - Base class for TOML-based version providers - """ - - def get_version(self) -> str: - document = tomlkit.parse(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = tomlkit.parse(self.file.read_text()) - self.set(document, version) - self.file.write_text(tomlkit.dumps(document)) - - def get(self, document: tomlkit.TOMLDocument) -> str: - return document["project"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - document["project"]["version"] = version # type: ignore - - -class Pep621Provider(TomlProvider): - """ - PEP-621 version management - """ - - filename = "pyproject.toml" - - -class PoetryProvider(TomlProvider): - """ - Poetry version management - """ - - filename = "pyproject.toml" - - def get(self, pyproject: tomlkit.TOMLDocument) -> str: - return pyproject["tool"]["poetry"]["version"] # type: ignore - - def set(self, pyproject: tomlkit.TOMLDocument, version: str): - pyproject["tool"]["poetry"]["version"] = version # type: ignore - - -class CargoProvider(TomlProvider): - """ - Cargo version management - - With support for `workspaces` - """ - - filename = "Cargo.toml" - - def get(self, document: tomlkit.TOMLDocument) -> str: - try: - return document["package"]["version"] # type: ignore - except tomlkit.exceptions.NonExistentKey: - ... - return document["workspace"]["package"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - try: - document["workspace"]["package"]["version"] = version # type: ignore - return - except tomlkit.exceptions.NonExistentKey: - ... - document["package"]["version"] = version # type: ignore - - -class JsonProvider(FileProvider): - """ - Base class for JSON-based version providers - """ - - indent: ClassVar[int] = 2 - - def get_version(self) -> str: - document = json.loads(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = json.loads(self.file.read_text()) - self.set(document, version) - self.file.write_text(json.dumps(document, indent=self.indent) + "\n") - - def get(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set(self, document: dict[str, Any], version: str): - document["version"] = version - - -class NpmProvider(JsonProvider): - """ - npm package.json and package-lock.json version management - """ - - indent: ClassVar[int] = 2 - package_filename = "package.json" - lock_filename = "package-lock.json" - shrinkwrap_filename = "npm-shrinkwrap.json" - - @property - def package_file(self) -> Path: - return Path() / self.package_filename - - @property - def lock_file(self) -> Path: - return Path() / self.lock_filename - - @property - def shrinkwrap_file(self) -> Path: - return Path() / self.shrinkwrap_filename - - def get_version(self) -> str: - """ - Get the current version from package.json - """ - package_document = json.loads(self.package_file.read_text()) - return self.get_package_version(package_document) - - def set_version(self, version: str) -> None: - package_document = self.set_package_version( - json.loads(self.package_file.read_text()), version - ) - self.package_file.write_text( - json.dumps(package_document, indent=self.indent) + "\n" - ) - if self.lock_file.exists(): - lock_document = self.set_lock_version( - json.loads(self.lock_file.read_text()), version - ) - self.lock_file.write_text( - json.dumps(lock_document, indent=self.indent) + "\n" - ) - if self.shrinkwrap_file.exists(): - shrinkwrap_document = self.set_shrinkwrap_version( - json.loads(self.shrinkwrap_file.read_text()), version - ) - self.shrinkwrap_file.write_text( - json.dumps(shrinkwrap_document, indent=self.indent) + "\n" - ) - - def get_package_version(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set_package_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - return document - - def set_lock_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - def set_shrinkwrap_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - -class ComposerProvider(JsonProvider): - """ - Composer version management - """ - - filename = "composer.json" - indent = 4 - - -class ScmProvider(VersionProvider): - """ - A provider fetching the current/last version from the repository history - - The version is fetched using `git describe` and is never set. - - It is meant for `setuptools-scm` or any package manager `*-scm` provider. - """ - - TAG_FORMAT_REGEXS = { - "$version": r"(?P.+)", - "$major": r"(?P\d+)", - "$minor": r"(?P\d+)", - "$patch": r"(?P\d+)", - "$prerelease": r"(?P\w+\d+)?", - "$devrelease": r"(?P\.dev\d+)?", - } - - def _tag_format_matcher(self) -> Callable[[str], str | None]: - version_scheme = get_version_scheme(self.config) - pattern = self.config.settings["tag_format"] - if pattern == "$version": - pattern = version_scheme.parser.pattern - for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): - pattern = pattern.replace(var, tag_pattern) - - regex = re.compile(f"^{pattern}$", re.VERBOSE) - - def matcher(tag: str) -> str | None: - match = regex.match(tag) - if not match: - return None - groups = match.groupdict() - if "version" in groups: - return groups["version"] - elif "major" in groups: - return "".join( - ( - groups["major"], - f".{groups['minor']}" if groups.get("minor") else "", - f".{groups['patch']}" if groups.get("patch") else "", - groups["prerelease"] if groups.get("prerelease") else "", - groups["devrelease"] if groups.get("devrelease") else "", - ) - ) - elif pattern == version_scheme.parser.pattern: - return str(version_scheme(tag)) - return None - - return matcher - - def get_version(self) -> str: - matcher = self._tag_format_matcher() - return next( - (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" - ) - - def set_version(self, version: str): - # Not necessary - pass - - -def get_provider(config: BaseConfig) -> VersionProvider: - """ - Get the version provider as defined in the configuration - - :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. - """ - provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER - try: - (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) - except ValueError: - raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') - provider_cls = ep.load() - return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py new file mode 100644 index 0000000000..16129af04b --- /dev/null +++ b/commitizen/providers/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import cast + +import importlib_metadata as metadata + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown + +from .base_provider import VersionProvider + +PROVIDER_ENTRYPOINT = "commitizen.provider" +DEFAULT_PROVIDER = "commitizen" + + +def get_provider(config: BaseConfig) -> VersionProvider: + """ + Get the version provider as defined in the configuration + + :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. + """ + provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER + try: + (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) + except ValueError: + raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') + provider_cls = ep.load() + return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/base_provider.py b/commitizen/providers/base_provider.py new file mode 100644 index 0000000000..34048318e2 --- /dev/null +++ b/commitizen/providers/base_provider.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, ClassVar + +import tomlkit + +from commitizen.config.base_config import BaseConfig + + +class VersionProvider(ABC): + """ + Abstract base class for version providers. + + Each version provider should inherit and implement this class. + """ + + config: BaseConfig + + def __init__(self, config: BaseConfig): + self.config = config + + @abstractmethod + def get_version(self) -> str: + """ + Get the current version + """ + + @abstractmethod + def set_version(self, version: str): + """ + Set the new current version + """ + + +class FileProvider(VersionProvider): + """ + Base class for file-based version providers + """ + + filename: ClassVar[str] + + @property + def file(self) -> Path: + return Path() / self.filename + + +class JsonProvider(FileProvider): + """ + Base class for JSON-based version providers + """ + + indent: ClassVar[int] = 2 + + def get_version(self) -> str: + document = json.loads(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = json.loads(self.file.read_text()) + self.set(document, version) + self.file.write_text(json.dumps(document, indent=self.indent) + "\n") + + def get(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set(self, document: dict[str, Any], version: str): + document["version"] = version + + +class TomlProvider(FileProvider): + """ + Base class for TOML-based version providers + """ + + def get_version(self) -> str: + document = tomlkit.parse(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = tomlkit.parse(self.file.read_text()) + self.set(document, version) + self.file.write_text(tomlkit.dumps(document)) + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["project"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["project"]["version"] = version # type: ignore diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py new file mode 100644 index 0000000000..f64c003edd --- /dev/null +++ b/commitizen/providers/cargo_provider.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class CargoProvider(TomlProvider): + """ + Cargo version management + + With support for `workspaces` + """ + + filename = "Cargo.toml" + + def get(self, document: tomlkit.TOMLDocument) -> str: + try: + return document["package"]["version"] # type: ignore + except tomlkit.exceptions.NonExistentKey: + ... + return document["workspace"]["package"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + try: + document["workspace"]["package"]["version"] = version # type: ignore + return + except tomlkit.exceptions.NonExistentKey: + ... + document["package"]["version"] = version # type: ignore diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py new file mode 100644 index 0000000000..fd0cdb3ebf --- /dev/null +++ b/commitizen/providers/commitizen_provider.py @@ -0,0 +1,16 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import VersionProvider + + +class CommitizenProvider(VersionProvider): + """ + Default version provider: Fetch and set version in commitizen config. + """ + + def get_version(self) -> str: + return self.config.settings["version"] # type: ignore + + def set_version(self, version: str): + self.config.set_key("version", version) diff --git a/commitizen/providers/composer_provider.py b/commitizen/providers/composer_provider.py new file mode 100644 index 0000000000..ef36af5a72 --- /dev/null +++ b/commitizen/providers/composer_provider.py @@ -0,0 +1,13 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import JsonProvider + + +class ComposerProvider(JsonProvider): + """ + Composer version management + """ + + filename = "composer.json" + indent = 4 diff --git a/commitizen/providers/npm_provider.py b/commitizen/providers/npm_provider.py new file mode 100644 index 0000000000..f625c3c6c3 --- /dev/null +++ b/commitizen/providers/npm_provider.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, ClassVar + + +from commitizen.providers.base_provider import VersionProvider + + +class NpmProvider(VersionProvider): + """ + npm package.json and package-lock.json version management + """ + + indent: ClassVar[int] = 2 + package_filename = "package.json" + lock_filename = "package-lock.json" + shrinkwrap_filename = "npm-shrinkwrap.json" + + @property + def package_file(self) -> Path: + return Path() / self.package_filename + + @property + def lock_file(self) -> Path: + return Path() / self.lock_filename + + @property + def shrinkwrap_file(self) -> Path: + return Path() / self.shrinkwrap_filename + + def get_version(self) -> str: + """ + Get the current version from package.json + """ + package_document = json.loads(self.package_file.read_text()) + return self.get_package_version(package_document) + + def set_version(self, version: str) -> None: + package_document = self.set_package_version( + json.loads(self.package_file.read_text()), version + ) + self.package_file.write_text( + json.dumps(package_document, indent=self.indent) + "\n" + ) + if self.lock_file.exists(): + lock_document = self.set_lock_version( + json.loads(self.lock_file.read_text()), version + ) + self.lock_file.write_text( + json.dumps(lock_document, indent=self.indent) + "\n" + ) + if self.shrinkwrap_file.exists(): + shrinkwrap_document = self.set_shrinkwrap_version( + json.loads(self.shrinkwrap_file.read_text()), version + ) + self.shrinkwrap_file.write_text( + json.dumps(shrinkwrap_document, indent=self.indent) + "\n" + ) + + def get_package_version(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set_package_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + return document + + def set_lock_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document + + def set_shrinkwrap_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document diff --git a/commitizen/providers/pep621_provider.py b/commitizen/providers/pep621_provider.py new file mode 100644 index 0000000000..b6d32f1a63 --- /dev/null +++ b/commitizen/providers/pep621_provider.py @@ -0,0 +1,12 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import TomlProvider + + +class Pep621Provider(TomlProvider): + """ + PEP-621 version management + """ + + filename = "pyproject.toml" diff --git a/commitizen/providers/poetry_provider.py b/commitizen/providers/poetry_provider.py new file mode 100644 index 0000000000..d301131115 --- /dev/null +++ b/commitizen/providers/poetry_provider.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class PoetryProvider(TomlProvider): + """ + Poetry version management + """ + + filename = "pyproject.toml" + + def get(self, pyproject: tomlkit.TOMLDocument) -> str: + return pyproject["tool"]["poetry"]["version"] # type: ignore + + def set(self, pyproject: tomlkit.TOMLDocument, version: str): + pyproject["tool"]["poetry"]["version"] = version # type: ignore diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py new file mode 100644 index 0000000000..bc9dda4b8a --- /dev/null +++ b/commitizen/providers/scm_provider.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import re +from typing import Callable, cast + + +from commitizen.git import get_tags +from commitizen.version_schemes import get_version_scheme + +from commitizen.providers.base_provider import VersionProvider + + +class ScmProvider(VersionProvider): + """ + A provider fetching the current/last version from the repository history + + The version is fetched using `git describe` and is never set. + + It is meant for `setuptools-scm` or any package manager `*-scm` provider. + """ + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + } + + def _tag_format_matcher(self) -> Callable[[str], str | None]: + version_scheme = get_version_scheme(self.config) + pattern = self.config.settings["tag_format"] + if pattern == "$version": + pattern = version_scheme.parser.pattern + for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): + pattern = pattern.replace(var, tag_pattern) + + regex = re.compile(f"^{pattern}$", re.VERBOSE) + + def matcher(tag: str) -> str | None: + match = regex.match(tag) + if not match: + return None + groups = match.groupdict() + if "version" in groups: + return groups["version"] + elif "major" in groups: + return "".join( + ( + groups["major"], + f".{groups['minor']}" if groups.get("minor") else "", + f".{groups['patch']}" if groups.get("patch") else "", + groups["prerelease"] if groups.get("prerelease") else "", + groups["devrelease"] if groups.get("devrelease") else "", + ) + ) + elif pattern == version_scheme.parser.pattern: + return str(version_scheme(tag)) + return None + + return matcher + + def get_version(self) -> str: + matcher = self._tag_format_matcher() + return next( + (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" + ) + + def set_version(self, version: str): + # Not necessary + pass diff --git a/pyproject.toml b/pyproject.toml index 3101d845a4..8b9d6430db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,13 +84,13 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers:CargoProvider" -commitizen = "commitizen.providers:CommitizenProvider" -composer = "commitizen.providers:ComposerProvider" -npm = "commitizen.providers:NpmProvider" -pep621 = "commitizen.providers:Pep621Provider" -poetry = "commitizen.providers:PoetryProvider" -scm = "commitizen.providers:ScmProvider" +cargo = "commitizen.providers.cargo_provider:CargoProvider" +commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" +composer = "commitizen.providers.composer_provider:ComposerProvider" +npm = "commitizen.providers.npm_provider:NpmProvider" +pep621 = "commitizen.providers.pep621_provider:Pep621Provider" +poetry = "commitizen.providers.poetry_provider:PoetryProvider" +scm = "commitizen.providers.scm_provider:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" From 892112eebbd34b3bfb4c97cefcf09cecb4dc6fb0 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:20:49 -0400 Subject: [PATCH 08/32] refactor(tests): split provider tests into separate files for maintainability closes #812 --- tests/providers/test_base_provider.py | 35 +++ tests/providers/test_cargo_provider.py | 75 +++++ tests/providers/test_commitizen_provider.py | 33 ++ tests/providers/test_composer_provider.py | 62 ++++ tests/providers/test_npm_provider.py | 112 +++++++ tests/providers/test_pep621_provider.py | 58 ++++ tests/providers/test_poetry_provider.py | 58 ++++ tests/providers/test_scm_provider.py | 68 +++++ tests/test_version_providers.py | 316 -------------------- 9 files changed, 501 insertions(+), 316 deletions(-) create mode 100644 tests/providers/test_base_provider.py create mode 100644 tests/providers/test_cargo_provider.py create mode 100644 tests/providers/test_commitizen_provider.py create mode 100644 tests/providers/test_composer_provider.py create mode 100644 tests/providers/test_npm_provider.py create mode 100644 tests/providers/test_pep621_provider.py create mode 100644 tests/providers/test_poetry_provider.py create mode 100644 tests/providers/test_scm_provider.py delete mode 100644 tests/test_version_providers.py diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py new file mode 100644 index 0000000000..fb5a3c3b39 --- /dev/null +++ b/tests/providers/test_base_provider.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.providers import get_provider +from commitizen.providers.commitizen_provider import CommitizenProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_default_version_provider_is_commitizen_config(config: BaseConfig): + provider = get_provider(config) + + assert isinstance(provider, CommitizenProvider) + + +def test_raise_for_unknown_provider(config: BaseConfig): + config.settings["version_provider"] = "unknown" + with pytest.raises(VersionProviderUnknown): + get_provider(config) diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py new file mode 100644 index 0000000000..d6b0573828 --- /dev/null +++ b/tests/providers/test_cargo_provider.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.cargo_provider import CargoProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +CARGO_TOML = """\ +[package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_EXPECTED = """\ +[package] +name = "whatever" +version = "42.1" +""" + +CARGO_WORKSPACE_TOML = """\ +[workspace.package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_WORKSPACE_EXPECTED = """\ +[workspace.package] +name = "whatever" +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ( + (CARGO_TOML, CARGO_EXPECTED), + (CARGO_WORKSPACE_TOML, CARGO_WORKSPACE_EXPECTED), + ), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py new file mode 100644 index 0000000000..8cca153c2f --- /dev/null +++ b/tests/providers/test_commitizen_provider.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers.commitizen_provider import CommitizenProvider + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): + config.settings["version"] = "42" + mock = mocker.patch.object(config, "set_key") + + provider = CommitizenProvider(config) + assert provider.get_version() == "42" + + provider.set_version("43.1") + mock.assert_called_once_with("version", "43.1") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py new file mode 100644 index 0000000000..11d295152e --- /dev/null +++ b/tests/providers/test_composer_provider.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.composer_provider import ComposerProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +COMPOSER_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +COMPOSER_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + + +@pytest.mark.parametrize( + "content, expected", + ((COMPOSER_JSON, COMPOSER_EXPECTED),), +) +def test_composer_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = ComposerProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "composer" + + provider = get_provider(config) + assert isinstance(provider, ComposerProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py new file mode 100644 index 0000000000..3b458a93de --- /dev/null +++ b/tests/providers/test_npm_provider.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.npm_provider import NpmProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +NPM_PACKAGE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +NPM_PACKAGE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + +NPM_LOCKFILE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "0.1.0" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + +NPM_LOCKFILE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "42.1" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + + +@pytest.mark.parametrize( + "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +@pytest.mark.parametrize( + "pkg_lock_content, pkg_lock_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +def test_npm_provider( + config: BaseConfig, + chdir: Path, + pkg_lock_content: str, + pkg_lock_expected: str, + pkg_shrinkwrap_content: str, + pkg_shrinkwrap_expected: str, +): + pkg = chdir / NpmProvider.package_filename + pkg.write_text(dedent(NPM_PACKAGE_JSON)) + if pkg_lock_content: + pkg_lock = chdir / NpmProvider.lock_filename + pkg_lock.write_text(dedent(pkg_lock_content)) + if pkg_shrinkwrap_content: + pkg_shrinkwrap = chdir / NpmProvider.shrinkwrap_filename + pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) + config.settings["version_provider"] = "npm" + + provider = get_provider(config) + assert isinstance(provider, NpmProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) + if pkg_lock_content: + assert pkg_lock.read_text() == dedent(pkg_lock_expected) + if pkg_shrinkwrap_content: + assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py new file mode 100644 index 0000000000..6288b47093 --- /dev/null +++ b/tests/providers/test_pep621_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.pep621_provider import Pep621Provider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +PEP621_TOML = """\ +[project] +version = "0.1.0" +""" + +PEP621_EXPECTED = """\ +[project] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((PEP621_TOML, PEP621_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = Pep621Provider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "pep621" + + provider = get_provider(config) + assert isinstance(provider, Pep621Provider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py new file mode 100644 index 0000000000..6a38d06075 --- /dev/null +++ b/tests/providers/test_poetry_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.poetry_provider import PoetryProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +POETRY_TOML = """\ +[tool.poetry] +version = "0.1.0" +""" + +POETRY_EXPECTED = """\ +[tool.poetry] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((POETRY_TOML, POETRY_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = PoetryProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "poetry" + + provider = get_provider(config) + assert isinstance(provider, PoetryProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py new file mode 100644 index 0000000000..bf8e28fd2a --- /dev/null +++ b/tests/providers/test_scm_provider.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.scm_provider import ScmProvider +from tests.utils import create_file_and_commit, create_tag + +if TYPE_CHECKING: + pass + + +@pytest.mark.parametrize( + "tag_format,tag,expected_version", + ( + # If tag_format is $version (the default), version_scheme.parser is used. + # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. + ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), + ("$version", "0.1.0", "0.1.0"), + ("$version", "v0.1.0", "0.1.0"), + ("$version", "v-0.1.0", "0.0.0"), + # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are + # much more lenient but require a v prefix. + ("v$version", "v0.1.0", "0.1.0"), + ("v$version", "no-match-because-no-v-prefix", "0.0.0"), + ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), + ("version-$version", "version-0.1.0", "0.1.0"), + ("version-$version", "version-0.1", "0.1"), + ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), + ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), + ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), + ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), + ), +) +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider( + config: BaseConfig, tag_format: str, tag: str, expected_version: str +): + create_file_and_commit("test: fake commit") + create_tag(tag) + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + + config.settings["version_provider"] = "scm" + config.settings["tag_format"] = tag_format + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + actual_version = provider.get_version() + assert actual_version == expected_version + + # Should not fail on set_version() + provider.set_version("43.1") + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" diff --git a/tests/test_version_providers.py b/tests/test_version_providers.py deleted file mode 100644 index 1cf0e736d8..0000000000 --- a/tests/test_version_providers.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from textwrap import dedent -from typing import TYPE_CHECKING, Iterator - -import pytest - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.providers import ( - CargoProvider, - CommitizenProvider, - ComposerProvider, - NpmProvider, - Pep621Provider, - PoetryProvider, - ScmProvider, - VersionProvider, - get_provider, -) -from tests.utils import create_file_and_commit, create_tag - -if TYPE_CHECKING: - from pytest_mock import MockerFixture - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - -def test_default_version_provider_is_commitizen_config(config: BaseConfig): - provider = get_provider(config) - - assert isinstance(provider, CommitizenProvider) - - -def test_raise_for_unknown_provider(config: BaseConfig): - config.settings["version_provider"] = "unknown" - with pytest.raises(VersionProviderUnknown): - get_provider(config) - - -def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): - config.settings["version"] = "42" - mock = mocker.patch.object(config, "set_key") - - provider = CommitizenProvider(config) - assert provider.get_version() == "42" - - provider.set_version("43.1") - mock.assert_called_once_with("version", "43.1") - - -FILE_PROVIDERS = [ - ( - "pep621", - "pyproject.toml", - Pep621Provider, - """\ - [project] - version = "0.1.0" - """, - """\ - [project] - version = "42.1" - """, - ), - ( - "poetry", - "pyproject.toml", - PoetryProvider, - """\ - [tool.poetry] - version = "0.1.0" - """, - """\ - [tool.poetry] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [workspace.package] - version = "0.1.0" - """, - """\ - [workspace.package] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [package] - version = "0.1.0" - """, - """\ - [package] - version = "42.1" - """, - ), - ( - "npm", - "package.json", - NpmProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), - ( - "composer", - "composer.json", - ComposerProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), -] - - -@pytest.mark.parametrize( - "id,filename,cls,content,expected", - FILE_PROVIDERS, -) -def test_file_providers( - config: BaseConfig, - chdir: Path, - id: str, - filename: str, - cls: type[VersionProvider], - content: str, - expected: str, -): - file = chdir / filename - file.write_text(dedent(content)) - config.settings["version_provider"] = id - - provider = get_provider(config) - assert isinstance(provider, cls) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert file.read_text() == dedent(expected) - - -@pytest.mark.parametrize( - "tag_format,tag,expected_version", - ( - # If tag_format is $version (the default), version_scheme.parser is used. - # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. - ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), - ("$version", "0.1.0", "0.1.0"), - ("$version", "v0.1.0", "0.1.0"), - ("$version", "v-0.1.0", "0.0.0"), - # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are - # much more lenient but require a v prefix. - ("v$version", "v0.1.0", "0.1.0"), - ("v$version", "no-match-because-no-v-prefix", "0.0.0"), - ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), - ("version-$version", "version-0.1.0", "0.1.0"), - ("version-$version", "version-0.1", "0.1"), - ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), - ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), - ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), - ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), - ), -) -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider( - config: BaseConfig, tag_format: str, tag: str, expected_version: str -): - create_file_and_commit("test: fake commit") - create_tag(tag) - create_file_and_commit("test: fake commit") - create_tag("should-not-match") - - config.settings["version_provider"] = "scm" - config.settings["tag_format"] = tag_format - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - actual_version = provider.get_version() - assert actual_version == expected_version - - # Should not fail on set_version() - provider.set_version("43.1") - - -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): - config.settings["version_provider"] = "scm" - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - assert provider.get_version() == "0.0.0" - - -NPM_PACKAGE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0" -} -""" - -NPM_PACKAGE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1" -} -""" - -NPM_LOCKFILE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "0.1.0" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - -NPM_LOCKFILE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "42.1" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - - -@pytest.mark.parametrize( - "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -@pytest.mark.parametrize( - "pkg_lock_content, pkg_lock_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -def test_npm_provider( - config: BaseConfig, - chdir: Path, - pkg_lock_content: str, - pkg_lock_expected: str, - pkg_shrinkwrap_content: str, - pkg_shrinkwrap_expected: str, -): - pkg = chdir / "package.json" - pkg.write_text(dedent(NPM_PACKAGE_JSON)) - if pkg_lock_content: - pkg_lock = chdir / "package-lock.json" - pkg_lock.write_text(dedent(pkg_lock_content)) - if pkg_shrinkwrap_content: - pkg_shrinkwrap = chdir / "npm-shrinkwrap.json" - pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) - config.settings["version_provider"] = "npm" - - provider = get_provider(config) - assert isinstance(provider, NpmProvider) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) - if pkg_lock_content: - assert pkg_lock.read_text() == dedent(pkg_lock_expected) - if pkg_shrinkwrap_content: - assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) From 5786ed04af1fb527c6c80f8647ac54c105fe05c0 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:30:46 -0400 Subject: [PATCH 09/32] refactor(provider): fully qualify import of commitizen.providers.base_provider --- commitizen/providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 16129af04b..37e330582e 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -7,7 +7,7 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionProviderUnknown -from .base_provider import VersionProvider +from commitizen.providers.base_provider import VersionProvider PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" From 0f5e54c42c7bcf6ca051d427a78965d2a18ddc6d Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:37:15 -0400 Subject: [PATCH 10/32] refactor(provider): move chdir to providers/conftest.py --- tests/providers/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/providers/conftest.py diff --git a/tests/providers/conftest.py b/tests/providers/conftest.py new file mode 100644 index 0000000000..b4432ca524 --- /dev/null +++ b/tests/providers/conftest.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Iterator + +import pytest + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) From 14be208eb863d3765ea690bf51d126f15a02ba86 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:38:14 -0400 Subject: [PATCH 11/32] refactor(provider): remove unnecessary TYPE_CHECKING from provider tests --- tests/providers/test_base_provider.py | 5 +---- tests/providers/test_cargo_provider.py | 14 -------------- tests/providers/test_commitizen_provider.py | 13 +------------ tests/providers/test_composer_provider.py | 14 -------------- tests/providers/test_npm_provider.py | 13 ------------- tests/providers/test_pep621_provider.py | 14 -------------- tests/providers/test_poetry_provider.py | 14 -------------- tests/providers/test_scm_provider.py | 4 ---- 8 files changed, 2 insertions(+), 89 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index fb5a3c3b39..d5557a3577 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import Iterator import pytest @@ -11,9 +11,6 @@ from commitizen.providers import get_provider from commitizen.providers.commitizen_provider import CommitizenProvider -if TYPE_CHECKING: - pass - @pytest.fixture def chdir(tmp_path: Path) -> Iterator[Path]: diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index d6b0573828..cde868deb5 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.cargo_provider import CargoProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - CARGO_TOML = """\ [package] name = "whatever" diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py index 8cca153c2f..887adf3d12 100644 --- a/tests/providers/test_commitizen_provider.py +++ b/tests/providers/test_commitizen_provider.py @@ -1,10 +1,7 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING -import pytest from commitizen.config.base_config import BaseConfig from commitizen.providers.commitizen_provider import CommitizenProvider @@ -14,14 +11,6 @@ from pytest_mock import MockerFixture -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): config.settings["version"] = "42" mock = mocker.patch.object(config, "set_key") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py index 11d295152e..ce72ae4703 100644 --- a/tests/providers/test_composer_provider.py +++ b/tests/providers/test_composer_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.composer_provider import ComposerProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - COMPOSER_JSON = """\ { "name": "whatever", diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py index 3b458a93de..2e5ceb42d5 100644 --- a/tests/providers/test_npm_provider.py +++ b/tests/providers/test_npm_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -11,17 +9,6 @@ from commitizen.providers import get_provider from commitizen.providers.npm_provider import NpmProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - NPM_PACKAGE_JSON = """\ { diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py index 6288b47093..9e82213294 100644 --- a/tests/providers/test_pep621_provider.py +++ b/tests/providers/test_pep621_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.pep621_provider import Pep621Provider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - PEP621_TOML = """\ [project] version = "0.1.0" diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py index 6a38d06075..9e327db6ad 100644 --- a/tests/providers/test_poetry_provider.py +++ b/tests/providers/test_poetry_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.poetry_provider import PoetryProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - POETRY_TOML = """\ [tool.poetry] version = "0.1.0" diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py index bf8e28fd2a..a0bfc46474 100644 --- a/tests/providers/test_scm_provider.py +++ b/tests/providers/test_scm_provider.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING import pytest @@ -9,9 +8,6 @@ from commitizen.providers.scm_provider import ScmProvider from tests.utils import create_file_and_commit, create_tag -if TYPE_CHECKING: - pass - @pytest.mark.parametrize( "tag_format,tag,expected_version", From 346919e8b4cf5025cb55a411b8a5ad2dfa75d687 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 12:00:11 -0400 Subject: [PATCH 12/32] refactor(provider): import all providers to providers/__init__.py to preserve plugin config --- commitizen/providers/__init__.py | 7 +++ pyproject.toml | 84 +++++++++++++++----------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 37e330582e..6f777096cf 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,6 +8,13 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider +from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 +from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 +from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 +from commitizen.providers.npm_provider import NpmProvider # noqa: F401 +from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 +from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 +from commitizen.providers.scm_provider import ScmProvider # noqa: F401 PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" diff --git a/pyproject.toml b/pyproject.toml index 8b9d6430db..cafa72681a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.7.1" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen" + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7"} +importlib_metadata = { version = ">=4.13,<7" } [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -84,46 +84,46 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers.cargo_provider:CargoProvider" -commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" -composer = "commitizen.providers.composer_provider:ComposerProvider" -npm = "commitizen.providers.npm_provider:NpmProvider" -pep621 = "commitizen.providers.pep621_provider:Pep621Provider" -poetry = "commitizen.providers.poetry_provider:PoetryProvider" -scm = "commitizen.providers.scm_provider:ScmProvider" +cargo = "commitizen.providers:CargoProvider" +commitizen = "commitizen.providers:CommitizenProvider" +composer = "commitizen.providers:ComposerProvider" +npm = "commitizen.providers:NpmProvider" +pep621 = "commitizen.providers:Pep621Provider" +poetry = "commitizen.providers:PoetryProvider" +scm = "commitizen.providers:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] - [tool.coverage.report] - show_missing = true - exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', - ] - omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*' - ] +[tool.coverage.report] +show_missing = true +exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', +] +omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', +] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,11 +134,7 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = [ - "E501", - "D1", - "D415", -] +ignore = ["E501", "D1", "D415"] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -156,5 +152,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From ebd1e717b76ec3ce3333b9a63daca635f1afd296 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:19:25 -0400 Subject: [PATCH 13/32] refactor(provider): split providers into separate files for maintainability closes #812 --- commitizen/providers.py | 319 -------------------- commitizen/providers/__init__.py | 28 ++ commitizen/providers/base_provider.py | 91 ++++++ commitizen/providers/cargo_provider.py | 31 ++ commitizen/providers/commitizen_provider.py | 16 + commitizen/providers/composer_provider.py | 13 + commitizen/providers/npm_provider.py | 83 +++++ commitizen/providers/pep621_provider.py | 12 + commitizen/providers/poetry_provider.py | 20 ++ commitizen/providers/scm_provider.py | 72 +++++ pyproject.toml | 14 +- 11 files changed, 373 insertions(+), 326 deletions(-) delete mode 100644 commitizen/providers.py create mode 100644 commitizen/providers/__init__.py create mode 100644 commitizen/providers/base_provider.py create mode 100644 commitizen/providers/cargo_provider.py create mode 100644 commitizen/providers/commitizen_provider.py create mode 100644 commitizen/providers/composer_provider.py create mode 100644 commitizen/providers/npm_provider.py create mode 100644 commitizen/providers/pep621_provider.py create mode 100644 commitizen/providers/poetry_provider.py create mode 100644 commitizen/providers/scm_provider.py diff --git a/commitizen/providers.py b/commitizen/providers.py deleted file mode 100644 index df353bb365..0000000000 --- a/commitizen/providers.py +++ /dev/null @@ -1,319 +0,0 @@ -from __future__ import annotations - -import json -import re -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, Callable, ClassVar, cast - -import importlib_metadata as metadata -import tomlkit - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.git import get_tags -from commitizen.version_schemes import get_version_scheme - -PROVIDER_ENTRYPOINT = "commitizen.provider" -DEFAULT_PROVIDER = "commitizen" - - -class VersionProvider(ABC): - """ - Abstract base class for version providers. - - Each version provider should inherit and implement this class. - """ - - config: BaseConfig - - def __init__(self, config: BaseConfig): - self.config = config - - @abstractmethod - def get_version(self) -> str: - """ - Get the current version - """ - - @abstractmethod - def set_version(self, version: str): - """ - Set the new current version - """ - - -class CommitizenProvider(VersionProvider): - """ - Default version provider: Fetch and set version in commitizen config. - """ - - def get_version(self) -> str: - return self.config.settings["version"] # type: ignore - - def set_version(self, version: str): - self.config.set_key("version", version) - - -class FileProvider(VersionProvider): - """ - Base class for file-based version providers - """ - - filename: ClassVar[str] - - @property - def file(self) -> Path: - return Path() / self.filename - - -class TomlProvider(FileProvider): - """ - Base class for TOML-based version providers - """ - - def get_version(self) -> str: - document = tomlkit.parse(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = tomlkit.parse(self.file.read_text()) - self.set(document, version) - self.file.write_text(tomlkit.dumps(document)) - - def get(self, document: tomlkit.TOMLDocument) -> str: - return document["project"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - document["project"]["version"] = version # type: ignore - - -class Pep621Provider(TomlProvider): - """ - PEP-621 version management - """ - - filename = "pyproject.toml" - - -class PoetryProvider(TomlProvider): - """ - Poetry version management - """ - - filename = "pyproject.toml" - - def get(self, pyproject: tomlkit.TOMLDocument) -> str: - return pyproject["tool"]["poetry"]["version"] # type: ignore - - def set(self, pyproject: tomlkit.TOMLDocument, version: str): - pyproject["tool"]["poetry"]["version"] = version # type: ignore - - -class CargoProvider(TomlProvider): - """ - Cargo version management - - With support for `workspaces` - """ - - filename = "Cargo.toml" - - def get(self, document: tomlkit.TOMLDocument) -> str: - try: - return document["package"]["version"] # type: ignore - except tomlkit.exceptions.NonExistentKey: - ... - return document["workspace"]["package"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - try: - document["workspace"]["package"]["version"] = version # type: ignore - return - except tomlkit.exceptions.NonExistentKey: - ... - document["package"]["version"] = version # type: ignore - - -class JsonProvider(FileProvider): - """ - Base class for JSON-based version providers - """ - - indent: ClassVar[int] = 2 - - def get_version(self) -> str: - document = json.loads(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = json.loads(self.file.read_text()) - self.set(document, version) - self.file.write_text(json.dumps(document, indent=self.indent) + "\n") - - def get(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set(self, document: dict[str, Any], version: str): - document["version"] = version - - -class NpmProvider(JsonProvider): - """ - npm package.json and package-lock.json version management - """ - - indent: ClassVar[int] = 2 - package_filename = "package.json" - lock_filename = "package-lock.json" - shrinkwrap_filename = "npm-shrinkwrap.json" - - @property - def package_file(self) -> Path: - return Path() / self.package_filename - - @property - def lock_file(self) -> Path: - return Path() / self.lock_filename - - @property - def shrinkwrap_file(self) -> Path: - return Path() / self.shrinkwrap_filename - - def get_version(self) -> str: - """ - Get the current version from package.json - """ - package_document = json.loads(self.package_file.read_text()) - return self.get_package_version(package_document) - - def set_version(self, version: str) -> None: - package_document = self.set_package_version( - json.loads(self.package_file.read_text()), version - ) - self.package_file.write_text( - json.dumps(package_document, indent=self.indent) + "\n" - ) - if self.lock_file.exists(): - lock_document = self.set_lock_version( - json.loads(self.lock_file.read_text()), version - ) - self.lock_file.write_text( - json.dumps(lock_document, indent=self.indent) + "\n" - ) - if self.shrinkwrap_file.exists(): - shrinkwrap_document = self.set_shrinkwrap_version( - json.loads(self.shrinkwrap_file.read_text()), version - ) - self.shrinkwrap_file.write_text( - json.dumps(shrinkwrap_document, indent=self.indent) + "\n" - ) - - def get_package_version(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set_package_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - return document - - def set_lock_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - def set_shrinkwrap_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - -class ComposerProvider(JsonProvider): - """ - Composer version management - """ - - filename = "composer.json" - indent = 4 - - -class ScmProvider(VersionProvider): - """ - A provider fetching the current/last version from the repository history - - The version is fetched using `git describe` and is never set. - - It is meant for `setuptools-scm` or any package manager `*-scm` provider. - """ - - TAG_FORMAT_REGEXS = { - "$version": r"(?P.+)", - "$major": r"(?P\d+)", - "$minor": r"(?P\d+)", - "$patch": r"(?P\d+)", - "$prerelease": r"(?P\w+\d+)?", - "$devrelease": r"(?P\.dev\d+)?", - } - - def _tag_format_matcher(self) -> Callable[[str], str | None]: - version_scheme = get_version_scheme(self.config) - pattern = self.config.settings["tag_format"] - if pattern == "$version": - pattern = version_scheme.parser.pattern - for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): - pattern = pattern.replace(var, tag_pattern) - - regex = re.compile(f"^{pattern}$", re.VERBOSE) - - def matcher(tag: str) -> str | None: - match = regex.match(tag) - if not match: - return None - groups = match.groupdict() - if "version" in groups: - return groups["version"] - elif "major" in groups: - return "".join( - ( - groups["major"], - f".{groups['minor']}" if groups.get("minor") else "", - f".{groups['patch']}" if groups.get("patch") else "", - groups["prerelease"] if groups.get("prerelease") else "", - groups["devrelease"] if groups.get("devrelease") else "", - ) - ) - elif pattern == version_scheme.parser.pattern: - return str(version_scheme(tag)) - return None - - return matcher - - def get_version(self) -> str: - matcher = self._tag_format_matcher() - return next( - (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" - ) - - def set_version(self, version: str): - # Not necessary - pass - - -def get_provider(config: BaseConfig) -> VersionProvider: - """ - Get the version provider as defined in the configuration - - :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. - """ - provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER - try: - (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) - except ValueError: - raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') - provider_cls = ep.load() - return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py new file mode 100644 index 0000000000..16129af04b --- /dev/null +++ b/commitizen/providers/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import cast + +import importlib_metadata as metadata + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown + +from .base_provider import VersionProvider + +PROVIDER_ENTRYPOINT = "commitizen.provider" +DEFAULT_PROVIDER = "commitizen" + + +def get_provider(config: BaseConfig) -> VersionProvider: + """ + Get the version provider as defined in the configuration + + :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. + """ + provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER + try: + (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) + except ValueError: + raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') + provider_cls = ep.load() + return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/base_provider.py b/commitizen/providers/base_provider.py new file mode 100644 index 0000000000..34048318e2 --- /dev/null +++ b/commitizen/providers/base_provider.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, ClassVar + +import tomlkit + +from commitizen.config.base_config import BaseConfig + + +class VersionProvider(ABC): + """ + Abstract base class for version providers. + + Each version provider should inherit and implement this class. + """ + + config: BaseConfig + + def __init__(self, config: BaseConfig): + self.config = config + + @abstractmethod + def get_version(self) -> str: + """ + Get the current version + """ + + @abstractmethod + def set_version(self, version: str): + """ + Set the new current version + """ + + +class FileProvider(VersionProvider): + """ + Base class for file-based version providers + """ + + filename: ClassVar[str] + + @property + def file(self) -> Path: + return Path() / self.filename + + +class JsonProvider(FileProvider): + """ + Base class for JSON-based version providers + """ + + indent: ClassVar[int] = 2 + + def get_version(self) -> str: + document = json.loads(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = json.loads(self.file.read_text()) + self.set(document, version) + self.file.write_text(json.dumps(document, indent=self.indent) + "\n") + + def get(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set(self, document: dict[str, Any], version: str): + document["version"] = version + + +class TomlProvider(FileProvider): + """ + Base class for TOML-based version providers + """ + + def get_version(self) -> str: + document = tomlkit.parse(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = tomlkit.parse(self.file.read_text()) + self.set(document, version) + self.file.write_text(tomlkit.dumps(document)) + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["project"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["project"]["version"] = version # type: ignore diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py new file mode 100644 index 0000000000..f64c003edd --- /dev/null +++ b/commitizen/providers/cargo_provider.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class CargoProvider(TomlProvider): + """ + Cargo version management + + With support for `workspaces` + """ + + filename = "Cargo.toml" + + def get(self, document: tomlkit.TOMLDocument) -> str: + try: + return document["package"]["version"] # type: ignore + except tomlkit.exceptions.NonExistentKey: + ... + return document["workspace"]["package"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + try: + document["workspace"]["package"]["version"] = version # type: ignore + return + except tomlkit.exceptions.NonExistentKey: + ... + document["package"]["version"] = version # type: ignore diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py new file mode 100644 index 0000000000..fd0cdb3ebf --- /dev/null +++ b/commitizen/providers/commitizen_provider.py @@ -0,0 +1,16 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import VersionProvider + + +class CommitizenProvider(VersionProvider): + """ + Default version provider: Fetch and set version in commitizen config. + """ + + def get_version(self) -> str: + return self.config.settings["version"] # type: ignore + + def set_version(self, version: str): + self.config.set_key("version", version) diff --git a/commitizen/providers/composer_provider.py b/commitizen/providers/composer_provider.py new file mode 100644 index 0000000000..ef36af5a72 --- /dev/null +++ b/commitizen/providers/composer_provider.py @@ -0,0 +1,13 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import JsonProvider + + +class ComposerProvider(JsonProvider): + """ + Composer version management + """ + + filename = "composer.json" + indent = 4 diff --git a/commitizen/providers/npm_provider.py b/commitizen/providers/npm_provider.py new file mode 100644 index 0000000000..f625c3c6c3 --- /dev/null +++ b/commitizen/providers/npm_provider.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, ClassVar + + +from commitizen.providers.base_provider import VersionProvider + + +class NpmProvider(VersionProvider): + """ + npm package.json and package-lock.json version management + """ + + indent: ClassVar[int] = 2 + package_filename = "package.json" + lock_filename = "package-lock.json" + shrinkwrap_filename = "npm-shrinkwrap.json" + + @property + def package_file(self) -> Path: + return Path() / self.package_filename + + @property + def lock_file(self) -> Path: + return Path() / self.lock_filename + + @property + def shrinkwrap_file(self) -> Path: + return Path() / self.shrinkwrap_filename + + def get_version(self) -> str: + """ + Get the current version from package.json + """ + package_document = json.loads(self.package_file.read_text()) + return self.get_package_version(package_document) + + def set_version(self, version: str) -> None: + package_document = self.set_package_version( + json.loads(self.package_file.read_text()), version + ) + self.package_file.write_text( + json.dumps(package_document, indent=self.indent) + "\n" + ) + if self.lock_file.exists(): + lock_document = self.set_lock_version( + json.loads(self.lock_file.read_text()), version + ) + self.lock_file.write_text( + json.dumps(lock_document, indent=self.indent) + "\n" + ) + if self.shrinkwrap_file.exists(): + shrinkwrap_document = self.set_shrinkwrap_version( + json.loads(self.shrinkwrap_file.read_text()), version + ) + self.shrinkwrap_file.write_text( + json.dumps(shrinkwrap_document, indent=self.indent) + "\n" + ) + + def get_package_version(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set_package_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + return document + + def set_lock_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document + + def set_shrinkwrap_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document diff --git a/commitizen/providers/pep621_provider.py b/commitizen/providers/pep621_provider.py new file mode 100644 index 0000000000..b6d32f1a63 --- /dev/null +++ b/commitizen/providers/pep621_provider.py @@ -0,0 +1,12 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import TomlProvider + + +class Pep621Provider(TomlProvider): + """ + PEP-621 version management + """ + + filename = "pyproject.toml" diff --git a/commitizen/providers/poetry_provider.py b/commitizen/providers/poetry_provider.py new file mode 100644 index 0000000000..d301131115 --- /dev/null +++ b/commitizen/providers/poetry_provider.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class PoetryProvider(TomlProvider): + """ + Poetry version management + """ + + filename = "pyproject.toml" + + def get(self, pyproject: tomlkit.TOMLDocument) -> str: + return pyproject["tool"]["poetry"]["version"] # type: ignore + + def set(self, pyproject: tomlkit.TOMLDocument, version: str): + pyproject["tool"]["poetry"]["version"] = version # type: ignore diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py new file mode 100644 index 0000000000..bc9dda4b8a --- /dev/null +++ b/commitizen/providers/scm_provider.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import re +from typing import Callable, cast + + +from commitizen.git import get_tags +from commitizen.version_schemes import get_version_scheme + +from commitizen.providers.base_provider import VersionProvider + + +class ScmProvider(VersionProvider): + """ + A provider fetching the current/last version from the repository history + + The version is fetched using `git describe` and is never set. + + It is meant for `setuptools-scm` or any package manager `*-scm` provider. + """ + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + } + + def _tag_format_matcher(self) -> Callable[[str], str | None]: + version_scheme = get_version_scheme(self.config) + pattern = self.config.settings["tag_format"] + if pattern == "$version": + pattern = version_scheme.parser.pattern + for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): + pattern = pattern.replace(var, tag_pattern) + + regex = re.compile(f"^{pattern}$", re.VERBOSE) + + def matcher(tag: str) -> str | None: + match = regex.match(tag) + if not match: + return None + groups = match.groupdict() + if "version" in groups: + return groups["version"] + elif "major" in groups: + return "".join( + ( + groups["major"], + f".{groups['minor']}" if groups.get("minor") else "", + f".{groups['patch']}" if groups.get("patch") else "", + groups["prerelease"] if groups.get("prerelease") else "", + groups["devrelease"] if groups.get("devrelease") else "", + ) + ) + elif pattern == version_scheme.parser.pattern: + return str(version_scheme(tag)) + return None + + return matcher + + def get_version(self) -> str: + matcher = self._tag_format_matcher() + return next( + (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" + ) + + def set_version(self, version: str): + # Not necessary + pass diff --git a/pyproject.toml b/pyproject.toml index 3101d845a4..8b9d6430db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,13 +84,13 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers:CargoProvider" -commitizen = "commitizen.providers:CommitizenProvider" -composer = "commitizen.providers:ComposerProvider" -npm = "commitizen.providers:NpmProvider" -pep621 = "commitizen.providers:Pep621Provider" -poetry = "commitizen.providers:PoetryProvider" -scm = "commitizen.providers:ScmProvider" +cargo = "commitizen.providers.cargo_provider:CargoProvider" +commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" +composer = "commitizen.providers.composer_provider:ComposerProvider" +npm = "commitizen.providers.npm_provider:NpmProvider" +pep621 = "commitizen.providers.pep621_provider:Pep621Provider" +poetry = "commitizen.providers.poetry_provider:PoetryProvider" +scm = "commitizen.providers.scm_provider:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" From 3eecbbef8be3024ec48bc7793130ce80f98758af Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:20:49 -0400 Subject: [PATCH 14/32] refactor(tests): split provider tests into separate files for maintainability closes #812 --- tests/providers/test_base_provider.py | 35 +++ tests/providers/test_cargo_provider.py | 75 +++++ tests/providers/test_commitizen_provider.py | 33 ++ tests/providers/test_composer_provider.py | 62 ++++ tests/providers/test_npm_provider.py | 112 +++++++ tests/providers/test_pep621_provider.py | 58 ++++ tests/providers/test_poetry_provider.py | 58 ++++ tests/providers/test_scm_provider.py | 68 +++++ tests/test_version_providers.py | 316 -------------------- 9 files changed, 501 insertions(+), 316 deletions(-) create mode 100644 tests/providers/test_base_provider.py create mode 100644 tests/providers/test_cargo_provider.py create mode 100644 tests/providers/test_commitizen_provider.py create mode 100644 tests/providers/test_composer_provider.py create mode 100644 tests/providers/test_npm_provider.py create mode 100644 tests/providers/test_pep621_provider.py create mode 100644 tests/providers/test_poetry_provider.py create mode 100644 tests/providers/test_scm_provider.py delete mode 100644 tests/test_version_providers.py diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py new file mode 100644 index 0000000000..fb5a3c3b39 --- /dev/null +++ b/tests/providers/test_base_provider.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.providers import get_provider +from commitizen.providers.commitizen_provider import CommitizenProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_default_version_provider_is_commitizen_config(config: BaseConfig): + provider = get_provider(config) + + assert isinstance(provider, CommitizenProvider) + + +def test_raise_for_unknown_provider(config: BaseConfig): + config.settings["version_provider"] = "unknown" + with pytest.raises(VersionProviderUnknown): + get_provider(config) diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py new file mode 100644 index 0000000000..d6b0573828 --- /dev/null +++ b/tests/providers/test_cargo_provider.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.cargo_provider import CargoProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +CARGO_TOML = """\ +[package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_EXPECTED = """\ +[package] +name = "whatever" +version = "42.1" +""" + +CARGO_WORKSPACE_TOML = """\ +[workspace.package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_WORKSPACE_EXPECTED = """\ +[workspace.package] +name = "whatever" +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ( + (CARGO_TOML, CARGO_EXPECTED), + (CARGO_WORKSPACE_TOML, CARGO_WORKSPACE_EXPECTED), + ), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py new file mode 100644 index 0000000000..8cca153c2f --- /dev/null +++ b/tests/providers/test_commitizen_provider.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers.commitizen_provider import CommitizenProvider + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): + config.settings["version"] = "42" + mock = mocker.patch.object(config, "set_key") + + provider = CommitizenProvider(config) + assert provider.get_version() == "42" + + provider.set_version("43.1") + mock.assert_called_once_with("version", "43.1") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py new file mode 100644 index 0000000000..11d295152e --- /dev/null +++ b/tests/providers/test_composer_provider.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.composer_provider import ComposerProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +COMPOSER_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +COMPOSER_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + + +@pytest.mark.parametrize( + "content, expected", + ((COMPOSER_JSON, COMPOSER_EXPECTED),), +) +def test_composer_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = ComposerProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "composer" + + provider = get_provider(config) + assert isinstance(provider, ComposerProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py new file mode 100644 index 0000000000..3b458a93de --- /dev/null +++ b/tests/providers/test_npm_provider.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.npm_provider import NpmProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +NPM_PACKAGE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +NPM_PACKAGE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + +NPM_LOCKFILE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "0.1.0" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + +NPM_LOCKFILE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "42.1" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + + +@pytest.mark.parametrize( + "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +@pytest.mark.parametrize( + "pkg_lock_content, pkg_lock_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +def test_npm_provider( + config: BaseConfig, + chdir: Path, + pkg_lock_content: str, + pkg_lock_expected: str, + pkg_shrinkwrap_content: str, + pkg_shrinkwrap_expected: str, +): + pkg = chdir / NpmProvider.package_filename + pkg.write_text(dedent(NPM_PACKAGE_JSON)) + if pkg_lock_content: + pkg_lock = chdir / NpmProvider.lock_filename + pkg_lock.write_text(dedent(pkg_lock_content)) + if pkg_shrinkwrap_content: + pkg_shrinkwrap = chdir / NpmProvider.shrinkwrap_filename + pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) + config.settings["version_provider"] = "npm" + + provider = get_provider(config) + assert isinstance(provider, NpmProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) + if pkg_lock_content: + assert pkg_lock.read_text() == dedent(pkg_lock_expected) + if pkg_shrinkwrap_content: + assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py new file mode 100644 index 0000000000..6288b47093 --- /dev/null +++ b/tests/providers/test_pep621_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.pep621_provider import Pep621Provider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +PEP621_TOML = """\ +[project] +version = "0.1.0" +""" + +PEP621_EXPECTED = """\ +[project] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((PEP621_TOML, PEP621_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = Pep621Provider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "pep621" + + provider = get_provider(config) + assert isinstance(provider, Pep621Provider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py new file mode 100644 index 0000000000..6a38d06075 --- /dev/null +++ b/tests/providers/test_poetry_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.poetry_provider import PoetryProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +POETRY_TOML = """\ +[tool.poetry] +version = "0.1.0" +""" + +POETRY_EXPECTED = """\ +[tool.poetry] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((POETRY_TOML, POETRY_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = PoetryProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "poetry" + + provider = get_provider(config) + assert isinstance(provider, PoetryProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py new file mode 100644 index 0000000000..bf8e28fd2a --- /dev/null +++ b/tests/providers/test_scm_provider.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.scm_provider import ScmProvider +from tests.utils import create_file_and_commit, create_tag + +if TYPE_CHECKING: + pass + + +@pytest.mark.parametrize( + "tag_format,tag,expected_version", + ( + # If tag_format is $version (the default), version_scheme.parser is used. + # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. + ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), + ("$version", "0.1.0", "0.1.0"), + ("$version", "v0.1.0", "0.1.0"), + ("$version", "v-0.1.0", "0.0.0"), + # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are + # much more lenient but require a v prefix. + ("v$version", "v0.1.0", "0.1.0"), + ("v$version", "no-match-because-no-v-prefix", "0.0.0"), + ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), + ("version-$version", "version-0.1.0", "0.1.0"), + ("version-$version", "version-0.1", "0.1"), + ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), + ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), + ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), + ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), + ), +) +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider( + config: BaseConfig, tag_format: str, tag: str, expected_version: str +): + create_file_and_commit("test: fake commit") + create_tag(tag) + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + + config.settings["version_provider"] = "scm" + config.settings["tag_format"] = tag_format + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + actual_version = provider.get_version() + assert actual_version == expected_version + + # Should not fail on set_version() + provider.set_version("43.1") + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" diff --git a/tests/test_version_providers.py b/tests/test_version_providers.py deleted file mode 100644 index 1cf0e736d8..0000000000 --- a/tests/test_version_providers.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from textwrap import dedent -from typing import TYPE_CHECKING, Iterator - -import pytest - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.providers import ( - CargoProvider, - CommitizenProvider, - ComposerProvider, - NpmProvider, - Pep621Provider, - PoetryProvider, - ScmProvider, - VersionProvider, - get_provider, -) -from tests.utils import create_file_and_commit, create_tag - -if TYPE_CHECKING: - from pytest_mock import MockerFixture - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - -def test_default_version_provider_is_commitizen_config(config: BaseConfig): - provider = get_provider(config) - - assert isinstance(provider, CommitizenProvider) - - -def test_raise_for_unknown_provider(config: BaseConfig): - config.settings["version_provider"] = "unknown" - with pytest.raises(VersionProviderUnknown): - get_provider(config) - - -def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): - config.settings["version"] = "42" - mock = mocker.patch.object(config, "set_key") - - provider = CommitizenProvider(config) - assert provider.get_version() == "42" - - provider.set_version("43.1") - mock.assert_called_once_with("version", "43.1") - - -FILE_PROVIDERS = [ - ( - "pep621", - "pyproject.toml", - Pep621Provider, - """\ - [project] - version = "0.1.0" - """, - """\ - [project] - version = "42.1" - """, - ), - ( - "poetry", - "pyproject.toml", - PoetryProvider, - """\ - [tool.poetry] - version = "0.1.0" - """, - """\ - [tool.poetry] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [workspace.package] - version = "0.1.0" - """, - """\ - [workspace.package] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [package] - version = "0.1.0" - """, - """\ - [package] - version = "42.1" - """, - ), - ( - "npm", - "package.json", - NpmProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), - ( - "composer", - "composer.json", - ComposerProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), -] - - -@pytest.mark.parametrize( - "id,filename,cls,content,expected", - FILE_PROVIDERS, -) -def test_file_providers( - config: BaseConfig, - chdir: Path, - id: str, - filename: str, - cls: type[VersionProvider], - content: str, - expected: str, -): - file = chdir / filename - file.write_text(dedent(content)) - config.settings["version_provider"] = id - - provider = get_provider(config) - assert isinstance(provider, cls) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert file.read_text() == dedent(expected) - - -@pytest.mark.parametrize( - "tag_format,tag,expected_version", - ( - # If tag_format is $version (the default), version_scheme.parser is used. - # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. - ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), - ("$version", "0.1.0", "0.1.0"), - ("$version", "v0.1.0", "0.1.0"), - ("$version", "v-0.1.0", "0.0.0"), - # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are - # much more lenient but require a v prefix. - ("v$version", "v0.1.0", "0.1.0"), - ("v$version", "no-match-because-no-v-prefix", "0.0.0"), - ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), - ("version-$version", "version-0.1.0", "0.1.0"), - ("version-$version", "version-0.1", "0.1"), - ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), - ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), - ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), - ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), - ), -) -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider( - config: BaseConfig, tag_format: str, tag: str, expected_version: str -): - create_file_and_commit("test: fake commit") - create_tag(tag) - create_file_and_commit("test: fake commit") - create_tag("should-not-match") - - config.settings["version_provider"] = "scm" - config.settings["tag_format"] = tag_format - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - actual_version = provider.get_version() - assert actual_version == expected_version - - # Should not fail on set_version() - provider.set_version("43.1") - - -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): - config.settings["version_provider"] = "scm" - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - assert provider.get_version() == "0.0.0" - - -NPM_PACKAGE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0" -} -""" - -NPM_PACKAGE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1" -} -""" - -NPM_LOCKFILE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "0.1.0" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - -NPM_LOCKFILE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "42.1" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - - -@pytest.mark.parametrize( - "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -@pytest.mark.parametrize( - "pkg_lock_content, pkg_lock_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -def test_npm_provider( - config: BaseConfig, - chdir: Path, - pkg_lock_content: str, - pkg_lock_expected: str, - pkg_shrinkwrap_content: str, - pkg_shrinkwrap_expected: str, -): - pkg = chdir / "package.json" - pkg.write_text(dedent(NPM_PACKAGE_JSON)) - if pkg_lock_content: - pkg_lock = chdir / "package-lock.json" - pkg_lock.write_text(dedent(pkg_lock_content)) - if pkg_shrinkwrap_content: - pkg_shrinkwrap = chdir / "npm-shrinkwrap.json" - pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) - config.settings["version_provider"] = "npm" - - provider = get_provider(config) - assert isinstance(provider, NpmProvider) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) - if pkg_lock_content: - assert pkg_lock.read_text() == dedent(pkg_lock_expected) - if pkg_shrinkwrap_content: - assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) From 2c1b4ec1679d348d200522d8dff7b2e7921f29f4 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:30:46 -0400 Subject: [PATCH 15/32] refactor(provider): fully qualify import of commitizen.providers.base_provider --- commitizen/providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 16129af04b..37e330582e 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -7,7 +7,7 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionProviderUnknown -from .base_provider import VersionProvider +from commitizen.providers.base_provider import VersionProvider PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" From 4b5becf539d5fb542b710cf672ef7ff43ef852df Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:37:15 -0400 Subject: [PATCH 16/32] refactor(provider): move chdir to providers/conftest.py --- tests/providers/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/providers/conftest.py diff --git a/tests/providers/conftest.py b/tests/providers/conftest.py new file mode 100644 index 0000000000..b4432ca524 --- /dev/null +++ b/tests/providers/conftest.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Iterator + +import pytest + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) From 765d3f49b09a84dd0a1150b47dd33b837aeef9f2 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:38:14 -0400 Subject: [PATCH 17/32] refactor(provider): remove unnecessary TYPE_CHECKING from provider tests --- tests/providers/test_base_provider.py | 5 +---- tests/providers/test_cargo_provider.py | 14 -------------- tests/providers/test_commitizen_provider.py | 13 +------------ tests/providers/test_composer_provider.py | 14 -------------- tests/providers/test_npm_provider.py | 13 ------------- tests/providers/test_pep621_provider.py | 14 -------------- tests/providers/test_poetry_provider.py | 14 -------------- tests/providers/test_scm_provider.py | 4 ---- 8 files changed, 2 insertions(+), 89 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index fb5a3c3b39..d5557a3577 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import Iterator import pytest @@ -11,9 +11,6 @@ from commitizen.providers import get_provider from commitizen.providers.commitizen_provider import CommitizenProvider -if TYPE_CHECKING: - pass - @pytest.fixture def chdir(tmp_path: Path) -> Iterator[Path]: diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index d6b0573828..cde868deb5 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.cargo_provider import CargoProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - CARGO_TOML = """\ [package] name = "whatever" diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py index 8cca153c2f..887adf3d12 100644 --- a/tests/providers/test_commitizen_provider.py +++ b/tests/providers/test_commitizen_provider.py @@ -1,10 +1,7 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING -import pytest from commitizen.config.base_config import BaseConfig from commitizen.providers.commitizen_provider import CommitizenProvider @@ -14,14 +11,6 @@ from pytest_mock import MockerFixture -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): config.settings["version"] = "42" mock = mocker.patch.object(config, "set_key") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py index 11d295152e..ce72ae4703 100644 --- a/tests/providers/test_composer_provider.py +++ b/tests/providers/test_composer_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.composer_provider import ComposerProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - COMPOSER_JSON = """\ { "name": "whatever", diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py index 3b458a93de..2e5ceb42d5 100644 --- a/tests/providers/test_npm_provider.py +++ b/tests/providers/test_npm_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -11,17 +9,6 @@ from commitizen.providers import get_provider from commitizen.providers.npm_provider import NpmProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - NPM_PACKAGE_JSON = """\ { diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py index 6288b47093..9e82213294 100644 --- a/tests/providers/test_pep621_provider.py +++ b/tests/providers/test_pep621_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.pep621_provider import Pep621Provider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - PEP621_TOML = """\ [project] version = "0.1.0" diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py index 6a38d06075..9e327db6ad 100644 --- a/tests/providers/test_poetry_provider.py +++ b/tests/providers/test_poetry_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.poetry_provider import PoetryProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - POETRY_TOML = """\ [tool.poetry] version = "0.1.0" diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py index bf8e28fd2a..a0bfc46474 100644 --- a/tests/providers/test_scm_provider.py +++ b/tests/providers/test_scm_provider.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING import pytest @@ -9,9 +8,6 @@ from commitizen.providers.scm_provider import ScmProvider from tests.utils import create_file_and_commit, create_tag -if TYPE_CHECKING: - pass - @pytest.mark.parametrize( "tag_format,tag,expected_version", From 023333481ab1c48c8c897b87a53d27de4c2d81cc Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 12:00:11 -0400 Subject: [PATCH 18/32] refactor(provider): import all providers to providers/__init__.py to preserve plugin config --- commitizen/providers/__init__.py | 7 +++ pyproject.toml | 84 +++++++++++++++----------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 37e330582e..6f777096cf 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,6 +8,13 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider +from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 +from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 +from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 +from commitizen.providers.npm_provider import NpmProvider # noqa: F401 +from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 +from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 +from commitizen.providers.scm_provider import ScmProvider # noqa: F401 PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" diff --git a/pyproject.toml b/pyproject.toml index 8b9d6430db..cafa72681a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.7.1" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen" + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7"} +importlib_metadata = { version = ">=4.13,<7" } [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -84,46 +84,46 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers.cargo_provider:CargoProvider" -commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" -composer = "commitizen.providers.composer_provider:ComposerProvider" -npm = "commitizen.providers.npm_provider:NpmProvider" -pep621 = "commitizen.providers.pep621_provider:Pep621Provider" -poetry = "commitizen.providers.poetry_provider:PoetryProvider" -scm = "commitizen.providers.scm_provider:ScmProvider" +cargo = "commitizen.providers:CargoProvider" +commitizen = "commitizen.providers:CommitizenProvider" +composer = "commitizen.providers:ComposerProvider" +npm = "commitizen.providers:NpmProvider" +pep621 = "commitizen.providers:Pep621Provider" +poetry = "commitizen.providers:PoetryProvider" +scm = "commitizen.providers:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] - [tool.coverage.report] - show_missing = true - exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', - ] - omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*' - ] +[tool.coverage.report] +show_missing = true +exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', +] +omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', +] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,11 +134,7 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = [ - "E501", - "D1", - "D415", -] +ignore = ["E501", "D1", "D415"] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -156,5 +152,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From 0c52b09488e86bac02e2dbd55628ddbdaa3b2099 Mon Sep 17 00:00:00 2001 From: "David L. Day" <1132144+davidlday@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:24:19 -0400 Subject: [PATCH 19/32] chore: removed chdir from tests/providers/test_base_provider.py Co-authored-by: Wei Lee --- tests/providers/test_base_provider.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index d5557a3577..f2601c4ba6 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -12,12 +12,6 @@ from commitizen.providers.commitizen_provider import CommitizenProvider -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) def test_default_version_provider_is_commitizen_config(config: BaseConfig): From ba73dcbfdb8e5fdf6e958bdf7d3a54f05d24a1ea Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:38:58 -0400 Subject: [PATCH 20/32] refactor: revert unintentionally reformatting --- pyproject.toml | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cafa72681a..d81f554e47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.7.1" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen", + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7" } +importlib_metadata = { version = ">=4.13,<7"} [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -97,33 +97,33 @@ pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] -[tool.coverage.report] -show_missing = true -exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', -] -omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*', -] + [tool.coverage.report] + show_missing = true + exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', + ] + omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', + ] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,7 +134,11 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = ["E501", "D1", "D415"] +ignore = [ + "E501", + "D1", + "D415" +] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -152,5 +156,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From 580da4a74832454f5b733de5b732610eb3c157e5 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:40:36 -0400 Subject: [PATCH 21/32] refactor: remove unused imports --- tests/providers/test_base_provider.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index f2601c4ba6..09a2bd71cd 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -1,8 +1,5 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import Iterator import pytest @@ -12,8 +9,6 @@ from commitizen.providers.commitizen_provider import CommitizenProvider - - def test_default_version_provider_is_commitizen_config(config: BaseConfig): provider = get_provider(config) From 2eb75c6437106756ef988378b9a78788e6c4eecd Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:41:23 -0400 Subject: [PATCH 22/32] refactor: use __all__ to name exports --- commitizen/providers/__init__.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 6f777096cf..51302d2b37 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,13 +8,24 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider -from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 -from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 -from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 -from commitizen.providers.npm_provider import NpmProvider # noqa: F401 -from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 -from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 -from commitizen.providers.scm_provider import ScmProvider # noqa: F401 +from commitizen.providers.cargo_provider import CargoProvider +from commitizen.providers.commitizen_provider import CommitizenProvider +from commitizen.providers.composer_provider import ComposerProvider +from commitizen.providers.npm_provider import NpmProvider +from commitizen.providers.pep621_provider import Pep621Provider +from commitizen.providers.poetry_provider import PoetryProvider +from commitizen.providers.scm_provider import ScmProvider + +__all__ = [ + "get_provider", + "CargoProvider", + "CommitizenProvider", + "ComposerProvider", + "NpmProvider", + "Pep621Provider", + "PoetryProvider", + "ScmProvider", +] PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" From e5cf494c133e2a5ea78ad07129bde97644dcd092 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:19:25 -0400 Subject: [PATCH 23/32] refactor(provider): split providers into separate files for maintainability closes #812 --- commitizen/providers.py | 319 -------------------- commitizen/providers/__init__.py | 28 ++ commitizen/providers/base_provider.py | 91 ++++++ commitizen/providers/cargo_provider.py | 31 ++ commitizen/providers/commitizen_provider.py | 16 + commitizen/providers/composer_provider.py | 13 + commitizen/providers/npm_provider.py | 83 +++++ commitizen/providers/pep621_provider.py | 12 + commitizen/providers/poetry_provider.py | 20 ++ commitizen/providers/scm_provider.py | 72 +++++ pyproject.toml | 14 +- 11 files changed, 373 insertions(+), 326 deletions(-) delete mode 100644 commitizen/providers.py create mode 100644 commitizen/providers/__init__.py create mode 100644 commitizen/providers/base_provider.py create mode 100644 commitizen/providers/cargo_provider.py create mode 100644 commitizen/providers/commitizen_provider.py create mode 100644 commitizen/providers/composer_provider.py create mode 100644 commitizen/providers/npm_provider.py create mode 100644 commitizen/providers/pep621_provider.py create mode 100644 commitizen/providers/poetry_provider.py create mode 100644 commitizen/providers/scm_provider.py diff --git a/commitizen/providers.py b/commitizen/providers.py deleted file mode 100644 index df353bb365..0000000000 --- a/commitizen/providers.py +++ /dev/null @@ -1,319 +0,0 @@ -from __future__ import annotations - -import json -import re -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, Callable, ClassVar, cast - -import importlib_metadata as metadata -import tomlkit - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.git import get_tags -from commitizen.version_schemes import get_version_scheme - -PROVIDER_ENTRYPOINT = "commitizen.provider" -DEFAULT_PROVIDER = "commitizen" - - -class VersionProvider(ABC): - """ - Abstract base class for version providers. - - Each version provider should inherit and implement this class. - """ - - config: BaseConfig - - def __init__(self, config: BaseConfig): - self.config = config - - @abstractmethod - def get_version(self) -> str: - """ - Get the current version - """ - - @abstractmethod - def set_version(self, version: str): - """ - Set the new current version - """ - - -class CommitizenProvider(VersionProvider): - """ - Default version provider: Fetch and set version in commitizen config. - """ - - def get_version(self) -> str: - return self.config.settings["version"] # type: ignore - - def set_version(self, version: str): - self.config.set_key("version", version) - - -class FileProvider(VersionProvider): - """ - Base class for file-based version providers - """ - - filename: ClassVar[str] - - @property - def file(self) -> Path: - return Path() / self.filename - - -class TomlProvider(FileProvider): - """ - Base class for TOML-based version providers - """ - - def get_version(self) -> str: - document = tomlkit.parse(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = tomlkit.parse(self.file.read_text()) - self.set(document, version) - self.file.write_text(tomlkit.dumps(document)) - - def get(self, document: tomlkit.TOMLDocument) -> str: - return document["project"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - document["project"]["version"] = version # type: ignore - - -class Pep621Provider(TomlProvider): - """ - PEP-621 version management - """ - - filename = "pyproject.toml" - - -class PoetryProvider(TomlProvider): - """ - Poetry version management - """ - - filename = "pyproject.toml" - - def get(self, pyproject: tomlkit.TOMLDocument) -> str: - return pyproject["tool"]["poetry"]["version"] # type: ignore - - def set(self, pyproject: tomlkit.TOMLDocument, version: str): - pyproject["tool"]["poetry"]["version"] = version # type: ignore - - -class CargoProvider(TomlProvider): - """ - Cargo version management - - With support for `workspaces` - """ - - filename = "Cargo.toml" - - def get(self, document: tomlkit.TOMLDocument) -> str: - try: - return document["package"]["version"] # type: ignore - except tomlkit.exceptions.NonExistentKey: - ... - return document["workspace"]["package"]["version"] # type: ignore - - def set(self, document: tomlkit.TOMLDocument, version: str): - try: - document["workspace"]["package"]["version"] = version # type: ignore - return - except tomlkit.exceptions.NonExistentKey: - ... - document["package"]["version"] = version # type: ignore - - -class JsonProvider(FileProvider): - """ - Base class for JSON-based version providers - """ - - indent: ClassVar[int] = 2 - - def get_version(self) -> str: - document = json.loads(self.file.read_text()) - return self.get(document) - - def set_version(self, version: str): - document = json.loads(self.file.read_text()) - self.set(document, version) - self.file.write_text(json.dumps(document, indent=self.indent) + "\n") - - def get(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set(self, document: dict[str, Any], version: str): - document["version"] = version - - -class NpmProvider(JsonProvider): - """ - npm package.json and package-lock.json version management - """ - - indent: ClassVar[int] = 2 - package_filename = "package.json" - lock_filename = "package-lock.json" - shrinkwrap_filename = "npm-shrinkwrap.json" - - @property - def package_file(self) -> Path: - return Path() / self.package_filename - - @property - def lock_file(self) -> Path: - return Path() / self.lock_filename - - @property - def shrinkwrap_file(self) -> Path: - return Path() / self.shrinkwrap_filename - - def get_version(self) -> str: - """ - Get the current version from package.json - """ - package_document = json.loads(self.package_file.read_text()) - return self.get_package_version(package_document) - - def set_version(self, version: str) -> None: - package_document = self.set_package_version( - json.loads(self.package_file.read_text()), version - ) - self.package_file.write_text( - json.dumps(package_document, indent=self.indent) + "\n" - ) - if self.lock_file.exists(): - lock_document = self.set_lock_version( - json.loads(self.lock_file.read_text()), version - ) - self.lock_file.write_text( - json.dumps(lock_document, indent=self.indent) + "\n" - ) - if self.shrinkwrap_file.exists(): - shrinkwrap_document = self.set_shrinkwrap_version( - json.loads(self.shrinkwrap_file.read_text()), version - ) - self.shrinkwrap_file.write_text( - json.dumps(shrinkwrap_document, indent=self.indent) + "\n" - ) - - def get_package_version(self, document: dict[str, Any]) -> str: - return document["version"] # type: ignore - - def set_package_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - return document - - def set_lock_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - def set_shrinkwrap_version( - self, document: dict[str, Any], version: str - ) -> dict[str, Any]: - document["version"] = version - document["packages"][""]["version"] = version - return document - - -class ComposerProvider(JsonProvider): - """ - Composer version management - """ - - filename = "composer.json" - indent = 4 - - -class ScmProvider(VersionProvider): - """ - A provider fetching the current/last version from the repository history - - The version is fetched using `git describe` and is never set. - - It is meant for `setuptools-scm` or any package manager `*-scm` provider. - """ - - TAG_FORMAT_REGEXS = { - "$version": r"(?P.+)", - "$major": r"(?P\d+)", - "$minor": r"(?P\d+)", - "$patch": r"(?P\d+)", - "$prerelease": r"(?P\w+\d+)?", - "$devrelease": r"(?P\.dev\d+)?", - } - - def _tag_format_matcher(self) -> Callable[[str], str | None]: - version_scheme = get_version_scheme(self.config) - pattern = self.config.settings["tag_format"] - if pattern == "$version": - pattern = version_scheme.parser.pattern - for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): - pattern = pattern.replace(var, tag_pattern) - - regex = re.compile(f"^{pattern}$", re.VERBOSE) - - def matcher(tag: str) -> str | None: - match = regex.match(tag) - if not match: - return None - groups = match.groupdict() - if "version" in groups: - return groups["version"] - elif "major" in groups: - return "".join( - ( - groups["major"], - f".{groups['minor']}" if groups.get("minor") else "", - f".{groups['patch']}" if groups.get("patch") else "", - groups["prerelease"] if groups.get("prerelease") else "", - groups["devrelease"] if groups.get("devrelease") else "", - ) - ) - elif pattern == version_scheme.parser.pattern: - return str(version_scheme(tag)) - return None - - return matcher - - def get_version(self) -> str: - matcher = self._tag_format_matcher() - return next( - (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" - ) - - def set_version(self, version: str): - # Not necessary - pass - - -def get_provider(config: BaseConfig) -> VersionProvider: - """ - Get the version provider as defined in the configuration - - :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. - """ - provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER - try: - (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) - except ValueError: - raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') - provider_cls = ep.load() - return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py new file mode 100644 index 0000000000..16129af04b --- /dev/null +++ b/commitizen/providers/__init__.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import cast + +import importlib_metadata as metadata + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown + +from .base_provider import VersionProvider + +PROVIDER_ENTRYPOINT = "commitizen.provider" +DEFAULT_PROVIDER = "commitizen" + + +def get_provider(config: BaseConfig) -> VersionProvider: + """ + Get the version provider as defined in the configuration + + :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. + """ + provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER + try: + (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) + except ValueError: + raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') + provider_cls = ep.load() + return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/providers/base_provider.py b/commitizen/providers/base_provider.py new file mode 100644 index 0000000000..34048318e2 --- /dev/null +++ b/commitizen/providers/base_provider.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, ClassVar + +import tomlkit + +from commitizen.config.base_config import BaseConfig + + +class VersionProvider(ABC): + """ + Abstract base class for version providers. + + Each version provider should inherit and implement this class. + """ + + config: BaseConfig + + def __init__(self, config: BaseConfig): + self.config = config + + @abstractmethod + def get_version(self) -> str: + """ + Get the current version + """ + + @abstractmethod + def set_version(self, version: str): + """ + Set the new current version + """ + + +class FileProvider(VersionProvider): + """ + Base class for file-based version providers + """ + + filename: ClassVar[str] + + @property + def file(self) -> Path: + return Path() / self.filename + + +class JsonProvider(FileProvider): + """ + Base class for JSON-based version providers + """ + + indent: ClassVar[int] = 2 + + def get_version(self) -> str: + document = json.loads(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = json.loads(self.file.read_text()) + self.set(document, version) + self.file.write_text(json.dumps(document, indent=self.indent) + "\n") + + def get(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set(self, document: dict[str, Any], version: str): + document["version"] = version + + +class TomlProvider(FileProvider): + """ + Base class for TOML-based version providers + """ + + def get_version(self) -> str: + document = tomlkit.parse(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = tomlkit.parse(self.file.read_text()) + self.set(document, version) + self.file.write_text(tomlkit.dumps(document)) + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["project"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["project"]["version"] = version # type: ignore diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py new file mode 100644 index 0000000000..f64c003edd --- /dev/null +++ b/commitizen/providers/cargo_provider.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class CargoProvider(TomlProvider): + """ + Cargo version management + + With support for `workspaces` + """ + + filename = "Cargo.toml" + + def get(self, document: tomlkit.TOMLDocument) -> str: + try: + return document["package"]["version"] # type: ignore + except tomlkit.exceptions.NonExistentKey: + ... + return document["workspace"]["package"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + try: + document["workspace"]["package"]["version"] = version # type: ignore + return + except tomlkit.exceptions.NonExistentKey: + ... + document["package"]["version"] = version # type: ignore diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py new file mode 100644 index 0000000000..fd0cdb3ebf --- /dev/null +++ b/commitizen/providers/commitizen_provider.py @@ -0,0 +1,16 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import VersionProvider + + +class CommitizenProvider(VersionProvider): + """ + Default version provider: Fetch and set version in commitizen config. + """ + + def get_version(self) -> str: + return self.config.settings["version"] # type: ignore + + def set_version(self, version: str): + self.config.set_key("version", version) diff --git a/commitizen/providers/composer_provider.py b/commitizen/providers/composer_provider.py new file mode 100644 index 0000000000..ef36af5a72 --- /dev/null +++ b/commitizen/providers/composer_provider.py @@ -0,0 +1,13 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import JsonProvider + + +class ComposerProvider(JsonProvider): + """ + Composer version management + """ + + filename = "composer.json" + indent = 4 diff --git a/commitizen/providers/npm_provider.py b/commitizen/providers/npm_provider.py new file mode 100644 index 0000000000..f625c3c6c3 --- /dev/null +++ b/commitizen/providers/npm_provider.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, ClassVar + + +from commitizen.providers.base_provider import VersionProvider + + +class NpmProvider(VersionProvider): + """ + npm package.json and package-lock.json version management + """ + + indent: ClassVar[int] = 2 + package_filename = "package.json" + lock_filename = "package-lock.json" + shrinkwrap_filename = "npm-shrinkwrap.json" + + @property + def package_file(self) -> Path: + return Path() / self.package_filename + + @property + def lock_file(self) -> Path: + return Path() / self.lock_filename + + @property + def shrinkwrap_file(self) -> Path: + return Path() / self.shrinkwrap_filename + + def get_version(self) -> str: + """ + Get the current version from package.json + """ + package_document = json.loads(self.package_file.read_text()) + return self.get_package_version(package_document) + + def set_version(self, version: str) -> None: + package_document = self.set_package_version( + json.loads(self.package_file.read_text()), version + ) + self.package_file.write_text( + json.dumps(package_document, indent=self.indent) + "\n" + ) + if self.lock_file.exists(): + lock_document = self.set_lock_version( + json.loads(self.lock_file.read_text()), version + ) + self.lock_file.write_text( + json.dumps(lock_document, indent=self.indent) + "\n" + ) + if self.shrinkwrap_file.exists(): + shrinkwrap_document = self.set_shrinkwrap_version( + json.loads(self.shrinkwrap_file.read_text()), version + ) + self.shrinkwrap_file.write_text( + json.dumps(shrinkwrap_document, indent=self.indent) + "\n" + ) + + def get_package_version(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set_package_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + return document + + def set_lock_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document + + def set_shrinkwrap_version( + self, document: dict[str, Any], version: str + ) -> dict[str, Any]: + document["version"] = version + document["packages"][""]["version"] = version + return document diff --git a/commitizen/providers/pep621_provider.py b/commitizen/providers/pep621_provider.py new file mode 100644 index 0000000000..b6d32f1a63 --- /dev/null +++ b/commitizen/providers/pep621_provider.py @@ -0,0 +1,12 @@ +from __future__ import annotations + + +from commitizen.providers.base_provider import TomlProvider + + +class Pep621Provider(TomlProvider): + """ + PEP-621 version management + """ + + filename = "pyproject.toml" diff --git a/commitizen/providers/poetry_provider.py b/commitizen/providers/poetry_provider.py new file mode 100644 index 0000000000..d301131115 --- /dev/null +++ b/commitizen/providers/poetry_provider.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +import tomlkit + + +from commitizen.providers.base_provider import TomlProvider + + +class PoetryProvider(TomlProvider): + """ + Poetry version management + """ + + filename = "pyproject.toml" + + def get(self, pyproject: tomlkit.TOMLDocument) -> str: + return pyproject["tool"]["poetry"]["version"] # type: ignore + + def set(self, pyproject: tomlkit.TOMLDocument, version: str): + pyproject["tool"]["poetry"]["version"] = version # type: ignore diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py new file mode 100644 index 0000000000..bc9dda4b8a --- /dev/null +++ b/commitizen/providers/scm_provider.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import re +from typing import Callable, cast + + +from commitizen.git import get_tags +from commitizen.version_schemes import get_version_scheme + +from commitizen.providers.base_provider import VersionProvider + + +class ScmProvider(VersionProvider): + """ + A provider fetching the current/last version from the repository history + + The version is fetched using `git describe` and is never set. + + It is meant for `setuptools-scm` or any package manager `*-scm` provider. + """ + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + } + + def _tag_format_matcher(self) -> Callable[[str], str | None]: + version_scheme = get_version_scheme(self.config) + pattern = self.config.settings["tag_format"] + if pattern == "$version": + pattern = version_scheme.parser.pattern + for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): + pattern = pattern.replace(var, tag_pattern) + + regex = re.compile(f"^{pattern}$", re.VERBOSE) + + def matcher(tag: str) -> str | None: + match = regex.match(tag) + if not match: + return None + groups = match.groupdict() + if "version" in groups: + return groups["version"] + elif "major" in groups: + return "".join( + ( + groups["major"], + f".{groups['minor']}" if groups.get("minor") else "", + f".{groups['patch']}" if groups.get("patch") else "", + groups["prerelease"] if groups.get("prerelease") else "", + groups["devrelease"] if groups.get("devrelease") else "", + ) + ) + elif pattern == version_scheme.parser.pattern: + return str(version_scheme(tag)) + return None + + return matcher + + def get_version(self) -> str: + matcher = self._tag_format_matcher() + return next( + (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" + ) + + def set_version(self, version: str): + # Not necessary + pass diff --git a/pyproject.toml b/pyproject.toml index 40f53129d4..e989fdc79b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,13 +84,13 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers:CargoProvider" -commitizen = "commitizen.providers:CommitizenProvider" -composer = "commitizen.providers:ComposerProvider" -npm = "commitizen.providers:NpmProvider" -pep621 = "commitizen.providers:Pep621Provider" -poetry = "commitizen.providers:PoetryProvider" -scm = "commitizen.providers:ScmProvider" +cargo = "commitizen.providers.cargo_provider:CargoProvider" +commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" +composer = "commitizen.providers.composer_provider:ComposerProvider" +npm = "commitizen.providers.npm_provider:NpmProvider" +pep621 = "commitizen.providers.pep621_provider:Pep621Provider" +poetry = "commitizen.providers.poetry_provider:PoetryProvider" +scm = "commitizen.providers.scm_provider:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" From 0b0597e4828a896d6611c64ffab2197b709cbc62 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Sun, 27 Aug 2023 21:20:49 -0400 Subject: [PATCH 24/32] refactor(tests): split provider tests into separate files for maintainability closes #812 --- tests/providers/test_base_provider.py | 35 +++ tests/providers/test_cargo_provider.py | 75 +++++ tests/providers/test_commitizen_provider.py | 33 ++ tests/providers/test_composer_provider.py | 62 ++++ tests/providers/test_npm_provider.py | 112 +++++++ tests/providers/test_pep621_provider.py | 58 ++++ tests/providers/test_poetry_provider.py | 58 ++++ tests/providers/test_scm_provider.py | 68 +++++ tests/test_version_providers.py | 316 -------------------- 9 files changed, 501 insertions(+), 316 deletions(-) create mode 100644 tests/providers/test_base_provider.py create mode 100644 tests/providers/test_cargo_provider.py create mode 100644 tests/providers/test_commitizen_provider.py create mode 100644 tests/providers/test_composer_provider.py create mode 100644 tests/providers/test_npm_provider.py create mode 100644 tests/providers/test_pep621_provider.py create mode 100644 tests/providers/test_poetry_provider.py create mode 100644 tests/providers/test_scm_provider.py delete mode 100644 tests/test_version_providers.py diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py new file mode 100644 index 0000000000..fb5a3c3b39 --- /dev/null +++ b/tests/providers/test_base_provider.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.providers import get_provider +from commitizen.providers.commitizen_provider import CommitizenProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_default_version_provider_is_commitizen_config(config: BaseConfig): + provider = get_provider(config) + + assert isinstance(provider, CommitizenProvider) + + +def test_raise_for_unknown_provider(config: BaseConfig): + config.settings["version_provider"] = "unknown" + with pytest.raises(VersionProviderUnknown): + get_provider(config) diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py new file mode 100644 index 0000000000..d6b0573828 --- /dev/null +++ b/tests/providers/test_cargo_provider.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.cargo_provider import CargoProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +CARGO_TOML = """\ +[package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_EXPECTED = """\ +[package] +name = "whatever" +version = "42.1" +""" + +CARGO_WORKSPACE_TOML = """\ +[workspace.package] +name = "whatever" +version = "0.1.0" +""" + +CARGO_WORKSPACE_EXPECTED = """\ +[workspace.package] +name = "whatever" +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ( + (CARGO_TOML, CARGO_EXPECTED), + (CARGO_WORKSPACE_TOML, CARGO_WORKSPACE_EXPECTED), + ), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = CargoProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "cargo" + + provider = get_provider(config) + assert isinstance(provider, CargoProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py new file mode 100644 index 0000000000..8cca153c2f --- /dev/null +++ b/tests/providers/test_commitizen_provider.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers.commitizen_provider import CommitizenProvider + + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): + config.settings["version"] = "42" + mock = mocker.patch.object(config, "set_key") + + provider = CommitizenProvider(config) + assert provider.get_version() == "42" + + provider.set_version("43.1") + mock.assert_called_once_with("version", "43.1") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py new file mode 100644 index 0000000000..11d295152e --- /dev/null +++ b/tests/providers/test_composer_provider.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.composer_provider import ComposerProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +COMPOSER_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +COMPOSER_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + + +@pytest.mark.parametrize( + "content, expected", + ((COMPOSER_JSON, COMPOSER_EXPECTED),), +) +def test_composer_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = ComposerProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "composer" + + provider = get_provider(config) + assert isinstance(provider, ComposerProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py new file mode 100644 index 0000000000..3b458a93de --- /dev/null +++ b/tests/providers/test_npm_provider.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.npm_provider import NpmProvider + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +NPM_PACKAGE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0" +} +""" + +NPM_PACKAGE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1" +} +""" + +NPM_LOCKFILE_JSON = """\ +{ + "name": "whatever", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "0.1.0" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + +NPM_LOCKFILE_EXPECTED = """\ +{ + "name": "whatever", + "version": "42.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "whatever", + "version": "42.1" + }, + "someotherpackage": { + "version": "0.1.0" + } + } +} +""" + + +@pytest.mark.parametrize( + "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +@pytest.mark.parametrize( + "pkg_lock_content, pkg_lock_expected", + ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), +) +def test_npm_provider( + config: BaseConfig, + chdir: Path, + pkg_lock_content: str, + pkg_lock_expected: str, + pkg_shrinkwrap_content: str, + pkg_shrinkwrap_expected: str, +): + pkg = chdir / NpmProvider.package_filename + pkg.write_text(dedent(NPM_PACKAGE_JSON)) + if pkg_lock_content: + pkg_lock = chdir / NpmProvider.lock_filename + pkg_lock.write_text(dedent(pkg_lock_content)) + if pkg_shrinkwrap_content: + pkg_shrinkwrap = chdir / NpmProvider.shrinkwrap_filename + pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) + config.settings["version_provider"] = "npm" + + provider = get_provider(config) + assert isinstance(provider, NpmProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) + if pkg_lock_content: + assert pkg_lock.read_text() == dedent(pkg_lock_expected) + if pkg_shrinkwrap_content: + assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py new file mode 100644 index 0000000000..6288b47093 --- /dev/null +++ b/tests/providers/test_pep621_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.pep621_provider import Pep621Provider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +PEP621_TOML = """\ +[project] +version = "0.1.0" +""" + +PEP621_EXPECTED = """\ +[project] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((PEP621_TOML, PEP621_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = Pep621Provider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "pep621" + + provider = get_provider(config) + assert isinstance(provider, Pep621Provider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py new file mode 100644 index 0000000000..6a38d06075 --- /dev/null +++ b/tests/providers/test_poetry_provider.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.poetry_provider import PoetryProvider + + +if TYPE_CHECKING: + pass + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +POETRY_TOML = """\ +[tool.poetry] +version = "0.1.0" +""" + +POETRY_EXPECTED = """\ +[tool.poetry] +version = "42.1" +""" + + +@pytest.mark.parametrize( + "content, expected", + ((POETRY_TOML, POETRY_EXPECTED),), +) +def test_cargo_provider( + config: BaseConfig, + chdir: Path, + content: str, + expected: str, +): + filename = PoetryProvider.filename + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = "poetry" + + provider = get_provider(config) + assert isinstance(provider, PoetryProvider) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py new file mode 100644 index 0000000000..bf8e28fd2a --- /dev/null +++ b/tests/providers/test_scm_provider.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.providers import get_provider +from commitizen.providers.scm_provider import ScmProvider +from tests.utils import create_file_and_commit, create_tag + +if TYPE_CHECKING: + pass + + +@pytest.mark.parametrize( + "tag_format,tag,expected_version", + ( + # If tag_format is $version (the default), version_scheme.parser is used. + # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. + ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), + ("$version", "0.1.0", "0.1.0"), + ("$version", "v0.1.0", "0.1.0"), + ("$version", "v-0.1.0", "0.0.0"), + # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are + # much more lenient but require a v prefix. + ("v$version", "v0.1.0", "0.1.0"), + ("v$version", "no-match-because-no-v-prefix", "0.0.0"), + ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), + ("version-$version", "version-0.1.0", "0.1.0"), + ("version-$version", "version-0.1", "0.1"), + ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), + ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), + ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), + ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), + ), +) +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider( + config: BaseConfig, tag_format: str, tag: str, expected_version: str +): + create_file_and_commit("test: fake commit") + create_tag(tag) + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + + config.settings["version_provider"] = "scm" + config.settings["tag_format"] = tag_format + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + actual_version = provider.get_version() + assert actual_version == expected_version + + # Should not fail on set_version() + provider.set_version("43.1") + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" diff --git a/tests/test_version_providers.py b/tests/test_version_providers.py deleted file mode 100644 index 1cf0e736d8..0000000000 --- a/tests/test_version_providers.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from textwrap import dedent -from typing import TYPE_CHECKING, Iterator - -import pytest - -from commitizen.config.base_config import BaseConfig -from commitizen.exceptions import VersionProviderUnknown -from commitizen.providers import ( - CargoProvider, - CommitizenProvider, - ComposerProvider, - NpmProvider, - Pep621Provider, - PoetryProvider, - ScmProvider, - VersionProvider, - get_provider, -) -from tests.utils import create_file_and_commit, create_tag - -if TYPE_CHECKING: - from pytest_mock import MockerFixture - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - -def test_default_version_provider_is_commitizen_config(config: BaseConfig): - provider = get_provider(config) - - assert isinstance(provider, CommitizenProvider) - - -def test_raise_for_unknown_provider(config: BaseConfig): - config.settings["version_provider"] = "unknown" - with pytest.raises(VersionProviderUnknown): - get_provider(config) - - -def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): - config.settings["version"] = "42" - mock = mocker.patch.object(config, "set_key") - - provider = CommitizenProvider(config) - assert provider.get_version() == "42" - - provider.set_version("43.1") - mock.assert_called_once_with("version", "43.1") - - -FILE_PROVIDERS = [ - ( - "pep621", - "pyproject.toml", - Pep621Provider, - """\ - [project] - version = "0.1.0" - """, - """\ - [project] - version = "42.1" - """, - ), - ( - "poetry", - "pyproject.toml", - PoetryProvider, - """\ - [tool.poetry] - version = "0.1.0" - """, - """\ - [tool.poetry] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [workspace.package] - version = "0.1.0" - """, - """\ - [workspace.package] - version = "42.1" - """, - ), - ( - "cargo", - "Cargo.toml", - CargoProvider, - """\ - [package] - version = "0.1.0" - """, - """\ - [package] - version = "42.1" - """, - ), - ( - "npm", - "package.json", - NpmProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), - ( - "composer", - "composer.json", - ComposerProvider, - """\ - { - "name": "whatever", - "version": "0.1.0" - } - """, - """\ - { - "name": "whatever", - "version": "42.1" - } - """, - ), -] - - -@pytest.mark.parametrize( - "id,filename,cls,content,expected", - FILE_PROVIDERS, -) -def test_file_providers( - config: BaseConfig, - chdir: Path, - id: str, - filename: str, - cls: type[VersionProvider], - content: str, - expected: str, -): - file = chdir / filename - file.write_text(dedent(content)) - config.settings["version_provider"] = id - - provider = get_provider(config) - assert isinstance(provider, cls) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert file.read_text() == dedent(expected) - - -@pytest.mark.parametrize( - "tag_format,tag,expected_version", - ( - # If tag_format is $version (the default), version_scheme.parser is used. - # Its DEFAULT_VERSION_PARSER allows a v prefix, but matches PEP440 otherwise. - ("$version", "no-match-because-version-scheme-is-strict", "0.0.0"), - ("$version", "0.1.0", "0.1.0"), - ("$version", "v0.1.0", "0.1.0"), - ("$version", "v-0.1.0", "0.0.0"), - # If tag_format is not None or $version, TAG_FORMAT_REGEXS are used, which are - # much more lenient but require a v prefix. - ("v$version", "v0.1.0", "0.1.0"), - ("v$version", "no-match-because-no-v-prefix", "0.0.0"), - ("v$version", "v-match-TAG_FORMAT_REGEXS", "-match-TAG_FORMAT_REGEXS"), - ("version-$version", "version-0.1.0", "0.1.0"), - ("version-$version", "version-0.1", "0.1"), - ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), - ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), - ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), - ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), - ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), - ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), - ), -) -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider( - config: BaseConfig, tag_format: str, tag: str, expected_version: str -): - create_file_and_commit("test: fake commit") - create_tag(tag) - create_file_and_commit("test: fake commit") - create_tag("should-not-match") - - config.settings["version_provider"] = "scm" - config.settings["tag_format"] = tag_format - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - actual_version = provider.get_version() - assert actual_version == expected_version - - # Should not fail on set_version() - provider.set_version("43.1") - - -@pytest.mark.usefixtures("tmp_git_project") -def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): - config.settings["version_provider"] = "scm" - - provider = get_provider(config) - assert isinstance(provider, ScmProvider) - assert provider.get_version() == "0.0.0" - - -NPM_PACKAGE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0" -} -""" - -NPM_PACKAGE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1" -} -""" - -NPM_LOCKFILE_JSON = """\ -{ - "name": "whatever", - "version": "0.1.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "0.1.0" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - -NPM_LOCKFILE_EXPECTED = """\ -{ - "name": "whatever", - "version": "42.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "whatever", - "version": "42.1" - }, - "someotherpackage": { - "version": "0.1.0" - } - } -} -""" - - -@pytest.mark.parametrize( - "pkg_shrinkwrap_content, pkg_shrinkwrap_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -@pytest.mark.parametrize( - "pkg_lock_content, pkg_lock_expected", - ((NPM_LOCKFILE_JSON, NPM_LOCKFILE_EXPECTED), (None, None)), -) -def test_npm_provider( - config: BaseConfig, - chdir: Path, - pkg_lock_content: str, - pkg_lock_expected: str, - pkg_shrinkwrap_content: str, - pkg_shrinkwrap_expected: str, -): - pkg = chdir / "package.json" - pkg.write_text(dedent(NPM_PACKAGE_JSON)) - if pkg_lock_content: - pkg_lock = chdir / "package-lock.json" - pkg_lock.write_text(dedent(pkg_lock_content)) - if pkg_shrinkwrap_content: - pkg_shrinkwrap = chdir / "npm-shrinkwrap.json" - pkg_shrinkwrap.write_text(dedent(pkg_shrinkwrap_content)) - config.settings["version_provider"] = "npm" - - provider = get_provider(config) - assert isinstance(provider, NpmProvider) - assert provider.get_version() == "0.1.0" - - provider.set_version("42.1") - assert pkg.read_text() == dedent(NPM_PACKAGE_EXPECTED) - if pkg_lock_content: - assert pkg_lock.read_text() == dedent(pkg_lock_expected) - if pkg_shrinkwrap_content: - assert pkg_shrinkwrap.read_text() == dedent(pkg_shrinkwrap_expected) From d7d1561c2f56eb5b833ac64513f8356d251d45bb Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:30:46 -0400 Subject: [PATCH 25/32] refactor(provider): fully qualify import of commitizen.providers.base_provider --- commitizen/providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 16129af04b..37e330582e 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -7,7 +7,7 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionProviderUnknown -from .base_provider import VersionProvider +from commitizen.providers.base_provider import VersionProvider PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" From 96a8bffaa034aafcf89c27766306ea53c7c102fe Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:37:15 -0400 Subject: [PATCH 26/32] refactor(provider): move chdir to providers/conftest.py --- tests/providers/conftest.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/providers/conftest.py diff --git a/tests/providers/conftest.py b/tests/providers/conftest.py new file mode 100644 index 0000000000..b4432ca524 --- /dev/null +++ b/tests/providers/conftest.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Iterator + +import pytest + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) From 11f0240b675a52b4758c6cd3c8bf250c706f5d7b Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 11:38:14 -0400 Subject: [PATCH 27/32] refactor(provider): remove unnecessary TYPE_CHECKING from provider tests --- tests/providers/test_base_provider.py | 5 +---- tests/providers/test_cargo_provider.py | 14 -------------- tests/providers/test_commitizen_provider.py | 13 +------------ tests/providers/test_composer_provider.py | 14 -------------- tests/providers/test_npm_provider.py | 13 ------------- tests/providers/test_pep621_provider.py | 14 -------------- tests/providers/test_poetry_provider.py | 14 -------------- tests/providers/test_scm_provider.py | 4 ---- 8 files changed, 2 insertions(+), 89 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index fb5a3c3b39..d5557a3577 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import Iterator import pytest @@ -11,9 +11,6 @@ from commitizen.providers import get_provider from commitizen.providers.commitizen_provider import CommitizenProvider -if TYPE_CHECKING: - pass - @pytest.fixture def chdir(tmp_path: Path) -> Iterator[Path]: diff --git a/tests/providers/test_cargo_provider.py b/tests/providers/test_cargo_provider.py index d6b0573828..cde868deb5 100644 --- a/tests/providers/test_cargo_provider.py +++ b/tests/providers/test_cargo_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.cargo_provider import CargoProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - CARGO_TOML = """\ [package] name = "whatever" diff --git a/tests/providers/test_commitizen_provider.py b/tests/providers/test_commitizen_provider.py index 8cca153c2f..887adf3d12 100644 --- a/tests/providers/test_commitizen_provider.py +++ b/tests/providers/test_commitizen_provider.py @@ -1,10 +1,7 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING -import pytest from commitizen.config.base_config import BaseConfig from commitizen.providers.commitizen_provider import CommitizenProvider @@ -14,14 +11,6 @@ from pytest_mock import MockerFixture -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): config.settings["version"] = "42" mock = mocker.patch.object(config, "set_key") diff --git a/tests/providers/test_composer_provider.py b/tests/providers/test_composer_provider.py index 11d295152e..ce72ae4703 100644 --- a/tests/providers/test_composer_provider.py +++ b/tests/providers/test_composer_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.composer_provider import ComposerProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - COMPOSER_JSON = """\ { "name": "whatever", diff --git a/tests/providers/test_npm_provider.py b/tests/providers/test_npm_provider.py index 3b458a93de..2e5ceb42d5 100644 --- a/tests/providers/test_npm_provider.py +++ b/tests/providers/test_npm_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -11,17 +9,6 @@ from commitizen.providers import get_provider from commitizen.providers.npm_provider import NpmProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - NPM_PACKAGE_JSON = """\ { diff --git a/tests/providers/test_pep621_provider.py b/tests/providers/test_pep621_provider.py index 6288b47093..9e82213294 100644 --- a/tests/providers/test_pep621_provider.py +++ b/tests/providers/test_pep621_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.pep621_provider import Pep621Provider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - PEP621_TOML = """\ [project] version = "0.1.0" diff --git a/tests/providers/test_poetry_provider.py b/tests/providers/test_poetry_provider.py index 6a38d06075..9e327db6ad 100644 --- a/tests/providers/test_poetry_provider.py +++ b/tests/providers/test_poetry_provider.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os from pathlib import Path from textwrap import dedent -from typing import TYPE_CHECKING, Iterator import pytest @@ -12,18 +10,6 @@ from commitizen.providers.poetry_provider import PoetryProvider -if TYPE_CHECKING: - pass - - -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) - - POETRY_TOML = """\ [tool.poetry] version = "0.1.0" diff --git a/tests/providers/test_scm_provider.py b/tests/providers/test_scm_provider.py index bf8e28fd2a..a0bfc46474 100644 --- a/tests/providers/test_scm_provider.py +++ b/tests/providers/test_scm_provider.py @@ -1,6 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING import pytest @@ -9,9 +8,6 @@ from commitizen.providers.scm_provider import ScmProvider from tests.utils import create_file_and_commit, create_tag -if TYPE_CHECKING: - pass - @pytest.mark.parametrize( "tag_format,tag,expected_version", From e6e079d0730956d377da472796c941f5630a8266 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Mon, 4 Sep 2023 12:00:11 -0400 Subject: [PATCH 28/32] refactor(provider): import all providers to providers/__init__.py to preserve plugin config --- commitizen/providers/__init__.py | 7 +++ pyproject.toml | 84 +++++++++++++++----------------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 37e330582e..6f777096cf 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,6 +8,13 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider +from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 +from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 +from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 +from commitizen.providers.npm_provider import NpmProvider # noqa: F401 +from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 +from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 +from commitizen.providers.scm_provider import ScmProvider # noqa: F401 PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen" diff --git a/pyproject.toml b/pyproject.toml index e989fdc79b..a22d9a7397 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.8.0" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen" + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7"} +importlib_metadata = { version = ">=4.13,<7" } [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -84,46 +84,46 @@ cz_jira = "commitizen.cz.jira:JiraSmartCz" cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" [tool.poetry.plugins."commitizen.provider"] -cargo = "commitizen.providers.cargo_provider:CargoProvider" -commitizen = "commitizen.providers.commitizen_provider:CommitizenProvider" -composer = "commitizen.providers.composer_provider:ComposerProvider" -npm = "commitizen.providers.npm_provider:NpmProvider" -pep621 = "commitizen.providers.pep621_provider:Pep621Provider" -poetry = "commitizen.providers.poetry_provider:PoetryProvider" -scm = "commitizen.providers.scm_provider:ScmProvider" +cargo = "commitizen.providers:CargoProvider" +commitizen = "commitizen.providers:CommitizenProvider" +composer = "commitizen.providers:ComposerProvider" +npm = "commitizen.providers:NpmProvider" +pep621 = "commitizen.providers:Pep621Provider" +poetry = "commitizen.providers:PoetryProvider" +scm = "commitizen.providers:ScmProvider" [tool.poetry.plugins."commitizen.scheme"] pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] - [tool.coverage.report] - show_missing = true - exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', - ] - omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*' - ] +[tool.coverage.report] +show_missing = true +exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', +] +omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', +] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,11 +134,7 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = [ - "E501", - "D1", - "D415", -] +ignore = ["E501", "D1", "D415"] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -156,5 +152,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From 07202775c7402e04bd247535dcc832aecfbc713e Mon Sep 17 00:00:00 2001 From: "David L. Day" <1132144+davidlday@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:24:19 -0400 Subject: [PATCH 29/32] chore: removed chdir from tests/providers/test_base_provider.py Co-authored-by: Wei Lee --- tests/providers/test_base_provider.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index d5557a3577..f2601c4ba6 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -12,12 +12,6 @@ from commitizen.providers.commitizen_provider import CommitizenProvider -@pytest.fixture -def chdir(tmp_path: Path) -> Iterator[Path]: - cwd = Path() - os.chdir(tmp_path) - yield tmp_path - os.chdir(cwd) def test_default_version_provider_is_commitizen_config(config: BaseConfig): From d0a0bfe4527ac9fb608f1e1dad6833758276dd3b Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:38:58 -0400 Subject: [PATCH 30/32] refactor: revert unintentionally reformatting --- pyproject.toml | 70 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a22d9a7397..b62fe7b3d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,9 @@ version = "3.8.0" tag_format = "v$version" version_files = [ - "pyproject.toml:version", - "commitizen/__version__.py", - ".pre-commit-config.yaml:rev:.+Commitizen", + "pyproject.toml:version", + "commitizen/__version__.py", + ".pre-commit-config.yaml:rev:.+Commitizen", ] [tool.poetry] @@ -47,7 +47,7 @@ argcomplete = ">=1.12.1,<3.2" typing-extensions = { version = "^4.0.1", python = "<3.8" } charset-normalizer = ">=2.1.0,<4" # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility -importlib_metadata = { version = ">=4.13,<7" } +importlib_metadata = { version = ">=4.13,<7"} [tool.poetry.group.dev.dependencies] ipython = "^7.34" @@ -97,33 +97,33 @@ pep440 = "commitizen.version_schemes:Pep440" semver = "commitizen.version_schemes:SemVer" [tool.coverage] -[tool.coverage.report] -show_missing = true -exclude_lines = [ - # Have to re-enable the standard pragma - 'pragma: no cover', - - # Don't complain about missing debug-only code: - 'def __repr__', - 'if self\.debug', - - # Don't complain if tests don't hit defensive assertion code: - 'raise AssertionError', - 'raise NotImplementedError', - - # Don't complain if non-runnable code isn't run: - 'if 0:', - 'if __name__ == .__main__.:', - 'if TYPE_CHECKING:', -] -omit = [ - 'env/*', - 'venv/*', - '.venv/*', - '*/virtualenv/*', - '*/virtualenvs/*', - '*/tests/*', -] + [tool.coverage.report] + show_missing = true + exclude_lines = [ + # Have to re-enable the standard pragma + 'pragma: no cover', + + # Don't complain about missing debug-only code: + 'def __repr__', + 'if self\.debug', + + # Don't complain if tests don't hit defensive assertion code: + 'raise AssertionError', + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if 0:', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', + ] + omit = [ + 'env/*', + 'venv/*', + '.venv/*', + '*/virtualenv/*', + '*/virtualenvs/*', + '*/tests/*', + ] [build-system] requires = ["poetry_core>=1.0.0"] @@ -134,7 +134,11 @@ addopts = "--strict-markers" [tool.ruff] line-length = 88 -ignore = ["E501", "D1", "D415"] +ignore = [ + "E501", + "D1", + "D415" +] [tool.ruff.isort] known-first-party = ["commitizen", "tests"] @@ -152,5 +156,5 @@ warn_unused_ignores = true warn_unused_configs = true [[tool.mypy.overrides]] -module = "py.*" # Legacy pytest dependencies +module = "py.*" # Legacy pytest dependencies ignore_missing_imports = true From 3e49c2a8c02ca1923a967b2e2020a6055dd15c52 Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:40:36 -0400 Subject: [PATCH 31/32] refactor: remove unused imports --- tests/providers/test_base_provider.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/providers/test_base_provider.py b/tests/providers/test_base_provider.py index f2601c4ba6..09a2bd71cd 100644 --- a/tests/providers/test_base_provider.py +++ b/tests/providers/test_base_provider.py @@ -1,8 +1,5 @@ from __future__ import annotations -import os -from pathlib import Path -from typing import Iterator import pytest @@ -12,8 +9,6 @@ from commitizen.providers.commitizen_provider import CommitizenProvider - - def test_default_version_provider_is_commitizen_config(config: BaseConfig): provider = get_provider(config) From bcb108d6cbf5d5d1e9e5599fbb75f3d4aaeab9ae Mon Sep 17 00:00:00 2001 From: "David L. Day" Date: Tue, 5 Sep 2023 07:41:23 -0400 Subject: [PATCH 32/32] refactor: use __all__ to name exports --- commitizen/providers/__init__.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 6f777096cf..51302d2b37 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -8,13 +8,24 @@ from commitizen.exceptions import VersionProviderUnknown from commitizen.providers.base_provider import VersionProvider -from commitizen.providers.cargo_provider import CargoProvider # noqa: F401 -from commitizen.providers.commitizen_provider import CommitizenProvider # noqa: F401 -from commitizen.providers.composer_provider import ComposerProvider # noqa: F401 -from commitizen.providers.npm_provider import NpmProvider # noqa: F401 -from commitizen.providers.pep621_provider import Pep621Provider # noqa: F401 -from commitizen.providers.poetry_provider import PoetryProvider # noqa: F401 -from commitizen.providers.scm_provider import ScmProvider # noqa: F401 +from commitizen.providers.cargo_provider import CargoProvider +from commitizen.providers.commitizen_provider import CommitizenProvider +from commitizen.providers.composer_provider import ComposerProvider +from commitizen.providers.npm_provider import NpmProvider +from commitizen.providers.pep621_provider import Pep621Provider +from commitizen.providers.poetry_provider import PoetryProvider +from commitizen.providers.scm_provider import ScmProvider + +__all__ = [ + "get_provider", + "CargoProvider", + "CommitizenProvider", + "ComposerProvider", + "NpmProvider", + "Pep621Provider", + "PoetryProvider", + "ScmProvider", +] PROVIDER_ENTRYPOINT = "commitizen.provider" DEFAULT_PROVIDER = "commitizen"