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
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
127 changes: 127 additions & 0 deletions src/wheel/_commands/info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""
Display information about wheel files.
"""

from __future__ import annotations

import email.policy
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")
python_tag = parsed.group("pyver")
abi_tag = parsed.group("abi")
platform_tag = parsed.group("plat")
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}")

generator = wheel_metadata.get("Generator")
Comment thread
agronholm marked this conversation as resolved.
Outdated
if generator:
print(f"Generator: {generator}")

except KeyError:
print("Warning: WHEEL metadata file not found")

# 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 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")
88 changes: 88 additions & 0 deletions tests/commands/test_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from __future__ import annotations

import os

from pytest import TempPathFactory

from .util import run_command

THISDIR = os.path.dirname(__file__)
TESTWHEEL_NAME = "test-1.0-py2.py3-none-any.whl"
TESTWHEEL_PATH = os.path.join(THISDIR, "..", "testdata", TESTWHEEL_NAME)


def test_info_basic(tmp_path_factory: TempPathFactory) -> None:
Comment thread
killerdevildog marked this conversation as resolved.
Outdated
"""Test basic wheel info display."""
output = run_command("info", TESTWHEEL_PATH)

# Check basic package information is displayed
assert "Name: test" in output
assert "Version: 1.0" in output
assert "Wheel-Version: 1.0" in output
assert "Root-Is-Purelib: false" in output

# Check tags are displayed
assert "Tags:" in output
assert "py2-none-any" in output
assert "py3-none-any" in output

# Check metadata is displayed
assert "Summary: Test module" in output
assert "Author: Paul Moore" in output
assert "Author-email: test@example.com" in output
assert "Home-page: http://test.example.com/" in output
assert "License: MIT License" in output

# Check file information
assert "Files: 14" in output
assert "Size: 8,114 bytes" in output


def test_info_verbose(tmp_path_factory: TempPathFactory) -> None:
Comment thread
killerdevildog marked this conversation as resolved.
Outdated
"""Test verbose wheel info display with file listing."""
output = run_command("info", "--verbose", TESTWHEEL_PATH)

# Check that basic info is still there
assert "Name: test" in output
assert "Version: 1.0" in output

# Check that file listing is included
assert "File listing:" in output
assert "hello/hello.py" in output
assert "hello.pyd" in output
assert "test-1.0.dist-info/METADATA" in output
assert "test-1.0.dist-info/WHEEL" in output
assert "test-1.0.dist-info/RECORD" in output

# Check file sizes are displayed
assert "6,656 bytes" in output # hello.pyd
assert "42 bytes" in output # hello.py


def test_info_nonexistent_file() -> None:
"""Test info command with non-existent wheel file."""
try:
output = run_command("info", "nonexistent.whl", catch_systemexit=False)
assert False, "Expected an error for non-existent file"
except Exception:
# Expected to fail
pass
Comment thread
killerdevildog marked this conversation as resolved.
Outdated


def test_info_help() -> None:
"""Test info command help."""
output = run_command("info", "--help")

assert "usage: wheel info" in output
assert "Wheel file to show information for" in output
assert "wheelfile" in output
assert "--verbose" in output


def test_info_short_verbose_flag() -> None:
"""Test that -v works as alias for --verbose."""
output = run_command("info", "-v", TESTWHEEL_PATH)

# Should include file listing like --verbose
assert "File listing:" in output
assert "hello/hello.py" in output
Loading