Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Allow EntryPoint to be pickled while still allowing to get the di…
…stribution.
  • Loading branch information
s0undt3ch committed Nov 17, 2020
commit 2a128848c7e09145e6fdeeb68ed8a58ae44d08f9
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
Comment thread
jaraco marked this conversation as resolved.
<importlib.metadata.PathDistribution object at 0x7f6b309fc668>
>>> main = wheel.load() # doctest: +SKIP
>>> main # doctest: +SKIP
<function main at 0x103528488>
Expand Down
24 changes: 17 additions & 7 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', 'dist name value group')):
collections.namedtuple('EntryPointBase', 'name value group dist')):
Comment thread
jaraco marked this conversation as resolved.
"""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,13 +104,21 @@ 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

Comment thread
jaraco marked this conversation as resolved.
@classmethod
def _from_config(cls, dist, config):
return [
cls(dist, name, value, group)
for group in config.sections()
for name, value in config.items(group)
]
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
Comment thread
jaraco marked this conversation as resolved.

@classmethod
def _from_text(cls, dist, text):
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
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
6 changes: 4 additions & 2 deletions Lib/test/test_importlib/test_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ def test_entry_points_distribution(self):
entries = dict(entry_points()['entries'])
for entry in ("main", "ns:sub"):
ep = entries[entry]
self.assertEqual(ep.dist.name, "distinfo-pkg")
self.assertEqual(ep.dist.version, "1.0.0")
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')
Expand Down