From f024dd472ed49335fc1941dee16b379fda188b66 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Sat, 3 Feb 2018 10:11:14 +0000 Subject: [PATCH 1/9] add support for comparing versions --- .gitignore | 2 + bumpversion/__init__.py | 78 +++++++++++++++++++++++++++++++++++-- bumpversion/functions.py | 5 ++- bumpversion/version_part.py | 31 ++++++++++++++- 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index ebe387dd..ad27a091 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,5 @@ Session.vim # Auto-generated tag files tags + +\.idea/ diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index a4af337a..d2bc0bdb 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -29,6 +29,7 @@ import codecs from bumpversion.version_part import VersionPart, NumericVersionPartConfiguration, ConfiguredVersionPartConfiguration +from bumpversion.functions import NumericFunction if sys.version_info[0] == 2: sys.stdout = codecs.getwriter('utf-8')(sys.stdout) @@ -314,8 +315,9 @@ def keyvaluestring(d): class Version(object): - def __init__(self, values, original=None): + def __init__(self, values, config, original=None): self._values = dict(values) + self.config = config self.original = original def __getitem__(self, key): @@ -328,7 +330,75 @@ def __iter__(self): return iter(self._values) def __repr__(self): - return ''.format(keyvaluestring(self._values)) + by_part = ", ".join("{}={}".format(k, v) for k, v in self.items()) + return ''.format(by_part) + + def __hash__(self): + return hash(tuple((k, v) for k, v in self.items())) + + def _compare(self, other, method): + """ + For >=, <= and == we need to compare all parts + For > and < we use the opposite operator and flip the operands + :param other: + :param method: + :return: + """ + try: + for vals in ((k, v, other[k]) for k, v in self.items()): + if not method(vals[1], vals[2]): + return False + except KeyError: + raise TypeError("Versions use different parts, cant compare them.") + return True + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, Version): + return False + return self._compare(other, lambda s, o: s == o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + # def _compare_fast(self, other, method): + # """ + # For >, < and != comparision can stop at first fail + # :param other: + # :param method: + # :return: + # """ + # try: + # for vals in ((k, v, other[k]) for k, v in self.items()): + # if method(vals[1], vals[2]): + # return True + # except KeyError: + # raise TypeError("Versions use different parts, cant compare them.") + # return False + + def __lt__(self, other): + return self._compare_fast(other, lambda s, o: o >= s) # s < o + + def __gt__(self, other): + return self._compare(other, lambda s, o: o <= s) # s > o + + def __ne__(self, other): + if self is other: + return False + if not isinstance(other, Version): + return True + return not self._compare(other, lambda s, o: s == o) + + def items(self): + for k in self.config.order(): + try: + yield k, self._values[k] + except KeyError: + raise StopIteration def bump(self, part_name, order): bumped = False @@ -346,7 +416,7 @@ def bump(self, part_name, order): else: new_values[label] = self._values[label].copy() - new_version = Version(new_values) + new_version = Version(new_values, self.config) return new_version @@ -403,7 +473,7 @@ def parse(self, version_string): _parsed[key] = VersionPart(value, self.part_configs.get(key)) - v = Version(_parsed, version_string) + v = Version(_parsed, self, version_string) logger.info("Parsed the following values: %s" % keyvaluestring(v._values)) diff --git a/bumpversion/functions.py b/bumpversion/functions.py index b00f726a..c44f5ee5 100644 --- a/bumpversion/functions.py +++ b/bumpversion/functions.py @@ -82,8 +82,11 @@ def __init__(self, values, optional_value=None, first_value=None): def bump(self, value): try: - return self._values[self._values.index(value)+1] + return self._values[self.index(value) + 1] except IndexError: raise ValueError( "The part has already the maximum value among {} and cannot be bumped.".format(self._values)) + def index(self, value): + return self._values.index(value) + diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index 6f5c1082..5a36df50 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -63,8 +63,37 @@ def __repr__(self): self.value ) + def __hash__(self): + return hash(self.value) + + def _compare(self, other, method): + if self.config.function_cls is not other.config.function_cls: + raise TypeError("Versions use different part specific configuration, cant comapre them.") + if self.config.function_cls is NumericFunction: + return method(self.value, other.value) + else: + # Compare order + idx1 = self.config.function.index(self.value) + idx2 = other.config.function.index(other.value) + return method(idx1, idx2) + def __eq__(self, other): - return self.value == other.value + return self._compare(other, lambda s, o: s == o) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) def null(self): return VersionPart(self.config.first_value, self.config) From 3a30f4e66b441d57509d96145c919a1f472d2e2f Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Wed, 9 May 2018 15:52:09 +0100 Subject: [PATCH 2/9] Fixes version comparision --- bumpversion/__init__.py | 54 +++++++++++++------------------------ bumpversion/version_part.py | 2 +- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index d2bc0bdb..429edf99 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -336,62 +336,46 @@ def __repr__(self): def __hash__(self): return hash(tuple((k, v) for k, v in self.items())) - def _compare(self, other, method): + def _compare_strict(self, other, method): """ - For >=, <= and == we need to compare all parts - For > and < we use the opposite operator and flip the operands - :param other: - :param method: + For strict relations, if a part is equal we skip it + :param other: the other Version + :param method: the compare method :return: """ try: - for vals in ((k, v, other[k]) for k, v in self.items()): - if not method(vals[1], vals[2]): + for vals in ((v, other[k]) for k, v in self.items()): + if vals[0] == vals[1]: + continue + if method(vals[0], vals[1]): + return True + else: + return False return False except KeyError: raise TypeError("Versions use different parts, cant compare them.") - return True def __eq__(self, other): if self is other: return True if not isinstance(other, Version): return False - return self._compare(other, lambda s, o: s == o) + try: + return all(v == other[k] for k, v in self.items()) + except KeyError: + raise TypeError("Versions use different parts, cant compare them.") def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) + return self._compare_strict(other, lambda s, o: o > s) # Note the change of order in operands def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - # def _compare_fast(self, other, method): - # """ - # For >, < and != comparision can stop at first fail - # :param other: - # :param method: - # :return: - # """ - # try: - # for vals in ((k, v, other[k]) for k, v in self.items()): - # if method(vals[1], vals[2]): - # return True - # except KeyError: - # raise TypeError("Versions use different parts, cant compare them.") - # return False + return self._compare_strict(other, lambda s, o: o < s) # Note the change of order in operands def __lt__(self, other): - return self._compare_fast(other, lambda s, o: o >= s) # s < o + return self._compare_strict(other, lambda s, o: s < o) def __gt__(self, other): - return self._compare(other, lambda s, o: o <= s) # s > o - - def __ne__(self, other): - if self is other: - return False - if not isinstance(other, Version): - return True - return not self._compare(other, lambda s, o: s == o) + return self._compare_strict(other, lambda s, o: s > o) def items(self): for k in self.config.order(): diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index 5a36df50..64e261df 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -68,7 +68,7 @@ def __hash__(self): def _compare(self, other, method): if self.config.function_cls is not other.config.function_cls: - raise TypeError("Versions use different part specific configuration, cant comapre them.") + raise TypeError("Versions use different part specific configuration, cant compare them.") if self.config.function_cls is NumericFunction: return method(self.value, other.value) else: From 13b145d396234754ee78b3a9d8c16b94906c43da Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Wed, 9 May 2018 15:54:47 +0100 Subject: [PATCH 3/9] Test bumped part is in parse parts --- bumpversion/__init__.py | 20 +++++++++++++++----- tests/test_cli.py | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index 429edf99..739e59f6 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -310,6 +310,10 @@ class MercurialDoesNotSupportSignedTagsException(Exception): def __init__(self, message): self.message = message +class UnkownPart(Exception): + def __init__(self, message): + self.message = message + def keyvaluestring(d): return ", ".join("{}={}".format(k, v) for k, v in sorted(d.items())) @@ -351,7 +355,7 @@ def _compare_strict(self, other, method): return True else: return False - return False + return False except KeyError: raise TypeError("Versions use different parts, cant compare them.") @@ -802,10 +806,16 @@ def main(original_args=None): if not 'new_version' in defaults and known_args.current_version: try: if current_version and len(positionals) > 0: - logger.info("Attempting to increment part '{}'".format(positionals[0])) - new_version = current_version.bump(positionals[0], vc.order()) - logger.info("Values are now: " + keyvaluestring(new_version._values)) - defaults['new_version'] = vc.serialize(new_version, context) + part = positionals[0] + logger.info("Attempting to increment part '{}'".format(part)) + if part in vc.order(): + logger.info("Bumped part found in parse parts.") + new_version = current_version.bump(part, vc.order()) + logger.info("Values are now: " + keyvaluestring(new_version._values)) + defaults['new_version'] = vc.serialize(new_version, context) + else: + logger.info("Bumped part not found in parse parts.") + raise UnkownPart("Bumped part not found in parse parts.") except MissingValueForSerializationException as e: logger.info("Opportunistic finding of new_version failed: " + e.message) except IncompleteVersionRepresenationException as e: diff --git a/tests/test_cli.py b/tests/test_cli.py index 7067472e..cfe65968 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -18,7 +18,7 @@ import bumpversion from bumpversion import main, DESCRIPTION, WorkingDirectoryIsDirtyException, \ - split_args_in_optional_and_positional + split_args_in_optional_and_positional, UnkownPart SUBPROCESS_ENV = dict( list(environ.items()) + [(b'HGENCODING', b'utf-8')] @@ -324,6 +324,19 @@ def test_bump_version(tmpdir): assert '1.0.1' == tmpdir.join("file5").read() +def test_bump_version_unkown_part(tmpdir): + + tmpdir.join("file5").write("1.0.0") + tmpdir.chdir() + with pytest.raises(UnkownPart): + with mock.patch("bumpversion.logger") as logger: + main(['bugfix', '--current-version', '1.0.0', 'file5']) + + actual_log ="\n".join(_mock_calls_to_string(logger)[4:]) + + assert 'Bumped part not found in parse parts' in actual_log + + def test_bump_version_custom_parse(tmpdir): tmpdir.join("file6").write("XXX1;0;0") From a79f1dbb12f0bd9b3bdefaba597e2caa2481800d Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Thu, 10 May 2018 09:13:22 +0100 Subject: [PATCH 4/9] Fixes ge and le comparisions --- bumpversion/__init__.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index 739e59f6..78c9d7f4 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -340,7 +340,7 @@ def __repr__(self): def __hash__(self): return hash(tuple((k, v) for k, v in self.items())) - def _compare_strict(self, other, method): + def _compare(self, other, method, strict=True): """ For strict relations, if a part is equal we skip it :param other: the other Version @@ -355,7 +355,7 @@ def _compare_strict(self, other, method): return True else: return False - return False + return not strict except KeyError: raise TypeError("Versions use different parts, cant compare them.") @@ -369,17 +369,20 @@ def __eq__(self, other): except KeyError: raise TypeError("Versions use different parts, cant compare them.") + def __ne__(self, other): + return not self.__eq__(other) + def __le__(self, other): - return self._compare_strict(other, lambda s, o: o > s) # Note the change of order in operands + return self._compare(other, lambda s, o: o > s, False) # Note the change of order in operands def __ge__(self, other): - return self._compare_strict(other, lambda s, o: o < s) # Note the change of order in operands + return self._compare(other, lambda s, o: o < s, False) # Note the change of order in operands def __lt__(self, other): - return self._compare_strict(other, lambda s, o: s < o) + return self._compare(other, lambda s, o: s < o) def __gt__(self, other): - return self._compare_strict(other, lambda s, o: s > o) + return self._compare(other, lambda s, o: s > o) def items(self): for k in self.config.order(): From 9ee9ec7fa71334ed62eed25d2942dabc5b780584 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Thu, 10 May 2018 09:13:37 +0100 Subject: [PATCH 5/9] Add test for parts comparision --- tests/test_version.py | 133 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/test_version.py diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..534bfedd --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals, print_function +import pytest + +from bumpversion import VersionConfig +from bumpversion.version_part import ConfiguredVersionPartConfiguration + + +def test_compare_versions_numeric(): + + version_parse = '(?P\d+)\.(?P\d+)\.(?P\d+)' + version_serialize = [str('{major}.{minor}.{patch}')] + vc = VersionConfig(version_parse, version_serialize, None, None) + v1 = vc.parse(version_string="1.0.0") + v2 = vc.parse(version_string="1.0.0") + assert v1 == v2 + assert not (v1 != v2) + assert not (v1 < v2) + assert not (v1 > v2) + assert v1 <= v2 + assert v1 >= v2 + assert v2 == v1 + assert not (v2 != v1) + assert not (v2 < v1) + assert not (v2 > v1) + assert v2 <= v1 + assert v2 >= v1 + v2 = vc.parse(version_string="1.0.1") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 + v1 = vc.parse(version_string="1.2.1") + v2 = vc.parse(version_string="1.3.1") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 + v1 = vc.parse(version_string="1.2.4") + v2 = vc.parse(version_string="1.3.1") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 + v1 = vc.parse(version_string="1.2.4") + v2 = vc.parse(version_string="2.3.1") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 + + +def test_compare_versions_values(): + + version_parse = '(?P\d+)\.(?P\d+)\.(?P.+)' + version_serialize = [str('{major}.{minor}.{release}')] + pc = ConfiguredVersionPartConfiguration(values=['witty-warthog', 'ridiculous-rat', 'marvelous-mantis']) + part_configs = {'release': pc} + vc = VersionConfig(version_parse, version_serialize, None, None, part_configs=part_configs) + v1 = vc.parse(version_string="1.0.witty-warthog") + v2 = vc.parse(version_string="1.0.witty-warthog") + assert v1 == v2 + assert not (v1 != v2) + assert not (v1 < v2) + assert not (v1 > v2) + assert v1 <= v2 + assert v1 >= v2 + assert v2 == v1 + assert not (v2 != v1) + assert not (v2 < v1) + assert not (v2 > v1) + assert v2 <= v1 + assert v2 >= v1 + v2 = vc.parse(version_string="1.0.ridiculous-rat") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 + v1 = vc.parse(version_string="1.2.marvelous-mantis") + v2 = vc.parse(version_string="1.3.ridiculous-rat") + assert not (v1 == v2) + assert v1 != v2 + assert v1 < v2 + assert not (v1 > v2) + assert v1 <= v2 + assert not (v1 >= v2) + assert not (v2 == v1) + assert v2 != v1 + assert not (v2 < v1) + assert v2 > v1 + assert not (v2 <= v1) + assert v2 >= v1 \ No newline at end of file From 91b213d6c5a2c3e66113ed607be4ef0650321650 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Thu, 10 May 2018 09:51:05 +0100 Subject: [PATCH 6/9] Fixes expected logs in tests for new part check testing --- .gitignore | 4 ++++ bumpversion/__init__.py | 4 ++-- tests/test_cli.py | 5 +++++ tests/test_version.py | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ad27a091..3d47d9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,7 @@ Session.vim tags \.idea/ + +\.pytest_cache/ + +\.DS_Store diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index 07010921..45a09a75 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -820,12 +820,12 @@ def main(original_args=None): part = positionals[0] logger.info("Attempting to increment part '{}'".format(part)) if part in vc.order(): - logger.info("Bumped part found in parse parts.") + logger.info("Bumped part found in parse parts") new_version = current_version.bump(part, vc.order()) logger.info("Values are now: " + keyvaluestring(new_version._values)) defaults['new_version'] = vc.serialize(new_version, context) else: - logger.info("Bumped part not found in parse parts.") + logger.info("Bumped part not found in parse parts") raise UnkownPart("Bumped part not found in parse parts.") except MissingValueForSerializationException as e: logger.info("Opportunistic finding of new_version failed: " + e.message) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8f72add1..8936de19 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1004,6 +1004,7 @@ def test_log_no_config_file_info_message(tmpdir, capsys): info|Parsing version '1.0.0' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=1, minor=0, patch=0| info|Attempting to increment part 'patch'| + info|Bumped part found in parse parts| info|Values are now: major=1, minor=0, patch=1| info|Parsing version '1.0.1' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=1, minor=0, patch=1| @@ -1096,6 +1097,7 @@ def test_complex_info_logging(tmpdir, capsys): info|Parsing version '0.4' using regexp '(?P\d+)\.(?P\d+)(\.(?P\d+))?'| info|Parsed the following values: major=0, minor=4, patch=0| info|Attempting to increment part 'patch'| + info|Bumped part found in parse parts| info|Values are now: major=0, minor=4, patch=1| info|Parsing version '0.4.1' using regexp '(?P\d+)\.(?P\d+)(\.(?P\d+))?'| info|Parsed the following values: major=0, minor=4, patch=1| @@ -1164,6 +1166,7 @@ def test_subjunctive_dry_run_logging(tmpdir, vcs): info|Parsing version '0.8' using regexp '(?P\d+)\.(?P\d+)(\.(?P\d+))?'| info|Parsed the following values: major=0, minor=8, patch=0| info|Attempting to increment part 'patch'| + info|Bumped part found in parse parts| info|Values are now: major=0, minor=8, patch=1| info|Dry run active, won't touch any files.| info|Parsing version '0.8.1' using regexp '(?P\d+)\.(?P\d+)(\.(?P\d+))?'| @@ -1235,6 +1238,7 @@ def test_log_commitmessage_if_no_commit_tag_but_usable_vcs(tmpdir, vcs): info|Parsing version '0.3.3' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=0, minor=3, patch=3| info|Attempting to increment part 'patch'| + info|Bumped part found in parse parts| info|Values are now: major=0, minor=3, patch=4| info|Parsing version '0.3.4' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=0, minor=3, patch=4| @@ -1574,6 +1578,7 @@ def test_search_replace_to_avoid_updating_unconcerned_lines(tmpdir, capsys): info|Parsing version '1.5.6' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=1, minor=5, patch=6| info|Attempting to increment part 'minor'| + info|Bumped part found in parse parts| info|Values are now: major=1, minor=6, patch=0| info|Parsing version '1.6.0' using regexp '(?P\d+)\.(?P\d+)\.(?P\d+)'| info|Parsed the following values: major=1, minor=6, patch=0| diff --git a/tests/test_version.py b/tests/test_version.py index 534bfedd..9d12cbd0 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function import pytest +import bumpversion from bumpversion import VersionConfig from bumpversion.version_part import ConfiguredVersionPartConfiguration From a135eb21ad3622350e678ea325ab5e3e96e52126 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Sat, 3 Feb 2018 10:11:14 +0000 Subject: [PATCH 7/9] Cleanup gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3d47d9e0..836cc513 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports +*/**/.pytest_cache/ htmlcov/ .tox/ .coverage @@ -132,6 +133,5 @@ tags \.idea/ -\.pytest_cache/ - \.DS_Store + From 23640ac5e5ce2cbd1afa4267e3e59067039d73d8 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Fri, 13 Jul 2018 09:42:24 +0100 Subject: [PATCH 8/9] Fix request from pull request #20 --- bumpversion/__init__.py | 5 +---- bumpversion/version_part.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index 45a09a75..29b816e2 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -359,10 +359,7 @@ def _compare(self, other, method, strict=True): for vals in ((v, other[k]) for k, v in self.items()): if vals[0] == vals[1]: continue - if method(vals[0], vals[1]): - return True - else: - return False + return method(vals[0], vals[1]) return not strict except KeyError: raise TypeError("Versions use different parts, cant compare them.") diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index 64e261df..d5762797 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -67,15 +67,15 @@ def __hash__(self): return hash(self.value) def _compare(self, other, method): - if self.config.function_cls is not other.config.function_cls: - raise TypeError("Versions use different part specific configuration, cant compare them.") - if self.config.function_cls is NumericFunction: - return method(self.value, other.value) - else: - # Compare order - idx1 = self.config.function.index(self.value) - idx2 = other.config.function.index(other.value) - return method(idx1, idx2) + if self.config.function_cls is not other.config.function_cls: + raise TypeError("Versions use different part specific configuration, cant compare them.") + if self.config.function_cls is NumericFunction: + return method(self.value, other.value) + else: + # Compare order + idx1 = self.config.function.index(self.value) + idx2 = other.config.function.index(other.value) + return method(idx1, idx2) def __eq__(self, other): return self._compare(other, lambda s, o: s == o) From fc768bd7d740d0d8d63b2cf32ad50fb389de6205 Mon Sep 17 00:00:00 2001 From: Horacio Hoyos Rodriguez Date: Fri, 7 Dec 2018 13:47:34 +0000 Subject: [PATCH 9/9] Reply to comments --- bumpversion/__init__.py | 81 ++++++++++++++++++++++++++----------- bumpversion/version_part.py | 16 ++++---- tests/test_version.py | 15 ++++++- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/bumpversion/__init__.py b/bumpversion/__init__.py index 29b816e2..b9eaaf95 100644 --- a/bumpversion/__init__.py +++ b/bumpversion/__init__.py @@ -14,12 +14,15 @@ import argparse +from argparse import _AppendAction import os import re import sre_constants import subprocess import warnings import io +import operator +import logging from string import Formatter from datetime import datetime from difflib import unified_diff @@ -41,11 +44,11 @@ sys.version.split("\n")[0].split(" ")[0], ) -import logging + logger = logging.getLogger("bumpversion.logger") logger_list = logging.getLogger("bumpversion.list") -from argparse import _AppendAction + class DiscardDefaultIfSpecifiedAppendAction(_AppendAction): ''' @@ -60,11 +63,13 @@ def __call__(self, parser, namespace, values, option_string=None): super(DiscardDefaultIfSpecifiedAppendAction, self).__call__( parser, namespace, values, option_string=None) + time_context = { 'now': datetime.now(), 'utcnow': datetime.utcnow(), } + class BaseVCS(object): @classmethod @@ -204,12 +209,14 @@ def tag(cls, sign, name, message): command += ['--message', message] subprocess.check_output(command) + VCS = [Git, Mercurial] def prefixed_environ(): return dict((("${}".format(key), value) for key, value in os.environ.items())) + class ConfiguredFile(object): def __init__(self, path, versionconfig): @@ -302,35 +309,41 @@ def __str__(self): def __repr__(self): return ''.format(self.path) + class IncompleteVersionRepresenationException(Exception): def __init__(self, message): self.message = message + class MissingValueForSerializationException(Exception): def __init__(self, message): self.message = message + class WorkingDirectoryIsDirtyException(Exception): def __init__(self, message): self.message = message + class MercurialDoesNotSupportSignedTagsException(Exception): def __init__(self, message): self.message = message + class UnkownPart(Exception): def __init__(self, message): self.message = message + def keyvaluestring(d): return ", ".join("{}={}".format(k, v) for k, v in sorted(d.items())) + class Version(object): - def __init__(self, values, config, original=None): + def __init__(self, values, order): self._values = dict(values) - self.config = config - self.original = original + self.order = order def __getitem__(self, key): return self._values[key] @@ -343,26 +356,38 @@ def __iter__(self): def __repr__(self): by_part = ", ".join("{}={}".format(k, v) for k, v in self.items()) - return ''.format(by_part) + return '<{}:{}>'.format(self.__class__, by_part) def __hash__(self): return hash(tuple((k, v) for k, v in self.items())) def _compare(self, other, method, strict=True): """ - For strict relations, if a part is equal we skip it + When comparing versions we need to compare the three parts before we can decide if there is a difference. + Non-strict comparators need to be treated differently as they can not fail if the initial parts are equal :param other: the other Version :param method: the compare method + :param strict: if the comparsion is strict :return: """ - try: - for vals in ((v, other[k]) for k, v in self.items()): - if vals[0] == vals[1]: - continue - return method(vals[0], vals[1]) - return not strict - except KeyError: + if set(self.order).difference(other.order): raise TypeError("Versions use different parts, cant compare them.") + for (x, y) in zip(self.values(), other.values()): + if x == y: + continue + else: + return method(x, y) + return not strict + # try: + + # + # for vals in ((v, other[k]) for k, v in self.items()): + # if vals[0] == vals[1]: + # continue + # return method(vals[0], vals[1]) + # return not strict + # except KeyError: + # def __eq__(self, other): if self is other: @@ -375,27 +400,34 @@ def __eq__(self, other): raise TypeError("Versions use different parts, cant compare them.") def __ne__(self, other): - return not self.__eq__(other) + return not self == other def __le__(self, other): - return self._compare(other, lambda s, o: o > s, False) # Note the change of order in operands + return self._compare(other, operator.lt, False) def __ge__(self, other): - return self._compare(other, lambda s, o: o < s, False) # Note the change of order in operands + return self._compare(other, operator.gt, False) def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) + return self._compare(other, operator.lt) def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) + return self._compare(other, operator.gt) def items(self): - for k in self.config.order(): + for k in self.order: try: yield k, self._values[k] except KeyError: raise StopIteration + def values(self): + for k in self.order: + try: + yield self._values[k] + except KeyError: + raise StopIteration + def bump(self, part_name, order): bumped = False @@ -412,10 +444,11 @@ def bump(self, part_name, order): else: new_values[label] = self._values[label].copy() - new_version = Version(new_values, self.config) + new_version = Version(new_values, self.order) return new_version + class VersionConfig(object): """ @@ -468,8 +501,7 @@ def parse(self, version_string): for key, value in match.groupdict().items(): _parsed[key] = VersionPart(value, self.part_configs.get(key)) - - v = Version(_parsed, self, version_string) + v = Version(_parsed, [o for o in self.order()]) logger.info("Parsed the following values: %s" % keyvaluestring(v._values)) @@ -530,7 +562,6 @@ def _serialize(self, version, serialize_format, context, raise_if_incomplete=Fal return serialized - def _choose_serialize_format(self, version, context): chosen = None @@ -562,6 +593,7 @@ def serialize(self, version, context): # logger.info("Serialized to '{}'".format(serialized)) return serialized + OPTIONAL_ARGUMENTS_THAT_TAKE_VALUES = [ '--config-file', '--current-version', @@ -598,6 +630,7 @@ def split_args_in_optional_and_positional(args): return (positionals, args) + def main(original_args=None): positionals, args = split_args_in_optional_and_positional( diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index d5762797..3e342fbd 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -1,4 +1,6 @@ from bumpversion.functions import NumericFunction, ValuesFunction +import operator + class PartConfiguration(object): function_cls = NumericFunction @@ -35,10 +37,8 @@ class VersionPart(object): def __init__(self, value, config=None): self._value = value - if config is None: config = NumericVersionPartConfiguration() - self.config = config @property @@ -78,22 +78,22 @@ def _compare(self, other, method): return method(idx1, idx2) def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) + return self._compare(other, operator.eq) def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) + return self._compare(other, operator.lt) def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) + return self._compare(other, operator.le) def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) + return self._compare(other, operator.ge) def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) + return self._compare(other, operator.gt) def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) + return not self == other def null(self): return VersionPart(self.config.first_value, self.config) diff --git a/tests/test_version.py b/tests/test_version.py index 9d12cbd0..37444393 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals, print_function import pytest -import bumpversion from bumpversion import VersionConfig from bumpversion.version_part import ConfiguredVersionPartConfiguration @@ -82,6 +81,20 @@ def test_compare_versions_numeric(): assert v2 > v1 assert not (v2 <= v1) assert v2 >= v1 + v1 = vc.parse(version_string="3.2.4") + v2 = vc.parse(version_string="2.3.1") + assert not (v1 == v2) + assert v1 != v2 + assert not (v1 < v2) + assert v1 > v2 + assert not (v1 <= v2) + assert v1 >= v2 + assert not (v2 == v1) + assert v2 != v1 + assert v2 < v1 + assert not (v2 > v1) + assert v2 <= v1 + assert not (v2 >= v1) def test_compare_versions_values():