Skip to content

Commit 6ca4b3e

Browse files
committed
Merge branch 'main' into typed-metadata
2 parents dcd7bd5 + 96cc328 commit 6ca4b3e

File tree

5 files changed

+77
-29
lines changed

5 files changed

+77
-29
lines changed

CHANGES.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ v3.2.0
66
with declared support for the ``.get_all()`` method.
77
Fixes #126.
88

9+
v3.1.1
10+
======
11+
12+
v2.1.1
13+
======
14+
15+
* #261: Restored compatibility for package discovery for
16+
metadata without version in the name and for legacy
17+
eggs.
18+
919
v3.1.0
1020
======
1121

importlib_metadata/__init__.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import io
21
import os
32
import re
43
import abc
@@ -124,11 +123,7 @@ def _from_text(cls, text):
124123
config = ConfigParser(delimiters='=')
125124
# case sensitive: https://stackoverflow.com/q/1611799/812183
126125
config.optionxform = str
127-
try:
128-
config.read_string(text)
129-
except AttributeError: # pragma: nocover
130-
# Python 2 has no read_string
131-
config.readfp(io.StringIO(text))
126+
config.read_string(text)
132127
return EntryPoint._from_config(config)
133128

134129
def __iter__(self):
@@ -469,47 +464,29 @@ def zip_children(self):
469464

470465
return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)
471466

472-
def is_egg(self, search):
473-
base = self.base
467+
def search(self, name):
474468
return (
475-
base == search.versionless_egg_name
476-
or base.startswith(search.prefix)
477-
and base.endswith('.egg')
469+
self.joinpath(child)
470+
for child in self.children()
471+
if name.matches(child, self.base)
478472
)
479473

480-
def search(self, name):
481-
for child in self.children():
482-
n_low = child.lower()
483-
if (
484-
n_low in name.exact_matches
485-
or n_low.replace('.', '_').startswith(name.prefix)
486-
and n_low.endswith(name.suffixes)
487-
# legacy case:
488-
or self.is_egg(name)
489-
and n_low == 'egg-info'
490-
):
491-
yield self.joinpath(child)
492-
493474

494475
class Prepared:
495476
"""
496477
A prepared search for metadata on a possibly-named package.
497478
"""
498479

499-
normalized = ''
500-
prefix = ''
480+
normalized = None
501481
suffixes = '.dist-info', '.egg-info'
502482
exact_matches = [''][:0]
503-
versionless_egg_name = ''
504483

505484
def __init__(self, name):
506485
self.name = name
507486
if name is None:
508487
return
509488
self.normalized = self.normalize(name)
510-
self.prefix = self.normalized + '-'
511489
self.exact_matches = [self.normalized + suffix for suffix in self.suffixes]
512-
self.versionless_egg_name = self.normalized + '.egg'
513490

514491
@staticmethod
515492
def normalize(name):
@@ -518,6 +495,37 @@ def normalize(name):
518495
"""
519496
return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_')
520497

498+
@staticmethod
499+
def legacy_normalize(name):
500+
"""
501+
Normalize the package name as found in the convention in
502+
older packaging tools versions and specs.
503+
"""
504+
return name.lower().replace('-', '_')
505+
506+
def matches(self, cand, base):
507+
low = cand.lower()
508+
pre, ext = os.path.splitext(low)
509+
name, sep, rest = pre.partition('-')
510+
return (
511+
low in self.exact_matches
512+
or ext in self.suffixes
513+
and (not self.normalized or name.replace('.', '_') == self.normalized)
514+
# legacy case:
515+
or self.is_egg(base)
516+
and low == 'egg-info'
517+
)
518+
519+
def is_egg(self, base):
520+
normalized = self.legacy_normalize(self.name or '')
521+
prefix = normalized + '-' if normalized else ''
522+
versionless_egg_name = normalized + '.egg' if self.name else ''
523+
return (
524+
base == versionless_egg_name
525+
or base.startswith(prefix)
526+
and base.endswith('.egg')
527+
)
528+
521529

522530
@install
523531
class MetadataPathFinder(NullFinder, DistributionFinder):

tests/fixtures.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir):
128128
Version: 1.0.0
129129
""",
130130
},
131+
"pkg.lot.egg-info": {
132+
"METADATA": """
133+
Name: pkg.lot
134+
Version: 1.0.0
135+
""",
136+
},
131137
}
132138

133139
def setUp(self):

tests/test_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ def test_name_normalization(self):
4545
with self.subTest(name):
4646
assert distribution(name).metadata['Name'] == 'pkg.dot'
4747

48+
def test_prefix_not_matched(self):
49+
prefixes = 'p', 'pkg', 'pkg.'
50+
for prefix in prefixes:
51+
with self.subTest(prefix):
52+
with self.assertRaises(PackageNotFoundError):
53+
distribution(prefix)
54+
4855
def test_for_top_level(self):
4956
self.assertEqual(
5057
distribution('egginfo-pkg').read_text('top_level.txt').strip(), 'mod'
@@ -160,6 +167,12 @@ def test_name_normalization(self):
160167
with self.subTest(name):
161168
assert distribution(name).metadata['Name'] == 'pkg.dot'
162169

170+
def test_name_normalization_versionless_egg_info(self):
171+
names = 'pkg.lot', 'pkg_lot', 'pkg-lot', 'pkg..lot', 'Pkg.Lot'
172+
for name in names:
173+
with self.subTest(name):
174+
assert distribution(name).metadata['Name'] == 'pkg.lot'
175+
163176

164177
class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
165178
def test_find_distributions_specified_path(self):

tests/test_main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,17 @@ def test_module(self):
257257
def test_attr(self):
258258
assert self.ep.attr is None
259259

260+
def test_sortable(self):
261+
"""
262+
EntryPoint objects are sortable, but result is undefined.
263+
"""
264+
sorted(
265+
[
266+
EntryPoint('b', 'val', 'group'),
267+
EntryPoint('a', 'val', 'group'),
268+
]
269+
)
270+
260271

261272
class FileSystem(
262273
fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase

0 commit comments

Comments
 (0)