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. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy