Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-42382: Make sure each EntryPoint carries it's Distribution information #23334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions Doc/library/importlib.metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,27 @@ Entry points

The ``entry_points()`` function returns a dictionary of all entry points,
keyed by group. Entry points are represented by ``EntryPoint`` instances;
each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
a ``.load()`` method to resolve the value. There are also ``.module``,
``.attr``, and ``.extras`` attributes for getting the components of the
``.value`` attribute::
each ``EntryPoint`` has a ``.name``, ``.group``, ``.value`` and ``.dist``
attributes and a ``.load()`` method to resolve the value. There are also
``.module``, ``.attr``, and ``.extras`` attributes for getting the components of the
``.value`` attribute and ``.distribution`` to get the ``Distribution`` instance
from where the ``EntryPoint`` was loaded::

>>> eps = entry_points() # doctest: +SKIP
>>> list(eps) # doctest: +SKIP
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
>>> scripts = eps['console_scripts'] # doctest: +SKIP
>>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0] # doctest: +SKIP
>>> wheel # doctest: +SKIP
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts', dist='wheel')
>>> wheel.module # doctest: +SKIP
'wheel.cli'
>>> wheel.attr # doctest: +SKIP
'main'
>>> wheel.extras # doctest: +SKIP
[]
>>> wheel.distributuion # doctest: +SKIP
<importlib.metadata.PathDistribution object at 0x7f6b309fc668>
>>> main = wheel.load() # doctest: +SKIP
>>> main # doctest: +SKIP
<function main at 0x103528488>
Expand Down
37 changes: 26 additions & 11 deletions Lib/importlib/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def name(self):


class EntryPoint(
collections.namedtuple('EntryPointBase', 'name value group')):
collections.namedtuple('EntryPointBase', 'name value group dist')):
"""An entry point as defined by Python packaging conventions.

See `the packaging docs on entry points
Expand Down Expand Up @@ -77,6 +77,8 @@ class EntryPoint(
following the attr, and following any extras.
"""

_distribution = None

def load(self):
"""Load the entry point from its definition. If only a module
is indicated by the value, return that module. Otherwise,
Expand All @@ -102,16 +104,24 @@ def extras(self):
match = self.pattern.match(self.value)
return list(re.finditer(r'\w+', match.group('extras') or ''))

@property
def distribution(self):
if self._distribution is None:
self._distribution = Distribution.from_name(self.dist)
return self._distribution

@classmethod
def _from_config(cls, config):
return [
cls(name, value, group)
for group in config.sections()
for name, value in config.items(group)
]
def _from_config(cls, dist, config):
eps = []
for group in config.sections():
for name, value in config.items(group):
ep = cls(name, value, group, dist.name)
ep._distribution = dist
eps.append(ep)
return eps

@classmethod
def _from_text(cls, text):
def _from_text(cls, dist, text):
config = ConfigParser(delimiters='=')
# case sensitive: https://stackoverflow.com/q/1611799/812183
config.optionxform = str
Expand All @@ -120,7 +130,7 @@ def _from_text(cls, text):
except AttributeError: # pragma: nocover
# Python 2 has no read_string
config.readfp(io.StringIO(text))
return EntryPoint._from_config(config)
return EntryPoint._from_config(dist, config)

def __iter__(self):
"""
Expand All @@ -131,7 +141,7 @@ def __iter__(self):
def __reduce__(self):
return (
self.__class__,
(self.name, self.value, self.group),
(self.name, self.value, self.group, self.dist),
)


Expand Down Expand Up @@ -260,14 +270,19 @@ def metadata(self):
)
return email.message_from_string(text)

@property
def name(self):
"""Return the 'Name' metadata for the distribution package."""
return self.metadata['Name']

@property
def version(self):
"""Return the 'Version' metadata for the distribution package."""
return self.metadata['Version']

@property
def entry_points(self):
return EntryPoint._from_text(self.read_text('entry_points.txt'))
return EntryPoint._from_text(self, self.read_text('entry_points.txt'))

@property
def files(self):
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_importlib/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_resolve_without_attr(self):
name='ep',
value='importlib.metadata',
group='grp',
dist='distribution'
)
assert ep.load() is importlib.metadata

Expand Down Expand Up @@ -232,7 +233,7 @@ def test_discovery(self):
class TestEntryPoints(unittest.TestCase):
def __init__(self, *args):
super(TestEntryPoints, self).__init__(*args)
self.ep = importlib.metadata.EntryPoint('name', 'value', 'group')
self.ep = importlib.metadata.EntryPoint('name', 'value', 'group', 'dist')

def test_entry_point_pickleable(self):
revived = pickle.loads(pickle.dumps(self.ep))
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_importlib/test_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ def test_entry_points(self):
self.assertEqual(ep.value, 'mod:main')
self.assertEqual(ep.extras, [])

def test_entry_points_distribution(self):
entries = dict(entry_points()['entries'])
for entry in ("main", "ns:sub"):
ep = entries[entry]
self.assertIsInstance(ep.distribution, Distribution)
dist = Distribution.from_name(ep.dist)
self.assertEqual(ep.distribution.name, dist.name)
self.assertEqual(ep.distribution.version, dist.version)

def test_metadata_for_this_package(self):
md = metadata('egginfo-pkg')
assert md['author'] == 'Steven Ma'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Each `importlib.metadata.EntryPoint` now has a `.dist` attribute which is the
`importlib.metadata.Distribution` name from where the `EntryPoint` was loaded
and a `.distribution` property which is the `importlib.metadata.Distribution`
instance from where the ``EntryPoint`` was loaded.