Skip to content

Commit 0ce509e

Browse files
Added the wheel info subcommand (#669)
Features: - Shows package name, version, and build information - Displays wheel format version and generator - Lists supported Python versions, ABI, and platform tags - Shows package metadata (summary, author, license, classifiers) - Displays dependencies and file count/size information - Optional verbose mode with detailed file listing - Comprehensive error handling for missing files Closes #639.
1 parent 39039c0 commit 0ce509e

8 files changed

Lines changed: 445 additions & 1 deletion

File tree

docs/manpages/wheel.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ Commands
2626
``convert``
2727
Convert egg or wininst to wheel
2828

29+
``info``
30+
Show information about a wheel file
31+
2932
``tags``
3033
Change the tags on a wheel file
3134

docs/news.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Release Notes
22
=============
33

4+
**UNRELEASED**
5+
6+
- Added the ``wheel info`` subcommand to display metadata about wheel files without
7+
unpacking them (`#639 <https://github.com/pypa/wheel/issues/639>`_)
8+
49
**0.46.3 (2026-01-22)**
510

611
- Fixed ``ImportError: cannot import name '_setuptools_logging' from 'wheel'`` when

docs/reference/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Reference Guide
55
:maxdepth: 2
66

77
wheel_convert
8-
wheel_unpack
8+
wheel_info
99
wheel_pack
1010
wheel_tags
11+
wheel_unpack

docs/reference/wheel_info.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
wheel info
2+
==========
3+
4+
Usage
5+
-----
6+
7+
::
8+
9+
wheel info [OPTIONS] <wheel_file>
10+
11+
12+
Description
13+
-----------
14+
15+
Display information about a wheel file without unpacking it.
16+
17+
This command shows comprehensive metadata about a wheel file including:
18+
19+
* Package name, version, and build information
20+
* Wheel format version and generator
21+
* Supported Python versions, ABI, and platform tags
22+
* Package metadata such as summary, author, and license
23+
* Classifiers and dependencies
24+
* File count and total size
25+
* Optional detailed file listing
26+
27+
28+
Options
29+
-------
30+
31+
.. option:: -v, --verbose
32+
33+
Show detailed file listing with individual file sizes.
34+
35+
36+
Examples
37+
--------
38+
39+
Display basic information about a wheel::
40+
41+
$ wheel info example_package-1.0-py3-none-any.whl
42+
Name: example-package
43+
Version: 1.0
44+
Wheel-Version: 1.0
45+
Root-Is-Purelib: true
46+
Tags:
47+
py3-none-any
48+
Generator: bdist_wheel (0.40.0)
49+
Summary: An example package
50+
Author: John Doe
51+
License: MIT
52+
Files: 12
53+
Size: 15,234 bytes
54+
55+
Display detailed information with file listing::
56+
57+
$ wheel info --verbose example_package-1.0-py3-none-any.whl
58+
Name: example-package
59+
Version: 1.0
60+
...
61+
62+
File listing:
63+
example_package/__init__.py 45 bytes
64+
example_package/module.py 1,234 bytes
65+
example_package-1.0.dist-info/METADATA 678 bytes
66+
example_package-1.0.dist-info/WHEEL 123 bytes
67+
example_package-1.0.dist-info/RECORD 456 bytes

docs/user_guide.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,31 @@ To install a wheel file, use pip_::
8484
$ pip install someproject-1.5.0-py2-py3-none.whl
8585

8686
.. _pip: https://pypi.org/project/pip/
87+
88+
89+
Inspecting Wheels
90+
-----------------
91+
92+
To inspect the metadata and contents of a wheel file without installing it,
93+
use the ``wheel info`` command::
94+
95+
$ wheel info someproject-1.5.0-py2-py3-none.whl
96+
97+
This will display information about the wheel including:
98+
99+
* Package name and version
100+
* Supported Python versions and platforms
101+
* Dependencies and other metadata
102+
* File count and total size
103+
104+
For more detailed information including a complete file listing, use the
105+
``--verbose`` flag::
106+
107+
$ wheel info --verbose someproject-1.5.0-py2-py3-none.whl
108+
109+
This is useful for:
110+
111+
* Verifying wheel contents before installation
112+
* Debugging packaging issues
113+
* Understanding wheel structure and metadata
114+
* Checking supported platforms and Python versions

src/wheel/_commands/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ def tags_f(args: argparse.Namespace) -> None:
4949
print(name)
5050

5151

52+
def info_f(args: argparse.Namespace) -> None:
53+
from .info import info
54+
55+
try:
56+
info(args.wheelfile, args.verbose)
57+
except FileNotFoundError as e:
58+
raise WheelError(str(e)) from e
59+
60+
5261
def version_f(args: argparse.Namespace) -> None:
5362
from .. import __version__
5463

@@ -129,6 +138,13 @@ def parser() -> argparse.ArgumentParser:
129138
)
130139
tags_parser.set_defaults(func=tags_f)
131140

