diff --git a/commitizen/__init__.py b/commitizen/__init__.py
index f16def4441..6db9e6e7db 100644
--- a/commitizen/__init__.py
+++ b/commitizen/__init__.py
@@ -1,7 +1,7 @@
 import logging
 import logging.config
 
-from colorama import init  # type: ignore
+from colorama import init
 
 from commitizen.cz.base import BaseCommitizen
 
diff --git a/commitizen/bump.py b/commitizen/bump.py
index 76a8e15893..6d6b6dc069 100644
--- a/commitizen/bump.py
+++ b/commitizen/bump.py
@@ -3,6 +3,7 @@
 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
@@ -61,7 +62,7 @@ def find_increment(
 def update_version_in_files(
     current_version: str,
     new_version: str,
-    files: list[str],
+    files: Iterable[str],
     *,
     check_consistency: bool = False,
     encoding: str = ENCODING,
@@ -76,7 +77,7 @@ def update_version_in_files(
     """
     # TODO: separate check step and write step
     updated = []
-    for path, regex in files_and_regexs(files, current_version):
+    for path, regex in _files_and_regexes(files, current_version):
         current_version_found, version_file = _bump_with_regex(
             path,
             current_version,
@@ -99,21 +100,22 @@ def update_version_in_files(
     return updated
 
 
-def files_and_regexs(patterns: list[str], version: str) -> list[tuple[str, str]]:
+def _files_and_regexes(patterns: Iterable[str], version: str) -> list[tuple[str, str]]:
     """
     Resolve all distinct files with their regexp from a list of glob patterns with optional regexp
     """
-    out = []
+    out: set[tuple[str, str]] = set()
     for pattern in patterns:
         drive, tail = os.path.splitdrive(pattern)
         path, _, regex = tail.partition(":")
         filepath = drive + path
         if not regex:
-            regex = _version_to_regex(version)
+            regex = re.escape(version)
 
-        for path in iglob(filepath):
-            out.append((path, regex))
-    return sorted(list(set(out)))
+        for file in iglob(filepath):
+            out.add((file, regex))
+
+    return sorted(out)
 
 
 def _bump_with_regex(
@@ -128,18 +130,16 @@ def _bump_with_regex(
     pattern = re.compile(regex)
     with open(version_filepath, encoding=encoding) as f:
         for line in f:
-            if pattern.search(line):
-                bumped_line = line.replace(current_version, new_version)
-                if bumped_line != line:
-                    current_version_found = True
-                lines.append(bumped_line)
-            else:
+            if not pattern.search(line):
                 lines.append(line)
-    return current_version_found, "".join(lines)
+                continue
 
+            bumped_line = line.replace(current_version, new_version)
+            if bumped_line != line:
+                current_version_found = True
+            lines.append(bumped_line)
 
-def _version_to_regex(version: str) -> str:
-    return version.replace(".", r"\.").replace("+", r"\+")
+    return current_version_found, "".join(lines)
 
 
 def create_commit_message(
diff --git a/commitizen/changelog.py b/commitizen/changelog.py
index 704efe6071..ba6fbbc6b3 100644
--- a/commitizen/changelog.py
+++ b/commitizen/changelog.py
@@ -29,10 +29,10 @@
 
 import re
 from collections import OrderedDict, defaultdict
-from collections.abc import Iterable
+from collections.abc import Generator, Iterable, Mapping, Sequence
 from dataclasses import dataclass
 from datetime import date
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
 
 from jinja2 import (
     BaseLoader,
@@ -63,7 +63,7 @@ class Metadata:
     latest_version_position: int | None = None
     latest_version_tag: str | None = None
 
-    def __post_init__(self):
+    def __post_init__(self) -> None:
         if self.latest_version and not self.latest_version_tag:
             # Test syntactic sugar
             # latest version tag is optional if same as latest version
@@ -84,7 +84,7 @@ def generate_tree_from_commits(
     changelog_message_builder_hook: MessageBuilderHook | None = None,
     changelog_release_hook: ChangelogReleaseHook | None = None,
     rules: TagRules | None = None,
-) -> Iterable[dict]:
+) -> Generator[dict[str, Any], None, None]:
     pat = re.compile(changelog_pattern)
     map_pat = re.compile(commit_parser, re.MULTILINE)
     body_map_pat = re.compile(commit_parser, re.MULTILINE | re.DOTALL)
@@ -169,8 +169,8 @@ def process_commit_message(
     commit: GitCommit,
     changes: dict[str | None, list],
     change_type_map: dict[str, str] | None = None,
-):
-    message: dict = {
+) -> None:
+    message: dict[str, Any] = {
         "sha1": commit.rev,
         "parents": commit.parents,
         "author": commit.author,
@@ -187,24 +187,27 @@ def process_commit_message(
             changes[change_type].append(msg)
 
 
-def order_changelog_tree(tree: Iterable, change_type_order: list[str]) -> Iterable:
+def generate_ordered_changelog_tree(
+    tree: Iterable[Mapping[str, Any]], change_type_order: list[str]
+) -> Generator[dict[str, Any], None, None]:
     if len(set(change_type_order)) != len(change_type_order):
         raise InvalidConfigurationError(
-            f"Change types contain duplicates types ({change_type_order})"
+            f"Change types contain duplicated types ({change_type_order})"
         )
 
-    sorted_tree = []
     for entry in tree:
-        ordered_change_types = change_type_order + sorted(
-            set(entry["changes"].keys()) - set(change_type_order)
-        )
-        changes = [
-            (ct, entry["changes"][ct])
-            for ct in ordered_change_types
-            if ct in entry["changes"]
-        ]
-        sorted_tree.append({**entry, **{"changes": OrderedDict(changes)}})
-    return sorted_tree
+        yield {
+            **entry,
+            "changes": _calculate_sorted_changes(change_type_order, entry["changes"]),
+        }
+
+
+def _calculate_sorted_changes(
+    change_type_order: list[str], changes: Mapping[str, Any]
+) -> OrderedDict[str, Any]:
+    remaining_change_types = set(changes.keys()) - set(change_type_order)
+    sorted_change_types = change_type_order + sorted(remaining_change_types)
+    return OrderedDict((ct, changes[ct]) for ct in sorted_change_types if ct in changes)
 
 
 def get_changelog_template(loader: BaseLoader, template: str) -> Template:
@@ -222,7 +225,7 @@ def render_changelog(
     tree: Iterable,
     loader: BaseLoader,
     template: str,
-    **kwargs,
+    **kwargs: Any,
 ) -> str:
     jinja_template = get_changelog_template(loader, template)
     changelog: str = jinja_template.render(tree=tree, **kwargs)
@@ -279,7 +282,7 @@ def incremental_build(
 
 
 def get_smart_tag_range(
-    tags: list[GitTag], newest: str, oldest: str | None = None
+    tags: Sequence[GitTag], newest: str, oldest: str | None = None
 ) -> list[GitTag]:
     """Smart because it finds the N+1 tag.
 
@@ -305,10 +308,10 @@ def get_smart_tag_range(
 
 
 def get_oldest_and_newest_rev(
-    tags: list[GitTag],
+    tags: Sequence[GitTag],
     version: str,
     rules: TagRules,
-) -> tuple[str | None, str | None]:
+) -> tuple[str | None, str]:
     """Find the tags for the given version.
 
     `version` may come in different formats:
diff --git a/commitizen/changelog_formats/__init__.py b/commitizen/changelog_formats/__init__.py
index 782bfb24cb..9a5eea7ab2 100644
--- a/commitizen/changelog_formats/__init__.py
+++ b/commitizen/changelog_formats/__init__.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 import sys
-from typing import ClassVar, Protocol
+from typing import Callable, ClassVar, Protocol
 
 if sys.version_info >= (3, 10):
     from importlib import metadata
@@ -25,7 +25,7 @@ class ChangelogFormat(Protocol):
 
     config: BaseConfig
 
-    def __init__(self, config: BaseConfig):
+    def __init__(self, config: BaseConfig) -> None:
         self.config = config
 
     @property
@@ -64,10 +64,9 @@ def get_changelog_format(
     :raises FormatUnknown: if a non-empty name is provided but cannot be found in the known formats
     """
     name: str | None = config.settings.get("changelog_format")
-    format: type[ChangelogFormat] | None = guess_changelog_format(filename)
-
-    if name and name in KNOWN_CHANGELOG_FORMATS:
-        format = KNOWN_CHANGELOG_FORMATS[name]
+    format = (
+        name and KNOWN_CHANGELOG_FORMATS.get(name) or _guess_changelog_format(filename)
+    )
 
     if not format:
         raise ChangelogFormatUnknown(f"Unknown changelog format '{name}'")
@@ -75,7 +74,7 @@ def get_changelog_format(
     return format(config)
 
 
-def guess_changelog_format(filename: str | None) -> type[ChangelogFormat] | None:
+def _guess_changelog_format(filename: str | None) -> type[ChangelogFormat] | None:
     """
     Try guessing the file format from the filename.
 
@@ -91,3 +90,9 @@ def guess_changelog_format(filename: str | None) -> type[ChangelogFormat] | None
             if filename.endswith(f".{alt_extension}"):
                 return format
     return None
+
+
+def __getattr__(name: str) -> Callable[[str], type[ChangelogFormat] | None]:
+    if name == "guess_changelog_format":
+        return _guess_changelog_format
+    raise AttributeError(f"module {__name__} has no attribute {name}")
diff --git a/commitizen/changelog_formats/base.py b/commitizen/changelog_formats/base.py
index f69cf8f00f..cb5d385bf8 100644
--- a/commitizen/changelog_formats/base.py
+++ b/commitizen/changelog_formats/base.py
@@ -20,7 +20,7 @@ class BaseFormat(ChangelogFormat, metaclass=ABCMeta):
     extension: ClassVar[str] = ""
     alternative_extensions: ClassVar[set[str]] = set()
 
-    def __init__(self, config: BaseConfig):
+    def __init__(self, config: BaseConfig) -> None:
         # Constructor needs to be redefined because `Protocol` prevent instantiation by default
         # See: https://bugs.python.org/issue44807
         self.config = config
diff --git a/commitizen/cli.py b/commitizen/cli.py
index cb834c5d6f..6f7556df49 100644
--- a/commitizen/cli.py
+++ b/commitizen/cli.py
@@ -3,12 +3,11 @@
 import argparse
 import logging
 import sys
-from collections.abc import Sequence
 from copy import deepcopy
 from functools import partial
 from pathlib import Path
 from types import TracebackType
-from typing import Any
+from typing import TYPE_CHECKING, cast
 
 import argcomplete
 from decli import cli
@@ -48,17 +47,17 @@ def __call__(
         self,
         parser: argparse.ArgumentParser,
         namespace: argparse.Namespace,
-        kwarg: str | Sequence[Any] | None,
+        values: object,
         option_string: str | None = None,
-    ):
-        if not isinstance(kwarg, str):
+    ) -> None:
+        if not isinstance(values, str):
             return
-        if "=" not in kwarg:
+        if "=" not in values:
             raise InvalidCommandArgumentError(
                 f"Option {option_string} expect a key=value format"
             )
         kwargs = getattr(namespace, self.dest, None) or {}
-        key, value = kwarg.split("=", 1)
+        key, value = values.split("=", 1)
         if not key:
             raise InvalidCommandArgumentError(
                 f"Option {option_string} expect a key=value format"
@@ -148,7 +147,7 @@ def __call__(
                     {
                         "name": ["-s", "--signoff"],
                         "action": "store_true",
-                        "help": "sign off the commit",
+                        "help": "Deprecated, use 'cz commit -- -s' instead",
                     },
                     {
                         "name": ["-a", "--all"],
@@ -348,7 +347,7 @@ def __call__(
                     },
                     {
                         "name": ["--version-type"],
-                        "help": "Deprecated, use --version-scheme",
+                        "help": "Deprecated, use --version-scheme instead",
                         "default": None,
                         "choices": version_schemes.KNOWN_SCHEMES,
                     },
@@ -550,22 +549,27 @@ def __call__(
 
 
 def commitizen_excepthook(
-    type, value, traceback, debug=False, no_raise: list[int] | None = None
-):
+    type: type[BaseException],
+    value: BaseException,
+    traceback: TracebackType | None,
+    debug: bool = False,
+    no_raise: list[int] | None = None,
+) -> None:
     traceback = traceback if isinstance(traceback, TracebackType) else None
+    if not isinstance(value, CommitizenException):
+        original_excepthook(type, value, traceback)
+        return
+
     if not no_raise:
         no_raise = []
-    if isinstance(value, CommitizenException):
-        if value.message:
-            value.output_method(value.message)
-        if debug:
-            original_excepthook(type, value, traceback)
-        exit_code = value.exit_code
-        if exit_code in no_raise:
-            exit_code = ExitCode.EXPECTED_EXIT
-        sys.exit(exit_code)
-    else:
+    if value.message:
+        value.output_method(value.message)
+    if debug:
         original_excepthook(type, value, traceback)
+    exit_code = value.exit_code
+    if exit_code in no_raise:
+        exit_code = ExitCode.EXPECTED_EXIT
+    sys.exit(exit_code)
 
 
 commitizen_debug_excepthook = partial(commitizen_excepthook, debug=True)
@@ -580,7 +584,7 @@ def parse_no_raise(comma_separated_no_raise: str) -> list[int]:
     represents the exit code found in exceptions.
     """
     no_raise_items: list[str] = comma_separated_no_raise.split(",")
-    no_raise_codes = []
+    no_raise_codes: list[int] = []
     for item in no_raise_items:
         if item.isdecimal():
             no_raise_codes.append(int(item))
@@ -595,8 +599,33 @@ def parse_no_raise(comma_separated_no_raise: str) -> list[int]:
     return no_raise_codes
 
 
-def main():
-    parser = cli(data)
+if TYPE_CHECKING:
+
+    class Args(argparse.Namespace):
+        config: str | None = None
+        debug: bool = False
+        name: str | None = None
+        no_raise: str | None = None  # comma-separated string, later parsed as list[int]
+        report: bool = False
+        project: bool = False
+        commitizen: bool = False
+        verbose: bool = False
+        func: type[
+            commands.Init  # init
+            | commands.Commit  # commit (c)
+            | commands.ListCz  # ls
+            | commands.Example  # example
+            | commands.Info  # info
+            | commands.Schema  # schema
+            | commands.Bump  # bump
+            | commands.Changelog  # changelog (ch)
+            | commands.Check  # check
+            | commands.Version  # version
+        ]
+
+
+def main() -> None:
+    parser: argparse.ArgumentParser = cli(data)
     argcomplete.autocomplete(parser)
     # Show help if no arg provided
     if len(sys.argv) == 1:
@@ -606,11 +635,8 @@ def main():
     # This is for the command required constraint in 2.0
     try:
         args, unknown_args = parser.parse_known_args()
-    except (TypeError, SystemExit) as e:
-        # https://github.com/commitizen-tools/commitizen/issues/429
-        # argparse raises TypeError when non exist command is provided on Python < 3.9
-        # but raise SystemExit with exit code == 2 on Python 3.9
-        if isinstance(e, TypeError) or (isinstance(e, SystemExit) and e.code == 2):
+    except SystemExit as e:
+        if e.code == 2:
             raise NoCommandFoundError()
         raise e
 
@@ -636,14 +662,11 @@ def main():
         extra_args = " ".join(unknown_args[1:])
         arguments["extra_cli_args"] = extra_args
 
-    if args.config:
-        conf = config.read_cfg(args.config)
-    else:
-        conf = config.read_cfg()
-
+    conf = config.read_cfg(args.config)
+    args = cast("Args", args)
     if args.name:
         conf.update({"name": args.name})
-    elif not args.name and not conf.path:
+    elif not conf.path:
         conf.update({"name": "cz_conventional_commits"})
 
     if args.debug:
@@ -656,7 +679,7 @@ def main():
         )
         sys.excepthook = no_raise_debug_excepthook
 
-    args.func(conf, arguments)()
+    args.func(conf, arguments)()  # type: ignore[arg-type]
 
 
 if __name__ == "__main__":
diff --git a/commitizen/cmd.py b/commitizen/cmd.py
index ba48ac7881..3f13087233 100644
--- a/commitizen/cmd.py
+++ b/commitizen/cmd.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 import os
 import subprocess
+from collections.abc import Mapping
 from typing import NamedTuple
 
 from charset_normalizer import from_bytes
@@ -28,7 +31,7 @@ def _try_decode(bytes_: bytes) -> str:
             raise CharacterSetDecodeError() from e
 
 
-def run(cmd: str, env=None) -> Command:
+def run(cmd: str, env: Mapping[str, str] | None = None) -> Command:
     if env is not None:
         env = {**os.environ, **env}
     process = subprocess.Popen(
diff --git a/commitizen/commands/__init__.py b/commitizen/commands/__init__.py
index 806e384522..58b18297dc 100644
--- a/commitizen/commands/__init__.py
+++ b/commitizen/commands/__init__.py
@@ -11,13 +11,13 @@
 
 __all__ = (
     "Bump",
+    "Changelog",
     "Check",
     "Commit",
-    "Changelog",
     "Example",
     "Info",
+    "Init",
     "ListCz",
     "Schema",
     "Version",
-    "Init",
 )
diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py
index 0a2bbe37fc..2a84483c94 100644
--- a/commitizen/commands/bump.py
+++ b/commitizen/commands/bump.py
@@ -37,37 +37,63 @@
 logger = getLogger("commitizen")
 
 
+class BumpArgs(Settings, total=False):
+    allow_no_commit: bool | None
+    annotated_tag_message: str | None
+    build_metadata: str | None
+    changelog_to_stdout: bool
+    changelog: bool
+    check_consistency: bool
+    devrelease: int | None
+    dry_run: bool
+    file_name: str
+    files_only: bool | None
+    get_next: bool
+    git_output_to_stderr: bool
+    increment_mode: str
+    increment: Increment | None
+    local_version: bool
+    manual_version: str | None
+    no_verify: bool
+    prerelease: Prerelease | None
+    retry: bool
+    yes: bool
+
+
 class Bump:
     """Show prompt for the user to create a guided commit."""
 
-    def __init__(self, config: BaseConfig, arguments: dict):
+    def __init__(self, config: BaseConfig, arguments: BumpArgs) -> None:
         if not git.is_git_project():
             raise NotAGitProjectError()
 
         self.config: BaseConfig = config
         self.encoding = config.settings["encoding"]
-        self.arguments: dict = arguments
-        self.bump_settings: dict = {
-            **config.settings,
-            **{
-                key: arguments[key]
-                for key in [
-                    "tag_format",
-                    "prerelease",
-                    "increment",
-                    "increment_mode",
-                    "bump_message",
-                    "gpg_sign",
-                    "annotated_tag",
-                    "annotated_tag_message",
-                    "major_version_zero",
-                    "prerelease_offset",
-                    "template",
-                    "file_name",
-                ]
-                if arguments[key] is not None
+        self.arguments = arguments
+        self.bump_settings = cast(
+            BumpArgs,
+            {
+                **config.settings,
+                **{
+                    k: v
+                    for k in (
+                        "annotated_tag_message",
+                        "annotated_tag",
+                        "bump_message",
+                        "file_name",
+                        "gpg_sign",
+                        "increment_mode",
+                        "increment",
+                        "major_version_zero",
+                        "prerelease_offset",
+                        "prerelease",
+                        "tag_format",
+                        "template",
+                    )
+                    if (v := arguments.get(k)) is not None
+                },
             },
-        }
+        )
         self.cz = factory.committer_factory(self.config)
         self.changelog_flag = arguments["changelog"]
         self.changelog_config = self.config.settings.get("update_changelog_on_bump")
@@ -82,7 +108,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
         if deprecated_version_type:
             warnings.warn(
                 DeprecationWarning(
-                    "`--version-type` parameter is deprecated and will be removed in commitizen 4. "
+                    "`--version-type` parameter is deprecated and will be removed in v5. "
                     "Please use `--version-scheme` instead"
                 )
             )
@@ -101,31 +127,29 @@ def __init__(self, config: BaseConfig, arguments: dict):
         )
         self.extras = arguments["extras"]
 
-    def is_initial_tag(
+    def _is_initial_tag(
         self, current_tag: git.GitTag | None, is_yes: bool = False
     ) -> bool:
         """Check if reading the whole git tree up to HEAD is needed."""
-        is_initial = False
-        if not current_tag:
-            if is_yes:
-                is_initial = True
-            else:
-                out.info("No tag matching configuration could not be found.")
-                out.info(
-                    "Possible causes:\n"
-                    "- version in configuration is not the current version\n"
-                    "- tag_format or legacy_tag_formats is missing, check them using 'git tag --list'\n"
-                )
-                is_initial = questionary.confirm("Is this the first tag created?").ask()
-        return is_initial
+        if current_tag:
+            return False
+        if is_yes:
+            return True
+
+        out.info("No tag matching configuration could be found.")
+        out.info(
+            "Possible causes:\n"
+            "- version in configuration is not the current version\n"
+            "- tag_format or legacy_tag_formats is missing, check them using 'git tag --list'\n"
+        )
+        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]) -> Increment | None:
         # Update the bump map to ensure major version doesn't increment.
-        is_major_version_zero: bool = self.bump_settings["major_version_zero"]
         # self.cz.bump_map = defaults.bump_map_major_version_zero
         bump_map = (
             self.cz.bump_map_major_version_zero
-            if is_major_version_zero
+            if self.bump_settings["major_version_zero"]
             else self.cz.bump_map
         )
         bump_pattern = self.cz.bump_pattern
@@ -134,12 +158,9 @@ def find_increment(self, commits: list[git.GitCommit]) -> Increment | None:
             raise NoPatternMapError(
                 f"'{self.config.settings['name']}' rule does not support bump"
             )
-        increment = bump.find_increment(
-            commits, regex=bump_pattern, increments_map=bump_map
-        )
-        return increment
+        return bump.find_increment(commits, regex=bump_pattern, increments_map=bump_map)
 
-    def __call__(self) -> None:  # noqa: C901
+    def __call__(self) -> None:
         """Steps executed to bump."""
         provider = get_provider(self.config)
 
@@ -148,23 +169,14 @@ def __call__(self) -> None:  # noqa: C901
         except TypeError:
             raise NoVersionSpecifiedError()
 
-        bump_commit_message: str = self.bump_settings["bump_message"]
-        version_files: list[str] = self.bump_settings["version_files"]
-        major_version_zero: bool = self.bump_settings["major_version_zero"]
-        prerelease_offset: int = self.bump_settings["prerelease_offset"]
-
-        dry_run: bool = self.arguments["dry_run"]
-        is_yes: bool = self.arguments["yes"]
-        increment: Increment | None = self.arguments["increment"]
-        prerelease: Prerelease | None = self.arguments["prerelease"]
-        devrelease: int | None = self.arguments["devrelease"]
-        is_files_only: bool | None = self.arguments["files_only"]
-        is_local_version: bool = self.arguments["local_version"]
+        increment = self.arguments["increment"]
+        prerelease = self.arguments["prerelease"]
+        devrelease = self.arguments["devrelease"]
+        is_local_version = self.arguments["local_version"]
         manual_version = self.arguments["manual_version"]
         build_metadata = self.arguments["build_metadata"]
-        increment_mode: str = self.arguments["increment_mode"]
-        get_next: bool = self.arguments["get_next"]
-        allow_no_commit: bool | None = self.arguments["allow_no_commit"]
+        get_next = self.arguments["get_next"]
+        allow_no_commit = self.arguments["allow_no_commit"]
 
         if manual_version:
             if increment:
@@ -186,7 +198,7 @@ def __call__(self) -> None:  # noqa: C901
                     "--build-metadata cannot be combined with MANUAL_VERSION"
                 )
 
-            if major_version_zero:
+            if self.bump_settings["major_version_zero"]:
                 raise NotAllowed(
                     "--major-version-zero cannot be combined with MANUAL_VERSION"
                 )
@@ -194,21 +206,17 @@ def __call__(self) -> None:  # noqa: C901
             if get_next:
                 raise NotAllowed("--get-next cannot be combined with MANUAL_VERSION")
 
-        if major_version_zero:
-            if not current_version.release[0] == 0:
-                raise NotAllowed(
-                    f"--major-version-zero is meaningless for current version {current_version}"
-                )
+        if self.bump_settings["major_version_zero"] and current_version.release[0]:
+            raise NotAllowed(
+                f"--major-version-zero is meaningless for current version {current_version}"
+            )
 
-        if build_metadata:
-            if is_local_version:
-                raise NotAllowed(
-                    "--local-version cannot be combined with --build-metadata"
-                )
+        if build_metadata and is_local_version:
+            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 bool(self.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"
                 )
@@ -219,10 +227,8 @@ def __call__(self) -> None:  # noqa: C901
         else:
             # If user specified changelog_to_stdout, they probably want the
             # changelog to be generated as well, this is the most intuitive solution
-            self.changelog_flag = (
-                self.changelog_flag
-                or bool(self.changelog_to_stdout)
-                or self.changelog_config
+            self.changelog_flag = any(
+                (self.changelog_flag, self.changelog_to_stdout, self.changelog_config)
             )
 
         rules = TagRules.from_settings(cast(Settings, self.bump_settings))
@@ -231,7 +237,7 @@ def __call__(self) -> None:  # noqa: C901
             current_tag, "name", rules.normalize_tag(current_version)
         )
 
-        is_initial = self.is_initial_tag(current_tag, is_yes)
+        is_initial = self._is_initial_tag(current_tag, self.arguments["yes"])
 
         if manual_version:
             try:
@@ -243,10 +249,7 @@ def __call__(self) -> None:  # noqa: C901
                 ) from exc
         else:
             if increment is None:
-                if current_tag:
-                    commits = git.get_commits(current_tag.name)
-                else:
-                    commits = git.get_commits()
+                commits = git.get_commits(current_tag.name if current_tag else None)
 
                 # No commits, there is no need to create an empty tag.
                 # Unless we previously had a prerelease.
@@ -259,7 +262,7 @@ def __call__(self) -> None:  # noqa: C901
                         "[NO_COMMITS_FOUND]\nNo new commits found."
                     )
 
