diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..ef07676fb6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +version: 2 +updates: + - + # Maintain dependencies for GitHub Actions + package-ecosystem: github-actions + directory: / + schedule: + interval: daily + labels: + - dependencies + commit-message: + prefix: "ci(actions)" + include: "scope" + - + # Maintain python dependencies + package-ecosystem: pip + directory: / + schedule: + interval: daily + labels: + - dependencies + commit-message: + prefix: "build(poetry)" + include: "scope" diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index e505aa58ad..656abae054 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -6,7 +6,7 @@ jobs: python-check: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] platform: [ubuntu-20.04, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: @@ -30,7 +30,7 @@ jobs: shell: bash - name: Upload coverage to Codecov if: runner.os == 'Linux' - uses: codecov/codecov-action@v1.0.3 + uses: codecov/codecov-action@v3 with: token: ${{secrets.CODECOV_TOKEN}} file: ./coverage.xml diff --git a/.gitignore b/.gitignore index 987c1a9c60..39b27e798a 100644 --- a/.gitignore +++ b/.gitignore @@ -108,8 +108,5 @@ venv.bak/ .vscode/ *.bak -# build -poetry.lock - # macOSX .DS_Store diff --git a/commitizen/bump.py b/commitizen/bump.py index 766ce1fbd2..c405414e87 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -1,9 +1,11 @@ import os import re +import sys +import typing from collections import OrderedDict from itertools import zip_longest from string import Template -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Tuple, Type, Union from packaging.version import Version @@ -11,6 +13,12 @@ from commitizen.exceptions import CurrentVersionNotFoundError from commitizen.git import GitCommit, smart_open +if sys.version_info >= (3, 8): + from commitizen.version_types import VersionProtocol +else: + # workaround mypy issue for 3.7 python + VersionProtocol = typing.Any + def find_increment( commits: List[GitCommit], regex: str, increments_map: Union[dict, OrderedDict] @@ -120,7 +128,8 @@ def generate_version( prerelease_offset: int = 0, devrelease: Optional[int] = None, is_local_version: bool = False, -) -> Version: + version_type_cls: Optional[Type[VersionProtocol]] = None, +) -> VersionProtocol: """Based on the given increment a proper semver will be generated. For now the rules and versioning scheme is based on @@ -132,15 +141,17 @@ def generate_version( MINOR 1.0.0 -> 1.1.0 MAJOR 1.0.0 -> 2.0.0 """ + if version_type_cls is None: + version_type_cls = Version if is_local_version: - version = Version(current_version) + version = version_type_cls(current_version) dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( str(version.local), prerelease=prerelease, offset=prerelease_offset ) semver = semver_generator(str(version.local), increment=increment) - return Version(f"{version.public}+{semver}{pre_version}{dev_version}") + return version_type_cls(f"{version.public}+{semver}{pre_version}{dev_version}") else: dev_version = devrelease_generator(devrelease=devrelease) pre_version = prerelease_generator( @@ -149,7 +160,7 @@ def generate_version( semver = semver_generator(current_version, increment=increment) # TODO: post version - return Version(f"{semver}{pre_version}{dev_version}") + return version_type_cls(f"{semver}{pre_version}{dev_version}") def update_version_in_files( @@ -208,7 +219,9 @@ def _version_to_regex(version: str) -> str: def normalize_tag( - version: Union[Version, str], tag_format: Optional[str] = None + version: Union[VersionProtocol, str], + tag_format: Optional[str] = None, + version_type_cls: Optional[Type[VersionProtocol]] = None, ) -> str: """The tag and the software version might be different. @@ -221,8 +234,10 @@ def normalize_tag( | ver1.0.0 | 1.0.0 | | ver1.0.0.a0 | 1.0.0a0 | """ + if version_type_cls is None: + version_type_cls = Version if isinstance(version, str): - version = Version(version) + version = version_type_cls(version) if not tag_format: return str(version) diff --git a/commitizen/changelog.py b/commitizen/changelog.py index ead80ab775..1a6ca04cc1 100644 --- a/commitizen/changelog.py +++ b/commitizen/changelog.py @@ -27,22 +27,56 @@ import os import re +import sys +import typing from collections import OrderedDict, defaultdict from datetime import date -from typing import Callable, Dict, Iterable, List, Optional, Tuple +from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type from jinja2 import Environment, PackageLoader +from packaging.version import InvalidVersion, Version from commitizen import defaults from commitizen.bump import normalize_tag from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError from commitizen.git import GitCommit, GitTag +if sys.version_info >= (3, 8): + from commitizen.version_types import VersionProtocol +else: + # workaround mypy issue for 3.7 python + VersionProtocol = typing.Any + def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]: return next((tag for tag in tags if tag.rev == commit.rev), None) +def get_version(tag: GitTag) -> Optional[Version]: + version = None + try: + version = Version(tag.name) + except InvalidVersion: + pass + return version + + +def tag_included_in_changelog( + tag: GitTag, used_tags: List, merge_prerelease: bool +) -> bool: + if tag in used_tags: + return False + + version = get_version(tag) + if version is None: + return False + + if merge_prerelease and version.is_prerelease: + return False + + return True + + def generate_tree_from_commits( commits: List[GitCommit], tags: List[GitTag], @@ -51,6 +85,7 @@ def generate_tree_from_commits( unreleased_version: Optional[str] = None, change_type_map: Optional[Dict[str, str]] = None, changelog_message_builder_hook: Optional[Callable] = None, + merge_prerelease: bool = False, ) -> Iterable[Dict]: pat = re.compile(changelog_pattern) map_pat = re.compile(commit_parser, re.MULTILINE) @@ -73,15 +108,15 @@ def generate_tree_from_commits( for commit in commits: commit_tag = get_commit_tag(commit, tags) - if commit_tag is not None and commit_tag not in used_tags: + if commit_tag is not None and tag_included_in_changelog( + commit_tag, used_tags, merge_prerelease + ): used_tags.append(commit_tag) yield { "version": current_tag_name, "date": current_tag_date, "changes": changes, } - # TODO: Check if tag matches the version pattern, otherwise skip it. - # This in order to prevent tags that are not versions. current_tag_name = commit_tag.name current_tag_date = commit_tag.date changes = defaultdict(list) @@ -286,7 +321,10 @@ def get_smart_tag_range( def get_oldest_and_newest_rev( - tags: List[GitTag], version: str, tag_format: str + tags: List[GitTag], + version: str, + tag_format: str, + version_type_cls: Optional[Type[VersionProtocol]] = None, ) -> Tuple[Optional[str], Optional[str]]: """Find the tags for the given version. @@ -301,11 +339,15 @@ def get_oldest_and_newest_rev( except ValueError: newest = version - newest_tag = normalize_tag(newest, tag_format=tag_format) + newest_tag = normalize_tag( + newest, tag_format=tag_format, version_type_cls=version_type_cls + ) oldest_tag = None if oldest: - oldest_tag = normalize_tag(oldest, tag_format=tag_format) + oldest_tag = normalize_tag( + oldest, tag_format=tag_format, version_type_cls=version_type_cls + ) tags_range = get_smart_tag_range(tags, newest=newest_tag, oldest=oldest_tag) if not tags_range: diff --git a/commitizen/cli.py b/commitizen/cli.py index 28f7e8d927..c08fa1ec87 100644 --- a/commitizen/cli.py +++ b/commitizen/cli.py @@ -2,12 +2,13 @@ import logging import sys from functools import partial +from types import TracebackType from typing import List import argcomplete from decli import cli -from commitizen import commands, config, out +from commitizen import commands, config, out, version_types from commitizen.exceptions import ( CommitizenException, ExitCode, @@ -202,6 +203,18 @@ "help": "bump to the given version (e.g: 1.5.3)", "metavar": "MANUAL_VERSION", }, + { + "name": ["--empty"], + "default": False, + "help": "bump tags without new commits", + "action": "store_true", + }, + { + "name": ["--version-type"], + "help": "choose version type", + "default": None, + "choices": version_types.VERSION_TYPES, + }, ], }, { @@ -247,10 +260,19 @@ "name": "--start-rev", "default": None, "help": ( - "start rev of the changelog." + "start rev of the changelog. " "If not set, it will generate changelog from the start" ), }, + { + "name": "--merge-prerelease", + "action": "store_true", + "default": False, + "help": ( + "collect all changes from prereleases into next non-prerelease. " + "If not set, it will include prereleases in the changelog" + ), + }, ], }, { @@ -330,21 +352,22 @@ def commitizen_excepthook( - type, value, tracekback, debug=False, no_raise: List[int] = None + type, value, traceback, debug=False, no_raise: List[int] = None ): + traceback = traceback if isinstance(traceback, TracebackType) else None if not no_raise: no_raise = [] if isinstance(value, CommitizenException): if value.message: value.output_method(value.message) if debug: - original_excepthook(type, value, tracekback) + original_excepthook(type, value, traceback) exit_code = value.exit_code if exit_code in no_raise: exit_code = 0 sys.exit(exit_code) else: - original_excepthook(type, value, tracekback) + original_excepthook(type, value, traceback) commitizen_debug_excepthook = partial(commitizen_excepthook, debug=True) diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 808141c32f..8e67664baa 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -5,7 +5,7 @@ import questionary from packaging.version import InvalidVersion, Version -from commitizen import bump, cmd, defaults, factory, git, hooks, out +from commitizen import bump, cmd, defaults, factory, git, hooks, out, version_types from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig from commitizen.exceptions import ( @@ -21,6 +21,7 @@ NotAllowed, NoVersionSpecifiedError, ) +from commitizen.providers import get_provider logger = getLogger("commitizen") @@ -61,6 +62,10 @@ def __init__(self, config: BaseConfig, arguments: dict): self.retry = arguments["retry"] self.pre_bump_hooks = self.config.settings["pre_bump_hooks"] self.post_bump_hooks = self.config.settings["post_bump_hooks"] + version_type = arguments["version_type"] or self.config.settings.get( + "version_type" + ) + self.version_type = version_type and version_types.VERSION_TYPES[version_type] def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool: """Check if reading the whole git tree up to HEAD is needed.""" @@ -94,14 +99,14 @@ def find_increment(self, commits: List[git.GitCommit]) -> Optional[str]: def __call__(self): # noqa: C901 """Steps executed to bump.""" + provider = get_provider(self.config) + current_version: str = provider.get_version() + try: - current_version_instance: Version = Version(self.bump_settings["version"]) + current_version_instance: Version = Version(current_version) except TypeError: raise NoVersionSpecifiedError() - # Initialize values from sources (conf) - current_version: str = self.config.settings["version"] - tag_format: str = self.bump_settings["tag_format"] bump_commit_message: str = self.bump_settings["bump_message"] version_files: List[str] = self.bump_settings["version_files"] @@ -116,6 +121,7 @@ def __call__(self): # noqa: C901 is_files_only: Optional[bool] = self.arguments["files_only"] is_local_version: Optional[bool] = self.arguments["local_version"] manual_version = self.arguments["manual_version"] + is_empty: Optional[bool] = self.arguments["empty"] if manual_version: if increment: @@ -152,7 +158,9 @@ def __call__(self): # noqa: C901 self.cz.bump_map = defaults.bump_map_major_version_zero current_tag_version: str = bump.normalize_tag( - current_version, tag_format=tag_format + current_version, + tag_format=tag_format, + version_type_cls=self.version_type, ) is_initial = self.is_initial_tag(current_tag_version, is_yes) @@ -168,7 +176,7 @@ def __call__(self): # noqa: C901 # No commits, there is no need to create an empty tag. # Unless we previously had a prerelease. - if not commits and not current_version_instance.is_prerelease: + if not commits and not current_version_instance.is_prerelease and not is_empty: raise NoCommitsFoundError("[NO_COMMITS_FOUND]\n" "No new commits found.") if manual_version: @@ -201,6 +209,10 @@ def __call__(self): # noqa: C901 if prerelease and current_version_instance.is_prerelease: increment = None + # we create an empty PATCH increment for empty tag + if increment is None and is_empty: + increment = "PATCH" + new_version = bump.generate_version( current_version, increment, @@ -208,9 +220,14 @@ def __call__(self): # noqa: C901 prerelease_offset=prerelease_offset, devrelease=devrelease, is_local_version=is_local_version, + version_type_cls=self.version_type, ) - new_tag_version = bump.normalize_tag(new_version, tag_format=tag_format) + new_tag_version = bump.normalize_tag( + new_version, + tag_format=tag_format, + version_type_cls=self.version_type, + ) message = bump.create_commit_message( current_version, new_version, bump_commit_message ) @@ -280,7 +297,7 @@ def __call__(self): # noqa: C901 check_consistency=self.check_consistency, ) - self.config.set_key("version", str(new_version)) + provider.set_version(str(new_version)) if self.pre_bump_hooks: hooks.run( diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index aab613fffc..4c291d3ac2 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -3,7 +3,7 @@ from operator import itemgetter from typing import Callable, Dict, List, Optional -from commitizen import bump, changelog, defaults, factory, git, out +from commitizen import bump, changelog, defaults, factory, git, out, version_types from commitizen.config import BaseConfig from commitizen.exceptions import ( DryRunExit, @@ -49,6 +49,12 @@ def __init__(self, config: BaseConfig, args): self.tag_format = args.get("tag_format") or self.config.settings.get( "tag_format" ) + self.merge_prerelease = args.get( + "merge_prerelease" + ) or self.config.settings.get("changelog_merge_prerelease") + + version_type = self.config.settings.get("version_type") + self.version_type = version_type and version_types.VERSION_TYPES[version_type] def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str: """Try to find the 'start_rev'. @@ -110,6 +116,7 @@ def __call__(self): changelog_message_builder_hook: Optional[ Callable ] = self.cz.changelog_message_builder_hook + merge_prerelease = self.merge_prerelease if not changelog_pattern or not commit_parser: raise NoPatternMapError( @@ -133,7 +140,9 @@ def __call__(self): latest_version = changelog_meta.get("latest_version") if latest_version: latest_tag_version: str = bump.normalize_tag( - latest_version, tag_format=self.tag_format + latest_version, + tag_format=self.tag_format, + version_type_cls=self.version_type, ) start_rev = self._find_incremental_rev(latest_tag_version, tags) @@ -142,11 +151,10 @@ def __call__(self): tags, version=self.rev_range, tag_format=self.tag_format, + version_type_cls=self.version_type, ) - commits = git.get_commits( - start=start_rev, end=end_rev, args="--author-date-order" - ) + commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order") if not commits: raise NoCommitsFoundError("No commits found") @@ -158,6 +166,7 @@ def __call__(self): unreleased_version, change_type_map=change_type_map, changelog_message_builder_hook=changelog_message_builder_hook, + merge_prerelease=merge_prerelease, ) if self.change_type_order: tree = changelog.order_changelog_tree(tree, self.change_type_order) diff --git a/commitizen/commands/version.py b/commitizen/commands/version.py index dc47e7aa0c..45d553c710 100644 --- a/commitizen/commands/version.py +++ b/commitizen/commands/version.py @@ -4,6 +4,7 @@ from commitizen import out from commitizen.__version__ import __version__ from commitizen.config import BaseConfig +from commitizen.providers import get_provider class Version: @@ -21,14 +22,14 @@ def __call__(self): out.write(f"Python Version: {self.python_version}") out.write(f"Operating System: {self.operating_system}") elif self.parameter.get("project"): - version = self.config.settings["version"] + version = get_provider(self.config).get_version() if version: out.write(f"{version}") else: out.error("No project information in this project.") elif self.parameter.get("verbose"): out.write(f"Installed Commitizen Version: {__version__}") - version = self.config.settings["version"] + version = get_provider(self.config).get_version() if version: out.write(f"Project Version: {version}") else: diff --git a/commitizen/cz/__init__.py b/commitizen/cz/__init__.py index e14cb9f7c9..5b974a99f2 100644 --- a/commitizen/cz/__init__.py +++ b/commitizen/cz/__init__.py @@ -1,15 +1,18 @@ +from __future__ import annotations + import importlib import pkgutil import warnings -from typing import Dict, Iterable, Type +from typing import Iterable, Optional + +import importlib_metadata as metadata from commitizen.cz.base import BaseCommitizen -from commitizen.cz.conventional_commits import ConventionalCommitsCz -from commitizen.cz.customize import CustomizeCommitsCz -from commitizen.cz.jira import JiraSmartCz -def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitizen]]: +def discover_plugins( + path: Optional[Iterable[str]] = None, +) -> dict[str, type[BaseCommitizen]]: """Discover commitizen plugins on the path Args: @@ -19,21 +22,19 @@ def discover_plugins(path: Iterable[str] = None) -> Dict[str, Type[BaseCommitize Returns: Dict[str, Type[BaseCommitizen]]: Registry with found plugins """ - plugins = {} - for _finder, name, _ispkg in pkgutil.iter_modules(path): - try: - if name.startswith("cz_"): - plugins[name] = importlib.import_module(name).discover_this - except AttributeError as e: - warnings.warn(UserWarning(e.args[0])) - continue - return plugins - - -registry: Dict[str, Type[BaseCommitizen]] = { - "cz_conventional_commits": ConventionalCommitsCz, - "cz_jira": JiraSmartCz, - "cz_customize": CustomizeCommitsCz, -} - -registry.update(discover_plugins()) + for _, name, _ in pkgutil.iter_modules(path): + if name.startswith("cz_"): + mod = importlib.import_module(name) + if hasattr(mod, "discover_this"): + warnings.warn( + UserWarning( + f"Legacy plugin '{name}' has been ignored: please expose it the 'commitizen.plugin' entrypoint" + ) + ) + + return { + ep.name: ep.load() for ep in metadata.entry_points(group="commitizen.plugin") + } + + +registry: dict[str, type[BaseCommitizen]] = discover_plugins() diff --git a/commitizen/defaults.py b/commitizen/defaults.py index da029db87d..713cfc2a40 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -33,12 +33,14 @@ class Settings(TypedDict, total=False): name: str version: Optional[str] version_files: List[str] + version_provider: Optional[str] tag_format: Optional[str] bump_message: Optional[str] allow_abort: bool changelog_file: str changelog_incremental: bool changelog_start_rev: Optional[str] + changelog_merge_prerelease: bool update_changelog_on_bump: bool use_shortcuts: bool style: Optional[List[Tuple[str, str]]] @@ -47,6 +49,7 @@ class Settings(TypedDict, total=False): pre_bump_hooks: Optional[List[str]] post_bump_hooks: Optional[List[str]] prerelease_offset: int + version_type: Optional[str] name: str = "cz_conventional_commits" @@ -63,18 +66,21 @@ class Settings(TypedDict, total=False): "name": "cz_conventional_commits", "version": None, "version_files": [], + "version_provider": "commitizen", "tag_format": None, # example v$version "bump_message": None, # bumped v$current_version to $new_version "allow_abort": False, "changelog_file": "CHANGELOG.md", "changelog_incremental": False, "changelog_start_rev": None, + "changelog_merge_prerelease": False, "update_changelog_on_bump": False, "use_shortcuts": False, "major_version_zero": False, "pre_bump_hooks": [], "post_bump_hooks": [], "prerelease_offset": 0, + "version_type": None, } MAJOR = "MAJOR" diff --git a/commitizen/exceptions.py b/commitizen/exceptions.py index c7d0b50e69..ba4aca1397 100644 --- a/commitizen/exceptions.py +++ b/commitizen/exceptions.py @@ -31,6 +31,7 @@ class ExitCode(enum.IntEnum): INVALID_MANUAL_VERSION = 24 INIT_FAILED = 25 RUN_HOOK_FAILED = 26 + VERSION_PROVIDER_UNKNOWN = 27 class CommitizenException(Exception): @@ -173,3 +174,7 @@ class InitFailedError(CommitizenException): class RunHookError(CommitizenException): exit_code = ExitCode.RUN_HOOK_FAILED + + +class VersionProviderUnknown(CommitizenException): + exit_code = ExitCode.VERSION_PROVIDER_UNKNOWN diff --git a/commitizen/providers.py b/commitizen/providers.py new file mode 100644 index 0000000000..17b99e7b23 --- /dev/null +++ b/commitizen/providers.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import json +import re +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any, Callable, ClassVar, Optional, cast + +import importlib_metadata as metadata +import tomlkit +from packaging.version import VERSION_PATTERN, Version + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.git import get_tags + +PROVIDER_ENTRYPOINT = "commitizen.provider" +DEFAULT_PROVIDER = "commitizen" + + +class VersionProvider(ABC): + """ + Abstract base class for version providers. + + Each version provider should inherit and implement this class. + """ + + config: BaseConfig + + def __init__(self, config: BaseConfig): + self.config = config + + @abstractmethod + def get_version(self) -> str: + """ + Get the current version + """ + ... + + @abstractmethod + def set_version(self, version: str): + """ + Set the new current version + """ + ... + + +class CommitizenProvider(VersionProvider): + """ + Default version provider: Fetch and set version in commitizen config. + """ + + def get_version(self) -> str: + return self.config.settings["version"] # type: ignore + + def set_version(self, version: str): + self.config.set_key("version", version) + + +class FileProvider(VersionProvider): + """ + Base class for file-based version providers + """ + + filename: ClassVar[str] + + @property + def file(self) -> Path: + return Path() / self.filename + + +class TomlProvider(FileProvider): + """ + Base class for TOML-based version providers + """ + + def get_version(self) -> str: + document = tomlkit.parse(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = tomlkit.parse(self.file.read_text()) + self.set(document, version) + self.file.write_text(tomlkit.dumps(document)) + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["project"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["project"]["version"] = version # type: ignore + + +class Pep621Provider(TomlProvider): + """ + PEP-621 version management + """ + + filename = "pyproject.toml" + + +class PoetryProvider(TomlProvider): + """ + Poetry version management + """ + + filename = "pyproject.toml" + + def get(self, pyproject: tomlkit.TOMLDocument) -> str: + return pyproject["tool"]["poetry"]["version"] # type: ignore + + def set(self, pyproject: tomlkit.TOMLDocument, version: str): + pyproject["tool"]["poetry"]["version"] = version # type: ignore + + +class CargoProvider(TomlProvider): + """ + Cargo version management + """ + + filename = "Cargo.toml" + + def get(self, document: tomlkit.TOMLDocument) -> str: + return document["package"]["version"] # type: ignore + + def set(self, document: tomlkit.TOMLDocument, version: str): + document["package"]["version"] = version # type: ignore + + +class JsonProvider(FileProvider): + """ + Base class for JSON-based version providers + """ + + indent: ClassVar[int] = 2 + + def get_version(self) -> str: + document = json.loads(self.file.read_text()) + return self.get(document) + + def set_version(self, version: str): + document = json.loads(self.file.read_text()) + self.set(document, version) + self.file.write_text(json.dumps(document, indent=self.indent) + "\n") + + def get(self, document: dict[str, Any]) -> str: + return document["version"] # type: ignore + + def set(self, document: dict[str, Any], version: str): + document["version"] = version + + +class NpmProvider(JsonProvider): + """ + npm package.json version management + """ + + filename = "package.json" + + +class ComposerProvider(JsonProvider): + """ + Composer version management + """ + + filename = "composer.json" + indent = 4 + + +class ScmProvider(VersionProvider): + """ + A provider fetching the current/last version from the repository history + + The version is fetched using `git describe` and is never set. + + It is meant for `setuptools-scm` or any package manager `*-scm` provider. + """ + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$prerelease": r"(?P\w+\d+)?", + "$devrelease": r"(?P\.dev\d+)?", + } + + def _tag_format_matcher(self) -> Callable[[str], Optional[str]]: + pattern = self.config.settings.get("tag_format") or VERSION_PATTERN + for var, tag_pattern in self.TAG_FORMAT_REGEXS.items(): + pattern = pattern.replace(var, tag_pattern) + + regex = re.compile(f"^{pattern}$", re.VERBOSE) + + def matcher(tag: str) -> Optional[str]: + match = regex.match(tag) + if not match: + return None + groups = match.groupdict() + if "version" in groups: + return groups["version"] + elif "major" in groups: + return "".join( + ( + groups["major"], + f".{groups['minor']}" if groups.get("minor") else "", + f".{groups['patch']}" if groups.get("patch") else "", + groups["prerelease"] if groups.get("prerelease") else "", + groups["devrelease"] if groups.get("devrelease") else "", + ) + ) + elif pattern == VERSION_PATTERN: + return str(Version(tag)) + return None + + return matcher + + def get_version(self) -> str: + matcher = self._tag_format_matcher() + return next( + (cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0" + ) + + def set_version(self, version: str): + # Not necessary + pass + + +def get_provider(config: BaseConfig) -> VersionProvider: + """ + Get the version provider as defined in the configuration + + :raises VersionProviderUnknown: if the provider named by `version_provider` is not found. + """ + provider_name = config.settings["version_provider"] or DEFAULT_PROVIDER + try: + (ep,) = metadata.entry_points(name=provider_name, group=PROVIDER_ENTRYPOINT) + except ValueError: + raise VersionProviderUnknown(f'Version Provider "{provider_name}" unknown.') + provider_cls = ep.load() + return cast(VersionProvider, provider_cls(config)) diff --git a/commitizen/version_types.py b/commitizen/version_types.py new file mode 100644 index 0000000000..d896d809b1 --- /dev/null +++ b/commitizen/version_types.py @@ -0,0 +1,99 @@ +import sys +from typing import Optional, Tuple, Union + +if sys.version_info >= (3, 8): + from typing import Protocol as _Protocol +else: + _Protocol = object + +from packaging.version import Version + + +class VersionProtocol(_Protocol): + def __init__(self, _version: Union[Version, str]): + raise NotImplementedError("must be implemented") + + def __str__(self) -> str: + raise NotImplementedError("must be implemented") + + @property + def release(self) -> Tuple[int, ...]: + raise NotImplementedError("must be implemented") + + @property + def is_prerelease(self) -> bool: + raise NotImplementedError("must be implemented") + + @property + def pre(self) -> Optional[Tuple[str, int]]: + raise NotImplementedError("must be implemented") + + @property + def local(self) -> Optional[str]: + raise NotImplementedError("must be implemented") + + @property + def public(self) -> str: + raise NotImplementedError("must be implemented") + + +class SemVerVersion(VersionProtocol): + def __init__(self, version: str): + self._version = Version(version) + + @property + def release(self) -> Tuple[int, ...]: + return self._version.release + + @property + def is_prerelease(self) -> bool: + return self._version.is_prerelease + + @property + def pre(self) -> Optional[Tuple[str, int]]: + return self._version.pre + + @property + def local(self) -> Optional[str]: + return self._version.local + + @property + def public(self) -> str: + return self._version.public + + def __str__(self) -> str: + parts = [] + + version = self._version + + # Epoch + if version.epoch != 0: + parts.append(f"{version.epoch}!") + + # Release segment + parts.append(".".join(str(x) for x in version.release)) + + # Pre-release + if version.pre: + pre = "".join(str(x) for x in version.pre) + parts.append(f"-{pre}") + + # Post-release + if version.post is not None: + parts.append(f"-post{version.post}") + + # Development release + if version.dev is not None: + parts.append(f"-dev{version.dev}") + + # Local version segment + if version.local: + parts.append(f"+{version.local}") + + return "".join(parts) + + +VERSION_TYPES = { + "pep440": Version, + "semver": SemVerVersion, +} diff --git a/docs/README.md b/docs/README.md index a167f5d775..75ad65a71e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,7 +38,7 @@ the version or a changelog. ## Requirements -Python 3.6+ +Python 3.7+ [Git][gitscm] `1.8.5.2`+ diff --git a/docs/bump.md b/docs/bump.md index ca5bdf7fe2..4a686b77ef 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -96,6 +96,9 @@ options: --retry retry commit if it fails the 1st time --major-version-zero keep major version at zero, even for breaking changes --prerelease-offset start pre-releases with this offset + --version-type {pep440,semver} + choose version type + ``` ### `--files-only` @@ -476,6 +479,26 @@ Defaults to: `0` prerelease_offset = 1 ``` +### `version_type` + +Choose version type + +* `pep440` - default version type. + - prerelease - `1.0.1a0` + - devrelease - `1.0.1dev0` + - dev and pre - `1.0.1a0.dev0` +* `semver` - semver compatibly type. Added "-" delimiter + - prerelease - `1.0.1-a0` + - devrelease - `1.0.1-dev0` + - dev and pre - `1.0.1-a0-dev0` + +Defaults to: `pep440` + +```toml +[tool.commitizen] +version_type = "semver" +``` + ## Custom bump Read the [customizing section](./customization.md). diff --git a/docs/changelog.md b/docs/changelog.md index 6f92bb21cd..b6e43c4fdd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,7 +21,9 @@ optional arguments: set the value for the new version (use the tag value), instead of using unreleased --incremental generates changelog from last created version, useful if the changelog has been manually modified --start-rev START_REV - start rev of the changelog.If not set, it will generate changelog from the start + start rev of the changelog. If not set, it will generate changelog from the start + --merge-prerelease + collect all changes from prereleases into next non-prerelease. If not set, it will include prereleases in the changelog ``` ### Examples @@ -161,6 +163,22 @@ cz changelog --start-rev="v0.2.0" changelog_start_rev = "v0.2.0" ``` +### merge-prerelease + +This flag can be set in the `toml` file with the key `changelog_merge_prerelease` under `tools.commitizen` + +Collects changes from prereleases into the next non-prerelease. This means that if you have a prerelease version, and then a normal release, the changelog will show the prerelease changes as part of the changes of the normal release. If not set, it will include prereleases in the changelog. + +```bash +cz changelog --merge-prerelease +``` + +```toml +[tools.commitizen] +# ... +changelog_merge_prerelease = true +``` + ## Hooks Supported hook methods: diff --git a/docs/config.md b/docs/config.md index c6cf12b02f..e30d8beaa0 100644 --- a/docs/config.md +++ b/docs/config.md @@ -3,10 +3,11 @@ ## Settings | Variable | Type | Default | Description | -| -------------------------- | ------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|----------------------------|--------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `name` | `str` | `"cz_conventional_commits"` | Name of the committing rules to use | | `version` | `str` | `None` | Current version. Example: "0.1.2" | | `version_files` | `list` | `[ ]` | Files were the version will be updated. A pattern to match a line, can also be specified, separated by `:` [See more][version_files] | +| `version_provider` | `str` | `commitizen` | Version provider used to read and write version [See more](#version-providers) | | `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more][tag_format] | | `update_changelog_on_bump` | `bool` | `false` | Create changelog when running `cz bump` | | `gpg_sign` | `bool` | `false` | Use gpg signed tags instead of lightweight tags. | @@ -16,11 +17,13 @@ | `changelog_file` | `str` | `CHANGELOG.md` | filename of exported changelog | | `changelog_incremental` | `bool` | `false` | Update changelog with the missing versions. This is good if you don't want to replace previous versions in the file. Note: when doing `cz bump --changelog` this is automatically set to `true` | | `changelog_start_rev` | `str` | `None` | Start from a given git rev to generate the changelog | +| `changelog_merge_prerelease` | `bool` | `false` | Collect all changes of prerelease versions into the next non-prerelease version when creating the changelog. | | `style` | `list` | see above | Style for the prompts (It will merge this value with default style.) [See More (Styling your prompts with your favorite colors)][additional-features] | | `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more][customization] | | `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] | | `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] | | `prerelease_offset` | `int` | `0` | In special cases it may be necessary that a prerelease cannot start with a 0, e.g. in an embedded project the individual characters are encoded in bytes. This can be done by specifying an offset from which to start counting. [prerelease-offset] | +| `version_type` | `str` | `pep440` | Select a version type from the following options [`pep440`, `semver`]. Useful for non-python projects. [See more][version_type] | ## pyproject.toml or .cz.toml @@ -112,12 +115,72 @@ commitizen: - fg:#858585 italic ``` +## Version providers + +Commitizen can read and write version from different sources. +By default, it use the `commitizen` one which is using the `version` field from the commitizen settings. +But you can use any `commitizen.provider` entrypoint as value for `version_provider`. + +Commitizen provides some version providers for some well known formats: + +| name | description | +| ---- | ----------- | +| `commitizen` | Default version provider: Fetch and set version in commitizen config. | +| `scm` | Fetch the version from git and does not need to set it back | +| `pep621` | Get and set version from `pyproject.toml` `project.version` field | +| `poetry` | Get and set version from `pyproject.toml` `tool.poetry.version` field | +| `cargo` | Get and set version from `Cargo.toml` `project.version` field | +| `npm` | Get and set version from `package.json` `project.version` field | +| `composer` | Get and set version from `composer.json` `project.version` field | + +!!! note + The `scm` provider is meant to be used with `setuptools-scm` or any packager `*-scm` plugin. + +### Custom version provider + +You can add you own version provider by extending `VersionProvider` and exposing it on the `commitizen.provider` entrypoint. + +Here a quick example of a `my-provider` provider reading and writing version in a `VERSION` file. + +```python title="my_provider.py" +from pathlib import Path +from commitizen.providers import VersionProvider + + +class MyProvider(VersionProvider): + file = Path() / "VERSION" + + def get_version(self) -> str: + return self.file.read_text() + + def set_version(self, version: str): + self.file.write_text(version) + +``` + +```python title="setup.py" +from setuptools import setup + +setup( + name='my-commitizen-provider', + version='0.1.0', + py_modules=['my_provider'], + install_requires=['commitizen'], + entry_points = { + 'commitizen.provider': [ + 'my-provider = my_provider:MyProvider', + ] + } +) +``` + [version_files]: bump.md#version_files [tag_format]: bump.md#tag_format [bump_message]: bump.md#bump_message [major-version-zero]: bump.md#-major-version-zero [prerelease-offset]: bump.md#-prerelease_offset [allow_abort]: check.md#allow-abort +[version_type]: bump.md#version_type [additional-features]: https://github.com/tmbo/questionary#additional-features [customization]: customization.md [shortcuts]: customization.md#shortcut-keys diff --git a/docs/customization.md b/docs/customization.md index 053dbc1230..c832761c43 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -190,8 +190,8 @@ The basic steps are: 1. Inheriting from `BaseCommitizen` 2. Give a name to your rules. -3. Expose the class at the end of your file assigning it to `discover_this` -4. Create a python package starting with `cz_` using `setup.py`, `poetry`, etc +3. Create a python package using `setup.py`, `poetry`, etc +4. Expose the class as a `commitizen.plugin` entrypoint Check an [example](convcomms) on how to configure `BaseCommitizen`. @@ -205,7 +205,7 @@ See [commitizen_cz_template](https://github.com/commitizen-tools/commitizen_cz_t ### Custom commit rules -Create a file starting with `cz_`, for example `cz_jira.py`. This prefix is used to detect the plug-in. Same method [flask uses] +Create a Python module, for example `cz_jira.py`. Inherit from `BaseCommitizen`, and you must define `questions` and `message`. The others are optional. @@ -257,8 +257,6 @@ class JiraCz(BaseCommitizen): """ return 'We use this because is useful' - -discover_this = JiraCz # used by the plug-in system ``` The next file required is `setup.py` modified from flask version. @@ -272,7 +270,12 @@ setup( py_modules=['cz_jira'], license='MIT', long_description='this is a long description', - install_requires=['commitizen'] + install_requires=['commitizen'], + entry_points = { + 'commitizen.plugin': [ + 'cz_jira = cz_jira:JiraCz' + ] + } ) ``` @@ -287,8 +290,6 @@ doing `pip install .` If you feel like it should be part of this repo, create a PR. -[flask uses]: http://flask.pocoo.org/docs/0.12/extensiondev/ - ### Custom bump rules You need to define 2 parameters inside your custom `BaseCommitizen`. @@ -381,3 +382,56 @@ from commitizen.cz.exception import CzException class NoSubjectProvidedException(CzException): ... ``` + +### Migrating from legacy plugin format + +Commitizen migrated to a new plugin format relying on `importlib.metadata.EntryPoint`. +Migration should be straight-forward for legacy plugins: + +- Remove the `discover_this` line from you plugin module +- Expose the plugin class under as a `commitizen.plugin` entrypoint. + +The name of the plugin is now determined by the name of the entrypoint. + +#### Example + +If you were having a `CzPlugin` class in a `cz_plugin.py` module like this: + +```python +from commitizen.cz.base import BaseCommitizen + +class PluginCz(BaseCommitizen): + ... + +discover_this = PluginCz +``` + +Then remove the `discover_this` line: + +```python +from commitizen.cz.base import BaseCommitizen + +class PluginCz(BaseCommitizen): + ... +``` + +and expose the class as entrypoint in you setuptools: + +```python +from setuptools import setup + +setup( + name='MyPlugin', + version='0.1.0', + py_modules=['cz_plugin'], + ... + entry_points = { + 'commitizen.plugin': [ + 'plugin = cz_plugin:PluginCz' + ] + } + ... +) +``` + +Then your plugin will be available under the name `plugin`. diff --git a/docs/exit_codes.md b/docs/exit_codes.md index 54be954ff4..e7c7454478 100644 --- a/docs/exit_codes.md +++ b/docs/exit_codes.md @@ -30,4 +30,7 @@ These exit codes can be found in `commitizen/exceptions.py::ExitCode`. | NotAllowed | 20 | `--incremental` cannot be combined with a `rev_range` | | NoneIncrementExit | 21 | The commits found are not eligible to be bumped | | CharacterSetDecodeError | 22 | The character encoding of the command output could not be determined | -| GitCommandError | 23 | Unexpected failure while calling a git command | +| GitCommandError | 23 | Unexpected failure while calling a git command | +| InvalidManualVersion | 24 | Manually provided version is invalid | +| InitFailedError | 25 | Failed to initialize pre-commit | +| VersionProviderUnknown | 26 | `version_provider` setting is set to an unknown version provider indentifier | diff --git a/docs/faq.md b/docs/faq.md index 7c076f0a62..060de78c30 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,26 +5,19 @@ PEP621 establishes a `[project]` definition inside `pyproject.toml` ```toml [project] name = "spam" -version = "2020.0.0" +version = "2.5.1" ``` -Commitizen **won't** use the `project.version` as a source of truth because it's a -tool aimed for any kind of project. - -If we were to use it, it would increase the complexity of the tool. Also why -wouldn't we support other project files like `cargo.toml` or `package.json`? - -Instead of supporting all the different project files, you can use `version_files` -inside `[tool.commitizen]`, and it will cheaply keep any of these project files in sync +Commitizen provides a [`pep621` version provider](config.md#version-providers) to get and set version from this field. +You just need to set the proper `version_provider` setting: ```toml -[tool.commitizen] +[project] +name = "spam" version = "2.5.1" -version_files = [ - "pyproject.toml:^version", - "cargo.toml:^version", - "package.json:\"version\":" -] + +[tool.commitizen] +version_provider = "pep621" ``` ## Why are `revert` and `chore` valid types in the check pattern of cz conventional_commits but not types we can select? diff --git a/docs/tutorials/gitlab_ci.md b/docs/tutorials/gitlab_ci.md index 2859db7318..8c3fd73711 100644 --- a/docs/tutorials/gitlab_ci.md +++ b/docs/tutorials/gitlab_ci.md @@ -74,7 +74,7 @@ test: auto-bump: stage: auto-bump - image: python:3.6 + image: python:3.7 before_script: - "which ssh-agent || ( apt-get update -qy && apt-get install openssh-client -qqy )" - eval `ssh-agent -s` diff --git a/mkdocs.yml b/mkdocs.yml index 7d8dc430b0..e9206b18c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,3 +35,5 @@ markdown_extensions: - admonition - codehilite - extra + - pymdownx.highlight + - pymdownx.superfences diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000000..33d1358a3c --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1510 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "argcomplete" +version = "2.0.0" +description = "Bash tab completion for argparse" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, + {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.23,<5", markers = "python_version == \"3.7\""} + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "black" +version = "22.12.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.0.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "decli" +version = "0.5.2" +description = "Minimal, easy-to-use, declarative cli tool" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"}, + {file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "filelock" +version = "3.8.2" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.8.2-py3-none-any.whl", hash = "sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c"}, + {file = "filelock-3.8.2.tar.gz", hash = "sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2"}, +] + +[package.extras] +docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "freezegun" +version = "1.2.2" +description = "Let your Python tests travel through time" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, + {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "identify" +version = "2.5.11" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.11-py2.py3-none-any.whl", hash = "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"}, + {file = "identify-2.5.11.tar.gz", hash = "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, + {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] + +[[package]] +name = "ipython" +version = "7.34.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] + +[[package]] +name = "isort" +version = "5.11.4" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, + {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jedi" +version = "0.18.2" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.3.7" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, + {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1,<3.4" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.8\""} +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-material" +version = "8.5.11" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, + {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, +] + +[package.dependencies] +jinja2 = ">=3.0.2" +markdown = ">=3.2" +mkdocs = ">=1.4.0" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.12" +pymdown-extensions = ">=9.4" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, +] + +[[package]] +name = "mypy" +version = "0.931" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, +] + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "22.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, +] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, +] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "platformdirs" +version = "2.6.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, + {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, +] + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.36" +description = "Library for building powerful interactive command lines in Python" +category = "main" +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, + {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] + +[[package]] +name = "pydocstyle" +version = "5.1.1" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, + {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, +] + +[package.dependencies] +snowballstemmer = "*" + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.9" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymdown_extensions-9.9-py3-none-any.whl", hash = "sha256:ac698c15265680db5eb13cd4342abfcde2079ac01e5486028f47a1b41547b859"}, + {file = "pymdown_extensions-9.9.tar.gz", hash = "sha256:0f8fb7b74a37a61cc34e90b2c91865458b713ec774894ffad64353a5fce85cfc"}, +] + +[package.dependencies] +markdown = ">=3.2" + +[[package]] +name = "pytest" +version = "7.2.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-datadir" +version = "1.4.1" +description = "pytest plugin for test data directories and files" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-datadir-1.4.1.tar.gz", hash = "sha256:9f7a3c4def6ac4cac3cc8181139ab53bd2667231052bd40cb07081748d4420f0"}, + {file = "pytest_datadir-1.4.1-py3-none-any.whl", hash = "sha256:095f441782b1b907587eca7227fdbae94be43f1c96b4b2cbcc6801a4645be1af"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[[package]] +name = "pytest-freezer" +version = "0.4.6" +description = "Pytest plugin providing a fixture interface for spulec/freezegun" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest_freezer-0.4.6-py3-none-any.whl", hash = "sha256:ca549c30a7e12bc7b242978b6fa0bb91e73cd1bd7d5b2bb658f0f9d7f1694cac"}, + {file = "pytest_freezer-0.4.6.tar.gz", hash = "sha256:8e88cd571d3ba10dd9e0cc09897eb01c32a37bef5ca4ff7c4ea8598c91aa6d96"}, +] + +[package.dependencies] +freezegun = ">=1.0" +pytest = ">=3.6" + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-regressions" +version = "2.4.1" +description = "Easy to use fixtures to write regression tests." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-regressions-2.4.1.tar.gz", hash = "sha256:264d23e8132deeb57441cf466b7b5db445797f346e72b672c95b7f52a47bf01b"}, + {file = "pytest_regressions-2.4.1-py3-none-any.whl", hash = "sha256:11c7cab5f431d1b21d0e351d0828b988c7f3e6b11e4ad6b61a8eae001873b56e"}, +] + +[package.dependencies] +pytest = ">=6.2.0" +pytest-datadir = ">=1.2.0" +pyyaml = "*" + +[package.extras] +dataframe = ["numpy", "pandas"] +dev = ["matplotlib", "mypy", "numpy", "pandas", "pillow", "pre-commit", "restructuredtext-lint", "tox"] +image = ["numpy", "pillow"] +num = ["numpy", "pandas"] + +[[package]] +name = "pytest-xdist" +version = "3.1.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"}, + {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" +optional = false +python-versions = ">=3.6" +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"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "questionary" +version = "1.10.0" +description = "Python library to build pretty command line user prompts ⭐️" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, + {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, +] + +[package.dependencies] +prompt_toolkit = ">=2.0,<4.0" + +[package.extras] +docs = ["Sphinx (>=3.3,<4.0)", "sphinx-autobuild (>=2020.9.1,<2021.0.0)", "sphinx-autodoc-typehints (>=1.11.1,<2.0.0)", "sphinx-copybutton (>=0.3.1,<0.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "termcolor" +version = "2.1.1" +description = "ANSI color formatting for output in terminal" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.1.1-py3-none-any.whl", hash = "sha256:fa852e957f97252205e105dd55bbc23b419a70fec0085708fc0515e399f304fd"}, + {file = "termcolor-2.1.1.tar.gz", hash = "sha256:67cee2009adc6449c650f6bcf3bdeed00c8ba53a8cda5362733c53e0a39fb70b"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +] + +[[package]] +name = "traitlets" +version = "5.8.0" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.8.0-py3-none-any.whl", hash = "sha256:c864831efa0ba6576d09b44884b34e41defc18c0d7e720b4a2d6698c842cab3e"}, + {file = "traitlets-5.8.0.tar.gz", hash = "sha256:6cc57d6dc28c85d5365961726ffd19b538739347749e13ebe34e03323a0e8f84"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] + +[[package]] +name = "types-pyyaml" +version = "5.4.12" +description = "Typing stubs for PyYAML" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-5.4.12.tar.gz", hash = "sha256:3f4daa754357491625ae8c3a39c9e1b0d7cd5632bc4e1c35e7a7f75a64aa124b"}, + {file = "types_PyYAML-5.4.12-py3-none-any.whl", hash = "sha256:e06083f85375a5678e4c19452ed6467ce2167b71db222313e1792cb8fc76859a"}, +] + +[[package]] +name = "types-termcolor" +version = "0.1.1" +description = "Typing stubs for termcolor" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-termcolor-0.1.1.tar.gz", hash = "sha256:4d9e09ce7f3267985f5280b22e25790c98cb64628b6466e1fb915dbb52ad7136"}, + {file = "types_termcolor-0.1.1-py2.py3-none-any.whl", hash = "sha256:3694c312e32f71fdc0f469c334ea21645f3130d90c93cd53bcb06b1233e174d5"}, +] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, + {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "watchdog" +version = "2.2.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"}, + {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b"}, + {file = "watchdog-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6"}, + {file = "watchdog-2.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba"}, + {file = "watchdog-2.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5"}, + {file = "watchdog-2.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1"}, + {file = "watchdog-2.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1"}, + {file = "watchdog-2.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_i686.whl", hash = "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e"}, + {file = "watchdog-2.2.0-py3-none-win32.whl", hash = "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e"}, + {file = "watchdog-2.2.0-py3-none-win_amd64.whl", hash = "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a"}, + {file = "watchdog-2.2.0-py3-none-win_ia64.whl", hash = "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6"}, + {file = "watchdog-2.2.0.tar.gz", hash = "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] + +[[package]] +name = "zipp" +version = "3.11.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "ddc808e77dc10ac4d6e9a373becc1090d35fe5234f0c02c8ec00415d36148e8e" diff --git a/pyproject.toml b/pyproject.toml index 7369391f57..9f52bd5b78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,34 +44,43 @@ classifiers = [ "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", ] [tool.poetry.dependencies] -python = "^3.6.2" +python = "^3.7" questionary = "^1.4.0" decli = "^0.5.2" colorama = "^0.4.1" -termcolor = ">=1.1,<3" +termcolor = ">= 1.1, < 3" packaging = ">=19" tomlkit = ">=0.5.3,<1.0.0" jinja2 = ">=2.10.3" pyyaml = ">=3.08" -argcomplete = ">=1.12.1,<2.2" -typing-extensions = { version = "^4.0.1", python = "<3.8" } -charset-normalizer = ">=2.1.0,<4" +argcomplete = ">=1.12.1,<2.1" +typing-extensions = "^4.0.1" +charset-normalizer = ">=2.1.0,<3.1" +# Use the Python 3.11 compatible API: https://github.com/python/importlib_metadata#compatibility +importlib_metadata = { version = ">=4.13,<5"} [tool.poetry.dev-dependencies] ipython = "^7.2" # test -pytest = "^7.0.0" -pytest-cov = "^2.6" -pytest-mock = "^2.0" -codecov = "^2.0" +pytest = "^7.2.0" +pytest-cov = "^4.0" +pytest-mock = "^3.10" pytest-regressions = "^2.4.0" pytest-freezer = "^0.4.6" # code formatter -black = "^21.12b0" -isort = "^5.7.0" +black = "^22.10" +isort = "^5.10.0" # linter flake8 = "^3.6" pre-commit = "^2.6.0" @@ -80,14 +89,28 @@ types-PyYAML = "^5.4.3" types-termcolor = "^0.1.1" # documentation mkdocs = "^1.0" -mkdocs-material = "^4.1" +mkdocs-material = "^8.5.11" pydocstyle = "^5.0.2" -pytest-xdist = "^2.5.0" +pytest-xdist = "^3.1.0" [tool.poetry.scripts] cz = "commitizen.cli:main" git-cz = "commitizen.cli:main" +[tool.poetry.plugins."commitizen.plugin"] +cz_conventional_commits = "commitizen.cz.conventional_commits:ConventionalCommitsCz" +cz_jira = "commitizen.cz.jira:JiraSmartCz" +cz_customize = "commitizen.cz.customize:CustomizeCommitsCz" + +[tool.poetry.plugins."commitizen.provider"] +cargo = "commitizen.providers:CargoProvider" +commitizen = "commitizen.providers:CommitizenProvider" +composer = "commitizen.providers:ComposerProvider" +npm = "commitizen.providers:NpmProvider" +pep621 = "commitizen.providers:Pep621Provider" +poetry = "commitizen.providers:PoetryProvider" +scm = "commitizen.providers:ScmProvider" + [tool.isort] profile = "black" known_first_party = ["commitizen", "tests"] diff --git a/scripts/test b/scripts/test index d57bdddc48..aa1e50c318 100755 --- a/scripts/test +++ b/scripts/test @@ -4,7 +4,7 @@ set -e export PREFIX='poetry run python -m ' export REGEX='^(?![.]|venv).*' -${PREFIX}pytest -n 3 --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen tests/ +${PREFIX}pytest -n 3 --dist=loadfile --cov-report term-missing --cov-report=xml:coverage.xml --cov=commitizen tests/ ${PREFIX}black commitizen tests --check ${PREFIX}isort --check-only commitizen tests ${PREFIX}flake8 commitizen/ tests/ diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 7b5a44b745..59a0e01ef3 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -278,7 +278,8 @@ def test_bump_on_git_with_hooks_no_verify_enabled(mocker: MockFixture): assert tag_exists is True -def test_bump_when_bumpping_is_not_support(mocker: MockFixture, tmp_commitizen_project): +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_when_bumpping_is_not_support(mocker: MockFixture): create_file_and_commit( "feat: new user interface\n\nBREAKING CHANGE: age is no longer supported" ) @@ -432,7 +433,8 @@ def test_bump_local_version(mocker: MockFixture, tmp_commitizen_project): assert "4.5.1+0.2.0" in f.read() -def test_bump_dry_run(mocker: MockFixture, capsys, tmp_commitizen_project): +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_dry_run(mocker: MockFixture, capsys): create_file_and_commit("feat: new file") testargs = ["cz", "bump", "--yes", "--dry-run"] @@ -475,7 +477,7 @@ def test_none_increment_exit_is_exception(): @pytest.mark.usefixtures("tmp_commitizen_project") def test_none_increment_should_not_call_git_tag_and_error_code_is_not_zero( - mocker: MockFixture, tmp_commitizen_project + mocker: MockFixture, ): create_file_and_commit("test(test_get_all_droplets): fix bad comparison test") testargs = ["cz", "bump", "--yes"] @@ -531,9 +533,8 @@ def test_bump_with_changelog_config(mocker: MockFixture, changelog_path, config_ assert "0.2.0" in out -def test_prevent_prerelease_when_no_increment_detected( - mocker: MockFixture, capsys, tmp_commitizen_project -): +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_prevent_prerelease_when_no_increment_detected(mocker: MockFixture, capsys): create_file_and_commit("feat: new file") testargs = ["cz", "bump", "--yes"] @@ -690,6 +691,7 @@ def test_bump_changelog_command_commits_untracked_changelog_and_version_files( ["cz", "bump", "--increment", "PATCH", "1.2.3"], ], ) +@pytest.mark.usefixtures("tmp_commitizen_project") def test_bump_invalid_manual_args_raises_exception(mocker, testargs): mocker.patch.object(sys, "argv", testargs) @@ -831,3 +833,150 @@ def test_bump_manual_version_disallows_prerelease_offset(mocker): "--prerelease-offset cannot be combined with MANUAL_VERSION" ) assert expected_error_message in str(excinfo.value) + + +@pytest.mark.usefixtures("tmp_git_project") +def test_bump_use_version_provider(mocker: MockFixture): + mock = mocker.MagicMock(name="provider") + mock.get_version.return_value = "0.0.0" + get_provider = mocker.patch( + "commitizen.commands.bump.get_provider", return_value=mock + ) + + create_file_and_commit("fix: fake commit") + testargs = ["cz", "bump", "--yes", "--changelog"] + mocker.patch.object(sys, "argv", testargs) + + cli.main() + + assert git.tag_exist("0.0.1") + get_provider.assert_called_once() + mock.get_version.assert_called_once() + mock.set_version.assert_called_once_with("0.0.1") + + +def test_bump_command_prelease_version_type_via_cli( + tmp_commitizen_project_initial, mocker: MockFixture +): + tmp_commitizen_project = tmp_commitizen_project_initial() + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + testargs = [ + "cz", + "bump", + "--prerelease", + "alpha", + "--yes", + "--version-type", + "semver", + ] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() + + +def test_bump_command_prelease_version_type_via_config( + tmp_commitizen_project_initial, mocker: MockFixture +): + tmp_commitizen_project = tmp_commitizen_project_initial( + config_extra='version_type = "semver"\n', + ) + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0-a1") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a1" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("0.2.0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() + + +def test_bump_command_prelease_version_type_check_old_tags( + tmp_commitizen_project_initial, mocker: MockFixture +): + tmp_commitizen_project = tmp_commitizen_project_initial( + config_extra=('tag_format = "v$version"\nversion_type = "semver"\n'), + ) + tmp_version_file = tmp_commitizen_project.join("__version__.py") + tmp_commitizen_cfg_file = tmp_commitizen_project.join("pyproject.toml") + + testargs = ["cz", "bump", "--prerelease", "alpha", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0-a0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a0" in f.read() + + testargs = ["cz", "bump", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0-a1") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0-a1" in f.read() + + # PRERELEASE BUMP CREATES VERSION WITHOUT PRERELEASE + testargs = ["cz", "bump"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + tag_exists = git.tag_exist("v0.2.0") + assert tag_exists is True + + for version_file in [tmp_version_file, tmp_commitizen_cfg_file]: + with open(version_file, "r") as f: + assert "0.2.0" in f.read() diff --git a/tests/commands/test_changelog_command.py b/tests/commands/test_changelog_command.py index f37915baa2..bdc384c3ca 100644 --- a/tests/commands/test_changelog_command.py +++ b/tests/commands/test_changelog_command.py @@ -13,7 +13,14 @@ NotAGitProjectError, NotAllowed, ) -from tests.utils import create_file_and_commit, wait_for_tag +from tests.utils import ( + create_branch, + create_file_and_commit, + get_current_branch, + merge_branch, + switch_branch, + wait_for_tag, +) @pytest.mark.usefixtures("tmp_commitizen_project") @@ -268,6 +275,75 @@ def test_changelog_hook_customize(mocker: MockFixture, config_customize): changelog_hook_mock.assert_called_with(full_changelog, full_changelog) +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_changelog_with_non_linear_merges_commit_order( + mocker: MockFixture, config_customize +): + """Test that commits merged non-linearly are correctly ordered in the changelog + + A typical scenario is having two branches from main like so: + * feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB) + | * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA) + |/ + * feat: initial commit - (2023-03-01 11:34:54 +0100) | (HEAD -> main) + + And merging them, for example in the reverse order they were created on would give the following: + * Merge branch 'branchA' - (2023-03-01 11:42:59 +0100) | (HEAD -> main) + |\ + | * feat: I will be merged second - (2023-03-01 11:35:22 +0100) | (branchA) + * | feat: I will be merged first - (2023-03-01 11:35:51 +0100) | (branchB) + |/ + * feat: initial commit - (2023-03-01 11:34:54 +0100) | + + In this case we want the changelog to reflect the topological order of commits, + i.e. the order in which they were merged into the main branch + + So the above example should result in the following: + ## Unreleased + + ### Feat + - I will be merged second + - I will be merged first + - initial commit + """ + changelog_hook_mock = mocker.Mock() + changelog_hook_mock.return_value = "cool changelog hook" + + create_file_and_commit("feat: initial commit") + + main_branch = get_current_branch() + + create_branch("branchA") + create_branch("branchB") + + switch_branch("branchA") + create_file_and_commit("feat: I will be merged second") + + switch_branch("branchB") + create_file_and_commit("feat: I will be merged first") + + # Note we merge branches opposite order than author_date + switch_branch(main_branch) + merge_branch("branchB") + merge_branch("branchA") + + changelog = Changelog( + config_customize, + {"unreleased_version": None, "incremental": True, "dry_run": False}, + ) + mocker.patch.object(changelog.cz, "changelog_hook", changelog_hook_mock) + changelog() + full_changelog = "\ +## Unreleased\n\n\ +\ +### Feat\n\n\ +- I will be merged second\n\ +- I will be merged first\n\ +- initial commit\n" + + changelog_hook_mock.assert_called_with(full_changelog, full_changelog) + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_changelog_multiple_incremental_do_not_add_new_lines( mocker: MockFixture, capsys, changelog_path, file_regression @@ -345,6 +421,7 @@ def test_changelog_without_revision(mocker: MockFixture, tmp_commitizen_project) cli.main() +@pytest.mark.usefixtures("tmp_commitizen_project") def test_changelog_incremental_with_revision(mocker): """combining incremental with a revision doesn't make sense""" testargs = ["cz", "changelog", "--incremental", "0.2.0"] @@ -461,6 +538,40 @@ def test_changelog_config_flag_increment( file_regression.check(out, extension=".md") +@pytest.mark.parametrize("test_input", ["rc", "alpha", "beta"]) +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_changelog_config_flag_merge_prerelease( + mocker: MockFixture, changelog_path, config_path, file_regression, test_input +): + with open(config_path, "a") as f: + f.write("changelog_merge_prerelease = true\n") + + create_file_and_commit("irrelevant commit") + mocker.patch("commitizen.git.GitTag.date", "1970-01-01") + git.tag("1.0.0") + + create_file_and_commit("feat: add new output") + create_file_and_commit("fix: output glitch") + + testargs = ["cz", "bump", "--prerelease", test_input, "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + create_file_and_commit("fix: mama gotta work") + create_file_and_commit("feat: add more stuff") + create_file_and_commit("Merge into master") + + testargs = ["cz", "changelog"] + + mocker.patch.object(sys, "argv", testargs) + cli.main() + + with open(changelog_path, "r") as f: + out = f.read() + + file_regression.check(out, extension=".md") + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_changelog_config_start_rev_option( mocker: MockFixture, capsys, config_path, file_regression @@ -549,6 +660,80 @@ def test_changelog_incremental_with_release_candidate_version( file_regression.check(out, extension=".md") +@pytest.mark.parametrize("test_input", ["rc", "alpha", "beta"]) +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_changelog_release_candidate_version_with_merge_prerelease( + mocker: MockFixture, changelog_path, file_regression, test_input +): + """Fix #357""" + with open(changelog_path, "w") as f: + f.write(KEEP_A_CHANGELOG) + create_file_and_commit("irrelevant commit") + mocker.patch("commitizen.git.GitTag.date", "1970-01-01") + git.tag("1.0.0") + + create_file_and_commit("feat: add new output") + create_file_and_commit("fix: output glitch") + + testargs = ["cz", "bump", "--prerelease", test_input, "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + create_file_and_commit("fix: mama gotta work") + create_file_and_commit("feat: add more stuff") + create_file_and_commit("Merge into master") + + testargs = ["cz", "changelog", "--merge-prerelease"] + + mocker.patch.object(sys, "argv", testargs) + cli.main() + + with open(changelog_path, "r") as f: + out = f.read() + + file_regression.check(out, extension=".md") + + +@pytest.mark.parametrize("test_input", ["rc", "alpha", "beta"]) +@pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2023-04-16") +def test_changelog_incremental_with_merge_prerelease( + mocker: MockFixture, changelog_path, file_regression, test_input +): + """Fix #357""" + with open(changelog_path, "w") as f: + f.write(KEEP_A_CHANGELOG) + create_file_and_commit("irrelevant commit") + mocker.patch("commitizen.git.GitTag.date", "1970-01-01") + git.tag("1.0.0") + + create_file_and_commit("feat: add new output") + + testargs = ["cz", "bump", "--prerelease", test_input, "--yes", "--changelog"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + create_file_and_commit("fix: output glitch") + + testargs = ["cz", "bump", "--prerelease", test_input, "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + + create_file_and_commit("fix: mama gotta work") + create_file_and_commit("feat: add more stuff") + create_file_and_commit("Merge into master") + + testargs = ["cz", "changelog", "--merge-prerelease", "--incremental"] + + mocker.patch.object(sys, "argv", testargs) + cli.main() + + with open(changelog_path, "r") as f: + out = f.read() + + file_regression.check(out, extension=".md") + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_changelog_with_filename_as_empty_string( mocker: MockFixture, changelog_path, config_path @@ -968,3 +1153,60 @@ def test_empty_commit_list(mocker): mocker.patch.object(sys, "argv", testargs) with pytest.raises(NoCommitsFoundError): cli.main() + + +@pytest.mark.usefixtures("tmp_commitizen_project") +@pytest.mark.freeze_time("2022-02-13") +def test_changelog_prerelease_rev_with_use_version_type_semver( + mocker: MockFixture, capsys, config_path, changelog_path, file_regression +): + mocker.patch("commitizen.git.GitTag.date", "2022-02-13") + + with open(config_path, "a") as f: + f.write('tag_format = "$version"\n' 'version_type = "semver"') + + # create commit and tag + create_file_and_commit("feat: new file") + testargs = ["cz", "bump", "--yes"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + wait_for_tag() + + create_file_and_commit("feat: after 0.2.0") + create_file_and_commit("feat: another feature") + + testargs = ["cz", "bump", "--yes", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + capsys.readouterr() + wait_for_tag() + + tag_exists = git.tag_exist("0.3.0-a0") + assert tag_exists is True + + testargs = ["cz", "changelog", "0.3.0-a0", "--dry-run"] + mocker.patch.object(sys, "argv", testargs) + with pytest.raises(DryRunExit): + cli.main() + + out, _ = capsys.readouterr() + + file_regression.check(out, extension=".md") + + testargs = ["cz", "bump", "--yes", "--prerelease", "alpha"] + mocker.patch.object(sys, "argv", testargs) + cli.main() + capsys.readouterr() + wait_for_tag() + + tag_exists = git.tag_exist("0.3.0-a1") + assert tag_exists is True + + testargs = ["cz", "changelog", "0.3.0-a1", "--dry-run"] + mocker.patch.object(sys, "argv", testargs) + with pytest.raises(DryRunExit): + cli.main() + + out, _ = capsys.readouterr() + + file_regression.check(out, extension=".second-prerelease.md") diff --git a/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_alpha_.md b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_alpha_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_alpha_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_beta_.md b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_beta_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_beta_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_rc_.md b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_rc_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_config_flag_merge_prerelease_rc_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_alpha_.md b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_alpha_.md new file mode 100644 index 0000000000..8e81f62572 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_alpha_.md @@ -0,0 +1,37 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Feat + +- add more stuff + +### Fix + +- mama gotta work +- output glitch + +## 0.2.0a0 (2023-04-16) + +### Feat + +- add new output + +## [1.0.0] - 2017-06-20 +### Added +- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8). +- Version navigation. + +### Changed +- Start using "changelog" over "change log" since it's the common usage. + +### Removed +- Section about "changelog" vs "CHANGELOG". + +## [0.3.0] - 2015-12-03 +### Added +- RU translation from [@aishek](https://github.com/aishek). diff --git a/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_beta_.md b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_beta_.md new file mode 100644 index 0000000000..65f14c068e --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_beta_.md @@ -0,0 +1,37 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Feat + +- add more stuff + +### Fix + +- mama gotta work +- output glitch + +## 0.2.0b0 (2023-04-16) + +### Feat + +- add new output + +## [1.0.0] - 2017-06-20 +### Added +- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8). +- Version navigation. + +### Changed +- Start using "changelog" over "change log" since it's the common usage. + +### Removed +- Section about "changelog" vs "CHANGELOG". + +## [0.3.0] - 2015-12-03 +### Added +- RU translation from [@aishek](https://github.com/aishek). diff --git a/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_rc_.md b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_rc_.md new file mode 100644 index 0000000000..0987e1127d --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_incremental_with_merge_prerelease_rc_.md @@ -0,0 +1,37 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Feat + +- add more stuff + +### Fix + +- mama gotta work +- output glitch + +## 0.2.0rc0 (2023-04-16) + +### Feat + +- add new output + +## [1.0.0] - 2017-06-20 +### Added +- New visual identity by [@tylerfortune8](https://github.com/tylerfortune8). +- Version navigation. + +### Changed +- Start using "changelog" over "change log" since it's the common usage. + +### Removed +- Section about "changelog" vs "CHANGELOG". + +## [0.3.0] - 2015-12-03 +### Added +- RU translation from [@aishek](https://github.com/aishek). diff --git a/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md new file mode 100644 index 0000000000..9a66210a74 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.md @@ -0,0 +1,7 @@ +## 0.3.0-a0 (2022-02-13) + +### Feat + +- another feature +- after 0.2.0 + diff --git a/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md new file mode 100644 index 0000000000..09fd10ee8d --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_prerelease_rev_with_use_version_type_semver.second-prerelease.md @@ -0,0 +1,2 @@ +## 0.3.0-a1 (2022-02-13) + diff --git a/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_alpha_.md b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_alpha_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_alpha_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_beta_.md b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_beta_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_beta_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_rc_.md b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_rc_.md new file mode 100644 index 0000000000..dca7824480 --- /dev/null +++ b/tests/commands/test_changelog_command/test_changelog_release_candidate_version_with_merge_prerelease_rc_.md @@ -0,0 +1,13 @@ +## Unreleased + +### Feat + +- add more stuff +- add new output + +### Fix + +- mama gotta work +- output glitch + +## 1.0.0 (1970-01-01) diff --git a/tests/commands/test_commit_command.py b/tests/commands/test_commit_command.py index 05e72cfabd..dd62fafe85 100644 --- a/tests/commands/test_commit_command.py +++ b/tests/commands/test_commit_command.py @@ -17,9 +17,10 @@ @pytest.fixture -def staging_is_clean(mocker: MockFixture): +def staging_is_clean(mocker: MockFixture, tmp_git_project): is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean") is_staging_clean_mock.return_value = False + return tmp_git_project @pytest.mark.usefixtures("staging_is_clean") @@ -128,6 +129,7 @@ def test_commit_command_with_signoff_option(config, mocker: MockFixture): success_mock.assert_called_once() +@pytest.mark.usefixtures("tmp_git_project") def test_commit_when_nothing_to_commit(config, mocker: MockFixture): is_staging_clean_mock = mocker.patch("commitizen.git.is_staging_clean") is_staging_clean_mock.return_value = True diff --git a/tests/commands/test_version_command.py b/tests/commands/test_version_command.py index 7e6ec3c851..3f9de50d00 100644 --- a/tests/commands/test_version_command.py +++ b/tests/commands/test_version_command.py @@ -1,8 +1,12 @@ import platform import sys +import pytest +from pytest_mock import MockerFixture + from commitizen import commands from commitizen.__version__ import __version__ +from commitizen.config.base_config import BaseConfig def test_version_for_showing_project_version(config, capsys): @@ -70,3 +74,35 @@ def test_version_for_showing_commitizen_system_info(config, capsys): assert f"Commitizen Version: {__version__}" in captured.out assert f"Python Version: {sys.version}" in captured.out assert f"Operating System: {platform.system()}" in captured.out + + +@pytest.mark.parametrize("project", (True, False)) +@pytest.mark.usefixtures("tmp_git_project") +def test_version_use_version_provider( + mocker: MockerFixture, + config: BaseConfig, + capsys: pytest.CaptureFixture, + project: bool, +): + version = "0.0.0" + mock = mocker.MagicMock(name="provider") + mock.get_version.return_value = version + get_provider = mocker.patch( + "commitizen.commands.version.get_provider", return_value=mock + ) + + commands.Version( + config, + { + "report": False, + "project": project, + "commitizen": False, + "verbose": not project, + }, + )() + captured = capsys.readouterr() + + assert version in captured.out + get_provider.assert_called_once() + mock.get_version.assert_called_once() + mock.set_version.assert_not_called() diff --git a/tests/conftest.py b/tests/conftest.py index bb18531699..25cbcbf27c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,31 @@ import os import re import tempfile +from pathlib import Path +from typing import Optional import pytest from commitizen import cmd, defaults from commitizen.config import BaseConfig +from tests.utils import create_file_and_commit + +SIGNER = "GitHub Action" +SIGNER_MAIL = "action@github.com" + + +@pytest.fixture(autouse=True) +def git_sandbox(monkeypatch: pytest.MonkeyPatch, tmp_path: Path): + """Ensure git commands are executed without the current user settings""" + # Clear any GIT_ prefixed environment variable + for var in os.environ: + if var.startswith("GIT_"): + monkeypatch.delenv(var) + + # Define a dedicated temporary git config + monkeypatch.setenv("GIT_CONFIG_GLOBAL", str(tmp_path / "gitconfig")) + cmd.run(f"git config --global user.name {SIGNER}") + cmd.run(f"git config --global user.email {SIGNER_MAIL}") @pytest.fixture(scope="function") @@ -18,11 +38,39 @@ def tmp_git_project(tmpdir): @pytest.fixture(scope="function") def tmp_commitizen_project(tmp_git_project): - with tmp_git_project.as_cwd(): - tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") - tmp_commitizen_cfg_file.write("[tool.commitizen]\n" 'version="0.1.0"\n') + tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") + tmp_commitizen_cfg_file.write("[tool.commitizen]\n" 'version="0.1.0"\n') - yield tmp_git_project + yield tmp_git_project + + +@pytest.fixture(scope="function") +def tmp_commitizen_project_initial(tmp_git_project): + def _initial( + config_extra: Optional[str] = None, + version="0.1.0", + initial_commit="feat: new user interface", + ): + with tmp_git_project.as_cwd(): + tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") + tmp_commitizen_cfg_file.write( + f"[tool.commitizen]\n" f'version="{version}"\n' + ) + tmp_version_file = tmp_git_project.join("__version__.py") + tmp_version_file.write(version) + tmp_commitizen_cfg_file = tmp_git_project.join("pyproject.toml") + tmp_version_file_string = str(tmp_version_file).replace("\\", "/") + tmp_commitizen_cfg_file.write( + f"{tmp_commitizen_cfg_file.read()}\n" + f'version_files = ["{tmp_version_file_string}"]\n' + ) + if config_extra: + tmp_commitizen_cfg_file.write(config_extra, mode="a") + create_file_and_commit(initial_commit) + + return tmp_git_project + + yield _initial def _get_gpg_keyid(signer_mail): @@ -36,9 +84,6 @@ def _get_gpg_keyid(signer_mail): @pytest.fixture(scope="function") def tmp_commitizen_project_with_gpg(tmp_commitizen_project): - signer = "GitHub Action" - signer_mail = "action@github.com" - # create a temporary GPGHOME to store a temporary keyring. # Home path must be less than 104 characters gpg_home = tempfile.TemporaryDirectory(suffix="_cz") @@ -47,17 +92,15 @@ def tmp_commitizen_project_with_gpg(tmp_commitizen_project): # create a key (a keyring will be generated within GPUPGHOME) c = cmd.run( - f"gpg --batch --yes --debug-quick-random --passphrase '' --quick-gen-key '{signer} {signer_mail}'" + f"gpg --batch --yes --debug-quick-random --passphrase '' --quick-gen-key '{SIGNER} {SIGNER_MAIL}'" ) if c.return_code != 0: raise Exception(f"gpg keygen failed with err: '{c.err}'") - key_id = _get_gpg_keyid(signer_mail) + key_id = _get_gpg_keyid(SIGNER_MAIL) assert key_id - # configure git + # configure git to use gpg signing cmd.run("git config commit.gpgsign true") - cmd.run(f"git config user.name {signer}") - cmd.run(f"git config user.email {signer_mail}") cmd.run(f"git config user.signingkey {key_id}") yield tmp_commitizen_project diff --git a/tests/test_bump_create_commit_message.py b/tests/test_bump_create_commit_message.py index 6fc31778d7..0609e1a236 100644 --- a/tests/test_bump_create_commit_message.py +++ b/tests/test_bump_create_commit_message.py @@ -1,4 +1,3 @@ -import os import sys from pathlib import Path from textwrap import dedent @@ -29,9 +28,8 @@ def test_create_tag(test_input, expected): @pytest.mark.parametrize("retry", (True, False)) -def test_bump_pre_commit_changelog( - tmp_commitizen_project, mocker: MockFixture, freezer, retry -): +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_pre_commit_changelog(mocker: MockFixture, freezer, retry): freezer.move_to("2022-04-01") testargs = ["cz", "bump", "--changelog", "--yes"] if retry: @@ -39,10 +37,10 @@ def test_bump_pre_commit_changelog( else: pytest.xfail("it will fail because pre-commit will reformat CHANGELOG.md") mocker.patch.object(sys, "argv", testargs) - with tmp_commitizen_project.as_cwd(): - # Configure prettier as a pre-commit hook - Path(".pre-commit-config.yaml").write_text( - """ + # Configure prettier as a pre-commit hook + Path(".pre-commit-config.yaml").write_text( + dedent( + """\ repos: - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.6.2 @@ -51,44 +49,43 @@ def test_bump_pre_commit_changelog( stages: [commit] """ ) - # Prettier inherits editorconfig - Path(".editorconfig").write_text( - """ + ) + # Prettier inherits editorconfig + Path(".editorconfig").write_text( + dedent( + """\ [*] indent_size = 4 """ ) - cmd.run("git add -A") - if os.name == "nt": - cmd.run('git commit -m "fix: _test"') - else: - cmd.run("git commit -m 'fix: _test'") - cmd.run("pre-commit install") - cli.main() - # Pre-commit fixed last line adding extra indent and "\" char - assert Path("CHANGELOG.md").read_text() == dedent( - """\ - ## 0.1.1 (2022-04-01) + ) + cmd.run("git add -A") + cmd.run('git commit -m "fix: _test"') + cmd.run("pre-commit install") + cli.main() + # Pre-commit fixed last line adding extra indent and "\" char + assert Path("CHANGELOG.md").read_text() == dedent( + """\ + ## 0.1.1 (2022-04-01) - ### Fix + ### Fix - - \\_test - """ - ) + - \\_test + """ + ) @pytest.mark.parametrize("retry", (True, False)) -def test_bump_pre_commit_changelog_fails_always( - tmp_commitizen_project, mocker: MockFixture, freezer, retry -): +@pytest.mark.usefixtures("tmp_commitizen_project") +def test_bump_pre_commit_changelog_fails_always(mocker: MockFixture, freezer, retry): freezer.move_to("2022-04-01") testargs = ["cz", "bump", "--changelog", "--yes"] if retry: testargs.append("--retry") mocker.patch.object(sys, "argv", testargs) - with tmp_commitizen_project.as_cwd(): - Path(".pre-commit-config.yaml").write_text( - """ + Path(".pre-commit-config.yaml").write_text( + dedent( + """\ repos: - repo: local hooks: @@ -99,11 +96,9 @@ def test_bump_pre_commit_changelog_fails_always( files: CHANGELOG.md """ ) - cmd.run("git add -A") - if os.name == "nt": - cmd.run('git commit -m "feat: forbid changelogs"') - else: - cmd.run("git commit -m 'feat: forbid changelogs'") - cmd.run("pre-commit install") - with pytest.raises(exceptions.BumpCommitFailedError): - cli.main() + ) + cmd.run("git add -A") + cmd.run('git commit -m "feat: forbid changelogs"') + cmd.run("pre-commit install") + with pytest.raises(exceptions.BumpCommitFailedError): + cli.main() diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py index 826490a3ba..32a978556c 100644 --- a/tests/test_bump_find_increment.py +++ b/tests/test_bump_find_increment.py @@ -5,7 +5,7 @@ import pytest from commitizen import bump -from commitizen.cz import ConventionalCommitsCz +from commitizen.cz.conventional_commits import ConventionalCommitsCz from commitizen.git import GitCommit NONE_INCREMENT_CC = ["docs(README): motivation", "ci: added travis"] diff --git a/tests/test_bump_find_version.py b/tests/test_bump_find_version.py index f12b87f1eb..394c8fd61a 100644 --- a/tests/test_bump_find_version.py +++ b/tests/test_bump_find_version.py @@ -91,16 +91,13 @@ def test_generate_version(test_input, expected): prerelease = test_input[2] prerelease_offset = test_input[3] devrelease = test_input[4] - assert ( - generate_version( - current_version, - increment=increment, - prerelease=prerelease, - prerelease_offset=prerelease_offset, - devrelease=devrelease, - ) - == Version(expected) - ) + assert generate_version( + current_version, + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + ) == Version(expected) @pytest.mark.parametrize( @@ -114,14 +111,11 @@ def test_generate_version_local(test_input, expected): prerelease_offset = test_input[3] devrelease = test_input[4] is_local_version = True - assert ( - generate_version( - current_version, - increment=increment, - prerelease=prerelease, - prerelease_offset=prerelease_offset, - devrelease=devrelease, - is_local_version=is_local_version, - ) - == Version(expected) - ) + assert generate_version( + current_version, + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + is_local_version=is_local_version, + ) == Version(expected) diff --git a/tests/test_bump_find_version_type_semver.py b/tests/test_bump_find_version_type_semver.py new file mode 100644 index 0000000000..633442f9ee --- /dev/null +++ b/tests/test_bump_find_version_type_semver.py @@ -0,0 +1,102 @@ +import itertools + +import pytest + +from commitizen.bump import generate_version +from commitizen.version_types import SemVerVersion + +simple_flow = [ + (("0.1.0", "PATCH", None, 0, None), "0.1.1"), + (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev1"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("0.2.0", "MINOR", None, 0, None), "0.3.0"), + (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev1"), + (("0.3.0", "PATCH", None, 0, None), "0.3.1"), + (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-a0"), + (("0.3.1a0", None, "alpha", 0, None), "0.3.1-a1"), + (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-a1"), + (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.3.1a0", None, None, 0, None), "0.3.1"), + (("0.3.1", "PATCH", None, 0, None), "0.3.2"), + (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-a0"), + (("1.0.0a0", None, "alpha", 0, None), "1.0.0-a1"), + (("1.0.0a1", None, "alpha", 0, None), "1.0.0-a2"), + (("1.0.0a1", None, "alpha", 0, 1), "1.0.0-a2-dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), + (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), + (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), + (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), + (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), + (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0", "PATCH", None, 0, None), "1.0.1"), + (("1.0.1", "PATCH", None, 0, None), "1.0.2"), + (("1.0.2", "MINOR", None, 0, None), "1.1.0"), + (("1.1.0", "MINOR", None, 0, None), "1.2.0"), + (("1.2.0", "PATCH", None, 0, None), "1.2.1"), + (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), +] + +# this cases should be handled gracefully +unexpected_cases = [ + (("0.1.1rc0", None, "alpha", 0, None), "0.1.1-a0"), + (("0.1.1b1", None, "alpha", 0, None), "0.1.1-a0"), +] + +weird_cases = [ + (("1.1", "PATCH", None, 0, None), "1.1.1"), + (("1", "MINOR", None, 0, None), "1.1.0"), + (("1", "MAJOR", None, 0, None), "2.0.0"), + (("1a0", None, "alpha", 0, None), "1.0.0-a1"), + (("1a0", None, "alpha", 1, None), "1.0.0-a1"), + (("1", None, "beta", 0, None), "1.0.0-b0"), + (("1", None, "beta", 1, None), "1.0.0-b1"), + (("1beta", None, "beta", 0, None), "1.0.0-b1"), + (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), +] + +# test driven development +tdd_cases = [ + (("0.1.1", "PATCH", None, 0, None), "0.1.2"), + (("0.1.1", "MINOR", None, 0, None), "0.2.0"), + (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), + (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-a0"), + (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-a0"), + (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-a0"), + (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-a1"), + (("1.0.0a2", None, "beta", 0, None), "1.0.0-b0"), + (("1.0.0a2", None, "beta", 1, None), "1.0.0-b1"), + (("1.0.0beta1", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), + (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), + (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), +] + + +@pytest.mark.parametrize( + "test_input, expected", + itertools.chain(tdd_cases, weird_cases, simple_flow, unexpected_cases), +) +def test_generate_version_type(test_input, expected): + current_version = test_input[0] + increment = test_input[1] + prerelease = test_input[2] + prerelease_offset = test_input[3] + devrelease = test_input[4] + assert ( + str( + generate_version( + current_version, + increment=increment, + prerelease=prerelease, + prerelease_offset=prerelease_offset, + devrelease=devrelease, + version_type_cls=SemVerVersion, + ) + ) + == expected + ) diff --git a/tests/test_bump_update_version_in_files.py b/tests/test_bump_update_version_in_files.py index 0c37bf538c..b1b9c0b1d5 100644 --- a/tests/test_bump_update_version_in_files.py +++ b/tests/test_bump_update_version_in_files.py @@ -1,7 +1,7 @@ +from pathlib import Path from shutil import copyfile import pytest -from py._path.local import LocalPath from commitizen import bump from commitizen.exceptions import CurrentVersionNotFoundError @@ -13,9 +13,9 @@ def _copy_sample_file_to_tmpdir( - tmpdir: LocalPath, source_filename: str, dest_filename: str -) -> str: - tmp_file = str(tmpdir.join(dest_filename)).replace("\\", "/") + tmp_path: Path, source_filename: str, dest_filename: str +) -> Path: + tmp_file = tmp_path / dest_filename copyfile(f"{TESTING_FILE_PREFIX}/{source_filename}", tmp_file) return tmp_file diff --git a/tests/test_changelog.py b/tests/test_changelog.py index e68a3abdcf..0aa9fdfbff 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -513,6 +513,17 @@ def test_get_commit_tag_is_None(gitcommits, tags): assert current_key is None +@pytest.mark.parametrize("test_input", TAGS) +def test_valid_tag_included_in_changelog(test_input): + tag = git.GitTag(*test_input) + assert changelog.tag_included_in_changelog(tag, [], False) + + +def test_invalid_tag_included_in_changelog(): + tag = git.GitTag("not_a_version", "rev", "date") + assert not changelog.tag_included_in_changelog(tag, [], False) + + COMMITS_TREE = ( { "version": "v1.2.0", @@ -789,15 +800,287 @@ def test_get_commit_tag_is_None(gitcommits, tags): }, ) +COMMITS_TREE_AFTER_MERGED_PRERELEASES = ( + { + "version": "v1.2.0", + "date": "2019-04-19", + "changes": { + "feat": [ + { + "scope": None, + "breaking": None, + "message": "custom cz plugins now support bumping version", + } + ] + }, + }, + { + "version": "v1.1.1", + "date": "2019-04-18", + "changes": { + "refactor": [ + { + "scope": None, + "breaking": None, + "message": "changed stdout statements", + }, + { + "scope": "schema", + "breaking": None, + "message": "command logic removed from commitizen base", + }, + { + "scope": "info", + "breaking": None, + "message": "command logic removed from commitizen base", + }, + { + "scope": "example", + "breaking": None, + "message": "command logic removed from commitizen base", + }, + { + "scope": "commit", + "breaking": None, + "message": "moved most of the commit logic to the commit command", + }, + ], + "fix": [ + { + "scope": "bump", + "breaking": None, + "message": "commit message now fits better with semver", + }, + { + "scope": None, + "breaking": None, + "message": "conventional commit 'breaking change' in body instead of title", + }, + ], + }, + }, + { + "version": "v1.1.0", + "date": "2019-04-14", + "changes": { + "feat": [ + { + "scope": None, + "breaking": None, + "message": "new working bump command", + }, + {"scope": None, "breaking": None, "message": "create version tag"}, + { + "scope": None, + "breaking": None, + "message": "update given files with new version", + }, + { + "scope": "config", + "breaking": None, + "message": "new set key, used to set version to cfg", + }, + { + "scope": None, + "breaking": None, + "message": "support for pyproject.toml", + }, + { + "scope": None, + "breaking": None, + "message": "first semantic version bump implementation", + }, + ], + "fix": [ + { + "scope": None, + "breaking": None, + "message": "removed all from commit", + }, + { + "scope": None, + "breaking": None, + "message": "fix config file not working", + }, + ], + "refactor": [ + { + "scope": None, + "breaking": None, + "message": "added commands folder, better integration with decli", + } + ], + }, + }, + { + "version": "v1.0.0", + "date": "2019-03-01", + "changes": { + "refactor": [ + { + "scope": None, + "breaking": None, + "message": "removed delegator, added decli and many tests", + } + ], + "feat": [ + { + "scope": None, + "breaking": None, + "message": "py3 only, tests and conventional commits 1.0", + } + ], + "BREAKING CHANGE": [ + {"scope": None, "breaking": None, "message": "API is stable"} + ], + }, + }, + { + "version": "v0.9.11", + "date": "2018-12-17", + "changes": { + "fix": [ + { + "scope": "config", + "breaking": None, + "message": "load config reads in order without failing if there is no commitizen section", + } + ] + }, + }, + { + "version": "v0.9.10", + "date": "2018-09-22", + "changes": { + "fix": [ + { + "scope": None, + "breaking": None, + "message": "parse scope (this is my punishment for not having tests)", + } + ] + }, + }, + { + "version": "v0.9.9", + "date": "2018-09-22", + "changes": { + "fix": [{"scope": None, "breaking": None, "message": "parse scope empty"}] + }, + }, + { + "version": "v0.9.8", + "date": "2018-09-22", + "changes": { + "fix": [ + { + "scope": "scope", + "breaking": None, + "message": "parse correctly again", + } + ] + }, + }, + { + "version": "v0.9.7", + "date": "2018-09-22", + "changes": { + "fix": [{"scope": "scope", "breaking": None, "message": "parse correctly"}] + }, + }, + { + "version": "v0.9.6", + "date": "2018-09-19", + "changes": { + "refactor": [ + { + "scope": "conventionalCommit", + "breaking": None, + "message": "moved filters to questions instead of message", + } + ], + "fix": [ + { + "scope": "manifest", + "breaking": None, + "message": "included missing files", + } + ], + }, + }, + { + "version": "v0.9.5", + "date": "2018-08-24", + "changes": { + "fix": [ + { + "scope": "config", + "breaking": None, + "message": "home path for python versions between 3.0 and 3.5", + } + ] + }, + }, + { + "version": "v0.9.4", + "date": "2018-08-02", + "changes": { + "feat": [{"scope": "cli", "breaking": None, "message": "added version"}] + }, + }, + { + "version": "v0.9.3", + "date": "2018-07-28", + "changes": { + "feat": [ + { + "scope": "committer", + "breaking": None, + "message": "conventional commit is a bit more intelligent now", + } + ] + }, + }, + { + "version": "v0.9.2", + "date": "2017-11-11", + "changes": { + "refactor": [ + { + "scope": None, + "breaking": None, + "message": "renamed conventional_changelog to conventional_commits, not backward compatible", + } + ] + }, + }, + { + "version": "v0.9.1", + "date": "2017-11-11", + "changes": { + "fix": [ + { + "scope": "setup.py", + "breaking": None, + "message": "future is now required for every python version", + } + ] + }, + }, +) + -def test_generate_tree_from_commits(gitcommits, tags): +@pytest.mark.parametrize("merge_prereleases", (True, False)) +def test_generate_tree_from_commits(gitcommits, tags, merge_prereleases): parser = defaults.commit_parser changelog_pattern = defaults.bump_pattern tree = changelog.generate_tree_from_commits( - gitcommits, tags, parser, changelog_pattern + gitcommits, tags, parser, changelog_pattern, merge_prerelease=merge_prereleases ) - - assert tuple(tree) == COMMITS_TREE + if merge_prereleases: + assert tuple(tree) == COMMITS_TREE_AFTER_MERGED_PRERELEASES + else: + assert tuple(tree) == COMMITS_TREE @pytest.mark.parametrize( diff --git a/tests/test_conf.py b/tests/test_conf.py index d39de8a048..4226096371 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -44,6 +44,7 @@ _settings = { "name": "cz_jira", "version": "1.0.0", + "version_provider": "commitizen", "tag_format": None, "bump_message": None, "allow_abort": False, @@ -52,17 +53,20 @@ "changelog_file": "CHANGELOG.md", "changelog_incremental": False, "changelog_start_rev": None, + "changelog_merge_prerelease": False, "update_changelog_on_bump": False, "use_shortcuts": False, "major_version_zero": False, "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, + "version_type": None, } _new_settings = { "name": "cz_jira", "version": "2.0.0", + "version_provider": "commitizen", "tag_format": None, "bump_message": None, "allow_abort": False, @@ -71,12 +75,14 @@ "changelog_file": "CHANGELOG.md", "changelog_incremental": False, "changelog_start_rev": None, + "changelog_merge_prerelease": False, "update_changelog_on_bump": False, "use_shortcuts": False, "major_version_zero": False, "pre_bump_hooks": ["scripts/generate_documentation.sh"], "post_bump_hooks": ["scripts/slack_notification.sh"], "prerelease_offset": 0, + "version_type": None, } _read_settings = { diff --git a/tests/test_factory.py b/tests/test_factory.py index a8e56f51d6..0ac8c0b275 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -1,13 +1,26 @@ import sys +from textwrap import dedent +import importlib_metadata as metadata import pytest from commitizen import BaseCommitizen, defaults, factory from commitizen.config import BaseConfig from commitizen.cz import discover_plugins +from commitizen.cz.conventional_commits import ConventionalCommitsCz +from commitizen.cz.customize import CustomizeCommitsCz +from commitizen.cz.jira import JiraSmartCz from commitizen.exceptions import NoCommitizenFoundException +class Plugin: + pass + + +class OtherPlugin: + pass + + def test_factory(): config = BaseConfig() config.settings.update({"name": defaults.DEFAULT_SETTINGS["name"]}) @@ -24,17 +37,19 @@ def test_factory_fails(): assert "The committer has not been found in the system." in str(excinfo) -@pytest.mark.parametrize( - "module_content, plugin_name, expected_plugins", - [ - ("", "cz_no_plugin", {}), - ], -) -def test_discover_plugins(module_content, plugin_name, expected_plugins, tmp_path): - no_plugin_folder = tmp_path / plugin_name - no_plugin_folder.mkdir() - init_file = no_plugin_folder / "__init__.py" - init_file.write_text(module_content) +def test_discover_plugins(tmp_path): + legacy_plugin_folder = tmp_path / "cz_legacy" + legacy_plugin_folder.mkdir() + init_file = legacy_plugin_folder / "__init__.py" + init_file.write_text( + dedent( + """\ + class Plugin: pass + + discover_this = Plugin + """ + ) + ) sys.path.append(tmp_path.as_posix()) with pytest.warns(UserWarning) as record: @@ -43,6 +58,36 @@ def test_discover_plugins(module_content, plugin_name, expected_plugins, tmp_pat assert ( record[0].message.args[0] - == f"module '{plugin_name}' has no attribute 'discover_this'" + == "Legacy plugin 'cz_legacy' has been ignored: please expose it the 'commitizen.plugin' entrypoint" + ) + assert "cz_legacy" not in discovered_plugins + + +def test_discover_external_plugin(mocker): + ep_plugin = metadata.EntryPoint( + "test", "tests.test_factory:Plugin", "commitizen.plugin" ) - assert expected_plugins == discovered_plugins + ep_other_plugin = metadata.EntryPoint( + "not-selected", "tests.test_factory::OtherPlugin", "commitizen.not_a_plugin" + ) + eps = [ep_plugin, ep_other_plugin] + + def mock_entrypoints(**kwargs): + group = kwargs.get("group") + return metadata.EntryPoints(ep for ep in eps if ep.group == group) + + mocker.patch.object(metadata, "entry_points", side_effect=mock_entrypoints) + + assert discover_plugins() == {"test": Plugin} + + +def test_discover_internal_plugins(): + expected = { + "cz_conventional_commits": ConventionalCommitsCz, + "cz_jira": JiraSmartCz, + "cz_customize": CustomizeCommitsCz, + } + + discovered = discover_plugins() + + assert set(expected.items()).issubset(set(discovered.items())) diff --git a/tests/test_version_providers.py b/tests/test_version_providers.py new file mode 100644 index 0000000000..1c48fc3603 --- /dev/null +++ b/tests/test_version_providers.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import os +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING, Iterator, Optional, Type + +import pytest + +from commitizen.config.base_config import BaseConfig +from commitizen.exceptions import VersionProviderUnknown +from commitizen.providers import ( + CargoProvider, + CommitizenProvider, + ComposerProvider, + NpmProvider, + Pep621Provider, + PoetryProvider, + ScmProvider, + VersionProvider, + get_provider, +) +from tests.utils import create_file_and_commit, create_tag + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture +def chdir(tmp_path: Path) -> Iterator[Path]: + cwd = Path() + os.chdir(tmp_path) + yield tmp_path + os.chdir(cwd) + + +def test_default_version_provider_is_commitizen_config(config: BaseConfig): + provider = get_provider(config) + + assert isinstance(provider, CommitizenProvider) + + +def test_raise_for_unknown_provider(config: BaseConfig): + config.settings["version_provider"] = "unknown" + with pytest.raises(VersionProviderUnknown): + get_provider(config) + + +def test_commitizen_provider(config: BaseConfig, mocker: MockerFixture): + config.settings["version"] = "42" + mock = mocker.patch.object(config, "set_key") + + provider = CommitizenProvider(config) + assert provider.get_version() == "42" + + provider.set_version("43.1") + mock.assert_called_once_with("version", "43.1") + + +FILE_PROVIDERS = dict( + pep621=( + "pyproject.toml", + Pep621Provider, + """\ + [project] + version = "0.1.0" + """, + """\ + [project] + version = "42.1" + """, + ), + poetry=( + "pyproject.toml", + PoetryProvider, + """\ + [tool.poetry] + version = "0.1.0" + """, + """\ + [tool.poetry] + version = "42.1" + """, + ), + cargo=( + "Cargo.toml", + CargoProvider, + """\ + [package] + version = "0.1.0" + """, + """\ + [package] + version = "42.1" + """, + ), + npm=( + "package.json", + NpmProvider, + """\ + { + "name": "whatever", + "version": "0.1.0" + } + """, + """\ + { + "name": "whatever", + "version": "42.1" + } + """, + ), + composer=( + "composer.json", + ComposerProvider, + """\ + { + "name": "whatever", + "version": "0.1.0" + } + """, + """\ + { + "name": "whatever", + "version": "42.1" + } + """, + ), +) + + +@pytest.mark.parametrize( + "id,filename,cls,content,expected", + (pytest.param(id, *FILE_PROVIDERS[id], id=id) for id in FILE_PROVIDERS), +) +def test_file_providers( + config: BaseConfig, + chdir: Path, + id: str, + filename: str, + cls: Type[VersionProvider], + content: str, + expected: str, +): + file = chdir / filename + file.write_text(dedent(content)) + config.settings["version_provider"] = id + + provider = get_provider(config) + assert isinstance(provider, cls) + assert provider.get_version() == "0.1.0" + + provider.set_version("42.1") + assert file.read_text() == dedent(expected) + + +@pytest.mark.parametrize( + "tag_format,tag,version", + ( + (None, "0.1.0", "0.1.0"), + (None, "v0.1.0", "0.1.0"), + ("v$version", "v0.1.0", "0.1.0"), + ("version-$version", "version-0.1.0", "0.1.0"), + ("version-$version", "version-0.1", "0.1"), + ("version-$version", "version-0.1.0rc1", "0.1.0rc1"), + ("v$minor.$major.$patch", "v1.0.0", "0.1.0"), + ("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"), + ("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"), + ("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"), + ("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"), + ), +) +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider( + config: BaseConfig, tag_format: Optional[str], tag: str, version: str +): + create_file_and_commit("test: fake commit") + create_tag(tag) + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + + config.settings["version_provider"] = "scm" + config.settings["tag_format"] = tag_format + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == version + + # Should not fail on set_version() + provider.set_version("43.1") + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_matching_tag(config: BaseConfig): + create_file_and_commit("test: fake commit") + create_tag("should-not-match") + create_file_and_commit("test: fake commit") + + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" + + +@pytest.mark.usefixtures("tmp_git_project") +def test_scm_provider_default_without_commits_and_tags(config: BaseConfig): + config.settings["version_provider"] = "scm" + + provider = get_provider(config) + assert isinstance(provider, ScmProvider) + assert provider.get_version() == "0.0.0" diff --git a/tests/utils.py b/tests/utils.py index 5dcf6722e9..2efe13fa9e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -17,7 +17,7 @@ def create_file_and_commit(message: str, filename: Optional[str] = None): if not filename: filename = str(uuid.uuid4()) - Path(f"./{filename}").touch() + Path(filename).touch() c = cmd.run("git add .") if c.return_code != 0: raise exceptions.CommitError(c.err) @@ -26,6 +26,31 @@ def create_file_and_commit(message: str, filename: Optional[str] = None): raise exceptions.CommitError(c.err) +def create_branch(name: str): + c = cmd.run(f"git branch {name}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def switch_branch(branch: str): + c = cmd.run(f"git switch {branch}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def merge_branch(branch: str): + c = cmd.run(f"git merge {branch}") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + + +def get_current_branch() -> str: + c = cmd.run("git rev-parse --abbrev-ref HEAD") + if c.return_code != 0: + raise exceptions.GitCommandError(c.err) + return c.out + + def create_tag(tag: str): c = git.tag(tag) if c.return_code != 0: