cheat sheet

importlib-metadata

Package-level reference for importlib-metadata on PyPI — install, distribution lookups, entry points, and the stdlib relationship from Python 3.10+.

#pip#package#languageupdated 05-31-2026

importlib-metadata

What it is

importlib-metadata is a PyPI package that provides a stable API for reading installed-distribution metadata — package versions, entry points, file lists, requirements — from *.dist-info directories that pip writes into site-packages. The same module is in the standard library as importlib.metadata since Python 3.8, with the API stabilizing through 3.10. The PyPI package exists primarily as a backport so libraries can pin a single behaviour across Python versions, and as a place where new features land before being merged into the stdlib.

Reach for importlib-metadata when: you target Python versions older than 3.10 and need newer-than-stdlib features, you want a single import path that behaves identically across CPython releases, or you're writing a plugin host using entry points and want a guarantee-of-newness on the API. For new code on Python 3.10+ that doesn't need backporting, prefer the stdlib from importlib.metadata import ... and skip this package entirely.

Install

bash
pip install importlib-metadata

Output: (none — exits 0 on success)

bash
uv add importlib-metadata

Output: resolved + added to pyproject.toml

bash
poetry add importlib-metadata

Output: updated lockfile + virtualenv install

Pure-Python wheel — no compile step. Adds one runtime dep on Python 3.9 (zipp); zero deps on Python 3.10+ because parts moved to stdlib.

Versioning & Python support

  • Current line is the 7.x / 8.x series in 2025-26.
  • Supports Python 3.8+ on recent releases.
  • The PyPI version tracks ahead of the stdlib version — features land here first, then are merged into the next CPython feature release. A library can pip install importlib-metadata>=6 and assume newer API regardless of the running Python.
  • Pin the major (importlib-metadata>=7,<9) in libraries; applications on Python 3.10+ rarely need this package at all.

Package metadata

  • Maintainer: Jason R. Coombs (the Python Packaging Authority maintainer for many adjacent packages)
  • Project home: github.com/python/importlib_metadata
  • Docs: importlib-metadata.readthedocs.io
  • PyPI: pypi.org/project/importlib-metadata
  • License: Apache-2.0
  • Governance: PyPA / CPython core
  • First released: 2019
  • Downloads: hundreds of millions per month — a near-universal transitive dep

Optional dependencies & extras

importlib-metadata has no PyPI extras for users. Runtime deps depend on Python version:

  • Python 3.9: depends on zipp>=0.5 (for reading metadata out of zipped wheels).
  • Python 3.10+: zero runtime deps; ships as a thin layer over importlib.metadata.

Stdlib relationship

Python 3.10 stabilized the importlib.metadata stdlib API to match this package. Most code can write:

python
from importlib.metadata import version, entry_points, distributions

…and work without any PyPI install on 3.10+. The compatibility pattern for code that needs to target 3.8 / 3.9 too:

python
try:
    from importlib.metadata import version, entry_points
except ImportError:
    from importlib_metadata import version, entry_points  # backport

Recent CPython feature releases (3.11, 3.12, 3.13) regularly pull updates from the PyPI package — entry-point grouping, selectable filters, the Distribution.read_text() semantics. If your code calls one of the newer methods on Python 3.10 specifically, prefer the backport package to side-step the version skew.

Alternatives

PackageTrade-off
importlib.metadata (stdlib, 3.10+)Same API; no extra install. Use when you don't need to target 3.8/3.9 or newer-than-stdlib features.
pkg_resources (setuptools)Legacy; slow startup, large dependency. Avoid in new code.
pkginfoReads metadata from sdists and wheels on disk; different use case (file-on-disk introspection).
metadata (PEP 566 parser)Lower-level; you usually don't want this directly.

Common gotchas

  1. The import name is importlib_metadata (underscore), the PyPI name is importlib-metadata (hyphen). PEP 503 normalization. Easy to mistype in requirements.txt vs Python.
  2. Don't use pkg_resources and importlib.metadata together. pkg_resources has a 100-300ms cold-start cost and reads metadata its own way; mixing the two confuses caching layers.
  3. version("PkgName") is case-insensitive and accepts both PkgName and pkg-name via PEP 503 normalization. Use whichever spelling is canonical for your project (lowercased-with-hyphens).
  4. entry_points() API changed in Python 3.10. Old form returned a dict; new form returns a SelectableGroups object. The backport keeps the new API consistently. If you mix from importlib.metadata on 3.10 with old code expecting a dict, behavior differs.
  5. Distribution.files returns PackagePath objects, not absolute paths. Convert with f.locate() for the real disk path.
  6. PackageNotFoundError is raised, not ModuleNotFoundError. They're different — version() looks up dist info, not importable modules. A namespace package's pieces may have multiple dists.
  7. Editable installs (pip install -e .) still produce *.dist-info so the lookups work; sdist-only installs without metadata fail.
  8. PYTHONDONTWRITEBYTECODE doesn't affect this; metadata reads .dist-info files directly off disk.

Real-world recipes

The recipes below cover the common cases: looking up a version, enumerating entry points for a plugin system, reading metadata, iterating distributions, and the stdlib-fallback pattern.

Recipe 1 — Read the installed version of a package.

python
try:
    from importlib.metadata import version, PackageNotFoundError
except ImportError:
    from importlib_metadata import version, PackageNotFoundError

try:
    v = version("requests")
except PackageNotFoundError:
    v = "unknown"
print(v)

Output: something like 2.32.3 — works on Python 3.8 through 3.13 with the same code.

Recipe 2 — Enumerate entry points (the canonical plugin-discovery API).

python
from importlib.metadata import entry_points

for ep in entry_points(group="console_scripts"):
    print(ep.name, "→", ep.value)

Output: one line per installed CLI script, e.g. pip → pip._internal.cli.main:main.

python
# Project-defined plugin group
for ep in entry_points(group="myapp.plugins"):
    plugin = ep.load()  # imports the target and returns the object
    plugin.register()

Output: each plugin module loaded and registered — the standard pattern for setup.cfg / pyproject.toml-declared plugin systems.

Recipe 3 — Read full metadata for a package.

python
from importlib.metadata import metadata

meta = metadata("requests")
print(meta["Name"], meta["Version"], meta["License"])
print(meta.get_all("Requires-Dist") or [])

Output: name, version, license, and the list of Requires-Dist lines from the wheel's METADATA file.

Recipe 4 — Iterate every installed distribution.

python
from importlib.metadata import distributions

for d in distributions():
    print(d.metadata["Name"], d.version)

Output: one line per installed distribution — useful for "what's in this environment" tooling.

Recipe 5 — Distinguish from the stdlib (Python 3.10+).

python
import sys
if sys.version_info >= (3, 10):
    from importlib.metadata import version
else:
    from importlib_metadata import version  # backport

# Even simpler — let pip's deps decide:
# importlib-metadata is conditionally pinned in pyproject.toml:
#   importlib-metadata; python_version < "3.10"

Output: code stays terse; the conditional dep means production install on 3.10+ doesn't pull the backport.

Production deployment notes

  • Pin conditionally. In pyproject.toml:
    toml
    dependencies = [
      "importlib-metadata>=6; python_version < '3.10'",
    ]
    
    Avoid installing the backport on Python 3.10+ where stdlib already has it.
  • Cache lookups. version() and entry_points() re-scan site-packages each call. For plugin hosts with many calls, cache the results.
  • Beware lazy-loaded entry points in long-lived processes. Newly installed packages mid-process do NOT auto-register; restart or call the *.cache_clear() helpers.
  • Don't ship pkg_resources if you don't need it. Importing pkg_resources is slow (200-500ms cold) and rarely needed once importlib.metadata covers the API surface.

Performance tuning

  • First call to entry_points() scans every *.dist-info in sys.path. Subsequent calls are cached. For CLIs measuring startup, this scan adds milliseconds (small but real).
  • distributions() is lazy. It yields a generator; iterate sparingly.
  • Replace pkg_resources.iter_entry_points with importlib.metadata.entry_points(group=...) for ~10× faster lookup. The Python ecosystem has been migrating off pkg_resources largely for this reason.
  • Use version() not pkg_resources.get_distribution(...).version — half the runtime cost.

Version migration guide

  • < 4.x — older API shapes. Avoid.
  • 4.x → 5.xentry_points() return type stabilized to SelectableGroups.
  • 5.x → 6.x — dropped Python 3.7.
  • 6.x → 7.x — minor; small API tightenings.
  • 7.x → 8.x — packaging modernization, no API change.
python
# Pre-5.0 entry-points (dict-style; deprecated)
from importlib.metadata import entry_points
eps = entry_points()                    # dict in old versions
console = eps["console_scripts"]

# 5.0+ (selectable API)
from importlib.metadata import entry_points
console = entry_points(group="console_scripts")  # filtered selection

Output: the new form is dict-free and faster.

Security considerations

  • Loading entry points executes Python code. ep.load() imports the target module. Don't load entry points from untrusted environments (e.g. user-uploaded wheels).
  • metadata() reads .dist-info/METADATA directly from disk. Don't trust the contents; a malicious package can lie about its name or version in metadata.
  • Distribution name parsing follows PEP 503. Be aware that Foo, foo, and Foo-Bar may all normalize to the same canonical key — typosquatting risk if you treat names as primary identifiers.
  • No remote calls. This package is purely local-file I/O; no network attack surface.

Testing & CI integration

  • For tests that inspect the running environment's metadata, isolate with a pip install -t tmp/ site-packages and sys.path.insert(0, "tmp").
  • Use pytest-virtualenv for full-isolation tests of entry-point loading.
  • Mock with monkeypatch.setattr(importlib.metadata, "version", lambda *_: "1.2.3") in unit tests.
python
import importlib.metadata as m

def test_my_version_callable():
    # Force a specific reported version for the unit under test.
    real = m.version
    m.version = lambda name: "0.0.test"
    try:
        from myapp import version_string
        assert "0.0.test" in version_string()
    finally:
        m.version = real

Output: test passes regardless of the actually-installed version.

Ecosystem integrations

  • setuptools — writes the *.dist-info directories importlib.metadata reads.
  • pip — installs wheels in the format this package reads.
  • pluggy — pytest's plugin manager; can use entry points for plugin discovery.
  • click — uses entry points for CLI registration in some patterns.
  • pip-audit, pipdeptree — both consume importlib.metadata to enumerate installed packages.
  • tox, hatch, pdm — use metadata to introspect environments.

Compatibility matrix