-                increment = self.find_increment(commits)
+                increment = self._find_increment(commits)
 
             # It may happen that there are commits, but they are not eligible
             # for an increment, this generates a problem when using prerelease (#281)
@@ -277,16 +280,16 @@ def __call__(self) -> None:  # noqa: C901
             new_version = current_version.bump(
                 increment,
                 prerelease=prerelease,
-                prerelease_offset=prerelease_offset,
+                prerelease_offset=self.bump_settings["prerelease_offset"],
                 devrelease=devrelease,
                 is_local_version=is_local_version,
                 build_metadata=build_metadata,
-                exact_increment=increment_mode == "exact",
+                exact_increment=self.arguments["increment_mode"] == "exact",
             )
 
         new_tag_version = rules.normalize_tag(new_version)
         message = bump.create_commit_message(
-            current_version, new_version, bump_commit_message
+            current_version, new_version, self.bump_settings["bump_message"]
         )
 
         if get_next:
@@ -318,6 +321,7 @@ def __call__(self) -> None:  # noqa: C901
             )
 
         files: list[str] = []
+        dry_run = self.arguments["dry_run"]
         if self.changelog_flag:
             args = {
                 "unreleased_version": new_tag_version,
@@ -327,14 +331,14 @@ def __call__(self) -> None:  # noqa: C901
                 "dry_run": dry_run,
             }
             if self.changelog_to_stdout:
-                changelog_cmd = Changelog(self.config, {**args, "dry_run": True})
+                changelog_cmd = Changelog(self.config, {**args, "dry_run": True})  # type: ignore[typeddict-item]
                 try:
                     changelog_cmd()
                 except DryRunExit:
                     pass
 
             args["file_name"] = self.file_name
-            changelog_cmd = Changelog(self.config, args)
+            changelog_cmd = Changelog(self.config, args)  # type: ignore[arg-type]
             changelog_cmd()
             files.append(changelog_cmd.file_name)
 
@@ -346,7 +350,7 @@ def __call__(self) -> None:  # noqa: C901
             bump.update_version_in_files(
                 str(current_version),
                 str(new_version),
-                version_files,
+                self.bump_settings["version_files"],
                 check_consistency=self.check_consistency,
                 encoding=self.encoding,
             )
@@ -370,7 +374,7 @@ def __call__(self) -> None:  # noqa: C901
                 else None,
             )
 
-        if is_files_only:
+        if self.arguments["files_only"]:
             raise ExpectedExit()
 
         # FIXME: check if any changes have been staged
@@ -386,24 +390,26 @@ def __call__(self) -> None:  # noqa: C901
             err = c.err.strip() or c.out
             raise BumpCommitFailedError(f'2nd git.commit error: "{err}"')
 
-        if c.out:
-            if self.git_output_to_stderr:
-                out.diagnostic(c.out)
-            else:
-                out.write(c.out)
-        if c.err:
-            if self.git_output_to_stderr:
-                out.diagnostic(c.err)
-            else:
-                out.write(c.err)
+        for msg in (c.out, c.err):
+            if msg:
+                out_func = out.diagnostic if self.git_output_to_stderr else out.write
+                out_func(msg)
 
         c = git.tag(
             new_tag_version,
-            signed=self.bump_settings.get("gpg_sign", False)
-            or bool(self.config.settings.get("gpg_sign", False)),
-            annotated=self.bump_settings.get("annotated_tag", False)
-            or bool(self.config.settings.get("annotated_tag", False))
-            or bool(self.bump_settings.get("annotated_tag_message", False)),
+            signed=any(
+                (
+                    self.bump_settings.get("gpg_sign"),
+                    self.config.settings.get("gpg_sign"),
+                )
+            ),
+            annotated=any(
+                (
+                    self.bump_settings.get("annotated_tag"),
+                    self.config.settings.get("annotated_tag"),
+                    self.bump_settings.get("annotated_tag_message"),
+                )
+            ),
             msg=self.bump_settings.get("annotated_tag_message", None),
             # TODO: also get from self.config.settings?
         )
diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py
index 0e4efabfa1..dc50eb3ad2 100644
--- a/commitizen/commands/changelog.py
+++ b/commitizen/commands/changelog.py
@@ -2,10 +2,11 @@
 
 import os
 import os.path
+from collections.abc import Generator, Iterable
 from difflib import SequenceMatcher
 from operator import itemgetter
 from pathlib import Path
-from typing import Callable, cast
+from typing import Any, TypedDict, cast
 
 from commitizen import changelog, defaults, factory, git, out
 from commitizen.changelog_formats import get_changelog_format
@@ -25,16 +26,35 @@
 from commitizen.version_schemes import get_version_scheme
 
 
+class ChangelogArgs(TypedDict, total=False):
+    change_type_map: dict[str, str]
+    change_type_order: list[str]
+    current_version: str
+    dry_run: bool
+    file_name: str
+    incremental: bool
+    merge_prerelease: bool
+    rev_range: str
+    start_rev: str
+    tag_format: str
+    unreleased_version: str | None
+    version_scheme: str
+    template: str
+    extras: dict[str, Any]
+    export_template: str
+
+
 class Changelog:
     """Generate a changelog based on the commit history."""
 
-    def __init__(self, config: BaseConfig, args):
+    def __init__(self, config: BaseConfig, arguments: ChangelogArgs) -> None:
         if not git.is_git_project():
             raise NotAGitProjectError()
 
-        self.config: BaseConfig = config
-        changelog_file_name = args.get("file_name") or cast(
-            str, self.config.settings.get("changelog_file")
+        self.config = config
+
+        changelog_file_name = arguments.get("file_name") or self.config.settings.get(
+            "changelog_file"
         )
         if not isinstance(changelog_file_name, str):
             raise NotAllowed(
@@ -51,57 +71,61 @@ def __init__(self, config: BaseConfig, args):
         self.encoding = self.config.settings["encoding"]
         self.cz = factory.committer_factory(self.config)
 
-        self.start_rev = args.get("start_rev") or self.config.settings.get(
+        self.start_rev = arguments.get("start_rev") or self.config.settings.get(
             "changelog_start_rev"
         )
 
         self.changelog_format = get_changelog_format(self.config, self.file_name)
 
-        self.incremental = args["incremental"] or self.config.settings.get(
-            "changelog_incremental"
+        self.incremental = bool(
+            arguments.get("incremental")
+            or self.config.settings.get("changelog_incremental")
         )
-        self.dry_run = args["dry_run"]
+        self.dry_run = bool(arguments.get("dry_run"))
 
         self.scheme = get_version_scheme(
-            self.config.settings, args.get("version_scheme")
+            self.config.settings, arguments.get("version_scheme")
         )
 
         current_version = (
-            args.get("current_version", config.settings.get("version")) or ""
+            arguments.get("current_version")
+            or self.config.settings.get("version")
+            or ""
         )
         self.current_version = self.scheme(current_version) if current_version else None
 
-        self.unreleased_version = args["unreleased_version"]
+        self.unreleased_version = arguments["unreleased_version"]
         self.change_type_map = (
             self.config.settings.get("change_type_map") or self.cz.change_type_map
         )
-        self.change_type_order = (
+        self.change_type_order = cast(
+            list[str],
             self.config.settings.get("change_type_order")
             or self.cz.change_type_order
-            or defaults.CHANGE_TYPE_ORDER
+            or defaults.CHANGE_TYPE_ORDER,
         )
-        self.rev_range = args.get("rev_range")
-        self.tag_format: str = (
-            args.get("tag_format") or self.config.settings["tag_format"]
+        self.rev_range = arguments.get("rev_range")
+        self.tag_format = (
+            arguments.get("tag_format") or self.config.settings["tag_format"]
         )
         self.tag_rules = TagRules(
             scheme=self.scheme,
             tag_format=self.tag_format,
             legacy_tag_formats=self.config.settings["legacy_tag_formats"],
             ignored_tag_formats=self.config.settings["ignored_tag_formats"],
-            merge_prereleases=args.get("merge_prerelease")
+            merge_prereleases=arguments.get("merge_prerelease")
             or self.config.settings["changelog_merge_prerelease"],
         )
 
         self.template = (
-            args.get("template")
+            arguments.get("template")
             or self.config.settings.get("template")
             or self.changelog_format.template
         )
-        self.extras = args.get("extras") or {}
-        self.export_template_to = args.get("export_template")
+        self.extras = arguments.get("extras") or {}
+        self.export_template_to = arguments.get("export_template")
 
-    def _find_incremental_rev(self, latest_version: str, tags: list[GitTag]) -> str:
+    def _find_incremental_rev(self, latest_version: str, tags: Iterable[GitTag]) -> str:
         """Try to find the 'start_rev'.
 
         We use a similarity approach. We know how to parse the version from the markdown
@@ -114,28 +138,28 @@ def _find_incremental_rev(self, latest_version: str, tags: list[GitTag]) -> str:
         on our experience.
         """
         SIMILARITY_THRESHOLD = 0.89
-        tag_ratio = map(
-            lambda tag: (
-                SequenceMatcher(
+        scores_and_tag_names: Generator[tuple[float, str]] = (
+            (
+                score,
+                tag.name,
+            )
+            for tag in tags
+            if (
+                score := SequenceMatcher(
                     None, latest_version, strip_local_version(tag.name)
-                ).ratio(),
-                tag,
-            ),
-            tags,
+                ).ratio()
+            )
+            >= SIMILARITY_THRESHOLD
         )
         try:
-            score, tag = max(tag_ratio, key=itemgetter(0))
+            _, start_rev = max(scores_and_tag_names, key=itemgetter(0))
         except ValueError:
             raise NoRevisionError()
-        if score < SIMILARITY_THRESHOLD:
-            raise NoRevisionError()
-        start_rev = tag.name
         return start_rev
 
-    def write_changelog(
+    def _write_changelog(
         self, changelog_out: str, lines: list[str], changelog_meta: changelog.Metadata
-    ):
-        changelog_hook: Callable | None = self.cz.changelog_hook
+    ) -> None:
         with smart_open(self.file_name, "w", encoding=self.encoding) as changelog_file:
             partial_changelog: str | None = None
             if self.incremental:
@@ -145,23 +169,24 @@ def write_changelog(
                 changelog_out = "".join(new_lines)
                 partial_changelog = changelog_out
 
-            if changelog_hook:
-                changelog_out = changelog_hook(changelog_out, partial_changelog)
+            if self.cz.changelog_hook:
+                changelog_out = self.cz.changelog_hook(changelog_out, partial_changelog)
 
             changelog_file.write(changelog_out)
 
-    def export_template(self):
+    def _export_template(self) -> None:
         tpl = changelog.get_changelog_template(self.cz.template_loader, self.template)
-        src = Path(tpl.filename)
-        Path(self.export_template_to).write_text(src.read_text())
+        # 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 __call__(self):
+    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 | None = self.change_type_map
+        change_type_map: dict[str, str] | None = self.change_type_map
         changelog_message_builder_hook: MessageBuilderHook | None = (
             self.cz.changelog_message_builder_hook
         )
@@ -170,7 +195,7 @@ def __call__(self):
         )
 
         if self.export_template_to:
-            return self.export_template()
+            return self._export_template()
 
         if not changelog_pattern or not commit_parser:
             raise NoPatternMapError(
@@ -189,7 +214,7 @@ def __call__(self):
             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), tags
+                    strip_local_version(changelog_meta.latest_version_tag or ""), tags
                 )
         if self.rev_range:
             start_rev, end_rev = changelog.get_oldest_and_newest_rev(
@@ -214,21 +239,21 @@ def __call__(self):
             rules=self.tag_rules,
         )
         if self.change_type_order:
-            tree = changelog.order_changelog_tree(tree, self.change_type_order)
+            tree = changelog.generate_ordered_changelog_tree(
+                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
-        )
-        changelog_out = changelog_out.lstrip("\n")
+        ).lstrip("\n")
 
         # Dry_run is executed here to avoid checking and reading the files
         if self.dry_run:
-            changelog_hook: Callable | None = self.cz.changelog_hook
-            if changelog_hook:
-                changelog_out = changelog_hook(changelog_out, "")
+            if self.cz.changelog_hook:
+                changelog_out = self.cz.changelog_hook(changelog_out, "")
             out.write(changelog_out)
             raise DryRunExit()
 
@@ -237,4 +262,4 @@ def __call__(self):
             with open(self.file_name, encoding=self.encoding) as changelog_file:
                 lines = changelog_file.readlines()
 
-        self.write_changelog(changelog_out, lines, changelog_meta)
+        self._write_changelog(changelog_out, lines, changelog_meta)
diff --git a/commitizen/commands/check.py b/commitizen/commands/check.py
index 1e3b8464e1..69147bcfbe 100644
--- a/commitizen/commands/check.py
+++ b/commitizen/commands/check.py
@@ -1,9 +1,8 @@
 from __future__ import annotations
 
-import os
 import re
 import sys
-from typing import Any
+from typing import TypedDict
 
 from commitizen import factory, git, out
 from commitizen.config import BaseConfig
@@ -14,10 +13,20 @@
 )
 
 
+class CheckArgs(TypedDict, total=False):
+    commit_msg_file: str
+    commit_msg: str
+    rev_range: str
+    allow_abort: bool
+    message_length_limit: int
+    allowed_prefixes: list[str]
+    message: str
+
+
 class Check:
     """Check if the current commit msg matches the commitizen format."""
 
-    def __init__(self, config: BaseConfig, arguments: dict[str, Any], cwd=os.getcwd()):
+    def __init__(self, config: BaseConfig, arguments: CheckArgs, *args: object) -> None:
         """Initial check command.
 
         Args:
@@ -25,16 +34,15 @@ def __init__(self, config: BaseConfig, arguments: dict[str, Any], cwd=os.getcwd(
             arguments: All the flags provided by the user
             cwd: Current work directory
         """
-        self.commit_msg_file: str | None = arguments.get("commit_msg_file")
-        self.commit_msg: str | None = arguments.get("message")
-        self.rev_range: str | None = arguments.get("rev_range")
-        self.allow_abort: bool = bool(
+        self.commit_msg_file = arguments.get("commit_msg_file")
+        self.commit_msg = arguments.get("message")
+        self.rev_range = arguments.get("rev_range")
+        self.allow_abort = bool(
             arguments.get("allow_abort", config.settings["allow_abort"])
         )
-        self.max_msg_length: int = arguments.get("message_length_limit", 0)
+        self.max_msg_length = arguments.get("message_length_limit", 0)
 
         # we need to distinguish between None and [], which is a valid value
-
         allowed_prefixes = arguments.get("allowed_prefixes")
         self.allowed_prefixes: list[str] = (
             allowed_prefixes
@@ -48,7 +56,7 @@ def __init__(self, config: BaseConfig, arguments: dict[str, Any], cwd=os.getcwd(
         self.encoding = config.settings["encoding"]
         self.cz = factory.committer_factory(self.config)
 
-    def _valid_command_argument(self):
+    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)
@@ -61,7 +69,7 @@ def _valid_command_argument(self):
                 "See 'cz check -h' for more information"
             )
 
-    def __call__(self):
+    def __call__(self) -> None:
         """Validate if commit messages follows the conventional pattern.
 
         Raises:
@@ -71,20 +79,19 @@ def __call__(self):
         if not commits:
             raise NoCommitsFoundError(f"No commit found with range: '{self.rev_range}'")
 
-        pattern = self.cz.schema_pattern()
-        displayed_msgs_content = "\n".join(
-            [
-                f'commit "{commit.rev}": "{commit.message}"'
-                for commit in commits
-                if not self.validate_commit_message(commit.message, pattern)
-            ]
+        pattern = re.compile(self.cz.schema_pattern())
+        invalid_msgs_content = "\n".join(
+            f'commit "{commit.rev}": "{commit.message}"'
+            for commit in commits
+            if not self._validate_commit_message(commit.message, pattern)
         )
-        if displayed_msgs_content:
+        if invalid_msgs_content:
+            # TODO: capitalize the first letter of the error message for consistency in v5
             raise InvalidCommitMessageError(
                 "commit validation: failed!\n"
                 "please enter a commit message in the commitizen format.\n"
-                f"{displayed_msgs_content}\n"
-                f"pattern: {pattern}"
+                f"{invalid_msgs_content}\n"
+                f"pattern: {pattern.pattern}"
             )
         out.success("Commit validation: successful!")
 
@@ -97,12 +104,12 @@ def _get_commit_message(self) -> str | None:
             # Get commit message from file (--commit-msg-file)
             return commit_file.read()
 
-    def _get_commits(self):
+    def _get_commits(self) -> list[git.GitCommit]:
         if (msg := self._get_commit_message()) is not None:
             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 or "HEAD")
+        return git.get_commits(end=self.rev_range)
 
     @staticmethod
     def _filter_comments(msg: str) -> str:
@@ -135,14 +142,18 @@ def _filter_comments(msg: str) -> str:
                 lines.append(line)
         return "\n".join(lines)
 
-    def validate_commit_message(self, commit_msg: str, pattern: str) -> bool:
+    def _validate_commit_message(
+        self, commit_msg: str, pattern: re.Pattern[str]
+    ) -> bool:
         if not commit_msg:
             return self.allow_abort
 
         if any(map(commit_msg.startswith, self.allowed_prefixes)):
             return True
+
         if self.max_msg_length:
             msg_len = len(commit_msg.partition("\n")[0].strip())
             if msg_len > self.max_msg_length:
                 return False
-        return bool(re.match(pattern, commit_msg))
+
+        return bool(pattern.match(commit_msg))
diff --git a/commitizen/commands/commit.py b/commitizen/commands/commit.py
index cb34c41a50..19bb72fb00 100644
--- a/commitizen/commands/commit.py
+++ b/commitizen/commands/commit.py
@@ -5,6 +5,8 @@
 import shutil
 import subprocess
 import tempfile
+from pathlib import Path
+from typing import TypedDict
 
 import questionary
 
@@ -26,10 +28,22 @@
 from commitizen.git import smart_open
 
 
+class CommitArgs(TypedDict, total=False):
+    all: bool
+    dry_run: bool
+    edit: bool
+    extra_cli_args: str
+    message_length_limit: int
+    no_retry: bool
+    signoff: bool
+    write_message_to_file: Path | None
+    retry: bool
+
+
 class Commit:
     """Show prompt for the user to create a guided commit."""
 
-    def __init__(self, config: BaseConfig, arguments: dict):
+    def __init__(self, config: BaseConfig, arguments: CommitArgs) -> None:
         if not git.is_git_project():
             raise NotAGitProjectError()
 
@@ -39,7 +53,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
         self.arguments = arguments
         self.temp_file: str = get_backup_file_path()
 
-    def read_backup_message(self) -> str | None:
+    def _read_backup_message(self) -> str | None:
         # Check the commit backup file exists
         if not os.path.isfile(self.temp_file):
             return None
@@ -48,11 +62,11 @@ def read_backup_message(self) -> str | None:
         with open(self.temp_file, encoding=self.encoding) as f:
             return f.read().strip()
 
-    def prompt_commit_questions(self) -> str:
+    def _prompt_commit_questions(self) -> str:
         # Prompt user for the commit message
         cz = self.cz
         questions = cz.questions()
-        for question in filter(lambda q: q["type"] == "list", questions):
+        for question in (q for q in questions if q["type"] == "list"):
             question["use_shortcuts"] = self.config.settings["use_shortcuts"]
         try:
             answers = questionary.prompt(questions, style=cz.style)
@@ -67,7 +81,7 @@ def prompt_commit_questions(self) -> str:
 
         message = cz.message(answers)
         message_len = len(message.partition("\n")[0].strip())
-        message_length_limit: int = self.arguments.get("message_length_limit", 0)
+        message_length_limit = self.arguments.get("message_length_limit", 0)
         if 0 < message_length_limit < message_len:
             raise CommitMessageLengthExceededError(
                 f"Length of commit message exceeds limit ({message_len}/{message_length_limit})"
@@ -92,41 +106,41 @@ def manual_edit(self, message: str) -> str:
         os.unlink(file.name)
         return message
 
-    def __call__(self):
-        extra_args: str = self.arguments.get("extra_cli_args", "")
+    def _get_message(self) -> str:
+        if self.arguments.get("retry"):
+            m = self._read_backup_message()
+            if m is None:
+                raise NoCommitBackupError()
+            return m
+
+        if self.config.settings.get("retry_after_failure") and not self.arguments.get(
+            "no_retry"
+        ):
+            return self._read_backup_message() or self._prompt_commit_questions()
+        return self._prompt_commit_questions()
 
-        allow_empty: bool = "--allow-empty" in extra_args
+    def __call__(self) -> None:
+        extra_args = self.arguments.get("extra_cli_args", "")
+        dry_run = bool(self.arguments.get("dry_run"))
+        write_message_to_file = self.arguments.get("write_message_to_file")
+        signoff = bool(self.arguments.get("signoff"))
 
-        dry_run: bool = self.arguments.get("dry_run")
-        write_message_to_file: bool = self.arguments.get("write_message_to_file")
-        manual_edit: bool = self.arguments.get("edit")
+        if signoff:
+            out.warn(
+                "Deprecated warning: `cz commit -s` is deprecated and will be removed in v5, please use `cz commit -- -s` instead."
+            )
 
-        is_all: bool = self.arguments.get("all")
-        if is_all:
-            c = git.add("-u")
+        if self.arguments.get("all"):
+            git.add("-u")
 
-        if git.is_staging_clean() and not (dry_run or allow_empty):
+        if git.is_staging_clean() and not (dry_run or "--allow-empty" in extra_args):
             raise NothingToCommitError("No files added to staging!")
 
         if write_message_to_file is not None and write_message_to_file.is_dir():
             raise NotAllowed(f"{write_message_to_file} is a directory")
 
-        retry: bool = self.arguments.get("retry")
-        no_retry: bool = self.arguments.get("no_retry")
-        retry_after_failure: bool = self.config.settings.get("retry_after_failure")
-
-        if retry:
-            m = self.read_backup_message()
-            if m is None:
-                raise NoCommitBackupError()
-        elif retry_after_failure and not no_retry:
-            m = self.read_backup_message()
-            if m is None:
-                m = self.prompt_commit_questions()
-        else:
-            m = self.prompt_commit_questions()
-
-        if manual_edit:
+        m = self._get_message()
+        if self.arguments.get("edit"):
             m = self.manual_edit(m)
 
         out.info(f"\n{m}\n")
@@ -138,19 +152,10 @@ def __call__(self):
         if dry_run:
             raise DryRunExit()
 
-        always_signoff: bool = self.config.settings["always_signoff"]
-        signoff: bool = self.arguments.get("signoff")
-
-        if signoff:
-            out.warn(
-                "signoff mechanic is deprecated, please use `cz commit -- -s` instead."
-            )
-
-        if always_signoff or signoff:
+        if self.config.settings["always_signoff"] or signoff:
             extra_args = f"{extra_args} -s".strip()
 
         c = git.commit(m, args=extra_args)
-
         if c.return_code != 0:
             out.error(c.err)
 
@@ -160,11 +165,12 @@ def __call__(self):
 
             raise CommitError()
 
-        if "nothing added" in c.out or "no changes added to commit" in c.out:
+        if any(s in c.out for s in ("nothing added", "no changes added to commit")):
             out.error(c.out)
-        else:
-            with contextlib.suppress(FileNotFoundError):
-                os.remove(self.temp_file)
-            out.write(c.err)
-            out.write(c.out)
-            out.success("Commit successful!")
+            return
+
+        with contextlib.suppress(FileNotFoundError):
+            os.remove(self.temp_file)
+        out.write(c.err)
+        out.write(c.out)
+        out.success("Commit successful!")
diff --git a/commitizen/commands/example.py b/commitizen/commands/example.py
index a28ad85f16..ba9f34adc4 100644
--- a/commitizen/commands/example.py
+++ b/commitizen/commands/example.py
@@ -5,9 +5,9 @@
 class Example:
     """Show an example so people understands the rules."""
 
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, *args: object) -> None:
         self.config: BaseConfig = config
         self.cz = factory.committer_factory(self.config)
 
-    def __call__(self):
+    def __call__(self) -> None:
         out.write(self.cz.example())
diff --git a/commitizen/commands/info.py b/commitizen/commands/info.py
index abd4197e7f..5ea8227313 100644
--- a/commitizen/commands/info.py
+++ b/commitizen/commands/info.py
@@ -5,9 +5,9 @@
 class Info:
     """Show in depth explanation of your rules."""
 
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, *args: object) -> None:
         self.config: BaseConfig = config
         self.cz = factory.committer_factory(self.config)
 
-    def __call__(self):
+    def __call__(self) -> None:
         out.write(self.cz.info())
diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py
index 0eb3d99d17..1207cd02ee 100644
--- a/commitizen/commands/init.py
+++ b/commitizen/commands/init.py
@@ -79,13 +79,13 @@ def is_pre_commit_installed(self) -> bool:
 
 
 class Init:
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, *args: object) -> None:
         self.config: BaseConfig = config
         self.encoding = config.settings["encoding"]
         self.cz = factory.committer_factory(self.config)
         self.project_info = ProjectInfo()
 
-    def __call__(self):
+    def __call__(self) -> None:
         if self.config.path:
             out.line(f"Config file {self.config.path} already exists")
             return
@@ -120,7 +120,7 @@ def __call__(self):
             self.config = JsonConfig(data="{}", path=config_path)
         elif "yaml" in config_path:
             self.config = YAMLConfig(data="", path=config_path)
-        values_to_add = {}
+        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
@@ -207,7 +207,7 @@ def _ask_tag(self) -> str:
                 raise NoAnswersError("Tag is required!")
         return latest_tag
 
-    def _ask_tag_format(self, latest_tag) -> str:
+    def _ask_tag_format(self, latest_tag: str) -> str:
         is_correct_format = False
         if latest_tag.startswith("v"):
             tag_format = r"v$version"
@@ -302,7 +302,7 @@ def _ask_update_changelog_on_bump(self) -> bool:
         ).unsafe_ask()
         return update_changelog_on_bump
 
-    def _exec_install_pre_commit_hook(self, hook_types: list[str]):
+    def _exec_install_pre_commit_hook(self, hook_types: list[str]) -> None:
         cmd_str = self._gen_pre_commit_cmd(hook_types)
         c = cmd.run(cmd_str)
         if c.return_code != 0:
@@ -323,7 +323,7 @@ def _gen_pre_commit_cmd(self, hook_types: list[str]) -> str:
         )
         return cmd_str
 
-    def _install_pre_commit_hook(self, hook_types: list[str] | None = None):
+    def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None:
         pre_commit_config_filename = ".pre-commit-config.yaml"
         cz_hook_config = {
             "repo": "https://github.com/commitizen-tools/commitizen",
@@ -369,6 +369,6 @@ def _install_pre_commit_hook(self, hook_types: list[str] | None = None):
         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]):
+    def _update_config_file(self, values: dict[str, Any]) -> None:
         for key, value in values.items():
             self.config.set_key(key, value)
diff --git a/commitizen/commands/list_cz.py b/commitizen/commands/list_cz.py
index 99701865af..412266f6c3 100644
--- a/commitizen/commands/list_cz.py
+++ b/commitizen/commands/list_cz.py
@@ -6,8 +6,8 @@
 class ListCz:
     """List currently installed rules."""
 
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, *args: object) -> None:
         self.config: BaseConfig = config
 
-    def __call__(self):
+    def __call__(self) -> None:
         out.write("\n".join(registry.keys()))
diff --git a/commitizen/commands/schema.py b/commitizen/commands/schema.py
index 4af5679cf5..a7aeb53569 100644
--- a/commitizen/commands/schema.py
+++ b/commitizen/commands/schema.py
@@ -5,9 +5,9 @@
 class Schema:
     """Show structure of the rule."""
 
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, *args: object) -> None:
         self.config: BaseConfig = config
         self.cz = factory.committer_factory(self.config)
 
-    def __call__(self):
+    def __call__(self) -> None:
         out.write(self.cz.schema())
diff --git a/commitizen/commands/version.py b/commitizen/commands/version.py
index 45d553c710..6b0aa331ad 100644
--- a/commitizen/commands/version.py
+++ b/commitizen/commands/version.py
@@ -1,5 +1,6 @@
 import platform
 import sys
+from typing import TypedDict
 
 from commitizen import out
 from commitizen.__version__ import __version__
@@ -7,16 +8,22 @@
 from commitizen.providers import get_provider
 
 
+class VersionArgs(TypedDict, total=False):
+    report: bool
+    project: bool
+    verbose: bool
+
+
 class Version:
     """Get the version of the installed commitizen or the current project."""
 
-    def __init__(self, config: BaseConfig, *args):
+    def __init__(self, config: BaseConfig, arguments: VersionArgs) -> None:
         self.config: BaseConfig = config
-        self.parameter = args[0]
+        self.parameter = arguments
         self.operating_system = platform.system()
         self.python_version = sys.version
 
-    def __call__(self):
+    def __call__(self) -> None:
         if self.parameter.get("report"):
             out.write(f"Commitizen Version: {__version__}")
             out.write(f"Python Version: {self.python_version}")
diff --git a/commitizen/config/base_config.py b/commitizen/config/base_config.py
index 478691aa14..4b8f5f05fc 100644
--- a/commitizen/config/base_config.py
+++ b/commitizen/config/base_config.py
@@ -1,12 +1,22 @@
 from __future__ import annotations
 
 from pathlib import Path
+from typing import TYPE_CHECKING, Any
 
 from commitizen.defaults import DEFAULT_SETTINGS, Settings
 
+if TYPE_CHECKING:
+    import sys
+
+    # Self is Python 3.11+ but backported in typing-extensions
+    if sys.version_info < (3, 11):
+        from typing_extensions import Self
+    else:
+        from typing import Self
+
 
 class BaseConfig:
-    def __init__(self):
+    def __init__(self) -> None:
         self._settings: Settings = DEFAULT_SETTINGS.copy()
         self.encoding = self.settings["encoding"]
         self._path: Path | None = None
@@ -16,10 +26,14 @@ def settings(self) -> Settings:
         return self._settings
 
     @property
-    def path(self) -> Path | None:
-        return self._path
+    def path(self) -> Path:
+        return self._path  # type: ignore[return-value]
+
+    @path.setter
+    def path(self, path: str | Path) -> None:
+        self._path = Path(path)
 
-    def set_key(self, key, value):
+    def set_key(self, key: str, value: Any) -> Self:
         """Set or update a key in the conf.
 
         For now only strings are supported.
@@ -30,8 +44,8 @@ def set_key(self, key, value):
     def update(self, data: Settings) -> None:
         self._settings.update(data)
 
-    def add_path(self, path: str | Path) -> None:
-        self._path = Path(path)
-
     def _parse_setting(self, data: bytes | str) -> None:
         raise NotImplementedError()
+
+    def init_empty_config_content(self) -> None:
+        raise NotImplementedError()
diff --git a/commitizen/config/json_config.py b/commitizen/config/json_config.py
index b6a07f4ced..be1f1c36b0 100644
--- a/commitizen/config/json_config.py
+++ b/commitizen/config/json_config.py
@@ -2,25 +2,35 @@
 
 import json
 from pathlib import Path
+from typing import TYPE_CHECKING, Any
 
 from commitizen.exceptions import InvalidConfigurationError
 from commitizen.git import smart_open
 
 from .base_config import BaseConfig
 
+if TYPE_CHECKING:
+    import sys
+
+    # Self is Python 3.11+ but backported in typing-extensions
+    if sys.version_info < (3, 11):
+        from typing_extensions import Self
+    else:
+        from typing import Self
+
 
 class JsonConfig(BaseConfig):
-    def __init__(self, *, data: bytes | str, path: Path | str):
+    def __init__(self, *, data: bytes | str, path: Path | str) -> None:
         super().__init__()
         self.is_empty_config = False
-        self.add_path(path)
+        self.path = path
         self._parse_setting(data)
 
-    def init_empty_config_content(self):
+    def init_empty_config_content(self) -> None:
         with smart_open(self.path, "a", encoding=self.encoding) as json_file:
             json.dump({"commitizen": {}}, json_file)
 
-    def set_key(self, key, value):
+    def set_key(self, key: str, value: Any) -> Self:
         """Set or update a key in the conf.
 
         For now only strings are supported.
diff --git a/commitizen/config/toml_config.py b/commitizen/config/toml_config.py
index 813389cbcf..2164d3f992 100644
--- a/commitizen/config/toml_config.py
+++ b/commitizen/config/toml_config.py
@@ -2,6 +2,7 @@
 
 import os
 from pathlib import Path
+from typing import TYPE_CHECKING, Any
 
 from tomlkit import exceptions, parse, table
 
@@ -9,15 +10,24 @@
 
 from .base_config import BaseConfig
 
+if TYPE_CHECKING:
+    import sys
+
+    # Self is Python 3.11+ but backported in typing-extensions
+    if sys.version_info < (3, 11):
+        from typing_extensions import Self
+    else:
+        from typing import Self
+
 
 class TomlConfig(BaseConfig):
-    def __init__(self, *, data: bytes | str, path: Path | str):
+    def __init__(self, *, data: bytes | str, path: Path | str) -> None:
         super().__init__()
         self.is_empty_config = False
-        self.add_path(path)
+        self.path = path
         self._parse_setting(data)
 
-    def init_empty_config_content(self):
+    def init_empty_config_content(self) -> None:
         if os.path.isfile(self.path):
             with open(self.path, "rb") as input_toml_file:
                 parser = parse(input_toml_file.read())
@@ -27,10 +37,10 @@ def init_empty_config_content(self):
         with open(self.path, "wb") as output_toml_file:
             if parser.get("tool") is None:
                 parser["tool"] = table()
-            parser["tool"]["commitizen"] = table()
+            parser["tool"]["commitizen"] = table()  # type: ignore[index]
             output_toml_file.write(parser.as_string().encode(self.encoding))
 
-    def set_key(self, key, value):
+    def set_key(self, key: str, value: Any) -> Self:
         """Set or update a key in the conf.
 
         For now only strings are supported.
@@ -39,7 +49,7 @@ def set_key(self, key, value):
         with open(self.path, "rb") as f:
             parser = parse(f.read())
 
-        parser["tool"]["commitizen"][key] = value
+        parser["tool"]["commitizen"][key] = value  # type: ignore[index]
         with open(self.path, "wb") as f:
             f.write(parser.as_string().encode(self.encoding))
         return self
@@ -58,6 +68,6 @@ def _parse_setting(self, data: bytes | str) -> None:
             raise InvalidConfigurationError(f"Failed to parse {self.path}: {e}")
 
         try:
-            self.settings.update(doc["tool"]["commitizen"])  # type: ignore
+            self.settings.update(doc["tool"]["commitizen"])  # type: ignore[index,typeddict-item] # TODO: fix this
         except exceptions.NonExistentKey:
             self.is_empty_config = True
diff --git a/commitizen/config/yaml_config.py b/commitizen/config/yaml_config.py
index 2bb6fe3af8..f2a79e6937 100644
--- a/commitizen/config/yaml_config.py
+++ b/commitizen/config/yaml_config.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 from pathlib import Path
+from typing import TYPE_CHECKING, Any
 
 import yaml
 
@@ -9,15 +10,24 @@
 
 from .base_config import BaseConfig
 
+if TYPE_CHECKING:
+    import sys
+
+    # Self is Python 3.11+ but backported in typing-extensions
+    if sys.version_info < (3, 11):
+        from typing_extensions import Self
+    else:
+        from typing import Self
+
 
 class YAMLConfig(BaseConfig):
-    def __init__(self, *, data: bytes | str, path: Path | str):
+    def __init__(self, *, data: bytes | str, path: Path | str) -> None:
         super().__init__()
         self.is_empty_config = False
-        self.add_path(path)
+        self.path = path
         self._parse_setting(data)
 
-    def init_empty_config_content(self):
+    def init_empty_config_content(self) -> None:
         with smart_open(self.path, "a", encoding=self.encoding) as json_file:
             yaml.dump({"commitizen": {}}, json_file, explicit_start=True)
 
@@ -41,7 +51,7 @@ def _parse_setting(self, data: bytes | str) -> None:
         except (KeyError, TypeError):
             self.is_empty_config = True
 
-    def set_key(self, key, value):
+    def set_key(self, key: str, value: Any) -> Self:
         """Set or update a key in the conf.
 
         For now only strings are supported.
diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py
index 43455a74ca..cdc1476694 100644
--- a/commitizen/cz/base.py
+++ b/commitizen/cz/base.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from abc import ABCMeta, abstractmethod
-from collections.abc import Iterable
+from collections.abc import Iterable, Mapping
 from typing import Any, Callable, Protocol
 
 from jinja2 import BaseLoader, PackageLoader
@@ -9,7 +9,7 @@
 
 from commitizen import git
 from commitizen.config.base_config import BaseConfig
-from commitizen.defaults import Questions
+from commitizen.question import CzQuestion
 
 
 class MessageBuilderHook(Protocol):
@@ -68,21 +68,21 @@ def __init__(self, config: BaseConfig) -> None:
             self.config.settings.update({"style": BaseCommitizen.default_style_config})
 
     @abstractmethod
-    def questions(self) -> Questions:
+    def questions(self) -> Iterable[CzQuestion]:
         """Questions regarding the commit message."""
 
     @abstractmethod
-    def message(self, answers: dict) -> str:
+    def message(self, answers: Mapping[str, Any]) -> str:
         """Format your git message."""
 
     @property
-    def style(self):
+    def style(self) -> Style:
         return merge_styles(
             [
                 Style(BaseCommitizen.default_style_config),
                 Style(self.config.settings["style"]),
             ]
-        )
+        )  # type: ignore[return-value]
 
     def example(self) -> str:
         """Example of the commit message."""
@@ -99,10 +99,3 @@ def schema_pattern(self) -> str:
     def info(self) -> str:
         """Information about the standardized commit message."""
         raise NotImplementedError("Not Implemented yet")
-
-    def process_commit(self, commit: str) -> str:
-        """Process commit for changelog.
-
-        If not overwritten, it returns the first line of commit.
-        """
-        return commit.split("\n")[0]
diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py
index af29a209fc..6893423478 100644
--- a/commitizen/cz/conventional_commits/conventional_commits.py
+++ b/commitizen/cz/conventional_commits/conventional_commits.py
@@ -1,37 +1,36 @@
 import os
-import re
+from typing import TypedDict
 
 from commitizen import defaults
 from commitizen.cz.base import BaseCommitizen
 from commitizen.cz.utils import multiple_line_breaker, required_validator
-from commitizen.defaults import Questions
+from commitizen.question import CzQuestion
 
 __all__ = ["ConventionalCommitsCz"]
 
 
-def parse_scope(text):
-    if not text:
-        return ""
+def _parse_scope(text: str) -> str:
+    return "-".join(text.strip().split())
 
-    scope = text.strip().split()
-    if len(scope) == 1:
-        return scope[0]
 
-    return "-".join(scope)
+def _parse_subject(text: str) -> str:
+    return required_validator(text.strip(".").strip(), msg="Subject is required.")
 
 
-def parse_subject(text):
-    if isinstance(text, str):
-        text = text.strip(".").strip()
-
-    return required_validator(text, msg="Subject is required.")
+class ConventionalCommitsAnswers(TypedDict):
+    prefix: str
+    scope: str
+    subject: str
+    body: str
+    footer: str
+    is_breaking_change: bool
 
 
 class ConventionalCommitsCz(BaseCommitizen):
     bump_pattern = defaults.BUMP_PATTERN
     bump_map = defaults.BUMP_MAP
     bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO
-    commit_parser = r"^((?P<change_type>feat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?|\w+!):\s(?P<message>.*)?"  # noqa
+    commit_parser = r"^((?P<change_type>feat|fix|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?|\w+!):\s(?P<message>.*)?"
     change_type_map = {
         "feat": "Feat",
         "fix": "Fix",
@@ -40,8 +39,8 @@ class ConventionalCommitsCz(BaseCommitizen):
     }
     changelog_pattern = defaults.BUMP_PATTERN
 
-    def questions(self) -> Questions:
-        questions: Questions = [
+    def questions(self) -> list[CzQuestion]:
+        return [
             {
                 "type": "list",
                 "name": "prefix",
@@ -113,12 +112,12 @@ def questions(self) -> Questions:
                 "message": (
                     "What is the scope of this change? (class or file name): (press [enter] to skip)\n"
                 ),
-                "filter": parse_scope,
+                "filter": _parse_scope,
             },
             {
                 "type": "input",
                 "name": "subject",
-                "filter": parse_subject,
+                "filter": _parse_subject,
                 "message": (
                     "Write a short and imperative summary of the code changes: (lower case and no period)\n"
                 ),
@@ -133,8 +132,8 @@ def questions(self) -> Questions:
             },
             {
                 "type": "confirm",
-                "message": "Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer",
                 "name": "is_breaking_change",
+                "message": "Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer",
                 "default": False,
             },
             {
@@ -146,9 +145,8 @@ def questions(self) -> Questions:
                 ),
             },
         ]
-        return questions
 
-    def message(self, answers: dict) -> str:
+    def message(self, answers: ConventionalCommitsAnswers) -> str:  # type: ignore[override]
         prefix = answers["prefix"]
         scope = answers["scope"]
         subject = answers["subject"]
@@ -165,9 +163,7 @@ def message(self, answers: dict) -> str:
         if footer:
             footer = f"\n\n{footer}"
 
-        message = f"{prefix}{scope}: {subject}{body}{footer}"
-
-        return message
+        return f"{prefix}{scope}: {subject}{body}{footer}"
 
     def example(self) -> str:
         return (
@@ -188,25 +184,32 @@ def schema(self) -> str:
         )
 
     def schema_pattern(self) -> str:
-        PATTERN = (
+        change_types = (
+            "build",
+            "bump",
+            "chore",
+            "ci",
+            "docs",
+            "feat",
+            "fix",
+            "perf",
+            "refactor",
+            "revert",
+            "style",
+            "test",
+        )
+        return (
             r"(?s)"  # To explicitly make . match new line
-            r"(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)"  # type
-            r"(\(\S+\))?!?:"  # scope
-            r"( [^\n\r]+)"  # subject
+            r"(" + "|".join(change_types) + r")"  # type
+            r"(\(\S+\))?"  # scope
+            r"!?"
+            r": "
+            r"([^\n\r]+)"  # subject
             r"((\n\n.*)|(\s*))?$"
         )
-        return PATTERN
 
     def info(self) -> str:
         dir_path = os.path.dirname(os.path.realpath(__file__))
         filepath = os.path.join(dir_path, "conventional_commits_info.txt")
         with open(filepath, encoding=self.config.settings["encoding"]) as f:
-            content = f.read()
-        return content
-
-    def process_commit(self, commit: str) -> str:
-        pat = re.compile(self.schema_pattern())
-        m = re.match(pat, commit)
-        if m is None:
-            return ""
-        return m.group(3).strip()
+            return f.read()
diff --git a/commitizen/cz/customize/__init__.py b/commitizen/cz/customize/__init__.py
index c5af001a79..0aedb9337a 100644
--- a/commitizen/cz/customize/__init__.py
+++ b/commitizen/cz/customize/__init__.py
@@ -1 +1 @@
-from .customize import CustomizeCommitsCz  # noqa
+from .customize import CustomizeCommitsCz  # noqa: F401
diff --git a/commitizen/cz/customize/customize.py b/commitizen/cz/customize/customize.py
index 53ada4b2c0..dde2685496 100644
--- a/commitizen/cz/customize/customize.py
+++ b/commitizen/cz/customize/customize.py
@@ -1,6 +1,9 @@
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any
+
+from commitizen.question import CzQuestion
 
 if TYPE_CHECKING:
     from jinja2 import Template
@@ -14,7 +17,6 @@
 from commitizen import defaults
 from commitizen.config import BaseConfig
 from commitizen.cz.base import BaseCommitizen
-from commitizen.defaults import Questions
 from commitizen.exceptions import MissingCzCustomizeConfigError
 
 __all__ = ["CustomizeCommitsCz"]
@@ -26,7 +28,7 @@ class CustomizeCommitsCz(BaseCommitizen):
     bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO
     change_type_order = defaults.CHANGE_TYPE_ORDER
 
-    def __init__(self, config: BaseConfig):
+    def __init__(self, config: BaseConfig) -> None:
         super().__init__(config)
 
         if "customize" not in self.config.settings:
@@ -45,13 +47,13 @@ def __init__(self, config: BaseConfig):
             if value := self.custom_settings.get(attr_name):
                 setattr(self, attr_name, value)
 
-    def questions(self) -> Questions:
-        return self.custom_settings.get("questions", [{}])
+    def questions(self) -> list[CzQuestion]:
+        return self.custom_settings.get("questions", [{}])  # type: ignore[return-value]
 
-    def message(self, answers: dict) -> str:
+    def message(self, answers: Mapping[str, Any]) -> str:
         message_template = Template(self.custom_settings.get("message_template", ""))
         if getattr(Template, "substitute", None):
-            return message_template.substitute(**answers)  # type: ignore
+            return message_template.substitute(**answers)  # type: ignore[attr-defined,no-any-return] # pragma: no cover # TODO: check if we can fix this
         return message_template.render(**answers)
 
     def example(self) -> str:
diff --git a/commitizen/cz/jira/jira.py b/commitizen/cz/jira/jira.py
index b8fd056a71..4e6024ff74 100644
--- a/commitizen/cz/jira/jira.py
+++ b/commitizen/cz/jira/jira.py
@@ -1,14 +1,15 @@
 import os
+from collections.abc import Mapping
 
 from commitizen.cz.base import BaseCommitizen
-from commitizen.defaults import Questions
+from commitizen.question import CzQuestion
 
 __all__ = ["JiraSmartCz"]
 
 
 class JiraSmartCz(BaseCommitizen):
-    def questions(self) -> Questions:
-        questions = [
+    def questions(self) -> list[CzQuestion]:
+        return [
             {
                 "type": "input",
                 "name": "message",
@@ -42,20 +43,12 @@ def questions(self) -> Questions:
                 "filter": lambda x: "#comment " + x if x else "",
             },
         ]
-        return questions
 
-    def message(self, answers: dict) -> str:
+    def message(self, answers: Mapping[str, str]) -> str:
         return " ".join(
-            filter(
-                bool,
-                [
-                    answers["message"],
-                    answers["issues"],
-                    answers["workflow"],
-                    answers["time"],
-                    answers["comment"],
-                ],
-            )
+            x
+            for k in ("message", "issues", "workflow", "time", "comment")
+            if (x := answers.get(k))
         )
 
     def example(self) -> str:
@@ -68,7 +61,7 @@ def example(self) -> str:
         )
 
     def schema(self) -> str:
-        return "<ignored text> <ISSUE_KEY> <ignored text> #<COMMAND> <optional COMMAND_ARGUMENTS>"  # noqa
+        return "<ignored text> <ISSUE_KEY> <ignored text> #<COMMAND> <optional COMMAND_ARGUMENTS>"
 
     def schema_pattern(self) -> str:
         return r".*[A-Z]{2,}\-[0-9]+( #| .* #).+( #.+)*"
@@ -77,5 +70,4 @@ def info(self) -> str:
         dir_path = os.path.dirname(os.path.realpath(__file__))
         filepath = os.path.join(dir_path, "jira_info.txt")
         with open(filepath, encoding=self.config.settings["encoding"]) as f:
-            content = f.read()
-        return content
+            return f.read()
diff --git a/commitizen/cz/utils.py b/commitizen/cz/utils.py
index 7bc89673c6..a6f687226c 100644
--- a/commitizen/cz/utils.py
+++ b/commitizen/cz/utils.py
@@ -5,28 +5,27 @@
 from commitizen import git
 from commitizen.cz import exceptions
 
+_RE_LOCAL_VERSION = re.compile(r"\+.+")
 
-def required_validator(answer, msg=None):
+
+def required_validator(answer: str, msg: object = None) -> str:
     if not answer:
         raise exceptions.AnswerRequiredError(msg)
     return answer
 
 
-def multiple_line_breaker(answer, sep="|"):
+def multiple_line_breaker(answer: str, sep: str = "|") -> str:
     return "\n".join(line.strip() for line in answer.split(sep) if line)
 
 
 def strip_local_version(version: str) -> str:
-    return re.sub(r"\+.+", "", version)
+    return _RE_LOCAL_VERSION.sub("", version)
 
 
 def get_backup_file_path() -> str:
     project_root = git.find_git_project_root()
-
-    if project_root is None:
-        project = ""
-    else:
-        project = project_root.as_posix().replace("/", "%")
+    project = project_root.as_posix().replace("/", "%") if project_root else ""
 
     user = os.environ.get("USER", "")
+
     return os.path.join(tempfile.gettempdir(), f"cz.commit%{user}%{project}.backup")
diff --git a/commitizen/defaults.py b/commitizen/defaults.py
index 0b6c28e6a9..a49d6d9428 100644
--- a/commitizen/defaults.py
+++ b/commitizen/defaults.py
@@ -1,12 +1,15 @@
 from __future__ import annotations
 
 import pathlib
+import warnings
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, Sequence
 from typing import Any, TypedDict
 
+from commitizen.question import CzQuestion
+
 # Type
-Questions = Iterable[MutableMapping[str, Any]]
+Questions = Iterable[MutableMapping[str, Any]]  # TODO: deprecate this?
 
 
 class CzSettings(TypedDict, total=False):
@@ -15,7 +18,7 @@ class CzSettings(TypedDict, total=False):
     bump_map_major_version_zero: OrderedDict[str, str]
     change_type_order: list[str]
 
-    questions: Questions
+    questions: Iterable[CzQuestion]
     example: str | None
     schema_pattern: str | None
     schema: str | None
@@ -28,36 +31,39 @@ class CzSettings(TypedDict, total=False):
 
 
 class Settings(TypedDict, total=False):
-    name: str
-    version: str | None
-    version_files: list[str]
-    version_provider: str | None
-    version_scheme: str | None
-    version_type: str | None
-    tag_format: str
-    legacy_tag_formats: Sequence[str]
-    ignored_tag_formats: Sequence[str]
-    bump_message: str | None
-    retry_after_failure: bool
     allow_abort: bool
     allowed_prefixes: list[str]
+    always_signoff: bool
+    annotated_tag: bool
+    bump_message: str | None
+    change_type_map: dict[str, str]
     changelog_file: str
     changelog_format: str | None
     changelog_incremental: bool
-    changelog_start_rev: str | None
     changelog_merge_prerelease: bool
-    update_changelog_on_bump: bool
-    use_shortcuts: bool
-    style: list[tuple[str, str]]
+    changelog_start_rev: str | None
     customize: CzSettings
+    encoding: str
+    extras: dict[str, Any]
+    gpg_sign: bool
+    ignored_tag_formats: Sequence[str]
+    legacy_tag_formats: Sequence[str]
     major_version_zero: bool
-    pre_bump_hooks: list[str] | None
+    name: str
     post_bump_hooks: list[str] | None
+    pre_bump_hooks: list[str] | None
     prerelease_offset: int
-    encoding: str
-    always_signoff: bool
+    retry_after_failure: bool
+    style: list[tuple[str, str]]
+    tag_format: str
     template: str | None
-    extras: dict[str, Any]
+    update_changelog_on_bump: bool
+    use_shortcuts: bool
+    version_files: list[str]
+    version_provider: str | None
+    version_scheme: str | None
+    version_type: str | None
+    version: str | None
 
 
 CONFIG_FILES: list[str] = [
@@ -141,7 +147,7 @@ class Settings(TypedDict, total=False):
 def get_tag_regexes(
     version_regex: str,
 ) -> dict[str, str]:
-    regexs = {
+    regexes = {
         "version": version_regex,
         "major": r"(?P<major>\d+)",
         "minor": r"(?P<minor>\d+)",
@@ -150,6 +156,34 @@ def get_tag_regexes(
         "devrelease": r"(?P<devrelease>\.dev\d+)?",
     }
     return {
-        **{f"${k}": v for k, v in regexs.items()},
-        **{f"${{{k}}}": v for k, v in regexs.items()},
+        **{f"${k}": v for k, v in regexes.items()},
+        **{f"${{{k}}}": v for k, v in regexes.items()},
+    }
+
+
+def __getattr__(name: str) -> Any:
+    # PEP-562: deprecate module-level variable
+
+    # {"deprecated key": (value, "new key")}
+    deprecated_vars = {
+        "bump_pattern": (BUMP_PATTERN, "BUMP_PATTERN"),
+        "bump_map": (BUMP_MAP, "BUMP_MAP"),
+        "bump_map_major_version_zero": (
+            BUMP_MAP_MAJOR_VERSION_ZERO,
+            "BUMP_MAP_MAJOR_VERSION_ZERO",
+        ),
+        "bump_message": (BUMP_MESSAGE, "BUMP_MESSAGE"),
+        "change_type_order": (CHANGE_TYPE_ORDER, "CHANGE_TYPE_ORDER"),
+        "encoding": (ENCODING, "ENCODING"),
+        "name": (DEFAULT_SETTINGS["name"], "DEFAULT_SETTINGS['name']"),
     }
+    if name in deprecated_vars:
+        value, replacement = deprecated_vars[name]
+        warnings.warn(
+            f"{name} is deprecated and will be removed in v5. "
+            f"Use {replacement} instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return value
+    raise AttributeError(f"{name} is not an attribute of {__name__}")
diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py
index 29733b624b..8c0956be53 100644
--- a/commitizen/exceptions.py
+++ b/commitizen/exceptions.py
@@ -1,4 +1,5 @@
 import enum
+from typing import Any
 
 from commitizen import out
 
@@ -40,7 +41,7 @@ class ExitCode(enum.IntEnum):
 
 
 class CommitizenException(Exception):
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args: str, **kwargs: Any) -> None:
         self.output_method = kwargs.get("output_method") or out.error
         self.exit_code: ExitCode = self.__class__.exit_code
         if args:
@@ -50,14 +51,14 @@ def __init__(self, *args, **kwargs):
         else:
             self.message = ""
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.message
 
 
 class ExpectedExit(CommitizenException):
     exit_code = ExitCode.EXPECTED_EXIT
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args: str, **kwargs: Any) -> None:
         output_method = kwargs.get("output_method") or out.write
         kwargs["output_method"] = output_method
         super().__init__(*args, **kwargs)
diff --git a/commitizen/factory.py b/commitizen/factory.py
index b5d665b65e..d9e99fb771 100644
--- a/commitizen/factory.py
+++ b/commitizen/factory.py
@@ -8,12 +8,10 @@ def committer_factory(config: BaseConfig) -> BaseCommitizen:
     """Return the correct commitizen existing in the registry."""
     name: str = config.settings["name"]
     try:
-        _cz = registry[name](config)
+        return registry[name](config)
     except KeyError:
         msg_error = (
             "The committer has not been found in the system.\n\n"
             f"Try running 'pip install {name}'\n"
         )
         raise NoCommitizenFoundException(msg_error)
-    else:
-        return _cz
diff --git a/commitizen/git.py b/commitizen/git.py
index 19ca46b6c3..8025041abb 100644
--- a/commitizen/git.py
+++ b/commitizen/git.py
@@ -2,56 +2,64 @@
 
 import os
 from enum import Enum
-from os import linesep
+from functools import lru_cache
 from pathlib import Path
 from tempfile import NamedTemporaryFile
 
 from commitizen import cmd, out
 from commitizen.exceptions import GitCommandError
 
-UNIX_EOL = "\n"
-WINDOWS_EOL = "\r\n"
 
-
-class EOLTypes(Enum):
+class EOLType(Enum):
     """The EOL type from `git config core.eol`."""
 
     LF = "lf"
     CRLF = "crlf"
     NATIVE = "native"
 
-    def get_eol_for_open(self) -> str:
+    @classmethod
+    def for_open(cls) -> str:
+        c = cmd.run("git config core.eol")
+        eol = c.out.strip().upper()
+        return cls._char_for_open()[cls._safe_cast(eol)]
+
+    @classmethod
+    def _safe_cast(cls, eol: str) -> EOLType:
+        try:
+            return cls[eol]
+        except KeyError:
+            return cls.NATIVE
+
+    @classmethod
+    @lru_cache
+    def _char_for_open(cls) -> dict[EOLType, str]:
         """Get the EOL character for `open()`."""
-        map = {
-            EOLTypes.CRLF: WINDOWS_EOL,
-            EOLTypes.LF: UNIX_EOL,
-            EOLTypes.NATIVE: linesep,
+        return {
+            cls.LF: "\n",
+            cls.CRLF: "\r\n",
+            cls.NATIVE: os.linesep,
         }
 
-        return map[self]
-
 
 class GitObject:
     rev: str
     name: str
     date: str
 
-    def __eq__(self, other) -> bool:
-        if not hasattr(other, "rev"):
-            return False
-        return self.rev == other.rev  # type: ignore
+    def __eq__(self, other: object) -> bool:
+        return hasattr(other, "rev") and self.rev == other.rev
 
 
 class GitCommit(GitObject):
     def __init__(
         self,
-        rev,
-        title,
+        rev: str,
+        title: str,
         body: str = "",
         author: str = "",
         author_email: str = "",
         parents: list[str] | None = None,
-    ):
+    ) -> None:
         self.rev = rev.strip()
         self.title = title.strip()
         self.body = body.strip()
@@ -60,26 +68,86 @@ def __init__(
         self.parents = parents or []
 
     @property
-    def message(self):
+    def message(self) -> str:
         return f"{self.title}\n\n{self.body}".strip()
 
-    def __repr__(self):
+    @classmethod
+    def from_rev_and_commit(cls, rev_and_commit: str) -> GitCommit:
+        """Create a GitCommit instance from a formatted commit string.
+
+        This method parses a multi-line string containing commit information in the following format:
+        ```
+        <rev>
+        <parents>
+        <title>
+        <author>
+        <author_email>
+        <body_line_1>
+        <body_line_2>
+        ...
+        ```
+
+        Args:
+            rev_and_commit (str): A string containing commit information with fields separated by newlines.
+                - rev: The commit hash/revision
+                - parents: Space-separated list of parent commit hashes
+                - title: The commit title/message
+                - author: The commit author's name
+                - author_email: The commit author's email
+                - body: Optional multi-line commit body
+
+        Returns:
+            GitCommit: A new GitCommit instance with the parsed information.
+
+        Example:
+            >>> commit_str = '''abc123
+            ... def456 ghi789
+            ... feat: add new feature
+            ... John Doe
+            ... john@example.com
+            ... This is a detailed description
+            ... of the new feature'''
+            >>> commit = GitCommit.from_rev_and_commit(commit_str)
+            >>> commit.rev
+            'abc123'
+            >>> commit.title
+            'feat: add new feature'
+            >>> commit.parents
+            ['def456', 'ghi789']
+        """
+        rev, parents, title, author, author_email, *body_list = rev_and_commit.split(
+            "\n"
+        )
+        return cls(
+            rev=rev.strip(),
+            title=title.strip(),
+            body="\n".join(body_list).strip(),
+            author=author,
+            author_email=author_email,
+            parents=[p for p in parents.strip().split(" ") if p],
+        )
+
+    def __repr__(self) -> str:
         return f"{self.title} ({self.rev})"
 
 
 class GitTag(GitObject):
-    def __init__(self, name, rev, date):
+    def __init__(self, name: str, rev: str, date: str) -> None:
         self.rev = rev.strip()
         self.name = name.strip()
         self._date = date.strip()
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return f"GitTag('{self.name}', '{self.rev}', '{self.date}')"
 
     @property
-    def date(self):
+    def date(self) -> str:
         return self._date
 
+    @date.setter
+    def date(self, value: str) -> None:
+        self._date = value
+
     @classmethod
     def from_line(cls, line: str, inner_delimiter: str) -> GitTag:
         name, objectname, date, obj = line.split(inner_delimiter)
@@ -92,22 +160,18 @@ def from_line(cls, line: str, inner_delimiter: str) -> GitTag:
 def tag(
     tag: str, annotated: bool = False, signed: bool = False, msg: str | None = None
 ) -> cmd.Command:
-    _opt = ""
-    if annotated:
-        _opt = f"-a {tag} -m"
-    if signed:
-        _opt = f"-s {tag} -m"
+    if not annotated and not signed:
+        return cmd.run(f"git tag {tag}")
 
     # according to https://git-scm.com/book/en/v2/Git-Basics-Tagging,
     # we're not able to create lightweight tag with message.
     # by adding message, we make it a annotated tags
-    c = cmd.run(f'git tag {_opt} "{tag if _opt == "" or msg is None else msg}"')
-    return c
+    option = "-s" if signed else "-a"  # The else case is for annotated tags
+    return cmd.run(f'git tag {option} {tag} -m "{msg or tag}"')
 
 
 def add(*args: str) -> cmd.Command:
-    c = cmd.run(f"git add {' '.join(args)}")
-    return c
+    return cmd.run(f"git add {' '.join(args)}")
 
 
 def commit(
@@ -119,48 +183,40 @@ def commit(
     f.write(message.encode("utf-8"))
     f.close()
 
-    command = f'git commit {args} -F "{f.name}"'
-
-    if committer_date and os.name == "nt":  # pragma: no cover
-        # Using `cmd /v /c "{command}"` sets environment variables only for that command
-        command = f'cmd /v /c "set GIT_COMMITTER_DATE={committer_date}&& {command}"'
-    elif committer_date:
-        command = f"GIT_COMMITTER_DATE={committer_date} {command}"
-
+    command = _create_commit_cmd_string(args, committer_date, f.name)
     c = cmd.run(command)
     os.unlink(f.name)
     return c
 
 
+def _create_commit_cmd_string(args: str, committer_date: str | None, name: str) -> str:
+    command = f'git commit {args} -F "{name}"'
+    if not committer_date:
+        return command
+    if os.name != "nt":
+        return f"GIT_COMMITTER_DATE={committer_date} {command}"
+    # Using `cmd /v /c "{command}"` sets environment variables only for that command
+    return f'cmd /v /c "set GIT_COMMITTER_DATE={committer_date}&& {command}"'
+
+
 def get_commits(
     start: str | None = None,
-    end: str = "HEAD",
+    end: str | None = None,
     *,
     args: str = "",
 ) -> list[GitCommit]:
     """Get the commits between start and end."""
+    if end is None:
+        end = "HEAD"
     git_log_entries = _get_log_as_str_list(start, end, args)
-    git_commits = []
-    for rev_and_commit in git_log_entries:
-        if not rev_and_commit:
-            continue
-        rev, parents, title, author, author_email, *body_list = rev_and_commit.split(
-            "\n"
-        )
-        if rev_and_commit:
-            git_commit = GitCommit(
-                rev=rev.strip(),
-                title=title.strip(),
-                body="\n".join(body_list).strip(),
-                author=author,
-                author_email=author_email,
-                parents=[p for p in parents.strip().split(" ") if p],
-            )
-            git_commits.append(git_commit)
-    return git_commits
-
-
-def get_filenames_in_commit(git_reference: str = ""):
+    return [
+        GitCommit.from_rev_and_commit(rev_and_commit)
+        for rev_and_commit in git_log_entries
+        if rev_and_commit
+    ]
+
+
+def get_filenames_in_commit(git_reference: str = "") -> list[str]:
     """Get the list of files that were committed in the requested git reference.
 
     :param git_reference: a git reference as accepted by `git show`, default: the last commit
@@ -170,8 +226,7 @@ def get_filenames_in_commit(git_reference: str = ""):
     c = cmd.run(f"git show --name-only --pretty=format: {git_reference}")
     if c.return_code == 0:
         return c.out.strip().split("\n")
-    else:
-        raise GitCommandError(c.err)
+    raise GitCommandError(c.err)
 
 
 def get_tags(
@@ -197,16 +252,11 @@ def get_tags(
     if c.err:
         out.warn(f"Attempting to proceed after: {c.err}")
 
-    if not c.out:
-        return []
-
-    git_tags = [
+    return [
         GitTag.from_line(line=line, inner_delimiter=inner_delimiter)
         for line in c.out.split("\n")[:-1]
     ]
 
-    return git_tags
-
 
 def tag_exist(tag: str) -> bool:
     c = cmd.run(f"git tag --list {tag}")
@@ -231,18 +281,18 @@ def get_tag_message(tag: str) -> str | None:
     return c.out.strip()
 
 
-def get_tag_names() -> list[str | None]:
+def get_tag_names() -> list[str]:
     c = cmd.run("git tag --list")
     if c.err:
         return []
-    return [tag.strip() for tag in c.out.split("\n") if tag.strip()]
+    return [tag for raw in c.out.split("\n") if (tag := raw.strip())]
 
 
 def find_git_project_root() -> Path | None:
     c = cmd.run("git rev-parse --show-toplevel")
-    if not c.err:
-        return Path(c.out.strip())
-    return None
+    if c.err:
+        return None
+    return Path(c.out.strip())
 
 
 def is_staging_clean() -> bool:
@@ -253,32 +303,7 @@ def is_staging_clean() -> bool:
 
 def is_git_project() -> bool:
     c = cmd.run("git rev-parse --is-inside-work-tree")
-    if c.out.strip() == "true":
-        return True
-    return False
-
-
-def get_eol_style() -> EOLTypes:
-    c = cmd.run("git config core.eol")
-    eol = c.out.strip().lower()
-
-    # We enumerate the EOL types of the response of
-    # `git config core.eol`, and map it to our enumration EOLTypes.
-    #
-    # It is just like the variant of the "match" syntax.
-    map = {
-        "lf": EOLTypes.LF,
-        "crlf": EOLTypes.CRLF,
-        "native": EOLTypes.NATIVE,
-    }
-
-    # If the response of `git config core.eol` is in the map:
-    if eol in map:
-        return map[eol]
-    else:
-        # The default value is "native".
-        # https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreeol
-        return map["native"]
+    return c.out.strip() == "true"
 
 
 def get_core_editor() -> str | None:
@@ -288,22 +313,18 @@ def get_core_editor() -> str | None:
     return None
 
 
-def smart_open(*args, **kargs):
+def smart_open(*args, **kwargs):  # type: ignore[no-untyped-def,unused-ignore] # noqa: ANN201
     """Open a file with the EOL style determined from Git."""
-    return open(*args, newline=get_eol_style().get_eol_for_open(), **kargs)
+    return open(*args, newline=EOLType.for_open(), **kwargs)
 
 
 def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]:
     """Get string representation of each log entry"""
     delimiter = "----------commit-delimiter----------"
     log_format: str = "%H%n%P%n%s%n%an%n%ae%n%b"
-    git_log_cmd = (
-        f"git -c log.showSignature=False log --pretty={log_format}{delimiter} {args}"
-    )
-    if start:
-        command = f"{git_log_cmd} {start}..{end}"
-    else:
-        command = f"{git_log_cmd} {end}"
+    command_range = f"{start}..{end}" if start else end
+    command = f"git -c log.showSignature=False log --pretty={log_format}{delimiter} {args} {command_range}"
+
     c = cmd.run(command)
     if c.return_code != 0:
         raise GitCommandError(c.err)
diff --git a/commitizen/hooks.py b/commitizen/hooks.py
index f5505d0e82..f60bd9b43e 100644
--- a/commitizen/hooks.py
+++ b/commitizen/hooks.py
@@ -1,12 +1,13 @@
 from __future__ import annotations
 
 import os
+from collections.abc import Mapping
 
 from commitizen import cmd, out
 from commitizen.exceptions import RunHookError
 
 
-def run(hooks, _env_prefix="CZ_", **env):
+def run(hooks: str | list[str], _env_prefix: str = "CZ_", **env: object) -> None:
     if isinstance(hooks, str):
         hooks = [hooks]
 
@@ -24,7 +25,7 @@ def run(hooks, _env_prefix="CZ_", **env):
             raise RunHookError(f"Running hook '{hook}' failed")
 
 
-def _format_env(prefix: str, env: dict[str, str]) -> dict[str, str]:
+def _format_env(prefix: str, env: Mapping[str, object]) -> dict[str, str]:
     """_format_env() prefixes all given environment variables with the given
     prefix so it can be passed directly to cmd.run()."""
     penv = dict(os.environ)
diff --git a/commitizen/out.py b/commitizen/out.py
index 40342e9de5..1bbfe4329d 100644
--- a/commitizen/out.py
+++ b/commitizen/out.py
@@ -1,5 +1,6 @@
 import io
 import sys
+from typing import Any
 
 from termcolor import colored
 
@@ -8,12 +9,12 @@
         sys.stdout.reconfigure(encoding="utf-8")
 
 
-def write(value: str, *args) -> None:
+def write(value: str, *args: object) -> None:
     """Intended to be used when value is multiline."""
     print(value, *args)
 
 
-def line(value: str, *args, **kwargs) -> None:
+def line(value: str, *args: object, **kwargs: Any) -> None:
     """Wrapper in case I want to do something different later."""
     print(value, *args, **kwargs)
 
@@ -33,7 +34,7 @@ def info(value: str) -> None:
     line(message)
 
 
-def diagnostic(value: str):
+def diagnostic(value: str) -> None:
     line(value, file=sys.stderr)
 
 
diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py
index 9cf4ce5927..3e01fe22f8 100644
--- a/commitizen/providers/__init__.py
+++ b/commitizen/providers/__init__.py
@@ -21,7 +21,6 @@
 from commitizen.providers.uv_provider import UvProvider
 
 __all__ = [
-    "get_provider",
     "CargoProvider",
     "CommitizenProvider",
     "ComposerProvider",
@@ -30,6 +29,7 @@
     "PoetryProvider",
     "ScmProvider",
     "UvProvider",
+    "get_provider",
 ]
 
 PROVIDER_ENTRYPOINT = "commitizen.provider"
diff --git a/commitizen/providers/base_provider.py b/commitizen/providers/base_provider.py
index 34048318e2..c91bfdae20 100644
--- a/commitizen/providers/base_provider.py
+++ b/commitizen/providers/base_provider.py
@@ -2,6 +2,7 @@
 
 import json
 from abc import ABC, abstractmethod
+from collections.abc import Mapping
 from pathlib import Path
 from typing import Any, ClassVar
 
@@ -19,7 +20,7 @@ class VersionProvider(ABC):
 
     config: BaseConfig
 
-    def __init__(self, config: BaseConfig):
+    def __init__(self, config: BaseConfig) -> None:
         self.config = config
 
     @abstractmethod
@@ -29,7 +30,7 @@ def get_version(self) -> str:
         """
 
     @abstractmethod
-    def set_version(self, version: str):
+    def set_version(self, version: str) -> None:
         """
         Set the new current version
         """
@@ -58,15 +59,15 @@ def get_version(self) -> str:
         document = json.loads(self.file.read_text())
         return self.get(document)
 
-    def set_version(self, version: str):
+    def set_version(self, version: str) -> None:
         document = json.loads(self.file.read_text())
         self.set(document, version)
         self.file.write_text(json.dumps(document, indent=self.indent) + "\n")
 
-    def get(self, document: dict[str, Any]) -> str:
-        return document["version"]  # type: ignore
+    def get(self, document: Mapping[str, str]) -> str:
+        return document["version"]
 
-    def set(self, document: dict[str, Any], version: str):
+    def set(self, document: dict[str, Any], version: str) -> None:
         document["version"] = version
 
 
@@ -79,13 +80,13 @@ def get_version(self) -> str:
         document = tomlkit.parse(self.file.read_text())
         return self.get(document)
 
-    def set_version(self, version: str):
+    def set_version(self, version: str) -> None:
         document = tomlkit.parse(self.file.read_text())
         self.set(document, version)
         self.file.write_text(tomlkit.dumps(document))
 
     def get(self, document: tomlkit.TOMLDocument) -> str:
-        return document["project"]["version"]  # type: ignore
+        return document["project"]["version"]  # type: ignore[index,return-value]
 
-    def set(self, document: tomlkit.TOMLDocument, version: str):
-        document["project"]["version"] = version  # type: ignore
+    def set(self, document: tomlkit.TOMLDocument, version: str) -> None:
+        document["project"]["version"] = version  # type: ignore[index]
diff --git a/commitizen/providers/cargo_provider.py b/commitizen/providers/cargo_provider.py
index 2e73ff35a1..d453a4aa5b 100644
--- a/commitizen/providers/cargo_provider.py
+++ b/commitizen/providers/cargo_provider.py
@@ -23,18 +23,18 @@ def lock_file(self) -> Path:
 
     def get(self, document: tomlkit.TOMLDocument) -> str:
         try:
-            return document["package"]["version"]  # type: ignore
+            return document["package"]["version"]  # type: ignore[index,return-value]
         except tomlkit.exceptions.NonExistentKey:
             ...
-        return document["workspace"]["package"]["version"]  # type: ignore
+        return document["workspace"]["package"]["version"]  # type: ignore[index,return-value]
 
-    def set(self, document: tomlkit.TOMLDocument, version: str):
+    def set(self, document: tomlkit.TOMLDocument, version: str) -> None:
         try:
-            document["workspace"]["package"]["version"] = version  # type: ignore
+            document["workspace"]["package"]["version"] = version  # type: ignore[index]
             return
         except tomlkit.exceptions.NonExistentKey:
             ...
-        document["package"]["version"] = version  # type: ignore
+        document["package"]["version"] = version  # type: ignore[index]
 
     def set_version(self, version: str) -> None:
         super().set_version(version)
@@ -44,9 +44,9 @@ def set_version(self, version: str) -> None:
     def set_lock_version(self, version: str) -> None:
         cargo_toml_content = tomlkit.parse(self.file.read_text())
         try:
-            package_name = cargo_toml_content["package"]["name"]  # type: ignore
+            package_name = cargo_toml_content["package"]["name"]  # type: ignore[index]
         except tomlkit.exceptions.NonExistentKey:
-            package_name = cargo_toml_content["workspace"]["package"]["name"]  # type: ignore
+            package_name = cargo_toml_content["workspace"]["package"]["name"]  # type: ignore[index]
 
         cargo_lock_content = tomlkit.parse(self.lock_file.read_text())
         packages: tomlkit.items.AoT = cargo_lock_content["package"]  # type: ignore[assignment]
diff --git a/commitizen/providers/commitizen_provider.py b/commitizen/providers/commitizen_provider.py
index a1da25ff72..d9207b19ff 100644
--- a/commitizen/providers/commitizen_provider.py
+++ b/commitizen/providers/commitizen_provider.py
@@ -9,7 +9,7 @@ class CommitizenProvider(VersionProvider):
     """
 
     def get_version(self) -> str:
-        return self.config.settings["version"]  # type: ignore
+        return self.config.settings["version"]  # type: ignore[return-value] # TODO: check if we can fix this by tweaking the `Settings` type
 
-    def set_version(self, version: str):
+    def set_version(self, version: str) -> None:
         self.config.set_key("version", version)
diff --git a/commitizen/providers/npm_provider.py b/commitizen/providers/npm_provider.py
index 12900ff7de..3125447250 100644
--- a/commitizen/providers/npm_provider.py
+++ b/commitizen/providers/npm_provider.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import json
+from collections.abc import Mapping
 from pathlib import Path
 from typing import Any, ClassVar
 
@@ -58,8 +59,8 @@ def set_version(self, version: str) -> None:
                 json.dumps(shrinkwrap_document, indent=self.indent) + "\n"
             )
 
-    def get_package_version(self, document: dict[str, Any]) -> str:
-        return document["version"]  # type: ignore
+    def get_package_version(self, document: Mapping[str, str]) -> str:
+        return document["version"]
 
     def set_package_version(
         self, document: dict[str, Any], version: str
diff --git a/commitizen/providers/poetry_provider.py b/commitizen/providers/poetry_provider.py
index 7aa28f56d9..f63b13b793 100644
--- a/commitizen/providers/poetry_provider.py
+++ b/commitizen/providers/poetry_provider.py
@@ -13,7 +13,7 @@ class PoetryProvider(TomlProvider):
     filename = "pyproject.toml"
 
     def get(self, pyproject: tomlkit.TOMLDocument) -> str:
-        return pyproject["tool"]["poetry"]["version"]  # type: ignore
+        return pyproject["tool"]["poetry"]["version"]  # type: ignore[index,return-value]
 
-    def set(self, pyproject: tomlkit.TOMLDocument, version: str):
-        pyproject["tool"]["poetry"]["version"] = version  # type: ignore
+    def set(self, pyproject: tomlkit.TOMLDocument, version: str) -> None:
+        pyproject["tool"]["poetry"]["version"] = version  # type: ignore[index]
diff --git a/commitizen/providers/scm_provider.py b/commitizen/providers/scm_provider.py
index cb575148cb..3085b16efa 100644
--- a/commitizen/providers/scm_provider.py
+++ b/commitizen/providers/scm_provider.py
@@ -23,6 +23,6 @@ def get_version(self) -> str:
             return "0.0.0"
         return str(versions[-1])
 
-    def set_version(self, version: str):
+    def set_version(self, version: str) -> None:
         # Not necessary
         pass
diff --git a/commitizen/question.py b/commitizen/question.py
new file mode 100644
index 0000000000..393e386092
--- /dev/null
+++ b/commitizen/question.py
@@ -0,0 +1,32 @@
+from typing import Callable, Literal, TypedDict, Union
+
+
+class Choice(TypedDict, total=False):
+    value: str
+    name: str
+    key: str
+
+
+class ListQuestion(TypedDict, total=False):
+    type: Literal["list"]
+    name: str
+    message: str
+    choices: list[Choice]
+    use_shortcuts: bool
+
+
+class InputQuestion(TypedDict, total=False):
+    type: Literal["input"]
+    name: str
+    message: str
+    filter: Callable[[str], str]
+
+
+class ConfirmQuestion(TypedDict):
+    type: Literal["confirm"]
+    name: str
+    message: str
+    default: bool
+
+
+CzQuestion = Union[ListQuestion, InputQuestion, ConfirmQuestion]
diff --git a/commitizen/tags.py b/commitizen/tags.py
index 2b9a4b091a..b19bb89e09 100644
--- a/commitizen/tags.py
+++ b/commitizen/tags.py
@@ -2,7 +2,7 @@
 
 import re
 import warnings
-from collections.abc import Sequence
+from collections.abc import Iterable, Sequence
 from dataclasses import dataclass, field
 from functools import cached_property
 from string import Template
@@ -89,14 +89,14 @@ class TagRules:
     merge_prereleases: bool = False
 
     @cached_property
-    def version_regexes(self) -> Sequence[re.Pattern]:
+    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]
 
     @cached_property
-    def ignored_regexes(self) -> Sequence[re.Pattern]:
+    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]
@@ -135,8 +135,8 @@ def is_ignored_tag(self, tag: str | GitTag) -> bool:
         return any(regex.match(tag) for regex in self.ignored_regexes)
 
     def get_version_tags(
-        self, tags: Sequence[GitTag], warn: bool = False
-    ) -> Sequence[GitTag]:
+        self, tags: Iterable[GitTag], warn: bool = False
+    ) -> list[GitTag]:
         """Filter in version tags and warn on unexpected tags"""
         return [tag for tag in tags if self.is_version_tag(tag, warn)]
 
@@ -174,11 +174,7 @@ def include_in_changelog(self, tag: GitTag) -> bool:
             version = self.extract_version(tag)
         except InvalidVersion:
             return False
-
-        if self.merge_prereleases and version.is_prerelease:
-            return False
-
-        return True
+        return not (self.merge_prereleases and version.is_prerelease)
 
     def search_version(self, text: str, last: bool = False) -> VersionTag | None:
         """
@@ -240,15 +236,15 @@ def normalize_tag(
         )
 
     def find_tag_for(
-        self, tags: Sequence[GitTag], version: Version | str
+        self, tags: Iterable[GitTag], version: Version | str
     ) -> GitTag | None:
         """Find the first matching tag for a given version."""
         version = self.scheme(version) if isinstance(version, str) else version
-        possible_tags = [
+        possible_tags = set(
             self.normalize_tag(version, f)
             for f in (self.tag_format, *self.legacy_tag_formats)
-        ]
-        candidates = [t for t in tags if any(t.name == p for p in possible_tags)]
+        )
+        candidates = [t for t in tags if t.name in possible_tags]
         if len(candidates) > 1:
             warnings.warn(
                 UserWarning(
diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py
index 84ded9316e..e9f99c5514 100644
--- a/commitizen/version_schemes.py
+++ b/commitizen/version_schemes.py
@@ -19,7 +19,7 @@
 else:
     import importlib_metadata as metadata
 
-from packaging.version import InvalidVersion  # noqa: F401: expose the common exception
+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
@@ -41,7 +41,9 @@
 
 Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"]
 Prerelease: TypeAlias = Literal["alpha", "beta", "rc"]
-DEFAULT_VERSION_PARSER = r"v?(?P<version>([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)"
+_DEFAULT_VERSION_PARSER = re.compile(
+    r"v?(?P<version>([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)"
+)
 
 
 @runtime_checkable
@@ -49,7 +51,7 @@ class VersionProtocol(Protocol):
     parser: ClassVar[re.Pattern]
     """Regex capturing this version scheme into a `version` group"""
 
-    def __init__(self, version: str):
+    def __init__(self, version: str) -> None:
         """
         Initialize a version object from its string representation.
 
@@ -156,7 +158,7 @@ class BaseVersion(_BaseVersion):
     A base class implementing the `VersionProtocol` for PEP440-like versions.
     """
 
-    parser: ClassVar[re.Pattern] = re.compile(DEFAULT_VERSION_PARSER)
+    parser: ClassVar[re.Pattern] = _DEFAULT_VERSION_PARSER
     """Regex capturing this version scheme into a `version` group"""
 
     @property
@@ -264,40 +266,36 @@ def bump(
 
         if self.local and is_local_version:
             local_version = self.scheme(self.local).bump(increment)
-            return self.scheme(f"{self.public}+{local_version}")  # type: ignore
-        else:
-            if not self.is_prerelease:
-                base = self.increment_base(increment)
-            elif exact_increment:
-                base = self.increment_base(increment)
-            else:
-                base = f"{self.major}.{self.minor}.{self.micro}"
-                if increment == PATCH:
-                    pass
-                elif increment == MINOR:
-                    if self.micro != 0:
-                        base = self.increment_base(increment)
-                elif increment == MAJOR:
-                    if self.minor != 0 or self.micro != 0:
-                        base = self.increment_base(increment)
-            dev_version = self.generate_devrelease(devrelease)
-
-            release = list(self.release)
-            if len(release) < 3:
-                release += [0] * (3 - len(release))
-            current_base = ".".join(str(part) for part in release)
-            if base == current_base:
-                pre_version = self.generate_prerelease(
-                    prerelease, offset=prerelease_offset
-                )
-            else:
-                base_version = cast(BaseVersion, self.scheme(base))
-                pre_version = base_version.generate_prerelease(
-                    prerelease, offset=prerelease_offset
-                )
-            build_metadata = self.generate_build_metadata(build_metadata)
-            # TODO: post version
-            return self.scheme(f"{base}{pre_version}{dev_version}{build_metadata}")  # type: ignore
+            return self.scheme(f"{self.public}+{local_version}")  # type: ignore[return-value]
+
+        base = self._get_increment_base(increment, exact_increment)
+        dev_version = self.generate_devrelease(devrelease)
+
+        release = list(self.release)
+        if len(release) < 3:
+            release += [0] * (3 - len(release))
+        current_base = ".".join(str(part) for part in release)
+
+        pre_version = (
+            self if base == current_base else cast(BaseVersion, self.scheme(base))
+        ).generate_prerelease(prerelease, offset=prerelease_offset)
+
+        # TODO: post version
+        return self.scheme(
+            f"{base}{pre_version}{dev_version}{self.generate_build_metadata(build_metadata)}"
+        )  # type: ignore[return-value]
+
+    def _get_increment_base(
+        self, increment: Increment | 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))
+        ):
+            return self.increment_base(increment)
+        return f"{self.major}.{self.minor}.{self.micro}"
 
 
 class Pep440(BaseVersion):
@@ -316,7 +314,7 @@ class SemVer(BaseVersion):
     """
 
     def __str__(self) -> str:
-        parts = []
+        parts: list[str] = []
 
         # Epoch
         if self.epoch != 0:
@@ -364,7 +362,7 @@ def prerelease(self) -> str | None:
         return None
 
     def __str__(self) -> str:
-        parts = []
+        parts: list[str] = []
 
         # Epoch
         if self.epoch != 0:
@@ -373,9 +371,19 @@ def __str__(self) -> str:
         # Release segment
         parts.append(".".join(str(x) for x in self.release))
 
+        if prerelease := self._get_prerelease():
+            parts.append(f"-{prerelease}")
+
+        # Local version segment
+        if self.local:
+            parts.append(f"+{self.local}")
+
+        return "".join(parts)
+
+    def _get_prerelease(self) -> str:
         # Pre-release identifiers
         # See: https://semver.org/spec/v2.0.0.html#spec-item-9
-        prerelease_parts = []
+        prerelease_parts: list[str] = []
         if self.prerelease:
             prerelease_parts.append(f"{self.prerelease}")
 
@@ -387,15 +395,7 @@ def __str__(self) -> str:
         if self.dev is not None:
             prerelease_parts.append(f"dev.{self.dev}")
 
-        if prerelease_parts:
-            parts.append("-")
-            parts.append(".".join(prerelease_parts))
-
-        # Local version segment
-        if self.local:
-            parts.append(f"+{self.local}")
-
-        return "".join(parts)
+        return ".".join(prerelease_parts)
 
 
 DEFAULT_SCHEME: VersionScheme = Pep440
@@ -419,7 +419,7 @@ def get_version_scheme(settings: Settings, name: str | None = None) -> VersionSc
     if deprecated_setting:
         warnings.warn(
             DeprecationWarning(
-                "`version_type` setting is deprecated and will be removed in commitizen 4. "
+                "`version_type` setting is deprecated and will be removed in v5. "
                 "Please use `version_scheme` instead"
             )
         )
diff --git a/docs/commands/commit.md b/docs/commands/commit.md
index 5a073a2644..be9d193b97 100644
--- a/docs/commands/commit.md
+++ b/docs/commands/commit.md
@@ -42,7 +42,7 @@ cz c -a -- -n            # Stage all changes and skip the pre-commit and commit-
 ```
 
 !!! warning
-    The `--signoff` option (or `-s`) is now recommended being used with the new syntax: `cz commit -- -s`. The old syntax `cz commit --signoff` is deprecated.
+    The `--signoff` option (or `-s`) is now recommended being used with the new syntax: `cz commit -- -s`. The old syntax `cz commit --signoff` is deprecated and will be removed in v5.
 
 ### Retry
 
diff --git a/poetry.lock b/poetry.lock
index 64ec358bb3..c5c893931a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -17,60 +17,76 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
 
 [[package]]
 name = "asttokens"
-version = "2.4.1"
+version = "3.0.0"
 description = "Annotate AST trees with source code positions"
 optional = false
-python-versions = "*"
+python-versions = ">=3.8"
 groups = ["dev"]
 files = [
-    {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
-    {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
+    {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
+    {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
 ]
 
-[package.dependencies]
-six = ">=1.12.0"
-
 [package.extras]
-astroid = ["astroid (>=1,<2) ; python_version < \"3\"", "astroid (>=2,<4) ; python_version >= \"3\""]
-test = ["astroid (>=1,<2) ; python_version < \"3\"", "astroid (>=2,<4) ; python_version >= \"3\"", "pytest"]
+astroid = ["astroid (>=2,<4)"]
+test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"]
 
 [[package]]
 name = "babel"
-version = "2.16.0"
+version = "2.17.0"
 description = "Internationalization utilities"
 optional = false
 python-versions = ">=3.8"
 groups = ["documentation"]
 files = [
-    {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
-    {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
+    {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
+    {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
 ]
 
 [package.extras]
-dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
+dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
+
+[[package]]
+name = "backrefs"
+version = "5.8"
+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"},
+]
+
+[package.extras]
+extras = ["regex"]
 
 [[package]]
 name = "cachetools"
-version = "5.5.1"
+version = "6.0.0"
 description = "Extensible memoizing collections and decorators"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.9"
 groups = ["dev"]
 files = [
-    {file = "cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb"},
-    {file = "cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95"},
+    {file = "cachetools-6.0.0-py3-none-any.whl", hash = "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e"},
+    {file = "cachetools-6.0.0.tar.gz", hash = "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf"},
 ]
 
 [[package]]
 name = "certifi"
-version = "2024.8.30"
+version = "2025.4.26"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
 groups = ["documentation"]
 files = [
-    {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
-    {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
+    {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
+    {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
 ]
 
 [[package]]
@@ -99,116 +115,133 @@ files = [
 
 [[package]]
 name = "charset-normalizer"
-version = "3.4.1"
+version = "3.4.2"
 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.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
-    {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
-    {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
-    {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
-    {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
-    {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
-    {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
-    {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
-    {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
-    {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
+    {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"},
 ]
 
 [[package]]
 name = "click"
-version = "8.1.7"
+version = "8.1.8"
 description = "Composable command line interface toolkit"
 optional = false
 python-versions = ">=3.7"
 groups = ["documentation"]
+markers = "python_version == \"3.9\""
 files = [
-    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
-    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+    {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
+    {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
+]
+
+[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]
@@ -228,74 +261,79 @@ files = [
 
 [[package]]
 name = "coverage"
-version = "7.6.8"
+version = "7.8.2"
 description = "Code coverage measurement for Python"
 optional = false
 python-versions = ">=3.9"
 groups = ["test"]
 files = [
-    {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"},
-    {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"},
-    {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"},
-    {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"},
-    {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"},
-    {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"},
-    {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"},
-    {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"},
-    {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"},
-    {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"},
-    {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"},
-    {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"},
-    {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"},
-    {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"},
-    {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"},
-    {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"},
-    {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"},
-    {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"},
-    {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"},
-    {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"},
-    {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"},
-    {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"},
-    {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"},
-    {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"},
-    {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"},
-    {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"},
-    {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"},
-    {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"},
-    {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"},
-    {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"},
-    {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"},
-    {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"},
-    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"},
-    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"},
-    {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"},
-    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"},
-    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"},
-    {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"},
-    {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"},
-    {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"},
-    {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"},
-    {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"},
-    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"},
-    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"},
-    {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"},
-    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"},
-    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"},
-    {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"},
-    {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"},
-    {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"},
-    {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"},
-    {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"},
-    {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"},
-    {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"},
-    {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"},
-    {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"},
-    {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"},
-    {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"},
-    {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"},
-    {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"},
-    {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"},
-    {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"},
+    {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"},
 ]
 
 [package.dependencies]
@@ -306,26 +344,26 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
 
 [[package]]
 name = "decli"
-version = "0.6.2"
+version = "0.6.3"
 description = "Minimal, easy-to-use, declarative cli tool"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.9"
 groups = ["main"]
 files = [
-    {file = "decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed"},
-    {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"},
+    {file = "decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3"},
+    {file = "decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656"},
 ]
 
 [[package]]
 name = "decorator"
-version = "5.1.1"
+version = "5.2.1"
 description = "Decorators for Humans"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
 groups = ["dev"]
 files = [
-    {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
-    {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+    {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
+    {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
 ]
 
 [[package]]
@@ -360,17 +398,20 @@ files = [
 
 [[package]]
 name = "exceptiongroup"
-version = "1.2.2"
+version = "1.3.0"
 description = "Backport of PEP 654 (exception groups)"
 optional = false
 python-versions = ">=3.7"
 groups = ["dev", "test"]
 markers = "python_version < \"3.11\""
 files = [
-    {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
-    {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
+    {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
+    {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
 ]
 
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
 [package.extras]
 test = ["pytest (>=6)"]
 
@@ -391,14 +432,14 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
 
 [[package]]
 name = "executing"
-version = "2.1.0"
+version = "2.2.0"
 description = "Get the currently executing AST node of a frame, and other information"
 optional = false
 python-versions = ">=3.8"
 groups = ["dev"]
 files = [
-    {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"},
-    {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"},
+    {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"},
+    {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"},
 ]
 
 [package.extras]
@@ -406,31 +447,31 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
 
 [[package]]
 name = "filelock"
-version = "3.16.1"
+version = "3.18.0"
 description = "A platform independent file lock."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["dev", "linters"]
 files = [
-    {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
-    {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
+    {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
+    {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
 ]
 
 [package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
+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.1"
+version = "1.5.2"
 description = "Let your Python tests travel through time"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 groups = ["test"]
 files = [
-    {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
-    {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
+    {file = "freezegun-1.5.2-py3-none-any.whl", hash = "sha256:5aaf3ba229cda57afab5bd311f0108d86b6fb119ae89d2cd9c43ec8c1733c85b"},
+    {file = "freezegun-1.5.2.tar.gz", hash = "sha256:a54ae1d2f9c02dbf42e02c18a3ab95ab4295818b549a34dac55592d72a905181"},
 ]
 
 [package.dependencies]
@@ -456,14 +497,14 @@ dev = ["flake8", "markdown", "twine", "wheel"]
 
 [[package]]
 name = "identify"
-version = "2.6.3"
+version = "2.6.12"
 description = "File identification library for Python"
 optional = false
 python-versions = ">=3.9"
 groups = ["linters"]
 files = [
-    {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"},
-    {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"},
+    {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"},
+    {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"},
 ]
 
 [package.extras]
@@ -509,16 +550,41 @@ 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.0.0"
+version = "2.1.0"
 description = "brain-dead simple config-ini parsing"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 groups = ["test"]
 files = [
-    {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
-    {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+    {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
+    {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
 ]
 
 [[package]]
@@ -528,6 +594,7 @@ 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"},
@@ -559,6 +626,46 @@ 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"
@@ -581,14 +688,14 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
 
 [[package]]
 name = "jinja2"
-version = "3.1.5"
+version = "3.1.6"
 description = "A very fast and expressive template engine."
 optional = false
 python-versions = ">=3.7"
 groups = ["main", "documentation"]
 files = [
-    {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
-    {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
+    {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+    {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
 ]
 
 [package.dependencies]
@@ -599,21 +706,21 @@ i18n = ["Babel (>=2.7)"]
 
 [[package]]
 name = "markdown"
-version = "3.7"
+version = "3.8"
 description = "Python implementation of John Gruber's Markdown."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["documentation"]
 files = [
-    {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"},
-    {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"},
+    {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"},
+    {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"},
 ]
 
 [package.dependencies]
 importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
 
 [package.extras]
-docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
 testing = ["coverage", "pyyaml"]
 
 [[package]]
@@ -803,27 +910,27 @@ pyyaml = ">=5.1"
 
 [[package]]
 name = "mkdocs-material"
-version = "9.5.50"
+version = "9.6.14"
 description = "Documentation that simply works"
 optional = false
 python-versions = ">=3.8"
 groups = ["documentation"]
 files = [
-    {file = "mkdocs_material-9.5.50-py3-none-any.whl", hash = "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385"},
-    {file = "mkdocs_material-9.5.50.tar.gz", hash = "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825"},
+    {file = "mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b"},
+    {file = "mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754"},
 ]
 
 [package.dependencies]
 babel = ">=2.10,<3.0"
+backrefs = ">=5.7.post1,<6.0"
 colorama = ">=0.4,<1.0"
-jinja2 = ">=3.0,<4.0"
+jinja2 = ">=3.1,<4.0"
 markdown = ">=3.2,<4.0"
 mkdocs = ">=1.6,<2.0"
 mkdocs-material-extensions = ">=1.3,<2.0"
 paginate = ">=0.5,<1.0"
 pygments = ">=2.16,<3.0"
 pymdown-extensions = ">=10.2,<11.0"
-regex = ">=2022.4"
 requests = ">=2.26,<3.0"
 
 [package.extras]
@@ -845,54 +952,49 @@ files = [
 
 [[package]]
 name = "mypy"
-version = "1.14.1"
+version = "1.16.0"
 description = "Optional static typing for Python"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["linters"]
 files = [
-    {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
-    {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
-    {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
-    {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
-    {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
-    {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
-    {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
-    {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
-    {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
-    {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
-    {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
-    {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
-    {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
-    {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
-    {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
-    {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
-    {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
-    {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
-    {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
-    {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
-    {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
-    {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
-    {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
-    {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
-    {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
-    {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
-    {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
-    {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
-    {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
-    {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
-    {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
-    {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
-    {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
-    {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
-    {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
-    {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
-    {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
-    {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
+    {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"},
 ]
 
 [package.dependencies]
 mypy_extensions = ">=1.0.0"
+pathspec = ">=0.9.0"
 tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
 typing_extensions = ">=4.6.0"
 
@@ -905,14 +1007,14 @@ reports = ["lxml"]
 
 [[package]]
 name = "mypy-extensions"
-version = "1.0.0"
+version = "1.1.0"
 description = "Type system extensions for programs checked with the mypy type checker."
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
 groups = ["linters"]
 files = [
-    {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
-    {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+    {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+    {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
 ]
 
 [[package]]
@@ -929,14 +1031,14 @@ files = [
 
 [[package]]
 name = "packaging"
-version = "24.2"
+version = "25.0"
 description = "Core utilities for Python packages"
 optional = false
 python-versions = ">=3.8"
 groups = ["main", "dev", "documentation", "test"]
 files = [
-    {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
-    {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
+    {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+    {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
 ]
 
 [[package]]
@@ -989,7 +1091,7 @@ version = "0.12.1"
 description = "Utility library for gitignore style pattern matching of file paths."
 optional = false
 python-versions = ">=3.8"
-groups = ["documentation"]
+groups = ["documentation", "linters"]
 files = [
     {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
     {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@@ -1002,7 +1104,7 @@ description = "Pexpect allows easy control of interactive console applications."
 optional = false
 python-versions = "*"
 groups = ["dev"]
-markers = "sys_platform != \"win32\""
+markers = "python_version == \"3.9\" and sys_platform != \"win32\" or sys_platform != \"win32\" and sys_platform != \"emscripten\""
 files = [
     {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
     {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
@@ -1013,36 +1115,36 @@ ptyprocess = ">=0.5"
 
 [[package]]
 name = "platformdirs"
-version = "4.3.6"
+version = "4.3.8"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["dev", "documentation", "linters"]
 files = [
-    {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
-    {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
+    {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
+    {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
 ]
 
 [package.extras]
-docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
-type = ["mypy (>=1.11.2)"]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
 
 [[package]]
 name = "pluggy"
-version = "1.5.0"
+version = "1.6.0"
 description = "plugin and hook calling mechanisms for python"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["dev", "test"]
 files = [
-    {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
-    {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+    {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+    {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
 ]
 
 [package.extras]
 dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
 
 [[package]]
 name = "poethepoet"
@@ -1066,14 +1168,14 @@ poetry-plugin = ["poetry (>=1.2.0,<3.0.0) ; python_version < \"4.0\""]
 
 [[package]]
 name = "pre-commit"
-version = "4.1.0"
+version = "4.2.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.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
-    {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
+    {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"},
 ]
 
 [package.dependencies]
@@ -1085,14 +1187,14 @@ virtualenv = ">=20.10.0"
 
 [[package]]
 name = "prompt-toolkit"
-version = "3.0.48"
+version = "3.0.51"
 description = "Library for building powerful interactive command lines in Python"
 optional = false
-python-versions = ">=3.7.0"
+python-versions = ">=3.8"
 groups = ["main", "dev"]
 files = [
-    {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"},
-    {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"},
+    {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"},
+    {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"},
 ]
 
 [package.dependencies]
@@ -1105,7 +1207,7 @@ description = "Run a subprocess in a pseudo terminal"
 optional = false
 python-versions = "*"
 groups = ["dev"]
-markers = "sys_platform != \"win32\""
+markers = "python_version == \"3.9\" and sys_platform != \"win32\" or sys_platform != \"win32\" and sys_platform != \"emscripten\""
 files = [
     {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
     {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
@@ -1128,14 +1230,14 @@ tests = ["pytest"]
 
 [[package]]
 name = "pygments"
-version = "2.18.0"
+version = "2.19.1"
 description = "Pygments is a syntax highlighting package written in Python."
 optional = false
 python-versions = ">=3.8"
-groups = ["dev", "documentation", "script"]
+groups = ["dev", "documentation", "script", "test"]
 files = [
-    {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
-    {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
+    {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
+    {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
 ]
 
 [package.extras]
@@ -1143,14 +1245,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
 
 [[package]]
 name = "pymdown-extensions"
-version = "10.12"
+version = "10.15"
 description = "Extension pack for Python Markdown."
 optional = false
 python-versions = ">=3.8"
 groups = ["documentation"]
 files = [
-    {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"},
-    {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"},
+    {file = "pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f"},
+    {file = "pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7"},
 ]
 
 [package.dependencies]
@@ -1158,61 +1260,62 @@ markdown = ">=3.6"
 pyyaml = "*"
 
 [package.extras]
-extra = ["pygments (>=2.12)"]
+extra = ["pygments (>=2.19.1)"]
 
 [[package]]
 name = "pyproject-api"
-version = "1.9.0"
+version = "1.9.1"
 description = "API to interact with the python pyproject.toml based projects"
 optional = false
 python-versions = ">=3.9"
 groups = ["dev"]
 files = [
-    {file = "pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766"},
-    {file = "pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e"},
+    {file = "pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948"},
+    {file = "pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335"},
 ]
 
 [package.dependencies]
-packaging = ">=24.2"
+packaging = ">=25"
 tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
 
 [package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3)"]
-testing = ["covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "setuptools (>=75.8)"]
+docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3.2)"]
+testing = ["covdefaults (>=2.3)", "pytest (>=8.3.5)", "pytest-cov (>=6.1.1)", "pytest-mock (>=3.14)", "setuptools (>=80.3.1)"]
 
 [[package]]
 name = "pytest"
-version = "8.3.4"
+version = "8.4.0"
 description = "pytest: simple powerful testing with Python"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["test"]
 files = [
-    {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
-    {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
+    {file = "pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e"},
+    {file = "pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6"},
 ]
 
 [package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
+iniconfig = ">=1"
+packaging = ">=20"
 pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
 tomli = {version = ">=1", markers = "python_version < \"3.11\""}
 
 [package.extras]
-dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
 
 [[package]]
 name = "pytest-cov"
-version = "6.0.0"
+version = "6.1.1"
 description = "Pytest plugin for measuring coverage."
 optional = false
 python-versions = ">=3.9"
 groups = ["test"]
 files = [
-    {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"},
-    {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
+    {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"},
+    {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"},
 ]
 
 [package.dependencies]
@@ -1224,18 +1327,22 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
 
 [[package]]
 name = "pytest-datadir"
-version = "1.5.0"
+version = "1.7.2"
 description = "pytest plugin for test data directories and files"
 optional = false
 python-versions = ">=3.8"
 groups = ["test"]
 files = [
-    {file = "pytest-datadir-1.5.0.tar.gz", hash = "sha256:1617ed92f9afda0c877e4eac91904b5f779d24ba8f5e438752e3ae39d8d2ee3f"},
-    {file = "pytest_datadir-1.5.0-py3-none-any.whl", hash = "sha256:34adf361bcc7b37961bbc1dfa8d25a4829e778bab461703c38a5c50ca9c36dc8"},
+    {file = "pytest_datadir-1.7.2-py3-none-any.whl", hash = "sha256:8392ba0e9eaf37030e663dcd91cc5123dec99c44300f0c5eac44f35f13f0e086"},
+    {file = "pytest_datadir-1.7.2.tar.gz", hash = "sha256:15f5228a35d0a3205e4968e75d3b9cca91762424e1eafc21eb637d380a48443e"},
 ]
 
 [package.dependencies]
-pytest = ">=5.0"
+pytest = ">=7.0"
+
+[package.extras]
+dev = ["pre-commit", "pytest-datadir[testing]"]
+testing = ["pytest", "tox"]
 
 [[package]]
 name = "pytest-freezer"
@@ -1255,14 +1362,14 @@ pytest = ">=3.6"
 
 [[package]]
 name = "pytest-mock"
-version = "3.14.0"
+version = "3.14.1"
 description = "Thin-wrapper around the mock package for easier use with pytest"
 optional = false
 python-versions = ">=3.8"
 groups = ["test"]
 files = [
-    {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
-    {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
+    {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"},
+    {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"},
 ]
 
 [package.dependencies]
@@ -1273,19 +1380,19 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
 
 [[package]]
 name = "pytest-regressions"
-version = "2.7.0"
+version = "2.8.0"
 description = "Easy to use fixtures to write regression tests."
 optional = false
 python-versions = ">=3.9"
 groups = ["test"]
 files = [
-    {file = "pytest_regressions-2.7.0-py3-none-any.whl", hash = "sha256:69f5e3f03493cf0ef84d96d23e50a546617c198b1d7746f2e2b9e441cbab4847"},
-    {file = "pytest_regressions-2.7.0.tar.gz", hash = "sha256:4c30064e0923929012c94f5d6f35205be06fd8709c7f0dba0228e05c460af05e"},
+    {file = "pytest_regressions-2.8.0-py3-none-any.whl", hash = "sha256:2926f37efa5fd6793806b10352e274c5284a5469a845aeab6243e86f9214766f"},
+    {file = "pytest_regressions-2.8.0.tar.gz", hash = "sha256:775044e17117f5427df2caad3ab1c66889abe770a0ce2bc3f24fdeac99af76fe"},
 ]
 
 [package.dependencies]
 pytest = ">=6.2.0"
-pytest-datadir = ">=1.2.0"
+pytest-datadir = ">=1.7.0"
 pyyaml = "*"
 
 [package.extras]
@@ -1296,14 +1403,14 @@ num = ["numpy", "pandas"]
 
 [[package]]
 name = "pytest-xdist"
-version = "3.6.1"
+version = "3.7.0"
 description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["test"]
 files = [
-    {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
-    {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
+    {file = "pytest_xdist-3.7.0-py3-none-any.whl", hash = "sha256:7d3fbd255998265052435eb9daa4e99b62e6fb9cfb6efd1f858d4d8c0c7f0ca0"},
+    {file = "pytest_xdist-3.7.0.tar.gz", hash = "sha256:f9248c99a7c15b7d2f90715df93610353a485827bc06eefb6566d23f6400f126"},
 ]
 
 [package.dependencies]
@@ -1395,14 +1502,14 @@ files = [
 
 [[package]]
 name = "pyyaml-env-tag"
-version = "0.1"
-description = "A custom YAML tag for referencing environment variables in YAML files. "
+version = "1.1"
+description = "A custom YAML tag for referencing environment variables in YAML files."
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.9"
 groups = ["documentation"]
 files = [
-    {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
-    {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
+    {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"},
+    {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"},
 ]
 
 [package.dependencies]
@@ -1423,110 +1530,6 @@ files = [
 [package.dependencies]
 prompt_toolkit = ">=2.0,<4.0"
 
-[[package]]
-name = "regex"
-version = "2024.11.6"
-description = "Alternative regular expression module, to replace re."
-optional = false
-python-versions = ">=3.8"
-groups = ["documentation"]
-files = [
-    {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
-    {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
-    {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
-    {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
-    {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
-    {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
-    {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
-    {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
-    {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
-    {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
-    {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
-    {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
-    {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
-    {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
-    {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
-    {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
-    {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
-    {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
-    {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
-    {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
-    {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
-    {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
-    {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
-    {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
-    {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
-    {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
-    {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
-    {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
-    {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
-    {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
-    {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
-    {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
-    {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
-    {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
-    {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
-    {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
-    {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
-    {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
-    {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
-    {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
-    {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
-    {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
-    {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
-    {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
-    {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
-    {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
-    {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
-    {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
-    {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
-    {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
-    {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
-    {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
-    {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
-    {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
-    {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
-    {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
-    {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
-    {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
-    {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
-    {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
-    {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
-    {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
-    {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
-    {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
-    {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
-    {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
-    {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
-    {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
-    {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
-    {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
-    {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
-    {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
-    {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
-    {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
-    {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
-    {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
-    {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
-    {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
-    {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
-]
-
 [[package]]
 name = "requests"
 version = "2.32.3"
@@ -1571,42 +1574,42 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
 
 [[package]]
 name = "ruff"
-version = "0.9.4"
+version = "0.11.13"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = false
 python-versions = ">=3.7"
 groups = ["linters"]
 files = [
-    {file = "ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706"},
-    {file = "ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf"},
-    {file = "ruff-0.9.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214"},
-    {file = "ruff-0.9.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231"},
-    {file = "ruff-0.9.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b"},
-    {file = "ruff-0.9.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6"},
-    {file = "ruff-0.9.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c"},
-    {file = "ruff-0.9.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0"},
-    {file = "ruff-0.9.4-py3-none-win32.whl", hash = "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402"},
-    {file = "ruff-0.9.4-py3-none-win_amd64.whl", hash = "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e"},
-    {file = "ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41"},
-    {file = "ruff-0.9.4.tar.gz", hash = "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7"},
+    {file = "ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46"},
+    {file = "ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48"},
+    {file = "ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71"},
+    {file = "ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9"},
+    {file = "ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc"},
+    {file = "ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7"},
+    {file = "ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432"},
+    {file = "ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492"},
+    {file = "ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250"},
+    {file = "ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3"},
+    {file = "ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b"},
+    {file = "ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514"},
 ]
 
 [[package]]
 name = "six"
-version = "1.16.0"
+version = "1.17.0"
 description = "Python 2 and 3 compatibility utilities"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-groups = ["dev", "documentation", "test"]
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["documentation", "test"]
 files = [
-    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
-    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+    {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+    {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
 ]
 
 [[package]]
@@ -1631,14 +1634,14 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
 
 [[package]]
 name = "termcolor"
-version = "2.5.0"
+version = "3.1.0"
 description = "ANSI color formatting for output in terminal"
 optional = false
 python-versions = ">=3.9"
 groups = ["main"]
 files = [
-    {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"},
-    {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"},
+    {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"},
+    {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"},
 ]
 
 [package.extras]
@@ -1689,30 +1692,30 @@ markers = {dev = "python_version < \"3.11\"", linters = "python_version < \"3.11
 
 [[package]]
 name = "tomlkit"
-version = "0.13.2"
+version = "0.13.3"
 description = "Style preserving TOML library"
 optional = false
 python-versions = ">=3.8"
 groups = ["main"]
 files = [
-    {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
-    {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
+    {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"},
+    {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"},
 ]
 
 [[package]]
 name = "tox"
-version = "4.24.1"
+version = "4.26.0"
 description = "tox is a generic virtualenv management and test command line tool"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["dev"]
 files = [
-    {file = "tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75"},
-    {file = "tox-4.24.1.tar.gz", hash = "sha256:083a720adbc6166fff0b7d1df9d154f9d00bfccb9403b8abf6bc0ee435d6a62e"},
+    {file = "tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224"},
+    {file = "tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca"},
 ]
 
 [package.dependencies]
-cachetools = ">=5.5"
+cachetools = ">=5.5.1"
 chardet = ">=5.2"
 colorama = ">=0.4.6"
 filelock = ">=3.16.1"
@@ -1720,12 +1723,12 @@ packaging = ">=24.2"
 platformdirs = ">=4.3.6"
 pluggy = ">=1.5"
 pyproject-api = ">=1.8"
-tomli = {version = ">=2.1", markers = "python_version < \"3.11\""}
+tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""}
 typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""}
-virtualenv = ">=20.27.1"
+virtualenv = ">=20.31"
 
 [package.extras]
-test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"]
+test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"]
 
 [[package]]
 name = "traitlets"
@@ -1743,40 +1746,52 @@ files = [
 docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
 test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
 
+[[package]]
+name = "types-colorama"
+version = "0.4.15.20240311"
+description = "Typing stubs for colorama"
+optional = false
+python-versions = ">=3.8"
+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"},
+]
+
 [[package]]
 name = "types-deprecated"
-version = "1.2.15.20241117"
+version = "1.2.15.20250304"
 description = "Typing stubs for Deprecated"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["linters"]
 files = [
-    {file = "types-Deprecated-1.2.15.20241117.tar.gz", hash = "sha256:924002c8b7fddec51ba4949788a702411a2e3636cd9b2a33abd8ee119701d77e"},
-    {file = "types_Deprecated-1.2.15.20241117-py3-none-any.whl", hash = "sha256:a0cc5e39f769fc54089fd8e005416b55d74aa03f6964d2ed1a0b0b2e28751884"},
+    {file = "types_deprecated-1.2.15.20250304-py3-none-any.whl", hash = "sha256:86a65aa550ea8acf49f27e226b8953288cd851de887970fbbdf2239c116c3107"},
+    {file = "types_deprecated-1.2.15.20250304.tar.gz", hash = "sha256:c329030553029de5cc6cb30f269c11f4e00e598c4241290179f63cda7d33f719"},
 ]
 
 [[package]]
 name = "types-python-dateutil"
-version = "2.9.0.20241206"
+version = "2.9.0.20250516"
 description = "Typing stubs for python-dateutil"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["linters"]
 files = [
-    {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"},
-    {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"},
+    {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"},
 ]
 
 [[package]]
 name = "types-pyyaml"
-version = "6.0.12.20241230"
+version = "6.0.12.20250516"
 description = "Typing stubs for PyYAML"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["linters"]
 files = [
-    {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"},
-    {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"},
+    {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"},
 ]
 
 [[package]]
@@ -1793,27 +1808,27 @@ files = [
 
 [[package]]
 name = "typing-extensions"
-version = "4.12.2"
-description = "Backported and Experimental Type Hints for Python 3.8+"
+version = "4.14.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
 optional = false
-python-versions = ">=3.8"
-groups = ["main", "dev", "linters", "script"]
+python-versions = ">=3.9"
+groups = ["main", "dev", "linters", "script", "test"]
 files = [
-    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
-    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+    {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.11\"", script = "python_version < \"3.11\""}
+markers = {main = "python_version < \"3.11\"", dev = "python_version < \"3.12\"", script = "python_version < \"3.11\"", test = "python_version < \"3.11\""}
 
 [[package]]
 name = "urllib3"
-version = "2.2.3"
+version = "2.4.0"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
 groups = ["documentation"]
 files = [
-    {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
-    {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
+    {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"},
+    {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
 ]
 
 [package.extras]
@@ -1824,14 +1839,14 @@ zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.27.1"
+version = "20.31.2"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.8"
 groups = ["dev", "linters"]
 files = [
-    {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"},
-    {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"},
+    {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"},
+    {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"},
 ]
 
 [package.dependencies]
@@ -1841,7 +1856,7 @@ platformdirs = ">=3.9.1,<5"
 
 [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)"]
-test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
 
 [[package]]
 name = "watchdog"
@@ -1900,101 +1915,115 @@ files = [
 
 [[package]]
 name = "wrapt"
-version = "1.17.0"
+version = "1.17.2"
 description = "Module for decorators, wrappers and monkey patching."
 optional = false
 python-versions = ">=3.8"
 groups = ["test"]
 files = [
-    {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"},
-    {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"},
-    {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"},
-    {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"},
-    {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"},
-    {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"},
-    {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"},
-    {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"},
-    {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"},
-    {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"},
-    {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"},
-    {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"},
-    {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"},
-    {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"},
-    {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"},
-    {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"},
-    {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"},
-    {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"},
-    {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"},
-    {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"},
-    {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"},
-    {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"},
-    {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"},
-    {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"},
-    {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"},
-    {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"},
-    {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"},
-    {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"},
-    {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"},
-    {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"},
-    {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"},
-    {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"},
-    {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"},
-    {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"},
-    {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"},
-    {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"},
-    {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"},
-    {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"},
-    {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"},
-    {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"},
-    {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"},
-    {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"},
-    {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"},
-    {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"},
-    {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"},
-    {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"},
-    {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"},
-    {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"},
-    {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"},
-    {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"},
-    {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"},
-    {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"},
-    {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"},
-    {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"},
-    {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"},
-    {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"},
-    {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"},
-    {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"},
-    {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"},
-    {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"},
-    {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"},
-    {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"},
-    {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"},
-    {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"},
-    {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"},
+    {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"},
 ]
 
 [[package]]
 name = "zipp"
-version = "3.21.0"
+version = "3.22.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.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
-    {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
+    {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\""]
 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", "importlib_resources ; python_version < \"3.9\"", "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 = "2951e44d1ec238ddef2139c76de3c838976a018b0993f7b84c6fd686e26fc5d0"
+content-hash = "f35cf57718e28836cf1e70b33edab9ce623b01a7f2d31d712585554721f6d403"
diff --git a/pyproject.toml b/pyproject.toml
index cff51a2094..0d135954a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,7 @@ dependencies = [
     "questionary (>=2.0,<3.0)",
     "decli (>=0.6.0,<1.0)",
     "colorama (>=0.4.1,<1.0)",
-    "termcolor (>=1.1,<3)",
+    "termcolor (>=1.1.0,<4.0.0)",
     "packaging>=19",
     "tomlkit (>=0.5.3,<1.0.0)",
     "jinja2>=2.10.3",
@@ -23,8 +23,8 @@ 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,<9) ; python_version < '3.10'",
-
+    "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'",
 ]
 keywords = ["commitizen", "conventional", "commits", "git"]
 # See also: https://pypi.org/classifiers/
@@ -119,13 +119,14 @@ pytest-xdist = "^3.1.0"
 deprecated = "^1.2.13"
 
 [tool.poetry.group.linters.dependencies]
-ruff = ">=0.5.0,<0.10.0"
+ruff = "^0.11.5"
 pre-commit = ">=2.18,<5.0"
-mypy = "^1.4"
+mypy = "^1.16.0"
 types-deprecated = "^1.2.9.2"
 types-python-dateutil = "^2.8.19.13"
 types-PyYAML = ">=5.4.3,<7.0.0"
 types-termcolor = "^0.1.1"
+types-colorama = "^0.4.15.20240311"
 
 [tool.poetry.group.documentation.dependencies]
 mkdocs = "^1.4.2"
@@ -182,10 +183,14 @@ commands_pre = [["poetry", "install", "--only", "main,test"]]
 commands = [["pytest", { replace = "posargs", extend = true }]]
 
 [tool.ruff]
+required-version = ">=0.11.5"
 line-length = 88
 
 [tool.ruff.lint]
 select = [
+    # flake8-annotations
+    "ANN001",
+    "ANN2",
     # pycodestyle
     "E",
     # Pyflakes
@@ -194,9 +199,19 @@ select = [
     "UP",
     # isort
     "I",
+    # pygrep-hooks
+    "PGH003",
+    "PGH004",
+    # unsorted-dunder-all
+    "RUF022",
+    # unused-noqa
+    "RUF100",
 ]
 ignore = ["E501", "D1", "D415"]
 
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["ANN"]
+
 [tool.ruff.lint.isort]
 known-first-party = ["commitizen", "tests"]
 
@@ -204,7 +219,7 @@ known-first-party = ["commitizen", "tests"]
 convention = "google"
 
 [tool.mypy]
-files = "commitizen"
+files = ["commitizen", "tests"]
 disallow_untyped_decorators = true
 disallow_subclassing_any = true
 warn_return_any = true
@@ -227,19 +242,13 @@ poetry_command = ""
 
 [tool.poe.tasks]
 format.help = "Format the code"
-format.sequence = [
-    { cmd = "ruff check --fix commitizen tests" },
-    { cmd = "ruff format commitizen tests" },
-]
+format.sequence = [{ cmd = "ruff check --fix" }, { cmd = "ruff format" }]
 
 lint.help = "Lint the code"
-lint.sequence = [
-    { cmd = "ruff check commitizen/ tests/ --fix" },
-    { cmd = "mypy commitizen/ tests/" },
-]
+lint.sequence = [{ cmd = "ruff check" }, { cmd = "mypy" }]
 
-check-commit.help = "Check the commit message"
-check-commit.cmd = "cz -nr 3 check --rev-range origin/master.."
+check-commit.help = "Check the commit messages"
+check-commit.cmd = "poetry run cz --no-raise 3 check --rev-range origin/master.."
 
 test.help = "Run the test suite"
 test.cmd = "pytest -n 3 --dist=loadfile"
@@ -251,12 +260,7 @@ cover.help = "Run the test suite with coverage"
 cover.ref = "test --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen"
 
 all.help = "Run all tasks"
-all.sequence = [
-    "format",
-    "lint",
-    "cover",
-    "check-commit",
-]
+all.sequence = ["format", "lint", "check-commit", "cover"]
 
 "doc:screenshots".help = "Render documentation screenshots"
 "doc:screenshots".script = "scripts.gen_cli_help_screenshots:gen_cli_help_screenshots"
@@ -268,10 +272,7 @@ doc.help = "Live documentation server"
 doc.cmd = "mkdocs serve"
 
 ci.help = "Run all tasks in CI"
-ci.sequence = [
-    { cmd = "pre-commit run --all-files" },
-    "cover",
-]
+ci.sequence = ["check-commit", { cmd = "pre-commit run --all-files" }, "cover"]
 ci.env = { SKIP = "no-commit-to-branch" }
 
 setup-pre-commit.help = "Install pre-commit hooks"
diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py
index e15539d8a7..59297b1726 100644
--- a/tests/commands/test_bump_command.py
+++ b/tests/commands/test_bump_command.py
@@ -12,8 +12,9 @@
 from pytest_mock import MockFixture
 
 import commitizen.commands.bump as bump
-from commitizen import cli, cmd, git, hooks
+from commitizen import cli, cmd, defaults, git, hooks
 from commitizen.changelog_formats import ChangelogFormat
+from commitizen.config.base_config import BaseConfig
 from commitizen.cz.base import BaseCommitizen
 from commitizen.exceptions import (
     BumpTagFailedError,
@@ -41,8 +42,8 @@
         "fix(user): username exception",
         "refactor: remove ini configuration support",
         "refactor(config): remove ini configuration support",
-        "perf: update to use multiproess",
-        "perf(worker): update to use multiproess",
+        "perf: update to use multiprocess",
+        "perf(worker): update to use multiprocess",
     ),
 )
 @pytest.mark.usefixtures("tmp_commitizen_project")
@@ -1688,3 +1689,51 @@ def test_bump_warn_but_dont_fail_on_invalid_tags(
 
     assert err.count("Invalid version tag: '0.4.3.deadbeaf'") == 1
     assert git.tag_exist("0.4.3")
+
+
+def test_is_initial_tag(mocker: MockFixture, tmp_commitizen_project):
+    """Test the _is_initial_tag method behavior."""
+    # Create a commit but no tags
+    create_file_and_commit("feat: initial commit")
+
+    # Initialize Bump with minimal config
+    config = BaseConfig()
+    config.settings.update(
+        {
+            "name": defaults.DEFAULT_SETTINGS["name"],
+            "encoding": "utf-8",
+            "pre_bump_hooks": [],
+            "post_bump_hooks": [],
+        }
+    )
+
+    # Initialize with required arguments
+    arguments = {
+        "changelog": False,
+        "changelog_to_stdout": False,
+        "git_output_to_stderr": False,
+        "no_verify": False,
+        "check_consistency": False,
+        "retry": False,
+        "version_scheme": None,
+        "file_name": None,
+        "template": None,
+        "extras": None,
+    }
+
+    bump_cmd = bump.Bump(config, arguments)  # type: ignore[arg-type]
+
+    # Test case 1: No current tag, not yes mode
+    mocker.patch("questionary.confirm", return_value=mocker.Mock(ask=lambda: True))
+    assert bump_cmd._is_initial_tag(None, is_yes=False) is True
+
+    # Test case 2: No current tag, yes mode
+    assert bump_cmd._is_initial_tag(None, is_yes=True) is True
+
+    # Test case 3: Has current tag
+    mock_tag = mocker.Mock()
+    assert bump_cmd._is_initial_tag(mock_tag, is_yes=False) is False
+
+    # Test case 4: No current tag, user denies
+    mocker.patch("questionary.confirm", return_value=mocker.Mock(ask=lambda: False))
+    assert bump_cmd._is_initial_tag(None, is_yes=False) is False
diff --git a/tests/commands/test_bump_command/test_bump_command_shows_description_when_use_help_option.txt b/tests/commands/test_bump_command/test_bump_command_shows_description_when_use_help_option.txt
index 5d4438875d..4cf8e6c91b 100644
--- a/tests/commands/test_bump_command/test_bump_command_shows_description_when_use_help_option.txt
+++ b/tests/commands/test_bump_command/test_bump_command_shows_description_when_use_help_option.txt
@@ -74,7 +74,7 @@ options:
   --version-scheme {pep440,semver,semver2}
                         choose version scheme
   --version-type {pep440,semver,semver2}
-                        Deprecated, use --version-scheme
+                        Deprecated, use --version-scheme instead
   --build-metadata BUILD_METADATA
                         Add additional build-metadata to the version-number
   --get-next            Determine the next version and write to stdout
diff --git a/tests/commands/test_commit_command.py b/tests/commands/test_commit_command.py
index 3a92f5af48..930e1a7a9b 100644
--- a/tests/commands/test_commit_command.py
+++ b/tests/commands/test_commit_command.py
@@ -523,3 +523,34 @@ def test_commit_command_shows_description_when_use_help_option(
 
     out, _ = capsys.readouterr()
     file_regression.check(out, extension=".txt")
+
+
+@pytest.mark.usefixtures("staging_is_clean")
+@pytest.mark.parametrize(
+    "out", ["no changes added to commit", "nothing added to commit"]
+)
+def test_commit_when_nothing_added_to_commit(config, mocker: MockFixture, out):
+    prompt_mock = mocker.patch("questionary.prompt")
+    prompt_mock.return_value = {
+        "prefix": "feat",
+        "subject": "user created",
+        "scope": "",
+        "is_breaking_change": False,
+        "body": "",
+        "footer": "",
+    }
+
+    commit_mock = mocker.patch("commitizen.git.commit")
+    commit_mock.return_value = cmd.Command(
+        out=out,
+        err="",
+        stdout=out.encode(),
+        stderr=b"",
+        return_code=0,
+    )
+    error_mock = mocker.patch("commitizen.out.error")
+
+    commands.Commit(config, {})()
+
+    commit_mock.assert_called_once()
+    error_mock.assert_called_once_with(out)
diff --git a/tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt b/tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt
index dd1f53f3da..ba531042aa 100644
--- a/tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt
+++ b/tests/commands/test_commit_command/test_commit_command_shows_description_when_use_help_option.txt
@@ -12,7 +12,7 @@ options:
   --write-message-to-file FILE_PATH
                         write message to file before committing (can be
                         combined with --dry-run)
-  -s, --signoff         sign off the commit
+  -s, --signoff         Deprecated, use 'cz commit -- -s' instead
   -a, --all             Tell the command to automatically stage files that
                         have been modified and deleted, but new files you have
                         not told Git about are not affected.
diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py
index f617c51d8f..3f12d0bd7f 100644
--- a/tests/commands/test_init_command.py
+++ b/tests/commands/test_init_command.py
@@ -86,7 +86,7 @@ def test_init_without_setup_pre_commit_hook(tmpdir, mocker: MockFixture, config)
 def test_init_when_config_already_exists(config, capsys):
     # Set config path
     path = os.sep.join(["tests", "pyproject.toml"])
-    config.add_path(path)
+    config.path = path
 
     commands.Init(config)()
     captured = capsys.readouterr()
diff --git a/tests/commands/test_version_command.py b/tests/commands/test_version_command.py
index 927cf55f25..3dcbed168b 100644
--- a/tests/commands/test_version_command.py
+++ b/tests/commands/test_version_command.py
@@ -97,7 +97,6 @@ def test_version_use_version_provider(
         {
             "report": False,
             "project": project,
-            "commitizen": False,
             "verbose": not project,
         },
     )()
diff --git a/tests/conftest.py b/tests/conftest.py
index 60c586f2e6..324ef9bebb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@
 import os
 import re
 import tempfile
-from collections.abc import Iterator
+from collections.abc import Iterator, Mapping
 from pathlib import Path
 
 import pytest
@@ -17,6 +17,7 @@
 from commitizen.config import BaseConfig
 from commitizen.cz import registry
 from commitizen.cz.base import BaseCommitizen
+from commitizen.question import CzQuestion
 from tests.utils import create_file_and_commit
 
 SIGNER = "GitHub Action"
@@ -169,7 +170,7 @@ class SemverCommitizen(BaseCommitizen):
         "patch": "PATCH",
     }
     changelog_pattern = r"^(patch|minor|major)"
-    commit_parser = r"^(?P<change_type>patch|minor|major)(?:\((?P<scope>[^()\r\n]*)\)|\()?:?\s(?P<message>.+)"  # noqa
+    commit_parser = r"^(?P<change_type>patch|minor|major)(?:\((?P<scope>[^()\r\n]*)\)|\()?:?\s(?P<message>.+)"
     change_type_map = {
         "major": "Breaking Changes",
         "minor": "Features",
@@ -209,7 +210,7 @@ def questions(self) -> list:
             },
         ]
 
-    def message(self, answers: dict) -> str:
+    def message(self, answers: Mapping) -> str:
         prefix = answers["prefix"]
         subject = answers.get("subject", "default message").trim()
         return f"{prefix}: {subject}"
@@ -222,10 +223,10 @@ def use_cz_semver(mocker):
 
 
 class MockPlugin(BaseCommitizen):
-    def questions(self) -> defaults.Questions:
+    def questions(self) -> list[CzQuestion]:
         return []
 
-    def message(self, answers: dict) -> str:
+    def message(self, answers: Mapping) -> str:
         return ""
 
 
diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py
index ff24ff17a7..77e11c78c7 100644
--- a/tests/test_bump_find_increment.py
+++ b/tests/test_bump_find_increment.py
@@ -32,14 +32,14 @@
 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",  # noqa
+    "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",  # noqa
+    "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",
 ]
 
diff --git a/tests/test_changelog.py b/tests/test_changelog.py
index b1c7c802e1..ed90ed08e4 100644
--- a/tests/test_changelog.py
+++ b/tests/test_changelog.py
@@ -1215,28 +1215,28 @@ def test_generate_tree_from_commits_with_no_commits(tags):
         ),
     ),
 )
-def test_order_changelog_tree(change_type_order, expected_reordering):
-    tree = changelog.order_changelog_tree(COMMITS_TREE, change_type_order)
+def test_generate_ordered_changelog_tree(change_type_order, expected_reordering):
+    tree = changelog.generate_ordered_changelog_tree(COMMITS_TREE, change_type_order)
 
     for index, entry in enumerate(tuple(tree)):
-        version = tree[index]["version"]
+        version = entry["version"]
         if version in expected_reordering:
             # Verify that all keys are present
-            assert [*tree[index].keys()] == [*COMMITS_TREE[index].keys()]
+            assert [*entry.keys()] == [*COMMITS_TREE[index].keys()]
             # Verify that the reorder only impacted the returned dict and not the original
             expected = expected_reordering[version]
-            assert [*tree[index]["changes"].keys()] == expected["sorted"]
+            assert [*entry["changes"].keys()] == expected["sorted"]
             assert [*COMMITS_TREE[index]["changes"].keys()] == expected["original"]
         else:
-            assert [*entry["changes"].keys()] == [*tree[index]["changes"].keys()]
+            assert [*entry["changes"].keys()] == [*entry["changes"].keys()]
 
 
-def test_order_changelog_tree_raises():
+def test_generate_ordered_changelog_tree_raises():
     change_type_order = ["BREAKING CHANGE", "feat", "refactor", "feat"]
     with pytest.raises(InvalidConfigurationError) as excinfo:
-        changelog.order_changelog_tree(COMMITS_TREE, change_type_order)
+        list(changelog.generate_ordered_changelog_tree(COMMITS_TREE, change_type_order))
 
-    assert "Change types contain duplicates types" in str(excinfo)
+    assert "Change types contain duplicated types" in str(excinfo)
 
 
 def test_render_changelog(
@@ -1639,7 +1639,9 @@ def test_tags_rules_get_version_tags(capsys: pytest.CaptureFixture):
 
 def test_changelog_file_name_from_args_and_config():
     mock_config = Mock(spec=BaseConfig)
-    mock_config.path.parent = "/my/project"
+    mock_path = Mock(spec=Path)
+    mock_path.parent = Path("/my/project")
+    mock_config.path = mock_path
     mock_config.settings = {
         "name": "cz_conventional_commits",
         "changelog_file": "CHANGELOG.md",
diff --git a/tests/test_changelog_formats.py b/tests/test_changelog_formats.py
index dec23720dc..e0d99e0325 100644
--- a/tests/test_changelog_formats.py
+++ b/tests/test_changelog_formats.py
@@ -6,8 +6,8 @@
 from commitizen.changelog_formats import (
     KNOWN_CHANGELOG_FORMATS,
     ChangelogFormat,
+    _guess_changelog_format,
     get_changelog_format,
-    guess_changelog_format,
 )
 from commitizen.config.base_config import BaseConfig
 from commitizen.exceptions import ChangelogFormatUnknown
@@ -15,14 +15,14 @@
 
 @pytest.mark.parametrize("format", KNOWN_CHANGELOG_FORMATS.values())
 def test_guess_format(format: type[ChangelogFormat]):
-    assert guess_changelog_format(f"CHANGELOG.{format.extension}") is format
+    assert _guess_changelog_format(f"CHANGELOG.{format.extension}") is format
     for ext in format.alternative_extensions:
-        assert guess_changelog_format(f"CHANGELOG.{ext}") is format
+        assert _guess_changelog_format(f"CHANGELOG.{ext}") is format
 
 
 @pytest.mark.parametrize("filename", ("CHANGELOG", "NEWS", "file.unknown", None))
 def test_guess_format_unknown(filename: str):
-    assert guess_changelog_format(filename) is None
+    assert _guess_changelog_format(filename) is None
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_cli.py b/tests/test_cli.py
index a91e633128..31371caea4 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,6 +1,7 @@
 import os
 import subprocess
 import sys
+import types
 from functools import partial
 
 import pytest
@@ -182,3 +183,59 @@ def test_unknown_args_before_double_dash_raises(mocker: MockFixture):
     assert "Invalid commitizen arguments were found before -- separator" in str(
         excinfo.value
     )
+
+
+def test_commitizen_excepthook_non_commitizen_exception(mocker: MockFixture):
+    """Test that commitizen_excepthook delegates to original_excepthook for non-CommitizenException."""
+    # Mock the original excepthook
+    mock_original_excepthook = mocker.Mock()
+    mocker.patch("commitizen.cli.original_excepthook", mock_original_excepthook)
+
+    # Create a regular exception
+    test_exception = ValueError("test error")
+
+    # Call commitizen_excepthook with the regular exception
+    cli.commitizen_excepthook(ValueError, test_exception, None)
+
+    # Verify original_excepthook was called with correct arguments
+    mock_original_excepthook.assert_called_once_with(ValueError, test_exception, None)
+
+
+def test_commitizen_excepthook_non_commitizen_exception_with_traceback(
+    mocker: MockFixture,
+):
+    """Test that commitizen_excepthook handles traceback correctly for non-CommitizenException."""
+    # Mock the original excepthook
+    mock_original_excepthook = mocker.Mock()
+    mocker.patch("commitizen.cli.original_excepthook", mock_original_excepthook)
+
+    # Create a regular exception with a traceback
+    test_exception = ValueError("test error")
+    test_traceback = mocker.Mock(spec=types.TracebackType)
+
+    # Call commitizen_excepthook with the regular exception and traceback
+    cli.commitizen_excepthook(ValueError, test_exception, test_traceback)
+
+    # Verify original_excepthook was called with correct arguments including traceback
+    mock_original_excepthook.assert_called_once_with(
+        ValueError, test_exception, test_traceback
+    )
+
+
+def test_commitizen_excepthook_non_commitizen_exception_with_invalid_traceback(
+    mocker: MockFixture,
+):
+    """Test that commitizen_excepthook handles invalid traceback correctly for non-CommitizenException."""
+    # Mock the original excepthook
+    mock_original_excepthook = mocker.Mock()
+    mocker.patch("commitizen.cli.original_excepthook", mock_original_excepthook)
+
+    # Create a regular exception with an invalid traceback
+    test_exception = ValueError("test error")
+    test_traceback = mocker.Mock()  # Not a TracebackType
+
+    # Call commitizen_excepthook with the regular exception and invalid traceback
+    cli.commitizen_excepthook(ValueError, test_exception, test_traceback)
+
+    # Verify original_excepthook was called with None as traceback
+    mock_original_excepthook.assert_called_once_with(ValueError, test_exception, None)
diff --git a/tests/test_cz_base.py b/tests/test_cz_base.py
index 4ee1cc6eda..0ee5a23fb8 100644
--- a/tests/test_cz_base.py
+++ b/tests/test_cz_base.py
@@ -1,3 +1,5 @@
+from collections.abc import Mapping
+
 import pytest
 
 from commitizen.cz.base import BaseCommitizen
@@ -7,7 +9,7 @@ class DummyCz(BaseCommitizen):
     def questions(self):
         return [{"type": "input", "name": "commit", "message": "Initial commit:\n"}]
 
-    def message(self, answers: dict):
+    def message(self, answers: Mapping):
         return answers["commit"]
 
 
@@ -42,9 +44,3 @@ def test_info(config):
     cz = DummyCz(config)
     with pytest.raises(NotImplementedError):
         cz.info()
-
-
-def test_process_commit(config):
-    cz = DummyCz(config)
-    message = cz.process_commit("test(test_scope): this is test msg")
-    assert message == "test(test_scope): this is test msg"
diff --git a/tests/test_cz_conventional_commits.py b/tests/test_cz_conventional_commits.py
index 6d4e0f7435..c96e036707 100644
--- a/tests/test_cz_conventional_commits.py
+++ b/tests/test_cz_conventional_commits.py
@@ -2,48 +2,42 @@
 
 from commitizen.cz.conventional_commits.conventional_commits import (
     ConventionalCommitsCz,
-    parse_scope,
-    parse_subject,
+    _parse_scope,
+    _parse_subject,
 )
 from commitizen.cz.exceptions import AnswerRequiredError
 
-valid_scopes = ["", "simple", "dash-separated", "camelCaseUPPERCASE"]
 
-scopes_transformations = [["with spaces", "with-spaces"], [None, ""]]
-
-valid_subjects = ["this is a normal text", "aword"]
-
-subjects_transformations = [["with dot.", "with dot"]]
-
-invalid_subjects = ["", "   ", ".", "   .", "", None]
-
-
-def test_parse_scope_valid_values():
-    for valid_scope in valid_scopes:
-        assert valid_scope == parse_scope(valid_scope)
+@pytest.mark.parametrize(
+    "valid_scope", ["", "simple", "dash-separated", "camelCaseUPPERCASE"]
+)
+def test_parse_scope_valid_values(valid_scope):
+    assert valid_scope == _parse_scope(valid_scope)
 
 
-def test_scopes_transformations():
-    for scopes_transformation in scopes_transformations:
-        invalid_scope, transformed_scope = scopes_transformation
-        assert transformed_scope == parse_scope(invalid_scope)
+@pytest.mark.parametrize(
+    "scopes_transformation", [["with spaces", "with-spaces"], ["", ""]]
+)
+def test_scopes_transformations(scopes_transformation):
+    invalid_scope, transformed_scope = scopes_transformation
+    assert transformed_scope == _parse_scope(invalid_scope)
 
 
-def test_parse_subject_valid_values():
-    for valid_subject in valid_subjects:
-        assert valid_subject == parse_subject(valid_subject)
+@pytest.mark.parametrize("valid_subject", ["this is a normal text", "aword"])
+def test_parse_subject_valid_values(valid_subject):
+    assert valid_subject == _parse_subject(valid_subject)
 
 
-def test_parse_subject_invalid_values():
-    for valid_subject in invalid_subjects:
-        with pytest.raises(AnswerRequiredError):
-            parse_subject(valid_subject)
+@pytest.mark.parametrize("invalid_subject", ["", "   ", ".", "   .", "\t\t."])
+def test_parse_subject_invalid_values(invalid_subject):
+    with pytest.raises(AnswerRequiredError):
+        _parse_subject(invalid_subject)
 
 
-def test_subject_transformations():
-    for subject_transformation in subjects_transformations:
-        invalid_subject, transformed_subject = subject_transformation
-        assert transformed_subject == parse_subject(invalid_subject)
+@pytest.mark.parametrize("subject_transformation", [["with dot.", "with dot"]])
+def test_subject_transformations(subject_transformation):
+    invalid_subject, transformed_subject = subject_transformation
+    assert transformed_subject == _parse_subject(invalid_subject)
 
 
 def test_questions(config):
@@ -89,7 +83,7 @@ def test_long_answer(config):
     message = conventional_commits.message(answers)
     assert (
         message
-        == "fix(users): email pattern corrected\n\ncomplete content\n\ncloses #24"  # noqa
+        == "fix(users): email pattern corrected\n\ncomplete content\n\ncloses #24"
     )
 
 
@@ -107,7 +101,7 @@ def test_breaking_change_in_footer(config):
     print(message)
     assert (
         message
-        == "fix(users): email pattern corrected\n\ncomplete content\n\nBREAKING CHANGE: migrate by renaming user to users"  # noqa
+        == "fix(users): email pattern corrected\n\ncomplete content\n\nBREAKING CHANGE: migrate by renaming user to users"
     )
 
 
@@ -130,26 +124,3 @@ def test_info(config):
     conventional_commits = ConventionalCommitsCz(config)
     info = conventional_commits.info()
     assert isinstance(info, str)
-
-
-@pytest.mark.parametrize(
-    ("commit_message", "expected_message"),
-    [
-        (
-            "test(test_scope): this is test msg",
-            "this is test msg",
-        ),
-        (
-            "test(test_scope)!: this is test msg",
-            "this is test msg",
-        ),
-        (
-            "test!(test_scope): this is test msg",
-            "",
-        ),
-    ],
-)
-def test_process_commit(commit_message, expected_message, config):
-    conventional_commits = ConventionalCommitsCz(config)
-    message = conventional_commits.process_commit(commit_message)
-    assert message == expected_message
diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py
new file mode 100644
index 0000000000..41bea81a73
--- /dev/null
+++ b/tests/test_deprecated.py
@@ -0,0 +1,33 @@
+import pytest
+
+from commitizen import changelog_formats, defaults
+
+
+def test_getattr_deprecated_vars():
+    # Test each deprecated variable
+    with pytest.warns(DeprecationWarning) as record:
+        assert defaults.bump_pattern == defaults.BUMP_PATTERN
+        assert defaults.bump_map == defaults.BUMP_MAP
+        assert (
+            defaults.bump_map_major_version_zero == defaults.BUMP_MAP_MAJOR_VERSION_ZERO
+        )
+        assert defaults.bump_message == defaults.BUMP_MESSAGE
+        assert defaults.change_type_order == defaults.CHANGE_TYPE_ORDER
+        assert defaults.encoding == defaults.ENCODING
+        assert defaults.name == defaults.DEFAULT_SETTINGS["name"]
+        assert (
+            changelog_formats._guess_changelog_format
+            == changelog_formats.guess_changelog_format
+        )
+
+    # Verify warning messages
+    assert len(record) == 7
+    for warning in record:
+        assert "is deprecated and will be removed" in str(warning.message)
+
+
+def test_getattr_non_existent():
+    # Test non-existent attribute
+    with pytest.raises(AttributeError) as exc_info:
+        _ = defaults.non_existent_attribute
+    assert "is not an attribute of" in str(exc_info.value)
diff --git a/tests/test_git.py b/tests/test_git.py
index 8b2fc2b86e..e242b3a2ae 100644
--- a/tests/test_git.py
+++ b/tests/test_git.py
@@ -18,6 +18,13 @@
 )
 
 
+@pytest.mark.parametrize("date", ["2020-01-21", "1970-01-01"])
+def test_git_tag_date(date: str):
+    git_tag = git.GitTag(rev="sha1-code", name="0.0.1", date="2025-05-30")
+    git_tag.date = date
+    assert git_tag.date == date
+
+
 def test_git_object_eq():
     git_commit = git.GitCommit(
         rev="sha1-code", title="this is title", body="this is body"
@@ -79,8 +86,7 @@ def test_get_reachable_tags_with_commits(
     monkeypatch.setenv("LANGUAGE", f"{locale}.UTF-8")
     monkeypatch.setenv("LC_ALL", f"{locale}.UTF-8")
     with tmp_commitizen_project.as_cwd():
-        tags = git.get_tags(reachable_only=True)
-        assert tags == []
+        assert git.get_tags(reachable_only=True) == []
 
 
 def test_get_tag_names(mocker: MockFixture):
@@ -271,7 +277,7 @@ def test_get_commits_with_signature():
 def test_get_tag_names_has_correct_arrow_annotation():
     arrow_annotation = inspect.getfullargspec(git.get_tag_names).annotations["return"]
 
-    assert arrow_annotation == "list[str | None]"
+    assert arrow_annotation == "list[str]"
 
 
 def test_get_latest_tag_name(tmp_commitizen_project):
@@ -317,24 +323,18 @@ def test_is_staging_clean_when_updating_file(tmp_commitizen_project):
         assert git.is_staging_clean() is False
 
 
-def test_git_eol_style(tmp_commitizen_project):
+def test_get_eol_for_open(tmp_commitizen_project):
     with tmp_commitizen_project.as_cwd():
-        assert git.get_eol_style() == git.EOLTypes.NATIVE
+        assert git.EOLType.for_open() == os.linesep
 
         cmd.run("git config core.eol lf")
-        assert git.get_eol_style() == git.EOLTypes.LF
+        assert git.EOLType.for_open() == "\n"
 
         cmd.run("git config core.eol crlf")
-        assert git.get_eol_style() == git.EOLTypes.CRLF
+        assert git.EOLType.for_open() == "\r\n"
 
         cmd.run("git config core.eol native")
-        assert git.get_eol_style() == git.EOLTypes.NATIVE
-
-
-def test_eoltypes_get_eol_for_open():
-    assert git.EOLTypes.get_eol_for_open(git.EOLTypes.NATIVE) == os.linesep
-    assert git.EOLTypes.get_eol_for_open(git.EOLTypes.LF) == "\n"
-    assert git.EOLTypes.get_eol_for_open(git.EOLTypes.CRLF) == "\r\n"
+        assert git.EOLType.for_open() == os.linesep
 
 
 def test_get_core_editor(mocker):
@@ -401,3 +401,82 @@ def test_commit_with_spaces_in_path(mocker, file_path, expected_cmd):
 
     mock_run.assert_called_once_with(expected_cmd)
     mock_unlink.assert_called_once_with(file_path)
+
+
+def test_get_filenames_in_commit_error(mocker: MockFixture):
+    """Test that GitCommandError is raised when git command fails."""
+    mocker.patch(
+        "commitizen.cmd.run",
+        return_value=FakeCommand(out="", err="fatal: bad object HEAD", return_code=1),
+    )
+    with pytest.raises(exceptions.GitCommandError) as excinfo:
+        git.get_filenames_in_commit()
+    assert str(excinfo.value) == "fatal: bad object HEAD"
+
+
+def test_git_commit_from_rev_and_commit():
+    # Test data with all fields populated
+    rev_and_commit = (
+        "abc123\n"  # rev
+        "def456 ghi789\n"  # parents
+        "feat: add new feature\n"  # title
+        "John Doe\n"  # author
+        "john@example.com\n"  # author_email
+        "This is a detailed description\n"  # body
+        "of the new feature\n"
+        "with multiple lines"
+    )
+
+    commit = git.GitCommit.from_rev_and_commit(rev_and_commit)
+
+    assert commit.rev == "abc123"
+    assert commit.title == "feat: add new feature"
+    assert (
+        commit.body
+        == "This is a detailed description\nof the new feature\nwith multiple lines"
+    )
+    assert commit.author == "John Doe"
+    assert commit.author_email == "john@example.com"
+    assert commit.parents == ["def456", "ghi789"]
+
+    # Test with minimal data
+    minimal_commit = (
+        "abc123\n"  # rev
+        "\n"  # no parents
+        "feat: minimal commit\n"  # title
+        "John Doe\n"  # author
+        "john@example.com\n"  # author_email
+    )
+
+    commit = git.GitCommit.from_rev_and_commit(minimal_commit)
+
+    assert commit.rev == "abc123"
+    assert commit.title == "feat: minimal commit"
+    assert commit.body == ""
+    assert commit.author == "John Doe"
+    assert commit.author_email == "john@example.com"
+    assert commit.parents == []
+
+
+@pytest.mark.parametrize(
+    "os_name,committer_date,expected_cmd",
+    [
+        (
+            "nt",
+            "2024-03-20",
+            'cmd /v /c "set GIT_COMMITTER_DATE=2024-03-20&& git commit  -F "temp.txt""',
+        ),
+        (
+            "posix",
+            "2024-03-20",
+            'GIT_COMMITTER_DATE=2024-03-20 git commit  -F "temp.txt"',
+        ),
+        ("nt", None, 'git commit  -F "temp.txt"'),
+        ("posix", None, 'git commit  -F "temp.txt"'),
+    ],
+)
+def test_create_commit_cmd_string(mocker, os_name, committer_date, expected_cmd):
+    """Test the OS-specific behavior of _create_commit_cmd_string"""
+    mocker.patch("os.name", os_name)
+    result = git._create_commit_cmd_string("", committer_date, "temp.txt")
+    assert result == expected_cmd