Description
Situation
Originally posted by @tomschr in #258 (comment)
From the above discussion, I thought it would be worth to decouple the compare
discussion from the initializer discussion. Both are somewhat related, but can live independently.
With a more "advanced" initializer/constructor we get the following benefits:
- more "pythonic": it's one, obvious way to get an instance.
- avoids the longer function call
Version.parse(...)
. - more readable
With such an (overloaded?) initializer, we could cover the following use cases:
>>> from semver import Version
>>> Version(1)
Version(major=1, minor=0, patch=0, prerelease=None, build=None)
>>> Version(1, "4", b"5")
Version(major=1, minor=4, patch=5, prerelease=None, build=None)
>>> Version(1, patch=2)
Version(major=1, minor=0, patch=2, prerelease=None, build=None)
>>> Version("1.2.3")
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
>>> Version(b"1.2.3")
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
>>> v = Version(b"2.3.4")
>>> Version(v)
Version(major=2, minor=3, patch=4, prerelease=None, build=None)
>>> t = (1, 2, 3)
>>> Version(*t)
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
>>> d = {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'}
>>> Version(**d)
Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4')
Discussions and Possible Solutions
To implement a somewhat more advanced constructor/initializer, we have these options:
- Program it manually with
isinstance
and if...else constructs - Use the
typing.overload
function (suggested by @tlaferriere) - Use
functools.singledispatch
However, all three comes at a cost or an issue:
- Maybe not impossible, but the code would look probably ugly.
- "The
@overload
decorator is purely for type hints, you can only specify one function body and it has to distinguish the different types using isinstance.` (Originally posted by @tlaferriere in Consider keepingcompare
module level function #258 (comment)) - Is not possible with an
__init__
method. Thesingledispatch
works only for functions(!), not methods. For methods we would needfunctools.singledispatchmethod
which is only available from Python >= 3.8.
Another idea goes into a completely different direction. Maybe we shouldn't change the Version
class much, but offer a much shorter variant: semver.v
.
from functools import singledispatch
# ...
@singledispatch
def ver(major, minor=0, patch=0, prerelease=None, build=None) -> "Version":
return Version(major, minor, patch, prerelease, build)
@ver.register(bytes)
@ver.register(str)
def _(ver: str) -> "Version":
if isinstance(ver, bytes):
ver = str(ver, "utf-8")
if "." in ver:
return Version.parse(ver)
return Version(int(ver))
@ver.register(Version)
def _(ver: "Version") -> "Version":
return ver
Which means, we could just use semver.v
as a much shorter variant:
>>> import semver
>>> semver.v("1.2.3")
Version(major=1, minor=2, patch=3, prerelease=None, build=None)
One drawback could be that v
is quite short. Maybe too short? Especially if you import it with from semver import v
it could be easily overwritten by other, local variables.
That could be a bit avoided to use capital V
or ver
. Or we use the much longer name semver.version
.
Thoughts? Any other ideas? Would that be worth the effort?