Skip to content

Commit e05adbb

Browse files
committed
Merge branch 'master' into feature/pep517-metadata
2 parents f694cb0 + e098c1f commit e05adbb

File tree

9 files changed

+131
-44
lines changed

9 files changed

+131
-44
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ codecov:
4141
release:
4242
stage: deploy
4343
only:
44-
- /^\d+\.\d+(\.\d+)?([abc]\d*)?$/
44+
- /^v\d+\.\d+(\.\d+)?([abc]\d*)?$/
4545
script:
4646
- tox -e release

importlib_metadata/__init__.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
ModuleNotFoundError,
2828
MetaPathFinder,
2929
email_message_from_string,
30-
ensure_is_path,
30+
PyPy_repr,
3131
)
3232
from importlib import import_module
3333
from itertools import starmap
@@ -54,7 +54,9 @@ class PackageNotFoundError(ModuleNotFoundError):
5454
"""The package was not found."""
5555

5656

57-
class EntryPoint(collections.namedtuple('EntryPointBase', 'name value group')):
57+
class EntryPoint(
58+
PyPy_repr,
59+
collections.namedtuple('EntryPointBase', 'name value group')):
5860
"""An entry point as defined by Python packaging conventions.
5961
6062
See `the packaging docs on entry points
@@ -124,6 +126,12 @@ def __iter__(self):
124126
"""
125127
return iter((self.name, self))
126128

129+
def __reduce__(self):
130+
return (
131+
self.__class__,
132+
(self.name, self.value, self.group),
133+
)
134+
127135

128136
class PackagePath(pathlib.PurePosixPath):
129137
"""A reference to a path in a package"""
@@ -211,7 +219,7 @@ def at(path):
211219
:param path: a string or path-like object
212220
:return: a concrete Distribution instance for the path
213221
"""
214-
return PathDistribution(ensure_is_path(path))
222+
return PathDistribution(pathlib.Path(path))
215223

216224
@staticmethod
217225
def _discover_resolvers():
@@ -362,10 +370,21 @@ class DistributionFinder(MetaPathFinder):
362370
"""
363371

364372
class Context:
373+
"""
374+
Keyword arguments presented by the caller to
375+
``distributions()`` or ``Distribution.discover()``
376+
to narrow the scope of a search for distributions
377+
in all DistributionFinders.
378+
379+
Each DistributionFinder may expect any parameters
380+
and should attempt to honor the canonical
381+
parameters defined below when appropriate.
382+
"""
365383

366384
name = None
367385
"""
368386
Specific name for which a distribution finder should match.
387+
A name of ``None`` matches all distributions.
369388
"""
370389

371390
def __init__(self, **kwargs):
@@ -375,6 +394,9 @@ def __init__(self, **kwargs):
375394
def path(self):
376395
"""
377396
The path that a distribution finder should search.
397+
398+
Typically refers to Python package paths and defaults
399+
to ``sys.path``.
378400
"""
379401
return vars(self).get('path', sys.path)
380402

importlib_metadata/_compat.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,20 @@ def py2_message_from_string(text): # nocoverpy3
115115
PYPY_OPEN_BUG = getattr(sys, 'pypy_version_info', (9, 9, 9))[:3] <= (7, 1, 1)
116116

117117

