From 194345d056128e9022d3a738da88a698f09a692e Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Thu, 22 Oct 2020 22:43:08 -0400 Subject: [PATCH 01/16] Create semver package Split up changelog to make more sense. Incorporate suggestions and increase coverage to 100% in non-deprecated parts. Bump the dev part Co-authored-by: Tom Schraitle Run docformatter Fix path for docformatter Add stubby setup.py file for compatibility with python 3.6 Run black Deprecate cli functions imported from root Revert to `pysemver` as console script. Refactor __main__.py * add type hints for correctness * run black Refactor and integrate suggestion from @tomschr * Create :file:`src/semver/cli.py` for all CLI methods * Create :file:`src/semver/_deprecated.py` for the ``deprecated`` decorator and other deprecated functions * Create :file:`src/semver/__main__.py` to allow calling the CLI using :command:`python -m semver` * Create :file:`src/semver/_types.py` to hold type aliases * Create :file:`src/semver/version.py` to hold the :class:`VersionInfo` class and its utility functions * Create :file:`src/semver/__about__.py` for all the metadata variables Adapted infrastructure code to the new project layout. * Replace :file:`setup.py` with :file:`setup.cfg` because the :file:`setup.cfg` is easier to use * Adapt documentation code snippets where needed * Adapt tests * Changed the ``deprecated`` to hardcode the ``semver`` package name in the warning. Change path for docformatter and run it. Remove pyi inclusion from black sine we aren't using them Clean up after messy rebase Run black --- changelog.d/169.deprecation.rst | 1 + changelog.d/169.feature.rst | 11 + changelog.d/169.trivial.rst | 8 + docs/conf.py | 2 +- docs/usage.rst | 2 +- pyproject.toml | 1 - setup.cfg | 59 ++- setup.py | 75 +--- src/semver/__about__.py | 8 + src/semver/__init__.py | 33 ++ src/semver/__main__.py | 23 + src/semver/_deprecated.py | 378 ++++++++++++++++ src/semver/_types.py | 8 + src/semver/cli.py | 162 +++++++ semver.py => src/semver/version.py | 691 +++-------------------------- tests/test_compare.py | 3 +- tests/test_deprecated_functions.py | 38 +- tests/test_pysemver-cli.py | 19 +- tests/test_semver.py | 5 + tests/test_typeerror-274.py | 7 +- tox.ini | 6 +- 21 files changed, 808 insertions(+), 732 deletions(-) create mode 100644 changelog.d/169.deprecation.rst create mode 100644 changelog.d/169.feature.rst create mode 100644 changelog.d/169.trivial.rst mode change 100755 => 100644 setup.py create mode 100644 src/semver/__about__.py create mode 100644 src/semver/__init__.py create mode 100644 src/semver/__main__.py create mode 100644 src/semver/_deprecated.py create mode 100644 src/semver/_types.py create mode 100644 src/semver/cli.py rename semver.py => src/semver/version.py (53%) diff --git a/changelog.d/169.deprecation.rst b/changelog.d/169.deprecation.rst new file mode 100644 index 00000000..9ce5ef6b --- /dev/null +++ b/changelog.d/169.deprecation.rst @@ -0,0 +1 @@ +Deprecate CLI functions not imported from ``semver.cli``. \ No newline at end of file diff --git a/changelog.d/169.feature.rst b/changelog.d/169.feature.rst new file mode 100644 index 00000000..4bfe0fcb --- /dev/null +++ b/changelog.d/169.feature.rst @@ -0,0 +1,11 @@ +Create semver package and split code among different modules in the packages. + +* Remove :file:`semver.py` +* Create :file:`src/semver/__init__.py` +* Create :file:`src/semver/cli.py` for all CLI methods +* Create :file:`src/semver/_deprecated.py` for the ``deprecated`` decorator and other deprecated functions +* Create :file:`src/semver/__main__.py` to allow calling the CLI using :command:`python -m semver` +* Create :file:`src/semver/_types.py` to hold type aliases +* Create :file:`src/semver/version.py` to hold the :class:`VersionInfo` class and its utility functions +* Create :file:`src/semver/__about__.py` for all the metadata variables + diff --git a/changelog.d/169.trivial.rst b/changelog.d/169.trivial.rst new file mode 100644 index 00000000..536e2b88 --- /dev/null +++ b/changelog.d/169.trivial.rst @@ -0,0 +1,8 @@ +Adapted infrastructure code to the new project layout. + +* Replace :file:`setup.py` with :file:`setup.cfg` because the :file:`setup.cfg` is easier to use +* Adapt documentation code snippets where needed +* Adapt tests +* Changed the ``deprecated`` to hardcode the ``semver`` package name in the warning. + +Increase coverage to 100% for all non-deprecated APIs \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 3653ecce..71738daa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ import os import sys -sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath("../src/")) from semver import __version__ # noqa: E402 diff --git a/docs/usage.rst b/docs/usage.rst index 4e2b6f92..3ceb2bf3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -26,7 +26,7 @@ Getting the Version of semver To know the version of semver itself, use the following construct:: >>> semver.__version__ - '3.0.0-dev.1' + '3.0.0-dev.2' Creating a Version diff --git a/pyproject.toml b/pyproject.toml index b3ee70a3..1b406dac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 target-version = ['py36', 'py37', 'py38'] -include = '\.pyi?$' # diff = true exclude = ''' ( diff --git a/setup.cfg b/setup.cfg index 5abd4bbb..52b5d3e5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,46 @@ +[metadata] +name = semver +version = attr: semver.__about__.__version__ +description = attr: semver.__about__.__description__ +long_description = file: README.rst +author = attr: semver.__about__.__author__ +author_email = attr: semver.__about__.__author_email__ +maintainer = attr: semver.__about__.__maintainer__ +maintainer_email = attr: semver.__about__.__maintainer_email__ +url = https://github.com/python-semver/python-semver +download_url = https://github.com/python-semver/python-semver/downloads +project_urls = + Documentation = https://python-semver.rtfd.io + Releases = https://github.com/python-semver/python-semver/releases + Bug Tracker = https://github.com/python-semver/python-semver/issues +classifiers = + Environment :: Web Environment + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Software Development :: Libraries :: Python Modules +license = BSD + +[options] +package_dir = + =src +packages = find: +python_requires = >=3.6.* +include_package_data = True + +[options.entry_points] +console_scripts = + pysemver = semver.cli:main + +[options.packages.find] +where = src + [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ testpaths = tests docs @@ -15,13 +58,14 @@ addopts = max-line-length = 88 ignore = F821,W503 exclude = - .env, - venv, - .eggs, - .tox, - .git, - __pycache__, - build, + src/semver/__init__.py + .env + venv + .eggs + .tox + .git + __pycache__ + build dist docs conftest.py @@ -32,6 +76,7 @@ count = False max-line-length = 88 statistics = True exclude = + src/semver/__init__.py .env, .eggs, .tox, diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 57ee4b26..88990ad8 --- a/setup.py +++ b/setup.py @@ -1,75 +1,4 @@ #!/usr/bin/env python3 -# import semver as package -from os.path import dirname, join -from setuptools import setup -import re +import setuptools - -VERSION_MATCH = re.compile(r"__version__ = ['\"]([^'\"]*)['\"]", re.M) - - -def read_file(filename): - """ - Read RST file and return content - - :param filename: the RST file - :return: content of the RST file - """ - with open(join(dirname(__file__), filename)) as f: - return f.read() - - -def find_meta(meta): - """ - Extract __*meta*__ from META_FILE. - """ - meta_match = re.search( - r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M - ) - if meta_match: - return meta_match.group(1) - raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta)) - - -NAME = "semver" -META_FILE = read_file("semver.py") - - -# ----------------------------------------------------------------------------- -setup( - name=NAME, - version=find_meta("version"), - description=find_meta("description").strip(), - long_description=read_file("README.rst"), - long_description_content_type="text/x-rst", - author=find_meta("author"), - author_email=find_meta("author_email"), - url="https://github.com/python-semver/python-semver", - download_url="https://github.com/python-semver/python-semver/downloads", - project_urls={ - "Documentation": "https://python-semver.rtfd.io", - "Releases": "https://github.com/python-semver/python-semver/releases", - "Bug Tracker": "https://github.com/python-semver/python-semver/issues", - }, - py_modules=[NAME], - include_package_data=True, - license="BSD", - classifiers=[ - # See https://pypi.org/pypi?%3Aaction=list_classifiers - "Environment :: Web Environment", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - # "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - python_requires=">=3.6.*", - tests_require=["tox", "virtualenv", "wheel"], - entry_points={"console_scripts": ["pysemver = semver:main"]}, -) +setuptools.setup() # For compatibility with python 3.6 diff --git a/src/semver/__about__.py b/src/semver/__about__.py new file mode 100644 index 00000000..5e7a3537 --- /dev/null +++ b/src/semver/__about__.py @@ -0,0 +1,8 @@ +__version__ = "3.0.0-dev.2" +__author__ = "Kostiantyn Rybnikov" +__author_email__ = "k-bx@k-bx.com" +__maintainer__ = ["Sebastien Celles", "Tom Schraitle"] +__maintainer_email__ = "s.celles@gmail.com" +__description__ = "Python helper for Semantic Versioning (http://semver.org)" + +SEMVER_SPEC_VERSION = "2.0.0" diff --git a/src/semver/__init__.py b/src/semver/__init__.py new file mode 100644 index 00000000..501a9586 --- /dev/null +++ b/src/semver/__init__.py @@ -0,0 +1,33 @@ +from ._deprecated import ( + bump_build, + bump_major, + bump_minor, + bump_patch, + bump_prerelease, + compare, + finalize_version, + format_version, + match, + max_ver, + min_ver, + parse, + parse_version_info, + replace, + cmd_bump, + cmd_compare, + cmd_nextver, + cmd_check, + createparser, + process, + main, +) +from .version import VersionInfo +from .__about__ import ( + __version__, + __author__, + __maintainer__, + __author_email__, + __description__, + __maintainer_email__, + SEMVER_SPEC_VERSION, +) diff --git a/src/semver/__main__.py b/src/semver/__main__.py new file mode 100644 index 00000000..0e0648a9 --- /dev/null +++ b/src/semver/__main__.py @@ -0,0 +1,23 @@ +""" +Module to support call with :file:`__main__.py`. Used to support the following +call: + +$ python3 -m semver ... +""" +import os.path +import sys +from typing import List + +from semver import cli + + +def main(cliargs: List[str] = None) -> int: + if __package__ == "": + path = os.path.dirname(os.path.dirname(__file__)) + sys.path[0:0] = [path] + + return cli.main(cliargs) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py new file mode 100644 index 00000000..56597e6c --- /dev/null +++ b/src/semver/_deprecated.py @@ -0,0 +1,378 @@ +import inspect +import warnings +from functools import partial, wraps +from types import FrameType +from typing import Type, Union, Callable, cast + +from . import cli +from .version import VersionInfo +from ._types import F, String + + +def deprecated( + func: F = None, + replace: str = None, + version: str = None, + category: Type[Warning] = DeprecationWarning, +) -> Union[Callable[..., F], partial]: + """ + Decorates a function to output a deprecation warning. + + :param func: the function to decorate + :param replace: the function to replace (use the full qualified + name like ``semver.VersionInfo.bump_major``. + :param version: the first version when this function was deprecated. + :param category: allow you to specify the deprecation warning class + of your choice. By default, it's :class:`DeprecationWarning`, but + you can choose :class:`PendingDeprecationWarning` or a custom class. + :return: decorated function which is marked as deprecated + """ + + if func is None: + return partial(deprecated, replace=replace, version=version, category=category) + + @wraps(func) + def wrapper(*args, **kwargs) -> Callable[..., F]: + msg_list = ["Function 'semver.{f}' is deprecated."] + + if version: + msg_list.append("Deprecated since version {v}. ") + msg_list.append("This function will be removed in semver 3.") + if replace: + msg_list.append("Use {r!r} instead.") + else: + msg_list.append("Use the respective 'semver.VersionInfo.{r}' instead.") + + f = cast(F, func).__qualname__ + r = replace or f + + frame = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back) + + msg = " ".join(msg_list) + warnings.warn_explicit( + msg.format(f=f, r=r, v=version), + category=category, + filename=inspect.getfile(frame.f_code), + lineno=frame.f_lineno, + ) + # As recommended in the Python documentation + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + # better remove the interpreter stack: + del frame + return func(*args, **kwargs) # type: ignore + + return wrapper + + +@deprecated(version="2.10.0") +def parse(version): + """ + Parse version to major, minor, patch, pre-release, build parts. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.parse` instead. + + :param version: version string + :return: dictionary with the keys 'build', 'major', 'minor', 'patch', + and 'prerelease'. The prerelease or build keys can be None + if not provided + :rtype: dict + + >>> ver = semver.parse('3.4.5-pre.2+build.4') + >>> ver['major'] + 3 + >>> ver['minor'] + 4 + >>> ver['patch'] + 5 + >>> ver['prerelease'] + 'pre.2' + >>> ver['build'] + 'build.4' + """ + return VersionInfo.parse(version).to_dict() + + +@deprecated(replace="semver.VersionInfo.parse", version="2.10.0") +def parse_version_info(version): + """ + Parse version string to a VersionInfo instance. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.parse` instead. + .. versionadded:: 2.7.2 + Added :func:`semver.parse_version_info` + :param version: version string + :return: a :class:`VersionInfo` instance + >>> version_info = semver.VersionInfo.parse("3.4.5-pre.2+build.4") + >>> version_info.major + 3 + >>> version_info.minor + 4 + >>> version_info.patch + 5 + >>> version_info.prerelease + 'pre.2' + >>> version_info.build + 'build.4' + """ + return VersionInfo.parse(version) + + +@deprecated(version="2.10.0") +def compare(ver1, ver2): + """ + Compare two versions strings. + + :param ver1: version string 1 + :param ver2: version string 2 + :return: The return value is negative if ver1 < ver2, + zero if ver1 == ver2 and strictly positive if ver1 > ver2 + :rtype: int + + >>> semver.compare("1.0.0", "2.0.0") + -1 + >>> semver.compare("2.0.0", "1.0.0") + 1 + >>> semver.compare("2.0.0", "2.0.0") + 0 + """ + v1 = VersionInfo.parse(ver1) + return v1.compare(ver2) + + +@deprecated(version="2.10.0") +def match(version, match_expr): + """ + Compare two versions strings through a comparison. + + :param str version: a version string + :param str match_expr: operator and version; valid operators are + < smaller than + > greater than + >= greator or equal than + <= smaller or equal than + == equal + != not equal + :return: True if the expression matches the version, otherwise False + :rtype: bool + + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False + """ + ver = VersionInfo.parse(version) + return ver.match(match_expr) + + +@deprecated(replace="max", version="2.10.2") +def max_ver(ver1, ver2): + """ + Returns the greater version of two versions strings. + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the greater version of the two + :rtype: :class:`VersionInfo` + + >>> semver.max_ver("1.0.0", "2.0.0") + '2.0.0' + """ + if isinstance(ver1, String.__args__): # type: ignore + ver1 = VersionInfo.parse(ver1) + elif not isinstance(ver1, VersionInfo): + raise TypeError() + cmp_res = ver1.compare(ver2) + if cmp_res >= 0: + return str(ver1) + else: + return ver2 + + +@deprecated(replace="min", version="2.10.2") +def min_ver(ver1, ver2): + """ + Returns the smaller version of two versions strings. + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the smaller version of the two + :rtype: :class:`VersionInfo` + + >>> semver.min_ver("1.0.0", "2.0.0") + '1.0.0' + """ + ver1 = VersionInfo.parse(ver1) + cmp_res = ver1.compare(ver2) + if cmp_res <= 0: + return str(ver1) + else: + return ver2 + + +@deprecated(replace="str(versionobject)", version="2.10.0") +def format_version(major, minor, patch, prerelease=None, build=None): + """ + Format a version string according to the Semantic Versioning specification. + + .. deprecated:: 2.10.0 + Use ``str(VersionInfo(VERSION)`` instead. + + :param int major: the required major part of a version + :param int minor: the required minor part of a version + :param int patch: the required patch part of a version + :param str prerelease: the optional prerelease part of a version + :param str build: the optional build part of a version + :return: the formatted string + :rtype: str + + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + """ + return str(VersionInfo(major, minor, patch, prerelease, build)) + + +@deprecated(version="2.10.0") +def bump_major(version): + """ + Raise the major part of the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.bump_major` instead. + + :param: version string + :return: the raised version string + :rtype: str + + >>> semver.bump_major("3.4.5") + '4.0.0' + """ + return str(VersionInfo.parse(version).bump_major()) + + +@deprecated(version="2.10.0") +def bump_minor(version): + """ + Raise the minor part of the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.bump_minor` instead. + + :param: version string + :return: the raised version string + :rtype: str + + >>> semver.bump_minor("3.4.5") + '3.5.0' + """ + return str(VersionInfo.parse(version).bump_minor()) + + +@deprecated(version="2.10.0") +def bump_patch(version): + """ + Raise the patch part of the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.bump_patch` instead. + + :param: version string + :return: the raised version string + :rtype: str + + >>> semver.bump_patch("3.4.5") + '3.4.6' + """ + return str(VersionInfo.parse(version).bump_patch()) + + +@deprecated(version="2.10.0") +def bump_prerelease(version, token="rc"): + """ + Raise the prerelease part of the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.bump_prerelease` instead. + + :param version: version string + :param token: defaults to 'rc' + :return: the raised version string + :rtype: str + + >>> semver.bump_prerelease('3.4.5', 'dev') + '3.4.5-dev.1' + """ + return str(VersionInfo.parse(version).bump_prerelease(token)) + + +@deprecated(version="2.10.0") +def bump_build(version, token="build"): + """ + Raise the build part of the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.bump_build` instead. + + :param version: version string + :param token: defaults to 'build' + :return: the raised version string + :rtype: str + + >>> semver.bump_build('3.4.5-rc.1+build.9') + '3.4.5-rc.1+build.10' + """ + return str(VersionInfo.parse(version).bump_build(token)) + + +@deprecated(version="2.10.0") +def finalize_version(version): + """ + Remove any prerelease and build metadata from the version string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.finalize_version` instead. + + .. versionadded:: 2.7.9 + Added :func:`finalize_version` + + :param version: version string + :return: the finalized version string + :rtype: str + + >>> semver.finalize_version('1.2.3-rc.5') + '1.2.3' + """ + verinfo = VersionInfo.parse(version) + return str(verinfo.finalize_version()) + + +@deprecated(version="2.10.0") +def replace(version, **parts): + """ + Replace one or more parts of a version and return the new string. + + .. deprecated:: 2.10.0 + Use :func:`semver.VersionInfo.replace` instead. + .. versionadded:: 2.9.0 + Added :func:`replace` + :param version: the version string to replace + :param parts: the parts to be updated. Valid keys are: + ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` + :return: the replaced version string + :raises TypeError: if ``parts`` contains invalid keys + >>> import semver + >>> semver.replace("1.2.3", major=2, patch=10) + '2.2.10' + """ + return str(VersionInfo.parse(version).replace(**parts)) + + +# CLI +cmd_bump = deprecated(cli.cmd_bump, "semver.cli.cmd_bump", "3.0.0") +cmd_check = deprecated(cli.cmd_check, "semver.cli.cmd_check", "3.0.0") +cmd_compare = deprecated(cli.cmd_compare, "semver.cli.cmd_compare", "3.0.0") +cmd_nextver = deprecated(cli.cmd_nextver, "semver.cli.cmd_nextver", "3.0.0") +createparser = deprecated(cli.createparser, "semver.cli.createparser", "3.0.0") +process = deprecated(cli.process, "semver.cli.process", "3.0.0") +main = deprecated(cli.main, "semver.cli.main", "3.0.0") diff --git a/src/semver/_types.py b/src/semver/_types.py new file mode 100644 index 00000000..823c7349 --- /dev/null +++ b/src/semver/_types.py @@ -0,0 +1,8 @@ +from typing import Union, Optional, Tuple, Dict, Iterable, Callable, TypeVar + +VersionPart = Union[int, Optional[str]] +VersionTuple = Tuple[int, int, int, Optional[str], Optional[str]] +VersionDict = Dict[str, VersionPart] +VersionIterator = Iterable[VersionPart] +String = Union[str, bytes] +F = TypeVar("F", bound=Callable) diff --git a/src/semver/cli.py b/src/semver/cli.py new file mode 100644 index 00000000..f0d8e4ac --- /dev/null +++ b/src/semver/cli.py @@ -0,0 +1,162 @@ +import argparse +import sys +from typing import cast, List + +from .version import VersionInfo +from .__about__ import __version__ + + +def cmd_bump(args: argparse.Namespace) -> str: + """ + Subcommand: Bumps a version. + + Synopsis: bump + can be major, minor, patch, prerelease, or build + + :param args: The parsed arguments + :return: the new, bumped version + """ + maptable = { + "major": "bump_major", + "minor": "bump_minor", + "patch": "bump_patch", + "prerelease": "bump_prerelease", + "build": "bump_build", + } + if args.bump is None: + # When bump is called without arguments, + # print the help and exit + args.parser.parse_args(["bump", "-h"]) + + ver = VersionInfo.parse(args.version) + # get the respective method and call it + func = getattr(ver, maptable[cast(str, args.bump)]) + return str(func()) + + +def cmd_check(args: argparse.Namespace) -> None: + """ + Subcommand: Checks if a string is a valid semver version. + + Synopsis: check + + :param args: The parsed arguments + """ + if VersionInfo.isvalid(args.version): + return None + raise ValueError("Invalid version %r" % args.version) + + +def cmd_compare(args: argparse.Namespace) -> str: + """ + Subcommand: Compare two versions + + Synopsis: compare + + :param args: The parsed arguments + """ + ver1 = VersionInfo.parse(args.version1) + return str(ver1.compare(args.version2)) + + +def cmd_nextver(args: argparse.Namespace) -> str: + """ + Subcommand: Determines the next version, taking prereleases into account. + + Synopsis: nextver + + :param args: The parsed arguments + """ + version = VersionInfo.parse(args.version) + return str(version.next_version(args.part)) + + +def createparser() -> argparse.ArgumentParser: + """ + Create an :class:`argparse.ArgumentParser` instance. + + :return: parser instance + """ + parser = argparse.ArgumentParser(prog=__package__, description=__doc__) + + parser.add_argument( + "--version", action="version", version="%(prog)s " + __version__ + ) + + s = parser.add_subparsers() + # create compare subcommand + parser_compare = s.add_parser("compare", help="Compare two versions") + parser_compare.set_defaults(func=cmd_compare) + parser_compare.add_argument("version1", help="First version") + parser_compare.add_argument("version2", help="Second version") + + # create bump subcommand + parser_bump = s.add_parser("bump", help="Bumps a version") + parser_bump.set_defaults(func=cmd_bump) + sb = parser_bump.add_subparsers(title="Bump commands", dest="bump") + + # Create subparsers for the bump subparser: + for p in ( + sb.add_parser("major", help="Bump the major part of the version"), + sb.add_parser("minor", help="Bump the minor part of the version"), + sb.add_parser("patch", help="Bump the patch part of the version"), + sb.add_parser("prerelease", help="Bump the prerelease part of the version"), + sb.add_parser("build", help="Bump the build part of the version"), + ): + p.add_argument("version", help="Version to raise") + + # Create the check subcommand + parser_check = s.add_parser( + "check", help="Checks if a string is a valid semver version" + ) + parser_check.set_defaults(func=cmd_check) + parser_check.add_argument("version", help="Version to check") + + # Create the nextver subcommand + parser_nextver = s.add_parser( + "nextver", help="Determines the next version, taking prereleases into account." + ) + parser_nextver.set_defaults(func=cmd_nextver) + parser_nextver.add_argument("version", help="Version to raise") + parser_nextver.add_argument( + "part", help="One of 'major', 'minor', 'patch', or 'prerelease'" + ) + return parser + + +def process(args: argparse.Namespace) -> str: + """ + Process the input from the CLI. + + :param args: The parsed arguments + :param parser: the parser instance + :return: result of the selected action + """ + if not hasattr(args, "func"): + args.parser.print_help() + raise SystemExit() + + # Call the respective function object: + return args.func(args) + + +def main(cliargs: List[str] = None) -> int: + """ + Entry point for the application script. + + :param list cliargs: Arguments to parse or None (=use :class:`sys.argv`) + :return: error code + """ + try: + parser = createparser() + args = parser.parse_args(args=cliargs) + # Save parser instance: + args.parser = parser + result = process(args) + if result is not None: + print(result) + return 0 + + except (ValueError, TypeError) as err: + print("ERROR", err, file=sys.stderr) + return 2 diff --git a/semver.py b/src/semver/version.py similarity index 53% rename from semver.py rename to src/semver/version.py index 09bca561..d0643641 100644 --- a/semver.py +++ b/src/semver/version.py @@ -1,86 +1,33 @@ -"""Python helper for Semantic Versioning (http://semver.org)""" -from __future__ import print_function - -import argparse import collections -import inspect import re -import sys -import warnings -from functools import partial, wraps -from types import FrameType +from functools import wraps from typing import ( Any, - Callable, - Collection, Dict, Iterable, - Iterator, - List, Optional, SupportsInt, Tuple, - TypeVar, Union, cast, + Callable, + Collection, ) -__version__ = "3.0.0-dev.1" -__author__ = "Kostiantyn Rybnikov" -__author_email__ = "k-bx@k-bx.com" -__maintainer__ = ["Sebastien Celles", "Tom Schraitle"] -__maintainer_email__ = "s.celles@gmail.com" -__description__ = "Python helper for Semantic Versioning (http://semver.org)" - -#: Our public interface -__all__ = ( - # - # Module level function: - "bump_build", - "bump_major", - "bump_minor", - "bump_patch", - "bump_prerelease", - "compare", - "deprecated", - "finalize_version", - "format_version", - "match", - "max_ver", - "min_ver", - "parse", - "parse_version_info", - "replace", - # - # CLI interface - "cmd_bump", - "cmd_check", - "cmd_compare", - "createparser", - "main", - "process", - # - # Constants and classes - "SEMVER_SPEC_VERSION", - "VersionInfo", +from ._types import ( + VersionTuple, + VersionDict, + VersionIterator, + String, + VersionPart, ) - -#: Contains the implemented semver.org version of the spec -SEMVER_SPEC_VERSION = "2.0.0" - - -# Types -VersionPart = Union[int, Optional[str]] +# These types are required here because of circular imports Comparable = Union["VersionInfo", Dict[str, VersionPart], Collection[VersionPart], str] Comparator = Callable[["VersionInfo", Comparable], bool] -String = Union[str, bytes] -VersionTuple = Tuple[int, int, int, Optional[str], Optional[str]] -VersionDict = Dict[str, VersionPart] -VersionIterator = Iterator[VersionPart] -def cmp(a, b): +def cmp(a, b): # TODO: type hints """Return negative if ab.""" return (a > b) - (a < b) @@ -114,92 +61,6 @@ def ensure_str(s: String, encoding="utf-8", errors="strict") -> str: return s -F = TypeVar("F", bound=Callable) - - -def deprecated( - func: F = None, - replace: str = None, - version: str = None, - category=DeprecationWarning, -) -> Union[Callable[..., F], partial]: - """ - Decorates a function to output a deprecation warning. - - :param func: the function to decorate - :param replace: the function to replace (use the full qualified - name like ``semver.VersionInfo.bump_major``. - :param version: the first version when this function was deprecated. - :param category: allow you to specify the deprecation warning class - of your choice. By default, it's :class:`DeprecationWarning`, but - you can choose :class:`PendingDeprecationWarning` or a custom class. - :return: decorated function which is marked as deprecated - """ - - if func is None: - return partial(deprecated, replace=replace, version=version, category=category) - - @wraps(func) - def wrapper(*args, **kwargs) -> Callable[..., F]: - msg_list = ["Function '{m}.{f}' is deprecated."] - - if version: - msg_list.append("Deprecated since version {v}. ") - msg_list.append("This function will be removed in semver 3.") - if replace: - msg_list.append("Use {r!r} instead.") - else: - msg_list.append("Use the respective 'semver.VersionInfo.{r}' instead.") - - f = cast(F, func).__qualname__ - r = replace or f - - frame = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back) - - msg = " ".join(msg_list) - warnings.warn_explicit( - msg.format(m=func.__module__, f=f, r=r, v=version), - category=category, - filename=inspect.getfile(frame.f_code), - lineno=frame.f_lineno, - ) - # As recommended in the Python documentation - # https://docs.python.org/3/library/inspect.html#the-interpreter-stack - # better remove the interpreter stack: - del frame - return func(*args, **kwargs) # type: ignore - - return wrapper - - -@deprecated(version="2.10.0") -def parse(version): - """ - Parse version to major, minor, patch, pre-release, build parts. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.parse` instead. - - :param version: version string - :return: dictionary with the keys 'build', 'major', 'minor', 'patch', - and 'prerelease'. The prerelease or build keys can be None - if not provided - - >>> ver = semver.parse('3.4.5-pre.2+build.4') - >>> ver['major'] - 3 - >>> ver['minor'] - 4 - >>> ver['patch'] - 5 - >>> ver['prerelease'] - 'pre.2' - >>> ver['build'] - 'build.4' - """ - return VersionInfo.parse(version).to_dict() - - def comparator(operator: Comparator) -> Comparator: """Wrap a VersionInfo binary op method in a type-check.""" @@ -221,6 +82,33 @@ def wrapper(self: "VersionInfo", other: Comparable) -> bool: return wrapper +def _nat_cmp(a, b): # TODO: type hints + def convert(text): + return int(text) if re.match("^[0-9]+$", text) else text + + def split_key(key): + return [convert(c) for c in key.split(".")] + + def cmp_prerelease_tag(a, b): + if isinstance(a, int) and isinstance(b, int): + return cmp(a, b) + elif isinstance(a, int): + return -1 + elif isinstance(b, int): + return 1 + else: + return cmp(a, b) + + a, b = a or "", b or "" + a_parts, b_parts = split_key(a), split_key(b) + for sub_a, sub_b in zip(a_parts, b_parts): + cmp_result = cmp_prerelease_tag(sub_a, sub_b) + if cmp_result != 0: + return cmp_result + else: + return cmp(len(a), len(b)) + + class VersionInfo: """ A semver compatible version class. @@ -395,7 +283,8 @@ def bump_major(self) -> "VersionInfo": :return: new object with the raised major part - >>> ver = semver.VersionInfo.parse("3.4.5") + + >>> ver = semver.parse("3.4.5") >>> ver.bump_major() VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) """ @@ -409,7 +298,8 @@ def bump_minor(self) -> "VersionInfo": :return: new object with the raised minor part - >>> ver = semver.VersionInfo.parse("3.4.5") + + >>> ver = semver.parse("3.4.5") >>> ver.bump_minor() VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) """ @@ -421,9 +311,10 @@ def bump_patch(self) -> "VersionInfo": Raise the patch part of the version, return a new object but leave self untouched. - :return: new object with the raised patch part + :return: new object with the raised patch part - >>> ver = semver.VersionInfo.parse("3.4.5") + + >>> ver = semver.parse("3.4.5") >>> ver.bump_patch() VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None) """ @@ -438,7 +329,7 @@ def bump_prerelease(self, token: str = "rc") -> "VersionInfo": :param token: defaults to 'rc' :return: new object with the raised prerelease part - >>> ver = semver.VersionInfo.parse("3.4.5-rc.1") + >>> ver = semver.parse("3.4.5") >>> ver.bump_prerelease() VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \ build=None) @@ -455,7 +346,7 @@ def bump_build(self, token: str = "build") -> "VersionInfo": :param token: defaults to 'build' :return: new object with the raised build part - >>> ver = semver.VersionInfo.parse("3.4.5-rc.1+build.9") + >>> ver = semver.parse("3.4.5-rc.1+build.9") >>> ver.bump_build() VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \ build='build.10') @@ -472,13 +363,14 @@ def compare(self, other: Comparable) -> int: :return: The return value is negative if ver1 < ver2, zero if ver1 == ver2 and strictly positive if ver1 > ver2 - >>> semver.VersionInfo.parse("1.0.0").compare("2.0.0") + + >>> semver.compare("2.0.0") -1 - >>> semver.VersionInfo.parse("2.0.0").compare("1.0.0") + >>> semver.compare("1.0.0") 1 - >>> semver.VersionInfo.parse("2.0.0").compare("2.0.0") + >>> semver.compare("2.0.0") 0 - >>> semver.VersionInfo.parse("2.0.0").compare(dict(major=2, minor=0, patch=0)) + >>> semver.compare(dict(major=2, minor=0, patch=0)) 0 """ cls = type(self) @@ -521,10 +413,10 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "VersionInfo" This function is taking prereleases into account. The "major", "minor", and "patch" raises the respective parts like the ``bump_*`` functions. The real difference is using the - "preprelease" part. It gives you the next patch version of the prerelease, - for example: + "preprelease" part. It gives you the next patch version of the + prerelease, for example: - >>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease")) + >>> str(semver.parse("0.1.4").next_version("prerelease")) '0.1.5-rc.1' :param part: One of "major", "minor", "patch", or "prerelease" @@ -587,17 +479,14 @@ def __getitem__( self, index: Union[int, slice] ) -> Union[int, Optional[str], Tuple[Union[int, str], ...]]: """ - self.__getitem__(index) <==> self[index] - - Implement getitem. If the part requested is undefined, or a part of the - range requested is undefined, it will throw an index error. - Negative indices are not supported + self.__getitem__(index) <==> self[index] Implement getitem. If the part + requested is undefined, or a part of the range requested is undefined, + it will throw an index error. Negative indices are not supported. :param Union[int, slice] index: a positive integer indicating the offset or a :func:`slice` object :raises IndexError: if index is beyond the range or a part is None :return: the requested part of the version at position index - >>> ver = semver.VersionInfo.parse("3.4.5") >>> ver[0], ver[1], ver[2] (3, 4, 5) @@ -642,9 +531,7 @@ def __hash__(self) -> int: def finalize_version(self) -> "VersionInfo": """ Remove any prerelease and build metadata from the version. - :return: a new instance with the finalized version string - >>> str(semver.VersionInfo.parse('1.2.3-rc.5').finalize_version()) '1.2.3' """ @@ -663,7 +550,6 @@ def match(self, match_expr: str) -> bool: == equal != not equal :return: True if the expression matches the version, otherwise False - >>> semver.VersionInfo.parse("2.0.0").match(">=1.0.0") True >>> semver.VersionInfo.parse("1.0.0").match(">1.0.0") @@ -705,11 +591,9 @@ def parse(cls, version: String) -> "VersionInfo": .. versionchanged:: 2.11.0 Changed method from static to classmethod to allow subclasses. - :param version: version string :return: a :class:`VersionInfo` instance :raises ValueError: if version is invalid - >>> semver.VersionInfo.parse('3.4.5-pre.2+build.4') VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') @@ -765,462 +649,3 @@ def isvalid(cls, version: str) -> bool: return True except ValueError: return False - - -@deprecated(replace="semver.VersionInfo.parse", version="2.10.0") -def parse_version_info(version): - """ - Parse version string to a VersionInfo instance. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.parse` instead. - - .. versionadded:: 2.7.2 - Added :func:`semver.parse_version_info` - - :param version: version string - :return: a :class:`VersionInfo` instance - - >>> version_info = semver.VersionInfo.parse("3.4.5-pre.2+build.4") - >>> version_info.major - 3 - >>> version_info.minor - 4 - >>> version_info.patch - 5 - >>> version_info.prerelease - 'pre.2' - >>> version_info.build - 'build.4' - """ - return VersionInfo.parse(version) - - -def _nat_cmp(a: Optional[str], b: Optional[str]) -> int: - def convert(text: str) -> Union[int, str]: - return int(text) if re.match("^[0-9]+$", text) else text # type: ignore - - def split_key(key: str) -> List[Union[int, str]]: - return [convert(c) for c in key.split(".")] - - def cmp_prerelease_tag(a: Union[int, str], b: Union[int, str]) -> int: - if isinstance(a, int) and isinstance(b, int): - return cmp(a, b) - elif isinstance(a, int): - return -1 - elif isinstance(b, int): - return 1 - else: - return cmp(a, b) - - a, b = a or "", b or "" - a_parts, b_parts = split_key(a), split_key(b) - for sub_a, sub_b in zip(a_parts, b_parts): - cmp_result = cmp_prerelease_tag(sub_a, sub_b) - if cmp_result != 0: - return cmp_result - else: - return cmp(len(a), len(b)) - - -@deprecated(version="2.10.0") -def compare(ver1, ver2): - """ - Compare two versions strings. - - :param ver1: version string 1 - :param ver2: version string 2 - :return: The return value is negative if ver1 < ver2, - zero if ver1 == ver2 and strictly positive if ver1 > ver2 - - >>> semver.compare("1.0.0", "2.0.0") - -1 - >>> semver.compare("2.0.0", "1.0.0") - 1 - >>> semver.compare("2.0.0", "2.0.0") - 0 - """ - v1 = VersionInfo.parse(ver1) - return v1.compare(ver2) - - -@deprecated(version="2.10.0") -def match(version, match_expr): - """ - Compare two versions strings through a comparison. - - :param version: a version string - :param match_expr: operator and version; valid operators are - < smaller than - > greater than - >= greator or equal than - <= smaller or equal than - == equal - != not equal - :return: True if the expression matches the version, otherwise False - - >>> semver.match("2.0.0", ">=1.0.0") - True - >>> semver.match("1.0.0", ">1.0.0") - False - """ - ver = VersionInfo.parse(version) - return ver.match(match_expr) - - -@deprecated(replace="max", version="2.10.2") -def max_ver(ver1, ver2): - """ - Returns the greater version of two versions strings. - - :param ver1: version string 1 - :param ver2: version string 2 - :return: the greater version of the two - - >>> semver.max_ver("1.0.0", "2.0.0") - '2.0.0' - """ - if isinstance(ver1, String.__args__): - ver1 = VersionInfo.parse(ver1) - elif not isinstance(ver1, VersionInfo): - raise TypeError() - cmp_res = ver1.compare(ver2) - if cmp_res >= 0: - return str(ver1) - else: - return ver2 - - -@deprecated(replace="min", version="2.10.2") -def min_ver(ver1, ver2): - """ - Returns the smaller version of two versions strings. - - :param ver1: version string 1 - :param ver2: version string 2 - :return: the smaller version of the two - - >>> semver.min_ver("1.0.0", "2.0.0") - '1.0.0' - """ - ver1 = VersionInfo.parse(ver1) - cmp_res = ver1.compare(ver2) - if cmp_res <= 0: - return str(ver1) - else: - return ver2 - - -@deprecated(replace="str(versionobject)", version="2.10.0") -def format_version(major, minor, patch, prerelease=None, build=None): - """ - Format a version string according to the Semantic Versioning specification. - - .. deprecated:: 2.10.0 - Use ``str(VersionInfo(VERSION)`` instead. - - :param major: the required major part of a version - :param minor: the required minor part of a version - :param patch: the required patch part of a version - :param prerelease: the optional prerelease part of a version - :param build: the optional build part of a version - :return: the formatted string - - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - """ - return str(VersionInfo(major, minor, patch, prerelease, build)) - - -@deprecated(version="2.10.0") -def bump_major(version): - """ - Raise the major part of the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_major` instead. - - :param: version string - :return: the raised version string - - >>> semver.bump_major("3.4.5") - '4.0.0' - """ - return str(VersionInfo.parse(version).bump_major()) - - -@deprecated(version="2.10.0") -def bump_minor(version): - """ - Raise the minor part of the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_minor` instead. - - :param: version string - :return: the raised version string - - >>> semver.bump_minor("3.4.5") - '3.5.0' - """ - return str(VersionInfo.parse(version).bump_minor()) - - -@deprecated(version="2.10.0") -def bump_patch(version): - """ - Raise the patch part of the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_patch` instead. - - :param: version string - :return: the raised version string - - >>> semver.bump_patch("3.4.5") - '3.4.6' - """ - return str(VersionInfo.parse(version).bump_patch()) - - -@deprecated(version="2.10.0") -def bump_prerelease(version, token="rc"): - """ - Raise the prerelease part of the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_prerelease` instead. - - :param version: version string - :param token: defaults to 'rc' - :return: the raised version string - - >>> semver.bump_prerelease('3.4.5', 'dev') - '3.4.5-dev.1' - """ - return str(VersionInfo.parse(version).bump_prerelease(token)) - - -@deprecated(version="2.10.0") -def bump_build(version, token="build"): - """ - Raise the build part of the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_build` instead. - - :param version: version string - :param token: defaults to 'build' - :return: the raised version string - - >>> semver.bump_build('3.4.5-rc.1+build.9') - '3.4.5-rc.1+build.10' - """ - return str(VersionInfo.parse(version).bump_build(token)) - - -@deprecated(version="2.10.0") -def finalize_version(version): - """ - Remove any prerelease and build metadata from the version string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.finalize_version` instead. - - .. versionadded:: 2.7.9 - Added :func:`finalize_version` - - :param version: version string - :return: the finalized version string - - >>> semver.finalize_version('1.2.3-rc.5') - '1.2.3' - """ - verinfo = VersionInfo.parse(version) - return str(verinfo.finalize_version()) - - -@deprecated(version="2.10.0") -def replace(version, **parts): - """ - Replace one or more parts of a version and return the new string. - - .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.replace` instead. - - .. versionadded:: 2.9.0 - Added :func:`replace` - - :param version: the version string to replace - :param parts: the parts to be updated. Valid keys are: - ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` - :return: the replaced version string - :raises TypeError: if ``parts`` contains invalid keys - - >>> import semver - >>> semver.replace("1.2.3", major=2, patch=10) - '2.2.10' - """ - return str(VersionInfo.parse(version).replace(**parts)) - - -# ---- CLI -def cmd_bump(args: argparse.Namespace) -> str: - """ - Subcommand: Bumps a version. - - Synopsis: bump - can be major, minor, patch, prerelease, or build - - :param args: The parsed arguments - :return: the new, bumped version - """ - maptable = { - "major": "bump_major", - "minor": "bump_minor", - "patch": "bump_patch", - "prerelease": "bump_prerelease", - "build": "bump_build", - } - if args.bump is None: - # When bump is called without arguments, - # print the help and exit - args.parser.parse_args(["bump", "-h"]) - - ver = VersionInfo.parse(args.version) - # get the respective method and call it - func = getattr(ver, maptable[cast(str, args.bump)]) - return str(func()) - - -def cmd_check(args: argparse.Namespace) -> None: - """ - Subcommand: Checks if a string is a valid semver version. - - Synopsis: check - - :param args: The parsed arguments - """ - if VersionInfo.isvalid(args.version): - return None - raise ValueError("Invalid version %r" % args.version) - - -def cmd_compare(args: argparse.Namespace) -> str: - """ - Subcommand: Compare two versions - - Synopsis: compare - - :param args: The parsed arguments - """ - return str(compare(args.version1, args.version2)) - - -def cmd_nextver(args: argparse.Namespace) -> str: - """ - Subcommand: Determines the next version, taking prereleases into account. - - Synopsis: nextver - - :param args: The parsed arguments - """ - version = VersionInfo.parse(args.version) - return str(version.next_version(args.part)) - - -def createparser() -> argparse.ArgumentParser: - """ - Create an :class:`argparse.ArgumentParser` instance. - - :return: parser instance - """ - parser = argparse.ArgumentParser(prog=__package__, description=__doc__) - - parser.add_argument( - "--version", action="version", version="%(prog)s " + __version__ - ) - - s = parser.add_subparsers() - # create compare subcommand - parser_compare = s.add_parser("compare", help="Compare two versions") - parser_compare.set_defaults(func=cmd_compare) - parser_compare.add_argument("version1", help="First version") - parser_compare.add_argument("version2", help="Second version") - - # create bump subcommand - parser_bump = s.add_parser("bump", help="Bumps a version") - parser_bump.set_defaults(func=cmd_bump) - sb = parser_bump.add_subparsers(title="Bump commands", dest="bump") - - # Create subparsers for the bump subparser: - for p in ( - sb.add_parser("major", help="Bump the major part of the version"), - sb.add_parser("minor", help="Bump the minor part of the version"), - sb.add_parser("patch", help="Bump the patch part of the version"), - sb.add_parser("prerelease", help="Bump the prerelease part of the version"), - sb.add_parser("build", help="Bump the build part of the version"), - ): - p.add_argument("version", help="Version to raise") - - # Create the check subcommand - parser_check = s.add_parser( - "check", help="Checks if a string is a valid semver version" - ) - parser_check.set_defaults(func=cmd_check) - parser_check.add_argument("version", help="Version to check") - - # Create the nextver subcommand - parser_nextver = s.add_parser( - "nextver", help="Determines the next version, taking prereleases into account." - ) - parser_nextver.set_defaults(func=cmd_nextver) - parser_nextver.add_argument("version", help="Version to raise") - parser_nextver.add_argument( - "part", help="One of 'major', 'minor', 'patch', or 'prerelease'" - ) - return parser - - -def process(args: argparse.Namespace) -> str: - """ - Process the input from the CLI. - - :param args: The parsed arguments - :param parser: the parser instance - :return: result of the selected action - """ - if not hasattr(args, "func"): - args.parser.print_help() - raise SystemExit() - - # Call the respective function object: - return args.func(args) - - -def main(cliargs: List[str] = None) -> int: - """ - Entry point for the application script. - - :param list cliargs: Arguments to parse or None (=use :class:`sys.argv`) - :return: error code - """ - try: - parser = createparser() - args = parser.parse_args(args=cliargs) - # Save parser instance: - args.parser = parser - result = process(args) - if result is not None: - print(result) - return 0 - - except (ValueError, TypeError) as err: - print("ERROR", err, file=sys.stderr) - return 2 - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/tests/test_compare.py b/tests/test_compare.py index 41caa08d..aeb38eb9 100644 --- a/tests/test_compare.py +++ b/tests/test_compare.py @@ -1,5 +1,6 @@ import pytest +import semver from semver import VersionInfo, compare @@ -291,7 +292,7 @@ def test_should_not_allow_to_compare_version_with_int(): with pytest.raises(TypeError): 1 > v1 with pytest.raises(TypeError): - v1.compare(1) + semver.compare(1) def test_should_compare_prerelease_and_build_with_numbers(): diff --git a/tests/test_deprecated_functions.py b/tests/test_deprecated_functions.py index 8a04e3e9..0b5123cc 100644 --- a/tests/test_deprecated_functions.py +++ b/tests/test_deprecated_functions.py @@ -1,22 +1,31 @@ +from argparse import Namespace + import pytest from semver import ( - bump_build, + parse, + parse_version_info, + compare, + match, + max_ver, + min_ver, + format_version, bump_major, bump_minor, bump_patch, bump_prerelease, - compare, - deprecated, + bump_build, finalize_version, - format_version, - match, - max_ver, - min_ver, - parse, - parse_version_info, replace, + cmd_bump, + cmd_compare, + cmd_check, + cmd_nextver, + createparser, + process, + main, ) +from semver._deprecated import deprecated @pytest.mark.parametrize( @@ -36,6 +45,17 @@ (replace, ("1.2.3",), dict(major=2, patch=10)), (max_ver, ("1.2.3", "1.2.4"), {}), (min_ver, ("1.2.3", "1.2.4"), {}), + (cmd_bump, (Namespace(bump="major", version="1.2.3"),), {}), + (cmd_compare, (Namespace(version1="1.2.3", version2="2.1.3"),), {}), + (cmd_check, (Namespace(version="1.2.3"),), {}), + (cmd_nextver, (Namespace(version="1.2.3", part="major"),), {}), + (createparser, (), {}), + ( + process, + (Namespace(func=cmd_compare, version1="1.2.3", version2="2.1.3"),), + {}, + ), + (main, (["bump", "major", "1.2.3"],), {}), ], ) def test_should_raise_deprecation_warnings(func, args, kwargs): diff --git a/tests/test_pysemver-cli.py b/tests/test_pysemver-cli.py index 1fbeef26..e783a0b4 100644 --- a/tests/test_pysemver-cli.py +++ b/tests/test_pysemver-cli.py @@ -1,9 +1,18 @@ from argparse import Namespace from contextlib import contextmanager +from unittest.mock import patch import pytest -from semver import cmd_bump, cmd_check, cmd_compare, cmd_nextver, createparser, main +from semver import ( + cmd_bump, + cmd_check, + cmd_compare, + cmd_nextver, + createparser, + main, + __main__, +) @contextmanager @@ -125,3 +134,11 @@ def test_should_process_check_iscalled_with_valid_version(capsys): assert not result captured = capsys.readouterr() assert not captured.out + + +@pytest.mark.parametrize("package_name", ["", "semver"]) +def test_main_file_should_call_cli_main(package_name): + with patch("semver.__main__.cli.main") as mocked_main: + with patch("semver.__main__.__package__", package_name): + __main__.main() + mocked_main.assert_called_once() diff --git a/tests/test_semver.py b/tests/test_semver.py index 630ebbce..de4e86f5 100644 --- a/tests/test_semver.py +++ b/tests/test_semver.py @@ -75,3 +75,8 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): def test_should_versioninfo_isvalid(): assert VersionInfo.isvalid("1.0.0") is True assert VersionInfo.isvalid("foo") is False + + +def test_versioninfo_compare_should_raise_when_passed_invalid_value(): + with pytest.raises(TypeError): + VersionInfo(1, 2, 3).compare(4) diff --git a/tests/test_typeerror-274.py b/tests/test_typeerror-274.py index a0375d0d..61480bcf 100644 --- a/tests/test_typeerror-274.py +++ b/tests/test_typeerror-274.py @@ -3,6 +3,7 @@ import pytest import semver +import semver.version PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 @@ -52,7 +53,7 @@ class TestEnsure: def test_ensure_binary_raise_type_error(self): with pytest.raises(TypeError): - semver.ensure_str(8) + semver.version.ensure_str(8) def test_errors_and_encoding(self): ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="ignore") @@ -77,10 +78,10 @@ def test_ensure_binary_raise(self): ) def test_ensure_str(self): - converted_unicode = semver.ensure_str( + converted_unicode = semver.version.ensure_str( self.UNICODE_EMOJI, encoding="utf-8", errors="strict" ) - converted_binary = semver.ensure_str( + converted_binary = semver.version.ensure_str( self.BINARY_EMOJI, encoding="utf-8", errors="strict" ) diff --git a/tox.ini b/tox.ini index d253f1f6..1071be92 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = py{36,37,38,39,310} docs mypy +isolated_build = True [testenv] @@ -35,14 +36,14 @@ commands = flake8 {posargs:} description = Check code style basepython = python3 deps = mypy -commands = mypy {posargs:--ignore-missing-imports .} +commands = mypy {posargs:--ignore-missing-imports --check-untyped-defs src} [testenv:docstrings] description = Check for PEP257 compatible docstrings basepython = python3 deps = docformatter -commands = docformatter --check {posargs:--pre-summary-newline semver.py} +commands = docformatter --check {posargs:--pre-summary-newline -r src} [testenv:checks] @@ -94,3 +95,4 @@ deps = git+https://github.com/twisted/towncrier.git commands = towncrier {posargs:build --draft} + From d38813d4e28aea4eb45c7e06ce185760c1ceb9f8 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Thu, 29 Oct 2020 18:06:22 -0400 Subject: [PATCH 02/16] Rename VersionInfo to Version to close #305 Add documentation for Version rename --- README.rst | 20 ++-- changelog.d/305.doc.rst | 1 + changelog.d/305.feature.rst | 1 + docs/coerce.py | 8 +- docs/semverwithvprefix.py | 8 +- docs/usage.rst | 226 +++++++++++++++++++----------------- src/semver/__init__.py | 2 +- src/semver/_deprecated.py | 62 +++++----- src/semver/cli.py | 10 +- src/semver/version.py | 68 ++++++----- tests/conftest.py | 4 +- tests/test_compare.py | 40 +++---- tests/test_format.py | 28 ++--- tests/test_index.py | 10 +- tests/test_parsing.py | 12 +- tests/test_replace.py | 6 +- tests/test_semver.py | 26 ++--- tests/test_subclass.py | 4 +- 18 files changed, 282 insertions(+), 254 deletions(-) create mode 100644 changelog.d/305.doc.rst create mode 100644 changelog.d/305.feature.rst diff --git a/README.rst b/README.rst index 2baef45c..03faebab 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,12 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. |MAINT| replace:: ``maint/v2`` .. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 +.. note:: + + The :class:`VersionInfo` has been renamed to :class:`Version`. An + alias has been created to preserve compatibility but the use of the old + name has been deprecated. + The module follows the ``MAJOR.MINOR.PATCH`` style: * ``MAJOR`` version when you make incompatible API changes, @@ -45,11 +51,11 @@ To import this library, use: >>> import semver Working with the library is quite straightforward. To turn a version string into the -different parts, use the ``semver.VersionInfo.parse`` function: +different parts, use the ``semver.Version.parse`` function: .. code-block:: python - >>> ver = semver.VersionInfo.parse('1.2.3-pre.2+build.4') + >>> ver = semver.Version.parse('1.2.3-pre.2+build.4') >>> ver.major 1 >>> ver.minor @@ -62,21 +68,21 @@ different parts, use the ``semver.VersionInfo.parse`` function: 'build.4' To raise parts of a version, there are a couple of functions available for -you. The function ``semver.VersionInfo.bump_major`` leaves the original object untouched, but -returns a new ``semver.VersionInfo`` instance with the raised major part: +you. The function ``semver.Version.bump_major`` leaves the original object untouched, but +returns a new ``semver.Version`` instance with the raised major part: .. code-block:: python - >>> ver = semver.VersionInfo.parse("3.4.5") + >>> ver = semver.Version.parse("3.4.5") >>> ver.bump_major() - VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) + Version(major=4, minor=0, patch=0, prerelease=None, build=None) It is allowed to concatenate different "bump functions": .. code-block:: python >>> ver.bump_major().bump_minor() - VersionInfo(major=4, minor=1, patch=0, prerelease=None, build=None) + Version(major=4, minor=1, patch=0, prerelease=None, build=None) To compare two versions, semver provides the ``semver.compare`` function. The return value indicates the relationship between the first and second diff --git a/changelog.d/305.doc.rst b/changelog.d/305.doc.rst new file mode 100644 index 00000000..1ce69247 --- /dev/null +++ b/changelog.d/305.doc.rst @@ -0,0 +1 @@ +Add note about :class:`Version` rename. \ No newline at end of file diff --git a/changelog.d/305.feature.rst b/changelog.d/305.feature.rst new file mode 100644 index 00000000..98ef9665 --- /dev/null +++ b/changelog.d/305.feature.rst @@ -0,0 +1 @@ +Rename :class:`VersionInfo` to :class:`Version` but keep an alias for compatibility \ No newline at end of file diff --git a/docs/coerce.py b/docs/coerce.py index 3e5eb21b..9fe87276 100644 --- a/docs/coerce.py +++ b/docs/coerce.py @@ -17,7 +17,7 @@ def coerce(version): """ - Convert an incomplete version string into a semver-compatible VersionInfo + Convert an incomplete version string into a semver-compatible Version object * Tries to detect a "basic" version string (``major.minor.patch``). @@ -25,10 +25,10 @@ def coerce(version): set to zero to obtain a valid semver version. :param str version: the version string to convert - :return: a tuple with a :class:`VersionInfo` instance (or ``None`` + :return: a tuple with a :class:`Version` instance (or ``None`` if it's not a version) and the rest of the string which doesn't belong to a basic version. - :rtype: tuple(:class:`VersionInfo` | None, str) + :rtype: tuple(:class:`Version` | None, str) """ match = BASEVERSION.search(version) if not match: @@ -37,6 +37,6 @@ def coerce(version): ver = { key: 0 if value is None else value for key, value in match.groupdict().items() } - ver = semver.VersionInfo(**ver) + ver = semver.Version(**ver) rest = match.string[match.end() :] # noqa:E203 return ver, rest diff --git a/docs/semverwithvprefix.py b/docs/semverwithvprefix.py index 13298d5f..304ce772 100644 --- a/docs/semverwithvprefix.py +++ b/docs/semverwithvprefix.py @@ -1,15 +1,15 @@ -from semver import VersionInfo +from semver import Version -class SemVerWithVPrefix(VersionInfo): +class SemVerWithVPrefix(Version): """ - A subclass of VersionInfo which allows a "v" prefix + A subclass of Version which allows a "v" prefix """ @classmethod def parse(cls, version): """ - Parse version string to a VersionInfo instance. + Parse version string to a Version instance. :param version: version string with "v" or "V" prefix :type version: str diff --git a/docs/usage.rst b/docs/usage.rst index 3ceb2bf3..31363bcf 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,7 +1,7 @@ Using semver ============ -The ``semver`` module can store a version in the :class:`semver.VersionInfo` class. +The ``semver`` module can store a version in the :class:`semver.Version` class. For historical reasons, a version can be also stored as a string or dictionary. Each type can be converted into the other, if the minimum requirements @@ -35,7 +35,7 @@ Creating a Version Due to historical reasons, the semver project offers two ways of creating a version: -* through an object oriented approach with the :class:`semver.VersionInfo` +* through an object oriented approach with the :class:`semver.Version` class. This is the preferred method when using semver. * through module level functions and builtin datatypes (usually string @@ -52,30 +52,46 @@ creating a version: :ref:`sec_display_deprecation_warnings`. -A :class:`semver.VersionInfo` instance can be created in different ways: +A :class:`semver.Version` instance can be created in different ways: * From a string (a Unicode string in Python 2):: - >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - >>> semver.VersionInfo.parse(u"5.3.1") - VersionInfo(major=5, minor=3, patch=1, prerelease=None, build=None) + >>> semver.Version.parse("3.4.5-pre.2+build.4") + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> semver.Version.parse(u"5.3.1") + Version(major=5, minor=3, patch=1, prerelease=None, build=None) * From a byte string:: - >>> semver.VersionInfo.parse(b"2.3.4") - VersionInfo(major=2, minor=3, patch=4, prerelease=None, build=None) + >>> semver.Version.parse(b"2.3.4") + Version(major=2, minor=3, patch=4, prerelease=None, build=None) * From individual parts by a dictionary:: >>> d = {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - >>> semver.VersionInfo(**d) - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> semver.Version(**d) + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') Keep in mind, the ``major``, ``minor``, ``patch`` parts has to + be positive. + + >>> semver.Version(-1) + Traceback (most recent call last): + ... + ValueError: 'major' is negative. A version can only be positive. + + As a minimum requirement, your dictionary needs at least the + be positive. + + >>> semver.Version(-1) + Traceback (most recent call last): + ... + ValueError: 'major' is negative. A version can only be positive. + + As a minimum requirement, your dictionary needs at least the be positive. - >>> semver.VersionInfo(-1) + >>> semver.Version(-1) Traceback (most recent call last): ... ValueError: 'major' is negative. A version can only be positive. @@ -89,20 +105,20 @@ A :class:`semver.VersionInfo` instance can be created in different ways: * From a tuple:: >>> t = (3, 5, 6) - >>> semver.VersionInfo(*t) - VersionInfo(major=3, minor=5, patch=6, prerelease=None, build=None) + >>> semver.Version(*t) + Version(major=3, minor=5, patch=6, prerelease=None, build=None) You can pass either an integer or a string for ``major``, ``minor``, or ``patch``:: - >>> semver.VersionInfo("3", "5", 6) - VersionInfo(major=3, minor=5, patch=6, prerelease=None, build=None) + >>> semver.Version("3", "5", 6) + Version(major=3, minor=5, patch=6, prerelease=None, build=None) The old, deprecated module level functions are still available. If you need them, they return different builtin objects (string and dictionary). Keep in mind, once you have converted a version into a string or dictionary, it's an ordinary builtin object. It's not a special version object like -the :class:`semver.VersionInfo` class anymore. +the :class:`semver.Version` class anymore. Depending on your use case, the following methods are available: @@ -137,13 +153,13 @@ Parsing a Version String * With :func:`semver.parse_version_info`:: >>> semver.parse_version_info("3.4.5-pre.2+build.4") - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') -* With :func:`semver.VersionInfo.parse` (basically the same as +* With :func:`semver.Version.parse` (basically the same as :func:`semver.parse_version_info`):: - >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> semver.Version.parse("3.4.5-pre.2+build.4") + Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') * With :func:`semver.parse`:: @@ -155,13 +171,13 @@ Checking for a Valid Semver Version ----------------------------------- If you need to check a string if it is a valid semver version, use the -classmethod :func:`semver.VersionInfo.isvalid`: +classmethod :func:`semver.Version.isvalid`: .. code-block:: python - >>> semver.VersionInfo.isvalid("1.0.0") + >>> semver.Version.isvalid("1.0.0") True - >>> semver.VersionInfo.isvalid("invalid") + >>> semver.Version.isvalid("invalid") False @@ -170,12 +186,12 @@ classmethod :func:`semver.VersionInfo.isvalid`: Accessing Parts of a Version Through Names ------------------------------------------ -The :class:`semver.VersionInfo` contains attributes to access the different +The :class:`semver.Version` contains attributes to access the different parts of a version: .. code-block:: python - >>> v = semver.VersionInfo.parse("3.4.5-pre.2+build.4") + >>> v = semver.Version.parse("3.4.5-pre.2+build.4") >>> v.major 3 >>> v.minor @@ -197,16 +213,16 @@ If you do, you get an ``AttributeError``:: If you need to replace different parts of a version, refer to section :ref:`sec.replace.parts`. -In case you need the different parts of a version stepwise, iterate over the :class:`semver.VersionInfo` instance:: +In case you need the different parts of a version stepwise, iterate over the :class:`semver.Version` instance:: - >>> for item in semver.VersionInfo.parse("3.4.5-pre.2+build.4"): + >>> for item in semver.Version.parse("3.4.5-pre.2+build.4"): ... print(item) 3 4 5 pre.2 build.4 - >>> list(semver.VersionInfo.parse("3.4.5-pre.2+build.4")) + >>> list(semver.Version.parse("3.4.5-pre.2+build.4")) [3, 4, 5, 'pre.2', 'build.4'] @@ -218,15 +234,15 @@ Accessing Parts Through Index Numbers .. versionadded:: 2.10.0 Another way to access parts of a version is to use an index notation. The underlying -:class:`VersionInfo ` object allows to access its data through -the magic method :func:`__getitem__ `. +:class:`Version ` object allows to access its data through +the magic method :func:`__getitem__ `. For example, the ``major`` part can be accessed by index number 0 (zero). Likewise the other parts: .. code-block:: python - >>> ver = semver.VersionInfo.parse("10.3.2-pre.5+build.10") + >>> ver = semver.Version.parse("10.3.2-pre.5+build.10") >>> ver[0], ver[1], ver[2], ver[3], ver[4] (10, 3, 2, 'pre.5', 'build.10') @@ -249,7 +265,7 @@ Negative numbers or undefined parts raise an :class:`IndexError` exception: .. code-block:: python - >>> ver = semver.VersionInfo.parse("10.3.2") + >>> ver = semver.Version.parse("10.3.2") >>> ver[3] Traceback (most recent call last): ... @@ -265,13 +281,13 @@ Replacing Parts of a Version ---------------------------- If you want to replace different parts of a version, but leave other parts -unmodified, use the function :func:`semver.VersionInfo.replace` or :func:`semver.replace`: +unmodified, use the function :func:`semver.Version.replace` or :func:`semver.replace`: -* From a :class:`semver.VersionInfo` instance:: +* From a :class:`semver.Version` instance:: - >>> version = semver.VersionInfo.parse("1.4.5-pre.1+build.6") + >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(major=2, minor=2) - VersionInfo(major=2, minor=2, patch=5, prerelease='pre.1', build='build.6') + Version(major=2, minor=2, patch=5, prerelease='pre.1', build='build.6') * From a version string:: @@ -284,7 +300,7 @@ If you pass invalid keys you get an exception:: Traceback (most recent call last): ... TypeError: replace() got 1 unexpected keyword argument(s): invalidkey - >>> version = semver.VersionInfo.parse("1.4.5-pre.1+build.6") + >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(invalidkey=2) Traceback (most recent call last): ... @@ -293,28 +309,28 @@ If you pass invalid keys you get an exception:: .. _sec.convert.versions: -Converting a VersionInfo instance into Different Types +Converting a Version instance into Different Types ------------------------------------------------------ -Sometimes it is needed to convert a :class:`semver.VersionInfo` instance into +Sometimes it is needed to convert a :class:`semver.Version` instance into a different type. For example, for displaying or to access all parts. -It is possible to convert a :class:`semver.VersionInfo` instance: +It is possible to convert a :class:`semver.Version` instance: * Into a string with the builtin function :func:`str`:: - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4")) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4")) '3.4.5-pre.2+build.4' -* Into a dictionary with :func:`semver.VersionInfo.to_dict`:: +* Into a dictionary with :func:`semver.Version.to_dict`:: - >>> v = semver.VersionInfo(major=3, minor=4, patch=5) + >>> v = semver.Version(major=3, minor=4, patch=5) >>> v.to_dict() OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) -* Into a tuple with :func:`semver.VersionInfo.to_tuple`:: +* Into a tuple with :func:`semver.Version.to_tuple`:: - >>> v = semver.VersionInfo(major=5, minor=4, patch=2) + >>> v = semver.Version(major=5, minor=4, patch=2) >>> v.to_tuple() (5, 4, 2, None, None) @@ -325,27 +341,27 @@ Raising Parts of a Version The ``semver`` module contains the following functions to raise parts of a version: -* :func:`semver.VersionInfo.bump_major`: raises the major part and set all other parts to +* :func:`semver.Version.bump_major`: raises the major part and set all other parts to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.VersionInfo.bump_minor`: raises the minor part and sets ``patch`` to zero. +* :func:`semver.Version.bump_minor`: raises the minor part and sets ``patch`` to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.VersionInfo.bump_patch`: raises the patch part. Set ``prerelease`` and +* :func:`semver.Version.bump_patch`: raises the patch part. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.VersionInfo.bump_prerelease`: raises the prerelease part and set +* :func:`semver.Version.bump_prerelease`: raises the prerelease part and set ``build`` to ``None``. -* :func:`semver.VersionInfo.bump_build`: raises the build part. +* :func:`semver.Version.bump_build`: raises the build part. .. code-block:: python - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").bump_major()) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_major()) '4.0.0' - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").bump_minor()) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_minor()) '3.5.0' - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").bump_patch()) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_patch()) '3.4.6' - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").bump_prerelease()) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_prerelease()) '3.4.5-pre.3' - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").bump_build()) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_build()) '3.4.5-pre.2+build.5' Likewise the module level functions :func:`semver.bump_major`. @@ -355,23 +371,23 @@ Increasing Parts of a Version Taking into Account Prereleases ------------------------------------------------------------- .. versionadded:: 2.10.0 - Added :func:`semver.VersionInfo.next_version`. + Added :func:`semver.Version.next_version`. If you want to raise your version and take prereleases into account, -the function :func:`semver.VersionInfo.next_version` would perhaps a +the function :func:`semver.Version.next_version` would perhaps a better fit. .. code-block:: python - >>> v = semver.VersionInfo.parse("3.4.5-pre.2+build.4") + >>> v = semver.Version.parse("3.4.5-pre.2+build.4") >>> str(v.next_version(part="prerelease")) '3.4.5-pre.3' - >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").next_version(part="patch")) + >>> str(semver.Version.parse("3.4.5-pre.2+build.4").next_version(part="patch")) '3.4.5' - >>> str(semver.VersionInfo.parse("3.4.5+build.4").next_version(part="patch")) + >>> str(semver.Version.parse("3.4.5+build.4").next_version(part="patch")) '3.4.5' - >>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease")) + >>> str(semver.Version.parse("0.1.4").next_version("prerelease")) '0.1.5-rc.1' @@ -394,23 +410,23 @@ To compare two versions depends on your type: The return value is negative if ``version1 < version2``, zero if ``version1 == version2`` and strictly positive if ``version1 > version2``. -* **Two** :class:`semver.VersionInfo` **instances** +* **Two** :class:`semver.Version` **instances** Use the specific operator. Currently, the operators ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: - >>> v1 = semver.VersionInfo.parse("3.4.5") - >>> v2 = semver.VersionInfo.parse("3.5.1") + >>> v1 = semver.Version.parse("3.4.5") + >>> v2 = semver.Version.parse("3.5.1") >>> v1 < v2 True >>> v1 > v2 False -* **A** :class:`semver.VersionInfo` **type and a** :func:`tuple` **or** :func:`list` +* **A** :class:`semver.Version` **type and a** :func:`tuple` **or** :func:`list` - Use the operator as with two :class:`semver.VersionInfo` types:: + Use the operator as with two :class:`semver.Version` types:: - >>> v = semver.VersionInfo.parse("3.4.5") + >>> v = semver.Version.parse("3.4.5") >>> v > (1, 0) True >>> v < [3, 5] @@ -423,7 +439,7 @@ To compare two versions depends on your type: >>> [3, 5] > v True -* **A** :class:`semver.VersionInfo` **type and a** :func:`str` +* **A** :class:`semver.Version` **type and a** :func:`str` You can use also raw strings to compare:: @@ -446,7 +462,7 @@ To compare two versions depends on your type: ... ValueError: 1.0 is not valid SemVer string -* **A** :class:`semver.VersionInfo` **type and a** :func:`dict` +* **A** :class:`semver.Version` **type and a** :func:`dict` You can also use a dictionary. In contrast to strings, you can have an "incomplete" version (as the other parts are set to zero):: @@ -483,16 +499,16 @@ Version equality means for semver, that major, minor, patch, and prerelease parts are equal in both versions you compare. The build part is ignored. For example:: - >>> v = semver.VersionInfo.parse("1.2.3-rc4+1e4664d") + >>> v = semver.Version.parse("1.2.3-rc4+1e4664d") >>> v == "1.2.3-rc4+dedbeef" True -This also applies when a :class:`semver.VersionInfo` is a member of a set, or a +This also applies when a :class:`semver.Version` is a member of a set, or a dictionary key:: >>> d = {} - >>> v1 = semver.VersionInfo.parse("1.2.3-rc4+1e4664d") - >>> v2 = semver.VersionInfo.parse("1.2.3-rc4+dedbeef") + >>> v1 = semver.Version.parse("1.2.3-rc4+1e4664d") + >>> v2 = semver.Version.parse("1.2.3-rc4+dedbeef") >>> d[v1] = 1 >>> d[v2] 1 @@ -538,34 +554,34 @@ Getting Minimum and Maximum of Multiple Versions The functions :func:`semver.max_ver` and :func:`semver.min_ver` are deprecated in favor of their builtin counterparts :func:`max` and :func:`min`. -Since :class:`semver.VersionInfo` implements :func:`__gt__()` and :func:`__lt__()`, it can be used with builtins requiring +Since :class:`semver.Version` implements :func:`__gt__()` and :func:`__lt__()`, it can be used with builtins requiring .. code-block:: python - >>> max([semver.VersionInfo(0, 1, 0), semver.VersionInfo(0, 2, 0), semver.VersionInfo(0, 1, 3)]) - VersionInfo(major=0, minor=2, patch=0, prerelease=None, build=None) - >>> min([semver.VersionInfo(0, 1, 0), semver.VersionInfo(0, 2, 0), semver.VersionInfo(0, 1, 3)]) - VersionInfo(major=0, minor=1, patch=0, prerelease=None, build=None) + >>> max([semver.Version(0, 1, 0), semver.Version(0, 2, 0), semver.Version(0, 1, 3)]) + Version(major=0, minor=2, patch=0, prerelease=None, build=None) + >>> min([semver.Version(0, 1, 0), semver.Version(0, 2, 0), semver.Version(0, 1, 3)]) + Version(major=0, minor=1, patch=0, prerelease=None, build=None) Incidentally, using :func:`map`, you can get the min or max version of any number of versions of the same type -(convertible to :class:`semver.VersionInfo`). +(convertible to :class:`semver.Version`). For example, here are the maximum and minimum versions of a list of version strings: .. code-block:: python - >>> str(max(map(semver.VersionInfo.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) + >>> str(max(map(semver.Version.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) '2.1.0' - >>> str(min(map(semver.VersionInfo.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) + >>> str(min(map(semver.Version.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) '0.4.99' And the same can be done with tuples: .. code-block:: python - >>> max(map(lambda v: semver.VersionInfo(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + >>> max(map(lambda v: semver.Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() (2, 1, 0, None, None) - >>> min(map(lambda v: semver.VersionInfo(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + >>> min(map(lambda v: semver.Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() (0, 4, 99, None, None) For dictionaries, it is very similar to finding the max version tuple: see :ref:`sec.convert.versions`. @@ -600,7 +616,7 @@ information and returns a tuple with two items: :language: python -The function returns a *tuple*, containing a :class:`VersionInfo` +The function returns a *tuple*, containing a :class:`Version` instance or None as the first element and the rest as the second element. The second element (the rest) can be used to make further adjustments. @@ -609,9 +625,9 @@ For example: .. code-block:: python >>> coerce("v1.2") - (VersionInfo(major=1, minor=2, patch=0, prerelease=None, build=None), '') + (Version(major=1, minor=2, patch=0, prerelease=None, build=None), '') >>> coerce("v2.5.2-bla") - (VersionInfo(major=2, minor=5, patch=2, prerelease=None, build=None), '-bla') + (Version(major=2, minor=5, patch=2, prerelease=None, build=None), '-bla') .. _sec_replace_deprecated_functions: @@ -622,7 +638,7 @@ Replacing Deprecated Functions .. versionchanged:: 2.10.0 The development team of semver has decided to deprecate certain functions on the module level. The preferred way of using semver is through the - :class:`semver.VersionInfo` class. + :class:`semver.Version` class. The deprecated functions can still be used in version 2.10.0 and above. In version 3 of semver, the deprecated functions will be removed. @@ -633,15 +649,15 @@ them with code which is compatible for future versions: * :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` - Replace them with the respective methods of the :class:`semver.VersionInfo` + Replace them with the respective methods of the :class:`semver.Version` class. For example, the function :func:`semver.bump_major` is replaced by - :func:`semver.VersionInfo.bump_major` and calling the ``str(versionobject)``: + :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: .. code-block:: python >>> s1 = semver.bump_major("3.4.5") - >>> s2 = str(semver.VersionInfo.parse("3.4.5").bump_major()) + >>> s2 = str(semver.Version.parse("3.4.5").bump_major()) >>> s1 == s2 True @@ -649,12 +665,12 @@ them with code which is compatible for future versions: * :func:`semver.finalize_version` - Replace it with :func:`semver.VersionInfo.finalize_version`: + Replace it with :func:`semver.Version.finalize_version`: .. code-block:: python >>> s1 = semver.finalize_version('1.2.3-rc.5') - >>> s2 = str(semver.VersionInfo.parse('1.2.3-rc.5').finalize_version()) + >>> s2 = str(semver.Version.parse('1.2.3-rc.5').finalize_version()) >>> s1 == s2 True @@ -665,7 +681,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.format_version(5, 4, 3, 'pre.2', 'build.1') - >>> s2 = str(semver.VersionInfo(5, 4, 3, 'pre.2', 'build.1')) + >>> s2 = str(semver.Version(5, 4, 3, 'pre.2', 'build.1')) >>> s1 == s2 True @@ -676,7 +692,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.max_ver("1.2.3", "1.2.4") - >>> s2 = str(max(map(semver.VersionInfo.parse, ("1.2.3", "1.2.4")))) + >>> s2 = str(max(map(semver.Version.parse, ("1.2.3", "1.2.4")))) >>> s1 == s2 True @@ -687,41 +703,41 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.min_ver("1.2.3", "1.2.4") - >>> s2 = str(min(map(semver.VersionInfo.parse, ("1.2.3", "1.2.4")))) + >>> s2 = str(min(map(semver.Version.parse, ("1.2.3", "1.2.4")))) >>> s1 == s2 True * :func:`semver.parse` - Replace it with :func:`semver.VersionInfo.parse` and - :func:`semver.VersionInfo.to_dict`: + Replace it with :func:`semver.Version.parse` and + :func:`semver.Version.to_dict`: .. code-block:: python >>> v1 = semver.parse("1.2.3") - >>> v2 = semver.VersionInfo.parse("1.2.3").to_dict() + >>> v2 = semver.Version.parse("1.2.3").to_dict() >>> v1 == v2 True * :func:`semver.parse_version_info` - Replace it with :func:`semver.VersionInfo.parse`: + Replace it with :func:`semver.Version.parse`: .. code-block:: python >>> v1 = semver.parse_version_info("3.4.5") - >>> v2 = semver.VersionInfo.parse("3.4.5") + >>> v2 = semver.Version.parse("3.4.5") >>> v1 == v2 True * :func:`semver.replace` - Replace it with :func:`semver.VersionInfo.replace`: + Replace it with :func:`semver.Version.replace`: .. code-block:: python >>> s1 = semver.replace("1.2.3", major=2, patch=10) - >>> s2 = str(semver.VersionInfo.parse('1.2.3').replace(major=2, patch=10)) + >>> s2 = str(semver.Version.parse('1.2.3').replace(major=2, patch=10)) >>> s1 == s2 True @@ -764,12 +780,12 @@ the following methods: .. _sec_creating_subclasses_from_versioninfo: -Creating Subclasses from VersionInfo +Creating Subclasses from Version ------------------------------------ If you do not like creating functions to modify the behavior of semver (as shown in section :ref:`sec_dealing_with_invalid_versions`), you can -also create a subclass of the :class:`VersionInfo` class. +also create a subclass of the :class:`Version` class. For example, if you want to output a "v" prefix before a version, but the other behavior is the same, use the following code: diff --git a/src/semver/__init__.py b/src/semver/__init__.py index 501a9586..1a80f6c3 100644 --- a/src/semver/__init__.py +++ b/src/semver/__init__.py @@ -21,7 +21,7 @@ process, main, ) -from .version import VersionInfo +from .version import Version, VersionInfo from .__about__ import ( __version__, __author__, diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 56597e6c..45c52753 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -5,7 +5,7 @@ from typing import Type, Union, Callable, cast from . import cli -from .version import VersionInfo +from .version import Version from ._types import F, String @@ -20,7 +20,7 @@ def deprecated( :param func: the function to decorate :param replace: the function to replace (use the full qualified - name like ``semver.VersionInfo.bump_major``. + name like ``semver.Version.bump_major``. :param version: the first version when this function was deprecated. :param category: allow you to specify the deprecation warning class of your choice. By default, it's :class:`DeprecationWarning`, but @@ -41,7 +41,7 @@ def wrapper(*args, **kwargs) -> Callable[..., F]: if replace: msg_list.append("Use {r!r} instead.") else: - msg_list.append("Use the respective 'semver.VersionInfo.{r}' instead.") + msg_list.append("Use the respective 'semver.Version.{r}' instead.") f = cast(F, func).__qualname__ r = replace or f @@ -70,7 +70,7 @@ def parse(version): Parse version to major, minor, patch, pre-release, build parts. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.parse` instead. + Use :func:`semver.Version.parse` instead. :param version: version string :return: dictionary with the keys 'build', 'major', 'minor', 'patch', @@ -90,10 +90,10 @@ def parse(version): >>> ver['build'] 'build.4' """ - return VersionInfo.parse(version).to_dict() + return Version.parse(version).to_dict() -@deprecated(replace="semver.VersionInfo.parse", version="2.10.0") +@deprecated(replace="semver.Version.parse", version="2.10.0") def parse_version_info(version): """ Parse version string to a VersionInfo instance. @@ -104,7 +104,7 @@ def parse_version_info(version): Added :func:`semver.parse_version_info` :param version: version string :return: a :class:`VersionInfo` instance - >>> version_info = semver.VersionInfo.parse("3.4.5-pre.2+build.4") + >>> version_info = semver.Version.parse("3.4.5-pre.2+build.4") >>> version_info.major 3 >>> version_info.minor @@ -116,7 +116,7 @@ def parse_version_info(version): >>> version_info.build 'build.4' """ - return VersionInfo.parse(version) + return Version.parse(version) @deprecated(version="2.10.0") @@ -137,7 +137,7 @@ def compare(ver1, ver2): >>> semver.compare("2.0.0", "2.0.0") 0 """ - v1 = VersionInfo.parse(ver1) + v1 = Version.parse(ver1) return v1.compare(ver2) @@ -162,7 +162,7 @@ def match(version, match_expr): >>> semver.match("1.0.0", ">1.0.0") False """ - ver = VersionInfo.parse(version) + ver = Version.parse(version) return ver.match(match_expr) @@ -174,14 +174,14 @@ def max_ver(ver1, ver2): :param ver1: version string 1 :param ver2: version string 2 :return: the greater version of the two - :rtype: :class:`VersionInfo` + :rtype: :class:`Version` >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' """ if isinstance(ver1, String.__args__): # type: ignore - ver1 = VersionInfo.parse(ver1) - elif not isinstance(ver1, VersionInfo): + ver1 = Version.parse(ver1) + elif not isinstance(ver1, Version): raise TypeError() cmp_res = ver1.compare(ver2) if cmp_res >= 0: @@ -198,12 +198,12 @@ def min_ver(ver1, ver2): :param ver1: version string 1 :param ver2: version string 2 :return: the smaller version of the two - :rtype: :class:`VersionInfo` + :rtype: :class:`Version` >>> semver.min_ver("1.0.0", "2.0.0") '1.0.0' """ - ver1 = VersionInfo.parse(ver1) + ver1 = Version.parse(ver1) cmp_res = ver1.compare(ver2) if cmp_res <= 0: return str(ver1) @@ -217,7 +217,7 @@ def format_version(major, minor, patch, prerelease=None, build=None): Format a version string according to the Semantic Versioning specification. .. deprecated:: 2.10.0 - Use ``str(VersionInfo(VERSION)`` instead. + Use ``str(Version(VERSION)`` instead. :param int major: the required major part of a version :param int minor: the required minor part of a version @@ -230,7 +230,7 @@ def format_version(major, minor, patch, prerelease=None, build=None): >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') '3.4.5-pre.2+build.4' """ - return str(VersionInfo(major, minor, patch, prerelease, build)) + return str(Version(major, minor, patch, prerelease, build)) @deprecated(version="2.10.0") @@ -239,7 +239,7 @@ def bump_major(version): Raise the major part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_major` instead. + Use :func:`semver.Version.bump_major` instead. :param: version string :return: the raised version string @@ -248,7 +248,7 @@ def bump_major(version): >>> semver.bump_major("3.4.5") '4.0.0' """ - return str(VersionInfo.parse(version).bump_major()) + return str(Version.parse(version).bump_major()) @deprecated(version="2.10.0") @@ -257,7 +257,7 @@ def bump_minor(version): Raise the minor part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_minor` instead. + Use :func:`semver.Version.bump_minor` instead. :param: version string :return: the raised version string @@ -266,7 +266,7 @@ def bump_minor(version): >>> semver.bump_minor("3.4.5") '3.5.0' """ - return str(VersionInfo.parse(version).bump_minor()) + return str(Version.parse(version).bump_minor()) @deprecated(version="2.10.0") @@ -275,7 +275,7 @@ def bump_patch(version): Raise the patch part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_patch` instead. + Use :func:`semver.Version.bump_patch` instead. :param: version string :return: the raised version string @@ -284,7 +284,7 @@ def bump_patch(version): >>> semver.bump_patch("3.4.5") '3.4.6' """ - return str(VersionInfo.parse(version).bump_patch()) + return str(Version.parse(version).bump_patch()) @deprecated(version="2.10.0") @@ -293,7 +293,7 @@ def bump_prerelease(version, token="rc"): Raise the prerelease part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_prerelease` instead. + Use :func:`semver.Version.bump_prerelease` instead. :param version: version string :param token: defaults to 'rc' @@ -303,7 +303,7 @@ def bump_prerelease(version, token="rc"): >>> semver.bump_prerelease('3.4.5', 'dev') '3.4.5-dev.1' """ - return str(VersionInfo.parse(version).bump_prerelease(token)) + return str(Version.parse(version).bump_prerelease(token)) @deprecated(version="2.10.0") @@ -312,7 +312,7 @@ def bump_build(version, token="build"): Raise the build part of the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.bump_build` instead. + Use :func:`semver.Version.bump_build` instead. :param version: version string :param token: defaults to 'build' @@ -322,7 +322,7 @@ def bump_build(version, token="build"): >>> semver.bump_build('3.4.5-rc.1+build.9') '3.4.5-rc.1+build.10' """ - return str(VersionInfo.parse(version).bump_build(token)) + return str(Version.parse(version).bump_build(token)) @deprecated(version="2.10.0") @@ -331,7 +331,7 @@ def finalize_version(version): Remove any prerelease and build metadata from the version string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.finalize_version` instead. + Use :func:`semver.Version.finalize_version` instead. .. versionadded:: 2.7.9 Added :func:`finalize_version` @@ -343,7 +343,7 @@ def finalize_version(version): >>> semver.finalize_version('1.2.3-rc.5') '1.2.3' """ - verinfo = VersionInfo.parse(version) + verinfo = Version.parse(version) return str(verinfo.finalize_version()) @@ -353,7 +353,7 @@ def replace(version, **parts): Replace one or more parts of a version and return the new string. .. deprecated:: 2.10.0 - Use :func:`semver.VersionInfo.replace` instead. + Use :func:`semver.Version.replace` instead. .. versionadded:: 2.9.0 Added :func:`replace` :param version: the version string to replace @@ -365,7 +365,7 @@ def replace(version, **parts): >>> semver.replace("1.2.3", major=2, patch=10) '2.2.10' """ - return str(VersionInfo.parse(version).replace(**parts)) + return str(Version.parse(version).replace(**parts)) # CLI diff --git a/src/semver/cli.py b/src/semver/cli.py index f0d8e4ac..1514979c 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -2,7 +2,7 @@ import sys from typing import cast, List -from .version import VersionInfo +from .version import Version from .__about__ import __version__ @@ -28,7 +28,7 @@ def cmd_bump(args: argparse.Namespace) -> str: # print the help and exit args.parser.parse_args(["bump", "-h"]) - ver = VersionInfo.parse(args.version) + ver = Version.parse(args.version) # get the respective method and call it func = getattr(ver, maptable[cast(str, args.bump)]) return str(func()) @@ -42,7 +42,7 @@ def cmd_check(args: argparse.Namespace) -> None: :param args: The parsed arguments """ - if VersionInfo.isvalid(args.version): + if Version.isvalid(args.version): return None raise ValueError("Invalid version %r" % args.version) @@ -55,7 +55,7 @@ def cmd_compare(args: argparse.Namespace) -> str: :param args: The parsed arguments """ - ver1 = VersionInfo.parse(args.version1) + ver1 = Version.parse(args.version1) return str(ver1.compare(args.version2)) @@ -67,7 +67,7 @@ def cmd_nextver(args: argparse.Namespace) -> str: :param args: The parsed arguments """ - version = VersionInfo.parse(args.version) + version = Version.parse(args.version) return str(version.next_version(args.part)) diff --git a/src/semver/version.py b/src/semver/version.py index d0643641..93a6d338 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -23,8 +23,8 @@ ) # These types are required here because of circular imports -Comparable = Union["VersionInfo", Dict[str, VersionPart], Collection[VersionPart], str] -Comparator = Callable[["VersionInfo", Comparable], bool] +Comparable = Union["Version", Dict[str, VersionPart], Collection[VersionPart], str] +Comparator = Callable[["Version", Comparable], bool] def cmp(a, b): # TODO: type hints @@ -62,12 +62,12 @@ def ensure_str(s: String, encoding="utf-8", errors="strict") -> str: def comparator(operator: Comparator) -> Comparator: - """Wrap a VersionInfo binary op method in a type-check.""" + """Wrap a Version binary op method in a type-check.""" @wraps(operator) - def wrapper(self: "VersionInfo", other: Comparable) -> bool: + def wrapper(self: "Version", other: Comparable) -> bool: comparable_types = ( - VersionInfo, + Version, dict, tuple, list, @@ -109,7 +109,7 @@ def cmp_prerelease_tag(a, b): return cmp(len(a), len(b)) -class VersionInfo: +class Version: """ A semver compatible version class. @@ -224,7 +224,7 @@ def to_tuple(self) -> VersionTuple: :return: a tuple with all the parts - >>> semver.VersionInfo(5, 3, 1).to_tuple() + >>> semver.Version(5, 3, 1).to_tuple() (5, 3, 1, None, None) """ return (self.major, self.minor, self.patch, self.prerelease, self.build) @@ -240,7 +240,7 @@ def to_dict(self) -> VersionDict: :return: an OrderedDict with the keys in the order ``major``, ``minor``, ``patch``, ``prerelease``, and ``build``. - >>> semver.VersionInfo(3, 2, 1).to_dict() + >>> semver.Version(3, 2, 1).to_dict() OrderedDict([('major', 3), ('minor', 2), ('patch', 1), \ ('prerelease', None), ('build', None)]) """ @@ -269,14 +269,14 @@ def _increment_string(string: str) -> str: Source: http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 """ - match = VersionInfo._LAST_NUMBER.search(string) + match = Version._LAST_NUMBER.search(string) if match: next_ = str(int(match.group(1)) + 1) start, end = match.span(1) string = string[: max(end - len(next_), start)] + next_ + string[end:] return string - def bump_major(self) -> "VersionInfo": + def bump_major(self) -> "Version": """ Raise the major part of the version, return a new object but leave self untouched. @@ -286,12 +286,12 @@ def bump_major(self) -> "VersionInfo": >>> ver = semver.parse("3.4.5") >>> ver.bump_major() - VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) + Version(major=4, minor=0, patch=0, prerelease=None, build=None) """ cls = type(self) return cls(self._major + 1) - def bump_minor(self) -> "VersionInfo": + def bump_minor(self) -> "Version": """ Raise the minor part of the version, return a new object but leave self untouched. @@ -301,12 +301,12 @@ def bump_minor(self) -> "VersionInfo": >>> ver = semver.parse("3.4.5") >>> ver.bump_minor() - VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + Version(major=3, minor=5, patch=0, prerelease=None, build=None) """ cls = type(self) return cls(self._major, self._minor + 1) - def bump_patch(self) -> "VersionInfo": + def bump_patch(self) -> "Version": """ Raise the patch part of the version, return a new object but leave self untouched. @@ -316,12 +316,12 @@ def bump_patch(self) -> "VersionInfo": >>> ver = semver.parse("3.4.5") >>> ver.bump_patch() - VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None) + Version(major=3, minor=4, patch=6, prerelease=None, build=None) """ cls = type(self) return cls(self._major, self._minor, self._patch + 1) - def bump_prerelease(self, token: str = "rc") -> "VersionInfo": + def bump_prerelease(self, token: str = "rc") -> "Version": """ Raise the prerelease part of the version, return a new object but leave self untouched. @@ -331,14 +331,14 @@ def bump_prerelease(self, token: str = "rc") -> "VersionInfo": >>> ver = semver.parse("3.4.5") >>> ver.bump_prerelease() - VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \ + Version(major=3, minor=4, patch=5, prerelease='rc.2', \ build=None) """ cls = type(self) prerelease = cls._increment_string(self._prerelease or (token or "rc") + ".0") return cls(self._major, self._minor, self._patch, prerelease) - def bump_build(self, token: str = "build") -> "VersionInfo": + def bump_build(self, token: str = "build") -> "Version": """ Raise the build part of the version, return a new object but leave self untouched. @@ -348,7 +348,7 @@ def bump_build(self, token: str = "build") -> "VersionInfo": >>> ver = semver.parse("3.4.5-rc.1+build.9") >>> ver.bump_build() - VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \ + Version(major=3, minor=4, patch=5, prerelease='rc.1', \ build='build.10') """ cls = type(self) @@ -404,7 +404,7 @@ def compare(self, other: Comparable) -> int: return rccmp - def next_version(self, part: str, prerelease_token: str = "rc") -> "VersionInfo": + def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": """ Determines next version, preserving natural order. @@ -487,7 +487,7 @@ def __getitem__( offset or a :func:`slice` object :raises IndexError: if index is beyond the range or a part is None :return: the requested part of the version at position index - >>> ver = semver.VersionInfo.parse("3.4.5") + >>> ver = semver.Version.parse("3.4.5") >>> ver[0], ver[1], ver[2] (3, 4, 5) """ @@ -528,11 +528,11 @@ def __str__(self) -> str: def __hash__(self) -> int: return hash(self.to_tuple()[:4]) - def finalize_version(self) -> "VersionInfo": + def finalize_version(self) -> "Version": """ Remove any prerelease and build metadata from the version. :return: a new instance with the finalized version string - >>> str(semver.VersionInfo.parse('1.2.3-rc.5').finalize_version()) + >>> str(semver.Version.parse('1.2.3-rc.5').finalize_version()) '1.2.3' """ cls = type(self) @@ -550,9 +550,9 @@ def match(self, match_expr: str) -> bool: == equal != not equal :return: True if the expression matches the version, otherwise False - >>> semver.VersionInfo.parse("2.0.0").match(">=1.0.0") + >>> semver.Version.parse("2.0.0").match(">=1.0.0") True - >>> semver.VersionInfo.parse("1.0.0").match(">1.0.0") + >>> semver.Version.parse("1.0.0").match(">1.0.0") False """ prefix = match_expr[:2] @@ -584,7 +584,7 @@ def match(self, match_expr: str) -> bool: return cmp_res in possibilities @classmethod - def parse(cls, version: String) -> "VersionInfo": + def parse(cls, version: String) -> "Version": """ Parse version string to a VersionInfo instance. @@ -594,7 +594,7 @@ def parse(cls, version: String) -> "VersionInfo": :param version: version string :return: a :class:`VersionInfo` instance :raises ValueError: if version is invalid - >>> semver.VersionInfo.parse('3.4.5-pre.2+build.4') + >>> semver.Version.parse('3.4.5-pre.2+build.4') VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') """ @@ -607,24 +607,24 @@ def parse(cls, version: String) -> "VersionInfo": return cls(**matched_version_parts) - def replace(self, **parts: Union[int, Optional[str]]) -> "VersionInfo": + def replace(self, **parts: Union[int, Optional[str]]) -> "Version": """ Replace one or more parts of a version and return a new - :class:`VersionInfo` object, but leave self untouched + :class:`Version` object, but leave self untouched .. versionadded:: 2.9.0 - Added :func:`VersionInfo.replace` + Added :func:`Version.replace` :param parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` - :return: the new :class:`VersionInfo` object with the changed + :return: the new :class:`Version` object with the changed parts :raises TypeError: if ``parts`` contains invalid keys """ version = self.to_dict() version.update(parts) try: - return VersionInfo(**version) # type: ignore + return Version(**version) # type: ignore except TypeError: unknownkeys = set(parts) - set(self.to_dict()) error = "replace() got %d unexpected keyword " "argument(s): %s" % ( @@ -649,3 +649,7 @@ def isvalid(cls, version: str) -> bool: return True except ValueError: return False + + +# Keep the VersionInfo name for compatibility +VersionInfo = Version diff --git a/tests/conftest.py b/tests/conftest.py index 2e935d0b..153edf0c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,8 +23,8 @@ def version(): Creates a version :return: a version type - :rtype: VersionInfo + :rtype: Version """ - return semver.VersionInfo( + return semver.Version( major=1, minor=2, patch=3, prerelease="alpha.1.2", build="build.11.e0f985a" ) diff --git a/tests/test_compare.py b/tests/test_compare.py index aeb38eb9..1c99f450 100644 --- a/tests/test_compare.py +++ b/tests/test_compare.py @@ -1,7 +1,7 @@ import pytest import semver -from semver import VersionInfo, compare +from semver import Version, compare @pytest.mark.parametrize( @@ -124,15 +124,15 @@ def test_should_get_more_rc1(): def test_should_compare_prerelease_with_numbers_and_letters(): - v1 = VersionInfo(major=1, minor=9, patch=1, prerelease="1unms", build=None) - v2 = VersionInfo(major=1, minor=9, patch=1, prerelease=None, build="1asd") + v1 = Version(major=1, minor=9, patch=1, prerelease="1unms", build=None) + v2 = Version(major=1, minor=9, patch=1, prerelease=None, build="1asd") assert v1 < v2 assert compare("1.9.1-1unms", "1.9.1+1") == -1 def test_should_compare_version_info_objects(): - v1 = VersionInfo(major=0, minor=10, patch=4) - v2 = VersionInfo(major=0, minor=10, patch=4, prerelease="beta.1", build=None) + v1 = Version(major=0, minor=10, patch=4) + v2 = Version(major=0, minor=10, patch=4, prerelease="beta.1", build=None) # use `not` to enforce using comparision operators assert v1 != v2 @@ -142,7 +142,7 @@ def test_should_compare_version_info_objects(): assert not (v1 <= v2) assert not (v1 == v2) - v3 = VersionInfo(major=0, minor=10, patch=4) + v3 = Version(major=0, minor=10, patch=4) assert not (v1 != v3) assert not (v1 > v3) @@ -151,7 +151,7 @@ def test_should_compare_version_info_objects(): assert v1 <= v3 assert v1 == v3 - v4 = VersionInfo(major=0, minor=10, patch=5) + v4 = Version(major=0, minor=10, patch=5) assert v1 != v4 assert not (v1 > v4) assert not (v1 >= v4) @@ -161,7 +161,7 @@ def test_should_compare_version_info_objects(): def test_should_compare_version_dictionaries(): - v1 = VersionInfo(major=0, minor=10, patch=4) + v1 = Version(major=0, minor=10, patch=4) v2 = dict(major=0, minor=10, patch=4, prerelease="beta.1", build=None) assert v1 != v2 @@ -200,8 +200,8 @@ def test_should_compare_version_dictionaries(): ), # fmt: on ) def test_should_compare_version_tuples(t): - v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") - v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") + v0 = Version(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") + v1 = Version(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") assert v0 < t assert v0 <= t @@ -229,8 +229,8 @@ def test_should_compare_version_tuples(t): ), # fmt: on ) def test_should_compare_version_list(lst): - v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") - v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") + v0 = Version(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") + v1 = Version(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") assert v0 < lst assert v0 <= lst @@ -258,8 +258,8 @@ def test_should_compare_version_list(lst): ), # fmt: on ) def test_should_compare_version_string(s): - v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") - v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") + v0 = Version(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4") + v1 = Version(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") assert v0 < s assert v0 <= s @@ -278,7 +278,7 @@ def test_should_compare_version_string(s): @pytest.mark.parametrize("s", ("1", "1.0", "1.0.x")) def test_should_not_allow_to_compare_invalid_versionstring(s): - v = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") + v = Version(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") with pytest.raises(ValueError): v < s with pytest.raises(ValueError): @@ -286,7 +286,7 @@ def test_should_not_allow_to_compare_invalid_versionstring(s): def test_should_not_allow_to_compare_version_with_int(): - v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") + v1 = Version(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") with pytest.raises(TypeError): v1 > 1 with pytest.raises(TypeError): @@ -296,9 +296,9 @@ def test_should_not_allow_to_compare_version_with_int(): def test_should_compare_prerelease_and_build_with_numbers(): - assert VersionInfo(major=1, minor=9, patch=1, prerelease=1, build=1) < VersionInfo( + assert Version(major=1, minor=9, patch=1, prerelease=1, build=1) < Version( major=1, minor=9, patch=1, prerelease=2, build=1 ) - assert VersionInfo(1, 9, 1, 1, 1) < VersionInfo(1, 9, 1, 2, 1) - assert VersionInfo("2") < VersionInfo(10) - assert VersionInfo("2") < VersionInfo("10") + assert Version(1, 9, 1, 1, 1) < Version(1, 9, 1, 2, 1) + assert Version("2") < Version(10) + assert Version("2") < Version("10") diff --git a/tests/test_format.py b/tests/test_format.py index b1c6ad5b..73ff3122 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -1,6 +1,6 @@ import pytest -from semver import VersionInfo, finalize_version, format_version +from semver import Version, finalize_version, format_version @pytest.mark.parametrize( @@ -28,7 +28,7 @@ def test_should_correctly_format_version(): def test_parse_method_for_version_info(): s_version = "1.2.3-alpha.1.2+build.11.e0f985a" - v = VersionInfo.parse(s_version) + v = Version.parse(s_version) assert str(v) == s_version @@ -36,28 +36,28 @@ def test_parse_method_for_version_info(): "version, expected", [ ( - VersionInfo(major=1, minor=2, patch=3, prerelease=None, build=None), - "VersionInfo(major=1, minor=2, patch=3, prerelease=None, build=None)", + Version(major=1, minor=2, patch=3, prerelease=None, build=None), + "Version(major=1, minor=2, patch=3, prerelease=None, build=None)", ), ( - VersionInfo(major=1, minor=2, patch=3, prerelease="r.1", build=None), - "VersionInfo(major=1, minor=2, patch=3, prerelease='r.1', build=None)", + Version(major=1, minor=2, patch=3, prerelease="r.1", build=None), + "Version(major=1, minor=2, patch=3, prerelease='r.1', build=None)", ), ( - VersionInfo(major=1, minor=2, patch=3, prerelease="dev.1", build=None), - "VersionInfo(major=1, minor=2, patch=3, prerelease='dev.1', build=None)", + Version(major=1, minor=2, patch=3, prerelease="dev.1", build=None), + "Version(major=1, minor=2, patch=3, prerelease='dev.1', build=None)", ), ( - VersionInfo(major=1, minor=2, patch=3, prerelease="dev.1", build="b.1"), - "VersionInfo(major=1, minor=2, patch=3, prerelease='dev.1', build='b.1')", + Version(major=1, minor=2, patch=3, prerelease="dev.1", build="b.1"), + "Version(major=1, minor=2, patch=3, prerelease='dev.1', build='b.1')", ), ( - VersionInfo(major=1, minor=2, patch=3, prerelease="r.1", build="b.1"), - "VersionInfo(major=1, minor=2, patch=3, prerelease='r.1', build='b.1')", + Version(major=1, minor=2, patch=3, prerelease="r.1", build="b.1"), + "Version(major=1, minor=2, patch=3, prerelease='r.1', build='b.1')", ), ( - VersionInfo(major=1, minor=2, patch=3, prerelease="r.1", build="build.1"), - "VersionInfo(major=1, minor=2, patch=3, prerelease='r.1', build='build.1')", + Version(major=1, minor=2, patch=3, prerelease="r.1", build="build.1"), + "Version(major=1, minor=2, patch=3, prerelease='r.1', build='build.1')", ), ], ) diff --git a/tests/test_index.py b/tests/test_index.py index d54ea110..79e45025 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,6 +1,6 @@ import pytest -from semver import VersionInfo +from semver import Version @pytest.mark.parametrize( @@ -24,7 +24,7 @@ ], ) def test_version_info_should_be_accessed_with_index(version, index, expected): - version_info = VersionInfo.parse(version) + version_info = Version.parse(version) assert version_info[index] == expected @@ -54,7 +54,7 @@ def test_version_info_should_be_accessed_with_index(version, index, expected): def test_version_info_should_be_accessed_with_slice_object( version, slice_object, expected ): - version_info = VersionInfo.parse(version) + version_info = Version.parse(version) assert version_info[slice_object] == expected @@ -74,7 +74,7 @@ def test_version_info_should_be_accessed_with_slice_object( ], ) def test_version_info_should_throw_index_error(version, index): - version_info = VersionInfo.parse(version) + version_info = Version.parse(version) with pytest.raises(IndexError, match=r"Version part undefined"): version_info[index] @@ -90,6 +90,6 @@ def test_version_info_should_throw_index_error(version, index): ], ) def test_version_info_should_throw_index_error_when_negative_index(version, index): - version_info = VersionInfo.parse(version) + version_info = Version.parse(version) with pytest.raises(IndexError, match=r"Version index cannot be negative"): version_info[index] diff --git a/tests/test_parsing.py b/tests/test_parsing.py index c31cca18..25c55c74 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,6 +1,6 @@ import pytest -from semver import VersionInfo, parse, parse_version_info +from semver import Version, parse, parse_version_info @pytest.mark.parametrize( @@ -58,7 +58,7 @@ def test_parse_version_info_str_hash(): v = parse_version_info(s_version) assert v.__str__() == s_version d = {} - d[v] = "" # to ensure that VersionInfo are hashable + d[v] = "" # to ensure that Version are hashable @pytest.mark.parametrize( @@ -115,12 +115,12 @@ def test_equal_versions_have_equal_hashes(): def test_parse_method_for_version_info(): s_version = "1.2.3-alpha.1.2+build.11.e0f985a" - v = VersionInfo.parse(s_version) + v = Version.parse(s_version) assert str(v) == s_version def test_next_version_with_invalid_parts(): - version = VersionInfo.parse("1.0.1") + version = Version.parse("1.0.1") with pytest.raises(ValueError): version.next_version("invalid") @@ -151,7 +151,7 @@ def test_next_version_with_invalid_parts(): ], ) def test_next_version_with_versioninfo(version, part, expected): - ver = VersionInfo.parse(version) + ver = Version.parse(version) next_version = ver.next_version(part) - assert isinstance(next_version, VersionInfo) + assert isinstance(next_version, Version) assert str(next_version) == expected diff --git a/tests/test_replace.py b/tests/test_replace.py index e8e417a7..f223eddb 100644 --- a/tests/test_replace.py +++ b/tests/test_replace.py @@ -1,6 +1,6 @@ import pytest -from semver import VersionInfo, replace +from semver import Version, replace @pytest.mark.parametrize( @@ -42,9 +42,9 @@ def test_replace_raises_TypeError_for_invalid_keyword_arg(): ], ) def test_should_return_versioninfo_with_replaced_parts(version, parts, expected): - assert VersionInfo.parse(version).replace(**parts) == VersionInfo.parse(expected) + assert Version.parse(version).replace(**parts) == Version.parse(expected) def test_replace_raises_ValueError_for_non_numeric_values(): with pytest.raises(ValueError): - VersionInfo.parse("1.2.3").replace(major="x") + Version.parse("1.2.3").replace(major="x") diff --git a/tests/test_semver.py b/tests/test_semver.py index de4e86f5..b15bfeaf 100644 --- a/tests/test_semver.py +++ b/tests/test_semver.py @@ -1,13 +1,13 @@ import pytest # noqa -from semver import VersionInfo +from semver import Version @pytest.mark.parametrize( "string,expected", [("rc", "rc"), ("rc.1", "rc.2"), ("2x", "3x")] ) def test_should_private_increment_string(string, expected): - assert VersionInfo._increment_string(string) == expected + assert Version._increment_string(string) == expected @pytest.mark.parametrize( @@ -21,7 +21,7 @@ def test_should_private_increment_string(string, expected): ) def test_should_not_allow_negative_numbers(ver): with pytest.raises(ValueError, match=".* is negative. .*"): - VersionInfo(**ver) + Version(**ver) def test_should_versioninfo_to_dict(version): @@ -47,36 +47,36 @@ def test_version_info_should_be_iterable(version): def test_should_be_able_to_use_strings_as_major_minor_patch(): - v = VersionInfo("1", "2", "3") + v = Version("1", "2", "3") assert isinstance(v.major, int) assert isinstance(v.minor, int) assert isinstance(v.patch, int) assert v.prerelease is None assert v.build is None - assert VersionInfo("1", "2", "3") == VersionInfo(1, 2, 3) + assert Version("1", "2", "3") == Version(1, 2, 3) def test_using_non_numeric_string_as_major_minor_patch_throws(): with pytest.raises(ValueError): - VersionInfo("a") + Version("a") with pytest.raises(ValueError): - VersionInfo(1, "a") + Version(1, "a") with pytest.raises(ValueError): - VersionInfo(1, 2, "a") + Version(1, 2, "a") def test_should_be_able_to_use_integers_as_prerelease_build(): - v = VersionInfo(1, 2, 3, 4, 5) + v = Version(1, 2, 3, 4, 5) assert isinstance(v.prerelease, str) assert isinstance(v.build, str) - assert VersionInfo(1, 2, 3, 4, 5) == VersionInfo(1, 2, 3, "4", "5") + assert Version(1, 2, 3, 4, 5) == Version(1, 2, 3, "4", "5") def test_should_versioninfo_isvalid(): - assert VersionInfo.isvalid("1.0.0") is True - assert VersionInfo.isvalid("foo") is False + assert Version.isvalid("1.0.0") is True + assert Version.isvalid("foo") is False def test_versioninfo_compare_should_raise_when_passed_invalid_value(): with pytest.raises(TypeError): - VersionInfo(1, 2, 3).compare(4) + Version(1, 2, 3).compare(4) diff --git a/tests/test_subclass.py b/tests/test_subclass.py index afd10b4a..cbf9d271 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -1,8 +1,8 @@ -from semver import VersionInfo +from semver import Version def test_subclass_from_versioninfo(): - class SemVerWithVPrefix(VersionInfo): + class SemVerWithVPrefix(Version): @classmethod def parse(cls, version): if not version[0] in ("v", "V"): From e1b3851590f7eb34486fd866d4eb78b8fb1683b0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 14:42:53 +0100 Subject: [PATCH 03/16] Doc: Create new section of semver2 vs. semver3 Create a separate file to describe how to migrate to new semver3. --- README.rst | 5 ----- docs/index.rst | 1 + docs/migratetosemver3.rst | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 docs/migratetosemver3.rst diff --git a/README.rst b/README.rst index 03faebab..d4f29819 100644 --- a/README.rst +++ b/README.rst @@ -30,11 +30,6 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. |MAINT| replace:: ``maint/v2`` .. _MAINT: https://github.com/python-semver/python-semver/tree/maint/v2 -.. note:: - - The :class:`VersionInfo` has been renamed to :class:`Version`. An - alias has been created to preserve compatibility but the use of the old - name has been deprecated. The module follows the ``MAJOR.MINOR.PATCH`` style: diff --git a/docs/index.rst b/docs/index.rst index aefdc843..5f44e17f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Semver |version| -- Semantic Versioning install usage + migratetosemver3 development api diff --git a/docs/migratetosemver3.rst b/docs/migratetosemver3.rst new file mode 100644 index 00000000..2bf229c3 --- /dev/null +++ b/docs/migratetosemver3.rst @@ -0,0 +1,16 @@ +.. _semver2-to-3: + +Migrating from semver2 to semver3 +================================= + +This chapter describes the visible differences for +the users and how your code stays compatible for semver3. + + +Use Version instead of VersionInfo +---------------------------------- + +The :class:`VersionInfo` has been renamed to :class:`Version`. +An alias has been created to preserve compatibility but the +use of the old name has been deprecated. + From 37c9e3b6ff7fd586199613b995356d5b40fdeb97 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 15:13:12 +0100 Subject: [PATCH 04/16] Improve API documentation With ideas from https://stackoverflow.com/a/62613202 * Use autosummary Sphinx extension * Extend docs/config.py * Ignore docs/_autosummary * Add module docstring * Add changelog entry * Improve custom.css for sidebar --- .gitignore | 2 +- changelog.d/304.doc.rst | 5 ++ docs/_static/css/custom.css | 5 ++ docs/_templates/autosummary/class.rst | 34 +++++++++++++ docs/_templates/autosummary/module.rst | 66 ++++++++++++++++++++++++++ docs/api.rst | 10 ++-- docs/conf.py | 49 +++++++++++++++++-- src/semver/__about__.py | 17 +++++++ src/semver/__init__.py | 4 ++ src/semver/cli.py | 4 ++ src/semver/version.py | 8 +++- 11 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 changelog.d/304.doc.rst create mode 100644 docs/_templates/autosummary/class.rst create mode 100644 docs/_templates/autosummary/module.rst diff --git a/.gitignore b/.gitignore index ffddda1c..8f76d83e 100644 --- a/.gitignore +++ b/.gitignore @@ -259,7 +259,7 @@ fabric.properties # -------- - # Patch/Diff Files *.patch *.diff +docs/_api diff --git a/changelog.d/304.doc.rst b/changelog.d/304.doc.rst new file mode 100644 index 00000000..8bb722de --- /dev/null +++ b/changelog.d/304.doc.rst @@ -0,0 +1,5 @@ +Several improvements in documentation: + +* Reorganize API documentation. +* Add migration chapter from semver2 to semver3. + diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 33ff51f1..002e6b2f 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -26,6 +26,11 @@ div.related.top nav { margin-top: 0.5em; } +.sphinxsidebarwrapper .caption { + margin-top: 1em; + margin-bottom: -0.75em; +} + .section h1 { font-weight: 700; } diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 00000000..11ccc3c6 --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,34 @@ +{{ fullname | escape | underline}} + +Toms was here! + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :show-inheritance: + :inherited-members: + + {% block methods %} + .. .. automethod:: __init__ + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst new file mode 100644 index 00000000..1772c873 --- /dev/null +++ b/docs/_templates/autosummary/module.rst @@ -0,0 +1,66 @@ +{{ fullname | escape | underline}} + + + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Module Attributes') }} + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/api.rst b/docs/api.rst index 0003fefc..c1845f5b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,6 +3,10 @@ API === -.. automodule:: semver - :members: - :undoc-members: + +.. autosummary:: + :toctree: _api + :recursive: + + semver.__about__ + semver \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 71738daa..55e40b86 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,12 +16,37 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +import codecs import os +import re import sys sys.path.insert(0, os.path.abspath("../src/")) - -from semver import __version__ # noqa: E402 +# from semver import __version__ # noqa: E402 + + +def read(*parts): + """ + Build an absolute path from *parts* and and return the contents of the + resulting file. Assume UTF-8 encoding. + """ + here = os.path.abspath(os.path.dirname(__file__)) + with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: + return f.read() + + +def find_version(*file_paths): + """ + Build a path from *file_paths* and search for a ``__version__`` + string inside. + """ + version_file = read(*file_paths) + version_match = re.search( + r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M + ) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") # -- General configuration ------------------------------------------------ @@ -35,12 +60,26 @@ # ones. extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.autosummary", "sphinx_autodoc_typehints", "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", "sphinx.ext.extlinks", ] +autosummary_generate = True +apidoc_excluded_paths = ['tests'] +autoclass_content = "class" +autodoc_default_options = { + "members": ("__version__," + "__about__, " + "__author__, " + "__author_email__," + "__maintainer__, " + "__maintainer_email__, " + "__description__"), + # "members-order": "groupwise", # alphabetical, groupwise, bysource +} + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -62,9 +101,9 @@ # built documents. # # The short X.Y version. -version = __version__ +release = find_version("../src/semver/__about__.py") # The full version, including alpha/beta/rc tags. -release = version +version = release # .rsplit(u".", 1)[0] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 5e7a3537..4f1edd39 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -1,8 +1,25 @@ +""" +Metadata for semver version, author, maintainers, and +description. +""" + +#: Semver version __version__ = "3.0.0-dev.2" + +#: Original semver author __author__ = "Kostiantyn Rybnikov" + +#: Author's email address __author_email__ = "k-bx@k-bx.com" + +#: Current maintainer __maintainer__ = ["Sebastien Celles", "Tom Schraitle"] + +#: Maintainer's email address __maintainer_email__ = "s.celles@gmail.com" + +#: Short description about semver __description__ = "Python helper for Semantic Versioning (http://semver.org)" +#: Supported semver specification SEMVER_SPEC_VERSION = "2.0.0" diff --git a/src/semver/__init__.py b/src/semver/__init__.py index 1a80f6c3..fe9c3dd7 100644 --- a/src/semver/__init__.py +++ b/src/semver/__init__.py @@ -1,3 +1,7 @@ +""" +semver package +""" + from ._deprecated import ( bump_build, bump_major, diff --git a/src/semver/cli.py b/src/semver/cli.py index 1514979c..21a547c5 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -1,3 +1,7 @@ +""" +CLI parsing for :command:`pysemver` command. +""" + import argparse import sys from typing import cast, List diff --git a/src/semver/version.py b/src/semver/version.py index 93a6d338..4699905d 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -1,3 +1,6 @@ +""" +Version handling +""" import collections import re from functools import wraps @@ -414,7 +417,7 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": The "major", "minor", and "patch" raises the respective parts like the ``bump_*`` functions. The real difference is using the "preprelease" part. It gives you the next patch version of the - prerelease, for example: + prerelease, for example: >>> str(semver.parse("0.1.4").next_version("prerelease")) '0.1.5-rc.1' @@ -550,6 +553,7 @@ def match(self, match_expr: str) -> bool: == equal != not equal :return: True if the expression matches the version, otherwise False + >>> semver.Version.parse("2.0.0").match(">=1.0.0") True >>> semver.Version.parse("1.0.0").match(">1.0.0") @@ -591,9 +595,11 @@ def parse(cls, version: String) -> "Version": .. versionchanged:: 2.11.0 Changed method from static to classmethod to allow subclasses. + :param version: version string :return: a :class:`VersionInfo` instance :raises ValueError: if version is invalid + >>> semver.Version.parse('3.4.5-pre.2+build.4') VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') From 5e5dd98f1cbcbf39ee079b7ee9a0b7a3bd2a66c3 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 16:20:38 +0100 Subject: [PATCH 05/16] Distinguish between changlog for version 2 and 3 Split changelog entries into semver 3 (new) and semver 2 (old). --- CHANGELOG.rst | 317 ------------- docs/changelog-2.7.9-and-before.rst | 353 --------------- docs/changelog-semver2.rst | 670 ++++++++++++++++++++++++++++ docs/index.rst | 2 +- 4 files changed, 671 insertions(+), 671 deletions(-) delete mode 100644 docs/changelog-2.7.9-and-before.rst create mode 100644 docs/changelog-semver2.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e529404..00bc7813 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -102,323 +102,6 @@ Trivial/Internal Changes * :pr:`290`: Add supported Python versions to :command:`black`. - ----- - - -Version 2.13.0 -============== - -:Released: 2020-10-20 -:Maintainer: Tom Schraitle - - -Features --------- - -* :pr:`287`: Document how to create subclass from ``VersionInfo`` - - -Bug Fixes ---------- - -* :pr:`283`: Ensure equal versions have equal hashes. - Version equality means for semver, that ``major``, - ``minor``, ``patch``, and ``prerelease`` parts are - equal in both versions you compare. The ``build`` part - is ignored. - - -Additions ---------- - -n/a - - -Deprecations ------------- - -n/a - - ----- - - -Version 2.12.0 -============== - -:Released: 2020-10-19 -:Maintainer: Tom Schraitle - - -Bug Fixes ---------- - -* :gh:`291` (:pr:`292`): Disallow negative numbers of - ``major``, ``minor``, and ``patch`` for :class:`semver.VersionInfo` - - ----- - - -Version 2.11.0 -============== - -:Released: 2020-10-17 -:Maintainer: Tom Schraitle - - -Bug Fixes ---------- - -* :gh:`276` (:pr:`277`): ``VersionInfo.parse`` should be a class method - Also add authors and update changelog in :gh:`286` -* :gh:`274` (:pr:`275`): Py2 vs. Py3 incompatibility TypeError - - ----- - - -Version 2.10.2 -============== - -:Released: 2020-06-15 -:Maintainer: Tom Schraitle - -Features --------- - -:gh:`268`: Increase coverage - - -Bug Fixes ---------- - -* :gh:`260` (:pr:`261`): Fixed ``__getitem__`` returning None on wrong parts -* :pr:`263`: Doc: Add missing "install" subcommand for openSUSE - - -Deprecations ------------- - -* :gh:`160` (:pr:`264`): - * :func:`semver.max_ver` - * :func:`semver.min_ver` - - ----- - - -Version 2.10.1 -============== - -:Released: 2020-05-13 -:Maintainer: Tom Schraitle - - -Features --------- - -* :pr:`249`: Added release policy and version restriction in documentation to - help our users which would like to stay on the major 2 release. -* :pr:`250`: Simplified installation semver on openSUSE with ``obs://``. -* :pr:`256`: Made docstrings consistent - - - -Bug Fixes ---------- - -* :gh:`251` (:pr:`254`): Fixed return type of ``semver.VersionInfo.next_version`` - to always return a ``VersionInfo`` instance. - - ----- - - - -Version 2.10.0 -============== - -:Released: 2020-05-05 -:Maintainer: Tom Schraitle - -Features --------- - -* :pr:`138`: Added ``__getitem__`` magic method to ``semver.VersionInfo`` class. - Allows to access a version like ``version[1]``. -* :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising - the old and deprecated module-level functions. -* :pr:`230`: Add version information in some functions: - - * Use ``.. versionadded::`` RST directive in docstrings to - make it more visible when something was added - * Minor wording fix in docstrings (versions -> version strings) - - -Bug Fixes ---------- - -* :gh:`224` (:pr:`226`): In ``setup.py``, replaced in class ``clean``, - ``super(CleanCommand, self).run()`` with ``CleanCommand.run(self)`` -* :gh:`244` (:pr:`245`): Allow comparison with ``VersionInfo``, tuple/list, dict, and string. - - -Additions ---------- - -* :pr:`228`: Added better doctest integration - - -Deprecations ------------- -* :gh:`225` (:pr:`229`): Output a DeprecationWarning for the following functions: - - - ``semver.parse`` - - ``semver.parse_version_info`` - - ``semver.format_version`` - - ``semver.bump_{major,minor,patch,prerelease,build}`` - - ``semver.finalize_version`` - - ``semver.replace`` - - ``semver.VersionInfo._asdict`` (use the new, public available - function ``semver.VersionInfo.to_dict()``) - - ``semver.VersionInfo._astuple`` (use the new, public available - function ``semver.VersionInfo.to_tuple()``) - - These deprecated functions will be removed in semver 3. - - ----- - - -Version 2.9.1 -============= -:Released: 2020-02-16 -:Maintainer: Tom Schraitle - -Features --------- - -* :gh:`177` (:pr:`178`): Fixed repository and CI links (moved https://github.com/k-bx/python-semver/ repository to https://github.com/python-semver/python-semver/) -* :pr:`179`: Added note about moving this project to the new python-semver organization on GitHub -* :gh:`187` (:pr:`188`): Added logo for python-semver organization and documentation -* :gh:`191` (:pr:`194`): Created manpage for pysemver -* :gh:`196` (:pr:`197`): Added distribution specific installation instructions -* :gh:`201` (:pr:`202`): Reformatted source code with black -* :gh:`208` (:pr:`209`): Introduce new function :func:`semver.VersionInfo.isvalid` - and extend :command:`pysemver` with :command:`check` subcommand -* :gh:`210` (:pr:`215`): Document how to deal with invalid versions -* :pr:`212`: Improve docstrings according to PEP257 - -Bug Fixes ---------- - -* :gh:`192` (:pr:`193`): Fixed "pysemver" and "pysemver bump" when called without arguments - - ----- - -Version 2.9.0 -============= -:Released: 2019-10-30 -:Maintainer: Sébastien Celles - -Features --------- - -* :gh:`59` (:pr:`164`): Implemented a command line interface -* :gh:`85` (:pr:`147`, :pr:`154`): Improved contribution section -* :gh:`104` (:pr:`125`): Added iterator to :func:`semver.VersionInfo` -* :gh:`112`, :gh:`113`: Added Python 3.7 support -* :pr:`120`: Improved test_immutable function with properties -* :pr:`125`: Created :file:`setup.cfg` for pytest and tox -* :gh:`126` (:pr:`127`): Added target for documentation in :file:`tox.ini` -* :gh:`142` (:pr:`143`): Improved usage section -* :gh:`144` (:pr:`156`): Added :func:`semver.replace` and :func:`semver.VersionInfo.replace` - functions -* :gh:`145` (:pr:`146`): Added posargs in :file:`tox.ini` -* :pr:`157`: Introduce :file:`conftest.py` to improve doctests -* :pr:`165`: Improved code coverage -* :pr:`166`: Reworked :file:`.gitignore` file -* :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` - -Bug Fixes ---------- - -* :gh:`102`: Fixed comparison between VersionInfo and tuple -* :gh:`103`: Disallow comparison between VersionInfo and string (and int) -* :gh:`121` (:pr:`122`): Use python3 instead of python3.4 in :file:`tox.ini` -* :pr:`123`: Improved :func:`__repr__` and derive class name from :func:`type` -* :gh:`128` (:pr:`129`): Fixed wrong datatypes in docstring for :func:`semver.format_version` -* :gh:`135` (:pr:`140`): Converted prerelease and build to string -* :gh:`136` (:pr:`151`): Added testsuite to tarball -* :gh:`154` (:pr:`155`): Improved README description - -Removals --------- - -* :gh:`111` (:pr:`110`): Dropped Python 3.3 -* :gh:`148` (:pr:`149`): Removed and replaced ``python setup.py test`` - - ----- - -Version 2.8.2 -============= -:Released: 2019-05-19 -:Maintainer: Sébastien Celles - -Skipped, not released. - ----- - -Version 2.8.1 -============= -:Released: 2018-07-09 -:Maintainer: Sébastien Celles - -Features --------- - -* :gh:`40` (:pr:`88`): Added a static parse method to VersionInfo -* :gh:`77` (:pr:`47`): Converted multiple tests into pytest.mark.parametrize -* :gh:`87`, :gh:`94` (:pr:`93`): Removed named tuple inheritance. -* :gh:`89` (:pr:`90`): Added doctests. - -Bug Fixes ---------- - -* :gh:`98` (:pr:`99`): Set prerelease and build to None by default -* :gh:`96` (:pr:`97`): Made VersionInfo immutable - - ----- - -Version 2.8.0 -============= -:Released: 2018-05-16 -:Maintainer: Sébastien Celles - - -Changes -------- - -* :gh:`82` (:pr:`83`): Renamed :file:`test.py` to :file:`test_semver.py` so - py.test can autodiscover test file - -Additions ---------- - -* :gh:`79` (:pr:`81`, :pr:`84`): Defined and improve a release procedure file -* :gh:`72`, :gh:`73` (:pr:`75`): Implemented :func:`__str__` and :func:`__hash__` - -Removals --------- - -* :gh:`76` (:pr:`80`): Removed Python 2.6 compatibility - - .. Local variables: coding: utf-8 diff --git a/docs/changelog-2.7.9-and-before.rst b/docs/changelog-2.7.9-and-before.rst deleted file mode 100644 index f7acc1e1..00000000 --- a/docs/changelog-2.7.9-and-before.rst +++ /dev/null @@ -1,353 +0,0 @@ -################ -Older Change Log -################ - -This changelog contains older entries from -2.7.9 and before. - -Version 2.7.9 -============= - -:Released: 2017-09-23 -:Maintainer: Kostiantyn Rybnikov - - -Additions ---------- - -* :gh:`65` (:pr:`66`): Added :func:`semver.finalize_version` function. - - ----- - -Version 2.7.8 -============= - -:Released: 2017-08-25 -:Maintainer: Kostiantyn Rybnikov - -* :gh:`62`: Support custom default names for pre and build - - ----- - -Version 2.7.7 -============= - -:Released: 2017-05-25 -:Maintainer: Kostiantyn Rybnikov - -* :gh:`54` (:pr:`55`): Added comparision between VersionInfo objects -* :pr:`56`: Added support for Python 3.6 - - ----- - -Version 2.7.2 -============= - -:Released: 2016-11-08 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Added :func:`semver.parse_version_info` to parse a version string to a - version info tuple. - -Bug Fixes ---------- - -* :gh:`37`: Removed trailing zeros from prelease doesn't allow to - parse 0 pre-release version - -* Refine parsing to conform more strictly to SemVer 2.0.0. - - SemVer 2.0.0 specification §9 forbids leading zero on identifiers in - the prerelease version. - - ----- - -Version 2.6.0 -============= - -:Released: 2016-06-08 -:Maintainer: Kostiantyn Rybnikov - -Removals --------- - -* Remove comparison of build component. - - SemVer 2.0.0 specification recommends that build component is - ignored in comparisons. - - ----- - -Version 2.5.0 -============= - -:Released: 2016-05-25 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Support matching 'not equal' with “!=”. - -Changes -------- - -* Made separate builds for tests on Travis CI. - - ----- - -Version 2.4.2 -============= - -:Released: 2016-05-16 -:Maintainer: Kostiantyn Rybnikov - -Changes -------- - -* Migrated README document to reStructuredText format. - -* Used Setuptools for distribution management. - -* Migrated test cases to Py.test. - -* Added configuration for Tox test runner. - - ----- - -Version 2.4.1 -============= - -:Released: 2016-03-04 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* :gh:`23`: Compared build component of a version. - - ----- - -Version 2.4.0 -============= - -:Released: 2016-02-12 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* :gh:`21`: Compared alphanumeric components correctly. - - ----- - -Version 2.3.1 -============= - -:Released: 2016-01-30 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Declared granted license name in distribution metadata. - - ----- - -Version 2.3.0 -============= - -:Released: 2016-01-29 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Added functions to increment prerelease and build components in a - version. - - ----- - -Version 2.2.1 -============= - -:Released: 2015-08-04 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Corrected comparison when any component includes zero. - - ----- - -Version 2.2.0 -============= - -:Released: 2015-06-21 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Add functions to determined minimum and maximum version. - -* Add code examples for recently-added functions. - - ----- - -Version 2.1.2 -============= - -:Released: 2015-05-23 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Restored current README document to distribution manifest. - - ----- - -Version 2.1.1 -============= - -:Released: 2015-05-23 -:Maintainer: Kostiantyn Rybnikov - -Bug Fixes ---------- - -* Removed absent document from distribution manifest. - - ----- - -Version 2.1.0 -============= - -:Released: 2015-05-22 -:Maintainer: Kostiantyn Rybnikov - -Additions ---------- - -* Documented installation instructions. - -* Documented project home page. - -* Added function to format a version string from components. - -* Added functions to increment specific components in a version. - -Changes -------- - -* Migrated README document to Markdown format. - -Bug Fixes ---------- - -* Corrected code examples in README document. - - ----- - -Version 2.0.2 -============= - -:Released: 2015-04-14 -:Maintainer: Konstantine Rybnikov - -Additions ---------- - -* Added configuration for Travis continuous integration. - -* Explicitly declared supported Python versions. - - ----- - -Version 2.0.1 -============= - -:Released: 2014-09-24 -:Maintainer: Konstantine Rybnikov - -Bug Fixes ---------- - -* :gh:`9`: Fixed comparison of equal version strings. - - ----- - -Version 2.0.0 -============= - -:Released: 2014-05-24 -:Maintainer: Konstantine Rybnikov - -Additions ---------- - -* Grant license in this code base under BSD 3-clause license terms. - -Changes -------- - -* Update parser to SemVer standard 2.0.0. - -* Ignore build component for comparison. - - ----- - -Version 0.0.2 -============= - -:Released: 2012-05-10 -:Maintainer: Konstantine Rybnikov - -Changes -------- - -* Use standard library Distutils for distribution management. - - ----- - -Version 0.0.1 -============= - -:Released: 2012-04-28 -:Maintainer: Konstantine Rybnikov - -* Initial release. - - -.. - Local variables: - coding: utf-8 - mode: text - mode: rst - End: - vim: fileencoding=utf-8 filetype=rst : diff --git a/docs/changelog-semver2.rst b/docs/changelog-semver2.rst new file mode 100644 index 00000000..28546d4d --- /dev/null +++ b/docs/changelog-semver2.rst @@ -0,0 +1,670 @@ +################## +Change Log semver2 +################## + +This changelog contains older entries for semver2. + +---- + + +Version 2.13.0 +============== + +:Released: 2020-10-20 +:Maintainer: Tom Schraitle + + +Features +-------- + +* :pr:`287`: Document how to create subclass from ``VersionInfo`` + + +Bug Fixes +--------- + +* :pr:`283`: Ensure equal versions have equal hashes. + Version equality means for semver, that ``major``, + ``minor``, ``patch``, and ``prerelease`` parts are + equal in both versions you compare. The ``build`` part + is ignored. + + +Additions +--------- + +n/a + + +Deprecations +------------ + +n/a + + +---- + + +Version 2.12.0 +============== + +:Released: 2020-10-19 +:Maintainer: Tom Schraitle + + +Bug Fixes +--------- + +* :gh:`291` (:pr:`292`): Disallow negative numbers of + ``major``, ``minor``, and ``patch`` for :class:`semver.VersionInfo` + + +---- + + +Version 2.11.0 +============== + +:Released: 2020-10-17 +:Maintainer: Tom Schraitle + + +Bug Fixes +--------- + +* :gh:`276` (:pr:`277`): ``VersionInfo.parse`` should be a class method + Also add authors and update changelog in :gh:`286` +* :gh:`274` (:pr:`275`): Py2 vs. Py3 incompatibility TypeError + + +---- + + +Version 2.10.2 +============== + +:Released: 2020-06-15 +:Maintainer: Tom Schraitle + +Features +-------- + +:gh:`268`: Increase coverage + + +Bug Fixes +--------- + +* :gh:`260` (:pr:`261`): Fixed ``__getitem__`` returning None on wrong parts +* :pr:`263`: Doc: Add missing "install" subcommand for openSUSE + + +Deprecations +------------ + +* :gh:`160` (:pr:`264`): + * :func:`semver.max_ver` + * :func:`semver.min_ver` + + +---- + + +Version 2.10.1 +============== + +:Released: 2020-05-13 +:Maintainer: Tom Schraitle + + +Features +-------- + +* :pr:`249`: Added release policy and version restriction in documentation to + help our users which would like to stay on the major 2 release. +* :pr:`250`: Simplified installation semver on openSUSE with ``obs://``. +* :pr:`256`: Made docstrings consistent + + + +Bug Fixes +--------- + +* :gh:`251` (:pr:`254`): Fixed return type of ``semver.VersionInfo.next_version`` + to always return a ``VersionInfo`` instance. + + +---- + + + +Version 2.10.0 +============== + +:Released: 2020-05-05 +:Maintainer: Tom Schraitle + +Features +-------- + +* :pr:`138`: Added ``__getitem__`` magic method to ``semver.VersionInfo`` class. + Allows to access a version like ``version[1]``. +* :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising + the old and deprecated module-level functions. +* :pr:`230`: Add version information in some functions: + + * Use ``.. versionadded::`` RST directive in docstrings to + make it more visible when something was added + * Minor wording fix in docstrings (versions -> version strings) + + +Bug Fixes +--------- + +* :gh:`224` (:pr:`226`): In ``setup.py``, replaced in class ``clean``, + ``super(CleanCommand, self).run()`` with ``CleanCommand.run(self)`` +* :gh:`244` (:pr:`245`): Allow comparison with ``VersionInfo``, tuple/list, dict, and string. + + +Additions +--------- + +* :pr:`228`: Added better doctest integration + + +Deprecations +------------ +* :gh:`225` (:pr:`229`): Output a DeprecationWarning for the following functions: + + - ``semver.parse`` + - ``semver.parse_version_info`` + - ``semver.format_version`` + - ``semver.bump_{major,minor,patch,prerelease,build}`` + - ``semver.finalize_version`` + - ``semver.replace`` + - ``semver.VersionInfo._asdict`` (use the new, public available + function ``semver.VersionInfo.to_dict()``) + - ``semver.VersionInfo._astuple`` (use the new, public available + function ``semver.VersionInfo.to_tuple()``) + + These deprecated functions will be removed in semver 3. + + +---- + + +Version 2.9.1 +============= +:Released: 2020-02-16 +:Maintainer: Tom Schraitle + +Features +-------- + +* :gh:`177` (:pr:`178`): Fixed repository and CI links (moved https://github.com/k-bx/python-semver/ repository to https://github.com/python-semver/python-semver/) +* :pr:`179`: Added note about moving this project to the new python-semver organization on GitHub +* :gh:`187` (:pr:`188`): Added logo for python-semver organization and documentation +* :gh:`191` (:pr:`194`): Created manpage for pysemver +* :gh:`196` (:pr:`197`): Added distribution specific installation instructions +* :gh:`201` (:pr:`202`): Reformatted source code with black +* :gh:`208` (:pr:`209`): Introduce new function :func:`semver.VersionInfo.isvalid` + and extend :command:`pysemver` with :command:`check` subcommand +* :gh:`210` (:pr:`215`): Document how to deal with invalid versions +* :pr:`212`: Improve docstrings according to PEP257 + +Bug Fixes +--------- + +* :gh:`192` (:pr:`193`): Fixed "pysemver" and "pysemver bump" when called without arguments + + +---- + +Version 2.9.0 +============= +:Released: 2019-10-30 +:Maintainer: Sébastien Celles + +Features +-------- + +* :gh:`59` (:pr:`164`): Implemented a command line interface +* :gh:`85` (:pr:`147`, :pr:`154`): Improved contribution section +* :gh:`104` (:pr:`125`): Added iterator to :func:`semver.VersionInfo` +* :gh:`112`, :gh:`113`: Added Python 3.7 support +* :pr:`120`: Improved test_immutable function with properties +* :pr:`125`: Created :file:`setup.cfg` for pytest and tox +* :gh:`126` (:pr:`127`): Added target for documentation in :file:`tox.ini` +* :gh:`142` (:pr:`143`): Improved usage section +* :gh:`144` (:pr:`156`): Added :func:`semver.replace` and :func:`semver.VersionInfo.replace` + functions +* :gh:`145` (:pr:`146`): Added posargs in :file:`tox.ini` +* :pr:`157`: Introduce :file:`conftest.py` to improve doctests +* :pr:`165`: Improved code coverage +* :pr:`166`: Reworked :file:`.gitignore` file +* :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` + +Bug Fixes +--------- + +* :gh:`102`: Fixed comparison between VersionInfo and tuple +* :gh:`103`: Disallow comparison between VersionInfo and string (and int) +* :gh:`121` (:pr:`122`): Use python3 instead of python3.4 in :file:`tox.ini` +* :pr:`123`: Improved :func:`__repr__` and derive class name from :func:`type` +* :gh:`128` (:pr:`129`): Fixed wrong datatypes in docstring for :func:`semver.format_version` +* :gh:`135` (:pr:`140`): Converted prerelease and build to string +* :gh:`136` (:pr:`151`): Added testsuite to tarball +* :gh:`154` (:pr:`155`): Improved README description + +Removals +-------- + +* :gh:`111` (:pr:`110`): Dropped Python 3.3 +* :gh:`148` (:pr:`149`): Removed and replaced ``python setup.py test`` + + +---- + +Version 2.8.2 +============= +:Released: 2019-05-19 +:Maintainer: Sébastien Celles + +Skipped, not released. + +---- + +Version 2.8.1 +============= +:Released: 2018-07-09 +:Maintainer: Sébastien Celles + +Features +-------- + +* :gh:`40` (:pr:`88`): Added a static parse method to VersionInfo +* :gh:`77` (:pr:`47`): Converted multiple tests into pytest.mark.parametrize +* :gh:`87`, :gh:`94` (:pr:`93`): Removed named tuple inheritance. +* :gh:`89` (:pr:`90`): Added doctests. + +Bug Fixes +--------- + +* :gh:`98` (:pr:`99`): Set prerelease and build to None by default +* :gh:`96` (:pr:`97`): Made VersionInfo immutable + + +---- + +Version 2.8.0 +============= +:Released: 2018-05-16 +:Maintainer: Sébastien Celles + + +Changes +------- + +* :gh:`82` (:pr:`83`): Renamed :file:`test.py` to :file:`test_semver.py` so + py.test can autodiscover test file + +Additions +--------- + +* :gh:`79` (:pr:`81`, :pr:`84`): Defined and improve a release procedure file +* :gh:`72`, :gh:`73` (:pr:`75`): Implemented :func:`__str__` and :func:`__hash__` + +Removals +-------- + +* :gh:`76` (:pr:`80`): Removed Python 2.6 compatibility + +---- + + +Version 2.7.9 +============= + +:Released: 2017-09-23 +:Maintainer: Kostiantyn Rybnikov + + +Additions +--------- + +* :gh:`65` (:pr:`66`): Added :func:`semver.finalize_version` function. + + +---- + +Version 2.7.8 +============= + +:Released: 2017-08-25 +:Maintainer: Kostiantyn Rybnikov + +* :gh:`62`: Support custom default names for pre and build + + +---- + +Version 2.7.7 +============= + +:Released: 2017-05-25 +:Maintainer: Kostiantyn Rybnikov + +* :gh:`54` (:pr:`55`): Added comparision between VersionInfo objects +* :pr:`56`: Added support for Python 3.6 + + +---- + +Version 2.7.2 +============= + +:Released: 2016-11-08 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Added :func:`semver.parse_version_info` to parse a version string to a + version info tuple. + +Bug Fixes +--------- + +* :gh:`37`: Removed trailing zeros from prelease doesn't allow to + parse 0 pre-release version + +* Refine parsing to conform more strictly to SemVer 2.0.0. + + SemVer 2.0.0 specification §9 forbids leading zero on identifiers in + the prerelease version. + + +---- + +Version 2.6.0 +============= + +:Released: 2016-06-08 +:Maintainer: Kostiantyn Rybnikov + +Removals +-------- + +* Remove comparison of build component. + + SemVer 2.0.0 specification recommends that build component is + ignored in comparisons. + + +---- + +Version 2.5.0 +============= + +:Released: 2016-05-25 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Support matching 'not equal' with “!=”. + +Changes +------- + +* Made separate builds for tests on Travis CI. + + +---- + +Version 2.4.2 +============= + +:Released: 2016-05-16 +:Maintainer: Kostiantyn Rybnikov + +Changes +------- + +* Migrated README document to reStructuredText format. + +* Used Setuptools for distribution management. + +* Migrated test cases to Py.test. + +* Added configuration for Tox test runner. + + +---- + +Version 2.4.1 +============= + +:Released: 2016-03-04 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* :gh:`23`: Compared build component of a version. + + +---- + +Version 2.4.0 +============= + +:Released: 2016-02-12 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* :gh:`21`: Compared alphanumeric components correctly. + + +---- + +Version 2.3.1 +============= + +:Released: 2016-01-30 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Declared granted license name in distribution metadata. + + +---- + +Version 2.3.0 +============= + +:Released: 2016-01-29 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Added functions to increment prerelease and build components in a + version. + + +---- + +Version 2.2.1 +============= + +:Released: 2015-08-04 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Corrected comparison when any component includes zero. + + +---- + +Version 2.2.0 +============= + +:Released: 2015-06-21 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Add functions to determined minimum and maximum version. + +* Add code examples for recently-added functions. + + +---- + +Version 2.1.2 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Restored current README document to distribution manifest. + + +---- + +Version 2.1.1 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Removed absent document from distribution manifest. + + +---- + +Version 2.1.0 +============= + +:Released: 2015-05-22 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Documented installation instructions. + +* Documented project home page. + +* Added function to format a version string from components. + +* Added functions to increment specific components in a version. + +Changes +------- + +* Migrated README document to Markdown format. + +Bug Fixes +--------- + +* Corrected code examples in README document. + + +---- + +Version 2.0.2 +============= + +:Released: 2015-04-14 +:Maintainer: Konstantine Rybnikov + +Additions +--------- + +* Added configuration for Travis continuous integration. + +* Explicitly declared supported Python versions. + + +---- + +Version 2.0.1 +============= + +:Released: 2014-09-24 +:Maintainer: Konstantine Rybnikov + +Bug Fixes +--------- + +* :gh:`9`: Fixed comparison of equal version strings. + + +---- + +Version 2.0.0 +============= + +:Released: 2014-05-24 +:Maintainer: Konstantine Rybnikov + +Additions +--------- + +* Grant license in this code base under BSD 3-clause license terms. + +Changes +------- + +* Update parser to SemVer standard 2.0.0. + +* Ignore build component for comparison. + + +---- + +Version 0.0.2 +============= + +:Released: 2012-05-10 +:Maintainer: Konstantine Rybnikov + +Changes +------- + +* Use standard library Distutils for distribution management. + + +---- + +Version 0.0.1 +============= + +:Released: 2012-04-28 +:Maintainer: Konstantine Rybnikov + +* Initial release. + + +.. + Local variables: + coding: utf-8 + mode: text + mode: rst + End: + vim: fileencoding=utf-8 filetype=rst : diff --git a/docs/index.rst b/docs/index.rst index 5f44e17f..405d9e27 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,7 @@ Semver |version| -- Semantic Versioning :hidden: changelog - changelog-2.7.9-and-before + changelog-semver2 Indices and Tables ================== From 30a556822bf6693190a09db52a41c38eeb79e1dd Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 16:20:56 +0100 Subject: [PATCH 06/16] Document migration from semver2 to semver3 --- docs/migratetosemver3.rst | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/migratetosemver3.rst b/docs/migratetosemver3.rst index 2bf229c3..d6d90954 100644 --- a/docs/migratetosemver3.rst +++ b/docs/migratetosemver3.rst @@ -4,13 +4,39 @@ Migrating from semver2 to semver3 ================================= This chapter describes the visible differences for -the users and how your code stays compatible for semver3. +users and how your code stays compatible for semver3. + +Although the development team tries to make the transition +to semver3 as smooth as possible, at some point change +is inevitable. + +For a more detailed overview of all the changes, refer +to our :ref:`changelog`. Use Version instead of VersionInfo ---------------------------------- -The :class:`VersionInfo` has been renamed to :class:`Version`. -An alias has been created to preserve compatibility but the -use of the old name has been deprecated. +The :class:`VersionInfo` has been renamed to :class:`Version` +to have a more succinct name. +An alias has been created to preserve compatibility but +using old name has been deprecated. + +If you still need the old version, use this line: + +.. code-block:: python + + from semver.version import Version as VersionInfo + + + +Use semver.cli instead of semver +-------------------------------- + +All functions related to CLI parsing are moved to :mod:`semver.cli`. +If you are such functions, like :func:`semver.cmd_bump `, +import it from :mod:`semver.cli` in the future: + +.. code-block:: python + from semver.cli import cmd_bump \ No newline at end of file From 87114946694bd8b9fd1664bf1ecd374bccdd0f12 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 17:20:53 +0100 Subject: [PATCH 07/16] Correct black and docformatter issues --- docs/conf.py | 22 +++++++++++----------- src/semver/__about__.py | 5 +---- src/semver/__init__.py | 4 +--- src/semver/cli.py | 4 +--- src/semver/ranges.lark | 20 ++++++++++++++++++++ src/semver/version.py | 5 ++--- 6 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 src/semver/ranges.lark diff --git a/docs/conf.py b/docs/conf.py index 55e40b86..454d6be5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,9 +41,7 @@ def find_version(*file_paths): string inside. """ version_file = read(*file_paths) - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M - ) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") @@ -67,16 +65,18 @@ def find_version(*file_paths): ] autosummary_generate = True -apidoc_excluded_paths = ['tests'] +apidoc_excluded_paths = ["tests"] autoclass_content = "class" autodoc_default_options = { - "members": ("__version__," - "__about__, " - "__author__, " - "__author_email__," - "__maintainer__, " - "__maintainer_email__, " - "__description__"), + "members": ( + "__version__," + "__about__, " + "__author__, " + "__author_email__," + "__maintainer__, " + "__maintainer_email__, " + "__description__" + ), # "members-order": "groupwise", # alphabetical, groupwise, bysource } diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 4f1edd39..2d8807cf 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -1,7 +1,4 @@ -""" -Metadata for semver version, author, maintainers, and -description. -""" +"""Metadata for semver version, author, maintainers, and description.""" #: Semver version __version__ = "3.0.0-dev.2" diff --git a/src/semver/__init__.py b/src/semver/__init__.py index fe9c3dd7..f406b916 100644 --- a/src/semver/__init__.py +++ b/src/semver/__init__.py @@ -1,6 +1,4 @@ -""" -semver package -""" +"""semver package.""" from ._deprecated import ( bump_build, diff --git a/src/semver/cli.py b/src/semver/cli.py index 21a547c5..ca400373 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -1,6 +1,4 @@ -""" -CLI parsing for :command:`pysemver` command. -""" +"""CLI parsing for :command:`pysemver` command.""" import argparse import sys diff --git a/src/semver/ranges.lark b/src/semver/ranges.lark new file mode 100644 index 00000000..dc9e644f --- /dev/null +++ b/src/semver/ranges.lark @@ -0,0 +1,20 @@ +start: range ( logical_or range)* +logical_or: ( " " ) * "||" ( " " )* +range: hyphen | simple ( " " simple )* +hyphen: partial " - " partial +simple: primitive | partial | tilde | caret +primitive: ( "<" | ">" | ">=" | "<=" | "=" ) partial +partial: xr ( "." xr ( "." xr qualifier ? )? )? +xr: "x" | "X" | "*" | NUMBER+ +// nr: 0 | [1-9] ( [0-9] )* +tilde: "~" partial +caret: "^" partial +qualifier: ( "-" pre )? ( "+" build )? +pre: parts +build: parts +parts: part ( "." part ) * +part: NUMBER | ("-" NUMBER)+ + +%import common.WS +%import common.NUMBER +%ignore WS \ No newline at end of file diff --git a/src/semver/version.py b/src/semver/version.py index 4699905d..cd08f642 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -1,6 +1,5 @@ -""" -Version handling -""" +"""Version handling.""" + import collections import re from functools import wraps From fa7b4404833ce9a024bf496bcf000161ada8aa8c Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Fri, 30 Oct 2020 12:36:03 -0400 Subject: [PATCH 08/16] Update changelog.d/169.feature.rst Co-authored-by: Tom Schraitle --- changelog.d/169.feature.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/changelog.d/169.feature.rst b/changelog.d/169.feature.rst index 4bfe0fcb..1b762676 100644 --- a/changelog.d/169.feature.rst +++ b/changelog.d/169.feature.rst @@ -6,6 +6,5 @@ Create semver package and split code among different modules in the packages. * Create :file:`src/semver/_deprecated.py` for the ``deprecated`` decorator and other deprecated functions * Create :file:`src/semver/__main__.py` to allow calling the CLI using :command:`python -m semver` * Create :file:`src/semver/_types.py` to hold type aliases -* Create :file:`src/semver/version.py` to hold the :class:`VersionInfo` class and its utility functions +* Create :file:`src/semver/version.py` to hold the :class:`Version` class (old name :class:`VersionInfo`) and its utility functions * Create :file:`src/semver/__about__.py` for all the metadata variables - From 7838196c8bf817764dbe55a0b03e868960b47b52 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Fri, 30 Oct 2020 12:36:13 -0400 Subject: [PATCH 09/16] Update docs/coerce.py Co-authored-by: Tom Schraitle --- docs/coerce.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/coerce.py b/docs/coerce.py index 9fe87276..dac1d5d0 100644 --- a/docs/coerce.py +++ b/docs/coerce.py @@ -15,7 +15,7 @@ ) -def coerce(version): +def coerce(version: str) -> tuple[Version, Optional[str]]: """ Convert an incomplete version string into a semver-compatible Version object From 0b60d1503b73be56bc8cbc782d6159810e07de04 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 30 Oct 2020 19:01:07 +0100 Subject: [PATCH 10/16] Fix Travis error in doctests --- docs/coerce.py | 8 +++++--- tests/conftest.py | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/coerce.py b/docs/coerce.py index dac1d5d0..7da20315 100644 --- a/docs/coerce.py +++ b/docs/coerce.py @@ -1,5 +1,7 @@ import re -import semver +from semver import Version +from typing import Optional, Tuple + BASEVERSION = re.compile( r"""[vV]? @@ -15,7 +17,7 @@ ) -def coerce(version: str) -> tuple[Version, Optional[str]]: +def coerce(version: str) -> Tuple[Version, Optional[str]]: """ Convert an incomplete version string into a semver-compatible Version object @@ -37,6 +39,6 @@ def coerce(version: str) -> tuple[Version, Optional[str]]: ver = { key: 0 if value is None else value for key, value in match.groupdict().items() } - ver = semver.Version(**ver) + ver = Version(**ver) rest = match.string[match.end() :] # noqa:E203 return ver, rest diff --git a/tests/conftest.py b/tests/conftest.py index 153edf0c..f7f927cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ @pytest.fixture(autouse=True) def add_semver(doctest_namespace): + doctest_namespace["Version"] = semver.Version doctest_namespace["semver"] = semver doctest_namespace["coerce"] = coerce doctest_namespace["SemVerWithVPrefix"] = SemVerWithVPrefix From 4ef918d93304f88e0b8c82e26e52a2f162c8b15b Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 17:28:17 +0100 Subject: [PATCH 11/16] Support PEP-561 py.typed Acoording to the mentioned PEP: "Package maintainers who wish to support type checking of their code MUST add a marker file named py.typed to their package supporting typing." Add package_data to setup.cfg to include this marker in dist and whl file. --- setup.cfg | 9 +++++++++ src/semver/py.typed | 0 2 files changed, 9 insertions(+) create mode 100644 src/semver/py.typed diff --git a/setup.cfg b/setup.cfg index 52b5d3e5..873be36d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,14 @@ +# +# Metadata for setup.py +# +# See https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html + [metadata] name = semver version = attr: semver.__about__.__version__ description = attr: semver.__about__.__description__ long_description = file: README.rst +long_description_content_type = text/x-rst author = attr: semver.__about__.__author__ author_email = attr: semver.__about__.__author_email__ maintainer = attr: semver.__about__.__maintainer__ @@ -41,6 +47,9 @@ console_scripts = [options.packages.find] where = src +[options.package_data] +semver = py.typed + [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ venv/ testpaths = tests docs diff --git a/src/semver/py.typed b/src/semver/py.typed new file mode 100644 index 00000000..e69de29b From 89df81ec32ba6c990d5f42e7d7dbb9fb0ce6c36b Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 18:42:27 +0100 Subject: [PATCH 12/16] Add and improve docstrings Add missing module docstrings and correct function docstring to avoid warnings (just missing empty lines) --- docs/readme.rst | 4 +++- docs/usage.rst | 2 +- src/semver/__about__.py | 9 ++++++++- src/semver/__init__.py | 6 +++++- src/semver/__main__.py | 12 ++++++++---- src/semver/_deprecated.py | 10 ++++++++++ src/semver/version.py | 2 +- 7 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/readme.rst b/docs/readme.rst index 0aa732a1..034e9ee6 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -1,2 +1,4 @@ -.. include:: ../README.rst +If you are searching for how to stay compatible +with semver3, refer to :ref:`semver2-to-3`. +.. include:: ../README.rst diff --git a/docs/usage.rst b/docs/usage.rst index 31363bcf..94a115a8 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -89,7 +89,7 @@ A :class:`semver.Version` instance can be created in different ways: ValueError: 'major' is negative. A version can only be positive. As a minimum requirement, your dictionary needs at least the - be positive. + be positive. >>> semver.Version(-1) Traceback (most recent call last): diff --git a/src/semver/__about__.py b/src/semver/__about__.py index 2d8807cf..aa293425 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -1,4 +1,11 @@ -"""Metadata for semver version, author, maintainers, and description.""" +""" +Metadata about semver. + +Contains information about semver's version, the implemented version +of the semver specifictation, author, maintainers, and description. + +.. autodata:: __version__ +""" #: Semver version __version__ = "3.0.0-dev.2" diff --git a/src/semver/__init__.py b/src/semver/__init__.py index f406b916..c6726f2e 100644 --- a/src/semver/__init__.py +++ b/src/semver/__init__.py @@ -1,4 +1,8 @@ -"""semver package.""" +""" +semver package major release 3. + +A Python module for semantic versioning. Simplifies comparing versions. +""" from ._deprecated import ( bump_build, diff --git a/src/semver/__main__.py b/src/semver/__main__.py index 0e0648a9..b383e68a 100644 --- a/src/semver/__main__.py +++ b/src/semver/__main__.py @@ -1,21 +1,25 @@ """ Module to support call with :file:`__main__.py`. Used to support the following -call: +call:: + + $ python3 -m semver ... + +This makes it also possible to "run" a wheel like in this command:: + + $ python3 semver-3*-py3-none-any.whl/semver -h -$ python3 -m semver ... """ import os.path import sys from typing import List -from semver import cli - def main(cliargs: List[str] = None) -> int: if __package__ == "": path = os.path.dirname(os.path.dirname(__file__)) sys.path[0:0] = [path] + from semver import cli return cli.main(cliargs) diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 45c52753..33bef919 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -1,3 +1,9 @@ +""" +Contains all deprecated functions. + +.. autofunction: deprecated + +""" import inspect import warnings from functools import partial, wraps @@ -102,8 +108,10 @@ def parse_version_info(version): Use :func:`semver.VersionInfo.parse` instead. .. versionadded:: 2.7.2 Added :func:`semver.parse_version_info` + :param version: version string :return: a :class:`VersionInfo` instance + >>> version_info = semver.Version.parse("3.4.5-pre.2+build.4") >>> version_info.major 3 @@ -356,11 +364,13 @@ def replace(version, **parts): Use :func:`semver.Version.replace` instead. .. versionadded:: 2.9.0 Added :func:`replace` + :param version: the version string to replace :param parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` :return: the replaced version string :raises TypeError: if ``parts`` contains invalid keys + >>> import semver >>> semver.replace("1.2.3", major=2, patch=10) '2.2.10' diff --git a/src/semver/version.py b/src/semver/version.py index cd08f642..64353011 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -656,5 +656,5 @@ def isvalid(cls, version: str) -> bool: return False -# Keep the VersionInfo name for compatibility +#: Keep the VersionInfo name for compatibility VersionInfo = Version From 18b9cf91d0b6099a5f9a86d7029228b36a315a75 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 19:30:19 +0100 Subject: [PATCH 13/16] Improve API documentation (second attempt) * Use sphinx-apidoc to build API documentation * Amend tox.ini and call sphinx-apidoc * Remove old autosummary; it turned out it was difficult to configure and returned warning messages which were hard to fix. --- docs/_templates/autosummary/class.rst | 34 ------------- docs/_templates/autosummary/module.rst | 66 -------------------------- docs/api.rst | 12 ++--- docs/conf.py | 28 ++++++----- src/semver/__main__.py | 3 +- src/semver/_deprecated.py | 1 - tox.ini | 17 +++++-- 7 files changed, 35 insertions(+), 126 deletions(-) delete mode 100644 docs/_templates/autosummary/class.rst delete mode 100644 docs/_templates/autosummary/module.rst diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index 11ccc3c6..00000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,34 +0,0 @@ -{{ fullname | escape | underline}} - -Toms was here! - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ objname }} - :members: - :show-inheritance: - :inherited-members: - - {% block methods %} - .. .. automethod:: __init__ - - {% if methods %} - .. rubric:: {{ _('Methods') }} - - .. autosummary:: - {% for item in methods %} - ~{{ name }}.{{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Attributes') }} - - .. autosummary:: - {% for item in attributes %} - ~{{ name }}.{{ item }} - {%- endfor %} - {% endif %} - {% endblock %} diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst deleted file mode 100644 index 1772c873..00000000 --- a/docs/_templates/autosummary/module.rst +++ /dev/null @@ -1,66 +0,0 @@ -{{ fullname | escape | underline}} - - - -.. automodule:: {{ fullname }} - - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Module Attributes') }} - - .. autosummary:: - :toctree: - {% for item in attributes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block functions %} - {% if functions %} - .. rubric:: {{ _('Functions') }} - - .. autosummary:: - :toctree: - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block classes %} - {% if classes %} - .. rubric:: {{ _('Classes') }} - - .. autosummary:: - :toctree: - {% for item in classes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block exceptions %} - {% if exceptions %} - .. rubric:: {{ _('Exceptions') }} - - .. autosummary:: - :toctree: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - -{% block modules %} -{% if modules %} -.. rubric:: Modules - -.. autosummary:: - :toctree: - :recursive: -{% for item in modules %} - {{ item }} -{%- endfor %} -{% endif %} -{% endblock %} diff --git a/docs/api.rst b/docs/api.rst index c1845f5b..9d884601 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,12 +1,10 @@ .. _api: +### API -=== +### +.. toctree:: + :maxdepth: 4 -.. autosummary:: - :toctree: _api - :recursive: - - semver.__about__ - semver \ No newline at end of file + _api/semver \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 454d6be5..c608c12f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,20 +64,22 @@ def find_version(*file_paths): "sphinx.ext.extlinks", ] -autosummary_generate = True -apidoc_excluded_paths = ["tests"] +# autosummary_generate = True +# apidoc_excluded_paths = ["tests"] autoclass_content = "class" +autosummary_imported_members = True autodoc_default_options = { - "members": ( - "__version__," - "__about__, " - "__author__, " - "__author_email__," - "__maintainer__, " - "__maintainer_email__, " - "__description__" - ), - # "members-order": "groupwise", # alphabetical, groupwise, bysource + # "members": ( + # "__version__," + # "__about__, " + # "__author__, " + # "__author_email__," + # "__maintainer__, " + # "__maintainer_email__, " + # "__description__" + # ), + "ignore-module-all": True, + "special-members": "__version__", } # Add any paths that contain templates here, relative to this directory. @@ -110,7 +112,7 @@ def find_version(*file_paths): # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/src/semver/__main__.py b/src/semver/__main__.py index b383e68a..7fde54d7 100644 --- a/src/semver/__main__.py +++ b/src/semver/__main__.py @@ -13,13 +13,14 @@ import sys from typing import List +from semver import cli + def main(cliargs: List[str] = None) -> int: if __package__ == "": path = os.path.dirname(os.path.dirname(__file__)) sys.path[0:0] = [path] - from semver import cli return cli.main(cliargs) diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 33bef919..545a2438 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -2,7 +2,6 @@ Contains all deprecated functions. .. autofunction: deprecated - """ import inspect import warnings diff --git a/tox.ini b/tox.ini index 1071be92..623d8dd2 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ isolated_build = True [testenv] description = Run test suite for {basepython} -whitelist_externals = make +allowlist_externals = make commands = pytest {posargs:} deps = pytest @@ -17,7 +17,6 @@ deps = setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 - [testenv:black] description = Check for formatting changes basepython = python3 @@ -66,8 +65,18 @@ description = Build HTML documentation basepython = python3 deps = -r{toxinidir}/docs/requirements.txt skip_install = true -commands = make -C docs html - +allowlist_externals = + make + rm + echo +commands_pre = + sphinx-apidoc --module-first -f -P --separate -H semver -o docs/_api src/semver src/semver/_types.py + # we don't need this, it just add another level and it's all in docs/api.rst + - rm docs/_api/modules.rst +commands = + make -C docs html +commands_post = + echo "Find the HTML documentation at {toxinidir}/docs/_build/html/index.html" [testenv:man] description = Build the manpage From 551eb3c26ac3534846c67022d1dcebe260af5be2 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 23:31:36 +0100 Subject: [PATCH 14/16] Doc: Add semver version in footer --- docs/_templates/layout.html | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index 6bae6eed..7a114d41 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -2,3 +2,48 @@ Import the theme's layout. #} {% extends "!layout.html" %} + +{%- block footer %} + + +{% if theme_github_banner|lower != 'false' %} + + Fork me on GitHub + +{% endif %} +{% if theme_analytics_id %} + +{% endif %} +{%- endblock %} \ No newline at end of file From 80f0d244f7ced2c28c5b3f9a78826de0429ffa13 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 23:45:30 +0100 Subject: [PATCH 15/16] Add semver.__about__ to API doc * Unorthodox solution with sed * Remove obsolete config variables in docs/config.py * Add docs/_api/semver.__about__.rst as a placeholder --- .gitignore | 1 + docs/_api/semver.__about__.rst | 5 +++++ docs/conf.py | 15 +-------------- tox.ini | 5 ++++- 4 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 docs/_api/semver.__about__.rst diff --git a/.gitignore b/.gitignore index 8f76d83e..d26b5515 100644 --- a/.gitignore +++ b/.gitignore @@ -263,3 +263,4 @@ fabric.properties *.patch *.diff docs/_api +!docs/_api/semver.__about__.rst diff --git a/docs/_api/semver.__about__.rst b/docs/_api/semver.__about__.rst new file mode 100644 index 00000000..22395ebd --- /dev/null +++ b/docs/_api/semver.__about__.rst @@ -0,0 +1,5 @@ +semver.\_\_about\_\_ module +=========================== + +.. automodule:: semver.__about__ + :members: diff --git a/docs/conf.py b/docs/conf.py index c608c12f..6d2760f6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,24 +64,11 @@ def find_version(*file_paths): "sphinx.ext.extlinks", ] -# autosummary_generate = True -# apidoc_excluded_paths = ["tests"] autoclass_content = "class" -autosummary_imported_members = True autodoc_default_options = { - # "members": ( - # "__version__," - # "__about__, " - # "__author__, " - # "__author_email__," - # "__maintainer__, " - # "__maintainer_email__, " - # "__description__" - # ), - "ignore-module-all": True, - "special-members": "__version__", } + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/tox.ini b/tox.ini index 623d8dd2..d2c31ffa 100644 --- a/tox.ini +++ b/tox.ini @@ -69,10 +69,13 @@ allowlist_externals = make rm echo + sed commands_pre = - sphinx-apidoc --module-first -f -P --separate -H semver -o docs/_api src/semver src/semver/_types.py + sphinx-apidoc --module-first -f --separate -H semver -o docs/_api src/semver src/semver/_types.py src/semver/_deprecated.py # we don't need this, it just add another level and it's all in docs/api.rst - rm docs/_api/modules.rst + # Include the semver.__about__ module before semver.cli: + sed -i '/semver\.cli/i\ \ \ semver.__about__' docs/_api/semver.rst commands = make -C docs html commands_post = From 3c636f319d706b8b6c2a115a7b1d4d2cf5bc14d0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 31 Oct 2020 23:49:15 +0100 Subject: [PATCH 16/16] Fix formatting --- docs/conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6d2760f6..f5e04b19 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,8 +65,7 @@ def find_version(*file_paths): ] autoclass_content = "class" -autodoc_default_options = { -} +autodoc_default_options = {} # Add any paths that contain templates here, relative to this directory.