Well, not exactly – it was rejected because the core devs thought it best to defer to PyPA (or, more correctly, the folks focusing on packaging standards) I interpreted that as meaning that the core devs didn’t want impose something on the packaging folks – not that they thought __version__
was necessarily a bad idea.
It does say: " The packaging ecosystem has changed significantly in the intervening years since this PEP was first written, and APIs such as importlib.metadata.version()
[11] provide for a much better experience."
So I suppose it is endorsing that – but whether importlib.metadata.version()
really provides a better experience or not is debatable - -as we can see in this thread, it’s not necessarily better.
The fact is that when a PEP is rejected, it’s rejected, none of us take much time arguing about exactly why it was rejected, and whether that’s consensus or not.
Frankly, when Barry rejected it, I thought of trying to revive it, with some modification, but didn’t have the energy. In fact, it was rejected in response to me asking that it be finally ruled on – it had been languishing for ages.
The PEP was also complicated by the fact that it was making suggestions for the standard library as well – which were more controversial.
Anyway – for now:
Frankly, performance is my least concern, but the facts are:
-
A LOT of packages use currently __version__
, and have for years (decades?)
-
importlib.metadata.version()
is not a “better experience” for many use-cases[1].
-
We have the tools – it’s just not that hard to provide a __version__
attribute, either hard-coding it, or by calling importlib.metadata.version()
.
So why not provide that easy and simple API [*] – and one that has a slightly different, but important meaning, as mentioned in this thread: a_module.__version__
is the version of the actual module that was imported – whether it has a different name than the distribution, whether it was installed as a “proper” package, etc.
So what I’d like to see as an official recommendation[2]:
If you have a version attribute in a module it should be called __version__
. We really don’t need VERSION
and who knows what else out there as well.
if you provide a __version__
attribute it must be in sync with what importlib.metadata.version()
would provide, if it’s functional.
It is highly recommended that you use the tools to single source the version and to keep everything in sync.
[1] I really fail to see how:
import some_package
import importlib.metadata
print(importlib.metadata.version('some_package'))
is a “better experience” than:
import some_package
print(some_package.__version__)
never mind:
In [13]: import numpy as np
In [14]: import importlib.metadata
In [15]: importlib.metadata.version('np')
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
File ~/miniforge3/envs/py3/lib/python3.12/importlib/metadata/__init__.py:397, in Distribution.from_name(cls, name)
396 try:
--> 397 return next(cls.discover(name=name))
398 except StopIteration:
StopIteration:
During handling of the above exception, another exception occurred:
PackageNotFoundError Traceback (most recent call last)
Cell In[15], line 1
----> 1 importlib.metadata.version('np')
File ~/miniforge3/envs/py3/lib/python3.12/importlib/metadata/__init__.py:889, in version(distribution_name)
882 def version(distribution_name):
883 """Get the version string for the named package.
884
885 :param distribution_name: The name of the distribution package to query.
886 :return: The version string for the package as defined in the package's
887 "Version" metadata key.
888 """
--> 889 return distribution(distribution_name).version
File ~/miniforge3/envs/py3/lib/python3.12/importlib/metadata/__init__.py:862, in distribution(distribution_name)
856 def distribution(distribution_name):
857 """Get the ``Distribution`` instance for the named package.
858
859 :param distribution_name: The name of the distribution package as a string.
860 :return: A ``Distribution`` instance (or subclass thereof).
861 """
--> 862 return Distribution.from_name(distribution_name)
File ~/miniforge3/envs/py3/lib/python3.12/importlib/metadata/__init__.py:399, in Distribution.from_name(cls, name)
397 return next(cls.discover(name=name))
398 except StopIteration:
--> 399 raise PackageNotFoundError(name)
PackageNotFoundError: No package metadata was found for np
In [16]: importlib.metadata.version('numpy')
Out[16]: '2.0.0'
NOTE: I use the iPython example to make the point that interactive use IS an important use case.
Or maybe worse:
import bs4
import importlib.metadata
print(importlib.metadata)
OOPS:
PackageNotFoundError: No package metadata was found for bs4
What the heck is the thing called ??? Oh yeah:
> importlib.metadata.version('beautifulsoup4')
> '4.12.3'
They key point is: we should make things easy and intuitive for all users: System developers; interactive data analysts; and quicky script writers, etc.
AND we should provide the tools for package builders to do that easily which, for the most part, is done.
[2] If I were king, I’d say __version__
should always be provided (at the top level import) – but I think I’ve lost that battle before it starts