Skip to content

Commit

Permalink
Merge pull request #345 from python/bugfix/344-test
Browse files Browse the repository at this point in the history
Wrap .files call in always_iterable per recommendation
  • Loading branch information
jaraco authored Aug 26, 2021
2 parents 15df009 + 131c1d2 commit 0bc774f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
v4.7.1
======

* #344: Fixed regression in ``packages_distributions`` when
neither top-level.txt nor a files manifest is present.

v4.7.0
======

Expand Down
4 changes: 2 additions & 2 deletions importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
pypy_partial,
)
from ._functools import method_cache
from ._itertools import unique_everseen
from ._itertools import always_iterable, unique_everseen
from ._meta import PackageMetadata, SimplePath

from contextlib import suppress
Expand Down Expand Up @@ -1025,6 +1025,6 @@ def _top_level_declared(dist):
def _top_level_inferred(dist):
return {
f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
for f in dist.files
for f in always_iterable(dist.files)
if f.suffix == ".py"
}
54 changes: 54 additions & 0 deletions importlib_metadata/_itertools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,57 @@ def unique_everseen(iterable, key=None):
if k not in seen:
seen_add(k)
yield element


# copied from more_itertools 8.8
def always_iterable(obj, base_type=(str, bytes)):
"""If *obj* is iterable, return an iterator over its items::
>>> obj = (1, 2, 3)
>>> list(always_iterable(obj))
[1, 2, 3]
If *obj* is not iterable, return a one-item iterable containing *obj*::
>>> obj = 1
>>> list(always_iterable(obj))
[1]
If *obj* is ``None``, return an empty iterable:
>>> obj = None
>>> list(always_iterable(None))
[]
By default, binary and text strings are not considered iterable::
>>> obj = 'foo'
>>> list(always_iterable(obj))
['foo']
If *base_type* is set, objects for which ``isinstance(obj, base_type)``
returns ``True`` won't be considered iterable.
>>> obj = {'a': 1}
>>> list(always_iterable(obj)) # Iterate over the dict's keys
['a']
>>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit
[{'a': 1}]
Set *base_type* to ``None`` to avoid any special handling and treat objects
Python considers iterable as iterable:
>>> obj = 'foo'
>>> list(always_iterable(obj, base_type=None))
['f', 'o', 'o']
"""
if obj is None:
return iter(())

if (base_type is not None) and isinstance(obj, base_type):
return iter((obj,))

try:
return iter(obj)
except TypeError:
return iter((obj,))
23 changes: 22 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def test_unicode_dir_on_sys_path(self):
list(distributions())


class PackagesDistributionsTest(fixtures.ZipFixtures, unittest.TestCase):
class PackagesDistributionsPrebuiltTest(fixtures.ZipFixtures, unittest.TestCase):
def test_packages_distributions_example(self):
self._fixture_on_path('example-21.12-py3-none-any.whl')
assert packages_distributions()['example'] == ['example']
Expand All @@ -297,3 +297,24 @@ def test_packages_distributions_example2(self):
"""
self._fixture_on_path('example2-1.0.0-py3-none-any.whl')
assert packages_distributions()['example2'] == ['example2']


class PackagesDistributionsTest(
fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase
):
def test_packages_distributions_neither_toplevel_nor_files(self):
"""
Test a package built without 'top-level.txt' or a file list.
"""
fixtures.build_files(
{
'trim_example-1.0.0.dist-info': {
'METADATA': """
Name: trim_example
Version: 1.0.0
""",
}
},
prefix=self.site_dir,
)
packages_distributions()

0 comments on commit 0bc774f

Please sign in to comment.