Adding a Version type

Hello all, I was thinking about the following idioms: __version__ = "1.0" import sys if sys.version_info < (3, 6): # Yell at the user I feel like there could be a better way to define versions. There's no real standard way to specify versions in Python, other than "use semantic versioning." This can cause problems such as: * Uncertainty of the type of the version (commence the pattern matching! Or `if... elif` statements if that's your thing) * No real way to specify an alpha, beta, pre, or post version number in a tuple of ints, e.g. `(2, 1, 0, "post0")` doesn't really work. I propose a new `Version` type, a standard way to define versions. The signature would look something like `Version(major: int, minor: int = 0, patch: Optional[int] = None, alpha: Optional[int] = None, beta: Optional[int] = None, pre: Optional[int] = None, post: Optional[int] = None)`. To maintain backwards compatibility, comparisons such as `Version(1, 2) == (1, 2)` and `Version(1, 2) == "1.2"` will return `True`. `str(Version(1, 2))` will return `"1.2"` (to clarify, if `patch` were 0 instead of None, it would return `"1.2.0"`). There will be an `as_tuple()` method to give the version number as a tuple (maybe named tuple?), e.g. `Version(1, 2).as_tuple()` will return `(1, 2)`. A problem is that not all versioning systems are covered by this proposal. For example, I've done some programming in C#, and many C# programs use a four number system instead of the three number system usually used in Python code, i.e. major.minor.patch.bugfix instead of major.minor.patch. (This may not seem very different, but it often is.) Where to place this type is an open question. I honestly have no good ideas. This could just be implemented as a PyPI package, but there may not be a point in adding a whole dependency to use a semantic type that other people probably aren't using (which wouldn't be the case if it were part of core Python). This would be implemented in Python 3.11 or 3.12, but I'd recommend a backport on PyPI if implemented. Questions? Thoughts? Suggestions to improve it?

There is a Version type in the packaging package that imjments the “official” versioning scheme for PyPI packages. https://packaging.pypa.io/en/latest/index.html# Should it be in the stdlib? Maybe, I think it has used casss beyond pip and friends. I’ve actually written my own more limited version (wink) myself for versioning a data model. I would probably have used one from the stdlib if it were there. And maybe I would have used the one in packaging if I had know about it when I wrote the code. -CHB On Sat, Oct 9, 2021 at 7:18 PM Finn Mason <finnjavier08@gmail.com> wrote:
-- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Sun, 10 Oct 2021 at 03:20, Finn Mason <finnjavier08@gmail.com> wrote:
There is - it's defined in PEP 440. Python's packaging tools follow this specification, so if you don't conform to it your package is potentially not installable. It doesn't mandate semantic versioning - PEP 440 is also compatible with CalVer, for example.
You should look at the `packaging` library on PyPI, which is a complete implementation of many of the packaging standards, including PEP 440 versions.
To maintain backwards compatibility, comparisons such as `Version(1, 2) == (1, 2)` and `Version(1, 2) == "1.2"` will return `True`. `str(Version(1, 2))` will return `"1.2"` (to clarify, if `patch` were 0 instead of None, it would return `"1.2.0"`). There will be an `as_tuple()` method to give the version number as a tuple (maybe named tuple?), e.g. `Version(1, 2).as_tuple()` will return `(1, 2)`.
PEP 440 also includes standards for saying things like "== 3.6.*" or ">= 3.6". These are typically better for handling version constraints than simple tuple or string comparisons (they have well-defined semantics for pre- and post-release versions, for example).
A problem is that not all versioning systems are covered by this proposal. For example, I've done some programming in C#, and many C# programs use a four number system instead of the three number system usually used in Python code, i.e. major.minor.patch.bugfix instead of major.minor.patch. (This may not seem very different, but it often is.)
PEP 440 covers all of these cases (and probably a lot more, as well).
Where to place this type is an open question. I honestly have no good ideas. This could just be implemented as a PyPI package, but there may not be a point in adding a whole dependency to use a semantic type that other people probably aren't using (which wouldn't be the case if it were part of core Python).
It is on PyPI, and is commonly used by packaging tools. Whether it's something that should be in the stdlib, I'll pass on for now - any proposal to move it into the stdlib would need to be supported by the owners of the `packaging` project.
This would be implemented in Python 3.11 or 3.12, but I'd recommend a backport on PyPI if implemented.
As I say, it already exists on PyPI, so the proposal should instead be framed in terms of moving that project into the stdlib, if that's what you feel is necessary (and the project maintainers are willing). Paul

On Sat, Oct 09, 2021 at 08:16:58PM -0600, Finn Mason wrote:
Please, version checking is usually an anti-pattern! You should use feature detection whenever possible, not version checking. For example, if you need the lcm function (least common multiple), rather than checking for Python 3.9, you should try to import it: try: from math import lcm except ImportError: ... which then gives you the opportunity to fallback on another version, imported from a third-party library, or to roll your own pure-Python implementation which may not be as full-featured or fast, but will get the job done. Feature detection also allows you to back-port necessary functionality via the PYTHONSTARTUP or usercustomize module, by monkey-patching the needed class or function into the appropriate module. -- Steve

On 10/10/2021 13:31, Steven D'Aprano wrote:
Understood. But would you agree that if you are writing code to be Python 2- and Python 3-compatible, it is reasonable to check the major version: Python3 = sys.version_info.major >= 3 .... if Python3: # this code used to work without this bit ... if not Python3: # can't use this feature etc. Best wishes Rob Cliffe

On Mon, Oct 11, 2021 at 10:59 PM Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
Maybe, but I still tend to feature-test. For instance: if str is bytes: def encode(text): return text else: def encode(text): return text.encode("UTF-8") In any case, there's not a lot of need to support Python 2 any more, so most of this sort of check doesn't exist in my code any more. ChrisA

On Mon, 11 Oct 2021 at 13:05, Chris Angelico <rosuav@gmail.com> wrote:
In any case, there's not a lot of need to support Python 2 any more, so most of this sort of check doesn't exist in my code any more.
... and in particular, "useful to help with code that needs to support Python 2" won't work as an argument for having a version type in the (Python 3.11+) stdlib :-) Paul

On Sat, Oct 09, 2021 at 08:16:58PM -0600, Finn Mason wrote:
It is a common myth that Python uses semantic versioning. It doesn't. https://www.python.org/dev/peps/pep-0440/#semantic-versioning Also, semantic versioning is not the gold standard of versioning schemes. Another popular one is calendar versioning: https://calver.org/ So you have a separate field for alpha, beta, pre and post. Great. So what happens if they conflict? Version(major=3, minor=10, patch=2, alpha=3, beta=7, pre=1) That suggests that this is all three of alpha, beta and pre-release all at once. If your data structure will allow something like that, there needs to be some sort of meaning for it. How do you store valid semantic versions such as these examples taken straight from the semver website? 1.0.0-x.7.z.92 1.0.0-x-y-z.– 1.0.0-beta+exp.sha.5114f85 1.0.0+21AF26D3—-117B344092BD https://semver.org/ If all you want is to cover Python's versioning system, you can just use the same named tuple as used by sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info
To maintain backwards compatibility, comparisons such as `Version(1, 2) == (1, 2)` and `Version(1, 2) == "1.2"` will return `True`.
What backwards compatibility? Since this Version type doesn't exist, there is no older behaviour that needs to not change. If you want to compare Version instances with strings and tuples, it is probably better, and cleaner, to be explict about it: version.compare_as_string("1.2") str(version) == "1.2" and reserve the equality operator for pure Version to Version comparisons. That way, any unexpected type passed by mistake (a bug) won't accidentally return True or False, but raise a TypeError.
A problem is that not all versioning systems are covered by this proposal.
Indeed, even semantic versioning is not covered. Nor is Python's versioning system. -- Steve

Thank you for the feedback. I was unaware of the packaging package and PEP 440. I recognize that there are definitely some problems with my idea. I took a look at the packaging package, and I think it might be a good idea to put something like it in the stdlib. The Version type there from what I can tell has everything I wanted plus more. -- Finn Mason On Sun, Oct 10, 2021, 7:25 AM Steven D'Aprano <steve@pearwood.info> wrote:

There is a Version type in the packaging package that imjments the “official” versioning scheme for PyPI packages. https://packaging.pypa.io/en/latest/index.html# Should it be in the stdlib? Maybe, I think it has used casss beyond pip and friends. I’ve actually written my own more limited version (wink) myself for versioning a data model. I would probably have used one from the stdlib if it were there. And maybe I would have used the one in packaging if I had know about it when I wrote the code. -CHB On Sat, Oct 9, 2021 at 7:18 PM Finn Mason <finnjavier08@gmail.com> wrote:
-- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

On Sun, 10 Oct 2021 at 03:20, Finn Mason <finnjavier08@gmail.com> wrote:
There is - it's defined in PEP 440. Python's packaging tools follow this specification, so if you don't conform to it your package is potentially not installable. It doesn't mandate semantic versioning - PEP 440 is also compatible with CalVer, for example.
You should look at the `packaging` library on PyPI, which is a complete implementation of many of the packaging standards, including PEP 440 versions.
To maintain backwards compatibility, comparisons such as `Version(1, 2) == (1, 2)` and `Version(1, 2) == "1.2"` will return `True`. `str(Version(1, 2))` will return `"1.2"` (to clarify, if `patch` were 0 instead of None, it would return `"1.2.0"`). There will be an `as_tuple()` method to give the version number as a tuple (maybe named tuple?), e.g. `Version(1, 2).as_tuple()` will return `(1, 2)`.
PEP 440 also includes standards for saying things like "== 3.6.*" or ">= 3.6". These are typically better for handling version constraints than simple tuple or string comparisons (they have well-defined semantics for pre- and post-release versions, for example).
A problem is that not all versioning systems are covered by this proposal. For example, I've done some programming in C#, and many C# programs use a four number system instead of the three number system usually used in Python code, i.e. major.minor.patch.bugfix instead of major.minor.patch. (This may not seem very different, but it often is.)
PEP 440 covers all of these cases (and probably a lot more, as well).
Where to place this type is an open question. I honestly have no good ideas. This could just be implemented as a PyPI package, but there may not be a point in adding a whole dependency to use a semantic type that other people probably aren't using (which wouldn't be the case if it were part of core Python).
It is on PyPI, and is commonly used by packaging tools. Whether it's something that should be in the stdlib, I'll pass on for now - any proposal to move it into the stdlib would need to be supported by the owners of the `packaging` project.
This would be implemented in Python 3.11 or 3.12, but I'd recommend a backport on PyPI if implemented.
As I say, it already exists on PyPI, so the proposal should instead be framed in terms of moving that project into the stdlib, if that's what you feel is necessary (and the project maintainers are willing). Paul

