Commit 8f848bd7 authored by Daniel Himmelstein's avatar Daniel Himmelstein Committed by Benoit Pierre

improve `package_data` check

Ensure the dictionary values are lists/tuples of strings.

Fix #1459.
parent 305bb1ce
Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings.
......@@ -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):
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, str):
break
try:
iter(v)
except TypeError:
break
else:
return
if not isinstance(k, six.string_types):
raise DistutilsSetupError(
attr + " must be a dictionary mapping package names to lists of "
"wildcard patterns"
"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):
......
......@@ -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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment