Skip to content
Merged
3 changes: 3 additions & 0 deletions docs/manpages/wheel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Commands
``convert``
Convert egg or wininst to wheel

``info``
Show information about a wheel file

``tags``
Change the tags on a wheel file

Expand Down
5 changes: 5 additions & 0 deletions docs/news.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Release Notes
=============

**UNRELEASED**

- Added the ``wheel info`` subcommand to display metadata about wheel files without
unpacking them (`#639 <https://github.com/pypa/wheel/issues/639>`_)

**0.46.3 (2026-01-22)**

- Fixed ``ImportError: cannot import name '_setuptools_logging' from 'wheel'`` when
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Reference Guide
:maxdepth: 2

wheel_convert
wheel_unpack
wheel_info
wheel_pack
wheel_tags
wheel_unpack
67 changes: 67 additions & 0 deletions docs/reference/wheel_info.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
wheel info
==========

Usage
-----

::

wheel info [OPTIONS] <wheel_file>


Description
-----------

Display information about a wheel file without unpacking it.

This command shows comprehensive metadata about a wheel file including:

* Package name, version, and build information
* Wheel format version and generator
* Supported Python versions, ABI, and platform tags
* Package metadata such as summary, author, and license
* Classifiers and dependencies
* File count and total size
* Optional detailed file listing


Options
-------

.. option:: -v, --verbose

Show detailed file listing with individual file sizes.


Examples
--------

Display basic information about a wheel::

$ wheel info example_package-1.0-py3-none-any.whl
Name: example-package
Version: 1.0
Wheel-Version: 1.0
Root-Is-Purelib: true
Tags:
py3-none-any
Generator: bdist_wheel (0.40.0)
Summary: An example package
Author: John Doe
License: MIT
Files: 12
Size: 15,234 bytes

Display detailed information with file listing::

$ wheel info --verbose example_package-1.0-py3-none-any.whl
Name: example-package
Version: 1.0
...

File listing:
example_package/__init__.py 45 bytes
example_package/module.py 1,234 bytes
example_package-1.0.dist-info/METADATA 678 bytes
example_package-1.0.dist-info/WHEEL 123 bytes
example_package-1.0.dist-info/RECORD 456 bytes
28 changes: 28 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,31 @@ To install a wheel file, use pip_::
$ pip install someproject-1.5.0-py2-py3-none.whl

.. _pip: https://pypi.org/project/pip/


Inspecting Wheels
-----------------

To inspect the metadata and contents of a wheel file without installing it,
use the ``wheel info`` command::

$ wheel info someproject-1.5.0-py2-py3-none.whl

This will display information about the wheel including:

* Package name and version
* Supported Python versions and platforms
* Dependencies and other metadata
* File count and total size

For more detailed information including a complete file listing, use the
``--verbose`` flag::

$ wheel info --verbose someproject-1.5.0-py2-py3-none.whl

This is useful for:

* Verifying wheel contents before installation
* Debugging packaging issues
* Understanding wheel structure and metadata
* Checking supported platforms and Python versions
16 changes: 16 additions & 0 deletions src/wheel/_commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ def tags_f(args: argparse.Namespace) -> None:
print(name)


def info_f(args: argparse.Namespace) -> None:
from .info import info

try:
info(args.wheelfile, args.verbose)
except FileNotFoundError as e:
raise WheelError(str(e)) from e


def version_f(args: argparse.Namespace) -> None:
from .. import __version__

Expand Down Expand Up @@ -129,6 +138,13 @@ def parser() -> argparse.ArgumentParser:
)
tags_parser.set_defaults(func=tags_f)

info_parser = s.add_parser("info", help="Show information about a wheel file")
info_parser.add_argument("wheelfile", help="Wheel file to show information for")
info_parser.add_argument(
"--verbose", "-v", action="store_true", help="Show detailed file listing"
)
info_parser.set_defaults(func=info_f)

version_parser = s.add_parser("version", help="Print version and exit")
version_parser.set_defaults(func=version_f)

Expand Down
124 changes: 124 additions & 0 deletions src/wheel/_commands/info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""
Display information about wheel files.
"""

from __future__ import annotations

import email.policy
import sys
from email.parser import BytesParser
from pathlib import Path

from ..wheelfile import WheelFile


def info(path: str, verbose: bool = False) -> None:
"""Display information about a wheel file.

:param path: The path to the wheel file
:param verbose: Show detailed file listing
"""
wheel_path = Path(path)
if not wheel_path.exists():
raise FileNotFoundError(f"Wheel file not found: {path}")

with WheelFile(path) as wf:
# Extract basic wheel information from filename
parsed = wf.parsed_filename
name = parsed.group("name")
version = parsed.group("ver")
build_tag = parsed.group("build")

print(f"Name: {name}")
print(f"Version: {version}")
if build_tag:
print(f"Build: {build_tag}")

# Read WHEEL metadata
try:
with wf.open(f"{wf.dist_info_path}/WHEEL") as wheel_file:
wheel_metadata = BytesParser(policy=email.policy.compat32).parse(
wheel_file
)

print(
f"Wheel-Version: {wheel_metadata.get('Wheel-Version', 'Unknown')}"
)
print(
f"Root-Is-Purelib: {wheel_metadata.get('Root-Is-Purelib', 'Unknown')}"
)

# Get all tags
tags = wheel_metadata.get_all("Tag", [])
if tags:
print("Tags:")
for tag in sorted(tags): # Sort tags for consistent output
print(f" {tag}")

generators = wheel_metadata.get_all("Generator", [])
for generator in generators:
print(f"Generator: {generator}")
except KeyError:
print("Warning: WHEEL metadata file not found", file=sys.stderr)

# Read package METADATA
try:
with wf.open(f"{wf.dist_info_path}/METADATA") as metadata_file:
pkg_metadata = BytesParser(policy=email.policy.compat32).parse(
metadata_file
)

summary = pkg_metadata.get("Summary", "")
if summary and summary != "UNKNOWN":
print(f"Summary: {summary}")

author = pkg_metadata.get("Author", "")
if author and author != "UNKNOWN":
print(f"Author: {author}")

author_email = pkg_metadata.get("Author-email")
if author_email and author_email != "UNKNOWN":
print(f"Author-email: {author_email}")

homepage = pkg_metadata.get("Home-page")
if homepage and homepage != "UNKNOWN":
print(f"Home-page: {homepage}")

license_info = pkg_metadata.get("License")
if license_info and license_info != "UNKNOWN":
print(f"License: {license_info}")

# Show classifiers
classifiers = pkg_metadata.get_all("Classifier", [])
if classifiers:
print("Classifiers:")
for classifier in sorted(
classifiers[:5]
): # Sort and limit to first 5
print(f" {classifier}")

if len(classifiers) > 5:
print(f" ... and {len(classifiers) - 5} more")

# Show dependencies
requires_dist = pkg_metadata.get_all("Requires-Dist", [])
if requires_dist:
print("Requires-Dist:")
for req in sorted(requires_dist): # Sort dependencies
print(f" {req}")
except KeyError:
print("Warning: METADATA file not found", file=sys.stderr)

# File information
file_count = len(wf.filelist)
total_size = sum(zinfo.file_size for zinfo in wf.filelist)

print(f"Files: {file_count}")
print(f"Size: {total_size:,} bytes")

# Show file listing if verbose
if verbose:
print("\nFile listing:")
for zinfo in wf.filelist:
size_str = f"{zinfo.file_size:,}" if zinfo.file_size > 0 else "0"
print(f" {zinfo.filename:60} {size_str:>10} bytes")
Loading
Loading