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
Consider keeping compare
module level function
#258
Comments
@Askaholic Sorry for the long time, but thanks for your ideas. I think, it depends on the use case. If you start with two version strings, then sure, the first variant with For example, you start with a VersionInfo object, do something in between and then compare it afterwards like this: >>> ver = ... # ver is an instance of VersionInfo
>>> # do something in between
>>> ver.compare(other) IMHO, the last line is basically the same then your first line, it's even shorter. It also doesn't matter which types you have in Regarding this part:
Not sure why do you think allowing different types is "wrong".
Normally, for the developer it's a "black box" and how things are converted from one type to another is an implementation detail. However, that doesn't mean that the implementation couldn't be improved. It was the easiest/simplest for the time being. All other solutions involves some kind of "single dispatch" functionality. But maybe there is better solution which I didn't think of. If you have an idea how this could be improved, I would be more than happy to learn a better solution. Maybe you could add some code snippets of your idea?
Actually, this is already the case. Before version 2.10.0, the functionality was split between module level functions and the VersionInfo class. This is now consolidated and a lot of the implementation was moved into the VersionInfo class. If you look at the existing module level functions, you will see that they all call the VersionInfo API. See a diff of
Theoretically, this is almost the case. See next paragraph:
How? The current implementation of def compare(ver1, ver2):
""" ... """
v1 = VersionInfo.parse(ver1)
return v1.compare(ver2) When you pass How would you implement these different types and avoid these "double conversions"? |
@tlaferriere would love to hear your opinion on this topic. Should we allow |
I must agree that dealing with version strings is a major use case (not to say the main one) and being able to operate on them directly is extremely convenient. This does raise the issue that typing out This gives me an idea: there exist many types of string literals in python (f-strings, r-strings) that use a prefixed letter to distinguish themselves. How hard would it be to add one more (a v-string) that you can activate through an import? (I'm not very familiar with the internals of python, but I suspect this could involve changing the python syntax). But let's forget about the potential hardships of implementing such a feature for a moment. v"1.2.3".compare(version_string) And I believe that writing version strings like that is so natural even non-technical people could understand the meaning of this line given the fact that In the end, I think that the concision of some obvious and common usages should be addressed and could be improved. import semver
v = semver.VersionInfo.parse
v("1.2.3").compare("3.2.1") |
@tlaferriere Thank you very much for your wonderful and logical analysis. 👍 Actually, I had something similar in my mind, but I didn't think that through how you did. It looks a little bit like Ruby, 😄 but IMHO this would be a good compromise between usability and maintainability. I'm thinking about a new section like "Migrating from older semver version" which we could integrate into semver 3 doc. There, we could describe this little trick and give recommendations for our developers. That still leaves one question: Should we (still) remove @Askaholic We can offer now two solutions, what do you think? Would that be an idea for you? |
I still prefer having a convenient function that I can call directly in the module API rather than needing to write my own wrapper, or renaming one of the module functions just to be able to write readable code. IMO Python is a language of convenience (why else would you choose to write your application in Python, if not because it is easy and a pleasure to write in?) and third party packages should also be written in that spirit. I understand that there is a tradeoff between convenience and API maintainability, but I would argue that this package isn't at the point where keeping one convenience function is going to put a huge burden on maintenance. The entirety of the code fits in a single 1000 line file (a lot of which is being deprecated and I would agree that it should be deprecated), so I wouldn't exactly say the size of the API is unmaintainable. |
Sorry to play the devil's advocate here, but I think it is a good way to really make sure every possible aspect of a decision is thoroughly covered.
Indeed, in the case of Python prior to 3. From Python 3 onward (I'd say post PEP 484), it is now a major language that is used in production that still values convenience, but only where it doesn't hurt maintainability (type hints aren't exactly convenient to type out, but are extremely valuable for maintainability). Although Python is a convenient language, I think it is attributable to the fact that it is excellently designed around a certain philosophy: The Zen of Python.
And now we have two ways to compare version strings: the Now I understand this is a special case where the current alternative is a character count monstrosity, but as The Zen of Python says:
And I tend to think that this is a special case. ...
@tomschr I've been reading up on mypy recently and I've come across an interesting feature: the Finally, I've never been a fan of the name
So there are my two suggestions for this issue:
With these, you could just write
|
Thank you very much for your detailed answer @tlaferriere! 👍
All good, no need to be sorry. 😄 I appreciate all the input. 👍 I fully agree with you, it's good to hear different aspects and very helpful in the decision process. Actually, you bring two interesting aspects. Which is funny, because my thoughts went into the same (or almost) same direction. 😆 Regarding the It seems the One question: wouldn't make the overloading of Regarding the rename of That raises the question if we should prepare this in semver2 already or if this feature should be implemented in semver3 only. Adding @python-semver/reviewers to notify them. |
The |
Ok, thanks for the hints, @tlaferriere! 👍 As we usually create a from typing import overload
class VersionInfo: # or just Version
@overload
def __init__(self, major:int, minor:int=0, patch:int=0, prerelease=None, build=None):
# ...
@overload
def __init__(self, version:str):
# ... With the above overloaded initializer, we could cover the following use cases: >>> from semver import VersionInfo as Version
>>> Version(1)
VersionInfo(major=1, minor=0, patch=0, prerelease=None, build=None)
>>> Version(1, patch=2)
VersionInfo(major=1, minor=0, patch=2, prerelease=None, build=None)
>>> Version("1.2.3")
VersionInfo(major=1, minor=2, patch=3, prerelease=None, build=None)
>>> t = (1, 2, 3)
>>> Version(*t)
VersionInfo(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)
VersionInfo(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') Do we need anything else? Any other type we need to consider? With the above changes, the static method Would that make sense? |
Well I'm pretty sure you covered them all.
It makes a lot of sense. I'll try it out in my draft PR #265 and share the learnings here. |
FWIW, having just updated our code in prep for the deprecation I have to say I agree with the OP that it's a shame we can't compare plain version strings with the same ease anymore... from:
to:
or, if you like long lines:
Maybe there's a cleaner way in the new world to get the comparison done when starting out with two strings though? Either way this is a really useful package, thanks to all involved 👍 |
@durera sorry for the delay. Yes, it looks a bit verbose. Some time ago, I've added in #303 an idea which is related to this issue:
With these changes it could be simplified like this: # version1 and version2 can be str type
semver.Version(version1).compare(version2) what do you think? |
I just released 3.0.0-rc.1, but this issue wasn't included. Haven't forgot about it, still thinking which was the best way to solve it. There are a lot of good arguments from both sides. As we don't have a uniform initializer yet, it seems we still need This would give us some benefits:
So maybe that gives us some more time to let it sink and think about it. People can still use Nevertheless, I reserve the right to remove it in version 4 if it turns out that this decision was bad or we have a better way until then. 😉 I plan to integrate it into 3.0.0-rc.2. |
Move from semver._deprecated.compare to semver.version.compare Although it breaks consistency with module level functions, it seems a much needed function. Function is also available accessing semver.compare
It's still unclear if we should deprecate this function or not. As we don't have a uniform initializer yet, this function stays in _deprecated.py and decorated with PendingDeprecationWarning. See python-semver#258 for details
Although it breaks consistency with module level functions, it seems it's a much needed/used function. * Function is also available accessing semver.compare * Decorate semver.compare as PendingDeprecationWarning * Adapt `deprecated` decorator and use enforce keyword arguments It's still unclear if we should deprecate this function or not (that's why we use PendingDeprecationWarning). As we don't have a uniform initializer yet, this function stays in _deprecated.py for the time being until we find a better soltuion. See python-semver#258 for details
Although it breaks consistency with module level functions, it seems it's a much needed/used function. * Function is also available accessing semver.compare * Decorate semver.compare as PendingDeprecationWarning * Adapt `deprecated` decorator and use enforce keyword arguments * Update CHANGELOG.rst * Use intersphinx to link to Python exception, use Python inventory It's still unclear if we should deprecate this function or not (that's why we use PendingDeprecationWarning). As we don't have a uniform initializer yet, this function stays in _deprecated.py for the time being until we find a better soltuion. See python-semver#258 for details
Fix #258: Keep semver._deprecated.compare
Hi @tomschr! I just arrived to this issue while trying to use This all makes a very confusing first impression of the library. The situation could be improved by:
Thanks for the great library and I hope this could help other users to have a smoother start with the library :) |
As an extra clarification, as I see you are not using the upcoming standard So maybe not that many users are affected by this, but it is something you might consider for the future. |
Thanks @llucax for your feedback. Much appreciated that! 👍 The I usually try hard to keep the docs up-to-date. However, in this case, due to the controversial nature, I was cautious to mention this. As a writer myself, I usually add something to the docs when it's stable, proven, and correct. As this was not the case with At least you stumbled upon this, so the deprecation did its job. 😉 Thanks for mentioning PEP 707. At the moment, this is still in draft state, so I'm not sure how this could help. But I will keep in mind. If you think you could improve the situation, feel free to open a pull request or discuss it here. 🙂 Thanks again! |
I would think that comparing two semver strings is a pretty common use case and this code:
is arguably a lot nicer than
plus it is probably already present in a large number of projects that use this library, so removing it entirely should be done only if absolutely necessary (which I do not believe that it is).
The fact that
VersionInfo.compare
accepts theother
argument as a string says a lot about the convenience and desirability of working with strings. But it leaves us in this weird situation whereVersionInfo.compare
takes 2 arguments (self
, andother
) whereself
must be aVersionInfo
object butother
can be one of many types. If we were to write out the type signature we would have something like:(Another quirk of this function is that given a tuple as an argument, it will convert to
VersionInfo
and then immediately back to a tuple again.)I think as far as API design goes, it doesn't make a lot of sense for the main comparison function to have this type signature. Why is only one argument required to be a parsed
VersionInfo
object? Why are we doing automatic conversion on one argument but not the other? What if I already have both arguments in tuple form and don't want to perform two useless conversions?I would suggest having the module level API be a sort of convenience wrapper around the
VersionInfo
API. I think it is likely that the vast majority of semver data starts out in string form, so there should be a quick and clean API that one can call on this data. I would suggest makingsemver.compare
look like this:And I would probably avoid double conversions on lists and tuples.
The text was updated successfully, but these errors were encountered: