Skip to content

Commit 2ef2eea

Browse files
authored
Merge pull request #243 from tomschr/feature/138-add-__getitem__
Implement __getitem__ for #138
2 parents 8d4336e + ec6588e commit 2ef2eea

File tree

4 files changed

+162
-2
lines changed

4 files changed

+162
-2
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Version 2.10.0 (WIP)
1515
Features
1616
--------
1717

18+
* :pr:`138`: Added ``__getitem__`` magic method to ``semver.VersionInfo`` class.
19+
Allows to access a version like ``version[1]``.
1820
* :pr:`235`: Improved documentation and shift focus on ``semver.VersionInfo`` instead of advertising
1921
the old and deprecated module-level functions.
2022

docs/usage.rst

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ classmethod :func:`semver.VersionInfo.isvalid`:
141141
False
142142
143143
144-
Accessing Parts of a Version
145-
----------------------------
144+
.. _sec.properties.parts:
145+
146+
Accessing Parts of a Version Through Names
147+
------------------------------------------
146148

147149
The :class:`semver.VersionInfo` contains attributes to access the different
148150
parts of a version:
@@ -184,6 +186,55 @@ In case you need the different parts of a version stepwise, iterate over the :cl
184186
[3, 4, 5, 'pre.2', 'build.4']
185187

186188

189+
.. _sec.getitem.parts:
190+
191+
Accessing Parts Through Index Numbers
192+
-------------------------------------
193+
194+
.. versionadded:: 2.10.0
195+
196+
Another way to access parts of a version is to use an index notation. The underlying
197+
:class:`VersionInfo <semver.VersionInfo>` object allows to access its data through
198+
the magic method :func:`__getitem__ <semver.VersionInfo.__getitem__>`.
199+
200+
For example, the ``major`` part can be accessed by index number 0 (zero).
201+
Likewise the other parts:
202+
203+
.. code-block:: python
204+
205+
>>> ver = semver.VersionInfo.parse("10.3.2-pre.5+build.10")
206+
>>> ver[0], ver[1], ver[2], ver[3], ver[4]
207+
(10, 3, 2, 'pre.5', 'build.10')
208+
209+
If you need more than one part at the same time, use the slice notation:
210+
211+
.. code-block:: python
212+
213+
>>> ver[0:3]
214+
(10, 3, 2)
215+
216+
Or, as an alternative, you can pass a :func:`slice` object:
217+
218+
.. code-block:: python
219+
220+
>>> sl = slice(0,3)
221+
>>> ver[sl]
222+
(10, 3, 2)
223+
224+
Negative numbers or undefined parts raise an :class:`IndexError` exception:
225+
226+
.. code-block:: python
227+
228+
>>> ver = semver.VersionInfo.parse("10.3.2")
229+
>>> ver[3]
230+
Traceback (most recent call last):
231+
...
232+
IndexError: Version part undefined
233+
>>> ver[-2]
234+
Traceback (most recent call last):
235+
...
236+
IndexError: Version index cannot be negative
237+
187238
.. _sec.replace.parts:
188239

189240
Replacing Parts of a Version

semver.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,42 @@ def __gt__(self, other):
493493
def __ge__(self, other):
494494
return self.compare(other) >= 0
495495

496+
def __getitem__(self, index):
497+
"""
498+
self.__getitem__(index) <==> self[index]
499+
500+
Implement getitem. If the part requested is undefined, or a part of the
501+
range requested is undefined, it will throw an index error.
502+
Negative indices are not supported
503+
504+
:param Union[int, slice] index: a positive integer indicating the
505+
offset or a :func:`slice` object
506+
:raises: IndexError, if index is beyond the range or a part is None
507+
:return: the requested part of the version at position index
508+
509+
>>> ver = semver.VersionInfo.parse("3.4.5")
510+
>>> ver[0], ver[1], ver[2]
511+
(3, 4, 5)
512+
"""
513+
if isinstance(index, int):
514+
index = slice(index, index + 1)
515+
516+
if (
517+
isinstance(index, slice)
518+
and (index.start is None or index.start < 0)
519+
and (index.stop is None or index.stop < 0)
520+
):
521+
raise IndexError("Version index cannot be negative")
522+
523+
# Could raise IndexError:
524+
part = tuple(filter(None, self.to_tuple()[index]))
525+
526+
if len(part) == 1:
527+
part = part[0]
528+
if not part:
529+
raise IndexError("Version part undefined")
530+
return part
531+
496532
def __repr__(self):
497533
s = ", ".join("%s=%r" % (key, val) for key, val in self.to_dict().items())
498534
return "%s(%s)" % (type(self).__name__, s)

