From f1d7cdee8c1deccfba9b0ee39f5f19a0a1cdfc42 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Fri, 16 Aug 2019 16:39:38 -0400 Subject: [PATCH 01/20] Add __getitem__ method to allow easy access to version parts --- semver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/semver.py b/semver.py index d234f26a..8f1b46e2 100644 --- a/semver.py +++ b/semver.py @@ -164,6 +164,9 @@ def __iter__(self): for v in self._astuple(): yield v + def __getitem__(self, index): + return self._astuple()[index] + @comparator def __eq__(self, other): return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 From ba5899e0bb95bb6c34ab86a4f928064c4ae06d6b Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Mon, 19 Aug 2019 09:56:55 -0400 Subject: [PATCH 02/20] Add unit tests for index access to version parts --- test_semver.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test_semver.py b/test_semver.py index 96b78967..d857f729 100644 --- a/test_semver.py +++ b/test_semver.py @@ -497,3 +497,15 @@ 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) + + +@pytest.mark.parametrize("version, index, expected", [ + ("1.2.3-rc.0+build.0", 0, 1), + ("1.2.3-rc.0+build.0", 1, 2), + ("1.2.3-rc.0+build.0", 2, 3), + ("1.2.3-rc.0+build.0", 3, "rc.0"), + ("1.2.3-rc.0+build.0", 4, "build.0"), +]) +def test_version_info_should_be_accessed_with_index(version, index, expected): + version_info = VersionInfo.parse(version) + assert version_info[index] == expected From e88e0ba8a49655cafbafe70f4674440dffb9a2dd Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Mon, 19 Aug 2019 09:59:41 -0400 Subject: [PATCH 03/20] Remove whitespace on blank line --- test_semver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_semver.py b/test_semver.py index d857f729..a22ed67c 100644 --- a/test_semver.py +++ b/test_semver.py @@ -498,7 +498,7 @@ def test_version_info_should_be_iterable(version): assert tuple(version) == (version.major, version.minor, version.patch, version.prerelease, version.build) - + @pytest.mark.parametrize("version, index, expected", [ ("1.2.3-rc.0+build.0", 0, 1), ("1.2.3-rc.0+build.0", 1, 2), From 80da20c78117c59a9d37567aeb8591f55cef9a93 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 8 Oct 2019 11:45:41 -0400 Subject: [PATCH 04/20] Fix stash conflicts and create more test vectors. Also add private utility function for check the validity of the index. --- semver.py | 27 ++++++++++++++++++- test_semver.py | 71 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/semver.py b/semver.py index f9950efc..39ac420d 100644 --- a/semver.py +++ b/semver.py @@ -168,8 +168,33 @@ def __iter__(self): for v in self._astuple(): yield v + @staticmethod + def _validate_index(index): + if isinstance(index, slice): + checks = [index.stop >= 0] + if index.start is not None: + checks.append(index.start >= 0) + if index.step is not None: + if index.step > 0: + checks.append(index.start < index.stop) + else: + checks.append(index.start > index.stop) + elif index.step is not None: + checks.append(index.step > 0) + return all(checks) + else: + return index >= 0 + def __getitem__(self, index): - return self._astuple()[index] + if VersionInfo._validate_index(index): + sub_version = self._astuple()[index] + if sub_version is not None: + return sub_version + else: + raise IndexError("Version part at index %d is not defined" + % index) + else: + raise IndexError("VersionInfo does not support negative index") def bump_major(self): """Raise the major part of the version, return a new object diff --git a/test_semver.py b/test_semver.py index f3d86604..fba6c053 100644 --- a/test_semver.py +++ b/test_semver.py @@ -548,18 +548,6 @@ def test_version_info_should_be_iterable(version): version.prerelease, version.build) -<<<<<<< HEAD -@pytest.mark.parametrize("version, index, expected", [ - ("1.2.3-rc.0+build.0", 0, 1), - ("1.2.3-rc.0+build.0", 1, 2), - ("1.2.3-rc.0+build.0", 2, 3), - ("1.2.3-rc.0+build.0", 3, "rc.0"), - ("1.2.3-rc.0+build.0", 4, "build.0"), -]) -def test_version_info_should_be_accessed_with_index(version, index, expected): - version_info = VersionInfo.parse(version) - assert version_info[index] == expected -======= 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) @@ -592,4 +580,61 @@ 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') ->>>>>>> c585f5cb8b9a0d5859a885e94a7e84597a554d67 + + +@pytest.mark.parametrize("version, index, expected", [ + # Simple positive indices + ("1.2.3-rc.0+build.0", 0, 1), + ("1.2.3-rc.0+build.0", 1, 2), + ("1.2.3-rc.0+build.0", 2, 3), + ("1.2.3-rc.0+build.0", 3, "rc.0"), + ("1.2.3-rc.0+build.0", 4, "build.0"), + ("1.2.3-rc.0", 0, 1), + ("1.2.3-rc.0", 1, 2), + ("1.2.3-rc.0", 2, 3), + ("1.2.3-rc.0", 3, "rc.0"), + ("1.2.3", 0, 1), + ("1.2.3", 1, 2), + ("1.2.3", 2, 3), +]) +def test_version_info_should_be_accessed_with_positive_index(version, + index, expected): + version_info = VersionInfo.parse(version) + assert version_info[index] == expected + + +@pytest.mark.parametrize("version, slice_object, expected", [ + # Slice indices + ("1.2.3-rc.0+build.0", slice(0, 5), (1, 2, 3, "rc.0", "build.0")), + ("1.2.3-rc.0+build.0", slice(0, 4), (1, 2, 3, "rc.0")), + ("1.2.3-rc.0+build.0", slice(0, 3), (1, 2, 3)), + ("1.2.3-rc.0+build.0", slice(0, 2), (1, 2)), + ("1.2.3-rc.0+build.0", slice(3, 5), ("rc.0", "build.0")), + ("1.2.3-rc.0", slice(0, 4), (1, 2, 3, "rc.0")), + ("1.2.3-rc.0", slice(0, 3), (1, 2, 3)), + ("1.2.3-rc.0", slice(0, 2), (1, 2)), + ("1.2.3", slice(0, 3), (1, 2, 3)), + ("1.2.3", slice(0, 2), (1, 2)), + # Special cases + ("1.2.3-rc.0+build.0", slice(3), (1, 2, 3)), + ("1.2.3-rc.0+build.0", slice(0, 5, 2), (1, 3, "build.0")), + ("1.2.3-rc.0+build.0", slice(None, 5, 2), (1, 3, "build.0")), + ("1.2.3-rc.0+build.0", slice(5, 0, -2), ("build.0", 3)), +]) +def test_version_info_should_be_accessed_with_slice_object(version, + slice_object, + expected): + version_info = VersionInfo.parse(version) + assert version_info[slice_object] == expected + + +@pytest.mark.parametrize("version, index", [ + ("1.2.3-rc.0", 4), + ("1.2.3-rc.0+build.0", -1), + ("1.2.3", 3), + ("1.2.3", 4), +]) +def test_version_info_should_throw_index_error(version, index): + version_info = VersionInfo.parse(version) + with pytest.raises(IndexError): + version_info[index] From a610c2256dfc2e9de4f254dc268700512b38a7dc Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 8 Oct 2019 13:49:05 -0400 Subject: [PATCH 05/20] Use simpler implementation that allows negative indices and remove now useless _validate_index. --- semver.py | 30 ++++-------------------------- test_semver.py | 8 +++++--- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/semver.py b/semver.py index 39ac420d..07bdb03d 100644 --- a/semver.py +++ b/semver.py @@ -168,33 +168,11 @@ def __iter__(self): for v in self._astuple(): yield v - @staticmethod - def _validate_index(index): - if isinstance(index, slice): - checks = [index.stop >= 0] - if index.start is not None: - checks.append(index.start >= 0) - if index.step is not None: - if index.step > 0: - checks.append(index.start < index.stop) - else: - checks.append(index.start > index.stop) - elif index.step is not None: - checks.append(index.step > 0) - return all(checks) - else: - return index >= 0 - def __getitem__(self, index): - if VersionInfo._validate_index(index): - sub_version = self._astuple()[index] - if sub_version is not None: - return sub_version - else: - raise IndexError("Version part at index %d is not defined" - % index) - else: - raise IndexError("VersionInfo does not support negative index") + """Implement getitem. This automatically strips empty parts of the + version from the iterable from which the index is taken.""" + return tuple(part for part in self._astuple() + if part is not None)[index] def bump_major(self): """Raise the major part of the version, return a new object diff --git a/test_semver.py b/test_semver.py index fba6c053..a0f75f81 100644 --- a/test_semver.py +++ b/test_semver.py @@ -589,16 +589,18 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): ("1.2.3-rc.0+build.0", 2, 3), ("1.2.3-rc.0+build.0", 3, "rc.0"), ("1.2.3-rc.0+build.0", 4, "build.0"), + ("1.2.3-rc.0+build.0", -1, "build.0"), ("1.2.3-rc.0", 0, 1), ("1.2.3-rc.0", 1, 2), ("1.2.3-rc.0", 2, 3), ("1.2.3-rc.0", 3, "rc.0"), + ("1.2.3-rc.0", -1, "rc.0"), ("1.2.3", 0, 1), ("1.2.3", 1, 2), ("1.2.3", 2, 3), + ("1.2.3", -1, 3), ]) -def test_version_info_should_be_accessed_with_positive_index(version, - index, expected): +def test_version_info_should_be_accessed_with_index(version, index, expected): version_info = VersionInfo.parse(version) assert version_info[index] == expected @@ -613,6 +615,7 @@ def test_version_info_should_be_accessed_with_positive_index(version, ("1.2.3-rc.0", slice(0, 4), (1, 2, 3, "rc.0")), ("1.2.3-rc.0", slice(0, 3), (1, 2, 3)), ("1.2.3-rc.0", slice(0, 2), (1, 2)), + ("1.2.3", slice(0, 10), (1, 2, 3)), ("1.2.3", slice(0, 3), (1, 2, 3)), ("1.2.3", slice(0, 2), (1, 2)), # Special cases @@ -630,7 +633,6 @@ def test_version_info_should_be_accessed_with_slice_object(version, @pytest.mark.parametrize("version, index", [ ("1.2.3-rc.0", 4), - ("1.2.3-rc.0+build.0", -1), ("1.2.3", 3), ("1.2.3", 4), ]) From 95e79d8d98c349f4aed1b23265851a36877e6835 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Thu, 10 Oct 2019 11:08:05 -0400 Subject: [PATCH 06/20] Add param and raises to __getitem__ docstring Co-Authored-By: Tom Schraitle --- semver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/semver.py b/semver.py index 07bdb03d..a15e2793 100644 --- a/semver.py +++ b/semver.py @@ -170,7 +170,11 @@ def __iter__(self): def __getitem__(self, index): """Implement getitem. This automatically strips empty parts of the - version from the iterable from which the index is taken.""" + version from the iterable from which the index is taken. + + :param int index: a positive or negative integer indicating the offset + :raises: IndexError, if index is beyond the range + """ return tuple(part for part in self._astuple() if part is not None)[index] From 27632b726ca63a2fdbfef2a787e8977fc218252c Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 19 Nov 2019 20:49:59 -0500 Subject: [PATCH 07/20] Reiterate the logic for __getitem__ and update tests to follow the actual features. --- semver.py | 33 +++++++++++++++++++++++++++------ test_semver.py | 6 +++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/semver.py b/semver.py index e6d0f88b..0c8b6fdd 100644 --- a/semver.py +++ b/semver.py @@ -175,14 +175,35 @@ def __iter__(self): yield v def __getitem__(self, index): - """Implement getitem. This automatically strips empty parts of the - version from the iterable from which the index is taken. + """Implement getitem. If the part requested is undefined, or a part of the + range requested is undefined, it will throw an index error. + Negative indices are not supported - :param int index: a positive or negative integer indicating the offset - :raises: IndexError, if index is beyond the range + :param int index: a positive integer indicating the offset or a slice + :raises: IndexError, if index is beyond the range or the part is None """ - return tuple(part for part in self._astuple() - if part is not None)[index] + undefined_error = IndexError("Version part undefined") + negative_error = IndexError("Version index cannot be negative") + if isinstance(index, slice): + if (False if index.start is None else index.start < 0) or (False if index.stop is None else index.stop < 0): + raise negative_error + else: + slice_is_full = True + part = self._astuple()[index] + for i in part: + if i is None: + slice_is_full = False + elif not slice_is_full: + raise undefined_error + part = tuple(filter(None, part)) + else: + if index < 0: + raise negative_error + else: + part = self._astuple()[index] + if part is None: + raise undefined_error + return part def bump_major(self): """Raise the major part of the version, return a new object diff --git a/test_semver.py b/test_semver.py index f09b5170..0edbe61c 100644 --- a/test_semver.py +++ b/test_semver.py @@ -707,16 +707,13 @@ def test_replace_raises_ValueError_for_non_numeric_values(): ("1.2.3-rc.0+build.0", 2, 3), ("1.2.3-rc.0+build.0", 3, "rc.0"), ("1.2.3-rc.0+build.0", 4, "build.0"), - ("1.2.3-rc.0+build.0", -1, "build.0"), ("1.2.3-rc.0", 0, 1), ("1.2.3-rc.0", 1, 2), ("1.2.3-rc.0", 2, 3), ("1.2.3-rc.0", 3, "rc.0"), - ("1.2.3-rc.0", -1, "rc.0"), ("1.2.3", 0, 1), ("1.2.3", 1, 2), ("1.2.3", 2, 3), - ("1.2.3", -1, 3), ]) def test_version_info_should_be_accessed_with_index(version, index, expected): version_info = VersionInfo.parse(version) @@ -750,7 +747,10 @@ def test_version_info_should_be_accessed_with_slice_object(version, @pytest.mark.parametrize("version, index", [ + ("1.2.3-rc.0+build.0", -1), + ("1.2.3-rc.0", -1), ("1.2.3-rc.0", 4), + ("1.2.3", -1), ("1.2.3", 3), ("1.2.3", 4), ]) From 976a4ee2da6ad276e26ff9839611dacd2e27d9e9 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 19 Nov 2019 20:54:12 -0500 Subject: [PATCH 08/20] Update __getitem__ docstring --- semver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 0c8b6fdd..f30af70e 100644 --- a/semver.py +++ b/semver.py @@ -179,8 +179,8 @@ def __getitem__(self, index): range requested is undefined, it will throw an index error. Negative indices are not supported - :param int index: a positive integer indicating the offset or a slice - :raises: IndexError, if index is beyond the range or the part is None + :param Union[int, slice] index: a positive integer indicating the offset or a slice + :raises: IndexError, if index is beyond the range or a part is None """ undefined_error = IndexError("Version part undefined") negative_error = IndexError("Version index cannot be negative") From cf65b3d38c77bc6286baa6ccf983f104dd404fd2 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 19 Nov 2019 21:03:38 -0500 Subject: [PATCH 09/20] Fix lint issues --- semver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index f30af70e..6b0a806f 100644 --- a/semver.py +++ b/semver.py @@ -179,13 +179,15 @@ def __getitem__(self, index): range requested is undefined, it will throw an index error. Negative indices are not supported - :param Union[int, slice] index: a positive integer indicating the offset or a slice + :param Union[int, slice] index: a positive integer indicating the offset + or a slice :raises: IndexError, if index is beyond the range or a part is None """ undefined_error = IndexError("Version part undefined") negative_error = IndexError("Version index cannot be negative") if isinstance(index, slice): - if (False if index.start is None else index.start < 0) or (False if index.stop is None else index.stop < 0): + if (False if index.start is None else index.start < 0) or \ + (False if index.stop is None else index.stop < 0): raise negative_error else: slice_is_full = True From dabebd96bbb93e1082e9f8f983900690ac02b09c Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 19 Nov 2019 23:03:58 -0500 Subject: [PATCH 10/20] Fix lint issues --- semver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 6b0a806f..66d5262d 100644 --- a/semver.py +++ b/semver.py @@ -179,8 +179,8 @@ def __getitem__(self, index): range requested is undefined, it will throw an index error. Negative indices are not supported - :param Union[int, slice] index: a positive integer indicating the offset - or a slice + :param Union[int, slice] index: a positive integer indicating the + offset or a slice :raises: IndexError, if index is beyond the range or a part is None """ undefined_error = IndexError("Version part undefined") From c358a3367cd375d32efcca39dda576135ebb8d52 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Wed, 13 May 2020 12:22:57 -0400 Subject: [PATCH 11/20] Add documentation to VersionInfo.next_version and return only VersionInfo --- semver.py | 5 +++-- test_semver.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/semver.py b/semver.py index c2697dd5..9145a2f9 100644 --- a/semver.py +++ b/semver.py @@ -471,7 +471,8 @@ def next_version(self, part, prerelease_token="rc"): :param part: One of "major", "minor", "patch", or "prerelease" :param prerelease_token: prefix string of prerelease, defaults to 'rc' - :return: + :return: new object with the appropriate raised part + :rtype: VersionInfo """ validparts = { "major", @@ -495,7 +496,7 @@ def next_version(self, part, prerelease_token="rc"): return version.replace(prerelease=None, build=None) if part in ("major", "minor", "patch"): - return str(getattr(version, "bump_" + part)()) + return getattr(version, "bump_" + part)() if not version.prerelease: version = version.bump_patch() diff --git a/test_semver.py b/test_semver.py index db0c7f4e..8ecc81fb 100644 --- a/test_semver.py +++ b/test_semver.py @@ -1050,4 +1050,6 @@ def test_next_version_with_invalid_parts(): ) def test_next_version_with_versioninfo(version, part, expected): ver = VersionInfo.parse(version) - assert str(ver.next_version(part)) == expected + next_version = ver.next_version(part) + assert isinstance(next_version, VersionInfo) + assert str(next_version) == expected From 85f7a88fef4d861a918077bcb190ccdf4b25e3fe Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Wed, 13 May 2020 12:26:18 -0400 Subject: [PATCH 12/20] Add :class: --- semver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index 9145a2f9..cf535350 100644 --- a/semver.py +++ b/semver.py @@ -471,8 +471,8 @@ def next_version(self, part, prerelease_token="rc"): :param part: One of "major", "minor", "patch", or "prerelease" :param prerelease_token: prefix string of prerelease, defaults to 'rc' - :return: new object with the appropriate raised part - :rtype: VersionInfo + :return: new object with the appropriate part raised + :rtype: :class:`VersionInfo` """ validparts = { "major", From a32de1065c92d7ff88e676ca2f68c606073d9eeb Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Wed, 13 May 2020 13:28:03 -0400 Subject: [PATCH 13/20] Add 2.10.1 to changelog --- CHANGELOG.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94564c7f..08c1659c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,32 @@ Change Log All notable changes to this code base will be documented in this file, in every released version. +Version 2.10.1 (WIP) +==================== + +:Released: 2020-0x-0y +:Maintainer: + + +Features +-------- + + +Bug Fixes +--------- + +* :gh:`251` (:pr:`000`): Fixed return type of ``semver.VersionInfo.next_version`` + to always return a ``VersionInfo`` instance. + + +Additions +--------- + + +Removals +-------- + + Version 2.10.0 ============== From d5a7f629a29878f3b4348bbd883346c1eb51b29a Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Wed, 13 May 2020 13:30:07 -0400 Subject: [PATCH 14/20] Update PR number in changelog --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 08c1659c..59459fe5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,7 +20,7 @@ Features Bug Fixes --------- -* :gh:`251` (:pr:`000`): Fixed return type of ``semver.VersionInfo.next_version`` +* :gh:`251` (:pr:`251`): Fixed return type of ``semver.VersionInfo.next_version`` to always return a ``VersionInfo`` instance. From bb4464dd77bd92d2c5f6766483072ce01697306c Mon Sep 17 00:00:00 2001 From: Tom Schraitle Date: Wed, 13 May 2020 20:09:41 +0200 Subject: [PATCH 15/20] Update CHANGELOG.rst --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 59459fe5..028e7fee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,7 +20,7 @@ Features Bug Fixes --------- -* :gh:`251` (:pr:`251`): Fixed return type of ``semver.VersionInfo.next_version`` +* :gh:`251` (:pr:`254`): Fixed return type of ``semver.VersionInfo.next_version`` to always return a ``VersionInfo`` instance. From 9c44e03b4213085a1b3dabfacc86c95925cfb059 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Mon, 8 Jun 2020 23:01:00 -0400 Subject: [PATCH 16/20] Fix #260 and add tests for these special cases and fix IndexError not being thrown every time it should. --- semver.py | 9 +++++++-- test_semver.py | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/semver.py b/semver.py index cf535350..dbdd1ddf 100644 --- a/semver.py +++ b/semver.py @@ -544,6 +544,8 @@ def __getitem__(self, index): (3, 4, 5) """ if isinstance(index, int): + if index < 0: + raise IndexError("Version index cannot be negative") index = slice(index, index + 1) if ( @@ -554,11 +556,14 @@ def __getitem__(self, index): raise IndexError("Version index cannot be negative") # Could raise IndexError: - part = tuple(filter(None, self.to_tuple()[index])) + part = tuple(filter(lambda p: p is not None, self.to_tuple()[index])) if len(part) == 1: part = part[0] - if not part: + elif len(part) == 0: + raise IndexError("Version part undefined") + + if part is None: raise IndexError("Version part undefined") return part diff --git a/test_semver.py b/test_semver.py index 8ecc81fb..1a820bc9 100644 --- a/test_semver.py +++ b/test_semver.py @@ -774,6 +774,8 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): ("1.2.3", 0, 1), ("1.2.3", 1, 2), ("1.2.3", 2, 3), + # Special cases + ("1.0.2", 1, 0), ], ) def test_version_info_should_be_accessed_with_index(version, index, expected): @@ -801,6 +803,7 @@ def test_version_info_should_be_accessed_with_index(version, index, expected): ("1.2.3-rc.0+build.0", slice(0, 5, 2), (1, 3, "build.0")), ("1.2.3-rc.0+build.0", slice(None, 5, 2), (1, 3, "build.0")), ("1.2.3-rc.0+build.0", slice(5, 0, -2), ("build.0", 3)), + ("1.2.0-rc.0+build.0", slice(3), (1, 2, 0)), ], ) def test_version_info_should_be_accessed_with_slice_object( From b6f1e20da883f13440d813ccc4a8d11d16148bc6 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 9 Jun 2020 08:03:26 -0400 Subject: [PATCH 17/20] Remove comment (suggestion) Co-authored-by: Tom Schraitle --- semver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/semver.py b/semver.py index 90bbfc22..a6c7513d 100644 --- a/semver.py +++ b/semver.py @@ -555,7 +555,6 @@ def __getitem__(self, index): ): raise IndexError("Version index cannot be negative") - # Could raise IndexError: part = tuple(filter(lambda p: p is not None, self.to_tuple()[index])) if len(part) == 1: From ac6ece5f1e0498893673dab40dddd8e0d90b6915 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 9 Jun 2020 10:08:31 -0400 Subject: [PATCH 18/20] Add tests, remove redundant if clause and fix logic for negative index error --- semver.py | 6 ++---- test_semver.py | 32 +++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/semver.py b/semver.py index 90bbfc22..1c57d771 100644 --- a/semver.py +++ b/semver.py @@ -544,14 +544,12 @@ def __getitem__(self, index): (3, 4, 5) """ if isinstance(index, int): - if index < 0: - raise IndexError("Version index cannot be negative") index = slice(index, index + 1) if ( isinstance(index, slice) - and (index.start is None or index.start < 0) - and (index.stop is None or index.stop < 0) + and (index.start is not None and index.start < 0) + or (index.stop is not None and index.stop < 0) ): raise IndexError("Version index cannot be negative") diff --git a/test_semver.py b/test_semver.py index 1a820bc9..1fd87ee6 100644 --- a/test_semver.py +++ b/test_semver.py @@ -816,19 +816,37 @@ def test_version_info_should_be_accessed_with_slice_object( @pytest.mark.parametrize( "version, index", [ - ("1.2.3-rc.0+build.0", -1), - ("1.2.3-rc.0", -1), - ("1.2.3-rc.0", 4), - ("1.2.3", -1), ("1.2.3", 3), + ("1.2.3", slice(3, 4)), ("1.2.3", 4), - ("1.2.3", 10), - ("1.2.3", slice(-3)), + ("1.2.3", slice(4, 5)), + ("1.2.3", 5), + ("1.2.3", slice(5, 6)), + ("1.2.3-rc.0", 5), + ("1.2.3-rc.0", slice(5, 6)), + ("1.2.3-rc.0", 6), + ("1.2.3-rc.0", slice(6, 7)), ], ) def test_version_info_should_throw_index_error(version, index): version_info = VersionInfo.parse(version) - with pytest.raises(IndexError): + with pytest.raises(IndexError, match=r"Version part undefined"): + version_info[index] + + +@pytest.mark.parametrize( + "version, index", + [ + ("1.2.3", -1), + ("1.2.3", -2), + ("1.2.3", slice(-2, 2)), + ("1.2.3", slice(2, -2)), + ("1.2.3", slice(-2, -2)), + ], +) +def test_version_info_should_throw_index_error_when_negative_index(version, index): + version_info = VersionInfo.parse(version) + with pytest.raises(IndexError, match=r"Version index cannot be negative"): version_info[index] From f6a17b3dccebc9702d9e27cb06b9ca493a7590e0 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 9 Jun 2020 17:51:41 -0400 Subject: [PATCH 19/20] Update CHANGELOG.rst --- CHANGELOG.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c28880e1..2671ef2e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,34 @@ All notable changes to this code base will be documented in this file, in every released version. +Version 2.10.2 (WIP) +==================== + +:Released: 2020-xx-yy +:Maintainer: + +Features +-------- + +n/a + +Bug Fixes +--------- + +:gh:`260` (:pr:`261`): Fixed ``__getitem__`` returning None on wrong parts + + +Additions +--------- + +n/a + +Removals +-------- + +n/a + + Version 2.10.1 ============== From aeae24a58c814678b41d29ee9fb81a8c517352c3 Mon Sep 17 00:00:00 2001 From: Thomas Laferriere Date: Tue, 9 Jun 2020 17:54:51 -0400 Subject: [PATCH 20/20] Apply suggestion to combine ifs then raise into one Co-authored-by: Tom Schraitle --- semver.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/semver.py b/semver.py index 84b6ed28..0c98af97 100644 --- a/semver.py +++ b/semver.py @@ -557,10 +557,7 @@ def __getitem__(self, index): if len(part) == 1: part = part[0] - elif len(part) == 0: - raise IndexError("Version part undefined") - - if part is None: + elif not part: raise IndexError("Version part undefined") return part