From ed5f934e0a49253d645b66dad65fd5b616133d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles=20Bouchard-L=C3=A9gar=C3=A9?= Date: Mon, 7 Nov 2016 22:07:29 -0500 Subject: Also suppress warning for a single file missing --- setuptools/command/egg_info.py | 11 +++++++++-- setuptools/tests/test_egg_info.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 6cc8f4c4..6f8fd874 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -554,10 +554,17 @@ class manifest_maker(sdist): msg = "writing manifest file '%s'" % self.manifest self.execute(write_file, (self.manifest, files), msg) - def warn(self, msg): # suppress missing-file warnings from sdist - if not msg.startswith("standard file not found:"): + def warn(self, msg): + if not self._should_suppress_warning(msg): sdist.warn(self, msg) + @staticmethod + def _should_suppress_warning(msg): + """ + suppress missing-file warnings from sdist + """ + return re.match(r"standard file .*not found", msg) + def add_defaults(self): sdist.add_defaults(self) self.filelist.append(self.template) diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 12c10497..dc41bc1f 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -4,7 +4,7 @@ import re import stat import sys -from setuptools.command.egg_info import egg_info +from setuptools.command.egg_info import egg_info, manifest_maker from setuptools.dist import Distribution from setuptools.extern.six.moves import map @@ -237,6 +237,15 @@ class TestEggInfo(object): pkginfo = os.path.join(egg_info_dir, 'PKG-INFO') assert 'Requires-Python: >=1.2.3' in open(pkginfo).read().split('\n') + def test_manifest_maker_warning_suppresion(self): + fixtures = [ + "standard file not found: should have one of foo.py, bar.py", + "standard file 'setup.py' not found" + ] + + for msg in fixtures: + assert manifest_maker._should_suppress_warning(msg) + def _run_install_command(self, tmpdir_cwd, env, cmd=None, output=None): environ = os.environ.copy().update( HOME=env.paths['home'], -- cgit v1.2.3 From cb75964e09d2441e0d00ee3b5861f8412a3a672d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 13 Nov 2016 10:53:34 -0500 Subject: Add test capturing (failing) expectation. Ref #805. --- setuptools/tests/test_namespaces.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 setuptools/tests/test_namespaces.py diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py new file mode 100644 index 00000000..f90c25cf --- /dev/null +++ b/setuptools/tests/test_namespaces.py @@ -0,0 +1,83 @@ +from __future__ import absolute_import + +import os +import textwrap +import sys +import subprocess + + +class TestNamespaces: + @staticmethod + def build_namespace_package(tmpdir, name): + src_dir = tmpdir / name + src_dir.mkdir() + setup_py = src_dir / 'setup.py' + namespace, sep, rest = name.partition('.') + script = textwrap.dedent(""" + import setuptools + setuptools.setup( + name={name!r}, + version="1.0", + namespace_packages=[{namespace!r}], + packages=[{namespace!r}], + ) + """).format(**locals()) + setup_py.write_text(script, encoding='utf-8') + ns_pkg_dir = src_dir / namespace + ns_pkg_dir.mkdir() + pkg_init = ns_pkg_dir / '__init__.py' + tmpl = '__import__("pkg_resources").declare_namespace({namespace!r})' + decl = tmpl.format(**locals()) + pkg_init.write_text(decl, encoding='utf-8') + pkg_mod = ns_pkg_dir / (rest + '.py') + some_functionality = 'name = {rest!r}'.format(**locals()) + pkg_mod.write_text(some_functionality, encoding='utf-8') + return src_dir + + @staticmethod + def make_site_dir(target): + """ + Add a sitecustomize.py module in target to cause + target to be added to site dirs such that .pth files + are processed there. + """ + sc = target / 'sitecustomize.py' + target_str = str(target) + tmpl = '__import__("site").addsitedir({target_str!r})' + sc.write_text(tmpl.format(**locals()), encoding='utf-8') + + def test_mixed_site_and_non_site(self, tmpdir): + """ + Installing two packages sharing the same namespace, one installed + to a site dir and the other installed just to a path on PYTHONPATH + should leave the namespace in tact and both packages reachable by + import. + """ + pkg_A = self.build_namespace_package(tmpdir, 'myns.pkgA') + pkg_B = self.build_namespace_package(tmpdir, 'myns.pkgB') + site_packages = tmpdir / 'site-packages' + path_packages = tmpdir / 'path-packages' + targets = site_packages, path_packages + python_path = os.pathsep.join(map(str, targets)) + # use pip to install to the target directory + install_cmd = [ + 'pip', + 'install', + str(pkg_A), + '-t', str(site_packages), + ] + subprocess.check_call(install_cmd) + self.make_site_dir(site_packages) + install_cmd = [ + 'pip', + 'install', + str(pkg_B), + '-t', str(path_packages), + ] + subprocess.check_call(install_cmd) + try_import = [ + sys.executable, + '-c', 'import myns.pkgA; import myns.pkgB', + ] + env = dict(PYTHONPATH=python_path) + subprocess.check_call(try_import, env=env) -- cgit v1.2.3 From 5d78aeb98a5d2b1c366421e68162d9d46de45502 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 13 Nov 2016 11:33:00 -0500 Subject: Fix test failures on Python 2 and suppress test failures when PEP 420 is not available. Ref #805. --- setuptools/tests/test_namespaces.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index f90c25cf..ad3c78b8 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -1,10 +1,12 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import os import textwrap import sys import subprocess +import pytest + class TestNamespaces: @staticmethod @@ -46,6 +48,8 @@ class TestNamespaces: tmpl = '__import__("site").addsitedir({target_str!r})' sc.write_text(tmpl.format(**locals()), encoding='utf-8') + @pytest.mark.xfail(sys.version_info < (3, 3), + reason="Requires PEP 420") def test_mixed_site_and_non_site(self, tmpdir): """ Installing two packages sharing the same namespace, one installed -- cgit v1.2.3 From ed765324e72e7e6be1f84778eaa7496df0e7a381 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 13 Nov 2016 11:39:11 -0500 Subject: Extract namespace support functionality into a separate module. --- setuptools/tests/namespaces.py | 42 +++++++++++++++++++++++++++++++++ setuptools/tests/test_namespaces.py | 47 ++++--------------------------------- 2 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 setuptools/tests/namespaces.py diff --git a/setuptools/tests/namespaces.py b/setuptools/tests/namespaces.py new file mode 100644 index 00000000..ef5ecdad --- /dev/null +++ b/setuptools/tests/namespaces.py @@ -0,0 +1,42 @@ +from __future__ import absolute_import, unicode_literals + +import textwrap + + +def build_namespace_package(tmpdir, name): + src_dir = tmpdir / name + src_dir.mkdir() + setup_py = src_dir / 'setup.py' + namespace, sep, rest = name.partition('.') + script = textwrap.dedent(""" + import setuptools + setuptools.setup( + name={name!r}, + version="1.0", + namespace_packages=[{namespace!r}], + packages=[{namespace!r}], + ) + """).format(**locals()) + setup_py.write_text(script, encoding='utf-8') + ns_pkg_dir = src_dir / namespace + ns_pkg_dir.mkdir() + pkg_init = ns_pkg_dir / '__init__.py' + tmpl = '__import__("pkg_resources").declare_namespace({namespace!r})' + decl = tmpl.format(**locals()) + pkg_init.write_text(decl, encoding='utf-8') + pkg_mod = ns_pkg_dir / (rest + '.py') + some_functionality = 'name = {rest!r}'.format(**locals()) + pkg_mod.write_text(some_functionality, encoding='utf-8') + return src_dir + + +def make_site_dir(target): + """ + Add a sitecustomize.py module in target to cause + target to be added to site dirs such that .pth files + are processed there. + """ + sc = target / 'sitecustomize.py' + target_str = str(target) + tmpl = '__import__("site").addsitedir({target_str!r})' + sc.write_text(tmpl.format(**locals()), encoding='utf-8') diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index ad3c78b8..c148577d 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -1,52 +1,15 @@ from __future__ import absolute_import, unicode_literals import os -import textwrap import sys import subprocess import pytest +from . import namespaces -class TestNamespaces: - @staticmethod - def build_namespace_package(tmpdir, name): - src_dir = tmpdir / name - src_dir.mkdir() - setup_py = src_dir / 'setup.py' - namespace, sep, rest = name.partition('.') - script = textwrap.dedent(""" - import setuptools - setuptools.setup( - name={name!r}, - version="1.0", - namespace_packages=[{namespace!r}], - packages=[{namespace!r}], - ) - """).format(**locals()) - setup_py.write_text(script, encoding='utf-8') - ns_pkg_dir = src_dir / namespace - ns_pkg_dir.mkdir() - pkg_init = ns_pkg_dir / '__init__.py' - tmpl = '__import__("pkg_resources").declare_namespace({namespace!r})' - decl = tmpl.format(**locals()) - pkg_init.write_text(decl, encoding='utf-8') - pkg_mod = ns_pkg_dir / (rest + '.py') - some_functionality = 'name = {rest!r}'.format(**locals()) - pkg_mod.write_text(some_functionality, encoding='utf-8') - return src_dir - @staticmethod - def make_site_dir(target): - """ - Add a sitecustomize.py module in target to cause - target to be added to site dirs such that .pth files - are processed there. - """ - sc = target / 'sitecustomize.py' - target_str = str(target) - tmpl = '__import__("site").addsitedir({target_str!r})' - sc.write_text(tmpl.format(**locals()), encoding='utf-8') +class TestNamespaces: @pytest.mark.xfail(sys.version_info < (3, 3), reason="Requires PEP 420") @@ -57,8 +20,8 @@ class TestNamespaces: should leave the namespace in tact and both packages reachable by import. """ - pkg_A = self.build_namespace_package(tmpdir, 'myns.pkgA') - pkg_B = self.build_namespace_package(tmpdir, 'myns.pkgB') + pkg_A = namespaces.build_namespace_package(tmpdir, 'myns.pkgA') + pkg_B = namespaces.build_namespace_package(tmpdir, 'myns.pkgB') site_packages = tmpdir / 'site-packages' path_packages = tmpdir / 'path-packages' targets = site_packages, path_packages @@ -71,7 +34,7 @@ class TestNamespaces: '-t', str(site_packages), ] subprocess.check_call(install_cmd) - self.make_site_dir(site_packages) + namespaces.make_site_dir(site_packages) install_cmd = [ 'pip', 'install', -- cgit v1.2.3 From 161c0a5072f6049f6e4790969a387e9aae41ad52 Mon Sep 17 00:00:00 2001 From: Julien Muchembled Date: Mon, 14 Nov 2016 00:25:57 +0100 Subject: package_index: fix bug not catching some network timeouts There are already so many exceptions catched, like socket errors (e.g. failure in name resolution) or HTTP errors. Depending on when a timeout occurs, it is either catched (URLError during the SSL handshake) or not (socket.error while getting a HTTP response). When used by buildout, this fixes random failures when running in newest mode (which is the default case), or when the requested version is available in the download-cache. --- setuptools/package_index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 024fab98..d80d43bc 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -768,7 +768,7 @@ class PackageIndex(Environment): 'down, %s' % (url, v.line) ) - except http_client.HTTPException as v: + except (http_client.HTTPException, socket.error) as v: if warning: self.warn(warning, v) else: -- cgit v1.2.3 From 7daf18ff0aaa6e9c9f5078ed1880512dbf8e497a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 18 Nov 2016 15:19:12 -0500 Subject: Drop exception support for packages triggering win32com cache generation during build/install. Fixes #841 --- CHANGES.rst | 7 +++++++ setuptools/sandbox.py | 8 -------- setuptools/tests/test_sandbox.py | 16 ---------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2dc68e71..3b9f0894 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,13 @@ CHANGES ======= +v29.0.0 +------- + +* #841: Drop special exception for packages invoking + win32com during the build/install process. See + Distribute #118 for history. + v28.8.0 ------- diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 39afd57e..d882d715 100755 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -373,14 +373,6 @@ if hasattr(os, 'devnull'): else: _EXCEPTIONS = [] -try: - from win32com.client.gencache import GetGeneratePath - _EXCEPTIONS.append(GetGeneratePath()) - del GetGeneratePath -except ImportError: - # it appears pywin32 is not installed, so no need to exclude. - pass - class DirectorySandbox(AbstractSandbox): """Restrict operations to a single subdirectory - pseudo-chroot""" diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index b92a477a..929f0a5b 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -23,22 +23,6 @@ class TestSandbox: return do_write - def test_win32com(self, tmpdir): - """ - win32com should not be prevented from caching COM interfaces - in gen_py. - """ - win32com = pytest.importorskip('win32com') - gen_py = win32com.__gen_path__ - target = os.path.join(gen_py, 'test_write') - sandbox = DirectorySandbox(str(tmpdir)) - try: - # attempt to create gen_py file - sandbox.run(self._file_writer(target)) - finally: - if os.path.exists(target): - os.remove(target) - def test_setup_py_with_BOM(self): """ It should be possible to execute a setup.py with a Byte Order Mark -- cgit v1.2.3 From b4d77154138d7fb7042153491e9b5c1af534eb4d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 18 Nov 2016 15:57:01 -0500 Subject: Add environment variable to detect APPVEYOR. Ref #851. --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 299c35b7..9313a482 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,7 @@ environment: + APPVEYOR: true + matrix: - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python27-x64" -- cgit v1.2.3 From e8d53c0b830744a3cec9c0080293c39dfbf5ac72 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 18 Nov 2016 16:07:10 -0500 Subject: Skip failing test on appveyor until the cause can be uncovered. Ref #851. --- setuptools/tests/test_namespaces.py | 2 ++ tox.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index c148577d..21fd69e7 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -13,6 +13,8 @@ class TestNamespaces: @pytest.mark.xfail(sys.version_info < (3, 3), reason="Requires PEP 420") + @pytest.mark.skipif('os.environ.get("APPVEYOR")', + reason="https://github.com/pypa/setuptools/issues/851") def test_mixed_site_and_non_site(self, tmpdir): """ Installing two packages sharing the same namespace, one installed diff --git a/tox.ini b/tox.ini index 6e03aef2..cfb682ee 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ [testenv] deps=-rtests/requirements.txt -passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir +passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR commands=python -m pytest {posargs:-rsx} -- cgit v1.2.3 From 23aba916e1070d3cf9723af85a6ce07c89053931 Mon Sep 17 00:00:00 2001 From: Tim Heap Date: Mon, 21 Nov 2016 01:44:22 +0200 Subject: Fix #849 global-exclude globbing After #764, `global-exclude .pyc` no longer excluded `.pyc` files. This fixes that regression, and adds a test for this behaviour. --- setuptools/command/egg_info.py | 4 ++-- setuptools/tests/test_manifest.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 6cc8f4c4..c4555b3e 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -457,7 +457,7 @@ class FileList(_FileList): """ if self.allfiles is None: self.findall() - match = translate_pattern(os.path.join('**', pattern)) + match = translate_pattern(os.path.join('**', '*' + pattern)) found = [f for f in self.allfiles if match.match(f)] self.extend(found) return bool(found) @@ -466,7 +466,7 @@ class FileList(_FileList): """ Exclude all files anywhere that match the pattern. """ - match = translate_pattern(os.path.join('**', pattern)) + match = translate_pattern(os.path.join('**', '*' + pattern)) return self._remove_files(match.match) def append(self, item): diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py index 602c43a2..62b6d708 100644 --- a/setuptools/tests/test_manifest.py +++ b/setuptools/tests/test_manifest.py @@ -449,6 +449,11 @@ class TestFileListTest(TempDirTestCase): assert file_list.files == ['a.py', l('d/c.py')] self.assertWarnings() + file_list.process_template_line('global-include .txt') + file_list.sort() + assert file_list.files == ['a.py', 'b.txt', l('d/c.py')] + self.assertNoWarnings() + def test_global_exclude(self): l = make_local_path # global-exclude @@ -465,6 +470,13 @@ class TestFileListTest(TempDirTestCase): assert file_list.files == ['b.txt'] self.assertWarnings() + file_list = FileList() + file_list.files = ['a.py', 'b.txt', l('d/c.pyc'), 'e.pyo'] + file_list.process_template_line('global-exclude .py[co]') + file_list.sort() + assert file_list.files == ['a.py', 'b.txt'] + self.assertNoWarnings() + def test_recursive_include(self): l = make_local_path # recursive-include -- cgit v1.2.3 From 11b921f2e17586e394639e7ddbcaeae727ece4d0 Mon Sep 17 00:00:00 2001 From: Thiebaud Weksteen Date: Tue, 1 Nov 2016 15:54:23 +1100 Subject: Change _add_defaults_data_files override and add unittest --- setuptools/command/sdist.py | 7 ++++--- setuptools/tests/test_sdist.py | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 9975753d..ba980622 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -142,9 +142,10 @@ class sdist(sdist_add_defaults, orig.sdist): for filename in filenames]) def _add_defaults_data_files(self): - """ - Don't add any data files, but why? - """ + try: + sdist_add_defaults._add_defaults_data_files(self) + except TypeError: + log.warn("data_files contains unexpected objects") def check_readme(self): for f in self.READMES: diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 609c7830..f34068dc 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -26,7 +26,8 @@ SETUP_ATTRS = { 'name': 'sdist_test', 'version': '0.0', 'packages': ['sdist_test'], - 'package_data': {'sdist_test': ['*.txt']} + 'package_data': {'sdist_test': ['*.txt']}, + 'data_files': [("data", [os.path.join("d", "e.dat")])], } SETUP_PY = """\ @@ -95,9 +96,12 @@ class TestSdistTest: # Set up the rest of the test package test_pkg = os.path.join(self.temp_dir, 'sdist_test') os.mkdir(test_pkg) + data_folder = os.path.join(self.temp_dir, "d") + os.mkdir(data_folder) # *.rst was not included in package_data, so c.rst should not be # automatically added to the manifest when not under version control - for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: + for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst', + os.path.join(data_folder, "e.dat")]: # Just touch the files; their contents are irrelevant open(os.path.join(test_pkg, fname), 'w').close() @@ -126,6 +130,7 @@ class TestSdistTest: assert os.path.join('sdist_test', 'a.txt') in manifest assert os.path.join('sdist_test', 'b.txt') in manifest assert os.path.join('sdist_test', 'c.rst') not in manifest + assert os.path.join('d', 'e.dat') in manifest def test_defaults_case_sensitivity(self): """ -- cgit v1.2.3 From 959e12ca13a0a006100c2fe5284f69ee0d5b6fbb Mon Sep 17 00:00:00 2001 From: Liao Penghui Date: Fri, 25 Nov 2016 12:41:53 +0800 Subject: Make Entrypoint methods docs up to date. Entrypoint.load() is deprecated and it's recommend to use `require` and `resolve` separately. --- docs/pkg_resources.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt index 7b979ec3..9a45b9e3 100644 --- a/docs/pkg_resources.txt +++ b/docs/pkg_resources.txt @@ -835,6 +835,8 @@ addition, the following methods are provided: Load the entry point, returning the advertised Python object, or raise ``ImportError`` if it cannot be obtained. If `require` is a true value, then ``require(env, installer)`` is called before attempting the import. + Parameters to load are deprecated. Call ``require`` and ``resolve`` + separately. ``require(env=None, installer=None)`` Ensure that any "extras" needed by the entry point are available on @@ -846,6 +848,10 @@ addition, the following methods are provided: taking a ``Requirement`` instance and returning a matching importable ``Distribution`` instance or None. +``resolve()`` + Resolve the entry point from its module and attrs, returning the advertised + Python object. + ``__str__()`` The string form of an ``EntryPoint`` is a string that could be passed to ``EntryPoint.parse()`` to produce an equivalent ``EntryPoint``. -- cgit v1.2.3 From 175171116dc40af5274c88ec461dd2e94c1dabc5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 25 Nov 2016 22:36:44 -0600 Subject: =?UTF-8?q?Bump=20version:=2028.8.0=20=E2=86=92=2029.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 34c1884c..589df541 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 28.8.0 +current_version = 29.0.0 commit = True tag = True diff --git a/setup.py b/setup.py index fc51401a..6286af63 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="28.8.0", + version="29.0.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 7df349d07e6441a33427ad5c371f12bf6bedc529 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 26 Nov 2016 21:36:35 +0700 Subject: Added config module. --- setuptools/config.py | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 setuptools/config.py diff --git a/setuptools/config.py b/setuptools/config.py new file mode 100644 index 00000000..94b2ab17 --- /dev/null +++ b/setuptools/config.py @@ -0,0 +1,367 @@ +import io +import os +import sys +from functools import partial + +from distutils.errors import DistutilsOptionError +from setuptools.py26compat import import_module +from setuptools.extern.six import string_types + + +class ConfigHandler(object): + """Handles metadata supplied in configuration files.""" + + section_prefix = None + + def __init__(self, target_obj, options): + sections = {} + + section_prefix = self.section_prefix + for section_name, section_options in options.items(): + if not section_name.startswith(section_prefix): + continue + + section_name = section_name.replace(section_prefix, '').strip(':') + sections[section_name] = section_options + + self.target_obj = target_obj + self.sections = sections + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + raise NotImplementedError( + '%s must provide .parsers property' % self.__class__.__name__) + + def __setitem__(self, option_name, value): + unknown = tuple() + target_obj = self.target_obj + + current_value = getattr(target_obj, option_name, unknown) + + if current_value is unknown: + raise KeyError(option_name) + + if current_value: + # Already inhabited. Skipping. + return + + parser = self.parsers.get(option_name) + if parser: + value = parser(value) + + setter = getattr(target_obj, 'set_%s' % option_name, None) + if setter is None: + setattr(target_obj, option_name, value) + else: + setter(value) + + @classmethod + def _parse_list(cls, value, separator=','): + """Represents value as a list. + + Value is split either by comma or by lines. + + :param value: + :param separator: List items separator character. + :rtype: list + """ + if isinstance(value, list): # _parse_complex case + return value + + if '\n' in value: + value = value.splitlines() + else: + value = value.split(separator) + + return [chunk.strip() for chunk in value] + + @classmethod + def _parse_dict(cls, value): + """Represents value as a dict. + + :param value: + :rtype: dict + """ + separator = '=' + result = {} + for line in cls._parse_list(value): + key, sep, val = line.partition(separator) + if sep != separator: + raise DistutilsOptionError( + 'Unable to parse option value to dict: %s' % value) + result[key.strip()] = val.strip() + + return result + + @classmethod + def _parse_bool(cls, value): + """Represents value as boolean. + + :param value: + :rtype: bool + """ + value = value.lower() + return value in ('1', 'true', 'yes') + + @classmethod + def _parse_file(cls, value): + """Represents value as a string, allowing including text + from nearest files using include(). + + Examples: + include: LICENSE + include: src/file.txt + + :param str value: + :rtype: str + """ + if not isinstance(value, string_types): + return value + + include_directive = 'file:' + if not value.startswith(include_directive): + return value + + filepath = value.replace(include_directive, '').strip() + + if os.path.isfile(filepath): + with io.open(filepath, encoding='utf-8') as f: + value = f.read() + + return value + + @classmethod + def _get_parser_compound(cls, *parse_methods): + """Returns parser function to represents value as a list. + + Parses a value applying given methods one after another. + + :param parse_methods: + :rtype: callable + """ + def parse(value): + parsed = value + + for method in parse_methods: + parsed = method(parsed) + + return parsed + + return parse + + @classmethod + def _parse_section_to_dict(cls, section_options, values_parser=None): + """Parses section options into a dictionary. + + Optionally applies a given parser to values. + + :param dict section_options: + :param callable values_parser: + :rtype: dict + """ + value = {} + values_parser = values_parser or (lambda val: val) + for key, (_, val) in section_options.items(): + value[key] = values_parser(val) + return value + + def parse_section(self, section_options): + """Parses configuration file section. + + :param dict section_options: + """ + for (name, (_, value)) in section_options.items(): + try: + self[name] = value + except KeyError: + raise DistutilsOptionError( + 'Unknown distribution option: %s' % name) + + def parse(self): + """Parses configuration file items from one + or more related sections. + + """ + for section_name, section_options in self.sections.items(): + + method_postfix = '' + if section_name: # [section:option] variant + method_postfix = '_%s' % section_name + + section_parser_method = getattr( + self, 'parse_section%s' % method_postfix, None) + + if section_parser_method is None: + raise DistutilsOptionError( + 'Unsupported distribution option section: [%s:%s]' % ( + self.section_prefix, section_name)) + + section_parser_method(section_options) + + +class ConfigMetadataHandler(ConfigHandler): + + section_prefix = 'metadata' + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_file = self._parse_file + + return { + 'platforms': parse_list, + 'keywords': parse_list, + 'provides': parse_list, + 'requires': parse_list, + 'obsoletes': parse_list, + 'classifiers': self._get_parser_compound(parse_file, parse_list), + 'license': parse_file, + 'description': parse_file, + 'long_description': parse_file, + 'version': self._parse_version, + } + + def parse_section_classifiers(self, section_options): + """Parses configuration file section. + + :param dict section_options: + """ + classifiers = [] + for begin, (_, rest) in section_options.items(): + classifiers.append('%s :%s' % (begin.title(), rest)) + + self['classifiers'] = classifiers + + def _parse_version(self, value): + """Parses `version` option value. + + :param value: + :rtype: str + + """ + attr_directive = 'attr:' + if not value.startswith(attr_directive): + return value + + attrs_path = value.replace(attr_directive, '').strip().split('.') + attr_name = attrs_path.pop() + + module_name = '.'.join(attrs_path) + module_name = module_name or '__init__' + + sys.path.insert(0, os.getcwd()) + try: + module = import_module(module_name) + version = getattr(module, attr_name) + + if callable(version): + version = version() + + if not isinstance(version, string_types): + if hasattr(version, '__iter__'): + version = '.'.join(map(str, version)) + else: + version = '%s' % version + + finally: + sys.path = sys.path[1:] + + return version + + +class ConfigOptionsHandler(ConfigHandler): + + section_prefix = 'options' + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_list_semicolon = partial(self._parse_list, separator=';') + parse_bool = self._parse_bool + parse_dict = self._parse_dict + + return { + 'zip_safe': parse_bool, + 'use_2to3': parse_bool, + 'include_package_data': parse_bool, + 'package_dir': parse_dict, + 'use_2to3_fixers': parse_list, + 'use_2to3_exclude_fixers': parse_list, + 'convert_2to3_doctests': parse_list, + 'scripts': parse_list, + 'eager_resources': parse_list, + 'dependency_links': parse_list, + 'namespace_packages': parse_list, + 'install_requires': parse_list_semicolon, + 'setup_requires': parse_list_semicolon, + 'tests_require': parse_list_semicolon, + 'packages': self._parse_packages, + 'entry_points': self._parse_file, + } + + def _parse_packages(self, value): + """Parses `packages` option value. + + :param value: + :rtype: list + """ + find_directive = 'find:' + + if not value.startswith(find_directive): + return self._parse_list(value) + + from setuptools import find_packages + return find_packages() + + def parse_section_dependency_links(self, section_options): + """Parses `dependency_links` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options) + self['dependency_links'] = list(parsed.values()) + + def parse_section_entry_points(self, section_options): + """Parses `entry_points` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['entry_points'] = parsed + + def _parse_package_data(self, section_options): + parsed = self._parse_section_to_dict(section_options, self._parse_list) + + root = parsed.get('*') + if root: + parsed[''] = root + del parsed['*'] + + return parsed + + def parse_section_package_data(self, section_options): + """Parses `package_data` configuration file section. + + :param dict section_options: + """ + self['package_data'] = self._parse_package_data(section_options) + + def parse_section_exclude_package_data(self, section_options): + """Parses `exclude_package_data` configuration file section. + + :param dict section_options: + """ + self['exclude_package_data'] = self._parse_package_data( + section_options) + + def parse_section_extras_require(self, section_options): + """Parses `extras_require` configuration file section. + + :param dict section_options: + """ + parse_list = partial(self._parse_list, separator=';') + self['extras_require'] = self._parse_section_to_dict( + section_options, parse_list) -- cgit v1.2.3 From 69130241500d78735375e36eca1b3dc6a7048dd6 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 26 Nov 2016 21:42:08 +0700 Subject: Metadata and options are now could be set in setup.cfg (see #394). --- setuptools/dist.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setuptools/dist.py b/setuptools/dist.py index 612040c8..c975abe0 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -19,6 +19,7 @@ from pkg_resources.extern import packaging from setuptools.depends import Require from setuptools import windows_support from setuptools.monkey import get_unpatched +from setuptools.config import ConfigMetadataHandler, ConfigOptionsHandler import pkg_resources @@ -342,6 +343,16 @@ class Distribution(_Distribution): if getattr(self, 'python_requires', None): self.metadata.python_requires = self.python_requires + def parse_config_files(self, filenames=None): + """Parses configuration files from various levels + and loads configuration. + + """ + _Distribution.parse_config_files(self, filenames=filenames) + + ConfigMetadataHandler(self.metadata, self.command_options).parse() + ConfigOptionsHandler(self, self.command_options).parse() + def parse_command_line(self): """Process features after parsing command line options""" result = _Distribution.parse_command_line(self) -- cgit v1.2.3 From 58a5c4ff662a19d07b81da4c7b08e851dc2f65c8 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 26 Nov 2016 21:42:27 +0700 Subject: Added tests for config module. --- setuptools/tests/test_config.py | 339 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 setuptools/tests/test_config.py diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py new file mode 100644 index 00000000..0ef7a994 --- /dev/null +++ b/setuptools/tests/test_config.py @@ -0,0 +1,339 @@ +import contextlib +import pytest +from distutils.errors import DistutilsOptionError +from setuptools.dist import Distribution +from setuptools.config import ConfigHandler + + +class ErrConfigHandler(ConfigHandler): + """Erroneous handler. Fails to implement required methods.""" + + +def fake_env(tmpdir, setup_cfg, setup_py=None): + + if setup_py is None: + setup_py = ( + 'from setuptools import setup\n' + 'setup()\n' + ) + + tmpdir.join('setup.py').write(setup_py) + tmpdir.join('setup.cfg').write(setup_cfg) + + package_name = 'fake_package' + dir_package = tmpdir.mkdir(package_name) + dir_package.join('__init__.py').write( + 'VERSION = (1, 2, 3)\n' + '\n' + 'VERSION_MAJOR = 1' + '\n' + 'def get_version():\n' + ' return [3, 4, 5, "dev"]\n' + '\n' + ) + + +@contextlib.contextmanager +def get_dist(tmpdir, kwargs_initial=None, parse=True): + kwargs_initial = kwargs_initial or {} + + with tmpdir.as_cwd(): + dist = Distribution(kwargs_initial) + dist.script_name = 'setup.py' + parse and dist.parse_config_files() + + yield dist + + +def test_parsers_implemented(): + + with pytest.raises(NotImplementedError): + handler = ErrConfigHandler(None, {}) + handler.parsers + + +class TestMetadata: + + def test_basic(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'version = 10.1.1\n' + 'description = Some description\n' + 'long_description = file: README\n' + 'name = fake_name\n' + 'keywords = one, two\n' + 'provides = package, package.sub\n' + 'license = otherlic\n' + ) + + tmpdir.join('README').write('readme contents\nline2') + + meta_initial = { + # This will be used so `otherlic` won't replace it. + 'license': 'BSD 3-Clause License', + } + + with get_dist(tmpdir, meta_initial) as dist: + metadata = dist.metadata + + assert metadata.version == '10.1.1' + assert metadata.description == 'Some description' + assert metadata.long_description == 'readme contents\nline2' + assert metadata.provides == ['package', 'package.sub'] + assert metadata.license == 'BSD 3-Clause License' + assert metadata.name == 'fake_name' + assert metadata.keywords == ['one', 'two'] + + def test_version(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'version = attr: fake_package.VERSION\n' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.version == '1.2.3' + + tmpdir.join('setup.cfg').write( + '[metadata]\n' + 'version = attr: fake_package.get_version\n' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.version == '3.4.5.dev' + + tmpdir.join('setup.cfg').write( + '[metadata]\n' + 'version = attr: fake_package.VERSION_MAJOR\n' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.version == '1' + + subpack = tmpdir.join('fake_package').mkdir('subpackage') + subpack.join('__init__.py').write('') + subpack.join('submodule.py').write('VERSION = (2016, 11, 26)') + + tmpdir.join('setup.cfg').write( + '[metadata]\n' + 'version = attr: fake_package.subpackage.submodule.VERSION\n' + ) + with get_dist(tmpdir) as dist: + assert dist.metadata.version == '2016.11.26' + + def test_unknown_meta_item(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'name = fake_name\n' + 'unknown = some\n' + ) + with get_dist(tmpdir, parse=False) as dist: + with pytest.raises(DistutilsOptionError): + dist.parse_config_files() + + def test_usupported_section(self, tmpdir): + + fake_env( + tmpdir, + '[metadata:some]\n' + 'key = val\n' + ) + with get_dist(tmpdir, parse=False) as dist: + with pytest.raises(DistutilsOptionError): + dist.parse_config_files() + + def test_classifiers(self, tmpdir): + expected = { + 'Framework :: Django', + 'Programming Language :: Python :: 3.5', + } + + # From file. + fake_env( + tmpdir, + '[metadata]\n' + 'classifiers = file: classifiers\n' + ) + + tmpdir.join('classifiers').write( + 'Framework :: Django\n' + 'Programming Language :: Python :: 3.5\n' + ) + + with get_dist(tmpdir) as dist: + assert set(dist.metadata.classifiers) == expected + + # From section. + tmpdir.join('setup.cfg').write( + '[metadata:classifiers]\n' + 'Framework :: Django\n' + 'Programming Language :: Python :: 3.5\n' + ) + + with get_dist(tmpdir) as dist: + assert set(dist.metadata.classifiers) == expected + + +class TestOptions: + + def test_basic(self, tmpdir): + + fake_env( + tmpdir, + '[options]\n' + 'zip_safe = True\n' + 'use_2to3 = 1\n' + 'include_package_data = yes\n' + 'package_dir = b=c, =src\n' + 'packages = pack_a, pack_b.subpack\n' + 'namespace_packages = pack1, pack2\n' + 'use_2to3_fixers = your.fixers, or.here\n' + 'use_2to3_exclude_fixers = one.here, two.there\n' + 'convert_2to3_doctests = src/tests/one.txt, src/two.txt\n' + 'scripts = bin/one.py, bin/two.py\n' + 'eager_resources = bin/one.py, bin/two.py\n' + 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n' + 'tests_require = mock==0.7.2; pytest\n' + 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n' + 'dependency_links = http://some.com/here/1, ' + 'http://some.com/there/2\n' + ) + with get_dist(tmpdir) as dist: + assert dist.zip_safe + assert dist.use_2to3 + assert dist.include_package_data + assert dist.package_dir == {'': 'src', 'b': 'c'} + assert set(dist.packages) == {'pack_a', 'pack_b.subpack'} + assert set(dist.namespace_packages) == {'pack1', 'pack2'} + assert set(dist.use_2to3_fixers) == {'your.fixers', 'or.here'} + assert set(dist.use_2to3_exclude_fixers) == { + 'one.here', 'two.there'} + assert set(dist.convert_2to3_doctests) == { + 'src/tests/one.txt', 'src/two.txt'} + assert set(dist.scripts) == {'bin/one.py', 'bin/two.py'} + assert set(dist.dependency_links) == { + 'http://some.com/here/1', + 'http://some.com/there/2' + } + assert set(dist.install_requires) == { + 'docutils>=0.3', + 'pack ==1.1, ==1.3', + 'hey' + } + assert set(dist.setup_requires) == { + 'docutils>=0.3', + 'spack ==1.1, ==1.3', + 'there' + } + assert set(dist.tests_require) == { + 'mock==0.7.2', + 'pytest' + } + + def test_package_dir_fail(self, tmpdir): + fake_env( + tmpdir, + '[options]\n' + 'package_dir = a b\n' + ) + with get_dist(tmpdir, parse=False) as dist: + with pytest.raises(DistutilsOptionError): + dist.parse_config_files() + + def test_package_data(self, tmpdir): + fake_env( + tmpdir, + '[options:package_data]\n' + '* = *.txt, *.rst\n' + 'hello = *.msg\n' + '\n' + '[options:exclude_package_data]\n' + '* = fake1.txt, fake2.txt\n' + 'hello = *.dat\n' + ) + + with get_dist(tmpdir) as dist: + assert dist.package_data == { + '': ['*.txt', '*.rst'], + 'hello': ['*.msg'], + } + assert dist.exclude_package_data == { + '': ['fake1.txt', 'fake2.txt'], + 'hello': ['*.dat'], + } + + def test_packages(self, tmpdir): + fake_env( + tmpdir, + '[options]\n' + 'packages = find:\n' + ) + + with get_dist(tmpdir) as dist: + assert dist.packages == ['fake_package'] + + def test_extras_require(self, tmpdir): + fake_env( + tmpdir, + '[options:extras_require]\n' + 'pdf = ReportLab>=1.2; RXP\n' + 'rest = docutils>=0.3; pack ==1.1, ==1.3\n' + ) + + with get_dist(tmpdir) as dist: + assert dist.extras_require == { + 'pdf': ['ReportLab>=1.2', 'RXP'], + 'rest': ['docutils>=0.3', 'pack ==1.1, ==1.3'] + } + + def test_entry_points(self, tmpdir): + fake_env( + tmpdir, + '[options:entry_points]\n' + 'group1 = point1 = pack.module:func, ' + '.point2 = pack.module2:func_rest [rest]\n' + 'group2 = point3 = pack.module:func2\n' + ) + + with get_dist(tmpdir) as dist: + assert dist.entry_points == { + 'group1': [ + 'point1 = pack.module:func', + '.point2 = pack.module2:func_rest [rest]', + ], + 'group2': ['point3 = pack.module:func2'] + } + + expected = ( + '[blogtool.parsers]\n' + '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n' + ) + + tmpdir.join('entry_points').write(expected) + + # From file. + tmpdir.join('setup.cfg').write( + '[options]\n' + 'entry_points = file: entry_points\n' + ) + + with get_dist(tmpdir) as dist: + assert dist.entry_points == expected + + def test_dependency_links(self, tmpdir): + expected = { + 'http://some.com/here/1', + 'http://some.com/there/2' + } + # From section. + fake_env( + tmpdir, + '[options:dependency_links]\n' + '1 = http://some.com/here/1\n' + '2 = http://some.com/there/2\n' + ) + + with get_dist(tmpdir) as dist: + assert set(dist.dependency_links) == expected -- cgit v1.2.3 From 280d8e98ba0c3c4e37e38dff79aaf6e9efaf4175 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 26 Nov 2016 22:21:38 +0700 Subject: Tests for config module 2.6 compatible. --- setuptools/tests/test_config.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 0ef7a994..d044cbac 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -145,10 +145,10 @@ class TestMetadata: dist.parse_config_files() def test_classifiers(self, tmpdir): - expected = { + expected = set([ 'Framework :: Django', 'Programming Language :: Python :: 3.5', - } + ]) # From file. fake_env( @@ -205,32 +205,32 @@ class TestOptions: assert dist.use_2to3 assert dist.include_package_data assert dist.package_dir == {'': 'src', 'b': 'c'} - assert set(dist.packages) == {'pack_a', 'pack_b.subpack'} - assert set(dist.namespace_packages) == {'pack1', 'pack2'} - assert set(dist.use_2to3_fixers) == {'your.fixers', 'or.here'} - assert set(dist.use_2to3_exclude_fixers) == { - 'one.here', 'two.there'} - assert set(dist.convert_2to3_doctests) == { - 'src/tests/one.txt', 'src/two.txt'} - assert set(dist.scripts) == {'bin/one.py', 'bin/two.py'} - assert set(dist.dependency_links) == { + assert set(dist.packages) == set(['pack_a', 'pack_b.subpack']) + assert set(dist.namespace_packages) == set(['pack1', 'pack2']) + assert set(dist.use_2to3_fixers) == set(['your.fixers', 'or.here']) + assert set(dist.use_2to3_exclude_fixers) == set([ + 'one.here', 'two.there']) + assert set(dist.convert_2to3_doctests) == set([ + 'src/tests/one.txt', 'src/two.txt']) + assert set(dist.scripts) == set(['bin/one.py', 'bin/two.py']) + assert set(dist.dependency_links) == set([ 'http://some.com/here/1', 'http://some.com/there/2' - } - assert set(dist.install_requires) == { + ]) + assert set(dist.install_requires) == set([ 'docutils>=0.3', 'pack ==1.1, ==1.3', 'hey' - } - assert set(dist.setup_requires) == { + ]) + assert set(dist.setup_requires) == set([ 'docutils>=0.3', 'spack ==1.1, ==1.3', 'there' - } - assert set(dist.tests_require) == { + ]) + assert set(dist.tests_require) == set([ 'mock==0.7.2', 'pytest' - } + ]) def test_package_dir_fail(self, tmpdir): fake_env( @@ -323,10 +323,10 @@ class TestOptions: assert dist.entry_points == expected def test_dependency_links(self, tmpdir): - expected = { + expected = set([ 'http://some.com/here/1', 'http://some.com/there/2' - } + ]) # From section. fake_env( tmpdir, -- cgit v1.2.3 From de11b125f7a94a13aff478482e3a83a30176f7b7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Nov 2016 21:24:11 -0600 Subject: Now by default include the windows script launchers. Fixes #861 by addressing the underlying cause. --- .travis.yml | 2 -- CHANGES.rst | 10 ++++++++++ setup.py | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 006316d1..30b69a69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,6 @@ script: #- python -m tox - tox -before_deploy: - - export SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES=1 deploy: provider: pypi # Also update server in setup.cfg diff --git a/CHANGES.rst b/CHANGES.rst index 3b9f0894..99041cde 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,16 @@ CHANGES ======= +v29.0.1 +------- + +* #861: Re-release of v29.0.1 with the executable script + launchers bundled. Now, launchers are included by default + and users that want to disable this behavior must set the + environment variable + 'SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES' to + a false value like "false" or "0". + v29.0.0 ------- diff --git a/setup.py b/setup.py index 6286af63..30c19d66 100755 --- a/setup.py +++ b/setup.py @@ -54,8 +54,8 @@ package_data = dict( ) force_windows_specific_files = ( - os.environ.get("SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES") - not in (None, "", "0") + os.environ.get("SETUPTOOLS_INSTALL_WINDOWS_SPECIFIC_FILES", "1").lower() + not in ("", "0", "false", "no") ) include_windows_files = ( -- cgit v1.2.3 From 0be2dfcfd85044932127ee47ad5d16e5069d3b3a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Nov 2016 21:28:45 -0600 Subject: =?UTF-8?q?Bump=20version:=2029.0.0=20=E2=86=92=2029.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 589df541..4e59c74e 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 29.0.0 +current_version = 29.0.1 commit = True tag = True diff --git a/setup.py b/setup.py index 30c19d66..79d30308 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="29.0.0", + version="29.0.1", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From a83ae992278916ade263379f2d7adc56a4539ff4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 29 Nov 2016 18:20:05 -0600 Subject: Evaluate the expression directly. Workaround for #860. --- setuptools/tests/test_namespaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index 21fd69e7..2d44ad86 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -13,7 +13,7 @@ class TestNamespaces: @pytest.mark.xfail(sys.version_info < (3, 3), reason="Requires PEP 420") - @pytest.mark.skipif('os.environ.get("APPVEYOR")', + @pytest.mark.skipif(os.environ.get("APPVEYOR"), reason="https://github.com/pypa/setuptools/issues/851") def test_mixed_site_and_non_site(self, tmpdir): """ -- cgit v1.2.3 From b890d3d27602b6c18985c735a0ba0933232deb82 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 09:54:40 -0500 Subject: Clean up conftest.py --- conftest.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/conftest.py b/conftest.py index 47a5d888..ec8ddd8b 100644 --- a/conftest.py +++ b/conftest.py @@ -1,8 +1,7 @@ -import pytest - pytest_plugins = 'setuptools.tests.fixtures' def pytest_addoption(parser): - parser.addoption("--package_name", action="append", default=[], - help="list of package_name to pass to test functions") - + parser.addoption( + "--package_name", action="append", default=[], + help="list of package_name to pass to test functions", + ) -- cgit v1.2.3 From 413c234459de94766c8c57f10d11ef1599ae6ac0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 09:59:21 -0500 Subject: Monkeypatch the 'setuptools.__file__' attribute in test setup to be absolute. Workaround for #852. --- conftest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/conftest.py b/conftest.py index ec8ddd8b..0da92be9 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,25 @@ +import os + + pytest_plugins = 'setuptools.tests.fixtures' + def pytest_addoption(parser): parser.addoption( "--package_name", action="append", default=[], help="list of package_name to pass to test functions", ) + + +def pytest_configure(): + _issue_852_workaround() + + +def _issue_852_workaround(): + """ + Patch 'setuptools.__file__' with an absolute path + for forward compatibility with Python 3. + Workaround for https://github.com/pypa/setuptools/issues/852 + """ + setuptools = __import__('setuptools') + setuptools.__file__ = os.path.abspath(setuptools.__file__) -- cgit v1.2.3 From 2c1ea18266b558cb208c070eed7de60e922aa91a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 10:55:55 -0500 Subject: Stop testing on pypy3 until Travis gets pypy 5.5 or later. Ref #864. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 30b69a69..2f9b6a7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ python: - "3.6-dev" - nightly - pypy - - pypy3 env: - "" - LC_ALL=C LC_CTYPE=C -- cgit v1.2.3 From b47fe15b9039a165589353a1a43f6dfe3bbe3a8e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 10:56:39 -0500 Subject: Hard fail on Python 3 prior to 3.3. Fixes #864. --- CHANGES.rst | 6 ++++++ pkg_resources/__init__.py | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 99041cde..e6fa0d66 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ CHANGES ======= +v30.0.0 +------- + +* #864: Drop support for Python 3.2. Systems requiring + Python 3.2 support must use 'setuptools < 30'. + v29.0.1 ------- diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index a323857c..dd561d2b 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -75,11 +75,7 @@ __import__('pkg_resources.extern.packaging.requirements') __import__('pkg_resources.extern.packaging.markers') if (3, 0) < sys.version_info < (3, 3): - msg = ( - "Support for Python 3.0-3.2 has been dropped. Future versions " - "will fail here." - ) - warnings.warn(msg) + raise RuntimeError("Python 3.3 or later is required") # declare some globals that will be defined later to # satisfy the linters. -- cgit v1.2.3 From d2287000895d403fd50c8c5777e9c67f22268d8b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:06:39 -0500 Subject: Update changelog. Ref #840. --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index e6fa0d66..16cc06be 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ v30.0.0 * #864: Drop support for Python 3.2. Systems requiring Python 3.2 support must use 'setuptools < 30'. +* #825: Suppress warnings for single files. v29.0.1 ------- -- cgit v1.2.3 From 7924b1ff5eb7b11299cb8fa469e1b74c13d86f3e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:15:23 -0500 Subject: Update changelog. Ref #843. --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 16cc06be..2d48c7da 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,8 +7,13 @@ v30.0.0 * #864: Drop support for Python 3.2. Systems requiring Python 3.2 support must use 'setuptools < 30'. + * #825: Suppress warnings for single files. +* #830 via #843: Once again restored inclusion of data + files to sdists, but now trap TypeError caused by + techniques employed rjsmin and similar. + v29.0.1 ------- -- cgit v1.2.3 From 5cfce47ddb304fc95660c1086f3230fc8fdead61 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:23:45 -0500 Subject: Use super to resolve the superclass, but fall back to direct access on Python 2 where old style classes are used. Ref #843. --- setuptools/command/sdist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index ba980622..84e29a1b 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -143,7 +143,10 @@ class sdist(sdist_add_defaults, orig.sdist): def _add_defaults_data_files(self): try: - sdist_add_defaults._add_defaults_data_files(self) + if six.PY2: + sdist_add_defaults._add_defaults_data_files(self) + else: + super()._add_defaults_data_files() except TypeError: log.warn("data_files contains unexpected objects") -- cgit v1.2.3 From d42c2ace9c89dccf0076c17d33f7202e05f97bc0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:30:26 -0500 Subject: cast the value to a bool so pytest doesn't try to eval it --- setuptools/tests/test_namespaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index 2d44ad86..28c5e9de 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -13,7 +13,7 @@ class TestNamespaces: @pytest.mark.xfail(sys.version_info < (3, 3), reason="Requires PEP 420") - @pytest.mark.skipif(os.environ.get("APPVEYOR"), + @pytest.mark.skipif(bool(os.environ.get("APPVEYOR")), reason="https://github.com/pypa/setuptools/issues/851") def test_mixed_site_and_non_site(self, tmpdir): """ -- cgit v1.2.3 From d64cca85dc48306a7745de4ae5224d29f5e58003 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:35:47 -0500 Subject: =?UTF-8?q?Bump=20version:=2029.0.1=20=E2=86=92=2030.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4e59c74e..55c86a3c 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 29.0.1 +current_version = 30.0.0 commit = True tag = True diff --git a/setup.py b/setup.py index 79d30308..26aaec6b 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="29.0.1", + version="30.0.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 5ff82539955b291be7b638a38ed4775960417681 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Dec 2016 11:38:36 -0500 Subject: Remove superfluous heading in changelog --- CHANGES.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2d48c7da..7daf81b2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,3 @@ -======= -CHANGES -======= - v30.0.0 ------- -- cgit v1.2.3 From bce931606700438765f63d7b0784122506619061 Mon Sep 17 00:00:00 2001 From: Xavier Fernandez Date: Fri, 2 Dec 2016 11:20:18 +0100 Subject: Add python_requires to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 26aaec6b..eda59c8c 100755 --- a/setup.py +++ b/setup.py @@ -156,6 +156,7 @@ setup_params = dict( Topic :: System :: Systems Administration Topic :: Utilities """).strip().splitlines(), + python_requires='>=2.6,!=3.0.*,!=3.1.*,!=3.2.*', extras_require={ "ssl:sys_platform=='win32'": "wincertstore==0.2", "certs": "certifi==2016.9.26", -- cgit v1.2.3 From 2e0b4d41da9042cf658499c95ec506a3b38734d1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Dec 2016 09:11:01 -0500 Subject: Update changelog. Ref #846. --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7daf81b2..494c8489 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v30.1.0 +------- + +* #846: Also trap 'socket.error' when opening URLs in + package_index. + v30.0.0 ------- -- cgit v1.2.3 From 03083d6f1596aaee9c7c46e58e57e8127b17dd2a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Dec 2016 09:22:53 -0500 Subject: Update changelog. Ref #853. --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 494c8489..096d77bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,10 @@ v30.1.0 * #846: Also trap 'socket.error' when opening URLs in package_index. +* #849: Manifest processing now matches the filename + pattern anywhere in the filename and not just at the + start. Restores behavior found prior to 28.5.0. + v30.0.0 ------- -- cgit v1.2.3 From aac9f9f31c0fdb97c52e343be6b8a641b2341232 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Dec 2016 09:38:25 -0500 Subject: Update docs to reflect supported interface. Ref #859. --- docs/pkg_resources.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt index 9a45b9e3..e8412b33 100644 --- a/docs/pkg_resources.txt +++ b/docs/pkg_resources.txt @@ -831,12 +831,9 @@ correspond exactly to the constructor argument names: ``name``, ``module_name``, ``attrs``, ``extras``, and ``dist`` are all available. In addition, the following methods are provided: -``load(require=True, env=None, installer=None)`` - Load the entry point, returning the advertised Python object, or raise - ``ImportError`` if it cannot be obtained. If `require` is a true value, - then ``require(env, installer)`` is called before attempting the import. - Parameters to load are deprecated. Call ``require`` and ``resolve`` - separately. +``load()`` + Load the entry point, returning the advertised Python object. Effectively + calls ``self.require()`` then returns ``self.resolve()``. ``require(env=None, installer=None)`` Ensure that any "extras" needed by the entry point are available on @@ -850,7 +847,7 @@ addition, the following methods are provided: ``resolve()`` Resolve the entry point from its module and attrs, returning the advertised - Python object. + Python object. Raises ``ImportError`` if it cannot be obtained. ``__str__()`` The string form of an ``EntryPoint`` is a string that could be passed to -- cgit v1.2.3 From 6608d8f7e0522b75445a984693da65005155895c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Dec 2016 10:14:19 -0500 Subject: =?UTF-8?q?Bump=20version:=2030.0.0=20=E2=86=92=2030.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 55c86a3c..66688138 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 30.0.0 +current_version = 30.1.0 commit = True tag = True diff --git a/setup.py b/setup.py index 26aaec6b..103c87e8 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="30.0.0", + version="30.1.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 03b00c2b81142b9b329d23fe3d055a14c8698daf Mon Sep 17 00:00:00 2001 From: stepshal Date: Mon, 21 Nov 2016 23:02:21 +0700 Subject: Upgrade packaging to 16.8 --- pkg_resources/_vendor/packaging/__about__.py | 2 +- pkg_resources/_vendor/packaging/markers.py | 24 +++++++++++++++++++----- pkg_resources/_vendor/vendored.txt | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pkg_resources/_vendor/packaging/__about__.py b/pkg_resources/_vendor/packaging/__about__.py index c21a758b..95d330ef 100644 --- a/pkg_resources/_vendor/packaging/__about__.py +++ b/pkg_resources/_vendor/packaging/__about__.py @@ -12,7 +12,7 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "16.7" +__version__ = "16.8" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" diff --git a/pkg_resources/_vendor/packaging/markers.py b/pkg_resources/_vendor/packaging/markers.py index c5d29cd9..892e578e 100644 --- a/pkg_resources/_vendor/packaging/markers.py +++ b/pkg_resources/_vendor/packaging/markers.py @@ -52,13 +52,26 @@ class Node(object): def __repr__(self): return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + def serialize(self): + raise NotImplementedError + class Variable(Node): - pass + + def serialize(self): + return str(self) class Value(Node): - pass + + def serialize(self): + return '"{0}"'.format(self) + + +class Op(Node): + + def serialize(self): + return str(self) VARIABLE = ( @@ -103,6 +116,7 @@ VERSION_CMP = ( ) MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) MARKER_VALUE = QuotedString("'") | QuotedString('"') MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) @@ -149,7 +163,7 @@ def _format_marker(marker, first=True): else: return "(" + " ".join(inner) + ")" elif isinstance(marker, tuple): - return '{0} {1} "{2}"'.format(*marker) + return " ".join([m.serialize() for m in marker]) else: return marker @@ -168,13 +182,13 @@ _operators = { def _eval_op(lhs, op, rhs): try: - spec = Specifier("".join([op, rhs])) + spec = Specifier("".join([op.serialize(), rhs])) except InvalidSpecifier: pass else: return spec.contains(lhs) - oper = _operators.get(op) + oper = _operators.get(op.serialize()) if oper is None: raise UndefinedComparison( "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt index 6b5eb450..9a94c5bc 100644 --- a/pkg_resources/_vendor/vendored.txt +++ b/pkg_resources/_vendor/vendored.txt @@ -1,4 +1,4 @@ -packaging==16.7 +packaging==16.8 pyparsing==2.1.10 six==1.10.0 appdirs==1.4.0 -- cgit v1.2.3 From 810eb439a629e1b2bc2d078f138126356e95a9bc Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 10:41:54 +0700 Subject: Added ConfigHandler.strict_mode. --- setuptools/config.py | 21 +++++++++++++++++++-- setuptools/tests/test_config.py | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 94b2ab17..3546ace9 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -12,6 +12,16 @@ class ConfigHandler(object): """Handles metadata supplied in configuration files.""" section_prefix = None + """Prefix for config sections handled by this handler. + Must be provided by class heirs. + + """ + + strict_mode = True + """Flag. Whether unknown options in config should + raise DistutilsOptionError exception, or pass silently. + + """ def __init__(self, target_obj, options): sections = {} @@ -174,9 +184,11 @@ class ConfigHandler(object): for (name, (_, value)) in section_options.items(): try: self[name] = value + except KeyError: - raise DistutilsOptionError( - 'Unknown distribution option: %s' % name) + if self.strict_mode: + raise DistutilsOptionError( + 'Unknown distribution option: %s' % name) def parse(self): """Parses configuration file items from one @@ -203,6 +215,11 @@ class ConfigHandler(object): class ConfigMetadataHandler(ConfigHandler): section_prefix = 'metadata' + strict_mode = False + """We need to keep it loose, to be compatible with `pbr` package + which also uses `metadata` section. + + """ @property def parsers(self): diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index d044cbac..f1b1aa3f 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -130,8 +130,7 @@ class TestMetadata: 'unknown = some\n' ) with get_dist(tmpdir, parse=False) as dist: - with pytest.raises(DistutilsOptionError): - dist.parse_config_files() + dist.parse_config_files() # Skip unknown. def test_usupported_section(self, tmpdir): @@ -274,6 +273,18 @@ class TestOptions: with get_dist(tmpdir) as dist: assert dist.packages == ['fake_package'] + def test_unknown_options_item(self, tmpdir): + + fake_env( + tmpdir, + '[options]\n' + 'zip_safe = True\n' + 'usr_2to3 = 1\n' + ) + with get_dist(tmpdir, parse=False) as dist: + with pytest.raises(DistutilsOptionError): + dist.parse_config_files() + def test_extras_require(self, tmpdir): fake_env( tmpdir, -- cgit v1.2.3 From a5567b762cfe48a8e5a4aada5a997e5fd8072420 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 11:05:11 +0700 Subject: Implemented proper dangling option values support. --- setuptools/config.py | 4 +- setuptools/tests/test_config.py | 111 +++++++++++++++++++++++++++++++++++----- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 3546ace9..ef416995 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -70,7 +70,7 @@ class ConfigHandler(object): def _parse_list(cls, value, separator=','): """Represents value as a list. - Value is split either by comma or by lines. + Value is split either by separator (defaults to comma) or by lines. :param value: :param separator: List items separator character. @@ -84,7 +84,7 @@ class ConfigHandler(object): else: value = value.split(separator) - return [chunk.strip() for chunk in value] + return [chunk.strip() for chunk in value if chunk.strip()] @classmethod def _parse_dict(cls, value): diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index f1b1aa3f..b4bd089c 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -86,6 +86,27 @@ class TestMetadata: assert metadata.name == 'fake_name' assert metadata.keywords == ['one', 'two'] + def test_multiline(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'name = fake_name\n' + 'keywords =\n' + ' one\n' + ' two\n' + 'classifiers =\n' + ' Framework :: Django\n' + ' Programming Language :: Python :: 3.5\n' + ) + with get_dist(tmpdir) as dist: + metadata = dist.metadata + assert metadata.keywords == ['one', 'two'] + assert metadata.classifiers == [ + 'Framework :: Django', + 'Programming Language :: Python :: 3.5', + ] + def test_version(self, tmpdir): fake_env( @@ -204,32 +225,96 @@ class TestOptions: assert dist.use_2to3 assert dist.include_package_data assert dist.package_dir == {'': 'src', 'b': 'c'} - assert set(dist.packages) == set(['pack_a', 'pack_b.subpack']) - assert set(dist.namespace_packages) == set(['pack1', 'pack2']) - assert set(dist.use_2to3_fixers) == set(['your.fixers', 'or.here']) - assert set(dist.use_2to3_exclude_fixers) == set([ - 'one.here', 'two.there']) - assert set(dist.convert_2to3_doctests) == set([ + assert dist.packages == ['pack_a', 'pack_b.subpack'] + assert dist.namespace_packages == ['pack1', 'pack2'] + assert dist.use_2to3_fixers == ['your.fixers', 'or.here'] + assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there'] + assert dist.convert_2to3_doctests == ([ 'src/tests/one.txt', 'src/two.txt']) - assert set(dist.scripts) == set(['bin/one.py', 'bin/two.py']) - assert set(dist.dependency_links) == set([ + assert dist.scripts == ['bin/one.py', 'bin/two.py'] + assert dist.dependency_links == ([ 'http://some.com/here/1', 'http://some.com/there/2' ]) - assert set(dist.install_requires) == set([ + assert dist.install_requires == ([ 'docutils>=0.3', 'pack ==1.1, ==1.3', 'hey' ]) - assert set(dist.setup_requires) == set([ + assert dist.setup_requires == ([ 'docutils>=0.3', 'spack ==1.1, ==1.3', 'there' ]) - assert set(dist.tests_require) == set([ - 'mock==0.7.2', - 'pytest' + assert dist.tests_require == ['mock==0.7.2', 'pytest'] + + def test_multiline(self, tmpdir): + fake_env( + tmpdir, + '[options]\n' + 'package_dir = \n' + ' b=c\n' + ' =src\n' + 'packages = \n' + ' pack_a\n' + ' pack_b.subpack\n' + 'namespace_packages = \n' + ' pack1\n' + ' pack2\n' + 'use_2to3_fixers = \n' + ' your.fixers\n' + ' or.here\n' + 'use_2to3_exclude_fixers = \n' + ' one.here\n' + ' two.there\n' + 'convert_2to3_doctests = \n' + ' src/tests/one.txt\n' + ' src/two.txt\n' + 'scripts = \n' + ' bin/one.py\n' + ' bin/two.py\n' + 'eager_resources = \n' + ' bin/one.py\n' + ' bin/two.py\n' + 'install_requires = \n' + ' docutils>=0.3\n' + ' pack ==1.1, ==1.3\n' + ' hey\n' + 'tests_require = \n' + ' mock==0.7.2\n' + ' pytest\n' + 'setup_requires = \n' + ' docutils>=0.3\n' + ' spack ==1.1, ==1.3\n' + ' there\n' + 'dependency_links = \n' + ' http://some.com/here/1\n' + ' http://some.com/there/2\n' + ) + with get_dist(tmpdir) as dist: + assert dist.package_dir == {'': 'src', 'b': 'c'} + assert dist.packages == ['pack_a', 'pack_b.subpack'] + assert dist.namespace_packages == ['pack1', 'pack2'] + assert dist.use_2to3_fixers == ['your.fixers', 'or.here'] + assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there'] + assert dist.convert_2to3_doctests == ( + ['src/tests/one.txt', 'src/two.txt']) + assert dist.scripts == ['bin/one.py', 'bin/two.py'] + assert dist.dependency_links == ([ + 'http://some.com/here/1', + 'http://some.com/there/2' + ]) + assert dist.install_requires == ([ + 'docutils>=0.3', + 'pack ==1.1, ==1.3', + 'hey' + ]) + assert dist.setup_requires == ([ + 'docutils>=0.3', + 'spack ==1.1, ==1.3', + 'there' ]) + assert dist.tests_require == ['mock==0.7.2', 'pytest'] def test_package_dir_fail(self, tmpdir): fake_env( -- cgit v1.2.3 From 49fc619dd4bc059ae823054f586753ebf35edeee Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 11:25:48 +0700 Subject: `dependency_links` as section not supported. --- setuptools/config.py | 8 -------- setuptools/tests/test_config.py | 16 ---------------- 2 files changed, 24 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index ef416995..9319f78f 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -333,14 +333,6 @@ class ConfigOptionsHandler(ConfigHandler): from setuptools import find_packages return find_packages() - def parse_section_dependency_links(self, section_options): - """Parses `dependency_links` configuration file section. - - :param dict section_options: - """ - parsed = self._parse_section_to_dict(section_options) - self['dependency_links'] = list(parsed.values()) - def parse_section_entry_points(self, section_options): """Parses `entry_points` configuration file section. diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index b4bd089c..bfc863ec 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -417,19 +417,3 @@ class TestOptions: with get_dist(tmpdir) as dist: assert dist.entry_points == expected - - def test_dependency_links(self, tmpdir): - expected = set([ - 'http://some.com/here/1', - 'http://some.com/there/2' - ]) - # From section. - fake_env( - tmpdir, - '[options:dependency_links]\n' - '1 = http://some.com/here/1\n' - '2 = http://some.com/there/2\n' - ) - - with get_dist(tmpdir) as dist: - assert set(dist.dependency_links) == expected -- cgit v1.2.3 From 566e9aee17dbe2cec92b9d793f2466681f2b1a7f Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 11:27:42 +0700 Subject: Future package imported. --- setuptools/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setuptools/config.py b/setuptools/config.py index 9319f78f..5c73ca62 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, unicode_literals import io import os import sys -- cgit v1.2.3 From 68c03bee07c55a9c337f1cb98fc102a3710add4b Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 16:22:07 +0700 Subject: Section names now dot-separated to mimic .toml table names. --- setuptools/config.py | 2 +- setuptools/tests/test_config.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 5c73ca62..a04c3ce8 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -32,7 +32,7 @@ class ConfigHandler(object): if not section_name.startswith(section_prefix): continue - section_name = section_name.replace(section_prefix, '').strip(':') + section_name = section_name.replace(section_prefix, '').strip('.') sections[section_name] = section_options self.target_obj = target_obj diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index bfc863ec..e53b5ffd 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -157,7 +157,7 @@ class TestMetadata: fake_env( tmpdir, - '[metadata:some]\n' + '[metadata.some]\n' 'key = val\n' ) with get_dist(tmpdir, parse=False) as dist: @@ -187,7 +187,7 @@ class TestMetadata: # From section. tmpdir.join('setup.cfg').write( - '[metadata:classifiers]\n' + '[metadata.classifiers]\n' 'Framework :: Django\n' 'Programming Language :: Python :: 3.5\n' ) @@ -329,11 +329,11 @@ class TestOptions: def test_package_data(self, tmpdir): fake_env( tmpdir, - '[options:package_data]\n' + '[options.package_data]\n' '* = *.txt, *.rst\n' 'hello = *.msg\n' '\n' - '[options:exclude_package_data]\n' + '[options.exclude_package_data]\n' '* = fake1.txt, fake2.txt\n' 'hello = *.dat\n' ) @@ -373,9 +373,11 @@ class TestOptions: def test_extras_require(self, tmpdir): fake_env( tmpdir, - '[options:extras_require]\n' + '[options.extras_require]\n' 'pdf = ReportLab>=1.2; RXP\n' - 'rest = docutils>=0.3; pack ==1.1, ==1.3\n' + 'rest = \n' + ' docutils>=0.3\n' + ' pack ==1.1, ==1.3\n' ) with get_dist(tmpdir) as dist: @@ -387,7 +389,7 @@ class TestOptions: def test_entry_points(self, tmpdir): fake_env( tmpdir, - '[options:entry_points]\n' + '[options.entry_points]\n' 'group1 = point1 = pack.module:func, ' '.point2 = pack.module2:func_rest [rest]\n' 'group2 = point3 = pack.module:func2\n' -- cgit v1.2.3 From 21333fe86db1888dbee134043ea8a2f85b69d439 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 17:11:37 +0700 Subject: Added `metadata` section aliases. --- setuptools/config.py | 22 ++++++++++++++++++++-- setuptools/tests/test_config.py | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index a04c3ce8..0c88df79 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -18,6 +18,12 @@ class ConfigHandler(object): """ + aliases = {} + """Options aliases. + For compatibility with various packages. E.g.: d2to1 and pbr. + + """ + strict_mode = True """Flag. Whether unknown options in config should raise DistutilsOptionError exception, or pass silently. @@ -48,6 +54,9 @@ class ConfigHandler(object): unknown = tuple() target_obj = self.target_obj + # Translate alias into real name. + option_name = self.aliases.get(option_name, option_name) + current_value = getattr(target_obj, option_name, unknown) if current_value is unknown: @@ -216,9 +225,18 @@ class ConfigHandler(object): class ConfigMetadataHandler(ConfigHandler): section_prefix = 'metadata' + + aliases = { + 'author-email': 'author_email', + 'home_page': 'url', + 'summary': 'description', + 'classifier': 'classifiers', + 'platform': 'platforms', + } + strict_mode = False - """We need to keep it loose, to be compatible with `pbr` package - which also uses `metadata` section. + """We need to keep it loose, to be partially compatible with + `pbr` and `d2to1` packages which also uses `metadata` section. """ diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index e53b5ffd..3fabfb94 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -86,6 +86,31 @@ class TestMetadata: assert metadata.name == 'fake_name' assert metadata.keywords == ['one', 'two'] + def test_aliases(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'author-email = test@test.com\n' + 'home_page = http://test.test.com/test/\n' + 'summary = Short summary\n' + 'platform = a, b\n' + 'classifier =\n' + ' Framework :: Django\n' + ' Programming Language :: Python :: 3.5\n' + ) + + with get_dist(tmpdir) as dist: + metadata = dist.metadata + assert metadata.author_email == 'test@test.com' + assert metadata.url == 'http://test.test.com/test/' + assert metadata.description == 'Short summary' + assert metadata.platforms == ['a', 'b'] + assert metadata.classifiers == [ + 'Framework :: Django', + 'Programming Language :: Python :: 3.5', + ] + def test_multiline(self, tmpdir): fake_env( -- cgit v1.2.3 From 8998172299cd562c937e83383e9fb666e6209b30 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 17:24:05 +0700 Subject: `metadata` aliases update. --- setuptools/config.py | 2 +- setuptools/tests/test_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 0c88df79..c6b93c4e 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -21,6 +21,7 @@ class ConfigHandler(object): aliases = {} """Options aliases. For compatibility with various packages. E.g.: d2to1 and pbr. + Note: `-` in keys is replaced with `_` by config parser. """ @@ -227,7 +228,6 @@ class ConfigMetadataHandler(ConfigHandler): section_prefix = 'metadata' aliases = { - 'author-email': 'author_email', 'home_page': 'url', 'summary': 'description', 'classifier': 'classifiers', diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 3fabfb94..08c5bd19 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -92,7 +92,7 @@ class TestMetadata: tmpdir, '[metadata]\n' 'author-email = test@test.com\n' - 'home_page = http://test.test.com/test/\n' + 'home-page = http://test.test.com/test/\n' 'summary = Short summary\n' 'platform = a, b\n' 'classifier =\n' -- cgit v1.2.3 From a5dadcf0eea5bda6991a77546787d1e657ae0411 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sun, 4 Dec 2016 17:35:44 +0700 Subject: _parse_attr() factored out. --- setuptools/config.py | 63 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index c6b93c4e..d8513a72 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -152,6 +152,37 @@ class ConfigHandler(object): return value + @classmethod + def _parse_attr(cls, value): + """Represents value as a module attribute. + + Examples: + attr: package.attr + attr: package.module.attr + + :param str value: + :rtype: str + """ + attr_directive = 'attr:' + if not value.startswith(attr_directive): + return value + + attrs_path = value.replace(attr_directive, '').strip().split('.') + attr_name = attrs_path.pop() + + module_name = '.'.join(attrs_path) + module_name = module_name or '__init__' + + sys.path.insert(0, os.getcwd()) + try: + module = import_module(module_name) + value = getattr(module, attr_name) + + finally: + sys.path = sys.path[1:] + + return value + @classmethod def _get_parser_compound(cls, *parse_methods): """Returns parser function to represents value as a list. @@ -277,32 +308,16 @@ class ConfigMetadataHandler(ConfigHandler): :rtype: str """ - attr_directive = 'attr:' - if not value.startswith(attr_directive): - return value + version = self._parse_attr(value) - attrs_path = value.replace(attr_directive, '').strip().split('.') - attr_name = attrs_path.pop() + if callable(version): + version = version() - module_name = '.'.join(attrs_path) - module_name = module_name or '__init__' - - sys.path.insert(0, os.getcwd()) - try: - module = import_module(module_name) - version = getattr(module, attr_name) - - if callable(version): - version = version() - - if not isinstance(version, string_types): - if hasattr(version, '__iter__'): - version = '.'.join(map(str, version)) - else: - version = '%s' % version - - finally: - sys.path = sys.path[1:] + if not isinstance(version, string_types): + if hasattr(version, '__iter__'): + version = '.'.join(map(str, version)) + else: + version = '%s' % version return version -- cgit v1.2.3 From 2cdc411bf1bab8635da22e15850a93023977cf53 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Dec 2016 10:44:07 -0500 Subject: Update changelog. Ref #854. --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 096d77bf..d100bdcf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,8 @@ +v30.2.0 +------- + +* #854: Bump to vendored Packaging 16.8. + v30.1.0 ------- -- cgit v1.2.3 From c4df6d7197a0e56ead10a1b1c0dbc81ea4a36615 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Dec 2016 10:49:17 -0500 Subject: =?UTF-8?q?Bump=20version:=2030.1.0=20=E2=86=92=2030.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 66688138..b234d0bd 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 30.1.0 +current_version = 30.2.0 commit = True tag = True diff --git a/setup.py b/setup.py index 83d85e60..e088928c 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="30.1.0", + version="30.2.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 24e0c48846956d55af3a191bed02e3ac84595fb2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Dec 2016 10:59:11 -0500 Subject: Prefer update and generator expression to for/if loop --- pkg_resources/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index dd561d2b..3be41752 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -3005,9 +3005,11 @@ def _initialize(g=globals()): "Set up global resource manager (deliberately not state-saved)" manager = ResourceManager() g['_manager'] = manager - for name in dir(manager): - if not name.startswith('_'): - g[name] = getattr(manager, name) + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) @_call_aside -- cgit v1.2.3 From f783aa36971d78b293fb5d627d1aacd6dbd49afc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Dec 2016 11:04:10 -0500 Subject: Use generator expression to manage the scope of 'dist' --- pkg_resources/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 3be41752..92503288 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -3038,10 +3038,10 @@ def _initialize_master_working_set(): # ensure that all distributions added to the working set in the future # (e.g. by calling ``require()``) will get activated as well, # with higher priority (replace=True). - dist = None # ensure dist is defined for del dist below - for dist in working_set: + tuple( dist.activate(replace=False) - del dist + for dist in working_set + ) add_activation_listener(lambda dist: dist.activate(replace=True), existing=False) working_set.entries = [] # match order -- cgit v1.2.3 From 1474cf0f7e2b973eb102aef311939228b83e31b6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Dec 2016 11:37:49 -0500 Subject: Use get_distribution to resolve version. Ref testing-cabal/mock#385. --- setuptools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/version.py b/setuptools/version.py index f2b40722..95e18696 100644 --- a/setuptools/version.py +++ b/setuptools/version.py @@ -1,6 +1,6 @@ import pkg_resources try: - __version__ = pkg_resources.require('setuptools')[0].version + __version__ = pkg_resources.get_distribution('setuptools').version except Exception: __version__ = 'unknown' -- cgit v1.2.3 From be09553790c866134411ce504d9bdaf4a924bf5a Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Mon, 5 Dec 2016 08:11:32 +0000 Subject: docs: update stray links to Bitbucket repo --- README.rst | 4 ++-- docs/_templates/indexsidebar.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 2cf762d7..50774ff7 100755 --- a/README.rst +++ b/README.rst @@ -139,10 +139,10 @@ Package Index`_. Scroll to the very bottom of the page to find the links. .. _the project's home page in the Python Package Index: https://pypi.python.org/pypi/setuptools In addition to the PyPI downloads, the development version of ``setuptools`` -is available from the `Bitbucket repo`_, and in-development versions of the +is available from the `GitHub repo`_, and in-development versions of the `0.6 branch`_ are available as well. -.. _Bitbucket repo: https://bitbucket.org/pypa/setuptools/get/default.tar.gz#egg=setuptools-dev +.. _GitHub repo: https://github.com/pypa/setuptools/archive/master.tar.gz#egg=setuptools-dev .. _0.6 branch: http://svn.python.org/projects/sandbox/branches/setuptools-0.6/#egg=setuptools-dev06 Uninstalling diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html index a27c85fe..3b127602 100644 --- a/docs/_templates/indexsidebar.html +++ b/docs/_templates/indexsidebar.html @@ -5,4 +5,4 @@

