diff --git a/doc/users/next_whats_new/version_info.rst b/doc/users/next_whats_new/version_info.rst new file mode 100644 index 000000000000..5c51f9fe7332 --- /dev/null +++ b/doc/users/next_whats_new/version_info.rst @@ -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 \ No newline at end of file diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index b657a35cf7ed..92ef93e0079c 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -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__ raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index cad9433a562e..6f92b4ca0abd 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -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,