Claude Code for importlib.metadata: Python Distribution Metadata — Claude Skills 360 Blog
Blog / AI / Claude Code for importlib.metadata: Python Distribution Metadata
AI

Claude Code for importlib.metadata: Python Distribution Metadata

Published: January 1, 2029
Read time: 5 min read
By: Claude Skills 360

Python’s importlib.metadata module (Python 3.8+) reads metadata from installed packages without importing them. import importlib.metadata as ilm. Version: ilm.version("requests")"2.31.0". All metadata: meta = ilm.metadata("requests")email.message.Message; meta["Name"], meta["Author"], meta["Requires-Python"], meta.get_all("Classifier"). Dependencies: ilm.requires("requests")list[str] | None — PEP 508 requirement strings. All packages: for dist in ilm.distributions(): — iterates every installed distribution. Entry points: eps = ilm.entry_points(group="console_scripts") → list of EntryPoint; ep.load() — import and return the callable. Files: ilm.files("pip")list[PackagePath] | None. Package→dist mapping: ilm.packages_distributions()dict[str, list[str]] (e.g. {"PIL": ["Pillow"]}). Error: PackageNotFoundError raised when a package is not installed. Claude Code generates dependency checkers, version validators, plugin registries, audit tools, and runtime environment reporters.

CLAUDE.md for importlib.metadata

## importlib.metadata Stack
- Stdlib: import importlib.metadata as ilm  (Python 3.8+)
- Version: ilm.version("requests")
- Meta:    meta = ilm.metadata("requests"); meta["Author"]
- Deps:    ilm.requires("requests")          # PEP 508 strings
- EPs:     ilm.entry_points(group="console_scripts")
- All:     for dist in ilm.distributions(): ...
- Error:   ilm.PackageNotFoundError

importlib.metadata Distribution Pipeline

# app/metadatautil.py — version check, deps, entry points, audit, env report
from __future__ import annotations

import importlib.metadata as ilm
import re
import sys
from dataclasses import dataclass, field
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Version helpers
# ─────────────────────────────────────────────────────────────────────────────

def pkg_version(name: str) -> "str | None":
    """
    Return the installed version of a package, or None if not installed.

    Example:
        print(pkg_version("requests"))   # "2.31.0"
        print(pkg_version("nope_xyz"))   # None
    """
    try:
        return ilm.version(name)
    except ilm.PackageNotFoundError:
        return None


def is_installed(name: str) -> bool:
    """Return True if the package is installed."""
    return pkg_version(name) is not None


def version_tuple(name: str) -> "tuple[int, ...] | None":
    """
    Return version as a tuple of ints for comparison, or None if not installed.

    Example:
        if version_tuple("requests") >= (2, 28, 0):
            print("new enough")
    """
    v = pkg_version(name)
    if v is None:
        return None
    parts = re.split(r"[.+]", v.split("-")[0])
    ints: list[int] = []
    for p in parts:
        if p.isdigit():
            ints.append(int(p))
        else:
            break
    return tuple(ints) if ints else None


def require_version(name: str, min_version: str) -> None:
    """
    Raise RuntimeError if the installed version of name is below min_version.

    Example:
        require_version("requests", "2.28.0")
    """
    installed = pkg_version(name)
    if installed is None:
        raise RuntimeError(f"Package {name!r} is not installed")
    inst = version_tuple(name) or ()
    req = tuple(int(p) for p in min_version.split(".") if p.isdigit())
    if inst < req:
        raise RuntimeError(
            f"{name} {installed} < required {min_version}"
        )


# ─────────────────────────────────────────────────────────────────────────────
# 2. Metadata access
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class PackageMeta:
    """Key metadata fields for an installed package."""
    name:           str
    version:        str
    summary:        str
    author:         str
    license:        str
    requires_python: str
    classifiers:    list[str] = field(default_factory=list)
    requires:       list[str] = field(default_factory=list)
    home_page:      str = ""


def package_meta(name: str) -> "PackageMeta | None":
    """
    Return PackageMeta for an installed package, or None if not found.

    Example:
        meta = package_meta("pip")
        if meta:
            print(meta.version, meta.summary)
    """
    try:
        dist = ilm.distribution(name)
    except ilm.PackageNotFoundError:
        return None
    m = dist.metadata
    return PackageMeta(
        name=m.get("Name", name),
        version=m.get("Version", ""),
        summary=m.get("Summary", ""),
        author=m.get("Author", "") or m.get("Author-email", ""),
        license=m.get("License", ""),
        requires_python=m.get("Requires-Python", ""),
        classifiers=m.get_all("Classifier") or [],
        requires=ilm.requires(name) or [],
        home_page=m.get("Home-page", "") or m.get("Project-URL", ""),
    )


def all_installed() -> list[PackageMeta]:
    """
    Return PackageMeta for every installed distribution, sorted by name.

    Example:
        pkgs = all_installed()
        print(f"Installed: {len(pkgs)} packages")
    """
    results: list[PackageMeta] = []
    for dist in ilm.distributions():
        m = dist.metadata
        name = m.get("Name", "")
        if not name:
            continue
        results.append(PackageMeta(
            name=name,
            version=m.get("Version", ""),
            summary=m.get("Summary", ""),
            author=m.get("Author", "") or m.get("Author-email", ""),
            license=m.get("License", ""),
            requires_python=m.get("Requires-Python", ""),
        ))
    return sorted(results, key=lambda p: p.name.lower())


def package_for_import(import_name: str) -> "str | None":
    """
    Return the distribution name that provides the given import package name.

    Example:
        package_for_import("PIL")      # "Pillow"
        package_for_import("requests") # "requests"
    """
    mapping = ilm.packages_distributions()
    dists = mapping.get(import_name)
    return dists[0] if dists else None


# ─────────────────────────────────────────────────────────────────────────────
# 3. Entry points
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class EntryPointInfo:
    """Metadata about one entry point."""
    group: str
    name:  str
    value: str   # "module:attr" string
    dist:  str

    def load(self) -> Any:
        """Import and return the object pointed to by this entry point."""
        ep = next(
            e for e in ilm.entry_points(group=self.group) if e.name == self.name
        )
        return ep.load()


def list_entry_points(group: str) -> list[EntryPointInfo]:
    """
    Return all entry points in a group with their distribution names.

    Example:
        for ep in list_entry_points("console_scripts"):
            print(ep.name, "→", ep.value, "(from", ep.dist + ")")
    """
    results: list[EntryPointInfo] = []
    for ep in ilm.entry_points(group=group):
        dist_name = ep.dist.name if ep.dist else ""
        results.append(EntryPointInfo(
            group=group,
            name=ep.name,
            value=ep.value,
            dist=dist_name,
        ))
    return sorted(results, key=lambda e: e.name)


def list_groups() -> list[str]:
    """
    Return all entry point group names across all installed packages.

    Example:
        for group in list_groups():
            print(group)
    """
    return sorted({ep.group for ep in ilm.entry_points()})


# ─────────────────────────────────────────────────────────────────────────────
# 4. Environment audit
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class EnvReport:
    """Summary of the current Python environment."""
    python_version:    str
    prefix:            str
    total_packages:    int
    packages_by_license: dict[str, int]   # license → count
    no_license:        int
    console_scripts:   list[str]


def env_report() -> EnvReport:
    """
    Build a summary report of the current Python environment.

    Example:
        r = env_report()
        print(f"Python {r.python_version}  {r.total_packages} packages")
        for lic, count in sorted(r.packages_by_license.items(), key=lambda x: -x[1])[:5]:
            print(f"  {lic}: {count}")
    """
    pkgs = all_installed()
    by_license: dict[str, int] = {}
    no_lic = 0
    for p in pkgs:
        lic = p.license.strip() or ""
        if not lic:
            no_lic += 1
        else:
            by_license[lic] = by_license.get(lic, 0) + 1

    scripts = [ep.name for ep in list_entry_points("console_scripts")]
    return EnvReport(
        python_version=sys.version.split()[0],
        prefix=sys.prefix,
        total_packages=len(pkgs),
        packages_by_license=by_license,
        no_license=no_lic,
        console_scripts=sorted(scripts),
    )


