Field Guides Wheel anatomy Sources checked 13 September 2026

Anatomy of a Python sdist

A source distribution contains the files that a build tool uses to make a wheel. Its contents, metadata, and build steps determine whether that build works.

The format rules come from the Python Packaging Authority (PyPA). File examples come from the published packaging-25.0.tar.gz archive. [1] [2]

The hands-on examples use Python 3.12 or later and macOS/Linux shell paths. Start in a new working directory and keep using it. Here, python means that Python interpreter.

Source, sdist, and wheel

A source tree is a set of project files, such as a Git checkout. A source distribution, or sdist, is an archive of source files for a specific release. A wheel is the built distribution that an installer uses. [3]

A build tool can turn an sdist into a wheel. pip can then install that wheel. The build may compile extension modules or package pure Python code. An sdist does not mean that the project needs a compiler. [4]

Source distributions also support downstream packagers, such as Linux distributions. These packagers may apply patches, run tests, or build against their own system libraries. An sdist lets them work from a named release without a live checkout. [3] [5]

From source files to an installed packageArtifacts · build · installation
A labeled source checkout, sdist archive, extracted source directory, temporary build environment, wheel archive, and target site-packages directory.

Scroll sideways to read the artifact and environment labels. Selecting a stage brings it into view.

The artifact and environment views identify the stages; the selected route specifies which stages run and in what order. The moving underline marks the selected stage, not a file transfer. Building ends at a wheel; installation is a separate operation. [10] [4]
Distinct inputs to the build and install process
ItemContentsUse
Source treeProject files and build configurationThe build backend reads these files.
SdistRelease source files, pyproject.toml, and PKG-INFOThe backend builds a wheel from the unpacked archive.
WheelInstallable files and a .dist-info directoryThe installer places files in the target environment.
The sdist row describes the current standard. The wheel format has its own layout and install rules. [1] [6]

Filename and archive format

The current filename format is {name}-{version}.tar.gz. The braces name fields in the specification; they are not literal filename characters. The name and version must match the archive metadata. [1]

packaging-25.0.tar.gz
Actual filename on PyPI: project packaging, version 25.0, gzip-compressed tar archive. [2]

Producers normalize the name with the wheel filename rules. They convert letters to lowercase and replace each run of hyphens, underscores, or dots with one underscore. They also use the canonical version form. The filename has no Python, application binary interface (ABI), or platform tags. [1] [6]

The archive must use the POSIX.1-2001 pax tar format, which supports UTF-8 filenames. It must contain one top-level directory named {name}-{version}. Python's tarfile module must be able to read it with mode r:gz. [1]

The filename identifies a release; it does not prove that a machine can build it. Python requirements, build dependencies, and any native toolchain still apply. A wheel records compatibility tags after the build. [1] [4] [6]

Try it: download without running a build backend

Save this as fetch_sdist.py and run python fetch_sdist.py. It fetches this release directly and checks its recorded hash.

import hashlib
from pathlib import Path
import urllib.request

url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz"
expected = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"
with urllib.request.urlopen(url) as response:
    data = response.read()
if hashlib.sha256(data).hexdigest() != expected:
    raise ValueError("Archive hash does not match")
Path("artifacts").mkdir(exist_ok=True)
Path("artifacts/packaging-25.0.tar.gz").write_bytes(data)
print("Saved the verified sdist; no build backend ran.")

The archive is now artifacts/packaging-25.0.tar.gz. Nothing has been extracted or built. Use it in the examples below. [2]

Files inside the archive

Inspect the release archivepackaging 25.0
A cutaway of packaging-25.0.tar.gz exposes its top-level directory, root pyproject.toml and PKG-INFO, license files, and src/packaging/__init__.py.
Selected members of the published sdist, not a complete listing. Select a labeled file or its inspection button to read a verified excerpt. [2]

The standard requires pyproject.toml and PKG-INFO at the root inside the top-level directory. PKG-INFO must use Core Metadata version 2.2 or later. For metadata version 2.4 or later, the archive must also contain each file that a License-File field names. [1]

The standard does not define the other files. The backend includes the files it needs to build the project. There is no required src/ directory, test directory, or MANIFEST.in. These depend on the project and its build backend. [1]

