diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ddefadcb..b731db8e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,8 @@ Version 2.10.0 (WIP) Features -------- +* :pr:`138`: Added ``__getitem__`` magic method to ``semver.VersionInfo`` class. + Allows to access a version like ``version[1]``. * :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising the old and deprecated module-level functions. diff --git a/docs/usage.rst b/docs/usage.rst index 91e4d069..63b18f6a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -141,8 +141,10 @@ classmethod :func:`semver.VersionInfo.isvalid`: False -Accessing Parts of a Version ----------------------------- +.. _sec.properties.parts: + +Accessing Parts of a Version Through Names +------------------------------------------ The :class:`semver.VersionInfo` contains attributes to access the different parts of a version: @@ -184,6 +186,55 @@ In case you need the different parts of a version stepwise, iterate over the :cl [3, 4, 5, 'pre.2', 'build.4'] +.. _sec.getitem.parts: + +Accessing Parts Through Index Numbers +------------------------------------- + +.. versionadded:: 2.10.0 + +Another way to access parts of a version is to use an index notation. The underlying +:class:`VersionInfo ` object allows to access its data through +the magic method :func:`__getitem__ `. + +For example, the ``major`` part can be accessed by index number 0 (zero). +Likewise the other parts: + +.. code-block:: python + + >>> ver = semver.VersionInfo.parse("10.3.2-pre.5+build.10") + >>> ver[0], ver[1], ver[2], ver[3], ver[4] + (10, 3, 2, 'pre.5', 'build.10') + +If you need more than one part at the same time, use the slice notation: + +.. code-block:: python + + >>> ver[0:3] + (10, 3, 2) + +Or, as an alternative, you can pass a :func:`slice` object: + +.. code-block:: python + + >>> sl = slice(0,3) + >>> ver[sl] + (10, 3, 2) + +Negative numbers or undefined parts raise an :class:`IndexError` exception: + +.. code-block:: python + + >>> ver = semver.VersionInfo.parse("10.3.2") + >>> ver[3] + Traceback (most recent call last): + ... + IndexError: Version part undefined + >>> ver[-2] + Traceback (most recent call last): + ... + IndexError: Version index cannot be negative + .. _sec.replace.parts: Replacing Parts of a Version diff --git a/semver.py b/semver.py index a5739b16..b3961083 100644 --- a/semver.py +++ b/semver.py @@ -446,6 +446,42 @@ def __gt__(self, other): def __ge__(self, other): return self.compare(other) >= 0 + def __getitem__(self, index): + """ + self.__getitem__(index) <==> self[index] + + Implement getitem. If the part requested is undefined, or a part of the + range requested is undefined, it will throw an index error. + Negative indices are not supported + + :param Union[int, slice] index: a positive integer indicating the + offset or a :func:`slice` object + :raises: IndexError, if index is beyond the range or a part is None + :return: the requested part of the version at position index + + >>> ver = semver.VersionInfo.parse("3.4.5") + >>> ver[0], ver[1], ver[2] + (3, 4, 5) + """ + if isinstance(index, int): + 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) + ): + raise IndexError("Version index cannot be negative") + + # Could raise IndexError: + part = tuple(filter(None, self.to_tuple()[index])) + + if len(part) == 1: + part = part[0] + if not part: + raise IndexError("Version part undefined") + return part + def __repr__(self): s = ", ".join("%s=%r" % (key, val) for key, val in self.to_dict().items()) return "%s(%s)" % (type(self).__name__, s) diff --git a/test_semver.py b/test_semver.py index 5daf3f1a..6c7eea49 100644 --- a/test_semver.py +++ b/test_semver.py @@ -695,6 +695,77 @@ def test_should_be_able_to_use_integers_as_prerelease_build(): assert VersionInfo(1, 2, 3, 4, 5) == VersionInfo(1, 2, 3, "4", "5") +@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_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, 10), (1, 2, 3)), + ("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+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), + ("1.2.3", 10), + ("1.2.3", slice(-3)), + ], +) +def test_version_info_should_throw_index_error(version, index): + version_info = VersionInfo.parse(version) + with pytest.raises(IndexError): + version_info[index] + + @pytest.mark.parametrize( "cli,expected", [ pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy