Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: Use case for a VersionIterator? #223

Open
tomschr opened this issue Feb 28, 2020 · 8 comments
Open

Idea: Use case for a VersionIterator? #223

tomschr opened this issue Feb 28, 2020 · 8 comments
Assignees
Labels
Design Ideas, suggestions, musings about design questions Doc Documentation related issue Enhancement Not a bug, but increases or improves in value, quality, desirability, or attractiveness Question Unclear or open issue subject for debate Release_3.x.y Only for the major release 3

Comments

@tomschr
Copy link
Member

tomschr commented Feb 28, 2020

Situation

For example, if we want to start from a certain version and get the next 3 versions we could do that:

v = VersionInfo.parse("1.2.3-rc.3")
for _ in range(3):
   print(v.next_version(part="prerelease"))

This would work, but maybe we could use the iterator protocol for that.

Proposed Solution

The solution is very rough, but that's on purpose. It's a minimal example to demonstrate the idea and not get distracted by other methods. So I know, this would need fine-tuning (add properties, protect from invalid datatypes etc.).

class VersionInfoIterator:
    """Iterator over VersionInfo objects"""
    def __init__(self, version, part, steps=10):
        self.version = version
        self.part = part
        self._steps = steps

    def __iter__(self):
        return self

    def __repr__(self):
        cls = type(self)
        return "{}(version={!r}, part={!r}, steps={})".format(cls.__name__,
                self.version, self.part, self._steps)

    def __next__(self):
        if not self.steps:
            raise StopIteration
        self._steps -= 1
        self.version = self.version.next_version(self.part)
        return self.version

An implementation could look like this:

from semver import VersionInfo, VersionInfoIterator
>>> v = VersionInfo.parse("1.2.3-rc.3")
>>> vit = VersionInfoIterator(v, "prerelease", steps=3)
>>> next(vit)                                               
VersionInfo(major=1, minor=2, patch=3, prerelease='rc.4', build=None)
>>> next(vit)                                                                                                                              
VersionInfo(major=1, minor=2, patch=3, prerelease='rc.5', build=None)
>>> vit.part = "minor"
>>> next(vit)    
VersionInfo(major=1, minor=3, patch=0, prerelease=None, build=None)
>>> next(vit)
Traceback (most recent call last)
...
StopIteration

# or

>>> vit = VersionInfoIterator(v, "prerelease", steps=3)
>>> list(vit)
[VersionInfo(major=1, minor=2, patch=3, prerelease='rc.4', build=None),
 VersionInfo(major=1, minor=2, patch=3, prerelease='rc.5', build=None),
 VersionInfo(major=1, minor=2, patch=3, prerelease='rc.6', build=None)]

Questions

  • Would such an iterator be useful?
  • Is it necessary or is first example above enough when iterating?
  • If you think it could be useful, would you change something in __init__?

Actually, I'm not sure myself if this is something good or completely unnecessary. I just had this idea when working on issue #222. 😉 Also I thought, it would be helpful to document it, regardless if this will be accepted or not.

@gsakkis, @scls19fr
What do you think? I would like to hear your opinion. 😉

@tomschr tomschr added Question Unclear or open issue subject for debate Enhancement Not a bug, but increases or improves in value, quality, desirability, or attractiveness labels Feb 28, 2020
@tomschr tomschr self-assigned this Feb 28, 2020
@gsakkis
Copy link
Contributor

gsakkis commented Feb 29, 2020

I kinda like the general idea and had also thought of something similar. Two comments for the specific API:

  • steps is unnecessary, this can (and should in my opinion) be an infinite iterator. The caller can always use itertools.islice or others to limit/filter the iterable.
  • Bumping to a different part than the initial one by setting an attribute is not ideal because
    (a) it is a 2-step process (vit.part = "minor"; next(vit)) and
    (b) it is sticky; all subsequent next calls will use the new part, although in practice I'd guess it would be more common to bump to minor/major once but continue with the original part for subsequent next versions.

Based on these I came up with a simple coroutine approach, where you can send(part) for a single next version but preserve the original:

def iter_versions(version, part, prerelease_token="rc"):
    current_part = part
    while True:
        version = next_version(version, current_part, prerelease_token)
        current_part = (yield version) or part

Usage:

import itertools as it

def test_iter_versions():
    iv = iter_versions("1.2.3-rc.3", "prerelease")
    assert list(it.islice(iv, 3)) == ["1.2.3-rc.4", "1.2.3-rc.5", "1.2.3-rc.6"]
    assert next(iv) == "1.2.3-rc.7"
    assert next(iv) == "1.2.3-rc.8"
    assert iv.send("patch") == "1.2.3"
    assert next(iv) == "1.2.4-rc.1"
    assert iv.send("minor") == "1.3.0"
    assert next(iv) == "1.3.1-rc.1"
    assert iv.send("major") == "2.0.0"
    assert next(iv) == "2.0.1-rc.1"

@tomschr
Copy link
Member Author

tomschr commented Feb 29, 2020

@gsakkis

I kinda like the general idea and had also thought of something similar.

Haha, cool! 😎 👍

Two comments for the specific API:

* `steps` is unnecessary, this can (and should in my opinion) be an infinite iterator. The caller can always use `itertools.islice` or others to limit/filter the iterable.

Yes, I thought about that too. But if we create an infinite iterator, we always have to use itertools.islice when we want to use, let say, 4 items.

* Bumping to a different `part` than the initial one by setting an attribute is not ideal because
  (a) it is a 2-step process (`vit.part = "minor"; next(vit)`) and
  (b) it is sticky; all subsequent `next` calls will use the new part, although in practice I'd guess it would be more common to bump to minor/major once but continue with the original part for subsequent next versions.

Ahh, I see. Good idea! I like your approach, it's much better.

@gsakkis
Copy link
Contributor

gsakkis commented Mar 3, 2020

An alternative api is to accept a (finite or infinite) iterable of parts:

def iter_versions(version, parts, prerelease_token="rc"):
    for part in parts:
        version = next_version(version, part, prerelease_token)
        yield version

def test_iter_versions():
    parts = it.chain(
        it.repeat("prerelease", 5),
        ["patch", "prerelease", "minor", "prerelease", "major", "prerelease"],
    )
    iv = iter_versions("1.2.3-rc.3", parts)
    assert list(it.islice(iv, 3)) == ["1.2.3-rc.4", "1.2.3-rc.5", "1.2.3-rc.6"]
    assert next(iv) == "1.2.3-rc.7"
    assert list(iv) == [
        "1.2.3-rc.8",
        "1.2.3",
        "1.2.4-rc.1",
        "1.3.0",
        "1.3.1-rc.1",
        "2.0.0",
        "2.0.1-rc.1",
    ]

@tomschr
Copy link
Member Author

tomschr commented Apr 13, 2020

I like this idea.
Same here. As in the light of #229, I would suggest to move the iter_versions functions into the VersionInfo class once issue #222 is merged:

class VersionInfo:
    # ...
    def iter_versions(self, parts, prerelease_token="rc"):
        current_version = self
        for part in parts:
            current_version = current_version.next_version(part, prerelease_token)
            yield current_version

@tomschr
Copy link
Member Author

tomschr commented Apr 13, 2020

Hmn, I'm wondering about the parts argument. Shouldn't it be *parts?

@gsakkis
Copy link
Contributor

gsakkis commented Apr 13, 2020

Hmn, I'm wondering about the parts argument. Shouldn't it be *parts?

Not if we want to support arbitrary iterables without expanding them into tuples first.

@tomschr
Copy link
Member Author

tomschr commented Apr 19, 2020

@gsakkis If you don't mind, I would suggest to add this to the semver3 release (as this is something new and I would like to get rid of semver2 as soon as possible 😉 ).

@tomschr tomschr added the Release_3.x.y Only for the major release 3 label Apr 19, 2020
@gsakkis
Copy link
Contributor

gsakkis commented Apr 23, 2020

@tomschr Sure no problem

tomschr added a commit to tomschr/python-semver that referenced this issue Dec 26, 2020
@tomschr tomschr added the Design Ideas, suggestions, musings about design questions label Nov 23, 2022
@tomschr tomschr added the Doc Documentation related issue label Jan 10, 2023
@tomschr tomschr changed the title Idea: Use case for a VersionInfoIterator? Idea: Use case for a VersionIterator? Jul 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Ideas, suggestions, musings about design questions Doc Documentation related issue Enhancement Not a bug, but increases or improves in value, quality, desirability, or attractiveness Question Unclear or open issue subject for debate Release_3.x.y Only for the major release 3
Projects
None yet
Development

No branches or pull requests

2 participants