Read metadata from Python packages, providing third-party access to importlib.metadata functionality
—
Distribution classes provide object-oriented access to package metadata, including abstract base classes and concrete implementations for different package formats. The Distribution system forms the core of importlib_metadata's architecture.
The base Distribution class defines the interface for accessing package metadata and provides common functionality.
class Distribution(metaclass=abc.ABCMeta):
"""
An abstract Python distribution package.
Custom providers may derive from this class and define the abstract methods
to provide a concrete implementation for their environment.
"""
@abc.abstractmethod
def read_text(self, filename) -> str | None:
"""
Attempt to load metadata file given by the name.
Parameters:
- filename: The name of the file in the distribution info
Returns:
str | None: The text if found, otherwise None
"""
@abc.abstractmethod
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
"""
Given a path to a file in this distribution, return a SimplePath to it.
Parameters:
- path: Path to a file in this distribution
Returns:
SimplePath: Path object for the file
Raises:
NotImplementedError: If provider doesn't support file location
"""
@classmethod
def from_name(cls, name: str) -> Distribution:
"""
Return the Distribution for the given package name.
Parameters:
- name: The name of the distribution package to search for
Returns:
Distribution: The Distribution instance for the named package
Raises:
PackageNotFoundError: When the named package's distribution metadata cannot be found
ValueError: When an invalid value is supplied for name
"""
@classmethod
def discover(cls, *, context: DistributionFinder.Context | None = None, **kwargs) -> Iterable[Distribution]:
"""
Return an iterable of Distribution objects for all packages.
Pass a context or pass keyword arguments for constructing a context.
Parameters:
- context: A DistributionFinder.Context object (optional)
- **kwargs: Keyword arguments for constructing a context
Returns:
Iterable[Distribution]: Iterable of Distribution objects for packages matching the context
Raises:
ValueError: If both context and kwargs are provided
"""
@staticmethod
def at(path: str | os.PathLike[str]) -> Distribution:
"""
Return a Distribution for the indicated metadata path.
Parameters:
- path: A string or path-like object to the metadata directory
Returns:
Distribution: A concrete Distribution instance for the path
"""
@staticmethod
def _discover_resolvers():
"""
Search the meta_path for resolvers (MetadataPathFinders).
Returns:
Iterator: Iterator of find_distributions methods from MetaPathFinders
"""
@staticmethod
def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]:
"""
Prefer (move to the front) distributions that have metadata.
This optimization addresses cases where multiple distributions
may be found but some lack proper metadata.
Parameters:
- dists: Iterable of Distribution instances
Returns:
Iterable[Distribution]: Distributions with valid metadata prioritized
"""Access to distribution metadata through properties:
class Distribution:
@property
def metadata(self) -> PackageMetadata | None:
"""
Return the parsed metadata for this Distribution.
The returned object will have keys that name the various bits of metadata
per the Core metadata specifications.
"""
@property
def name(self) -> str:
"""Return the 'Name' metadata for the distribution package."""
@property
def version(self) -> str:
"""Return the 'Version' metadata for the distribution package."""
@property
def entry_points(self) -> EntryPoints:
"""Return EntryPoints for this distribution."""
@property
def files(self) -> list[PackagePath] | None:
"""
Files in this distribution.
Returns:
list[PackagePath] | None: List of PackagePath for this distribution or None
if the metadata file that enumerates files is missing
"""
@property
def requires(self) -> list[str] | None:
"""Generated requirements specified for this Distribution."""
@property
def origin(self):
"""
Return the origin metadata from direct_url.json.
This provides information about how the package was installed,
including VCS information, local paths, or archive URLs.
Returns:
SimpleNamespace | None: Origin metadata object or None if not available
"""
@property
def _normalized_name(self):
"""
Return a normalized version of the name.
Used internally for consistent name comparisons and lookups.
Returns:
str: Normalized package name
"""import importlib_metadata
# Get distribution and access properties
dist = importlib_metadata.Distribution.from_name('requests')
print(f"Name: {dist.name}")
print(f"Version: {dist.version}")
# Access metadata
metadata = dist.metadata
if metadata:
print(f"Summary: {metadata['Summary']}")
print(f"Author: {metadata['Author']}")
# Get entry points for this distribution
eps = dist.entry_points
console_scripts = eps.select(group='console_scripts')
for ep in console_scripts:
print(f"Script: {ep.name}")
# Get files
files = dist.files
if files:
print(f"Distribution has {len(files)} files")Concrete implementation for filesystem-based distributions:
class PathDistribution(Distribution):
"""Concrete Distribution implementation for filesystem paths."""
def __init__(self, path: SimplePath) -> None:
"""
Construct a distribution.
Parameters:
- path: SimplePath indicating the metadata directory
"""
def read_text(self, filename: str | os.PathLike[str]) -> str | None:
"""Read text from a file in the distribution metadata."""
def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
"""Locate a file relative to the distribution."""import importlib_metadata
from pathlib import Path
# Create distribution from path
metadata_path = Path('/path/to/package-1.0.dist-info')
dist = importlib_metadata.PathDistribution(metadata_path)
# Use like any other distribution
print(f"Package: {dist.name}")
print(f"Version: {dist.version}")The DistributionFinder system enables pluggable package discovery:
class DistributionFinder(MetaPathFinder):
"""
A MetaPathFinder capable of discovering installed distributions.
Custom providers should implement this interface in order to supply metadata.
"""
class Context:
"""
Keyword arguments presented by the caller to distributions() or
Distribution.discover() to narrow the scope of a search for distributions.
"""
name = None # Specific name for which a distribution finder should match
def __init__(self, **kwargs):
"""
Initialize context with keyword arguments.
Parameters:
- **kwargs: Context parameters for filtering distributions
"""
@property
def path(self) -> list[str]:
"""
The sequence of directory paths that a distribution finder should search.
Typically refers to Python installed package paths such as "site-packages"
directories and defaults to sys.path.
Returns:
list[str]: List of directory paths to search
"""
@abc.abstractmethod
def find_distributions(self, context=Context()) -> Iterable[Distribution]:
"""
Find distributions.
Parameters:
- context: DistributionFinder.Context instance for filtering
Returns:
Iterable[Distribution]: Iterable of all Distribution instances capable of
loading the metadata for packages matching the context
"""Concrete implementation of DistributionFinder for filesystem-based package discovery:
class MetadataPathFinder(DistributionFinder):
"""
A degenerate finder for distribution packages on the file system.
This finder supplies only a find_distributions() method for versions
of Python that do not have a PathFinder find_distributions().
"""
@classmethod
def find_distributions(cls, context=DistributionFinder.Context()) -> Iterable[PathDistribution]:
"""
Find distributions.
Return an iterable of all Distribution instances capable of
loading the metadata for packages matching context.name
(or all names if None indicated) along the paths in the list
of directories context.path.
Parameters:
- context: DistributionFinder.Context instance for filtering
Returns:
Iterable[PathDistribution]: PathDistribution instances for found packages
"""
@classmethod
def invalidate_caches(cls) -> None:
"""Invalidate internal caches used for distribution discovery."""import importlib_metadata
# Discover all distributions
all_dists = list(importlib_metadata.Distribution.discover())
print(f"Found {len(all_dists)} distributions")
# Discover with context
context = importlib_metadata.DistributionFinder.Context(name='requests')
matching_dists = list(importlib_metadata.Distribution.discover(context=context))
# Discover with kwargs
specific_dists = list(importlib_metadata.Distribution.discover(name='requests'))Distribution classes raise specific exceptions for various error conditions:
import importlib_metadata
try:
dist = importlib_metadata.Distribution.from_name('nonexistent')
except importlib_metadata.PackageNotFoundError:
print("Package not found")
try:
dist = importlib_metadata.Distribution.from_name('')
except ValueError:
print("Invalid distribution name")Install with Tessl CLI
npx tessl i tessl/pypi-importlib-metadata@8.7.2