summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Himmelstein <daniel.himmelstein@gmail.com>2019-05-22 17:45:44 -0400
committerBenoit Pierre <benoit.pierre@gmail.com>2019-07-16 13:20:36 +0200
commit8f848bd777278fc8dcb42dc45751cd8b95ec2a02 (patch)
treed78ada4b85b44d48a3bda0a2a43b4261c4847e89
parent305bb1cefc3251c67b55149139a768ddf474f7b6 (diff)
downloadpython-setuptools-git-8f848bd777278fc8dcb42dc45751cd8b95ec2a02.tar.gz
improve `package_data` check
Ensure the dictionary values are lists/tuples of strings. Fix #1459.
-rw-r--r--changelog.d/1769.change.rst1
-rw-r--r--setuptools/dist.py29
-rw-r--r--setuptools/tests/test_dist.py53
3 files changed, 68 insertions, 15 deletions
diff --git a/changelog.d/1769.change.rst b/changelog.d/1769.change.rst
new file mode 100644
index 00000000..d48a23b6
--- /dev/null
+++ b/changelog.d/1769.change.rst
@@ -0,0 +1 @@
+Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings.
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 1e1b83be..f0f030b5 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -215,6 +215,10 @@ def check_importable(dist, attr, value):
def assert_string_list(dist, attr, value):
"""Verify that value is a string list"""
try:
+ # verify that value is a list or tuple to exclude unordered
+ # or single-use iterables
+ assert isinstance(value, (list, tuple))
+ # verify that elements of value are strings
assert ''.join(value) != value
except (TypeError, ValueError, AttributeError, AssertionError):
raise DistutilsSetupError(
@@ -307,20 +311,17 @@ def check_test_suite(dist, attr, value):
def check_package_data(dist, attr, value):
"""Verify that value is a dictionary of package names to glob lists"""
- if isinstance(value, dict):
- for k, v in value.items():
- if not isinstance(k, str):
- break
- try:
- iter(v)
- except TypeError:
- break
- else:
- return
- raise DistutilsSetupError(
- attr + " must be a dictionary mapping package names to lists of "
- "wildcard patterns"
- )
+ if not isinstance(value, dict):
+ raise DistutilsSetupError(
+ "{!r} must be a dictionary mapping package names to lists of "
+ "string wildcard patterns".format(attr))
+ for k, v in value.items():
+ if not isinstance(k, six.string_types):
+ raise DistutilsSetupError(
+ "keys of {!r} dict must be strings (got {!r})"
+ .format(attr, k)
+ )
+ assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
def check_packages(dist, attr, value):
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index 390c3dfc..c771a19a 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -3,7 +3,13 @@
from __future__ import unicode_literals
import io
-from setuptools.dist import DistDeprecationWarning, _get_unpatched
+import re
+from distutils.errors import DistutilsSetupError
+from setuptools.dist import (
+ _get_unpatched,
+ check_package_data,
+ DistDeprecationWarning,
+)
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
@@ -263,3 +269,48 @@ def test_maintainer_author(name, attrs, tmpdir):
else:
line = '%s: %s' % (fkey, val)
assert line in pkg_lines_set
+
+
+CHECK_PACKAGE_DATA_TESTS = (
+ # Valid.
+ ({
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ }, None),
+ # Not a dictionary.
+ ((
+ ('', ['*.txt', '*.rst']),
+ ('hello', ['*.msg']),
+ ), (
+ "'package_data' must be a dictionary mapping package"
+ " names to lists of string wildcard patterns"
+ )),
+ # Invalid key type.
+ ({
+ 400: ['*.txt', '*.rst'],
+ }, (
+ "keys of 'package_data' dict must be strings (got 400)"
+ )),
+ # Invalid value type.
+ ({
+ 'hello': str('*.msg'),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')"
+ )),
+ # Invalid value type (generators are single use)
+ ({
+ 'hello': (x for x in "generator"),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings "
+ "(got <generator object"
+ )),
+)
+
+
+@pytest.mark.parametrize('package_data, expected_message', CHECK_PACKAGE_DATA_TESTS)
+def test_check_package_data(package_data, expected_message):
+ if expected_message is None:
+ assert check_package_data(None, 'package_data', package_data) is None
+ else:
+ with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)):
+ check_package_data(None, str('package_data'), package_data)