Skip to content

Commit e92f16f

Browse files
cjerdonekpganssle
authored andcommitted
Include file path when Version: missing
Related to pip's github issue pypa/pip#6194. This has come up in pip's issue tracker (github) multiple times: - pypa/pip#6177 - pypa/pip#6283 - pypa/pip#6194
1 parent 1371ea1 commit e92f16f

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

changelog.d/1664.change.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added the path to the ``PKG-INFO`` or ``METADATA`` file in the exception
2+
text when the ``Version:`` header can't be found.

pkg_resources/__init__.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,8 +1403,15 @@ def get_resource_string(self, manager, resource_name):
14031403
def has_resource(self, resource_name):
14041404
return self._has(self._fn(self.module_path, resource_name))
14051405

1406+
def _get_metadata_path(self, name):
1407+
return self._fn(self.egg_info, name)
1408+
14061409
def has_metadata(self, name):
1407-
return self.egg_info and self._has(self._fn(self.egg_info, name))
1410+
if not self.egg_info:
1411+
return self.egg_info
1412+
1413+
path = self._get_metadata_path(name)
1414+
return self._has(path)
14081415

14091416
def get_metadata(self, name):
14101417
if not self.egg_info:
@@ -1868,6 +1875,9 @@ class FileMetadata(EmptyProvider):
18681875
def __init__(self, path):
18691876
self.path = path
18701877

1878+
def _get_metadata_path(self, name):
1879+
return self.path
1880+
18711881
def has_metadata(self, name):
18721882
return name == 'PKG-INFO' and os.path.isfile(self.path)
18731883

@@ -2663,8 +2673,12 @@ def version(self):
26632673
except AttributeError:
26642674
version = self._get_version()
26652675
if version is None:
2666-
tmpl = "Missing 'Version:' header and/or %s file"
2667-
raise ValueError(tmpl % self.PKG_INFO, self)
2676+
path = self._get_metadata_path_for_display(self.PKG_INFO)
2677+
msg = (
2678+
"Missing 'Version:' header and/or {} file at path: {}"
2679+
).format(self.PKG_INFO, path)
2680+
raise ValueError(msg, self)
2681+
26682682
return version
26692683

26702684
@property
@@ -2722,6 +2736,23 @@ def requires(self, extras=()):
27222736
)
27232737
return deps
27242738

2739+
def _get_metadata_path_for_display(self, name):
2740+
"""
2741+
Return the path to the given metadata file, if available.
2742+
"""
2743+
try:
2744+
# We need to access _get_metadata_path() on the provider object
2745+
# directly rather than through this class's __getattr__()
2746+
# since _get_metadata_path() is marked private.
2747+
path = self._provider._get_metadata_path(name)
2748+
2749+
# Handle exceptions e.g. in case the distribution's metadata
2750+
# provider doesn't support _get_metadata_path().
2751+
except Exception:
2752+
return '[could not detect]'
2753+
2754+
return path
2755+
27252756
def _get_metadata(self, name):
27262757
if self.has_metadata(name):
27272758
for line in self.get_metadata_lines(name):

pkg_resources/tests/test_pkg_resources.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
except ImportError:
1818
import mock
1919

20+
from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
2021
from pkg_resources.extern.six.moves import map
2122
from pkg_resources.extern.six import text_type, string_types
2223

@@ -190,6 +191,89 @@ def test_setuptools_not_imported(self):
190191
subprocess.check_call(cmd)
191192

192193

194+
# TODO: remove this in favor of Path.touch() when Python 2 is dropped.
195+
def touch_file(path):
196+
"""
197+
Create an empty file.
198+
"""
199+
with open(path, 'w'):
200+
pass
201+
202+
203+
def make_distribution_no_version(tmpdir, basename):
204+
"""
205+
Create a distribution directory with no file containing the version.
206+
"""
207+
# Convert the LocalPath object to a string before joining.
208+
dist_dir = os.path.join(str(tmpdir), basename)
209+
os.mkdir(dist_dir)
210+
# Make the directory non-empty so distributions_from_metadata()
211+
# will detect it and yield it.
212+
touch_file(os.path.join(dist_dir, 'temp.txt'))
213+
214+
dists = list(pkg_resources.distributions_from_metadata(dist_dir))
215+
assert len(dists) == 1
216+
dist, = dists
217+
218+
return dist, dist_dir
219+
220+
221+
@pytest.mark.parametrize(
222+
'suffix, expected_filename, expected_dist_type',
223+
[
224+
('egg-info', 'PKG-INFO', EggInfoDistribution),
225+
('dist-info', 'METADATA', DistInfoDistribution),
226+
],
227+
)
228+
def test_distribution_version_missing(tmpdir, suffix, expected_filename,
229+
expected_dist_type):
230+
"""
231+
Test Distribution.version when the "Version" header is missing.
232+
"""
233+
basename = 'foo.{}'.format(suffix)
234+
dist, dist_dir = make_distribution_no_version(tmpdir, basename)
235+
236+
expected_text = (
237+
"Missing 'Version:' header and/or {} file at path: "
238+
).format(expected_filename)
239+
metadata_path = os.path.join(dist_dir, expected_filename)
240+
241+
# Now check the exception raised when the "version" attribute is accessed.
242+
with pytest.raises(ValueError) as excinfo:
243+
dist.version
244+
245+
err = str(excinfo)
246+
# Include a string expression after the assert so the full strings
247+
# will be visible for inspection on failure.
248+
assert expected_text in err, str((expected_text, err))
249+
250+
# Also check the args passed to the ValueError.
251+
msg, dist = excinfo.value.args
252+
assert expected_text in msg
253+
# Check that the message portion contains the path.
254+
assert metadata_path in msg, str((metadata_path, msg))
255+
assert type(dist) == expected_dist_type
256+
257+
258+
def test_distribution_version_missing_undetected_path():
259+
"""
260+
Test Distribution.version when the "Version" header is missing and
261+
the path can't be detected.
262+
"""
263+
# Create a Distribution object with no metadata argument, which results
264+
# in an empty metadata provider.
265+
dist = Distribution('/foo')
266+
with pytest.raises(ValueError) as excinfo:
267+
dist.version
268+
269+
msg, dist = excinfo.value.args
270+
expected = (
271+
"Missing 'Version:' header and/or PKG-INFO file at path: "
272+
'[could not detect]'
273+
)
274+
assert msg == expected
275+
276+
193277
class TestDeepVersionLookupDistutils:
194278
@pytest.fixture
195279
def env(self, tmpdir):

0 commit comments

Comments
 (0)