Skip to content

Add __version_info__ as a tuple-based version identifier #18869

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

Merged
merged 1 commit into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/users/next_whats_new/version_info.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Version information
-------------------
We switched to the `release-branch-semver`_ version scheme. This only affects,
the version information for development builds. Their version number now
describes the targeted release, i.e. 3.5.0.dev820+g6768ef8c4c.d20210520
is 820 commits after the previous release and is scheduled to be officially
released as 3.5.0 later.

In addition to the string ``__version__``, there is now a namedtuple
``__version_info__`` as well, which is modelled after `sys.version_info`_.
Its primary use is safely comparing version information, e.g.
``if __version_info__ >= (3, 4, 2)``.

.. _release-branch-semver: https://github.com/pypa/setuptools_scm#version-number-construction
.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info
67 changes: 51 additions & 16 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,25 +129,60 @@
year = 2007
}"""

# modelled after sys.version_info
_VersionInfo = namedtuple('_VersionInfo',
'major, minor, micro, releaselevel, serial')

def __getattr__(name):
if name == "__version__":

def _parse_to_version_info(version_str):
"""
Parse a version string to a namedtuple analogous to sys.version_info.

See:
https://packaging.pypa.io/en/latest/version.html#packaging.version.parse
https://docs.python.org/3/library/sys.html#sys.version_info
"""
v = parse_version(version_str)
if v.pre is None and v.post is None and v.dev is None:
return _VersionInfo(v.major, v.minor, v.micro, 'final', 0)
elif v.dev is not None:
return _VersionInfo(v.major, v.minor, v.micro, 'alpha', v.dev)
elif v.pre is not None:
releaselevel = {
'a': 'alpha',
'b': 'beta',
'rc': 'candidate'}.get(v.pre[0], 'alpha')
return _VersionInfo(v.major, v.minor, v.micro, releaselevel, v.pre[1])
else:
# fallback for v.post: guess-next-dev scheme from setuptools_scm
return _VersionInfo(v.major, v.minor, v.micro + 1, 'alpha', v.post)


def _get_version():
"""Return the version string used for __version__."""
# Only shell out to a git subprocess if really needed, and not on a
# shallow clone, such as those used by CI, as the latter would trigger
# a warning from setuptools_scm.
root = Path(__file__).resolve().parents[2]
if (root / ".git").exists() and not (root / ".git/shallow").exists():
import setuptools_scm
return setuptools_scm.get_version(
root=root,
version_scheme="post-release",
local_scheme="node-and-date",
fallback_version=_version.version,
)
else: # Get the version from the _version.py setuptools_scm file.
return _version.version


def __getattr__(name):
if name in ("__version__", "__version_info__"):
global __version__ # cache it.
# Only shell out to a git subprocess if really needed, and not on a
# shallow clone, such as those used by CI, as the latter would trigger
# a warning from setuptools_scm.
root = Path(__file__).resolve().parents[2]
if (root / ".git").exists() and not (root / ".git/shallow").exists():
__version__ = setuptools_scm.get_version(
root=root,
version_scheme="post-release",
local_scheme="node-and-date",
fallback_version=_version.version,
)
else: # Get the version from the _version.py setuptools_scm file.
__version__ = _version.version
return __version__
__version__ = _get_version()
global __version__info__ # cache it.
__version_info__ = _parse_to_version_info(__version__)
return __version__ if name == "__version__" else __version_info__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do return globals()[name] as that seems more robust if we ever want to add more entries to the module-level __getattr__, but I'm not going to block over that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this as is because explicit is better and still simple enough for two cases. We can (and should) switch to your proposal if we add more entries.

raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


Expand Down
10 changes: 10 additions & 0 deletions lib/matplotlib/tests/test_matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
import matplotlib


@pytest.mark.parametrize('version_str, version_tuple', [
('3.5.0', (3, 5, 0, 'final', 0)),
('3.5.0rc2', (3, 5, 0, 'candidate', 2)),
('3.5.0.dev820+g6768ef8c4c', (3, 5, 0, 'alpha', 820)),
('3.5.0.post820+g6768ef8c4c', (3, 5, 1, 'alpha', 820)),
])
def test_parse_to_version_info(version_str, version_tuple):
assert matplotlib._parse_to_version_info(version_str) == version_tuple


@pytest.mark.skipif(
os.name == "nt", reason="chmod() doesn't work as is on Windows")
@pytest.mark.skipif(os.name != "nt" and os.geteuid() == 0,
Expand Down