def missing_packages(names: "list[str]") -> list[str]:
    """
    Return names from the list that are NOT installed.

    Example:
        missing_packages(["requests", "nope_xyz", "pip"])  # ["nope_xyz"]
    """
    return [n for n in names if not is_installed(n)]


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== importlib.metadata demo ===")

    # ── version helpers ───────────────────────────────────────────────────────
    print("\n--- pkg_version / is_installed / version_tuple ---")
    for name in ["pip", "setuptools", "nonexistent_xyz_pkg"]:
        v = pkg_version(name)
        vt = version_tuple(name)
        print(f"  {name:20s}  installed={is_installed(name)}  version={v!r}  tuple={vt}")

    # ── package_meta ──────────────────────────────────────────────────────────
    print("\n--- package_meta ---")
    for name in ["pip", "setuptools"]:
        meta = package_meta(name)
        if meta:
            print(f"  {meta.name} {meta.version}")
            print(f"    summary:  {meta.summary[:60]!r}")
            print(f"    python:   {meta.requires_python!r}")
            print(f"    requires: {len(meta.requires)} deps")

    # ── all_installed ─────────────────────────────────────────────────────────
    print("\n--- all_installed (first 5) ---")
    all_pkgs = all_installed()
    print(f"  total: {len(all_pkgs)} packages")
    for p in all_pkgs[:5]:
        print(f"  {p.name:25s}  {p.version}")

    # ── entry_points ──────────────────────────────────────────────────────────
    print("\n--- list_entry_points (console_scripts, first 5) ---")
    scripts = list_entry_points("console_scripts")
    print(f"  total console_scripts: {len(scripts)}")
    for ep in scripts[:5]:
        print(f"  {ep.name:20s}  {ep.value!r}  ({ep.dist})")

    print("\n--- list_groups (first 8) ---")
    groups = list_groups()
    print(f"  total groups: {len(groups)}")
    for g in groups[:8]:
        print(f"  {g}")

    # ── env_report ────────────────────────────────────────────────────────────
    print("\n--- env_report ---")
    report = env_report()
    print(f"  Python:   {report.python_version}")
    print(f"  Packages: {report.total_packages}")
    print(f"  Scripts:  {len(report.console_scripts)}")
    print(f"  No-license pkgs: {report.no_license}")
    top_licenses = sorted(report.packages_by_license.items(), key=lambda x: -x[1])[:3]
    print(f"  Top licenses: {top_licenses}")

    # ── missing_packages ──────────────────────────────────────────────────────
    print("\n--- missing_packages ---")
    missing = missing_packages(["pip", "setuptools", "nope_xyz_a", "nope_xyz_b"])
    print(f"  missing: {missing}")

    print("\n=== done ===")

For the pkg_resources (setuptools) alternative — pkg_resources.require("requests>=2.0"), pkg_resources.get_distribution("pip").version, and pkg_resources.iter_entry_points("console_scripts") provide similar functionality but import the full setuptools machinery on startup — always prefer importlib.metadata for new code; it is stdlib, faster to import, and covers 100% of pkg_resources use cases for reading metadata (not for building/installing packages). For the packaging (PyPI) alternative — packaging.version.Version("1.2.3") >= packaging.version.Version("1.0.0") provides PEP 440-compliant version comparison that handles pre-releases, post-releases, and local versions correctly — combine importlib.metadata.version() with packaging.version.Version when you need rigorous semver or PEP 440 comparison; the version_tuple() helper in this pipeline handles simple numeric versions only. The Claude Skills 360 bundle includes importlib.metadata skill sets covering pkg_version()/is_installed()/version_tuple()/require_version() version helpers, PackageMeta/package_meta()/all_installed()/package_for_import() metadata access, EntryPointInfo/list_entry_points()/list_groups() entry point tools, and EnvReport/env_report()/missing_packages() audit utilities. Start with the free tier to try distribution metadata patterns and importlib.metadata pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free