packaging-25.0/PKG-INFO
packaging-25.0/pyproject.toml
packaging-25.0/src/packaging/__init__.py
packaging-25.0/tests/test_tags.py
packaging-25.0/docs/index.rst
packaging-25.0/LICENSE
packaging-25.0/LICENSE.APACHE
packaging-25.0/LICENSE.BSD
Selected paths read from the real archive. This is a subset, not its full file list. [2]

packaging includes tests and documentation in this sdist. The standard does not require those files. PyPA's downstream guide recommends including the project files needed for tests and documentation as well as the build. That guide states recommendations, not format requirements. [2] [5]

An sdist has no required wheel-style RECORD file with a hash for each member. Indexes can publish a hash of the whole archive. These are different checks. [1] [6] [7]

Checkpoint 1 · source, filename, and contents

Q1A project ships only an sdist, no wheel, on PyPI. What can you conclude about whether installing it needs a compiler?

B. A source build can compile extension modules or package pure Python code. The project and its backend determine whether a compiler is needed.

Q2How does an sdist filename such as packaging-25.0.tar.gz differ from a wheel filename for the same release?

D. The standard sdist filename contains a normalized name and version, followed by .tar.gz. Unlike a wheel filename, it has no compatibility tags.

Q3A modern sdist uses metadata 2.4 or later and declares License-File headers. What must its archive contain?

A. The current sdist standard requires pyproject.toml and PKG-INFO inside the top-level directory. For metadata 2.4 or later, every named license file must also be present.

Metadata and Dynamic fields

Follow a metadata fieldProject · sdist · wheel
Annotated pyproject.toml, PKG-INFO, and wheel METADATA show version resolution, Python compatibility, and the distinction between project dynamic and sdist Dynamic.
Values read from the published artifacts; matching values do not establish the wheel’s build provenance. [2] [8]

Core Metadata describes a distribution. Its fields include the name, version, Python requirement, and dependencies. PKG-INFO stores this metadata in an sdist. A wheel stores Core Metadata in .dist-info/METADATA. [8] [6]

Metadata-Version: 2.4
Name: packaging
Version: 25.0
Summary: Core utilities for Python packages
Requires-Python: >=3.8
License-File: LICENSE
License-File: LICENSE.APACHE
License-File: LICENSE.BSD
Selected PKG-INFO headers from packaging-25.0.tar.gz. The file has no Dynamic or Requires-Dist headers. [2]

The sdist-to-wheel rules

The Dynamic field lists metadata fields that can change when a backend builds a wheel. It first appeared in Core Metadata 2.2. Name, Version, and Metadata-Version cannot be dynamic. [8]

Before metadata 2.6, consumers ignored an sdist field's value if the sdist marked it dynamic. For metadata older than 2.2, consumers treat all fields as dynamic. Those older files do not offer the sdist-to-wheel consistency rules above. [8]

These rules concern a wheel built from that sdist. A downloaded wheel might come from a different build path or a tool that changed an existing wheel. Do not assume that every wheel for a release has the same metadata as its sdist. [8]

Project dynamic and sdist Dynamic

The [project].dynamic key in pyproject.toml tells the backend which project fields it must supply. The Dynamic header in PKG-INFO governs changes during a later wheel build. The two declarations apply at different stages. [9] [8]

In this real example, packaging declares dynamic = ["version"] in pyproject.toml. Its sdist already has Version: 25.0 and no Dynamic headers. Core Metadata forbids a dynamic version. A backend must resolve the version before it writes the sdist metadata. [2] [8]

Try it: compare the two dynamic declarations

Save this as read_sdist.py and run python read_sdist.py. Both inputs come from the archive you downloaded.

from email.parser import Parser
import tarfile
import tomllib

with tarfile.open("artifacts/packaging-25.0.tar.gz", "r:gz") as archive:
    project = tomllib.loads(archive.extractfile("packaging-25.0/pyproject.toml").read().decode())
    metadata = Parser().parsestr(archive.extractfile("packaging-25.0/PKG-INFO").read().decode())
print("Project dynamic:", project["project"]["dynamic"])
print("Sdist version:", metadata["Version"])
print("Sdist Dynamic:", metadata.get_all("Dynamic", []))

Output:

Project dynamic: ['version']
Sdist version: 25.0
Sdist Dynamic: []

The project asked Flit to supply the version. By the time Flit wrote this sdist, it had resolved that value to 25.0. The version is no longer an open question for a wheel built from this sdist.