141+
info_parser = s.add_parser("info", help="Show information about a wheel file")
142+
info_parser.add_argument("wheelfile", help="Wheel file to show information for")
143+
info_parser.add_argument(
144+
"--verbose", "-v", action="store_true", help="Show detailed file listing"
145+
)
146+
info_parser.set_defaults(func=info_f)
147+
132148
version_parser = s.add_parser("version", help="Print version and exit")
133149
version_parser.set_defaults(func=version_f)
134150

src/wheel/_commands/info.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Display information about wheel files.
3+
"""
4+
5+
from __future__ import annotations
6+
7+
import email.policy
8+
import sys
9+
from email.parser import BytesParser
10+
from pathlib import Path
11+
12+
from ..wheelfile import WheelFile
13+
14+
15+
def info(path: str, verbose: bool = False) -> None:
16+
"""Display information about a wheel file.
17+
18+
:param path: The path to the wheel file
19+
:param verbose: Show detailed file listing
20+
"""
21+
wheel_path = Path(path)
22+
if not wheel_path.exists():
23+
raise FileNotFoundError(f"Wheel file not found: {path}")
24+
25+
with WheelFile(path) as wf:
26+
# Extract basic wheel information from filename
27+
parsed = wf.parsed_filename
28+
name = parsed.group("name")
29+
version = parsed.group("ver")
30+
build_tag = parsed.group("build")
31+
32+
print(f"Name: {name}")
33+
print(f"Version: {version}")
34+
if build_tag:
35+
print(f"Build: {build_tag}")
36+
37+
# Read WHEEL metadata
38+
try:
39+
with wf.open(f"{wf.dist_info_path}/WHEEL") as wheel_file:
40+
wheel_metadata = BytesParser(policy=email.policy.compat32).parse(
41+
wheel_file
42+
)
43+
44+
print(
45+
f"Wheel-Version: {wheel_metadata.get('Wheel-Version', 'Unknown')}"
46+
)
47+
print(
48+
f"Root-Is-Purelib: {wheel_metadata.get('Root-Is-Purelib', 'Unknown')}"
49+
)
50+
51+
# Get all tags
52+
tags = wheel_metadata.get_all("Tag", [])
53+
if tags:
54+
print("Tags:")
55+
for tag in sorted(tags): # Sort tags for consistent output
56+
print(f" {tag}")
57+
58+
generators = wheel_metadata.get_all("Generator", [])
59+
for generator in generators:
60+
print(f"Generator: {generator}")
61+
except KeyError:
62+
print("Warning: WHEEL metadata file not found", file=sys.stderr)
63+
64+
# Read package METADATA
65+
try:
66+
with wf.open(f"{wf.dist_info_path}/METADATA") as metadata_file:
67+
pkg_metadata = BytesParser(policy=email.policy.compat32).parse(
68+
metadata_file
69+
)
70+
71+
summary = pkg_metadata.get("Summary", "")
72+
if summary and summary != "UNKNOWN":
73+
print(f"Summary: {summary}")
74+
75+
author = pkg_metadata.get("Author", "")
76+
if author and author != "UNKNOWN":
77+
print(f"Author: {author}")
78+
79+
author_email = pkg_metadata.get("Author-email")
80+
if author_email and author_email != "UNKNOWN":
81+
print(f"Author-email: {author_email}")
82+
83+
homepage = pkg_metadata.get("Home-page")
84+
if homepage and homepage != "UNKNOWN":
85+
print(f"Home-page: {homepage}")
86+
87+
license_info = pkg_metadata.get("License")
88+
if license_info and license_info != "UNKNOWN":
89+
print(f"License: {license_info}")
90+
91+
# Show classifiers
92+
classifiers = pkg_metadata.get_all("Classifier", [])
93+
if classifiers:
94+
print("Classifiers:")
95+
for classifier in sorted(
96+
classifiers[:5]
97+
): # Sort and limit to first 5
98+
print(f" {classifier}")
99+
100+
if len(classifiers) > 5:
101+
print(f" ... and {len(classifiers) - 5} more")
102+
103+
# Show dependencies
104+
requires_dist = pkg_metadata.get_all("Requires-Dist", [])
105+
if requires_dist:
106+
print("Requires-Dist:")
107+
for req in sorted(requires_dist): # Sort dependencies
108+
print(f" {req}")
109+
except KeyError:
110+
print("Warning: METADATA file not found", file=sys.stderr)
111+
112+
# File information
113+
file_count = len(wf.filelist)
114+
total_size = sum(zinfo.file_size for zinfo in wf.filelist)
115+
116+
print(f"Files: {file_count}")
117+
print(f"Size: {total_size:,} bytes")
118+
119+
# Show file listing if verbose
120+
if verbose:
121+
print("\nFile listing:")
122+
for zinfo in wf.filelist:
123+
size_str = f"{zinfo.file_size:,}" if zinfo.file_size > 0 else "0"
124+
print(f" {zinfo.filename:60} {size_str:>10} bytes")

0 commit comments

Comments
 (0)