2019-05-24 20:59:01 -03:00
.. _using:
2023-05-05 23:40:19 -03:00
========================================================
:mod: `!importlib.metadata` -- Accessing package metadata
========================================================
2019-05-24 20:59:01 -03:00
2021-04-14 21:56:21 -03:00
.. module :: importlib.metadata
2023-05-05 23:40:19 -03:00
:synopsis: Accessing package metadata
2021-04-14 21:56:21 -03:00
2021-04-16 20:13:38 -03:00
.. versionadded :: 3.8
2021-07-12 20:56:40 -03:00
.. versionchanged :: 3.10
`` importlib.metadata `` is no longer provisional.
2021-04-16 20:13:38 -03:00
2021-10-30 12:12:47 -03:00
**Source code:** :source: `Lib/importlib/metadata/__init__.py`
2021-04-14 21:56:21 -03:00
2023-05-05 23:40:19 -03:00
`` importlib.metadata `` is a library that provides access to
2022-10-06 16:25:24 -03:00
the metadata of an installed `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ ,
such as its entry points
or its top-level names (`Import Package <https://packaging.python.org/en/latest/glossary/#term-Import-Package> `_ \s, modules, if any).
Built in part on Python's import system, this library
2019-05-24 20:59:01 -03:00
intends to replace similar functionality in the `entry point
API`_ and `metadata API`_ of `` pkg_resources `` . Along with
2022-06-25 22:04:28 -03:00
:mod: `importlib.resources` ,
this package can eliminate the need to use the older and less efficient
2019-05-24 20:59:01 -03:00
`` pkg_resources `` package.
2023-05-05 23:40:19 -03:00
`` importlib.metadata `` operates on third-party *distribution packages*
2022-10-06 16:25:24 -03:00
installed into Python's `` site-packages `` directory via tools such as
2024-04-15 15:22:00 -03:00
:pypi: `pip` .
2022-10-06 16:25:24 -03:00
Specifically, it works with distributions with discoverable
`` dist-info `` or `` egg-info `` directories,
and metadata defined by the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata> `_ .
.. important ::
These are *not* necessarily equivalent to or correspond 1:1 with
the top-level *import package* names
that can be imported inside Python code.
One *distribution package* can contain multiple *import packages*
(and single modules),
and one top-level *import package*
may map to multiple *distribution packages*
if it is a namespace package.
2023-11-17 19:11:30 -04:00
You can use :ref: `packages_distributions() <package-distributions>`
2022-10-06 16:25:24 -03:00
to get a mapping between them.
By default, distribution metadata can live on the file system
or in zip archives on
2019-12-29 13:26:35 -04:00
:data: `sys.path` . Through an extension mechanism, the metadata can live almost
2019-05-24 20:59:01 -03:00
anywhere.
2022-06-25 22:04:28 -03:00
.. seealso ::
https://importlib-metadata.readthedocs.io/
The documentation for `` importlib_metadata `` , which supplies a
backport of `` importlib.metadata `` .
2022-10-06 16:25:24 -03:00
This includes an `API reference
<https://importlib-metadata.readthedocs.io/en/latest/api.html>`__
for this module's classes and functions,
as well as a `migration guide
<https://importlib-metadata.readthedocs.io/en/latest/migration.html>`__
for existing users of `` pkg_resources `` .
2022-06-25 22:04:28 -03:00
2019-05-24 20:59:01 -03:00
Overview
========
2022-10-06 16:25:24 -03:00
Let's say you wanted to get the version string for a
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ you've installed
2019-05-24 20:59:01 -03:00
using `` pip `` . We start by creating a virtual environment and installing
2019-05-26 11:30:52 -03:00
something into it:
2019-05-24 20:59:01 -03:00
2019-05-26 11:30:52 -03:00
.. code-block :: shell-session
2019-05-24 20:59:01 -03:00
2023-03-14 15:40:12 -03:00
$ python -m venv example
2019-05-24 20:59:01 -03:00
$ source example/bin/activate
2022-04-09 15:37:01 -03:00
(example) $ python -m pip install wheel
2019-05-24 20:59:01 -03:00
2019-05-26 11:30:52 -03:00
You can get the version string for `` wheel `` by running the following:
2019-05-24 20:59:01 -03:00
2019-05-26 11:30:52 -03:00
.. code-block :: pycon
2019-05-24 20:59:01 -03:00
(example) $ python
>>> from importlib.metadata import version # doctest: +SKIP
>>> version('wheel') # doctest: +SKIP
'0.32.3'
2022-06-25 22:04:28 -03:00
You can also get a collection of entry points selectable by properties of the EntryPoint (typically 'group' or 'name'), such as
2019-05-24 20:59:01 -03:00
`` console_scripts `` , `` distutils.commands `` and others. Each group contains a
2022-06-25 22:04:28 -03:00
collection of :ref: `EntryPoint <entry-points>` objects.
2019-05-24 20:59:01 -03:00
You can get the :ref: `metadata for a distribution <metadata>` ::
>>> list(metadata('wheel')) # doctest: +SKIP
['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist']
You can also get a :ref: `distribution's version number <version>` , list its
:ref: `constituent files <files>` , and get a list of the distribution's
:ref: `requirements` .
Functional API
==============
This package provides the following functionality via its public API.
.. _entry-points:
Entry points
------------
2021-03-13 12:31:45 -04:00
The `` entry_points() `` function returns a collection of entry points.
Entry points are represented by `` EntryPoint `` instances;
2019-05-24 20:59:01 -03:00
each `` EntryPoint `` has a `` .name `` , `` .group `` , and `` .value `` attributes and
2020-06-05 17:34:16 -03:00
a `` .load() `` method to resolve the value. There are also `` .module `` ,
`` .attr `` , and `` .extras `` attributes for getting the components of the
2021-03-14 23:20:49 -03:00
`` .value `` attribute.
Query all entry points::
2019-05-24 20:59:01 -03:00
>>> eps = entry_points() # doctest: +SKIP
2021-03-14 23:20:49 -03:00
The `` entry_points() `` function returns an `` EntryPoints `` object,
2022-06-25 22:04:28 -03:00
a collection of all `` EntryPoint `` objects with `` names `` and `` groups ``
2021-03-14 23:20:49 -03:00
attributes for convenience::
2021-03-13 12:31:45 -04:00
>>> sorted(eps.groups) # doctest: +SKIP
2019-05-24 20:59:01 -03:00
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
2021-03-14 23:20:49 -03:00
`` EntryPoints `` has a `` select `` method to select entry points
matching specific properties. Select entry points in the
`` console_scripts `` group::
2021-03-13 12:31:45 -04:00
>>> scripts = eps.select(group='console_scripts') # doctest: +SKIP
2021-03-14 23:20:49 -03:00
Equivalently, since `` entry_points `` passes keyword arguments
through to select::
>>> scripts = entry_points(group='console_scripts') # doctest: +SKIP
Pick out a specific script named "wheel" (found in the wheel project)::
2021-03-13 12:31:45 -04:00
>>> 'wheel' in scripts.names # doctest: +SKIP
True
>>> wheel = scripts['wheel'] # doctest: +SKIP
2021-03-14 23:20:49 -03:00
Equivalently, query for that entry point during selection::
>>> (wheel,) = entry_points(group='console_scripts', name='wheel') # doctest: +SKIP
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel') # doctest: +SKIP
Inspect the resolved entry point::
2019-05-24 20:59:01 -03:00
>>> wheel # doctest: +SKIP
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
2020-06-05 17:34:16 -03:00
>>> wheel.module # doctest: +SKIP
'wheel.cli'
>>> wheel.attr # doctest: +SKIP
'main'
>>> wheel.extras # doctest: +SKIP
[]
2019-05-24 20:59:01 -03:00
>>> main = wheel.load() # doctest: +SKIP
>>> main # doctest: +SKIP
<function main at 0x103528488>
The `` group `` and `` name `` are arbitrary values defined by the package author
and usually a client will wish to resolve all entry points for a particular
group. Read `the setuptools docs
2022-06-21 15:55:18 -03:00
<https://setuptools.pypa.io/en/latest/userguide/entry_point.html> `_
2020-06-05 17:34:16 -03:00
for more information on entry points, their definition, and usage.
2019-05-24 20:59:01 -03:00
2023-12-21 16:04:05 -04:00
.. versionchanged :: 3.12
The "selectable" entry points were introduced in `` importlib_metadata ``
3.6 and Python 3.10. Prior to those changes, `` entry_points `` accepted
no parameters and always returned a dictionary of entry points, keyed
by group. With `` importlib_metadata `` 5.0 and Python 3.12,
`` entry_points `` always returns an `` EntryPoints `` object. See
2024-04-15 15:22:00 -03:00
:pypi: `backports.entry_points_selectable`
2023-12-21 16:04:05 -04:00
for compatibility options.
.. versionchanged :: 3.13
`` EntryPoint `` objects no longer present a tuple-like interface
(:meth: `~object.__getitem__` ).
2019-05-24 20:59:01 -03:00
.. _metadata:
Distribution metadata
---------------------
2022-10-06 16:25:24 -03:00
Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ includes some metadata,
which you can extract using the
2019-05-24 20:59:01 -03:00
`` metadata() `` function::
>>> wheel_metadata = metadata('wheel') # doctest: +SKIP
2020-12-31 13:56:43 -04:00
The keys of the returned data structure, a `` PackageMetadata `` ,
name the metadata keywords, and
the values are returned unparsed from the distribution metadata::
2019-05-24 20:59:01 -03:00
>>> wheel_metadata['Requires-Python'] # doctest: +SKIP
'>=2.7, !=3.0.*, !=3.1.* , !=3.2.*, !=3.3.* '
2021-05-02 18:03:40 -03:00
`` PackageMetadata `` also presents a `` json `` attribute that returns
all the metadata in a JSON-compatible form per :PEP: `566` ::
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.* , !=3.2.*, !=3.3.* '
2022-06-25 22:04:28 -03:00
.. note ::
The actual type of the object returned by `` metadata() `` is an
implementation detail and should be accessed only through the interface
described by the
2022-10-18 10:28:19 -03:00
`PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata> `_ .
2022-06-25 22:04:28 -03:00
2021-05-02 18:03:40 -03:00
.. versionchanged :: 3.10
The `` Description `` is now included in the metadata when presented
through the payload. Line continuation characters have been removed.
The `` json `` attribute was added.
2019-05-24 20:59:01 -03:00
.. _version:
Distribution versions
---------------------
2022-10-06 16:25:24 -03:00
The `` version() `` function is the quickest way to get a
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ 's version
2019-05-24 20:59:01 -03:00
number, as a string::
>>> version('wheel') # doctest: +SKIP
'0.32.3'
.. _files:
Distribution files
------------------
You can also get the full set of files contained within a distribution. The
2022-10-06 16:25:24 -03:00
`` files() `` function takes a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ name
and returns all of the
2019-05-24 20:59:01 -03:00
files installed by this distribution. Each file object returned is a
2021-04-28 20:27:37 -03:00
`` PackagePath `` , a :class: `pathlib.PurePath` derived object with additional `` dist `` ,
2019-05-24 20:59:01 -03:00
`` size `` , and `` hash `` properties as indicated by the metadata. For example::
>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP
>>> util # doctest: +SKIP
PackagePath('wheel/util.py')
>>> util.size # doctest: +SKIP
859
>>> util.dist # doctest: +SKIP
<importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
>>> util.hash # doctest: +SKIP
<FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>
Once you have the file, you can also read its contents::
>>> print(util.read_text()) # doctest: +SKIP
import base64
import sys
...
def as_bytes(s):
if isinstance(s, text_type):
return s.encode('utf-8')
return s
2021-04-28 20:27:37 -03:00
You can also use the `` locate `` method to get a the absolute path to the
file::
>>> util.locate() # doctest: +SKIP
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
2019-09-02 12:08:03 -03:00
In the case where the metadata file listing files
(RECORD or SOURCES.txt) is missing, `` files() `` will
return `` None `` . The caller may wish to wrap calls to
`` files() `` in `always_iterable
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable> `_
or otherwise guard against this condition if the target
distribution is not known to have the metadata present.
2019-05-24 20:59:01 -03:00
.. _requirements:
Distribution requirements
-------------------------
2022-10-06 16:25:24 -03:00
To get the full set of requirements for a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ ,
use the `` requires() ``
2019-09-10 10:53:31 -03:00
function::
2019-05-24 20:59:01 -03:00
2019-09-10 10:53:31 -03:00
>>> requires('wheel') # doctest: +SKIP
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
2019-05-24 20:59:01 -03:00
2022-10-06 16:25:24 -03:00
.. _package-distributions:
.. _import-distribution-package-mapping:
Mapping import to distribution packages
---------------------------------------
2021-03-13 12:31:45 -04:00
2022-10-06 16:25:24 -03:00
A convenience method to resolve the `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_
name (or names, in the case of a namespace package)
that provide each importable top-level
Python module or `Import Package <https://packaging.python.org/en/latest/glossary/#term-Import-Package> `_ ::
2021-03-13 12:31:45 -04:00
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
2023-04-20 23:12:48 -03:00
Some editable installs, `do not supply top-level names
<https://github.com/pypa/packaging-problems/issues/609> `_, and thus this
function is not reliable with such installs.
2021-03-14 23:20:49 -03:00
.. versionadded :: 3.10
2022-03-13 16:53:29 -03:00
.. _distributions:
2021-03-13 12:31:45 -04:00
2019-05-24 20:59:01 -03:00
Distributions
=============
While the above API is the most common and convenient usage, you can get all
of that information from the `` Distribution `` class. A `` Distribution `` is an
2022-10-06 16:25:24 -03:00
abstract object that represents the metadata for
a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ . You can
2019-05-24 20:59:01 -03:00
get the `` Distribution `` instance::
>>> from importlib.metadata import distribution # doctest: +SKIP
>>> dist = distribution('wheel') # doctest: +SKIP
Thus, an alternative way to get the version number is through the
`` Distribution `` instance::
>>> dist.version # doctest: +SKIP
'0.32.3'
There are all kinds of additional metadata available on the `` Distribution ``
instance::
2020-12-31 15:37:53 -04:00
>>> dist.metadata['Requires-Python'] # doctest: +SKIP
2019-05-24 20:59:01 -03:00
'>=2.7, !=3.0.*, !=3.1.* , !=3.2.*, !=3.3.* '
2020-12-31 15:37:53 -04:00
>>> dist.metadata['License'] # doctest: +SKIP
2019-05-24 20:59:01 -03:00
'MIT'
2023-12-21 16:04:05 -04:00
For editable packages, an origin property may present :pep: `610`
metadata::
>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
2022-10-06 16:25:24 -03:00
The full set of available metadata is not described here.
See the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata> `_ for additional details.
2019-05-24 20:59:01 -03:00
2023-12-21 16:04:05 -04:00
.. versionadded :: 3.13
The `` .origin `` property was added.
2019-05-24 20:59:01 -03:00
2022-06-25 22:04:28 -03:00
Distribution Discovery
======================
2022-10-06 16:25:24 -03:00
By default, this package provides built-in support for discovery of metadata
for file system and zip file `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ \s.
This metadata finder search defaults to `` sys.path `` , but varies slightly in how it interprets those values from how other import machinery does. In particular:
2022-06-25 22:04:28 -03:00
- `` importlib.metadata `` does not honor :class: `bytes` objects on `` sys.path `` .
- `` importlib.metadata `` will incidentally honor :py:class: `pathlib.Path` objects on `` sys.path `` even though such values will be ignored for imports.
2019-05-24 20:59:01 -03:00
Extending the search algorithm
==============================
2022-10-06 16:25:24 -03:00
Because `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package> `_ metadata
is not available through :data: `sys.path` searches, or
package loaders directly,
the metadata for a distribution is found through import
2022-10-26 00:26:28 -03:00
system :ref: `finders <finders-and-loaders>` . To find a distribution package's metadata,
2019-12-29 13:26:35 -04:00
`` importlib.metadata `` queries the list of :term: `meta path finders <meta path finder>` on
:data: `sys.meta_path` .
2019-05-24 20:59:01 -03:00
2023-05-05 23:40:19 -03:00
By default `` importlib.metadata `` installs a finder for distribution packages
2022-10-06 16:25:24 -03:00
found on the file system.
This finder doesn't actually find any *distributions* ,
but it can find their metadata.
2019-05-24 20:59:01 -03:00
The abstract class :py:class: `importlib.abc.MetaPathFinder` defines the
interface expected of finders by Python's import system.
`` importlib.metadata `` extends this protocol by looking for an optional
`` find_distributions `` callable on the finders from
2019-12-29 13:26:35 -04:00
:data: `sys.meta_path` and presents this extended interface as the
2019-09-10 10:53:31 -03:00
`` DistributionFinder `` abstract base class, which defines this abstract
method::
2019-05-24 20:59:01 -03:00
2019-09-10 10:53:31 -03:00
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()):
2019-05-24 20:59:01 -03:00
"""Return an iterable of all Distribution instances capable of
2019-09-10 10:53:31 -03:00
loading the metadata for packages for the indicated `` context `` .
2019-05-24 20:59:01 -03:00
"""
2019-09-10 10:53:31 -03:00
The `` DistributionFinder.Context `` object provides `` .path `` and `` .name ``
2020-06-05 17:34:16 -03:00
properties indicating the path to search and name to match and may
2019-09-10 10:53:31 -03:00
supply other relevant context.
2019-05-24 20:59:01 -03:00
What this means in practice is that to support finding distribution package
2019-12-10 21:05:10 -04:00
metadata in locations other than the file system, subclass
`` Distribution `` and implement the abstract methods. Then from
a custom finder, return instances of this derived `` Distribution `` in the
2019-09-10 10:53:31 -03:00
`` find_distributions() `` method.
2019-05-24 20:59:01 -03:00
2024-05-01 14:04:02 -03:00
Example
-------
Consider for example a custom finder that loads Python
modules from a database::
class DatabaseImporter(importlib.abc.MetaPathFinder):
def __init__(self, db):
self.db = db
def find_spec(self, fullname, target=None) -> ModuleSpec:
return self.db.spec_from_name(fullname)
sys.meta_path.append(DatabaseImporter(connect_db(...)))
That importer now presumably provides importable modules from a
database, but it provides no metadata or entry points. For this
custom importer to provide metadata, it would also need to implement
`` DistributionFinder `` ::
from importlib.metadata import DistributionFinder
class DatabaseImporter(DistributionFinder):
...
def find_distributions(self, context=DistributionFinder.Context()):
query = dict(name=context.name) if context.name else {}
for dist_record in self.db.query_distributions(query):
yield DatabaseDistribution(dist_record)
In this way, `` query_distributions `` would return records for
each distribution served by the database matching the query. For
example, if `` requests-1.0 `` is in the database, `` find_distributions ``
would yield a `` DatabaseDistribution `` for `` Context(name='requests') ``
or `` Context(name=None) `` .
For the sake of simplicity, this example ignores `` context.path ` ` \. The
`` path `` attribute defaults to `` sys.path `` and is the set of import paths to
be considered in the search. A `` DatabaseImporter `` could potentially function
without any concern for a search path. Assuming the importer does no
partitioning, the "path" would be irrelevant. In order to illustrate the
purpose of `` path `` , the example would need to illustrate a more complex
`` DatabaseImporter `` whose behavior varied depending on
`` sys.path `` /`` PYTHONPATH `` . In that case, the `` find_distributions `` should
honor the `` context.path `` and only yield `` Distribution ` ` \ s pertinent to that
path.
`` DatabaseDistribution `` , then, would look something like::
class DatabaseDistribution(importlib.metadata.Distributon):
def __init__(self, record):
self.record = record
def read_text(self, filename):
"""
Read a file like "METADATA" for the current distribution.
"""
if filename == "METADATA":
return f"""Name: {self.record.name}
Version: {self.record.version}
"""
if filename == "entry_points.txt":
return "\n".join(
f"""[{ep.group}]\n{ep.name}={ep.value}"""
for ep in self.record.entry_points)
def locate_file(self, path):
raise RuntimeError("This distribution has no file system")
This basic implementation should provide metadata and entry points for
packages served by the `` DatabaseImporter `` , assuming that the
`` record `` supplies suitable `` .name `` , `` .version `` , and
`` .entry_points `` attributes.
The `` DatabaseDistribution `` may also provide other metadata files, like
`` RECORD `` (required for `` Distribution.files `` ) or override the
implementation of `` Distribution.files `` . See the source for more inspiration.
2019-05-24 20:59:01 -03:00
.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api