Metadata on a package index

The Simple Repository API can expose Core Metadata in a separate file. Its JSON core-metadata key tells a client whether that file exists. The file URL appends .metadata to the distribution URL. A client must check the index response rather than assume that every archive has this file. [7]

A separate metadata file avoids a full archive download when the index provides it. It does not let a client treat dynamic sdist fields as final wheel metadata. The Core Metadata rules still apply. [7] [8]

Build steps and dependencies

Separate the build inputsFrontend · backend
A build frontend requests hooks from flit_core.buildapi inside a temporary Python build environment. Build requirements are separate from runtime dependencies and the resulting wheel.
Compare the real build requirement with runtime metadata. Isolation manages dependencies, not security. [2] [3]

A build frontend is the tool that requests a build, such as build or pip. A build backend is the project-selected code that produces the archive. PEP 517 defines the functions, called hooks, that connect them. [3]

[build-system]
requires = ["flit_core >=3.3"]
build-backend = "flit_core.buildapi"
The full [build-system] table in the published packaging 25.0 sdist. [2]

requires lists Python dependencies needed to run the build system. build-backend names the backend that the frontend imports. These build dependencies differ from [project].dependencies, which become the package's Requires-Dist metadata. [9] [3]

For this archive, flit_core >=3.3 is a build requirement. The project declares dependencies = []. The absence of runtime dependencies does not remove its need for a build backend. [2]

PEP 517 hooks relevant to sdists
HookPurpose
build_sdistCreate the sdist and return its filename, without its directory path.
get_requires_for_build_sdistOptionally report extra Python dependencies for the sdist build.
build_wheelCreate a wheel from a source directory, including an unpacked sdist.
get_requires_for_build_wheelOptionally report extra Python dependencies for the wheel build.
prepare_metadata_for_build_wheelOptionally create wheel metadata without a full wheel build.
PEP 517 defines no prepare_metadata_for_build_sdist hook. A backend can report that it cannot build an sdist through the specified UnsupportedOperation exception. [3]

The default build command

For a source tree that you trust, run this command in its root directory. The Python environment must have the build package installed. Build commands execute backend code. [10] [3]

python -m build
  1. The frontend asks the backend to create an sdist.
  2. It extracts that sdist into a temporary directory.
  3. It asks the backend to build a wheel from that directory.

This default checks the wheel build against the sdist's files. python -m build --sdist creates only the sdist. python -m build --wheel builds directly from the source tree. With both explicit flags, --sdist --wheel, the tool builds both files directly from the source tree. [10]

These are build tool defaults, not requirements of the sdist format. A successful wheel build checks one build path. It does not prove that the sdist contains all tests or works on every platform. [10] [5]

Try it: build from the downloaded source archive

This step executes the release's build backend. Run it only if you trust the source and build tools. The virtual environment is not a security sandbox.

First create a separate Python environment and install the frontend:

python -m venv .venv
.venv/bin/python -m pip install --index-url https://pypi.org/simple build

Save this as unpack_sdist.py, then run python unpack_sdist.py. It extracts the verified archive into source/.

import tarfile

with tarfile.open("artifacts/packaging-25.0.tar.gz", "r:gz") as archive:
    archive.extractall("source", filter="data")

Now ask the frontend to build from that extracted source tree:

.venv/bin/python -m build source/packaging-25.0 --outdir built

The checked run used build 1.6.1 and selected flit-core 4.0.2. It created these files:

built/packaging-25.0.tar.gz
built/packaging-25.0-py3-none-any.whl

The frontend first created a new sdist from the extracted source. It then built the wheel from that new sdist. You did not need the project's Git repository. Backend versions can change because flit_core >=3.3 is not an exact pin.

Checkpoint 2 · metadata and the build

Q4packaging 25.0 declares dynamic = ["version"] in [project] of pyproject.toml, yet its sdist's PKG-INFO has Version: 25.0 and no Dynamic header. What explains this?

C. [project].dynamic tells the backend which fields to supply. PKG-INFO governs later wheel builds from that sdist. Its Version cannot be dynamic.

Q5In Core Metadata 2.6, a PKG-INFO field is dynamic and multiple-use (for example, one that can repeat like Requires-Dist). What can a wheel built from that sdist do with it?