test_semver.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,77 @@ def test_should_be_able_to_use_integers_as_prerelease_build():
695695
assert VersionInfo(1, 2, 3, 4, 5) == VersionInfo(1, 2, 3, "4", "5")
696696

697697

698+
@pytest.mark.parametrize(
699+
"version, index, expected",
700+
[
701+
# Simple positive indices
702+
("1.2.3-rc.0+build.0", 0, 1),
703+
("1.2.3-rc.0+build.0", 1, 2),
704+
("1.2.3-rc.0+build.0", 2, 3),
705+
("1.2.3-rc.0+build.0", 3, "rc.0"),
706+
("1.2.3-rc.0+build.0", 4, "build.0"),
707+
("1.2.3-rc.0", 0, 1),
708+
("1.2.3-rc.0", 1, 2),
709+
("1.2.3-rc.0", 2, 3),
710+
("1.2.3-rc.0", 3, "rc.0"),
711+
("1.2.3", 0, 1),
712+
("1.2.3", 1, 2),
713+
("1.2.3", 2, 3),
714+
],
715+
)
716+
def test_version_info_should_be_accessed_with_index(version, index, expected):
717+
version_info = VersionInfo.parse(version)
718+
assert version_info[index] == expected
719+
720+
721+
@pytest.mark.parametrize(
722+
"version, slice_object, expected",
723+
[
724+
# Slice indices
725+
("1.2.3-rc.0+build.0", slice(0, 5), (1, 2, 3, "rc.0", "build.0")),
726+
("1.2.3-rc.0+build.0", slice(0, 4), (1, 2, 3, "rc.0")),
727+
("1.2.3-rc.0+build.0", slice(0, 3), (1, 2, 3)),
728+
("1.2.3-rc.0+build.0", slice(0, 2), (1, 2)),
729+
("1.2.3-rc.0+build.0", slice(3, 5), ("rc.0", "build.0")),
730+
("1.2.3-rc.0", slice(0, 4), (1, 2, 3, "rc.0")),
731+
("1.2.3-rc.0", slice(0, 3), (1, 2, 3)),
732+
("1.2.3-rc.0", slice(0, 2), (1, 2)),
733+
("1.2.3", slice(0, 10), (1, 2, 3)),
734+
("1.2.3", slice(0, 3), (1, 2, 3)),
735+
("1.2.3", slice(0, 2), (1, 2)),
736+
# Special cases
737+
("1.2.3-rc.0+build.0", slice(3), (1, 2, 3)),
738+
("1.2.3-rc.0+build.0", slice(0, 5, 2), (1, 3, "build.0")),
739+
("1.2.3-rc.0+build.0", slice(None, 5, 2), (1, 3, "build.0")),
740+
("1.2.3-rc.0+build.0", slice(5, 0, -2), ("build.0", 3)),
741+
],
742+
)
743+
def test_version_info_should_be_accessed_with_slice_object(
744+
version, slice_object, expected
745+
):
746+
version_info = VersionInfo.parse(version)
747+
assert version_info[slice_object] == expected
748+
749+
750+
@pytest.mark.parametrize(
751+
"version, index",
752+
[
753+
("1.2.3-rc.0+build.0", -1),
754+
("1.2.3-rc.0", -1),
755+
("1.2.3-rc.0", 4),
756+
("1.2.3", -1),
757+
("1.2.3", 3),
758+
("1.2.3", 4),
759+
("1.2.3", 10),
760+
("1.2.3", slice(-3)),
761+
],
762+
)
763+
def test_version_info_should_throw_index_error(version, index):
764+
version_info = VersionInfo.parse(version)
765+
with pytest.raises(IndexError):
766+
version_info[index]
767+
768+
698769
@pytest.mark.parametrize(
699770
"cli,expected",
700771
[

0 commit comments

Comments
 (0)