From c4dbbc3f9eed39bdbbf46c3a20d8ce808e7f7a09 Mon Sep 17 00:00:00 2001 From: Tuure Laurinolli Date: Thu, 11 Feb 2016 21:23:46 +0200 Subject: [PATCH 001/312] Fix prerelease ordering --- semver.py | 6 +++--- tests/semver_test.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/semver.py b/semver.py index 44bfc9f2..d0185663 100644 --- a/semver.py +++ b/semver.py @@ -34,9 +34,9 @@ def parse(version): def compare(ver1, ver2): def nat_cmp(a, b): a, b = a or '', b or '' - convert = lambda text: int(text) if text.isdigit() else text.lower() - alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] - return cmp(alphanum_key(a), alphanum_key(b)) + convert = lambda text: (2, int(text)) if re.match('[0-9]+', text) else (1, text) + split_key = lambda key: [convert(c) for c in key.split('.')] + return cmp(split_key(a), split_key(b)) def compare_by_keys(d1, d2): for key in ['major', 'minor', 'patch']: diff --git a/tests/semver_test.py b/tests/semver_test.py index f86bb1cd..6dc288f5 100644 --- a/tests/semver_test.py +++ b/tests/semver_test.py @@ -128,6 +128,28 @@ def test_should_get_more_rc1(self): compare("1.0.0-rc1", "1.0.0-rc0"), 1) + def test_prerelease_order(self): + self.assertEqual(min_ver('1.2.3-rc.2', '1.2.3-rc.10'), + '1.2.3-rc.2') + self.assertEqual(min_ver('1.2.3-rc2', '1.2.3-rc10'), + '1.2.3-rc10') + # identifiers with letters or hyphens are compared lexically in ASCII sort order. + self.assertEqual(min_ver('1.2.3-Rc10', '1.2.3-rc10'), + '1.2.3-Rc10') + # Numeric identifiers always have lower precedence than non-numeric identifiers. + self.assertEqual(min_ver('1.2.3-2', '1.2.3-rc'), + '1.2.3-rc') + # A larger set of pre-release fields has a higher precedence than a smaller set, + # if all of the preceding identifiers are equal. + self.assertEqual(min_ver('1.2.3-rc.2.1', '1.2.3-rc.2'), + '1.2.3-rc.2') + # When major, minor, and patch are equal, a pre-release version has lower precedence + # than a normal version. + self.assertEqual(min_ver('1.2.3', '1.2.3-1'), + '1.2.3-1') + self.assertEqual(min_ver('1.0.0-alpha', '1.0.0-alpha.1'), + '1.0.0-alpha') + def test_should_bump_prerelease(self): self.assertEqual(bump_prerelease('3.4.5-rc.9'), '3.4.5-rc.10') From f6146fd647db99030b99f5e20fa72de05e0c7fb9 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 11 Feb 2016 22:52:47 +0200 Subject: [PATCH 002/312] 2.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f97e4b26..376c0a68 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='semver', - version='2.3.1', + version='2.4.0', description='Python package to work with Semantic Versioning (http://semver.org/)', long_description=LONG_DESCRIPTION, author='Konstantine Rybnikov', From 24cd0a40565e9b62e652b66a397e52cbdefb065c Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Thu, 3 Mar 2016 09:37:31 +0100 Subject: [PATCH 003/312] Fix bug in comparision between versions with build and prerelease --- semver.py | 16 +++++++++++----- tests/semver_test.py | 9 ++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/semver.py b/semver.py index d0185663..c14d9651 100644 --- a/semver.py +++ b/semver.py @@ -43,15 +43,21 @@ def compare_by_keys(d1, d2): v = cmp(d1.get(key), d2.get(key)) if v: return v + rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') rccmp = nat_cmp(rc1, rc2) - if not (rc1 or rc2): - return rccmp - if not rc1: + + build_1, build_2 = d1.get('build'), d2.get('build') + build_cmp = nat_cmp(build_1, build_2) + + if not rccmp and not build_cmp: + return 0 + if not rc1 and not build_1: return 1 - elif not rc2: + elif not rc2 and not build_2: return -1 - return rccmp or 0 + + return rccmp or build_cmp v1, v2 = parse(ver1), parse(ver2) diff --git a/tests/semver_test.py b/tests/semver_test.py index 6dc288f5..1827c1ca 100644 --- a/tests/semver_test.py +++ b/tests/semver_test.py @@ -93,7 +93,14 @@ def test_should_compare_release_candidate_with_release(self): self.assertEqual(compare('1.0.0-rc.1+build.1', '1.0.0'), -1) def test_should_say_equal_versions_are_equal(self): - self.assertEqual(compare("2.0.0", "2.0.0"), 0) + self.assertEqual(compare('2.0.0', '2.0.0'), 0) + self.assertEqual(compare('1.1.9-rc.1', '1.1.9-rc.1'), 0) + self.assertEqual(compare('1.1.9+build.1', '1.1.9+build.1'), 0) + self.assertEqual(compare('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1'), 0) + + def test_should_compare_versions_with_build_and_release(self): + self.assertEqual(compare('1.1.9-rc.1', '1.1.9-rc.1+build.1'), -1) + self.assertEqual(compare('1.1.9-rc.1', '1.1.9+build.1'), 1) def test_should_correctly_format_version(self): self.assertEqual(format_version(3, 4, 5), '3.4.5') From 8ec63e0fb0280470dc31a5dacf58772720d90349 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 3 Mar 2016 16:56:52 +0200 Subject: [PATCH 004/312] 2.4.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 376c0a68..1d3a2673 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='semver', - version='2.4.0', + version='2.4.1', description='Python package to work with Semantic Versioning (http://semver.org/)', long_description=LONG_DESCRIPTION, author='Konstantine Rybnikov', From 53c0ca2ede85f33631edc3b8b82dad77250aa21b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 15:35:26 +0200 Subject: [PATCH 005/312] Convert README to reStructuredText (as required by PyPI) Add more badges --- README.md | 51 --------------------------------------- README.rst | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 51 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index a23f2c36..00000000 --- a/README.md +++ /dev/null @@ -1,51 +0,0 @@ -Semver -- python module for semantic versioning -=============================================== - -![Travis CI](https://travis-ci.org/k-bx/python-semver.svg?branch=master) - -Simple module for comparing versions as noted at [semver.org](http://semver.org/). - -This module provides just couple of functions, main of which are: - -```python ->>> import semver ->>> 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 ->>> semver.match("2.0.0", ">=1.0.0") -True ->>> semver.match("1.0.0", ">1.0.0") -False ->>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') -'3.4.5-pre.2+build.4' ->>> semver.bump_major("3.4.5") -'4.0.0' ->>> semver.bump_minor("3.4.5") -'3.5.0' ->>> semver.bump_patch("3.4.5") -'3.4.6' ->>> semver.max_ver("1.0.0", "2.0.0") -'2.0.0' ->>> semver.min_ver("1.0.0", "2.0.0") -'1.0.0' -``` - -Installation ------------- - -For Python 2: - -``` -pip install semver -``` - -For Python 3: - -``` -pip3 install semver -``` - -Homepage at PyPi: https://pypi.python.org/pypi/semver diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..2699f390 --- /dev/null +++ b/README.rst @@ -0,0 +1,71 @@ +Semver |latest-version| +======================= + +|build-status| |python-support| |downloads| |license| + +A Python module for `semantic versioning`_. Simplifies comparing versions. + + +.. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg + :alt: Latest version on PyPI + :target: https://pypi.python.org/pypi/semver +.. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master + :alt: Build status + :target: https://travis-ci.org/k-bx/python-semver +.. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg + :target: https://pypi.python.org/pypi/semver + :alt: Python versions +.. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg + :alt: Monthly downloads from PyPI + :target: https://pypi.python.org/pypi/semver +.. |license| image:: https://img.shields.io/pypi/l/semver.svg + :alt: Software license + :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt + +.. _semantic versioning: http://semver.org/ + +Usage +----- + +This module provides just couple of functions, main of which are: + +.. code-block:: python + + >>> import semver + >>> 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 + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + >>> semver.bump_major("3.4.5") + '4.0.0' + >>> semver.bump_minor("3.4.5") + '3.5.0' + >>> semver.bump_patch("3.4.5") + '3.4.6' + >>> semver.max_ver("1.0.0", "2.0.0") + '2.0.0' + >>> semver.min_ver("1.0.0", "2.0.0") + '1.0.0' + +Installation +------------ + +For Python 2: + +.. code-block:: bash + + pip install semver + +For Python 3: + +.. code-block:: bash + + pip3 install semver From 446b09c2fa444d19375c325366087effc4f15f4f Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 15:59:10 +0200 Subject: [PATCH 006/312] Fix flake8 complaints in semver and setup --- semver.py | 29 ++++++++++++++++++++--------- setup.py | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/semver.py b/semver.py index c14d9651..efe07325 100644 --- a/semver.py +++ b/semver.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- - +"""Python helper for Semantic Versioning (http://semver.org/)""" import re _REGEX = re.compile('^(?P(?:0|[1-9][0-9]*))' @@ -11,7 +10,8 @@ _LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') if not hasattr(__builtins__, 'cmp'): - cmp = lambda a, b: (a > b) - (a < b) + def cmp(a, b): + return (a > b) - (a < b) def parse(version): @@ -33,9 +33,13 @@ def parse(version): def compare(ver1, ver2): def nat_cmp(a, b): + def convert(text): + return (2, int(text)) if re.match('[0-9]+', text) else (1, text) + + def split_key(key): + return [convert(c) for c in key.split('.')] + a, b = a or '', b or '' - convert = lambda text: (2, int(text)) if re.match('[0-9]+', text) else (1, text) - split_key = lambda key: [convert(c) for c in key.split('.')] return cmp(split_key(a), split_key(b)) def compare_by_keys(d1, d2): @@ -118,11 +122,13 @@ def format_version(major, minor, patch, prerelease=None, build=None): def _increment_string(string): - # look for the last sequence of number(s) in a string and increment, from: - # http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 + """ + Look for the last sequence of number(s) in a string and increment, from: + http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 + """ match = _LAST_NUMBER.search(string) if match: - next_ = str(int(match.group(1))+1) + next_ = str(int(match.group(1)) + 1) start, end = match.span(1) string = string[:max(end - len(next_), start)] + next_ + string[end:] return string @@ -132,13 +138,17 @@ def bump_major(version): verinfo = parse(version) return format_version(verinfo['major'] + 1, 0, 0) + def bump_minor(version): verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'] + 1, 0) + def bump_patch(version): verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'] + 1) + return format_version(verinfo['major'], verinfo['minor'], + verinfo['patch'] + 1) + def bump_prerelease(version): verinfo = parse(version) @@ -146,6 +156,7 @@ def bump_prerelease(version): return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease']) + def bump_build(version): verinfo = parse(version) verinfo['build'] = _increment_string(verinfo['build'] or 'build.0') diff --git a/setup.py b/setup.py index 1d3a2673..737db988 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='semver', version='2.4.1', - description='Python package to work with Semantic Versioning (http://semver.org/)', + description='Python helper for Semantic Versioning (http://semver.org/)', long_description=LONG_DESCRIPTION, author='Konstantine Rybnikov', author_email='k-bx@k-bx.com', From 9d120393f8ddfc75b597d409e765594b2257dad5 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 16:31:27 +0200 Subject: [PATCH 007/312] Add tox configuration, use py.test for testing Also test Python 3.5 Remove MANIFEST file (no need for version control) --- .gitignore | 17 ++++++++++++----- MANIFEST | 4 ---- setup.py | 39 ++++++++++++++++++++++++++++++++++----- tox.ini | 18 ++++++++++++++++++ 4 files changed, 64 insertions(+), 14 deletions(-) delete mode 100644 MANIFEST create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 1a4c52bb..390b4226 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ -.emacs-project -*.pyc -dist/ -.venv* -build/ +/.coverage +/.emacs-project +/.idea +/.tox +/.venv* + +/*.egg-info +/build +/dist +/MANIFEST + +*.py[co] diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index d6800425..00000000 --- a/MANIFEST +++ /dev/null @@ -1,4 +0,0 @@ -# file GENERATED by distutils, do NOT edit -README.md -semver.py -setup.py diff --git a/setup.py b/setup.py index 737db988..7bcf919e 100755 --- a/setup.py +++ b/setup.py @@ -1,15 +1,39 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +from setuptools import setup +from setuptools.command.test import test as TestCommand +from shlex import split -from distutils.core import setup -with open('README.md') as f: - LONG_DESCRIPTION = f.read() +class Tox(TestCommand): + user_options = [('tox-args=', 'a', "Arguments to pass to tox")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.tox_args = None + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + from tox import cmdline + args = self.tox_args + if args: + args = split(self.tox_args) + errno = cmdline(args=args) + exit(errno) + + +def read_file(filename): + with open(filename) as f: + return f.read() setup( name='semver', version='2.4.1', description='Python helper for Semantic Versioning (http://semver.org/)', - long_description=LONG_DESCRIPTION, + long_description=read_file('README.md'), author='Konstantine Rybnikov', author_email='k-bx@k-bx.com', url='https://github.com/k-bx/python-semver', @@ -31,6 +55,11 @@ 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Python Modules', ], + tests_require=['tox'], + cmdclass={ + 'test': Tox, + }, ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..0eeed06a --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist = + flake8 + py{26,27,32,33,34,35} + +[testenv] +commands = + py.test -q tests +deps = + pip==7.1.2 + pytest +setenv = + PIP_DISABLE_PIP_VERSION_CHECK = 1 + +[testenv:flake8] +basepython = python3.4 +deps = flake8 +commands = flake8 --max-line-length=95 From 8953bbdba2d2f5aa39c19cbcd9a6a0f1aface3d6 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 16:50:26 +0200 Subject: [PATCH 008/312] Integrate tox in Travis-CI configuration --- .travis.yml | 14 +++----------- requirements.txt | 1 - tox.ini | 1 + 3 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml index 41ec12ca..1a722589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,4 @@ language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "pypy" -# command to install dependencies -install: "pip install -r requirements.txt" -# command to run tests -script: nosetests +python: "3.5" +install: pip install virtualenv +script: python setup.py test diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f3c7e8e6..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -nose diff --git a/tox.ini b/tox.ini index 0eeed06a..23cf3c07 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ envlist = flake8 py{26,27,32,33,34,35} + pypy [testenv] commands = From 9325a49ccb311475e32b8851a9d8cb71abaf28a6 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 16:53:56 +0200 Subject: [PATCH 009/312] Rename tests module (/tests/semver_test.py -> /tests.py) --- tests/semver_test.py => tests.py | 0 tox.ini | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/semver_test.py => tests.py (100%) diff --git a/tests/semver_test.py b/tests.py similarity index 100% rename from tests/semver_test.py rename to tests.py diff --git a/tox.ini b/tox.ini index 23cf3c07..19e3008c 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = [testenv] commands = - py.test -q tests + py.test -q tests.py deps = pip==7.1.2 pytest From c0d81f9d1d35e8c2ec6708280f8db58b52257314 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 17:16:14 +0200 Subject: [PATCH 010/312] Migrate tests from unittest to py.test --- .gitignore | 1 + tests.py | 347 +++++++++++++++++++++++++++-------------------------- tox.ini | 2 +- 3 files changed, 179 insertions(+), 171 deletions(-) diff --git a/.gitignore b/.gitignore index 390b4226..84c639e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.cache /.coverage /.emacs-project /.idea diff --git a/tests.py b/tests.py index 1827c1ca..253b3712 100644 --- a/tests.py +++ b/tests.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- +import pytest # noqa -import unittest -from unittest import TestCase from semver import compare from semver import match from semver import parse @@ -15,170 +13,179 @@ from semver import max_ver -class TestSemver(TestCase): - def test_should_parse_version(self): - self.assertEqual( - parse("1.2.3-alpha.1.2+build.11.e0f985a"), - {'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'alpha.1.2', - 'build': 'build.11.e0f985a'}) - - self.assertEqual( - parse("1.2.3-alpha-1+build.11.e0f985a"), - {'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'alpha-1', - 'build': 'build.11.e0f985a'}) - - def test_should_get_less(self): - self.assertEqual( - compare("1.0.0", "2.0.0"), - -1) - - def test_should_get_greater(self): - self.assertEqual( - compare("2.0.0", "1.0.0"), - 1) - - def test_should_match_simple(self): - self.assertEqual( - match("2.3.7", ">=2.3.6"), - True) - - def test_should_no_match_simple(self): - self.assertEqual( - match("2.3.7", ">=2.3.8"), - False) - - def test_should_raise_value_error_for_zero_prefixed_versions(self): - self.assertRaises(ValueError, parse, "01.2.3") - self.assertRaises(ValueError, parse, "1.02.3") - self.assertRaises(ValueError, parse, "1.2.03") - - def test_should_raise_value_error_for_invalid_value(self): - self.assertRaises(ValueError, compare, 'foo', 'bar') - self.assertRaises(ValueError, compare, '1.0', '1.0.0') - self.assertRaises(ValueError, compare, '1.x', '1.0.0') - - def test_should_raise_value_error_for_invalid_match_expression(self): - self.assertRaises(ValueError, match, '1.0.0', '') - self.assertRaises(ValueError, match, '1.0.0', '!') - self.assertRaises(ValueError, match, '1.0.0', '1.0.0') - - def test_should_follow_specification_comparison(self): - # produce comparsion chain: - # 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 - # < 1.0.0-rc.1 < 1.0.0-rc.1+build.1 < 1.0.0 < 1.0.0+0.3.7 < 1.3.7+build - # < 1.3.7+build.2.b8f12d7 < 1.3.7+build.11.e0f985a - # and in backward too. - chain = ['1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-beta.2', - '1.0.0-beta.11', '1.0.0-rc.1', - '1.0.0', '1.3.7+build'] - versions = zip(chain[:-1], chain[1:]) - for low_version, high_version in versions: - self.assertEqual( - compare(low_version, high_version), -1, - '%s should be lesser than %s' % (low_version, high_version)) - self.assertEqual( - compare(high_version, low_version), 1, - '%s should be higher than %s' % (high_version, low_version)) - - def test_should_compare_rc_builds(self): - self.assertEqual(compare('1.0.0-beta.2', '1.0.0-beta.11'), -1) - - def test_should_compare_release_candidate_with_release(self): - self.assertEqual(compare('1.0.0-rc.1+build.1', '1.0.0'), -1) - - def test_should_say_equal_versions_are_equal(self): - self.assertEqual(compare('2.0.0', '2.0.0'), 0) - self.assertEqual(compare('1.1.9-rc.1', '1.1.9-rc.1'), 0) - self.assertEqual(compare('1.1.9+build.1', '1.1.9+build.1'), 0) - self.assertEqual(compare('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1'), 0) - - def test_should_compare_versions_with_build_and_release(self): - self.assertEqual(compare('1.1.9-rc.1', '1.1.9-rc.1+build.1'), -1) - self.assertEqual(compare('1.1.9-rc.1', '1.1.9+build.1'), 1) - - def test_should_correctly_format_version(self): - self.assertEqual(format_version(3, 4, 5), '3.4.5') - self.assertEqual(format_version(3, 4, 5, 'rc.1'), '3.4.5-rc.1') - self.assertEqual(format_version(3, 4, 5, prerelease='rc.1'), '3.4.5-rc.1') - self.assertEqual(format_version(3, 4, 5, build='build.4'), '3.4.5+build.4') - self.assertEqual(format_version(3, 4, 5, 'rc.1', 'build.4'), '3.4.5-rc.1+build.4') - - def test_should_bump_major(self): - self.assertEqual(bump_major('3.4.5'), '4.0.0') - - def test_should_bump_minor(self): - self.assertEqual(bump_minor('3.4.5'), '3.5.0') - - def test_should_bump_patch(self): - self.assertEqual(bump_patch('3.4.5'), '3.4.6') - - def test_should_ignore_extensions_for_bump(self): - self.assertEqual(bump_patch('3.4.5-rc1+build4'), '3.4.6') - - def test_should_get_max(self): - self.assertEqual(max_ver('3.4.5', '4.0.2'), '4.0.2') - - def test_should_get_min(self): - self.assertEqual(min_ver('3.4.5', '4.0.2'), '3.4.5') - - def test_should_get_min_same(self): - self.assertEqual(min_ver('3.4.5', '3.4.5'), '3.4.5') - - def test_should_get_more_rc1(self): - self.assertEqual( - compare("1.0.0-rc1", "1.0.0-rc0"), - 1) - - def test_prerelease_order(self): - self.assertEqual(min_ver('1.2.3-rc.2', '1.2.3-rc.10'), - '1.2.3-rc.2') - self.assertEqual(min_ver('1.2.3-rc2', '1.2.3-rc10'), - '1.2.3-rc10') - # identifiers with letters or hyphens are compared lexically in ASCII sort order. - self.assertEqual(min_ver('1.2.3-Rc10', '1.2.3-rc10'), - '1.2.3-Rc10') - # Numeric identifiers always have lower precedence than non-numeric identifiers. - self.assertEqual(min_ver('1.2.3-2', '1.2.3-rc'), - '1.2.3-rc') - # A larger set of pre-release fields has a higher precedence than a smaller set, - # if all of the preceding identifiers are equal. - self.assertEqual(min_ver('1.2.3-rc.2.1', '1.2.3-rc.2'), - '1.2.3-rc.2') - # When major, minor, and patch are equal, a pre-release version has lower precedence - # than a normal version. - self.assertEqual(min_ver('1.2.3', '1.2.3-1'), - '1.2.3-1') - self.assertEqual(min_ver('1.0.0-alpha', '1.0.0-alpha.1'), - '1.0.0-alpha') - - def test_should_bump_prerelease(self): - self.assertEqual(bump_prerelease('3.4.5-rc.9'), - '3.4.5-rc.10') - self.assertEqual(bump_prerelease('3.4.5-0009.dev'), - '3.4.5-0010.dev') - self.assertEqual(bump_prerelease('3.4.5'), - '3.4.5-rc.1') - - def test_should_ignore_build_on_prerelease_bump(self): - self.assertEqual(bump_prerelease('3.4.5-rc.1+build.4'), - '3.4.5-rc.2') - - def test_should_bump_build(self): - self.assertEqual(bump_build('3.4.5-rc.1+build.9'), - '3.4.5-rc.1+build.10') - self.assertEqual(bump_build('3.4.5-rc.1+0009.dev'), - '3.4.5-rc.1+0010.dev') - self.assertEqual(bump_build('3.4.5-rc.1'), - '3.4.5-rc.1+build.1') - self.assertEqual(bump_build('3.4.5'), - '3.4.5+build.1') - - -if __name__ == '__main__': - unittest.main() +def test_should_parse_version(): + result = parse("1.2.3-alpha.1.2+build.11.e0f985a") + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'alpha.1.2', + 'build': 'build.11.e0f985a', + } + + result = parse("1.2.3-alpha-1+build.11.e0f985a") + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'alpha-1', + 'build': 'build.11.e0f985a', + } + + +def test_should_get_less(): + assert compare("1.0.0", "2.0.0") == -1 + + +def test_should_get_greater(): + assert compare("2.0.0", "1.0.0") == 1 + + +def test_should_match_simple(): + assert match("2.3.7", ">=2.3.6") is True + + +def test_should_no_match_simple(): + assert match("2.3.7", ">=2.3.8") is False + + +def test_should_raise_value_error_for_zero_prefixed_versions(): + with pytest.raises(ValueError): + parse("01.2.3") + with pytest.raises(ValueError): + parse("1.02.3") + with pytest.raises(ValueError): + parse("1.2.03") + + +def test_should_raise_value_error_for_invalid_value(): + with pytest.raises(ValueError): + compare('foo', 'bar') + with pytest.raises(ValueError): + compare('1.0', '1.0.0') + with pytest.raises(ValueError): + compare('1.x', '1.0.0') + + +def test_should_raise_value_error_for_invalid_match_expression(): + with pytest.raises(ValueError): + match('1.0.0', '') + with pytest.raises(ValueError): + match('1.0.0', '!') + with pytest.raises(ValueError): + match('1.0.0', '1.0.0') + + +def test_should_follow_specification_comparison(): + """ + produce comparison chain: + 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 + < 1.0.0-rc.1 < 1.0.0-rc.1+build.1 < 1.0.0 < 1.0.0+0.3.7 < 1.3.7+build + < 1.3.7+build.2.b8f12d7 < 1.3.7+build.11.e0f985a + and in backward too. + """ + chain = [ + '1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-beta.2', '1.0.0-beta.11', + '1.0.0-rc.1', '1.0.0', '1.3.7+build', + ] + versions = zip(chain[:-1], chain[1:]) + for low_version, high_version in versions: + assert compare(low_version, high_version) == -1, \ + '%s should be lesser than %s' % (low_version, high_version) + assert compare(high_version, low_version) == 1, \ + '%s should be higher than %s' % (high_version, low_version) + + +def test_should_compare_rc_builds(): + assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 + + +def test_should_compare_release_candidate_with_release(): + assert compare('1.0.0-rc.1+build.1', '1.0.0') == -1 + + +def test_should_say_equal_versions_are_equal(): + assert compare('2.0.0', '2.0.0') == 0 + assert compare('1.1.9-rc.1', '1.1.9-rc.1') == 0 + assert compare('1.1.9+build.1', '1.1.9+build.1') == 0 + assert compare('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1') == 0 + + +def test_should_compare_versions_with_build_and_release(): + assert compare('1.1.9-rc.1', '1.1.9-rc.1+build.1') == -1 + assert compare('1.1.9-rc.1', '1.1.9+build.1') == 1 + + +def test_should_correctly_format_version(): + assert format_version(3, 4, 5) == '3.4.5' + assert format_version(3, 4, 5, 'rc.1') == '3.4.5-rc.1' + assert format_version(3, 4, 5, prerelease='rc.1') == '3.4.5-rc.1' + assert format_version(3, 4, 5, build='build.4') == '3.4.5+build.4' + assert format_version(3, 4, 5, 'rc.1', 'build.4') == '3.4.5-rc.1+build.4' + + +def test_should_bump_major(): + assert bump_major('3.4.5') == '4.0.0' + + +def test_should_bump_minor(): + assert bump_minor('3.4.5') == '3.5.0' + + +def test_should_bump_patch(): + assert bump_patch('3.4.5') == '3.4.6' + + +def test_should_ignore_extensions_for_bump(): + assert bump_patch('3.4.5-rc1+build4') == '3.4.6' + + +def test_should_get_max(): + assert max_ver('3.4.5', '4.0.2') == '4.0.2' + + +def test_should_get_min(): + assert min_ver('3.4.5', '4.0.2') == '3.4.5' + + +def test_should_get_min_same(): + assert min_ver('3.4.5', '3.4.5') == '3.4.5' + + +def test_should_get_more_rc1(): + assert compare("1.0.0-rc1", "1.0.0-rc0") == 1 + + +def test_prerelease_order(): + assert min_ver('1.2.3-rc.2', '1.2.3-rc.10') == '1.2.3-rc.2' + assert min_ver('1.2.3-rc2', '1.2.3-rc10') == '1.2.3-rc10' + # identifiers with letters or hyphens are compared lexically in ASCII sort + # order. + assert min_ver('1.2.3-Rc10', '1.2.3-rc10') == '1.2.3-Rc10' + # Numeric identifiers always have lower precedence than non-numeric + # identifiers. + assert min_ver('1.2.3-2', '1.2.3-rc') == '1.2.3-rc' + # A larger set of pre-release fields has a higher precedence than a + # smaller set, if all of the preceding identifiers are equal. + assert min_ver('1.2.3-rc.2.1', '1.2.3-rc.2') == '1.2.3-rc.2' + # When major, minor, and patch are equal, a pre-release version has lower + # precedence than a normal version. + assert min_ver('1.2.3', '1.2.3-1') == '1.2.3-1' + assert min_ver('1.0.0-alpha', '1.0.0-alpha.1') == '1.0.0-alpha' + + +def test_should_bump_prerelease(): + assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' + assert bump_prerelease('3.4.5-0009.dev') == '3.4.5-0010.dev' + assert bump_prerelease('3.4.5') == '3.4.5-rc.1' + + +def test_should_ignore_build_on_prerelease_bump(): + assert bump_prerelease('3.4.5-rc.1+build.4') == '3.4.5-rc.2' + + +def test_should_bump_build(): + assert bump_build('3.4.5-rc.1+build.9') == '3.4.5-rc.1+build.10' + assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev' + assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' + assert bump_build('3.4.5') == '3.4.5+build.1' diff --git a/tox.ini b/tox.ini index 19e3008c..4c2f721e 100644 --- a/tox.ini +++ b/tox.ini @@ -16,4 +16,4 @@ setenv = [testenv:flake8] basepython = python3.4 deps = flake8 -commands = flake8 --max-line-length=95 +commands = flake8 From 2275800c9cf0f10e62be5f5d348f6d43c8be5f4f Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 18:02:23 +0200 Subject: [PATCH 011/312] Ensure Python 3.2 support with tox --- .gitignore | 1 + .travis.yml | 2 +- setup.py | 2 +- tox.ini | 7 ++----- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 84c639e8..03729020 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /.venv* /*.egg-info +/.eggs /build /dist /MANIFEST diff --git a/.travis.yml b/.travis.yml index 1a722589..942f2a9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: python python: "3.5" -install: pip install virtualenv +install: pip install "virtualenv<14.0.0" script: python setup.py test diff --git a/setup.py b/setup.py index 7bcf919e..37078743 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ def read_file(filename): 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Python Modules', ], - tests_require=['tox'], + tests_require=['tox', 'virtualenv<14.0.0'], cmdclass={ 'test': Tox, }, diff --git a/tox.ini b/tox.ini index 4c2f721e..bde3b0d2 100644 --- a/tox.ini +++ b/tox.ini @@ -5,11 +5,8 @@ envlist = pypy [testenv] -commands = - py.test -q tests.py -deps = - pip==7.1.2 - pytest +commands = py.test -q tests.py +deps = pytest setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 From 6f52a436a8eb80be7d214a6946e9a3535819ea96 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 19:04:27 +0200 Subject: [PATCH 012/312] Include README.rst (instead of README.md) for PyPI Add Contribute section to README --- MANIFEST.in | 2 +- README.rst | 24 +++++++++++++++++++++++- setup.py | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index bb3ec5f0..9561fb10 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.md +include README.rst diff --git a/README.rst b/README.rst index 2699f390..db1d93d7 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,6 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. |license| image:: https://img.shields.io/pypi/l/semver.svg :alt: Software license :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt - .. _semantic versioning: http://semver.org/ Usage @@ -69,3 +68,26 @@ For Python 3: .. code-block:: bash pip3 install semver + +How to Contribute +----------------- + +When you make changes to the code please run the tests before pushing your +code to your fork and opening a `pull request`_: + +.. code-block:: bash + + python setup.py test + +We use `py.test`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically, apart from +virtualenv, which for the moment you still may have to install manually: + +.. code-block:: bash + + pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only + + +.. _pull request: https://github.com/k-bx/python-semver/pulls +.. _py.test: http://pytest.org/ +.. _tox: http://tox.testrun.org/ diff --git a/setup.py b/setup.py index 37078743..5eda23a6 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def read_file(filename): name='semver', version='2.4.1', description='Python helper for Semantic Versioning (http://semver.org/)', - long_description=read_file('README.md'), + long_description=read_file('README.rst'), author='Konstantine Rybnikov', author_email='k-bx@k-bx.com', url='https://github.com/k-bx/python-semver', From d8b75a1ad60435ddb38384df32dc6110713cdc3a Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 19:05:59 +0200 Subject: [PATCH 013/312] Move package meta information into module --- semver.py | 8 +++++++- setup.py | 13 +++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/semver.py b/semver.py index efe07325..13e3b303 100644 --- a/semver.py +++ b/semver.py @@ -1,6 +1,12 @@ -"""Python helper for Semantic Versioning (http://semver.org/)""" +""" +Python helper for Semantic Versioning (http://semver.org/) +""" import re +__version__ = '2.4.1' +__author__ = 'Konstantine Rybnikov' +__author_email__ = 'k-bx@k-bx.com' + _REGEX = re.compile('^(?P(?:0|[1-9][0-9]*))' '\.(?P(?:0|[1-9][0-9]*))' '\.(?P(?:0|[1-9][0-9]*))' diff --git a/setup.py b/setup.py index 5eda23a6..891adbe2 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +import semver as package from setuptools import setup from setuptools.command.test import test as TestCommand from shlex import split @@ -30,15 +31,15 @@ def read_file(filename): return f.read() setup( - name='semver', - version='2.4.1', - description='Python helper for Semantic Versioning (http://semver.org/)', + name=package.__name__, + version=package.__version__, + description=package.__doc__.strip(), long_description=read_file('README.rst'), - author='Konstantine Rybnikov', - author_email='k-bx@k-bx.com', + author=package.__author__, + author_email=package.__author_email__, url='https://github.com/k-bx/python-semver', download_url='https://github.com/k-bx/python-semver/downloads', - py_modules=['semver'], + py_modules=[package.__name__], include_package_data=True, license='BSD', classifiers=[ From f251b4a3c858511781598fe682ffe49ccb99c3da Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 19:08:07 +0200 Subject: [PATCH 014/312] Don't state we're useful only for Django --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 891adbe2..ee3d4267 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,6 @@ def read_file(filename): license='BSD', classifiers=[ 'Environment :: Web Environment', - 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', From 24e7b2f13ac45bd67a084343625954bb6d68636b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 15 May 2016 19:18:27 +0200 Subject: [PATCH 015/312] Add clean command to setup.py --- README.rst | 6 ++++++ setup.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index db1d93d7..10739101 100644 --- a/README.rst +++ b/README.rst @@ -87,6 +87,12 @@ virtualenv, which for the moment you still may have to install manually: pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only +You can use the ``clean`` command to remove build and test files and folders: + +.. code-block:: bash + + python setup.py clean + .. _pull request: https://github.com/k-bx/python-semver/pulls .. _py.test: http://pytest.org/ diff --git a/setup.py b/setup.py index ee3d4267..ea363bc7 100755 --- a/setup.py +++ b/setup.py @@ -1,8 +1,12 @@ #!/usr/bin/env python import semver as package +from glob import glob +from os import remove +from os.path import dirname, join from setuptools import setup from setuptools.command.test import test as TestCommand from shlex import split +from shutil import rmtree class Tox(TestCommand): @@ -26,8 +30,43 @@ def run_tests(self): exit(errno) +class Clean(TestCommand): + def run(self): + delete_in_root = [ + 'build', + '.cache', + 'dist', + '.eggs', + '*.egg-info', + '.tox', + ] + delete_everywhere = [ + '__pycache__', + '*.pyc', + ] + for candidate in delete_in_root: + rmtree_glob(candidate) + for visible_dir in glob('[A-Za-z0-9]*'): + for candidate in delete_everywhere: + rmtree_glob(join(visible_dir, candidate)) + rmtree_glob(join(visible_dir, '*', candidate)) + + +def rmtree_glob(file_glob): + for fobj in glob(file_glob): + try: + rmtree(fobj) + print('%s/ removed ...' % fobj) + except OSError: + try: + remove(fobj) + print('%s removed ...' % fobj) + except OSError: + pass + + def read_file(filename): - with open(filename) as f: + with open(join(dirname(__file__), filename)) as f: return f.read() setup( @@ -60,6 +99,7 @@ def read_file(filename): ], tests_require=['tox', 'virtualenv<14.0.0'], cmdclass={ + 'clean': Clean, 'test': Tox, }, ) From 86d6c10f2d3ed6093025f51a1c74cf7378d23491 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Sun, 15 May 2016 20:35:24 +0300 Subject: [PATCH 016/312] 2.4.2 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 13e3b303..d0683170 100644 --- a/semver.py +++ b/semver.py @@ -3,7 +3,7 @@ """ import re -__version__ = '2.4.1' +__version__ = '2.4.2' __author__ = 'Konstantine Rybnikov' __author_email__ = 'k-bx@k-bx.com' From 547cbdf89d0803fe75a041feba85d6a0689376a0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Mon, 16 May 2016 05:50:10 +0200 Subject: [PATCH 017/312] Make separate builds for tests on Travis CI --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 942f2a9e..b20f8b03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,14 @@ language: python python: "3.5" install: pip install "virtualenv<14.0.0" script: python setup.py test +env: + # To generate (update) the env list run + # $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM + - TOXENV=flake8 + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py32 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=py35 + - TOXENV=pypy From b5f69d408e6c8d61de074fd48dcfed151e7669fe Mon Sep 17 00:00:00 2001 From: robi-wan Date: Mon, 23 May 2016 08:47:13 +0200 Subject: [PATCH 018/312] Support matching 'not equal' with '!=' Remove check for unused/unsupported match expression '='. --- semver.py | 7 ++++--- tests.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/semver.py b/semver.py index d0683170..7fdae5b6 100644 --- a/semver.py +++ b/semver.py @@ -76,20 +76,21 @@ def compare_by_keys(d1, d2): def match(version, match_expr): prefix = match_expr[:2] - if prefix in ('>=', '<=', '=='): + if prefix in ('>=', '<=', '==', '!='): match_version = match_expr[2:] - elif prefix and prefix[0] in ('>', '<', '='): + elif prefix and prefix[0] in ('>', '<'): prefix = prefix[0] match_version = match_expr[1:] else: raise ValueError("match_expr parameter should be in format , " - "where is one of ['<', '>', '==', '<=', '>=']. " + "where is one of ['<', '>', '==', '<=', '>=', '!=']. " "You provided: %r" % match_expr) possibilities_dict = { '>': (1,), '<': (-1,), '==': (0,), + '!=': (-1, 1), '>=': (0, 1), '<=': (-1, 0) } diff --git a/tests.py b/tests.py index 253b3712..9d4f1a77 100644 --- a/tests.py +++ b/tests.py @@ -49,6 +49,31 @@ def test_should_no_match_simple(): assert match("2.3.7", ">=2.3.8") is False +def test_should_match_not_equal(): + assert match("2.3.7", "!=2.3.8") is True + assert match("2.3.7", "!=2.3.6") is True + assert match("2.3.7", "!=2.3.7") is False + + +def test_should_not_raise_value_error_for_expected_match_expression(): + assert match("2.3.7", "<2.4.0") is True + assert match("2.3.7", ">2.3.5") is True + + assert match("2.3.7", "<=2.3.9") is True + assert match("2.3.7", ">=2.3.5") is True + assert match("2.3.7", "==2.3.7") is True + assert match("2.3.7", "!=2.3.7") is False + + +def test_should_raise_value_error_for_unexpected_match_expression(): + with pytest.raises(ValueError): + match("2.3.7", "=2.3.7") + with pytest.raises(ValueError): + match("2.3.7", "~2.3.7") + with pytest.raises(ValueError): + match("2.3.7", "^2.3.7") + + def test_should_raise_value_error_for_zero_prefixed_versions(): with pytest.raises(ValueError): parse("01.2.3") From becd7089459cd26a5091d0e732397767cdf14f29 Mon Sep 17 00:00:00 2001 From: robi-wan Date: Mon, 23 May 2016 09:29:08 +0200 Subject: [PATCH 019/312] Format ValueError message to meet expected line length. --- semver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 7fdae5b6..8e65ecb0 100644 --- a/semver.py +++ b/semver.py @@ -83,7 +83,8 @@ def match(version, match_expr): match_version = match_expr[1:] else: raise ValueError("match_expr parameter should be in format , " - "where is one of ['<', '>', '==', '<=', '>=', '!=']. " + "where is one of " + "['<', '>', '==', '<=', '>=', '!=']. " "You provided: %r" % match_expr) possibilities_dict = { From 1e3ad7d8ae90ca62cdcd21dbe7451ad7f1c85b35 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 24 May 2016 23:16:50 +0300 Subject: [PATCH 020/312] 2.5.0 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 8e65ecb0..9d015f34 100644 --- a/semver.py +++ b/semver.py @@ -3,7 +3,7 @@ """ import re -__version__ = '2.4.2' +__version__ = '2.5.0' __author__ = 'Konstantine Rybnikov' __author_email__ = 'k-bx@k-bx.com' From 0e168ed07a7e52a912fa592e358cb73ed29f3dd8 Mon Sep 17 00:00:00 2001 From: Jelo Agnasin Date: Mon, 6 Jun 2016 17:37:36 +0800 Subject: [PATCH 021/312] Remove comparison of build metadata --- semver.py | 11 ++++------- tests.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/semver.py b/semver.py index 8e65ecb0..64e3ca8e 100644 --- a/semver.py +++ b/semver.py @@ -57,17 +57,14 @@ def compare_by_keys(d1, d2): rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') rccmp = nat_cmp(rc1, rc2) - build_1, build_2 = d1.get('build'), d2.get('build') - build_cmp = nat_cmp(build_1, build_2) - - if not rccmp and not build_cmp: + if not rccmp: return 0 - if not rc1 and not build_1: + if not rc1: return 1 - elif not rc2 and not build_2: + elif not rc2: return -1 - return rccmp or build_cmp + return rccmp v1, v2 = parse(ver1), parse(ver2) diff --git a/tests.py b/tests.py index 9d4f1a77..05241e11 100644 --- a/tests.py +++ b/tests.py @@ -126,6 +126,7 @@ def test_should_compare_rc_builds(): def test_should_compare_release_candidate_with_release(): + assert compare('1.0.0-rc.1', '1.0.0') == -1 assert compare('1.0.0-rc.1+build.1', '1.0.0') == -1 @@ -137,8 +138,15 @@ def test_should_say_equal_versions_are_equal(): def test_should_compare_versions_with_build_and_release(): - assert compare('1.1.9-rc.1', '1.1.9-rc.1+build.1') == -1 - assert compare('1.1.9-rc.1', '1.1.9+build.1') == 1 + assert compare('1.1.9-rc.1', '1.1.9-rc.1+build.1') == 0 + assert compare('1.1.9-rc.1', '1.1.9+build.1') == -1 + + +def test_should_ignore_builds_on_compare(): + assert compare('1.0.0+build.1', '1.0.0') == 0 + assert compare('1.0.0-alpha.1+build.1', '1.0.0-alpha.1') == 0 + assert compare('1.0.0+build.1', '1.0.0-alpha.1') == 1 + assert compare('1.0.0+build.1', '1.0.0-alpha.1+build.1') == 1 def test_should_correctly_format_version(): From c061a877bf7f8db2e00286452fa6b93a3298da7a Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 7 Jun 2016 19:40:49 +0300 Subject: [PATCH 022/312] 2.6.0 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 962b9676..ead16281 100644 --- a/semver.py +++ b/semver.py @@ -3,7 +3,7 @@ """ import re -__version__ = '2.5.0' +__version__ = '2.6.0' __author__ = 'Konstantine Rybnikov' __author_email__ = 'k-bx@k-bx.com' From c1505c943697fed5fe47cd6d970a98ab9d8dc648 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 20:45:25 +1000 Subject: [PATCH 023/312] Begin the change log document. --- CHANGELOG | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..ddb1c958 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,18 @@ +########## +Change Log +########## + +Python SemVer library +##################### + +All notable changes to this code base will be documented in this file, +in every released version. + + +.. + Local variables: + coding: utf-8 + mode: text + mode: rst + End: + vim: fileencoding=utf-8 filetype=rst : From 91d17a8ee5f13491a4a95c98dd914504a1ef922d Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 20:46:01 +1000 Subject: [PATCH 024/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C0.0.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ddb1c958..8b9c2087 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,15 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 0.0.1 +============= + +:Released: 2012-04-28 +:Maintainer: Konstantine Rybnikov + +* Initial release. + + .. Local variables: coding: utf-8 From 30c7e1f53727a06458a2886057c1cb3875bee171 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 20:53:02 +1000 Subject: [PATCH 025/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C0.0.2=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8b9c2087..1008881f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 0.0.2 +============= + +:Released: 2012-05-10 +:Maintainer: Konstantine Rybnikov + +Changes +------- + +* Use standard library Distutils for distribution management. + + Version 0.0.1 ============= From 9af27f15a2c3a7bb2a52062f80d7c8354ba61cd7 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 21:00:09 +1000 Subject: [PATCH 026/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.0.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1008881f..a88600d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,25 @@ All notable changes to this code base will be documented in this file, in every released version. +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 ============= From 97065a04f1f5be3567b9ccc5e5af0b75d41952e3 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 21:41:19 +1000 Subject: [PATCH 027/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.0.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index a88600d8..d3fd1026 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.0.1 +============= + +:Released: 2014-09-24 +:Maintainer: Konstantine Rybnikov + +Bug Fixes +--------- + +* [GitHub issue #9] Correct comparison of equal version strings. + + Version 2.0.0 ============= From 2cc1e53384f5ffb384ad334e4c31ed94a164815d Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 21:58:54 +1000 Subject: [PATCH 028/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.0.2=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d3fd1026..42e4beca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,20 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.0.2 +============= + +:Released: 2015-04-14 +:Maintainer: Konstantine Rybnikov + +Additions +--------- + +* Add configuration for Travis continuous integration. + +* Explicitly declare supported Python versions. + + Version 2.0.1 ============= From 777e3f16056c23aca680252ada71b9b590e79f8b Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:11:08 +1000 Subject: [PATCH 029/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.1.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 42e4beca..ddd6e90f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,34 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.1.0 +============= + +:Released: 2015-05-22 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Document installation instructions. + +* Document project home page. + +* Add function to format a version string from components. + +* Add functions to increment specific components in a version. + +Changes +------- + +* Migrate README document to Markdown format. + +Bug Fixes +--------- + +* Correct code examples in README document. + + Version 2.0.2 ============= From 15c3a17b1ccd1f5d9d7905fab961787e388514ef Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:20:54 +1000 Subject: [PATCH 030/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.1.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ddd6e90f..f0859fda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.1.1 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Remove absent document from distribution manifest. + + Version 2.1.0 ============= From f773bb1576149d80fcf710b53c87b0b50caadf6d Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:23:54 +1000 Subject: [PATCH 031/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.1.2=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f0859fda..e5cb5f92 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.1.2 +============= + +:Released: 2015-05-23 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Restore current README document to distribution manifest. + + Version 2.1.1 ============= From 508926b062d0abab08d097746347c221235a0794 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:31:05 +1000 Subject: [PATCH 032/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.2.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e5cb5f92..f898d595 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,20 @@ All notable changes to this code base will be documented in this file, in every released version. +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 ============= From 5d0435910c0de6d5323c957764e7336fedf774a6 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:55:10 +1000 Subject: [PATCH 033/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.2.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f898d595..facc9566 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.2.1 +============= + +:Released: 2015-08-04 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* Correct comparison when any component includes zero. + + Version 2.2.0 ============= From 16fc04ce9a896502407383b7bd989066a6813d82 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 22:58:58 +1000 Subject: [PATCH 034/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.3.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index facc9566..95bf704a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,19 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.3.0 +============= + +:Released: 2016-01-29 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Add functions to increment prerelease and build components in a + version. + + Version 2.2.1 ============= From 83b42a1a66e75f809e0bf09047b3e60be4615246 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:01:22 +1000 Subject: [PATCH 035/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.3.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 95bf704a..2b94448c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.3.1 +============= + +:Released: 2016-01-30 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Declare granted license name in distribution metadata. + + Version 2.3.0 ============= From 22d2704842267fb2300c3b473c30b38b15150da4 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:08:18 +1000 Subject: [PATCH 036/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.4.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 2b94448c..ca4eb53f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.4.0 +============= + +:Released: 2016-02-12 +:Maintainer: Kostiantyn Rybnikov + +Bug Fixes +--------- + +* [GitHub issue #21] Compare alphanumeric components correctly. + + Version 2.3.1 ============= From ff8a9601ee3fb9dbea345b0834ed124962ef4873 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:19:51 +1000 Subject: [PATCH 037/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.4.1=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ca4eb53f..80f88c0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,18 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.4.1 +============= + +:Released: 2016-03-04 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* [GitHub issue #23] Compare build component of a version. + + Version 2.4.0 ============= From b3579829b068742691625bbf36a27a8d2b8162ee Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:31:06 +1000 Subject: [PATCH 038/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.4.2=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 80f88c0c..e5aac3cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,24 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.4.2 +============= + +:Released: 2016-05-16 +:Maintainer: Kostiantyn Rybnikov + +Changes +------- + +* Migrate README document to reStructuredText format. + +* Use Setuptools for distribution management. + +* Migrate test cases to Py.test. + +* Add configuration for Tox test runner. + + Version 2.4.1 ============= From 0f024a7dbccb5b386ba99a1fa7b841e3061b171e Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:35:52 +1000 Subject: [PATCH 039/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.5.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e5aac3cd..9557c837 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,23 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.5.0 +============= + +:Released: 2016-05-25 +:Maintainer: Kostiantyn Rybnikov + +Additions +--------- + +* Support matching 'not equal' with “!=”. + +Changes +------- + +* Make separate builds for tests on Travis CI. + + Version 2.4.2 ============= From 9c65daef117e56739c6b47114d2a1e68acb8b438 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:40:08 +1000 Subject: [PATCH 040/312] =?UTF-8?q?Document=20change=20log=20entry=20for?= =?UTF-8?q?=20version=20=E2=80=9C2.6.0=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9557c837..6164bf2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,21 @@ All notable changes to this code base will be documented in this file, in every released 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 ============= From 3ac8505cfd77c7a24f9d336b5b43ddcd65c65238 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Sat, 20 Aug 2016 23:55:38 +1000 Subject: [PATCH 041/312] Document all significant contributors to date. --- CONTRIBUTORS | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000..bf1fbc11 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,44 @@ +############ +Contributors +############ + +Python SemVer library +##################### + +This document records the primary maintainers and significant +contributors to this code base. + +Thank you to everyone whose work has made this possible. + + +Primary maintainers +=================== + +* Kostiantyn Rybnikov + + +Significant contributors +======================== + +* Alexander Shorin +* Anton Talevnin +* Ben Finney +* Carles Barrobés +* Craig Blaszczyk +* Jan Pieter Waagmeester +* Jelo Agnasin +* Karol Werner +* Peter Bittner +* robi-wan +* T. Jameson Little +* Tuure Laurinolli +* Tyler Cross +* Zack Lalanne + +.. + Local variables: + coding: utf-8 + mode: text + mode: rst + End: + vim: fileencoding=utf-8 filetype=rst : From 3c0cf9b50767abbfcbdfdba27c01b916041d310f Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 25 Aug 2016 12:30:37 +0300 Subject: [PATCH 042/312] Remove python 3.2 support --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b20f8b03..10712b6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ env: - TOXENV=flake8 - TOXENV=py26 - TOXENV=py27 - - TOXENV=py32 + # - TOXENV=py32 - TOXENV=py33 - TOXENV=py34 - TOXENV=py35 From 98e83c4361211bd745eae4a17905d562f86eaed7 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Tue, 18 Oct 2016 23:59:54 +1100 Subject: [PATCH 043/312] =?UTF-8?q?Add=20documentation=20examples=20for=20?= =?UTF-8?q?=E2=80=98semver.parse=E2=80=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index 10739101..a9c9eab6 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,11 @@ This module provides just couple of functions, main of which are: False >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') '3.4.5-pre.2+build.4' + >>> version_parts = semver.parse("3.4.5-pre.2+build.4") + >>> version_parts == { + ... 'major': 3, 'minor': 4, 'patch': 5, + ... 'prerelease': 'pre.2', 'build': 'build.4'} + True >>> semver.bump_major("3.4.5") '4.0.0' >>> semver.bump_minor("3.4.5") From 94b478badf70b01ef02411b435278f9cf86e010e Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:00:21 +1100 Subject: [PATCH 044/312] Rename version parts binding for clarity. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The “version info” meaning already conventionally means the tuple of version components, like ‘sys.version_info’. Rename the mapping to avoid this meaning. --- semver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/semver.py b/semver.py index ead16281..7baf57a3 100644 --- a/semver.py +++ b/semver.py @@ -28,13 +28,13 @@ def parse(version): if match is None: raise ValueError('%s is not valid SemVer string' % version) - verinfo = match.groupdict() + version_parts = match.groupdict() - verinfo['major'] = int(verinfo['major']) - verinfo['minor'] = int(verinfo['minor']) - verinfo['patch'] = int(verinfo['patch']) + version_parts['major'] = int(version_parts['major']) + version_parts['minor'] = int(version_parts['minor']) + version_parts['patch'] = int(version_parts['patch']) - return verinfo + return version_parts def compare(ver1, ver2): From 6f95ca74d5dfac4b9c6586c674789c4268028720 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:03:01 +1100 Subject: [PATCH 045/312] Add a function to parse a version string to a version info tuple. --- README.rst | 9 +++++++++ semver.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.rst b/README.rst index a9c9eab6..ecc95e1a 100644 --- a/README.rst +++ b/README.rst @@ -48,6 +48,15 @@ This module provides just couple of functions, main of which are: ... 'major': 3, 'minor': 4, 'patch': 5, ... 'prerelease': 'pre.2', 'build': 'build.4'} True + >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") + >>> version_info + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> version_info.major + 3 + >>> version_info > (1, 0) + True + >>> version_info < (3, 5) + True >>> semver.bump_major("3.4.5") '4.0.0' >>> semver.bump_minor("3.4.5") diff --git a/semver.py b/semver.py index 7baf57a3..d9d99509 100644 --- a/semver.py +++ b/semver.py @@ -1,8 +1,11 @@ """ Python helper for Semantic Versioning (http://semver.org/) """ + +import collections import re + __version__ = '2.6.0' __author__ = 'Konstantine Rybnikov' __author_email__ = 'k-bx@k-bx.com' @@ -37,6 +40,21 @@ def parse(version): return version_parts +VersionInfo = collections.namedtuple( + 'VersionInfo', 'major minor patch prerelease build') + +def parse_version_info(version): + """ + Parse version string to a VersionInfo instance. + """ + parts = parse(version) + version_info = VersionInfo( + parts['major'], parts['minor'], parts['patch'], + parts['prerelease'], parts['build']) + + return version_info + + def compare(ver1, ver2): def nat_cmp(a, b): def convert(text): From fbeef3845f23eb036e6fa5df4a7a30159e6ff5d4 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:04:55 +1100 Subject: [PATCH 046/312] Document the new feature in the change log. --- CHANGELOG | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6164bf2c..2603dd49 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,19 @@ All notable changes to this code base will be documented in this file, in every released version. +Version NEXT +============ + +:Released: FUTURE +:Maintainer: UNKNOWN + +Additions +--------- + +* Add ‘parse_version_info’ to parse a version string to a version info + tuple. + + Version 2.6.0 ============= From 5746d91dfb5c64d69b93d3c5c415a18e9e6df8a6 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:25:49 +1100 Subject: [PATCH 047/312] Reformat the regex pattern for readability. --- semver.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/semver.py b/semver.py index ead16281..1321bcba 100644 --- a/semver.py +++ b/semver.py @@ -7,11 +7,24 @@ __author__ = 'Konstantine Rybnikov' __author_email__ = 'k-bx@k-bx.com' -_REGEX = re.compile('^(?P(?:0|[1-9][0-9]*))' - '\.(?P(?:0|[1-9][0-9]*))' - '\.(?P(?:0|[1-9][0-9]*))' - '(\-(?P[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' - '(\+(?P[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$') +_REGEX = re.compile( + r""" + ^ + (?P(?:0|[1-9][0-9]*)) + \. + (?P(?:0|[1-9][0-9]*)) + \. + (?P(?:0|[1-9][0-9]*)) + (\-(?P + [0-9A-Za-z-]+ + (\.[0-9A-Za-z-]+)* + ))? + (\+(?P + [0-9A-Za-z-]+ + (\.[0-9A-Za-z-]+)* + ))? + $ + """, re.VERBOSE) _LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') From 69e9125cfe3dc1537163ffbd4fdfe9c09025b686 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:35:21 +1100 Subject: [PATCH 048/312] Restrict pattern for prerelease component to match SemVer 2.0.0. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The specification §9 says, in part: “[…] Numeric identifiers MUST NOT include leading zeroes. […]”. --- semver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 1321bcba..18fe29e0 100644 --- a/semver.py +++ b/semver.py @@ -16,8 +16,8 @@ \. (?P(?:0|[1-9][0-9]*)) (\-(?P - [0-9A-Za-z-]+ - (\.[0-9A-Za-z-]+)* + [1-9A-Za-z-][0-9A-Za-z-]* + (\.[1-9A-Za-z-][0-9A-Za-z-]*)* ))? (\+(?P [0-9A-Za-z-]+ From ce6b285313c6da20d20b5239c1da713360821956 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Wed, 19 Oct 2016 00:37:57 +1100 Subject: [PATCH 049/312] Document the bug fix. --- CHANGELOG | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6164bf2c..fcf67f0d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,21 @@ All notable changes to this code base will be documented in this file, in every released version. +Version NEXT +============ + +:Released: FUTURE +:Maintainer: UNKNOWN + +Bug Fixes +--------- + +* 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 ============= From 518f9fcc4f12e77c021f82b3c30b7be9f2fbec84 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 20 Oct 2016 09:09:12 +0300 Subject: [PATCH 050/312] Add blank line --- semver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/semver.py b/semver.py index d9d99509..21b4d109 100644 --- a/semver.py +++ b/semver.py @@ -43,6 +43,7 @@ def parse(version): VersionInfo = collections.namedtuple( 'VersionInfo', 'major minor patch prerelease build') + def parse_version_info(version): """ Parse version string to a VersionInfo instance. From baf44a03282b26f62c791ade6ed35c348b9c8cd9 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 20 Oct 2016 12:54:11 +0300 Subject: [PATCH 051/312] Make 2.7.0 version --- semver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 4b6fdb55..d8688d31 100644 --- a/semver.py +++ b/semver.py @@ -6,8 +6,8 @@ import re -__version__ = '2.6.0' -__author__ = 'Konstantine Rybnikov' +__version__ = '2.7.0' +__author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' _REGEX = re.compile( From 398905881718b73deee22f5370575f34fe3ab83c Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 20 Oct 2016 12:55:43 +0300 Subject: [PATCH 052/312] Fix version in CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 43fbd7d6..22ea79f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,7 +9,7 @@ All notable changes to this code base will be documented in this file, in every released version. -Version NEXT +Version 2.7.0 ============ :Released: FUTURE From 45b562e77011c121f9ba7df6aedd609939d0d370 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 20 Oct 2016 13:01:06 +0300 Subject: [PATCH 053/312] Fix tests --- CHANGELOG | 2 +- tests.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 22ea79f9..35d48644 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,7 +10,7 @@ in every released version. Version 2.7.0 -============ +============= :Released: FUTURE :Maintainer: UNKNOWN diff --git a/tests.py b/tests.py index 05241e11..cb223368 100644 --- a/tests.py +++ b/tests.py @@ -209,7 +209,6 @@ def test_prerelease_order(): def test_should_bump_prerelease(): assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' - assert bump_prerelease('3.4.5-0009.dev') == '3.4.5-0010.dev' assert bump_prerelease('3.4.5') == '3.4.5-rc.1' From ce3b603314e7c9747823468ad8c058e87ffa9e85 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 20 Oct 2016 13:01:23 +0300 Subject: [PATCH 054/312] 2.7.1 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index d8688d31..79bbc112 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.7.0' +__version__ = '2.7.1' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From 5e41beb0aedf9ef43ea0a97c0e1696fb1270f1dc Mon Sep 17 00:00:00 2001 From: Alexander Puzynia Date: Wed, 9 Nov 2016 00:38:17 +0300 Subject: [PATCH 055/312] Fix #37 allow single zero without leading zeros in prerelease version --- semver.py | 4 ++-- tests.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 79bbc112..358670dd 100644 --- a/semver.py +++ b/semver.py @@ -19,8 +19,8 @@ \. (?P(?:0|[1-9][0-9]*)) (\-(?P - [1-9A-Za-z-][0-9A-Za-z-]* - (\.[1-9A-Za-z-][0-9A-Za-z-]*)* + (?:0|[1-9A-Za-z-][0-9A-Za-z-]*) + (\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))* ))? (\+(?P [0-9A-Za-z-]+ diff --git a/tests.py b/tests.py index cb223368..377a7d20 100644 --- a/tests.py +++ b/tests.py @@ -33,6 +33,28 @@ def test_should_parse_version(): } +def test_should_parse_zero_prerelease(): + result = parse("1.2.3-rc.0+build.0") + + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'rc.0', + 'build': 'build.0', + } + + result = parse("1.2.3-rc.0.0+build.0") + + assert result == { + 'major': 1, + 'minor': 2, + 'patch': 3, + 'prerelease': 'rc.0.0', + 'build': 'build.0', + } + + def test_should_get_less(): assert compare("1.0.0", "2.0.0") == -1 From d1a353575594df1fb4d06917ac922f2ab081760f Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 8 Nov 2016 23:45:26 +0200 Subject: [PATCH 056/312] 2.7.2 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 358670dd..0164bed9 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.7.1' +__version__ = '2.7.2' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From 8ac1146a91fbd6e16855164ad6ec07e9f65802f2 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Wed, 9 Nov 2016 00:45:04 +0200 Subject: [PATCH 057/312] Update changelog and contributors list --- CHANGELOG | 8 +++++--- CONTRIBUTORS | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 35d48644..d9aa0d10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,15 +9,17 @@ All notable changes to this code base will be documented in this file, in every released version. -Version 2.7.0 +Version 2.7.2 ============= -:Released: FUTURE -:Maintainer: UNKNOWN +:Released: 2016-11-08 +:Maintainer: Kostiantyn Rybnikov Additions --------- +* Fix issue #37 (Remove trailing zeros from prelease doesn't allow to + parse 0 pre-release version) * Add ‘parse_version_info’ to parse a version string to a version info tuple. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index bf1fbc11..68410992 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -20,6 +20,7 @@ Primary maintainers Significant contributors ======================== +* Alexander Puzynia * Alexander Shorin * Anton Talevnin * Ben Finney From c42aae342ca7a8e933773ea2dc8a2a14f2fb5698 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Mon, 19 Dec 2016 20:43:03 +0100 Subject: [PATCH 058/312] Add test case for docstring --- tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests.py b/tests.py index 377a7d20..5681aefc 100644 --- a/tests.py +++ b/tests.py @@ -13,6 +13,19 @@ from semver import max_ver +SEMVERFUNCS = [ + compare, match, parse, format_version, + bump_major, bump_minor, bump_patch, bump_prerelease, bump_build, + max_ver, min_ver, +] + + +@pytest.mark.parametrize("func", SEMVERFUNCS, + ids=[func.__name__ for func in SEMVERFUNCS]) +def test_fordocstrings(func): + assert func.__doc__, "Need a docstring for function %r" % func.__name + + def test_should_parse_version(): result = parse("1.2.3-alpha.1.2+build.11.e0f985a") assert result == { From 7afb3805db85f6127d571863e94a75e72e8f795a Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Mon, 19 Dec 2016 20:43:15 +0100 Subject: [PATCH 059/312] Add docstrings for each function --- semver.py | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 4 deletions(-) diff --git a/semver.py b/semver.py index 0164bed9..e264c6f7 100644 --- a/semver.py +++ b/semver.py @@ -1,9 +1,49 @@ """ Python helper for Semantic Versioning (http://semver.org/) + +Examples: +>>> import semver +>>> 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 +>>> semver.match("2.0.0", ">=1.0.0") +True +>>> semver.match("1.0.0", ">1.0.0") +False +>>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') +'3.4.5-pre.2+build.4' +>>> version_parts = semver.parse("3.4.5-pre.2+build.4") +>>> version_parts == { +... 'major': 3, 'minor': 4, 'patch': 5, +... 'prerelease': 'pre.2', 'build': 'build.4'} +True +>>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") +>>> version_info +VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') +>>> version_info.major +3 +>>> version_info > (1, 0) +True +>>> version_info < (3, 5) +True +>>> semver.bump_major("3.4.5") +'4.0.0' +>>> semver.bump_minor("3.4.5") +'3.5.0' +>>> semver.bump_patch("3.4.5") +'3.4.6' +>>> semver.max_ver("1.0.0", "2.0.0") +'2.0.0' +>>> semver.min_ver("1.0.0", "2.0.0") +'1.0.0' """ import collections import re +import sys __version__ = '2.7.2' @@ -37,8 +77,13 @@ def cmp(a, b): def parse(version): - """ - Parse version to major, minor, patch, pre-release, build parts. + """Parse version to major, minor, patch, pre-release, build parts. + + :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 """ match = _REGEX.match(version) if match is None: @@ -56,10 +101,31 @@ def parse(version): VersionInfo = collections.namedtuple( 'VersionInfo', 'major minor patch prerelease build') +# Only change it for Python > 3 as it is readonly +# for version 2 +if sys.version_info > (3,0): + VersionInfo.__doc__ = """ +:param int major: version when you make incompatible API changes. +:param int minor: version when you add functionality in + a backwards-compatible manner. +:param int patch: version when you make backwards-compatible bug fixes. +:param str prerelease: an optional prerelease string +:param str build: an optional build string + +>>> import semver +>>> ver = semver.parse('3.4.5-pre.2+build.4') +>>> ver +{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5, +'prerelease': 'pre.2'} +""" + def parse_version_info(version): - """ - Parse version string to a VersionInfo instance. + """Parse version string to a VersionInfo instance. + + :param version: version string + :return: a :class:`VersionInfo` instance + :rtype: :class:`VersionInfo` """ parts = parse(version) version_info = VersionInfo( @@ -70,6 +136,14 @@ def parse_version_info(version): def compare(ver1, ver2): + """Compare two versions + + :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 + """ def nat_cmp(a, b): def convert(text): return (2, int(text)) if re.match('[0-9]+', text) else (1, text) @@ -104,6 +178,19 @@ def compare_by_keys(d1, d2): def match(version, match_expr): + """Compare two versions 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 + """ prefix = match_expr[:2] if prefix in ('>=', '<=', '==', '!='): match_version = match_expr[2:] @@ -132,6 +219,13 @@ def match(version, match_expr): def max_ver(ver1, ver2): + """Returns the greater version of two versions + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the greater version of the two + :rtype: :class:`VersionInfo` + """ cmp_res = compare(ver1, ver2) if cmp_res == 0 or cmp_res == 1: return ver1 @@ -140,6 +234,13 @@ def max_ver(ver1, ver2): def min_ver(ver1, ver2): + """Returns the smaller version of two versions + + :param ver1: version string 1 + :param ver2: version string 2 + :return: the smaller version of the two + :rtype: :class:`VersionInfo` + """ cmp_res = compare(ver1, ver2) if cmp_res == 0 or cmp_res == -1: return ver1 @@ -148,6 +249,16 @@ def min_ver(ver1, ver2): def format_version(major, minor, patch, prerelease=None, build=None): + """Format a version according to the Semantic Versioning specification + + :param str major: the required major part of a version + :param str minor: the required minor part of a version + :param str 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 + """ version = "%d.%d.%d" % (major, minor, patch) if prerelease is not None: version = version + "-%s" % prerelease @@ -172,22 +283,46 @@ def _increment_string(string): def bump_major(version): + """Raise the major part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ verinfo = parse(version) return format_version(verinfo['major'] + 1, 0, 0) def bump_minor(version): + """Raise the minor part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'] + 1, 0) def bump_patch(version): + """Raise the patch part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'] + 1) def bump_prerelease(version): + """Raise the prerelease part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ verinfo = parse(version) verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or 'rc.0') return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], @@ -195,6 +330,12 @@ def bump_prerelease(version): def bump_build(version): + """Raise the build part of the version + + :param: version string + :return: the raised version string + :rtype: str + """ verinfo = parse(version) verinfo['build'] = _increment_string(verinfo['build'] or 'build.0') return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], From 9f60ad5a49dbaecf9fdae6a8338f1bb66c149562 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Mon, 19 Dec 2016 20:58:28 +0100 Subject: [PATCH 060/312] Fix flake8 errors --- semver.py | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index e264c6f7..7af1266d 100644 --- a/semver.py +++ b/semver.py @@ -103,7 +103,7 @@ def parse(version): # Only change it for Python > 3 as it is readonly # for version 2 -if sys.version_info > (3,0): +if sys.version_info > (3, 0): VersionInfo.__doc__ = """ :param int major: version when you make incompatible API changes. :param int minor: version when you add functionality in diff --git a/setup.py b/setup.py index ea363bc7..e9f60b17 100755 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ def read_file(filename): with open(join(dirname(__file__), filename)) as f: return f.read() + setup( name=package.__name__, version=package.__version__, From 41a071595cdb400e625f366838b35d61d538ac7e Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 20 Dec 2016 00:05:27 +0200 Subject: [PATCH 061/312] 2.7.3 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 7af1266d..d82718b6 100644 --- a/semver.py +++ b/semver.py @@ -46,7 +46,7 @@ import sys -__version__ = '2.7.2' +__version__ = '2.7.3' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From e1a633cc445ba63a9fbf23b679994d5fa0554cf7 Mon Sep 17 00:00:00 2001 From: Piper Merriam Date: Mon, 16 Jan 2017 08:55:02 -0700 Subject: [PATCH 062/312] fix version comparisons --- semver.py | 20 ++++++++++++++++++-- tests.py | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/semver.py b/semver.py index d82718b6..4d1c6eb6 100644 --- a/semver.py +++ b/semver.py @@ -146,13 +146,29 @@ def compare(ver1, ver2): """ def nat_cmp(a, b): def convert(text): - return (2, int(text)) if re.match('[0-9]+', text) else (1, 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 '' - return cmp(split_key(a), split_key(b)) + 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)) def compare_by_keys(d1, d2): for key in ['major', 'minor', 'patch']: diff --git a/tests.py b/tests.py index 5681aefc..1e724efe 100644 --- a/tests.py +++ b/tests.py @@ -70,10 +70,24 @@ def test_should_parse_zero_prerelease(): def test_should_get_less(): assert compare("1.0.0", "2.0.0") == -1 + assert compare('1.0.0-alpha', '1.0.0-alpha.1') == -1 + assert compare('1.0.0-alpha.1', '1.0.0-alpha.beta') == -1 + assert compare('1.0.0-alpha.beta', '1.0.0-beta') == -1 + assert compare('1.0.0-beta', '1.0.0-beta.2') == -1 + assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 + assert compare('1.0.0-beta.11', '1.0.0-rc.1') == -1 + assert compare('1.0.0-rc.1', '1.0.0') == -1 def test_should_get_greater(): assert compare("2.0.0", "1.0.0") == 1 + assert compare('1.0.0-alpha.1', '1.0.0-alpha') == 1 + assert compare('1.0.0-alpha.beta', '1.0.0-alpha.1') == 1 + assert compare('1.0.0-beta', '1.0.0-alpha.beta') == 1 + assert compare('1.0.0-beta.2', '1.0.0-beta') == 1 + assert compare('1.0.0-beta.11', '1.0.0-beta.2') == 1 + assert compare('1.0.0-rc.1', '1.0.0-beta.11') == 1 + assert compare('1.0.0', '1.0.0-rc.1') == 1 def test_should_match_simple(): @@ -232,7 +246,7 @@ def test_prerelease_order(): assert min_ver('1.2.3-Rc10', '1.2.3-rc10') == '1.2.3-Rc10' # Numeric identifiers always have lower precedence than non-numeric # identifiers. - assert min_ver('1.2.3-2', '1.2.3-rc') == '1.2.3-rc' + assert min_ver('1.2.3-2', '1.2.3-rc') == '1.2.3-2' # A larger set of pre-release fields has a higher precedence than a # smaller set, if all of the preceding identifiers are equal. assert min_ver('1.2.3-rc.2.1', '1.2.3-rc.2') == '1.2.3-rc.2' From 821961810dc3a3964df84454fcbb42e069e99cfe Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Mon, 16 Jan 2017 23:38:30 +0200 Subject: [PATCH 063/312] 2.7.4 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 4d1c6eb6..4d4bb631 100644 --- a/semver.py +++ b/semver.py @@ -46,7 +46,7 @@ import sys -__version__ = '2.7.3' +__version__ = '2.7.4' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From d3d707a502dbac0b3966e25b7c94daba51756561 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Sun, 29 Jan 2017 12:33:41 +0200 Subject: [PATCH 064/312] Fix docstring --- semver.py | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/semver.py b/semver.py index 4d4bb631..c804479b 100644 --- a/semver.py +++ b/semver.py @@ -1,44 +1,5 @@ """ Python helper for Semantic Versioning (http://semver.org/) - -Examples: ->>> import semver ->>> 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 ->>> semver.match("2.0.0", ">=1.0.0") -True ->>> semver.match("1.0.0", ">1.0.0") -False ->>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') -'3.4.5-pre.2+build.4' ->>> version_parts = semver.parse("3.4.5-pre.2+build.4") ->>> version_parts == { -... 'major': 3, 'minor': 4, 'patch': 5, -... 'prerelease': 'pre.2', 'build': 'build.4'} -True ->>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") ->>> version_info -VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') ->>> version_info.major -3 ->>> version_info > (1, 0) -True ->>> version_info < (3, 5) -True ->>> semver.bump_major("3.4.5") -'4.0.0' ->>> semver.bump_minor("3.4.5") -'3.5.0' ->>> semver.bump_patch("3.4.5") -'3.4.6' ->>> semver.max_ver("1.0.0", "2.0.0") -'2.0.0' ->>> semver.min_ver("1.0.0", "2.0.0") -'1.0.0' """ import collections @@ -46,7 +7,7 @@ import sys -__version__ = '2.7.4' +__version__ = '2.7.5' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From c83add849e5661d18f2313acd3253dba83ac21b2 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 28 Feb 2017 18:03:10 +0200 Subject: [PATCH 065/312] Write docstring on 3.5+ --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index c804479b..eea54910 100644 --- a/semver.py +++ b/semver.py @@ -64,7 +64,7 @@ def parse(version): # Only change it for Python > 3 as it is readonly # for version 2 -if sys.version_info > (3, 0): +if sys.version_info >= (3, 5): VersionInfo.__doc__ = """ :param int major: version when you make incompatible API changes. :param int minor: version when you add functionality in From c679d138abf6fc79f0575390197828601103941f Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Tue, 28 Feb 2017 18:11:00 +0200 Subject: [PATCH 066/312] 2.7.6 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index eea54910..c1b177df 100644 --- a/semver.py +++ b/semver.py @@ -7,7 +7,7 @@ import sys -__version__ = '2.7.5' +__version__ = '2.7.6' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From a69b632f655dbfe2ed1e62d61d2d26a983bfbd46 Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Fri, 19 May 2017 23:02:03 +0200 Subject: [PATCH 067/312] Add comparision between VersionInfo objects --- semver.py | 165 +++++++++++++++++++++++++++++++++--------------------- tests.py | 61 ++++++++++++++++++++ 2 files changed, 161 insertions(+), 65 deletions(-) diff --git a/semver.py b/semver.py index c1b177df..bed3f928 100644 --- a/semver.py +++ b/semver.py @@ -4,7 +4,6 @@ import collections import re -import sys __version__ = '2.7.6' @@ -59,26 +58,59 @@ def parse(version): return version_parts -VersionInfo = collections.namedtuple( - 'VersionInfo', 'major minor patch prerelease build') - -# Only change it for Python > 3 as it is readonly -# for version 2 -if sys.version_info >= (3, 5): - VersionInfo.__doc__ = """ -:param int major: version when you make incompatible API changes. -:param int minor: version when you add functionality in - a backwards-compatible manner. -:param int patch: version when you make backwards-compatible bug fixes. -:param str prerelease: an optional prerelease string -:param str build: an optional build string - ->>> import semver ->>> ver = semver.parse('3.4.5-pre.2+build.4') ->>> ver -{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5, -'prerelease': 'pre.2'} -""" +class VersionInfo(collections.namedtuple( + 'VersionInfo', 'major minor patch prerelease build')): + """ + :param int major: version when you make incompatible API changes. + :param int minor: version when you add functionality in + a backwards-compatible manner. + :param int patch: version when you make backwards-compatible bug fixes. + :param str prerelease: an optional prerelease string + :param str build: an optional build string + + >>> import semver + >>> ver = semver.parse('3.4.5-pre.2+build.4') + >>> ver + {'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5, + 'prerelease': 'pre.2'} + """ + __slots__ = () + + def __eq__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 + + def __ne__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) != 0 + + def __lt__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) < 0 + + def __le__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0 + + def __gt__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) > 0 + + def __ge__(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 + + +def _to_dict(obj): + if isinstance(obj, VersionInfo): + return obj._asdict() + return obj def parse_version_info(version): @@ -96,6 +128,52 @@ def parse_version_info(version): return version_info +def _nat_cmp(a, b): + 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)) + + +def _compare_by_keys(d1, d2): + for key in ['major', 'minor', 'patch']: + v = cmp(d1.get(key), d2.get(key)) + if v: + return v + + rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') + rccmp = _nat_cmp(rc1, rc2) + + if not rccmp: + return 0 + if not rc1: + return 1 + elif not rc2: + return -1 + + return rccmp + + def compare(ver1, ver2): """Compare two versions @@ -105,53 +183,10 @@ def compare(ver1, ver2): zero if ver1 == ver2 and strictly positive if ver1 > ver2 :rtype: int """ - def nat_cmp(a, b): - 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)) - - def compare_by_keys(d1, d2): - for key in ['major', 'minor', 'patch']: - v = cmp(d1.get(key), d2.get(key)) - if v: - return v - - rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') - rccmp = nat_cmp(rc1, rc2) - - if not rccmp: - return 0 - if not rc1: - return 1 - elif not rc2: - return -1 - - return rccmp v1, v2 = parse(ver1), parse(ver2) - return compare_by_keys(v1, v2) + return _compare_by_keys(v1, v2) def match(version, match_expr): diff --git a/tests.py b/tests.py index 1e724efe..afc552c9 100644 --- a/tests.py +++ b/tests.py @@ -11,6 +11,7 @@ from semver import bump_build from semver import min_ver from semver import max_ver +from semver import VersionInfo SEMVERFUNCS = [ @@ -270,3 +271,63 @@ def test_should_bump_build(): assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev' assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' assert bump_build('3.4.5') == '3.4.5+build.1' + + +def test_should_compare_version_info_objects(): + v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v2 = VersionInfo( + major=0, minor=10, patch=4, prerelease='beta.1', build=None) + + # use `not` to enforce using comparision operators + assert v1 != v2 + assert v1 > v2 + assert v1 >= v2 + assert not(v1 < v2) + assert not(v1 <= v2) + assert not(v1 == v2) + + v3 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + + assert not(v1 != v3) + assert not(v1 > v3) + assert v1 >= v3 + assert not(v1 < v3) + assert v1 <= v3 + assert v1 == v3 + + v4 = VersionInfo(major=0, minor=10, patch=5, prerelease=None, build=None) + assert v1 != v4 + assert not(v1 > v4) + assert not(v1 >= v4) + assert v1 < v4 + assert v1 <= v4 + assert not(v1 == v4) + + +def test_should_compare_version_dictionaries(): + v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None) + + assert v1 != v2 + assert v1 > v2 + assert v1 >= v2 + assert not(v1 < v2) + assert not(v1 <= v2) + assert not(v1 == v2) + + v3 = dict(major=0, minor=10, patch=4, prerelease=None, build=None) + + assert not(v1 != v3) + assert not(v1 > v3) + assert v1 >= v3 + assert not(v1 < v3) + assert v1 <= v3 + assert v1 == v3 + + v4 = dict(major=0, minor=10, patch=5, prerelease=None, build=None) + assert v1 != v4 + assert not(v1 > v4) + assert not(v1 >= v4) + assert v1 < v4 + assert v1 <= v4 + assert not(v1 == v4) From 2d92aa1f1e3a5216b162dee19ec914806b950c18 Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Mon, 22 May 2017 18:21:45 +0200 Subject: [PATCH 068/312] Add support for Python 3.6 --- setup.py | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e9f60b17..4b02e18d 100755 --- a/setup.py +++ b/setup.py @@ -96,6 +96,7 @@ def read_file(filename): 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Libraries :: Python Modules', ], tests_require=['tox', 'virtualenv<14.0.0'], diff --git a/tox.ini b/tox.ini index bde3b0d2..c909157d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = flake8 - py{26,27,32,33,34,35} + py{26,27,32,33,34,35,36} pypy [testenv] From 33fbbcb3525b932d571fbe636e5c8a8686e3e963 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 25 May 2017 16:59:39 +0300 Subject: [PATCH 069/312] 2.7.3 changelog --- CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d9aa0d10..2d09bc86 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,14 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.7.3 +============= + +:Released: 2017-05-25 +:Maintainer: Kostiantyn Rybnikov + +* Issue #54 (PR #55) Add comparision between VersionInfo objects +* PR #56. Add support for Python 3.6 Version 2.7.2 ============= From 91ec1df3bc478d7bcd5d4a429b34ba2658e31106 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 25 May 2017 17:00:07 +0300 Subject: [PATCH 070/312] 2.7.7 changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2d09bc86..0ad2b120 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,7 +8,7 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. -Version 2.7.3 +Version 2.7.7 ============= :Released: 2017-05-25 From d4e55391194b42f0a0fded81d4c0e5f660c8117f Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Thu, 25 May 2017 17:02:22 +0300 Subject: [PATCH 071/312] Version 2.7.7 --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index bed3f928..e66baf28 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.7.6' +__version__ = '2.7.7' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From a5d781de138e32815e8647ceeb6bcb5187520a39 Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Fri, 26 May 2017 11:41:45 +0200 Subject: [PATCH 072/312] Update Travis config file to use correct Python versions for testing --- .travis.yml | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10712b6a..350e5801 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,31 @@ language: python -python: "3.5" install: pip install "virtualenv<14.0.0" script: python setup.py test -env: - # To generate (update) the env list run - # $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM - - TOXENV=flake8 - - TOXENV=py26 - - TOXENV=py27 - # - TOXENV=py32 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=py35 - - TOXENV=pypy +matrix: + include: + - python: "2.6" + env: TOXENV=py26 + + - python: "2.7" + env: TOXENV=py27 + + - python: "3.2" + env: TOXENV=py32 + + - python: "3.3" + env: TOXENV=py33 + + - python: "3.4" + env: TOXENV=py34 + + - python: "3.4" + env: TOXENV=flake8 + + - python: "3.5" + env: TOXENV=py35 + + - python: "3.6" + env: TOXENV=py36 + + - python: "pypy" + env: TOXENV=pypy From d2eb12b37df4cc9999d13a0b7a48ab88562d61b6 Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Fri, 26 May 2017 11:47:14 +0200 Subject: [PATCH 073/312] Remove Python 3.2 from testing matrix --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 350e5801..5fbc4df5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,6 @@ matrix: - python: "2.7" env: TOXENV=py27 - - python: "3.2" - env: TOXENV=py32 - - python: "3.3" env: TOXENV=py33 From b103ab68c55c4d152ca0199b52e4c5eafa36dbc9 Mon Sep 17 00:00:00 2001 From: Karol Werner Date: Sun, 13 Aug 2017 23:36:28 +0200 Subject: [PATCH 074/312] Update regex in 'convert' to match only strings with numbers, fixes #60 --- semver.py | 2 +- tests.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index e66baf28..fb943d99 100644 --- a/semver.py +++ b/semver.py @@ -130,7 +130,7 @@ def parse_version_info(version): def _nat_cmp(a, b): def convert(text): - return int(text) if re.match('[0-9]+', text) else text + return int(text) if re.match('^[0-9]+$', text) else text def split_key(key): return [convert(c) for c in key.split('.')] diff --git a/tests.py b/tests.py index afc552c9..6be4aabb 100644 --- a/tests.py +++ b/tests.py @@ -331,3 +331,10 @@ def test_should_compare_version_dictionaries(): assert v1 < v4 assert v1 <= v4 assert not(v1 == v4) + + +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') + assert v1 < v2 + assert compare("1.9.1-1unms", "1.9.1+1") == -1 From 2e0dcadd4d680fee20c9ac802f25cb5c255eb517 Mon Sep 17 00:00:00 2001 From: ofek Date: Thu, 24 Aug 2017 22:11:11 -0400 Subject: [PATCH 075/312] support custom names for pre and build --- semver.py | 8 ++++---- tests.py | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/semver.py b/semver.py index fb943d99..d34de99f 100644 --- a/semver.py +++ b/semver.py @@ -328,7 +328,7 @@ def bump_patch(version): verinfo['patch'] + 1) -def bump_prerelease(version): +def bump_prerelease(version, token='rc'): """Raise the prerelease part of the version :param: version string @@ -336,12 +336,12 @@ def bump_prerelease(version): :rtype: str """ verinfo = parse(version) - verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or 'rc.0') + verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or token + '.0') return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease']) -def bump_build(version): +def bump_build(version, token='build'): """Raise the build part of the version :param: version string @@ -349,6 +349,6 @@ def bump_build(version): :rtype: str """ verinfo = parse(version) - verinfo['build'] = _increment_string(verinfo['build'] or 'build.0') + verinfo['build'] = _increment_string(verinfo['build'] or token + '.0') return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease'], verinfo['build']) diff --git a/tests.py b/tests.py index 6be4aabb..2e0f13bb 100644 --- a/tests.py +++ b/tests.py @@ -260,6 +260,7 @@ def test_prerelease_order(): def test_should_bump_prerelease(): assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' assert bump_prerelease('3.4.5') == '3.4.5-rc.1' + assert bump_prerelease('3.4.5', 'dev') == '3.4.5-dev.1' def test_should_ignore_build_on_prerelease_bump(): @@ -271,6 +272,7 @@ def test_should_bump_build(): assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev' assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' assert bump_build('3.4.5') == '3.4.5+build.1' + assert bump_build('3.4.5', 'nightly') == '3.4.5+nightly.1' def test_should_compare_version_info_objects(): From b871a14a34f5e12557286a9a4a04e1747be4abc9 Mon Sep 17 00:00:00 2001 From: ofek Date: Thu, 24 Aug 2017 22:15:29 -0400 Subject: [PATCH 076/312] fix pep8 --- semver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index d34de99f..79060890 100644 --- a/semver.py +++ b/semver.py @@ -336,7 +336,9 @@ def bump_prerelease(version, token='rc'): :rtype: str """ verinfo = parse(version) - verinfo['prerelease'] = _increment_string(verinfo['prerelease'] or token + '.0') + verinfo['prerelease'] = _increment_string( + verinfo['prerelease'] or token + '.0' + ) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease']) From 5d88053177b254c2ecd320c76f723ea8b2305702 Mon Sep 17 00:00:00 2001 From: ofek Date: Thu, 24 Aug 2017 22:44:25 -0400 Subject: [PATCH 077/312] finish up --- semver.py | 12 ++++++++---- tests.py | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/semver.py b/semver.py index 79060890..70adf07d 100644 --- a/semver.py +++ b/semver.py @@ -331,13 +331,14 @@ def bump_patch(version): def bump_prerelease(version, token='rc'): """Raise the prerelease part of the version - :param: version string + :param version: version string + :param token: defaults to 'rc' :return: the raised version string :rtype: str """ verinfo = parse(version) verinfo['prerelease'] = _increment_string( - verinfo['prerelease'] or token + '.0' + verinfo['prerelease'] or (token or 'rc') + '.0' ) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease']) @@ -346,11 +347,14 @@ def bump_prerelease(version, token='rc'): def bump_build(version, token='build'): """Raise the build part of the version - :param: version string + :param version: version string + :param token: defaults to 'build' :return: the raised version string :rtype: str """ verinfo = parse(version) - verinfo['build'] = _increment_string(verinfo['build'] or token + '.0') + verinfo['build'] = _increment_string( + verinfo['build'] or (token or 'build') + '.0' + ) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease'], verinfo['build']) diff --git a/tests.py b/tests.py index 2e0f13bb..15d04f66 100644 --- a/tests.py +++ b/tests.py @@ -261,6 +261,7 @@ def test_should_bump_prerelease(): assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' assert bump_prerelease('3.4.5') == '3.4.5-rc.1' assert bump_prerelease('3.4.5', 'dev') == '3.4.5-dev.1' + assert bump_prerelease('3.4.5', '') == '3.4.5-rc.1' def test_should_ignore_build_on_prerelease_bump(): @@ -273,6 +274,7 @@ def test_should_bump_build(): assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' assert bump_build('3.4.5') == '3.4.5+build.1' assert bump_build('3.4.5', 'nightly') == '3.4.5+nightly.1' + assert bump_build('3.4.5', '') == '3.4.5+build.1' def test_should_compare_version_info_objects(): From 88e523a4cee204398311be044dc1b6dae78d7dc1 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Fri, 25 Aug 2017 10:04:06 +0300 Subject: [PATCH 078/312] 2.7.8 changelog --- CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0ad2b120..2394ac9b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,14 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.7.8 +============= + +:Released: 2017-08-25 +:Maintainer: Kostiantyn Rybnikov + +* PR #62. Support custom default names for pre and build + Version 2.7.7 ============= From b4275e5b00e15dedb9b8ddacc194e3faf7f15feb Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Fri, 25 Aug 2017 10:04:48 +0300 Subject: [PATCH 079/312] Update version in code --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 70adf07d..7abc6b39 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.7.7' +__version__ = '2.7.8' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From d64861157328cb695f0872b6335c44f98349f098 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Sun, 27 Aug 2017 10:59:00 +0300 Subject: [PATCH 080/312] Add a Makefile to remember the sdist command --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f1272fa8 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +sdist: + python setup.py sdist bdist_wheel --universal + +.PHONY sdist + From 95b4cf1cb50ab9c4a7694b04a64b2afa6da7191c Mon Sep 17 00:00:00 2001 From: Philip Blair Date: Sat, 23 Sep 2017 11:53:22 -0400 Subject: [PATCH 081/312] Add finalize_version function --- semver.py | 11 +++++++++++ tests.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 7abc6b39..abb6aa6c 100644 --- a/semver.py +++ b/semver.py @@ -358,3 +358,14 @@ def bump_build(version, token='build'): ) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], verinfo['prerelease'], verinfo['build']) + + +def finalize_version(version): + """Remove any prerelease and build metadata from the version + + :param version: version string + :return: the finalized version string + :rtype: str + """ + verinfo = parse(version) + return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) diff --git a/tests.py b/tests.py index 15d04f66..a3e2ab7b 100644 --- a/tests.py +++ b/tests.py @@ -9,6 +9,7 @@ from semver import bump_patch from semver import bump_prerelease from semver import bump_build +from semver import finalize_version from semver import min_ver from semver import max_ver from semver import VersionInfo @@ -17,7 +18,7 @@ SEMVERFUNCS = [ compare, match, parse, format_version, bump_major, bump_minor, bump_patch, bump_prerelease, bump_build, - max_ver, min_ver, + max_ver, min_ver, finalize_version ] @@ -277,6 +278,15 @@ def test_should_bump_build(): assert bump_build('3.4.5', '') == '3.4.5+build.1' +def test_should_finalize_version(): + assert finalize_version('1.2.3') == '1.2.3' + assert finalize_version('1.2.3-rc.5') == '1.2.3' + assert finalize_version('1.2.3+build.2') == '1.2.3' + assert finalize_version('1.2.3-rc.1+build.5') == '1.2.3' + assert finalize_version('1.2.3-alpha') == '1.2.3' + assert finalize_version('1.2.0') == '1.2.0' + + def test_should_compare_version_info_objects(): v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) v2 = VersionInfo( From 2001c62d1a0361c44acc7076d8ce91e1d1c66141 Mon Sep 17 00:00:00 2001 From: Kostiantyn Rybnikov Date: Sat, 23 Sep 2017 20:11:06 +0300 Subject: [PATCH 082/312] 2.7.9 version --- CHANGELOG | 8 ++++++++ semver.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2394ac9b..d13b8874 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,14 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.7.9 +============= + +:Released: 2017-09-23 +:Maintainer: Kostiantyn Rybnikov + +* Issue #65 (PR #66). Add finalize_version function + Version 2.7.8 ============= diff --git a/semver.py b/semver.py index abb6aa6c..6ed7887b 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.7.8' +__version__ = '2.7.9' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' From 65d05f02456cd0b40b3bc65e6822cc79583343e4 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Tue, 15 May 2018 18:55:59 +0200 Subject: [PATCH 083/312] Remove Python 2.6 support --- .travis.yml | 3 --- setup.py | 1 - tox.ini | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fbc4df5..b89767aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ install: pip install "virtualenv<14.0.0" script: python setup.py test matrix: include: - - python: "2.6" - env: TOXENV=py26 - - python: "2.7" env: TOXENV=py27 diff --git a/setup.py b/setup.py index 4b02e18d..84512cdb 100755 --- a/setup.py +++ b/setup.py @@ -89,7 +89,6 @@ def read_file(filename): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', diff --git a/tox.ini b/tox.ini index c909157d..dee4a7d3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = flake8 - py{26,27,32,33,34,35,36} + py{27,32,33,34,35,36} pypy [testenv] From 3a195f2c6ef8d393c6ab1c4c9d47710667603394 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Tue, 15 May 2018 22:08:07 +0200 Subject: [PATCH 084/312] Add a release procedure (#81) --- release-procedure.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 release-procedure.md diff --git a/release-procedure.md b/release-procedure.md new file mode 100644 index 00000000..53ab04a7 --- /dev/null +++ b/release-procedure.md @@ -0,0 +1,16 @@ +* Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated +* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG) have been updated +* Tag commit and push to github using command line interface +```bash +git tag -a x.x.x -m 'Version x.x.x' +git push python-semver master --tags +``` +or using GitHub web interface available at https://github.com/k-bx/python-semver/releases + +* Upload to PyPI + +```bash +git clean -xfd +python setup.py register sdist bdist_wheel --universal +twine upload dist/* +``` From 3dfd54f8a05058259614eb5f07f445f984f11acd Mon Sep 17 00:00:00 2001 From: scls19fr Date: Tue, 15 May 2018 22:14:38 +0200 Subject: [PATCH 085/312] Implements __str__ and __hash__ (#75) * Implements __str__ and __hash__ Closes #72, #73 Should help #74 --- semver.py | 6 ++++++ tests.py | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/semver.py b/semver.py index 6ed7887b..ecebafd0 100644 --- a/semver.py +++ b/semver.py @@ -106,6 +106,12 @@ def __ge__(self, other): return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 + def __str__(self): + return format_version(*self) + + def __hash__(self): + return hash(tuple(self)) + def _to_dict(obj): if isinstance(obj, VersionInfo): diff --git a/tests.py b/tests.py index a3e2ab7b..2b54a66f 100644 --- a/tests.py +++ b/tests.py @@ -13,6 +13,7 @@ from semver import min_ver from semver import max_ver from semver import VersionInfo +from semver import parse_version_info SEMVERFUNCS = [ @@ -352,3 +353,11 @@ def test_should_compare_prerelease_with_numbers_and_letters(): v2 = VersionInfo(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_parse_version_info_str_hash(): + s_version = "1.2.3-alpha.1.2+build.11.e0f985a" + v = parse_version_info(s_version) + assert v.__str__() == s_version + d = {} + d[v] = "" # to ensure that VersionInfo are hashable From e6d4ff5b2e2145faa3c6f5e83a65f65160f8363e Mon Sep 17 00:00:00 2001 From: scls19fr Date: Tue, 15 May 2018 22:21:45 +0200 Subject: [PATCH 086/312] Rename tests file (#83) Closes #82 --- tests.py => test_semver.py | 0 tox.ini | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests.py => test_semver.py (100%) diff --git a/tests.py b/test_semver.py similarity index 100% rename from tests.py rename to test_semver.py diff --git a/tox.ini b/tox.ini index dee4a7d3..8a59328a 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = pypy [testenv] -commands = py.test -q tests.py +commands = py.test -q deps = pytest setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 From e4e9e558bf3e1d2cd5f41a58bf4fc48ca5ae424a Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 16 May 2018 13:05:23 +0200 Subject: [PATCH 087/312] Release 2.8.0 (#84) Closes #79 --- CHANGELOG | 10 ++++++++++ CONTRIBUTORS | 1 + release-procedure.md | 18 +++++++++++++++++- semver.py | 4 +++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d13b8874..db9118a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,16 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.8.0 +============= +:Released: 2018-05-16 +:Maintainer: Sébastien Celles + +* Issue #76 (PR #80). Remove Python 2.6 compatibility +* Issue #79 (PR #81 #84). Define and improve a release procedure file +* Issue #72 #73 (PR #75). Implements __str__ and __hash__ +* Issue #82 (PR #83). Rename test.py to test_semver.py so py.test can autodiscover test file + Version 2.7.9 ============= diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 68410992..ce0b166c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -15,6 +15,7 @@ Primary maintainers =================== * Kostiantyn Rybnikov +* Sébastien Celles Significant contributors diff --git a/release-procedure.md b/release-procedure.md index 53ab04a7..595b2f11 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -1,5 +1,18 @@ -* Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated +* Verify that latest build was passing https://travis-ci.org/k-bx/python-semver + +* Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated and follow https://semver.org/ + * Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG) have been updated + +* If one or several supported Python versions have removed or add, verify that the 3 following files have been updated: + * [setup.py](https://github.com/k-bx/python-semver/blob/master/setup.py) + * [tox.ini](https://github.com/k-bx/python-semver/blob/master/tox.ini) + * [.travis.yml](https://github.com/k-bx/python-semver/blob/master/.travis.yml) + +* Verify that doc reflecting new changes have been updated + +* Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/k-bx/python-semver/blob/master/CONTRIBUTORS) + * Tag commit and push to github using command line interface ```bash git tag -a x.x.x -m 'Version x.x.x' @@ -14,3 +27,6 @@ git clean -xfd python setup.py register sdist bdist_wheel --universal twine upload dist/* ``` + +* Go to https://pypi.org/project/semver/ to verify that new version is online and page is rendered correctly + diff --git a/semver.py b/semver.py index ecebafd0..dfeb431d 100644 --- a/semver.py +++ b/semver.py @@ -6,9 +6,11 @@ import re -__version__ = '2.7.9' +__version__ = '2.8.0' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' +__maintainer__ = 'Sebastien Celles' +__maintainer_email__ = "s.celles@gmail.com" _REGEX = re.compile( r""" From 1268276d6ec26ebf9ee01381843bca64a614c842 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Wed, 16 May 2018 16:00:57 +0200 Subject: [PATCH 088/312] Convert multiple tests into pytest.mark.parametrize (#47) * Convert multiple tests into pytest.mark.parametrize * Prepare for v2.8.1 --- CHANGELOG | 7 ++ semver.py | 2 +- test_semver.py | 301 ++++++++++++++++++++++++++++--------------------- 3 files changed, 182 insertions(+), 128 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index db9118a3..8291e0a3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,13 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.8.1 (WIP) +=================== +:Released: 2018-mm-dd +:Maintainer: Sébastien Celles + +* Issue #77 (PR #47). Convert multiple tests into pytest.mark.parametrize + Version 2.8.0 ============= :Released: 2018-05-16 diff --git a/semver.py b/semver.py index dfeb431d..e3d2d5bd 100644 --- a/semver.py +++ b/semver.py @@ -6,7 +6,7 @@ import re -__version__ = '2.8.0' +__version__ = '2.8.1' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' __maintainer__ = 'Sebastien Celles' diff --git a/test_semver.py b/test_semver.py index 2b54a66f..74903cde 100644 --- a/test_semver.py +++ b/test_semver.py @@ -29,68 +29,82 @@ def test_fordocstrings(func): assert func.__doc__, "Need a docstring for function %r" % func.__name -def test_should_parse_version(): - result = parse("1.2.3-alpha.1.2+build.11.e0f985a") - assert result == { +@pytest.mark.parametrize("version,expected", [ + # no. 1 + ("1.2.3-alpha.1.2+build.11.e0f985a", + { 'major': 1, 'minor': 2, 'patch': 3, 'prerelease': 'alpha.1.2', 'build': 'build.11.e0f985a', - } - - result = parse("1.2.3-alpha-1+build.11.e0f985a") - assert result == { + }), + # no. 2 + ("1.2.3-alpha-1+build.11.e0f985a", + { 'major': 1, 'minor': 2, 'patch': 3, 'prerelease': 'alpha-1', 'build': 'build.11.e0f985a', - } - + }), +]) +def test_should_parse_version(version, expected): + result = parse(version) + assert result == expected -def test_should_parse_zero_prerelease(): - result = parse("1.2.3-rc.0+build.0") - assert result == { +@pytest.mark.parametrize("version,expected", [ + # no. 1 + ("1.2.3-rc.0+build.0", + { 'major': 1, 'minor': 2, 'patch': 3, 'prerelease': 'rc.0', 'build': 'build.0', - } - - result = parse("1.2.3-rc.0.0+build.0") - - assert result == { + }), + # no. 2 + ("1.2.3-rc.0.0+build.0", + { 'major': 1, 'minor': 2, 'patch': 3, 'prerelease': 'rc.0.0', 'build': 'build.0', - } - - -def test_should_get_less(): - assert compare("1.0.0", "2.0.0") == -1 - assert compare('1.0.0-alpha', '1.0.0-alpha.1') == -1 - assert compare('1.0.0-alpha.1', '1.0.0-alpha.beta') == -1 - assert compare('1.0.0-alpha.beta', '1.0.0-beta') == -1 - assert compare('1.0.0-beta', '1.0.0-beta.2') == -1 - assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 - assert compare('1.0.0-beta.11', '1.0.0-rc.1') == -1 - assert compare('1.0.0-rc.1', '1.0.0') == -1 - - -def test_should_get_greater(): - assert compare("2.0.0", "1.0.0") == 1 - assert compare('1.0.0-alpha.1', '1.0.0-alpha') == 1 - assert compare('1.0.0-alpha.beta', '1.0.0-alpha.1') == 1 - assert compare('1.0.0-beta', '1.0.0-alpha.beta') == 1 - assert compare('1.0.0-beta.2', '1.0.0-beta') == 1 - assert compare('1.0.0-beta.11', '1.0.0-beta.2') == 1 - assert compare('1.0.0-rc.1', '1.0.0-beta.11') == 1 - assert compare('1.0.0', '1.0.0-rc.1') == 1 + }), +]) +def test_should_parse_zero_prerelease(version, expected): + result = parse(version) + assert result == expected + + +@pytest.mark.parametrize("left,right", [ + ("1.0.0", "2.0.0"), + ('1.0.0-alpha', '1.0.0-alpha.1'), + ('1.0.0-alpha.1', '1.0.0-alpha.beta'), + ('1.0.0-alpha.beta', '1.0.0-beta'), + ('1.0.0-beta', '1.0.0-beta.2'), + ('1.0.0-beta.2', '1.0.0-beta.11'), + ('1.0.0-beta.11', '1.0.0-rc.1'), + ('1.0.0-rc.1', '1.0.0'), +]) +def test_should_get_less(left, right): + assert compare(left, right) == -1 + + +@pytest.mark.parametrize("left,right", [ + ("2.0.0", "1.0.0"), + ('1.0.0-alpha.1', '1.0.0-alpha'), + ('1.0.0-alpha.beta', '1.0.0-alpha.1'), + ('1.0.0-beta', '1.0.0-alpha.beta'), + ('1.0.0-beta.2', '1.0.0-beta'), + ('1.0.0-beta.11', '1.0.0-beta.2'), + ('1.0.0-rc.1', '1.0.0-beta.11'), + ('1.0.0', '1.0.0-rc.1') +]) +def test_should_get_greater(left, right): + assert compare(left, right) == 1 def test_should_match_simple(): @@ -101,56 +115,63 @@ def test_should_no_match_simple(): assert match("2.3.7", ">=2.3.8") is False -def test_should_match_not_equal(): - assert match("2.3.7", "!=2.3.8") is True - assert match("2.3.7", "!=2.3.6") is True - assert match("2.3.7", "!=2.3.7") is False - - -def test_should_not_raise_value_error_for_expected_match_expression(): - assert match("2.3.7", "<2.4.0") is True - assert match("2.3.7", ">2.3.5") is True - - assert match("2.3.7", "<=2.3.9") is True - assert match("2.3.7", ">=2.3.5") is True - assert match("2.3.7", "==2.3.7") is True - assert match("2.3.7", "!=2.3.7") is False - - -def test_should_raise_value_error_for_unexpected_match_expression(): - with pytest.raises(ValueError): - match("2.3.7", "=2.3.7") - with pytest.raises(ValueError): - match("2.3.7", "~2.3.7") +@pytest.mark.parametrize("left,right,expected", [ + ("2.3.7", "!=2.3.8", True), + ("2.3.7", "!=2.3.6", True), + ("2.3.7", "!=2.3.7", False), +]) +def test_should_match_not_equal(left, right, expected): + assert match(left, right) is expected + + +@pytest.mark.parametrize("left,right,expected", [ + ("2.3.7", "<2.4.0", True), + ("2.3.7", ">2.3.5", True), + ("2.3.7", "<=2.3.9", True), + ("2.3.7", ">=2.3.5", True), + ("2.3.7", "==2.3.7", True), + ("2.3.7", "!=2.3.7", False), +]) +def test_should_not_raise_value_error_for_expected_match_expression(left, + right, + expected): + assert match(left, right) is expected + + +@pytest.mark.parametrize("left,right", [ + ("2.3.7", "=2.3.7"), + ("2.3.7", "~2.3.7"), + ("2.3.7", "^2.3.7"), +]) +def test_should_raise_value_error_for_unexpected_match_expression(left, right): with pytest.raises(ValueError): - match("2.3.7", "^2.3.7") + match(left, right) -def test_should_raise_value_error_for_zero_prefixed_versions(): +@pytest.mark.parametrize("version", ["01.2.3", "1.02.3", "1.2.03"]) +def test_should_raise_value_error_for_zero_prefixed_versions(version): with pytest.raises(ValueError): - parse("01.2.3") - with pytest.raises(ValueError): - parse("1.02.3") - with pytest.raises(ValueError): - parse("1.2.03") + parse(version) -def test_should_raise_value_error_for_invalid_value(): - with pytest.raises(ValueError): - compare('foo', 'bar') +@pytest.mark.parametrize("left,right", [ + ('foo', 'bar'), + ('1.0', '1.0.0'), + ('1.x', '1.0.0'), +]) +def test_should_raise_value_error_for_invalid_value(left, right): with pytest.raises(ValueError): - compare('1.0', '1.0.0') - with pytest.raises(ValueError): - compare('1.x', '1.0.0') + compare(left, right) -def test_should_raise_value_error_for_invalid_match_expression(): - with pytest.raises(ValueError): - match('1.0.0', '') - with pytest.raises(ValueError): - match('1.0.0', '!') +@pytest.mark.parametrize("left,right", [ + ('1.0.0', ''), + ('1.0.0', '!'), + ('1.0.0', '1.0.0'), +]) +def test_should_raise_value_error_for_invalid_match_expression(left, right): with pytest.raises(ValueError): - match('1.0.0', '1.0.0') + match(left, right) def test_should_follow_specification_comparison(): @@ -173,32 +194,47 @@ def test_should_follow_specification_comparison(): '%s should be higher than %s' % (high_version, low_version) -def test_should_compare_rc_builds(): - assert compare('1.0.0-beta.2', '1.0.0-beta.11') == -1 +@pytest.mark.parametrize("left,right", [ + ('1.0.0-beta.2', '1.0.0-beta.11'), +]) +def test_should_compare_rc_builds(left, right): + assert compare(left, right) == -1 -def test_should_compare_release_candidate_with_release(): - assert compare('1.0.0-rc.1', '1.0.0') == -1 - assert compare('1.0.0-rc.1+build.1', '1.0.0') == -1 +@pytest.mark.parametrize("left,right", [ + ('1.0.0-rc.1', '1.0.0'), + ('1.0.0-rc.1+build.1', '1.0.0'), +]) +def test_should_compare_release_candidate_with_release(left, right): + assert compare(left, right) == -1 -def test_should_say_equal_versions_are_equal(): - assert compare('2.0.0', '2.0.0') == 0 - assert compare('1.1.9-rc.1', '1.1.9-rc.1') == 0 - assert compare('1.1.9+build.1', '1.1.9+build.1') == 0 - assert compare('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1') == 0 +@pytest.mark.parametrize("left,right", [ + ('2.0.0', '2.0.0'), + ('1.1.9-rc.1', '1.1.9-rc.1'), + ('1.1.9+build.1', '1.1.9+build.1'), + ('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1'), +]) +def test_should_say_equal_versions_are_equal(left, right): + assert compare(left, right) == 0 -def test_should_compare_versions_with_build_and_release(): - assert compare('1.1.9-rc.1', '1.1.9-rc.1+build.1') == 0 - assert compare('1.1.9-rc.1', '1.1.9+build.1') == -1 +@pytest.mark.parametrize("left,right,expected", [ + ('1.1.9-rc.1', '1.1.9-rc.1+build.1', 0), + ('1.1.9-rc.1', '1.1.9+build.1', -1), +]) +def test_should_compare_versions_with_build_and_release(left, right, expected): + assert compare(left, right) == expected -def test_should_ignore_builds_on_compare(): - assert compare('1.0.0+build.1', '1.0.0') == 0 - assert compare('1.0.0-alpha.1+build.1', '1.0.0-alpha.1') == 0 - assert compare('1.0.0+build.1', '1.0.0-alpha.1') == 1 - assert compare('1.0.0+build.1', '1.0.0-alpha.1+build.1') == 1 +@pytest.mark.parametrize("left,right,expected", [ + ('1.0.0+build.1', '1.0.0', 0), + ('1.0.0-alpha.1+build.1', '1.0.0-alpha.1', 0), + ('1.0.0+build.1', '1.0.0-alpha.1', 1), + ('1.0.0+build.1', '1.0.0-alpha.1+build.1', 1), +]) +def test_should_ignore_builds_on_compare(left, right, expected): + assert compare(left, right) == expected def test_should_correctly_format_version(): @@ -241,51 +277,62 @@ def test_should_get_more_rc1(): assert compare("1.0.0-rc1", "1.0.0-rc0") == 1 -def test_prerelease_order(): - assert min_ver('1.2.3-rc.2', '1.2.3-rc.10') == '1.2.3-rc.2' - assert min_ver('1.2.3-rc2', '1.2.3-rc10') == '1.2.3-rc10' +@pytest.mark.parametrize("left,right,expected", [ + ('1.2.3-rc.2', '1.2.3-rc.10', '1.2.3-rc.2'), + ('1.2.3-rc2', '1.2.3-rc10', '1.2.3-rc10'), # identifiers with letters or hyphens are compared lexically in ASCII sort # order. - assert min_ver('1.2.3-Rc10', '1.2.3-rc10') == '1.2.3-Rc10' + ('1.2.3-Rc10', '1.2.3-rc10', '1.2.3-Rc10'), # Numeric identifiers always have lower precedence than non-numeric # identifiers. - assert min_ver('1.2.3-2', '1.2.3-rc') == '1.2.3-2' + ('1.2.3-2', '1.2.3-rc', '1.2.3-2'), # A larger set of pre-release fields has a higher precedence than a # smaller set, if all of the preceding identifiers are equal. - assert min_ver('1.2.3-rc.2.1', '1.2.3-rc.2') == '1.2.3-rc.2' + ('1.2.3-rc.2.1', '1.2.3-rc.2', '1.2.3-rc.2'), # When major, minor, and patch are equal, a pre-release version has lower # precedence than a normal version. - assert min_ver('1.2.3', '1.2.3-1') == '1.2.3-1' - assert min_ver('1.0.0-alpha', '1.0.0-alpha.1') == '1.0.0-alpha' + ('1.2.3', '1.2.3-1', '1.2.3-1'), + ('1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-alpha') +]) +def test_prerelease_order(left, right, expected): + assert min_ver(left, right) == expected -def test_should_bump_prerelease(): - assert bump_prerelease('3.4.5-rc.9') == '3.4.5-rc.10' - assert bump_prerelease('3.4.5') == '3.4.5-rc.1' - assert bump_prerelease('3.4.5', 'dev') == '3.4.5-dev.1' - assert bump_prerelease('3.4.5', '') == '3.4.5-rc.1' +@pytest.mark.parametrize("version,token,expected", [ + ('3.4.5-rc.9', None, '3.4.5-rc.10'), + ('3.4.5', None, '3.4.5-rc.1'), + ('3.4.5', 'dev', '3.4.5-dev.1'), + ('3.4.5', '', '3.4.5-rc.1'), +]) +def test_should_bump_prerelease(version, token, expected): + token = "rc" if not token else token + assert bump_prerelease(version, token) == expected def test_should_ignore_build_on_prerelease_bump(): assert bump_prerelease('3.4.5-rc.1+build.4') == '3.4.5-rc.2' -def test_should_bump_build(): - assert bump_build('3.4.5-rc.1+build.9') == '3.4.5-rc.1+build.10' - assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev' - assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1' - assert bump_build('3.4.5') == '3.4.5+build.1' - assert bump_build('3.4.5', 'nightly') == '3.4.5+nightly.1' - assert bump_build('3.4.5', '') == '3.4.5+build.1' - - -def test_should_finalize_version(): - assert finalize_version('1.2.3') == '1.2.3' - assert finalize_version('1.2.3-rc.5') == '1.2.3' - assert finalize_version('1.2.3+build.2') == '1.2.3' - assert finalize_version('1.2.3-rc.1+build.5') == '1.2.3' - assert finalize_version('1.2.3-alpha') == '1.2.3' - assert finalize_version('1.2.0') == '1.2.0' +@pytest.mark.parametrize("version,expected", [ + ('3.4.5-rc.1+build.9', '3.4.5-rc.1+build.10'), + ('3.4.5-rc.1+0009.dev', '3.4.5-rc.1+0010.dev'), + ('3.4.5-rc.1', '3.4.5-rc.1+build.1'), + ('3.4.5', '3.4.5+build.1'), +]) +def test_should_bump_build(version, expected): + assert bump_build(version) == expected + + +@pytest.mark.parametrize("version,expected", [ + ('1.2.3', '1.2.3'), + ('1.2.3-rc.5', '1.2.3'), + ('1.2.3+build.2', '1.2.3'), + ('1.2.3-rc.1+build.5', '1.2.3'), + ('1.2.3-alpha', '1.2.3'), + ('1.2.0', '1.2.0'), +]) +def test_should_finalize_version(version, expected): + assert finalize_version(version) == expected def test_should_compare_version_info_objects(): From 8ef64f2688c8e543eab9cb401208fcca9d3cc2d9 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Fri, 25 May 2018 17:04:31 +0200 Subject: [PATCH 089/312] Add doctests (#90) * Add doctests Closes #89 --- CHANGELOG | 1 + semver.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++---- tox.ini | 2 +- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8291e0a3..733a6bff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ Version 2.8.1 (WIP) :Maintainer: Sébastien Celles * Issue #77 (PR #47). Convert multiple tests into pytest.mark.parametrize +* Issue #89 (PR #90). Add doctests. Version 2.8.0 ============= diff --git a/semver.py b/semver.py index e3d2d5bd..db7a449e 100644 --- a/semver.py +++ b/semver.py @@ -46,6 +46,19 @@ def parse(version): and 'prerelease'. The prerelease or build keys can be None if not provided :rtype: dict + + >>> import semver + >>> 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' """ match = _REGEX.match(version) if match is None: @@ -69,12 +82,6 @@ class VersionInfo(collections.namedtuple( :param int patch: version when you make backwards-compatible bug fixes. :param str prerelease: an optional prerelease string :param str build: an optional build string - - >>> import semver - >>> ver = semver.parse('3.4.5-pre.2+build.4') - >>> ver - {'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5, - 'prerelease': 'pre.2'} """ __slots__ = () @@ -127,6 +134,19 @@ def parse_version_info(version): :param version: version string :return: a :class:`VersionInfo` instance :rtype: :class:`VersionInfo` + + >>> import semver + >>> version_info = semver.parse_version_info("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' """ parts = parse(version) version_info = VersionInfo( @@ -190,6 +210,14 @@ def compare(ver1, ver2): :return: The return value is negative if ver1 < ver2, zero if ver1 == ver2 and strictly positive if ver1 > ver2 :rtype: int + + >>> import semver + >>> 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, v2 = parse(ver1), parse(ver2) @@ -210,6 +238,12 @@ def match(version, match_expr): != not equal :return: True if the expression matches the version, otherwise False :rtype: bool + + >>> import semver + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False """ prefix = match_expr[:2] if prefix in ('>=', '<=', '==', '!='): @@ -245,6 +279,10 @@ def max_ver(ver1, ver2): :param ver2: version string 2 :return: the greater version of the two :rtype: :class:`VersionInfo` + + >>> import semver + >>> semver.max_ver("1.0.0", "2.0.0") + '2.0.0' """ cmp_res = compare(ver1, ver2) if cmp_res == 0 or cmp_res == 1: @@ -260,6 +298,10 @@ def min_ver(ver1, ver2): :param ver2: version string 2 :return: the smaller version of the two :rtype: :class:`VersionInfo` + + >>> import semver + >>> semver.min_ver("1.0.0", "2.0.0") + '1.0.0' """ cmp_res = compare(ver1, ver2) if cmp_res == 0 or cmp_res == -1: @@ -278,6 +320,10 @@ def format_version(major, minor, patch, prerelease=None, build=None): :param str build: the optional build part of a version :return: the formatted string :rtype: str + + >>> import semver + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' """ version = "%d.%d.%d" % (major, minor, patch) if prerelease is not None: @@ -308,6 +354,10 @@ def bump_major(version): :param: version string :return: the raised version string :rtype: str + + >>> import semver + >>> semver.bump_major("3.4.5") + '4.0.0' """ verinfo = parse(version) return format_version(verinfo['major'] + 1, 0, 0) @@ -319,6 +369,10 @@ def bump_minor(version): :param: version string :return: the raised version string :rtype: str + + >>> import semver + >>> semver.bump_minor("3.4.5") + '3.5.0' """ verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'] + 1, 0) @@ -330,6 +384,10 @@ def bump_patch(version): :param: version string :return: the raised version string :rtype: str + + >>> import semver + >>> semver.bump_patch("3.4.5") + '3.4.6' """ verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'], @@ -343,6 +401,9 @@ def bump_prerelease(version, token='rc'): :param token: defaults to 'rc' :return: the raised version string :rtype: str + + >>> bump_prerelease('3.4.5', 'dev') + '3.4.5-dev.1' """ verinfo = parse(version) verinfo['prerelease'] = _increment_string( @@ -359,6 +420,9 @@ def bump_build(version, token='build'): :param token: defaults to 'build' :return: the raised version string :rtype: str + + >>> bump_build('3.4.5-rc.1+build.9') + '3.4.5-rc.1+build.10' """ verinfo = parse(version) verinfo['build'] = _increment_string( @@ -374,6 +438,14 @@ def finalize_version(version): :param version: version string :return: the finalized version string :rtype: str + + >>> finalize_version('1.2.3-rc.5') + '1.2.3' """ verinfo = parse(version) return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/tox.ini b/tox.ini index 8a59328a..95d293cc 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = pypy [testenv] -commands = py.test -q +commands = py.test -q --doctest-modules deps = pytest setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 From 9887884c63bf08d2489740303d8fa454e66495b9 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Fri, 25 May 2018 17:04:57 +0200 Subject: [PATCH 090/312] static method parse for VersionInfo (#88) --- README.rst | 3 +++ semver.py | 15 +++++++++++++++ test_semver.py | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/README.rst b/README.rst index ecc95e1a..5e2f650c 100644 --- a/README.rst +++ b/README.rst @@ -49,6 +49,9 @@ This module provides just couple of functions, main of which are: ... 'prerelease': 'pre.2', 'build': 'build.4'} True >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") + >>> # or using static method parse + >>> from semver import VersionInfo + >>> version_info = VersionInfo.parse("3.4.5-pre.2+build.4") >>> version_info VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') >>> version_info.major diff --git a/semver.py b/semver.py index db7a449e..a9e3e312 100644 --- a/semver.py +++ b/semver.py @@ -121,6 +121,21 @@ def __str__(self): def __hash__(self): return hash(tuple(self)) + @staticmethod + def parse(version): + """Parse version string to a VersionInfo instance. + + >>> from semver import VersionInfo + >>> VersionInfo.parse('3.4.5-pre.2+build.4') + VersionInfo(major=3, minor=4, patch=5, \ +prerelease='pre.2', build='build.4') + + :param version: version string + :return: a :class:`VersionInfo` instance + :rtype: :class:`VersionInfo` + """ + return parse_version_info(version) + def _to_dict(obj): if isinstance(obj, VersionInfo): diff --git a/test_semver.py b/test_semver.py index 74903cde..f00e6a05 100644 --- a/test_semver.py +++ b/test_semver.py @@ -408,3 +408,9 @@ def test_parse_version_info_str_hash(): assert v.__str__() == s_version d = {} d[v] = "" # to ensure that VersionInfo are hashable + + +def test_parse_method_for_version_info(): + s_version = "1.2.3-alpha.1.2+build.11.e0f985a" + v = VersionInfo.parse(s_version) + assert str(v) == s_version From 007dde239f467edfc1d7018134b24b98639ee833 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Fri, 25 May 2018 21:20:01 +0200 Subject: [PATCH 091/312] Update release-procedure.md --- release-procedure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-procedure.md b/release-procedure.md index 595b2f11..682f959f 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -4,7 +4,7 @@ * Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG) have been updated -* If one or several supported Python versions have removed or add, verify that the 3 following files have been updated: +* If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: * [setup.py](https://github.com/k-bx/python-semver/blob/master/setup.py) * [tox.ini](https://github.com/k-bx/python-semver/blob/master/tox.ini) * [.travis.yml](https://github.com/k-bx/python-semver/blob/master/.travis.yml) From 2754b7e178f41f1e4647359e49b3a8dce2550d30 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Sat, 26 May 2018 13:34:21 +0200 Subject: [PATCH 092/312] Remove namedtuple inheritance (#93) Closes #87 Closes #94 Add entries to CHANGELOG --- CHANGELOG | 2 ++ semver.py | 33 ++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 733a6bff..82a0aafc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,8 @@ Version 2.8.1 (WIP) * Issue #77 (PR #47). Convert multiple tests into pytest.mark.parametrize * Issue #89 (PR #90). Add doctests. +* Issue #40 (PR #88). Add a static parse method to VersionInfo +* Issue #87 #94 (PR #93). Remove named tuple inheritance. Fix bad rendering in Pandas DataFrame Version 2.8.0 ============= diff --git a/semver.py b/semver.py index a9e3e312..b61515b5 100644 --- a/semver.py +++ b/semver.py @@ -73,8 +73,7 @@ def parse(version): return version_parts -class VersionInfo(collections.namedtuple( - 'VersionInfo', 'major minor patch prerelease build')): +class VersionInfo: """ :param int major: version when you make incompatible API changes. :param int minor: version when you add functionality in @@ -83,7 +82,26 @@ class VersionInfo(collections.namedtuple( :param str prerelease: an optional prerelease string :param str build: an optional build string """ - __slots__ = () + + def __init__(self, major, minor, patch, prerelease, build): + self.major = major + self.minor = minor + self.patch = patch + self.prerelease = prerelease + self.build = build + + def _astuple(self): + return (self.major, self.minor, self.patch, + self.prerelease, self.build) + + def _asdict(self): + return collections.OrderedDict(( + ("major", self.major), + ("minor", self.minor), + ("patch", self.patch), + ("prerelease", self.prerelease), + ("build", self.build) + )) def __eq__(self, other): if not isinstance(other, (VersionInfo, dict)): @@ -115,11 +133,16 @@ def __ge__(self, other): return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 + def __repr__(self): + s = ", ".join("%s=%r" % (key, val) + for key, val in self._asdict().items()) + return "VersionInfo(%s)" % s + def __str__(self): - return format_version(*self) + return format_version(*(self._astuple())) def __hash__(self): - return hash(tuple(self)) + return hash(self._astuple()) @staticmethod def parse(version): From 023dbe144954943aa2c75455834f153cd42d8e94 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Sat, 26 May 2018 13:50:24 +0200 Subject: [PATCH 093/312] Doc with Sphynx (#92) * Doc with Sphynx Install, usage, development and API doc Closes #86 --- README.rst | 99 ++---------------------- docs/Makefile | 20 +++++ docs/api.rst | 6 ++ docs/conf.py | 177 +++++++++++++++++++++++++++++++++++++++++++ docs/development.rst | 28 +++++++ docs/index.rst | 49 ++++++++++++ docs/install.rst | 14 ++++ docs/make.bat | 36 +++++++++ docs/usage.rst | 47 ++++++++++++ 9 files changed, 384 insertions(+), 92 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/conf.py create mode 100644 docs/development.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/make.bat create mode 100644 docs/usage.rst diff --git a/README.rst b/README.rst index 5e2f650c..5c10ecbd 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Semver |latest-version| ======================= -|build-status| |python-support| |downloads| |license| +|build-status| |python-support| |downloads| |license| |docs| A Python module for `semantic versioning`_. Simplifies comparing versions. @@ -21,96 +21,11 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. |license| image:: https://img.shields.io/pypi/l/semver.svg :alt: Software license :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt +.. |docs| image:: https://readthedocs.org/projects/python-semver/badge/?version=latest + :target: http://python-semver.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status .. _semantic versioning: http://semver.org/ -Usage ------ - -This module provides just couple of functions, main of which are: - -.. code-block:: python - - >>> import semver - >>> 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 - >>> semver.match("2.0.0", ">=1.0.0") - True - >>> semver.match("1.0.0", ">1.0.0") - False - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - >>> version_parts = semver.parse("3.4.5-pre.2+build.4") - >>> version_parts == { - ... 'major': 3, 'minor': 4, 'patch': 5, - ... 'prerelease': 'pre.2', 'build': 'build.4'} - True - >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") - >>> # or using static method parse - >>> from semver import VersionInfo - >>> version_info = VersionInfo.parse("3.4.5-pre.2+build.4") - >>> version_info - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - >>> version_info.major - 3 - >>> version_info > (1, 0) - True - >>> version_info < (3, 5) - True - >>> semver.bump_major("3.4.5") - '4.0.0' - >>> semver.bump_minor("3.4.5") - '3.5.0' - >>> semver.bump_patch("3.4.5") - '3.4.6' - >>> semver.max_ver("1.0.0", "2.0.0") - '2.0.0' - >>> semver.min_ver("1.0.0", "2.0.0") - '1.0.0' - -Installation ------------- - -For Python 2: - -.. code-block:: bash - - pip install semver - -For Python 3: - -.. code-block:: bash - - pip3 install semver - -How to Contribute ------------------ - -When you make changes to the code please run the tests before pushing your -code to your fork and opening a `pull request`_: - -.. code-block:: bash - - python setup.py test - -We use `py.test`_ and `tox`_ to run tests against all supported Python -versions. All test dependencies are resolved automatically, apart from -virtualenv, which for the moment you still may have to install manually: - -.. code-block:: bash - - pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only - -You can use the ``clean`` command to remove build and test files and folders: - -.. code-block:: bash - - python setup.py clean - - -.. _pull request: https://github.com/k-bx/python-semver/pulls -.. _py.test: http://pytest.org/ -.. _tox: http://tox.testrun.org/ +Documentation +------------- +|docs| diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..a7e3cbaf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = semver +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..cd8e73b4 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,6 @@ +API +=== + +.. automodule:: semver + :members: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..843b7466 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# python-semver documentation build configuration file +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# 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 os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'python-semver' +copyright = '2018, Kostiantyn Rybnikov and all' +author = 'Kostiantyn Rybnikov and all' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# 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 + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'semverdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'semver.tex', 'python-semver Documentation', + 'Kostiantyn Rybnikov and all', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'semver', 'python-semver Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'semver', 'python-semver Documentation', + author, 'semver', 'One line description of project.', + 'Miscellaneous'), +] + + +interpshinx_mapping = { + "matplotlib": ('http://matplotlib.org', None), +} diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 00000000..000b2f65 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,28 @@ +How to Contribute +----------------- + +When you make changes to the code please run the tests before pushing your +code to your fork and opening a `pull request`_: + +.. code-block:: bash + + python setup.py test + +We use `py.test`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically, apart from +virtualenv, which for the moment you still may have to install manually: + +.. code-block:: bash + + pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only + +You can use the ``clean`` command to remove build and test files and folders: + +.. code-block:: bash + + python setup.py clean + + +.. _pull request: https://github.com/k-bx/python-semver/pulls +.. _py.test: http://pytest.org/ +.. _tox: http://tox.testrun.org/ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..84136292 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,49 @@ +Semver |latest-version| +======================= + +|build-status| |python-support| |downloads| |license| + +.. python-semver documentation master file, created by + sphinx-quickstart on Tue May 1 16:51:19 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to python-semver's documentation! +========================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Install + Usage + Development + API + +A Python module for `semantic versioning`_. Simplifies comparing versions. + + +.. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg + :alt: Latest version on PyPI + :target: https://pypi.python.org/pypi/semver +.. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master + :alt: Build status + :target: https://travis-ci.org/k-bx/python-semver +.. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg + :target: https://pypi.python.org/pypi/semver + :alt: Python versions +.. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg + :alt: Monthly downloads from PyPI + :target: https://pypi.python.org/pypi/semver +.. |license| image:: https://img.shields.io/pypi/l/semver.svg + :alt: Software license + :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt +.. _semantic versioning: http://semver.org/ + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..26f4e1c0 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,14 @@ +Installation +------------ + +For Python 2: + +.. code-block:: bash + + pip install semver + +For Python 3: + +.. code-block:: bash + + pip3 install semver diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..dae3d085 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=python -msphinx +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=semver + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The Sphinx module was not found. Make sure you have Sphinx installed, + echo.then set the SPHINXBUILD environment variable to point to the full + echo.path of the 'sphinx-build' executable. Alternatively you may add the + echo.Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 00000000..c30ffefb --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,47 @@ +Usage +----- + +This module provides just couple of functions, main of which are: + +.. code-block:: python + + >>> import semver + >>> 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 + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + >>> version_parts = semver.parse("3.4.5-pre.2+build.4") + >>> version_parts == { + ... 'major': 3, 'minor': 4, 'patch': 5, + ... 'prerelease': 'pre.2', 'build': 'build.4'} + True + >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") + >>> # or using static method parse + >>> from semver import VersionInfo + >>> version_info = VersionInfo.parse("3.4.5-pre.2+build.4") + >>> version_info + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> version_info.major + 3 + >>> version_info > (1, 0) + True + >>> version_info < (3, 5) + True + >>> semver.bump_major("3.4.5") + '4.0.0' + >>> semver.bump_minor("3.4.5") + '3.5.0' + >>> semver.bump_patch("3.4.5") + '3.4.6' + >>> semver.max_ver("1.0.0", "2.0.0") + '2.0.0' + >>> semver.min_ver("1.0.0", "2.0.0") + '1.0.0' From d964b1668cb9ae0d941b9fdbec9f3b42121805c3 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Sat, 26 May 2018 14:08:38 +0200 Subject: [PATCH 094/312] Update README.rst Change pypi url --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5c10ecbd..aa81944b 100644 --- a/README.rst +++ b/README.rst @@ -8,16 +8,16 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg :alt: Latest version on PyPI - :target: https://pypi.python.org/pypi/semver + :target: https://pypi.org/project/semver .. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master :alt: Build status :target: https://travis-ci.org/k-bx/python-semver .. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg - :target: https://pypi.python.org/pypi/semver + :target: https://pypi.org/project/semver :alt: Python versions .. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg :alt: Monthly downloads from PyPI - :target: https://pypi.python.org/pypi/semver + :target: https://pypi.org/project/semver .. |license| image:: https://img.shields.io/pypi/l/semver.svg :alt: Software license :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt From 97d41b925088eb77d95e9d5299050e891ef08496 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Thu, 31 May 2018 19:03:44 +0200 Subject: [PATCH 095/312] Immutable VersionInfo (#97) Closes #96 --- CHANGELOG | 1 + semver.py | 33 +++++++++++++++++++++++++++------ test_semver.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 82a0aafc..8f65d3c5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ Version 2.8.1 (WIP) * Issue #89 (PR #90). Add doctests. * Issue #40 (PR #88). Add a static parse method to VersionInfo * Issue #87 #94 (PR #93). Remove named tuple inheritance. Fix bad rendering in Pandas DataFrame +* Issue #96 (PR #97). Make VersionInfo immutable Version 2.8.0 ============= diff --git a/semver.py b/semver.py index b61515b5..077e8a93 100644 --- a/semver.py +++ b/semver.py @@ -73,7 +73,7 @@ def parse(version): return version_parts -class VersionInfo: +class VersionInfo(object): """ :param int major: version when you make incompatible API changes. :param int minor: version when you add functionality in @@ -82,13 +82,34 @@ class VersionInfo: :param str prerelease: an optional prerelease string :param str build: an optional build string """ + __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') def __init__(self, major, minor, patch, prerelease, build): - self.major = major - self.minor = minor - self.patch = patch - self.prerelease = prerelease - self.build = build + self._major = major + self._minor = minor + self._patch = patch + self._prerelease = prerelease + self._build = build + + @property + def major(self): + return self._major + + @property + def minor(self): + return self._minor + + @property + def patch(self): + return self._patch + + @property + def prerelease(self): + return self._prerelease + + @property + def build(self): + return self._build def _astuple(self): return (self.major, self.minor, self.patch, diff --git a/test_semver.py b/test_semver.py index f00e6a05..dcdb8b01 100644 --- a/test_semver.py +++ b/test_semver.py @@ -414,3 +414,49 @@ def test_parse_method_for_version_info(): s_version = "1.2.3-alpha.1.2+build.11.e0f985a" v = VersionInfo.parse(s_version) assert str(v) == s_version + + +def test_immutable(): + v = VersionInfo(major=1, minor=2, patch=3, + prerelease='alpha.1.2', build='build.11.e0f985a') + try: + v.major = 9 + except AttributeError: + pass + else: + assert 0, "attribute 'major' must be readonly" + + try: + v.minor = 9 + except AttributeError: + pass + else: + assert 0, "attribute 'minor' must be readonly" + + try: + v.patch = 9 + except AttributeError: + pass + else: + assert 0, "attribute 'patch' must be readonly" + + try: + v.prerelease = 'alpha.9.9' + except AttributeError: + pass + else: + assert 0, "attribute 'prerelease' must be readonly" + + try: + v.build = 'build.99.e0f985a' + except AttributeError: + pass + else: + assert 0, "attribute 'build' must be readonly" + + try: + v.new_attribute = 'forbidden' + except AttributeError: + pass + else: + assert 0, "no new attribute can be set" From 7fdf7b242dd993aa7a0b22821f94379ac8d983d4 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Thu, 31 May 2018 19:54:32 +0200 Subject: [PATCH 096/312] prerelease and build set to None by default (#99) --- CHANGELOG | 1 + semver.py | 2 +- test_semver.py | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8f65d3c5..afae98cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ Version 2.8.1 (WIP) * Issue #40 (PR #88). Add a static parse method to VersionInfo * Issue #87 #94 (PR #93). Remove named tuple inheritance. Fix bad rendering in Pandas DataFrame * Issue #96 (PR #97). Make VersionInfo immutable +* Issue #98 (PR #99). prerelease and build set to None by default Version 2.8.0 ============= diff --git a/semver.py b/semver.py index 077e8a93..5f5be2c2 100644 --- a/semver.py +++ b/semver.py @@ -84,7 +84,7 @@ class VersionInfo(object): """ __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') - def __init__(self, major, minor, patch, prerelease, build): + def __init__(self, major, minor, patch, prerelease=None, build=None): self._major = major self._minor = minor self._patch = patch diff --git a/test_semver.py b/test_semver.py index dcdb8b01..c8532202 100644 --- a/test_semver.py +++ b/test_semver.py @@ -336,7 +336,7 @@ def test_should_finalize_version(version, expected): def test_should_compare_version_info_objects(): - v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v1 = VersionInfo(major=0, minor=10, patch=4) v2 = VersionInfo( major=0, minor=10, patch=4, prerelease='beta.1', build=None) @@ -348,7 +348,7 @@ def test_should_compare_version_info_objects(): assert not(v1 <= v2) assert not(v1 == v2) - v3 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v3 = VersionInfo(major=0, minor=10, patch=4) assert not(v1 != v3) assert not(v1 > v3) @@ -357,7 +357,7 @@ def test_should_compare_version_info_objects(): assert v1 <= v3 assert v1 == v3 - v4 = VersionInfo(major=0, minor=10, patch=5, prerelease=None, build=None) + v4 = VersionInfo(major=0, minor=10, patch=5) assert v1 != v4 assert not(v1 > v4) assert not(v1 >= v4) @@ -367,7 +367,7 @@ def test_should_compare_version_info_objects(): def test_should_compare_version_dictionaries(): - v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None) + v1 = VersionInfo(major=0, minor=10, patch=4) v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None) assert v1 != v2 @@ -377,7 +377,7 @@ def test_should_compare_version_dictionaries(): assert not(v1 <= v2) assert not(v1 == v2) - v3 = dict(major=0, minor=10, patch=4, prerelease=None, build=None) + v3 = dict(major=0, minor=10, patch=4) assert not(v1 != v3) assert not(v1 > v3) @@ -386,7 +386,7 @@ def test_should_compare_version_dictionaries(): assert v1 <= v3 assert v1 == v3 - v4 = dict(major=0, minor=10, patch=5, prerelease=None, build=None) + v4 = dict(major=0, minor=10, patch=5) assert v1 != v4 assert not(v1 > v4) assert not(v1 >= v4) From 41775dd5f143dfa6ca94885056c9ef5b3ed4e6e1 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Mon, 9 Jul 2018 15:52:10 +0200 Subject: [PATCH 097/312] Release 2.8.1 (#101) --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index afae98cf..721047b5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,9 +8,9 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. -Version 2.8.1 (WIP) -=================== -:Released: 2018-mm-dd +Version 2.8.1 +============= +:Released: 2018-07-09 :Maintainer: Sébastien Celles * Issue #77 (PR #47). Convert multiple tests into pytest.mark.parametrize From 7826ea553a1dec3a15eb9edff78a87067003984b Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 10 Oct 2018 15:40:50 +0200 Subject: [PATCH 098/312] Drop py33 (#111) * Update setup.py * Update tox.ini * Update .travis.yml --- .travis.yml | 3 --- setup.py | 2 -- tox.ini | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b89767aa..68925a5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,6 @@ matrix: - python: "2.7" env: TOXENV=py27 - - python: "3.3" - env: TOXENV=py33 - - python: "3.4" env: TOXENV=py34 diff --git a/setup.py b/setup.py index 84512cdb..acdb6024 100755 --- a/setup.py +++ b/setup.py @@ -91,8 +91,6 @@ def read_file(filename): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', diff --git a/tox.ini b/tox.ini index 95d293cc..15773afe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = flake8 - py{27,32,33,34,35,36} + py{27,34,35,36} pypy [testenv] From 353c3a910423e6d24f88d0d864ca79bc615e998f Mon Sep 17 00:00:00 2001 From: Carlo Date: Sun, 24 Mar 2019 12:45:01 +0100 Subject: [PATCH 099/312] add LICENSE file to distribution (#116) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 9561fb10..926cc7b1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include README.rst +include LICENSE.txt From 2a7e0207352b1942f298f1194c14f7bc03cb39f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B6hn?= Date: Sun, 24 Mar 2019 07:45:26 -0400 Subject: [PATCH 100/312] Use a method decorator to deduplicate binary operator logic (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The binary-operator comparison methods of the `VersionInfo` class all contain the same boilerplate type-checking code. This patch adds a decorator function that wraps these methods in the type-check logic, which allows the operator method implementations to be stripped down to single lines of code – whose legibility allows them to be readily scanned by newcomers to the project and others unfamiliar with the codebase. --- semver.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/semver.py b/semver.py index 5f5be2c2..76045b5e 100644 --- a/semver.py +++ b/semver.py @@ -5,6 +5,7 @@ import collections import re +from functools import wraps __version__ = '2.8.1' __author__ = 'Kostiantyn Rybnikov' @@ -73,6 +74,16 @@ def parse(version): return version_parts +def comparator(operator): + """ Wrap a VersionInfo binary op method in a type-check """ + @wraps(operator) + def wrapper(self, other): + if not isinstance(other, (VersionInfo, dict)): + return NotImplemented + return operator(self, other) + return wrapper + + class VersionInfo(object): """ :param int major: version when you make incompatible API changes. @@ -124,34 +135,28 @@ def _asdict(self): ("build", self.build) )) + @comparator def __eq__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 + @comparator def __ne__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) != 0 + @comparator def __lt__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) < 0 + @comparator def __le__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0 + @comparator def __gt__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) > 0 + @comparator def __ge__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 def __repr__(self): From d2671fd54246224f83c4ffac2fad40409475eae3 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Sun, 24 Mar 2019 12:46:00 +0100 Subject: [PATCH 101/312] Improve test_immutable function (#120) * Add property setter and raise AttributeError Raise AttributeError with useful message for major, minor, patch, prerelease, and build. * Improve test_immutable with pytest.raises * Use pytest.raises which replace and shorten the try..except..else blocks * Use match keyword in pytest.raises which matches the AttributeError from the property setter * Split test_immutable into several different test functions, e.g. test_immutable_major * Fix flake8 issues --- semver.py | 20 +++++++++++++ test_semver.py | 80 +++++++++++++++++++++++--------------------------- 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/semver.py b/semver.py index 76045b5e..e76bf5a5 100644 --- a/semver.py +++ b/semver.py @@ -106,22 +106,42 @@ def __init__(self, major, minor, patch, prerelease=None, build=None): def major(self): return self._major + @major.setter + def major(self, value): + raise AttributeError("attribute 'major' is readonly") + @property def minor(self): return self._minor + @minor.setter + def minor(self, value): + raise AttributeError("attribute 'minor' is readonly") + @property def patch(self): return self._patch + @patch.setter + def patch(self, value): + raise AttributeError("attribute 'patch' is readonly") + @property def prerelease(self): return self._prerelease + @prerelease.setter + def prerelease(self, value): + raise AttributeError("attribute 'prerelease' is readonly") + @property def build(self): return self._build + @build.setter + def build(self, value): + raise AttributeError("attribute 'build' is readonly") + def _astuple(self): return (self.major, self.minor, self.patch, self.prerelease, self.build) diff --git a/test_semver.py b/test_semver.py index c8532202..13a0d716 100644 --- a/test_semver.py +++ b/test_semver.py @@ -23,6 +23,12 @@ ] +@pytest.fixture +def version(): + return VersionInfo(major=1, minor=2, patch=3, + prerelease='alpha.1.2', build='build.11.e0f985a') + + @pytest.mark.parametrize("func", SEMVERFUNCS, ids=[func.__name__ for func in SEMVERFUNCS]) def test_fordocstrings(func): @@ -416,47 +422,33 @@ def test_parse_method_for_version_info(): assert str(v) == s_version -def test_immutable(): - v = VersionInfo(major=1, minor=2, patch=3, - prerelease='alpha.1.2', build='build.11.e0f985a') - try: - v.major = 9 - except AttributeError: - pass - else: - assert 0, "attribute 'major' must be readonly" - - try: - v.minor = 9 - except AttributeError: - pass - else: - assert 0, "attribute 'minor' must be readonly" - - try: - v.patch = 9 - except AttributeError: - pass - else: - assert 0, "attribute 'patch' must be readonly" - - try: - v.prerelease = 'alpha.9.9' - except AttributeError: - pass - else: - assert 0, "attribute 'prerelease' must be readonly" - - try: - v.build = 'build.99.e0f985a' - except AttributeError: - pass - else: - assert 0, "attribute 'build' must be readonly" - - try: - v.new_attribute = 'forbidden' - except AttributeError: - pass - else: - assert 0, "no new attribute can be set" +def test_immutable_major(version): + with pytest.raises(AttributeError, match="attribute 'major' is readonly"): + version.major = 9 + + +def test_immutable_minor(version): + with pytest.raises(AttributeError, match="attribute 'minor' is readonly"): + version.minor = 9 + + +def test_immutable_patch(version): + with pytest.raises(AttributeError, match="attribute 'patch' is readonly"): + version.patch = 9 + + +def test_immutable_prerelease(version): + with pytest.raises(AttributeError, + match="attribute 'prerelease' is readonly"): + version.prerelease = 'alpha.9.9' + + +def test_immutable_build(version): + with pytest.raises(AttributeError, match="attribute 'build' is readonly"): + version.build = 'build.99.e0f985a' + + +def test_immutable_unknown_attribute(version): + # "no new attribute can be set" + with pytest.raises(AttributeError): + version.new_attribute = 'forbidden' From 0566d968d93f6f33c3b99c2b9d367622939cb261 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Mar 2019 14:09:46 +0100 Subject: [PATCH 102/312] Fix #121: use python3 instead of python3.4 (#122) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 15773afe..20024a57 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,6 @@ setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 [testenv:flake8] -basepython = python3.4 +basepython = python3 deps = flake8 commands = flake8 From 122dcaeae1c46e4dc31abc8d052494dd54c6c1a7 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Mar 2019 15:02:25 +0100 Subject: [PATCH 103/312] Fix __repr__ and derive class name from type (#123) Avoids hard coded class name --- semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.py b/semver.py index e76bf5a5..4475a767 100644 --- a/semver.py +++ b/semver.py @@ -182,7 +182,7 @@ def __ge__(self, other): def __repr__(self): s = ", ".join("%s=%r" % (key, val) for key, val in self._asdict().items()) - return "VersionInfo(%s)" % s + return "%s(%s)" % (type(self).__name__, s) def __str__(self): return format_version(*(self._astuple())) From 373af82cebee874709662257df6831977d2b9cae Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Mar 2019 15:43:53 +0100 Subject: [PATCH 104/312] Fixes #109 and add iterator to VersionInfo (#124) * Add Karl to CONTRIBUTORS * Use version fixture in test function test_version_info_should_be_iterable based on the work of Karol Werner in PR #109 --- CONTRIBUTORS | 2 +- semver.py | 6 ++++++ test_semver.py | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ce0b166c..205de33a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -29,7 +29,7 @@ Significant contributors * Craig Blaszczyk * Jan Pieter Waagmeester * Jelo Agnasin -* Karol Werner +* Karol Werner * Peter Bittner * robi-wan * T. Jameson Little diff --git a/semver.py b/semver.py index 4475a767..5f9a938c 100644 --- a/semver.py +++ b/semver.py @@ -155,6 +155,12 @@ def _asdict(self): ("build", self.build) )) + def __iter__(self): + """Implement iter(self).""" + # As long as we support Py2.7, we can't use the "yield from" syntax + for v in self._astuple(): + yield v + @comparator def __eq__(self, other): return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 diff --git a/test_semver.py b/test_semver.py index 13a0d716..5e8aae5b 100644 --- a/test_semver.py +++ b/test_semver.py @@ -452,3 +452,8 @@ def test_immutable_unknown_attribute(version): # "no new attribute can be set" with pytest.raises(AttributeError): version.new_attribute = 'forbidden' + + +def test_version_info_should_be_iterable(version): + assert tuple(version) == (version.major, version.minor, version.patch, + version.prerelease, version.build) From 344ea8902a52b198534e15759925884601ac7509 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Mar 2019 17:07:49 +0100 Subject: [PATCH 105/312] Create setup.cfg for pytest & tox config and coverage (#125) * Create setup.cfg for pytest config * Use "pytest" command instead of "py.test" (the latter may be obsolete in the future) * Move some options into setup.cfg * Introduce pytest-cov in "tox.ini" for coverage and config file ".coveragerc" * Add additional options to activate coverage for semver script and output coverage report * Use py.test again instead of pytest For some reasons, this works not on Travis with 14.04 LTS (Trusty Tahr) * Ignore .egg/ directory * Typo fix: .egg -> .eggs * Use --ignore option --- .coveragerc | 9 +++++++++ setup.cfg | 9 +++++++++ tox.ini | 6 ++++-- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .coveragerc create mode 100644 setup.cfg diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..0b2e8f7a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,9 @@ +# https://pytest-cov.readthedocs.io/ + +[run] +source = semver +branch = True + +[report] +exclude_lines = + pragma: no cover diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..fcaea3c3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[tool:pytest] +norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ +addopts = + -q + --ignore=.eggs/ + --no-cov-on-fail + --cov=semver + --cov-report=term-missing + --doctest-modules diff --git a/tox.ini b/tox.ini index 20024a57..82695362 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,10 @@ envlist = pypy [testenv] -commands = py.test -q --doctest-modules -deps = pytest +commands = py.test +deps = + pytest + pytest-cov setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 From c399fe7a2075dd699a79d27d7a16d4bee953d2ed Mon Sep 17 00:00:00 2001 From: Jarek Zgoda Date: Mon, 15 Apr 2019 21:02:39 +0200 Subject: [PATCH 106/312] Update doc for format_version to match expected param types (#129) --- semver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/semver.py b/semver.py index 5f9a938c..0709a77f 100644 --- a/semver.py +++ b/semver.py @@ -403,9 +403,9 @@ def min_ver(ver1, ver2): def format_version(major, minor, patch, prerelease=None, build=None): """Format a version according to the Semantic Versioning specification - :param str major: the required major part of a version - :param str minor: the required minor part of a version - :param str patch: the required patch part of a version + :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 From 17cfd1b824e8afbb334dace3f09bb9022a5a4af1 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 29 May 2019 16:09:54 +0200 Subject: [PATCH 107/312] Fix CI (#133) * Update setup.py * Update tox.ini * Update .travis.yml * Allow recent virtualenv * Use newer pip and setuptools * Fix for 3.7 on travis See https://docs.travis-ci.com/user/languages/python/#python-37-and-higher Closes #131 Closes #112 Replaces #113 --- .travis.yml | 8 +++++++- setup.py | 3 ++- tox.ini | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68925a5d..633becbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: python -install: pip install "virtualenv<14.0.0" +install: + - pip install --upgrade pip setuptools + - pip install virtualenv script: python setup.py test matrix: include: @@ -18,5 +20,9 @@ matrix: - python: "3.6" env: TOXENV=py36 + - python: "3.7" + dist: xenial + env: TOXENV=py37 + - python: "pypy" env: TOXENV=pypy diff --git a/setup.py b/setup.py index acdb6024..9deec889 100755 --- a/setup.py +++ b/setup.py @@ -94,9 +94,10 @@ def read_file(filename): 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries :: Python Modules', ], - tests_require=['tox', 'virtualenv<14.0.0'], + tests_require=['tox', 'virtualenv'], cmdclass={ 'clean': Clean, 'test': Tox, diff --git a/tox.ini b/tox.ini index 82695362..a9d4ad57 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] envlist = flake8 - py{27,34,35,36} + py{27,34,35,36,37} pypy [testenv] From 21f87f6403b10bc262ece0f540be043ceef51c23 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 29 May 2019 17:27:55 +0200 Subject: [PATCH 108/312] Fix comparison (#132) Closes #102 Closes #103 --- CHANGELOG | 8 ++++++++ semver.py | 13 +++++++++---- test_semver.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 721047b5..f794629a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,14 @@ Python SemVer library All notable changes to this code base will be documented in this file, in every released version. +Version 2.8.x +============= +:Released: 20yy-mm-dd +:Maintainer: Sébastien Celles + +* Issue #102 (PR #...). Fix comparison between VersionInfo and tuple +* Issue #103 (PR #...). Disallow comparison between VersionInfo and string (and int) + Version 2.8.1 ============= :Released: 2018-07-09 diff --git a/semver.py b/semver.py index 0709a77f..d234f26a 100644 --- a/semver.py +++ b/semver.py @@ -7,7 +7,8 @@ from functools import wraps -__version__ = '2.8.1' + +__version__ = '2.8.2' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' __maintainer__ = 'Sebastien Celles' @@ -78,8 +79,10 @@ def comparator(operator): """ Wrap a VersionInfo binary op method in a type-check """ @wraps(operator) def wrapper(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented + comparable_types = (VersionInfo, dict, tuple) + if not isinstance(other, comparable_types): + raise TypeError("other type %r must be in %r" + % (type(other), comparable_types)) return operator(self, other) return wrapper @@ -95,7 +98,7 @@ class VersionInfo(object): """ __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') - def __init__(self, major, minor, patch, prerelease=None, build=None): + def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): self._major = major self._minor = minor self._patch = patch @@ -215,6 +218,8 @@ def parse(version): def _to_dict(obj): if isinstance(obj, VersionInfo): return obj._asdict() + elif isinstance(obj, tuple): + return VersionInfo(*obj)._asdict() return obj diff --git a/test_semver.py b/test_semver.py index 5e8aae5b..96b78967 100644 --- a/test_semver.py +++ b/test_semver.py @@ -401,6 +401,46 @@ def test_should_compare_version_dictionaries(): assert not(v1 == v4) +def test_should_compare_version_tuples(): + 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') + for t in ((1, 0, 0), (1, 0), (1,), (1, 0, 0, 'pre.2'), + (1, 0, 0, 'pre.2', 'build.4')): + assert v0 < t + assert v0 <= t + assert v0 != t + assert not v0 == t + assert v1 > t + assert v1 >= t + # Symmetric + assert t > v0 + assert t >= v0 + assert t < v1 + assert t <= v1 + assert t != v0 + assert not t == v0 + + +def test_should_not_allow_to_compare_version_with_string(): + v1 = VersionInfo(major=3, minor=4, patch=5, + prerelease='pre.2', build='build.4') + with pytest.raises(TypeError): + v1 > "1.0.0" + with pytest.raises(TypeError): + "1.0.0" > v1 + + +def test_should_not_allow_to_compare_version_with_int(): + v1 = VersionInfo(major=3, minor=4, patch=5, + prerelease='pre.2', build='build.4') + with pytest.raises(TypeError): + v1 > 1 + with pytest.raises(TypeError): + 1 > v1 + + 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') From c1ce5a12b1ca07708e3e472ec7b485986a5d4f6f Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 29 Sep 2019 19:41:57 +0200 Subject: [PATCH 109/312] Fix #126: Provide tox.ini for documentation (#127) * Add "docs" target to run sphinx and output HTML * Provide docs/requirements.txt file * Fix typo in docs/conf.py --- docs/conf.py | 2 +- docs/requirements.txt | 3 +++ tox.ini | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 docs/requirements.txt diff --git a/docs/conf.py b/docs/conf.py index 843b7466..1afd8e7f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -172,6 +172,6 @@ ] -interpshinx_mapping = { +intersphinx_mapping = { "matplotlib": ('http://matplotlib.org', None), } diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..383acaaa --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +# requirements file for documentation +sphinx +sphinx_rtd_theme diff --git a/tox.ini b/tox.ini index a9d4ad57..e172f8a1 100644 --- a/tox.ini +++ b/tox.ini @@ -16,3 +16,9 @@ setenv = basepython = python3 deps = flake8 commands = flake8 + +[testenv:docs] +basepython = python3 +deps = -r{toxinidir}/docs/requirements.txt +skip_install = true +commands = sphinx-build {posargs:-E} -b html docs dist/docs From a598059a3efababba0bd9d626cfef6e0bc7361d1 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 30 Sep 2019 20:27:19 +0200 Subject: [PATCH 110/312] Implement #139: Bump version inside VersionInfo (#141) * Implement #139: Bump version inside VersionInfo * Add bump_major, bump_minor, bump_patch, bump_prerelease and bump_build as methods in class VersionInfo. With this methods, it is now possible to write something like this: ```python ver = semver.parse_version_info("3.4.5") new_ver = ver.bump_major().bump_minor() ``` * Add test cases * Fix Travis CI build * Fix docstring to indicate about new object Co-authored-by: Karol "ppkt" --- semver.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ test_semver.py | 45 ++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/semver.py b/semver.py index d234f26a..ea0ac81b 100644 --- a/semver.py +++ b/semver.py @@ -164,6 +164,80 @@ def __iter__(self): for v in self._astuple(): yield v + def bump_major(self): + """Raise the major part of the version, return a new object + but leave self untouched + + :return: new object with the raised major part + :rtype: VersionInfo + + >>> import semver + >>> ver = semver.parse_version_info("3.4.5") + >>> ver.bump_major() + VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) + """ + return parse_version_info(bump_major(str(self))) + + def bump_minor(self): + """Raise the minor part of the version, return a new object + but leave self untouched + + :return: new object with the raised minor part + :rtype: VersionInfo + + >>> import semver + >>> ver = semver.parse_version_info("3.4.5") + >>> ver.bump_minor() + VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + """ + return parse_version_info(bump_minor(str(self))) + + def bump_patch(self): + """Raise the patch part of the version, return a new object + but leave self untouched + + :return: new object with the raised patch part + :rtype: VersionInfo + + >>> import semver + >>> ver = semver.parse_version_info("3.4.5") + >>> ver.bump_patch() + VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None) + """ + return parse_version_info(bump_patch(str(self))) + + def bump_prerelease(self, token='rc'): + """Raise the prerelease part of the version, return a new object + but leave self untouched + + :param token: defaults to 'rc' + :return: new object with the raised prerelease part + :rtype: str + + >>> import semver + >>> ver = semver.parse_version_info("3.4.5-rc.1") + >>> ver.bump_prerelease() + VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \ +build=None) + """ + return parse_version_info(bump_prerelease(str(self), token)) + + def bump_build(self, token='build'): + """Raise the build part of the version, return a new object + but leave self untouched + + :param token: defaults to 'build' + :return: new object with the raised build part + :rtype: str + + >>> import semver + >>> ver = semver.parse_version_info("3.4.5-rc.1+build.9") + >>> ver.bump_build() + VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \ +build='build.10') + """ + return parse_version_info(bump_build(str(self), token)) + @comparator def __eq__(self, other): return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 diff --git a/test_semver.py b/test_semver.py index 96b78967..45ec3405 100644 --- a/test_semver.py +++ b/test_semver.py @@ -263,6 +263,51 @@ def test_should_bump_patch(): assert bump_patch('3.4.5') == '3.4.6' +def test_should_versioninfo_bump_major_and_minor(): + v = parse_version_info("3.4.5") + expected = parse_version_info("4.1.0") + assert v.bump_major().bump_minor() == expected + + +def test_should_versioninfo_bump_minor_and_patch(): + v = parse_version_info("3.4.5") + expected = parse_version_info("3.5.1") + assert v.bump_minor().bump_patch() == expected + + +def test_should_versioninfo_bump_patch_and_prerelease(): + v = parse_version_info("3.4.5-rc.1") + expected = parse_version_info("3.4.6-rc.1") + assert v.bump_patch().bump_prerelease() == expected + + +def test_should_versioninfo_bump_patch_and_prerelease_with_token(): + v = parse_version_info("3.4.5-dev.1") + expected = parse_version_info("3.4.6-dev.1") + assert v.bump_patch().bump_prerelease("dev") == expected + + +def test_should_versioninfo_bump_prerelease_and_build(): + v = parse_version_info("3.4.5-rc.1+build.1") + expected = parse_version_info("3.4.5-rc.2+build.2") + assert v.bump_prerelease().bump_build() == expected + + +def test_should_versioninfo_bump_prerelease_and_build_with_token(): + v = parse_version_info("3.4.5-rc.1+b.1") + expected = parse_version_info("3.4.5-rc.2+b.2") + assert v.bump_prerelease().bump_build("b") == expected + + +def test_should_versioninfo_bump_multiple(): + v = parse_version_info("3.4.5-rc.1+build.1") + expected = parse_version_info("3.4.5-rc.2+build.2") + assert v.bump_prerelease().bump_build().bump_build() == expected + expected = parse_version_info("3.4.5-rc.3") + assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == \ + expected + + def test_should_ignore_extensions_for_bump(): assert bump_patch('3.4.5-rc1+build4') == '3.4.6' From ecb07365ea2856e70b241bae73d83c7d485630d5 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 1 Oct 2019 07:34:37 +0200 Subject: [PATCH 111/312] Doc: improve Usage section (#143) * Change titles to using verb instead of noun * Adding version information in docs/conf.py Currently, the version of semver is imported (using from semver import __version__). To avoid importing semver, version string could be managed through bumpversion2. However, this is out of scope for this PR. * Improve entry page * Improve main title with semver's version and a catchy phrase * Move the |latest-version| placeholder to the other placeholders like build status, downloads etc. * Remove the "Welcome bla" heading as it introduces a lonly section and it is mostly useless * Add a short explanation about the "version style" major.minor.patch (cited from semver.org) * Reference the other parts with just the filename (with the .rst extension) and without the title. The title is automatically retrieved from the file, no need to repeat it. This makes the overall doc more consistent. * Change py.test -> pytest * Fix #142: Improve usage documentation * Use descriptive titles (verb+ing) to better find the topic * Structure the content with (sub)sections * Try to structure the different sections with list if there are different options to make it more readable * Document other, possible unknown features * Silence flake8 with "noqa: E402" --- docs/conf.py | 5 +- docs/development.rst | 8 +- docs/index.rst | 33 +++-- docs/install.rst | 4 +- docs/usage.rst | 282 ++++++++++++++++++++++++++++++++++++++----- 5 files changed, 283 insertions(+), 49 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1afd8e7f..786d444e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,6 +20,7 @@ import sys sys.path.insert(0, os.path.abspath('..')) +from semver import __version__ # noqa: E402 # -- General configuration ------------------------------------------------ @@ -57,9 +58,9 @@ # built documents. # # The short X.Y version. -version = '' +version = __version__ # The full version, including alpha/beta/rc tags. -release = '' +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/development.rst b/docs/development.rst index 000b2f65..05542d29 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -1,5 +1,5 @@ -How to Contribute ------------------ +Contributing to semver +====================== When you make changes to the code please run the tests before pushing your code to your fork and opening a `pull request`_: @@ -8,7 +8,7 @@ code to your fork and opening a `pull request`_: python setup.py test -We use `py.test`_ and `tox`_ to run tests against all supported Python +We use `pytest`_ and `tox`_ to run tests against all supported Python versions. All test dependencies are resolved automatically, apart from virtualenv, which for the moment you still may have to install manually: @@ -24,5 +24,5 @@ You can use the ``clean`` command to remove build and test files and folders: .. _pull request: https://github.com/k-bx/python-semver/pulls -.. _py.test: http://pytest.org/ +.. _pytest: http://pytest.org/ .. _tox: http://tox.testrun.org/ diff --git a/docs/index.rst b/docs/index.rst index 84136292..1c15abab 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,26 +1,33 @@ -Semver |latest-version| -======================= +Semver |version| -- Semantic Versioning +======================================= -|build-status| |python-support| |downloads| |license| +|build-status| |latest-version| |python-support| |downloads| |license| .. python-semver documentation master file, created by sphinx-quickstart on Tue May 1 16:51:19 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to python-semver's documentation! -========================================= +A Python module for `semantic versioning`_. Simplifies comparing versions. + +The module follows the ``MAJOR.MINOR.PATCH`` style: + +* ``MAJOR`` version when you make incompatible API changes, +* ``MINOR`` version when you add functionality in a backwards compatible manner, and +* ``PATCH`` version when you make backwards compatible bug fixes. + +Additional labels for pre-release and build metadata are supported. + + .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Contents - Install - Usage - Development - API - -A Python module for `semantic versioning`_. Simplifies comparing versions. + install + usage + development + api .. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg @@ -41,7 +48,7 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. _semantic versioning: http://semver.org/ -Indices and tables +Indices and Tables ================== * :ref:`genindex` diff --git a/docs/install.rst b/docs/install.rst index 26f4e1c0..1abb7ed7 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,5 +1,5 @@ -Installation ------------- +Installing semver +================= For Python 2: diff --git a/docs/usage.rst b/docs/usage.rst index c30ffefb..795cb3ae 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,46 +1,272 @@ -Usage ------ +Using semver +============ -This module provides just couple of functions, main of which are: +The ``semver`` module can store a version in different types: + +* as a string. +* as :class:`semver.VersionInfo`, a dedicated class for a version type. +* as a dictionary. + +Each type can be converted into the other, if the minimum requirements +are met. + + +Creating a Version +------------------ + +A version can be created in different ways: + +* as a complete version string:: + + >>> semver.parse_version_info("3.4.5-pre.2+build.4") + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + +* with individual parts:: + + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + >>> semver.VersionInfo(3, 5) + VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + + You can pass either an integer or a string for ``major``, ``minor``, or + ``patch``:: + + >>> semver.VersionInfo("3", "5") + VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + + In the simplest form, ``prerelease`` and ``build`` can also be + integers:: + + >>> semver.VersionInfo(1, 2, 3, 4, 5) + VersionInfo(major=1, minor=2, patch=3, prerelease=4, build=5) + + +Parsing a Version String +------------------------ + +"Parsing" in this context means to identify the different parts in a 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') + +* With :func:`semver.VersionInfo.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') + +* With :func:`semver.parse`:: + + >>> semver.parse("3.4.5-pre.2+build.4") + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + + +Accessing Parts of a Version +---------------------------- + +The :class:`semver.VersionInfo` contains attributes to access the different +parts of a version: .. code-block:: python - >>> import semver + >>> v = VersionInfo.parse("3.4.5-pre.2+build.4") + >>> v.major + 3 + >>> v.minor + 4 + >>> v.patch + 5 + >>> v.prerelease + 'pre.2' + >>> v.build + 'build.4' + +However, the attributes are read-only. You cannot change an attribute. +If you do, you get an ``AttributeError``:: + + >>> v.minor = 5 + Traceback (most recent call last) + ... + AttributeError: attribute 'minor' is readonly + +In case you need the different parts of a version stepwise, iterate over the :class:`semver.VersionInfo` instance:: + + >>> for item in VersionInfo.parse("3.4.5-pre.2+build.4"): + ... print(item) + 3 + 4 + 5 + pre.2 + build.4 + >>> list(VersionInfo.parse("3.4.5-pre.2+build.4")) + [3, 4, 5, 'pre.2', 'build.4'] + + +.. _sec.convert.versions: + +Converting Different Version Types +---------------------------------- + +Depending which function you call, you get different types +(as explained in the beginning of this chapter). + +* From a string into :class:`semver.VersionInfo`:: + + >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") + VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') + +* From :class:`semver.VersionInfo` into a string:: + + >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4")) + '3.4.5-pre.2+build.4' + +* From a dictionary into :class:`semver.VersionInfo`:: + + >>> 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') + + As a minimum requirement, your dictionary needs at least the ``major`` + key, others can be omitted. You get a ``TypeError`` if your + dictionary contains invalid keys. + Only ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` + are allowed. + +* From a tuple into :class:`semver.VersionInfo`:: + + >>> t = (3, 5, 6) + >>> semver.VersionInfo(*t) + VersionInfo(major=3, minor=5, patch=6, prerelease=None, build=None) + +* From a :class:`semver.VersionInfo` into a dictionary:: + + >>> v = semver.VersionInfo(major=3, minor=4, patch=5) + >>> semver.parse(str(v)) + {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} + + +Increasing Parts of a Version +----------------------------- + +The ``semver`` module contains the following functions to raise parts of +a version: + +* :func:`semver.bump_major`: raises the major part and set all other parts to + zero. Set ``prerelease`` and ``build`` to ``None``. +* :func:`semver.bump_minor`: raises the minor part and sets ``patch`` to zero. + Set ``prerelease`` and ``build`` to ``None``. +* :func:`semver.bump_patch`: raises the patch part. Set ``prerelease`` and + ``build`` to ``None``. +* :func:`semver.bump_prerelease`: raises the prerelease part and set + ``build`` to ``None``. +* :func:`semver.bump_build`: raises the build part. + +.. code-block:: python + + >>> semver.bump_major("3.4.5-pre.2+build.4") + '4.0.0' + >>> semver.bump_minor("3.4.5-pre.2+build.4") + '3.5.0' + >>> semver.bump_patch("3.4.5-pre.2+build.4") + '3.4.6' + >>> semver.bump_prerelease("3.4.5-pre.2+build.4") + '3.4.5-pre.3' + >>> semver.bump_build("3.4.5-pre.2+build.4") + '3.4.5-pre.2+build.5' + + +Comparing Versions +------------------ + +To compare two versions depends on your type: + +* **Two strings** + + Use :func:`semver.compare`:: + >>> 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 - >>> semver.match("2.0.0", ">=1.0.0") + + The return value is negative if ``version1 < version2``, zero if + ``version1 == version2`` and strictly positive if ``version1 > version2``. + +* **Two** :class:`semver.VersionInfo` **types** + + Use the specific operator. Currently, the operators ``<``, + ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: + + >>> v1 = VersionInfo.parse("3.4.5") + >>> v2 = VersionInfo.parse("3.5.1") + >>> v1 < v2 True - >>> semver.match("1.0.0", ">1.0.0") + >>> v1 > v2 False - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - >>> version_parts = semver.parse("3.4.5-pre.2+build.4") - >>> version_parts == { - ... 'major': 3, 'minor': 4, 'patch': 5, - ... 'prerelease': 'pre.2', 'build': 'build.4'} + +* **A** :class:`semver.VersionInfo` **type and a** ``tuple`` + + Use the operator as with two :class:`semver.VersionInfo` types:: + + >>> v = VersionInfo.parse("3.4.5") + >>> v > (1, 0) True - >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") - >>> # or using static method parse - >>> from semver import VersionInfo - >>> version_info = VersionInfo.parse("3.4.5-pre.2+build.4") - >>> version_info - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - >>> version_info.major - 3 - >>> version_info > (1, 0) + >>> v < (3, 5) True - >>> version_info < (3, 5) + + The opposite does also work:: + + >>> (1, 0) < v True - >>> semver.bump_major("3.4.5") - '4.0.0' - >>> semver.bump_minor("3.4.5") - '3.5.0' - >>> semver.bump_patch("3.4.5") - '3.4.6' + >>> (3, 5) > v + True + +Other types cannot be compared (like dictionaries, lists etc). + +If you need to convert some types into other, refer to :ref:`sec.convert.versions`. + + + +Comparing Versions through an Expression +--------------------------------------- + +If you need a more fine-grained approach of comparing two versions, +use the :func:`semver.match` function. It expects two arguments: + +1. a version string +2. a match expression + +Currently, the match expression supports the following operators: + +* ``<`` smaller than +* ``>`` greater than +* ``>=`` greater or equal than +* ``<=`` smaller or equal than +* ``==`` equal +* ``!=`` not equal + +That gives you the following possibilities to express your condition: + +.. code-block:: python + + >>> semver.match("2.0.0", ">=1.0.0") + True + >>> semver.match("1.0.0", ">1.0.0") + False + + +Getting Minimum and Maximum of two Versions +------------------------------------------- + +.. code-block:: python + >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' >>> semver.min_ver("1.0.0", "2.0.0") From f60e11cf632635d6a9a2d370896ef29abff10df3 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 1 Oct 2019 13:36:09 +0200 Subject: [PATCH 112/312] Fix #145: add posargs in tox.ini (single test cases) (#146) Fix #146: improve tox.ini/.travis.yml * Changes in tox.ini: * add posargs for pytest. This allows to run only a single test function. * Rename py.test -> pytest as the old spelling py.test will be deprecated in the future. * Changes in .travis.yml: * Add cache for pip to speed up things a little bit * Add tox as a installation requirement * Replace setup.py with tox as it discouraged in https://tox.readthedocs.io/en/latest/example/basic.html#integration-with-setup-py-test-command --- .travis.yml | 6 ++++-- tox.ini | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 633becbe..0082ac4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ +# config file for automatic testing at travis-ci.org language: python +cache: pip install: - pip install --upgrade pip setuptools - - pip install virtualenv -script: python setup.py test + - pip install virtualenv tox +script: tox -v matrix: include: - python: "2.7" diff --git a/tox.ini b/tox.ini index e172f8a1..33b60895 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = pypy [testenv] -commands = py.test +commands = pytest {posargs:} deps = pytest pytest-cov From 7e48417aa86470df755415efbbaedf83f1264f5d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 1 Oct 2019 13:38:30 +0200 Subject: [PATCH 113/312] Fix #85: Improve contribution section (#147) * Fix #85: Improve contribution section Closes #115 too. This is a mostly a complete rewrite with the following ideas and changes in mind: * Add a brief workflow, starting from forking, cloning, creating a branch, and opening a pull request. * Use "procedure style" and use active voice to make reader aware of an action * Remove intersphinx section for matplotlib (not used) in docs/conf.py * Fix flake8 W391 for Python 3.4 Error message was: W391 blank line at end of file --- CONTRIBUTORS | 1 + docs/conf.py | 5 --- docs/development.rst | 72 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 205de33a..98d1d623 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -33,6 +33,7 @@ Significant contributors * Peter Bittner * robi-wan * T. Jameson Little +* Tom Schraitle * Tuure Laurinolli * Tyler Cross * Zack Lalanne diff --git a/docs/conf.py b/docs/conf.py index 786d444e..c2b49f49 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -171,8 +171,3 @@ author, 'semver', 'One line description of project.', 'Miscellaneous'), ] - - -intersphinx_mapping = { - "matplotlib": ('http://matplotlib.org', None), -} diff --git a/docs/development.rst b/docs/development.rst index 05542d29..ed0bdf89 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -1,12 +1,21 @@ Contributing to semver ====================== -When you make changes to the code please run the tests before pushing your -code to your fork and opening a `pull request`_: +Do you want to contribute? Great! We would like to give you some +helpful tips and tricks. +When you make changes to the code, we would greatly appreciate if you +consider the following requirements: -.. code-block:: bash +* Make sure your code adheres to the `Semantic Versioning`_ specification. + +* Check if your feature is covered by the Semantic Versioning specification. + If not, ask on its GitHub project https://github.com/semver/semver. + +* Write test cases if you implement a new feature. - python setup.py test +* Test also for side effects of your new feature and run the complete + test suite. +* Document the new feature. We use `pytest`_ and `tox`_ to run tests against all supported Python versions. All test dependencies are resolved automatically, apart from @@ -16,13 +25,60 @@ virtualenv, which for the moment you still may have to install manually: pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only -You can use the ``clean`` command to remove build and test files and folders: +We recommend to use the following workflow if you would like to contribute: -.. code-block:: bash +1. Fork our project on GitHub using this link: + https://github.com/k-bx/python-semver/fork + +2. Clone your forked Git repository (replace ``GITHUB_USER`` with your + account name on GitHub):: + + $ git clone git@github.com:GITHUB_USER/python-semver.git + +3. Create a new branch. You can name your branch whatever you like, but we + recommend to use some meaningful name. If your fix is based on a + existing GitHub issue, add also the number. Good examples would be: + + * ``feature/123-improve-foo`` when implementing a new feature + * ``bugfix/123-fix-security-bar`` when dealing with bugfixes + + Use this :command:`git` command:: + + $ git checkout -b feature/NAME_OF_YOUR_FEATURE + +4. Work on your branch. Commit your work. Don't forget to write test cases + for your new feature. + +5. Run the test suite. You have the following options: + + * To run a complete test use the ``setup.py`` script (shown for Python 3):: + + $ python3 setup.py test + + This may create some errors as you probably do not have all Python + versions installed on your system. To restrict it to only installed + version (probably 2.7 and 3.x), pass this options:: + + $ python3 setup.py test -a --skip-missing-interpreters + + * To run a test for a specific Python version, use the + :command:`tox` command, for example, for Python 3.6:: + + $ tox -e py36 + +6. Create a `pull request`_. Describe in the pull request what you did + and why. If you have open questions, ask. + +7. Wait for feedback. If you receive any comments, address these. + +8. After your pull request got accepted, delete your branch. + +9. Use the ``clean`` command to remove build and test files and folders:: - python setup.py clean + $ python setup.py clean .. _pull request: https://github.com/k-bx/python-semver/pulls .. _pytest: http://pytest.org/ -.. _tox: http://tox.testrun.org/ +.. _tox: https://tox.readthedocs.org/ +.. _Semantic Versioning: https://semver.org From 5fb101191ad7b7c0104a6372a29ac75da04771f0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 2 Oct 2019 15:29:05 +0200 Subject: [PATCH 114/312] Fix #148: Remove & replace python setup.py test (#149) As tox discouraged using "python setup.py test", we shouldn't recommend that in our documentation. Replacing it with "tox". --- docs/development.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/development.rst b/docs/development.rst index ed0bdf89..db15619e 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -49,17 +49,17 @@ We recommend to use the following workflow if you would like to contribute: 4. Work on your branch. Commit your work. Don't forget to write test cases for your new feature. -5. Run the test suite. You have the following options: +5. Run the test suite. You can decide to run the complete test suite or + only part of it: - * To run a complete test use the ``setup.py`` script (shown for Python 3):: + * To run all tests, use:: - $ python3 setup.py test + $ tox - This may create some errors as you probably do not have all Python - versions installed on your system. To restrict it to only installed - version (probably 2.7 and 3.x), pass this options:: + If you have not all Python interpreters installed on your system + it will probably give you some errors. To avoid such errors, use:: - $ python3 setup.py test -a --skip-missing-interpreters + $ tox --skip-missing-interpreters * To run a test for a specific Python version, use the :command:`tox` command, for example, for Python 3.6:: From e3f62e13ab76336cd88bbf0f81ff7eb6c72069b2 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 2 Oct 2019 15:29:29 +0200 Subject: [PATCH 115/312] Fix #136: add testsuite to tar ball (#151) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 926cc7b1..9ed3b402 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include README.rst include LICENSE.txt +include test_*.py From 1f0b21c8ee76bb6187710b03962d352c114b89c0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 2 Oct 2019 15:31:01 +0200 Subject: [PATCH 116/312] Improve coverage (#150) --- .coveragerc | 1 + test_semver.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.coveragerc b/.coveragerc index 0b2e8f7a..7ed871ce 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,3 +7,4 @@ branch = True [report] exclude_lines = pragma: no cover + if __name__ == .__main__.: diff --git a/test_semver.py b/test_semver.py index 45ec3405..b694296d 100644 --- a/test_semver.py +++ b/test_semver.py @@ -316,6 +316,10 @@ def test_should_get_max(): assert max_ver('3.4.5', '4.0.2') == '4.0.2' +def test_should_get_max_same(): + assert max_ver('3.4.5', '3.4.5') == '3.4.5' + + def test_should_get_min(): assert min_ver('3.4.5', '4.0.2') == '3.4.5' From 8ee92b498db01ad02833322e88bbb8fe6884d132 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 2 Oct 2019 16:57:52 +0200 Subject: [PATCH 117/312] Fix #135, convert prerelease and build to string (#140) * Fix #135, convert prerelease and build to string Creating VersionInfo(3, 2, 1, 1) succeeds, but fails later when comparing it with another VersionInfo instance (for example, v1 < v2): ``` def split_key(key): > return [convert(c) for c in key.split('.')] E AttributeError: 'int' object has no attribute 'split' ``` * Force major, minor, and patch as int To allow VersionInfo(10) > VersionInfo('2') == True * Enhance test_should_compare_prerelease_and_build_with_numbers Add example from #135 * Simplify test case, thanks to Alexander Co-authored-by: Alexander Grund * Split test case and check types Co-authored-by: Alexander Grund * Move assert line * Move assert line to test_should_be_able_to_use_strings_as_major_minor_patch * Add additional assert, comparing VersionInfo created with strings and with integers Co-authored-by: Alexander Grund * Integrate tests from Alex, many thanks! Co-authored-by: Alexander Grund * Remove v variable to silence flake8 --- semver.py | 10 +++++----- test_semver.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/semver.py b/semver.py index ea0ac81b..93cde842 100644 --- a/semver.py +++ b/semver.py @@ -99,11 +99,11 @@ class VersionInfo(object): __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): - self._major = major - self._minor = minor - self._patch = patch - self._prerelease = prerelease - self._build = build + self._major = int(major) + self._minor = int(minor) + self._patch = int(patch) + self._prerelease = None if prerelease is None else str(prerelease) + self._build = None if build is None else str(build) @property def major(self): diff --git a/test_semver.py b/test_semver.py index b694296d..fee77f84 100644 --- a/test_semver.py +++ b/test_semver.py @@ -546,3 +546,37 @@ def test_immutable_unknown_attribute(version): def test_version_info_should_be_iterable(version): assert tuple(version) == (version.major, version.minor, version.patch, version.prerelease, version.build) + + +def test_should_compare_prerelease_and_build_with_numbers(): + assert VersionInfo(major=1, minor=9, patch=1, prerelease=1, build=1) < \ + VersionInfo(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') + + +def test_should_be_able_to_use_strings_as_major_minor_patch(): + v = VersionInfo('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) + + +def test_using_non_numeric_string_as_major_minor_patch_throws(): + with pytest.raises(ValueError): + VersionInfo('a') + with pytest.raises(ValueError): + VersionInfo(1, 'a') + with pytest.raises(ValueError): + VersionInfo(1, 2, 'a') + + +def test_should_be_able_to_use_integers_as_prerelease_build(): + v = VersionInfo(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') From 161713f680675f635072ed388f48634e85bdf390 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 5 Oct 2019 17:16:17 +0200 Subject: [PATCH 118/312] Fix #154: Improve Description on PyPI (#155) * Fix #154: Improve Description * Improve README and describe a short session with parsing a version string and access the different parts of a version. * Include README (=Quickstart) as part of the documentation. * Add missing docstrings for properties * Correct HTML theme --- README.rst | 81 ++++++++++++++++++++++++++++++++++++++++++++----- docs/index.rst | 36 +--------------------- docs/readme.rst | 2 ++ docs/usage.rst | 2 +- semver.py | 5 +++ 5 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 docs/readme.rst diff --git a/README.rst b/README.rst index aa81944b..6833b3b4 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,81 @@ -Semver |latest-version| -======================= +Quickstart +========== -|build-status| |python-support| |downloads| |license| |docs| +.. teaser-begin A Python module for `semantic versioning`_. Simplifies comparing versions. +|build-status| |python-support| |downloads| |license| |docs| + +.. teaser-end + + +The module follows the ``MAJOR.MINOR.PATCH`` style: + +* ``MAJOR`` version when you make incompatible API changes, +* ``MINOR`` version when you add functionality in a backwards compatible manner, and +* ``PATCH`` version when you make backwards compatible bug fixes. + +Additional labels for pre-release and build metadata are supported. + +To import this library, use: + +.. code-block:: python + + >>> import semver + +Working with the library is quite straightforward. To turn a version string into the +different parts, use the :func:`semver.parse` function: + +.. code-block:: python + + >>> ver = semver.parse('1.2.3-pre.2+build.4') + >>> ver['major'] + 1 + >>> ver['minor'] + 2 + >>> ver['patch'] + 3 + >>> ver['prerelease'] + 'pre.2' + >>> ver['build'] + 'build.5' + +To raise parts of a version, there are a couple of functions available for +you. The :func:`semver.parse_version_info` function converts a version string +into a :class:`semver.VersionInfo` class. The function +:func:`semver.VersionInfo.bump_major` leaves the original object untouched, but +returns a new :class:`semver.VersionInfo` instance with the raised major part: + +.. code-block:: python + + >>> ver = semver.parse_version_info("3.4.5") + >>> ver.bump_major() + VersionInfo(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=0, patch=1, prerelease=None, build=None) + +To compare two versions, semver provides the :func:`semver.compare` function. +The return value indicates the relationship between the first and second +version: + +.. code-block:: python + + >>> 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 + + +There are other functions to discover. Read on! + .. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg :alt: Latest version on PyPI @@ -25,7 +96,3 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. :target: http://python-semver.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. _semantic versioning: http://semver.org/ - -Documentation -------------- -|docs| diff --git a/docs/index.rst b/docs/index.rst index 1c15abab..ecf160d9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,52 +1,18 @@ Semver |version| -- Semantic Versioning ======================================= -|build-status| |latest-version| |python-support| |downloads| |license| - -.. python-semver documentation master file, created by - sphinx-quickstart on Tue May 1 16:51:19 2018. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -A Python module for `semantic versioning`_. Simplifies comparing versions. - -The module follows the ``MAJOR.MINOR.PATCH`` style: - -* ``MAJOR`` version when you make incompatible API changes, -* ``MINOR`` version when you add functionality in a backwards compatible manner, and -* ``PATCH`` version when you make backwards compatible bug fixes. - -Additional labels for pre-release and build metadata are supported. - - .. toctree:: :maxdepth: 2 :caption: Contents + readme install usage development api -.. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg - :alt: Latest version on PyPI - :target: https://pypi.python.org/pypi/semver -.. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master - :alt: Build status - :target: https://travis-ci.org/k-bx/python-semver -.. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg - :target: https://pypi.python.org/pypi/semver - :alt: Python versions -.. |downloads| image:: https://img.shields.io/pypi/dm/semver.svg - :alt: Monthly downloads from PyPI - :target: https://pypi.python.org/pypi/semver -.. |license| image:: https://img.shields.io/pypi/l/semver.svg - :alt: Software license - :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt -.. _semantic versioning: http://semver.org/ - Indices and Tables ================== diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 00000000..0aa732a1 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1,2 @@ +.. include:: ../README.rst + diff --git a/docs/usage.rst b/docs/usage.rst index 795cb3ae..c2a80a73 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -235,7 +235,7 @@ If you need to convert some types into other, refer to :ref:`sec.convert.version Comparing Versions through an Expression ---------------------------------------- +---------------------------------------- If you need a more fine-grained approach of comparing two versions, use the :func:`semver.match` function. It expects two arguments: diff --git a/semver.py b/semver.py index 93cde842..ea02a68d 100644 --- a/semver.py +++ b/semver.py @@ -107,6 +107,7 @@ def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): @property def major(self): + """The major part of a version""" return self._major @major.setter @@ -115,6 +116,7 @@ def major(self, value): @property def minor(self): + """The minor part of a version""" return self._minor @minor.setter @@ -123,6 +125,7 @@ def minor(self, value): @property def patch(self): + """The patch part of a version""" return self._patch @patch.setter @@ -131,6 +134,7 @@ def patch(self, value): @property def prerelease(self): + """The prerelease part of a version""" return self._prerelease @prerelease.setter @@ -139,6 +143,7 @@ def prerelease(self, value): @property def build(self): + """The build part of a version""" return self._build @build.setter From cc050440178e117128c29c29ccd2df2bc52771b9 Mon Sep 17 00:00:00 2001 From: Karol Date: Sat, 5 Oct 2019 17:16:38 +0200 Subject: [PATCH 119/312] Fix #119: Invoke clean from distutils before running our code (#152) --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9deec889..19442380 100755 --- a/setup.py +++ b/setup.py @@ -5,6 +5,10 @@ from os.path import dirname, join from setuptools import setup from setuptools.command.test import test as TestCommand +try: + from setuptools.command.clean import clean as CleanCommand +except ImportError: + from distutils.command.clean import clean as CleanCommand from shlex import split from shutil import rmtree @@ -30,8 +34,9 @@ def run_tests(self): exit(errno) -class Clean(TestCommand): +class Clean(CleanCommand): def run(self): + super().run() delete_in_root = [ 'build', '.cache', From d8da4821856d283deb975c9ba888b97a8c02c2e2 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 5 Oct 2019 17:19:53 +0200 Subject: [PATCH 120/312] Improve doctests (#157) * Introduce conftest.py to add pytest fixture for doctest_namespace See https://docs.pytest.org/en/latest/doctest.html#doctest-namespace-fixture * Remove the "import semver" line in each doctest; this is covered in conftest.py now as a pytest fixture * Extend setup.cfg with --doctest-report ndiff Makes the output a bit nicer in case something goes wrong --- conftest.py | 7 +++++++ semver.py | 32 ++++++++------------------------ setup.cfg | 1 + 3 files changed, 16 insertions(+), 24 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..4f49a137 --- /dev/null +++ b/conftest.py @@ -0,0 +1,7 @@ +import pytest +import semver + + +@pytest.fixture(autouse=True) +def add_semver(doctest_namespace): + doctest_namespace["semver"] = semver diff --git a/semver.py b/semver.py index ea02a68d..d4fbb1dd 100644 --- a/semver.py +++ b/semver.py @@ -49,7 +49,6 @@ def parse(version): if not provided :rtype: dict - >>> import semver >>> ver = semver.parse('3.4.5-pre.2+build.4') >>> ver['major'] 3 @@ -176,7 +175,6 @@ def bump_major(self): :return: new object with the raised major part :rtype: VersionInfo - >>> import semver >>> ver = semver.parse_version_info("3.4.5") >>> ver.bump_major() VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) @@ -190,7 +188,6 @@ def bump_minor(self): :return: new object with the raised minor part :rtype: VersionInfo - >>> import semver >>> ver = semver.parse_version_info("3.4.5") >>> ver.bump_minor() VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) @@ -204,7 +201,6 @@ def bump_patch(self): :return: new object with the raised patch part :rtype: VersionInfo - >>> import semver >>> ver = semver.parse_version_info("3.4.5") >>> ver.bump_patch() VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None) @@ -219,7 +215,6 @@ def bump_prerelease(self, token='rc'): :return: new object with the raised prerelease part :rtype: str - >>> import semver >>> ver = semver.parse_version_info("3.4.5-rc.1") >>> ver.bump_prerelease() VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \ @@ -235,7 +230,6 @@ def bump_build(self, token='build'): :return: new object with the raised build part :rtype: str - >>> import semver >>> ver = semver.parse_version_info("3.4.5-rc.1+build.9") >>> ver.bump_build() VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \ @@ -282,14 +276,13 @@ def __hash__(self): def parse(version): """Parse version string to a VersionInfo instance. - >>> from semver import VersionInfo - >>> VersionInfo.parse('3.4.5-pre.2+build.4') + :param version: version string + :return: a :class:`semver.VersionInfo` instance + :rtype: :class:`semver.VersionInfo` + + >>> semver.VersionInfo.parse('3.4.5-pre.2+build.4') VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') - - :param version: version string - :return: a :class:`VersionInfo` instance - :rtype: :class:`VersionInfo` """ return parse_version_info(version) @@ -309,7 +302,6 @@ def parse_version_info(version): :return: a :class:`VersionInfo` instance :rtype: :class:`VersionInfo` - >>> import semver >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") >>> version_info.major 3 @@ -385,7 +377,6 @@ def compare(ver1, ver2): zero if ver1 == ver2 and strictly positive if ver1 > ver2 :rtype: int - >>> import semver >>> semver.compare("1.0.0", "2.0.0") -1 >>> semver.compare("2.0.0", "1.0.0") @@ -413,7 +404,6 @@ def match(version, match_expr): :return: True if the expression matches the version, otherwise False :rtype: bool - >>> import semver >>> semver.match("2.0.0", ">=1.0.0") True >>> semver.match("1.0.0", ">1.0.0") @@ -454,7 +444,6 @@ def max_ver(ver1, ver2): :return: the greater version of the two :rtype: :class:`VersionInfo` - >>> import semver >>> semver.max_ver("1.0.0", "2.0.0") '2.0.0' """ @@ -473,7 +462,6 @@ def min_ver(ver1, ver2): :return: the smaller version of the two :rtype: :class:`VersionInfo` - >>> import semver >>> semver.min_ver("1.0.0", "2.0.0") '1.0.0' """ @@ -495,7 +483,6 @@ def format_version(major, minor, patch, prerelease=None, build=None): :return: the formatted string :rtype: str - >>> import semver >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') '3.4.5-pre.2+build.4' """ @@ -529,7 +516,6 @@ def bump_major(version): :return: the raised version string :rtype: str - >>> import semver >>> semver.bump_major("3.4.5") '4.0.0' """ @@ -544,7 +530,6 @@ def bump_minor(version): :return: the raised version string :rtype: str - >>> import semver >>> semver.bump_minor("3.4.5") '3.5.0' """ @@ -559,7 +544,6 @@ def bump_patch(version): :return: the raised version string :rtype: str - >>> import semver >>> semver.bump_patch("3.4.5") '3.4.6' """ @@ -576,7 +560,7 @@ def bump_prerelease(version, token='rc'): :return: the raised version string :rtype: str - >>> bump_prerelease('3.4.5', 'dev') + >>> semver.bump_prerelease('3.4.5', 'dev') '3.4.5-dev.1' """ verinfo = parse(version) @@ -595,7 +579,7 @@ def bump_build(version, token='build'): :return: the raised version string :rtype: str - >>> bump_build('3.4.5-rc.1+build.9') + >>> semver.bump_build('3.4.5-rc.1+build.9') '3.4.5-rc.1+build.10' """ verinfo = parse(version) @@ -613,7 +597,7 @@ def finalize_version(version): :return: the finalized version string :rtype: str - >>> finalize_version('1.2.3-rc.5') + >>> semver.finalize_version('1.2.3-rc.5') '1.2.3' """ verinfo = parse(version) diff --git a/setup.cfg b/setup.cfg index fcaea3c3..ba377b10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,4 @@ addopts = --cov=semver --cov-report=term-missing --doctest-modules + --doctest-report ndiff From c585f5cb8b9a0d5859a885e94a7e84597a554d67 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 6 Oct 2019 21:26:31 +0200 Subject: [PATCH 121/312] Doc: Clarify Contributing section (#158) * Structure the text with more subsection * Introduce a separate section about "Running the Test Suite" This gives an overview, recommends flake8, and how to run the complete test suite or only a specific test function * Introduce a separate section about "Documenting semver" Gives contributors some helpful advise * Use references (:ref:) to other section This helps to reduce the "big" procedure and make it more readable. * Change wording, correct phrases, clarify text As well as in Python's Zen "Simple is better than complex", this applies to documentation too. * setup.cfg: remove "-q" option Removing it does NOT change the default output. However, you can pass "-v" and run "tox -- -v" to print all test function. This is covered in the documentation now --- docs/api.rst | 2 + docs/development.rst | 175 +++++++++++++++++++++++++++++++++++-------- setup.cfg | 1 - 3 files changed, 144 insertions(+), 34 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index cd8e73b4..0003fefc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,3 +1,5 @@ +.. _api: + API === diff --git a/docs/development.rst b/docs/development.rst index db15619e..be66c14f 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -1,10 +1,26 @@ Contributing to semver ====================== -Do you want to contribute? Great! We would like to give you some -helpful tips and tricks. -When you make changes to the code, we would greatly appreciate if you -consider the following requirements: +The semver source code is managed using Git and is hosted on GitHub:: + + git clone git://github.com/k-bx/python-semver + + +Reporting Bugs and Feedback +--------------------------- + +If you think you have encountered a bug in semver or have an idea for a new +feature? Great! We like to hear from you. + +First, take the time to look into our GitHub `issues`_ tracker if +this already covered. If not, changes are good that we avoid double work. + + +Fixing Bugs and Implementing New Features +----------------------------------------- + +Before you make changes to the code, we would highly appreciate if you +consider the following general requirements: * Make sure your code adheres to the `Semantic Versioning`_ specification. @@ -15,70 +31,163 @@ consider the following requirements: * Test also for side effects of your new feature and run the complete test suite. -* Document the new feature. -We use `pytest`_ and `tox`_ to run tests against all supported Python -versions. All test dependencies are resolved automatically, apart from -virtualenv, which for the moment you still may have to install manually: +* Document the new feature, see :ref:`doc` for details. -.. code-block:: bash - pip install "virtualenv<14.0.0" # <14.0.0 needed for Python 3.2 only +Modifying the Code +------------------ -We recommend to use the following workflow if you would like to contribute: +We recommend the following workflow: -1. Fork our project on GitHub using this link: +#. Fork our project on GitHub using this link: https://github.com/k-bx/python-semver/fork -2. Clone your forked Git repository (replace ``GITHUB_USER`` with your +#. Clone your forked Git repository (replace ``GITHUB_USER`` with your account name on GitHub):: $ git clone git@github.com:GITHUB_USER/python-semver.git -3. Create a new branch. You can name your branch whatever you like, but we +#. Create a new branch. You can name your branch whatever you like, but we recommend to use some meaningful name. If your fix is based on a existing GitHub issue, add also the number. Good examples would be: - * ``feature/123-improve-foo`` when implementing a new feature - * ``bugfix/123-fix-security-bar`` when dealing with bugfixes + * ``feature/123-improve-foo`` when implementing a new feature in issue 123 + * ``bugfix/234-fix-security-bar`` a bugfixes for issue 234 Use this :command:`git` command:: $ git checkout -b feature/NAME_OF_YOUR_FEATURE -4. Work on your branch. Commit your work. Don't forget to write test cases - for your new feature. +#. Work on your branch. Commit your work. + +#. Write test cases and run the test suite, see :ref:`testsuite` for details. + +#. Create a `pull request`_. Describe in the pull request what you did + and why. If you have open questions, ask. + +#. Wait for feedback. If you receive any comments, address these. -5. Run the test suite. You can decide to run the complete test suite or - only part of it: +#. After your pull request got accepted, delete your branch. - * To run all tests, use:: +#. Use the ``clean`` command to remove build and test files and folders:: + + $ python setup.py clean + + +.. _testsuite: + +Running the Test Suite +---------------------- + +We use `pytest`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically. + +You can decide to run the complete test suite or only part of it: + +* To run all tests, use:: $ tox - If you have not all Python interpreters installed on your system - it will probably give you some errors. To avoid such errors, use:: + If you have not all Python interpreters installed on your system + it will probably give you some errors (``InterpreterNotFound``). + To avoid such errors, use:: $ tox --skip-missing-interpreters - * To run a test for a specific Python version, use the - :command:`tox` command, for example, for Python 3.6:: + It is possible to use only specific Python versions. Use the ``-e`` + option and one or more abbreviations (``py27`` for Python 2.7, ``py34`` for + Python 3.4 etc.):: - $ tox -e py36 + $ tox -e py34 + $ tox -e py27,py34 -6. Create a `pull request`_. Describe in the pull request what you did - and why. If you have open questions, ask. + To get a complete list, run:: -7. Wait for feedback. If you receive any comments, address these. + $ tox -l -8. After your pull request got accepted, delete your branch. +* To run only a specific test, pytest requires the syntax + ``TEST_FILE::TEST_FUNCTION``. -9. Use the ``clean`` command to remove build and test files and folders:: + For example, the following line tests only the function + :func:`test_immutable_major` in the file :file:`test_semver.py` for all + Python versions:: - $ python setup.py clean + $ tox test_semver.py::test_immutable_major + + By default, pytest prints a dot for each test function only. To + reveal the executed test function, use the following syntax:: + $ tox -- -v + You can combine the specific test function with the ``-e`` option, for + example, to limit the tests for Python 2.7 and 3.6 only:: + + $ tox -e py27,py36 test_semver.py::test_immutable_major + +Our code is checked against `flake8`_ for style guide issues. It is recommended +to run your tests in combination with :command:`flake8`, for example:: + + $ tox -e py27,py36,flake8 + + +.. _doc: + +Documenting semver +------------------ + +Documenting the features of semver is very important. It gives our developers +an overview what is possible with semver, how it "feels", and how it is +used efficiently. + +.. note:: + + To build the documentation locally use the following command:: + + $ tox -e docs + + The built documentation is available in :file:`dist/docs`. + + +A new feature is *not* complete if it isn't proberly documented. A good +documentation includes: + + * **A docstring** + + Each docstring contains a summary line, a linebreak, the description + of its arguments in `Sphinx style`_, and an optional doctest. + The docstring is extracted and reused in the :ref:`api` section. + An appropriate docstring should look like this:: + + def compare(ver1, ver2): + """Compare two versions + + :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 + """ + + * **The documentation** + + A docstring is good, but in most cases it's too dense. Describe how + to use your new feature in our documentation. Here you can give your + readers more examples, describe it in a broader context or show + edge cases. + + +.. _flake8: https://flake8.readthedocs.io +.. _issues: https://github.com/k-bx/python-semver/issues .. _pull request: https://github.com/k-bx/python-semver/pulls .. _pytest: http://pytest.org/ -.. _tox: https://tox.readthedocs.org/ .. _Semantic Versioning: https://semver.org +.. _Sphinx style: https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html +.. _tox: https://tox.readthedocs.org/ diff --git a/setup.cfg b/setup.cfg index ba377b10..7967d292 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,6 @@ [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ addopts = - -q --ignore=.eggs/ --no-cov-on-fail --cov=semver From 8a1ae0ddb217b05963b4909860d8f54315d6d0fc Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 8 Oct 2019 20:44:57 +0200 Subject: [PATCH 122/312] Fix #159: Improve Changelog (#162) * Fix #159: Improve Changelog * Rename CHANGELOG -> CHANGELOG.rst * Adapt changelog: * Group the issues into the sections "Bug Fixes", "Features", "Removals", * Try to sort issue number from low to high to make it consistent * Prepare version 2.9.0 as WIP * Include CHANGELOG.rst into documentation (through docs/changelog.rst) * Introduce "extlinks" to docs/conf.py to allow to link to GitHub issues and pull requests. When the text uses :gh:`123` it is automatically converted into a link that points to the corresponding GitHub issue (same for pull requests with :pr:`234`). * Integrate all entries of 2.8.2 into 2.9.0 * Set 2.8.2 as unreleased * Sort all entries of 2.8.2 into 2.9.0 --- CHANGELOG => CHANGELOG.rst | 158 ++++++++++++++++++++++++++----------- docs/changelog.rst | 1 + docs/conf.py | 8 ++ docs/index.rst | 2 +- 4 files changed, 122 insertions(+), 47 deletions(-) rename CHANGELOG => CHANGELOG.rst (50%) create mode 100644 docs/changelog.rst diff --git a/CHANGELOG b/CHANGELOG.rst similarity index 50% rename from CHANGELOG rename to CHANGELOG.rst index f794629a..37dbf7f1 100644 --- a/CHANGELOG +++ b/CHANGELOG.rst @@ -2,41 +2,99 @@ Change Log ########## -Python SemVer library -##################### All notable changes to this code base will be documented in this file, in every released version. -Version 2.8.x -============= + +Version 2.9.0 (WIP) +=================== :Released: 20yy-mm-dd :Maintainer: Sébastien Celles -* Issue #102 (PR #...). Fix comparison between VersionInfo and tuple -* Issue #103 (PR #...). Disallow comparison between VersionInfo and string (and int) +Features +-------- + +* :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:`145` (:pr:`146`): Added posargs in :file:`tox.ini` +* :pr:`157`: Introduce :file:`conftest.py` to improve doctests + +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`): Droped 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 -* Issue #77 (PR #47). Convert multiple tests into pytest.mark.parametrize -* Issue #89 (PR #90). Add doctests. -* Issue #40 (PR #88). Add a static parse method to VersionInfo -* Issue #87 #94 (PR #93). Remove named tuple inheritance. Fix bad rendering in Pandas DataFrame -* Issue #96 (PR #97). Make VersionInfo immutable -* Issue #98 (PR #99). prerelease and build set to None by default +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 -* Issue #76 (PR #80). Remove Python 2.6 compatibility -* Issue #79 (PR #81 #84). Define and improve a release procedure file -* Issue #72 #73 (PR #75). Implements __str__ and __hash__ -* Issue #82 (PR #83). Rename test.py to test_semver.py so py.test can autodiscover test file + +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 ============= @@ -44,7 +102,12 @@ Version 2.7.9 :Released: 2017-09-23 :Maintainer: Kostiantyn Rybnikov -* Issue #65 (PR #66). Add finalize_version function + +Additions +--------- + +* :gh:`65` (:pr:`66`): Added :func:`semver.finalize_version` function. + Version 2.7.8 ============= @@ -52,7 +115,8 @@ Version 2.7.8 :Released: 2017-08-25 :Maintainer: Kostiantyn Rybnikov -* PR #62. Support custom default names for pre and build +* :gh:`62`: Support custom default names for pre and build + Version 2.7.7 ============= @@ -60,8 +124,9 @@ Version 2.7.7 :Released: 2017-05-25 :Maintainer: Kostiantyn Rybnikov -* Issue #54 (PR #55) Add comparision between VersionInfo objects -* PR #56. Add support for Python 3.6 +* :gh:`54` (:pr:`55`): Added comparision between VersionInfo objects +* :pr:`56`: Added support for Python 3.6 + Version 2.7.2 ============= @@ -72,14 +137,15 @@ Version 2.7.2 Additions --------- -* Fix issue #37 (Remove trailing zeros from prelease doesn't allow to - parse 0 pre-release version) -* Add ‘parse_version_info’ to parse a version string to a version info - tuple. +* 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 @@ -115,7 +181,7 @@ Additions Changes ------- -* Make separate builds for tests on Travis CI. +* Made separate builds for tests on Travis CI. Version 2.4.2 @@ -127,13 +193,13 @@ Version 2.4.2 Changes ------- -* Migrate README document to reStructuredText format. +* Migrated README document to reStructuredText format. -* Use Setuptools for distribution management. +* Used Setuptools for distribution management. -* Migrate test cases to Py.test. +* Migrated test cases to Py.test. -* Add configuration for Tox test runner. +* Added configuration for Tox test runner. Version 2.4.1 @@ -145,7 +211,7 @@ Version 2.4.1 Additions --------- -* [GitHub issue #23] Compare build component of a version. +* :gh:`23`: Compared build component of a version. Version 2.4.0 @@ -157,7 +223,7 @@ Version 2.4.0 Bug Fixes --------- -* [GitHub issue #21] Compare alphanumeric components correctly. +* :gh:`21`: Compared alphanumeric components correctly. Version 2.3.1 @@ -169,7 +235,7 @@ Version 2.3.1 Additions --------- -* Declare granted license name in distribution metadata. +* Declared granted license name in distribution metadata. Version 2.3.0 @@ -181,7 +247,7 @@ Version 2.3.0 Additions --------- -* Add functions to increment prerelease and build components in a +* Added functions to increment prerelease and build components in a version. @@ -194,7 +260,7 @@ Version 2.2.1 Bug Fixes --------- -* Correct comparison when any component includes zero. +* Corrected comparison when any component includes zero. Version 2.2.0 @@ -220,7 +286,7 @@ Version 2.1.2 Bug Fixes --------- -* Restore current README document to distribution manifest. +* Restored current README document to distribution manifest. Version 2.1.1 @@ -232,7 +298,7 @@ Version 2.1.1 Bug Fixes --------- -* Remove absent document from distribution manifest. +* Removed absent document from distribution manifest. Version 2.1.0 @@ -244,23 +310,23 @@ Version 2.1.0 Additions --------- -* Document installation instructions. +* Documented installation instructions. -* Document project home page. +* Documented project home page. -* Add function to format a version string from components. +* Added function to format a version string from components. -* Add functions to increment specific components in a version. +* Added functions to increment specific components in a version. Changes ------- -* Migrate README document to Markdown format. +* Migrated README document to Markdown format. Bug Fixes --------- -* Correct code examples in README document. +* Corrected code examples in README document. Version 2.0.2 @@ -272,9 +338,9 @@ Version 2.0.2 Additions --------- -* Add configuration for Travis continuous integration. +* Added configuration for Travis continuous integration. -* Explicitly declare supported Python versions. +* Explicitly declared supported Python versions. Version 2.0.1 @@ -286,7 +352,7 @@ Version 2.0.1 Bug Fixes --------- -* [GitHub issue #9] Correct comparison of equal version strings. +* :gh:`9`: Fixed comparison of equal version strings. Version 2.0.0 diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 00000000..565b0521 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py index c2b49f49..22f02b70 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', + 'sphinx.ext.extlinks', ] # Add any paths that contain templates here, relative to this directory. @@ -80,6 +81,13 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False +# Markup to shorten external links +# See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html +extlinks = {'gh': ('https://github.com/k-bx/python-semver/issues/%s', + '#'), + 'pr': ('https://github.com/k-bx/python-semver/pull/%s', + 'PR #'), + } # -- Options for HTML output ---------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index ecf160d9..8c9153dd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Semver |version| -- Semantic Versioning usage development api - + changelog Indices and Tables From 1f7778aa230f9a54d0989dc4b45bca357f04e16c Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 8 Oct 2019 21:26:19 +0200 Subject: [PATCH 123/312] Add support state for Python 2.7/3.4 for #161 (#163) --- README.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.rst b/README.rst index 6833b3b4..083bb39e 100644 --- a/README.rst +++ b/README.rst @@ -18,6 +18,23 @@ The module follows the ``MAJOR.MINOR.PATCH`` style: Additional labels for pre-release and build metadata are supported. + +.. warning:: + + Major version 3.0.0 of semver will remove support for Python 2.7 and 3.4. + + As anything comes to an end, this project will focus on Python 3.x. + New features and bugfixes will be integrated only into the 3.x.y branch + of semver. + + The last version of semver which supports Python 2.7 and 3.4 will be + 2.9.x. However, keep in mind, version 2.9.x is frozen: no new + features nor backports will be integrated. + + We recommend to upgrade your workflow to Python 3.x to gain support, + bugfixes, and new features. + + To import this library, use: .. code-block:: python From 5d0dfafaa8ba27f5450621f66ec98aac9848bc8e Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 13 Oct 2019 20:58:34 +0200 Subject: [PATCH 124/312] Fix #167: Introdue SEMVER_SPEC_VERSION (#168) Global constant, defines the implemented semantic version specification --- CHANGELOG.rst | 1 + docs/usage.rst | 11 +++++++++++ semver.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 37dbf7f1..d12ec79c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,7 @@ Features * :gh:`142` (:pr:`143`): Improved usage section * :gh:`145` (:pr:`146`): Added posargs in :file:`tox.ini` * :pr:`157`: Introduce :file:`conftest.py` to improve doctests +* :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` Bug Fixes --------- diff --git a/docs/usage.rst b/docs/usage.rst index c2a80a73..b6699956 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -11,6 +11,17 @@ Each type can be converted into the other, if the minimum requirements are met. +Knowing the Implemented semver.org Version +------------------------------------------ + +The semver.org is the authorative specification of how semantical versioning is +definied. To know which version of semver.org is implemented in the semver +libary, use the following constant:: + + >>> semver.SEMVER_SPEC_VERSION + '2.0.0' + + Creating a Version ------------------ diff --git a/semver.py b/semver.py index d4fbb1dd..4cb8d44d 100644 --- a/semver.py +++ b/semver.py @@ -35,6 +35,10 @@ _LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') +#: Contains the implemented semver.org version of the spec +SEMVER_SPEC_VERSION = "2.0.0" + + if not hasattr(__builtins__, 'cmp'): def cmp(a, b): return (a > b) - (a < b) From 4bc39a2df05d78419b903250a28e569a11e16d24 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 13 Oct 2019 21:00:47 +0200 Subject: [PATCH 125/312] Rework .gitignore (#166) * Taken most entries from https://github.com/github/gitignore/blob/master/Python.gitignore but adapt it this project. * Update CHANGELOG.rst --- .gitignore | 87 ++++++++++++++++++++++++++++++++++++++++++--------- CHANGELOG.rst | 1 + 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 03729020..f0636016 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,73 @@ -/.cache -/.coverage -/.emacs-project -/.idea -/.tox -/.venv* - -/*.egg-info -/.eggs -/build -/dist -/MANIFEST - -*.py[co] +# Files +*.patch +*.diff +*.kate-swp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +.pytest_cache/ + +# Distribution / packaging +.cache +.emacs-project +.installed.cfg +.idea/ +*.egg +*.egg-info/ +.eggs/ +.Python +.tmp/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Environment +env*/ +venv*/ +.env* +.venv* + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Spyder project settings +.spyderproject +.spyproject + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +coverage.xml +*,cover +.hypothesis/ +.pytest_cache/ + +# Sphinx documentation +doc/_build/ + +# PyBuilder +target/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d12ec79c..c5c78cea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,7 @@ Features * :gh:`142` (:pr:`143`): Improved usage section * :gh:`145` (:pr:`146`): Added posargs in :file:`tox.ini` * :pr:`157`: Introduce :file:`conftest.py` to improve doctests +* :pr:`166`: Reworked :file:`.gitignore` file * :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` Bug Fixes From b2ca4e58c2653011f24db6168b2b5d1f5fcc81af Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 13 Oct 2019 21:02:32 +0200 Subject: [PATCH 126/312] Improve code coverage (#165) * Add test for (private) _increment_string() function * Adapt .coveragerc: * Introduce option precision * Disable coverage for cmp * Update CHANGELOG.rst --- .coveragerc | 3 +++ CHANGELOG.rst | 1 + test_semver.py | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/.coveragerc b/.coveragerc index 7ed871ce..4903985c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,9 @@ source = semver branch = True [report] +show_missing = true +precision = 1 exclude_lines = pragma: no cover if __name__ == .__main__.: + if not hasattr\(__builtins__, .cmp.\): diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5c78cea..feb57a1e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,7 @@ Features * :gh:`142` (:pr:`143`): Improved usage section * :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` diff --git a/test_semver.py b/test_semver.py index fee77f84..fa45f0cc 100644 --- a/test_semver.py +++ b/test_semver.py @@ -23,6 +23,16 @@ ] +@pytest.mark.parametrize("string,expected", [ + ("rc", "rc"), + ("rc.1", "rc.2"), + ("2x", "3x"), +]) +def test_should_private_increment_string(string, expected): + from semver import _increment_string + assert _increment_string(string) == expected + + @pytest.fixture def version(): return VersionInfo(major=1, minor=2, patch=3, From 4887ecf80fab577f2461c236312aa0e891da86ec Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 13 Oct 2019 21:35:03 +0200 Subject: [PATCH 127/312] Fix #59: Implement command line interface (#164) * Extend setup.py with entry_point key and point to semver.main. The script is named "pysemver" * Introduce 3 new functions: * createparser: creates and returns an argparse.ArgumentParser instance * process: process the CLI arguments and call the requested actions * main: entry point for the application script * Add test cases * sort import lines of semver functions/class with isort tool * sort list of SEMVERFUNCS variable * Extend documentation * Add sphinx-argparse as a doc requirement * Include new cli.rst file which (self)documents the arguments of the semver script with the help of sphinx-argparse * Extend extensions variable in conf.py to be able to use the sphinx-argparse module * Update CHANGELOG.rst --- CHANGELOG.rst | 2 + docs/cli.rst | 45 +++++++++++++++++++ docs/conf.py | 1 + docs/index.rst | 1 + docs/requirements.txt | 1 + semver.py | 100 ++++++++++++++++++++++++++++++++++++++++- setup.py | 3 ++ test_semver.py | 101 ++++++++++++++++++++++++++++++++++-------- 8 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 docs/cli.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index feb57a1e..aa35c93a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ Version 2.9.0 (WIP) 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 @@ -28,6 +29,7 @@ Features * :pr:`166`: Reworked :file:`.gitignore` file * :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` + Bug Fixes --------- diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 00000000..9f21bfeb --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,45 @@ +CLI +=== + +The library provides also a command line interface. This allows to include +the functionality of semver into shell scripts. + +Using the pysemver Script +------------------------- + +The script name is :command:`pysemver` and provides the subcommands ``bump`` +and ``compare``. + +To bump a version, you pass the name of the part (major, minor, patch, prerelease, or +build) and the version string, for example:: + + $ pysemver bump major 1.2.3 + 2.0.0 + $ pysemver bump minor 1.2.3 + 1.3.0 + +If you pass a version string which is not a valid semantical version, you get +an error message:: + + $ pysemver bump build 1.5 + ERROR 1.5 is not valid SemVer string + +To compare two versions, use the ``compare`` subcommand. The result is + +* ``-1`` if first version is smaller than the second version, +* ``0`` if both are the same, +* ``1`` if the first version is greater than the second version. + +For example:: + + $ pysemver compare 1.2.3 2.4.0 + + +.. _interface: + +Interface +--------- + +.. argparse:: + :ref: semver.createparser + :prog: pysemver diff --git a/docs/conf.py b/docs/conf.py index 22f02b70..c4afdf8e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,7 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx.ext.extlinks', + 'sphinxarg.ext', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/index.rst b/docs/index.rst index 8c9153dd..140ebad0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Semver |version| -- Semantic Versioning readme install usage + cli development api changelog diff --git a/docs/requirements.txt b/docs/requirements.txt index 383acaaa..28467ce6 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ # requirements file for documentation sphinx sphinx_rtd_theme +sphinx-argparse diff --git a/semver.py b/semver.py index 4cb8d44d..bfe1f73c 100644 --- a/semver.py +++ b/semver.py @@ -1,11 +1,13 @@ """ Python helper for Semantic Versioning (http://semver.org/) """ +from __future__ import print_function +import argparse import collections -import re - from functools import wraps +import re +import sys __version__ = '2.8.2' @@ -608,6 +610,100 @@ def finalize_version(version): return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) +def createparser(): + """Create an :class:`argparse.ArgumentParser` instance + + :return: parser instance + :rtype: :class:`argparse.ArgumentParser` + """ + parser = argparse.ArgumentParser(prog=__package__, + description=__doc__) + s = parser.add_subparsers() + + # create compare subcommand + parser_compare = s.add_parser("compare", + help="Compare two versions" + ) + parser_compare.set_defaults(which="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(which="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" + ) + + return parser + + +def process(args): + """Process the input from the CLI + + :param args: The parsed arguments + :type args: :class:`argparse.Namespace` + :param parser: the parser instance + :type parser: :class:`argparse.ArgumentParser` + :return: result of the selected action + :rtype: str + """ + if args.which == "bump": + maptable = {'major': 'bump_major', + 'minor': 'bump_minor', + 'patch': 'bump_patch', + 'prerelease': 'bump_prerelease', + 'build': 'bump_build', + } + ver = parse_version_info(args.version) + # get the respective method and call it + func = getattr(ver, maptable[args.bump]) + return str(func()) + + elif args.which == "compare": + return str(compare(args.version1, args.version2)) + + +def main(cliargs=None): + """Entry point for the application script + + :param list cliargs: Arguments to parse or None (=use :class:`sys.argv`) + :return: error code + :rtype: int + """ + try: + parser = createparser() + args = parser.parse_args(args=cliargs) + # args.parser = parser + result = process(args) + 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/setup.py b/setup.py index 19442380..2003c19f 100755 --- a/setup.py +++ b/setup.py @@ -107,4 +107,7 @@ def read_file(filename): 'clean': Clean, 'test': Tox, }, + entry_points={ + 'console_scripts': ['pysemver = semver:main'], + } ) diff --git a/test_semver.py b/test_semver.py index fa45f0cc..0fb78981 100644 --- a/test_semver.py +++ b/test_semver.py @@ -1,25 +1,30 @@ +from argparse import Namespace import pytest # noqa -from semver import compare -from semver import match -from semver import parse -from semver import format_version -from semver import bump_major -from semver import bump_minor -from semver import bump_patch -from semver import bump_prerelease -from semver import bump_build -from semver import finalize_version -from semver import min_ver -from semver import max_ver -from semver import VersionInfo -from semver import parse_version_info - +from semver import (VersionInfo, + bump_build, + bump_major, + bump_minor, + bump_patch, + bump_prerelease, + compare, + createparser, + finalize_version, + format_version, + main, + match, + max_ver, + min_ver, + parse, + parse_version_info, + process, + ) SEMVERFUNCS = [ - compare, match, parse, format_version, - bump_major, bump_minor, bump_patch, bump_prerelease, bump_build, - max_ver, min_ver, finalize_version + compare, createparser, + bump_build, bump_major, bump_minor, bump_patch, bump_prerelease, + finalize_version, format_version, + match, max_ver, min_ver, parse, process, ] @@ -590,3 +595,63 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): assert isinstance(v.prerelease, str) assert isinstance(v.build, str) assert VersionInfo(1, 2, 3, 4, 5) == VersionInfo(1, 2, 3, '4', '5') + + +@pytest.mark.parametrize("cli,expected", [ + (["bump", "major", "1.2.3"], + Namespace(which='bump', bump='major', version='1.2.3')), + (["bump", "minor", "1.2.3"], + Namespace(which='bump', bump='minor', version='1.2.3')), + (["bump", "patch", "1.2.3"], + Namespace(which='bump', bump='patch', version='1.2.3')), + (["bump", "prerelease", "1.2.3"], + Namespace(which='bump', bump='prerelease', version='1.2.3')), + (["bump", "build", "1.2.3"], + Namespace(which='bump', bump='build', version='1.2.3')), + # --- + (["compare", "1.2.3", "2.1.3"], + Namespace(which='compare', version1='1.2.3', version2='2.1.3')), +]) +def test_should_parse_cli_arguments(cli, expected): + parser = createparser() + assert parser + result = parser.parse_args(cli) + assert result == expected + + +@pytest.mark.parametrize("args,expected", [ + # bump subcommand + (Namespace(which='bump', bump='major', version='1.2.3'), + "2.0.0"), + (Namespace(which='bump', bump='minor', version='1.2.3'), + "1.3.0"), + (Namespace(which='bump', bump='patch', version='1.2.3'), + "1.2.4"), + (Namespace(which='bump', bump='prerelease', version='1.2.3-rc1'), + "1.2.3-rc2"), + (Namespace(which='bump', bump='build', version='1.2.3+build.13'), + "1.2.3+build.14"), + # compare subcommand + (Namespace(which='compare', version1='1.2.3', version2='2.1.3'), + "-1"), + (Namespace(which='compare', version1='1.2.3', version2='1.2.3'), + "0"), + (Namespace(which='compare', version1='2.4.0', version2='2.1.3'), + "1"), +]) +def test_should_process_parsed_cli_arguments(args, expected): + assert process(args) == expected + + +def test_should_process_print(capsys): + rc = main(["bump", "major", "1.2.3"]) + assert rc == 0 + captured = capsys.readouterr() + assert captured.out.rstrip() == "2.0.0" + + +def test_should_process_raise_error(capsys): + rc = main(["bump", "major", "1.2"]) + assert rc != 0 + captured = capsys.readouterr() + assert captured.err.startswith("ERROR") From 2d0f8055270c8a316e4325b5caeac5a05553d623 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 30 Oct 2019 15:19:10 +0100 Subject: [PATCH 128/312] Fix 144: Add replace function (#156) This commit implements: * semver.replace(version: str, **parts: dict) -> str * semver.VersionInfo.replace(**parts: dict) -> VersionInfo * Add test cases * Extend usage documentation to describe new functions * Update CHANGELOG.rst Both do almost the same: it replaces parts of a version string or a VersionInfo object and returns either a new string or a new VersionInfo object (with the changed parts); see the signature above. Co-authored-by: Alexander Grund --- CHANGELOG.rst | 2 ++ docs/usage.rst | 31 +++++++++++++++++++++++++++++++ semver.py | 40 ++++++++++++++++++++++++++++++++++++++++ test_semver.py | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa35c93a..f667f68a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,8 @@ Features * :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 diff --git a/docs/usage.rst b/docs/usage.rst index b6699956..1ae7d8da 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -118,6 +118,37 @@ In case you need the different parts of a version stepwise, iterate over the :cl [3, 4, 5, 'pre.2', 'build.4'] +Replacing Parts of a Version +---------------------------- + +If you want to replace different parts of a version, but leave other parts +unmodified, use one of the functions :func:`semver.replace` or +:func:`semver.VersionInfo.replace`: + +* From a version string:: + + >>> semver.replace("1.4.5-pre.1+build.6", major=2) + '2.4.5-pre.1+build.6' + +* From a :class:`semver.VersionInfo` instance:: + + >>> version = semver.VersionInfo.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') + +If you pass invalid keys you get an exception:: + + >>> semver.replace("1.2.3", invalidkey=2) + 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.replace(invalidkey=2) + Traceback (most recent call last) + ... + TypeError: replace() got 1 unexpected keyword argument(s): invalidkey + + .. _sec.convert.versions: Converting Different Version Types diff --git a/semver.py b/semver.py index bfe1f73c..2bcb4de7 100644 --- a/semver.py +++ b/semver.py @@ -292,6 +292,28 @@ def parse(version): """ return parse_version_info(version) + def replace(self, **parts): + """Replace one or more parts of a version and return a new + :class:`semver.VersionInfo` object, but leave self untouched + + :param dict parts: the parts to be updated. Valid keys are: + ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` + :return: the new :class:`semver.VersionInfo` object with the changed + parts + :raises: TypeError, if ``parts`` contains invalid keys + """ + version = self._asdict() + version.update(parts) + try: + return VersionInfo(**version) + except TypeError: + unknownkeys = set(parts) - set(self._asdict()) + error = ("replace() got %d unexpected keyword " + "argument(s): %s" % (len(unknownkeys), + ", ".join(unknownkeys)) + ) + raise TypeError(error) + def _to_dict(obj): if isinstance(obj, VersionInfo): @@ -704,6 +726,24 @@ def main(cliargs=None): return 2 +def replace(version, **parts): + """Replace one or more parts of a version and return the new string + + :param str version: the version string to replace + :param dict 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 + :rtype: str + + >>> import semver + >>> semver.replace("1.2.3", major=2, patch=10) + '2.2.10' + """ + version = parse_version_info(version) + return str(version.replace(**parts)) + + if __name__ == "__main__": import doctest doctest.testmod() diff --git a/test_semver.py b/test_semver.py index 0fb78981..6b1bf579 100644 --- a/test_semver.py +++ b/test_semver.py @@ -18,14 +18,15 @@ parse, parse_version_info, process, + replace, ) SEMVERFUNCS = [ compare, createparser, bump_build, bump_major, bump_minor, bump_patch, bump_prerelease, finalize_version, format_version, - match, max_ver, min_ver, parse, process, -] + match, max_ver, min_ver, parse, process, replace, + ] @pytest.mark.parametrize("string,expected", [ @@ -655,3 +656,45 @@ def test_should_process_raise_error(capsys): assert rc != 0 captured = capsys.readouterr() assert captured.err.startswith("ERROR") + + +@pytest.mark.parametrize("version,parts,expected", [ + ("3.4.5", dict(major=2), '2.4.5'), + ("3.4.5", dict(major="2"), '2.4.5'), + ("3.4.5", dict(major=2, minor=5), '2.5.5'), + ("3.4.5", dict(minor=2), '3.2.5'), + ("3.4.5", dict(major=2, minor=5, patch=10), '2.5.10'), + ("3.4.5", dict(major=2, minor=5, patch=10, prerelease="rc1"), + '2.5.10-rc1'), + ("3.4.5", dict(major=2, minor=5, patch=10, prerelease="rc1", build="b1"), + '2.5.10-rc1+b1'), + ("3.4.5-alpha.1.2", dict(major=2), '2.4.5-alpha.1.2'), + ("3.4.5-alpha.1.2", dict(build="x1"), '3.4.5-alpha.1.2+x1'), + ("3.4.5+build1", dict(major=2), '2.4.5+build1'), +]) +def test_replace_method_replaces_requested_parts(version, parts, expected): + assert replace(version, **parts) == expected + + +def test_replace_raises_TypeError_for_invalid_keyword_arg(): + with pytest.raises(TypeError, match=r"replace\(\).*unknown.*"): + assert replace("1.2.3", unknown="should_raise") + + +@pytest.mark.parametrize("version,parts,expected", [ + ("3.4.5", dict(major=2, minor=5), '2.5.5'), + ("3.4.5", dict(major=2, minor=5, patch=10), '2.5.10'), + ("3.4.5-alpha.1.2", dict(major=2), '2.4.5-alpha.1.2'), + ("3.4.5-alpha.1.2", dict(build="x1"), '3.4.5-alpha.1.2+x1'), + ("3.4.5+build1", dict(major=2), '2.4.5+build1'), +]) +def test_should_return_versioninfo_with_replaced_parts(version, + parts, + expected): + assert VersionInfo.parse(version).replace(**parts) == \ + VersionInfo.parse(expected) + + +def test_replace_raises_ValueError_for_non_numeric_values(): + with pytest.raises(ValueError): + VersionInfo.parse("1.2.3").replace(major="x") From 014d153386b61566d9393ea1daa113e56a4ba03f Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 30 Oct 2019 15:36:41 +0100 Subject: [PATCH 129/312] Release 2.9.0 (#170) * Release 2.9.0 * Fix release procedure --- release-procedure.md | 15 +++++++++++---- semver.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/release-procedure.md b/release-procedure.md index 682f959f..fca90b93 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -1,23 +1,31 @@ -* Verify that latest build was passing https://travis-ci.org/k-bx/python-semver +################# +Release procedure +################# + +* Verify that issues about new release are closed https://github.com/k-bx/python-semver/issues and that no pull requests that should be included in this release haven't been left out https://github.com/k-bx/python-semver/pulls + +* Verify that continuous integration for latest build was passing https://travis-ci.org/k-bx/python-semver * Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated and follow https://semver.org/ -* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG) have been updated +* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG.rst) have been updated * If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: * [setup.py](https://github.com/k-bx/python-semver/blob/master/setup.py) * [tox.ini](https://github.com/k-bx/python-semver/blob/master/tox.ini) * [.travis.yml](https://github.com/k-bx/python-semver/blob/master/.travis.yml) -* Verify that doc reflecting new changes have been updated +* Verify that doc reflecting new changes have been updated and are available at https://python-semver.readthedocs.io/en/latest/ If necessary, trigger doc build at https://readthedocs.org/projects/python-semver/ * Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/k-bx/python-semver/blob/master/CONTRIBUTORS) * Tag commit and push to github using command line interface + ```bash git tag -a x.x.x -m 'Version x.x.x' git push python-semver master --tags ``` + or using GitHub web interface available at https://github.com/k-bx/python-semver/releases * Upload to PyPI @@ -29,4 +37,3 @@ twine upload dist/* ``` * Go to https://pypi.org/project/semver/ to verify that new version is online and page is rendered correctly - diff --git a/semver.py b/semver.py index 2bcb4de7..6bc9fcab 100644 --- a/semver.py +++ b/semver.py @@ -10,7 +10,7 @@ import sys -__version__ = '2.8.2' +__version__ = '2.9.0' __author__ = 'Kostiantyn Rybnikov' __author_email__ = 'k-bx@k-bx.com' __maintainer__ = 'Sebastien Celles' From 1bafb9f0aea0117a196eb7d112c3f0a0da783abf Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 30 Oct 2019 16:15:53 +0100 Subject: [PATCH 130/312] Fix long description (#172) * Fix long description * Fix release procedure --- README.rst | 12 ++++++------ release-procedure.md | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 083bb39e..9d8c155d 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ 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 :func:`semver.parse` function: +different parts, use the `semver.parse` function: .. code-block:: python @@ -59,10 +59,10 @@ different parts, use the :func:`semver.parse` function: 'build.5' To raise parts of a version, there are a couple of functions available for -you. The :func:`semver.parse_version_info` function converts a version string -into a :class:`semver.VersionInfo` class. The function -:func:`semver.VersionInfo.bump_major` leaves the original object untouched, but -returns a new :class:`semver.VersionInfo` instance with the raised major part: +you. The `semver.parse_version_info` function converts a version string +into a `semver.VersionInfo` class. The function +`semver.VersionInfo.bump_major` leaves the original object untouched, but +returns a new `semver.VersionInfo` instance with the raised major part: .. code-block:: python @@ -77,7 +77,7 @@ It is allowed to concatenate different "bump functions": >>> ver.bump_major().bump_minor() VersionInfo(major=4, minor=0, patch=1, prerelease=None, build=None) -To compare two versions, semver provides the :func:`semver.compare` function. +To compare two versions, semver provides the `semver.compare` function. The return value indicates the relationship between the first and second version: diff --git a/release-procedure.md b/release-procedure.md index fca90b93..17708175 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -19,14 +19,7 @@ Release procedure * Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/k-bx/python-semver/blob/master/CONTRIBUTORS) -* Tag commit and push to github using command line interface - -```bash -git tag -a x.x.x -m 'Version x.x.x' -git push python-semver master --tags -``` - -or using GitHub web interface available at https://github.com/k-bx/python-semver/releases +* Ensure that long description (ie [README.rst](https://github.com/k-bx/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` * Upload to PyPI @@ -37,3 +30,12 @@ twine upload dist/* ``` * Go to https://pypi.org/project/semver/ to verify that new version is online and page is rendered correctly + +* Tag commit and push to github using command line interface + +```bash +git tag -a x.x.x -m 'Version x.x.x' +git push python-semver master --tags +``` + +or using GitHub web interface available at https://github.com/k-bx/python-semver/releases From 7c2c59c3ae44fdffd2cb93fe98587fc70dc6d422 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 30 Oct 2019 16:36:13 +0100 Subject: [PATCH 131/312] Update CHANGELOG.rst (#174) --- CHANGELOG.rst | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f667f68a..fad5d48b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,10 +6,23 @@ Change Log All notable changes to this code base will be documented in this file, in every released version. - -Version 2.9.0 (WIP) +Version 2.9.1 (WIP) =================== -:Released: 20yy-mm-dd +:Released: 20xy-xy-xy +:Maintainer: ... + +Features +-------- + +Bug Fixes +--------- + +Removals +-------- + +Version 2.9.0 +============= +:Released: 2019-10-30 :Maintainer: Sébastien Celles Features From 97230efc7d42314e14a5a6917d8c17dfb172bb28 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Wed, 30 Oct 2019 16:38:59 +0100 Subject: [PATCH 132/312] Update release-procedure.md --- release-procedure.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-procedure.md b/release-procedure.md index 17708175..7f53ea40 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -2,13 +2,13 @@ Release procedure ################# -* Verify that issues about new release are closed https://github.com/k-bx/python-semver/issues and that no pull requests that should be included in this release haven't been left out https://github.com/k-bx/python-semver/pulls +* Verify that issues about new release are closed https://github.com/k-bx/python-semver/issues and verify that no pull requests that should be included in this release haven't been left out https://github.com/k-bx/python-semver/pulls * Verify that continuous integration for latest build was passing https://travis-ci.org/k-bx/python-semver * Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated and follow https://semver.org/ -* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG.rst) have been updated +* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG.rst) have been updated. No WIP should be present in CHANGELOG during release! * If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: * [setup.py](https://github.com/k-bx/python-semver/blob/master/setup.py) From e2b07af2eb25005eb57a66cc045263c26388fb57 Mon Sep 17 00:00:00 2001 From: scls19fr Date: Tue, 5 Nov 2019 11:48:56 +0100 Subject: [PATCH 133/312] Move to GH org (#178) * Update setup.py * Update conf.py * Update README.rst * Update development.rst * Update release-procedure.md * Update CHANGELOG.rst --- CHANGELOG.rst | 2 ++ README.rst | 6 +++--- docs/conf.py | 4 ++-- docs/development.rst | 8 ++++---- release-procedure.md | 20 ++++++++++---------- setup.py | 4 ++-- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fad5d48b..2baf9cd4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ Version 2.9.1 (WIP) 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/) + Bug Fixes --------- diff --git a/README.rst b/README.rst index 9d8c155d..d03e1455 100644 --- a/README.rst +++ b/README.rst @@ -97,9 +97,9 @@ There are other functions to discover. Read on! .. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg :alt: Latest version on PyPI :target: https://pypi.org/project/semver -.. |build-status| image:: https://travis-ci.org/k-bx/python-semver.svg?branch=master +.. |build-status| image:: https://travis-ci.com/python-semver/python-semver.svg?branch=master :alt: Build status - :target: https://travis-ci.org/k-bx/python-semver + :target: https://travis-ci.com/python-semver/python-semver .. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg :target: https://pypi.org/project/semver :alt: Python versions @@ -108,7 +108,7 @@ There are other functions to discover. Read on! :target: https://pypi.org/project/semver .. |license| image:: https://img.shields.io/pypi/l/semver.svg :alt: Software license - :target: https://github.com/k-bx/python-semver/blob/master/LICENSE.txt + :target: https://github.com/python-semver/python-semver/blob/master/LICENSE.txt .. |docs| image:: https://readthedocs.org/projects/python-semver/badge/?version=latest :target: http://python-semver.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status diff --git a/docs/conf.py b/docs/conf.py index c4afdf8e..525c6201 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -84,9 +84,9 @@ # Markup to shorten external links # See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html -extlinks = {'gh': ('https://github.com/k-bx/python-semver/issues/%s', +extlinks = {'gh': ('https://github.com/python-semver/python-semver/issues/%s', '#'), - 'pr': ('https://github.com/k-bx/python-semver/pull/%s', + 'pr': ('https://github.com/python-semver/python-semver/pull/%s', 'PR #'), } diff --git a/docs/development.rst b/docs/development.rst index be66c14f..7ad63d74 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -3,7 +3,7 @@ Contributing to semver The semver source code is managed using Git and is hosted on GitHub:: - git clone git://github.com/k-bx/python-semver + git clone git://github.com/python-semver/python-semver Reporting Bugs and Feedback @@ -41,7 +41,7 @@ Modifying the Code We recommend the following workflow: #. Fork our project on GitHub using this link: - https://github.com/k-bx/python-semver/fork + https://github.com/python-semver/python-semver/fork #. Clone your forked Git repository (replace ``GITHUB_USER`` with your account name on GitHub):: @@ -185,8 +185,8 @@ documentation includes: .. _flake8: https://flake8.readthedocs.io -.. _issues: https://github.com/k-bx/python-semver/issues -.. _pull request: https://github.com/k-bx/python-semver/pulls +.. _issues: https://github.com/python-semver/python-semver/issues +.. _pull request: https://github.com/python-semver/python-semver/pulls .. _pytest: http://pytest.org/ .. _Semantic Versioning: https://semver.org .. _Sphinx style: https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html diff --git a/release-procedure.md b/release-procedure.md index 7f53ea40..52a43fd5 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -2,24 +2,24 @@ Release procedure ################# -* Verify that issues about new release are closed https://github.com/k-bx/python-semver/issues and verify that no pull requests that should be included in this release haven't been left out https://github.com/k-bx/python-semver/pulls +* Verify that issues about new release are closed https://github.com/python-semver/python-semver/issues and verify that no pull requests that should be included in this release haven't been left out https://github.com/python-semver/python-semver/pulls -* Verify that continuous integration for latest build was passing https://travis-ci.org/k-bx/python-semver +* Verify that continuous integration for latest build was passing https://travis-ci.com/python-semver/python-semver -* Verify that `__version__` in [semver.py](https://github.com/k-bx/python-semver/blob/master/semver.py) have been updated and follow https://semver.org/ +* Verify that `__version__` in [semver.py](https://github.com/python-semver/python-semver/blob/master/semver.py) have been updated and follow https://semver.org/ -* Verify that [CHANGELOG](https://github.com/k-bx/python-semver/blob/master/CHANGELOG.rst) have been updated. No WIP should be present in CHANGELOG during release! +* Verify that [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) have been updated. No WIP should be present in CHANGELOG during release! * If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: - * [setup.py](https://github.com/k-bx/python-semver/blob/master/setup.py) - * [tox.ini](https://github.com/k-bx/python-semver/blob/master/tox.ini) - * [.travis.yml](https://github.com/k-bx/python-semver/blob/master/.travis.yml) + * [setup.py](https://github.com/python-semver/python-semver/blob/master/setup.py) + * [tox.ini](https://github.com/python-semver/python-semver/blob/master/tox.ini) + * [.travis.yml](https://github.com/python-semver/python-semver/blob/master/.travis.yml) * Verify that doc reflecting new changes have been updated and are available at https://python-semver.readthedocs.io/en/latest/ If necessary, trigger doc build at https://readthedocs.org/projects/python-semver/ -* Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/k-bx/python-semver/blob/master/CONTRIBUTORS) +* Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/python-semver/python-semver/blob/master/CONTRIBUTORS) -* Ensure that long description (ie [README.rst](https://github.com/k-bx/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` +* Ensure that long description (ie [README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` * Upload to PyPI @@ -38,4 +38,4 @@ git tag -a x.x.x -m 'Version x.x.x' git push python-semver master --tags ``` -or using GitHub web interface available at https://github.com/k-bx/python-semver/releases +or using GitHub web interface available at https://github.com/python-semver/python-semver/releases diff --git a/setup.py b/setup.py index 2003c19f..da0fab65 100755 --- a/setup.py +++ b/setup.py @@ -82,8 +82,8 @@ def read_file(filename): long_description=read_file('README.rst'), author=package.__author__, author_email=package.__author_email__, - url='https://github.com/k-bx/python-semver', - download_url='https://github.com/k-bx/python-semver/downloads', + url='https://github.com/python-semver/python-semver', + download_url='https://github.com/python-semver/python-semver/downloads', py_modules=[package.__name__], include_package_data=True, license='BSD', From 2b5ffb7a48d939569c196b92aa6e8dc7a710469a Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 5 Nov 2019 13:48:42 +0100 Subject: [PATCH 134/312] Add note for GH repo to new python-semver org (#179) * Mentioned in #177 and #178 * Update CHANGELOG.rst --- CHANGELOG.rst | 1 + README.rst | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2baf9cd4..bda9b17c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ 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 Bug Fixes --------- diff --git a/README.rst b/README.rst index d03e1455..62e6b92a 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,18 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. teaser-end +.. note:: + + With version 2.9.0 we've moved the GitHub project. The project is now + located under the organization ``python-semver``. + The complete URL is:: + + https://github.com/python-semver/python-semver + + If you still have an old repository, correct your upstream URL to the new URL:: + + $ git remote set-url upstream git@github.com:python-semver/python-semver.git + The module follows the ``MAJOR.MINOR.PATCH`` style: From 18ea3f8a791984429caf9063fd5103291d3e8e9e Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 13 Nov 2019 09:42:09 +0100 Subject: [PATCH 135/312] Fix #187: Create a squared logo (#189) * Mentioned in issue #177 * Add logo to documentation * Update CHANGELOG Co-Authored-By: scls19fr --- CHANGELOG.rst | 2 + docs/conf.py | 1 + docs/logo.svg | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 docs/logo.svg diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bda9b17c..3eec5d53 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,8 @@ 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 + Bug Fixes --------- diff --git a/docs/conf.py b/docs/conf.py index 525c6201..8f972ae1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -124,6 +124,7 @@ ] } +html_logo = "logo.svg" # -- Options for HTMLHelp output ------------------------------------------ diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 00000000..b2853465 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,229 @@ + + From 985a5a749ba251950e4e7d8400e3866924ca92aa Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 13 Nov 2019 11:10:49 +0100 Subject: [PATCH 136/312] Correct logo width, add white background (#190) * Add custom CSS file to change size of the logo * Add white background --- docs/_static/css/default.css | 6 ++++++ docs/conf.py | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 docs/_static/css/default.css diff --git a/docs/_static/css/default.css b/docs/_static/css/default.css new file mode 100644 index 00000000..ed7cf80a --- /dev/null +++ b/docs/_static/css/default.css @@ -0,0 +1,6 @@ +/* Customize logo width */ + +.wy-side-nav-search > a img.logo { + width: 6em; + background: white; +} diff --git a/docs/conf.py b/docs/conf.py index 8f972ae1..12ae5f35 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -109,6 +109,8 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_css_files = ['css/default.css'] + # Custom sidebar templates, must be a dictionary that maps document names # to template names. # From 61f4521fbd8e335c2f39b847f3248219c490b8f2 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Fri, 15 Nov 2019 11:30:55 +0100 Subject: [PATCH 137/312] Fix empty call with "pysemver" and "pysemver bump" (#193) * Raise SystemExit when "pysemver" is called without arguments. * When "pysemver bump" is called without arguments, print the help text for the bump subcommand and exit. * Add test cases for both calls * Update CHANGELOG Without this patch, "pysemver bump" raises an AttributeError: ``` Traceback (most recent call last): File ".tox/py36/bin/pysemver", line 8, in sys.exit(main()) File ".tox/py36/lib/python3.6/site-packages/semver.py", line 726, in main result = process(args) File ".tox/py36/lib/python3.6/site-packages/semver.py", line 693, in process if args.which == "bump": AttributeError: 'Namespace' object has no attribute 'which' ``` --- CHANGELOG.rst | 3 +++ semver.py | 14 +++++++++++--- test_semver.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3eec5d53..0cd4a8f5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,9 @@ Features Bug Fixes --------- +* :gh:`192` (:pr:`193`): Fixed "pysemver" and "pysemver bump" when called without arguments + + Removals -------- diff --git a/semver.py b/semver.py index 6bc9fcab..da477370 100644 --- a/semver.py +++ b/semver.py @@ -676,7 +676,6 @@ def createparser(): p.add_argument("version", help="Version to raise" ) - return parser @@ -690,13 +689,21 @@ def process(args): :return: result of the selected action :rtype: str """ - if args.which == "bump": + if not hasattr(args, "which"): + args.parser.print_help() + raise SystemExit() + elif args.which == "bump": 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([args.which, "-h"]) + ver = parse_version_info(args.version) # get the respective method and call it func = getattr(ver, maptable[args.bump]) @@ -716,7 +723,8 @@ def main(cliargs=None): try: parser = createparser() args = parser.parse_args(args=cliargs) - # args.parser = parser + # Save parser instance: + args.parser = parser result = process(args) print(result) return 0 diff --git a/test_semver.py b/test_semver.py index 6b1bf579..023488da 100644 --- a/test_semver.py +++ b/test_semver.py @@ -658,6 +658,16 @@ def test_should_process_raise_error(capsys): assert captured.err.startswith("ERROR") +def test_should_raise_systemexit_when_called_with_empty_arguments(): + with pytest.raises(SystemExit): + main([]) + + +def test_should_raise_systemexit_when_bump_iscalled_with_empty_arguments(): + with pytest.raises(SystemExit): + main(["bump"]) + + @pytest.mark.parametrize("version,parts,expected", [ ("3.4.5", dict(major=2), '2.4.5'), ("3.4.5", dict(major="2"), '2.4.5'), From dbc2e40f72f945572d2b36812bc08f0bc09a90c1 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 17 Nov 2019 11:47:55 +0100 Subject: [PATCH 138/312] Fix #191: Add manpage for pysemver (#195) * Rename `cli.rst` -> `pysemver.rst` to be consistent with the name of our little script * Add missing --version in pysemver script * Restructure pysemver.rst to reflect manpage style * Add additional target "man" in tox.ini; it's now possible to run "tox -e man". Manpage can be found at `docs/_build/man`. * Ignore `docs/_build/` * Update `CHANGELOG.rst` --- .gitignore | 1 + CHANGELOG.rst | 2 +- docs/Makefile | 2 +- docs/cli.rst | 45 ---------------- docs/conf.py | 10 ++-- docs/index.rst | 2 +- docs/pysemver.rst | 130 ++++++++++++++++++++++++++++++++++++++++++++++ semver.py | 7 ++- tox.ini | 7 +++ 9 files changed, 154 insertions(+), 52 deletions(-) delete mode 100644 docs/cli.rst create mode 100644 docs/pysemver.rst diff --git a/.gitignore b/.gitignore index f0636016..994eb868 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ coverage.xml # Sphinx documentation doc/_build/ +docs/_build/ # PyBuilder target/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0cd4a8f5..e55f1a8e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,7 +17,7 @@ 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 Bug Fixes --------- diff --git a/docs/Makefile b/docs/Makefile index a7e3cbaf..ea7c53f0 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = python -msphinx +SPHINXBUILD = sphinx-build SPHINXPROJ = semver SOURCEDIR = . BUILDDIR = _build diff --git a/docs/cli.rst b/docs/cli.rst deleted file mode 100644 index 9f21bfeb..00000000 --- a/docs/cli.rst +++ /dev/null @@ -1,45 +0,0 @@ -CLI -=== - -The library provides also a command line interface. This allows to include -the functionality of semver into shell scripts. - -Using the pysemver Script -------------------------- - -The script name is :command:`pysemver` and provides the subcommands ``bump`` -and ``compare``. - -To bump a version, you pass the name of the part (major, minor, patch, prerelease, or -build) and the version string, for example:: - - $ pysemver bump major 1.2.3 - 2.0.0 - $ pysemver bump minor 1.2.3 - 1.3.0 - -If you pass a version string which is not a valid semantical version, you get -an error message:: - - $ pysemver bump build 1.5 - ERROR 1.5 is not valid SemVer string - -To compare two versions, use the ``compare`` subcommand. The result is - -* ``-1`` if first version is smaller than the second version, -* ``0`` if both are the same, -* ``1`` if the first version is greater than the second version. - -For example:: - - $ pysemver compare 1.2.3 2.4.0 - - -.. _interface: - -Interface ---------- - -.. argparse:: - :ref: semver.createparser - :prog: pysemver diff --git a/docs/conf.py b/docs/conf.py index 12ae5f35..107765f2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,6 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx.ext.extlinks', - 'sphinxarg.ext', ] # Add any paths that contain templates here, relative to this directory. @@ -167,9 +166,14 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). +manpage_doc = "pysemver" + man_pages = [ - (master_doc, 'semver', 'python-semver Documentation', - [author], 1) + (manpage_doc, + 'pysemver', + 'Helper script for Semantic Versioning', + ["Thomas Schraitle"], + 1) ] diff --git a/docs/index.rst b/docs/index.rst index 140ebad0..4cc5a966 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,7 @@ Semver |version| -- Semantic Versioning readme install usage - cli + pysemver development api changelog diff --git a/docs/pysemver.rst b/docs/pysemver.rst new file mode 100644 index 00000000..17cd6554 --- /dev/null +++ b/docs/pysemver.rst @@ -0,0 +1,130 @@ +:orphan: + +pysemver |version| +================== + +Synopsis +-------- + +.. _invocation: + +.. code:: bash + + pysemver compare + pysemver bump {major,minor,patch,prerelease,build} + + +Description +----------- + +The semver library provides a command line interface with the name +:command:`pysemver` to make the functionality accessible for shell +scripts. The script supports several subcommands. + + +Global Options +~~~~~~~~~~~~~~ + +.. program:: pysemver + +.. option:: -h, --help + + Display usage summary. + +.. option:: --version + + Show program's version number and exit. + + +Commands +-------- + +.. HINT: Sort the subcommands alphabetically + +pysemver bump +~~~~~~~~~~~~~ + +Bump a version. + +.. code:: bash + + pysemver bump + +.. option:: + + The part to bump. Valid strings can be ``major``, ``minor``, + ``patch``, ``prerelease``, or ``build``. The part has the + following effects: + + * ``major``: Raise the major part of the version and set + minor and patch to zero, remove prerelease and build. + * ``minor``: Raise the minor part of the version and set + patch to zero, remove prerelease and build. + * ``patch``: Raise the patch part of the version and + remove prerelease and build. + * ``prerelease`` Raise the prerelease of the version and + remove the build part. + * ``build``: Raise the build part. + +.. option:: + + The version to bump. + +To bump a version, you pass the name of the part (``major``, ``minor``, +``patch``, ``prerelease``, or ``build``) and the version string. +The bumped version is printed on standard out:: + + $ pysemver bump major 1.2.3 + 2.0.0 + $ pysemver bump minor 1.2.3 + 1.3.0 + +If you pass a version string which is not a valid semantical version, +you get an error message and a return code != 0:: + + $ pysemver bump build 1.5 + ERROR 1.5 is not valid SemVer string + + +pysemver compare +~~~~~~~~~~~~~~~~ + +Compare two versions. + +.. code:: bash + + pysemver compare + +.. option:: + + First version + +.. option:: + + Second version + +When you compare two versions, the result is printed on *standard out*, +to indicates which is the bigger version: + +* ``-1`` if first version is smaller than the second version, +* ``0`` if both versions are the same, +* ``1`` if the first version is greater than the second version. + +The *error code* returned by the script indicates if both versions +are valid (return code 0) or not (return code != 0):: + + $ pysemver compare 1.2.3 2.4.0 + -1 + $ pysemver compare 1.2.3 2.4.0 ; echo $? + 0 + $ pysemver compare 1.2.3 2.4.0 ; echo $? + ERROR 1.2.x is not valid SemVer string + 2 + + +See also +-------- + +:Documentation: https://python-semver.readthedocs.io/ +:Source code: https://github.com/python-semver/python-semver +:Bug tracker: https://github.com/python-semver/python-semver/issues diff --git a/semver.py b/semver.py index da477370..db82585d 100644 --- a/semver.py +++ b/semver.py @@ -640,8 +640,13 @@ def createparser(): """ parser = argparse.ArgumentParser(prog=__package__, description=__doc__) - s = parser.add_subparsers() + 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" diff --git a/tox.ini b/tox.ini index 33b60895..abca346b 100644 --- a/tox.ini +++ b/tox.ini @@ -22,3 +22,10 @@ basepython = python3 deps = -r{toxinidir}/docs/requirements.txt skip_install = true commands = sphinx-build {posargs:-E} -b html docs dist/docs + +[testenv:man] +whitelist_externals = make +basepython = python3 +deps = sphinx +skip_install = true +commands = make -C docs man From 91cbce94879cd04de6d4ea3ab11929cf13742a39 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 20 Nov 2019 21:32:02 +0100 Subject: [PATCH 139/312] Fix #196: Add distribution specific install hints (#197) * Improve the installation section and add installation instructions for Arch Linux, Debian, Fedora, FreeBSD, openSUSE, and Ubuntu. * Adapt CHANGELOG.rst --- CHANGELOG.rst | 2 ++ docs/install.rst | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e55f1a8e..53079d3c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,8 @@ Features * :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 + Bug Fixes --------- diff --git a/docs/install.rst b/docs/install.rst index 1abb7ed7..2e43feb1 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -12,3 +12,79 @@ For Python 3: .. code-block:: bash pip3 install semver + + +.. note:: + + Some Linux distributions can have outdated packages. + These outdated packages does not contain the latest bug fixes or new features. + If you need a newer package, you have these option: + + * Ask the maintainer to update the package. + * Update the package for your favorite distribution and submit it. + * Use a Python virtual environment and :command:`pip install`. + + +Arch Linux +---------- + +1. Enable the community repositories first: + + .. code-block:: ini + + [community] + Include = /etc/pacman.d/mirrorlist + +2. Install the package:: + + $ pacman -Sy python-semver + + +Debian +------ + +1. Update the package index:: + + $ sudo apt-get update + +2. Install the package:: + + $ sudo apt-get install python3-semver + + +Fedora +------ + +.. code-block:: bash + + $ dnf install python3-semver + + +FreeBSD +------- + +.. code-block:: bash + + +openSUSE +-------- + +1. Enable the the ``devel:languages:python`` repository on the Open Build Service (replace ```` with the preferred openSUSE release):: + + $ zypper addrepo https://download.opensuse.org/repositories/devel:/languages:/python/openSUSE_Leap_/devel:languages:python.repo + +2. Install the package:: + + $ zypper --repo devel_languages_python python3-semver + + +Ubuntu +------ + +1. Update the package index:: + + $ sudo apt-get update + +2. Install the package:: + + $ sudo apt-get install python3-semver From 05948ec47b224eae20b96348e55099bbaf7b1e8e Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Nov 2019 16:51:23 +0100 Subject: [PATCH 140/312] Doc: Correct missing parts in installation section (#199) * Add missing install command for FreeBSD * Introduce a "Pip" and "Linux Distributions" subsection * Change header level of every distribution (to make it appear under the "Linux Distributions" subsection) * Move note into "Linux Distributions" subsection --- docs/install.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 2e43feb1..437e3c3d 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,6 +1,10 @@ Installing semver ================= + +Pip +--- + For Python 2: .. code-block:: bash @@ -13,6 +17,8 @@ For Python 3: pip3 install semver +Linux Distributions +------------------- .. note:: @@ -26,7 +32,7 @@ For Python 3: Arch Linux ----------- +^^^^^^^^^^ 1. Enable the community repositories first: @@ -41,7 +47,7 @@ Arch Linux Debian ------- +^^^^^^ 1. Update the package index:: @@ -53,7 +59,7 @@ Debian Fedora ------- +^^^^^^ .. code-block:: bash @@ -61,13 +67,14 @@ Fedora FreeBSD -------- +^^^^^^^ .. code-block:: bash + $ pkg install py36-semver openSUSE --------- +^^^^^^^^ 1. Enable the the ``devel:languages:python`` repository on the Open Build Service (replace ```` with the preferred openSUSE release):: @@ -79,7 +86,7 @@ openSUSE Ubuntu ------- +^^^^^^ 1. Update the package index:: From 9e4ebcfa33ec1ea43b900c20e55ba0e399f70ca5 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 24 Nov 2019 18:24:01 +0100 Subject: [PATCH 141/312] Extend #186: Add GitHub Action for black formatter (#200) The GH Action does basically this: 1. Setup Python 3.7 2. Install dependencies (mainly black) 3. Run black and create a diff file 4. Upload the diff as artifact Currently, it does not any pip caching (I had some problems with that; it didn't work reliably). --- .github/workflows/black-formatting.yml | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/black-formatting.yml diff --git a/.github/workflows/black-formatting.yml b/.github/workflows/black-formatting.yml new file mode 100644 index 00000000..83ad2892 --- /dev/null +++ b/.github/workflows/black-formatting.yml @@ -0,0 +1,49 @@ +name: Black Formatting + +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Output env variables + run: | + echo "GITHUB_WORKFLOW=${GITHUB_WORKFLOW}" + echo "GITHUB_ACTION=$GITHUB_ACTION" + echo "GITHUB_ACTIONS=$GITHUB_ACTIONS" + echo "GITHUB_ACTOR=$GITHUB_ACTOR" + echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" + echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" + echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH" + echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" + echo "GITHUB_SHA=$GITHUB_SHA" + echo "GITHUB_REF=$GITHUB_REF" + echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" + echo "::debug::---Start content of file $GITHUB_EVENT_PATH" + cat $GITHUB_EVENT_PATH + echo "\n" + echo "::debug::---end" + + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip black + + - name: Run black + id: black + run: | + black . > project.diff + echo "::set-output name=rc::$?" + + - name: Upload diff artifact + uses: actions/upload-artifact@v1 + with: + name: black-project-diff + path: project.diff From 017e29633562acc8ae9a76c7c8389424263dafd0 Mon Sep 17 00:00:00 2001 From: Karol Date: Sun, 24 Nov 2019 19:46:37 +0100 Subject: [PATCH 142/312] Fix #194 and #114: Update semver regex to version from semver.org (#198) Also, fix problem with invalid Python 2.7 super call. --- semver.py | 18 +++++++++--------- setup.py | 2 +- test_semver.py | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/semver.py b/semver.py index db82585d..af7dec66 100644 --- a/semver.py +++ b/semver.py @@ -19,18 +19,18 @@ _REGEX = re.compile( r""" ^ - (?P(?:0|[1-9][0-9]*)) + (?P0|[1-9]\d*) \. - (?P(?:0|[1-9][0-9]*)) + (?P0|[1-9]\d*) \. - (?P(?:0|[1-9][0-9]*)) - (\-(?P - (?:0|[1-9A-Za-z-][0-9A-Za-z-]*) - (\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))* + (?P0|[1-9]\d*) + (?:-(?P + (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) + (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* ))? - (\+(?P - [0-9A-Za-z-]+ - (\.[0-9A-Za-z-]+)* + (?:\+(?P + [0-9a-zA-Z-]+ + (?:\.[0-9a-zA-Z-]+)* ))? $ """, re.VERBOSE) diff --git a/setup.py b/setup.py index da0fab65..7c31635b 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ def run_tests(self): class Clean(CleanCommand): def run(self): - super().run() + super(CleanCommand, self).run() delete_in_root = [ 'build', '.cache', diff --git a/test_semver.py b/test_semver.py index 023488da..053031f6 100644 --- a/test_semver.py +++ b/test_semver.py @@ -70,6 +70,30 @@ def test_fordocstrings(func): 'prerelease': 'alpha-1', 'build': 'build.11.e0f985a', }), + ("0.1.0-0f", + { + 'major': 0, + 'minor': 1, + 'patch': 0, + 'prerelease': '0f', + 'build': None, + }), + ("0.0.0-0foo.1", + { + 'major': 0, + 'minor': 0, + 'patch': 0, + 'prerelease': '0foo.1', + 'build': None, + }), + ("0.0.0-0foo.1+build.1", + { + 'major': 0, + 'minor': 0, + 'patch': 0, + 'prerelease': '0foo.1', + 'build': 'build.1', + }), ]) def test_should_parse_version(version, expected): result = parse(version) From dfc2270c680221e59568abc37f101e4e051f66a0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 25 Nov 2019 09:31:05 +0100 Subject: [PATCH 143/312] Fix #201: Reformat source code with black (#202) * Fix #201: Reformat source code with black * Add `pyproject.toml` containing black configuration * Add batch image in `README.rst` * Reformat source code with `black` * Add config for flake8 Use max-line-length of 88 (default for black) --- CHANGELOG.rst | 2 +- README.rst | 5 +- docs/conf.py | 92 +++--- pyproject.toml | 20 ++ semver.py | 214 +++++++------- setup.cfg | 11 + setup.py | 71 ++--- test_semver.py | 765 +++++++++++++++++++++++++++---------------------- 8 files changed, 642 insertions(+), 538 deletions(-) create mode 100644 pyproject.toml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 53079d3c..df88ddcc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,7 +19,7 @@ Features * :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 Bug Fixes --------- diff --git a/README.rst b/README.rst index 62e6b92a..5c35b423 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Quickstart A Python module for `semantic versioning`_. Simplifies comparing versions. -|build-status| |python-support| |downloads| |license| |docs| +|build-status| |python-support| |downloads| |license| |docs| |black| .. teaser-end @@ -125,3 +125,6 @@ There are other functions to discover. Read on! :target: http://python-semver.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. _semantic versioning: http://semver.org/ +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Black Formatter diff --git a/docs/conf.py b/docs/conf.py index 107765f2..a07c94a8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) from semver import __version__ # noqa: E402 @@ -32,27 +33,27 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.extlinks', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.extlinks", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'python-semver' -copyright = '2018, Kostiantyn Rybnikov and all' -author = 'Kostiantyn Rybnikov and all' +project = "python-semver" +copyright = "2018, Kostiantyn Rybnikov and all" +author = "Kostiantyn Rybnikov and all" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -73,21 +74,20 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # Markup to shorten external links # See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html -extlinks = {'gh': ('https://github.com/python-semver/python-semver/issues/%s', - '#'), - 'pr': ('https://github.com/python-semver/python-semver/pull/%s', - 'PR #'), - } +extlinks = { + "gh": ("https://github.com/python-semver/python-semver/issues/%s", "#"), + "pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #"), +} # -- Options for HTML output ---------------------------------------------- @@ -95,7 +95,7 @@ # a list of builtin themes. # # html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -106,9 +106,9 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] -html_css_files = ['css/default.css'] +html_css_files = ["css/default.css"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -116,12 +116,12 @@ # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", ] } @@ -130,7 +130,7 @@ # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'semverdoc' +htmlhelp_basename = "semverdoc" # -- Options for LaTeX output --------------------------------------------- @@ -139,15 +139,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -157,8 +154,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'semver.tex', 'python-semver Documentation', - 'Kostiantyn Rybnikov and all', 'manual'), + ( + master_doc, + "semver.tex", + "python-semver Documentation", + "Kostiantyn Rybnikov and all", + "manual", + ) ] @@ -169,11 +171,13 @@ manpage_doc = "pysemver" man_pages = [ - (manpage_doc, - 'pysemver', - 'Helper script for Semantic Versioning', - ["Thomas Schraitle"], - 1) + ( + manpage_doc, + "pysemver", + "Helper script for Semantic Versioning", + ["Thomas Schraitle"], + 1, + ) ] @@ -183,7 +187,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'semver', 'python-semver Documentation', - author, 'semver', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "semver", + "python-semver Documentation", + author, + "semver", + "One line description of project.", + "Miscellaneous", + ) ] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..eca41891 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.black] +line-length = 88 +target-version = ['py37'] +include = '\.pyi?$' +# diff = true +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.mypy_cache + | \.tox + | \.venv + | \.env + | _build + | build + | dist + )/ +) +''' diff --git a/semver.py b/semver.py index af7dec66..efe1102e 100644 --- a/semver.py +++ b/semver.py @@ -10,14 +10,14 @@ import sys -__version__ = '2.9.0' -__author__ = 'Kostiantyn Rybnikov' -__author_email__ = 'k-bx@k-bx.com' -__maintainer__ = 'Sebastien Celles' +__version__ = "2.9.0" +__author__ = "Kostiantyn Rybnikov" +__author_email__ = "k-bx@k-bx.com" +__maintainer__ = "Sebastien Celles" __maintainer_email__ = "s.celles@gmail.com" _REGEX = re.compile( - r""" + r""" ^ (?P0|[1-9]\d*) \. @@ -33,15 +33,18 @@ (?:\.[0-9a-zA-Z-]+)* ))? $ - """, re.VERBOSE) + """, + re.VERBOSE, +) -_LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') +_LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Contains the implemented semver.org version of the spec SEMVER_SPEC_VERSION = "2.0.0" -if not hasattr(__builtins__, 'cmp'): +if not hasattr(__builtins__, "cmp"): + def cmp(a, b): return (a > b) - (a < b) @@ -69,26 +72,29 @@ def parse(version): """ match = _REGEX.match(version) if match is None: - raise ValueError('%s is not valid SemVer string' % version) + raise ValueError("%s is not valid SemVer string" % version) version_parts = match.groupdict() - version_parts['major'] = int(version_parts['major']) - version_parts['minor'] = int(version_parts['minor']) - version_parts['patch'] = int(version_parts['patch']) + version_parts["major"] = int(version_parts["major"]) + version_parts["minor"] = int(version_parts["minor"]) + version_parts["patch"] = int(version_parts["patch"]) return version_parts def comparator(operator): """ Wrap a VersionInfo binary op method in a type-check """ + @wraps(operator) def wrapper(self, other): comparable_types = (VersionInfo, dict, tuple) if not isinstance(other, comparable_types): - raise TypeError("other type %r must be in %r" - % (type(other), comparable_types)) + raise TypeError( + "other type %r must be in %r" % (type(other), comparable_types) + ) return operator(self, other) + return wrapper @@ -101,7 +107,8 @@ class VersionInfo(object): :param str prerelease: an optional prerelease string :param str build: an optional build string """ - __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') + + __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build") def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): self._major = int(major) @@ -156,17 +163,18 @@ def build(self, value): raise AttributeError("attribute 'build' is readonly") def _astuple(self): - return (self.major, self.minor, self.patch, - self.prerelease, self.build) + return (self.major, self.minor, self.patch, self.prerelease, self.build) def _asdict(self): - return collections.OrderedDict(( - ("major", self.major), - ("minor", self.minor), - ("patch", self.patch), - ("prerelease", self.prerelease), - ("build", self.build) - )) + return collections.OrderedDict( + ( + ("major", self.major), + ("minor", self.minor), + ("patch", self.patch), + ("prerelease", self.prerelease), + ("build", self.build), + ) + ) def __iter__(self): """Implement iter(self).""" @@ -213,7 +221,7 @@ def bump_patch(self): """ return parse_version_info(bump_patch(str(self))) - def bump_prerelease(self, token='rc'): + def bump_prerelease(self, token="rc"): """Raise the prerelease part of the version, return a new object but leave self untouched @@ -228,7 +236,7 @@ def bump_prerelease(self, token='rc'): """ return parse_version_info(bump_prerelease(str(self), token)) - def bump_build(self, token='build'): + def bump_build(self, token="build"): """Raise the build part of the version, return a new object but leave self untouched @@ -268,8 +276,7 @@ def __ge__(self, other): return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 def __repr__(self): - s = ", ".join("%s=%r" % (key, val) - for key, val in self._asdict().items()) + s = ", ".join("%s=%r" % (key, val) for key, val in self._asdict().items()) return "%s(%s)" % (type(self).__name__, s) def __str__(self): @@ -308,10 +315,10 @@ def replace(self, **parts): return VersionInfo(**version) except TypeError: unknownkeys = set(parts) - set(self._asdict()) - error = ("replace() got %d unexpected keyword " - "argument(s): %s" % (len(unknownkeys), - ", ".join(unknownkeys)) - ) + error = "replace() got %d unexpected keyword " "argument(s): %s" % ( + len(unknownkeys), + ", ".join(unknownkeys), + ) raise TypeError(error) @@ -344,18 +351,22 @@ def parse_version_info(version): """ parts = parse(version) version_info = VersionInfo( - parts['major'], parts['minor'], parts['patch'], - parts['prerelease'], parts['build']) + parts["major"], + parts["minor"], + parts["patch"], + parts["prerelease"], + parts["build"], + ) return version_info def _nat_cmp(a, b): def convert(text): - return int(text) if re.match('^[0-9]+$', text) else text + return int(text) if re.match("^[0-9]+$", text) else text def split_key(key): - return [convert(c) for c in key.split('.')] + return [convert(c) for c in key.split(".")] def cmp_prerelease_tag(a, b): if isinstance(a, int) and isinstance(b, int): @@ -367,7 +378,7 @@ def cmp_prerelease_tag(a, b): else: return cmp(a, b) - a, b = a or '', b or '' + 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) @@ -378,12 +389,12 @@ def cmp_prerelease_tag(a, b): def _compare_by_keys(d1, d2): - for key in ['major', 'minor', 'patch']: + for key in ["major", "minor", "patch"]: v = cmp(d1.get(key), d2.get(key)) if v: return v - rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') + rc1, rc2 = d1.get("prerelease"), d2.get("prerelease") rccmp = _nat_cmp(rc1, rc2) if not rccmp: @@ -438,24 +449,26 @@ def match(version, match_expr): False """ prefix = match_expr[:2] - if prefix in ('>=', '<=', '==', '!='): + if prefix in (">=", "<=", "==", "!="): match_version = match_expr[2:] - elif prefix and prefix[0] in ('>', '<'): + elif prefix and prefix[0] in (">", "<"): prefix = prefix[0] match_version = match_expr[1:] else: - raise ValueError("match_expr parameter should be in format , " - "where is one of " - "['<', '>', '==', '<=', '>=', '!=']. " - "You provided: %r" % match_expr) + raise ValueError( + "match_expr parameter should be in format , " + "where is one of " + "['<', '>', '==', '<=', '>=', '!=']. " + "You provided: %r" % match_expr + ) possibilities_dict = { - '>': (1,), - '<': (-1,), - '==': (0,), - '!=': (-1, 1), - '>=': (0, 1), - '<=': (-1, 0) + ">": (1,), + "<": (-1,), + "==": (0,), + "!=": (-1, 1), + ">=": (0, 1), + "<=": (-1, 0), } possibilities = possibilities_dict[prefix] @@ -533,7 +546,7 @@ def _increment_string(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:] + string = string[: max(end - len(next_), start)] + next_ + string[end:] return string @@ -548,7 +561,7 @@ def bump_major(version): '4.0.0' """ verinfo = parse(version) - return format_version(verinfo['major'] + 1, 0, 0) + return format_version(verinfo["major"] + 1, 0, 0) def bump_minor(version): @@ -562,7 +575,7 @@ def bump_minor(version): '3.5.0' """ verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'] + 1, 0) + return format_version(verinfo["major"], verinfo["minor"] + 1, 0) def bump_patch(version): @@ -576,11 +589,10 @@ def bump_patch(version): '3.4.6' """ verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'], - verinfo['patch'] + 1) + return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"] + 1) -def bump_prerelease(version, token='rc'): +def bump_prerelease(version, token="rc"): """Raise the prerelease part of the version :param version: version string @@ -592,14 +604,15 @@ def bump_prerelease(version, token='rc'): '3.4.5-dev.1' """ verinfo = parse(version) - verinfo['prerelease'] = _increment_string( - verinfo['prerelease'] or (token or 'rc') + '.0' + verinfo["prerelease"] = _increment_string( + verinfo["prerelease"] or (token or "rc") + ".0" + ) + return format_version( + verinfo["major"], verinfo["minor"], verinfo["patch"], verinfo["prerelease"] ) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], - verinfo['prerelease']) -def bump_build(version, token='build'): +def bump_build(version, token="build"): """Raise the build part of the version :param version: version string @@ -611,11 +624,14 @@ def bump_build(version, token='build'): '3.4.5-rc.1+build.10' """ verinfo = parse(version) - verinfo['build'] = _increment_string( - verinfo['build'] or (token or 'build') + '.0' + verinfo["build"] = _increment_string(verinfo["build"] or (token or "build") + ".0") + return format_version( + verinfo["major"], + verinfo["minor"], + verinfo["patch"], + verinfo["prerelease"], + verinfo["build"], ) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], - verinfo['prerelease'], verinfo['build']) def finalize_version(version): @@ -629,7 +645,7 @@ def finalize_version(version): '1.2.3' """ verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) + return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"]) def createparser(): @@ -638,49 +654,33 @@ def createparser(): :return: parser instance :rtype: :class:`argparse.ArgumentParser` """ - parser = argparse.ArgumentParser(prog=__package__, - description=__doc__) + parser = argparse.ArgumentParser(prog=__package__, description=__doc__) - parser.add_argument('--version', - action='version', - version='%(prog)s ' + __version__ - ) + 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 = s.add_parser("compare", help="Compare two versions") parser_compare.set_defaults(which="compare") - parser_compare.add_argument("version1", - help="First version" - ) - parser_compare.add_argument("version2", - help="Second version" - ) + 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 = s.add_parser("bump", help="Bumps a version") parser_bump.set_defaults(which="bump") - sb = parser_bump.add_subparsers(title="Bump commands", - dest="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" - ) + 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") return parser @@ -698,12 +698,13 @@ def process(args): args.parser.print_help() raise SystemExit() elif args.which == "bump": - maptable = {'major': 'bump_major', - 'minor': 'bump_minor', - 'patch': 'bump_patch', - 'prerelease': 'bump_prerelease', - 'build': 'bump_build', - } + 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 @@ -759,4 +760,5 @@ def replace(version, **parts): if __name__ == "__main__": import doctest + doctest.testmod() diff --git a/setup.cfg b/setup.cfg index 7967d292..b8a0ea49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,14 @@ addopts = --cov-report=term-missing --doctest-modules --doctest-report ndiff + +[flake8] +max-line-length = 88 +exclude = + .env, + .eggs, + .tox, + .git, + __pycache__, + build, + dist \ No newline at end of file diff --git a/setup.py b/setup.py index 7c31635b..77f19e73 100755 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ from os.path import dirname, join from setuptools import setup from setuptools.command.test import test as TestCommand + try: from setuptools.command.clean import clean as CleanCommand except ImportError: @@ -14,7 +15,7 @@ class Tox(TestCommand): - user_options = [('tox-args=', 'a', "Arguments to pass to tox")] + user_options = [("tox-args=", "a", "Arguments to pass to tox")] def initialize_options(self): TestCommand.initialize_options(self) @@ -27,6 +28,7 @@ def finalize_options(self): def run_tests(self): from tox import cmdline + args = self.tox_args if args: args = split(self.tox_args) @@ -37,35 +39,25 @@ def run_tests(self): class Clean(CleanCommand): def run(self): super(CleanCommand, self).run() - delete_in_root = [ - 'build', - '.cache', - 'dist', - '.eggs', - '*.egg-info', - '.tox', - ] - delete_everywhere = [ - '__pycache__', - '*.pyc', - ] + delete_in_root = ["build", ".cache", "dist", ".eggs", "*.egg-info", ".tox"] + delete_everywhere = ["__pycache__", "*.pyc"] for candidate in delete_in_root: rmtree_glob(candidate) - for visible_dir in glob('[A-Za-z0-9]*'): + for visible_dir in glob("[A-Za-z0-9]*"): for candidate in delete_everywhere: rmtree_glob(join(visible_dir, candidate)) - rmtree_glob(join(visible_dir, '*', candidate)) + rmtree_glob(join(visible_dir, "*", candidate)) def rmtree_glob(file_glob): for fobj in glob(file_glob): try: rmtree(fobj) - print('%s/ removed ...' % fobj) + print("%s/ removed ..." % fobj) except OSError: try: remove(fobj) - print('%s removed ...' % fobj) + print("%s removed ..." % fobj) except OSError: pass @@ -79,35 +71,30 @@ def read_file(filename): name=package.__name__, version=package.__version__, description=package.__doc__.strip(), - long_description=read_file('README.rst'), + long_description=read_file("README.rst"), author=package.__author__, author_email=package.__author_email__, - url='https://github.com/python-semver/python-semver', - download_url='https://github.com/python-semver/python-semver/downloads', + url="https://github.com/python-semver/python-semver", + download_url="https://github.com/python-semver/python-semver/downloads", py_modules=[package.__name__], include_package_data=True, - license='BSD', + license="BSD", classifiers=[ - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Software Development :: Libraries :: Python Modules", ], - tests_require=['tox', 'virtualenv'], - cmdclass={ - 'clean': Clean, - 'test': Tox, - }, - entry_points={ - 'console_scripts': ['pysemver = semver:main'], - } + tests_require=["tox", "virtualenv"], + cmdclass={"clean": Clean, "test": Tox}, + entry_points={"console_scripts": ["pysemver = semver:main"]}, ) diff --git a/test_semver.py b/test_semver.py index 053031f6..b3d0929d 100644 --- a/test_semver.py +++ b/test_semver.py @@ -1,154 +1,181 @@ from argparse import Namespace import pytest # noqa -from semver import (VersionInfo, - bump_build, - bump_major, - bump_minor, - bump_patch, - bump_prerelease, - compare, - createparser, - finalize_version, - format_version, - main, - match, - max_ver, - min_ver, - parse, - parse_version_info, - process, - replace, - ) +from semver import ( + VersionInfo, + bump_build, + bump_major, + bump_minor, + bump_patch, + bump_prerelease, + compare, + createparser, + finalize_version, + format_version, + main, + match, + max_ver, + min_ver, + parse, + parse_version_info, + process, + replace, +) SEMVERFUNCS = [ - compare, createparser, - bump_build, bump_major, bump_minor, bump_patch, bump_prerelease, - finalize_version, format_version, - match, max_ver, min_ver, parse, process, replace, - ] - - -@pytest.mark.parametrize("string,expected", [ - ("rc", "rc"), - ("rc.1", "rc.2"), - ("2x", "3x"), -]) + compare, + createparser, + bump_build, + bump_major, + bump_minor, + bump_patch, + bump_prerelease, + finalize_version, + format_version, + match, + max_ver, + min_ver, + parse, + process, + replace, +] + + +@pytest.mark.parametrize( + "string,expected", [("rc", "rc"), ("rc.1", "rc.2"), ("2x", "3x")] +) def test_should_private_increment_string(string, expected): from semver import _increment_string + assert _increment_string(string) == expected @pytest.fixture def version(): - return VersionInfo(major=1, minor=2, patch=3, - prerelease='alpha.1.2', build='build.11.e0f985a') + return VersionInfo( + major=1, minor=2, patch=3, prerelease="alpha.1.2", build="build.11.e0f985a" + ) -@pytest.mark.parametrize("func", SEMVERFUNCS, - ids=[func.__name__ for func in SEMVERFUNCS]) +@pytest.mark.parametrize( + "func", SEMVERFUNCS, ids=[func.__name__ for func in SEMVERFUNCS] +) def test_fordocstrings(func): assert func.__doc__, "Need a docstring for function %r" % func.__name -@pytest.mark.parametrize("version,expected", [ - # no. 1 - ("1.2.3-alpha.1.2+build.11.e0f985a", - { - 'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'alpha.1.2', - 'build': 'build.11.e0f985a', - }), - # no. 2 - ("1.2.3-alpha-1+build.11.e0f985a", - { - 'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'alpha-1', - 'build': 'build.11.e0f985a', - }), - ("0.1.0-0f", - { - 'major': 0, - 'minor': 1, - 'patch': 0, - 'prerelease': '0f', - 'build': None, - }), - ("0.0.0-0foo.1", - { - 'major': 0, - 'minor': 0, - 'patch': 0, - 'prerelease': '0foo.1', - 'build': None, - }), - ("0.0.0-0foo.1+build.1", - { - 'major': 0, - 'minor': 0, - 'patch': 0, - 'prerelease': '0foo.1', - 'build': 'build.1', - }), -]) +@pytest.mark.parametrize( + "version,expected", + [ + # no. 1 + ( + "1.2.3-alpha.1.2+build.11.e0f985a", + { + "major": 1, + "minor": 2, + "patch": 3, + "prerelease": "alpha.1.2", + "build": "build.11.e0f985a", + }, + ), + # no. 2 + ( + "1.2.3-alpha-1+build.11.e0f985a", + { + "major": 1, + "minor": 2, + "patch": 3, + "prerelease": "alpha-1", + "build": "build.11.e0f985a", + }, + ), + ( + "0.1.0-0f", + {"major": 0, "minor": 1, "patch": 0, "prerelease": "0f", "build": None}, + ), + ( + "0.0.0-0foo.1", + {"major": 0, "minor": 0, "patch": 0, "prerelease": "0foo.1", "build": None}, + ), + ( + "0.0.0-0foo.1+build.1", + { + "major": 0, + "minor": 0, + "patch": 0, + "prerelease": "0foo.1", + "build": "build.1", + }, + ), + ], +) def test_should_parse_version(version, expected): result = parse(version) assert result == expected -@pytest.mark.parametrize("version,expected", [ - # no. 1 - ("1.2.3-rc.0+build.0", - { - 'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'rc.0', - 'build': 'build.0', - }), - # no. 2 - ("1.2.3-rc.0.0+build.0", - { - 'major': 1, - 'minor': 2, - 'patch': 3, - 'prerelease': 'rc.0.0', - 'build': 'build.0', - }), -]) +@pytest.mark.parametrize( + "version,expected", + [ + # no. 1 + ( + "1.2.3-rc.0+build.0", + { + "major": 1, + "minor": 2, + "patch": 3, + "prerelease": "rc.0", + "build": "build.0", + }, + ), + # no. 2 + ( + "1.2.3-rc.0.0+build.0", + { + "major": 1, + "minor": 2, + "patch": 3, + "prerelease": "rc.0.0", + "build": "build.0", + }, + ), + ], +) def test_should_parse_zero_prerelease(version, expected): result = parse(version) assert result == expected -@pytest.mark.parametrize("left,right", [ - ("1.0.0", "2.0.0"), - ('1.0.0-alpha', '1.0.0-alpha.1'), - ('1.0.0-alpha.1', '1.0.0-alpha.beta'), - ('1.0.0-alpha.beta', '1.0.0-beta'), - ('1.0.0-beta', '1.0.0-beta.2'), - ('1.0.0-beta.2', '1.0.0-beta.11'), - ('1.0.0-beta.11', '1.0.0-rc.1'), - ('1.0.0-rc.1', '1.0.0'), -]) +@pytest.mark.parametrize( + "left,right", + [ + ("1.0.0", "2.0.0"), + ("1.0.0-alpha", "1.0.0-alpha.1"), + ("1.0.0-alpha.1", "1.0.0-alpha.beta"), + ("1.0.0-alpha.beta", "1.0.0-beta"), + ("1.0.0-beta", "1.0.0-beta.2"), + ("1.0.0-beta.2", "1.0.0-beta.11"), + ("1.0.0-beta.11", "1.0.0-rc.1"), + ("1.0.0-rc.1", "1.0.0"), + ], +) def test_should_get_less(left, right): assert compare(left, right) == -1 -@pytest.mark.parametrize("left,right", [ - ("2.0.0", "1.0.0"), - ('1.0.0-alpha.1', '1.0.0-alpha'), - ('1.0.0-alpha.beta', '1.0.0-alpha.1'), - ('1.0.0-beta', '1.0.0-alpha.beta'), - ('1.0.0-beta.2', '1.0.0-beta'), - ('1.0.0-beta.11', '1.0.0-beta.2'), - ('1.0.0-rc.1', '1.0.0-beta.11'), - ('1.0.0', '1.0.0-rc.1') -]) +@pytest.mark.parametrize( + "left,right", + [ + ("2.0.0", "1.0.0"), + ("1.0.0-alpha.1", "1.0.0-alpha"), + ("1.0.0-alpha.beta", "1.0.0-alpha.1"), + ("1.0.0-beta", "1.0.0-alpha.beta"), + ("1.0.0-beta.2", "1.0.0-beta"), + ("1.0.0-beta.11", "1.0.0-beta.2"), + ("1.0.0-rc.1", "1.0.0-beta.11"), + ("1.0.0", "1.0.0-rc.1"), + ], +) def test_should_get_greater(left, right): assert compare(left, right) == 1 @@ -161,34 +188,38 @@ def test_should_no_match_simple(): assert match("2.3.7", ">=2.3.8") is False -@pytest.mark.parametrize("left,right,expected", [ - ("2.3.7", "!=2.3.8", True), - ("2.3.7", "!=2.3.6", True), - ("2.3.7", "!=2.3.7", False), -]) +@pytest.mark.parametrize( + "left,right,expected", + [ + ("2.3.7", "!=2.3.8", True), + ("2.3.7", "!=2.3.6", True), + ("2.3.7", "!=2.3.7", False), + ], +) def test_should_match_not_equal(left, right, expected): assert match(left, right) is expected -@pytest.mark.parametrize("left,right,expected", [ - ("2.3.7", "<2.4.0", True), - ("2.3.7", ">2.3.5", True), - ("2.3.7", "<=2.3.9", True), - ("2.3.7", ">=2.3.5", True), - ("2.3.7", "==2.3.7", True), - ("2.3.7", "!=2.3.7", False), -]) -def test_should_not_raise_value_error_for_expected_match_expression(left, - right, - expected): +@pytest.mark.parametrize( + "left,right,expected", + [ + ("2.3.7", "<2.4.0", True), + ("2.3.7", ">2.3.5", True), + ("2.3.7", "<=2.3.9", True), + ("2.3.7", ">=2.3.5", True), + ("2.3.7", "==2.3.7", True), + ("2.3.7", "!=2.3.7", False), + ], +) +def test_should_not_raise_value_error_for_expected_match_expression( + left, right, expected +): assert match(left, right) is expected -@pytest.mark.parametrize("left,right", [ - ("2.3.7", "=2.3.7"), - ("2.3.7", "~2.3.7"), - ("2.3.7", "^2.3.7"), -]) +@pytest.mark.parametrize( + "left,right", [("2.3.7", "=2.3.7"), ("2.3.7", "~2.3.7"), ("2.3.7", "^2.3.7")] +) def test_should_raise_value_error_for_unexpected_match_expression(left, right): with pytest.raises(ValueError): match(left, right) @@ -200,21 +231,17 @@ def test_should_raise_value_error_for_zero_prefixed_versions(version): parse(version) -@pytest.mark.parametrize("left,right", [ - ('foo', 'bar'), - ('1.0', '1.0.0'), - ('1.x', '1.0.0'), -]) +@pytest.mark.parametrize( + "left,right", [("foo", "bar"), ("1.0", "1.0.0"), ("1.x", "1.0.0")] +) def test_should_raise_value_error_for_invalid_value(left, right): with pytest.raises(ValueError): compare(left, right) -@pytest.mark.parametrize("left,right", [ - ('1.0.0', ''), - ('1.0.0', '!'), - ('1.0.0', '1.0.0'), -]) +@pytest.mark.parametrize( + "left,right", [("1.0.0", ""), ("1.0.0", "!"), ("1.0.0", "1.0.0")] +) def test_should_raise_value_error_for_invalid_match_expression(left, right): with pytest.raises(ValueError): match(left, right) @@ -229,78 +256,88 @@ def test_should_follow_specification_comparison(): and in backward too. """ chain = [ - '1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-beta.2', '1.0.0-beta.11', - '1.0.0-rc.1', '1.0.0', '1.3.7+build', + "1.0.0-alpha", + "1.0.0-alpha.1", + "1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-rc.1", + "1.0.0", + "1.3.7+build", ] versions = zip(chain[:-1], chain[1:]) for low_version, high_version in versions: - assert compare(low_version, high_version) == -1, \ - '%s should be lesser than %s' % (low_version, high_version) - assert compare(high_version, low_version) == 1, \ - '%s should be higher than %s' % (high_version, low_version) + assert ( + compare(low_version, high_version) == -1 + ), "%s should be lesser than %s" % (low_version, high_version) + assert ( + compare(high_version, low_version) == 1 + ), "%s should be higher than %s" % (high_version, low_version) -@pytest.mark.parametrize("left,right", [ - ('1.0.0-beta.2', '1.0.0-beta.11'), -]) +@pytest.mark.parametrize("left,right", [("1.0.0-beta.2", "1.0.0-beta.11")]) def test_should_compare_rc_builds(left, right): assert compare(left, right) == -1 -@pytest.mark.parametrize("left,right", [ - ('1.0.0-rc.1', '1.0.0'), - ('1.0.0-rc.1+build.1', '1.0.0'), -]) +@pytest.mark.parametrize( + "left,right", [("1.0.0-rc.1", "1.0.0"), ("1.0.0-rc.1+build.1", "1.0.0")] +) def test_should_compare_release_candidate_with_release(left, right): assert compare(left, right) == -1 -@pytest.mark.parametrize("left,right", [ - ('2.0.0', '2.0.0'), - ('1.1.9-rc.1', '1.1.9-rc.1'), - ('1.1.9+build.1', '1.1.9+build.1'), - ('1.1.9-rc.1+build.1', '1.1.9-rc.1+build.1'), -]) +@pytest.mark.parametrize( + "left,right", + [ + ("2.0.0", "2.0.0"), + ("1.1.9-rc.1", "1.1.9-rc.1"), + ("1.1.9+build.1", "1.1.9+build.1"), + ("1.1.9-rc.1+build.1", "1.1.9-rc.1+build.1"), + ], +) def test_should_say_equal_versions_are_equal(left, right): assert compare(left, right) == 0 -@pytest.mark.parametrize("left,right,expected", [ - ('1.1.9-rc.1', '1.1.9-rc.1+build.1', 0), - ('1.1.9-rc.1', '1.1.9+build.1', -1), -]) +@pytest.mark.parametrize( + "left,right,expected", + [("1.1.9-rc.1", "1.1.9-rc.1+build.1", 0), ("1.1.9-rc.1", "1.1.9+build.1", -1)], +) def test_should_compare_versions_with_build_and_release(left, right, expected): assert compare(left, right) == expected -@pytest.mark.parametrize("left,right,expected", [ - ('1.0.0+build.1', '1.0.0', 0), - ('1.0.0-alpha.1+build.1', '1.0.0-alpha.1', 0), - ('1.0.0+build.1', '1.0.0-alpha.1', 1), - ('1.0.0+build.1', '1.0.0-alpha.1+build.1', 1), -]) +@pytest.mark.parametrize( + "left,right,expected", + [ + ("1.0.0+build.1", "1.0.0", 0), + ("1.0.0-alpha.1+build.1", "1.0.0-alpha.1", 0), + ("1.0.0+build.1", "1.0.0-alpha.1", 1), + ("1.0.0+build.1", "1.0.0-alpha.1+build.1", 1), + ], +) def test_should_ignore_builds_on_compare(left, right, expected): assert compare(left, right) == expected def test_should_correctly_format_version(): - assert format_version(3, 4, 5) == '3.4.5' - assert format_version(3, 4, 5, 'rc.1') == '3.4.5-rc.1' - assert format_version(3, 4, 5, prerelease='rc.1') == '3.4.5-rc.1' - assert format_version(3, 4, 5, build='build.4') == '3.4.5+build.4' - assert format_version(3, 4, 5, 'rc.1', 'build.4') == '3.4.5-rc.1+build.4' + assert format_version(3, 4, 5) == "3.4.5" + assert format_version(3, 4, 5, "rc.1") == "3.4.5-rc.1" + assert format_version(3, 4, 5, prerelease="rc.1") == "3.4.5-rc.1" + assert format_version(3, 4, 5, build="build.4") == "3.4.5+build.4" + assert format_version(3, 4, 5, "rc.1", "build.4") == "3.4.5-rc.1+build.4" def test_should_bump_major(): - assert bump_major('3.4.5') == '4.0.0' + assert bump_major("3.4.5") == "4.0.0" def test_should_bump_minor(): - assert bump_minor('3.4.5') == '3.5.0' + assert bump_minor("3.4.5") == "3.5.0" def test_should_bump_patch(): - assert bump_patch('3.4.5') == '3.4.6' + assert bump_patch("3.4.5") == "3.4.6" def test_should_versioninfo_bump_major_and_minor(): @@ -344,159 +381,172 @@ def test_should_versioninfo_bump_multiple(): expected = parse_version_info("3.4.5-rc.2+build.2") assert v.bump_prerelease().bump_build().bump_build() == expected expected = parse_version_info("3.4.5-rc.3") - assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == \ - expected + assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected def test_should_ignore_extensions_for_bump(): - assert bump_patch('3.4.5-rc1+build4') == '3.4.6' + assert bump_patch("3.4.5-rc1+build4") == "3.4.6" def test_should_get_max(): - assert max_ver('3.4.5', '4.0.2') == '4.0.2' + assert max_ver("3.4.5", "4.0.2") == "4.0.2" def test_should_get_max_same(): - assert max_ver('3.4.5', '3.4.5') == '3.4.5' + assert max_ver("3.4.5", "3.4.5") == "3.4.5" def test_should_get_min(): - assert min_ver('3.4.5', '4.0.2') == '3.4.5' + assert min_ver("3.4.5", "4.0.2") == "3.4.5" def test_should_get_min_same(): - assert min_ver('3.4.5', '3.4.5') == '3.4.5' + assert min_ver("3.4.5", "3.4.5") == "3.4.5" def test_should_get_more_rc1(): assert compare("1.0.0-rc1", "1.0.0-rc0") == 1 -@pytest.mark.parametrize("left,right,expected", [ - ('1.2.3-rc.2', '1.2.3-rc.10', '1.2.3-rc.2'), - ('1.2.3-rc2', '1.2.3-rc10', '1.2.3-rc10'), - # identifiers with letters or hyphens are compared lexically in ASCII sort - # order. - ('1.2.3-Rc10', '1.2.3-rc10', '1.2.3-Rc10'), - # Numeric identifiers always have lower precedence than non-numeric - # identifiers. - ('1.2.3-2', '1.2.3-rc', '1.2.3-2'), - # A larger set of pre-release fields has a higher precedence than a - # smaller set, if all of the preceding identifiers are equal. - ('1.2.3-rc.2.1', '1.2.3-rc.2', '1.2.3-rc.2'), - # When major, minor, and patch are equal, a pre-release version has lower - # precedence than a normal version. - ('1.2.3', '1.2.3-1', '1.2.3-1'), - ('1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-alpha') -]) +@pytest.mark.parametrize( + "left,right,expected", + [ + ("1.2.3-rc.2", "1.2.3-rc.10", "1.2.3-rc.2"), + ("1.2.3-rc2", "1.2.3-rc10", "1.2.3-rc10"), + # identifiers with letters or hyphens are compared lexically in ASCII sort + # order. + ("1.2.3-Rc10", "1.2.3-rc10", "1.2.3-Rc10"), + # Numeric identifiers always have lower precedence than non-numeric + # identifiers. + ("1.2.3-2", "1.2.3-rc", "1.2.3-2"), + # A larger set of pre-release fields has a higher precedence than a + # smaller set, if all of the preceding identifiers are equal. + ("1.2.3-rc.2.1", "1.2.3-rc.2", "1.2.3-rc.2"), + # When major, minor, and patch are equal, a pre-release version has lower + # precedence than a normal version. + ("1.2.3", "1.2.3-1", "1.2.3-1"), + ("1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha"), + ], +) def test_prerelease_order(left, right, expected): assert min_ver(left, right) == expected -@pytest.mark.parametrize("version,token,expected", [ - ('3.4.5-rc.9', None, '3.4.5-rc.10'), - ('3.4.5', None, '3.4.5-rc.1'), - ('3.4.5', 'dev', '3.4.5-dev.1'), - ('3.4.5', '', '3.4.5-rc.1'), -]) +@pytest.mark.parametrize( + "version,token,expected", + [ + ("3.4.5-rc.9", None, "3.4.5-rc.10"), + ("3.4.5", None, "3.4.5-rc.1"), + ("3.4.5", "dev", "3.4.5-dev.1"), + ("3.4.5", "", "3.4.5-rc.1"), + ], +) def test_should_bump_prerelease(version, token, expected): token = "rc" if not token else token assert bump_prerelease(version, token) == expected def test_should_ignore_build_on_prerelease_bump(): - assert bump_prerelease('3.4.5-rc.1+build.4') == '3.4.5-rc.2' - - -@pytest.mark.parametrize("version,expected", [ - ('3.4.5-rc.1+build.9', '3.4.5-rc.1+build.10'), - ('3.4.5-rc.1+0009.dev', '3.4.5-rc.1+0010.dev'), - ('3.4.5-rc.1', '3.4.5-rc.1+build.1'), - ('3.4.5', '3.4.5+build.1'), -]) + assert bump_prerelease("3.4.5-rc.1+build.4") == "3.4.5-rc.2" + + +@pytest.mark.parametrize( + "version,expected", + [ + ("3.4.5-rc.1+build.9", "3.4.5-rc.1+build.10"), + ("3.4.5-rc.1+0009.dev", "3.4.5-rc.1+0010.dev"), + ("3.4.5-rc.1", "3.4.5-rc.1+build.1"), + ("3.4.5", "3.4.5+build.1"), + ], +) def test_should_bump_build(version, expected): assert bump_build(version) == expected -@pytest.mark.parametrize("version,expected", [ - ('1.2.3', '1.2.3'), - ('1.2.3-rc.5', '1.2.3'), - ('1.2.3+build.2', '1.2.3'), - ('1.2.3-rc.1+build.5', '1.2.3'), - ('1.2.3-alpha', '1.2.3'), - ('1.2.0', '1.2.0'), -]) +@pytest.mark.parametrize( + "version,expected", + [ + ("1.2.3", "1.2.3"), + ("1.2.3-rc.5", "1.2.3"), + ("1.2.3+build.2", "1.2.3"), + ("1.2.3-rc.1+build.5", "1.2.3"), + ("1.2.3-alpha", "1.2.3"), + ("1.2.0", "1.2.0"), + ], +) def test_should_finalize_version(version, expected): assert finalize_version(version) == expected 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) + v2 = VersionInfo(major=0, minor=10, patch=4, prerelease="beta.1", build=None) # use `not` to enforce using comparision operators assert v1 != v2 assert v1 > v2 assert v1 >= v2 - assert not(v1 < v2) - assert not(v1 <= v2) - assert not(v1 == v2) + assert not (v1 < v2) + assert not (v1 <= v2) + assert not (v1 == v2) v3 = VersionInfo(major=0, minor=10, patch=4) - assert not(v1 != v3) - assert not(v1 > v3) + assert not (v1 != v3) + assert not (v1 > v3) assert v1 >= v3 - assert not(v1 < v3) + assert not (v1 < v3) assert v1 <= v3 assert v1 == v3 v4 = VersionInfo(major=0, minor=10, patch=5) assert v1 != v4 - assert not(v1 > v4) - assert not(v1 >= v4) + assert not (v1 > v4) + assert not (v1 >= v4) assert v1 < v4 assert v1 <= v4 - assert not(v1 == v4) + assert not (v1 == v4) def test_should_compare_version_dictionaries(): v1 = VersionInfo(major=0, minor=10, patch=4) - v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None) + v2 = dict(major=0, minor=10, patch=4, prerelease="beta.1", build=None) assert v1 != v2 assert v1 > v2 assert v1 >= v2 - assert not(v1 < v2) - assert not(v1 <= v2) - assert not(v1 == v2) + assert not (v1 < v2) + assert not (v1 <= v2) + assert not (v1 == v2) v3 = dict(major=0, minor=10, patch=4) - assert not(v1 != v3) - assert not(v1 > v3) + assert not (v1 != v3) + assert not (v1 > v3) assert v1 >= v3 - assert not(v1 < v3) + assert not (v1 < v3) assert v1 <= v3 assert v1 == v3 v4 = dict(major=0, minor=10, patch=5) assert v1 != v4 - assert not(v1 > v4) - assert not(v1 >= v4) + assert not (v1 > v4) + assert not (v1 >= v4) assert v1 < v4 assert v1 <= v4 - assert not(v1 == v4) + assert not (v1 == v4) def test_should_compare_version_tuples(): - 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') - for t in ((1, 0, 0), (1, 0), (1,), (1, 0, 0, 'pre.2'), - (1, 0, 0, 'pre.2', 'build.4')): + 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") + for t in ( + (1, 0, 0), + (1, 0), + (1,), + (1, 0, 0, "pre.2"), + (1, 0, 0, "pre.2", "build.4"), + ): assert v0 < t assert v0 <= t assert v0 != t @@ -513,8 +563,7 @@ def test_should_compare_version_tuples(): def test_should_not_allow_to_compare_version_with_string(): - v1 = VersionInfo(major=3, minor=4, patch=5, - prerelease='pre.2', build='build.4') + v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") with pytest.raises(TypeError): v1 > "1.0.0" with pytest.raises(TypeError): @@ -522,8 +571,7 @@ def test_should_not_allow_to_compare_version_with_string(): 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 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4") with pytest.raises(TypeError): v1 > 1 with pytest.raises(TypeError): @@ -531,8 +579,8 @@ def test_should_not_allow_to_compare_version_with_int(): 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 = VersionInfo(major=1, minor=9, patch=1, prerelease="1unms", build=None) + v2 = VersionInfo(major=1, minor=9, patch=1, prerelease=None, build="1asd") assert v1 < v2 assert compare("1.9.1-1unms", "1.9.1+1") == -1 @@ -567,76 +615,96 @@ def test_immutable_patch(version): def test_immutable_prerelease(version): - with pytest.raises(AttributeError, - match="attribute 'prerelease' is readonly"): - version.prerelease = 'alpha.9.9' + with pytest.raises(AttributeError, match="attribute 'prerelease' is readonly"): + version.prerelease = "alpha.9.9" def test_immutable_build(version): with pytest.raises(AttributeError, match="attribute 'build' is readonly"): - version.build = 'build.99.e0f985a' + version.build = "build.99.e0f985a" def test_immutable_unknown_attribute(version): # "no new attribute can be set" with pytest.raises(AttributeError): - version.new_attribute = 'forbidden' + version.new_attribute = "forbidden" def test_version_info_should_be_iterable(version): - assert tuple(version) == (version.major, version.minor, version.patch, - version.prerelease, version.build) + assert tuple(version) == ( + version.major, + version.minor, + version.patch, + version.prerelease, + version.build, + ) def test_should_compare_prerelease_and_build_with_numbers(): - assert VersionInfo(major=1, minor=9, patch=1, prerelease=1, build=1) < \ - VersionInfo(major=1, minor=9, patch=1, prerelease=2, build=1) + assert VersionInfo(major=1, minor=9, patch=1, prerelease=1, build=1) < VersionInfo( + 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 VersionInfo("2") < VersionInfo(10) + assert VersionInfo("2") < VersionInfo("10") def test_should_be_able_to_use_strings_as_major_minor_patch(): - v = VersionInfo('1', '2', '3') + v = VersionInfo("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 VersionInfo("1", "2", "3") == VersionInfo(1, 2, 3) def test_using_non_numeric_string_as_major_minor_patch_throws(): with pytest.raises(ValueError): - VersionInfo('a') + VersionInfo("a") with pytest.raises(ValueError): - VersionInfo(1, 'a') + VersionInfo(1, "a") with pytest.raises(ValueError): - VersionInfo(1, 2, 'a') + VersionInfo(1, 2, "a") def test_should_be_able_to_use_integers_as_prerelease_build(): v = VersionInfo(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') - - -@pytest.mark.parametrize("cli,expected", [ - (["bump", "major", "1.2.3"], - Namespace(which='bump', bump='major', version='1.2.3')), - (["bump", "minor", "1.2.3"], - Namespace(which='bump', bump='minor', version='1.2.3')), - (["bump", "patch", "1.2.3"], - Namespace(which='bump', bump='patch', version='1.2.3')), - (["bump", "prerelease", "1.2.3"], - Namespace(which='bump', bump='prerelease', version='1.2.3')), - (["bump", "build", "1.2.3"], - Namespace(which='bump', bump='build', version='1.2.3')), - # --- - (["compare", "1.2.3", "2.1.3"], - Namespace(which='compare', version1='1.2.3', version2='2.1.3')), -]) + assert VersionInfo(1, 2, 3, 4, 5) == VersionInfo(1, 2, 3, "4", "5") + + +@pytest.mark.parametrize( + "cli,expected", + [ + ( + ["bump", "major", "1.2.3"], + Namespace(which="bump", bump="major", version="1.2.3"), + ), + ( + ["bump", "minor", "1.2.3"], + Namespace(which="bump", bump="minor", version="1.2.3"), + ), + ( + ["bump", "patch", "1.2.3"], + Namespace(which="bump", bump="patch", version="1.2.3"), + ), + ( + ["bump", "prerelease", "1.2.3"], + Namespace(which="bump", bump="prerelease", version="1.2.3"), + ), + ( + ["bump", "build", "1.2.3"], + Namespace(which="bump", bump="build", version="1.2.3"), + ), + # --- + ( + ["compare", "1.2.3", "2.1.3"], + Namespace(which="compare", version1="1.2.3", version2="2.1.3"), + ), + ], +) def test_should_parse_cli_arguments(cli, expected): parser = createparser() assert parser @@ -644,26 +712,24 @@ def test_should_parse_cli_arguments(cli, expected): assert result == expected -@pytest.mark.parametrize("args,expected", [ - # bump subcommand - (Namespace(which='bump', bump='major', version='1.2.3'), - "2.0.0"), - (Namespace(which='bump', bump='minor', version='1.2.3'), - "1.3.0"), - (Namespace(which='bump', bump='patch', version='1.2.3'), - "1.2.4"), - (Namespace(which='bump', bump='prerelease', version='1.2.3-rc1'), - "1.2.3-rc2"), - (Namespace(which='bump', bump='build', version='1.2.3+build.13'), - "1.2.3+build.14"), - # compare subcommand - (Namespace(which='compare', version1='1.2.3', version2='2.1.3'), - "-1"), - (Namespace(which='compare', version1='1.2.3', version2='1.2.3'), - "0"), - (Namespace(which='compare', version1='2.4.0', version2='2.1.3'), - "1"), -]) +@pytest.mark.parametrize( + "args,expected", + [ + # bump subcommand + (Namespace(which="bump", bump="major", version="1.2.3"), "2.0.0"), + (Namespace(which="bump", bump="minor", version="1.2.3"), "1.3.0"), + (Namespace(which="bump", bump="patch", version="1.2.3"), "1.2.4"), + (Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"), "1.2.3-rc2"), + ( + Namespace(which="bump", bump="build", version="1.2.3+build.13"), + "1.2.3+build.14", + ), + # compare subcommand + (Namespace(which="compare", version1="1.2.3", version2="2.1.3"), "-1"), + (Namespace(which="compare", version1="1.2.3", version2="1.2.3"), "0"), + (Namespace(which="compare", version1="2.4.0", version2="2.1.3"), "1"), + ], +) def test_should_process_parsed_cli_arguments(args, expected): assert process(args) == expected @@ -692,20 +758,25 @@ def test_should_raise_systemexit_when_bump_iscalled_with_empty_arguments(): main(["bump"]) -@pytest.mark.parametrize("version,parts,expected", [ - ("3.4.5", dict(major=2), '2.4.5'), - ("3.4.5", dict(major="2"), '2.4.5'), - ("3.4.5", dict(major=2, minor=5), '2.5.5'), - ("3.4.5", dict(minor=2), '3.2.5'), - ("3.4.5", dict(major=2, minor=5, patch=10), '2.5.10'), - ("3.4.5", dict(major=2, minor=5, patch=10, prerelease="rc1"), - '2.5.10-rc1'), - ("3.4.5", dict(major=2, minor=5, patch=10, prerelease="rc1", build="b1"), - '2.5.10-rc1+b1'), - ("3.4.5-alpha.1.2", dict(major=2), '2.4.5-alpha.1.2'), - ("3.4.5-alpha.1.2", dict(build="x1"), '3.4.5-alpha.1.2+x1'), - ("3.4.5+build1", dict(major=2), '2.4.5+build1'), -]) +@pytest.mark.parametrize( + "version,parts,expected", + [ + ("3.4.5", dict(major=2), "2.4.5"), + ("3.4.5", dict(major="2"), "2.4.5"), + ("3.4.5", dict(major=2, minor=5), "2.5.5"), + ("3.4.5", dict(minor=2), "3.2.5"), + ("3.4.5", dict(major=2, minor=5, patch=10), "2.5.10"), + ("3.4.5", dict(major=2, minor=5, patch=10, prerelease="rc1"), "2.5.10-rc1"), + ( + "3.4.5", + dict(major=2, minor=5, patch=10, prerelease="rc1", build="b1"), + "2.5.10-rc1+b1", + ), + ("3.4.5-alpha.1.2", dict(major=2), "2.4.5-alpha.1.2"), + ("3.4.5-alpha.1.2", dict(build="x1"), "3.4.5-alpha.1.2+x1"), + ("3.4.5+build1", dict(major=2), "2.4.5+build1"), + ], +) def test_replace_method_replaces_requested_parts(version, parts, expected): assert replace(version, **parts) == expected @@ -715,18 +786,18 @@ def test_replace_raises_TypeError_for_invalid_keyword_arg(): assert replace("1.2.3", unknown="should_raise") -@pytest.mark.parametrize("version,parts,expected", [ - ("3.4.5", dict(major=2, minor=5), '2.5.5'), - ("3.4.5", dict(major=2, minor=5, patch=10), '2.5.10'), - ("3.4.5-alpha.1.2", dict(major=2), '2.4.5-alpha.1.2'), - ("3.4.5-alpha.1.2", dict(build="x1"), '3.4.5-alpha.1.2+x1'), - ("3.4.5+build1", dict(major=2), '2.4.5+build1'), -]) -def test_should_return_versioninfo_with_replaced_parts(version, - parts, - expected): - assert VersionInfo.parse(version).replace(**parts) == \ - VersionInfo.parse(expected) +@pytest.mark.parametrize( + "version,parts,expected", + [ + ("3.4.5", dict(major=2, minor=5), "2.5.5"), + ("3.4.5", dict(major=2, minor=5, patch=10), "2.5.10"), + ("3.4.5-alpha.1.2", dict(major=2), "2.4.5-alpha.1.2"), + ("3.4.5-alpha.1.2", dict(build="x1"), "3.4.5-alpha.1.2+x1"), + ("3.4.5+build1", dict(major=2), "2.4.5+build1"), + ], +) +def test_should_return_versioninfo_with_replaced_parts(version, parts, expected): + assert VersionInfo.parse(version).replace(**parts) == VersionInfo.parse(expected) def test_replace_raises_ValueError_for_non_numeric_values(): From 8169c12e4df4fd7a30c9b09fa75239ade47750c8 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Mon, 25 Nov 2019 10:24:21 +0100 Subject: [PATCH 144/312] Fix #203: Use --check option for black (infra) (#204) * Remove uploading the diff file as artifact * Use `--check` to test our source code --- .github/workflows/black-formatting.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/black-formatting.yml b/.github/workflows/black-formatting.yml index 83ad2892..25b34f21 100644 --- a/.github/workflows/black-formatting.yml +++ b/.github/workflows/black-formatting.yml @@ -39,11 +39,5 @@ jobs: - name: Run black id: black run: | - black . > project.diff + black --check . echo "::set-output name=rc::$?" - - - name: Upload diff artifact - uses: actions/upload-artifact@v1 - with: - name: black-project-diff - path: project.diff From 63b9163ed2837cffadbdfa5c777d6521a4e84761 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 7 Dec 2019 21:04:21 +0100 Subject: [PATCH 145/312] Improve consistency in tox.ini (#206) * Describe each target with `description` keyword; useful when running tox as `tox -a -v` * Move whitelist_externals to `textenv` section * Use `make` in `docs` target instead of running `sphinx-build` directly * Introduce black target to check for changes in formatting * Use `posargs` for flake8 --- tox.ini | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index abca346b..6f7d06e4 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ envlist = pypy [testenv] +description = Run test suite +whitelist_externals = make commands = pytest {posargs:} deps = pytest @@ -12,19 +14,27 @@ deps = setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 +[testenv:black] +description = Check for formatting changes +basepython = python3 +deps = black +commands = black --check {posargs:.} + [testenv:flake8] +description = Check code style basepython = python3 deps = flake8 -commands = flake8 +commands = flake8 {posargs:} [testenv:docs] +description = Build HTML documentation basepython = python3 deps = -r{toxinidir}/docs/requirements.txt skip_install = true -commands = sphinx-build {posargs:-E} -b html docs dist/docs +commands = make -C docs html [testenv:man] -whitelist_externals = make +description = Build the manpage basepython = python3 deps = sphinx skip_install = true From 445f47ab652ce9cc4f3b55e32733192a4cc7df7c Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 7 Dec 2019 23:04:23 +0100 Subject: [PATCH 146/312] Add python_requires keyword in setup.py (#207) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 77f19e73..992860f9 100755 --- a/setup.py +++ b/setup.py @@ -94,6 +94,7 @@ def read_file(filename): "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules", ], + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", tests_require=["tox", "virtualenv"], cmdclass={"clean": Clean, "test": Tox}, entry_points={"console_scripts": ["pysemver = semver:main"]}, From f2c23f63822077fde271fd8e876d608b8d69189f Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Tue, 10 Dec 2019 06:44:34 +0100 Subject: [PATCH 147/312] Fix #208: Introduce VersionInfo.isvalid() function (#209) * VersionInfo.isvalid(cls, version:str) -> bool * Add test case * Describe function in documentation * Amend pysemver script with "check" subcommand * Update manpage (pysemver.rst) * Update `CHANGELOG.rst` --- CHANGELOG.rst | 4 +++- docs/pysemver.rst | 24 ++++++++++++++++++- docs/usage.rst | 21 +++++++++++++++++ semver.py | 32 ++++++++++++++++++++++++- test_semver.py | 60 ++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 127 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index df88ddcc..8eb124d8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,9 @@ Features * :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 + Bug Fixes --------- @@ -54,7 +57,6 @@ Features * :pr:`166`: Reworked :file:`.gitignore` file * :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION` - Bug Fixes --------- diff --git a/docs/pysemver.rst b/docs/pysemver.rst index 17cd6554..881000e1 100644 --- a/docs/pysemver.rst +++ b/docs/pysemver.rst @@ -86,6 +86,29 @@ you get an error message and a return code != 0:: ERROR 1.5 is not valid SemVer string +pysemver check +~~~~~~~~~~~~~~ + +Checks if a string is a valid semver version. + +.. code:: bash + + pysemver check + +.. option:: + + The version string to check. + +The *error code* returned by the script indicates if the +version is valid (=0) or not (!=0):: + + $ pysemver check 1.2.3; echo $? + 0 + $ pysemver check 2.1; echo $? + ERROR Invalid version '2.1' + 2 + + pysemver compare ~~~~~~~~~~~~~~~~ @@ -121,7 +144,6 @@ are valid (return code 0) or not (return code != 0):: ERROR 1.2.x is not valid SemVer string 2 - See also -------- diff --git a/docs/usage.rst b/docs/usage.rst index 1ae7d8da..013391f0 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -53,6 +53,13 @@ A version can be created in different ways: >>> semver.VersionInfo(1, 2, 3, 4, 5) VersionInfo(major=1, minor=2, patch=3, prerelease=4, build=5) +If you pass an invalid version string you will get a ``ValueError``:: + + >>> semver.parse("1.2") + Traceback (most recent call last) + ... + ValueError: 1.2 is not valid SemVer string + Parsing a Version String ------------------------ @@ -77,6 +84,20 @@ Parsing a Version String {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} +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`: + +.. code-block:: python + + >>> VersionInfo.isvalid("1.0.0") + True + >>> VersionInfo.isvalid("invalid") + False + + Accessing Parts of a Version ---------------------------- diff --git a/semver.py b/semver.py index efe1102e..59c54f4c 100644 --- a/semver.py +++ b/semver.py @@ -321,6 +321,21 @@ def replace(self, **parts): ) raise TypeError(error) + @classmethod + def isvalid(cls, version): + """Check if the string is a valid semver version + + :param str version: the version string to check + :return: True if the version string is a valid semver version, False + otherwise. + :rtype: bool + """ + try: + cls.parse(version) + return True + except ValueError: + return False + def _to_dict(obj): if isinstance(obj, VersionInfo): @@ -681,6 +696,14 @@ def createparser(): 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(which="check") + parser_check.add_argument("version", help="Version to check") + return parser @@ -697,6 +720,7 @@ def process(args): if not hasattr(args, "which"): args.parser.print_help() raise SystemExit() + elif args.which == "bump": maptable = { "major": "bump_major", @@ -718,6 +742,11 @@ def process(args): elif args.which == "compare": return str(compare(args.version1, args.version2)) + elif args.which == "check": + if VersionInfo.isvalid(args.version): + return None + raise ValueError("Invalid version %r" % args.version) + def main(cliargs=None): """Entry point for the application script @@ -732,7 +761,8 @@ def main(cliargs=None): # Save parser instance: args.parser = parser result = process(args) - print(result) + if result is not None: + print(result) return 0 except (ValueError, TypeError) as err: diff --git a/test_semver.py b/test_semver.py index b3d0929d..8ca6985c 100644 --- a/test_semver.py +++ b/test_semver.py @@ -1,4 +1,5 @@ from argparse import Namespace +from contextlib import contextmanager import pytest # noqa from semver import ( @@ -41,6 +42,11 @@ ] +@contextmanager +def does_not_raise(item): + yield item + + @pytest.mark.parametrize( "string,expected", [("rc", "rc"), ("rc.1", "rc.2"), ("2x", "3x")] ) @@ -703,6 +709,8 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): ["compare", "1.2.3", "2.1.3"], Namespace(which="compare", version1="1.2.3", version2="2.1.3"), ), + # --- + (["check", "1.2.3"], Namespace(which="check", version="1.2.3")), ], ) def test_should_parse_cli_arguments(cli, expected): @@ -713,25 +721,50 @@ def test_should_parse_cli_arguments(cli, expected): @pytest.mark.parametrize( - "args,expected", + "args,expectation", [ # bump subcommand - (Namespace(which="bump", bump="major", version="1.2.3"), "2.0.0"), - (Namespace(which="bump", bump="minor", version="1.2.3"), "1.3.0"), - (Namespace(which="bump", bump="patch", version="1.2.3"), "1.2.4"), - (Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"), "1.2.3-rc2"), + ( + Namespace(which="bump", bump="major", version="1.2.3"), + does_not_raise("2.0.0"), + ), + ( + Namespace(which="bump", bump="minor", version="1.2.3"), + does_not_raise("1.3.0"), + ), + ( + Namespace(which="bump", bump="patch", version="1.2.3"), + does_not_raise("1.2.4"), + ), + ( + Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"), + does_not_raise("1.2.3-rc2"), + ), ( Namespace(which="bump", bump="build", version="1.2.3+build.13"), - "1.2.3+build.14", + does_not_raise("1.2.3+build.14"), ), # compare subcommand - (Namespace(which="compare", version1="1.2.3", version2="2.1.3"), "-1"), - (Namespace(which="compare", version1="1.2.3", version2="1.2.3"), "0"), - (Namespace(which="compare", version1="2.4.0", version2="2.1.3"), "1"), + ( + Namespace(which="compare", version1="1.2.3", version2="2.1.3"), + does_not_raise("-1"), + ), + ( + Namespace(which="compare", version1="1.2.3", version2="1.2.3"), + does_not_raise("0"), + ), + ( + Namespace(which="compare", version1="2.4.0", version2="2.1.3"), + does_not_raise("1"), + ), + # check subcommand + (Namespace(which="check", version="1.2.3"), does_not_raise(None)), + (Namespace(which="check", version="1.2"), pytest.raises(ValueError)), ], ) -def test_should_process_parsed_cli_arguments(args, expected): - assert process(args) == expected +def test_should_process_parsed_cli_arguments(args, expectation): + with expectation as expected: + assert process(args) == expected def test_should_process_print(capsys): @@ -803,3 +836,8 @@ def test_should_return_versioninfo_with_replaced_parts(version, parts, expected) def test_replace_raises_ValueError_for_non_numeric_values(): with pytest.raises(ValueError): VersionInfo.parse("1.2.3").replace(major="x") + + +def test_should_versioninfo_isvalid(): + assert VersionInfo.isvalid("1.0.0") is True + assert VersionInfo.isvalid("foo") is False From 177f08037aa6fc54a5db57dba01e0d8ea3bdc787 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 19 Jan 2020 19:37:47 +0100 Subject: [PATCH 148/312] Improve docstrings according to PEP257 (#212) * Use `docformatter` from https://github.com/myint/docformatter/ * Reformat `semver.py` to be compatible with PEP257 docstrings with `docformatter -i --pre-summary-newline semver.py` * `tox.ini` - Introduce `docstrings` target which calls `docformatter` to check for PEP257 compatible docstrings - Introduce a new `check` target which calls `black`, `flake8`, and `docstrings` * `.travis.yml` - Run `check` target instead of `flake8` - Add `before_install` section to install python3-dev package for black --- .travis.yml | 10 ++++- CHANGELOG.rst | 2 +- semver.py | 101 +++++++++++++++++++++++++++++++------------------- tox.ini | 18 +++++++++ 4 files changed, 90 insertions(+), 41 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0082ac4d..54165f6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,16 @@ # config file for automatic testing at travis-ci.org language: python cache: pip + +before_install: + sudo apt-get install -y python3-dev + install: - pip install --upgrade pip setuptools - pip install virtualenv tox + script: tox -v + matrix: include: - python: "2.7" @@ -13,8 +19,8 @@ matrix: - python: "3.4" env: TOXENV=py34 - - python: "3.4" - env: TOXENV=flake8 + - python: "3.6" + env: TOXENV=checks - python: "3.5" env: TOXENV=py35 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8eb124d8..aa89e107 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,7 +22,7 @@ Features * :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 - +* :pr:`212`: Improve docstrings according to PEP257 Bug Fixes --------- diff --git a/semver.py b/semver.py index 59c54f4c..41d315ea 100644 --- a/semver.py +++ b/semver.py @@ -1,6 +1,4 @@ -""" -Python helper for Semantic Versioning (http://semver.org/) -""" +"""Python helper for Semantic Versioning (http://semver.org/)""" from __future__ import print_function import argparse @@ -50,7 +48,8 @@ def cmp(a, b): def parse(version): - """Parse version to major, minor, patch, pre-release, build parts. + """ + Parse version to major, minor, patch, pre-release, build parts. :param version: version string :return: dictionary with the keys 'build', 'major', 'minor', 'patch', @@ -84,7 +83,7 @@ def parse(version): def comparator(operator): - """ Wrap a VersionInfo binary op method in a type-check """ + """Wrap a VersionInfo binary op method in a type-check.""" @wraps(operator) def wrapper(self, other): @@ -100,6 +99,8 @@ def wrapper(self, other): class VersionInfo(object): """ + A semver compatible version class. + :param int major: version when you make incompatible API changes. :param int minor: version when you add functionality in a backwards-compatible manner. @@ -119,7 +120,7 @@ def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): @property def major(self): - """The major part of a version""" + """The major part of a version.""" return self._major @major.setter @@ -128,7 +129,7 @@ def major(self, value): @property def minor(self): - """The minor part of a version""" + """The minor part of a version.""" return self._minor @minor.setter @@ -137,7 +138,7 @@ def minor(self, value): @property def patch(self): - """The patch part of a version""" + """The patch part of a version.""" return self._patch @patch.setter @@ -146,7 +147,7 @@ def patch(self, value): @property def prerelease(self): - """The prerelease part of a version""" + """The prerelease part of a version.""" return self._prerelease @prerelease.setter @@ -155,7 +156,7 @@ def prerelease(self, value): @property def build(self): - """The build part of a version""" + """The build part of a version.""" return self._build @build.setter @@ -183,8 +184,9 @@ def __iter__(self): yield v def bump_major(self): - """Raise the major part of the version, return a new object - but leave self untouched + """ + Raise the major part of the version, return a new object but leave self + untouched. :return: new object with the raised major part :rtype: VersionInfo @@ -196,8 +198,9 @@ def bump_major(self): return parse_version_info(bump_major(str(self))) def bump_minor(self): - """Raise the minor part of the version, return a new object - but leave self untouched + """ + Raise the minor part of the version, return a new object but leave self + untouched. :return: new object with the raised minor part :rtype: VersionInfo @@ -209,8 +212,9 @@ def bump_minor(self): return parse_version_info(bump_minor(str(self))) def bump_patch(self): - """Raise the patch part of the version, return a new object - but leave self untouched + """ + Raise the patch part of the version, return a new object but leave self + untouched. :return: new object with the raised patch part :rtype: VersionInfo @@ -222,8 +226,9 @@ def bump_patch(self): return parse_version_info(bump_patch(str(self))) def bump_prerelease(self, token="rc"): - """Raise the prerelease part of the version, return a new object - but leave self untouched + """ + Raise the prerelease part of the version, return a new object but leave + self untouched. :param token: defaults to 'rc' :return: new object with the raised prerelease part @@ -237,8 +242,9 @@ def bump_prerelease(self, token="rc"): return parse_version_info(bump_prerelease(str(self), token)) def bump_build(self, token="build"): - """Raise the build part of the version, return a new object - but leave self untouched + """ + Raise the build part of the version, return a new object but leave self + untouched. :param token: defaults to 'build' :return: new object with the raised build part @@ -287,7 +293,8 @@ def __hash__(self): @staticmethod def parse(version): - """Parse version string to a VersionInfo instance. + """ + Parse version string to a VersionInfo instance. :param version: version string :return: a :class:`semver.VersionInfo` instance @@ -323,7 +330,8 @@ def replace(self, **parts): @classmethod def isvalid(cls, version): - """Check if the string is a valid semver version + """ + Check if the string is a valid semver version. :param str version: the version string to check :return: True if the version string is a valid semver version, False @@ -346,7 +354,8 @@ def _to_dict(obj): def parse_version_info(version): - """Parse version string to a VersionInfo instance. + """ + Parse version string to a VersionInfo instance. :param version: version string :return: a :class:`VersionInfo` instance @@ -423,7 +432,8 @@ def _compare_by_keys(d1, d2): def compare(ver1, ver2): - """Compare two versions + """ + Compare two versions. :param ver1: version string 1 :param ver2: version string 2 @@ -445,7 +455,8 @@ def compare(ver1, ver2): def match(version, match_expr): - """Compare two versions through a comparison + """ + Compare two versions through a comparison. :param str version: a version string :param str match_expr: operator and version; valid operators are @@ -493,7 +504,8 @@ def match(version, match_expr): def max_ver(ver1, ver2): - """Returns the greater version of two versions + """ + Returns the greater version of two versions. :param ver1: version string 1 :param ver2: version string 2 @@ -511,7 +523,8 @@ def max_ver(ver1, ver2): def min_ver(ver1, ver2): - """Returns the smaller version of two versions + """ + Returns the smaller version of two versions. :param ver1: version string 1 :param ver2: version string 2 @@ -529,7 +542,8 @@ def min_ver(ver1, ver2): def format_version(major, minor, patch, prerelease=None, build=None): - """Format a version according to the Semantic Versioning specification + """ + Format a version according to the Semantic Versioning specification. :param int major: the required major part of a version :param int minor: the required minor part of a version @@ -555,6 +569,7 @@ def format_version(major, minor, patch, prerelease=None, build=None): def _increment_string(string): """ Look for the last sequence of number(s) in a string and increment, from: + http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 """ match = _LAST_NUMBER.search(string) @@ -566,7 +581,8 @@ def _increment_string(string): def bump_major(version): - """Raise the major part of the version + """ + Raise the major part of the version. :param: version string :return: the raised version string @@ -580,7 +596,8 @@ def bump_major(version): def bump_minor(version): - """Raise the minor part of the version + """ + Raise the minor part of the version. :param: version string :return: the raised version string @@ -594,7 +611,8 @@ def bump_minor(version): def bump_patch(version): - """Raise the patch part of the version + """ + Raise the patch part of the version. :param: version string :return: the raised version string @@ -608,7 +626,8 @@ def bump_patch(version): def bump_prerelease(version, token="rc"): - """Raise the prerelease part of the version + """ + Raise the prerelease part of the version. :param version: version string :param token: defaults to 'rc' @@ -628,7 +647,8 @@ def bump_prerelease(version, token="rc"): def bump_build(version, token="build"): - """Raise the build part of the version + """ + Raise the build part of the version. :param version: version string :param token: defaults to 'build' @@ -650,7 +670,8 @@ def bump_build(version, token="build"): def finalize_version(version): - """Remove any prerelease and build metadata from the version + """ + Remove any prerelease and build metadata from the version. :param version: version string :return: the finalized version string @@ -664,7 +685,8 @@ def finalize_version(version): def createparser(): - """Create an :class:`argparse.ArgumentParser` instance + """ + Create an :class:`argparse.ArgumentParser` instance. :return: parser instance :rtype: :class:`argparse.ArgumentParser` @@ -708,7 +730,8 @@ def createparser(): def process(args): - """Process the input from the CLI + """ + Process the input from the CLI. :param args: The parsed arguments :type args: :class:`argparse.Namespace` @@ -749,7 +772,8 @@ def process(args): def main(cliargs=None): - """Entry point for the application script + """ + Entry point for the application script. :param list cliargs: Arguments to parse or None (=use :class:`sys.argv`) :return: error code @@ -771,7 +795,8 @@ def main(cliargs=None): def replace(version, **parts): - """Replace one or more parts of a version and return the new string + """ + Replace one or more parts of a version and return the new string. :param str version: the version string to replace :param dict parts: the parts to be updated. Valid keys are: diff --git a/tox.ini b/tox.ini index 6f7d06e4..397c2440 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,24 @@ basepython = python3 deps = flake8 commands = flake8 {posargs:} +[testenv:docstrings] +description = Check for PEP257 compatible docstrings +basepython = python3 +deps = docformatter +commands = docformatter --check {posargs:--pre-summary-newline semver.py} + +[testenv:checks] +description = Run code style checks +basepython = python3 +deps = + {[testenv:black]deps} + {[testenv:flake8]deps} + {[testenv:docstrings]deps} +commands = + {[testenv:black]commands} + {[testenv:flake8]commands} + {[testenv:docstrings]commands} + [testenv:docs] description = Build HTML documentation basepython = python3 From 5755f9a58cbe0178f83e7fdff1464cafbe7fb7d2 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 19 Jan 2020 19:39:50 +0100 Subject: [PATCH 149/312] Improve pysemver subcommands (#214) * Use separate cmd_* functions for each subcommand * Replace which with func keyword * Use func keyword to store the specific cmd_* function * Adapt tests * (Re)format with black * Clarify return code in manpage (add new section "Return Code") => Easier to extend --- docs/pysemver.rst | 32 ++++++++++++++--- semver.py | 90 ++++++++++++++++++++++++++++++++--------------- test_semver.py | 74 ++++++++++++++++---------------------- 3 files changed, 118 insertions(+), 78 deletions(-) diff --git a/docs/pysemver.rst b/docs/pysemver.rst index 881000e1..3c247b62 100644 --- a/docs/pysemver.rst +++ b/docs/pysemver.rst @@ -133,17 +133,39 @@ to indicates which is the bigger version: * ``0`` if both versions are the same, * ``1`` if the first version is greater than the second version. -The *error code* returned by the script indicates if both versions -are valid (return code 0) or not (return code != 0):: + +Return Code +----------- + +The *return code* of the script (accessible by ``$?`` from the Bash) +indicates if the subcommand returned successfully nor not. It is *not* +meant as the result of the subcommand. + +The result of the subcommand is printed on the standard out channel +("stdout" or ``0``), any error messages to standard error ("stderr" or +``2``). + +For example, to compare two versions, the command expects two valid +semver versions:: $ pysemver compare 1.2.3 2.4.0 -1 - $ pysemver compare 1.2.3 2.4.0 ; echo $? + $ echo $? 0 - $ pysemver compare 1.2.3 2.4.0 ; echo $? - ERROR 1.2.x is not valid SemVer string + +The return code is zero, but the result is ``-1``. + +However, if you pass invalid versions, you get this situation:: + + $ pysemver compare 1.2.3 2.4 + ERROR 2.4 is not valid SemVer string + $ echo $? 2 +If you use the :command:`pysemver` in your own scripts, check the +return code first before you process the standard output. + + See also -------- diff --git a/semver.py b/semver.py index 41d315ea..3ae2e6f5 100644 --- a/semver.py +++ b/semver.py @@ -684,6 +684,61 @@ def finalize_version(version): return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"]) +def cmd_bump(args): + """ + Subcommand: Bumps a version. + + Synopsis: bump + can be major, minor, patch, prerelease, or build + + :param args: The parsed arguments + :type args: :class:`argparse.Namespace` + :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 = parse_version_info(args.version) + # get the respective method and call it + func = getattr(ver, maptable[args.bump]) + return str(func()) + + +def cmd_check(args): + """ + Subcommand: Checks if a string is a valid semver version. + + Synopsis: check + + :param args: The parsed arguments + :type args: :class:`argparse.Namespace` + """ + if VersionInfo.isvalid(args.version): + return None + raise ValueError("Invalid version %r" % args.version) + + +def cmd_compare(args): + """ + Subcommand: Compare two versions + + Synopsis: compare + + :param args: The parsed arguments + :type args: :class:`argparse.Namespace` + """ + return str(compare(args.version1, args.version2)) + + def createparser(): """ Create an :class:`argparse.ArgumentParser` instance. @@ -700,13 +755,13 @@ def createparser(): s = parser.add_subparsers() # create compare subcommand parser_compare = s.add_parser("compare", help="Compare two versions") - parser_compare.set_defaults(which="compare") + 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(which="bump") + parser_bump.set_defaults(func=cmd_bump) sb = parser_bump.add_subparsers(title="Bump commands", dest="bump") # Create subparsers for the bump subparser: @@ -723,7 +778,7 @@ def createparser(): parser_check = s.add_parser( "check", help="Checks if a string is a valid semver version" ) - parser_check.set_defaults(which="check") + parser_check.set_defaults(func=cmd_check) parser_check.add_argument("version", help="Version to check") return parser @@ -740,35 +795,12 @@ def process(args): :return: result of the selected action :rtype: str """ - if not hasattr(args, "which"): + if not hasattr(args, "func"): args.parser.print_help() raise SystemExit() - elif args.which == "bump": - 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([args.which, "-h"]) - - ver = parse_version_info(args.version) - # get the respective method and call it - func = getattr(ver, maptable[args.bump]) - return str(func()) - - elif args.which == "compare": - return str(compare(args.version1, args.version2)) - - elif args.which == "check": - if VersionInfo.isvalid(args.version): - return None - raise ValueError("Invalid version %r" % args.version) + # Call the respective function object: + return args.func(args) def main(cliargs=None): diff --git a/test_semver.py b/test_semver.py index 8ca6985c..ed7d80bc 100644 --- a/test_semver.py +++ b/test_semver.py @@ -9,6 +9,9 @@ bump_minor, bump_patch, bump_prerelease, + cmd_bump, + cmd_check, + cmd_compare, compare, createparser, finalize_version, @@ -684,87 +687,70 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): @pytest.mark.parametrize( "cli,expected", [ - ( - ["bump", "major", "1.2.3"], - Namespace(which="bump", bump="major", version="1.2.3"), - ), - ( - ["bump", "minor", "1.2.3"], - Namespace(which="bump", bump="minor", version="1.2.3"), - ), - ( - ["bump", "patch", "1.2.3"], - Namespace(which="bump", bump="patch", version="1.2.3"), - ), + (["bump", "major", "1.2.3"], Namespace(bump="major", version="1.2.3")), + (["bump", "minor", "1.2.3"], Namespace(bump="minor", version="1.2.3")), + (["bump", "patch", "1.2.3"], Namespace(bump="patch", version="1.2.3")), ( ["bump", "prerelease", "1.2.3"], - Namespace(which="bump", bump="prerelease", version="1.2.3"), - ), - ( - ["bump", "build", "1.2.3"], - Namespace(which="bump", bump="build", version="1.2.3"), + Namespace(bump="prerelease", version="1.2.3"), ), + (["bump", "build", "1.2.3"], Namespace(bump="build", version="1.2.3")), # --- - ( - ["compare", "1.2.3", "2.1.3"], - Namespace(which="compare", version1="1.2.3", version2="2.1.3"), - ), + (["compare", "1.2.3", "2.1.3"], Namespace(version1="1.2.3", version2="2.1.3")), # --- - (["check", "1.2.3"], Namespace(which="check", version="1.2.3")), + (["check", "1.2.3"], Namespace(version="1.2.3")), ], ) def test_should_parse_cli_arguments(cli, expected): parser = createparser() assert parser result = parser.parse_args(cli) + del result.func assert result == expected @pytest.mark.parametrize( - "args,expectation", + "func,args,expectation", [ # bump subcommand + (cmd_bump, Namespace(bump="major", version="1.2.3"), does_not_raise("2.0.0")), + (cmd_bump, Namespace(bump="minor", version="1.2.3"), does_not_raise("1.3.0")), + (cmd_bump, Namespace(bump="patch", version="1.2.3"), does_not_raise("1.2.4")), ( - Namespace(which="bump", bump="major", version="1.2.3"), - does_not_raise("2.0.0"), - ), - ( - Namespace(which="bump", bump="minor", version="1.2.3"), - does_not_raise("1.3.0"), - ), - ( - Namespace(which="bump", bump="patch", version="1.2.3"), - does_not_raise("1.2.4"), - ), - ( - Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"), + cmd_bump, + Namespace(bump="prerelease", version="1.2.3-rc1"), does_not_raise("1.2.3-rc2"), ), ( - Namespace(which="bump", bump="build", version="1.2.3+build.13"), + cmd_bump, + Namespace(bump="build", version="1.2.3+build.13"), does_not_raise("1.2.3+build.14"), ), # compare subcommand ( - Namespace(which="compare", version1="1.2.3", version2="2.1.3"), + cmd_compare, + Namespace(version1="1.2.3", version2="2.1.3"), does_not_raise("-1"), ), ( - Namespace(which="compare", version1="1.2.3", version2="1.2.3"), + cmd_compare, + Namespace(version1="1.2.3", version2="1.2.3"), does_not_raise("0"), ), ( - Namespace(which="compare", version1="2.4.0", version2="2.1.3"), + cmd_compare, + Namespace(version1="2.4.0", version2="2.1.3"), does_not_raise("1"), ), # check subcommand - (Namespace(which="check", version="1.2.3"), does_not_raise(None)), - (Namespace(which="check", version="1.2"), pytest.raises(ValueError)), + (cmd_check, Namespace(version="1.2.3"), does_not_raise(None)), + (cmd_check, Namespace(version="1.2"), pytest.raises(ValueError)), ], ) -def test_should_process_parsed_cli_arguments(args, expectation): +def test_should_process_parsed_cli_arguments(func, args, expectation): with expectation as expected: - assert process(args) == expected + result = func(args) + assert result == expected def test_should_process_print(capsys): From b5ccad691c396a5a34ad3a18486f451e00377da0 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 19 Jan 2020 19:48:36 +0100 Subject: [PATCH 150/312] Fix #210: how to deal with invalid versions (#215) * Document how to deal with invalid versions * Use coerce(version) as an example * Update CHANGELOG.rst Co-authored-by: scls19fr --- CHANGELOG.rst | 1 + docs/usage.rst | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa89e107..474cdc8a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,6 +22,7 @@ Features * :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 diff --git a/docs/usage.rst b/docs/usage.rst index 013391f0..f7b5da39 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -334,3 +334,75 @@ Getting Minimum and Maximum of two Versions '2.0.0' >>> semver.min_ver("1.0.0", "2.0.0") '1.0.0' + + +Dealing with Invalid Versions +----------------------------- + +As semver follows the semver specification, it cannot parse version +strings which are considered "invalid" by that specification. The semver +library cannot know all the possible variations so you need to help the +library a bit. + +For example, if you have a version string ``v1.2`` would be an invalid +semver version. +However, "basic" version strings consisting of major, minor, +and patch part, can be easy to convert. The following function extract this +information and returns a tuple with two items: + +.. code-block:: python + + import re + + BASEVERSION = re.compile( + r"""[vV]? + (?P0|[1-9]\d*) + (\. + (?P0|[1-9]\d*) + (\. + (?P0|[1-9]\d*) + )? + )? + """, + re.VERBOSE, + ) + def coerce(version): + """ + Convert an incomplete version string into a semver-compatible VersionInfo + object + + * Tries to detect a "basic" version string (``major.minor.patch``). + * If not enough components can be found, missing components are + 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`` + 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) + """ + match = BASEVERSION.search(version) + if not match: + return (None, version) + + ver = { + key: 0 if value is None else value + for key, value in match.groupdict().items() + } + ver = semver.VersionInfo(**ver) + rest = match.string[match.end() :] + return ver, rest + +The function returns a *tuple*, containing a :class:`VersionInfo` +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. + +For example: + +.. code-block:: python + + >>> coerce("v1.2") + (VersionInfo(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') + From 3f92aa5494252387807fefc6083c090cbc67098d Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sun, 16 Feb 2020 20:30:07 +0100 Subject: [PATCH 151/312] Create semver version 2.9.1 (#219) * Raise version number in `__version__` * Update CHANGELOG * Mention TestPyPI in `release-procedure.md` * MANIFEST.in: * Exclude `.travis.yml` * Exclude `.github` directory (pretty useless in an archive/wheel) * Exclude `docs/_build` directory * Exclude temporary Python files like `__pycache__`, `*.py[cod]` * Include all `*.txt` and `*.rst` files Co-authored-by: Sebastien Celles Co-authored-by: scls19fr --- CHANGELOG.rst | 12 ++++++++---- MANIFEST.in | 10 ++++++++-- release-procedure.md | 13 +++++++++++++ semver.py | 4 ++-- setup.py | 1 + 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 474cdc8a..a10cc03a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,10 +6,11 @@ Change Log All notable changes to this code base will be documented in this file, in every released version. -Version 2.9.1 (WIP) -=================== -:Released: 20xy-xy-xy -:Maintainer: ... + +Version 2.9.1 +============= +:Released: 2020-02-16 +:Maintainer: Tom Schraitle Features -------- @@ -34,6 +35,9 @@ Bug Fixes Removals -------- +not available + + Version 2.9.0 ============= :Released: 2019-10-30 diff --git a/MANIFEST.in b/MANIFEST.in index 9ed3b402..80257f1f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,9 @@ -include README.rst -include LICENSE.txt +include *.rst +include *.txt include test_*.py + +exclude .travis.yml +prune docs/_build +recursive-exclude .github * + +global-exclude *.py[cod] __pycache__ *.so *.dylib diff --git a/release-procedure.md b/release-procedure.md index 52a43fd5..8d223fd3 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -21,6 +21,19 @@ Release procedure * Ensure that long description (ie [README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` +* Upload it to TestPyPI first: + +```bash +git clean -xfd +python setup.py register sdist bdist_wheel --universal +twine upload --repository-url https://test.pypi.org/legacy/ dist/* +``` + + If you have a `~/.pypirc` with a `testpyi` section, the upload can be + simplified: + + twine upload --repository testpyi dist/* + * Upload to PyPI ```bash diff --git a/semver.py b/semver.py index 3ae2e6f5..46f3a2e5 100644 --- a/semver.py +++ b/semver.py @@ -8,10 +8,10 @@ import sys -__version__ = "2.9.0" +__version__ = "2.9.1" __author__ = "Kostiantyn Rybnikov" __author_email__ = "k-bx@k-bx.com" -__maintainer__ = "Sebastien Celles" +__maintainer__ = ["Sebastien Celles", "Tom Schraitle"] __maintainer_email__ = "s.celles@gmail.com" _REGEX = re.compile( diff --git a/setup.py b/setup.py index 992860f9..d73690ee 100755 --- a/setup.py +++ b/setup.py @@ -80,6 +80,7 @@ def read_file(filename): 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", From 687891655d548f87d7404718bb7f420b586cf8f3 Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Sat, 29 Feb 2020 22:37:21 +0100 Subject: [PATCH 152/312] Fix #224: Replace super() call (#226) In class clean, replace super(CleanCommand, self).run() with CleanCommand.run(self) Co-authored-by: Dennis Menschel Co-authored-by: Dennis Menschel --- CHANGELOG.rst | 19 +++++++++++++++++++ setup.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a10cc03a..03a18b7f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,25 @@ Change Log All notable changes to this code base will be documented in this file, in every released version. +Version 2.9.x (WIP) +=================== + +:Released: 2020-xx-yy +:Maintainer: + +Features +-------- + +Bug Fixes +--------- + +* :gh:`224` (:pr:`226`): Replaced in class ``clean``, ``super(CleanCommand, self).run()`` with + ``CleanCommand.run(self)`` + + +Removals +-------- + Version 2.9.1 ============= diff --git a/setup.py b/setup.py index d73690ee..15829461 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ def run_tests(self): class Clean(CleanCommand): def run(self): - super(CleanCommand, self).run() + CleanCommand.run(self) delete_in_root = ["build", ".cache", "dist", ".eggs", "*.egg-info", ".tox"] delete_everywhere = ["__pycache__", "*.pyc"] for candidate in delete_in_root: From 6669ba055e166cf4f32b30ebbef43ad634acc7c8 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Sat, 14 Mar 2020 17:37:16 +0100 Subject: [PATCH 153/312] Integrate better doctest integration into pytest * Fix typos in `README.rst`. * Move `coerce()` function into separate file; this was needed so it can be both included into the documentation and inside `conftest.py`. * In `docs/usage.rst`: - Fix typos - Add missing semver module name as prefix - Slightly rewrite some doctests which involves dicts (unfortunately, order matters still in Python2) * In `setup.cfg`: - Add `--doctest-glob` option to look for all `*.rst` files. - Add `testpaths` key to restrict testing paths to current dir and `docs`. * Update `CHANGELOG.rst` --- CHANGELOG.rst | 9 ++++-- README.rst | 4 +-- conftest.py | 6 ++++ docs/coerce.py | 42 ++++++++++++++++++++++++ docs/development.rst | 1 + docs/usage.rst | 77 +++++++++++--------------------------------- setup.cfg | 5 +-- 7 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 docs/coerce.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 03a18b7f..2ae377d9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,10 +18,15 @@ Features Bug Fixes --------- -* :gh:`224` (:pr:`226`): Replaced in class ``clean``, ``super(CleanCommand, self).run()`` with - ``CleanCommand.run(self)`` +* :gh:`224` (:pr:`226`): In ``setup.py``, replaced in class ``clean``, + ``super(CleanCommand, self).run()`` with ``CleanCommand.run(self)`` +Additions +--------- + +* :pr:`228`: Added better doctest integration + Removals -------- diff --git a/README.rst b/README.rst index 5c35b423..862fbdc6 100644 --- a/README.rst +++ b/README.rst @@ -68,7 +68,7 @@ different parts, use the `semver.parse` function: >>> ver['prerelease'] 'pre.2' >>> ver['build'] - 'build.5' + 'build.4' To raise parts of a version, there are a couple of functions available for you. The `semver.parse_version_info` function converts a version string @@ -87,7 +87,7 @@ It is allowed to concatenate different "bump functions": .. code-block:: python >>> ver.bump_major().bump_minor() - VersionInfo(major=4, minor=0, patch=1, prerelease=None, build=None) + VersionInfo(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/conftest.py b/conftest.py index 4f49a137..3e05cb52 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,13 @@ import pytest import semver +import sys + +sys.path.insert(0, "docs") + +from coerce import coerce # noqa:E402 @pytest.fixture(autouse=True) def add_semver(doctest_namespace): doctest_namespace["semver"] = semver + doctest_namespace["coerce"] = coerce diff --git a/docs/coerce.py b/docs/coerce.py new file mode 100644 index 00000000..3e5eb21b --- /dev/null +++ b/docs/coerce.py @@ -0,0 +1,42 @@ +import re +import semver + +BASEVERSION = re.compile( + r"""[vV]? + (?P0|[1-9]\d*) + (\. + (?P0|[1-9]\d*) + (\. + (?P0|[1-9]\d*) + )? + )? + """, + re.VERBOSE, +) + + +def coerce(version): + """ + Convert an incomplete version string into a semver-compatible VersionInfo + object + + * Tries to detect a "basic" version string (``major.minor.patch``). + * If not enough components can be found, missing components are + 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`` + 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) + """ + match = BASEVERSION.search(version) + if not match: + return (None, version) + + ver = { + key: 0 if value is None else value for key, value in match.groupdict().items() + } + ver = semver.VersionInfo(**ver) + rest = match.string[match.end() :] # noqa:E203 + return ver, rest diff --git a/docs/development.rst b/docs/development.rst index 7ad63d74..dad14641 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -174,6 +174,7 @@ documentation includes: 1 >>> semver.compare("2.0.0", "2.0.0") 0 + """ * **The documentation** diff --git a/docs/usage.rst b/docs/usage.rst index f7b5da39..dd05fa1c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -51,12 +51,12 @@ A version can be created in different ways: integers:: >>> semver.VersionInfo(1, 2, 3, 4, 5) - VersionInfo(major=1, minor=2, patch=3, prerelease=4, build=5) + VersionInfo(major=1, minor=2, patch=3, prerelease='4', build='5') If you pass an invalid version string you will get a ``ValueError``:: >>> semver.parse("1.2") - Traceback (most recent call last) + Traceback (most recent call last): ... ValueError: 1.2 is not valid SemVer string @@ -80,8 +80,8 @@ Parsing a Version String * With :func:`semver.parse`:: - >>> semver.parse("3.4.5-pre.2+build.4") - {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + >>> semver.parse("3.4.5-pre.2+build.4") == {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + True Checking for a Valid Semver Version @@ -92,9 +92,9 @@ classmethod :func:`semver.VersionInfo.isvalid`: .. code-block:: python - >>> VersionInfo.isvalid("1.0.0") + >>> semver.VersionInfo.isvalid("1.0.0") True - >>> VersionInfo.isvalid("invalid") + >>> semver.VersionInfo.isvalid("invalid") False @@ -106,7 +106,7 @@ parts of a version: .. code-block:: python - >>> v = VersionInfo.parse("3.4.5-pre.2+build.4") + >>> v = semver.VersionInfo.parse("3.4.5-pre.2+build.4") >>> v.major 3 >>> v.minor @@ -122,20 +122,20 @@ However, the attributes are read-only. You cannot change an attribute. If you do, you get an ``AttributeError``:: >>> v.minor = 5 - Traceback (most recent call last) + Traceback (most recent call last): ... AttributeError: attribute 'minor' is readonly In case you need the different parts of a version stepwise, iterate over the :class:`semver.VersionInfo` instance:: - >>> for item in VersionInfo.parse("3.4.5-pre.2+build.4"): + >>> for item in semver.VersionInfo.parse("3.4.5-pre.2+build.4"): ... print(item) 3 4 5 pre.2 build.4 - >>> list(VersionInfo.parse("3.4.5-pre.2+build.4")) + >>> list(semver.VersionInfo.parse("3.4.5-pre.2+build.4")) [3, 4, 5, 'pre.2', 'build.4'] @@ -160,12 +160,12 @@ unmodified, use one of the functions :func:`semver.replace` or If you pass invalid keys you get an exception:: >>> semver.replace("1.2.3", invalidkey=2) - Traceback (most recent call last) + 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.replace(invalidkey=2) - Traceback (most recent call last) + Traceback (most recent call last): ... TypeError: replace() got 1 unexpected keyword argument(s): invalidkey @@ -209,8 +209,8 @@ Depending which function you call, you get different types * From a :class:`semver.VersionInfo` into a dictionary:: >>> v = semver.VersionInfo(major=3, minor=4, patch=5) - >>> semver.parse(str(v)) - {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} + >>> semver.parse(str(v)) == {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} + True Increasing Parts of a Version @@ -267,8 +267,8 @@ To compare two versions depends on your type: Use the specific operator. Currently, the operators ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: - >>> v1 = VersionInfo.parse("3.4.5") - >>> v2 = VersionInfo.parse("3.5.1") + >>> v1 = semver.VersionInfo.parse("3.4.5") + >>> v2 = semver.VersionInfo.parse("3.5.1") >>> v1 < v2 True >>> v1 > v2 @@ -278,7 +278,7 @@ To compare two versions depends on your type: Use the operator as with two :class:`semver.VersionInfo` types:: - >>> v = VersionInfo.parse("3.4.5") + >>> v = semver.VersionInfo.parse("3.4.5") >>> v > (1, 0) True >>> v < (3, 5) @@ -350,48 +350,9 @@ However, "basic" version strings consisting of major, minor, and patch part, can be easy to convert. The following function extract this information and returns a tuple with two items: -.. code-block:: python +.. literalinclude:: coerce.py + :language: python - import re - - BASEVERSION = re.compile( - r"""[vV]? - (?P0|[1-9]\d*) - (\. - (?P0|[1-9]\d*) - (\. - (?P0|[1-9]\d*) - )? - )? - """, - re.VERBOSE, - ) - def coerce(version): - """ - Convert an incomplete version string into a semver-compatible VersionInfo - object - - * Tries to detect a "basic" version string (``major.minor.patch``). - * If not enough components can be found, missing components are - 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`` - 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) - """ - match = BASEVERSION.search(version) - if not match: - return (None, version) - - ver = { - key: 0 if value is None else value - for key, value in match.groupdict().items() - } - ver = semver.VersionInfo(**ver) - rest = match.string[match.end() :] - return ver, rest The function returns a *tuple*, containing a :class:`VersionInfo` instance or None as the first element and the rest as the second element. diff --git a/setup.cfg b/setup.cfg index b8a0ea49..6ab7e562 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,11 @@ [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ +testpaths = . docs addopts = - --ignore=.eggs/ --no-cov-on-fail --cov=semver --cov-report=term-missing + --doctest-glob='*.rst' --doctest-modules --doctest-report ndiff @@ -17,4 +18,4 @@ exclude = .git, __pycache__, build, - dist \ No newline at end of file + dist From 1e6a25180ea6b01ef109e9e65a0460cf60469eaf Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Mon, 16 Mar 2020 10:59:17 +0100 Subject: [PATCH 154/312] 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) --- semver.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/semver.py b/semver.py index 46f3a2e5..aca7242e 100644 --- a/semver.py +++ b/semver.py @@ -307,8 +307,12 @@ def parse(version): return parse_version_info(version) def replace(self, **parts): - """Replace one or more parts of a version and return a new - :class:`semver.VersionInfo` object, but leave self untouched + """ + Replace one or more parts of a version and return a new + :class:`semver.VersionInfo` object, but leave self untouched + + .. versionadded:: 2.9.0 + Added :func:`VersionInfo.replace` :param dict parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` @@ -333,6 +337,8 @@ def isvalid(cls, version): """ Check if the string is a valid semver version. + .. versionadded:: 2.9.1 + :param str version: the version string to check :return: True if the version string is a valid semver version, False otherwise. @@ -357,6 +363,9 @@ def parse_version_info(version): """ Parse version string to a VersionInfo instance. + .. versionadded:: 2.7.2 + Added :func:`parse_version_info` + :param version: version string :return: a :class:`VersionInfo` instance :rtype: :class:`VersionInfo` @@ -433,7 +442,7 @@ def _compare_by_keys(d1, d2): def compare(ver1, ver2): """ - Compare two versions. + Compare two versions strings. :param ver1: version string 1 :param ver2: version string 2 @@ -456,7 +465,7 @@ def compare(ver1, ver2): def match(version, match_expr): """ - Compare two versions through a comparison. + Compare two versions strings through a comparison. :param str version: a version string :param str match_expr: operator and version; valid operators are @@ -505,7 +514,7 @@ def match(version, match_expr): def max_ver(ver1, ver2): """ - Returns the greater version of two versions. + Returns the greater version of two versions strings. :param ver1: version string 1 :param ver2: version string 2 @@ -524,7 +533,7 @@ def max_ver(ver1, ver2): def min_ver(ver1, ver2): """ - Returns the smaller version of two versions. + Returns the smaller version of two versions strings. :param ver1: version string 1 :param ver2: version string 2 @@ -543,7 +552,7 @@ def min_ver(ver1, ver2): def format_version(major, minor, patch, prerelease=None, build=None): """ - Format a version according to the Semantic Versioning specification. + Format a version string according to the Semantic Versioning specification. :param int major: the required major part of a version :param int minor: the required minor part of a version @@ -582,7 +591,7 @@ def _increment_string(string): def bump_major(version): """ - Raise the major part of the version. + Raise the major part of the version string. :param: version string :return: the raised version string @@ -597,7 +606,7 @@ def bump_major(version): def bump_minor(version): """ - Raise the minor part of the version. + Raise the minor part of the version string. :param: version string :return: the raised version string @@ -612,7 +621,7 @@ def bump_minor(version): def bump_patch(version): """ - Raise the patch part of the version. + Raise the patch part of the version string. :param: version string :return: the raised version string @@ -627,7 +636,7 @@ def bump_patch(version): def bump_prerelease(version, token="rc"): """ - Raise the prerelease part of the version. + Raise the prerelease part of the version string. :param version: version string :param token: defaults to 'rc' @@ -648,7 +657,7 @@ def bump_prerelease(version, token="rc"): def bump_build(version, token="build"): """ - Raise the build part of the version. + Raise the build part of the version string. :param version: version string :param token: defaults to 'build' @@ -671,7 +680,10 @@ def bump_build(version, token="build"): def finalize_version(version): """ - Remove any prerelease and build metadata from the version. + Remove any prerelease and build metadata from the version string. + + .. versionadded:: 2.7.9 + Added :func:`finalize_version` :param version: version string :return: the finalized version string @@ -830,6 +842,9 @@ def replace(version, **parts): """ Replace one or more parts of a version and return the new string. + .. versionadded:: 2.9.0 + Added :func:`replace` + :param str version: the version string to replace :param dict parts: the parts to be updated. Valid keys are: ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` From 5b8bb16cbd124ae7bf79a11abd61743f5d564c71 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Sun, 15 Mar 2020 18:54:48 +0100 Subject: [PATCH 155/312] Fix #225: Deprecate module level functions * Add test cases - Add additional test case for "check" - test_should_process_check_iscalled_with_valid_version - Test also missing finalize_version - Test the warning more thoroughly with pytest.warns instead of just pytest.deprecated_call * In `setup.cfg`, add deprecation warnings filter for pytest * Implement DeprecationWarning with warnings module and the new decorator `deprecated` * Output a DeprecationWarning for the following functions: - semver.bump_{major,minor,patch,prerelease,build} - semver.format_version - semver.finalize_version - semver.parse - semver.parse_version_info - semver.replace - semver.VersionInfo._asdict - semver.VersionInfo._astuple Add also a deprecation notice in the docstrings of these functions * Introduce new public functions: - semver.VersionInfo.to_dict (from former _asdict) - semver.VersionInfo.to_tuple (from former _astuple) - Keep _asdict and _astuple as a (deprecated) function for compatibility reasons * Update CHANGELOG.rst * Update usage documentation: - Move some information to make them more useful for for the reader - Add deprecation warning - Explain how to replace deprecated functions - Explain how to display deprecation warnings from semver * Improve documentation of deprecated functions - List deprecated module level functions - Make recommendation and show equivalent code - Mention that deprecated functions will be replaced in semver 3. That means, all deprecated function will be still available in semver 2.x.y. * Move _increment_string into VersionInfo class - Makes removing deprecating functions easier as, for example, bump_prerelease is no longer dependant from an "external" function. - Move _LAST_NUMBER regex into VersionInfo class - Implement _increment_string as a staticmethod Co-authored-by: Karol Co-authored-by: scls19fr Co-authored-by: George Sakkis --- .gitignore | 8 +- CHANGELOG.rst | 16 ++ docs/usage.rst | 254 ++++++++++++++++++++++++------ semver.py | 408 ++++++++++++++++++++++++++++++++----------------- setup.cfg | 2 + test_semver.py | 58 ++++++- 6 files changed, 556 insertions(+), 190 deletions(-) diff --git a/.gitignore b/.gitignore index 994eb868..2ef76af8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -# Files +# Patch/Diff Files *.patch *.diff -*.kate-swp # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] .pytest_cache/ +*$py.class # Distribution / packaging .cache @@ -72,3 +72,7 @@ docs/_build/ # PyBuilder target/ + +# Backup files +*~ +*.kate-swp diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2ae377d9..48e3d82b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,8 +27,24 @@ Additions * :pr:`228`: Added better doctest integration + Removals -------- +* :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 diff --git a/docs/usage.rst b/docs/usage.rst index dd05fa1c..cdf08b9a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -14,9 +14,10 @@ are met. Knowing the Implemented semver.org Version ------------------------------------------ -The semver.org is the authorative specification of how semantical versioning is -definied. To know which version of semver.org is implemented in the semver -libary, use the following constant:: +The semver.org page is the authorative specification of how semantical +versioning is definied. +To know which version of semver.org is implemented in the semver libary, +use the following constant:: >>> semver.SEMVER_SPEC_VERSION '2.0.0' @@ -25,35 +26,81 @@ libary, use the following constant:: Creating a Version ------------------ -A version can be created in different ways: +Due to historical reasons, the semver project offers two ways of +creating a version: -* as a complete version string:: +* through an object oriented approach with the :class:`semver.VersionInfo` + class. This is the preferred method when using semver. + +* through module level functions and builtin datatypes (usually strings + and dicts). + These method are still available for compatibility reasons, but are + marked as deprecated. Using one of these will emit a DeprecationWarning. + + +.. warning:: **Deprecation Warning** + + Module level functions are marked as *deprecated* in version 2.9.2 now. + These functions will be removed in semver 3. + For details, see the sections :ref:`sec_replace_deprecated_functions` and + :ref:`sec_display_deprecation_warnings`. + + +A :class:`semver.VersionInfo` instance can be created in different ways: + + +* From a string:: - >>> semver.parse_version_info("3.4.5-pre.2+build.4") - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') -* with individual parts:: +* From individual parts by a dictionary:: - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - >>> semver.VersionInfo(3, 5) - VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + >>> 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') + + As a minimum requirement, your dictionary needs at least the ``major`` + key, others can be omitted. You get a ``TypeError`` if your + dictionary contains invalid keys. + Only the keys ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` + are allowed. + +* From a tuple:: + + >>> t = (3, 5, 6) + >>> semver.VersionInfo(*t) + VersionInfo(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") - VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) + >>> semver.VersionInfo("3", "5", 6) + VersionInfo(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. + +Depending on your use case, the following methods are available: + +* From individual version parts into a string + + In some cases you only need a string from your version data:: + + >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') + '3.4.5-pre.2+build.4' + +* From a string into a dictionary - In the simplest form, ``prerelease`` and ``build`` can also be - integers:: + To access individual parts, you can use the function :func:`semver.parse`:: - >>> semver.VersionInfo(1, 2, 3, 4, 5) - VersionInfo(major=1, minor=2, patch=3, prerelease='4', build='5') + >>> semver.parse("3.4.5-pre.2+build.4") + OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) -If you pass an invalid version string you will get a ``ValueError``:: + If you pass an invalid version string you will get a ``ValueError``:: >>> semver.parse("1.2") Traceback (most recent call last): @@ -172,45 +219,30 @@ If you pass invalid keys you get an exception:: .. _sec.convert.versions: -Converting Different Version Types ----------------------------------- +Converting a VersionInfo instance into Different Types +------------------------------------------------------ -Depending which function you call, you get different types -(as explained in the beginning of this chapter). +Sometimes it is needed to convert a :class:`semver.VersionInfo` instance into +a different type. For example, for displaying or to access all parts. -* From a string into :class:`semver.VersionInfo`:: - - >>> semver.VersionInfo.parse("3.4.5-pre.2+build.4") - VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') +It is possible to convert a :class:`semver.VersionInfo` instance: -* From :class:`semver.VersionInfo` into a string:: +* Into a string with the builtin function :func:`str`:: >>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4")) '3.4.5-pre.2+build.4' -* From a dictionary into :class:`semver.VersionInfo`:: +* Into a dictionary with :func:`semver.VersionInfo.to_dict`:: - >>> 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') - - As a minimum requirement, your dictionary needs at least the ``major`` - key, others can be omitted. You get a ``TypeError`` if your - dictionary contains invalid keys. - Only ``major``, ``minor``, ``patch``, ``prerelease``, and ``build`` - are allowed. - -* From a tuple into :class:`semver.VersionInfo`:: - - >>> t = (3, 5, 6) - >>> semver.VersionInfo(*t) - VersionInfo(major=3, minor=5, patch=6, prerelease=None, build=None) + >>> v = semver.VersionInfo(major=3, minor=4, patch=5) + >>> v.to_dict() + OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) -* From a :class:`semver.VersionInfo` into a dictionary:: +* Into a tuple with :func:`semver.VersionInfo.to_tuple`:: - >>> v = semver.VersionInfo(major=3, minor=4, patch=5) - >>> semver.parse(str(v)) == {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': None, 'build': None} - True + >>> v = semver.VersionInfo(major=5, minor=4, patch=2) + >>> v.to_tuple() + (5, 4, 2, None, None) Increasing Parts of a Version @@ -362,8 +394,132 @@ For example: .. code-block:: python - >>> coerce("v1.2") + >>> coerce("v1.2") (VersionInfo(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') + +.. _sec_replace_deprecated_functions: + +Replacing Deprecated Functions +------------------------------ + +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. + +The deprecated functions can still be used in version 2.x.y. In version 3 of +semver, the deprecated functions will be removed. + +The following list shows the deprecated functions and how you can replace +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` + class. + For example, the function :func:`semver.bump_major` is replaced by + :func:`semver.VersionInfo.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()) + >>> s1 == s2 + True + + Likewise with the other module level functions. + +* :func:`semver.finalize_version` + + Replace it with :func:`semver.VersionInfo.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()) + >>> s1 == s2 + True + +* :func:`semver.format_version` + + Replace it with ``str(versionobject)``: + + .. 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')) + >>> s1 == s2 + True + +* :func:`semver.parse` + + Replace it with :func:`semver.VersionInfo.parse` and + :func:`semver.VersionInfo.to_dict`: + + .. code-block:: python + + >>> v1 = semver.parse("1.2.3") + >>> v2 = semver.VersionInfo.parse("1.2.3").to_dict() + >>> v1 == v2 + True + +* :func:`semver.parse_version_info` + + Replace it with :func:`semver.VersionInfo.parse`: + + .. code-block:: python + + >>> v1 = semver.parse_version_info("3.4.5") + >>> v2 = semver.VersionInfo.parse("3.4.5") + >>> v1 == v2 + True + +* :func:`semver.replace` + + Replace it with :func:`semver.VersionInfo.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)) + >>> s1 == s2 + True + + +.. _sec_display_deprecation_warnings: + +Displaying Deprecation Warnings +------------------------------- + +By default, deprecation warnings are `ignored in Python `_. +This also affects semver's own warnings. + +It is recommended that you turn on deprecation warnings in your scripts. Use one of +the following methods: + +* Use the option `-Wd `_ + to enable default warnings: + + * Directly running the Python command:: + + $ python3 -Wd scriptname.py + + * Add the option in the shebang line (something like ``#!/usr/bin/python3``) + after the command:: + + #!/usr/bin/python3 -Wd + +* In your own scripts add a filter to ensure that *all* warnings are displayed: + + .. code-block:: python + + import warnings + warnings.simplefilter("default") + # Call your semver code + + For further details, see the section + `Overriding the default filter `_ + of the Python documentation. diff --git a/semver.py b/semver.py index aca7242e..aec5e9ef 100644 --- a/semver.py +++ b/semver.py @@ -3,9 +3,11 @@ import argparse import collections -from functools import wraps +from functools import wraps, partial +import inspect import re import sys +import warnings __version__ = "2.9.1" @@ -14,28 +16,6 @@ __maintainer__ = ["Sebastien Celles", "Tom Schraitle"] __maintainer_email__ = "s.celles@gmail.com" -_REGEX = re.compile( - r""" - ^ - (?P0|[1-9]\d*) - \. - (?P0|[1-9]\d*) - \. - (?P0|[1-9]\d*) - (?:-(?P - (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) - (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* - ))? - (?:\+(?P - [0-9a-zA-Z-]+ - (?:\.[0-9a-zA-Z-]+)* - ))? - $ - """, - re.VERBOSE, -) - -_LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") #: Contains the implemented semver.org version of the spec SEMVER_SPEC_VERSION = "2.0.0" @@ -47,10 +27,66 @@ def cmp(a, b): return (a > b) - (a < b) +def deprecated(func=None, replace=None, version=None, category=DeprecationWarning): + """ + Decorates a function to output a deprecation warning. + + This function will be removed once major version 3 of semver is + released. + + :param str replace: the function to replace (use the full qualified + name like ``semver.VersionInfo.bump_major``. + :param str 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. + """ + + if func is None: + return partial(deprecated, replace=replace, version=version, category=category) + + @wraps(func) + def wrapper(*args, **kwargs): + msg = ["Function '{m}.{f}' is deprecated."] + + if version: + msg.append("Deprecated since version {v}. ") + msg.append("This function will be removed in semver 3.") + if replace: + msg.append("Use {r!r} instead.") + else: + msg.append("Use the respective 'semver.VersionInfo.{r}' instead.") + + # hasattr is needed for Python2 compatibility: + f = func.__qualname__ if hasattr(func, "__qualname__") else func.__name__ + r = replace or f + + frame = inspect.currentframe().f_back + + msg = " ".join(msg) + 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) + + return wrapper + + +@deprecated(version="2.9.2") def parse(version): """ Parse version to major, minor, patch, pre-release, build parts. + .. deprecated:: 2.9.2 + 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 @@ -69,17 +105,7 @@ def parse(version): >>> ver['build'] 'build.4' """ - match = _REGEX.match(version) - if match is None: - raise ValueError("%s is not valid SemVer string" % version) - - version_parts = match.groupdict() - - version_parts["major"] = int(version_parts["major"]) - version_parts["minor"] = int(version_parts["minor"]) - version_parts["patch"] = int(version_parts["patch"]) - - return version_parts + return VersionInfo.parse(version).to_dict() def comparator(operator): @@ -110,6 +136,29 @@ class VersionInfo(object): """ __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build") + #: Regex for number in a prerelease + _LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+") + #: Regex for a semver version + _REGEX = re.compile( + r""" + ^ + (?P0|[1-9]\d*) + \. + (?P0|[1-9]\d*) + \. + (?P0|[1-9]\d*) + (?:-(?P + (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) + (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* + ))? + (?:\+(?P + [0-9a-zA-Z-]+ + (?:\.[0-9a-zA-Z-]+)* + ))? + $ + """, + re.VERBOSE, + ) def __init__(self, major, minor=0, patch=0, prerelease=None, build=None): self._major = int(major) @@ -163,10 +212,38 @@ def build(self): def build(self, value): raise AttributeError("attribute 'build' is readonly") - def _astuple(self): + def to_tuple(self): + """ + Convert the VersionInfo object to a tuple. + + .. versionadded:: 2.9.2 + Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + make this function available in the public API. + + :return: a tuple with all the parts + :rtype: tuple + + >>> semver.VersionInfo(5, 3, 1).to_tuple() + (5, 3, 1, None, None) + """ return (self.major, self.minor, self.patch, self.prerelease, self.build) - def _asdict(self): + def to_dict(self): + """ + Convert the VersionInfo object to an OrderedDict. + + .. versionadded:: 2.9.2 + Renamed ``VersionInfo._asdict`` to ``VersionInfo.to_dict`` to + make this function available in the public API. + + :return: an OrderedDict with the keys in the order ``major``, ``minor``, + ``patch``, ``prerelease``, and ``build``. + :rtype: :class:`collections.OrderedDict` + + >>> semver.VersionInfo(3, 2, 1).to_dict() + OrderedDict([('major', 3), ('minor', 2), ('patch', 1), \ +('prerelease', None), ('build', None)]) + """ return collections.OrderedDict( ( ("major", self.major), @@ -177,12 +254,43 @@ def _asdict(self): ) ) + # For compatibility reasons: + @deprecated(replace="semver.VersionInfo.to_tuple", version="2.9.2") + def _astuple(self): + return self.to_tuple() # pragma: no cover + + _astuple.__doc__ = to_tuple.__doc__ + + @deprecated(replace="semver.VersionInfo.to_dict", version="2.9.2") + def _asdict(self): + return self.to_dict() # pragma: no cover + + _asdict.__doc__ = to_dict.__doc__ + def __iter__(self): """Implement iter(self).""" # As long as we support Py2.7, we can't use the "yield from" syntax - for v in self._astuple(): + for v in self.to_tuple(): yield v + @staticmethod + def _increment_string(string): + """ + Look for the last sequence of number(s) in a string and increment. + + :param str string: the string to search for. + :return: the incremented string + + Source: + http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 + """ + match = VersionInfo._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): """ Raise the major part of the version, return a new object but leave self @@ -195,7 +303,8 @@ def bump_major(self): >>> ver.bump_major() VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) """ - return parse_version_info(bump_major(str(self))) + cls = type(self) + return cls(self._major + 1) def bump_minor(self): """ @@ -209,7 +318,8 @@ def bump_minor(self): >>> ver.bump_minor() VersionInfo(major=3, minor=5, patch=0, prerelease=None, build=None) """ - return parse_version_info(bump_minor(str(self))) + cls = type(self) + return cls(self._major, self._minor + 1) def bump_patch(self): """ @@ -223,7 +333,8 @@ def bump_patch(self): >>> ver.bump_patch() VersionInfo(major=3, minor=4, patch=6, prerelease=None, build=None) """ - return parse_version_info(bump_patch(str(self))) + cls = type(self) + return cls(self._major, self._minor, self._patch + 1) def bump_prerelease(self, token="rc"): """ @@ -239,7 +350,9 @@ def bump_prerelease(self, token="rc"): VersionInfo(major=3, minor=4, patch=5, prerelease='rc.2', \ build=None) """ - return parse_version_info(bump_prerelease(str(self), token)) + 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="build"): """ @@ -255,41 +368,62 @@ def bump_build(self, token="build"): VersionInfo(major=3, minor=4, patch=5, prerelease='rc.1', \ build='build.10') """ - return parse_version_info(bump_build(str(self), token)) + cls = type(self) + build = cls._increment_string(self._build or (token or "build") + ".0") + return cls(self._major, self._minor, self._patch, self._prerelease, build) @comparator def __eq__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) == 0 @comparator def __ne__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) != 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) != 0 @comparator def __lt__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) < 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) < 0 @comparator def __le__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) <= 0 @comparator def __gt__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) > 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) > 0 @comparator def __ge__(self, other): - return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 + return _compare_by_keys(self.to_dict(), _to_dict(other)) >= 0 def __repr__(self): - s = ", ".join("%s=%r" % (key, val) for key, val in self._asdict().items()) + s = ", ".join("%s=%r" % (key, val) for key, val in self.to_dict().items()) return "%s(%s)" % (type(self).__name__, s) def __str__(self): - return format_version(*(self._astuple())) + """str(self)""" + version = "%d.%d.%d" % (self.major, self.minor, self.patch) + if self.prerelease: + version += "-%s" % self.prerelease + if self.build: + version += "+%s" % self.build + return version def __hash__(self): - return hash(self._astuple()) + return hash(self.to_tuple()) + + def finalize_version(self): + """ + Remove any prerelease and build metadata from the version. + + :return: a new instance with the finalized version string + :rtype: :class:`VersionInfo` + + >>> str(semver.VersionInfo.parse('1.2.3-rc.5').finalize_version()) + '1.2.3' + """ + cls = type(self) + return cls(self.major, self.minor, self.patch) @staticmethod def parse(version): @@ -304,7 +438,17 @@ def parse(version): VersionInfo(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') """ - return parse_version_info(version) + match = VersionInfo._REGEX.match(version) + if match is None: + raise ValueError("%s is not valid SemVer string" % version) + + version_parts = match.groupdict() + + version_parts["major"] = int(version_parts["major"]) + version_parts["minor"] = int(version_parts["minor"]) + version_parts["patch"] = int(version_parts["patch"]) + + return VersionInfo(**version_parts) def replace(self, **parts): """ @@ -320,12 +464,12 @@ def replace(self, **parts): parts :raises: TypeError, if ``parts`` contains invalid keys """ - version = self._asdict() + version = self.to_dict() version.update(parts) try: return VersionInfo(**version) except TypeError: - unknownkeys = set(parts) - set(self._asdict()) + unknownkeys = set(parts) - set(self.to_dict()) error = "replace() got %d unexpected keyword " "argument(s): %s" % ( len(unknownkeys), ", ".join(unknownkeys), @@ -353,16 +497,23 @@ def isvalid(cls, version): def _to_dict(obj): if isinstance(obj, VersionInfo): - return obj._asdict() + return obj.to_dict() elif isinstance(obj, tuple): - return VersionInfo(*obj)._asdict() + return VersionInfo(*obj).to_dict() return obj +@deprecated(replace="semver.VersionInfo.parse", version="2.9.2") def parse_version_info(version): """ Parse version string to a VersionInfo instance. + .. versionadded:: 2.7.2 + Added :func:`parse_version_info` + + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.parse` instead. + .. versionadded:: 2.7.2 Added :func:`parse_version_info` @@ -382,16 +533,7 @@ def parse_version_info(version): >>> version_info.build 'build.4' """ - parts = parse(version) - version_info = VersionInfo( - parts["major"], - parts["minor"], - parts["patch"], - parts["prerelease"], - parts["build"], - ) - - return version_info + return VersionInfo.parse(version) def _nat_cmp(a, b): @@ -458,7 +600,8 @@ def compare(ver1, ver2): 0 """ - v1, v2 = parse(ver1), parse(ver2) + v1 = VersionInfo.parse(ver1).to_dict() + v2 = VersionInfo.parse(ver2).to_dict() return _compare_by_keys(v1, v2) @@ -550,10 +693,14 @@ def min_ver(ver1, ver2): return ver2 +@deprecated(replace="str(versionobject)", version="2.9.2") def format_version(major, minor, patch, prerelease=None, build=None): """ Format a version string according to the Semantic Versioning specification. + .. deprecated:: 2.9.2 + 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 @@ -565,34 +712,17 @@ 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' """ - version = "%d.%d.%d" % (major, minor, patch) - if prerelease is not None: - version = version + "-%s" % prerelease - - if build is not None: - version = version + "+%s" % build - - return version - - -def _increment_string(string): - """ - Look for the last sequence of number(s) in a string and increment, from: - - http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 - """ - match = _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 + return str(VersionInfo(major, minor, patch, prerelease, build)) +@deprecated(version="2.9.2") def bump_major(version): """ Raise the major part of the version string. + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.bump_major` instead. + :param: version string :return: the raised version string :rtype: str @@ -600,14 +730,17 @@ def bump_major(version): >>> semver.bump_major("3.4.5") '4.0.0' """ - verinfo = parse(version) - return format_version(verinfo["major"] + 1, 0, 0) + return str(VersionInfo.parse(version).bump_major()) +@deprecated(version="2.9.2") def bump_minor(version): """ Raise the minor part of the version string. + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.bump_minor` instead. + :param: version string :return: the raised version string :rtype: str @@ -615,14 +748,17 @@ def bump_minor(version): >>> semver.bump_minor("3.4.5") '3.5.0' """ - verinfo = parse(version) - return format_version(verinfo["major"], verinfo["minor"] + 1, 0) + return str(VersionInfo.parse(version).bump_minor()) +@deprecated(version="2.9.2") def bump_patch(version): """ Raise the patch part of the version string. + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.bump_patch` instead. + :param: version string :return: the raised version string :rtype: str @@ -630,14 +766,17 @@ def bump_patch(version): >>> semver.bump_patch("3.4.5") '3.4.6' """ - verinfo = parse(version) - return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"] + 1) + return str(VersionInfo.parse(version).bump_patch()) +@deprecated(version="2.9.2") def bump_prerelease(version, token="rc"): """ Raise the prerelease part of the version string. + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.bump_prerelease` instead. + :param version: version string :param token: defaults to 'rc' :return: the raised version string @@ -646,19 +785,17 @@ def bump_prerelease(version, token="rc"): >>> semver.bump_prerelease('3.4.5', 'dev') '3.4.5-dev.1' """ - verinfo = parse(version) - verinfo["prerelease"] = _increment_string( - verinfo["prerelease"] or (token or "rc") + ".0" - ) - return format_version( - verinfo["major"], verinfo["minor"], verinfo["patch"], verinfo["prerelease"] - ) + return str(VersionInfo.parse(version).bump_prerelease(token)) +@deprecated(version="2.9.2") def bump_build(version, token="build"): """ Raise the build part of the version string. + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.bump_build` instead. + :param version: version string :param token: defaults to 'build' :return: the raised version string @@ -667,17 +804,10 @@ def bump_build(version, token="build"): >>> semver.bump_build('3.4.5-rc.1+build.9') '3.4.5-rc.1+build.10' """ - verinfo = parse(version) - verinfo["build"] = _increment_string(verinfo["build"] or (token or "build") + ".0") - return format_version( - verinfo["major"], - verinfo["minor"], - verinfo["patch"], - verinfo["prerelease"], - verinfo["build"], - ) + return str(VersionInfo.parse(version).bump_build(token)) +@deprecated(version="2.9.2") def finalize_version(version): """ Remove any prerelease and build metadata from the version string. @@ -685,6 +815,9 @@ def finalize_version(version): .. versionadded:: 2.7.9 Added :func:`finalize_version` + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.finalize_version` instead. + :param version: version string :return: the finalized version string :rtype: str @@ -692,8 +825,33 @@ def finalize_version(version): >>> semver.finalize_version('1.2.3-rc.5') '1.2.3' """ - verinfo = parse(version) - return format_version(verinfo["major"], verinfo["minor"], verinfo["patch"]) + verinfo = VersionInfo.parse(version) + return str(verinfo.finalize_version()) + + +@deprecated(version="2.9.2") +def replace(version, **parts): + """ + Replace one or more parts of a version and return the new string. + + .. versionadded:: 2.9.0 + Added :func:`replace` + + .. deprecated:: 2.9.2 + Use :func:`semver.VersionInfo.replace` instead. + + :param str version: the version string to replace + :param dict 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 + :rtype: str + + >>> import semver + >>> semver.replace("1.2.3", major=2, patch=10) + '2.2.10' + """ + return str(VersionInfo.parse(version).replace(**parts)) def cmd_bump(args): @@ -719,7 +877,7 @@ def cmd_bump(args): # print the help and exit args.parser.parse_args(["bump", "-h"]) - ver = parse_version_info(args.version) + ver = VersionInfo.parse(args.version) # get the respective method and call it func = getattr(ver, maptable[args.bump]) return str(func()) @@ -838,28 +996,6 @@ def main(cliargs=None): return 2 -def replace(version, **parts): - """ - Replace one or more parts of a version and return the new string. - - .. versionadded:: 2.9.0 - Added :func:`replace` - - :param str version: the version string to replace - :param dict 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 - :rtype: str - - >>> import semver - >>> semver.replace("1.2.3", major=2, patch=10) - '2.2.10' - """ - version = parse_version_info(version) - return str(version.replace(**parts)) - - if __name__ == "__main__": import doctest diff --git a/setup.cfg b/setup.cfg index 6ab7e562..1cefc4bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,8 @@ [tool:pytest] norecursedirs = .git build .env/ env/ .pyenv/ .tmp/ .eggs/ testpaths = . docs +filterwarnings = + ignore:Function 'semver.*:DeprecationWarning addopts = --no-cov-on-fail --cov=semver diff --git a/test_semver.py b/test_semver.py index ed7d80bc..4611d771 100644 --- a/test_semver.py +++ b/test_semver.py @@ -14,6 +14,7 @@ cmd_compare, compare, createparser, + deprecated, finalize_version, format_version, main, @@ -54,9 +55,7 @@ def does_not_raise(item): "string,expected", [("rc", "rc"), ("rc.1", "rc.2"), ("2x", "3x")] ) def test_should_private_increment_string(string, expected): - from semver import _increment_string - - assert _increment_string(string) == expected + assert VersionInfo._increment_string(string) == expected @pytest.fixture @@ -393,6 +392,18 @@ def test_should_versioninfo_bump_multiple(): assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected +def test_should_versioninfo_to_dict(version): + resultdict = version.to_dict() + assert isinstance(resultdict, dict), "Got type from to_dict" + assert list(resultdict.keys()) == ["major", "minor", "patch", "prerelease", "build"] + + +def test_should_versioninfo_to_tuple(version): + result = version.to_tuple() + assert isinstance(result, tuple), "Got type from to_dict" + assert len(result) == 5, "Different length from to_tuple()" + + def test_should_ignore_extensions_for_bump(): assert bump_patch("3.4.5-rc1+build4") == "3.4.6" @@ -777,6 +788,13 @@ def test_should_raise_systemexit_when_bump_iscalled_with_empty_arguments(): main(["bump"]) +def test_should_process_check_iscalled_with_valid_version(capsys): + result = main(["check", "1.1.1"]) + assert not result + captured = capsys.readouterr() + assert not captured.out + + @pytest.mark.parametrize( "version,parts,expected", [ @@ -827,3 +845,37 @@ def test_replace_raises_ValueError_for_non_numeric_values(): def test_should_versioninfo_isvalid(): assert VersionInfo.isvalid("1.0.0") is True assert VersionInfo.isvalid("foo") is False + + +@pytest.mark.parametrize( + "func, args, kwargs", + [ + (bump_build, ("1.2.3",), {}), + (bump_major, ("1.2.3",), {}), + (bump_minor, ("1.2.3",), {}), + (bump_patch, ("1.2.3",), {}), + (bump_prerelease, ("1.2.3",), {}), + (format_version, (3, 4, 5), {}), + (finalize_version, ("1.2.3-rc.5",), {}), + (parse, ("1.2.3",), {}), + (parse_version_info, ("1.2.3",), {}), + (replace, ("1.2.3",), dict(major=2, patch=10)), + ], +) +def test_should_raise_deprecation_warnings(func, args, kwargs): + with pytest.warns( + DeprecationWarning, match=r"Function 'semver.[_a-zA-Z]+' is deprecated." + ) as record: + func(*args, **kwargs) + if not record: + pytest.fail("Expected a DeprecationWarning for {}".format(func.__name__)) + assert len(record), "Expected one DeprecationWarning record" + + +def test_deprecated_deco_without_argument(): + @deprecated + def mock_func(): + return True + + with pytest.deprecated_call(): + assert mock_func() From b145931366a03ba8a763dc550dab746a1bbc3594 Mon Sep 17 00:00:00 2001 From: Thomas Schraitle Date: Sat, 18 Apr 2020 00:28:21 +0200 Subject: [PATCH 156/312] Fix #235: Shift focus on semver.VersionInfo.* * Module level functions like `semver.bump_version` are still available in the documentation, but they play a much less important role now. The preferred way is to use semver.Versioninfo instances to use. * Replace 2.9.2 -> 2.10.0 due to #237 * Fix docstring examples --- CHANGELOG.rst | 6 +++- README.rst | 58 ++++++++++++-------------------- docs/pysemver.rst | 5 ++- docs/usage.rst | 66 ++++++++++++++++++------------------ semver.py | 85 +++++++++++++++++++++++------------------------ 5 files changed, 104 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48e3d82b..ddefadcb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ Change Log All notable changes to this code base will be documented in this file, in every released version. -Version 2.9.x (WIP) +Version 2.10.0 (WIP) =================== :Released: 2020-xx-yy @@ -15,6 +15,10 @@ Version 2.9.x (WIP) Features -------- +* :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising + the old and deprecated module-level functions. + + Bug Fixes --------- diff --git a/README.rst b/README.rst index 862fbdc6..265aac46 100644 --- a/README.rst +++ b/README.rst @@ -9,18 +9,23 @@ A Python module for `semantic versioning`_. Simplifies comparing versions. .. teaser-end -.. note:: +.. warning:: - With version 2.9.0 we've moved the GitHub project. The project is now - located under the organization ``python-semver``. - The complete URL is:: + As anything comes to an end, this project will focus on Python 3.x only. + New features and bugfixes will be integrated into the 3.x.y branch only. - https://github.com/python-semver/python-semver + Major version 3 of semver will contain some incompatible changes: - If you still have an old repository, correct your upstream URL to the new URL:: + * removes support for Python 2.7 and 3.3 + * removes deprecated functions (see :ref:`sec_replace_deprecated_functions` for + further information). - $ git remote set-url upstream git@github.com:python-semver/python-semver.git + The last version of semver which supports Python 2.7 and 3.4 will be + 2.10.x. However, keep in mind, version 2.10.x is frozen: no new + features nor backports will be integrated. + We recommend to upgrade your workflow to Python 3.x to gain support, + bugfixes, and new features. The module follows the ``MAJOR.MINOR.PATCH`` style: @@ -30,23 +35,6 @@ The module follows the ``MAJOR.MINOR.PATCH`` style: Additional labels for pre-release and build metadata are supported. - -.. warning:: - - Major version 3.0.0 of semver will remove support for Python 2.7 and 3.4. - - As anything comes to an end, this project will focus on Python 3.x. - New features and bugfixes will be integrated only into the 3.x.y branch - of semver. - - The last version of semver which supports Python 2.7 and 3.4 will be - 2.9.x. However, keep in mind, version 2.9.x is frozen: no new - features nor backports will be integrated. - - We recommend to upgrade your workflow to Python 3.x to gain support, - bugfixes, and new features. - - To import this library, use: .. code-block:: python @@ -54,31 +42,29 @@ 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.parse` function: +different parts, use the ``semver.VersionInfo.parse`` function: .. code-block:: python - >>> ver = semver.parse('1.2.3-pre.2+build.4') - >>> ver['major'] + >>> ver = semver.VersionInfo.parse('1.2.3-pre.2+build.4') + >>> ver.major 1 - >>> ver['minor'] + >>> ver.minor 2 - >>> ver['patch'] + >>> ver.patch 3 - >>> ver['prerelease'] + >>> ver.prerelease 'pre.2' - >>> ver['build'] + >>> ver.build 'build.4' To raise parts of a version, there are a couple of functions available for -you. The `semver.parse_version_info` function converts a version string -into a `semver.VersionInfo` class. 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 :func:`semver.VersionInfo.bump_major` leaves the original object untouched, but +returns a new :class:`semver.VersionInfo` instance with the raised major part: .. code-block:: python - >>> ver = semver.parse_version_info("3.4.5") + >>> ver = semver.VersionInfo.parse("3.4.5") >>> ver.bump_major() VersionInfo(major=4, minor=0, patch=0, prerelease=None, build=None) diff --git a/docs/pysemver.rst b/docs/pysemver.rst index 3c247b62..b0896eea 100644 --- a/docs/pysemver.rst +++ b/docs/pysemver.rst @@ -10,8 +10,7 @@ Synopsis .. code:: bash - pysemver compare - pysemver bump {major,minor,patch,prerelease,build} + pysemver