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.