Questions? Suggestions? Contributions?

-

Visit the Setuptools project page

+

Visit the Setuptools project page

-- cgit v1.2.3 From 7ab3af3cf7f8ffa512ff74cd24f1b833decdef6f Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Mon, 5 Dec 2016 08:13:43 +0000 Subject: docs: reorder table of contents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “Building and Distributing Packages with Setuptools” is a better entry point to the documentation than the changelog. --- docs/index.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 6ac37252..74aabb5e 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -16,10 +16,10 @@ Documentation content: .. toctree:: :maxdepth: 2 - history - roadmap - python3 setuptools easy_install pkg_resources + python3 development + roadmap + history -- cgit v1.2.3 From af321fc6ad82c54a78e7c1a74601e0a6b34997da Mon Sep 17 00:00:00 2001 From: idle sign Date: Mon, 5 Dec 2016 21:55:48 +0700 Subject: `file:` directive sandboxed. --- setuptools/config.py | 12 +++++++++++- setuptools/tests/test_config.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/setuptools/config.py b/setuptools/config.py index d8513a72..c2319ed5 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -128,7 +128,10 @@ class ConfigHandler(object): @classmethod def _parse_file(cls, value): """Represents value as a string, allowing including text - from nearest files using include(). + from nearest files using `file:` directive. + + Directive is sandboxed and won't reach anything outside + directory with setup.py. Examples: include: LICENSE @@ -144,7 +147,14 @@ class ConfigHandler(object): if not value.startswith(include_directive): return value + current_directory = os.getcwd() + filepath = value.replace(include_directive, '').strip() + filepath = os.path.abspath(filepath) + + if not filepath.startswith(current_directory): + raise DistutilsOptionError( + '`file:` directive can not access %s' % filepath) if os.path.isfile(filepath): with io.open(filepath, encoding='utf-8') as f: diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 08c5bd19..9fb55b06 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -86,6 +86,18 @@ class TestMetadata: assert metadata.name == 'fake_name' assert metadata.keywords == ['one', 'two'] + def test_file_sandboxed(self, tmpdir): + + fake_env( + tmpdir, + '[metadata]\n' + 'long_description = file: ../../README\n' + ) + + with get_dist(tmpdir, parse=False) as dist: + with pytest.raises(DistutilsOptionError): + dist.parse_config_files() # file: out of sandbox + def test_aliases(self, tmpdir): fake_env( -- cgit v1.2.3 From acaece809ee3592c0d135a9a0a8e556db0a9e587 Mon Sep 17 00:00:00 2001 From: idle sign Date: Mon, 5 Dec 2016 22:07:16 +0700 Subject: Tests and docstrings update. --- setuptools/config.py | 6 +++--- setuptools/tests/test_config.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index c2319ed5..2dd42893 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -87,7 +87,7 @@ class ConfigHandler(object): :param separator: List items separator character. :rtype: list """ - if isinstance(value, list): # _parse_complex case + if isinstance(value, list): # _get_parser_compound case return value if '\n' in value: @@ -250,7 +250,7 @@ class ConfigHandler(object): for section_name, section_options in self.sections.items(): method_postfix = '' - if section_name: # [section:option] variant + if section_name: # [section.option] variant method_postfix = '_%s' % section_name section_parser_method = getattr( @@ -258,7 +258,7 @@ class ConfigHandler(object): if section_parser_method is None: raise DistutilsOptionError( - 'Unsupported distribution option section: [%s:%s]' % ( + 'Unsupported distribution option section: [%s.%s]' % ( self.section_prefix, section_name)) section_parser_method(section_options) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 9fb55b06..259a396a 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -66,6 +66,8 @@ class TestMetadata: 'keywords = one, two\n' 'provides = package, package.sub\n' 'license = otherlic\n' + 'download_url = http://test.test.com/test/\n' + 'maintainer_email = test@test.com\n' ) tmpdir.join('README').write('readme contents\nline2') @@ -85,6 +87,8 @@ class TestMetadata: assert metadata.license == 'BSD 3-Clause License' assert metadata.name == 'fake_name' assert metadata.keywords == ['one', 'two'] + assert metadata.download_url == 'http://test.test.com/test/' + assert metadata.maintainer_email == 'test@test.com' def test_file_sandboxed(self, tmpdir): -- cgit v1.2.3 From 163f36449c2b8c19c272414bff0bf80c9f3f2c7d Mon Sep 17 00:00:00 2001 From: idle sign Date: Mon, 5 Dec 2016 23:13:35 +0700 Subject: Added API functions. --- setuptools/config.py | 78 +++++++++++++++++++++++++++++++++++++++++ setuptools/dist.py | 5 ++- setuptools/tests/test_config.py | 20 ++++++++++- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 2dd42893..6459e1de 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import io import os import sys +from collections import defaultdict from functools import partial from distutils.errors import DistutilsOptionError @@ -9,6 +10,80 @@ from setuptools.py26compat import import_module from setuptools.extern.six import string_types +def read_configuration(filepath, find_others=False): + """Read given configuration file and returns options from it as a dict. + + :param str|unicode filepath: Path to configuration file + to get options from. + + :param bool find_others: Whether to search for other configuration files + which could be on in various places. + + :rtype: dict + """ + from setuptools.dist import Distribution, _Distribution + + dist = Distribution() + + filenames = dist.find_config_files() if find_others else [] + if filepath not in filenames: + filenames.append(filepath) + + _Distribution.parse_config_files(dist, filenames=filenames) + + handlers = parse_configuration(dist, dist.command_options) + + return configuration_to_dict(handlers) + + +def configuration_to_dict(handlers): + """Returns configuration data gathered by given handlers as a dict. + + :param list[ConfigHandler] handlers: Handlers list, + usually from parse_configuration() + + :rtype: dict + """ + config_dict = defaultdict(dict) + + for handler in handlers: + + obj_alias = handler.section_prefix + target_obj = handler.target_obj + + for option in handler.set_options: + getter = getattr(target_obj, 'get_%s' % option, None) + + if getter is None: + value = getattr(target_obj, option) + + else: + value = getter() + + config_dict[obj_alias][option] = value + + return config_dict + + +def parse_configuration(distribution, command_options): + """Performs additional parsing of configuration options + for a distribution. + + Returns a list of used option handlers. + + :param Distribution distribution: + :param dict command_options: + :rtype: list + """ + meta = ConfigMetadataHandler(distribution.metadata, command_options) + meta.parse() + + options = ConfigOptionsHandler(distribution, command_options) + options.parse() + + return [meta, options] + + class ConfigHandler(object): """Handles metadata supplied in configuration files.""" @@ -44,6 +119,7 @@ class ConfigHandler(object): self.target_obj = target_obj self.sections = sections + self.set_options = [] @property def parsers(self): @@ -77,6 +153,8 @@ class ConfigHandler(object): else: setter(value) + self.set_options.append(option_name) + @classmethod def _parse_list(cls, value, separator=','): """Represents value as a list. diff --git a/setuptools/dist.py b/setuptools/dist.py index c975abe0..c04e6426 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -19,7 +19,7 @@ from pkg_resources.extern import packaging from setuptools.depends import Require from setuptools import windows_support from setuptools.monkey import get_unpatched -from setuptools.config import ConfigMetadataHandler, ConfigOptionsHandler +from setuptools.config import parse_configuration import pkg_resources @@ -350,8 +350,7 @@ class Distribution(_Distribution): """ _Distribution.parse_config_files(self, filenames=filenames) - ConfigMetadataHandler(self.metadata, self.command_options).parse() - ConfigOptionsHandler(self, self.command_options).parse() + parse_configuration(self, self.command_options) def parse_command_line(self): """Process features after parsing command line options""" diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 259a396a..cd646dba 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -2,7 +2,7 @@ import contextlib import pytest from distutils.errors import DistutilsOptionError from setuptools.dist import Distribution -from setuptools.config import ConfigHandler +from setuptools.config import ConfigHandler, read_configuration class ErrConfigHandler(ConfigHandler): @@ -52,6 +52,24 @@ def test_parsers_implemented(): handler.parsers +class TestConfigurationReader: + + def test_basic(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'version = 10.1.1\n' + 'keywords = one, two\n' + '\n' + '[options]\n' + 'scripts = bin/a.py, bin/b.py\n' + ) + config_dict = read_configuration('%s' % tmpdir.join('setup.cfg')) + assert config_dict['metadata']['version'] == '10.1.1' + assert config_dict['metadata']['keywords'] == ['one', 'two'] + assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py'] + + class TestMetadata: def test_basic(self, tmpdir): -- cgit v1.2.3 From 6aae9fb3f2bf222466fc2fd0db5e22760c6239c6 Mon Sep 17 00:00:00 2001 From: idle sign Date: Mon, 5 Dec 2016 23:15:36 +0700 Subject: `strict_mode` removed to improve forward compatibility. --- setuptools/config.py | 10 +--------- setuptools/tests/test_config.py | 12 ------------ 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 6459e1de..b2c0cea3 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -100,12 +100,6 @@ class ConfigHandler(object): """ - strict_mode = True - """Flag. Whether unknown options in config should - raise DistutilsOptionError exception, or pass silently. - - """ - def __init__(self, target_obj, options): sections = {} @@ -316,9 +310,7 @@ class ConfigHandler(object): self[name] = value except KeyError: - if self.strict_mode: - raise DistutilsOptionError( - 'Unknown distribution option: %s' % name) + pass # Keep silent for a new option may appear anytime. def parse(self): """Parses configuration file items from one diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index cd646dba..2e8510be 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -417,18 +417,6 @@ class TestOptions: with get_dist(tmpdir) as dist: assert dist.packages == ['fake_package'] - def test_unknown_options_item(self, tmpdir): - - fake_env( - tmpdir, - '[options]\n' - 'zip_safe = True\n' - 'usr_2to3 = 1\n' - ) - with get_dist(tmpdir, parse=False) as dist: - with pytest.raises(DistutilsOptionError): - dist.parse_config_files() - def test_extras_require(self, tmpdir): fake_env( tmpdir, -- cgit v1.2.3 From 43af23dcff02695ef77b862d2266d10019b7c67c Mon Sep 17 00:00:00 2001 From: idle sign Date: Mon, 5 Dec 2016 23:25:33 +0700 Subject: Docs update. --- docs/setuptools.txt | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 5ce2c7b1..77de255b 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2398,6 +2398,191 @@ The ``upload_docs`` command has the following options: https://pypi.python.org/pypi (i.e., the main PyPI installation). +----------------------------------------- +Configuring setup() using setup.cfg files +----------------------------------------- + +``Setuptools`` allows using configuration files (usually `setup.cfg`) +to define package’s metadata and other options which are normally supplied +to ``setup()`` function. + +This approach not only allows automation scenarios, but also reduces +boilerplate code in some cases. + +.. note:: + Implementation presents limited compatibility with distutils2-like + ``setup.cfg`` sections (used by ``pbr`` and ``d2to1`` packages). + + Namely: only metadata related keys from ``metadata`` section are supported + (except for ``description-file``); keys from ``files``, ``entry_points`` + and ``backwards_compat`` are not supported. + + +.. code-block:: ini + + [metadata] + name = my_package + version = attr: src.VERSION + description = My package description + long_description = file: README.rst + keywords = one, two + license = BSD 3-Clause License + + [metadata.classifiers] + Framework :: Django + Programming Language :: Python :: 3.5 + + [options] + zip_safe = False + include_package_data = True + packages = find: + scripts = + bin/first.py + bin/second.py + + [options.package_data] + * = *.txt, *.rst + hello = *.msg + + [options.extras_require] + pdf = ReportLab>=1.2; RXP + rest = docutils>=0.3; pack ==1.1, ==1.3 + + +Metadata and options could be set in sections with the same names. + +* Keys are the same as keyword arguments one provides to ``setup()`` function. + +* Complex values could be placed comma-separated or one per line + in *dangling* sections. The following are the same: + + .. code-block:: ini + + [metadata] + keywords = one, two + + [metadata] + keywords = + one + two + +* In some cases complex values could be provided in subsections for clarity. + +* Some keys allow ``file:``, ``attr:`` and ``find:`` directives to cover + common usecases. + +* Unknown keys are ignored. + + +Specifying values +================= + +Some values are treated as simple strings, some allow more logic. + +Type names used below: + +* ``str`` - simple string +* ``list-comma`` - dangling list or comma-separated values string +* ``list-semi`` - dangling list or semicolon-separated values string +* ``bool`` - ``True`` is 1, yes, true +* ``dict`` - list-comma where keys from values are separated by = + + +Special directives: + +* ``attr:`` - value could be read from module attribute +* ``file:`` - value could be read from a file +* ``section:`` - values could be read from a dedicated (sub)section + + +.. note:: + ``file:`` directive is sandboxed and won't reach anything outside + directory with ``setup.py``. + + +Metadata +-------- + +.. note:: + Aliases given below are supported for compatibility reasons, + but not advised. + +================= ================= ===== +Key Aliases Accepted value type +================= ================= ===== +name str +version attr:, str +url home-page str +download_url download-url str +author str +author_email author-email str +maintainer str +maintainer_email maintainer-email str +classifiers classifier file:, section, list-comma +license file:, str +description summary file:, str +long_description long-description file:, str +keywords list-comma +platforms platform list-comma +provides list-comma +requires list-comma +obsoletes list-comma +================= ================= ===== + +**version** - ``attr:`` supports callables; supports iterables; +unsupported types are casted using ``str()``. + + +Options +------- + +======================= ===== +Key Accepted value type +======================= ===== +zip_safe bool +setup_requires list-semi +install_requires list-semi +extras_require section +entry_points file, section +use_2to3 bool +use_2to3_fixers list-comma +use_2to3_exclude_fixers list-comma +convert_2to3_doctests list-comma +scripts list-comma +eager_resources list-comma +dependency_links list-comma +tests_require list-semi +include_package_data bool +packages find:, list-comma +package_dir dict +package_data section +exclude_package_data section +namespace_packages list-comma +======================= ===== + + +Configuration API +================= + +Some automation tools may wish to access data from a configuration file. + +``Setuptools`` exposes ``read_configuration()`` function allowing +parsing ``metadata`` and ``options`` sections into a dictionary. + + +.. code-block:: python + + from setuptools.config import read_configuration + + conf_dict = read_configuration('/home/user/dev/package/setup.cfg') + + +By default ``read_configuration()`` will read only file provided +in the first argument. To include values from other configuration files +which could be in various places set `find_others` function argument +to ``True``. + + -------------------------------- Extending and Reusing Setuptools -------------------------------- -- cgit v1.2.3 From 1ca6f3bf272d8ba2c0d4161cc56a74c63c8afb82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 7 Dec 2016 14:59:34 +0200 Subject: Spelling fixes --- setuptools/command/build_py.py | 2 +- setuptools/tests/environment.py | 2 +- setuptools/tests/test_egg_info.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 289e6fb8..b0314fd4 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -219,7 +219,7 @@ class build_py(orig.build_py, Mixin2to3): @staticmethod def _get_platform_patterns(spec, package, src_dir): """ - yield platfrom-specific path patterns (suitable for glob + yield platform-specific path patterns (suitable for glob or fn_match) from a glob-based spec (such as self.package_data or self.exclude_package_data) matching package in src_dir. diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py index b0e3bd36..c67898ca 100644 --- a/setuptools/tests/environment.py +++ b/setuptools/tests/environment.py @@ -56,5 +56,5 @@ def run_setup_py(cmd, pypath=None, path=None, data = data.decode() data = unicodedata.normalize('NFC', data) - # communciate calls wait() + # communicate calls wait() return proc.returncode, data diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index dc41bc1f..75ae18df 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -237,7 +237,7 @@ class TestEggInfo(object): pkginfo = os.path.join(egg_info_dir, 'PKG-INFO') assert 'Requires-Python: >=1.2.3' in open(pkginfo).read().split('\n') - def test_manifest_maker_warning_suppresion(self): + def test_manifest_maker_warning_suppression(self): fixtures = [ "standard file not found: should have one of foo.py, bar.py", "standard file 'setup.py' not found" -- cgit v1.2.3 From a9350f32d3eeef3a1c53b243e763e60e211b72f6 Mon Sep 17 00:00:00 2001 From: idle sign Date: Wed, 7 Dec 2016 20:21:31 +0700 Subject: `read_configuration` now chdirs and tests for file. --- setuptools/config.py | 13 ++++++++++++- setuptools/tests/test_config.py | 6 +++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index b2c0cea3..889dc683 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -5,7 +5,7 @@ import sys from collections import defaultdict from functools import partial -from distutils.errors import DistutilsOptionError +from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.py26compat import import_module from setuptools.extern.six import string_types @@ -23,6 +23,15 @@ def read_configuration(filepath, find_others=False): """ from setuptools.dist import Distribution, _Distribution + filepath = os.path.abspath(filepath) + + if not os.path.isfile(filepath): + raise DistutilsFileError( + 'Configuration file %s does not exist.' % filepath) + + current_directory = os.getcwd() + os.chdir(os.path.dirname(filepath)) + dist = Distribution() filenames = dist.find_config_files() if find_others else [] @@ -33,6 +42,8 @@ def read_configuration(filepath, find_others=False): handlers = parse_configuration(dist, dist.command_options) + os.chdir(current_directory) + return configuration_to_dict(handlers) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 2e8510be..21487720 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -1,6 +1,6 @@ import contextlib import pytest -from distutils.errors import DistutilsOptionError +from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.dist import Distribution from setuptools.config import ConfigHandler, read_configuration @@ -69,6 +69,10 @@ class TestConfigurationReader: assert config_dict['metadata']['keywords'] == ['one', 'two'] assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py'] + def test_no_config(self, tmpdir): + with pytest.raises(DistutilsFileError): + read_configuration('%s' % tmpdir.join('setup.cfg')) + class TestMetadata: -- cgit v1.2.3 From 53b47e1dfa9dfb1e8b94172a4650409fc03d4048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 7 Dec 2016 15:00:43 +0200 Subject: Tell unittest.main not to exit, fixes #850. --- setuptools/command/test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setuptools/command/test.py b/setuptools/command/test.py index 270674e2..9a5117be 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -225,10 +225,12 @@ class test(Command): del_modules.append(name) list(map(sys.modules.__delitem__, del_modules)) + exit_kwarg = {} if sys.version_info < (2, 7) else {"exit": False} unittest_main( None, None, self._argv, testLoader=self._resolve_as_ep(self.test_loader), testRunner=self._resolve_as_ep(self.test_runner), + **exit_kwarg ) @property -- cgit v1.2.3 From e1c0fa4e8358ce25ff9c3678c4f8261339f1896f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Dec 2016 09:53:50 -0500 Subject: Update changelog. Ref #873. --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d100bdcf..e442e286 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v30.2.1 +------- + +* #850: In test command, invoke unittest.main with + indication not to exit the process. + v30.2.0 ------- -- cgit v1.2.3 From 17f752d3be0b2f89b405976364653f081f522fb3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Dec 2016 09:54:03 -0500 Subject: =?UTF-8?q?Bump=20version:=2030.2.0=20=E2=86=92=2030.2.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index b234d0bd..f101a990 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 30.2.0 +current_version = 30.2.1 commit = True tag = True diff --git a/setup.py b/setup.py index e088928c..a12d8984 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="30.2.0", + version="30.2.1", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 9c81a2f62b8d160d2a6c3b2d6595b849fdf60c52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Dec 2016 10:02:50 -0500 Subject: Update changelog; ref #862. --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e442e286..248a52c6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v30.3.0 +------- + +* #394 via #862: Added support for `declarative package + config in a setup.cfg file + `_. + v30.2.1 ------- -- cgit v1.2.3 From ac9997648d89131412eacbb198e2d3a7c97f69e4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 8 Dec 2016 10:05:07 -0500 Subject: =?UTF-8?q?Bump=20version:=2030.2.1=20=E2=86=92=2030.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index f101a990..950924b1 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 30.2.1 +current_version = 30.3.0 commit = True tag = True diff --git a/setup.py b/setup.py index a12d8984..da2c6473 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="30.2.1", + version="30.3.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 56dea7f0334f60603d4ca6a884ca523fe3389ef3 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 12:06:26 +0700 Subject: `read_configuration()` now accepts `ignore_option_errors`. --- docs/setuptools.txt | 8 +++++++- setuptools/config.py | 40 +++++++++++++++++++++++++++++++++------- setuptools/tests/test_config.py | 16 ++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 77de255b..1721edaf 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2543,7 +2543,7 @@ zip_safe bool setup_requires list-semi install_requires list-semi extras_require section -entry_points file, section +entry_points file:, section use_2to3 bool use_2to3_fixers list-comma use_2to3_exclude_fixers list-comma @@ -2582,6 +2582,12 @@ in the first argument. To include values from other configuration files which could be in various places set `find_others` function argument to ``True``. +If you have only a configuration file but not the whole package you can still +try to get data out of it with the help of `ignore_option_errors` function +argument. When it is set to ``True`` all options with errors possibly produced +by directives, such as ``attr:`` and others will be silently ignored. +As a consequence the resulting dictionary will include no such options. + -------------------------------- Extending and Reusing Setuptools diff --git a/setuptools/config.py b/setuptools/config.py index 889dc683..007d24e2 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -10,7 +10,8 @@ from setuptools.py26compat import import_module from setuptools.extern.six import string_types -def read_configuration(filepath, find_others=False): +def read_configuration( + filepath, find_others=False, ignore_option_errors=False): """Read given configuration file and returns options from it as a dict. :param str|unicode filepath: Path to configuration file @@ -19,6 +20,11 @@ def read_configuration(filepath, find_others=False): :param bool find_others: Whether to search for other configuration files which could be on in various places. + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + :rtype: dict """ from setuptools.dist import Distribution, _Distribution @@ -40,7 +46,9 @@ def read_configuration(filepath, find_others=False): _Distribution.parse_config_files(dist, filenames=filenames) - handlers = parse_configuration(dist, dist.command_options) + handlers = parse_configuration( + dist, dist.command_options, + ignore_option_errors=ignore_option_errors) os.chdir(current_directory) @@ -76,7 +84,8 @@ def configuration_to_dict(handlers): return config_dict -def parse_configuration(distribution, command_options): +def parse_configuration( + distribution, command_options, ignore_option_errors=False): """Performs additional parsing of configuration options for a distribution. @@ -84,12 +93,18 @@ def parse_configuration(distribution, command_options): :param Distribution distribution: :param dict command_options: + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. :rtype: list """ - meta = ConfigMetadataHandler(distribution.metadata, command_options) + meta = ConfigMetadataHandler( + distribution.metadata, command_options, ignore_option_errors) meta.parse() - options = ConfigOptionsHandler(distribution, command_options) + options = ConfigOptionsHandler( + distribution, command_options, ignore_option_errors) options.parse() return [meta, options] @@ -111,7 +126,7 @@ class ConfigHandler(object): """ - def __init__(self, target_obj, options): + def __init__(self, target_obj, options, ignore_option_errors=False): sections = {} section_prefix = self.section_prefix @@ -122,6 +137,7 @@ class ConfigHandler(object): section_name = section_name.replace(section_prefix, '').strip('.') sections[section_name] = section_options + self.ignore_option_errors = ignore_option_errors self.target_obj = target_obj self.sections = sections self.set_options = [] @@ -148,9 +164,19 @@ class ConfigHandler(object): # Already inhabited. Skipping. return + skip_option = False parser = self.parsers.get(option_name) if parser: - value = parser(value) + try: + value = parser(value) + + except Exception: + skip_option = True + if not self.ignore_option_errors: + raise + + if skip_option: + return setter = getattr(target_obj, 'set_%s' % option_name, None) if setter is None: diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 21487720..aaf78aef 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -73,6 +73,22 @@ class TestConfigurationReader: with pytest.raises(DistutilsFileError): read_configuration('%s' % tmpdir.join('setup.cfg')) + def test_ignore_errors(self, tmpdir): + fake_env( + tmpdir, + '[metadata]\n' + 'version = attr: none.VERSION\n' + 'keywords = one, two\n' + ) + with pytest.raises(ImportError): + read_configuration('%s' % tmpdir.join('setup.cfg')) + + config_dict = read_configuration( + '%s' % tmpdir.join('setup.cfg'), ignore_option_errors=True) + + assert config_dict['metadata']['keywords'] == ['one', 'two'] + assert 'version' not in config_dict['metadata'] + class TestMetadata: -- cgit v1.2.3 From b73891f82d5f1a353a2ad0090b1f5edece921508 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 12:30:24 +0700 Subject: config tests refactored. --- setuptools/tests/test_config.py | 43 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index aaf78aef..35bdbad1 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -9,6 +9,13 @@ class ErrConfigHandler(ConfigHandler): """Erroneous handler. Fails to implement required methods.""" +def make_package_dir(name, base_dir): + dir_package = base_dir.mkdir(name) + init_file = dir_package.join('__init__.py') + init_file.write('') + return dir_package, init_file + + def fake_env(tmpdir, setup_cfg, setup_py=None): if setup_py is None: @@ -18,11 +25,12 @@ def fake_env(tmpdir, setup_cfg, setup_py=None): ) tmpdir.join('setup.py').write(setup_py) - tmpdir.join('setup.cfg').write(setup_cfg) + config = tmpdir.join('setup.cfg') + config.write(setup_cfg) + + package_dir, init_file = make_package_dir('fake_package', tmpdir) - package_name = 'fake_package' - dir_package = tmpdir.mkdir(package_name) - dir_package.join('__init__.py').write( + init_file.write( 'VERSION = (1, 2, 3)\n' '\n' 'VERSION_MAJOR = 1' @@ -31,6 +39,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None): ' return [3, 4, 5, "dev"]\n' '\n' ) + return package_dir, config @contextlib.contextmanager @@ -55,7 +64,7 @@ def test_parsers_implemented(): class TestConfigurationReader: def test_basic(self, tmpdir): - fake_env( + _, config = fake_env( tmpdir, '[metadata]\n' 'version = 10.1.1\n' @@ -64,7 +73,7 @@ class TestConfigurationReader: '[options]\n' 'scripts = bin/a.py, bin/b.py\n' ) - config_dict = read_configuration('%s' % tmpdir.join('setup.cfg')) + config_dict = read_configuration('%s' % config) assert config_dict['metadata']['version'] == '10.1.1' assert config_dict['metadata']['keywords'] == ['one', 'two'] assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py'] @@ -74,17 +83,17 @@ class TestConfigurationReader: read_configuration('%s' % tmpdir.join('setup.cfg')) def test_ignore_errors(self, tmpdir): - fake_env( + _, config = fake_env( tmpdir, '[metadata]\n' 'version = attr: none.VERSION\n' 'keywords = one, two\n' ) with pytest.raises(ImportError): - read_configuration('%s' % tmpdir.join('setup.cfg')) + read_configuration('%s' % config) config_dict = read_configuration( - '%s' % tmpdir.join('setup.cfg'), ignore_option_errors=True) + '%s' % config, ignore_option_errors=True) assert config_dict['metadata']['keywords'] == ['one', 'two'] assert 'version' not in config_dict['metadata'] @@ -188,7 +197,7 @@ class TestMetadata: def test_version(self, tmpdir): - fake_env( + _, config = fake_env( tmpdir, '[metadata]\n' 'version = attr: fake_package.VERSION\n' @@ -196,14 +205,14 @@ class TestMetadata: with get_dist(tmpdir) as dist: assert dist.metadata.version == '1.2.3' - tmpdir.join('setup.cfg').write( + config.write( '[metadata]\n' 'version = attr: fake_package.get_version\n' ) with get_dist(tmpdir) as dist: assert dist.metadata.version == '3.4.5.dev' - tmpdir.join('setup.cfg').write( + config.write( '[metadata]\n' 'version = attr: fake_package.VERSION_MAJOR\n' ) @@ -214,7 +223,7 @@ class TestMetadata: subpack.join('__init__.py').write('') subpack.join('submodule.py').write('VERSION = (2016, 11, 26)') - tmpdir.join('setup.cfg').write( + config.write( '[metadata]\n' 'version = attr: fake_package.subpackage.submodule.VERSION\n' ) @@ -250,7 +259,7 @@ class TestMetadata: ]) # From file. - fake_env( + _, config = fake_env( tmpdir, '[metadata]\n' 'classifiers = file: classifiers\n' @@ -265,7 +274,7 @@ class TestMetadata: assert set(dist.metadata.classifiers) == expected # From section. - tmpdir.join('setup.cfg').write( + config.write( '[metadata.classifiers]\n' 'Framework :: Django\n' 'Programming Language :: Python :: 3.5\n' @@ -454,7 +463,7 @@ class TestOptions: } def test_entry_points(self, tmpdir): - fake_env( + _, config = fake_env( tmpdir, '[options.entry_points]\n' 'group1 = point1 = pack.module:func, ' @@ -479,7 +488,7 @@ class TestOptions: tmpdir.join('entry_points').write(expected) # From file. - tmpdir.join('setup.cfg').write( + config.write( '[options]\n' 'entry_points = file: entry_points\n' ) -- cgit v1.2.3 From a262947e39e6125ee15d3752a2124acf0c62bda6 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 13:33:57 +0700 Subject: Implemented find() configuration support for `packages`. --- docs/setuptools.txt | 20 +++++++++++++++++--- setuptools/config.py | 33 +++++++++++++++++++++++++++++++-- setuptools/tests/test_config.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 1721edaf..2f78b133 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2448,6 +2448,11 @@ boilerplate code in some cases. pdf = ReportLab>=1.2; RXP rest = docutils>=0.3; pack ==1.1, ==1.3 + [options.packages.find] + exclude = + src.subpackage1 + src.subpackage2 + Metadata and options could be set in sections with the same names. @@ -2486,13 +2491,13 @@ Type names used below: * ``list-semi`` - dangling list or semicolon-separated values string * ``bool`` - ``True`` is 1, yes, true * ``dict`` - list-comma where keys from values are separated by = +* ``section`` - values could be read from a dedicated (sub)section Special directives: * ``attr:`` - value could be read from module attribute * ``file:`` - value could be read from a file -* ``section:`` - values could be read from a dedicated (sub)section .. note:: @@ -2529,8 +2534,10 @@ requires list-comma obsoletes list-comma ================= ================= ===== -**version** - ``attr:`` supports callables; supports iterables; -unsupported types are casted using ``str()``. +.. note:: + + **version** - ``attr:`` supports callables; supports iterables; + unsupported types are casted using ``str()``. Options @@ -2560,6 +2567,13 @@ exclude_package_data section namespace_packages list-comma ======================= ===== +.. note:: + + **packages** - ``find:`` directive can be further configured + in a dedicated subsection `options.packages.find`. This subsection + accepts the same keys as `setuptools.find` function: + `where`, `include`, `exclude`. + Configuration API ================= diff --git a/setuptools/config.py b/setuptools/config.py index 007d24e2..743575f0 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -361,7 +361,10 @@ class ConfigHandler(object): method_postfix = '_%s' % section_name section_parser_method = getattr( - self, 'parse_section%s' % method_postfix, None) + self, + # Dots in section names are tranlsated into dunderscores. + ('parse_section%s' % method_postfix).replace('.', '__'), + None) if section_parser_method is None: raise DistutilsOptionError( @@ -481,8 +484,34 @@ class ConfigOptionsHandler(ConfigHandler): if not value.startswith(find_directive): return self._parse_list(value) + # Read function arguments from a dedicated section. + find_kwargs = self.parse_section_packages__find( + self.sections.get('packages.find', {})) + from setuptools import find_packages - return find_packages() + + return find_packages(**find_kwargs) + + def parse_section_packages__find(self, section_options): + """Parses `packages.find` configuration file section. + + To be used in conjunction with _parse_packages(). + + :param dict section_options: + """ + section_data = self._parse_section_to_dict( + section_options, self._parse_list) + + valid_keys = ['where', 'include', 'exclude'] + + find_kwargs = dict( + [(k, v) for k, v in section_data.items() if k in valid_keys and v]) + + where = find_kwargs.get('where') + if where is not None: + find_kwargs['where'] = where[0] # cast list to single val + + return find_kwargs def parse_section_entry_points(self, section_options): """Parses `entry_points` configuration file section. diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 35bdbad1..08e398b3 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -446,6 +446,44 @@ class TestOptions: with get_dist(tmpdir) as dist: assert dist.packages == ['fake_package'] + def test_find_directive(self, tmpdir): + dir_package, config = fake_env( + tmpdir, + '[options]\n' + 'packages = find:\n' + ) + + dir_sub_one, _ = make_package_dir('sub_one', dir_package) + dir_sub_two, _ = make_package_dir('sub_two', dir_package) + + with get_dist(tmpdir) as dist: + assert dist.packages == [ + 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'] + + config.write( + '[options]\n' + 'packages = find:\n' + '\n' + '[options.packages.find]\n' + 'where = .\n' + 'include =\n' + ' fake_package.sub_one\n' + ' two\n' + ) + with get_dist(tmpdir) as dist: + assert dist.packages == ['fake_package.sub_one'] + + config.write( + '[options]\n' + 'packages = find:\n' + '\n' + '[options.packages.find]\n' + 'exclude =\n' + ' fake_package.sub_one\n' + ) + with get_dist(tmpdir) as dist: + assert dist.packages == ['fake_package', 'fake_package.sub_two'] + def test_extras_require(self, tmpdir): fake_env( tmpdir, -- cgit v1.2.3 From f85a821b039a03eb0231e6bd0fc925a4e37f3911 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 13:48:03 +0700 Subject: Fixed test for `find()` results. --- setuptools/tests/test_config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 08e398b3..677ccf2c 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -457,8 +457,9 @@ class TestOptions: dir_sub_two, _ = make_package_dir('sub_two', dir_package) with get_dist(tmpdir) as dist: - assert dist.packages == [ - 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'] + assert set(dist.packages) == set([ + 'fake_package', 'fake_package.sub_two', 'fake_package.sub_one' + ]) config.write( '[options]\n' @@ -482,7 +483,8 @@ class TestOptions: ' fake_package.sub_one\n' ) with get_dist(tmpdir) as dist: - assert dist.packages == ['fake_package', 'fake_package.sub_two'] + assert set(dist.packages) == set( + ['fake_package', 'fake_package.sub_two']) def test_extras_require(self, tmpdir): fake_env( -- cgit v1.2.3 From 35f3d1f37fdb137d5f5316058d49c96b9f28ca2d Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 15:23:49 +0700 Subject: `test_ignore_errors` side effect mitigated. --- setuptools/tests/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 677ccf2c..fa8d523b 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -98,6 +98,8 @@ class TestConfigurationReader: assert config_dict['metadata']['keywords'] == ['one', 'two'] assert 'version' not in config_dict['metadata'] + config.remove() + class TestMetadata: -- cgit v1.2.3 From c471788dbccf4fcf669d141e6f1325c1b43b8b94 Mon Sep 17 00:00:00 2001 From: idle sign Date: Sat, 10 Dec 2016 22:24:01 +0700 Subject: Proper finalization for `read_configuration()`. --- setuptools/config.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/setuptools/config.py b/setuptools/config.py index 743575f0..d71ff028 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -38,19 +38,21 @@ def read_configuration( current_directory = os.getcwd() os.chdir(os.path.dirname(filepath)) - dist = Distribution() + try: + dist = Distribution() - filenames = dist.find_config_files() if find_others else [] - if filepath not in filenames: - filenames.append(filepath) + filenames = dist.find_config_files() if find_others else [] + if filepath not in filenames: + filenames.append(filepath) - _Distribution.parse_config_files(dist, filenames=filenames) + _Distribution.parse_config_files(dist, filenames=filenames) - handlers = parse_configuration( - dist, dist.command_options, - ignore_option_errors=ignore_option_errors) + handlers = parse_configuration( + dist, dist.command_options, + ignore_option_errors=ignore_option_errors) - os.chdir(current_directory) + finally: + os.chdir(current_directory) return configuration_to_dict(handlers) -- cgit v1.2.3 From e9f06d225b9a58807e4fc6c9f3219a6c7b460d17 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Dec 2016 11:34:19 -0500 Subject: Update changelog. Ref #879. --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 248a52c6..a61ea855 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v30.4.0 +------- + +* #879: For declarative config: + - read_configuration() now accepts ignore_option_errors argument. This allows scraping tools to read metadata without a need to download entire packages. E.g. we can gather some stats right from GitHub repos just by downloading setup.cfg. + - packages find: directive now supports fine tuning from a subsection. The same arguments as for find() are accepted. + v30.3.0 ------- -- cgit v1.2.3 From e48430161fd59169cbe38c372d79a9c16e041d39 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Dec 2016 11:34:29 -0500 Subject: =?UTF-8?q?Bump=20version:=2030.3.0=20=E2=86=92=2030.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 950924b1..08e51b5b 100755 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 30.3.0 +current_version = 30.4.0 commit = True tag = True diff --git a/setup.py b/setup.py index da2c6473..d6dc048f 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def pypi_link(pkg_filename): setup_params = dict( name="setuptools", - version="30.3.0", + version="30.4.0", description="Easily download, build, install, upgrade, and uninstall " "Python packages", author="Python Packaging Authority", -- cgit v1.2.3 From 1a273f3d9d3a110361a6db6db1afaf704a031acb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Dec 2016 11:40:04 -0500 Subject: Reformat changelog for better RST formatting --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a61ea855..1efdb498 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,9 @@ v30.4.0 ------- * #879: For declarative config: + - read_configuration() now accepts ignore_option_errors argument. This allows scraping tools to read metadata without a need to download entire packages. E.g. we can gather some stats right from GitHub repos just by downloading setup.cfg. + - packages find: directive now supports fine tuning from a subsection. The same arguments as for find() are accepted. v30.3.0 -- cgit v1.2.3 From 5c9406987aacf17d9c004aa1456797e0044306e5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Dec 2016 14:20:23 -0500 Subject: Use py.test to launch pytest, rather than python -m test, preventing the empty path from being added to sys.path per pytest-dev/pytest#2104. Fixes #852. Also use 'usedevelop' as the setuptools.tests.fixtures aren't available in a standard install. --- conftest.py | 17 ----------------- tox.ini | 3 ++- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/conftest.py b/conftest.py index 0da92be9..3cccfe1a 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,3 @@ -import os - - pytest_plugins = 'setuptools.tests.fixtures' @@ -9,17 +6,3 @@ def pytest_addoption(parser): "--package_name", action="append", default=[], help="list of package_name to pass to test functions", ) - - -def pytest_configure(): - _issue_852_workaround() - - -def _issue_852_workaround(): - """ - Patch 'setuptools.__file__' with an absolute path - for forward compatibility with Python 3. - Workaround for https://github.com/pypa/setuptools/issues/852 - """ - setuptools = __import__('setuptools') - setuptools.__file__ = os.path.abspath(setuptools.__file__) diff --git a/tox.ini b/tox.ini index cfb682ee..cae9c745 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ [testenv] deps=-rtests/requirements.txt passenv=APPDATA USERPROFILE HOMEDRIVE HOMEPATH windir APPVEYOR -commands=python -m pytest {posargs:-rsx} +commands=py.test {posargs:-rsx} +usedevelop=True -- cgit v1.2.3