From 312ca491dd656c646f8fc487fef766dee3fd2169 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 21:54:37 +0800 Subject: [PATCH 01/51] refactor(ParseArgs): simplify __call__ function body --- commitizen/cli.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/commitizen/cli.py b/commitizen/cli.py index 6f7556df4..a73377fe4 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -52,16 +52,13 @@ def __call__( ) -> None: if not isinstance(values, str): return - if "=" not in values: + + key, sep, value = values.partition("=") + if not key or not sep: raise InvalidCommandArgumentError( f"Option {option_string} expect a key=value format" ) kwargs = getattr(namespace, self.dest, None) or {} - key, value = values.split("=", 1) - if not key: - raise InvalidCommandArgumentError( - f"Option {option_string} expect a key=value format" - ) kwargs[key] = value.strip("'\"") setattr(namespace, self.dest, kwargs) From 56ef1e063135dbae0ffe8aa9bed4dd2b02ccaecb Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 22:28:57 +0800 Subject: [PATCH 02/51] refactor(ExpectedExit): make the constructor more compact --- commitizen/exceptions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py index 8c0956be5..8be792258 100644 --- a/commitizen/exceptions.py +++ b/commitizen/exceptions.py @@ -59,8 +59,7 @@ class ExpectedExit(CommitizenException): exit_code = ExitCode.EXPECTED_EXIT def __init__(self, *args: str, **kwargs: Any) -> None: - output_method = kwargs.get("output_method") or out.write - kwargs["output_method"] = output_method + kwargs["output_method"] = kwargs.get("output_method") or out.write super().__init__(*args, **kwargs) From 8cf7acda121769b071c52b5e76f9b1e40b767147 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 21:42:30 +0800 Subject: [PATCH 03/51] refactor(ScmProvider): replace sorted with max --- commitizen/providers/scm_provider.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py index 3085b16ef..687775da3 100644 --- a/commitizen/providers/scm_provider.py +++ b/commitizen/providers/scm_provider.py @@ -18,10 +18,8 @@ def get_version(self) -> str: rules = TagRules.from_settings(self.config.settings) tags = get_tags(reachable_only=True) version_tags = rules.get_version_tags(tags) - versions = sorted(rules.extract_version(t) for t in version_tags) - if not versions: - return "0.0.0" - return str(versions[-1]) + version = max((rules.extract_version(t) for t in version_tags), default=None) + return str(version) if version is not None else "0.0.0" def set_version(self, version: str) -> None: # Not necessary From fb04e28d14da1bfe857a12da022491a0fa9ba9da Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 22:43:30 +0800 Subject: [PATCH 04/51] refactor(git): remove redundant if branch --- commitizen/git.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/commitizen/git.py b/commitizen/git.py index 8025041ab..4883b34e7 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -328,6 +328,4 @@ def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]: c = cmd.run(command) if c.return_code != 0: raise GitCommandError(c.err) - if not c.out: - return [] return c.out.split(f"{delimiter}\n") From 1a1fdc624523707328f02aca4e0bb0ed659cfc5d Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Fri, 13 Jun 2025 21:00:01 +0800 Subject: [PATCH 05/51] fix(Bump): rewrite --get-next NotAllowed error message for consistency --- commitizen/commands/bump.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 2a84483c9..e07a359cf 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -215,11 +215,13 @@ def __call__(self) -> None: raise NotAllowed("--local-version cannot be combined with --build-metadata") if get_next: - # if trying to use --get-next, we should not allow --changelog or --changelog-to-stdout - if self.changelog_flag or self.changelog_to_stdout: - raise NotAllowed( - "--changelog or --changelog-to-stdout is not allowed with --get-next" - ) + for value, option in ( + (self.changelog_flag, "--changelog"), + (self.changelog_to_stdout, "--changelog-to-stdout"), + ): + if value: + raise NotAllowed(f"{option} cannot be combined with --get-next") + # --get-next is a special case, taking precedence over config for 'update_changelog_on_bump' self.changelog_config = False # Setting dry_run to prevent any unwanted changes to the repo or files From 93f0f277bb29639c71b5c334d60ba19d0db849f0 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Fri, 13 Jun 2025 19:40:48 +0800 Subject: [PATCH 06/51] fix(Changelog): fix _export_template variable type Closes #1503 --- commitizen/commands/changelog.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index dc50eb3ad..8668dd44c 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -174,11 +174,15 @@ def _write_changelog( changelog_file.write(changelog_out) - def _export_template(self) -> None: - tpl = changelog.get_changelog_template(self.cz.template_loader, self.template) - # TODO: fix the following type ignores - src = Path(tpl.filename) # type: ignore[arg-type] - Path(self.export_template_to).write_text(src.read_text()) # type: ignore[arg-type] + def _export_template(self, dist: str) -> None: + filename = changelog.get_changelog_template( + self.cz.template_loader, self.template + ).filename + if filename is None: + raise NotAllowed("Template filename is not set") + + text = Path(filename).read_text() + Path(dist).write_text(text) def __call__(self) -> None: commit_parser = self.cz.commit_parser @@ -195,7 +199,7 @@ def __call__(self) -> None: ) if self.export_template_to: - return self._export_template() + return self._export_template(self.export_template_to) if not changelog_pattern or not commit_parser: raise NoPatternMapError( From 7a531dbd320c9ddd2b80c71a57115c30e8c1dbf3 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Fri, 13 Jun 2025 13:57:59 +0800 Subject: [PATCH 07/51] refactor(TagRules): extract tag_formats property and simplify list comprehension --- commitizen/tags.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/commitizen/tags.py b/commitizen/tags.py index b19bb89e0..e3f54370f 100644 --- a/commitizen/tags.py +++ b/commitizen/tags.py @@ -5,6 +5,7 @@ from collections.abc import Iterable, Sequence from dataclasses import dataclass, field from functools import cached_property +from itertools import chain from string import Template from typing import TYPE_CHECKING, NamedTuple @@ -88,18 +89,22 @@ class TagRules: ignored_tag_formats: Sequence[str] = field(default_factory=list) merge_prereleases: bool = False + @property + def tag_formats(self) -> Iterable[str]: + return chain([self.tag_format], self.legacy_tag_formats) + @cached_property def version_regexes(self) -> list[re.Pattern]: """Regexes for all legit tag formats, current and legacy""" - tag_formats = [self.tag_format, *self.legacy_tag_formats] - regexes = (self._format_regex(p) for p in tag_formats) - return [re.compile(r) for r in regexes] + return [re.compile(self._format_regex(f)) for f in self.tag_formats] @cached_property def ignored_regexes(self) -> list[re.Pattern]: """Regexes for known but ignored tag formats""" - regexes = (self._format_regex(p, star=True) for p in self.ignored_tag_formats) - return [re.compile(r) for r in regexes] + return [ + re.compile(self._format_regex(f, star=True)) + for f in self.ignored_tag_formats + ] def _format_regex(self, tag_pattern: str, star: bool = False) -> str: """ @@ -240,10 +245,7 @@ def find_tag_for( ) -> GitTag | None: """Find the first matching tag for a given version.""" version = self.scheme(version) if isinstance(version, str) else version - possible_tags = set( - self.normalize_tag(version, f) - for f in (self.tag_format, *self.legacy_tag_formats) - ) + possible_tags = set(self.normalize_tag(version, f) for f in self.tag_formats) candidates = [t for t in tags if t.name in possible_tags] if len(candidates) > 1: warnings.warn( From 9163ae29ad152561106af49b0ca384bad7dafec1 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 11:46:44 +0800 Subject: [PATCH 08/51] refactor(Init): use ternary operator --- commitizen/commands/init.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 1207cd02e..356c4ce27 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -159,9 +159,9 @@ def __call__(self) -> None: out.success("Configuration complete ๐Ÿš€") def _ask_config_path(self) -> str: - default_path = ".cz.toml" - if self.project_info.has_pyproject: - default_path = "pyproject.toml" + default_path = ( + "pyproject.toml" if self.project_info.has_pyproject else ".cz.toml" + ) name: str = questionary.select( "Please choose a supported config file: ", @@ -270,15 +270,13 @@ def _ask_version_provider(self) -> str: def _ask_version_scheme(self) -> str: """Ask for setting: version_scheme""" - default = "semver" - if self.project_info.is_python: - default = "pep440" + default_scheme = "pep440" if self.project_info.is_python else "semver" scheme: str = questionary.select( "Choose version scheme: ", - choices=list(KNOWN_SCHEMES), + choices=KNOWN_SCHEMES, style=self.cz.style, - default=default, + default=default_scheme, ).unsafe_ask() return scheme @@ -318,10 +316,9 @@ def _gen_pre_commit_cmd(self, hook_types: list[str]) -> str: """Generate pre-commit command according to given hook types""" if not hook_types: raise ValueError("At least 1 hook type should be provided.") - cmd_str = "pre-commit install " + " ".join( + return "pre-commit install " + " ".join( f"--hook-type {ty}" for ty in hook_types ) - return cmd_str def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: pre_commit_config_filename = ".pre-commit-config.yaml" From 6a90185a127e6efde95a44c310c6f367e434a071 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 00:11:54 +0800 Subject: [PATCH 09/51] refactor(bump): use a loop to shorten a series of similar NotAllowed exceptions --- commitizen/commands/bump.py | 40 ++++++++++++------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index e07a359cf..49d90f14d 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -177,36 +177,22 @@ def __call__(self) -> None: build_metadata = self.arguments["build_metadata"] get_next = self.arguments["get_next"] allow_no_commit = self.arguments["allow_no_commit"] + major_version_zero = self.arguments["major_version_zero"] if manual_version: - if increment: - raise NotAllowed("--increment cannot be combined with MANUAL_VERSION") - - if prerelease: - raise NotAllowed("--prerelease cannot be combined with MANUAL_VERSION") - - if devrelease is not None: - raise NotAllowed("--devrelease cannot be combined with MANUAL_VERSION") - - if is_local_version: - raise NotAllowed( - "--local-version cannot be combined with MANUAL_VERSION" - ) - - if build_metadata: - raise NotAllowed( - "--build-metadata cannot be combined with MANUAL_VERSION" - ) - - if self.bump_settings["major_version_zero"]: - raise NotAllowed( - "--major-version-zero cannot be combined with MANUAL_VERSION" - ) - - if get_next: - raise NotAllowed("--get-next cannot be combined with MANUAL_VERSION") + for val, option in ( + (increment, "--increment"), + (prerelease, "--prerelease"), + (devrelease is not None, "--devrelease"), + (is_local_version, "--local-version"), + (build_metadata, "--build-metadata"), + (major_version_zero, "--major-version-zero"), + (get_next, "--get-next"), + ): + if val: + raise NotAllowed(f"{option} cannot be combined with MANUAL_VERSION") - if self.bump_settings["major_version_zero"] and current_version.release[0]: + if major_version_zero and current_version.release[0]: raise NotAllowed( f"--major-version-zero is meaningless for current version {current_version}" ) From c169e0958ecbb162051a589a0661495402ecfa80 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Fri, 13 Jun 2025 20:26:51 +0800 Subject: [PATCH 10/51] fix: raise NoVersionSpecifiedError if version is None, and adjust call sites of get_version --- commitizen/commands/bump.py | 7 +---- commitizen/commands/version.py | 32 +++++++++++---------- commitizen/providers/commitizen_provider.py | 5 +++- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 49d90f14d..07338afb8 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -23,7 +23,6 @@ NoPatternMapError, NotAGitProjectError, NotAllowed, - NoVersionSpecifiedError, ) from commitizen.providers import get_provider from commitizen.tags import TagRules @@ -163,11 +162,7 @@ def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None: def __call__(self) -> None: """Steps executed to bump.""" provider = get_provider(self.config) - - try: - current_version = self.scheme(provider.get_version()) - except TypeError: - raise NoVersionSpecifiedError() + current_version = self.scheme(provider.get_version()) increment = self.arguments["increment"] prerelease = self.arguments["prerelease"] diff --git a/commitizen/commands/version.py b/commitizen/commands/version.py index 6b0aa331a..35e9aa6cd 100644 --- a/commitizen/commands/version.py +++ b/commitizen/commands/version.py @@ -5,6 +5,7 @@ from commitizen import out from commitizen.__version__ import __version__ from commitizen.config import BaseConfig +from commitizen.exceptions import NoVersionSpecifiedError from commitizen.providers import get_provider @@ -28,19 +29,20 @@ def __call__(self) -> None: out.write(f"Commitizen Version: {__version__}") out.write(f"Python Version: {self.python_version}") out.write(f"Operating System: {self.operating_system}") - elif self.parameter.get("project"): - version = get_provider(self.config).get_version() - if version: - out.write(f"{version}") - else: - out.error("No project information in this project.") - elif self.parameter.get("verbose"): - out.write(f"Installed Commitizen Version: {__version__}") - version = get_provider(self.config).get_version() - if version: - out.write(f"Project Version: {version}") - else: + return + + if (verbose := self.parameter.get("verbose")) or self.parameter.get("project"): + if verbose: + out.write(f"Installed Commitizen Version: {__version__}") + + try: + version = get_provider(self.config).get_version() + except NoVersionSpecifiedError: out.error("No project information in this project.") - else: - # if no argument is given, show installed commitizen version - out.write(f"{__version__}") + return + + out.write(f"Project Version: {version}" if verbose else version) + return + + # if no argument is given, show installed commitizen version + out.write(f"{__version__}") diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py index d9207b19f..f90d7d9b6 100644 --- a/commitizen/providers/commitizen_provider.py +++ b/commitizen/providers/commitizen_provider.py @@ -1,5 +1,6 @@ from __future__ import annotations +from commitizen.exceptions import NoVersionSpecifiedError from commitizen.providers.base_provider import VersionProvider @@ -9,7 +10,9 @@ class CommitizenProvider(VersionProvider): """ def get_version(self) -> str: - return self.config.settings["version"] # type: ignore[return-value] # TODO: check if we can fix this by tweaking the `Settings` type + if ret := self.config.settings["version"]: + return ret + raise NoVersionSpecifiedError() def set_version(self, version: str) -> None: self.config.set_key("version", version) From 0fa668d76e10b5c3960573934378f5d0bf4edfd7 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 25 May 2025 17:46:33 +0800 Subject: [PATCH 11/51] docs(taplo): add toml formatter --- .taplo.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .taplo.toml diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 000000000..36a3453ac --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,4 @@ +include = ["pyproject.toml", ".taplo.toml"] + +[formatting] +indent_string = " " From d9f5b49b5c9b684b451bc55f09901d9f467fc9d6 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 25 May 2025 20:25:49 +0800 Subject: [PATCH 12/51] docs(pre-commit): add taplo to pre-commit hook --- .pre-commit-config.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e09fb63e..06bf4c2ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - id: debug-statements - id: no-commit-to-branch - id: check-merge-conflict - - id: check-toml + - id: check-toml # TOML linter (syntax checker) - id: check-yaml args: [ '--unsafe' ] # for mkdocs.yml - id: detect-private-key @@ -55,17 +55,22 @@ repos: stages: - post-commit + - repo: https://github.com/ComPWA/taplo-pre-commit + rev: v0.9.3 + hooks: + - id: taplo-format + - repo: local hooks: - id: format - name: Format + name: Format Python code via Poetry language: system pass_filenames: false entry: poetry format types: [ python ] - id: linter and test - name: Linters + name: Linters via Poetry language: system pass_filenames: false entry: poetry lint From aa9ab248bcc70f6b90cdab8fee983677bf96e44e Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Fri, 13 Jun 2025 20:09:07 +0800 Subject: [PATCH 13/51] refactor(Changelog): remove unnecessary intermediate variables for better readability --- commitizen/commands/changelog.py | 36 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 8668dd44c..0fffd8ed7 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -11,7 +11,6 @@ from commitizen import changelog, defaults, factory, git, out from commitizen.changelog_formats import get_changelog_format from commitizen.config import BaseConfig -from commitizen.cz.base import ChangelogReleaseHook, MessageBuilderHook from commitizen.cz.utils import strip_local_version from commitizen.exceptions import ( DryRunExit, @@ -188,15 +187,6 @@ def __call__(self) -> None: commit_parser = self.cz.commit_parser changelog_pattern = self.cz.changelog_pattern start_rev = self.start_rev - unreleased_version = self.unreleased_version - changelog_meta = changelog.Metadata() - change_type_map: dict[str, str] | None = self.change_type_map - changelog_message_builder_hook: MessageBuilderHook | None = ( - self.cz.changelog_message_builder_hook - ) - changelog_release_hook: ChangelogReleaseHook | None = ( - self.cz.changelog_release_hook - ) if self.export_template_to: return self._export_template(self.export_template_to) @@ -213,33 +203,37 @@ def __call__(self) -> None: assert self.file_name tags = self.tag_rules.get_version_tags(git.get_tags(), warn=True) - end_rev = "" + changelog_meta = changelog.Metadata() if self.incremental: changelog_meta = self.changelog_format.get_metadata(self.file_name) if changelog_meta.latest_version: start_rev = self._find_incremental_rev( strip_local_version(changelog_meta.latest_version_tag or ""), tags ) + + end_rev = "" if self.rev_range: start_rev, end_rev = changelog.get_oldest_and_newest_rev( tags, self.rev_range, self.tag_rules, ) + commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order") if not commits and ( self.current_version is None or not self.current_version.is_prerelease ): raise NoCommitsFoundError("No commits found") + tree = changelog.generate_tree_from_commits( commits, tags, commit_parser, changelog_pattern, - unreleased_version, - change_type_map=change_type_map, - changelog_message_builder_hook=changelog_message_builder_hook, - changelog_release_hook=changelog_release_hook, + self.unreleased_version, + change_type_map=self.change_type_map, + changelog_message_builder_hook=self.cz.changelog_message_builder_hook, + changelog_release_hook=self.cz.changelog_release_hook, rules=self.tag_rules, ) if self.change_type_order: @@ -247,11 +241,15 @@ def __call__(self) -> None: tree, self.change_type_order ) - extras = self.cz.template_extras.copy() - extras.update(self.config.settings["extras"]) - extras.update(self.extras) changelog_out = changelog.render_changelog( - tree, loader=self.cz.template_loader, template=self.template, **extras + tree, + self.cz.template_loader, + self.template, + **{ + **self.cz.template_extras, + **self.config.settings["extras"], + **self.extras, + }, ).lstrip("\n") # Dry_run is executed here to avoid checking and reading the files From 3d635d2bc196b5d296883a5c7fe1876c668f423d Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 17:52:46 +0800 Subject: [PATCH 14/51] refactor(changelog): shorten condition expression and early return --- commitizen/changelog.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/commitizen/changelog.py b/commitizen/changelog.py index ba6fbbc6b..8721734af 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -251,6 +251,7 @@ def incremental_build( unreleased_start = metadata.unreleased_start unreleased_end = metadata.unreleased_end latest_version_position = metadata.latest_version_position + skip = False output_lines: list[str] = [] for index, line in enumerate(lines): @@ -260,9 +261,7 @@ def incremental_build( skip = False if ( latest_version_position is None - or isinstance(latest_version_position, int) - and isinstance(unreleased_end, int) - and latest_version_position > unreleased_end + or latest_version_position > unreleased_end ): continue @@ -271,13 +270,15 @@ def incremental_build( if index == latest_version_position: output_lines.extend([new_content, "\n"]) - output_lines.append(line) - if not isinstance(latest_version_position, int): - if output_lines and output_lines[-1].strip(): - # Ensure at least one blank line between existing and new content. - output_lines.append("\n") - output_lines.append(new_content) + + if latest_version_position is not None: + return output_lines + + if output_lines and output_lines[-1].strip(): + # Ensure at least one blank line between existing and new content. + output_lines.append("\n") + output_lines.append(new_content) return output_lines From 68c436033a056ebe3ffcb969172e87fc7b656acf Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Wed, 11 Jun 2025 22:40:59 +0800 Subject: [PATCH 15/51] refactor(Init): extract _get_config_data for readability --- commitizen/commands/init.py | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 356c4ce27..c298ffec8 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -79,6 +79,8 @@ def is_pre_commit_installed(self) -> bool: class Init: + _PRE_COMMIT_CONFIG_PATH = ".pre-commit-config.yaml" + def __init__(self, config: BaseConfig, *args: object) -> None: self.config: BaseConfig = config self.encoding = config.settings["encoding"] @@ -320,9 +322,8 @@ def _gen_pre_commit_cmd(self, hook_types: list[str]) -> str: f"--hook-type {ty}" for ty in hook_types ) - def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: - pre_commit_config_filename = ".pre-commit-config.yaml" - cz_hook_config = { + def _get_config_data(self) -> dict[str, Any]: + CZ_HOOK_CONFIG = { "repo": "https://github.com/commitizen-tools/commitizen", "rev": f"v{__version__}", "hooks": [ @@ -331,31 +332,29 @@ def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: ], } - config_data = {} if not self.project_info.has_pre_commit_config: # .pre-commit-config.yaml does not exist - config_data["repos"] = [cz_hook_config] + return {"repos": [CZ_HOOK_CONFIG]} + + with open(self._PRE_COMMIT_CONFIG_PATH, encoding=self.encoding) as config_file: + config_data: dict[str, Any] = yaml.safe_load(config_file) or {} + + if not isinstance(repos := config_data.get("repos"), list): + # .pre-commit-config.yaml exists but there's no "repos" key + config_data["repos"] = [CZ_HOOK_CONFIG] + return config_data + + # Check if commitizen pre-commit hook is already in the config + if any("commitizen" in hook_config["repo"] for hook_config in repos): + out.write("commitizen already in pre-commit config") else: - with open( - pre_commit_config_filename, encoding=self.encoding - ) as config_file: - yaml_data = yaml.safe_load(config_file) - if yaml_data: - config_data = yaml_data - - if "repos" in config_data: - for pre_commit_hook in config_data["repos"]: - if "commitizen" in pre_commit_hook["repo"]: - out.write("commitizen already in pre-commit config") - break - else: - config_data["repos"].append(cz_hook_config) - else: - # .pre-commit-config.yaml exists but there's no "repos" key - config_data["repos"] = [cz_hook_config] + repos.append(CZ_HOOK_CONFIG) + return config_data + def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: + config_data = self._get_config_data() with smart_open( - pre_commit_config_filename, "w", encoding=self.encoding + self._PRE_COMMIT_CONFIG_PATH, "w", encoding=self.encoding ) as config_file: yaml.safe_dump(config_data, stream=config_file) From 05c17227b54a8b77abab033ee7713bc9675ef979 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 20:32:51 +0800 Subject: [PATCH 16/51] refactor(process_commit_message): better type and early return --- commitizen/changelog.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/commitizen/changelog.py b/commitizen/changelog.py index 8721734af..93b533996 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -29,7 +29,7 @@ import re from collections import OrderedDict, defaultdict -from collections.abc import Generator, Iterable, Mapping, Sequence +from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence from dataclasses import dataclass from datetime import date from typing import TYPE_CHECKING, Any @@ -167,8 +167,8 @@ def process_commit_message( hook: MessageBuilderHook | None, parsed: re.Match[str], commit: GitCommit, - changes: dict[str | None, list], - change_type_map: dict[str, str] | None = None, + ref_changes: MutableMapping[str | None, list], + change_type_map: Mapping[str, str] | None = None, ) -> None: message: dict[str, Any] = { "sha1": commit.rev, @@ -178,13 +178,16 @@ def process_commit_message( **parsed.groupdict(), } - if processed := hook(message, commit) if hook else message: - messages = [processed] if isinstance(processed, dict) else processed - for msg in messages: - change_type = msg.pop("change_type", None) - if change_type_map: - change_type = change_type_map.get(change_type, change_type) - changes[change_type].append(msg) + processed_msg = hook(message, commit) if hook else message + if not processed_msg: + return + + messages = [processed_msg] if isinstance(processed_msg, dict) else processed_msg + for msg in messages: + change_type = msg.pop("change_type", None) + if change_type_map: + change_type = change_type_map.get(change_type, change_type) + ref_changes[change_type].append(msg) def generate_ordered_changelog_tree( From 4a1c51636b8744c945097b88fbf4c4560b1e19e5 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 19:13:10 +0800 Subject: [PATCH 17/51] build: remove not needed importlib-metadata dependency for python >= 3.10 --- poetry.lock | 93 +++----------------------------------------------- pyproject.toml | 3 +- 2 files changed, 6 insertions(+), 90 deletions(-) diff --git a/poetry.lock b/poetry.lock index c5c893931..e7f0e96e5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -222,7 +222,6 @@ description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["documentation"] -markers = "python_version == \"3.9\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -231,22 +230,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -[[package]] -name = "click" -version = "8.2.1" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["documentation"] -markers = "python_version != \"3.9\"" -files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.6" @@ -550,31 +533,6 @@ perf = ["ipython"] test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] -[[package]] -name = "importlib-metadata" -version = "8.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version != \"3.9\"" -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.1.0" @@ -594,7 +552,6 @@ description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version == \"3.9\"" files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -626,46 +583,6 @@ qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] -[[package]] -name = "ipython" -version = "8.37.0" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -markers = "python_version != \"3.9\"" -files = [ - {file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"}, - {file = "ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt_toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack_data = "*" -traitlets = ">=5.13.0" -typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] -kernel = ["ipykernel"] -matplotlib = ["matplotlib"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] - [[package]] name = "jedi" version = "0.19.2" @@ -1104,7 +1021,7 @@ description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" groups = ["dev"] -markers = "python_version == \"3.9\" and sys_platform != \"win32\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" +markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1207,7 +1124,7 @@ description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version == \"3.9\" and sys_platform != \"win32\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" +markers = "sys_platform != \"win32\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1817,7 +1734,7 @@ files = [ {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, ] -markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.12\"", script = "python_version < \"3.11\"", test = "python_version < \"3.11\""} +markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.11\"", script = "python_version < \"3.11\"", test = "python_version < \"3.11\""} [[package]] name = "urllib3" @@ -2009,11 +1926,11 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main", "documentation"] +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343"}, {file = "zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5"}, ] -markers = {documentation = "python_version == \"3.9\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] @@ -2026,4 +1943,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "f35cf57718e28836cf1e70b33edab9ce623b01a7f2d31d712585554721f6d403" +content-hash = "c98af83d4ce726bbfba04eea3de90e642fb7bdb08bedf0bf1b00b92a1b140e76" diff --git a/pyproject.toml b/pyproject.toml index 699074195..ab3ba39ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,7 @@ dependencies = [ "typing-extensions (>=4.0.1,<5.0.0) ; python_version < '3.11'", "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 >=8.0.0,!=8.7.0,<9.0.0 ; python_version == '3.9'", # importlib-metadata@8.7.0 + python3.9 breaks our unit test - "importlib-metadata >=8.0.0,<9.0.0 ; python_version != '3.9'", + "importlib-metadata >=8.0.0,<8.7.0 ; python_version < '3.10'", ] keywords = ["commitizen", "conventional", "commits", "git"] # See also: https://pypi.org/classifiers/ From 84706e1421862cad69291a5cc37d0f20d8146a0c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 19:31:34 +0800 Subject: [PATCH 18/51] docs(defaults): deprecate type Questions --- commitizen/defaults.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commitizen/defaults.py b/commitizen/defaults.py index a49d6d942..94d4d97b2 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -8,9 +8,6 @@ from commitizen.question import CzQuestion -# Type -Questions = Iterable[MutableMapping[str, Any]] # TODO: deprecate this? - class CzSettings(TypedDict, total=False): bump_pattern: str @@ -161,6 +158,10 @@ def get_tag_regexes( } +# Type +Questions = Iterable[MutableMapping[str, Any]] # TODO: remove this in v5 + + def __getattr__(name: str) -> Any: # PEP-562: deprecate module-level variable @@ -176,6 +177,7 @@ def __getattr__(name: str) -> Any: "change_type_order": (CHANGE_TYPE_ORDER, "CHANGE_TYPE_ORDER"), "encoding": (ENCODING, "ENCODING"), "name": (DEFAULT_SETTINGS["name"], "DEFAULT_SETTINGS['name']"), + "Questions": (Questions, "Iterable[CzQuestion]"), } if name in deprecated_vars: value, replacement = deprecated_vars[name] From 936352ba64415b53283d35ed14304929d1f82c3b Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 20:44:06 +0800 Subject: [PATCH 19/51] docs(customization.md): fix grammar mistake, add title to code blocks --- docs/customization.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/customization.md b/docs/customization.md index e97558a30..df7717107 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -10,7 +10,7 @@ The basic steps are: Example: -```toml +```toml title="pyproject.toml" [tool.commitizen] name = "cz_customize" @@ -50,7 +50,7 @@ message = "Do you want to add body message in commit?" The equivalent example for a json config file: -```json +```json title=".cz.json" { "commitizen": { "name": "cz_customize", @@ -106,7 +106,7 @@ The equivalent example for a json config file: And the correspondent example for a yaml file: -```yaml +```yaml title=".cz.yaml" commitizen: name: cz_customize customize: @@ -149,16 +149,16 @@ commitizen: | Parameter | Type | Default | Description | | ------------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `questions` | `Questions` | `None` | Questions regarding the commit message. Detailed below. The type `Questions` is an alias to `Iterable[MutableMapping[str, Any]]` which is defined in `commitizen.defaults`. It expects a list of dictionaries. | +| `questions` | `Questions` | `None` | Questions regarding the commit message. Detailed below. The type `Questions` is an alias to `Iterable[MutableMapping[str, Any]]` which is defined in `commitizen.defaults`. It expects a list of dictionaries. | | `message_template` | `str` | `None` | The template for generating message from the given answers. `message_template` should either follow [Jinja2][jinja2] formatting specification, and all the variables in this template should be defined in `name` in `questions` | -| `example` | `str` | `""` | (OPTIONAL) Provide an example to help understand the style. Used by `cz example`. | -| `schema` | `str` | `""` | (OPTIONAL) Show the schema used. Used by `cz schema`. | -| `schema_pattern` | `str` | `""` | (OPTIONAL) The regular expression used to do commit message validation. Used by `cz check`. | -| `info_path` | `str` | `""` | (OPTIONAL) The path to the file that contains explanation of the commit rules. Used by `cz info`. If not provided `cz info`, will load `info` instead. | -| `info` | `str` | `""` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. | +| `example` | `str` | `""` | (OPTIONAL) Provide an example to help understand the style. Used by `cz example`. | +| `schema` | `str` | `""` | (OPTIONAL) Show the schema used. Used by `cz schema`. | +| `schema_pattern` | `str` | `""` | (OPTIONAL) The regular expression used to do commit message validation. Used by `cz check`. | +| `info_path` | `str` | `""` | (OPTIONAL) The path to the file that contains explanation of the commit rules. Used by `cz info`. If not provided `cz info`, will load `info` instead. | +| `info` | `str` | `""` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. | | `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) | | `bump_pattern` | `str` | `None` | (OPTIONAL) Regex to extract information from commit (subject and body) | -| `change_type_order`| `str` | `None` | (OPTIONAL) List of strings used to order the Changelog. All other types will be sorted alphabetically. Default is `["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"]` | +| `change_type_order` | `str` | `None` | (OPTIONAL) List of strings used to order the Changelog. All other types will be sorted alphabetically. Default is `["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"]` | | `commit_parser` | `str` | `None` | (OPTIONAL) Regex to extract information used in creating changelog. [See more][changelog-spec] | | `changelog_pattern` | `str` | `None` | (OPTIONAL) Regex to understand which commits to include in the changelog | | `change_type_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the type of the commit to a changelog entry | @@ -215,7 +215,7 @@ Create a Python module, for example `cz_jira.py`. Inherit from `BaseCommitizen`, and you must define `questions` and `message`. The others are optional. -```python +```python title="cz_jira.py" from commitizen.cz.base import BaseCommitizen from commitizen.defaults import Questions @@ -259,7 +259,7 @@ class JiraCz(BaseCommitizen): The next file required is `setup.py` modified from flask version. -```python +```python title="setup.py" from setuptools import setup setup( @@ -295,7 +295,7 @@ You need to define 2 parameters inside your custom `BaseCommitizen`. Let's see an example. -```python +```python title="cz_strange.py" from commitizen.cz.base import BaseCommitizen @@ -315,7 +315,7 @@ cz -n cz_strange bump ### Custom changelog generator The changelog generator should just work in a very basic manner without touching anything. -You can customize it of course, and this are the variables you need to add to your custom `BaseCommitizen`. +You can customize it of course, and the following variables are the ones you need to add to your custom `BaseCommitizen`. | Parameter | Type | Required | Description | | -------------------------------- | ------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -326,7 +326,7 @@ You can customize it of course, and this are the variables you need to add to yo | `changelog_hook` | `method: (full_changelog: str, partial_changelog: Optional[str]) -> str` | NO | Receives the whole and partial (if used incremental) changelog. Useful to send slack messages or notify a compliance department. Must return the full_changelog | | `changelog_release_hook` | `method: (release: dict, tag: git.GitTag) -> dict` | NO | Receives each generated changelog release and its associated tag. Useful to enrich releases before they are rendered. Must return the update release -```python +```python title="cz_strange.py" from commitizen.cz.base import BaseCommitizen import chat import compliance @@ -376,8 +376,6 @@ class StrangeCommitizen(BaseCommitizen): return full_changelog ``` -[changelog-des]: ./commands/changelog.md#description - ### Raise Customize Exception If you want `commitizen` to catch your exception and print the message, you'll have to inherit `CzException`. @@ -528,3 +526,4 @@ by: [template-config]: config.md#template [extras-config]: config.md#extras +[changelog-des]: ./commands/changelog.md#description From 7dccbf68a9c9d7af34b0d3cdc930c2694135eccd Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Wed, 11 Jun 2025 14:21:02 +0800 Subject: [PATCH 20/51] docs(bump.md): add titles to code blocks and fix minor grammar issues --- docs/commands/bump.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/commands/bump.md b/docs/commands/bump.md index 8219cc346..1320e30a7 100644 --- a/docs/commands/bump.md +++ b/docs/commands/bump.md @@ -31,7 +31,7 @@ cz bump --version-scheme semver ``` 2. Configuration file: -```toml +```toml title="pyproject.toml" [tool.commitizen] version_scheme = "semver" ``` @@ -113,7 +113,7 @@ Note that as per [semantic versioning spec](https://semver.org/#spec-item-9) For example, the following versions (using the [PEP 440](https://peps.python.org/pep-0440/) scheme) are ordered by their precedence and showcase how a release might flow through a development cycle: -- `1.0.0` is the current published version +- `1.0.0` is the currently published version - `1.0.1a0` after committing a `fix:` for pre-release - `1.1.0a1` after committing an additional `feat:` for pre-release - `1.1.0b0` after bumping a beta release @@ -153,7 +153,7 @@ cz bump --check-consistency For example, if we have `pyproject.toml` -```toml +```toml title="pyproject.toml" [tool.commitizen] version = "1.21.0" version_files = [ @@ -162,15 +162,16 @@ version_files = [ ] ``` -`src/__version__.py`, +`src/__version__.py` -```python + +```python title="src/__version__.py" __version__ = "1.21.0" ``` -and `setup.py`. +and `setup.py` -```python +```python title="setup.py" from setuptools import setup setup(..., version="1.0.5", ...) @@ -193,7 +194,7 @@ cz bump --local-version For example, if we have `pyproject.toml` -```toml +```toml title="pyproject.toml" [tool.commitizen] version = "5.3.5+0.1.0" ``` @@ -454,7 +455,7 @@ In your `pyproject.toml` or `.cz.toml` tag_format = "v$major.$minor.$patch$prerelease" ``` -The variables must be preceded by a `$` sign and optionally can be wrapped in `{}` . Default is `$version`. +The variables must be preceded by a `$` sign and optionally can be wrapped in `{}`. The default is `$version`. Supported variables: @@ -483,7 +484,7 @@ Some examples `pyproject.toml`, `.cz.toml` or `cz.toml` -```toml +```toml title="pyproject.toml" [tool.commitizen] version_files = [ "src/__version__.py", @@ -496,8 +497,7 @@ This means that it will find a file `setup.py` and will only make a change in a line containing the `version` substring. !!! note - Files can be specified using relative (to the execution) paths, absolute paths - or glob patterns. + Files can be specified using relative (to the execution) paths, absolute paths, or glob patterns. --- @@ -516,7 +516,7 @@ Some examples `pyproject.toml`, `.cz.toml` or `cz.toml` -```toml +```toml title="pyproject.toml" [tool.commitizen] bump_message = "release $current_version โ†’ $new_version [skip-ci]" ``` @@ -529,7 +529,7 @@ When set to `true` the changelog is always updated incrementally when running `c Defaults to: `false` -```toml +```toml title="pyproject.toml" [tool.commitizen] update_changelog_on_bump = true ``` @@ -540,7 +540,7 @@ update_changelog_on_bump = true When set to `true`, Commitizen will create annotated tags. -```toml +```toml title="pyproject.toml" [tool.commitizen] annotated_tag = true ``` @@ -551,7 +551,7 @@ annotated_tag = true When set to `true`, Commitizen will create gpg signed tags. -```toml +```toml title="pyproject.toml" [tool.commitizen] gpg_sign = true ``` @@ -565,7 +565,7 @@ Useful during the initial development stage of your project. Defaults to: `false` -```toml +```toml title="pyproject.toml" [tool.commitizen] major_version_zero = true ``` @@ -591,7 +591,7 @@ execution of the script, some environment variables are available: | `CZ_PRE_INCREMENT` | Whether this is a `MAJOR`, `MINOR` or `PATH` release | | `CZ_PRE_CHANGELOG_FILE_NAME` | Path to the changelog file, if available | -```toml +```toml title="pyproject.toml" [tool.commitizen] pre_bump_hooks = [ "scripts/generate_documentation.sh" @@ -618,7 +618,7 @@ release. During execution of the script, some environment variables are availabl | `CZ_POST_INCREMENT` | Whether this was a `MAJOR`, `MINOR` or `PATH` release | | `CZ_POST_CHANGELOG_FILE_NAME` | Path to the changelog file, if available | -```toml +```toml title="pyproject.toml" [tool.commitizen] post_bump_hooks = [ "scripts/slack_notification.sh" @@ -631,7 +631,7 @@ Offset with which to start counting prereleases. Defaults to: `0` -```toml +```toml title="pyproject.toml" [tool.commitizen] prerelease_offset = 1 ``` @@ -651,7 +651,7 @@ Options: `pep440`, `semver`, `semver2` Defaults to: `pep440` -```toml +```toml title="pyproject.toml" [tool.commitizen] version_scheme = "semver" ``` From 189cf0b6818de0cfe8572919fcc76611d00c57b8 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 13 Jun 2025 23:22:08 +0800 Subject: [PATCH 21/51] ci(github-actions): set organization to true for JamesIves/github-sponsors-readme-action@v1 --- .github/workflows/docspublish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docspublish.yml b/.github/workflows/docspublish.yml index a871d3c37..e6bb9c722 100644 --- a/.github/workflows/docspublish.yml +++ b/.github/workflows/docspublish.yml @@ -68,6 +68,7 @@ jobs: with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN_FOR_ORG }} file: "docs/README.md" + organization: true - name: Push doc to Github Page uses: peaceiris/actions-gh-pages@v4 with: From 1d92a0568304026a1341abdae9d875b7715bc191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 01:17:47 +0000 Subject: [PATCH 22/51] ci(deps): bump dawidd6/action-homebrew-bump-formula from 4 to 5 Bumps [dawidd6/action-homebrew-bump-formula](https://github.com/dawidd6/action-homebrew-bump-formula) from 4 to 5. - [Release notes](https://github.com/dawidd6/action-homebrew-bump-formula/releases) - [Commits](https://github.com/dawidd6/action-homebrew-bump-formula/compare/v4...v5) --- updated-dependencies: - dependency-name: dawidd6/action-homebrew-bump-formula dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/homebrewpublish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/homebrewpublish.yml b/.github/workflows/homebrewpublish.yml index 443a13b1b..f5d8004e7 100644 --- a/.github/workflows/homebrewpublish.yml +++ b/.github/workflows/homebrewpublish.yml @@ -24,7 +24,7 @@ jobs: run: | echo "project_version=$(cz version --project)" >> $GITHUB_ENV - name: Update Homebrew formula - uses: dawidd6/action-homebrew-bump-formula@v4 + uses: dawidd6/action-homebrew-bump-formula@v5 with: token: ${{secrets.PERSONAL_ACCESS_TOKEN}} formula: commitizen From fe483535544cc4519169a952e9fa2a588685d345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 11 Jul 2025 20:54:37 -0600 Subject: [PATCH 23/51] docs(config.md): Document glob pattern support in `version_files` It was added in https://github.com/commitizen-tools/commitizen/pull/1070 but never documented AFAICT. --- docs/commands/bump.md | 5 +++-- docs/config.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/commands/bump.md b/docs/commands/bump.md index 1320e30a7..548f6e361 100644 --- a/docs/commands/bump.md +++ b/docs/commands/bump.md @@ -472,7 +472,7 @@ Supported variables: ### `version_files` \* -It is used to identify the files which should be updated with the new version. +It is used to identify the files or glob patterns which should be updated with the new version. It is also possible to provide a pattern for each file, separated by colons (`:`). Commitizen will update its configuration file automatically (`pyproject.toml`, `.cz`) when bumping, @@ -488,7 +488,8 @@ Some examples [tool.commitizen] version_files = [ "src/__version__.py", - "setup.py:version" + "packages/*/pyproject.toml:version", + "setup.py:version", ] ``` diff --git a/docs/config.md b/docs/config.md index 5ca2c5d78..649881dae 100644 --- a/docs/config.md +++ b/docs/config.md @@ -24,7 +24,7 @@ Type: `list` Default: `[ ]` -Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [Read more][version_files] +Files (or glob patterns) where the version will be updated. A pattern to match a line, can also be specified, separated by `:` [Read more][version_files] ### `version_provider` From eca443d07f9c7a3eccd90b7c721d06c0e123ac1d Mon Sep 17 00:00:00 2001 From: Andrew Udvare Date: Sun, 13 Jul 2025 22:16:57 -0400 Subject: [PATCH 24/51] docs(third-party-commitizen.md): Add cz-path info --- docs/third-party-commitizen.md | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/third-party-commitizen.md b/docs/third-party-commitizen.md index dc9b539c8..0c35a3155 100644 --- a/docs/third-party-commitizen.md +++ b/docs/third-party-commitizen.md @@ -145,3 +145,56 @@ commitizen: version_provider: deno-provider version_scheme: semver ``` + +### [cz-path](https://pypi.org/project/cz-path/) + +Provides prefix choices for commit messages based on staged files (Git only). +For example, if the staged files are `component/z/a.ts` and `component/z/b.ts`, +the path prefix option will be `component/z` and commit message might look like: +`component/z/: description of changes`. If only one file is staged, the extension +is removed in the prefix. + +#### Installation + +```sh +pip install cz-path +``` + +#### Usage + +Add `cz-path` to your configuration file. + +Example for `.cz.json`: + +```json +{ + "commitizen": { + "name": "cz_path", + "remove_path_prefixes": ["src", "module_name"] + } +} +``` + +The default value for `remove_path_prefixes` is `["src"]`. Adding `/` to the +prefixes is not required. + +#### Example session + +```plain + $ git add .vscode/ + $ cz -n cz_path c +? Prefix: (Use arrow keys) + ยป .vscode + .vscode/ + project + (empty) +? Prefix: .vscode +? Commit title: adjust settings + +.vscode: adjust settings + +[main 0000000] .vscode: adjust settings + 2 files changed, 1 insertion(+), 11 deletions(-) + +Commit successful! +``` From 83d9308487c3671f70b4c614b9b08d1fdd59bf28 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 15 Jul 2025 18:45:12 +0200 Subject: [PATCH 25/51] ci(github-actions): fix sponsorship page generation --- .github/workflows/docspublish.yml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docspublish.yml b/.github/workflows/docspublish.yml index e6bb9c722..6418a20fa 100644 --- a/.github/workflows/docspublish.yml +++ b/.github/workflows/docspublish.yml @@ -4,6 +4,7 @@ on: push: branches: - master + workflow_dispatch: jobs: update-cli-screenshots: @@ -58,22 +59,19 @@ jobs: python -m pip install -U pip poetry poethepoet poetry --version poetry install --no-root --only documentation - - name: Build docs - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - poetry doc:build - name: Generate Sponsors ๐Ÿ’– uses: JamesIves/github-sponsors-readme-action@v1 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN_FOR_ORG }} file: "docs/README.md" organization: true - - name: Push doc to Github Page - uses: peaceiris/actions-gh-pages@v4 + - name: Build docs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + poetry doc:build + - name: Deploy ๐Ÿš€ + uses: JamesIves/github-pages-deploy-action@v4 with: - personal_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - publish_branch: gh-pages - publish_dir: ./site - user_name: "github-actions[bot]" - user_email: "github-actions[bot]@users.noreply.github.com" + folder: ./site # The folder the action should deploy. + branch: gh-pages From 40d3062650617c67604e3c5f98bf35b5001973d3 Mon Sep 17 00:00:00 2001 From: timsu92 <33785401+timsu92@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:55:35 +0800 Subject: [PATCH 26/51] docs(README): install into dev dependency when using uv --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 31f2a77d1..c944855f3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -111,7 +111,7 @@ poetry add commitizen --dev **Using uv:** ```bash -uv add commitizen +uv add --dev commitizen ``` **Using pdm:** From 89600f08e86ad975dc43d685968ac95282e14852 Mon Sep 17 00:00:00 2001 From: timsu92 <33785401+timsu92@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:57:15 +0800 Subject: [PATCH 27/51] docs(README): ensure line break --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index c944855f3..fa3fab223 100644 --- a/docs/README.md +++ b/docs/README.md @@ -63,6 +63,7 @@ Before installing Commitizen, ensure you have: #### Global Installation (Recommended) The recommended way to install Commitizen is using [`pipx`](https://pipx.pypa.io/) or [`uv`](https://docs.astral.sh/uv/), which ensures a clean, isolated installation: + **Using pipx:** ```bash # Install Commitizen From a69d4415a34409809eaa3ac475c7f164be80110f Mon Sep 17 00:00:00 2001 From: timsu92 <33785401+timsu92@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:13:11 +0800 Subject: [PATCH 28/51] refactor(init): remote extra words --- commitizen/commands/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index c298ffec8..8acc3ae5c 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -238,7 +238,7 @@ def _ask_version_provider(self) -> str: "npm": "npm: Get and set version from package.json:project.version field", "pep621": "pep621: Get and set version from pyproject.toml:project.version field", "poetry": "poetry: Get and set version from pyproject.toml:tool.poetry.version field", - "uv": "uv: Get and Get and set version from pyproject.toml and uv.lock", + "uv": "uv: Get and set version from pyproject.toml and uv.lock", "scm": "scm: Fetch the version from git and does not need to set it back", } From b8918a1999107c19b6c00eddf921c2058f79ec23 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 12:25:24 +0800 Subject: [PATCH 29/51] refactor(Init): fix unbounded variable in _ask_tag_format --- commitizen/commands/init.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 8acc3ae5c..eed7ff2d6 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -210,23 +210,20 @@ def _ask_tag(self) -> str: return latest_tag def _ask_tag_format(self, latest_tag: str) -> str: - is_correct_format = False if latest_tag.startswith("v"): - tag_format = r"v$version" - is_correct_format = questionary.confirm( - f'Is "{tag_format}" the correct tag format?', style=self.cz.style - ).unsafe_ask() + v_tag_format = r"v$version" + if questionary.confirm( + f'Is "{v_tag_format}" the correct tag format?', style=self.cz.style + ).unsafe_ask(): + return v_tag_format default_format = DEFAULT_SETTINGS["tag_format"] - if not is_correct_format: - tag_format = questionary.text( - f'Please enter the correct version format: (default: "{default_format}")', - style=self.cz.style, - ).unsafe_ask() + tag_format: str = questionary.text( + f'Please enter the correct version format: (default: "{default_format}")', + style=self.cz.style, + ).unsafe_ask() - if not tag_format: - tag_format = default_format - return tag_format + return tag_format or default_format def _ask_version_provider(self) -> str: """Ask for setting: version_provider""" From 30baeabc54947f517bdd1c3befa8ce61d6ccb7e5 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 13:22:44 +0800 Subject: [PATCH 30/51] test(Init): improve coverage for _ask_tag_format --- tests/commands/test_init_command.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py index 3f12d0bd7..2d39ae085 100644 --- a/tests/commands/test_init_command.py +++ b/tests/commands/test_init_command.py @@ -255,6 +255,38 @@ def test_pre_commit_exec_failed( commands.Init(config)() +class TestAskTagFormat: + def test_confirm_v_tag_format(self, mocker: MockFixture, config): + init = commands.Init(config) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + + result = init._ask_tag_format("v1.0.0") + assert result == r"v$version" + + def test_reject_v_tag_format(self, mocker: MockFixture, config): + init = commands.Init(config) + mocker.patch("questionary.confirm", return_value=FakeQuestion(False)) + mocker.patch("questionary.text", return_value=FakeQuestion("custom-$version")) + + result = init._ask_tag_format("v1.0.0") + assert result == "custom-$version" + + def test_non_v_tag_format(self, mocker: MockFixture, config): + init = commands.Init(config) + mocker.patch("questionary.text", return_value=FakeQuestion("custom-$version")) + + result = init._ask_tag_format("1.0.0") + assert result == "custom-$version" + + def test_empty_input_returns_default(self, mocker: MockFixture, config): + init = commands.Init(config) + mocker.patch("questionary.confirm", return_value=FakeQuestion(False)) + mocker.patch("questionary.text", return_value=FakeQuestion("")) + + result = init._ask_tag_format("v1.0.0") + assert result == "$version" # This is the default format from DEFAULT_SETTINGS + + @skip_below_py_3_10 def test_init_command_shows_description_when_use_help_option( mocker: MockFixture, capsys, file_regression From e70c0a6bf75404d08f18fa5185eceb1d1d39cc58 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 22:17:06 +0800 Subject: [PATCH 31/51] fix(ExitCode): add from_str in ExitCode and replace parse_no_raise with it Warn if a given decimal string is not in range --- commitizen/cli.py | 25 ++++++++++++------------- commitizen/exceptions.py | 12 ++++++++++-- tests/test_exceptions.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 tests/test_exceptions.py diff --git a/commitizen/cli.py b/commitizen/cli.py index a73377fe4..3862b8843 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -580,20 +580,19 @@ def parse_no_raise(comma_separated_no_raise: str) -> list[int]: Receives digits and strings and outputs the parsed integer which represents the exit code found in exceptions. """ - no_raise_items: list[str] = comma_separated_no_raise.split(",") - no_raise_codes: list[int] = [] - for item in no_raise_items: - if item.isdecimal(): - no_raise_codes.append(int(item)) - continue + + def exit_code_from_str_or_skip(s: str) -> ExitCode | None: try: - exit_code = ExitCode[item.strip()] - except KeyError: - out.warn(f"WARN: no_raise key `{item}` does not exist. Skipping.") - continue - else: - no_raise_codes.append(exit_code.value) - return no_raise_codes + return ExitCode.from_str(s) + except (KeyError, ValueError): + out.warn(f"WARN: no_raise value `{s}` is not a valid exit code. Skipping.") + return None + + return [ + code.value + for s in comma_separated_no_raise.split(",") + if (code := exit_code_from_str_or_skip(s)) is not None + ] if TYPE_CHECKING: diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py index 8be792258..75b0ab2fb 100644 --- a/commitizen/exceptions.py +++ b/commitizen/exceptions.py @@ -1,10 +1,12 @@ -import enum +from __future__ import annotations + +from enum import IntEnum from typing import Any from commitizen import out -class ExitCode(enum.IntEnum): +class ExitCode(IntEnum): EXPECTED_EXIT = 0 NO_COMMITIZEN_FOUND = 1 NOT_A_GIT_PROJECT = 2 @@ -39,6 +41,12 @@ class ExitCode(enum.IntEnum): CONFIG_FILE_IS_EMPTY = 31 COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED = 32 + @classmethod + def from_str(cls, value: str) -> ExitCode: + if value.isdecimal(): + return cls(int(value)) + return cls[value.strip()] + class CommitizenException(Exception): def __init__(self, *args: str, **kwargs: Any) -> None: diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 000000000..1a66c79d0 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,38 @@ +import pytest + +from commitizen.exceptions import ExitCode + + +def test_from_str_with_decimal(): + """Test from_str with decimal values.""" + assert ExitCode.from_str("0") == ExitCode.EXPECTED_EXIT + assert ExitCode.from_str("1") == ExitCode.NO_COMMITIZEN_FOUND + assert ExitCode.from_str("32") == ExitCode.COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED + + +def test_from_str_with_enum_name(): + """Test from_str with enum names.""" + assert ExitCode.from_str("EXPECTED_EXIT") == ExitCode.EXPECTED_EXIT + assert ExitCode.from_str("NO_COMMITIZEN_FOUND") == ExitCode.NO_COMMITIZEN_FOUND + assert ( + ExitCode.from_str("COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED") + == ExitCode.COMMIT_MESSAGE_LENGTH_LIMIT_EXCEEDED + ) + + +def test_from_str_with_whitespace(): + """Test from_str with whitespace in enum names.""" + assert ExitCode.from_str(" EXPECTED_EXIT ") == ExitCode.EXPECTED_EXIT + assert ExitCode.from_str("\tNO_COMMITIZEN_FOUND\t") == ExitCode.NO_COMMITIZEN_FOUND + + +def test_from_str_with_invalid_values(): + """Test from_str with invalid values.""" + with pytest.raises(KeyError): + ExitCode.from_str("invalid_name") + with pytest.raises(ValueError): + ExitCode.from_str("999") # Out of range decimal + with pytest.raises(KeyError): + ExitCode.from_str("") + with pytest.raises(KeyError): + ExitCode.from_str(" ") From d417cf7ff1ea858260dbb0d0a4a94ddefbd5c1d0 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 12:05:16 +0800 Subject: [PATCH 32/51] fix(Init): fix a typo in _ask_version_provider options and remove unnecessary filter, use named tuple for options --- commitizen/commands/init.py | 105 ++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index eed7ff2d6..33be58544 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -2,7 +2,7 @@ import os import shutil -from typing import Any +from typing import Any, NamedTuple import questionary import yaml @@ -17,6 +17,54 @@ from commitizen.version_schemes import KNOWN_SCHEMES, Version, get_version_scheme +class _VersionProviderOption(NamedTuple): + provider_name: str + description: str + + @property + def title(self) -> str: + return f"{self.provider_name}: {self.description}" + + +_VERSION_PROVIDER_CHOICES = tuple( + questionary.Choice(title=option.title, value=option.provider_name) + for option in ( + _VersionProviderOption( + provider_name="commitizen", + description="Fetch and set version in commitizen config (default)", + ), + _VersionProviderOption( + provider_name="cargo", + description="Get and set version from Cargo.toml:project.version field", + ), + _VersionProviderOption( + provider_name="composer", + description="Get and set version from composer.json:project.version field", + ), + _VersionProviderOption( + provider_name="npm", + description="Get and set version from package.json:project.version field", + ), + _VersionProviderOption( + provider_name="pep621", + description="Get and set version from pyproject.toml:project.version field", + ), + _VersionProviderOption( + provider_name="poetry", + description="Get and set version from pyproject.toml:tool.poetry.version field", + ), + _VersionProviderOption( + provider_name="uv", + description="Get and set version from pyproject.toml and uv.lock", + ), + _VersionProviderOption( + provider_name="scm", + description="Fetch the version from git and does not need to set it back", + ), + ) +) + + class ProjectInfo: """Discover information about the current folder.""" @@ -228,45 +276,32 @@ def _ask_tag_format(self, latest_tag: str) -> str: def _ask_version_provider(self) -> str: """Ask for setting: version_provider""" - OPTS = { - "commitizen": "commitizen: Fetch and set version in commitizen config (default)", - "cargo": "cargo: Get and set version from Cargo.toml:project.version field", - "composer": "composer: Get and set version from composer.json:project.version field", - "npm": "npm: Get and set version from package.json:project.version field", - "pep621": "pep621: Get and set version from pyproject.toml:project.version field", - "poetry": "poetry: Get and set version from pyproject.toml:tool.poetry.version field", - "uv": "uv: Get and set version from pyproject.toml and uv.lock", - "scm": "scm: Fetch the version from git and does not need to set it back", - } - - default_val = "commitizen" - if self.project_info.is_python: - if self.project_info.is_python_poetry: - default_val = "poetry" - elif self.project_info.is_python_uv: - default_val = "uv" - else: - default_val = "pep621" - elif self.project_info.is_rust_cargo: - default_val = "cargo" - elif self.project_info.is_npm_package: - default_val = "npm" - elif self.project_info.is_php_composer: - default_val = "composer" - - choices = [ - questionary.Choice(title=title, value=value) - for value, title in OPTS.items() - ] - default = next(filter(lambda x: x.value == default_val, choices)) version_provider: str = questionary.select( "Choose the source of the version:", - choices=choices, + choices=_VERSION_PROVIDER_CHOICES, style=self.cz.style, - default=default, + default=self._default_version_provider, ).unsafe_ask() return version_provider + @property + def _default_version_provider(self) -> str: + if self.project_info.is_python: + if self.project_info.is_python_poetry: + return "poetry" + if self.project_info.is_python_uv: + return "uv" + return "pep621" + + if self.project_info.is_rust_cargo: + return "cargo" + if self.project_info.is_npm_package: + return "npm" + if self.project_info.is_php_composer: + return "composer" + + return "commitizen" + def _ask_version_scheme(self) -> str: """Ask for setting: version_scheme""" default_scheme = "pep440" if self.project_info.is_python else "semver" @@ -291,7 +326,7 @@ def _ask_major_version_zero(self, version: Version) -> bool: return major_version_zero def _ask_update_changelog_on_bump(self) -> bool: - "Ask for setting: update_changelog_on_bump" + """Ask for setting: update_changelog_on_bump""" update_changelog_on_bump: bool = questionary.confirm( "Create changelog automatically on bump", default=True, From 1118f659ea9a88a70702761357f443a3e2d7646f Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 00:51:31 +0800 Subject: [PATCH 33/51] fix(init): make welcome message easier to read --- commitizen/commands/init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 33be58544..0f19786d0 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -142,8 +142,8 @@ def __call__(self) -> None: out.info("Welcome to commitizen!\n") out.line( - "Answer the questions to configure your project.\n" - "For further configuration visit:\n" + "Answer the following questions to configure your project.\n" + "For further configuration, visit:\n" "\n" "https://commitizen-tools.github.io/commitizen/config/" "\n" From e28e3ceff6cec3d74b81a35c07cd9747769f3a8c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 11:37:15 +0800 Subject: [PATCH 34/51] refactor(Init): remove unnecessary methods from ProjectInfo and refactor _ask_tag --- commitizen/commands/init.py | 49 +++++++++++++++---------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 0f19786d0..4b5649b67 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -111,16 +111,6 @@ def is_npm_package(self) -> bool: def is_php_composer(self) -> bool: return os.path.isfile("composer.json") - @property - def latest_tag(self) -> str | None: - return get_latest_tag_name() - - def tags(self) -> list | None: - """Not a property, only use if necessary""" - if self.latest_tag is None: - return None - return get_tag_names() - @property def is_pre_commit_installed(self) -> bool: return bool(shutil.which("pre-commit")) @@ -231,31 +221,32 @@ def _ask_name(self) -> str: return name def _ask_tag(self) -> str: - latest_tag = self.project_info.latest_tag + latest_tag = get_latest_tag_name() if not latest_tag: out.error("No Existing Tag. Set tag to v0.0.1") return "0.0.1" - is_correct_tag = questionary.confirm( + if questionary.confirm( f"Is {latest_tag} the latest tag?", style=self.cz.style, default=False + ).unsafe_ask(): + return latest_tag + + existing_tags = get_tag_names() + if not existing_tags: + out.error("No Existing Tag. Set tag to v0.0.1") + return "0.0.1" + + answer: str = questionary.select( + "Please choose the latest tag: ", + # The latest tag is most likely with the largest number. + # Thus, listing the existing_tags in reverse order makes more sense. + choices=sorted(existing_tags, reverse=True), + style=self.cz.style, ).unsafe_ask() - if not is_correct_tag: - tags = self.project_info.tags() - if not tags: - out.error("No Existing Tag. Set tag to v0.0.1") - return "0.0.1" - - # the latest tag is most likely with the largest number. Thus list the tags in reverse order makes more sense - sorted_tags = sorted(tags, reverse=True) - latest_tag = questionary.select( - "Please choose the latest tag: ", - choices=sorted_tags, - style=self.cz.style, - ).unsafe_ask() - - if not latest_tag: - raise NoAnswersError("Tag is required!") - return latest_tag + + if not answer: + raise NoAnswersError("Tag is required!") + return answer def _ask_tag_format(self, latest_tag: str) -> str: if latest_tag.startswith("v"): From a498804a4b74652a87e9cf104a825d1a8ad7b405 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 11:51:03 +0800 Subject: [PATCH 35/51] test(Init): cover _ask_tag test --- tests/commands/test_init_command.py | 122 ++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py index 2d39ae085..7feaf18ef 100644 --- a/tests/commands/test_init_command.py +++ b/tests/commands/test_init_command.py @@ -298,3 +298,125 @@ def test_init_command_shows_description_when_use_help_option( out, _ = capsys.readouterr() file_regression.check(out, extension=".txt") + + +def test_init_with_confirmed_tag_format(config, mocker: MockFixture, tmpdir): + mocker.patch( + "commitizen.commands.init.get_tag_names", return_value=["v0.0.2", "v0.0.1"] + ) + mocker.patch("commitizen.commands.init.get_latest_tag_name", return_value="v0.0.2") + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("commitizen"), + FakeQuestion("semver"), + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + with open("pyproject.toml", encoding="utf-8") as toml_file: + assert 'tag_format = "v$version"' in toml_file.read() + + +def test_init_with_no_existing_tags(config, mocker: MockFixture, tmpdir): + mocker.patch("commitizen.commands.init.get_tag_names", return_value=[]) + mocker.patch("commitizen.commands.init.get_latest_tag_name", return_value="v1.0.0") + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("commitizen"), + FakeQuestion("semver"), + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(False)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + with open("pyproject.toml", encoding="utf-8") as toml_file: + assert 'version = "0.0.1"' in toml_file.read() + + +def test_init_with_no_existing_latest_tag(config, mocker: MockFixture, tmpdir): + mocker.patch("commitizen.commands.init.get_latest_tag_name", return_value=None) + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("commitizen"), + FakeQuestion("semver"), + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + with open("pyproject.toml", encoding="utf-8") as toml_file: + assert 'version = "0.0.1"' in toml_file.read() + + +def test_init_with_existing_tags(config, mocker: MockFixture, tmpdir): + expected_tags = ["v1.0.0", "v0.9.0", "v0.8.0"] + mocker.patch("commitizen.commands.init.get_tag_names", return_value=expected_tags) + mocker.patch("commitizen.commands.init.get_latest_tag_name", return_value="v1.0.0") + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("commitizen"), + FakeQuestion("semver"), # Select version scheme first + FakeQuestion("v1.0.0"), # Then select the latest tag + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + with open("pyproject.toml", encoding="utf-8") as toml_file: + assert 'version = "1.0.0"' in toml_file.read() + + +def test_init_with_valid_tag_selection(config, mocker: MockFixture, tmpdir): + expected_tags = ["v1.0.0", "v0.9.0", "v0.8.0"] + mocker.patch("commitizen.commands.init.get_tag_names", return_value=expected_tags) + mocker.patch("commitizen.commands.init.get_latest_tag_name", return_value="v1.0.0") + + # Mock all questionary.select calls in the exact order they appear in Init.__call__ + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), # _ask_config_path + FakeQuestion("cz_conventional_commits"), # _ask_name + FakeQuestion("commitizen"), # _ask_version_provider + FakeQuestion("v0.9.0"), # _ask_tag (after confirm=False) + FakeQuestion("semver"), # _ask_version_scheme + ], + ) + + mocker.patch( + "questionary.confirm", return_value=FakeQuestion(False) + ) # Don't confirm latest tag + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + with open("pyproject.toml", encoding="utf-8") as toml_file: + content = toml_file.read() + assert 'version = "0.9.0"' in content + assert 'version_scheme = "semver"' in content From d9058b6feac361eaf810d7b9b1bcbd7bb4e79dcd Mon Sep 17 00:00:00 2001 From: timsu92 <33785401+timsu92@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:49:58 +0800 Subject: [PATCH 36/51] fix(init): use pre-push as pre-commit stage Original "push" stage has been deprecated since pre-commit v3.2.0. See https://github.com/pre-commit/pre-commit/issues/2732 and https://github.com/pre-commit/pre-commit/pull/2808 for detailed information. --- .pre-commit-hooks.yaml | 2 +- commitizen/commands/init.py | 2 +- poetry.lock | 406 +++++++++++++++++++----------------- pyproject.toml | 2 +- 4 files changed, 219 insertions(+), 193 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 2a3a08848..c14263458 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -24,4 +24,4 @@ pass_filenames: false language: python language_version: python3 - minimum_pre_commit_version: "1.4.3" + minimum_pre_commit_version: "3.2.0" diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 4b5649b67..a6efe0acc 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -351,7 +351,7 @@ def _get_config_data(self) -> dict[str, Any]: "rev": f"v{__version__}", "hooks": [ {"id": "commitizen"}, - {"id": "commitizen-branch", "stages": ["push"]}, + {"id": "commitizen-branch", "stages": ["pre-push"]}, ], } diff --git a/poetry.lock b/poetry.lock index e7f0e96e5..67972ae45 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "argcomplete" @@ -48,18 +48,19 @@ dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)" [[package]] name = "backrefs" -version = "5.8" +version = "5.9" description = "A wrapper around re and regex that adds additional back references." optional = false python-versions = ">=3.9" groups = ["documentation"] files = [ - {file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"}, - {file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"}, - {file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"}, - {file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"}, - {file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"}, - {file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"}, + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, ] [package.extras] @@ -67,26 +68,26 @@ extras = ["regex"] [[package]] name = "cachetools" -version = "6.0.0" +version = "6.1.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "cachetools-6.0.0-py3-none-any.whl", hash = "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e"}, - {file = "cachetools-6.0.0.tar.gz", hash = "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf"}, + {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"}, + {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"}, ] [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["documentation"] files = [ - {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, - {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] @@ -244,79 +245,100 @@ files = [ [[package]] name = "coverage" -version = "7.8.2" +version = "7.10.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a"}, - {file = "coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404"}, - {file = "coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7"}, - {file = "coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347"}, - {file = "coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9"}, - {file = "coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54"}, - {file = "coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a"}, - {file = "coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975"}, - {file = "coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53"}, - {file = "coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c"}, - {file = "coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f"}, - {file = "coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8"}, - {file = "coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223"}, - {file = "coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f"}, - {file = "coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca"}, - {file = "coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48"}, - {file = "coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7"}, - {file = "coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3"}, - {file = "coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7"}, - {file = "coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008"}, - {file = "coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199"}, - {file = "coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8"}, - {file = "coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d"}, - {file = "coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b"}, - {file = "coverage-7.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:496948261eaac5ac9cf43f5d0a9f6eb7a6d4cb3bedb2c5d294138142f5c18f2a"}, - {file = "coverage-7.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eacd2de0d30871eff893bab0b67840a96445edcb3c8fd915e6b11ac4b2f3fa6d"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b039ffddc99ad65d5078ef300e0c7eed08c270dc26570440e3ef18beb816c1ca"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e49824808d4375ede9dd84e9961a59c47f9113039f1a525e6be170aa4f5c34d"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b069938961dfad881dc2f8d02b47645cd2f455d3809ba92a8a687bf513839787"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:de77c3ba8bb686d1c411e78ee1b97e6e0b963fb98b1637658dd9ad2c875cf9d7"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1676628065a498943bd3f64f099bb573e08cf1bc6088bbe33cf4424e0876f4b3"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8e1a26e7e50076e35f7afafde570ca2b4d7900a491174ca357d29dece5aacee7"}, - {file = "coverage-7.8.2-cp39-cp39-win32.whl", hash = "sha256:6782a12bf76fa61ad9350d5a6ef5f3f020b57f5e6305cbc663803f2ebd0f270a"}, - {file = "coverage-7.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1efa4166ba75ccefd647f2d78b64f53f14fb82622bc94c5a5cb0a622f50f1c9e"}, - {file = "coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837"}, - {file = "coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32"}, - {file = "coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27"}, + {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, + {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8"}, + {file = "coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87"}, + {file = "coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f"}, + {file = "coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27"}, + {file = "coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1"}, + {file = "coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce"}, + {file = "coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2"}, + {file = "coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293"}, + {file = "coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83"}, + {file = "coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b"}, + {file = "coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340"}, + {file = "coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388"}, + {file = "coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20"}, + {file = "coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186"}, + {file = "coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a"}, + {file = "coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b"}, + {file = "coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe"}, + {file = "coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7"}, + {file = "coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e"}, + {file = "coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb"}, + {file = "coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824"}, + {file = "coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3"}, + {file = "coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f"}, + {file = "coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6"}, + {file = "coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd"}, + {file = "coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8"}, + {file = "coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3"}, + {file = "coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc"}, + {file = "coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b"}, + {file = "coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed"}, + {file = "coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0"}, + {file = "coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf"}, + {file = "coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc"}, + {file = "coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd"}, + {file = "coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0"}, + {file = "coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf"}, + {file = "coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e"}, + {file = "coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f"}, + {file = "coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055"}, ] [package.dependencies] @@ -369,14 +391,14 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev", "linters"] files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] @@ -447,14 +469,14 @@ typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "freezegun" -version = "1.5.2" +version = "1.5.4" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.8" groups = ["test"] files = [ - {file = "freezegun-1.5.2-py3-none-any.whl", hash = "sha256:5aaf3ba229cda57afab5bd311f0108d86b6fb119ae89d2cd9c43ec8c1733c85b"}, - {file = "freezegun-1.5.2.tar.gz", hash = "sha256:a54ae1d2f9c02dbf42e02c18a3ab95ab4295818b549a34dac55592d72a905181"}, + {file = "freezegun-1.5.4-py3-none-any.whl", hash = "sha256:8bdd75c9d790f53d5a173d273064ccd7900984b36635be552befeedb0cd47b20"}, + {file = "freezegun-1.5.4.tar.gz", hash = "sha256:798b9372fdd4d907f33e8b6a58bc64e682d9ffa8d494ce60f780197ee81faed1"}, ] [package.dependencies] @@ -623,14 +645,14 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.8" +version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" groups = ["documentation"] files = [ - {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, - {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, + {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, + {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, ] [package.dependencies] @@ -827,14 +849,14 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.6.14" +version = "9.6.16" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["documentation"] files = [ - {file = "mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b"}, - {file = "mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754"}, + {file = "mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c"}, + {file = "mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19"}, ] [package.dependencies] @@ -869,44 +891,50 @@ files = [ [[package]] name = "mypy" -version = "1.16.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c"}, - {file = "mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571"}, - {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491"}, - {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777"}, - {file = "mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b"}, - {file = "mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93"}, - {file = "mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab"}, - {file = "mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2"}, - {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff"}, - {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666"}, - {file = "mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c"}, - {file = "mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b"}, - {file = "mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13"}, - {file = "mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090"}, - {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1"}, - {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8"}, - {file = "mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730"}, - {file = "mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec"}, - {file = "mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b"}, - {file = "mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0"}, - {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b"}, - {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d"}, - {file = "mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52"}, - {file = "mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb"}, - {file = "mypy-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f56236114c425620875c7cf71700e3d60004858da856c6fc78998ffe767b73d3"}, - {file = "mypy-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15486beea80be24ff067d7d0ede673b001d0d684d0095803b3e6e17a886a2a92"}, - {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ed0e0847a80655afa2c121835b848ed101cc7b8d8d6ecc5205aedc732b1436"}, - {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb5fbc8063cb4fde7787e4c0406aa63094a34a2daf4673f359a1fb64050e9cb2"}, - {file = "mypy-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5fcfdb7318c6a8dd127b14b1052743b83e97a970f0edb6c913211507a255e20"}, - {file = "mypy-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7e0ad35275e02797323a5aa1be0b14a4d03ffdb2e5f2b0489fa07b89c67b21"}, - {file = "mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031"}, - {file = "mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] @@ -1147,14 +1175,14 @@ tests = ["pytest"] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["dev", "documentation", "script", "test"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -1162,14 +1190,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.15" +version = "10.16.1" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["documentation"] files = [ - {file = "pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f"}, - {file = "pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7"}, + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] @@ -1201,14 +1229,14 @@ testing = ["covdefaults (>=2.3)", "pytest (>=8.3.5)", "pytest-cov (>=6.1.1)", "p [[package]] name = "pytest" -version = "8.4.0" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e"}, - {file = "pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] @@ -1225,33 +1253,34 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, - {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" +pluggy = ">=1.2" +pytest = ">=6.2.5" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-datadir" -version = "1.7.2" +version = "1.8.0" description = "pytest plugin for test data directories and files" optional = false python-versions = ">=3.8" groups = ["test"] files = [ - {file = "pytest_datadir-1.7.2-py3-none-any.whl", hash = "sha256:8392ba0e9eaf37030e663dcd91cc5123dec99c44300f0c5eac44f35f13f0e086"}, - {file = "pytest_datadir-1.7.2.tar.gz", hash = "sha256:15f5228a35d0a3205e4968e75d3b9cca91762424e1eafc21eb637d380a48443e"}, + {file = "pytest_datadir-1.8.0-py3-none-any.whl", hash = "sha256:5c677bc097d907ac71ca418109adc3abe34cf0bddfe6cf78aecfbabd96a15cf0"}, + {file = "pytest_datadir-1.8.0.tar.gz", hash = "sha256:7a15faed76cebe87cc91941dd1920a9a38eba56a09c11e9ddf1434d28a0f78eb"}, ] [package.dependencies] @@ -1297,14 +1326,14 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-regressions" -version = "2.8.0" +version = "2.8.1" description = "Easy to use fixtures to write regression tests." optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest_regressions-2.8.0-py3-none-any.whl", hash = "sha256:2926f37efa5fd6793806b10352e274c5284a5469a845aeab6243e86f9214766f"}, - {file = "pytest_regressions-2.8.0.tar.gz", hash = "sha256:775044e17117f5427df2caad3ab1c66889abe770a0ce2bc3f24fdeac99af76fe"}, + {file = "pytest_regressions-2.8.1-py3-none-any.whl", hash = "sha256:bcae249df5214225bbe4bd77c146d8f331f19b0b26805486533b1e49680950a5"}, + {file = "pytest_regressions-2.8.1.tar.gz", hash = "sha256:20080d44cf46b40407956af5e44a7f932cbc3159018de81fb51e62b97e96a5fb"}, ] [package.dependencies] @@ -1320,14 +1349,14 @@ num = ["numpy", "pandas"] [[package]] name = "pytest-xdist" -version = "3.7.0" +version = "3.8.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest_xdist-3.7.0-py3-none-any.whl", hash = "sha256:7d3fbd255998265052435eb9daa4e99b62e6fb9cfb6efd1f858d4d8c0c7f0ca0"}, - {file = "pytest_xdist-3.7.0.tar.gz", hash = "sha256:f9248c99a7c15b7d2f90715df93610353a485827bc06eefb6566d23f6400f126"}, + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, ] [package.dependencies] @@ -1449,19 +1478,19 @@ prompt_toolkit = ">=2.0,<4.0" [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["documentation"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -1621,31 +1650,28 @@ files = [ [[package]] name = "tox" -version = "4.26.0" +version = "4.28.4" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224"}, - {file = "tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca"}, + {file = "tox-4.28.4-py3-none-any.whl", hash = "sha256:8d4ad9ee916ebbb59272bb045e154a10fa12e3bbdcf94cc5185cbdaf9b241f99"}, + {file = "tox-4.28.4.tar.gz", hash = "sha256:b5b14c6307bd8994ff1eba5074275826620325ee1a4f61316959d562bfd70b9d"}, ] [package.dependencies] -cachetools = ">=5.5.1" +cachetools = ">=6.1" chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.16.1" -packaging = ">=24.2" -platformdirs = ">=4.3.6" -pluggy = ">=1.5" -pyproject-api = ">=1.8" +filelock = ">=3.18" +packaging = ">=25" +platformdirs = ">=4.3.8" +pluggy = ">=1.6" +pyproject-api = ">=1.9.1" tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} -virtualenv = ">=20.31" - -[package.extras] -test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"] +typing-extensions = {version = ">=4.14.1", markers = "python_version < \"3.11\""} +virtualenv = ">=20.31.2" [[package]] name = "traitlets" @@ -1665,14 +1691,14 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "types-colorama" -version = "0.4.15.20240311" +version = "0.4.15.20250801" description = "Typing stubs for colorama" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a"}, - {file = "types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e"}, + {file = "types_colorama-0.4.15.20250801-py3-none-any.whl", hash = "sha256:b6e89bd3b250fdad13a8b6a465c933f4a5afe485ea2e2f104d739be50b13eea9"}, + {file = "types_colorama-0.4.15.20250801.tar.gz", hash = "sha256:02565d13d68963d12237d3f330f5ecd622a3179f7b5b14ee7f16146270c357f5"}, ] [[package]] @@ -1689,14 +1715,14 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20250516" +version = "2.9.0.20250708" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93"}, - {file = "types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5"}, + {file = "types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f"}, + {file = "types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab"}, ] [[package]] @@ -1725,27 +1751,27 @@ files = [ [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev", "linters", "script", "test"] files = [ - {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, - {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.11\"", script = "python_version < \"3.11\"", test = "python_version < \"3.11\""} [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["documentation"] files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -1756,14 +1782,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.31.2" +version = "20.33.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev", "linters"] files = [ - {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, - {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, + {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, + {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, ] [package.dependencies] @@ -1921,15 +1947,15 @@ files = [ [[package]] name = "zipp" -version = "3.22.0" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main", "documentation"] markers = "python_version == \"3.9\"" files = [ - {file = "zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343"}, - {file = "zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] @@ -1937,10 +1963,10 @@ check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \" cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib_resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "c98af83d4ce726bbfba04eea3de90e642fb7bdb08bedf0bf1b00b92a1b140e76" +content-hash = "ac1b07dc1d59d75607768904af6528a4d2f2af1695a7a1639973d8b35ed48008" diff --git a/pyproject.toml b/pyproject.toml index ab3ba39ee..d287a2258 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,7 @@ deprecated = "^1.2.13" [tool.poetry.group.linters.dependencies] ruff = "^0.11.5" -pre-commit = ">=2.18,<5.0" +pre-commit = ">=3.2.0,<5.0" mypy = "^1.16.0" types-deprecated = "^1.2.9.2" types-python-dateutil = "^2.8.19.13" From 445f01372bff028c3b80a57fad3aca3eff8a4cd6 Mon Sep 17 00:00:00 2001 From: timsu92 <33785401+timsu92@users.noreply.github.com> Date: Wed, 6 Aug 2025 21:25:30 +0800 Subject: [PATCH 37/51] test(init): check "pre-" is showing in outputs --- tests/commands/test_init_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py index 7feaf18ef..26f67dfae 100644 --- a/tests/commands/test_init_command.py +++ b/tests/commands/test_init_command.py @@ -32,7 +32,7 @@ def unsafe_ask(self): "rev": f"v{__version__}", "hooks": [ {"id": "commitizen"}, - {"id": "commitizen-branch", "stages": ["push"]}, + {"id": "commitizen-branch", "stages": ["pre-push"]}, ], } From 0b374aff920cf9129aa24730b27e77c6c115fca3 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Wed, 11 Jun 2025 15:41:05 +0800 Subject: [PATCH 38/51] refactor(Init): remove the variable values_to_add and the update_config function for readability --- commitizen/commands/init.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index a6efe0acc..2ce3981f4 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -160,21 +160,6 @@ def __call__(self) -> None: self.config = JsonConfig(data="{}", path=config_path) elif "yaml" in config_path: self.config = YAMLConfig(data="", path=config_path) - values_to_add: dict[str, Any] = {} - values_to_add["name"] = cz_name - values_to_add["tag_format"] = tag_format - values_to_add["version_scheme"] = version_scheme - - if version_provider == "commitizen": - values_to_add["version"] = version.public - else: - values_to_add["version_provider"] = version_provider - - if update_changelog_on_bump: - values_to_add["update_changelog_on_bump"] = update_changelog_on_bump - - if major_version_zero: - values_to_add["major_version_zero"] = major_version_zero # Collect hook data hook_types = questionary.checkbox( @@ -192,7 +177,18 @@ def __call__(self) -> None: # Create and initialize config self.config.init_empty_config_content() - self._update_config_file(values_to_add) + + self.config.set_key("name", cz_name) + self.config.set_key("tag_format", tag_format) + self.config.set_key("version_scheme", version_scheme) + if version_provider == "commitizen": + self.config.set_key("version", version.public) + else: + self.config.set_key("version_provider", version_provider) + if update_changelog_on_bump: + self.config.set_key("update_changelog_on_bump", update_changelog_on_bump) + if major_version_zero: + self.config.set_key("major_version_zero", major_version_zero) out.write("\nYou can bump the version running:\n") out.info("\tcz bump\n") @@ -387,7 +383,3 @@ def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None: hook_types = ["commit-msg", "pre-push"] self._exec_install_pre_commit_hook(hook_types) out.write("commitizen pre-commit hook is now installed in your '.git'\n") - - def _update_config_file(self, values: dict[str, Any]) -> None: - for key, value in values.items(): - self.config.set_key(key, value) From f8be15a82db4b37aba646571c260c7d0b418d9b4 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Wed, 11 Jun 2025 15:55:35 +0800 Subject: [PATCH 39/51] test(Init): improve test coverage on config initialization --- tests/commands/test_init_command.py | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py index 26f67dfae..c156fb023 100644 --- a/tests/commands/test_init_command.py +++ b/tests/commands/test_init_command.py @@ -420,3 +420,66 @@ def test_init_with_valid_tag_selection(config, mocker: MockFixture, tmpdir): content = toml_file.read() assert 'version = "0.9.0"' in content assert 'version_scheme = "semver"' in content + + +def test_init_configuration_settings(tmpdir, mocker: MockFixture, config): + """Test that all configuration settings are properly initialized.""" + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("commitizen"), + FakeQuestion("semver"), + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + + with open("pyproject.toml", encoding="utf-8") as toml_file: + config_data = toml_file.read() + + # Verify all expected settings are present + assert 'name = "cz_conventional_commits"' in config_data + assert 'tag_format = "$version"' in config_data + assert 'version_scheme = "semver"' in config_data + assert 'version = "0.0.1"' in config_data + assert "update_changelog_on_bump = true" in config_data + assert "major_version_zero = true" in config_data + + +def test_init_configuration_with_version_provider(tmpdir, mocker: MockFixture, config): + """Test configuration initialization with a different version provider.""" + mocker.patch( + "questionary.select", + side_effect=[ + FakeQuestion("pyproject.toml"), + FakeQuestion("cz_conventional_commits"), + FakeQuestion("pep621"), # Different version provider + FakeQuestion("semver"), + ], + ) + mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) + mocker.patch("questionary.text", return_value=FakeQuestion("$version")) + mocker.patch("questionary.checkbox", return_value=FakeQuestion(None)) + + with tmpdir.as_cwd(): + commands.Init(config)() + + with open("pyproject.toml", encoding="utf-8") as toml_file: + config_data = toml_file.read() + + # Verify version provider is set instead of version + assert 'name = "cz_conventional_commits"' in config_data + assert 'tag_format = "$version"' in config_data + assert 'version_scheme = "semver"' in config_data + assert 'version_provider = "pep621"' in config_data + assert "update_changelog_on_bump = true" in config_data + assert "major_version_zero = true" in config_data + assert ( + "version = " not in config_data + ) # Version should not be set when using version_provider From f30ae2e35fae36bc5fb347c3cf3bf771a5031817 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Thu, 12 Jun 2025 16:59:11 +0800 Subject: [PATCH 40/51] refactor(changelog): shorten generate_tree_from_commits --- commitizen/changelog.py | 57 +++++++++++++++++------------------------ commitizen/git.py | 3 +++ 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/commitizen/changelog.py b/commitizen/changelog.py index 93b533996..1a1938c7c 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -32,6 +32,7 @@ from collections.abc import Generator, Iterable, Mapping, MutableMapping, Sequence from dataclasses import dataclass from datetime import date +from itertools import chain from typing import TYPE_CHECKING, Any from jinja2 import ( @@ -88,33 +89,32 @@ def generate_tree_from_commits( pat = re.compile(changelog_pattern) map_pat = re.compile(commit_parser, re.MULTILINE) body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL) - current_tag: GitTag | None = None rules = rules or TagRules() # Check if the latest commit is not tagged - if commits: - latest_commit = commits[0] - current_tag = get_commit_tag(latest_commit, tags) - - current_tag_name: str = unreleased_version or "Unreleased" - current_tag_date: str = "" - if unreleased_version is not None: - current_tag_date = date.today().isoformat() - if current_tag is not None and current_tag.name: - current_tag_name = current_tag.name - current_tag_date = current_tag.date + current_tag = get_commit_tag(commits[0], tags) if commits else None + current_tag_name = unreleased_version or "Unreleased" + current_tag_date = ( + date.today().isoformat() if unreleased_version is not None else "" + ) + + used_tags: set[GitTag] = set() + if current_tag: + used_tags.add(current_tag) + if current_tag.name: + current_tag_name = current_tag.name + current_tag_date = current_tag.date + + commit_tag: GitTag | None = None changes: dict = defaultdict(list) - used_tags: list = [current_tag] for commit in commits: - commit_tag = get_commit_tag(commit, tags) - if ( - commit_tag + (commit_tag := get_commit_tag(commit, tags)) and commit_tag not in used_tags and rules.include_in_changelog(commit_tag) ): - used_tags.append(commit_tag) + used_tags.add(commit_tag) release = { "version": current_tag_name, "date": current_tag_date, @@ -127,24 +127,15 @@ def generate_tree_from_commits( current_tag_date = commit_tag.date changes = defaultdict(list) - matches = pat.match(commit.message) - if not matches: + if not pat.match(commit.message): continue - # Process subject from commit message - if message := map_pat.match(commit.message): - process_commit_message( - changelog_message_builder_hook, - message, - commit, - changes, - change_type_map, - ) - - # Process body from commit message - body_parts = commit.body.split("\n\n") - for body_part in body_parts: - if message := body_map_pat.match(body_part): + # Process subject and body from commit message + for message in chain( + [map_pat.match(commit.message)], + (body_map_pat.match(block) for block in commit.body.split("\n\n")), + ): + if message: process_commit_message( changelog_message_builder_hook, message, diff --git a/commitizen/git.py b/commitizen/git.py index 4883b34e7..c2bab176e 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -49,6 +49,9 @@ class GitObject: def __eq__(self, other: object) -> bool: return hasattr(other, "rev") and self.rev == other.rev + def __hash__(self) -> int: + return hash(self.rev) + class GitCommit(GitObject): def __init__( From 7cd62fba9cbd2c147069426bf0490cfe83d62181 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 3 Jun 2025 20:13:03 +0800 Subject: [PATCH 41/51] feat(check): add check against default branch --- commitizen/cli.py | 7 ++++ commitizen/commands/check.py | 34 ++++++++++++------- commitizen/git.py | 7 ++++ docs/commands/check.md | 24 ++++++++----- ...shows_description_when_use_help_option.txt | 5 ++- tests/test_git.py | 19 +++++++++++ 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/commitizen/cli.py b/commitizen/cli.py index 3862b8843..ed4305ea1 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -471,6 +471,13 @@ def __call__( "help": "a range of git rev to check. e.g, master..HEAD", "exclusive_group": "group1", }, + { + "name": ["-d", "--use-default-range"], + "action": "store_true", + "default": False, + "help": "check from the default branch to HEAD. e.g, refs/remotes/origin/master..HEAD", + "exclusive_group": "group1", + }, { "name": ["-m", "--message"], "help": "commit message that needs to be checked", diff --git a/commitizen/commands/check.py b/commitizen/commands/check.py index 69147bcfb..e6ebc928e 100644 --- a/commitizen/commands/check.py +++ b/commitizen/commands/check.py @@ -21,6 +21,7 @@ class CheckArgs(TypedDict, total=False): message_length_limit: int allowed_prefixes: list[str] message: str + use_default_range: bool class Check: @@ -40,6 +41,7 @@ def __init__(self, config: BaseConfig, arguments: CheckArgs, *args: object) -> N self.allow_abort = bool( arguments.get("allow_abort", config.settings["allow_abort"]) ) + self.use_default_range = bool(arguments.get("use_default_range")) self.max_msg_length = arguments.get("message_length_limit", 0) # we need to distinguish between None and [], which is a valid value @@ -50,25 +52,28 @@ def __init__(self, config: BaseConfig, arguments: CheckArgs, *args: object) -> N else config.settings["allowed_prefixes"] ) - self._valid_command_argument() - - self.config: BaseConfig = config - self.encoding = config.settings["encoding"] - self.cz = factory.committer_factory(self.config) - - def _valid_command_argument(self) -> None: num_exclusive_args_provided = sum( arg is not None - for arg in (self.commit_msg_file, self.commit_msg, self.rev_range) + for arg in ( + self.commit_msg_file, + self.commit_msg, + self.rev_range, + ) ) - if num_exclusive_args_provided == 0 and not sys.stdin.isatty(): - self.commit_msg = sys.stdin.read() - elif num_exclusive_args_provided != 1: + + if num_exclusive_args_provided > 1: raise InvalidCommandArgumentError( "Only one of --rev-range, --message, and --commit-msg-file is permitted by check command! " "See 'cz check -h' for more information" ) + if num_exclusive_args_provided == 0 and not sys.stdin.isatty(): + self.commit_msg = sys.stdin.read() + + self.config: BaseConfig = config + self.encoding = config.settings["encoding"] + self.cz = factory.committer_factory(self.config) + def __call__(self) -> None: """Validate if commit messages follows the conventional pattern. @@ -109,7 +114,10 @@ def _get_commits(self) -> list[git.GitCommit]: return [git.GitCommit(rev="", title="", body=self._filter_comments(msg))] # Get commit messages from git log (--rev-range) - return git.get_commits(end=self.rev_range) + return git.get_commits( + git.get_default_branch() if self.use_default_range else None, + self.rev_range, + ) @staticmethod def _filter_comments(msg: str) -> str: @@ -134,7 +142,7 @@ def _filter_comments(msg: str) -> str: The filtered commit message without comments. """ - lines = [] + lines: list[str] = [] for line in msg.split("\n"): if "# ------------------------ >8 ------------------------" in line: break diff --git a/commitizen/git.py b/commitizen/git.py index c2bab176e..c124cd937 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -332,3 +332,10 @@ def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]: if c.return_code != 0: raise GitCommandError(c.err) return c.out.split(f"{delimiter}\n") + + +def get_default_branch() -> str: + c = cmd.run("git symbolic-ref refs/remotes/origin/HEAD") + if c.return_code != 0: + raise GitCommandError(c.err) + return c.out.strip() diff --git a/docs/commands/check.md b/docs/commands/check.md index 33e41e04f..320bffb88 100644 --- a/docs/commands/check.md +++ b/docs/commands/check.md @@ -27,19 +27,21 @@ $ cz check --rev-range REV_RANGE For example, if you'd like to check all commits on a branch, you can use `--rev-range master..HEAD`. Or, if you'd like to check all commits starting from when you first implemented commit message linting, you can use `--rev-range ..HEAD`. +You can also use `--use-default-range` to check all commits from the default branch up to HEAD. This is equivalent to using `--rev-range ..HEAD`. + For more information on how git commit ranges work, you can check the [git documentation](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#_commit_ranges). ### Commit Message There are two ways you can provide your plain message and check it. -#### Method 1: use -m or --message +#### Method 1: use `-m` or `--message` ```bash $ cz check --message MESSAGE ``` -In this option, MESSAGE is the commit message to be checked. +In this option, `MESSAGE` is the commit message to be checked. #### Method 2: use pipe to pipe it to `cz check` @@ -47,7 +49,7 @@ In this option, MESSAGE is the commit message to be checked. $ echo MESSAGE | cz check ``` -In this option, MESSAGE is piped to cz check and will be checked. +In this option, `MESSAGE` is piped to `cz check` and will be checked. ### Commit Message File @@ -55,7 +57,7 @@ In this option, MESSAGE is piped to cz check and will be checked. $ cz check --commit-msg-file COMMIT_MSG_FILE ``` -In this option, COMMIT_MSG_FILE is the path of the temporary file that contains the commit message. +In this option, `COMMIT_MSG_FILE` is the path of the temporary file that contains the commit message. This argument can be useful when cooperating with git hooks. Please check [Automatically check message before commit](../tutorials/auto_check.md) for more information about how to use this argument with git hooks. ### Allow Abort @@ -65,12 +67,18 @@ cz check --message MESSAGE --allow-abort ``` Empty commit messages typically instruct Git to abort a commit, so you can pass `--allow-abort` to -permit them. Since `git commit` accepts an `--allow-empty-message` flag (primarily for wrapper scripts), you may wish to disallow such commits in CI. `--allow-abort` may be used in conjunction with any of the other options. +permit them. Since `git commit` accepts the `--allow-empty-message` flag (primarily for wrapper scripts), you may wish to disallow such commits in CI. `--allow-abort` may be used in conjunction with any of the other options. ### Allowed Prefixes If the commit message starts with some specific prefixes, `cz check` returns `True` without checking the regex. -By default, the following prefixes are allowed: `Merge`, `Revert`, `Pull request`, `fixup!` and `squash!`. +By default, the following prefixes are allowed: + +- `Merge` +- `Revert` +- `Pull request` +- `fixup!` +- `squash!` ```bash cz check --message MESSAGE --allowed-prefixes 'Merge' 'Revert' 'Custom Prefix' @@ -83,5 +91,5 @@ For example, `cz check --message MESSAGE -l 3` would fail the check, since `MESS By default, the limit is set to 0, which means no limit on the length. **Note that the limit applies only to the first line of the message.** -Specifically, for `ConventionalCommitsCz` the length only counts from the type of change to the subject, -while the body and the footer are not counted. + +Specifically, for `ConventionalCommitsCz` the length only counts from the type of change to the subject, while the body and the footer are not counted. diff --git a/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt b/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt index 85f42f6d2..406674855 100644 --- a/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt +++ b/tests/commands/test_check_command/test_check_command_shows_description_when_use_help_option.txt @@ -1,5 +1,5 @@ usage: cz check [-h] [--commit-msg-file COMMIT_MSG_FILE | - --rev-range REV_RANGE | -m MESSAGE] [--allow-abort] + --rev-range REV_RANGE | -d | -m MESSAGE] [--allow-abort] [--allowed-prefixes [ALLOWED_PREFIXES ...]] [-l MESSAGE_LENGTH_LIMIT] @@ -13,6 +13,9 @@ options: MSG_FILE=$1 --rev-range REV_RANGE a range of git rev to check. e.g, master..HEAD + -d, --use-default-range + check from the default branch to HEAD. e.g, + refs/remotes/origin/master..HEAD -m, --message MESSAGE commit message that needs to be checked --allow-abort allow empty commit messages, which typically abort a diff --git a/tests/test_git.py b/tests/test_git.py index e242b3a2a..2a31d9c0b 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -480,3 +480,22 @@ def test_create_commit_cmd_string(mocker, os_name, committer_date, expected_cmd) mocker.patch("os.name", os_name) result = git._create_commit_cmd_string("", committer_date, "temp.txt") assert result == expected_cmd + + +def test_get_default_branch_success(mocker: MockFixture): + mocker.patch( + "commitizen.cmd.run", return_value=FakeCommand(out="refs/remotes/origin/main\n") + ) + assert git.get_default_branch() == "refs/remotes/origin/main" + + +def test_get_default_branch_error(mocker: MockFixture): + mocker.patch( + "commitizen.cmd.run", + return_value=FakeCommand( + err="fatal: ref refs/remotes/origin/HEAD is not a symbolic ref", + return_code=1, + ), + ) + with pytest.raises(exceptions.GitCommandError): + git.get_default_branch() From 3b00235b159c02c5d57b51e3338638509105c9f8 Mon Sep 17 00:00:00 2001 From: ongdisheng Date: Sun, 6 Jul 2025 22:11:09 +0800 Subject: [PATCH 42/51] test(changelog): ensure error on missing changelog template filename --- tests/commands/test_changelog_command.py | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index 0eb29cdb0..1f3dabd76 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -1914,6 +1914,32 @@ def test_export_changelog_template_from_plugin( assert target.read_text() == tpl +def test_export_changelog_template_fails_when_template_has_no_filename( + mocker: MockFixture, + tmp_commitizen_project: Path, +): + project_root = Path(tmp_commitizen_project) + target = project_root / "changelog.jinja" + + # Mock a template object with no filename + class FakeTemplate: + filename = None + + # Patch get_changelog_template to return a template without a filename + mocker.patch( + "commitizen.changelog.get_changelog_template", return_value=FakeTemplate() + ) + + args = ["cz", "changelog", "--export-template", str(target)] + mocker.patch.object(sys, "argv", args) + + with pytest.raises(NotAllowed) as exc_info: + cli.main() + + assert not target.exists() + assert "Template filename is not set" in str(exc_info.value) + + @skip_below_py_3_13 def test_changelog_command_shows_description_when_use_help_option( mocker: MockFixture, capsys, file_regression From 69db5b44bdb8e9db60656789d26f0ce91e2f75aa Mon Sep 17 00:00:00 2001 From: catfish Date: Wed, 20 Aug 2025 00:36:15 +0800 Subject: [PATCH 43/51] fix(dependencies): update tomlkit version to >=0.8.0,<1.0.0 --- poetry.lock | 590 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 289 insertions(+), 303 deletions(-) diff --git a/poetry.lock b/poetry.lock index 67972ae45..bdc1ee0ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -116,104 +116,91 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "documentation"] files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -245,100 +232,100 @@ files = [ [[package]] name = "coverage" -version = "7.10.2" +version = "7.10.5" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, - {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8"}, - {file = "coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87"}, - {file = "coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1"}, - {file = "coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce"}, - {file = "coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2"}, - {file = "coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b"}, - {file = "coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340"}, - {file = "coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388"}, - {file = "coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a"}, - {file = "coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b"}, - {file = "coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe"}, - {file = "coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb"}, - {file = "coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824"}, - {file = "coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3"}, - {file = "coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd"}, - {file = "coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8"}, - {file = "coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3"}, - {file = "coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed"}, - {file = "coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0"}, - {file = "coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf"}, - {file = "coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0"}, - {file = "coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf"}, - {file = "coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e"}, - {file = "coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f"}, - {file = "coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055"}, + {file = "coverage-7.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c6a5c3414bfc7451b879141ce772c546985163cf553f08e0f135f0699a911801"}, + {file = "coverage-7.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bc8e4d99ce82f1710cc3c125adc30fd1487d3cf6c2cd4994d78d68a47b16989a"}, + {file = "coverage-7.10.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:02252dc1216e512a9311f596b3169fad54abcb13827a8d76d5630c798a50a754"}, + {file = "coverage-7.10.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73269df37883e02d460bee0cc16be90509faea1e3bd105d77360b512d5bb9c33"}, + {file = "coverage-7.10.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f8a81b0614642f91c9effd53eec284f965577591f51f547a1cbeb32035b4c2f"}, + {file = "coverage-7.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6a29f8e0adb7f8c2b95fa2d4566a1d6e6722e0a637634c6563cb1ab844427dd9"}, + {file = "coverage-7.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fcf6ab569436b4a647d4e91accba12509ad9f2554bc93d3aee23cc596e7f99c3"}, + {file = "coverage-7.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:90dc3d6fb222b194a5de60af8d190bedeeddcbc7add317e4a3cd333ee6b7c879"}, + {file = "coverage-7.10.5-cp310-cp310-win32.whl", hash = "sha256:414a568cd545f9dc75f0686a0049393de8098414b58ea071e03395505b73d7a8"}, + {file = "coverage-7.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:e551f9d03347196271935fd3c0c165f0e8c049220280c1120de0084d65e9c7ff"}, + {file = "coverage-7.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c177e6ffe2ebc7c410785307758ee21258aa8e8092b44d09a2da767834f075f2"}, + {file = "coverage-7.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:14d6071c51ad0f703d6440827eaa46386169b5fdced42631d5a5ac419616046f"}, + {file = "coverage-7.10.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:61f78c7c3bc272a410c5ae3fde7792b4ffb4acc03d35a7df73ca8978826bb7ab"}, + {file = "coverage-7.10.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f39071caa126f69d63f99b324fb08c7b1da2ec28cbb1fe7b5b1799926492f65c"}, + {file = "coverage-7.10.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343a023193f04d46edc46b2616cdbee68c94dd10208ecd3adc56fcc54ef2baa1"}, + {file = "coverage-7.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:585ffe93ae5894d1ebdee69fc0b0d4b7c75d8007983692fb300ac98eed146f78"}, + {file = "coverage-7.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0ef4e66f006ed181df29b59921bd8fc7ed7cd6a9289295cd8b2824b49b570df"}, + {file = "coverage-7.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb7b0bbf7cc1d0453b843eca7b5fa017874735bef9bfdfa4121373d2cc885ed6"}, + {file = "coverage-7.10.5-cp311-cp311-win32.whl", hash = "sha256:1d043a8a06987cc0c98516e57c4d3fc2c1591364831e9deb59c9e1b4937e8caf"}, + {file = "coverage-7.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:fefafcca09c3ac56372ef64a40f5fe17c5592fab906e0fdffd09543f3012ba50"}, + {file = "coverage-7.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:7e78b767da8b5fc5b2faa69bb001edafcd6f3995b42a331c53ef9572c55ceb82"}, + {file = "coverage-7.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c2d05c7e73c60a4cecc7d9b60dbfd603b4ebc0adafaef371445b47d0f805c8a9"}, + {file = "coverage-7.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32ddaa3b2c509778ed5373b177eb2bf5662405493baeff52278a0b4f9415188b"}, + {file = "coverage-7.10.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd382410039fe062097aa0292ab6335a3f1e7af7bba2ef8d27dcda484918f20c"}, + {file = "coverage-7.10.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7fa22800f3908df31cea6fb230f20ac49e343515d968cc3a42b30d5c3ebf9b5a"}, + {file = "coverage-7.10.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f366a57ac81f5e12797136552f5b7502fa053c861a009b91b80ed51f2ce651c6"}, + {file = "coverage-7.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1dc8f1980a272ad4a6c84cba7981792344dad33bf5869361576b7aef42733a"}, + {file = "coverage-7.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2285c04ee8676f7938b02b4936d9b9b672064daab3187c20f73a55f3d70e6b4a"}, + {file = "coverage-7.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2492e4dd9daab63f5f56286f8a04c51323d237631eb98505d87e4c4ff19ec34"}, + {file = "coverage-7.10.5-cp312-cp312-win32.whl", hash = "sha256:38a9109c4ee8135d5df5505384fc2f20287a47ccbe0b3f04c53c9a1989c2bbaf"}, + {file = "coverage-7.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:6b87f1ad60b30bc3c43c66afa7db6b22a3109902e28c5094957626a0143a001f"}, + {file = "coverage-7.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:672a6c1da5aea6c629819a0e1461e89d244f78d7b60c424ecf4f1f2556c041d8"}, + {file = "coverage-7.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef3b83594d933020f54cf65ea1f4405d1f4e41a009c46df629dd964fcb6e907c"}, + {file = "coverage-7.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b96bfdf7c0ea9faebce088a3ecb2382819da4fbc05c7b80040dbc428df6af44"}, + {file = "coverage-7.10.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63df1fdaffa42d914d5c4d293e838937638bf75c794cf20bee12978fc8c4e3bc"}, + {file = "coverage-7.10.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8002dc6a049aac0e81ecec97abfb08c01ef0c1fbf962d0c98da3950ace89b869"}, + {file = "coverage-7.10.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63d4bb2966d6f5f705a6b0c6784c8969c468dbc4bcf9d9ded8bff1c7e092451f"}, + {file = "coverage-7.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1f672efc0731a6846b157389b6e6d5d5e9e59d1d1a23a5c66a99fd58339914d5"}, + {file = "coverage-7.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3f39cef43d08049e8afc1fde4a5da8510fc6be843f8dea350ee46e2a26b2f54c"}, + {file = "coverage-7.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2968647e3ed5a6c019a419264386b013979ff1fb67dd11f5c9886c43d6a31fc2"}, + {file = "coverage-7.10.5-cp313-cp313-win32.whl", hash = "sha256:0d511dda38595b2b6934c2b730a1fd57a3635c6aa2a04cb74714cdfdd53846f4"}, + {file = "coverage-7.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a86281794a393513cf117177fd39c796b3f8e3759bb2764259a2abba5cce54b"}, + {file = "coverage-7.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:cebd8e906eb98bb09c10d1feed16096700b1198d482267f8bf0474e63a7b8d84"}, + {file = "coverage-7.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0520dff502da5e09d0d20781df74d8189ab334a1e40d5bafe2efaa4158e2d9e7"}, + {file = "coverage-7.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d9cd64aca68f503ed3f1f18c7c9174cbb797baba02ca8ab5112f9d1c0328cd4b"}, + {file = "coverage-7.10.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0913dd1613a33b13c4f84aa6e3f4198c1a21ee28ccb4f674985c1f22109f0aae"}, + {file = "coverage-7.10.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b7181c0feeb06ed8a02da02792f42f829a7b29990fef52eff257fef0885d760"}, + {file = "coverage-7.10.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d42b7396b605f774d4372dd9c49bed71cbabce4ae1ccd074d155709dd8f235"}, + {file = "coverage-7.10.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b4fdc777e05c4940b297bf47bf7eedd56a39a61dc23ba798e4b830d585486ca5"}, + {file = "coverage-7.10.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:42144e8e346de44a6f1dbd0a56575dd8ab8dfa7e9007da02ea5b1c30ab33a7db"}, + {file = "coverage-7.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:66c644cbd7aed8fe266d5917e2c9f65458a51cfe5eeff9c05f15b335f697066e"}, + {file = "coverage-7.10.5-cp313-cp313t-win32.whl", hash = "sha256:2d1b73023854068c44b0c554578a4e1ef1b050ed07cf8b431549e624a29a66ee"}, + {file = "coverage-7.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:54a1532c8a642d8cc0bd5a9a51f5a9dcc440294fd06e9dda55e743c5ec1a8f14"}, + {file = "coverage-7.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:74d5b63fe3f5f5d372253a4ef92492c11a4305f3550631beaa432fc9df16fcff"}, + {file = "coverage-7.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:68c5e0bc5f44f68053369fa0d94459c84548a77660a5f2561c5e5f1e3bed7031"}, + {file = "coverage-7.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cf33134ffae93865e32e1e37df043bef15a5e857d8caebc0099d225c579b0fa3"}, + {file = "coverage-7.10.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad8fa9d5193bafcf668231294241302b5e683a0518bf1e33a9a0dfb142ec3031"}, + {file = "coverage-7.10.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:146fa1531973d38ab4b689bc764592fe6c2f913e7e80a39e7eeafd11f0ef6db2"}, + {file = "coverage-7.10.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6013a37b8a4854c478d3219ee8bc2392dea51602dd0803a12d6f6182a0061762"}, + {file = "coverage-7.10.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eb90fe20db9c3d930fa2ad7a308207ab5b86bf6a76f54ab6a40be4012d88fcae"}, + {file = "coverage-7.10.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:384b34482272e960c438703cafe63316dfbea124ac62006a455c8410bf2a2262"}, + {file = "coverage-7.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:467dc74bd0a1a7de2bedf8deaf6811f43602cb532bd34d81ffd6038d6d8abe99"}, + {file = "coverage-7.10.5-cp314-cp314-win32.whl", hash = "sha256:556d23d4e6393ca898b2e63a5bca91e9ac2d5fb13299ec286cd69a09a7187fde"}, + {file = "coverage-7.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:f4446a9547681533c8fa3e3c6cf62121eeee616e6a92bd9201c6edd91beffe13"}, + {file = "coverage-7.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:5e78bd9cf65da4c303bf663de0d73bf69f81e878bf72a94e9af67137c69b9fe9"}, + {file = "coverage-7.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5661bf987d91ec756a47c7e5df4fbcb949f39e32f9334ccd3f43233bbb65e508"}, + {file = "coverage-7.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a46473129244db42a720439a26984f8c6f834762fc4573616c1f37f13994b357"}, + {file = "coverage-7.10.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1f64b8d3415d60f24b058b58d859e9512624bdfa57a2d1f8aff93c1ec45c429b"}, + {file = "coverage-7.10.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:44d43de99a9d90b20e0163f9770542357f58860a26e24dc1d924643bd6aa7cb4"}, + {file = "coverage-7.10.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a931a87e5ddb6b6404e65443b742cb1c14959622777f2a4efd81fba84f5d91ba"}, + {file = "coverage-7.10.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9559b906a100029274448f4c8b8b0a127daa4dade5661dfd821b8c188058842"}, + {file = "coverage-7.10.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b08801e25e3b4526ef9ced1aa29344131a8f5213c60c03c18fe4c6170ffa2874"}, + {file = "coverage-7.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed9749bb8eda35f8b636fb7632f1c62f735a236a5d4edadd8bbcc5ea0542e732"}, + {file = "coverage-7.10.5-cp314-cp314t-win32.whl", hash = "sha256:609b60d123fc2cc63ccee6d17e4676699075db72d14ac3c107cc4976d516f2df"}, + {file = "coverage-7.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0666cf3d2c1626b5a3463fd5b05f5e21f99e6aec40a3192eee4d07a15970b07f"}, + {file = "coverage-7.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:bc85eb2d35e760120540afddd3044a5bf69118a91a296a8b3940dfc4fdcfe1e2"}, + {file = "coverage-7.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:62835c1b00c4a4ace24c1a88561a5a59b612fbb83a525d1c70ff5720c97c0610"}, + {file = "coverage-7.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5255b3bbcc1d32a4069d6403820ac8e6dbcc1d68cb28a60a1ebf17e47028e898"}, + {file = "coverage-7.10.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3876385722e335d6e991c430302c24251ef9c2a9701b2b390f5473199b1b8ebf"}, + {file = "coverage-7.10.5-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8048ce4b149c93447a55d279078c8ae98b08a6951a3c4d2d7e87f4efc7bfe100"}, + {file = "coverage-7.10.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4028e7558e268dd8bcf4d9484aad393cafa654c24b4885f6f9474bf53183a82a"}, + {file = "coverage-7.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03f47dc870eec0367fcdd603ca6a01517d2504e83dc18dbfafae37faec66129a"}, + {file = "coverage-7.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2d488d7d42b6ded7ea0704884f89dcabd2619505457de8fc9a6011c62106f6e5"}, + {file = "coverage-7.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3dcf2ead47fa8be14224ee817dfc1df98043af568fe120a22f81c0eb3c34ad2"}, + {file = "coverage-7.10.5-cp39-cp39-win32.whl", hash = "sha256:02650a11324b80057b8c9c29487020073d5e98a498f1857f37e3f9b6ea1b2426"}, + {file = "coverage-7.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:b45264dd450a10f9e03237b41a9a24e85cbb1e278e5a32adb1a303f58f0017f3"}, + {file = "coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a"}, + {file = "coverage-7.10.5.tar.gz", hash = "sha256:f2e57716a78bc3ae80b2207be0709a3b2b63b9f2dcf9740ee6ac03588a2015b6"}, ] [package.dependencies] @@ -452,31 +439,26 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "filelock" -version = "3.18.0" +version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev", "linters"] files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] - [[package]] name = "freezegun" -version = "1.5.4" +version = "1.5.5" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.8" groups = ["test"] files = [ - {file = "freezegun-1.5.4-py3-none-any.whl", hash = "sha256:8bdd75c9d790f53d5a173d273064ccd7900984b36635be552befeedb0cd47b20"}, - {file = "freezegun-1.5.4.tar.gz", hash = "sha256:798b9372fdd4d907f33e8b6a58bc64e682d9ffa8d494ce60f780197ee81faed1"}, + {file = "freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2"}, + {file = "freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a"}, ] [package.dependencies] @@ -502,14 +484,14 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "identify" -version = "2.6.12" +version = "2.6.13" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, - {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, ] [package.extras] @@ -849,19 +831,20 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.6.16" +version = "9.6.18" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["documentation"] files = [ - {file = "mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c"}, - {file = "mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19"}, + {file = "mkdocs_material-9.6.18-py3-none-any.whl", hash = "sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701"}, + {file = "mkdocs_material-9.6.18.tar.gz", hash = "sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05"}, ] [package.dependencies] babel = ">=2.10,<3.0" backrefs = ">=5.7.post1,<6.0" +click = "<8.2.2" colorama = ">=0.4,<1.0" jinja2 = ">=3.1,<4.0" markdown = ">=3.2,<4.0" @@ -1004,14 +987,14 @@ lint = ["black"] [[package]] name = "parso" -version = "0.8.4" +version = "0.8.5" description = "A Python Parser" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, + {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, + {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, ] [package.extras] @@ -1113,14 +1096,14 @@ poetry-plugin = ["poetry (>=1.2.0,<3.0.0) ; python_version < \"4.0\""] [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.3.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, - {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, ] [package.dependencies] @@ -1326,14 +1309,14 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-regressions" -version = "2.8.1" +version = "2.8.2" description = "Easy to use fixtures to write regression tests." optional = false python-versions = ">=3.9" groups = ["test"] files = [ - {file = "pytest_regressions-2.8.1-py3-none-any.whl", hash = "sha256:bcae249df5214225bbe4bd77c146d8f331f19b0b26805486533b1e49680950a5"}, - {file = "pytest_regressions-2.8.1.tar.gz", hash = "sha256:20080d44cf46b40407956af5e44a7f932cbc3159018de81fb51e62b97e96a5fb"}, + {file = "pytest_regressions-2.8.2-py3-none-any.whl", hash = "sha256:a0804c1ce66d8e4d9a3c7c68f42a3d436182edca8e86565c232caeaf9e080fc2"}, + {file = "pytest_regressions-2.8.2.tar.gz", hash = "sha256:1d8f4767be58b9994bfa7d60271099469ad32b8ca9f9d9ceca1c1d6827156b19"}, ] [package.dependencies] @@ -1478,14 +1461,14 @@ prompt_toolkit = ">=2.0,<4.0" [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["documentation"] files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] @@ -1715,26 +1698,26 @@ files = [ [[package]] name = "types-python-dateutil" -version = "2.9.0.20250708" +version = "2.9.0.20250822" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f"}, - {file = "types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab"}, + {file = "types_python_dateutil-2.9.0.20250822-py3-none-any.whl", hash = "sha256:849d52b737e10a6dc6621d2bd7940ec7c65fcb69e6aa2882acf4e56b2b508ddc"}, + {file = "types_python_dateutil-2.9.0.20250822.tar.gz", hash = "sha256:84c92c34bd8e68b117bff742bc00b692a1e8531262d4507b33afcc9f7716cd53"}, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20250516" +version = "6.0.12.20250822" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.9" groups = ["linters"] files = [ - {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, - {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, + {file = "types_pyyaml-6.0.12.20250822-py3-none-any.whl", hash = "sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098"}, + {file = "types_pyyaml-6.0.12.20250822.tar.gz", hash = "sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413"}, ] [[package]] @@ -1782,20 +1765,21 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.33.1" +version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev", "linters"] files = [ - {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, - {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -1858,91 +1842,93 @@ files = [ [[package]] name = "wrapt" -version = "1.17.2" +version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["test"] files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, + {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, + {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, + {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, + {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, + {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, + {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, + {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, + {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, + {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, + {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, + {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, + {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, + {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, + {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, + {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, + {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, + {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, + {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, + {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, + {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, + {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, + {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, + {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, ] [[package]] @@ -1969,4 +1955,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "ac1b07dc1d59d75607768904af6528a4d2f2af1695a7a1639973d8b35ed48008" +content-hash = "604c29d8def84b7a18a1d221cc6e0ce04fbc9dc2fb6ea3109c28449bbb5a5f54" diff --git a/pyproject.toml b/pyproject.toml index d287a2258..a2e1427f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "colorama (>=0.4.1,<1.0)", "termcolor (>=1.1.0,<4.0.0)", "packaging>=19", - "tomlkit (>=0.5.3,<1.0.0)", + "tomlkit (>=0.8.0,<1.0.0)", "jinja2>=2.10.3", "pyyaml>=3.08", "argcomplete >=1.12.1,<3.7", From 046e7498ba621dcbd1c7bcc973eb66c68949111f Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 18 May 2025 01:25:19 +0800 Subject: [PATCH 44/51] feat(bump_rule): add BumpRule, VersionIncrement, Prerelease Enum Closes #129 --- commitizen/bump.py | 53 +- commitizen/bump_rule.py | 253 +++++++ commitizen/commands/bump.py | 32 +- commitizen/cz/base.py | 45 ++ .../conventional_commits.py | 3 + commitizen/defaults.py | 36 +- commitizen/version_schemes.py | 90 ++- tests/commands/test_bump_command.py | 5 +- tests/test_bump_find_increment.py | 124 ---- tests/test_bump_rule.py | 649 ++++++++++++++++++ tests/test_version_scheme_pep440.py | 250 +++---- tests/test_version_scheme_semver.py | 159 ++--- tests/test_version_scheme_semver2.py | 123 ++-- tests/test_version_schemes.py | 30 +- 14 files changed, 1342 insertions(+), 510 deletions(-) create mode 100644 commitizen/bump_rule.py delete mode 100644 tests/test_bump_find_increment.py create mode 100644 tests/test_bump_rule.py diff --git a/commitizen/bump.py b/commitizen/bump.py index 6d6b6dc06..30e83f8c3 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -2,61 +2,14 @@ import os import re -from collections import OrderedDict from collections.abc import Iterable from glob import iglob -from logging import getLogger from string import Template -from typing import cast -from commitizen.defaults import BUMP_MESSAGE, ENCODING, MAJOR, MINOR, PATCH +from commitizen.defaults import BUMP_MESSAGE, ENCODING from commitizen.exceptions import CurrentVersionNotFoundError -from commitizen.git import GitCommit, smart_open -from commitizen.version_schemes import Increment, Version - -VERSION_TYPES = [None, PATCH, MINOR, MAJOR] - -logger = getLogger("commitizen") - - -def find_increment( - commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict -) -> Increment | None: - if isinstance(increments_map, dict): - increments_map = OrderedDict(increments_map) - - # Most important cases are major and minor. - # Everything else will be considered patch. - select_pattern = re.compile(regex) - increment: str | None = None - - for commit in commits: - for message in commit.message.split("\n"): - result = select_pattern.search(message) - - if result: - found_keyword = result.group(1) - new_increment = None - for match_pattern in increments_map.keys(): - if re.match(match_pattern, found_keyword): - new_increment = increments_map[match_pattern] - break - - if new_increment is None: - logger.debug( - f"no increment needed for '{found_keyword}' in '{message}'" - ) - - if VERSION_TYPES.index(increment) < VERSION_TYPES.index(new_increment): - logger.debug( - f"increment detected is '{new_increment}' due to '{found_keyword}' in '{message}'" - ) - increment = new_increment - - if increment == MAJOR: - break - - return cast(Increment, increment) +from commitizen.git import smart_open +from commitizen.version_schemes import Version def update_version_in_files( diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py new file mode 100644 index 000000000..bfdcfcd99 --- /dev/null +++ b/commitizen/bump_rule.py @@ -0,0 +1,253 @@ +from __future__ import annotations + +import re +from collections.abc import Iterable, Mapping +from enum import IntEnum, auto +from functools import cached_property +from typing import Callable, Protocol + +from commitizen.exceptions import NoPatternMapError + + +class VersionIncrement(IntEnum): + """An enumeration representing semantic versioning increments. + + This class defines the three types of version increments according to semantic versioning: + - PATCH: For backwards-compatible bug fixes + - MINOR: For backwards-compatible functionality additions + - MAJOR: For incompatible API changes + """ + + PATCH = auto() + MINOR = auto() + MAJOR = auto() + + def __str__(self) -> str: + return self.name + + @classmethod + def safe_cast(cls, value: object) -> VersionIncrement | None: + if not isinstance(value, str): + return None + try: + return cls[value] + except KeyError: + return None + + @classmethod + def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, VersionIncrement]: + return { + k: v + for k, v in ((k, VersionIncrement.safe_cast(v)) for k, v in d.items()) + if v is not None + } + + @staticmethod + def get_highest_by_messages( + commit_messages: Iterable[str], + get_increment: Callable[[str], VersionIncrement | None], + ) -> VersionIncrement | None: + """Find the highest version increment from a list of messages. + + This function processes a list of messages and determines the highest version + increment needed based on the commit messages. It splits multi-line commit messages + and evaluates each line using the provided get_increment callable. + + Args: + commit_messages: A list of messages to analyze. + get_increment: A callable that takes a commit message string and returns an + VersionIncrement value (MAJOR, MINOR, PATCH) or None if no increment is needed. + + Returns: + The highest version increment needed (MAJOR, MINOR, PATCH) or None if no + increment is needed. The order of precedence is MAJOR > MINOR > PATCH. + + Example: + >>> commit_messages = ["feat: new feature", "fix: bug fix"] + >>> rule = ConventionalCommitBumpRule() + >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.get_increment(x, False)) + 'MINOR' + """ + return VersionIncrement.get_highest( + get_increment(line) + for message in commit_messages + for line in message.split("\n") + ) + + @staticmethod + def get_highest( + increments: Iterable[VersionIncrement | None], + ) -> VersionIncrement | None: + return max(filter(None, increments), default=None) + + +class BumpRule(Protocol): + """A protocol defining the interface for version bump rules. + + This protocol specifies the contract that all version bump rule implementations must follow. + It defines how commit messages should be analyzed to determine the appropriate semantic + version increment. + + The protocol is used to ensure consistent behavior across different bump rule implementations, + such as conventional commits or custom rules. + """ + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> VersionIncrement | None: + """Determine the version increment based on a commit message. + + This method analyzes a commit message to determine what kind of version increment + is needed according to the Conventional Commits specification. It handles special + cases for breaking changes and respects the major_version_zero flag. + + Args: + commit_message: The commit message to analyze. Should follow conventional commit format. + major_version_zero: If True, breaking changes will result in a MINOR version bump + instead of MAJOR. This is useful for projects in 0.x.x versions. + + Returns: + VersionIncrement | None: The type of version increment needed: + - MAJOR: For breaking changes when major_version_zero is False + - MINOR: For breaking changes when major_version_zero is True, or for new features + - PATCH: For bug fixes, performance improvements, or refactors + - None: For commits that don't require a version bump (docs, style, etc.) + """ + + +class ConventionalCommitBumpRule(BumpRule): + _BREAKING_CHANGE_TYPES = set(["BREAKING CHANGE", "BREAKING-CHANGE"]) + _MINOR_CHANGE_TYPES = set(["feat"]) + _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> VersionIncrement | None: + if not (m := self._head_pattern.match(commit_message)): + return None + + change_type = m.group("change_type") + if m.group("bang") or change_type in self._BREAKING_CHANGE_TYPES: + return ( + VersionIncrement.MINOR if major_version_zero else VersionIncrement.MAJOR + ) + + if change_type in self._MINOR_CHANGE_TYPES: + return VersionIncrement.MINOR + + if change_type in self._PATCH_CHANGE_TYPES: + return VersionIncrement.PATCH + + return None + + @cached_property + def _head_pattern(self) -> re.Pattern: + change_types = [ + *self._BREAKING_CHANGE_TYPES, + *self._PATCH_CHANGE_TYPES, + *self._MINOR_CHANGE_TYPES, + "docs", + "style", + "test", + "build", + "ci", + ] + re_change_type = r"(?P" + "|".join(change_types) + r")" + re_scope = r"(?P\(.+\))?" + re_bang = r"(?P!)?" + return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") + + +class CustomBumpRule(BumpRule): + def __init__( + self, + bump_pattern: str, + bump_map: Mapping[str, VersionIncrement], + bump_map_major_version_zero: Mapping[str, VersionIncrement], + ) -> None: + """Initialize a custom bump rule for version incrementing. + + This constructor creates a rule that determines how version numbers should be + incremented based on commit messages. It validates and compiles the provided + pattern and maps for use in version bumping. + + The fallback logic is used for backward compatibility. + + Args: + bump_pattern: A regex pattern string used to match commit messages. + Example: r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + Or with fallback regex: r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" # First group is type + bump_map: A mapping of commit types to their corresponding version increments. + Example: { + "major": VersionIncrement.MAJOR, + "bang": VersionIncrement.MAJOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", VersionIncrement.MAJOR), + (r"^BREAKING[\-\ ]CHANGE", VersionIncrement.MAJOR), + (r"^feat", VersionIncrement.MINOR), + (r"^fix", VersionIncrement.PATCH), + (r"^refactor", VersionIncrement.PATCH), + (r"^perf", VersionIncrement.PATCH), + } + bump_map_major_version_zero: A mapping of commit types to version increments + specifically for when the major version is 0. This allows for different + versioning behavior during initial development. + The format is the same as bump_map. + Example: { + "major": VersionIncrement.MINOR, # MAJOR becomes MINOR in version zero + "bang": VersionIncrement.MINOR, # Breaking changes become MINOR in version zero + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", VersionIncrement.MINOR), + (r"^BREAKING[\-\ ]CHANGE", VersionIncrement.MINOR), + (r"^feat", VersionIncrement.MINOR), + (r"^fix", VersionIncrement.PATCH), + (r"^refactor", VersionIncrement.PATCH), + (r"^perf", VersionIncrement.PATCH), + } + + Raises: + NoPatternMapError: If any of the required parameters are empty or None + """ + if not bump_map or not bump_pattern or not bump_map_major_version_zero: + raise NoPatternMapError( + f"Invalid bump rule: {bump_pattern=} and {bump_map=} and {bump_map_major_version_zero=}" + ) + + self.bump_pattern = re.compile(bump_pattern) + self.bump_map = bump_map + self.bump_map_major_version_zero = bump_map_major_version_zero + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> VersionIncrement | None: + if not (m := self.bump_pattern.search(commit_message)): + return None + + effective_bump_map = ( + self.bump_map_major_version_zero if major_version_zero else self.bump_map + ) + + try: + if ret := VersionIncrement.get_highest( + ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ), + ): + return ret + except IndexError: + pass + + # Fallback to legacy bump rule, for backward compatibility + found_keyword = m.group(1) + for match_pattern, increment in effective_bump_map.items(): + if re.match(match_pattern, found_keyword): + return increment + return None diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 07338afb8..934f5c063 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,6 +7,9 @@ import questionary from commitizen import bump, factory, git, hooks, out +from commitizen.bump_rule import ( + VersionIncrement, +) from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -20,14 +23,12 @@ InvalidManualVersion, NoCommitsFoundError, NoneIncrementExit, - NoPatternMapError, NotAGitProjectError, NotAllowed, ) from commitizen.providers import get_provider from commitizen.tags import TagRules from commitizen.version_schemes import ( - Increment, InvalidVersion, Prerelease, get_version_scheme, @@ -50,7 +51,7 @@ class BumpArgs(Settings, total=False): get_next: bool git_output_to_stderr: bool increment_mode: str - increment: Increment | None + increment: VersionIncrement | None local_version: bool manual_version: str | None no_verify: bool @@ -143,29 +144,22 @@ def _is_initial_tag( ) return bool(questionary.confirm("Is this the first tag created?").ask()) - def _find_increment(self, commits: list[git.GitCommit]) -> Increment | None: + def _find_increment(self, commits: list[git.GitCommit]) -> VersionIncrement | None: # Update the bump map to ensure major version doesn't increment. - # self.cz.bump_map = defaults.bump_map_major_version_zero - bump_map = ( - self.cz.bump_map_major_version_zero - if self.bump_settings["major_version_zero"] - else self.cz.bump_map - ) - bump_pattern = self.cz.bump_pattern + is_major_version_zero = self.bump_settings["major_version_zero"] - if not bump_map or not bump_pattern: - raise NoPatternMapError( - f"'{self.config.settings['name']}' rule does not support bump" - ) - return bump.find_increment(commits, regex=bump_pattern, increments_map=bump_map) + return VersionIncrement.get_highest_by_messages( + (commit.message for commit in commits), + lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero), + ) def __call__(self) -> None: """Steps executed to bump.""" provider = get_provider(self.config) current_version = self.scheme(provider.get_version()) - increment = self.arguments["increment"] - prerelease = self.arguments["prerelease"] + increment = VersionIncrement.safe_cast(self.arguments["increment"]) + prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) devrelease = self.arguments["devrelease"] is_local_version = self.arguments["local_version"] manual_version = self.arguments["manual_version"] @@ -258,7 +252,7 @@ def __call__(self) -> None: # we create an empty PATCH increment for empty tag if increment is None and allow_no_commit: - increment = "PATCH" + increment = VersionIncrement.PATCH new_version = current_version.bump( increment, diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index cdc147669..597c65962 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -2,13 +2,16 @@ from abc import ABCMeta, abstractmethod from collections.abc import Iterable, Mapping +from functools import cached_property from typing import Any, Callable, Protocol from jinja2 import BaseLoader, PackageLoader from prompt_toolkit.styles import Style, merge_styles from commitizen import git +from commitizen.bump_rule import BumpRule, CustomBumpRule, VersionIncrement from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import NoPatternMapError from commitizen.question import CzQuestion @@ -25,9 +28,13 @@ def __call__( class BaseCommitizen(metaclass=ABCMeta): + _bump_rule: BumpRule | None = None + + # TODO: decide if these should be removed bump_pattern: str | None = None bump_map: dict[str, str] | None = None bump_map_major_version_zero: dict[str, str] | None = None + default_style_config: list[tuple[str, str]] = [ ("qmark", "fg:#ff9d00 bold"), ("question", "bold"), @@ -84,6 +91,44 @@ def style(self) -> Style: ] ) # type: ignore[return-value] + @cached_property + def bump_rule(self) -> BumpRule: + """Get the bump rule for version incrementing. + + This property returns a BumpRule instance that determines how version numbers + should be incremented based on commit messages. It first checks if a custom + bump rule was set via `_bump_rule`. If not, it falls back to creating a + CustomBumpRule using the class's bump pattern and maps. + + The CustomBumpRule requires three components to be defined: + - bump_pattern: A regex pattern to match commit messages + - bump_map: A mapping of commit types to version increments + - bump_map_major_version_zero: A mapping for version increments when major version is 0 + + Returns: + BumpRule: A rule instance that determines version increments + + Raises: + NoPatternMapError: If the required bump pattern or maps are not defined + """ + if self._bump_rule: + return self._bump_rule + + # Fallback to custom bump rule if no bump rule is provided + if ( + not self.bump_pattern + or not self.bump_map + or not self.bump_map_major_version_zero + ): + raise NoPatternMapError( + f"'{self.config.settings['name']}' rule does not support bump: {self.bump_pattern=}, {self.bump_map=}, {self.bump_map_major_version_zero=}" + ) + return CustomBumpRule( + self.bump_pattern, + VersionIncrement.safe_cast_dict(self.bump_map), + VersionIncrement.safe_cast_dict(self.bump_map_major_version_zero), + ) + def example(self) -> str: """Example of the commit message.""" raise NotImplementedError("Not Implemented yet") diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index 689342347..fd8e3062e 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -2,6 +2,7 @@ from typing import TypedDict from commitizen import defaults +from commitizen.bump_rule import ConventionalCommitBumpRule from commitizen.cz.base import BaseCommitizen from commitizen.cz.utils import multiple_line_breaker, required_validator from commitizen.question import CzQuestion @@ -27,6 +28,8 @@ class ConventionalCommitsAnswers(TypedDict): class ConventionalCommitsCz(BaseCommitizen): + _bump_rule = ConventionalCommitBumpRule() + bump_pattern = defaults.BUMP_PATTERN bump_map = defaults.BUMP_MAP bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 94d4d97b2..c2120c6c0 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -6,6 +6,7 @@ from collections.abc import Iterable, MutableMapping, Sequence from typing import Any, TypedDict +from commitizen.bump_rule import VersionIncrement from commitizen.question import CzQuestion @@ -110,31 +111,27 @@ class Settings(TypedDict, total=False): "extras": {}, } -MAJOR = "MAJOR" -MINOR = "MINOR" -PATCH = "PATCH" - CHANGELOG_FORMAT = "markdown" BUMP_PATTERN = r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" -BUMP_MAP = OrderedDict( +BUMP_MAP = dict( ( - (r"^.+!$", MAJOR), - (r"^BREAKING[\-\ ]CHANGE", MAJOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(VersionIncrement.MAJOR)), + (r"^BREAKING[\-\ ]CHANGE", str(VersionIncrement.MAJOR)), + (r"^feat", str(VersionIncrement.MINOR)), + (r"^fix", str(VersionIncrement.PATCH)), + (r"^refactor", str(VersionIncrement.PATCH)), + (r"^perf", str(VersionIncrement.PATCH)), ) ) -BUMP_MAP_MAJOR_VERSION_ZERO = OrderedDict( +BUMP_MAP_MAJOR_VERSION_ZERO = dict( ( - (r"^.+!$", MINOR), - (r"^BREAKING[\-\ ]CHANGE", MINOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(VersionIncrement.MINOR)), + (r"^BREAKING[\-\ ]CHANGE", str(VersionIncrement.MINOR)), + (r"^feat", str(VersionIncrement.MINOR)), + (r"^fix", str(VersionIncrement.PATCH)), + (r"^refactor", str(VersionIncrement.PATCH)), + (r"^perf", str(VersionIncrement.PATCH)), ) ) CHANGE_TYPE_ORDER = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] @@ -178,6 +175,9 @@ def __getattr__(name: str) -> Any: "encoding": (ENCODING, "ENCODING"), "name": (DEFAULT_SETTINGS["name"], "DEFAULT_SETTINGS['name']"), "Questions": (Questions, "Iterable[CzQuestion]"), + "MAJOR": str(VersionIncrement.MAJOR), + "MINOR": str(VersionIncrement.MINOR), + "PATCH": str(VersionIncrement.PATCH), } if name in deprecated_vars: value, replacement = deprecated_vars[name] diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index e9f99c551..9e982f265 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -3,17 +3,19 @@ import re import sys import warnings +from enum import Enum from itertools import zip_longest from typing import ( TYPE_CHECKING, Any, ClassVar, - Literal, Protocol, cast, runtime_checkable, ) +from commitizen.bump_rule import VersionIncrement + if sys.version_info >= (3, 10): from importlib import metadata else: @@ -22,7 +24,7 @@ from packaging.version import InvalidVersion # noqa: F401 (expose the common exception) from packaging.version import Version as _BaseVersion -from commitizen.defaults import MAJOR, MINOR, PATCH, Settings +from commitizen.defaults import Settings from commitizen.exceptions import VersionSchemeUnknown if TYPE_CHECKING: @@ -39,8 +41,21 @@ from typing import Self -Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] -Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] +class Prerelease(Enum): + ALPHA = "alpha" + BETA = "beta" + RC = "rc" + + @classmethod + def safe_cast(cls, value: object) -> Prerelease | None: + if not isinstance(value, str): + return None + try: + return cls[value.upper()] + except KeyError: + return None + + _DEFAULT_VERSION_PARSER = re.compile( r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)" ) @@ -128,7 +143,7 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: Increment | None, + increment: VersionIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -173,7 +188,7 @@ def prerelease(self) -> str | None: return None def generate_prerelease( - self, prerelease: str | None = None, offset: int = 0 + self, prerelease: Prerelease | None = None, offset: int = 0 ) -> str: """Generate prerelease @@ -188,20 +203,18 @@ def generate_prerelease( if not prerelease: return "" + prerelease_value = prerelease.value + new_prerelease_number = offset + # prevent down-bumping the pre-release phase, e.g. from 'b1' to 'a2' # https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases # https://semver.org/#spec-item-11 if self.is_prerelease and self.pre: - prerelease = max(prerelease, self.pre[0]) + prerelease_value = max(prerelease_value, self.pre[0]) + if prerelease_value.startswith(self.pre[0]): + new_prerelease_number = self.pre[1] + 1 - # version.pre is needed for mypy check - if self.is_prerelease and self.pre and prerelease.startswith(self.pre[0]): - prev_prerelease: int = self.pre[1] - new_prerelease_number = prev_prerelease + 1 - else: - new_prerelease_number = offset - pre_version = f"{prerelease}{new_prerelease_number}" - return pre_version + return f"{prerelease_value}{new_prerelease_number}" def generate_devrelease(self, devrelease: int | None) -> str: """Generate devrelease @@ -225,26 +238,34 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: Increment | None = None) -> str: - prev_release = list(self.release) - increments = [MAJOR, MINOR, PATCH] - base = dict(zip_longest(increments, prev_release, fillvalue=0)) + def increment_base(self, increment: VersionIncrement | None = None) -> str: + base = dict( + zip_longest( + ( + VersionIncrement.MAJOR, + VersionIncrement.MINOR, + VersionIncrement.PATCH, + ), + self.release, + fillvalue=0, + ) + ) - if increment == MAJOR: - base[MAJOR] += 1 - base[MINOR] = 0 - base[PATCH] = 0 - elif increment == MINOR: - base[MINOR] += 1 - base[PATCH] = 0 - elif increment == PATCH: - base[PATCH] += 1 + if increment == VersionIncrement.MAJOR: + base[VersionIncrement.MAJOR] += 1 + base[VersionIncrement.MINOR] = 0 + base[VersionIncrement.PATCH] = 0 + elif increment == VersionIncrement.MINOR: + base[VersionIncrement.MINOR] += 1 + base[VersionIncrement.PATCH] = 0 + elif increment == VersionIncrement.PATCH: + base[VersionIncrement.PATCH] += 1 - return f"{base[MAJOR]}.{base[MINOR]}.{base[PATCH]}" + return f"{base[VersionIncrement.MAJOR]}.{base[VersionIncrement.MINOR]}.{base[VersionIncrement.PATCH]}" def bump( self, - increment: Increment | None, + increment: VersionIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -286,13 +307,16 @@ def bump( ) # type: ignore[return-value] def _get_increment_base( - self, increment: Increment | None, exact_increment: bool + self, increment: VersionIncrement | None, exact_increment: bool ) -> str: if ( not self.is_prerelease or exact_increment - or (increment == MINOR and self.micro != 0) - or (increment == MAJOR and (self.minor != 0 or self.micro != 0)) + or (increment == VersionIncrement.MINOR and self.micro != 0) + or ( + increment == VersionIncrement.MAJOR + and (self.minor != 0 or self.micro != 0) + ) ): return self.increment_base(increment) return f"{self.major}.{self.minor}.{self.micro}" diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 59297b172..94fb136f0 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -13,6 +13,7 @@ import commitizen.commands.bump as bump from commitizen import cli, cmd, defaults, git, hooks +from commitizen.bump_rule import VersionIncrement from commitizen.changelog_formats import ChangelogFormat from commitizen.config.base_config import BaseConfig from commitizen.cz.base import BaseCommitizen @@ -1001,7 +1002,7 @@ def test_bump_with_pre_bump_hooks( new_version="0.2.0", new_tag_version="0.2.0", message="bump: version 0.1.0 โ†’ 0.2.0", - increment="MINOR", + increment=VersionIncrement.MINOR, changelog_file_name=None, ), call( @@ -1013,7 +1014,7 @@ def test_bump_with_pre_bump_hooks( current_version="0.2.0", current_tag_version="0.2.0", message="bump: version 0.1.0 โ†’ 0.2.0", - increment="MINOR", + increment=VersionIncrement.MINOR, changelog_file_name=None, ), ] diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py deleted file mode 100644 index 77e11c78c..000000000 --- a/tests/test_bump_find_increment.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -CC: Conventional commits -SVE: Semantic version at the end -""" - -import pytest - -from commitizen import bump -from commitizen.cz.conventional_commits import ConventionalCommitsCz -from commitizen.git import GitCommit - -NONE_INCREMENT_CC = [ - "docs(README): motivation", - "ci: added travis", - "performance. Remove or disable the reimplemented linters", - "refactor that how this line starts", -] - -PATCH_INCREMENTS_CC = [ - "fix(setup.py): future is now required for every python version", - "docs(README): motivation", -] - -MINOR_INCREMENTS_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", - "perf: app is much faster", - "refactor: app is much faster", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING CHANGE: `extends` key in config file is now used for extending other config files", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING-CHANGE: `extends` key in config file is now used for extending other config files", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC = [ - "feat(cli)!: added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2 = [ - "feat(pipeline)!: some text with breaking change" -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC = [ - "chore!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC = [ - "chore(deps)!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -PATCH_INCREMENTS_SVE = ["readme motivation PATCH", "fix setup.py PATCH"] - -MINOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", -] - -MAJOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", - "extends key is used for other config files MAJOR", -] - -semantic_version_pattern = r"(MAJOR|MINOR|PATCH)" -semantic_version_map = {"MAJOR": "MAJOR", "MINOR": "MINOR", "PATCH": "PATCH"} - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_CC, "PATCH"), - (MINOR_INCREMENTS_CC, "MINOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_CC, "MAJOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2, "MAJOR"), - (NONE_INCREMENT_CC, None), - ), -) -def test_find_increment(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, - regex=ConventionalCommitsCz.bump_pattern, - increments_map=ConventionalCommitsCz.bump_map, - ) - assert increment_type == expected_type - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_SVE, "PATCH"), - (MINOR_INCREMENTS_SVE, "MINOR"), - (MAJOR_INCREMENTS_SVE, "MAJOR"), - ), -) -def test_find_increment_sve(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, regex=semantic_version_pattern, increments_map=semantic_version_map - ) - assert increment_type == expected_type diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py new file mode 100644 index 000000000..77bf28386 --- /dev/null +++ b/tests/test_bump_rule.py @@ -0,0 +1,649 @@ +import pytest + +from commitizen.bump_rule import ( + ConventionalCommitBumpRule, + CustomBumpRule, + VersionIncrement, +) +from commitizen.defaults import ( + BUMP_MAP, + BUMP_MAP_MAJOR_VERSION_ZERO, + BUMP_PATTERN, +) +from commitizen.exceptions import NoPatternMapError + + +@pytest.fixture +def bump_rule(): + return ConventionalCommitBumpRule() + + +class TestConventionalCommitBumpRule: + def test_feat_commit(self, bump_rule): + assert ( + bump_rule.get_increment("feat: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("feat: add new feature", True) + == VersionIncrement.MINOR + ) + + def test_fix_commit(self, bump_rule): + assert bump_rule.get_increment("fix: fix bug", False) == VersionIncrement.PATCH + assert bump_rule.get_increment("fix: fix bug", True) == VersionIncrement.PATCH + + def test_perf_commit(self, bump_rule): + assert ( + bump_rule.get_increment("perf: improve performance", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("perf: improve performance", True) + == VersionIncrement.PATCH + ) + + def test_refactor_commit(self, bump_rule): + assert ( + bump_rule.get_increment("refactor: restructure code", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: restructure code", True) + == VersionIncrement.PATCH + ) + + def test_breaking_change_with_bang(self, bump_rule): + assert ( + bump_rule.get_increment("feat!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat!: breaking change", True) + == VersionIncrement.MINOR + ) + + def test_breaking_change_type(self, bump_rule): + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", True) + == VersionIncrement.MINOR + ) + + def test_commit_with_scope(self, bump_rule): + assert ( + bump_rule.get_increment("feat(api): add new endpoint", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui): fix button alignment", False) + == VersionIncrement.PATCH + ) + + def test_commit_with_complex_scopes(self, bump_rule): + # Test with multiple word scopes + assert ( + bump_rule.get_increment("feat(user_management): add user roles", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(database_connection): handle timeout", False) + == VersionIncrement.PATCH + ) + + # Test with nested scopes + assert ( + bump_rule.get_increment("feat(api/auth): implement OAuth", False) + == VersionIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui/components): fix dropdown", False) + == VersionIncrement.PATCH + ) + + # Test with breaking changes and scopes + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) + == VersionIncrement.MINOR + ) + + # Test with BREAKING CHANGE and scopes + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", False + ) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", True + ) + == VersionIncrement.MINOR + ) + + def test_invalid_commit_message(self, bump_rule): + assert bump_rule.get_increment("invalid commit message", False) is None + assert bump_rule.get_increment("", False) is None + assert bump_rule.get_increment("feat", False) is None + + def test_other_commit_types(self, bump_rule): + # These commit types should not trigger any version bump + assert bump_rule.get_increment("docs: update documentation", False) is None + assert bump_rule.get_increment("style: format code", False) is None + assert bump_rule.get_increment("test: add unit tests", False) is None + assert bump_rule.get_increment("build: update build config", False) is None + assert bump_rule.get_increment("ci: update CI pipeline", False) is None + + def test_breaking_change_with_refactor(self, bump_rule): + """Test breaking changes with refactor type commit messages.""" + # Breaking change with refactor type + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + + # Breaking change with refactor type and scope + assert ( + bump_rule.get_increment( + "refactor(api)!: remove deprecated endpoints", False + ) + == VersionIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) + == VersionIncrement.MINOR + ) + + # Regular refactor (should be VersionIncrement.PATCH) + assert ( + bump_rule.get_increment("refactor: improve code structure", False) + == VersionIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: improve code structure", True) + == VersionIncrement.PATCH + ) + + +class TestFindIncrementByCallable: + @pytest.fixture + def get_increment(self, bump_rule): + return lambda x: bump_rule.get_increment(x, False) + + def test_single_commit(self, get_increment): + commit_messages = ["feat: add new feature"] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_multiple_commits(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "docs: update readme", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_breaking_change(self, get_increment): + commit_messages = [ + "feat: new feature", + "feat!: breaking change", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MAJOR + ) + + def test_multi_line_commit(self, get_increment): + commit_messages = [ + "feat: new feature\n\nBREAKING CHANGE: major change", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MAJOR + ) + + def test_no_increment_needed(self, get_increment): + commit_messages = [ + "docs: update documentation", + "style: format code", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_empty_commits(self, get_increment): + commit_messages = [] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_major_version_zero(self): + bump_rule = ConventionalCommitBumpRule() + + commit_messages = [ + "feat!: breaking change", + "BREAKING CHANGE: major change", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: bump_rule.get_increment(x, True) + ) + == VersionIncrement.MINOR + ) + + def test_mixed_commit_types(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "perf: improve performance", + "refactor: restructure code", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + def test_commit_with_scope(self, get_increment): + commit_messages = [ + "feat(api): add new endpoint", + "fix(ui): fix button alignment", + ] + assert ( + VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + == VersionIncrement.MINOR + ) + + +class TestCustomBumpRule: + @pytest.fixture + def bump_pattern(self): + return r"^.*?\[(.*?)\].*$" + + @pytest.fixture + def bump_map(self): + return { + "MAJOR": VersionIncrement.MAJOR, + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + + @pytest.fixture + def bump_map_major_version_zero(self): + return { + "MAJOR": VersionIncrement.MINOR, # VersionIncrement.MAJOR becomes VersionIncrement.MINOR in version zero + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + + @pytest.fixture + def custom_bump_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): + return CustomBumpRule(bump_pattern, bump_map, bump_map_major_version_zero) + + def test_major_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", False) + == VersionIncrement.MAJOR + ) + + def test_minor_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MINOR]", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MINOR]", False) + == VersionIncrement.MINOR + ) + + def test_patch_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [PATCH]", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [PATCH]", False) + == VersionIncrement.PATCH + ) + + def test_major_version_zero(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", True) + == VersionIncrement.MINOR + ) + + def test_no_match(self, custom_bump_rule): + assert custom_bump_rule.get_increment("feat: add new feature", False) is None + assert custom_bump_rule.get_increment("fix: bug fix", False) is None + + def test_invalid_pattern(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_invalid_bump_map(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_invalid_bump_map_major_version_zero(self, bump_pattern, bump_map): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, bump_map, {}) + + def test_all_invalid(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", {}, {}) + + def test_none_values(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule(None, {}, {}) + + def test_empty_pattern_with_valid_maps(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_empty_maps_with_valid_pattern(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_complex_pattern(self): + pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" + bump_map = { + "MAJOR": VersionIncrement.MAJOR, + "MINOR": VersionIncrement.MINOR, + "PATCH": VersionIncrement.PATCH, + } + rule = CustomBumpRule(pattern, bump_map, bump_map) + + assert ( + rule.get_increment( + "feat: add new feature [MAJOR] [MINOR]", + False, + ) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) + == VersionIncrement.MINOR + ) + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat: add new feature [MAJOR]", + "fix: bug fix [PATCH]", + "docs: update readme [MINOR]", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == VersionIncrement.MAJOR + ) + + def test_flexible_bump_map(self, custom_bump_rule): + """Test that _find_highest_increment is used correctly in bump map processing.""" + # Test with multiple matching patterns + pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + bump_map = { + "major": VersionIncrement.MAJOR, + "bang": VersionIncrement.MAJOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH, + } + bump_map_major_version_zero = { + "major": VersionIncrement.MINOR, + "bang": VersionIncrement.MINOR, + "minor": VersionIncrement.MINOR, + "patch": VersionIncrement.PATCH, + } + rule = CustomBumpRule(pattern, bump_map, bump_map_major_version_zero) + + # Test with multiple version tags + assert ( + rule.get_increment("major!: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("major!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", False) + == VersionIncrement.MAJOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", True) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", False) + == VersionIncrement.PATCH + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", True) + == VersionIncrement.PATCH + ) + assert ( + rule.get_increment("minor: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + rule.get_increment("minor: add new feature", True) == VersionIncrement.MINOR + ) + assert rule.get_increment("patch: fix bug", False) == VersionIncrement.PATCH + assert rule.get_increment("patch: fix bug", True) == VersionIncrement.PATCH + + +class TestCustomBumpRuleWithDefault: + @pytest.fixture + def custom_bump_rule(self): + return CustomBumpRule( + BUMP_PATTERN, + VersionIncrement.safe_cast_dict(BUMP_MAP), + VersionIncrement.safe_cast_dict(BUMP_MAP_MAJOR_VERSION_ZERO), + ) + + def test_breaking_change_with_bang(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("feat!: breaking change", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", True) + == VersionIncrement.MINOR + ) + + def test_breaking_change_type(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", False) + == VersionIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", True) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", True) + == VersionIncrement.MINOR + ) + + def test_feat_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("feat: add new feature", True) + == VersionIncrement.MINOR + ) + + def test_fix_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("fix: fix bug", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: fix bug", True) + == VersionIncrement.PATCH + ) + + def test_refactor_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("refactor: restructure code", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor: restructure code", True) + == VersionIncrement.PATCH + ) + + def test_perf_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("perf: improve performance", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("perf: improve performance", True) + == VersionIncrement.PATCH + ) + + def test_commit_with_scope(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat(api): add new endpoint", False) + == VersionIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix(ui): fix button alignment", False) + == VersionIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor(core): restructure", False) + == VersionIncrement.PATCH + ) + + def test_no_match(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("docs: update documentation", False) is None + ) + assert custom_bump_rule.get_increment("style: format code", False) is None + assert custom_bump_rule.get_increment("test: add unit tests", False) is None + assert ( + custom_bump_rule.get_increment("build: update build config", False) is None + ) + assert custom_bump_rule.get_increment("ci: update CI pipeline", False) is None + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat!: breaking change", + "fix: bug fix", + "perf: improve performance", + ] + assert ( + VersionIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == VersionIncrement.MAJOR + ) + + +class TestGetHighest: + def test_get_highest_with_major(self): + increments = [ + VersionIncrement.PATCH, + VersionIncrement.MINOR, + VersionIncrement.MAJOR, + ] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR + + def test_get_highest_with_minor(self): + increments = [VersionIncrement.PATCH, VersionIncrement.MINOR, None] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR + + def test_get_highest_with_patch(self): + increments = [VersionIncrement.PATCH, None, None] + assert VersionIncrement.get_highest(increments) == VersionIncrement.PATCH + + def test_get_highest_with_none(self): + increments = [None, None, None] + assert VersionIncrement.get_highest(increments) is None + + def test_get_highest_empty(self): + increments = [] + assert VersionIncrement.get_highest(increments) is None + + def test_get_highest_mixed_order(self): + increments = [ + VersionIncrement.MAJOR, + VersionIncrement.PATCH, + VersionIncrement.MINOR, + ] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR + + def test_get_highest_with_none_values(self): + increments = [None, VersionIncrement.MINOR, None, VersionIncrement.PATCH] + assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR + + +class TestSafeCast: + def test_safe_cast_valid_strings(self): + assert VersionIncrement.safe_cast("MAJOR") == VersionIncrement.MAJOR + assert VersionIncrement.safe_cast("MINOR") == VersionIncrement.MINOR + assert VersionIncrement.safe_cast("PATCH") == VersionIncrement.PATCH + + def test_safe_cast_invalid_strings(self): + assert VersionIncrement.safe_cast("invalid") is None + assert VersionIncrement.safe_cast("major") is None # case sensitive + assert VersionIncrement.safe_cast("") is None + + def test_safe_cast_non_string_values(self): + assert VersionIncrement.safe_cast(None) is None + assert VersionIncrement.safe_cast(1) is None + assert VersionIncrement.safe_cast(True) is None + assert VersionIncrement.safe_cast([]) is None + assert VersionIncrement.safe_cast({}) is None + assert ( + VersionIncrement.safe_cast(VersionIncrement.MAJOR) is None + ) # enum value itself diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index a983dad14..2ba7d7dc1 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -3,184 +3,188 @@ import pytest -from commitizen.version_schemes import Pep440, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Pep440, Prerelease, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1.dev1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0.dev1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1a0"), - (("0.3.1a0", None, "alpha", 0, None), "0.3.1a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1.dev1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0.dev1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1a1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0a0"), - (("1.0.0a0", None, "alpha", 0, None), "1.0.0a1"), - (("1.0.0a1", None, "alpha", 0, None), "1.0.0a2"), - (("1.0.0a1", None, "alpha", 0, 1), "1.0.0a2.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0a3.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0a3.dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0rc1.dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0a2.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0a3.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0a3.dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0rc1.dev1"), + (("1.0.0rc0", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1rc1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.0a1"), - (("1a0", None, "alpha", 1, None), "1.0.0a1"), - (("1", None, "beta", 0, None), "1.0.0b0"), - (("1", None, "beta", 1, None), "1.0.0b1"), - (("1beta", None, "beta", 0, None), "1.0.0b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0a2"), - (("1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc1+e20d7b57f3eb", VersionIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0a1"), - (("1.0.0a2", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0a2", None, "beta", 1, None), "1.0.0b1"), - (("1.0.0beta1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0rc2"), + (("0.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1a0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0rc2"), ] # additional pre-release tests run through various release scenarios prerelease_cases = [ # - (("3.3.3", "PATCH", "alpha", 0, None), "3.3.4a0"), - (("3.3.4a0", "PATCH", "alpha", 0, None), "3.3.4a1"), - (("3.3.4a1", "MINOR", "alpha", 0, None), "3.4.0a0"), - (("3.4.0a0", "PATCH", "alpha", 0, None), "3.4.0a1"), - (("3.4.0a1", "MINOR", "alpha", 0, None), "3.4.0a2"), - (("3.4.0a2", "MAJOR", "alpha", 0, None), "4.0.0a0"), - (("4.0.0a0", "PATCH", "alpha", 0, None), "4.0.0a1"), - (("4.0.0a1", "MINOR", "alpha", 0, None), "4.0.0a2"), - (("4.0.0a2", "MAJOR", "alpha", 0, None), "4.0.0a3"), + (("3.3.3", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a0"), + (("3.3.4a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a1"), + (("3.3.4a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a0"), + (("3.4.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.4.0a1"), + (("3.4.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a2"), + (("3.4.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), + (("4.0.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "4.0.0a1"), + (("4.0.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "4.0.0a2"), + (("4.0.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a3"), # - (("1.0.0", "PATCH", "alpha", 0, None), "1.0.1a0"), - (("1.0.1a0", "PATCH", "alpha", 0, None), "1.0.1a1"), - (("1.0.1a1", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a0"), + (("1.0.1a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a1"), + (("1.0.1a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), # - (("1.0.0", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "PATCH", "alpha", 0, None), "1.1.0a3"), - (("1.1.0a3", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a3"), + (("1.1.0a3", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), # - (("1.0.0", "MAJOR", "alpha", 0, None), "2.0.0a0"), - (("2.0.0a0", "MINOR", "alpha", 0, None), "2.0.0a1"), - (("2.0.0a1", "PATCH", "alpha", 0, None), "2.0.0a2"), - (("2.0.0a2", "MAJOR", "alpha", 0, None), "2.0.0a3"), - (("2.0.0a3", "MINOR", "alpha", 0, None), "2.0.0a4"), - (("2.0.0a4", "PATCH", "alpha", 0, None), "2.0.0a5"), - (("2.0.0a5", "MAJOR", "alpha", 0, None), "2.0.0a6"), + (("1.0.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), + (("2.0.0a0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a1"), + (("2.0.0a1", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a2"), + (("2.0.0a2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a3"), + (("2.0.0a3", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a4"), + (("2.0.0a4", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a5"), + (("2.0.0a5", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a6"), # - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.0.0b1"), - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0b1"), + (("2.0.0b0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0b1"), # - (("1.0.1a0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1a0", "MINOR", None, 0, None), "1.1.0"), - (("1.0.1a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.0.1a0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1a0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.0.1a0", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("1.1.0a0", "PATCH", None, 0, None), "1.1.0"), - (("1.1.0a0", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.1.0a0", VersionIncrement.PATCH, None, 0, None), "1.1.0"), + (("1.1.0a0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0a0", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("2.0.0a0", "MINOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "MAJOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "PATCH", None, 0, None), "2.0.0"), + (("2.0.0a0", VersionIncrement.MINOR, None, 0, None), "2.0.0"), + (("2.0.0a0", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("2.0.0a0", VersionIncrement.PATCH, None, 0, None), "2.0.0"), # (("3.0.0a1", None, None, 0, None), "3.0.0"), (("3.0.0b1", None, None, 0, None), "3.0.0"), (("3.0.0rc1", None, None, 0, None), "3.0.0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4", None, "beta", 0, None), "3.1.4b0"), - (("3.1.4", None, "rc", 0, None), "3.1.4rc0"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + (("3.1.4", None, Prerelease.BETA, 0, None), "3.1.4b0"), + (("3.1.4", None, Prerelease.RC, 0, None), "3.1.4rc0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4a0", "PATCH", "alpha", 0, None), "3.1.4a1"), # UNEXPECTED! - (("3.1.4a0", "MINOR", "alpha", 0, None), "3.2.0a0"), - (("3.1.4a0", "MAJOR", "alpha", 0, None), "4.0.0a0"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + ( + ("3.1.4a0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), + "3.1.4a1", + ), # UNEXPECTED! + (("3.1.4a0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.2.0a0"), + (("3.1.4a0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0a1", VersionIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0b0", VersionIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0b1", VersionIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0rc0", VersionIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1rc0.dev1"), + (("1.0.0rc0", VersionIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1rc0.dev1"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0a1", VersionIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0b0", VersionIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "alpha", 0, None), "1.1.0a0"), + (("1.0.0b0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0b1", VersionIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0rc0", VersionIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0rc0.dev1"), + (("1.0.0rc0", VersionIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0rc0.dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", VersionIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", VersionIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0a0"), + (("2.0.0b0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0a0"), + (("2.0.0b0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1a0"), + (("2.0.0b0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1a0"), ] diff --git a/tests/test_version_scheme_semver.py b/tests/test_version_scheme_semver.py index 8785717a3..712d63052 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -3,121 +3,122 @@ import pytest -from commitizen.version_schemes import SemVer, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Prerelease, SemVer, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-a0"), - (("0.3.1a0", None, "alpha", 0, None), "0.3.1-a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1-dev1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0-dev1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1-a1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1-a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("1.0.0a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1.0.0a1", None, "alpha", 0, None), "1.0.0-a2"), - (("1.0.0a1", None, "alpha", 0, 1), "1.0.0-a2-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0-a2-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0-a3-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0-a3-dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0-rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0-rc1-dev1"), + (("1.0.0rc0", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1-b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1-rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1-rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1-b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1-rc1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1a0", None, "alpha", 1, None), "1.0.0-a1"), - (("1", None, "beta", 0, None), "1.0.0-b0"), - (("1", None, "beta", 1, None), "1.0.0-b1"), - (("1beta", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), - (("1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc1+e20d7b57f3eb", VersionIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-a1"), - (("1.0.0a2", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0a2", None, "beta", 1, None), "1.0.0-b1"), - (("1.0.0beta1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), - (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("0.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-a0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0-rc2"), + (("1.0.0-a0", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0-alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", VersionIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0a1", VersionIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0b0", VersionIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0b1", VersionIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0rc0", VersionIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1-rc0-dev1"), + (("1.0.0rc0", VersionIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1-rc0-dev1"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0a1", VersionIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0b0", VersionIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0b1", VersionIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0rc0", VersionIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0-rc0-dev1"), + (("1.0.0rc0", VersionIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0-rc0-dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", VersionIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", VersionIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0-a0"), + (("2.0.0b0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0-a0"), + (("2.0.0b0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1-a0"), + (("2.0.0b0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1-a0"), ] diff --git a/tests/test_version_scheme_semver2.py b/tests/test_version_scheme_semver2.py index d18a058a7..be7795d03 100644 --- a/tests/test_version_scheme_semver2.py +++ b/tests/test_version_scheme_semver2.py @@ -3,84 +3,85 @@ import pytest -from commitizen.version_schemes import SemVer2, VersionProtocol +from commitizen.bump_rule import VersionIncrement +from commitizen.version_schemes import Prerelease, SemVer2, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev.1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev.1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-alpha.0"), - (("0.3.1-alpha.0", None, "alpha", 0, None), "0.3.1-alpha.1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-alpha.1"), - (("0.3.1-alpha.0", None, "alpha", 1, None), "0.3.1-alpha.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", VersionIncrement.PATCH, None, 0, 1), "0.1.1-dev.1"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", VersionIncrement.MINOR, None, 0, 1), "0.3.0-dev.1"), + (("0.3.0", VersionIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-alpha.0"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 0, None), "0.3.1-alpha.1"), + (("0.3.0", VersionIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), (("0.3.1-alpha.0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"), - (("1.0.0-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), - (("1.0.0-alpha.1", None, "alpha", 0, 1), "1.0.0-alpha.2.dev.1"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 1), "1.0.0-alpha.3.dev.1"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 0), "1.0.0-alpha.3.dev.0"), - (("1.0.0-alpha.1", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0-beta.0", None, "beta", 0, None), "1.0.0-beta.1"), - (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.0", None, "rc", 0, None), "1.0.0-rc.1"), - (("1.0.0-rc.0", None, "rc", 0, 1), "1.0.0-rc.1.dev.1"), - (("1.0.0-rc.0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0-alpha.3.dev.0", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", VersionIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("1.0.0-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.2.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.3.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 0), "1.0.0-alpha.3.dev.0"), + (("1.0.0-alpha.1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-beta.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, None), "1.0.0-rc.1"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, 1), "1.0.0-rc.1.dev.1"), + (("1.0.0-rc.0", VersionIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0-alpha.3.dev.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0", VersionIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", VersionIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", VersionIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", VersionIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", VersionIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", VersionIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", VersionIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1-beta.1", None, "alpha", 0, None), "0.1.1-beta.2"), - (("0.1.1-rc.0", None, "alpha", 0, None), "0.1.1-rc.1"), - (("0.1.1-rc.0", None, "beta", 0, None), "0.1.1-rc.1"), + (("0.1.1-beta.1", None, Prerelease.ALPHA, 0, None), "0.1.1-beta.2"), + (("0.1.1-rc.0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc.1"), + (("0.1.1-rc.0", None, Prerelease.BETA, 0, None), "0.1.1-rc.1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), - (("1-alpha.0", None, "alpha", 1, None), "1.0.0-alpha.1"), - (("1", None, "beta", 0, None), "1.0.0-beta.0"), - (("1", None, "beta", 1, None), "1.0.0-beta.1"), - (("1-beta", None, "beta", 0, None), "1.0.0-beta.1"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), - (("1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", VersionIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", VersionIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", VersionIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1-alpha.0", None, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1-beta", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.1+e20d7b57f3eb", VersionIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-alpha.0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-alpha.1"), - (("1.0.0-alpha.2", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0-alpha.2", None, "beta", 1, None), "1.0.0-beta.1"), - (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.1", None, "rc", 0, None), "1.0.0-rc.2"), - (("1.0.0-alpha.0", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), + (("0.1.1", VersionIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", VersionIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", VersionIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", VersionIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-alpha.0"), + (("0.9.0", VersionIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-alpha.0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("0.9.0", VersionIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.1", None, Prerelease.RC, 0, None), "1.0.0-rc.2"), + (("1.0.0-alpha.0", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), ] diff --git a/tests/test_version_schemes.py b/tests/test_version_schemes.py index 8e2dae902..ef646d6d2 100644 --- a/tests/test_version_schemes.py +++ b/tests/test_version_schemes.py @@ -12,7 +12,35 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionSchemeUnknown -from commitizen.version_schemes import Pep440, SemVer, get_version_scheme +from commitizen.version_schemes import Pep440, Prerelease, SemVer, get_version_scheme + + +class TestPrereleaseSafeCast: + def test_safe_cast_valid_strings(self): + assert Prerelease.safe_cast("ALPHA") == Prerelease.ALPHA + assert Prerelease.safe_cast("BETA") == Prerelease.BETA + assert Prerelease.safe_cast("RC") == Prerelease.RC + + def test_safe_cast_case_insensitive(self): + assert Prerelease.safe_cast("alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("beta") == Prerelease.BETA + assert Prerelease.safe_cast("rc") == Prerelease.RC + assert Prerelease.safe_cast("Alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("Beta") == Prerelease.BETA + assert Prerelease.safe_cast("Rc") == Prerelease.RC + + def test_safe_cast_invalid_strings(self): + assert Prerelease.safe_cast("invalid") is None + assert Prerelease.safe_cast("") is None + assert Prerelease.safe_cast("release") is None + + def test_safe_cast_non_string_values(self): + assert Prerelease.safe_cast(None) is None + assert Prerelease.safe_cast(1) is None + assert Prerelease.safe_cast(True) is None + assert Prerelease.safe_cast([]) is None + assert Prerelease.safe_cast({}) is None + assert Prerelease.safe_cast(Prerelease.ALPHA) is None # enum value itself def test_default_version_scheme_is_pep440(config: BaseConfig): From bec90cd8ea326c274641698ad35b04b4417a486b Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Tue, 10 Jun 2025 21:28:24 +0800 Subject: [PATCH 45/51] docs(bump.md): add documentation about the bump rule change --- docs/commands/bump.md | 92 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/docs/commands/bump.md b/docs/commands/bump.md index 548f6e361..5e51da1ec 100644 --- a/docs/commands/bump.md +++ b/docs/commands/bump.md @@ -99,7 +99,7 @@ cz bump --changelog ### `--prerelease` The bump is a pre-release bump, meaning that in addition to a possible version bump the new version receives a -pre-release segment compatible with the bumpโ€™s version scheme, where the segment consist of a _phase_ and a +pre-release segment compatible with the bump's version scheme, where the segment consist of a _phase_ and a non-negative number. Supported options for `--prerelease` are the following phase names `alpha`, `beta`, or `rc` (release candidate). For more details, refer to the [Python Packaging User Guide](https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases). @@ -659,7 +659,95 @@ version_scheme = "semver" ## Custom bump -Read the [customizing section](../customization.md). +You can create custom bump rules to define how version numbers should be incremented based on your commit messages. This is done by configuring three main components: + +### Bump Pattern + +The `bump_pattern` is a regex pattern string used to match commit messages. It defines the structure of your commit messages and captures named groups for different types of changes. The captured groups will be used as keys in the bump map. + +Example: +```python +# Simple pattern matching major/minor/patch types +r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + +# Conventional commits style pattern +r"^((?Pfeat)|(?Pfix|perf|refactor)|(?PBREAKING[\-\ ]CHANGE))(?P\(.+\))?(?P!)?:" +``` + +### Bump Map + +The `bump_map` defines how different commit types map to version increments. The keys in this dictionary must match the named capture groups from your pattern. The values are the version increment types: + +- `"MAJOR"` +- `"MINOR"` +- `"PATCH"` + +Example for conventional commits: +```python +{ + # Breaking changes (either by type or bang) + "breaking": "MAJOR", # When type is "BREAKING CHANGE" + "bang": "MAJOR", # When commit ends with ! (e.g., feat!: new feature) + # New features + "minor": "MINOR", + # Bug fixes and improvements + "patch": "PATCH", +} +``` + +Or using regex patterns: +```python +{ + (r"^.+!$", "MAJOR"), + (r"^BREAKING[\-\ ]CHANGE", "MAJOR"), + (r"^feat", "MINOR"), + (r"^fix", "PATCH"), + (r"^refactor", "PATCH"), + (r"^perf", "PATCH"), +} +``` + +### Major Version Zero Map + +The `bump_map_major_version_zero` allows you to define different versioning behavior when your project is in initial development (major version is 0). This is useful for following semantic versioning principles where breaking changes in 0.x.x versions don't require a major version bump. + +Example for conventional commits during initial development: +```python +{ + # Breaking changes (either by type or bang) + "breaking": "MINOR", # When type is "BREAKING CHANGE" + "bang": "MINOR", # When commit ends with ! (e.g., feat!: new feature) + # New features + "minor": "MINOR", + # Bug fixes and improvements + "patch": "PATCH", +} +``` + +Or with regex patterns: +```python +{ + (r"^.+!$", "MINOR"), + (r"^BREAKING[\-\ ]CHANGE", "MINOR"), + (r"^feat", "MINOR"), + (r"^fix", "PATCH"), + (r"^refactor", "PATCH"), + (r"^perf", "PATCH"), +} +``` + +This configuration will handle commit messages like: + +- `BREAKING CHANGE: remove deprecated API` โ†’ MAJOR (or MINOR in major version zero) +- `feat!: add new API` โ†’ MAJOR (or MINOR in major version zero) +- `feat: add new feature` โ†’ MINOR +- `fix: fix bug` โ†’ PATCH +- `perf: improve performance` โ†’ PATCH +- `refactor: restructure code` โ†’ PATCH + +For more details on implementing custom bump rules, see the [customization guide](../customization.md). + + [pep440]: https://www.python.org/dev/peps/pep-0440/ [semver]: https://semver.org/ From 2c51510fc1746030a63337740fb10ddb03bf77cf Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 21:32:46 +0800 Subject: [PATCH 46/51] refactor(bump_rule): rename function --- commitizen/bump_rule.py | 16 +-- commitizen/commands/bump.py | 2 +- tests/test_bump_rule.py | 227 +++++++++++++++++++----------------- 3 files changed, 131 insertions(+), 114 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index bfdcfcd99..126b2def5 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -45,17 +45,17 @@ def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, VersionIncrement]: @staticmethod def get_highest_by_messages( commit_messages: Iterable[str], - get_increment: Callable[[str], VersionIncrement | None], + extract_increment: Callable[[str], VersionIncrement | None], ) -> VersionIncrement | None: """Find the highest version increment from a list of messages. This function processes a list of messages and determines the highest version increment needed based on the commit messages. It splits multi-line commit messages - and evaluates each line using the provided get_increment callable. + and evaluates each line using the provided extract_increment callable. Args: commit_messages: A list of messages to analyze. - get_increment: A callable that takes a commit message string and returns an + extract_increment: A callable that takes a commit message string and returns an VersionIncrement value (MAJOR, MINOR, PATCH) or None if no increment is needed. Returns: @@ -65,11 +65,11 @@ def get_highest_by_messages( Example: >>> commit_messages = ["feat: new feature", "fix: bug fix"] >>> rule = ConventionalCommitBumpRule() - >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.get_increment(x, False)) + >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.extract_increment(x, False)) 'MINOR' """ return VersionIncrement.get_highest( - get_increment(line) + extract_increment(line) for message in commit_messages for line in message.split("\n") ) @@ -92,7 +92,7 @@ class BumpRule(Protocol): such as conventional commits or custom rules. """ - def get_increment( + def extract_increment( self, commit_message: str, major_version_zero: bool ) -> VersionIncrement | None: """Determine the version increment based on a commit message. @@ -120,7 +120,7 @@ class ConventionalCommitBumpRule(BumpRule): _MINOR_CHANGE_TYPES = set(["feat"]) _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) - def get_increment( + def extract_increment( self, commit_message: str, major_version_zero: bool ) -> VersionIncrement | None: if not (m := self._head_pattern.match(commit_message)): @@ -223,7 +223,7 @@ def __init__( self.bump_map = bump_map self.bump_map_major_version_zero = bump_map_major_version_zero - def get_increment( + def extract_increment( self, commit_message: str, major_version_zero: bool ) -> VersionIncrement | None: if not (m := self.bump_pattern.search(commit_message)): diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 934f5c063..030bf7541 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -150,7 +150,7 @@ def _find_increment(self, commits: list[git.GitCommit]) -> VersionIncrement | No return VersionIncrement.get_highest_by_messages( (commit.message for commit in commits), - lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero), + lambda x: self.cz.bump_rule.extract_increment(x, is_major_version_zero), ) def __call__(self) -> None: diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index 77bf28386..be8352fd3 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -21,217 +21,227 @@ def bump_rule(): class TestConventionalCommitBumpRule: def test_feat_commit(self, bump_rule): assert ( - bump_rule.get_increment("feat: add new feature", False) + bump_rule.extract_increment("feat: add new feature", False) == VersionIncrement.MINOR ) assert ( - bump_rule.get_increment("feat: add new feature", True) + bump_rule.extract_increment("feat: add new feature", True) == VersionIncrement.MINOR ) def test_fix_commit(self, bump_rule): - assert bump_rule.get_increment("fix: fix bug", False) == VersionIncrement.PATCH - assert bump_rule.get_increment("fix: fix bug", True) == VersionIncrement.PATCH + assert ( + bump_rule.extract_increment("fix: fix bug", False) == VersionIncrement.PATCH + ) + assert ( + bump_rule.extract_increment("fix: fix bug", True) == VersionIncrement.PATCH + ) def test_perf_commit(self, bump_rule): assert ( - bump_rule.get_increment("perf: improve performance", False) + bump_rule.extract_increment("perf: improve performance", False) == VersionIncrement.PATCH ) assert ( - bump_rule.get_increment("perf: improve performance", True) + bump_rule.extract_increment("perf: improve performance", True) == VersionIncrement.PATCH ) def test_refactor_commit(self, bump_rule): assert ( - bump_rule.get_increment("refactor: restructure code", False) + bump_rule.extract_increment("refactor: restructure code", False) == VersionIncrement.PATCH ) assert ( - bump_rule.get_increment("refactor: restructure code", True) + bump_rule.extract_increment("refactor: restructure code", True) == VersionIncrement.PATCH ) def test_breaking_change_with_bang(self, bump_rule): assert ( - bump_rule.get_increment("feat!: breaking change", False) + bump_rule.extract_increment("feat!: breaking change", False) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment("feat!: breaking change", True) + bump_rule.extract_increment("feat!: breaking change", True) == VersionIncrement.MINOR ) def test_breaking_change_type(self, bump_rule): assert ( - bump_rule.get_increment("BREAKING CHANGE: major change", False) + bump_rule.extract_increment("BREAKING CHANGE: major change", False) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment("BREAKING CHANGE: major change", True) + bump_rule.extract_increment("BREAKING CHANGE: major change", True) == VersionIncrement.MINOR ) def test_commit_with_scope(self, bump_rule): assert ( - bump_rule.get_increment("feat(api): add new endpoint", False) + bump_rule.extract_increment("feat(api): add new endpoint", False) == VersionIncrement.MINOR ) assert ( - bump_rule.get_increment("fix(ui): fix button alignment", False) + bump_rule.extract_increment("fix(ui): fix button alignment", False) == VersionIncrement.PATCH ) def test_commit_with_complex_scopes(self, bump_rule): # Test with multiple word scopes assert ( - bump_rule.get_increment("feat(user_management): add user roles", False) + bump_rule.extract_increment("feat(user_management): add user roles", False) == VersionIncrement.MINOR ) assert ( - bump_rule.get_increment("fix(database_connection): handle timeout", False) + bump_rule.extract_increment( + "fix(database_connection): handle timeout", False + ) == VersionIncrement.PATCH ) # Test with nested scopes assert ( - bump_rule.get_increment("feat(api/auth): implement OAuth", False) + bump_rule.extract_increment("feat(api/auth): implement OAuth", False) == VersionIncrement.MINOR ) assert ( - bump_rule.get_increment("fix(ui/components): fix dropdown", False) + bump_rule.extract_increment("fix(ui/components): fix dropdown", False) == VersionIncrement.PATCH ) # Test with breaking changes and scopes assert ( - bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) + bump_rule.extract_increment( + "feat(api)!: remove deprecated endpoints", False + ) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) + bump_rule.extract_increment("feat(api)!: remove deprecated endpoints", True) == VersionIncrement.MINOR ) # Test with BREAKING CHANGE and scopes assert ( - bump_rule.get_increment( + bump_rule.extract_increment( "BREAKING CHANGE(api): remove deprecated endpoints", False ) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment( + bump_rule.extract_increment( "BREAKING CHANGE(api): remove deprecated endpoints", True ) == VersionIncrement.MINOR ) def test_invalid_commit_message(self, bump_rule): - assert bump_rule.get_increment("invalid commit message", False) is None - assert bump_rule.get_increment("", False) is None - assert bump_rule.get_increment("feat", False) is None + assert bump_rule.extract_increment("invalid commit message", False) is None + assert bump_rule.extract_increment("", False) is None + assert bump_rule.extract_increment("feat", False) is None def test_other_commit_types(self, bump_rule): # These commit types should not trigger any version bump - assert bump_rule.get_increment("docs: update documentation", False) is None - assert bump_rule.get_increment("style: format code", False) is None - assert bump_rule.get_increment("test: add unit tests", False) is None - assert bump_rule.get_increment("build: update build config", False) is None - assert bump_rule.get_increment("ci: update CI pipeline", False) is None + assert bump_rule.extract_increment("docs: update documentation", False) is None + assert bump_rule.extract_increment("style: format code", False) is None + assert bump_rule.extract_increment("test: add unit tests", False) is None + assert bump_rule.extract_increment("build: update build config", False) is None + assert bump_rule.extract_increment("ci: update CI pipeline", False) is None def test_breaking_change_with_refactor(self, bump_rule): """Test breaking changes with refactor type commit messages.""" # Breaking change with refactor type assert ( - bump_rule.get_increment("refactor!: drop support for Python 2.7", False) + bump_rule.extract_increment("refactor!: drop support for Python 2.7", False) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment("refactor!: drop support for Python 2.7", True) + bump_rule.extract_increment("refactor!: drop support for Python 2.7", True) == VersionIncrement.MINOR ) # Breaking change with refactor type and scope assert ( - bump_rule.get_increment( + bump_rule.extract_increment( "refactor(api)!: remove deprecated endpoints", False ) == VersionIncrement.MAJOR ) assert ( - bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) + bump_rule.extract_increment( + "refactor(api)!: remove deprecated endpoints", True + ) == VersionIncrement.MINOR ) # Regular refactor (should be VersionIncrement.PATCH) assert ( - bump_rule.get_increment("refactor: improve code structure", False) + bump_rule.extract_increment("refactor: improve code structure", False) == VersionIncrement.PATCH ) assert ( - bump_rule.get_increment("refactor: improve code structure", True) + bump_rule.extract_increment("refactor: improve code structure", True) == VersionIncrement.PATCH ) class TestFindIncrementByCallable: @pytest.fixture - def get_increment(self, bump_rule): - return lambda x: bump_rule.get_increment(x, False) + def extract_increment(self, bump_rule): + return lambda x: bump_rule.extract_increment(x, False) - def test_single_commit(self, get_increment): + def test_single_commit(self, extract_increment): commit_messages = ["feat: add new feature"] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MINOR ) - def test_multiple_commits(self, get_increment): + def test_multiple_commits(self, extract_increment): commit_messages = [ "feat: new feature", "fix: bug fix", "docs: update readme", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MINOR ) - def test_breaking_change(self, get_increment): + def test_breaking_change(self, extract_increment): commit_messages = [ "feat: new feature", "feat!: breaking change", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MAJOR ) - def test_multi_line_commit(self, get_increment): + def test_multi_line_commit(self, extract_increment): commit_messages = [ "feat: new feature\n\nBREAKING CHANGE: major change", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MAJOR ) - def test_no_increment_needed(self, get_increment): + def test_no_increment_needed(self, extract_increment): commit_messages = [ "docs: update documentation", "style: format code", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) is None ) - def test_empty_commits(self, get_increment): + def test_empty_commits(self, extract_increment): commit_messages = [] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) is None ) @@ -244,12 +254,12 @@ def test_major_version_zero(self): ] assert ( VersionIncrement.get_highest_by_messages( - commit_messages, lambda x: bump_rule.get_increment(x, True) + commit_messages, lambda x: bump_rule.extract_increment(x, True) ) == VersionIncrement.MINOR ) - def test_mixed_commit_types(self, get_increment): + def test_mixed_commit_types(self, extract_increment): commit_messages = [ "feat: new feature", "fix: bug fix", @@ -257,17 +267,17 @@ def test_mixed_commit_types(self, get_increment): "refactor: restructure code", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MINOR ) - def test_commit_with_scope(self, get_increment): + def test_commit_with_scope(self, extract_increment): commit_messages = [ "feat(api): add new endpoint", "fix(ui): fix button alignment", ] assert ( - VersionIncrement.get_highest_by_messages(commit_messages, get_increment) + VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) == VersionIncrement.MINOR ) @@ -299,47 +309,49 @@ def custom_bump_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): def test_major_version(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat: add new feature [MAJOR]", False) + custom_bump_rule.extract_increment("feat: add new feature [MAJOR]", False) == VersionIncrement.MAJOR ) assert ( - custom_bump_rule.get_increment("fix: bug fix [MAJOR]", False) + custom_bump_rule.extract_increment("fix: bug fix [MAJOR]", False) == VersionIncrement.MAJOR ) def test_minor_version(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat: add new feature [MINOR]", False) + custom_bump_rule.extract_increment("feat: add new feature [MINOR]", False) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("fix: bug fix [MINOR]", False) + custom_bump_rule.extract_increment("fix: bug fix [MINOR]", False) == VersionIncrement.MINOR ) def test_patch_version(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat: add new feature [PATCH]", False) + custom_bump_rule.extract_increment("feat: add new feature [PATCH]", False) == VersionIncrement.PATCH ) assert ( - custom_bump_rule.get_increment("fix: bug fix [PATCH]", False) + custom_bump_rule.extract_increment("fix: bug fix [PATCH]", False) == VersionIncrement.PATCH ) def test_major_version_zero(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat: add new feature [MAJOR]", True) + custom_bump_rule.extract_increment("feat: add new feature [MAJOR]", True) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("fix: bug fix [MAJOR]", True) + custom_bump_rule.extract_increment("fix: bug fix [MAJOR]", True) == VersionIncrement.MINOR ) def test_no_match(self, custom_bump_rule): - assert custom_bump_rule.get_increment("feat: add new feature", False) is None - assert custom_bump_rule.get_increment("fix: bug fix", False) is None + assert ( + custom_bump_rule.extract_increment("feat: add new feature", False) is None + ) + assert custom_bump_rule.extract_increment("fix: bug fix", False) is None def test_invalid_pattern(self, bump_map, bump_map_major_version_zero): with pytest.raises(NoPatternMapError): @@ -379,14 +391,14 @@ def test_complex_pattern(self): rule = CustomBumpRule(pattern, bump_map, bump_map) assert ( - rule.get_increment( + rule.extract_increment( "feat: add new feature [MAJOR] [MINOR]", False, ) == VersionIncrement.MAJOR ) assert ( - rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) + rule.extract_increment("fix: bug fix [MINOR] [PATCH]", False) == VersionIncrement.MINOR ) @@ -398,7 +410,7 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): ] assert ( VersionIncrement.get_highest_by_messages( - commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + commit_messages, lambda x: custom_bump_rule.extract_increment(x, False) ) == VersionIncrement.MAJOR ) @@ -423,46 +435,47 @@ def test_flexible_bump_map(self, custom_bump_rule): # Test with multiple version tags assert ( - rule.get_increment("major!: drop support for Python 2.7", False) + rule.extract_increment("major!: drop support for Python 2.7", False) == VersionIncrement.MAJOR ) assert ( - rule.get_increment("major!: drop support for Python 2.7", True) + rule.extract_increment("major!: drop support for Python 2.7", True) == VersionIncrement.MINOR ) assert ( - rule.get_increment("major: drop support for Python 2.7", False) + rule.extract_increment("major: drop support for Python 2.7", False) == VersionIncrement.MAJOR ) assert ( - rule.get_increment("major: drop support for Python 2.7", True) + rule.extract_increment("major: drop support for Python 2.7", True) == VersionIncrement.MINOR ) assert ( - rule.get_increment("patch!: drop support for Python 2.7", False) + rule.extract_increment("patch!: drop support for Python 2.7", False) == VersionIncrement.MAJOR ) assert ( - rule.get_increment("patch!: drop support for Python 2.7", True) + rule.extract_increment("patch!: drop support for Python 2.7", True) == VersionIncrement.MINOR ) assert ( - rule.get_increment("patch: drop support for Python 2.7", False) + rule.extract_increment("patch: drop support for Python 2.7", False) == VersionIncrement.PATCH ) assert ( - rule.get_increment("patch: drop support for Python 2.7", True) + rule.extract_increment("patch: drop support for Python 2.7", True) == VersionIncrement.PATCH ) assert ( - rule.get_increment("minor: add new feature", False) + rule.extract_increment("minor: add new feature", False) == VersionIncrement.MINOR ) assert ( - rule.get_increment("minor: add new feature", True) == VersionIncrement.MINOR + rule.extract_increment("minor: add new feature", True) + == VersionIncrement.MINOR ) - assert rule.get_increment("patch: fix bug", False) == VersionIncrement.PATCH - assert rule.get_increment("patch: fix bug", True) == VersionIncrement.PATCH + assert rule.extract_increment("patch: fix bug", False) == VersionIncrement.PATCH + assert rule.extract_increment("patch: fix bug", True) == VersionIncrement.PATCH class TestCustomBumpRuleWithDefault: @@ -476,104 +489,108 @@ def custom_bump_rule(self): def test_breaking_change_with_bang(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat!: breaking change", False) + custom_bump_rule.extract_increment("feat!: breaking change", False) == VersionIncrement.MAJOR ) assert ( - custom_bump_rule.get_increment("fix!: breaking change", False) + custom_bump_rule.extract_increment("fix!: breaking change", False) == VersionIncrement.MAJOR ) assert ( - custom_bump_rule.get_increment("feat!: breaking change", True) + custom_bump_rule.extract_increment("feat!: breaking change", True) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("fix!: breaking change", True) + custom_bump_rule.extract_increment("fix!: breaking change", True) == VersionIncrement.MINOR ) def test_breaking_change_type(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("BREAKING CHANGE: major change", False) + custom_bump_rule.extract_increment("BREAKING CHANGE: major change", False) == VersionIncrement.MAJOR ) assert ( - custom_bump_rule.get_increment("BREAKING-CHANGE: major change", False) + custom_bump_rule.extract_increment("BREAKING-CHANGE: major change", False) == VersionIncrement.MAJOR ) assert ( - custom_bump_rule.get_increment("BREAKING CHANGE: major change", True) + custom_bump_rule.extract_increment("BREAKING CHANGE: major change", True) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("BREAKING-CHANGE: major change", True) + custom_bump_rule.extract_increment("BREAKING-CHANGE: major change", True) == VersionIncrement.MINOR ) def test_feat_commit(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat: add new feature", False) + custom_bump_rule.extract_increment("feat: add new feature", False) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("feat: add new feature", True) + custom_bump_rule.extract_increment("feat: add new feature", True) == VersionIncrement.MINOR ) def test_fix_commit(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("fix: fix bug", False) + custom_bump_rule.extract_increment("fix: fix bug", False) == VersionIncrement.PATCH ) assert ( - custom_bump_rule.get_increment("fix: fix bug", True) + custom_bump_rule.extract_increment("fix: fix bug", True) == VersionIncrement.PATCH ) def test_refactor_commit(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("refactor: restructure code", False) + custom_bump_rule.extract_increment("refactor: restructure code", False) == VersionIncrement.PATCH ) assert ( - custom_bump_rule.get_increment("refactor: restructure code", True) + custom_bump_rule.extract_increment("refactor: restructure code", True) == VersionIncrement.PATCH ) def test_perf_commit(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("perf: improve performance", False) + custom_bump_rule.extract_increment("perf: improve performance", False) == VersionIncrement.PATCH ) assert ( - custom_bump_rule.get_increment("perf: improve performance", True) + custom_bump_rule.extract_increment("perf: improve performance", True) == VersionIncrement.PATCH ) def test_commit_with_scope(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("feat(api): add new endpoint", False) + custom_bump_rule.extract_increment("feat(api): add new endpoint", False) == VersionIncrement.MINOR ) assert ( - custom_bump_rule.get_increment("fix(ui): fix button alignment", False) + custom_bump_rule.extract_increment("fix(ui): fix button alignment", False) == VersionIncrement.PATCH ) assert ( - custom_bump_rule.get_increment("refactor(core): restructure", False) + custom_bump_rule.extract_increment("refactor(core): restructure", False) == VersionIncrement.PATCH ) def test_no_match(self, custom_bump_rule): assert ( - custom_bump_rule.get_increment("docs: update documentation", False) is None + custom_bump_rule.extract_increment("docs: update documentation", False) + is None + ) + assert custom_bump_rule.extract_increment("style: format code", False) is None + assert custom_bump_rule.extract_increment("test: add unit tests", False) is None + assert ( + custom_bump_rule.extract_increment("build: update build config", False) + is None ) - assert custom_bump_rule.get_increment("style: format code", False) is None - assert custom_bump_rule.get_increment("test: add unit tests", False) is None assert ( - custom_bump_rule.get_increment("build: update build config", False) is None + custom_bump_rule.extract_increment("ci: update CI pipeline", False) is None ) - assert custom_bump_rule.get_increment("ci: update CI pipeline", False) is None def test_with_find_increment_by_callable(self, custom_bump_rule): commit_messages = [ @@ -583,7 +600,7 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): ] assert ( VersionIncrement.get_highest_by_messages( - commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + commit_messages, lambda x: custom_bump_rule.extract_increment(x, False) ) == VersionIncrement.MAJOR ) From d2413c13dda69ff2194a295255d9a72db03528eb Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 22:03:30 +0800 Subject: [PATCH 47/51] refactor(bump_rule): resolve comments --- commitizen/bump_rule.py | 48 ++++++++------- commitizen/commands/bump.py | 24 +++++--- commitizen/version_schemes.py | 8 +-- tests/test_bump_rule.py | 112 ++++++++++++++++++++++++---------- 4 files changed, 126 insertions(+), 66 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 126b2def5..7d1817682 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -12,12 +12,14 @@ class VersionIncrement(IntEnum): """An enumeration representing semantic versioning increments. - This class defines the three types of version increments according to semantic versioning: + This class defines the four types of version increments according to semantic versioning: + - NONE: For commits that don't require a version bump (docs, style, etc.) - PATCH: For backwards-compatible bug fixes - MINOR: For backwards-compatible functionality additions - MAJOR: For incompatible API changes """ + NONE = auto() PATCH = auto() MINOR = auto() MAJOR = auto() @@ -26,16 +28,16 @@ def __str__(self) -> str: return self.name @classmethod - def safe_cast(cls, value: object) -> VersionIncrement | None: + def safe_cast(cls, value: object) -> VersionIncrement: if not isinstance(value, str): - return None + return VersionIncrement.NONE try: return cls[value] except KeyError: - return None + return VersionIncrement.NONE - @classmethod - def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, VersionIncrement]: + @staticmethod + def safe_cast_dict(d: Mapping[str, object]) -> dict[str, VersionIncrement]: return { k: v for k, v in ((k, VersionIncrement.safe_cast(v)) for k, v in d.items()) @@ -45,8 +47,8 @@ def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, VersionIncrement]: @staticmethod def get_highest_by_messages( commit_messages: Iterable[str], - extract_increment: Callable[[str], VersionIncrement | None], - ) -> VersionIncrement | None: + extract_increment: Callable[[str], VersionIncrement], + ) -> VersionIncrement: """Find the highest version increment from a list of messages. This function processes a list of messages and determines the highest version @@ -76,9 +78,9 @@ def get_highest_by_messages( @staticmethod def get_highest( - increments: Iterable[VersionIncrement | None], - ) -> VersionIncrement | None: - return max(filter(None, increments), default=None) + increments: Iterable[VersionIncrement], + ) -> VersionIncrement: + return max(increments, default=VersionIncrement.NONE) class BumpRule(Protocol): @@ -94,7 +96,7 @@ class BumpRule(Protocol): def extract_increment( self, commit_message: str, major_version_zero: bool - ) -> VersionIncrement | None: + ) -> VersionIncrement: """Determine the version increment based on a commit message. This method analyzes a commit message to determine what kind of version increment @@ -107,24 +109,24 @@ def extract_increment( instead of MAJOR. This is useful for projects in 0.x.x versions. Returns: - VersionIncrement | None: The type of version increment needed: + VersionIncrement: The type of version increment needed: + - NONE: For commits that don't require a version bump (docs, style, etc.) - MAJOR: For breaking changes when major_version_zero is False - MINOR: For breaking changes when major_version_zero is True, or for new features - PATCH: For bug fixes, performance improvements, or refactors - - None: For commits that don't require a version bump (docs, style, etc.) """ class ConventionalCommitBumpRule(BumpRule): - _BREAKING_CHANGE_TYPES = set(["BREAKING CHANGE", "BREAKING-CHANGE"]) - _MINOR_CHANGE_TYPES = set(["feat"]) - _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + _BREAKING_CHANGE_TYPES = {"BREAKING CHANGE", "BREAKING-CHANGE"} + _MINOR_CHANGE_TYPES = {"feat"} + _PATCH_CHANGE_TYPES = {"fix", "perf", "refactor"} def extract_increment( self, commit_message: str, major_version_zero: bool - ) -> VersionIncrement | None: + ) -> VersionIncrement: if not (m := self._head_pattern.match(commit_message)): - return None + return VersionIncrement.NONE change_type = m.group("change_type") if m.group("bang") or change_type in self._BREAKING_CHANGE_TYPES: @@ -138,7 +140,7 @@ def extract_increment( if change_type in self._PATCH_CHANGE_TYPES: return VersionIncrement.PATCH - return None + return VersionIncrement.NONE @cached_property def _head_pattern(self) -> re.Pattern: @@ -225,9 +227,9 @@ def __init__( def extract_increment( self, commit_message: str, major_version_zero: bool - ) -> VersionIncrement | None: + ) -> VersionIncrement: if not (m := self.bump_pattern.search(commit_message)): - return None + return VersionIncrement.NONE effective_bump_map = ( self.bump_map_major_version_zero if major_version_zero else self.bump_map @@ -250,4 +252,4 @@ def extract_increment( for match_pattern, increment in effective_bump_map.items(): if re.match(match_pattern, found_keyword): return increment - return None + return VersionIncrement.NONE diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 030bf7541..845bf4f5d 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -144,7 +144,7 @@ def _is_initial_tag( ) return bool(questionary.confirm("Is this the first tag created?").ask()) - def _find_increment(self, commits: list[git.GitCommit]) -> VersionIncrement | None: + def _find_increment(self, commits: list[git.GitCommit]) -> VersionIncrement: # Update the bump map to ensure major version doesn't increment. is_major_version_zero = self.bump_settings["major_version_zero"] @@ -170,7 +170,7 @@ def __call__(self) -> None: if manual_version: for val, option in ( - (increment, "--increment"), + (increment != VersionIncrement.NONE, "--increment"), (prerelease, "--prerelease"), (devrelease is not None, "--devrelease"), (is_local_version, "--local-version"), @@ -225,7 +225,7 @@ def __call__(self) -> None: f"Invalid manual version: '{manual_version}'" ) from exc else: - if increment is None: + if increment == VersionIncrement.NONE: commits = git.get_commits(current_tag.name if current_tag else None) # No commits, there is no need to create an empty tag. @@ -243,7 +243,11 @@ def __call__(self) -> None: # It may happen that there are commits, but they are not eligible # for an increment, this generates a problem when using prerelease (#281) - if prerelease and increment is None and not current_version.is_prerelease: + if ( + prerelease + and increment == VersionIncrement.NONE + and not current_version.is_prerelease + ): raise NoCommitsFoundError( "[NO_COMMITS_FOUND]\n" "No commits found to generate a pre-release.\n" @@ -251,7 +255,7 @@ def __call__(self) -> None: ) # we create an empty PATCH increment for empty tag - if increment is None and allow_no_commit: + if increment == VersionIncrement.NONE and allow_no_commit: increment = VersionIncrement.PATCH new_version = current_version.bump( @@ -270,7 +274,10 @@ def __call__(self) -> None: ) if get_next: - if increment is None and new_tag_version == current_tag_version: + if ( + increment == VersionIncrement.NONE + and new_tag_version == current_tag_version + ): raise NoneIncrementExit( "[NO_COMMITS_TO_BUMP]\n" "The commits found are not eligible to be bumped" @@ -292,7 +299,10 @@ def __call__(self) -> None: else: out.write(information) - if increment is None and new_tag_version == current_tag_version: + if ( + increment == VersionIncrement.NONE + and new_tag_version == current_tag_version + ): raise NoneIncrementExit( "[NO_COMMITS_TO_BUMP]\nThe commits found are not eligible to be bumped" ) diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 9e982f265..0b766995a 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -143,7 +143,7 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: VersionIncrement | None, + increment: VersionIncrement, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -238,7 +238,7 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: VersionIncrement | None = None) -> str: + def increment_base(self, increment: VersionIncrement) -> str: base = dict( zip_longest( ( @@ -265,7 +265,7 @@ def increment_base(self, increment: VersionIncrement | None = None) -> str: def bump( self, - increment: VersionIncrement | None, + increment: VersionIncrement, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -307,7 +307,7 @@ def bump( ) # type: ignore[return-value] def _get_increment_base( - self, increment: VersionIncrement | None, exact_increment: bool + self, increment: VersionIncrement, exact_increment: bool ) -> str: if ( not self.is_prerelease diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index be8352fd3..da2dc2c52 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -137,17 +137,35 @@ def test_commit_with_complex_scopes(self, bump_rule): ) def test_invalid_commit_message(self, bump_rule): - assert bump_rule.extract_increment("invalid commit message", False) is None - assert bump_rule.extract_increment("", False) is None - assert bump_rule.extract_increment("feat", False) is None + assert ( + bump_rule.extract_increment("invalid commit message", False) + == VersionIncrement.NONE + ) + assert bump_rule.extract_increment("", False) == VersionIncrement.NONE + assert bump_rule.extract_increment("feat", False) == VersionIncrement.NONE def test_other_commit_types(self, bump_rule): # These commit types should not trigger any version bump - assert bump_rule.extract_increment("docs: update documentation", False) is None - assert bump_rule.extract_increment("style: format code", False) is None - assert bump_rule.extract_increment("test: add unit tests", False) is None - assert bump_rule.extract_increment("build: update build config", False) is None - assert bump_rule.extract_increment("ci: update CI pipeline", False) is None + assert ( + bump_rule.extract_increment("docs: update documentation", False) + == VersionIncrement.NONE + ) + assert ( + bump_rule.extract_increment("style: format code", False) + == VersionIncrement.NONE + ) + assert ( + bump_rule.extract_increment("test: add unit tests", False) + == VersionIncrement.NONE + ) + assert ( + bump_rule.extract_increment("build: update build config", False) + == VersionIncrement.NONE + ) + assert ( + bump_rule.extract_increment("ci: update CI pipeline", False) + == VersionIncrement.NONE + ) def test_breaking_change_with_refactor(self, bump_rule): """Test breaking changes with refactor type commit messages.""" @@ -235,14 +253,14 @@ def test_no_increment_needed(self, extract_increment): ] assert ( VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) - is None + == VersionIncrement.NONE ) def test_empty_commits(self, extract_increment): commit_messages = [] assert ( VersionIncrement.get_highest_by_messages(commit_messages, extract_increment) - is None + == VersionIncrement.NONE ) def test_major_version_zero(self): @@ -349,9 +367,13 @@ def test_major_version_zero(self, custom_bump_rule): def test_no_match(self, custom_bump_rule): assert ( - custom_bump_rule.extract_increment("feat: add new feature", False) is None + custom_bump_rule.extract_increment("feat: add new feature", False) + == VersionIncrement.NONE + ) + assert ( + custom_bump_rule.extract_increment("fix: bug fix", False) + == VersionIncrement.NONE ) - assert custom_bump_rule.extract_increment("fix: bug fix", False) is None def test_invalid_pattern(self, bump_map, bump_map_major_version_zero): with pytest.raises(NoPatternMapError): @@ -580,16 +602,23 @@ def test_commit_with_scope(self, custom_bump_rule): def test_no_match(self, custom_bump_rule): assert ( custom_bump_rule.extract_increment("docs: update documentation", False) - is None + == VersionIncrement.NONE + ) + assert ( + custom_bump_rule.extract_increment("style: format code", False) + == VersionIncrement.NONE + ) + assert ( + custom_bump_rule.extract_increment("test: add unit tests", False) + == VersionIncrement.NONE ) - assert custom_bump_rule.extract_increment("style: format code", False) is None - assert custom_bump_rule.extract_increment("test: add unit tests", False) is None assert ( custom_bump_rule.extract_increment("build: update build config", False) - is None + == VersionIncrement.NONE ) assert ( - custom_bump_rule.extract_increment("ci: update CI pipeline", False) is None + custom_bump_rule.extract_increment("ci: update CI pipeline", False) + == VersionIncrement.NONE ) def test_with_find_increment_by_callable(self, custom_bump_rule): @@ -616,20 +645,32 @@ def test_get_highest_with_major(self): assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR def test_get_highest_with_minor(self): - increments = [VersionIncrement.PATCH, VersionIncrement.MINOR, None] + increments = [ + VersionIncrement.PATCH, + VersionIncrement.MINOR, + VersionIncrement.NONE, + ] assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR def test_get_highest_with_patch(self): - increments = [VersionIncrement.PATCH, None, None] + increments = [ + VersionIncrement.PATCH, + VersionIncrement.NONE, + VersionIncrement.NONE, + ] assert VersionIncrement.get_highest(increments) == VersionIncrement.PATCH def test_get_highest_with_none(self): - increments = [None, None, None] - assert VersionIncrement.get_highest(increments) is None + increments = [ + VersionIncrement.NONE, + VersionIncrement.NONE, + VersionIncrement.NONE, + ] + assert VersionIncrement.get_highest(increments) == VersionIncrement.NONE def test_get_highest_empty(self): increments = [] - assert VersionIncrement.get_highest(increments) is None + assert VersionIncrement.get_highest(increments) == VersionIncrement.NONE def test_get_highest_mixed_order(self): increments = [ @@ -640,7 +681,12 @@ def test_get_highest_mixed_order(self): assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR def test_get_highest_with_none_values(self): - increments = [None, VersionIncrement.MINOR, None, VersionIncrement.PATCH] + increments = [ + VersionIncrement.NONE, + VersionIncrement.MINOR, + VersionIncrement.NONE, + VersionIncrement.PATCH, + ] assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR @@ -651,16 +697,18 @@ def test_safe_cast_valid_strings(self): assert VersionIncrement.safe_cast("PATCH") == VersionIncrement.PATCH def test_safe_cast_invalid_strings(self): - assert VersionIncrement.safe_cast("invalid") is None - assert VersionIncrement.safe_cast("major") is None # case sensitive - assert VersionIncrement.safe_cast("") is None + assert VersionIncrement.safe_cast("invalid") == VersionIncrement.NONE + assert ( + VersionIncrement.safe_cast("major") == VersionIncrement.NONE + ) # case sensitive + assert VersionIncrement.safe_cast("") == VersionIncrement.NONE def test_safe_cast_non_string_values(self): - assert VersionIncrement.safe_cast(None) is None - assert VersionIncrement.safe_cast(1) is None - assert VersionIncrement.safe_cast(True) is None - assert VersionIncrement.safe_cast([]) is None - assert VersionIncrement.safe_cast({}) is None + assert VersionIncrement.safe_cast(None) == VersionIncrement.NONE + assert VersionIncrement.safe_cast(1) == VersionIncrement.NONE + assert VersionIncrement.safe_cast(True) == VersionIncrement.NONE + assert VersionIncrement.safe_cast([]) == VersionIncrement.NONE + assert VersionIncrement.safe_cast({}) == VersionIncrement.NONE assert ( - VersionIncrement.safe_cast(VersionIncrement.MAJOR) is None + VersionIncrement.safe_cast(VersionIncrement.MAJOR) == VersionIncrement.NONE ) # enum value itself From 9cacfcb922c09736fcc1310473d134d6d152d4b9 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 22:13:10 +0800 Subject: [PATCH 48/51] docs(BumpRule): adjust docstring --- commitizen/bump_rule.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 7d1817682..6e5826af6 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -100,20 +100,18 @@ def extract_increment( """Determine the version increment based on a commit message. This method analyzes a commit message to determine what kind of version increment - is needed according to the Conventional Commits specification. It handles special - cases for breaking changes and respects the major_version_zero flag. + is needed. It handles special cases for breaking changes and respects the major_version_zero flag. + + See the following subclasses for more details: + - ConventionalCommitBumpRule: For conventional commits + - CustomBumpRule: For custom bump rules Args: - commit_message: The commit message to analyze. Should follow conventional commit format. - major_version_zero: If True, breaking changes will result in a MINOR version bump - instead of MAJOR. This is useful for projects in 0.x.x versions. + commit_message: The commit message to analyze. + major_version_zero: If True, breaking changes will result in a MINOR version bump instead of MAJOR Returns: VersionIncrement: The type of version increment needed: - - NONE: For commits that don't require a version bump (docs, style, etc.) - - MAJOR: For breaking changes when major_version_zero is False - - MINOR: For breaking changes when major_version_zero is True, or for new features - - PATCH: For bug fixes, performance improvements, or refactors """ From 6685282449e4b81828d5f2f03eec141b6bc6b26c Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 22:31:19 +0800 Subject: [PATCH 49/51] refactor(bump): nit --- commitizen/commands/bump.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 845bf4f5d..b6cda3a5a 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -255,8 +255,8 @@ def __call__(self) -> None: ) # we create an empty PATCH increment for empty tag - if increment == VersionIncrement.NONE and allow_no_commit: - increment = VersionIncrement.PATCH + if allow_no_commit: + increment = max(increment, VersionIncrement.PATCH) new_version = current_version.bump( increment, From b57628a62bcad16c8627c6024f39813d2b5f5b25 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 22:36:38 +0800 Subject: [PATCH 50/51] refactor(bump_rule): get rid of a simple function --- commitizen/bump_rule.py | 34 ++++++++++++------------- tests/test_bump_rule.py | 55 ----------------------------------------- 2 files changed, 17 insertions(+), 72 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 6e5826af6..76d46a919 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -70,18 +70,15 @@ def get_highest_by_messages( >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.extract_increment(x, False)) 'MINOR' """ - return VersionIncrement.get_highest( - extract_increment(line) - for message in commit_messages - for line in message.split("\n") + return max( + ( + extract_increment(line) + for message in commit_messages + for line in message.split("\n") + ), + default=VersionIncrement.NONE, ) - @staticmethod - def get_highest( - increments: Iterable[VersionIncrement], - ) -> VersionIncrement: - return max(increments, default=VersionIncrement.NONE) - class BumpRule(Protocol): """A protocol defining the interface for version bump rules. @@ -234,13 +231,16 @@ def extract_increment( ) try: - if ret := VersionIncrement.get_highest( - ( - increment - for name, increment in effective_bump_map.items() - if m.group(name) - ), - ): + if ( + ret := max( + ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ), + default=VersionIncrement.NONE, + ) + ) is not VersionIncrement.NONE: return ret except IndexError: pass diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py index da2dc2c52..f4733f1be 100644 --- a/tests/test_bump_rule.py +++ b/tests/test_bump_rule.py @@ -635,61 +635,6 @@ def test_with_find_increment_by_callable(self, custom_bump_rule): ) -class TestGetHighest: - def test_get_highest_with_major(self): - increments = [ - VersionIncrement.PATCH, - VersionIncrement.MINOR, - VersionIncrement.MAJOR, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR - - def test_get_highest_with_minor(self): - increments = [ - VersionIncrement.PATCH, - VersionIncrement.MINOR, - VersionIncrement.NONE, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR - - def test_get_highest_with_patch(self): - increments = [ - VersionIncrement.PATCH, - VersionIncrement.NONE, - VersionIncrement.NONE, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.PATCH - - def test_get_highest_with_none(self): - increments = [ - VersionIncrement.NONE, - VersionIncrement.NONE, - VersionIncrement.NONE, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.NONE - - def test_get_highest_empty(self): - increments = [] - assert VersionIncrement.get_highest(increments) == VersionIncrement.NONE - - def test_get_highest_mixed_order(self): - increments = [ - VersionIncrement.MAJOR, - VersionIncrement.PATCH, - VersionIncrement.MINOR, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.MAJOR - - def test_get_highest_with_none_values(self): - increments = [ - VersionIncrement.NONE, - VersionIncrement.MINOR, - VersionIncrement.NONE, - VersionIncrement.PATCH, - ] - assert VersionIncrement.get_highest(increments) == VersionIncrement.MINOR - - class TestSafeCast: def test_safe_cast_valid_strings(self): assert VersionIncrement.safe_cast("MAJOR") == VersionIncrement.MAJOR From 3cf6689071a000d4d295d38f0dc715ae6acda9f8 Mon Sep 17 00:00:00 2001 From: Yu-Ting Hsiung Date: Sun, 24 Aug 2025 22:43:13 +0800 Subject: [PATCH 51/51] refactor(bump_rule): nit --- commitizen/bump_rule.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py index 76d46a919..cb7649396 100644 --- a/commitizen/bump_rule.py +++ b/commitizen/bump_rule.py @@ -68,16 +68,14 @@ def get_highest_by_messages( >>> commit_messages = ["feat: new feature", "fix: bug fix"] >>> rule = ConventionalCommitBumpRule() >>> VersionIncrement.get_highest_by_messages(commit_messages, lambda x: rule.extract_increment(x, False)) - 'MINOR' + VersionIncrement.MINOR """ - return max( - ( - extract_increment(line) - for message in commit_messages - for line in message.split("\n") - ), - default=VersionIncrement.NONE, + increments = ( + extract_increment(line) + for message in commit_messages + for line in message.split("\n") ) + return max(increments, default=VersionIncrement.NONE) class BumpRule(Protocol): @@ -231,17 +229,14 @@ def extract_increment( ) try: - if ( - ret := max( - ( - increment - for name, increment in effective_bump_map.items() - if m.group(name) - ), - default=VersionIncrement.NONE, - ) - ) is not VersionIncrement.NONE: - return ret + increments = ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ) + increment = max(increments, default=VersionIncrement.NONE) + if increment != VersionIncrement.NONE: + return increment except IndexError: pass