PythonRecommendation
3.7importlib-metadata PyPI required (stdlib doesn't have it).
3.8stdlib works for basics; backport for newer features.
3.9stdlib mostly OK; backport recommended for entry_points(group=...) consistency.
3.10stdlib has the stabilized API; backport not needed unless you want newer-than-stdlib features.
3.11+stdlib is canonical; install the backport only as a transitive dep.

Troubleshooting common errors

Error / SymptomLikely causeFix
PackageNotFoundError: No package metadata was found for 'foo'Package not installed, or pip install -e left no metadataCheck pip show foo; reinstall with pip install.
entry_points()['console_scripts'] raises TypeErrorCalling old dict-style API on 5.0+Use entry_points(group="console_scripts").
Slow CLI startupImporting pkg_resources somewhere in the import chainMigrate to importlib.metadata; cuts 200-500 ms.
version("MyPkg") returns wrong versionMultiple installs in different sys.path entriesInspect distributions() to find duplicates; clean the env.
ImportError: cannot import name 'X' from importlib.metadataUsing a newer API on Python < 3.10Use the backport: from importlib_metadata import X.
FileNotFoundError reading METADATADistribution metadata directory deleted manuallyReinstall the package with pip install --force-reinstall.
EntryPoint.load() raises ModuleNotFoundErrorPlugin declared but its package is uninstalledVerify the plugin package is installed; entry-point declarations can outlive their package.

When NOT to use this

  • You're on Python 3.10+ and don't need newer-than-stdlib features. Use the stdlib directly. import importlib.metadata adds nothing.
  • You need wheel-file introspection without installing. Use pkginfo or parse the wheel directly.
  • You're using pkg_resources for a specific reason (legacy plugins, .egg files). Keep using it — but plan migration.
  • You need fast startup at any cost. Even importlib.metadata does an os.scandir(); for ultra-fast CLIs, cache the answer at build time.

Worked example: a tiny plugin host using entry points

A common real-world use of importlib.metadata is plugin discovery. Two packages — a host that loads plugins, and one or more plugin packages that declare themselves via entry points.

Step 1 — the host package's interface.

python
# myapp/plugins.py
from typing import Protocol

class Plugin(Protocol):
    name: str
    def run(self, context: dict) -> None: ...

Output: a structural-typing contract for plugins — no inheritance required.

Step 2 — discover and load installed plugins.

python
# myapp/host.py
try:
    from importlib.metadata import entry_points
except ImportError:
    from importlib_metadata import entry_points

def load_plugins(group: str = "myapp.plugins") -> dict[str, "Plugin"]:
    found: dict[str, Plugin] = {}
    for ep in entry_points(group=group):
        try:
            obj = ep.load()
        except Exception as e:
            print(f"plugin {ep.name} failed to load: {e}")
            continue
        found[ep.name] = obj
    return found

Output: a name-keyed mapping of plugin objects; load failures don't crash the host.

Step 3 — a plugin package declares itself.

toml
# myapp-greeter/pyproject.toml
[project]
name = "myapp-greeter"
version = "1.0.0"

[project.entry-points."myapp.plugins"]
greeter = "myapp_greeter:plugin"

Output: when pip install myapp-greeter runs, the entry point is recorded in *.dist-info/entry_points.txt.

Step 4 — the plugin's module.

python
# myapp_greeter/__init__.py
class Greeter:
    name = "greeter"
    def run(self, context):
        print(f"Hello, {context.get('user', 'world')}!")

plugin = Greeter()

Output: the entry point points at plugin = Greeter() so the host gets an instance, not a class.

Step 5 — wire it into the host's CLI.

python
# myapp/__main__.py
from myapp.host import load_plugins

plugins = load_plugins()
for name, plugin in plugins.items():
    plugin.run({"user": "Alice Dev"})

Output: running python -m myapp after pip install myapp-greeter prints the plugin's greeting — no host-side code changes required to add new plugins.

FAQ

Q: How do I version-check at startup ("require pip >= 24")? A:

python
from importlib.metadata import version
from packaging.version import Version
if Version(version("pip")) < Version("24"):
    raise SystemExit("pip 24+ required")

Q: Why is my version("MyPackage") returning a 0.0.0 placeholder? A: Almost certainly an editable install (pip install -e .) where setuptools_scm couldn't compute the version. Set SETUPTOOLS_SCM_PRETEND_VERSION=1.2.3 for the build, or fall back to reading __version__ from your package.

Q: Are entry points safe to load from anywhere? A: They execute Python code on .load(). Treat entry points like any third-party plugin system — only the packages installed in the active environment are loaded, and that's still arbitrary code execution at startup.

Q: How do I avoid pulling pkg_resources into a CLI's import path? A: Avoid import pkg_resources anywhere. Use importlib.metadata instead. Tools like pyinstaller and nuitka benefit (smaller binary, faster startup).

Q: Does entry_points() work for packages installed with --user? A: Yes — it scans every sys.path entry. Custom --target installs work too, as long as the directory is on sys.path.

Q: How do I get the install location of a package? A:

python
from importlib.metadata import distribution
d = distribution("requests")
print(d.locate_file(""))

Output: the directory containing the package on disk.

See also