On Sat, Oct 09, 2021 at 08:16:58PM -0600, Finn Mason wrote:
Please, version checking is usually an anti-pattern! You should use feature detection whenever possible, not version checking. For example, if you need the lcm function (least common multiple), rather than checking for Python 3.9, you should try to import it: try: from math import lcm except ImportError: ... which then gives you the opportunity to fallback on another version, imported from a third-party library, or to roll your own pure-Python implementation which may not be as full-featured or fast, but will get the job done. Feature detection also allows you to back-port necessary functionality via the PYTHONSTARTUP or usercustomize module, by monkey-patching the needed class or function into the appropriate module. -- Steve

On 10/10/2021 13:31, Steven D'Aprano wrote:
Understood. But would you agree that if you are writing code to be Python 2- and Python 3-compatible, it is reasonable to check the major version: Python3 = sys.version_info.major >= 3 .... if Python3: # this code used to work without this bit ... if not Python3: # can't use this feature etc. Best wishes Rob Cliffe

On Mon, Oct 11, 2021 at 10:59 PM Rob Cliffe via Python-ideas <python-ideas@python.org> wrote:
Maybe, but I still tend to feature-test. For instance: if str is bytes: def encode(text): return text else: def encode(text): return text.encode("UTF-8") In any case, there's not a lot of need to support Python 2 any more, so most of this sort of check doesn't exist in my code any more. ChrisA

On Mon, 11 Oct 2021 at 13:05, Chris Angelico <rosuav@gmail.com> wrote:
In any case, there's not a lot of need to support Python 2 any more, so most of this sort of check doesn't exist in my code any more.
... and in particular, "useful to help with code that needs to support Python 2" won't work as an argument for having a version type in the (Python 3.11+) stdlib :-) Paul

On Sat, Oct 09, 2021 at 08:16:58PM -0600, Finn Mason wrote:
It is a common myth that Python uses semantic versioning. It doesn't. https://www.python.org/dev/peps/pep-0440/#semantic-versioning Also, semantic versioning is not the gold standard of versioning schemes. Another popular one is calendar versioning: https://calver.org/ So you have a separate field for alpha, beta, pre and post. Great. So what happens if they conflict? Version(major=3, minor=10, patch=2, alpha=3, beta=7, pre=1) That suggests that this is all three of alpha, beta and pre-release all at once. If your data structure will allow something like that, there needs to be some sort of meaning for it. How do you store valid semantic versions such as these examples taken straight from the semver website? 1.0.0-x.7.z.92 1.0.0-x-y-z.– 1.0.0-beta+exp.sha.5114f85 1.0.0+21AF26D3—-117B344092BD https://semver.org/ If all you want is to cover Python's versioning system, you can just use the same named tuple as used by sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info
To maintain backwards compatibility, comparisons such as `Version(1, 2) == (1, 2)` and `Version(1, 2) == "1.2"` will return `True`.
What backwards compatibility? Since this Version type doesn't exist, there is no older behaviour that needs to not change. If you want to compare Version instances with strings and tuples, it is probably better, and cleaner, to be explict about it: version.compare_as_string("1.2") str(version) == "1.2" and reserve the equality operator for pure Version to Version comparisons. That way, any unexpected type passed by mistake (a bug) won't accidentally return True or False, but raise a TypeError.
A problem is that not all versioning systems are covered by this proposal.
Indeed, even semantic versioning is not covered. Nor is Python's versioning system. -- Steve

Thank you for the feedback. I was unaware of the packaging package and PEP 440. I recognize that there are definitely some problems with my idea. I took a look at the packaging package, and I think it might be a good idea to put something like it in the stdlib. The Version type there from what I can tell has everything I wanted plus more. -- Finn Mason On Sun, Oct 10, 2021, 7:25 AM Steven D'Aprano <steve@pearwood.info> wrote:
participants (6)
-
Chris Angelico
-
Christopher Barker
-
Finn Mason
-
Paul Moore
-
Rob Cliffe
-
Steven D'Aprano