Python importlib.metadata exposes information about installed distribution packages. It can report versions, core metadata, declared dependencies, installed files, entry points, and the relationship between names used by import and names used by packaging tools such as pip.
The distinction matters. A distribution named PyYAML provides the importable module yaml. One distribution may expose several import packages, while a namespace package may be supplied by multiple distributions. Never assume a one-to-one mapping.
Distribution packages versus import packages
A distribution is the unit installed by packaging tools and usually contains .dist-info or .egg-info metadata. An import package is the name referenced in source code.
from importlib.metadata import packages_distributions
mapping = packages_distributions()
print(mapping.get('yaml'))
# commonly ['PyYAML']This mapping is useful in audits and support messages. Some editable installations do not provide complete top-level-name information, so missing entries are possible.
Read an installed version
version() returns a distribution version as text.
from importlib.metadata import version
print(version('pip'))Do not convert the result to float. Python package versions may contain several components, pre-release markers, and local identifiers. Use a PEP 440-compatible parser for comparisons.
Handle PackageNotFoundError
Queries for a distribution that is not installed raise PackageNotFoundError.
from importlib.metadata import PackageNotFoundError, version
try:
current = version('my-plugin')
except PackageNotFoundError:
current = NoneDifferentiate an absent distribution from a broken import. Metadata may exist while an import fails because of a missing native library or invalid configuration.
Read core metadata
metadata() returns a mapping-like object containing Core Metadata fields.
from importlib.metadata import metadata
meta = metadata('pip')
print(meta['Name'])
print(meta['Version'])
print(meta.get('Requires-Python'))
print(meta.get_all('Project-URL'))Fields such as Classifier, Project-URL, and Requires-Dist may appear multiple times. Use get_all() when multiplicity matters.
JSON-compatible metadata
The json property exposes a PEP 566-compatible form.
data = metadata('pip').json
print(data.get('requires_python'))The values still come from the installed package. Validate types and fields before sending them to APIs or external reports.
Inspect declared requirements
requires() returns requirement strings declared by the distribution.
from importlib.metadata import requires
requirements = requires('my-package') or []
for requirement in requirements:
print(requirement)Entries may contain Python-version markers, platform markers, and extras. They describe declared requirements; they do not prove that the current environment satisfies them.
List installed files
files() returns PackagePath objects containing optional size, hash, and distribution information.
from importlib.metadata import files
items = files('pip')
if items is not None:
for item in list(items)[:10]:
print(item, item.size, item.hash)The function may return None when installation metadata does not include a file list. Always guard iteration.
Locate a physical file
PackagePath.locate() resolves an installed path.
for item in files('pip') or []:
if str(item).endswith('__init__.py'):
print(item.locate())
breakAvoid exposing absolute paths without a reason. They may reveal usernames, virtual environments, and server layout.
Use recorded hashes carefully
When the distribution’s RECORD contains a hash and size, PackagePath exposes them. You may compare installed content with the recorded value.
Not every entry has a hash, and local metadata is not an external signature. An attacker able to alter both a file and its RECORD can change both values.
Discover entry points
entry_points() returns EntryPoint objects. Select them by group and optionally by name.
from importlib.metadata import entry_points
plugins = entry_points(group='my_app.plugins')
for plugin in plugins:
print(plugin.name, plugin.value, plugin.dist.name)Groups are conventions defined by package authors. console_scripts is a common example; custom systems should use a clearly namespaced group.
Load an entry point
EntryPoint.load() imports the module and resolves the configured object.
(plugin,) = entry_points(
group='my_app.plugins',
name='csv',
)
plugin_class = plugin.load()Loading triggers imports and may execute side effects. Inspect and validate metadata before loading approved plugins.
Inspect without loading
module, attr, extras, name, group, value, and dist provide useful details without importing the target.
for ep in entry_points(group='console_scripts'):
print(ep.name, ep.module, ep.attr)This is suitable for audits and allowlists. Metadata is still supplied by installed packages and should not be trusted solely because it exists.
Entry-point API changes
Older Python releases returned different structures. Modern entry_points() returns an EntryPoints collection with selection methods. Since Python 3.13, EntryPoint no longer behaves like a tuple.
Libraries supporting older versions should test compatibility or depend on the importlib_metadata backport with a controlled range.
Use a Distribution object
distribution() returns an object exposing version, metadata, files, requirements, and entry points.
from importlib.metadata import distribution
dist = distribution('pip')
print(dist.version)
print(dist.metadata.get('License'))
print(len(dist.entry_points))Separate Distribution instances do not necessarily compare equal even when they describe the same installed package. Compare normalized name and version instead.
Editable-install origins
Since Python 3.13, Distribution.origin may expose PEP 610 origin information for editable packages.
dist = distribution('my-package')
if dist.origin is not None:
print(dist.origin.url)Origins may contain local paths. Normalize or remove sensitive values from shared reports.
Map imports to distributions
packages_distributions() returns lists because namespace packages can be provided by multiple distributions.
mapping = packages_distributions()
for package, distributions_ in sorted(mapping.items()):
if len(distributions_) > 1:
print(package, distributions_)This complements Python modulefinder, which follows imports from an entry script, and Python pkgutil, which lists available modules.
Audit the environment
distributions() iterates installed distributions.
from importlib.metadata import distributions
for dist in sorted(
distributions(),
key=lambda d: d.metadata['Name'].lower(),
):
print(dist.metadata['Name'], dist.version)Large environments may contain editable installs, incomplete metadata, and apparent duplicates. Record the Python version and environment location.
Generate a JSON inventory
import json
from importlib.metadata import distributions
inventory = []
for dist in distributions():
inventory.append({
'name': dist.metadata['Name'],
'version': dist.version,
'requires_python': dist.metadata.get('Requires-Python'),
})
print(json.dumps(inventory, indent=2))Do not include long descriptions or absolute paths by default. A complete SBOM requires additional formats and fields.
Compare minimum versions correctly
Use a proper version implementation rather than string comparison.
from importlib.metadata import version
from packaging.version import Version
if Version(version('my-plugin')) < Version('2.0'):
raise RuntimeError('Upgrade my-plugin')packaging is an external dependency. Lexicographic comparisons such as '10' < '2' are wrong.
Apply trust policy to plugins
Any installed distribution can register an entry point in a group. Verify the provider, version, configuration, and permissions.
APPROVED = {'official-plugin', 'internal-plugin'}
for ep in entry_points(group='my_app.plugins'):
if ep.dist.name not in APPROVED:
continue
load_plugin(ep)In sensitive systems, install plugins from a controlled repository with pinned hashes.
Metadata is not proof of integrity
Name, version, license, and URLs are declarations from the distribution. They do not guarantee authenticity, security, or compatibility. Combine metadata with trusted installation sources, hashes, signatures, and tests.
ZIP distributions and custom providers
By default, metadata may live on the filesystem or in ZIP files on sys.path. Custom importers can implement find_distributions() and return custom Distribution objects.
A provider must honor name and path filters appropriately. Test custom metadata discovery carefully.
Differences in sys.path handling
Metadata discovery does not interpret every sys.path value exactly like normal imports. Byte strings are ignored, while pathlib.Path values may be honored incidentally.
Keep sys.path conventional and use Python sysconfig for explicit installation paths.
Environment changes and caches
Installing or removing packages inside a running process can leave stale observations. In production, construct the environment before workers start and restart them after updates.
Test metadata-dependent code
Avoid relying on every package installed on a developer machine. Abstract access functions and inject test doubles.
def installed_version(name, get_version=version):
try:
return get_version(name)
except PackageNotFoundError:
return NoneTest missing distributions, unusual versions, empty file lists, and unapproved entry points.
Common mistakes
- Confusing distribution names with import names.
- Comparing versions as plain strings.
- Loading every entry point automatically.
- Assuming
files()never returnsNone. - Exposing editable-install paths.
- Treating metadata as integrity proof.
- Depending on the old entry-point API.
Best practices
- Query using distribution names.
- Handle
PackageNotFoundError. - Use
get_all()for repeated fields. - Validate entry points before loading.
- Redact paths and origins in reports.
- Compare versions with a proper parser.
- Restart processes after environment changes.
Conclusion
Python importlib.metadata provides a structured view of installed distributions: versions, requirements, files, metadata, entry points, and mappings to importable packages.
Use this information for diagnostics, plugin systems, and inventories, but do not confuse declarations with trust. Consult the official importlib.metadata documentation and the PyPA Core Metadata specification.







