Read metadata from Python packages, providing third-party access to importlib.metadata functionality
—
Comprehensive entry point discovery and management, including entry point objects, collections, and selection mechanisms for package-defined executable scripts and plugins. Entry points allow packages to advertise functionality that can be discovered and loaded at runtime.
Individual entry point objects representing package-defined entry points:
class EntryPoint:
"""
An entry point as defined by Python packaging conventions.
Entry points allow packages to advertise components for discovery by other packages.
Common uses include console scripts, plugin systems, and extensible applications.
"""
name: str # Entry point name
value: str # Entry point value specification (module:attribute)
group: str # Entry point group (e.g., 'console_scripts')
dist: Distribution | None # Associated distribution (optional)
def __init__(self, name: str, value: str, group: str) -> None:
"""
Create an entry point.
Parameters:
- name: Entry point name
- value: Entry point value (e.g., 'package.module:function')
- group: Entry point group (e.g., 'console_scripts')
"""
def load(self) -> Any:
"""
Load the entry point from its definition.
If only a module is indicated by the value, return that module.
Otherwise, return the named object.
Returns:
Any: The loaded object or module
"""
def matches(self, **params) -> bool:
"""
Check if EntryPoint matches the given parameters.
Parameters:
- **params: Parameters to match against (name, group, module, attr, extras)
Returns:
bool: True if all parameters match
Raises:
ValueError: If 'dist' parameter is provided (not suitable for matching)
"""
@property
def module(self) -> str:
"""Return the module name from the entry point value."""
@property
def attr(self) -> str:
"""Return the attribute name from the entry point value."""
@property
def extras(self) -> list[str]:
"""Return the list of extras from the entry point value."""
# Class attributes
pattern: re.Pattern[str] # Regular expression for parsing entry point values
# Internal methods
def _for(self, dist: Distribution) -> EntryPoint:
"""
Associate this entry point with a distribution.
Parameters:
- dist: Distribution to associate with
Returns:
EntryPoint: This entry point with distribution associated
"""
def _key(self) -> tuple[str, str, str]:
"""
Return comparison key for sorting and equality.
Returns:
tuple[str, str, str]: Tuple of (name, value, group)
"""
# Comparison and representation methods
def __lt__(self, other: EntryPoint) -> bool:
"""Less than comparison for sorting."""
def __eq__(self, other: object) -> bool:
"""Equality comparison."""
def __hash__(self) -> int:
"""Hash for use in sets and dictionaries."""
def __repr__(self) -> str:
"""String representation of entry point."""
def __setattr__(self, name: str, value: Any) -> None:
"""
Prevent attribute modification (EntryPoint objects are immutable).
Raises:
AttributeError: Always, as EntryPoint objects are immutable
"""import importlib_metadata
# Get entry points and work with individual ones
eps = importlib_metadata.entry_points(group='console_scripts')
if eps:
ep = next(iter(eps))
print(f"Entry point: {ep.name}")
print(f"Module: {ep.module}")
print(f"Attribute: {ep.attr}")
print(f"Group: {ep.group}")
print(f"Extras: {ep.extras}")
# Load the actual object
try:
obj = ep.load()
print(f"Loaded: {obj}")
except ImportError as e:
print(f"Failed to load: {e}")
# Check if entry point matches criteria
pip_eps = importlib_metadata.entry_points(name='pip')
for ep in pip_eps:
if ep.matches(group='console_scripts'):
print(f"Found pip console script: {ep.value}")Immutable collection class for working with multiple entry points:
class EntryPoints(tuple):
"""
An immutable collection of selectable EntryPoint objects.
Provides methods for filtering and accessing entry points by various criteria.
"""
def __getitem__(self, name: str) -> EntryPoint:
"""
Get the EntryPoint in self matching name.
Parameters:
- name: Entry point name to find
Returns:
EntryPoint: The matching entry point
Raises:
KeyError: If no entry point with the given name is found
"""
def select(self, **params) -> EntryPoints:
"""
Select entry points from self that match the given parameters.
Parameters:
- **params: Selection criteria (typically group and/or name)
Returns:
EntryPoints: New EntryPoints collection with matching entry points
"""
@property
def names(self) -> set[str]:
"""
Return the set of all names of all entry points.
Returns:
set[str]: Set of entry point names
"""
@property
def groups(self) -> set[str]:
"""
Return the set of all groups of all entry points.
Returns:
set[str]: Set of entry point groups
"""
# Class attributes
__slots__ = () # Memory optimization for tuple subclass
# Class methods for construction
@classmethod
def _from_text_for(cls, text: str | None, dist: Distribution) -> EntryPoints:
"""
Create EntryPoints from text with associated distribution.
Parameters:
- text: Entry points text content (e.g., from entry_points.txt)
- dist: Distribution to associate with entry points
Returns:
EntryPoints: Collection of entry points with distribution associated
"""
@staticmethod
def _from_text(text: str | None) -> Iterator[EntryPoint]:
"""
Parse entry points from text content.
Parameters:
- text: Entry points text content in INI format
Returns:
Iterator[EntryPoint]: Iterator of parsed entry points
"""
# Representation method
def __repr__(self) -> str:
"""
Custom representation showing class name and tuple content.
Returns:
str: String representation like 'EntryPoints([...])'
"""import importlib_metadata
# Get all entry points
all_eps = importlib_metadata.entry_points()
print(f"Total entry points: {len(all_eps)}")
print(f"Available groups: {all_eps.groups}")
print(f"Available names: {len(all_eps.names)} unique names")
# Select by group
console_scripts = all_eps.select(group='console_scripts')
print(f"Console scripts: {len(console_scripts)}")
# Select by name
pip_eps = all_eps.select(name='pip')
print(f"Entry points named 'pip': {len(pip_eps)}")
# Combine selection criteria
specific_eps = all_eps.select(group='console_scripts', name='pip')
print(f"pip console scripts: {len(specific_eps)}")
# Access by name (dictionary-like)
if 'pip' in console_scripts.names:
pip_ep = console_scripts['pip']
print(f"pip script: {pip_ep.value}")Entry points use a specific syntax for the value specification. The EntryPoint class uses a compiled regular expression pattern to parse these values:
# EntryPoint.pattern - Regular expression for parsing entry point values
# Pattern: r'(?P<module>[\w.]+)\s*(:\s*(?P<attr>[\w.]+)\s*)?((?P<extras>\[.*\])\s*)?$'
# Entry point value patterns (examples):
# - "module" # Module only
# - "package.module" # Package and module
# - "package.module:attribute" # Module with attribute
# - "package.module:object.attribute" # Nested attribute
# - "package.module:attr [extra1, extra2]" # With extras
# The pattern captures three groups:
# - module: Required module/package name ([\w.]+)
# - attr: Optional attribute name after colon ([\w.]+)
# - extras: Optional extras specification in brackets (\[.*\])The pattern is lenient about whitespace around the colon, following the attribute, and following any extras. Invalid entry point references raise ValueError with a descriptive message pointing to the packaging specification.
import importlib_metadata
# Create entry point manually (for testing/examples)
ep = importlib_metadata.EntryPoint(
name='my-script',
value='mypackage.cli:main',
group='console_scripts'
)
print(f"Entry point module: {ep.module}") # 'mypackage.cli'
print(f"Entry point attr: {ep.attr}") # 'main'
print(f"Entry point extras: {ep.extras}") # []
# Entry point with extras
ep_with_extras = importlib_metadata.EntryPoint(
name='advanced-script',
value='mypackage.advanced:run [dev, test]',
group='console_scripts'
)
print(f"Extras: {ep_with_extras.extras}") # ['dev', 'test']Entry points are associated with specific distributions and can be accessed through Distribution objects:
import importlib_metadata
# Get entry points for a specific distribution
dist = importlib_metadata.distribution('pip')
dist_eps = dist.entry_points
# Filter distribution entry points
console_scripts = dist_eps.select(group='console_scripts')
for ep in console_scripts:
print(f"pip provides script: {ep.name} -> {ep.value}")
print(f"Associated distribution: {ep.dist.name if ep.dist else 'None'}")Entry point operations can raise various exceptions:
import importlib_metadata
eps = importlib_metadata.entry_points(group='console_scripts')
# KeyError when accessing non-existent entry point
try:
ep = eps['nonexistent-script']
except KeyError:
print("Entry point not found")
# ImportError when loading entry point
try:
ep = next(iter(eps))
obj = ep.load()
except ImportError as e:
print(f"Failed to load entry point: {e}")
# ValueError when matching with 'dist' parameter
try:
ep = next(iter(eps))
ep.matches(dist=dist) # Not allowed
except ValueError:
print("Cannot match on 'dist' parameter")
# ValueError when creating entry point with invalid value
try:
invalid_ep = importlib_metadata.EntryPoint(
name='bad-script',
value='invalid-name', # Invalid module name
group='console_scripts'
)
except ValueError as e:
print(f"Invalid entry point reference: {e}")
# AttributeError when trying to modify entry point (immutable)
try:
ep = next(iter(eps))
ep.name = 'new-name' # Not allowed
except AttributeError:
print("EntryPoint objects are immutable")Install with Tessl CLI
npx tessl i tessl/pypi-importlib-metadata