B. Metadata 2.6 allows a dynamic multiple-use field to gain values. Existing values must remain unchanged and in order.

Q6Running plain python -m build in a project's root directory, with no flags, does what?

D. The default builds an sdist, extracts it, then builds the wheel from that copy. Explicit --sdist --wheel instead builds both directly from the source tree.

File selection and source control

Does this file reach the wheel?Actual archive membership
Labeled sdist members map into wheel paths: src/packaging/tags.py loses the src prefix, LICENSE moves under dist-info/licenses, and the selected test and documentation files are absent from the wheel.
Membership checked in the published packaging 25.0 sdist and wheel. License files also change location. [2] [19]

The backend chooses which project files enter the sdist. A Git archive does not automatically meet the sdist format. It must also have the required metadata, build configuration, directory layout, and filename. [1]

For setuptools, MANIFEST.in adds or removes files from the sdist. Setuptools processes its commands in order. It also has default include rules and plugins that can use tracked files. These are setuptools features; MANIFEST.in is not a file-selection standard for all backends. [11]

A file in an sdist does not automatically enter its wheel. With setuptools' include_package_data=True, the default wheel inclusion applies to files inside package directories. Tests outside those directories can remain in the sdist without entering the wheel. [11]

packaging 25.0 uses Flit instead. Its [tool.flit.sdist] table explicitly includes LICENSE*, tests/, docs/, and CHANGELOG.rst. It also lists exclusions. The selected file paths above come from the resulting archive, not from an assumed directory layout. [2]

Keep release builds independent of an unavailable Git checkout. Version tools can use tags that a source archive does not contain. PyPA's downstream guide recommends resolving these inputs when you create the release archive. It also recommends builds that can use dependencies supplied without network access. [5]

Try it: find a test that stays out of the wheel

Use the files in built/ from the previous example. Save this as compare_files.py and run python compare_files.py.

import tarfile
from zipfile import ZipFile

with tarfile.open("built/packaging-25.0.tar.gz", "r:gz") as source:
    source_files = source.getnames()
with ZipFile("built/packaging-25.0-py3-none-any.whl") as wheel:
    wheel_files = wheel.namelist()
print("Test module in sdist:", "packaging-25.0/tests/test_tags.py" in source_files)
print("Test module in wheel:", "tests/test_tags.py" in wheel_files)
print("Package module in wheel:", "packaging/tags.py" in wheel_files)

Output from that build:

Test module in sdist: True
Test module in wheel: False
Package module in wheel: True

The sdist keeps the test source for people who build and test the project. The wheel contains the package module, without that test file. File inclusion in one archive does not imply inclusion in the other.

How pip uses an sdist

Locate the installed filesWheel to environment
pip maps a wheel's packaging directory and dist-info metadata into the target site-packages directory, then updates the installation RECORD. This target is not the temporary build environment.
Wheel members are real; <site-packages> denotes your target environment’s package directory. This does not run an installation. [6] [19]

For a source build, pip creates an isolated build environment and installs the build dependencies there. It obtains metadata when needed, then asks the backend for a wheel. pip installs the wheel, rather than copying the sdist's source tree into the environment. [4]

If the backend provides prepare_metadata_for_build_wheel, pip can use that hook. Otherwise, pip builds a wheel and reads its metadata. Thus a request for metadata can run a build, even before installation. [4]

Build isolation keeps build-time Python dependencies separate from the user's runtime environment. It does not supply every native compiler, header, or system library a project might need. The backend and project determine those requirements. [4] [5]

--no-build-isolation
pip stops managing build dependencies. You must provide them in the build environment. This option does not fix a missing dependency by itself. [4]
--no-binary :all:
pip excludes downloaded binary distributions. It still uses wheels as the result of a source build. Since pip 23.1, this option does not disable its cache of locally built wheels. [12] [13]
--only-binary :all:
pip excludes source candidates from indexes and --find-links. A requirement fails if no suitable binary distribution is available there. Explicit source URLs and editable installs can still trigger builds. This option is not a general block on build-code execution. [12] [14] [18]
pip download
This command resolves requirements as pip install does, but saves distributions instead of installing them. Do not treat it as a raw file fetch: metadata work can call the build backend. [15] [4]

Checkpoint 3 · selection, isolation, and binaries

