diff --git a/changelog.d/1679.change.rst b/changelog.d/1679.change.rst new file mode 100644 index 0000000000..05fc148239 --- /dev/null +++ b/changelog.d/1679.change.rst @@ -0,0 +1,3 @@ +Add ``setuptools.get_version(path, field='__version__')``. It extracts version +info from lines like ``__version__ = '0.12'"`` in text files and Python +sources without importing them. diff --git a/setup.cfg b/setup.cfg index f0932e1c86..993ef6b08d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,5 +25,3 @@ universal = 1 [metadata] license_file = LICENSE -[bumpversion:file:setup.py] - diff --git a/setup.py b/setup.py index 8ec7ce06e5..4c9f2055e1 100755 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="40.8.0", + version=setuptools.get_version("setup.cfg", field="current_version"), description=( "Easily download, build, install, upgrade, and uninstall " "Python packages" diff --git a/setuptools/__init__.py b/setuptools/__init__.py index a71b2bbdc6..632647c0b8 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -27,7 +27,7 @@ __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'SetuptoolsDeprecationWarning', - 'find_packages' + 'find_packages', 'get_version' ] if PY3: @@ -224,5 +224,25 @@ def findall(dir=os.curdir): return list(files) +def get_version(path, field='__version__'): + """ + Get version information from a text file. Version is extracted + from "key = value" format with key matched at the beginning of + a line and specified by `field` parameter. Returns string. + """ + version_file = os.path.abspath(path) + # Using binary matching to avoid possible encoding problems + # when reading arbitrary text files + if type(field) is not bytes: + field = field.encode('utf-8') + for line in open(version_file, 'rb'): + if line.startswith(field): + # __version__ = "0.9" + # current_version = 4.8.0 + _, value = line.split(b'=') + version = value.strip(b' \r\n\t\'\"').decode() + return version + + # Apply monkey patches monkey.patch_all() diff --git a/setuptools/tests/test_get_version.py b/setuptools/tests/test_get_version.py new file mode 100644 index 0000000000..f9c93912f6 --- /dev/null +++ b/setuptools/tests/test_get_version.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +"""Tests for setuptools.get_version().""" +import os +import codecs +import shutil +import tempfile + +import pytest + +from setuptools import __version__, get_version + + +def test_own_version(): + version = get_version('setup.cfg', field='current_version') + + # `setup.py egg_info` which is run in bootstrap.py during package + # installation adds `.post` prefix to setuptools.__version__ + # which becomes different from 'setup.cfg` file + + # https://setuptools.readthedocs.io/en/latest/setuptools.html#egg-info + + assert __version__.startswith(version + '.post') + + +class TestFiles: + def setup_method(self, method): + self.tmpdir = tempfile.mkdtemp() + + def teardown_method(self, method): + shutil.rmtree(self.tmpdir) + + def test_python_file(self): + path = os.path.join(self.tmpdir, 'version.py') + with open(path, 'w') as fp: + fp.write('__version__ = "0.23beta"\n') + + version = get_version(path) + assert version == '0.23beta' + + def test_non_utf8_python_file(self): + path = os.path.join(self.tmpdir, 'russian.py') + with open(path, 'wb') as fp: + fp.write(u'# файл в русской кодировке\n\n'.encode('cp1251')) + fp.write(u'__version__ = "17.0"\n'.encode('cp1251')) + + version = get_version(path) + assert version == '17.0' +