Python importlib.metadata: Versions and Plugins

Published on: August 27, 2026
Reading time: 9 minutes
Close-up of a USB pen drive being inserted into a laptop USB port on a white surface.

The importlib.metadata module reads metadata from installed Python distributions without importing their packages. It can query versions, names, authors, requirements, installed files, entry points, and relationships between distributions and importable modules. This makes it useful for diagnostic CLIs, plugin systems, support reports, compatibility checks, environment inventories, and observability.

An installed distribution is not the same thing as an importable module. For example, a distribution installed with one package name may expose a different top-level import name. The API works with installation metadata, usually stored in .dist-info or legacy .egg-info directories.

Query an installed version

The version() function returns the version declared by a distribution.

from importlib.metadata import version

print(version("requests"))

Use the distribution name, which may differ from the name used in an import statement.

Handle PackageNotFoundError

When a distribution is not installed, the API raises PackageNotFoundError.

from importlib.metadata import PackageNotFoundError, version

try:
    plugin_version = version("my-plugin")
except PackageNotFoundError:
    plugin_version = None

Distinguish a missing package from invalid metadata, a broken environment, or querying through the wrong interpreter.

Do not import a package only for its version

Many libraries expose __version__, but importing them can be slow or trigger side effects.

importlib.metadata.version() reads installation metadata without executing package code.

Distribution-name normalization

Packaging tools normalize names according to ecosystem rules. Hyphens, underscores, case, and repeated separators may compare as equivalent.

Use the canonical name stored in metadata when presenting reports, and avoid implementing normalization manually.

Read metadata fields

metadata(name) returns a structure similar to email headers.

from importlib.metadata import metadata

meta = metadata("requests")
print(meta["Name"])
print(meta["Version"])
print(meta.get("Summary"))

Not every field is required or consistently populated across all distributions.

Repeated metadata fields

Fields such as classifiers, project URLs, and requirements may appear several times.

Use the returned structure’s multi-value access methods rather than assuming one string contains every value.

Long descriptions

Metadata can include a full project description or README. Avoid writing the complete object to production logs.

Select only the fields needed and enforce reasonable size limits in support reports.

Inspect declared requirements

requires(name) returns requirement strings declared by the distribution.

from importlib.metadata import requires

for requirement in requires("requests") or []:
    print(requirement)

These strings can contain version specifiers, extras, direct references, and environment markers.

Parse requirements with packaging

Do not split requirement strings manually. Use packaging.requirements.Requirement.

from packaging.requirements import Requirement

req = Requirement('urllib3<3,>=1.21.1')
print(req.name, req.specifier)

Environment markers need to be evaluated in the target environment, not necessarily the machine generating the report.

Declared does not mean imported

A requirement may be optional, platform-specific, activated by an extra, or used only by one feature.

Combine metadata with tests and import analysis when you need to know which modules an application actually loads.

List installed files

files(name) lists files registered by a distribution.

from importlib.metadata import files

for file in files("requests") or []:
    print(file)

The result may be None when an installation does not contain a complete file record.

PackagePath objects

Items returned by files() can locate their installed paths.

for item in files("my-package") or []:
    path = item.locate()
    print(path)

Verify that the path exists before opening it. Editable and manually modified installations can point to unexpected locations.

Hashes and file sizes

File records may include a hash and size, depending on how the distribution was installed.

These values help audits and diagnostics, but they are not a complete signature or software-supply-chain verification system.

Use distribution for detailed access

distribution(name) returns a Distribution object.

from importlib.metadata import distribution

dist = distribution("requests")
print(dist.version)
print(dist.metadata["Name"])

The object centralizes access to metadata, requirements, files, and entry points.

Locate an installed file

A Distribution object can locate a relative installation path.

path = dist.locate_file("requests/__init__.py")

Validate the returned location. Editable installs and special packaging layouts may resolve outside ordinary site-packages directories.

Enumerate all distributions

distributions() iterates over distributions visible to the current interpreter.

from importlib.metadata import distributions

for dist in distributions():
    print(dist.metadata.get("Name"), dist.version)

Sort the output and select a stable subset of fields for reproducible inventories.

Build an environment inventory

A useful inventory includes canonical name, version, origin, and optionally selected file hashes.

Do not expose a complete inventory publicly by default. It reveals internal components and can help an attacker identify vulnerable dependencies.

Use the correct virtual environment

The API sees distributions available to the current interpreter.

Run diagnostics with the same sys.executable used by the application. The system Python and project virtual environment can produce completely different inventories.

Editable installations

Editable installs provide metadata, but source code may live in a checkout outside site-packages.

Support reports should distinguish immutable release artifacts from development environments.

Map import packages to distributions

packages_distributions() maps top-level import names to distribution names.

from importlib.metadata import packages_distributions

mapping = packages_distributions()
print(mapping.get("bs4"))

One import package can be provided by more than one distribution, especially with namespace packages.

Module names and distribution names differ

This mapping is useful when relating results from Python modulefinder to installed versions.

Do not guess the distribution name by changing capitalization or replacing underscores.

Discover entry points

Entry points declare installed extensions, commands, and plugins.

from importlib.metadata import entry_points

plugins = entry_points(group="myapp.plugins")
for entry_point in plugins:
    print(entry_point.name, entry_point.value)

Selection APIs evolved across Python versions, so use the interface supported by the project’s declared minimum version.

Entry-point groups

A group creates a logical namespace such as console_scripts or mycompany.myapp.plugins.

Choose a specific group name owned by the project or organization to avoid collisions.

Inspect EntryPoint objects

Each entry point has a name, group, and value. The value usually identifies a module and object.

for ep in plugins:
    print(ep.group, ep.name, ep.value)

You can inspect these fields without importing plugin code.

Load an entry point

EntryPoint.load() imports the declared module and returns its object.

factory = ep.load()
plugin = factory()

This executes import-time code and may fail or produce side effects.

Do not load every plugin automatically

Filter by configuration, authorization, compatibility, distribution, and version before calling load().

Third-party plugins may need process isolation and a reduced privilege model.

Duplicate entry-point names

Two distributions can declare the same name in the same group.

Define an explicit conflict policy: fail, use configured priority, or require selection by distribution. Never rely on environment iteration order.

Entry-point API compatibility

Older Python and backport versions returned different collection shapes and used different selection methods.

Centralize compatibility logic and test every supported Python version.

Console scripts

The console_scripts group declares commands created by packaging tools.

Inspecting these entry points can explain why a CLI was not installed or which callable it should invoke.

Do not treat a console script as a normal plugin callable

A console entry point is designed to run as a command with arguments and an exit status.

For realistic behavior, invoke the installed command or use a subprocess instead of calling arbitrary objects directly.

Compare versions correctly

Use packaging.version.Version, not string comparison.

from packaging.version import Version

if Version(version("my-plugin")) < Version("2.0"):
    raise RuntimeError("plugin is too old")

Lexicographic string comparison handles versions such as 10 and 2 incorrectly.

Environment markers

Requirement markers can depend on Python version, operating system, implementation, architecture, and selected extras.

Evaluate them with the packaging library and the environment where the software will run.

Extras

Extras declare optional dependency groups such as package[postgres].

Metadata shows declared requirements and installed distributions, but it does not always prove which extra the user intended to install.

License metadata

License fields and classifiers help inventories but can be missing, ambiguous, or outdated.

Compliance work should use dedicated tooling and review the actual license texts.

Project URLs

Metadata may include homepage, repository, documentation, and issue-tracker URLs.

Do not automatically trust or visit URLs from an unknown distribution without normal security controls.

Application-level caching

Repeated metadata queries can be cached when the environment is immutable.

Installing or removing packages while the process runs can make an application cache stale. Immutable production images simplify this problem.

Performance

Looking up one version is inexpensive in normal use, but enumerating all distributions and every installed file can be costly.

Generate large inventories at startup, during diagnostics, or on demand rather than on every request.

Do not depend on iteration order

The order of distributions, files, and entry points should not define application behavior.

Sort explicitly and resolve collisions through a documented policy.

Incomplete or damaged metadata

Old, manual, or corrupted installations may lack expected records.

Use clear fallbacks and report the environment as unverifiable when critical fields are absent.

Standard-library modules

Most standard-library modules do not correspond to independently installed distributions queryable by name.

Use Python version and sysconfig paths to identify standard-library components.

Vendored dependencies

A project can copy third-party code into its own package without separate installation metadata.

importlib.metadata does not automatically identify vendored components as distinct distributions.

Containers

Generate inventories from the final runtime image. Multi-stage builds may install packages in a stage that does not match the deployed filesystem.

Validate both the build manifest and the environment actually started in production.

Software bills of materials

Distribution metadata is a valuable input for an SBOM, but a complete SBOM also needs native libraries, operating-system packages, vendored code, and integrity information.

Use established SBOM formats and dedicated tooling for production supply-chain reporting.

Security of diagnostic endpoints

Version information helps operators but also reveals the software stack to attackers.

Protect support endpoints with authentication and authorization, and avoid publishing complete dependency inventories.

Metadata is not proof of trust

Name, author, version, and URLs are declarations from the installed package.

Verify source, hashes, signatures, and package indexes according to the organization’s supply-chain policy.

Plugin isolation

Entry points make discovery convenient, but load() executes code.

Load only approved plugins and consider separate processes for untrusted or high-risk extensions.

Observability

Record versions of a small set of critical components in startup logs or metrics with controlled cardinality.

Do not add every installed distribution as a metric label.

Support reports

A diagnostic command can report Python version, platform, application version, and selected dependencies.

Provide redaction for filesystem paths, internal package names, and system details before users share the report.

Integration with pkgutil

pkgutil discovers modules offered by importers, while importlib.metadata describes installed distributions and declared plugins.

See Python pkgutil.

Integration with importlib.resources

After identifying a plugin or distribution, access package assets through the plugin’s package anchor.

See Python importlib.resources.

Testing

Create fixture distributions or install small test wheels. Cover missing packages, versions, requirements, absent file records, duplicate entry points, editable installs, and namespace packages.

Do not depend on whatever happens to be installed on a developer workstation.

Backport compatibility

For older Python versions, the external importlib_metadata package provides the API and often delivers newer features.

Centralize imports and version differences in one compatibility module.

Common mistakes

Common failures include using an import name instead of a distribution name, importing a package only to get its version, comparing versions as strings, loading every plugin, relying on entry-point order, assuming metadata is complete, enumerating everything per request, and exposing inventories publicly.

Conclusion

importlib.metadata queries versions, requirements, installed files, distributions, and entry points without importing the corresponding package code. Use version() for simple checks, distribution() for details, and entry_points() for declared plugins.

Treat metadata as information rather than proof of trust, map module and distribution names correctly, and load plugins only after validation. Consult the official importlib.metadata documentation and Python modulefinder.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    importlib.resources: Read Packaged Files

    Learn Python importlib.resources to read templates and package data with Traversable, files, and as_file across wheels, ZIPs, and frozen apps.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python runpy: Execute Modules and Scripts

    Learn Python runpy to execute modules and scripts, control __main__, run_path, alter_sys, namespaces, testing, and process isolation.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pkgutil: Discover Packages

    Learn Python pkgutil to list modules, walk packages, discover plugins, inspect importers, and read package resources safely.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Colorful stacked shipping containers at Hamburg port, showcasing global trade and logistics.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python modulefinder: Discover Imports

    Learn Python modulefinder to discover imports, transitive dependencies, missing modules, paths, plugins, and limitations of static analysis.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026
    Detailed view of code and file structure in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python py_compile: Compile One File

    Learn Python py_compile to compile one file, control .pyc output, logical filenames, optimization, hash invalidation, and build errors.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python compileall: Generate .pyc Bytecode

    Learn Python compileall to generate .pyc files, validate syntax, compile in parallel, control optimization, paths, and reproducible builds.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026