Q7A setuptools project uses include_package_data=True and keeps its tests/ directory outside any package directory. The tests are present in the sdist. Do they end up in the wheel?

C. Sdist and wheel inclusion follow separate rules. The default behavior of include_package_data concerns files inside package directories, not every file in the sdist.

Q8You disable build isolation to speed up repeated builds. What does pip then do, and what should you not assume?

A. With --no-build-isolation, you must supply the build dependencies. Disabling isolation does not provide a missing dependency.

Q9A requirement is installed with --only-binary :all:, which is meant to exclude source builds. Under what circumstance can build-backend code still run?

D. The binary filter applies to candidates from indexes and --find-links. Explicit source URLs and editable installs can still run build code.

Build and extraction risks

Only build source code that you trust. A backend is executable code, and its hooks can run code supplied with a project. Metadata generation can also execute code. pip's build isolation separates Python dependencies; it is not a security sandbox for that code. [3] [4] [14]

Use a separate, restricted environment when you must examine an untrusted build. Do not provide release credentials to it. This is operational advice based on the code-execution risk, not a guarantee from the sdist standard.

Archive extraction is a separate risk

The sdist specification requires tools to use tarfile.data_filter or meet its alternative extraction rules. These rules forbid writes outside the destination, links outside the destination, and device files, including pipes. They also require tools to clear high permission bits. [1]

Python 3.14 uses the data extraction filter by default. Older Python versions can have different defaults. Check for tarfile.data_filter support instead of relying only on a version number, because some security updates backport it. [16]

The data filter does not make every archive safe. Python's documentation warns that it does not prevent denial-of-service attacks. For untrusted files, use limits on disk, memory, and CPU use, and inspect archive members before extraction. [16]

Hashes have a limited role

A hash checks whether bytes match an expected file. It does not prove that the file's code is safe. A hash fetched from the same server as the archive also does not protect against that server changing both. pip distinguishes index-provided hashes from hashes pinned locally with --require-hashes. [14]

Inspect a real sdist

The Python script below fetches the published packaging 25.0 sdist over HTTPS. It checks the archive against the SHA-256 value recorded in PyPI's release data. It then reads PKG-INFO in memory. It does not extract files to disk, import the package, or call a build backend. [2] [16]

Run this as a Python script. It uses only the Python standard library and needs network access to files.pythonhosted.org. The URL and hash identify this exact release.

import hashlib
import io
import tarfile
import urllib.request

url = (
    "https://files.pythonhosted.org/packages/a1/d4/"
    "1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/"
    "packaging-25.0.tar.gz"
)
expected = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"

with urllib.request.urlopen(url, timeout=30) as response:
    data = response.read()
if hashlib.sha256(data).hexdigest() != expected:
    raise ValueError("Archive SHA-256 does not match")

with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive:
    member = archive.getmember("packaging-25.0/PKG-INFO")
    if not member.isfile():
        raise ValueError("PKG-INFO is not a regular file")
    with archive.extractfile(member) as metadata:
        print(metadata.read().decode("utf-8"))

The output includes the metadata headers shown earlier, followed by the project's description. This script checks a specific small archive. It is not a general validator or a resource-limited scanner for unknown archives.

Release checks and build failures

Check the archive that you plan to publish. A build from a checkout cannot show whether the sdist omitted a file. The default python -m build uses the sdist for the wheel build. Use that path in the release process. [10]

Also run the project's relevant tests against the built package. If downstream users need the tests or documentation sources, check that the archive contains them. A successful wheel build does not exercise those files. [5]

Twine uploads existing distribution files. Its twine check command checks whether the long description will render on PyPI. That check is not a substitute for a build or the project's tests. [17]

Checks for a failed source build
FailureCheck
A file exists in the checkout but not in the sdistRead the archive's member list. Check the backend's include and exclude rules. For setuptools, check MANIFEST.in and stale build metadata. [11]
The backend cannot import a build dependencyCheck [build-system].requires and the extra requirements from the relevant build hook. If isolation is disabled, check the supplied environment. [4]
A compiler or a system header is missingRead the project's native build requirements. Python build isolation manages Python dependencies, not the full operating-system toolchain. [4] [5]
The build cannot determine a version without GitCheck how the version tool handles an unpacked sdist. The archive must contain the fixed release version in PKG-INFO. [1] [5]
A build passes but a runtime data file is absentCheck the wheel's contents as well as the sdist's contents. Backend rules can include a file in only one of them. [11]

