Skip to content

Commit c328fbc

Browse files
authored
Merge pull request #18869 from timhoffm/version_info
Add __version_info__ as a tuple-based version identifier
2 parents 5b87c98 + adbc5c5 commit c328fbc

File tree

3 files changed

+76
-16
lines changed

3 files changed

+76
-16
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Version information
2+
-------------------
3+
We switched to the `release-branch-semver`_ version scheme. This only affects,
4+
the version information for development builds. Their version number now
5+
describes the targeted release, i.e. 3.5.0.dev820+g6768ef8c4c.d20210520
6+
is 820 commits after the previous release and is scheduled to be officially
7+
released as 3.5.0 later.
8+
9+
In addition to the string ``__version__``, there is now a namedtuple
10+
``__version_info__`` as well, which is modelled after `sys.version_info`_.
11+
Its primary use is safely comparing version information, e.g.
12+
``if __version_info__ >= (3, 4, 2)``.
13+
14+
.. _release-branch-semver: https://github.com/pypa/setuptools_scm#version-number-construction
15+
.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info

lib/matplotlib/__init__.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,25 +129,60 @@
129129
year = 2007
130130
}"""
131131

132+
# modelled after sys.version_info
133+
_VersionInfo = namedtuple('_VersionInfo',
134+
'major, minor, micro, releaselevel, serial')
132135

133-
def __getattr__(name):
134-
if name == "__version__":
136+
137+
def _parse_to_version_info(version_str):
138+
"""
139+
Parse a version string to a namedtuple analogous to sys.version_info.
140+
141+
See:
142+
https://packaging.pypa.io/en/latest/version.html#packaging.version.parse
143+
https://docs.python.org/3/library/sys.html#sys.version_info
144+
"""
145+
v = parse_version(version_str)
146+
if v.pre is None and v.post is None and v.dev is None:
147+
return _VersionInfo(v.major, v.minor, v.micro, 'final', 0)
148+
elif v.dev is not None:
149+
return _VersionInfo(v.major, v.minor, v.micro, 'alpha', v.dev)
150+
elif v.pre is not None:
151+
releaselevel = {
152+
'a': 'alpha',
153+
'b': 'beta',
154+
'rc': 'candidate'}.get(v.pre[0], 'alpha')
155+
return _VersionInfo(v.major, v.minor, v.micro, releaselevel, v.pre[1])
156+
else:
157+
# fallback for v.post: guess-next-dev scheme from setuptools_scm
158+
return _VersionInfo(v.major, v.minor, v.micro + 1, 'alpha', v.post)
159+
160+
161+
def _get_version():
162+
"""Return the version string used for __version__."""
163+
# Only shell out to a git subprocess if really needed, and not on a
164+
# shallow clone, such as those used by CI, as the latter would trigger
165+
# a warning from setuptools_scm.
166+
root = Path(__file__).resolve().parents[2]
167+
if (root / ".git").exists() and not (root / ".git/shallow").exists():
135168
import setuptools_scm
169+
return setuptools_scm.get_version(
170+
root=root,
171+
version_scheme="post-release",
172+
local_scheme="node-and-date",
173+
fallback_version=_version.version,
174+
)
175+
else: # Get the version from the _version.py setuptools_scm file.
176+
return _version.version
177+
178+
179+
def __getattr__(name):
180+
if name in ("__version__", "__version_info__"):
136181
global __version__ # cache it.
137-
# Only shell out to a git subprocess if really needed, and not on a
138-
# shallow clone, such as those used by CI, as the latter would trigger
139-
# a warning from setuptools_scm.
140-
root = Path(__file__).resolve().parents[2]
141-
if (root / ".git").exists() and not (root / ".git/shallow").exists():
142-
__version__ = setuptools_scm.get_version(
143-
root=root,
144-
version_scheme="post-release",
145-
local_scheme="node-and-date",
146-
fallback_version=_version.version,
147-
)
148-
else: # Get the version from the _version.py setuptools_scm file.
149-
__version__ = _version.version
150-
return __version__
182+
__version__ = _get_version()
183+
global __version__info__ # cache it.
184+
__version_info__ = _parse_to_version_info(__version__)
185+
return __version__ if name == "__version__" else __version_info__
151186
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
152187

153188

lib/matplotlib/tests/test_matplotlib.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
import matplotlib
88

99

10+
@pytest.mark.parametrize('version_str, version_tuple', [
11+
('3.5.0', (3, 5, 0, 'final', 0)),
12+
('3.5.0rc2', (3, 5, 0, 'candidate', 2)),
13+
('3.5.0.dev820+g6768ef8c4c', (3, 5, 0, 'alpha', 820)),
14+
('3.5.0.post820+g6768ef8c4c', (3, 5, 1, 'alpha', 820)),
15+
])
16+
def test_parse_to_version_info(version_str, version_tuple):
17+
assert matplotlib._parse_to_version_info(version_str) == version_tuple
18+
19+
1020
@pytest.mark.skipif(
1121
os.name == "nt", reason="chmod() doesn't work as is on Windows")
1222
@pytest.mark.skipif(os.name != "nt" and os.geteuid() == 0,

0 commit comments

Comments
 (0)