118-
def ensure_is_path(ob):
119-
"""Construct a Path from ob even if it's already one.
120-
Specialized for Python 3.4.
118+
class PyPy_repr:
121119
"""
122-
if (3,) < sys.version_info < (3, 5):
123-
ob = str(ob) # pragma: nocover
124-
return pathlib.Path(ob)
120+
Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
121+
Ref #97, #102.
122+
"""
123+
affected = hasattr(sys, 'pypy_version_info')
124+
125+
def __compat_repr__(self): # pragma: nocover
126+
def make_param(name):
127+
value = getattr(self, name)
128+
return '{name}={value!r}'.format(**locals())
129+
params = ', '.join(map(make_param, self._fields))
130+
return 'EntryPoint({params})'.format(**locals())
131+
132+
if affected: # pragma: nocover
133+
__repr__ = __compat_repr__
134+
del affected

importlib_metadata/docs/changelog.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,40 @@
22
importlib_metadata NEWS
33
=========================
44

5+
v1.3.0
6+
======
7+
8+
* Improve custom finders documentation. Closes #105.
9+
10+
v1.2.0
11+
======
12+
13+
* Once again, drop support for Python 3.4. Ref #104.
14+
15+
v1.1.3
16+
======
17+
18+
* Restored support for Python 3.4 due to improper version
19+
compatibility declarations in the v1.1.0 and v1.1.1
20+
releases. Closes #104.
21+
22+
v1.1.2
23+
======
24+
25+
* Repaired project metadata to correctly declare the
26+
``python_requires`` directive. Closes #103.
27+
28+
v1.1.1
29+
======
30+
31+
* Fixed ``repr(EntryPoint)`` on PyPy 3 also. Closes #102.
32+
533
v1.1.0
634
======
735

836
* Dropped support for Python 3.4.
37+
* EntryPoints are now pickleable. Closes #96.
38+
* Fixed ``repr(EntryPoint)`` on PyPy 2. Closes #97.
939

1040
v1.0.0
1141
======

importlib_metadata/docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@
166166
# Example configuration for intersphinx: refer to the Python standard library.
167167
intersphinx_mapping = {
168168
'python': ('https://docs.python.org/3', None),
169+
'importlib_resources': (
170+
'https://importlib-resources.readthedocs.io/en/latest/', None
171+
),
169172
}
170173

171174

importlib_metadata/docs/index.rst

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
===============================
44

55
``importlib_metadata`` is a library which provides an API for accessing an
6-
installed package's `metadata`_, such as its entry points or its top-level
6+
installed package's metadata (see :pep:`566`), such as its entry points or its top-level
77
name. This functionality intends to replace most uses of ``pkg_resources``
8-
`entry point API`_ and `metadata API`_. Along with ``importlib.resources`` in
9-
`Python 3.7 and newer`_ (backported as `importlib_resources`_ for older
8+
`entry point API`_ and `metadata API`_. Along with :mod:`importlib.resources` in
9+
Python 3.7 and newer (backported as :doc:`importlib_resources <importlib_resources:index>` for older
1010
versions of Python), this can eliminate the need to use the older and less
1111
efficient ``pkg_resources`` package.
1212

1313
``importlib_metadata`` is a backport of Python 3.8's standard library
14-
`importlib.metadata`_ module for Python 2.7, and 3.4 through 3.7. Users of
14+
:doc:`importlib.metadata <library/importlib.metadata>` module for Python 2.7, and 3.4 through 3.7. Users of
1515
Python 3.8 and beyond are encouraged to use the standard library module.
1616
When imported on Python 3.8 and later, ``importlib_metadata`` replaces the
1717
DistributionFinder behavior from the stdlib, but leaves the API in tact.
@@ -46,9 +46,5 @@ Indices and tables
4646
* :ref:`search`
4747

4848

49-
.. _`metadata`: https://www.python.org/dev/peps/pep-0566/
5049
.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
5150
.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
52-
.. _`Python 3.7 and newer`: https://docs.python.org/3/library/importlib.html#module-importlib.resources
53-
.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
54-
.. _`importlib.metadata`: TBD

importlib_metadata/docs/using.rst

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
.. _using:
22

3-
==========================
4-
Using importlib_metadata
5-
==========================
3+
=================================
4+
Using :mod:`!importlib_metadata`
5+
=================================
66

77
``importlib_metadata`` is a library that provides for access to installed
88
package metadata. Built in part on Python's import system, this library
99
intends to replace similar functionality in the `entry point
1010
API`_ and `metadata API`_ of ``pkg_resources``. Along with
11-
``importlib.resources`` in `Python 3.7
12-
and newer`_ (backported as `importlib_resources`_ for older versions of
11+
:mod:`importlib.resources` in Python 3.7
12+
and newer (backported as :doc:`importlib_resources <importlib_resources:index>` for older versions of
1313
Python), this can eliminate the need to use the older and less efficient
1414
``pkg_resources`` package.
1515

1616
By "installed package" we generally mean a third-party package installed into
1717
Python's ``site-packages`` directory via tools such as `pip
1818
<https://pypi.org/project/pip/>`_. Specifically,
1919
it means a package with either a discoverable ``dist-info`` or ``egg-info``
20-
directory, and metadata defined by `PEP 566`_ or its older specifications.
20+
directory, and metadata defined by :pep:`566` or its older specifications.
2121
By default, package metadata can live on the file system or in zip archives on
22-
``sys.path``. Through an extension mechanism, the metadata can live almost
22+
:data:`sys.path`. Through an extension mechanism, the metadata can live almost
2323
anywhere.
2424

2525

@@ -127,7 +127,7 @@ Distribution files
127127
You can also get the full set of files contained within a distribution. The
128128
``files()`` function takes a distribution package name and returns all of the
129129
files installed by this distribution. Each file object returned is a
130-
``PackagePath``, a `pathlib.Path`_ derived object with additional ``dist``,
130+
``PackagePath``, a :class:`pathlib.Path` derived object with additional ``dist``,
131131
``size``, and ``hash`` properties as indicated by the metadata. For example::
132132

133133
>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]
@@ -196,18 +196,18 @@ instance::
196196
>>> d.metadata['License']
197197
'MIT'
198198

199-
The full set of available metadata is not described here. See `PEP 566
200-
<https://www.python.org/dev/peps/pep-0566/>`_ for additional details.
199+
The full set of available metadata is not described here. See :pep:`566`
200+
for additional details.
201201

202202

203203
Extending the search algorithm
204204
==============================
205205

206-
Because package metadata is not available through ``sys.path`` searches, or
206+
Because package metadata is not available through :data:`sys.path` searches, or
207207
package loaders directly, the metadata for a package is found through import
208208
system `finders`_. To find a distribution package's metadata,
209-
``importlib_metadata`` queries the list of `meta path finders`_ on
210-
`sys.meta_path`_.
209+
``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on
210+
:data:`sys.meta_path`.
211211

212212
By default ``importlib_metadata`` installs a finder for distribution packages
213213
found on the file system. This finder doesn't actually find any *packages*,
@@ -217,7 +217,7 @@ The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
217217
interface expected of finders by Python's import system.
218218
``importlib_metadata`` extends this protocol by looking for an optional
219219
``find_distributions`` callable on the finders from
220-
``sys.meta_path`` and presents this extended interface as the
220+
:data:`sys.meta_path` and presents this extended interface as the
221221
``DistributionFinder`` abstract base class, which defines this abstract
222222
method::
223223

@@ -232,28 +232,21 @@ properties indicating the path to search and names to match and may
232232
supply other relevant context.
233233

234234
What this means in practice is that to support finding distribution package
235-
metadata in locations other than the file system, you should derive from
236-
``Distribution`` and implement the ``load_metadata()`` method. Then from
237-
your finder, return instances of this derived ``Distribution`` in the
235+
metadata in locations other than the file system, subclass
236+
``Distribution`` and implement the abstract methods. Then from
237+
a custom finder, return instances of this derived ``Distribution`` in the
238238
``find_distributions()`` method.
239239

240240

241241
.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
242242
.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
243-
.. _`Python 3.7 and newer`: https://docs.python.org/3/library/importlib.html#module-importlib.resources
244-
.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
245-
.. _`PEP 566`: https://www.python.org/dev/peps/pep-0566/
246243
.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders
247-
.. _`meta path finders`: https://docs.python.org/3/glossary.html#term-meta-path-finder
248-
.. _`sys.meta_path`: https://docs.python.org/3/library/sys.html#sys.meta_path
249-
.. _`pathlib.Path`: https://docs.python.org/3/library/pathlib.html#pathlib.Path
250244

251245

252246
.. rubric:: Footnotes
253247

254248
.. [#f1] Technically, the returned distribution metadata object is an
255-
`email.message.Message
256-
<https://docs.python.org/3/library/email.message.html#email.message.EmailMessage>`_
249+
:class:`email.message.EmailMessage`
257250
instance, but this is an implementation detail, and not part of the
258251
stable API. You should only use dictionary-like methods and syntax
259252
to access the metadata contents.

importlib_metadata/tests/test_main.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from __future__ import unicode_literals
33

44
import re
5+
import json
6+
import pickle
57
import textwrap
68
import unittest
79
import importlib
@@ -189,3 +191,34 @@ def test_egg(self):
189191
with self.add_sys_path(egg):
190192
with self.assertRaises(PackageNotFoundError):
191193
version('foo')
194+
195+
196+
class TestEntryPoints(unittest.TestCase):
197+
def __init__(self, *args):
198+
super(TestEntryPoints, self).__init__(*args)
199+
self.ep = importlib_metadata.EntryPoint('name', 'value', 'group')
200+
201+
def test_entry_point_pickleable(self):
202+
revived = pickle.loads(pickle.dumps(self.ep))
203+
assert revived == self.ep
204+
205+
def test_immutable(self):
206+
"""EntryPoints should be immutable"""
207+
with self.assertRaises(AttributeError):
208+
self.ep.name = 'badactor'
209+
210+
def test_repr(self):
211+
assert 'EntryPoint' in repr(self.ep)
212+
assert 'name=' in repr(self.ep)
213+
assert "'name'" in repr(self.ep)
214+
215+
def test_hashable(self):
216+
"""EntryPoints should be hashable"""
217+
hash(self.ep)
218+
219+
def test_json_dump(self):
220+
"""
221+
json should not expect to be able to dump an EntryPoint
222+
"""
223+
with self.assertRaises(Exception):
224+
json.dumps(self.ep)

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ classifiers =
1515
Programming Language :: Python :: 2
1616

1717
[options]
18-
python_requires = >=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4
18+
python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*
1919
setup_requires = setuptools-scm
2020
install_requires =
2121
zipp>=0.5

0 commit comments

Comments
 (0)