Do not read build isolation as a promise of identical bytes across builds. This archive declares flit_core >=3.3, not one exact backend version. Record the actual tools and environment when you need to investigate build differences. [2] [10]

Checkpoint 4 · risk, inspection, and release

Q10The inspection script in this guide fetches packaging-25.0.tar.gz, checks its SHA-256, and prints PKG-INFO. What does it deliberately not do?

B. This inspection script hashes the archive and reads PKG-INFO in memory. It does not extract files, import the package, or invoke a backend.

Q11Python 3.14's default data extraction filter for tar archives is in place. Does that mean it's safe to extract an arbitrary untrusted sdist with no further precautions?

A. The data filter blocks specific archive hazards, including writes outside the destination. It does not prevent denial-of-service attacks. Untrusted extraction still needs resource limits.

Q12You build a wheel successfully straight from your Git checkout, and it looks fine. Does that confirm the sdist you're about to publish is complete?

C. The checkout and sdist can contain different files. Build the wheel from the sdist to check the release archive rather than only the checkout.

Standards and older archives

The current format uses pyproject.toml to identify a modern sdist. Older source archives follow rules set by tools such as distutils. The current format specification does not standardize those older layouts. [1]

A legacy sdist that contains PKG-INFO version 2.2 or later must still follow the relevant Core Metadata rules. A legacy archive is not exempt from those rules merely because it lacks the current layout. [1]

setup.py is not a required file in a modern sdist. A backend may need it, and older projects can still contain it. pip documents a setuptools fallback for a project with setup.py but no [build-system] section. [3] [4]

PEP 517
Defines the frontend-to-backend build interface and the modern source tree. Read PEP 517.
PEP 625
Standardizes sdist filenames. Read PEP 625.
PEP 643
Adds sdist metadata rules and the Dynamic field. The living Core Metadata specification includes later changes. Read PEP 643.
PEP 721
Defines limits on sdist archive features and extraction. Read PEP 721.
PEP 639
Adds the modern license metadata and license-file requirements. Read PEP 639.

Use the living PyPA specifications for current requirements. Read the PEPs for the decisions that led to them. [1] [8]

Sources

Format claims cite PyPA specifications. Tool behavior cites the tool's documentation. Archive examples cite the published release. Sources were checked on 13 September 2026; tool documentation can change. The workshop’s sdist and wheel excerpts were rechecked against their pinned SHA-256 hashes on 11 October 2026.

  1. PyPA: Source distribution format. Current requirements, legacy scope, and archive extraction rules.
  2. PyPI: packaging 25.0 release data and the published sdist. The archive's SHA-256 matches PyPI's recorded value. File paths and excerpts come from this archive.
  3. PEP 517: A build-system independent format for source trees. Terms, hooks, and backend requirements.
  4. pip: Build System Interface. Build isolation, metadata, hooks, and legacy fallback. Documentation version 26.2.1 when checked.
  5. PyPA: Supporting downstream packaging. Recommendations on complete sources and system dependencies. This discussion page is marked Draft.
  6. PyPA: Binary distribution format. Wheel filename normalization, contents, and installation.
  7. PyPA: Simple repository API. File links, hashes, and separate metadata files.
  8. PyPA: Core Metadata. Version 2.6, including the version-specific Dynamic rules.
  9. PyPA: pyproject.toml specification. Build requirements and project metadata.
  10. build: Basic Usage. Default sdist-to-wheel build and explicit flags. Documentation version 1.6.1 when checked.
  11. Setuptools: Controlling files in the distribution. Defaults, manifests, and file-selection limits.
  12. pip install reference. Binary and source selection options.
  13. pip 23.1 changelog. The change to --no-binary and locally built wheel caching.
  14. pip: Secure installs. Code execution, wheel-only installs, and hash checks.
  15. pip download reference. Resolution and download behavior.
  16. Python: tarfile extraction filters. Filter support, defaults, and limits.
  17. Twine documentation. Uploads and description checks.
  18. uv: Compatibility with pip, --only-binary enforcement. Direct-URL and editable-install exceptions to pip's binary filter.
  19. Published packaging 25.0 wheel. SHA-256 29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484; inspected without importing package code.